diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..02df470 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,5 @@ +*.ts linguist-language=Go +*.js linguist-language=Go +*.css linguist-language=Go +*.scss linguist-language=Go +*.html linguist-language=Go diff --git a/.github/ISSUE_TEMPLATE/1_bug_report.yml b/.github/ISSUE_TEMPLATE/1_bug_report.yml new file mode 100644 index 0000000..dcaec9c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/1_bug_report.yml @@ -0,0 +1,50 @@ +name: 'Bug Report' +description: 'Report an Bug' +title: '[Bug] ' +assignees: wanghe-fit2cloud +body: + - type: markdown + attributes: + value: "## Contact Information" + - type: input + validations: + required: false + attributes: + label: "Contact Information" + description: "The ways to quickly contact you: WeChat group number and nickname, email, etc." + - type: markdown + attributes: + value: "## Environment Information" + - type: input + validations: + required: true + attributes: + label: "1Panel Version" + description: "Log in to the 1Panel Web console and check the current version at the bottom right of the page." + - type: markdown + attributes: + value: "## Detailed information" + - type: textarea + attributes: + label: "Problem Description" + description: "Briefly describe the issue you’ve encountered." + validations: + required: true + - type: textarea + attributes: + label: "Steps to Reproduce" + description: "How can this issue be reproduced." + validations: + required: true + - type: textarea + attributes: + label: "The expected correct result" + - type: textarea + attributes: + label: "Related log output" + description: "Please paste any relevant log output here. It will automatically be formatted as code, so no backticks are necessary." + render: shell + - type: textarea + attributes: + label: "Additional Information" + description: "If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome)." \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/2_feature_request.yml b/.github/ISSUE_TEMPLATE/2_feature_request.yml new file mode 100644 index 0000000..d99c016 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/2_feature_request.yml @@ -0,0 +1,29 @@ +name: 'Feature Request' +description: 'Suggest an idea' +title: '[Feature] ' +assignees: wanghe-fit2cloud +body: + - type: markdown + attributes: + value: "## Environment Information" + - type: input + validations: + required: true + attributes: + label: "1Panel Version" + description: "Log in to the 1Panel Web console and check the current version at the bottom right of the page." + - type: markdown + attributes: + value: "## Detailed information" + - type: textarea + attributes: + label: "Please describe your needs or suggestions for improvements" + validations: + required: true + - type: textarea + attributes: + label: "Please describe the solution you suggest" + - type: textarea + attributes: + label: "Additional Information" + description: "If you have any additional information to provide, you can include it here (screenshots, videos, etc., are welcome)." \ No newline at end of file diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..d292a26 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: Questions & Discussions + url: https://github.com/1Panel-dev/1Panel/discussions + about: Raise questions about the installation, deployment, use and other aspects of the project. \ 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 0000000..1106f42 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,9 @@ +#### What this PR does / why we need it? + +#### Summary of your change + +#### Please indicate you've done the following: + +- [ ] Made sure tests are passing and test coverage is added if needed. +- [ ] Made sure commit message follow the rule of [Conventional Commits specification](https://www.conventionalcommits.org/). +- [ ] Considered the docs impact and opened a new docs issue or PR with docs changes if needed. \ No newline at end of file diff --git a/.github/workflows/add-labels-for-pr.yml b/.github/workflows/add-labels-for-pr.yml new file mode 100644 index 0000000..81e9c27 --- /dev/null +++ b/.github/workflows/add-labels-for-pr.yml @@ -0,0 +1,16 @@ + +name: General PR Handling for 1Panel +on: pull_request +permissions: + pull-requests: write +jobs: + generic_handler: + name: Add Labels to PR + if: github.repository == '1Panel-dev/1Panel' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions-ecosystem/action-add-labels@v1 + with: + github_token: ${{ secrets.GITHUBTOKEN }} + labels: ${{ github.base_ref }} diff --git a/.github/workflows/build-publish-to-oss.yml b/.github/workflows/build-publish-to-oss.yml new file mode 100644 index 0000000..5cba511 --- /dev/null +++ b/.github/workflows/build-publish-to-oss.yml @@ -0,0 +1,47 @@ +name: Create Release And Upload assets +on: + push: + tags: + - 'v*' +jobs: + create-release: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22.19.0' + - name: Build Web + run: | + cd frontend && npm install && npm run build:pro + env: + NODE_OPTIONS: --max-old-space-size=8192 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + - name: Build Release + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: '~> v2' + args: release --skip=publish --clean + - name: Upload Assets + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + draft: true + files: | + dist/*.tar.gz + dist/checksums.txt + - name: Setup OSSUTIL + uses: yizhoumo/setup-ossutil@v2 + with: + endpoint: ${{ secrets.OSS_ENDPOINT }} + access-key-id: ${{ secrets.OSS_ACCESS_KEY_ID }} + access-key-secret: ${{ secrets.OSS_ACCESS_KEY_SECRET }} + ossutil-version: '1.7.18' + - name: Upload Assets to OSS + run: ossutil cp -r dist/ oss://resource-fit2cloud-com/1panel/package/v2/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --only-current-dir --force diff --git a/.github/workflows/build-publish-to-r2.yml b/.github/workflows/build-publish-to-r2.yml new file mode 100644 index 0000000..0f624c5 --- /dev/null +++ b/.github/workflows/build-publish-to-r2.yml @@ -0,0 +1,45 @@ +name: Create Release And Upload Cloudflare R2 +on: + push: + tags: + - 'v*' +jobs: + create-release: + runs-on: ubuntu-latest + steps: + - name: Checkout Code + uses: actions/checkout@v4 + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '22.19.0' + - name: Build Web + run: | + cd frontend && npm install && npm run build:pro + env: + NODE_OPTIONS: --max-old-space-size=8192 + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version: '1.24' + - name: Build Release + uses: goreleaser/goreleaser-action@v6 + with: + distribution: goreleaser + version: '~> v2' + args: release --skip=publish --clean + - name: Upload Assets + uses: softprops/action-gh-release@v1 + if: startsWith(github.ref, 'refs/tags/') + with: + draft: true + files: | + dist/*.tar.gz + dist/checksums.txt + - name: Setup Rclone + uses: AnimMouse/setup-rclone@v1 + with: + rclone_config: ${{ secrets.RCLONE_CONFIG }} + - name: Upload to Cloudflare R2 + run: | + rclone copy dist/ cloudflare_r2:package/v2/stable/${{ github.ref_name }}/release/ --include "*.tar.gz" --include "checksums.txt" --progress diff --git a/.github/workflows/issue-translator.yml b/.github/workflows/issue-translator.yml new file mode 100644 index 0000000..cd6edaf --- /dev/null +++ b/.github/workflows/issue-translator.yml @@ -0,0 +1,14 @@ +name: Issue Translator +on: + issue_comment: + types: [created] + issues: + types: [opened] +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: usthe/issues-translate-action@v2.7 + with: + IS_MODIFY_TITLE: true + BOT_GITHUB_TOKEN: ${{ secrets.ISSUE_TRANSLATOR_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/llm-code-review.yml b/.github/workflows/llm-code-review.yml new file mode 100644 index 0000000..40668b6 --- /dev/null +++ b/.github/workflows/llm-code-review.yml @@ -0,0 +1,25 @@ +name: LLM Code Review +permissions: + contents: read + pull-requests: write +on: + pull_request: + types: [opened, reopened, synchronize] +jobs: + llm-code-review: + runs-on: ubuntu-latest + steps: + - uses: fit2cloud/LLM-CodeReview-Action@main + env: + GITHUB_TOKEN: ${{ secrets.FIT2CLOUDRD_LLM_CODE_REVIEW_TOKEN }} + OPENAI_API_KEY: ${{ secrets.ALIYUN_LLM_API_KEY }} + LANGUAGE: English + OPENAI_API_ENDPOINT: https://dashscope.aliyuncs.com/compatible-mode/v1 + MODEL: qwen2.5-coder-3b-instruct + PROMPT: "Please check the following code differences for any irregularities, potential issues, or optimization suggestions, and provide your answers in English." + top_p: 1 + temperature: 1 + # max_tokens: 10000 + MAX_PATCH_LENGTH: 10000 + IGNORE_PATTERNS: "/node_modules,*.md,/dist,/.github" + FILE_PATTERNS: "*.java,*.go,*.py,*.vue,*.ts,*.js,*.css,*.scss,*.html" \ No newline at end of file diff --git a/.github/workflows/sonarcloud-scan.yml b/.github/workflows/sonarcloud-scan.yml new file mode 100644 index 0000000..bfbd2b6 --- /dev/null +++ b/.github/workflows/sonarcloud-scan.yml @@ -0,0 +1,21 @@ +name: SonarCloud Scan +on: + push: + branches: + - dev + pull_request: + types: [opened, synchronize, reopened] +jobs: + sonarcloud: + name: SonarCloud + if: github.repository == '1Panel-dev/1Panel' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: SonarCloud Scan + uses: SonarSource/sonarcloud-github-action@master + env: + GITHUB_TOKEN: ${{ secrets.GITHUBTOKEN }} + SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/sync2gitee.yml b/.github/workflows/sync2gitee.yml new file mode 100644 index 0000000..f11e8be --- /dev/null +++ b/.github/workflows/sync2gitee.yml @@ -0,0 +1,16 @@ +name: Synchronize to Gitee +on: [push] +jobs: + repo-sync: + if: github.repository == '1Panel-dev/1Panel' + runs-on: ubuntu-latest + steps: + - name: Mirror the Github organization repos to Gitee. + uses: Yikun/hub-mirror-action@master + with: + src: 'github/1Panel-dev' + dst: 'gitee/fit2cloud-feizhiyun' + dst_key: ${{ secrets.GITEE_PRIVATE_KEY }} + dst_token: ${{ secrets.GITEE_TOKEN }} + static_list: "1Panel" + force_update: true \ No newline at end of file diff --git a/.github/workflows/tyops-check.yml b/.github/workflows/tyops-check.yml new file mode 100644 index 0000000..88c0daf --- /dev/null +++ b/.github/workflows/tyops-check.yml @@ -0,0 +1,11 @@ +name: Typos Check +on: pull_request +jobs: + run: + name: Spell Check with Typos + runs-on: ubuntu-latest + steps: + - name: Checkout Actions Repository + uses: actions/checkout@v2 + - name: Check spelling + uses: crate-ci/typos@master \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5c105b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,71 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +build/1panel-agent +build/1panel-core + +# Mac +.DS_Store +*/.DS_Store + +# VS Code +.vscode +*.project +*.factorypath +__debug* + +# IntelliJ IDEA +.idea/* +!.idea/icon.png +*.iws +*.iml +*.ipr + + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories +/pkg/ +backend/__debug_bin +core/cmd/server/__debug_bin +core/cmd/server/web/assets +core/cmd/server/web/monacoeditorwork +core/cmd/server/web/index.html +frontend/auto-imports.d.ts +frontend/components.d.ts +frontend/src/xpack +agent/xpack +agent/router/entry_xpack.go +agent/server/init_xpack.go +agent/utils/xpack/xpack_xpack.go +core/xpack +core/router/entry_xpack.go +core/server/init_xpack.go +core/utils/xpack/xpack_xpack.go +xpack + +.history/ +dist/ +1pctl +1panel.service +install.sh +quick_start.sh +cmd/server/fileList.txt +.fileList.txt +1Panel.code-workspace + +core/.golangci.yml +agent/.golangci.yml + +.opencode +openspec +CLAUDE.md +AGENTS.md +opencode.json \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml new file mode 100644 index 0000000..3ef2ce7 --- /dev/null +++ b/.goreleaser.yaml @@ -0,0 +1,82 @@ +# yaml-language-server: $schema=https://goreleaser.com/static/schema.json +# vim: set ts=2 sw=2 tw=0 fo=jcroql +version: 2 + +before: + hooks: + # - export NODE_OPTIONS="--max-old-space-size=8192" + # - make build_web + - chmod +x ./ci/script.sh + - ./ci/script.sh + - sed -i 's@ORIGINAL_VERSION=.*@ORIGINAL_VERSION=v{{ .Version }}@g' 1pctl + +builds: + - id: agent + dir: agent + main: cmd/server/main.go + binary: 1panel-agent + flags: + - -tags=xpack + - -trimpath + ldflags: + - -w -s + env: + - CGO_ENABLED=0 + goos: + - linux + goarm: + - 7 + goarch: + - amd64 + - arm64 + - arm + - ppc64le + - s390x + - riscv64 + + - id: core + dir: core + main: cmd/server/main.go + binary: 1panel-core + flags: + - -tags=xpack + - -trimpath + ldflags: + - -w -s + env: + - CGO_ENABLED=0 + goos: + - linux + goarm: + - 7 + goarch: + - amd64 + - arm64 + - arm + - ppc64le + - s390x + - riscv64 + +archives: + - formats: [ 'tar.gz' ] + ids: [core, agent] + name_template: "1panel-v{{ .Version }}-{{ .Os }}-{{ .Arch }}{{- if .Arm }}v{{ .Arm }}{{ end }}" + wrap_in_directory: true + files: + - 1pctl + - install.sh + - 1panel-core.service + - 1panel-agent.service + - initscript/* + - lang/* + - GeoIP.mmdb + +checksum: + name_template: 'checksums.txt' + +changelog: + sort: asc + filters: + exclude: + - "^docs:" + - "^test:" diff --git a/.idea/icon.png b/.idea/icon.png new file mode 100644 index 0000000..c80e8f0 Binary files /dev/null and b/.idea/icon.png differ diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..18c9147 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..eb5f502 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,30 @@ +# Contributing + +As a contributor, you should agree that: + +- The producer can adjust the open-source agreement to be more strict or relaxed as deemed necessary. +- Your contributed code may be used for commercial purposes, including but not limited to its cloud business operations. + +## Create pull request +PR are always welcome, even if they only contain small fixes like typos or a few lines of code. If there will be a significant effort, please document it as an issue and get a discussion going before starting to work on it. + +Please submit a PR broken down into small changes bit by bit. A PR consisting of a lot of features and code changes may be hard to review. It is recommended to submit PRs in an incremental fashion. + +This [development guideline](https://docs.1panel.pro/dev_manual/dev_manual/) contains information about repository structure, how to set up development environment, how to run it, and more. + +Note: If you split your pull request to small changes, please make sure any of the changes goes to master will not break anything. Otherwise, it can not be merged until this feature complete. + +## Report issues +It is a great way to contribute by reporting an issue. Well-written and complete bug reports are always welcome! Please open an issue and follow the template to fill in required information. + +Before opening any issue, please look up the existing issues to avoid submitting a duplication. +If you find a match, you can "subscribe" to it to get notified on updates. If you have additional helpful information about the issue, please leave a comment. + +When reporting issues, always include: + +* Which version you are using. +* Steps to reproduce the issue. +* Snapshots or log files if needed + +Because the issues are open to the public, when submitting files, be sure to remove any sensitive information, e.g. user name, password, IP address, and company name. You can +replace those parts with "REDACTED" or other strings like "****". diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..118b305 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,39 @@ +# syntax=docker/dockerfile:1 +ARG NODE_VERSION=20 +ARG GO_VERSION=1.22 + +FROM node:${NODE_VERSION}-bookworm AS web-builder +WORKDIR /src +COPY frontend/package*.json ./frontend/ +RUN cd frontend && npm install +COPY frontend ./frontend +COPY core ./core +RUN cd frontend && npm run build:pro + +FROM golang:${GO_VERSION}-bookworm AS go-builder +WORKDIR /src +ARG GOTOOLCHAIN=auto +ENV GOTOOLCHAIN=${GOTOOLCHAIN} +ENV CGO_ENABLED=0 +COPY core/go.mod core/go.sum ./core/ +COPY agent/go.mod agent/go.sum ./agent/ +RUN cd core && go mod download +RUN cd agent && go mod download +COPY . . +COPY --from=web-builder /src/core/cmd/server/web /src/core/cmd/server/web +RUN cd core && go build -trimpath -ldflags "-s -w" -o /out/1panel-core ./cmd/server/main.go +RUN cd agent && go build -trimpath -ldflags "-s -w" -o /out/1panel-agent ./cmd/server/main.go + +FROM debian:bookworm-slim +RUN apt-get update \ + && apt-get install -y --no-install-recommends bash ca-certificates tzdata tar gzip grep sed \ + && rm -rf /var/lib/apt/lists/* +COPY --from=go-builder /out/1panel-core /usr/local/bin/1panel-core +COPY --from=go-builder /out/1panel-agent /usr/local/bin/1panel-agent +COPY docker/entrypoint.sh /usr/local/bin/entrypoint.sh +COPY docker/1pctl /usr/local/bin/1pctl +COPY core/cmd/server/conf/app.yaml /usr/local/share/1panel/app.yaml +RUN chmod 755 /usr/local/bin/entrypoint.sh /usr/local/bin/1pctl \ + && mkdir -p /opt/1panel/conf +EXPOSE 9999 +ENTRYPOINT ["/usr/local/bin/entrypoint.sh"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f288702 --- /dev/null +++ b/LICENSE @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + 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 for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..ee29f24 --- /dev/null +++ b/Makefile @@ -0,0 +1,49 @@ +GOCMD=go +GOBUILD=$(GOCMD) build +GOCLEAN=$(GOCMD) clean +GOARCH=$(shell go env GOARCH) +GOOS=$(shell go env GOOS ) + +BASE_PATH := $(shell pwd) +BUILD_PATH = $(BASE_PATH)/build +WEB_PATH=$(BASE_PATH)/frontend +ASSERT_PATH= $(BASE_PATH)/core/cmd/server/web/assets + +CORE_PATH=$(BASE_PATH)/core +CORE_MAIN=$(CORE_PATH)/cmd/server/main.go +CORE_NAME=1panel-core + +AGENT_PATH=$(BASE_PATH)/agent +AGENT_MAIN=$(AGENT_PATH)/cmd/server/main.go +AGENT_NAME=1panel-agent + + +clean_assets: + rm -rf $(ASSERT_PATH) + +upx_bin: + upx $(BUILD_PATH)/$(CORE_NAME) + upx $(BUILD_PATH)/$(AGENT_NAME) + +build_frontend: + cd $(WEB_PATH) && npm install && npm run build:pro + +build_core_on_linux: + cd $(CORE_PATH) \ + && CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(CORE_NAME) $(CORE_MAIN) + +build_agent_on_linux: + cd $(AGENT_PATH) \ + && CGO_ENABLED=0 GOOS=$(GOOS) GOARCH=$(GOARCH) $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(AGENT_NAME) $(AGENT_MAIN) + +build_core_on_darwin: + cd $(CORE_PATH) \ + && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(CORE_NAME) $(CORE_MAIN) + +build_agent_on_darwin: + cd $(AGENT_PATH) \ + && CGO_ENABLED=0 GOOS=linux GOARCH=amd64 $(GOBUILD) -trimpath -ldflags '-s -w' -o $(BUILD_PATH)/$(AGENT_NAME) $(AGENT_MAIN) + +build_all: build_frontend build_core_on_linux build_agent_on_linux + +build_on_local: clean_assets build_frontend build_core_on_darwin build_agent_on_darwin diff --git a/OWNERS b/OWNERS new file mode 100644 index 0000000..f279733 --- /dev/null +++ b/OWNERS @@ -0,0 +1,9 @@ +reviewers: +- wanghe-fit2cloud +- zhengkunwang223 +- ssongliu + +approvers: +- wanghe-fit2cloud +- zhengkunwang223 +- ssongliu diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..eb9cf5d --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,13 @@ +# Security policy + +## Supported versions + +| Version | Supported | +| ------- | ------------------ | +| v1.x | :white_check_mark: | + +## Reporting a vulnerability + +We first appreciate and are very thankful that you've found a vulnerability issue in 1Panel! By disclosing such issue to 1Panel development team you are helping 1Panel to become a much more safer project than before! ;) + +To protect the existing users of 1Panel, we kindly ask you to not disclose the vulnerability to anyone except the 1Panel development team before a fix has been rolled out. Send an email to `wanghe@fit2cloud.com` instead. diff --git a/agent/app/api/v2/ai.go b/agent/app/api/v2/ai.go new file mode 100644 index 0000000..11bdd22 --- /dev/null +++ b/agent/app/api/v2/ai.go @@ -0,0 +1,256 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu" + "github.com/gin-gonic/gin" +) + +// @Tags AI +// @Summary Create Ollama model +// @Accept json +// @Param request body dto.OllamaModelName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型 [name]","formatEN":"add Ollama model [name]"} +func (b *BaseApi) CreateOllamaModel(c *gin.Context) { + var req dto.OllamaModelName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := aiToolService.Create(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags AI +// @Summary Rereate Ollama model +// @Accept json +// @Param request body dto.OllamaModelName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/recreate [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 Ollama 模型重试 [name]","formatEN":"re-add Ollama model [name]"} +func (b *BaseApi) RecreateOllamaModel(c *gin.Context) { + var req dto.OllamaModelName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := aiToolService.Recreate(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags AI +// @Summary Close Ollama model conn +// @Accept json +// @Param request body dto.OllamaModelName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/close [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"关闭 Ollama 模型连接 [name]","formatEN":"close conn for Ollama model [name]"} +func (b *BaseApi) CloseOllamaModel(c *gin.Context) { + var req dto.OllamaModelName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := aiToolService.Close(req.Name); err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, nil) +} + +// @Tags AI +// @Summary Sync Ollama model list +// @Success 200 {array} dto.OllamaModelDropList +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/sync [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 Ollama 模型列表","formatEN":"sync Ollama model list"} +func (b *BaseApi) SyncOllamaModel(c *gin.Context) { + list, err := aiToolService.Sync() + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags AI +// @Summary Page Ollama models +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/search [post] +func (b *BaseApi) SearchOllamaModel(c *gin.Context) { + var req dto.SearchWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := aiToolService.Search(req) + if err != nil { + helper.BadRequest(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags AI +// @Summary Page Ollama models +// @Accept json +// @Param request body dto.OllamaModelName true "request" +// @Success 200 {string} details +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/load [post] +func (b *BaseApi) LoadOllamaModelDetail(c *gin.Context) { + var req dto.OllamaModelName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + detail, err := aiToolService.LoadDetail(req.Name) + if err != nil { + helper.BadRequest(c, err) + return + } + + helper.SuccessWithData(c, detail) +} + +// @Tags AI +// @Summary Delete Ollama model +// @Accept json +// @Param request body dto.ForceDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/ollama/model/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"ollama_models","output_column":"name","output_value":"names"}],"formatZH":"删除 Ollama 模型 [names]","formatEN":"remove Ollama model [names]"} +func (b *BaseApi) DeleteOllamaModel(c *gin.Context) { + var req dto.ForceDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := aiToolService.Delete(req); err != nil { + helper.BadRequest(c, err) + return + } + + helper.Success(c) +} + +// @Tags AI +// @Summary Load gpu / xpu info +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/gpu/load [get] +func (b *BaseApi) LoadGpuInfo(c *gin.Context) { + ok, client := gpu.New() + if ok { + info, err := client.LoadGpuInfo() + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, info) + return + } + xpuOK, xpuClient := xpu.New() + if xpuOK { + info, err := xpuClient.LoadGpuInfo() + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, info) + return + } + helper.SuccessWithData(c, &common.GpuInfo{}) +} + +// @Tags AI +// @Summary Bind domain +// @Accept json +// @Param request body dto.OllamaBindDomain true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/domain/bind [post] +func (b *BaseApi) BindDomain(c *gin.Context) { + var req dto.OllamaBindDomain + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := aiToolService.BindDomain(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.Success(c) +} + +// @Tags AI +// @Summary Get bind domain +// @Accept json +// @Param request body dto.OllamaBindDomainReq true "request" +// @Success 200 {object} dto.OllamaBindDomainRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/domain/get [post] +func (b *BaseApi) GetBindDomain(c *gin.Context) { + var req dto.OllamaBindDomainReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := aiToolService.GetBindDomain(req) + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// Tags AI +// Summary Update bind domain +// Accept json +// Param request body dto.OllamaBindDomain true "request" +// Success 200 +// Security ApiKeyAuth +// Security Timestamp +// Router /ai/domain/update [post] +func (b *BaseApi) UpdateBindDomain(c *gin.Context) { + var req dto.OllamaBindDomain + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := aiToolService.UpdateBindDomain(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/alert.go b/agent/app/api/v2/alert.go new file mode 100644 index 0000000..28f8f83 --- /dev/null +++ b/agent/app/api/v2/alert.go @@ -0,0 +1,200 @@ +package v2 + +import ( + "errors" + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) PageAlert(c *gin.Context) { + var req dto.AlertSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, alerts, err := alertService.PageAlert(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: alerts, + }) +} + +func (b *BaseApi) GetAlerts(c *gin.Context) { + alerts, err := alertService.GetAlerts() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, alerts) +} + +func (b *BaseApi) CreateAlert(c *gin.Context) { + var req dto.AlertCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := alertService.CreateAlert(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) DeleteAlert(c *gin.Context) { + var req dto.DeleteRequest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := alertService.DeleteAlert(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) UpdateAlert(c *gin.Context) { + var req dto.AlertUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := alertService.UpdateAlert(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) GetAlert(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, errors.New("no such id in request param")) + return + } + alert, err := alertService.GetAlert(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, alert) +} + +func (b *BaseApi) UpdateAlertStatus(c *gin.Context) { + var req dto.AlertUpdateStatus + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := alertService.UpdateStatus(req.ID, req.Status); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) GetDisks(c *gin.Context) { + alerts, err := alertService.GetDisks() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, alerts) +} + +func (b *BaseApi) PageAlertLogs(c *gin.Context) { + var req dto.AlertLogSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, alertLogs, err := alertService.PageAlertLogs(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: alertLogs, + }) +} + +func (b *BaseApi) CleanAlertLogs(c *gin.Context) { + if err := alertService.CleanAlertLogs(); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) GetClams(c *gin.Context) { + clams, err := alertService.GetClams() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, clams) +} + +func (b *BaseApi) GetCronJobs(c *gin.Context) { + var req dto.CronJobReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + cronJobs, err := alertService.GetCronJobs(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, cronJobs) +} + +func (b *BaseApi) GetAlertConfig(c *gin.Context) { + config, err := alertService.GetAlertConfig() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, config) +} + +func (b *BaseApi) UpdateAlertConfig(c *gin.Context) { + var req dto.AlertConfigUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := alertService.UpdateAlertConfig(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) DeleteAlertConfig(c *gin.Context) { + var req dto.DeleteRequest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := alertService.DeleteAlertConfig(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) TestAlertConfig(c *gin.Context) { + var req dto.AlertConfigTest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + flag, err := alertService.TestAlertConfig(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, flag) +} diff --git a/agent/app/api/v2/app.go b/agent/app/api/v2/app.go new file mode 100644 index 0000000..4f7fb9a --- /dev/null +++ b/agent/app/api/v2/app.go @@ -0,0 +1,242 @@ +package v2 + +import ( + "net/http" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/gin-gonic/gin" +) + +// @Tags App +// @Summary List apps +// @Accept json +// @Param request body request.AppSearch true "request" +// @Success 200 {object} response.AppRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/search [post] +func (b *BaseApi) SearchApp(c *gin.Context) { + var req request.AppSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + list, err := appService.PageApp(c, req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithDataGzipped(c, list) +} + +// @Tags App +// @Summary Sync remote app list +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/sync/remote [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"} +func (b *BaseApi) SyncApp(c *gin.Context) { + var req dto.OperateWithTask + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := appService.GetAppUpdate() + if err != nil { + helper.InternalServer(c, err) + return + } + if !res.CanUpdate { + if res.IsSyncing { + helper.SuccessWithMsg(c, i18n.GetMsgByKey("AppStoreIsSyncing")) + } else { + helper.SuccessWithMsg(c, i18n.GetMsgByKey("AppStoreIsUpToDate")) + } + return + } + if err = appService.SyncAppListFromRemote(req.TaskID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags App +// @Summary Sync local app list +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/sync/local [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用商店同步","formatEN":"App store synchronization"} +func (b *BaseApi) SyncLocalApp(c *gin.Context) { + var req dto.OperateWithTask + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + go appService.SyncAppListFromLocal(req.TaskID) + helper.Success(c) +} + +// @Tags App +// @Summary Search app by key +// @Accept json +// @Param key path string true "app key" +// @Success 200 {object} response.AppDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/:key [get] +func (b *BaseApi) GetApp(c *gin.Context) { + appKey, err := helper.GetStrParamByKey(c, "key") + if err != nil { + helper.BadRequest(c, err) + return + } + appDTO, err := appService.GetApp(c, appKey) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, appDTO) +} + +// @Tags App +// @Summary Search app detail by appid +// @Accept json +// @Param appId path integer true "app id" +// @Param version path string true "app 版本" +// @Param version path string true "app 类型" +// @Success 200 {object} response.AppDetailDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/detail/:appId/:version/:type [get] +func (b *BaseApi) GetAppDetail(c *gin.Context) { + appID, err := helper.GetIntParamByKey(c, "appId") + if err != nil { + helper.BadRequest(c, err) + return + } + version := c.Param("version") + appType := c.Param("type") + appDetailDTO, err := appService.GetAppDetail(appID, version, appType) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, appDetailDTO) +} + +// @Tags App +// @Summary Get app detail by id +// @Accept json +// @Param appId path integer true "id" +// @Success 200 {object} response.AppDetailDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/details/:id [get] +func (b *BaseApi) GetAppDetailByID(c *gin.Context) { + appDetailID, err := helper.GetIntParamByKey(c, "id") + if err != nil { + helper.BadRequest(c, err) + return + } + appDetailDTO, err := appService.GetAppDetailByID(appDetailID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, appDetailDTO) +} + +// @Tags App +// @Summary Install app +// @Accept json +// @Param request body request.AppInstallCreate true "request" +// @Success 200 {object} model.AppInstall +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/install [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"安装应用 [name]","formatEN":"Install app [name]"} +func (b *BaseApi) InstallApp(c *gin.Context) { + var req request.AppInstallCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + install, err := appService.Install(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, install) +} + +func (b *BaseApi) GetAppTags(c *gin.Context) { + tags, err := appService.GetAppTags(c) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, tags) +} + +// @Tags App +// @Summary Get app list update +// @Success 200 {object} response.AppUpdateRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/checkupdate [get] +func (b *BaseApi) GetAppListUpdate(c *gin.Context) { + res, err := appService.GetAppUpdate() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags App +// @Summary Get app icon by app_id +// @Accept json +// @Param appId path integer true "app id" +// @Success 200 {file} file "app icon" +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/icon/:key [get] +func (b *BaseApi) GetAppIcon(c *gin.Context) { + appKey, err := helper.GetStrParamByKey(c, "key") + if err != nil { + helper.BadRequest(c, err) + return + } + iconBytes, err := appService.GetAppIcon(appKey) + if err != nil { + helper.InternalServer(c, err) + return + } + c.Header("Content-Type", "image/png") + c.Header("Cache-Control", "public, max-age=31536000, immutable") + c.Header("Last-Modified", time.Now().UTC().Format(http.TimeFormat)) + c.Data(http.StatusOK, "image/png", iconBytes) +} + +// @Tags App +// @Summary Search app detail by appkey and version +// @Accept json +// @Param appId path integer true "app key" +// @Param version path string true "app version" +// @Success 200 {object} response.AppDetailSimpleDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/detail/node/:appKey/:version [get] +func (b *BaseApi) GetAppDetailForNode(c *gin.Context) { + appKey := c.Param("appKey") + version := c.Param("version") + appDetailDTO, err := appService.GetAppDetailByKey(appKey, version) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, appDetailDTO) +} diff --git a/agent/app/api/v2/app_ignore_upgrade.go b/agent/app/api/v2/app_ignore_upgrade.go new file mode 100644 index 0000000..48073a4 --- /dev/null +++ b/agent/app/api/v2/app_ignore_upgrade.go @@ -0,0 +1,66 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags App +// @Summary List Upgrade Ignored App +// @Accept json +// @Success 200 {array} model.AppIgnoreUpgrade +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/ignored/detail [get] +func (b *BaseApi) ListAppIgnored(c *gin.Context) { + res, err := appIgnoreUpgradeService.List() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags App +// @Summary Ignore Upgrade App +// @Accept json +// @Param request body request.AppIgnoreUpgradeReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/ignore [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"忽略应用升级","formatEN":"Ignore application upgrade"} +func (b *BaseApi) IgnoreAppUpgrade(c *gin.Context) { + var req request.AppIgnoreUpgradeReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := appIgnoreUpgradeService.CreateAppIgnore(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// 写一个去掉忽略的接口 +// @Tags App +// @Summary Cancel Ignore Upgrade App +// @Accept json +// @Param request body request.ReqWithID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/ignored/cancel [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"取消忽略应用升级","formatEN":"Cancel ignore application upgrade"} +func (b *BaseApi) CancelIgnoreAppUpgrade(c *gin.Context) { + var req request.ReqWithID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := appIgnoreUpgradeService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/app_install.go b/agent/app/api/v2/app_install.go new file mode 100644 index 0000000..7d8b840 --- /dev/null +++ b/agent/app/api/v2/app_install.go @@ -0,0 +1,351 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags App +// @Summary Page app installed +// @Accept json +// @Param request body request.AppInstalledSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/search [post] +func (b *BaseApi) SearchAppInstalled(c *gin.Context) { + var req request.AppInstalledSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if req.All { + list, err := appInstallService.SearchForWebsite(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: int64(len(list)), + }) + } else { + total, list, err := appInstallService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) + } +} + +// @Tags App +// @Summary List app installed +// @Accept json +// @Success 200 {array} dto.AppInstallInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/list [get] +func (b *BaseApi) ListAppInstalled(c *gin.Context) { + list, err := appInstallService.GetInstallList() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags App +// @Summary Check app installed +// @Accept json +// @Param request body request.AppInstalledInfo true "request" +// @Success 200 {object} response.AppInstalledCheck +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/check [post] +func (b *BaseApi) CheckAppInstalled(c *gin.Context) { + var req request.AppInstalledInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + checkData, err := appInstallService.CheckExist(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, checkData) +} + +// @Tags App +// @Summary Search app port by key +// @Accept json +// @Param request body dto.OperationWithNameAndType true "request" +// @Success 200 {integer} port +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/loadport [post] +func (b *BaseApi) LoadPort(c *gin.Context) { + var req dto.OperationWithNameAndType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + port, err := appInstallService.LoadPort(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, port) +} + +// @Tags App +// @Summary Search app password by key +// @Accept json +// @Param request body dto.OperationWithNameAndType true "request" +// @Success 200 {object} response.DatabaseConn +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/conninfo [POST] +func (b *BaseApi) LoadConnInfo(c *gin.Context) { + var req dto.OperationWithNameAndType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + conn, err := appInstallService.LoadConnInfo(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, conn) +} + +// @Tags App +// @Summary Check before delete +// @Accept json +// @Param appInstallId path integer true "App install id" +// @Success 200 {array} dto.AppResource +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/delete/check/:appInstallId [get] +func (b *BaseApi) DeleteCheck(c *gin.Context) { + appInstallId, err := helper.GetIntParamByKey(c, "appInstallId") + if err != nil { + helper.BadRequest(c, err) + return + } + checkData, err := appInstallService.DeleteCheck(appInstallId) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, checkData) +} + +// Sync app installed +// @Tags App +// @Summary Sync app installed +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/sync [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步已安装应用列表","formatEN":"Sync the list of installed apps"} +func (b *BaseApi) SyncInstalled(c *gin.Context) { + if err := appInstallService.SyncAll(false); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags App +// @Summary Operate installed app +// @Accept json +// @Param request body request.AppInstalledOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/op [post] +// @x-panel-log {"bodyKeys":["installId","operate"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"installId","isList":false,"db":"app_installs","output_column":"app_id","output_value":"appId"},{"input_column":"id","input_value":"installId","isList":false,"db":"app_installs","output_column":"name","output_value":"appName"},{"input_column":"id","input_value":"appId","isList":false,"db":"apps","output_column":"key","output_value":"appKey"}],"formatZH":"[operate] 应用 [appKey][appName]","formatEN":"[operate] App [appKey][appName]"} +func (b *BaseApi) OperateInstalled(c *gin.Context) { + var req request.AppInstalledOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := appInstallService.Operate(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags App +// @Summary Search app service by key +// @Accept json +// @Param key path string true "request" +// @Success 200 {array} response.AppService +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/services/:key [get] +func (b *BaseApi) GetServices(c *gin.Context) { + key := c.Param("key") + services, err := appInstallService.GetServices(key) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, services) +} + +// @Tags App +// @Summary Search app update version by install id +// @Accept json +// @Param appInstallId path integer true "request" +// @Success 200 {array} dto.AppVersion +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/update/versions [post] +func (b *BaseApi) GetUpdateVersions(c *gin.Context) { + var req request.AppUpdateVersion + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + versions, err := appInstallService.GetUpdateVersions(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, versions) +} + +// @Tags App +// @Summary Change app port +// @Accept json +// @Param request body request.PortUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/port/change [post] +// @x-panel-log {"bodyKeys":["key","name","port"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用端口修改 [key]-[name] => [port]","formatEN":"Application port update [key]-[name] => [port]"} +func (b *BaseApi) ChangeAppPort(c *gin.Context) { + var req request.PortUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := appInstallService.ChangeAppPort(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags App +// @Summary Search default config by key +// @Accept json +// @Param request body dto.OperationWithNameAndType true "request" +// @Success 200 {string} content +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/conf [post] +func (b *BaseApi) GetDefaultConfig(c *gin.Context) { + var req dto.OperationWithNameAndType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + content, err := appInstallService.GetDefaultConfigByKey(req.Type, req.Name) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, content) +} + +// @Tags App +// @Summary Search params by appInstallId +// @Accept json +// @Param appInstallId path string true "request" +// @Success 200 {object} response.AppConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/params/:appInstallId [get] +func (b *BaseApi) GetParams(c *gin.Context) { + appInstallId, err := helper.GetIntParamByKey(c, "appInstallId") + if err != nil { + helper.BadRequest(c, err) + return + } + content, err := appInstallService.GetParams(appInstallId) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, content) +} + +// @Tags App +// @Summary Change app params +// @Accept json +// @Param request body request.AppInstalledUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/params/update [post] +// @x-panel-log {"bodyKeys":["installId"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用参数修改 [installId]","formatEN":"Application param update [installId]"} +func (b *BaseApi) UpdateInstalled(c *gin.Context) { + var req request.AppInstalledUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := appInstallService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags App +// @Summary Update app config +// @Accept json +// @Param request body request.AppConfigUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/config/update [post] +// @x-panel-log {"bodyKeys":["installID","webUI"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"应用配置更新 [installID]","formatEN":"Application config update [installID]"} +func (b *BaseApi) UpdateAppConfig(c *gin.Context) { + var req request.AppConfigUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := appInstallService.UpdateAppConfig(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags App +// @Summary Get app install info +// @Accept json +// @Param appInstallId path integer true "App install id" +// @Success 200 {object} dto.AppInstallInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /apps/installed/info/:appInstallId [get] +func (b *BaseApi) GetAppInstallInfo(c *gin.Context) { + appInstallId, err := helper.GetIntParamByKey(c, "appInstallId") + if err != nil { + helper.BadRequest(c, err) + return + } + info, err := appInstallService.GetAppInstallInfo(appInstallId) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, info) +} diff --git a/agent/app/api/v2/backup.go b/agent/app/api/v2/backup.go new file mode 100644 index 0000000..d998f26 --- /dev/null +++ b/agent/app/api/v2/backup.go @@ -0,0 +1,530 @@ +package v2 + +import ( + "fmt" + "path" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) CheckBackupUsed(c *gin.Context) { + name, err := helper.GetStrParamByKey(c, "name") + if err != nil { + helper.BadRequest(c, err) + return + } + + if err := backupService.CheckUsed(name, true); err != nil { + helper.BadRequest(c, err) + return + } + + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Check backup account +// @Accept json +// @Param request body dto.BackupOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/check [post] +func (b *BaseApi) CheckBackup(c *gin.Context) { + var req dto.BackupOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + helper.SuccessWithData(c, backupService.CheckConn(req)) +} + +// @Tags Backup Account +// @Summary Create backup account +// @Accept json +// @Param request body dto.BackupOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建备份账号 [type]","formatEN":"create backup account [type]"} +func (b *BaseApi) CreateBackup(c *gin.Context) { + var req dto.BackupOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := backupService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Refresh token +// @Accept json +// @Param request body dto.BackupOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/refresh/token [post] +func (b *BaseApi) RefreshToken(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := backupService.RefreshToken(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary List buckets +// @Accept json +// @Param request body dto.ForBuckets true "request" +// @Success 200 {array} object +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/buckets [post] +func (b *BaseApi) ListBuckets(c *gin.Context) { + var req dto.ForBuckets + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + buckets, err := backupService.GetBuckets(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, buckets) +} + +// @Tags Backup Account +// @Summary Delete backup account +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"backup_accounts","output_column":"type","output_value":"types"}],"formatZH":"删除备份账号 [types]","formatEN":"delete backup account [types]"} +func (b *BaseApi) DeleteBackup(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := backupService.Delete(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Update backup account +// @Accept json +// @Param request body dto.BackupOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/update [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新备份账号 [types]","formatEN":"update backup account [types]"} +func (b *BaseApi) UpdateBackup(c *gin.Context) { + var req dto.BackupOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := backupService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Upload file for recover +// @Accept json +// @Param request body dto.UploadForRecover true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/upload [post] +// @x-panel-log {"bodyKeys":["filePath"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传备份文件 [filePath]","formatEN":"upload backup file [filePath]"} +func (b *BaseApi) UploadForRecover(c *gin.Context) { + var req dto.UploadForRecover + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := backupService.UploadForRecover(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Load backup account options +// @Accept json +// @Success 200 {array} dto.BackupOption +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/options [get] +func (b *BaseApi) LoadBackupOptions(c *gin.Context) { + list, err := backupService.LoadBackupOptions() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags Backup Account +// @Summary Search backup accounts with page +// @Accept json +// @Param request body dto.SearchPageWithType true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/search [post] +func (b *BaseApi) SearchBackup(c *gin.Context) { + var req dto.SearchPageWithType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := backupService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Backup Account +// @Summary get local backup dir +// @Success 200 {string} dir +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/local [get] +func (b *BaseApi) GetLocalDir(c *gin.Context) { + dir, err := backupService.GetLocalDir() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dir) +} + +// @Tags Backup Account +// @Summary Load backup record size +// @Accept json +// @Param request body dto.SearchForSize true "request" +// @Success 200 {array} dto.RecordFileSize +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/record/size [post] +func (b *BaseApi) LoadBackupRecordSize(c *gin.Context) { + var req dto.SearchForSize + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := backupRecordService.LoadRecordSize(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Backup Account +// @Summary Page backup records +// @Accept json +// @Param request body dto.RecordSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/record/search [post] +func (b *BaseApi) SearchBackupRecords(c *gin.Context) { + var req dto.RecordSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := backupRecordService.SearchRecordsWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Backup Account +// @Summary Page backup records by cronjob +// @Accept json +// @Param request body dto.RecordSearchByCronjob true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/record/search/bycronjob [post] +func (b *BaseApi) SearchBackupRecordsByCronjob(c *gin.Context) { + var req dto.RecordSearchByCronjob + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := backupRecordService.SearchRecordsByCronjobWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Backup Account +// @Summary Download backup record +// @Accept json +// @Param request body dto.DownloadRecord true "request" +// @Success 200 {string} filePath +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/record/download [post] +// @x-panel-log {"bodyKeys":["source","fileName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载备份记录 [source][fileName]","formatEN":"download backup records [source][fileName]"} +func (b *BaseApi) DownloadRecord(c *gin.Context) { + var req dto.DownloadRecord + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + filePath, err := backupRecordService.DownloadRecord(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, filePath) +} + +// @Tags Backup Account +// @Summary Update backup record description +// @Accept json +// @Param request body dto.UpdateDescription true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/record/description/update [post] +func (b *BaseApi) UpdateRecordDescription(c *gin.Context) { + var req dto.UpdateDescription + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := backupRecordService.UpdateDescription(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Delete backup record +// @Accept json +// @Param request body dto.BatchDeleteReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/record/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"backup_records","output_column":"file_name","output_value":"files"}],"formatZH":"删除备份记录 [files]","formatEN":"delete backup records [files]"} +func (b *BaseApi) DeleteBackupRecord(c *gin.Context) { + var req dto.BatchDeleteReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := backupRecordService.BatchDeleteRecord(req.Ids); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary List files from backup accounts +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/search/files [post] +func (b *BaseApi) LoadFilesFromBackup(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data := backupRecordService.ListFiles(req) + helper.SuccessWithData(c, data) +} + +// @Tags Backup Account +// @Summary Backup system data +// @Accept json +// @Param request body dto.CommonBackup true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/backup [post] +// @x-panel-log {"bodyKeys":["type","name","detailName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"备份 [type] 数据 [name][detailName]","formatEN":"backup [type] data [name][detailName]"} +func (b *BaseApi) Backup(c *gin.Context) { + var req dto.CommonBackup + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + switch req.Type { + case "app": + if _, err := backupService.AppBackup(req); err != nil { + helper.InternalServer(c, err) + return + } + case "mysql", "mariadb", constant.AppMysqlCluster: + if err := backupService.MysqlBackup(req); err != nil { + helper.InternalServer(c, err) + return + } + case constant.AppPostgresql, constant.AppPostgresqlCluster: + if err := backupService.PostgresqlBackup(req); err != nil { + helper.InternalServer(c, err) + return + } + case "website": + if err := backupService.WebsiteBackup(req); err != nil { + helper.InternalServer(c, err) + return + } + case "redis", constant.AppRedisCluster: + if err := backupService.RedisBackup(req); err != nil { + helper.InternalServer(c, err) + return + } + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Recover system data +// @Accept json +// @Param request body dto.CommonRecover true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/recover [post] +// @x-panel-log {"bodyKeys":["type","name","detailName","file"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"从 [file] 恢复 [type] 数据 [name][detailName]","formatEN":"recover [type] data [name][detailName] from [file]"} +func (b *BaseApi) Recover(c *gin.Context) { + var req dto.CommonRecover + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + downloadPath, err := backupRecordService.DownloadRecord(dto.DownloadRecord{ + DownloadAccountID: req.DownloadAccountID, + FileDir: path.Dir(req.File), + FileName: path.Base(req.File), + }) + if err != nil { + helper.BadRequest(c, fmt.Errorf("download file failed, err: %v", err)) + return + } + req.File = downloadPath + switch req.Type { + case "mysql", "mariadb", constant.AppMysqlCluster: + if err := backupService.MysqlRecover(req); err != nil { + helper.InternalServer(c, err) + return + } + case constant.AppPostgresql, constant.AppPostgresqlCluster: + if err := backupService.PostgresqlRecover(req); err != nil { + helper.InternalServer(c, err) + return + } + case "website": + if err := backupService.WebsiteRecover(req); err != nil { + helper.InternalServer(c, err) + return + } + case "redis", constant.AppRedisCluster: + if err := backupService.RedisRecover(req); err != nil { + helper.InternalServer(c, err) + return + } + case "app": + if err := backupService.AppRecover(req); err != nil { + helper.InternalServer(c, err) + return + } + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Recover system data by upload +// @Accept json +// @Param request body dto.CommonRecover true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /backups/recover/byupload [post] +// @x-panel-log {"bodyKeys":["type","name","detailName","file"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"从 [file] 恢复 [type] 数据 [name][detailName]","formatEN":"recover [type] data [name][detailName] from [file]"} +func (b *BaseApi) RecoverByUpload(c *gin.Context) { + var req dto.CommonRecover + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + switch req.Type { + case "mysql", "mariadb", constant.AppMysqlCluster: + if err := backupService.MysqlRecoverByUpload(req); err != nil { + helper.InternalServer(c, err) + return + } + case constant.AppPostgresql, constant.AppPostgresqlCluster: + if err := backupService.PostgresqlRecoverByUpload(req); err != nil { + helper.InternalServer(c, err) + return + } + case "app": + if err := backupService.AppRecover(req); err != nil { + helper.InternalServer(c, err) + return + } + case "website": + if err := backupService.WebsiteRecover(req); err != nil { + helper.InternalServer(c, err) + return + } + } + helper.Success(c) +} diff --git a/agent/app/api/v2/clam.go b/agent/app/api/v2/clam.go new file mode 100644 index 0000000..d6c59a6 --- /dev/null +++ b/agent/app/api/v2/clam.go @@ -0,0 +1,274 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Clam +// @Summary Create clam +// @Accept json +// @Param request body dto.ClamCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam [post] +// @x-panel-log {"bodyKeys":["name","path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建扫描规则 [name][path]","formatEN":"create clam [name][path]"} +func (b *BaseApi) CreateClam(c *gin.Context) { + var req dto.ClamCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := clamService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Clam +// @Summary Update clam +// @Accept json +// @Param request body dto.ClamUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/update [post] +// @x-panel-log {"bodyKeys":["name","path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改扫描规则 [name][path]","formatEN":"update clam [name][path]"} +func (b *BaseApi) UpdateClam(c *gin.Context) { + var req dto.ClamUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := clamService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Clam +// @Summary Update clam status +// @Accept json +// @Param request body dto.ClamUpdateStatus true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/status/update [post] +// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"clams","output_column":"name","output_value":"name"}],"formatZH":"修改扫描规则 [name] 状态为 [status]","formatEN":"change the status of clam [name] to [status]."} +func (b *BaseApi) UpdateClamStatus(c *gin.Context) { + var req dto.ClamUpdateStatus + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := clamService.UpdateStatus(req.ID, req.Status); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Clam +// @Summary Page clam +// @Accept json +// @Param request body dto.SearchClamWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/search [post] +func (b *BaseApi) SearchClam(c *gin.Context) { + var req dto.SearchClamWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := clamService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Clam +// @Summary Load clam base info +// @Accept json +// @Success 200 {object} dto.ClamBaseInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/base [post] +func (b *BaseApi) LoadClamBaseInfo(c *gin.Context) { + info, err := clamService.LoadBaseInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, info) +} + +// @Tags Clam +// @Summary Operate Clam +// @Accept json +// @Success 200 +// @Param request body dto.Operate true "request" +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/operate [post] +// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] Clam","formatEN":"[operation] FTP"} +func (b *BaseApi) OperateClam(c *gin.Context) { + var req dto.Operate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := clamService.Operate(req.Operation); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Clam +// @Summary Clean clam record +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/record/clean [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":true,"db":"clams","output_column":"name","output_value":"name"}],"formatZH":"清空扫描报告 [name]","formatEN":"clean clam record [name]"} +func (b *BaseApi) CleanClamRecord(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := clamService.CleanRecord(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Clam +// @Summary Page clam record +// @Accept json +// @Param request body dto.ClamLogSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/record/search [post] +func (b *BaseApi) SearchClamRecord(c *gin.Context) { + var req dto.ClamLogSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := clamService.SearchRecords(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Clam +// @Summary Load clam file +// @Accept json +// @Param request body dto.ClamFileReq true "request" +// @Success 200 {string} content +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/file/search [post] +func (b *BaseApi) SearchClamFile(c *gin.Context) { + var req dto.ClamFileReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + content, err := clamService.LoadFile(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, content) +} + +// @Tags Clam +// @Summary Update clam file +// @Accept json +// @Param request body dto.UpdateByNameAndFile true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/file/update [post] +func (b *BaseApi) UpdateFile(c *gin.Context) { + var req dto.UpdateByNameAndFile + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := clamService.UpdateFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Clam +// @Summary Delete clam +// @Accept json +// @Param request body dto.ClamDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"clams","output_column":"name","output_value":"names"}],"formatZH":"删除扫描规则 [names]","formatEN":"delete clam [names]"} +func (b *BaseApi) DeleteClam(c *gin.Context) { + var req dto.ClamDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := clamService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Clam +// @Summary Handle clam scan +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clam/handle [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":true,"db":"clams","output_column":"name","output_value":"name"}],"formatZH":"执行病毒扫描 [name]","formatEN":"handle clam scan [name]"} +func (b *BaseApi) HandleClamScan(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := clamService.HandleOnce(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/compose_template.go b/agent/app/api/v2/compose_template.go new file mode 100644 index 0000000..c19ff80 --- /dev/null +++ b/agent/app/api/v2/compose_template.go @@ -0,0 +1,142 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Container Compose-template +// @Summary Create compose template +// @Accept json +// @Param request body dto.ComposeTemplateCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/template [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 compose 模版 [name]","formatEN":"create compose template [name]"} +func (b *BaseApi) CreateComposeTemplate(c *gin.Context) { + var req dto.ComposeTemplateCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := composeTemplateService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Compose-template +// @Summary Bacth compose template +// @Accept json +// @Param request body dto.ComposeTemplateBatch true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/template/batch [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"批量导入编排模版","formatEN":"batch import compose templates"} +func (b *BaseApi) BatchComposeTemplate(c *gin.Context) { + var req dto.ComposeTemplateBatch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := composeTemplateService.Batch(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Compose-template +// @Summary Page compose templates +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Produce json +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/template/search [post] +func (b *BaseApi) SearchComposeTemplate(c *gin.Context) { + var req dto.SearchWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := composeTemplateService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Container Compose-template +// @Summary List compose templates +// @Produce json +// @Success 200 {array} dto.ComposeTemplateInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/template [get] +func (b *BaseApi) ListComposeTemplate(c *gin.Context) { + list, err := composeTemplateService.List() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Container Compose-template +// @Summary Delete compose template +// @Accept json +// @Param request body dto.BatchDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/template/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"compose_templates","output_column":"name","output_value":"names"}],"formatZH":"删除 compose 模版 [names]","formatEN":"delete compose template [names]"} +func (b *BaseApi) DeleteComposeTemplate(c *gin.Context) { + var req dto.BatchDeleteReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := composeTemplateService.Delete(req.Ids); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Compose-template +// @Summary Update compose template +// @Accept json +// @Param request body dto.ComposeTemplateUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/template/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"compose_templates","output_column":"name","output_value":"name"}],"formatZH":"更新 compose 模版 [name]","formatEN":"update compose template information [name]"} +func (b *BaseApi) UpdateComposeTemplate(c *gin.Context) { + var req dto.ComposeTemplateUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + upMap := make(map[string]interface{}) + upMap["content"] = req.Content + upMap["description"] = req.Description + if err := composeTemplateService.Update(req.ID, upMap); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/container.go b/agent/app/api/v2/container.go new file mode 100644 index 0000000..0a9c868 --- /dev/null +++ b/agent/app/api/v2/container.go @@ -0,0 +1,769 @@ +package v2 + +import ( + "strconv" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" +) + +// @Tags Container +// @Summary Page containers +// @Accept json +// @Param request body dto.PageContainer true "request" +// @Produce json +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/search [post] +func (b *BaseApi) SearchContainer(c *gin.Context) { + var req dto.PageContainer + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := containerService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Container +// @Summary Load container users +// @Accept json +// @Param request body dto.OperationWithName true "request" +// @Produce json +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/users [post] +func (b *BaseApi) LoadContainerUsers(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + helper.SuccessWithData(c, containerService.LoadUsers(req)) +} + +// @Tags Container +// @Summary List containers +// @Accept json +// @Produce json +// @Success 200 {array} dto.ContainerOptions +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/list [post] +func (b *BaseApi) ListContainer(c *gin.Context) { + helper.SuccessWithData(c, containerService.List()) +} + +// @Tags Container +// @Summary List containers by image +// @Accept json +// @Produce json +// @Success 200 {array} dto.ContainerOptions +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/list/byimage [post] +func (b *BaseApi) ListContainerByImage(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + helper.SuccessWithData(c, containerService.ListByImage(req.Name)) +} + +// @Tags Container +// @Summary Load containers status +// @Accept json +// @Produce json +// @Success 200 {object} dto.ContainerStatus +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/status [get] +func (b *BaseApi) LoadContainerStatus(c *gin.Context) { + data, err := containerService.LoadStatus() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Container Compose +// @Summary Page composes +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/compose/search [post] +func (b *BaseApi) SearchCompose(c *gin.Context) { + var req dto.SearchWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := containerService.PageCompose(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Container Compose +// @Summary Test compose +// @Accept json +// @Param request body dto.ComposeCreate true "request" +// @Success 200 {boolean} isOK +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/compose/test [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"检测 compose [name] 格式","formatEN":"check compose [name]"} +func (b *BaseApi) TestCompose(c *gin.Context) { + var req dto.ComposeCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + isOK, err := containerService.TestCompose(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, isOK) +} + +// @Tags Container Compose +// @Summary Create compose +// @Accept json +// @Param request body dto.ComposeCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/compose [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 compose [name]","formatEN":"create compose [name]"} +func (b *BaseApi) CreateCompose(c *gin.Context) { + var req dto.ComposeCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.CreateCompose(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Compose +// @Summary Operate compose +// @Accept json +// @Param request body dto.ComposeOperation true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/compose/operate [post] +// @x-panel-log {"bodyKeys":["name","operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"compose [operation] [name]","formatEN":"compose [operation] [name]"} +func (b *BaseApi) OperatorCompose(c *gin.Context) { + var req dto.ComposeOperation + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ComposeOperation(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Update container +// @Accept json +// @Param request body dto.ContainerOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/update [post] +// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新容器 [name][image]","formatEN":"update container [name][image]"} +func (b *BaseApi) ContainerUpdate(c *gin.Context) { + var req dto.ContainerOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ContainerUpdate(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Load container info +// @Accept json +// @Param request body dto.OperationWithName true "request" +// @Success 200 {object} dto.ContainerOperate +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/info [post] +func (b *BaseApi) ContainerInfo(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := containerService.ContainerInfo(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Summary Load container limits +// @Success 200 {object} dto.ResourceLimit +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/limit [get] +func (b *BaseApi) LoadResourceLimit(c *gin.Context) { + data, err := containerService.LoadResourceLimit() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Summary Load container stats +// @Success 200 {array} dto.ContainerListStats +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/list/stats [get] +func (b *BaseApi) ContainerListStats(c *gin.Context) { + data, err := containerService.ContainerListStats() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Summary Load container stats size +// @Accept json +// @Param request body dto.OperationWithName true "request" +// @Success 200 {object} dto.ContainerItemStats +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/item/stats [post] +func (b *BaseApi) ContainerItemStats(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := containerService.ContainerItemStats(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Container +// @Summary Create container +// @Accept json +// @Param request body dto.ContainerOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers [post] +// @x-panel-log {"bodyKeys":["name","image"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建容器 [name][image]","formatEN":"create container [name][image]"} +func (b *BaseApi) ContainerCreate(c *gin.Context) { + var req dto.ContainerOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ContainerCreate(req, true); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Create container by command +// @Accept json +// @Param request body dto.ContainerCreateByCommand true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/command [post] +func (b *BaseApi) ContainerCreateByCommand(c *gin.Context) { + var req dto.ContainerCreateByCommand + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ContainerCreateByCommand(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Upgrade container +// @Accept json +// @Param request body dto.ContainerUpgrade true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/upgrade [post] +// @x-panel-log {"bodyKeys":["names","image"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新容器镜像 [names][image]","formatEN":"upgrade container image [names][image]"} +func (b *BaseApi) ContainerUpgrade(c *gin.Context) { + var req dto.ContainerUpgrade + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ContainerUpgrade(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Clean container +// @Accept json +// @Param request body dto.ContainerPrune true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/prune [post] +// @x-panel-log {"bodyKeys":["pruneType"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理容器 [pruneType]","formatEN":"clean container [pruneType]"} +func (b *BaseApi) ContainerPrune(c *gin.Context) { + var req dto.ContainerPrune + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.Prune(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Clean container log +// @Accept json +// @Param request body dto.OperationWithName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/clean/log [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理容器 [name] 日志","formatEN":"clean container [name] logs"} +func (b *BaseApi) CleanContainerLog(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ContainerLogClean(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Clean compose log +// @Accept json +// @Param request body dto.ComposeLogClean true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/compose/clean/log [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理容器编排 [name] 日志","formatEN":"clean compose [name] logs"} +func (b *BaseApi) CleanComposeLog(c *gin.Context) { + var req dto.ComposeLogClean + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ComposeLogClean(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Rename Container +// @Accept json +// @Param request body dto.ContainerRename true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/rename [post] +// @x-panel-log {"bodyKeys":["name","newName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"容器重命名 [name] => [newName]","formatEN":"rename container [name] => [newName]"} +func (b *BaseApi) ContainerRename(c *gin.Context) { + var req dto.ContainerRename + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ContainerRename(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Commit Container +// @Accept json +// @Param request body dto.ContainerCommit true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/commit [post] +func (b *BaseApi) ContainerCommit(c *gin.Context) { + var req dto.ContainerCommit + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ContainerCommit(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Operate Container +// @Accept json +// @Param request body dto.ContainerOperation true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/operate [post] +// @x-panel-log {"bodyKeys":["names","operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"容器 [names] 执行 [operation]","formatEN":"container [operation] [names]"} +func (b *BaseApi) ContainerOperation(c *gin.Context) { + var req dto.ContainerOperation + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ContainerOperation(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Container stats +// @Param id path string true "容器id" +// @Success 200 {object} dto.ContainerStats +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/stats/:id [get] +func (b *BaseApi) ContainerStats(c *gin.Context) { + containerID, ok := c.Params.Get("id") + if !ok { + helper.BadRequest(c, errors.New("error container id in path")) + return + } + + result, err := containerService.ContainerStats(containerID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, result) +} + +// @Tags Container +// @Summary Container inspect +// @Accept json +// @Param request body dto.InspectReq true "request" +// @Success 200 {string} result +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/inspect [post] +func (b *BaseApi) Inspect(c *gin.Context) { + var req dto.InspectReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + result, err := containerService.Inspect(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, result) +} + +func (b *BaseApi) DownloadContainerLogs(c *gin.Context) { + var req dto.ContainerLog + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := containerService.DownloadContainerLogs(req.ContainerType, req.Container, req.Since, strconv.Itoa(int(req.Tail)), c) + if err != nil { + helper.InternalServer(c, err) + } +} + +// @Tags Container Network +// @Summary Page networks +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Produce json +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/network/search [post] +func (b *BaseApi) SearchNetwork(c *gin.Context) { + var req dto.SearchWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := containerService.PageNetwork(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Container Network +// @Summary List networks +// @Accept json +// @Produce json +// @Success 200 {array} dto.Options +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/network [get] +func (b *BaseApi) ListNetwork(c *gin.Context) { + list, err := containerService.ListNetwork() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags Container Network +// @Summary Delete network +// @Accept json +// @Param request body dto.BatchDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/network/del [post] +// @x-panel-log {"bodyKeys":["names"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除容器网络 [names]","formatEN":"delete container network [names]"} +func (b *BaseApi) DeleteNetwork(c *gin.Context) { + var req dto.BatchDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.DeleteNetwork(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Network +// @Summary Create network +// @Accept json +// @Param request body dto.NetworkCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/network [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建容器网络 name","formatEN":"create container network [name]"} +func (b *BaseApi) CreateNetwork(c *gin.Context) { + var req dto.NetworkCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.CreateNetwork(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Volume +// @Summary Page volumes +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Produce json +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/volume/search [post] +func (b *BaseApi) SearchVolume(c *gin.Context) { + var req dto.SearchWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := containerService.PageVolume(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Container Volume +// @Summary List volumes +// @Accept json +// @Produce json +// @Success 200 {array} dto.Options +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/volume [get] +func (b *BaseApi) ListVolume(c *gin.Context) { + list, err := containerService.ListVolume() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags Container Volume +// @Summary Delete volume +// @Accept json +// @Param request body dto.BatchDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/volume/del [post] +// @x-panel-log {"bodyKeys":["names"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除容器存储卷 [names]","formatEN":"delete container volume [names]"} +func (b *BaseApi) DeleteVolume(c *gin.Context) { + var req dto.BatchDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.DeleteVolume(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Volume +// @Summary Create volume +// @Accept json +// @Param request body dto.VolumeCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/volume [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建容器存储卷 [name]","formatEN":"create container volume [name]"} +func (b *BaseApi) CreateVolume(c *gin.Context) { + var req dto.VolumeCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.CreateVolume(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Compose +// @Summary Update compose +// @Accept json +// @Param request body dto.ComposeUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/compose/update [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 compose [name]","formatEN":"update compose information [name]"} +func (b *BaseApi) ComposeUpdate(c *gin.Context) { + var req dto.ComposeUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := containerService.ComposeUpdate(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container +// @Summary Container logs +// @Param container query string false "容器名称" +// @Param since query string false "时间筛选" +// @Param follow query string false "是否追踪" +// @Param tail query string false "显示行号" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/search/log [get] +func (b *BaseApi) ContainerStreamLogs(c *gin.Context) { + c.Header("Content-Type", "text/event-stream") + c.Header("Cache-Control", "no-cache") + c.Header("Connection", "keep-alive") + c.Header("Transfer-Encoding", "chunked") + + since := c.Query("since") + follow := c.Query("follow") == "true" + tail := c.Query("tail") + + container := c.Query("container") + compose := c.Query("compose") + streamLog := dto.StreamLog{ + Compose: compose, + Container: container, + Since: since, + Follow: follow, + Tail: tail, + Type: "container", + } + if compose != "" { + streamLog.Type = "compose" + } + + containerService.StreamLogs(c, streamLog) +} diff --git a/agent/app/api/v2/cronjob.go b/agent/app/api/v2/cronjob.go new file mode 100644 index 0000000..45e2a48 --- /dev/null +++ b/agent/app/api/v2/cronjob.go @@ -0,0 +1,359 @@ +package v2 + +import ( + "net/http" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/gin-gonic/gin" +) + +// @Tags Cronjob +// @Summary Create cronjob +// @Accept json +// @Param request body dto.CronjobOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs [post] +// @x-panel-log {"bodyKeys":["type","name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建计划任务 [type][name]","formatEN":"create cronjob [type][name]"} +func (b *BaseApi) CreateCronjob(c *gin.Context) { + var req dto.CronjobOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := cronjobService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Cronjob +// @Summary Load cronjob info +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/load/info [post] +func (b *BaseApi) LoadCronjobInfo(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := cronjobService.LoadInfo(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Cronjob +// @Summary Export cronjob list +// @Accept json +// @Param request body dto.OperateByIDs true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/export [post] +func (b *BaseApi) ExportCronjob(c *gin.Context) { + var req dto.OperateByIDs + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + content, err := cronjobService.Export(req) + if err != nil { + helper.InternalServer(c, err) + return + } + http.ServeContent(c.Writer, c.Request, "", time.Now(), strings.NewReader(content)) +} + +// @Tags Cronjob +// @Summary Import cronjob list +// @Accept json +// @Param request body dto.CronjobImport true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/import [post] +func (b *BaseApi) ImportCronjob(c *gin.Context) { + var req dto.CronjobImport + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := cronjobService.Import(req.Cronjobs); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Cronjob +// @Summary Load script options +// @Success 200 {array} dto.ScriptOptions +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/script/options [get] +func (b *BaseApi) LoadScriptOptions(c *gin.Context) { + helper.SuccessWithData(c, cronjobService.LoadScriptOptions()) +} + +// @Tags Cronjob +// @Summary Load cronjob spec time +// @Accept json +// @Param request body dto.CronjobSpec true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/next [post] +func (b *BaseApi) LoadNextHandle(c *gin.Context) { + var req dto.CronjobSpec + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := cronjobService.LoadNextHandle(req.Spec) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags Cronjob +// @Summary Page cronjobs +// @Accept json +// @Param request body dto.PageCronjob true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/search [post] +func (b *BaseApi) SearchCronjob(c *gin.Context) { + var req dto.PageCronjob + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := cronjobService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Cronjob +// @Summary Page job records +// @Accept json +// @Param request body dto.SearchRecord true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/search/records [post] +func (b *BaseApi) SearchJobRecords(c *gin.Context) { + var req dto.SearchRecord + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + req.StartTime = req.StartTime.In(loc) + req.EndTime = req.EndTime.In(loc) + + total, list, err := cronjobService.SearchRecords(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Cronjob +// @Summary Load Cronjob record log +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 {string} content +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/records/log [post] +func (b *BaseApi) LoadRecordLog(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + content := cronjobService.LoadRecordLog(req) + helper.SuccessWithData(c, content) +} + +// @Tags Cronjob +// @Summary Clean job records +// @Accept json +// @Param request body dto.CronjobClean true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/records/clean [post] +// @x-panel-log {"bodyKeys":["cronjobID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"cronjobID","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"清空计划任务记录 [name]","formatEN":"clean cronjob [name] records"} +func (b *BaseApi) CleanRecord(c *gin.Context) { + var req dto.CronjobClean + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := cronjobService.CleanRecord(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Cronjob +// @Summary Handle stop job +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/stop [post] +func (b *BaseApi) StopCronJob(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := cronjobService.HandleStop(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Cronjob +// @Summary Delete cronjob +// @Accept json +// @Param request body dto.CronjobBatchDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"cronjobs","output_column":"name","output_value":"names"}],"formatZH":"删除计划任务 [names]","formatEN":"delete cronjob [names]"} +func (b *BaseApi) DeleteCronjob(c *gin.Context) { + var req dto.CronjobBatchDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := cronjobService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Cronjob +// @Summary Update cronjob +// @Accept json +// @Param request body dto.CronjobOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新计划任务 [name]","formatEN":"update cronjob [name]"} +func (b *BaseApi) UpdateCronjob(c *gin.Context) { + var req dto.CronjobOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := cronjobService.Update(req.ID, req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Cronjob +// @Summary Update cronjob group +// @Accept json +// @Param request body dto.ChangeGroup true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/group/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新计划任务分组 [name]","formatEN":"update cronjob group [name]"} +func (b *BaseApi) UpdateCronjobGroup(c *gin.Context) { + var req dto.ChangeGroup + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := cronjobService.UpdateGroup(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Cronjob +// @Summary Update cronjob status +// @Accept json +// @Param request body dto.CronjobUpdateStatus true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/status [post] +// @x-panel-log {"bodyKeys":["id","status"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"修改计划任务 [name] 状态为 [status]","formatEN":"change the status of cronjob [name] to [status]."} +func (b *BaseApi) UpdateCronjobStatus(c *gin.Context) { + var req dto.CronjobUpdateStatus + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := cronjobService.UpdateStatus(req.ID, req.Status); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Cronjob +// @Summary Handle cronjob once +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /cronjobs/handle [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"手动执行计划任务 [name]","formatEN":"manually execute the cronjob [name]"} +func (b *BaseApi) HandleOnce(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := cronjobService.HandleOnce(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/dashboard.go b/agent/app/api/v2/dashboard.go new file mode 100644 index 0000000..f78b7e2 --- /dev/null +++ b/agent/app/api/v2/dashboard.go @@ -0,0 +1,223 @@ +package v2 + +import ( + "errors" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Dashboard +// @Summary Load os info +// @Accept json +// @Success 200 {object} dto.OsInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/base/os [get] +func (b *BaseApi) LoadDashboardOsInfo(c *gin.Context) { + data, err := dashboardService.LoadOsInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Dashboard +// @Summary Load app launcher +// @Accept json +// @Success 200 {array} dto.AppLauncher +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/app/launcher [get] +func (b *BaseApi) LoadAppLauncher(c *gin.Context) { + data, err := dashboardService.LoadAppLauncher(c) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithDataGzipped(c, data) +} + +// @Tags Dashboard +// @Summary Load app launcher options +// @Accept json +// @Param request body dto.SearchByFilter true "request" +// @Success 200 {array} dto.LauncherOption +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/app/launcher/option [post] +func (b *BaseApi) LoadAppLauncherOption(c *gin.Context) { + var req dto.SearchByFilter + if err := helper.CheckBind(&req, c); err != nil { + return + } + data, err := dashboardService.ListLauncherOption(req.Filter) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Dashboard +// @Summary Update app Launcher +// @Accept json +// @Param request body dto.SettingUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/app/launcher/show [post] +// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"首页应用 [key] => 显示:[value]","formatEN":"app launcher [key] => show: [value]"} +func (b *BaseApi) UpdateAppLauncher(c *gin.Context) { + var req dto.SettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := dashboardService.ChangeShow(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Dashboard +// @Summary Load quick jump options +// @Success 200 {array} dto.QuickJump +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/quick/option [get] +func (b *BaseApi) LoadQuickOption(c *gin.Context) { + helper.SuccessWithData(c, dashboardService.LoadQuickOptions()) +} + +// @Tags Dashboard +// @Summary Update quick jump +// @Accept json +// @Param request body dto.ChangeQuicks true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/quick/change [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"切换快速跳转","formatEN":"change quick jump"} +func (b *BaseApi) UpdateQuickJump(c *gin.Context) { + var req dto.ChangeQuicks + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := dashboardService.ChangeQuick(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Dashboard +// @Summary Load dashboard base info +// @Accept json +// @Param ioOption path string true "request" +// @Param netOption path string true "request" +// @Success 200 {object} dto.DashboardBase +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/base/:ioOption/:netOption [get] +func (b *BaseApi) LoadDashboardBaseInfo(c *gin.Context) { + ioOption, ok := c.Params.Get("ioOption") + if !ok { + helper.BadRequest(c, errors.New("error ioOption in path")) + return + } + netOption, ok := c.Params.Get("netOption") + if !ok { + helper.BadRequest(c, errors.New("error ioOption in path")) + return + } + data, err := dashboardService.LoadBaseInfo(ioOption, netOption) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Dashboard +// @Summary Load dashboard current info for node +// @Success 200 {object} dto.NodeCurrent +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/current/node [get] +func (b *BaseApi) LoadCurrentInfoForNode(c *gin.Context) { + data := dashboardService.LoadCurrentInfoForNode() + helper.SuccessWithData(c, data) +} + +// @Tags Dashboard +// @Summary Load dashboard current info +// @Accept json +// @Param ioOption path string true "request" +// @Param netOption path string true "request" +// @Success 200 {object} dto.DashboardCurrent +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/current/:ioOption/:netOption [get] +func (b *BaseApi) LoadDashboardCurrentInfo(c *gin.Context) { + ioOption, ok := c.Params.Get("ioOption") + if !ok { + helper.BadRequest(c, errors.New("error ioOption in path")) + return + } + netOption, ok := c.Params.Get("netOption") + if !ok { + helper.BadRequest(c, errors.New("error netOption in path")) + return + } + + data := dashboardService.LoadCurrentInfo(ioOption, netOption) + helper.SuccessWithData(c, data) +} + +// @Tags Dashboard +// @Summary Load top cpu processes +// @Success 200 {array} dto.Process +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/current/top/cpu [get] +func (b *BaseApi) LoadDashboardTopCPU(c *gin.Context) { + data := dashboardService.LoadTopCPU() + helper.SuccessWithData(c, data) +} + +// @Tags Dashboard +// @Summary Load top memory processes +// @Success 200 {array} dto.Process +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/current/top/mem [get] +func (b *BaseApi) LoadDashboardTopMem(c *gin.Context) { + data := dashboardService.LoadTopMem() + helper.SuccessWithData(c, data) +} + +// @Tags Dashboard +// @Summary System restart +// @Accept json +// @Param operation path string true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /dashboard/system/restart/:operation [post] +func (b *BaseApi) SystemRestart(c *gin.Context) { + operation, ok := c.Params.Get("operation") + if !ok { + helper.BadRequest(c, errors.New("error operation in path")) + return + } + if err := dashboardService.Restart(operation); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/database.go b/agent/app/api/v2/database.go new file mode 100644 index 0000000..82ac835 --- /dev/null +++ b/agent/app/api/v2/database.go @@ -0,0 +1,228 @@ +package v2 + +import ( + "encoding/base64" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Database +// @Summary Create database +// @Accept json +// @Param request body dto.DatabaseCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/db [post] +// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建远程数据库 [name][type]","formatEN":"create database [name][type]"} +func (b *BaseApi) CreateDatabase(c *gin.Context) { + var req dto.DatabaseCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if req.SSL { + key, _ := base64.StdEncoding.DecodeString(req.ClientKey) + req.ClientKey = string(key) + cert, _ := base64.StdEncoding.DecodeString(req.ClientCert) + req.ClientCert = string(cert) + ca, _ := base64.StdEncoding.DecodeString(req.RootCert) + req.RootCert = string(ca) + } + + if err := databaseService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database +// @Summary Check database +// @Accept json +// @Param request body dto.DatabaseCreate true "request" +// @Success 200 {boolean} isOk +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/db/check [post] +// @x-panel-log {"bodyKeys":["name", "type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"检测远程数据库 [name][type] 连接性","formatEN":"check if database [name][type] is connectable"} +func (b *BaseApi) CheckDatabase(c *gin.Context) { + var req dto.DatabaseCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if req.SSL { + clientKey, _ := base64.StdEncoding.DecodeString(req.ClientKey) + req.ClientKey = string(clientKey) + clientCert, _ := base64.StdEncoding.DecodeString(req.ClientCert) + req.ClientCert = string(clientCert) + rootCert, _ := base64.StdEncoding.DecodeString(req.RootCert) + req.RootCert = string(rootCert) + } + + helper.SuccessWithData(c, databaseService.CheckDatabase(req)) +} + +// @Tags Database +// @Summary Page databases +// @Accept json +// @Param request body dto.DatabaseSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/db/search [post] +func (b *BaseApi) SearchDatabase(c *gin.Context) { + var req dto.DatabaseSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := databaseService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Database +// @Summary List databases +// @Success 200 {array} dto.DatabaseOption +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/db/list/:type [get] +func (b *BaseApi) ListDatabase(c *gin.Context) { + dbType, err := helper.GetStrParamByKey(c, "type") + if err != nil { + helper.BadRequest(c, err) + return + } + list, err := databaseService.List(dbType) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Database +// @Summary List databases +// @Success 200 {array} dto.DatabaseItem +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/db/item/:type [get] +func (b *BaseApi) LoadDatabaseItems(c *gin.Context) { + dbType, err := helper.GetStrParamByKey(c, "type") + if err != nil { + helper.BadRequest(c, err) + return + } + list, err := databaseService.LoadItems(dbType) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Database +// @Summary Get databases +// @Success 200 {object} dto.DatabaseInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/db/:name [get] +func (b *BaseApi) GetDatabase(c *gin.Context) { + name, err := helper.GetStrParamByKey(c, "name") + if err != nil { + helper.BadRequest(c, err) + return + } + data, err := databaseService.Get(name) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Database +// @Summary Check before delete remote database +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/db/del/check [post] +func (b *BaseApi) DeleteCheckDatabase(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + apps, err := databaseService.DeleteCheck(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, apps) +} + +// @Tags Database +// @Summary Delete database +// @Accept json +// @Param request body dto.DatabaseDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/db/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"databases","output_column":"name","output_value":"names"}],"formatZH":"删除远程数据库 [names]","formatEN":"delete database [names]"} +func (b *BaseApi) DeleteDatabase(c *gin.Context) { + var req dto.DatabaseDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := databaseService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database +// @Summary Update database +// @Accept json +// @Param request body dto.DatabaseUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/db/update [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新远程数据库 [name]","formatEN":"update database [name]"} +func (b *BaseApi) UpdateDatabase(c *gin.Context) { + var req dto.DatabaseUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if req.SSL { + cKey, _ := base64.StdEncoding.DecodeString(req.ClientKey) + req.ClientKey = string(cKey) + cCert, _ := base64.StdEncoding.DecodeString(req.ClientCert) + req.ClientCert = string(cCert) + ca, _ := base64.StdEncoding.DecodeString(req.RootCert) + req.RootCert = string(ca) + } + + if err := databaseService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/database_common.go b/agent/app/api/v2/database_common.go new file mode 100644 index 0000000..4cc5658 --- /dev/null +++ b/agent/app/api/v2/database_common.go @@ -0,0 +1,74 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Database Common +// @Summary Load base info +// @Accept json +// @Param request body dto.OperationWithNameAndType true "request" +// @Success 200 {object} dto.DBBaseInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/common/info [post] +func (b *BaseApi) LoadDBBaseInfo(c *gin.Context) { + var req dto.OperationWithNameAndType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := dbCommonService.LoadBaseInfo(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Database Common +// @Summary Load Database conf +// @Accept json +// @Param request body dto.OperationWithNameAndType true "request" +// @Success 200 {string} content +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/common/load/file [post] +func (b *BaseApi) LoadDBFile(c *gin.Context) { + var req dto.OperationWithNameAndType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + content, err := dbCommonService.LoadDatabaseFile(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, content) +} + +// @Tags Database Common +// @Summary Update conf by upload file +// @Accept json +// @Param request body dto.DBConfUpdateByFile true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/common/update/conf [post] +// @x-panel-log {"bodyKeys":["type","database"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 [type] 数据库 [database] 配置信息","formatEN":"update the [type] [database] database configuration information"} +func (b *BaseApi) UpdateDBConfByFile(c *gin.Context) { + var req dto.DBConfUpdateByFile + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := dbCommonService.UpdateConfByFile(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} diff --git a/agent/app/api/v2/database_mysql.go b/agent/app/api/v2/database_mysql.go new file mode 100644 index 0000000..2af4e04 --- /dev/null +++ b/agent/app/api/v2/database_mysql.go @@ -0,0 +1,348 @@ +package v2 + +import ( + "context" + "encoding/base64" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Database Mysql +// @Summary Create mysql database +// @Accept json +// @Param request body dto.MysqlDBCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 mysql 数据库 [name]","formatEN":"create mysql database [name]"} +func (b *BaseApi) CreateMysql(c *gin.Context) { + var req dto.MysqlDBCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if len(req.Password) != 0 { + password, err := base64.StdEncoding.DecodeString(req.Password) + if err != nil { + helper.BadRequest(c, err) + return + } + req.Password = string(password) + } + + if _, err := mysqlService.Create(context.Background(), req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database Mysql +// @Summary Bind user of mysql database +// @Accept json +// @Param request body dto.BindUser true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/bind [post] +// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"绑定 mysql 数据库名 [database] [username]","formatEN":"bind mysql database [database] [username]"} +func (b *BaseApi) BindUser(c *gin.Context) { + var req dto.BindUser + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if len(req.Password) != 0 { + password, err := base64.StdEncoding.DecodeString(req.Password) + if err != nil { + helper.BadRequest(c, err) + return + } + req.Password = string(password) + } + + if err := mysqlService.BindUser(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database Mysql +// @Summary Update mysql database description +// @Accept json +// @Param request body dto.UpdateDescription true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/description/update [post] +// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"mysql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the mysql database [name] is modified => [description]"} +func (b *BaseApi) UpdateMysqlDescription(c *gin.Context) { + var req dto.UpdateDescription + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := mysqlService.UpdateDescription(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database Mysql +// @Summary Change mysql password +// @Accept json +// @Param request body dto.ChangeDBInfo true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/change/password [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 密码","formatEN":"Update database [name] password"} +func (b *BaseApi) ChangeMysqlPassword(c *gin.Context) { + var req dto.ChangeDBInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if len(req.Value) != 0 { + value, err := base64.StdEncoding.DecodeString(req.Value) + if err != nil { + helper.BadRequest(c, err) + return + } + req.Value = string(value) + } + + if err := mysqlService.ChangePassword(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database Mysql +// @Summary Change mysql access +// @Accept json +// @Param request body dto.ChangeDBInfo true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/change/access [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 访问权限","formatEN":"Update database [name] access"} +func (b *BaseApi) ChangeMysqlAccess(c *gin.Context) { + var req dto.ChangeDBInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := mysqlService.ChangeAccess(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database Mysql +// @Summary Update mysql variables +// @Accept json +// @Param request body dto.MysqlVariablesUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/variables/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"调整 mysql 数据库性能参数","formatEN":"adjust mysql database performance parameters"} +func (b *BaseApi) UpdateMysqlVariables(c *gin.Context) { + var req dto.MysqlVariablesUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := mysqlService.UpdateVariables(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database Mysql +// @Summary Page mysql databases +// @Accept json +// @Param request body dto.MysqlDBSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/search [post] +func (b *BaseApi) SearchMysql(c *gin.Context) { + var req dto.MysqlDBSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := mysqlService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Database Mysql +// @Summary List mysql database format collation options +// @Accept json +// @Param request body dto.OperationWithName true "request" +// @Success 200 {array} dto.MysqlFormatCollationOption +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/format/options [post] +func (b *BaseApi) ListDBFormatCollationOptions(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + helper.SuccessWithData(c, mysqlService.LoadFormatOption(req)) +} + +// @Tags Database Mysql +// @Summary Load mysql database from remote +// @Accept json +// @Param request body dto.MysqlLoadDB true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/load [post] +func (b *BaseApi) LoadDBFromRemote(c *gin.Context) { + var req dto.MysqlLoadDB + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := mysqlService.LoadFromRemote(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Database Mysql +// @Summary Check before delete mysql database +// @Accept json +// @Param request body dto.MysqlDBDeleteCheck true "request" +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/del/check [post] +func (b *BaseApi) DeleteCheckMysql(c *gin.Context) { + var req dto.MysqlDBDeleteCheck + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + apps, err := mysqlService.DeleteCheck(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, apps) +} + +// @Tags Database Mysql +// @Summary Delete mysql database +// @Accept json +// @Param request body dto.MysqlDBDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_mysqls","output_column":"name","output_value":"name"}],"formatZH":"删除 mysql 数据库 [name]","formatEN":"delete mysql database [name]"} +func (b *BaseApi) DeleteMysql(c *gin.Context) { + var req dto.MysqlDBDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + tx, ctx := helper.GetTxAndContext() + if err := mysqlService.Delete(ctx, req); err != nil { + helper.InternalServer(c, err) + tx.Rollback() + return + } + tx.Commit() + helper.Success(c) +} + +// @Tags Database Mysql +// @Summary Load mysql remote access +// @Accept json +// @Param request body dto.OperationWithNameAndType true "request" +// @Success 200 {boolean} isRemote +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/remote [post] +func (b *BaseApi) LoadRemoteAccess(c *gin.Context) { + var req dto.OperationWithNameAndType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + isRemote, err := mysqlService.LoadRemoteAccess(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, isRemote) +} + +// @Tags Database Mysql +// @Summary Load mysql status info +// @Accept json +// @Param request body dto.OperationWithNameAndType true "request" +// @Success 200 {object} dto.MysqlStatus +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/status [post] +func (b *BaseApi) LoadStatus(c *gin.Context) { + var req dto.OperationWithNameAndType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := mysqlService.LoadStatus(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Database Mysql +// @Summary Load mysql variables info +// @Accept json +// @Param request body dto.OperationWithNameAndType true "request" +// @Success 200 {object} dto.MysqlVariables +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/variables [post] +func (b *BaseApi) LoadVariables(c *gin.Context) { + var req dto.OperationWithNameAndType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := mysqlService.LoadVariables(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} diff --git a/agent/app/api/v2/database_postgresql.go b/agent/app/api/v2/database_postgresql.go new file mode 100644 index 0000000..775ac1e --- /dev/null +++ b/agent/app/api/v2/database_postgresql.go @@ -0,0 +1,233 @@ +package v2 + +import ( + "context" + "encoding/base64" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Database PostgreSQL +// @Summary Create postgresql database +// @Accept json +// @Param request body dto.PostgresqlDBCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/pg [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 postgresql 数据库 [name]","formatEN":"create postgresql database [name]"} +func (b *BaseApi) CreatePostgresql(c *gin.Context) { + var req dto.PostgresqlDBCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if len(req.Password) != 0 { + password, err := base64.StdEncoding.DecodeString(req.Password) + if err != nil { + helper.BadRequest(c, err) + return + } + req.Password = string(password) + } + + if _, err := postgresqlService.Create(context.Background(), req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database PostgreSQL +// @Summary Bind postgresql user +// @Accept json +// @Param request body dto.PostgresqlBindUser true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/pg/bind [post] +// @x-panel-log {"bodyKeys":["name", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"绑定 postgresql 数据库 [name] 用户 [username]","formatEN":"bind postgresql database [name] user [username]"} +func (b *BaseApi) BindPostgresqlUser(c *gin.Context) { + var req dto.PostgresqlBindUser + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := postgresqlService.BindUser(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database PostgreSQL +// @Summary Update postgresql database description +// @Accept json +// @Param request body dto.UpdateDescription true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/pg/description [post] +// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_postgresqls","output_column":"name","output_value":"name"}],"formatZH":"postgresql 数据库 [name] 描述信息修改 [description]","formatEN":"The description of the postgresql database [name] is modified => [description]"} +func (b *BaseApi) UpdatePostgresqlDescription(c *gin.Context) { + var req dto.UpdateDescription + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := postgresqlService.UpdateDescription(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database PostgreSQL +// @Summary Change postgresql privileges +// @Accept json +// @Param request body dto.ChangeDBInfo true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/pg/privileges [post] +// @x-panel-log {"bodyKeys":["database", "username"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新数据库 [database] 用户 [username] 权限","formatEN":"Update [user] privileges of database [database]"} +func (b *BaseApi) ChangePostgresqlPrivileges(c *gin.Context) { + var req dto.PostgresqlPrivileges + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := postgresqlService.ChangePrivileges(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database PostgreSQL +// @Summary Change postgresql password +// @Accept json +// @Param request body dto.ChangeDBInfo true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/pg/password [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_postgresqls","output_column":"name","output_value":"name"}],"formatZH":"更新数据库 [name] 密码","formatEN":"Update database [name] password"} +func (b *BaseApi) ChangePostgresqlPassword(c *gin.Context) { + var req dto.ChangeDBInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if len(req.Value) != 0 { + value, err := base64.StdEncoding.DecodeString(req.Value) + if err != nil { + helper.BadRequest(c, err) + return + } + req.Value = string(value) + } + + if err := postgresqlService.ChangePassword(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database PostgreSQL +// @Summary Page postgresql databases +// @Accept json +// @Param request body dto.PostgresqlDBSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/pg/search [post] +func (b *BaseApi) SearchPostgresql(c *gin.Context) { + var req dto.PostgresqlDBSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := postgresqlService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Database PostgreSQL +// @Summary Load postgresql database from remote +// @Accept json +// @Param request body dto.PostgresqlLoadDB true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/pg/:database/load [post] +func (b *BaseApi) LoadPostgresqlDBFromRemote(c *gin.Context) { + database, err := helper.GetStrParamByKey(c, "database") + if err != nil { + helper.BadRequest(c, err) + return + } + + if err := postgresqlService.LoadFromRemote(database); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Database PostgreSQL +// @Summary Check before delete postgresql database +// @Accept json +// @Param request body dto.PostgresqlDBDeleteCheck true "request" +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/pg/del/check [post] +func (b *BaseApi) DeleteCheckPostgresql(c *gin.Context) { + var req dto.PostgresqlDBDeleteCheck + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + apps, err := postgresqlService.DeleteCheck(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, apps) +} + +// @Tags Database PostgreSQL +// @Summary Delete postgresql database +// @Accept json +// @Param request body dto.PostgresqlDBDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/pg/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"database_postgresqls","output_column":"name","output_value":"name"}],"formatZH":"删除 postgresql 数据库 [name]","formatEN":"delete postgresql database [name]"} +func (b *BaseApi) DeletePostgresql(c *gin.Context) { + var req dto.PostgresqlDBDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + tx, ctx := helper.GetTxAndContext() + if err := postgresqlService.Delete(ctx, req); err != nil { + helper.InternalServer(c, err) + tx.Rollback() + return + } + tx.Commit() + helper.Success(c) +} diff --git a/agent/app/api/v2/database_redis.go b/agent/app/api/v2/database_redis.go new file mode 100644 index 0000000..83033c8 --- /dev/null +++ b/agent/app/api/v2/database_redis.go @@ -0,0 +1,169 @@ +package v2 + +import ( + "encoding/base64" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Database Redis +// @Summary Load redis status info +// @Accept json +// @Param request body dto.LoadRedisStatus true "request" +// @Success 200 {object} dto.RedisStatus +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/redis/status [post] +func (b *BaseApi) LoadRedisStatus(c *gin.Context) { + var req dto.LoadRedisStatus + if err := helper.CheckBind(&req, c); err != nil { + return + } + data, err := redisService.LoadStatus(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Database Redis +// @Summary Load redis conf +// @Accept json +// @Param request body dto.LoadRedisStatus true "request" +// @Success 200 {object} dto.RedisConf +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/redis/conf [post] +func (b *BaseApi) LoadRedisConf(c *gin.Context) { + var req dto.LoadRedisStatus + if err := helper.CheckBind(&req, c); err != nil { + return + } + data, err := redisService.LoadConf(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Database Redis +// @Summary Load redis persistence conf +// @Accept json +// @Param request body dto.LoadRedisStatus true "request" +// @Success 200 {object} dto.RedisPersistence +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/redis/persistence/conf [post] +func (b *BaseApi) LoadPersistenceConf(c *gin.Context) { + var req dto.LoadRedisStatus + if err := helper.CheckBind(&req, c); err != nil { + return + } + data, err := redisService.LoadPersistenceConf(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +func (b *BaseApi) CheckHasCli(c *gin.Context) { + helper.SuccessWithData(c, redisService.CheckHasCli()) +} + +// @Tags Database Redis +// @Summary Install redis-cli +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/redis/install/cli [post] +func (b *BaseApi) InstallCli(c *gin.Context) { + if err := redisService.InstallCli(); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Database Redis +// @Summary Update redis conf +// @Accept json +// @Param request body dto.RedisConfUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/redis/conf/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 redis 数据库配置信息","formatEN":"update the redis database configuration information"} +func (b *BaseApi) UpdateRedisConf(c *gin.Context) { + var req dto.RedisConfUpdate + if err := helper.CheckBind(&req, c); err != nil { + return + } + + if err := redisService.UpdateConf(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database Redis +// @Summary Change redis password +// @Accept json +// @Param request body dto.ChangeRedisPass true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/redis/password [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 redis 数据库密码","formatEN":"change the password of the redis database"} +func (b *BaseApi) ChangeRedisPassword(c *gin.Context) { + var req dto.ChangeRedisPass + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if len(req.Value) != 0 { + value, err := base64.StdEncoding.DecodeString(req.Value) + if err != nil { + helper.BadRequest(c, err) + return + } + req.Value = string(value) + } + + if err := redisService.ChangePassword(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Database Redis +// @Summary Update redis persistence conf +// @Accept json +// @Param request body dto.RedisConfPersistenceUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /databases/redis/persistence/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"redis 数据库持久化配置更新","formatEN":"redis database persistence configuration update"} +func (b *BaseApi) UpdateRedisPersistenceConf(c *gin.Context) { + var req dto.RedisConfPersistenceUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := redisService.UpdatePersistenceConf(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/device.go b/agent/app/api/v2/device.go new file mode 100644 index 0000000..f5c85fd --- /dev/null +++ b/agent/app/api/v2/device.go @@ -0,0 +1,250 @@ +package v2 + +import ( + "encoding/base64" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Device +// @Summary Load device base info +// @Success 200 {object} dto.DeviceBaseInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/base [post] +func (b *BaseApi) LoadDeviceBaseInfo(c *gin.Context) { + data, err := deviceService.LoadBaseInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Device +// @Summary list time zone options +// @Accept json +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/zone/options [get] +func (b *BaseApi) LoadTimeOption(c *gin.Context) { + list, err := deviceService.LoadTimeZone() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Device +// @Summary load conf +// @Accept json +// @Param request body dto.OperationWithName true "request" +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/conf [post] +func (b *BaseApi) LoadDeviceConf(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := deviceService.LoadConf(req.Name) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Device +// @Summary Update device conf by file +// @Accept json +// @Param request body dto.UpdateByNameAndFile true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/update/byconf [post] +func (b *BaseApi) UpdateDeviceByFile(c *gin.Context) { + var req dto.UpdateByNameAndFile + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := deviceService.UpdateByConf(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Device +// @Summary Load user list +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/users [get] +func (b *BaseApi) LoadUsers(c *gin.Context) { + users, err := deviceService.LoadUsers() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, users) +} + +// @Tags Device +// @Summary Update device +// @Accept json +// @Param request body dto.SettingUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/update/conf [post] +// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改主机参数 [key] => [value]","formatEN":"update device conf [key] => [value]"} +func (b *BaseApi) UpdateDeviceConf(c *gin.Context) { + var req dto.SettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := deviceService.Update(req.Key, req.Value); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Device +// @Summary Update device hosts +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/update/host [post] +// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改主机 Host [key] => [value]","formatEN":"update device host [key] => [value]"} +func (b *BaseApi) UpdateDeviceHost(c *gin.Context) { + var req []dto.HostHelper + if err := helper.CheckBind(&req, c); err != nil { + return + } + + if err := deviceService.UpdateHosts(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Device +// @Summary Update device passwd +// @Accept json +// @Param request body dto.ChangePasswd true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/update/passwd [post] +func (b *BaseApi) UpdateDevicePasswd(c *gin.Context) { + var req dto.ChangePasswd + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if len(req.Passwd) != 0 { + password, err := base64.StdEncoding.DecodeString(req.Passwd) + if err != nil { + helper.BadRequest(c, err) + return + } + req.Passwd = string(password) + } + if err := deviceService.UpdatePasswd(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Device +// @Summary Update device swap +// @Accept json +// @Param request body dto.SwapHelper true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/update/swap [post] +// @x-panel-log {"bodyKeys":["operate","path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 主机 swap [path]","formatEN":"[operate] device swap [path]"} +func (b *BaseApi) UpdateDeviceSwap(c *gin.Context) { + var req dto.SwapHelper + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := deviceService.UpdateSwap(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Device +// @Summary Check device DNS conf +// @Accept json +// @Param request body dto.SettingUpdate true "request" +// @Success 200 {boolean} data +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/device/check/dns [post] +func (b *BaseApi) CheckDNS(c *gin.Context) { + var req dto.SettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := deviceService.CheckDNS(req.Key, req.Value) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Device +// @Summary Scan system +// @Success 200 {object} dto.CleanData +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/scan [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"扫描系统垃圾文件","formatEN":"scan System Junk Files"} +func (b *BaseApi) ScanSystem(c *gin.Context) { + helper.SuccessWithData(c, deviceService.Scan()) +} + +// @Tags Device +// @Summary Clean system +// @Accept json +// @Param request body []dto.Clean true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/clean [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理系统垃圾文件","formatEN":"Clean system junk files"} +func (b *BaseApi) SystemClean(c *gin.Context) { + var req []dto.Clean + if err := helper.CheckBind(&req, c); err != nil { + return + } + + deviceService.Clean(req) + + helper.Success(c) +} diff --git a/agent/app/api/v2/disk.go b/agent/app/api/v2/disk.go new file mode 100644 index 0000000..2bddd2c --- /dev/null +++ b/agent/app/api/v2/disk.go @@ -0,0 +1,99 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags Disk Management +// @Summary Get complete disk information +// @Description Get information about all disks including partitioned and unpartitioned disks +// @Produce json +// @Success 200 {object} response.CompleteDiskInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/disks [get] +func (b *BaseApi) GetCompleteDiskInfo(c *gin.Context) { + diskInfo, err := diskService.GetCompleteDiskInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, diskInfo) +} + +// @Tags Disk Management +// @Summary Partition disk +// @Description Create partition and format disk with specified filesystem +// @Accept json +// @Param request body request.DiskPartitionRequest true "partition request" +// @Success 200 {string} string "Partition created successfully" +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/disks/partition [post] +// @x-panel-log {"bodyKeys":["device", "filesystem", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"对磁盘 [device] 进行分区,文件系统 [filesystem],挂载点 [mountPoint]","formatEN":"Partition disk [device] with filesystem [filesystem], mount point [mountPoint]"} +func (b *BaseApi) PartitionDisk(c *gin.Context) { + var req request.DiskPartitionRequest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + result, err := diskService.PartitionDisk(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, result) +} + +// @Tags Disk Management +// @Summary Mount disk +// @Description Mount partition to specified mount point +// @Accept json +// @Param request body request.DiskMountRequest true "mount request" +// @Success 200 {string} string "Disk mounted successfully" +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/disks/mount [post] +// @x-panel-log {"bodyKeys":["device", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"挂载磁盘 [device] 到 [mountPoint]","formatEN":"Mount disk [device] to [mountPoint]"} +func (b *BaseApi) MountDisk(c *gin.Context) { + var req request.DiskMountRequest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + err := diskService.MountDisk(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Disk Management +// @Summary Unmount disk +// @Description Unmount partition from mount point +// @Accept json +// @Param request body request.DiskUnmountRequest true "unmount request" +// @Success 200 {string} string "Disk unmounted successfully" +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/disks/unmount [post] +// @x-panel-log {"bodyKeys":["device", "mountPoint"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"卸载磁盘 [device] 从 [mountPoint]","formatEN":"Unmount disk [device] from [mountPoint]"} +func (b *BaseApi) UnmountDisk(c *gin.Context) { + var req request.DiskUnmountRequest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + err := diskService.UnmountDisk(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} diff --git a/agent/app/api/v2/docker.go b/agent/app/api/v2/docker.go new file mode 100644 index 0000000..622dcbb --- /dev/null +++ b/agent/app/api/v2/docker.go @@ -0,0 +1,173 @@ +package v2 + +import ( + "os" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/gin-gonic/gin" +) + +// @Tags Container Docker +// @Summary Load docker status +// @Produce json +// @Success 200 {string} dto.DockerStatus +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/docker/status [get] +func (b *BaseApi) LoadDockerStatus(c *gin.Context) { + status := dockerService.LoadDockerStatus() + helper.SuccessWithData(c, status) +} + +// @Tags Container Docker +// @Summary Load docker daemon.json +// @Produce json +// @Success 200 {object} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/daemonjson/file [get] +func (b *BaseApi) LoadDaemonJsonFile(c *gin.Context) { + if _, err := os.Stat(constant.DaemonJsonPath); err != nil { + helper.SuccessWithData(c, "daemon.json is not find in path") + return + } + content, err := os.ReadFile(constant.DaemonJsonPath) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, string(content)) +} + +// @Tags Container Docker +// @Summary Load docker daemon.json +// @Produce json +// @Success 200 {object} dto.DaemonJsonConf +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/daemonjson [get] +func (b *BaseApi) LoadDaemonJson(c *gin.Context) { + conf, err := dockerService.LoadDockerConf() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, conf) +} + +// @Tags Container Docker +// @Summary Update docker daemon.json +// @Accept json +// @Param request body dto.SettingUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/daemonjson/update [post] +// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新配置 [key]","formatEN":"Updated configuration [key]"} +func (b *BaseApi) UpdateDaemonJson(c *gin.Context) { + var req dto.SettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := dockerService.UpdateConf(req, true); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Docker +// @Summary Update docker daemon.json log option +// @Accept json +// @Param request body dto.LogOption true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/logoption/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新日志配置","formatEN":"Updated the log option"} +func (b *BaseApi) UpdateLogOption(c *gin.Context) { + var req dto.LogOption + if err := helper.CheckBind(&req, c); err != nil { + return + } + + if err := dockerService.UpdateLogOption(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Docker +// @Summary Update docker daemon.json ipv6 option +// @Accept json +// @Param request body dto.LogOption true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/ipv6option/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 ipv6 配置","formatEN":"Updated the ipv6 option"} +func (b *BaseApi) UpdateIpv6Option(c *gin.Context) { + var req dto.Ipv6Option + if err := helper.CheckBind(&req, c); err != nil { + return + } + + if err := dockerService.UpdateIpv6Option(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Docker +// @Summary Update docker daemon.json by upload file +// @Accept json +// @Param request body dto.DaemonJsonUpdateByFile true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/daemonjson/update/byfile [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新配置文件","formatEN":"Updated configuration file"} +func (b *BaseApi) UpdateDaemonJsonByFile(c *gin.Context) { + var req dto.DaemonJsonUpdateByFile + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := dockerService.UpdateConfByFile(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Docker +// @Summary Operate docker +// @Accept json +// @Param request body dto.DockerOperation true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/docker/operate [post] +// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"docker 服务 [operation]","formatEN":"[operation] docker service"} +func (b *BaseApi) OperateDocker(c *gin.Context) { + var req dto.DockerOperation + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := dockerService.OperateDocker(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} diff --git a/agent/app/api/v2/entry.go b/agent/app/api/v2/entry.go new file mode 100644 index 0000000..ef1ee05 --- /dev/null +++ b/agent/app/api/v2/entry.go @@ -0,0 +1,79 @@ +package v2 + +import "github.com/1Panel-dev/1Panel/agent/app/service" + +type ApiGroup struct { + BaseApi +} + +var ApiGroupApp = new(ApiGroup) + +type BaseApi struct{} + +var ( + dashboardService = service.NewIDashboardService() + + appService = service.NewIAppService() + appInstallService = service.NewIAppInstalledService() + appIgnoreUpgradeService = service.NewIAppIgnoreUpgradeService() + + aiToolService = service.NewIAIToolService() + mcpServerService = service.NewIMcpServerService() + tensorrtLLMService = service.NewITensorRTLLMService() + + containerService = service.NewIContainerService() + composeTemplateService = service.NewIComposeTemplateService() + imageRepoService = service.NewIImageRepoService() + imageService = service.NewIImageService() + dockerService = service.NewIDockerService() + + dbCommonService = service.NewIDBCommonService() + mysqlService = service.NewIMysqlService() + postgresqlService = service.NewIPostgresqlService() + databaseService = service.NewIDatabaseService() + redisService = service.NewIRedisService() + + cronjobService = service.NewICronjobService() + + fileService = service.NewIFileService() + sshService = service.NewISSHService() + firewallService = service.NewIFirewallService() + iptablesService = service.NewIIptablesService() + monitorService = service.NewIMonitorService() + systemService = service.NewISystemService() + + deviceService = service.NewIDeviceService() + fail2banService = service.NewIFail2BanService() + ftpService = service.NewIFtpService() + clamService = service.NewIClamService() + + settingService = service.NewISettingService() + backupService = service.NewIBackupService() + backupRecordService = service.NewIBackupRecordService() + + websiteService = service.NewIWebsiteService() + websiteDnsAccountService = service.NewIWebsiteDnsAccountService() + websiteSSLService = service.NewIWebsiteSSLService() + websiteAcmeAccountService = service.NewIWebsiteAcmeAccountService() + + nginxService = service.NewINginxService() + + logService = service.NewILogService() + snapshotService = service.NewISnapshotService() + + runtimeService = service.NewRuntimeService() + processService = service.NewIProcessService() + phpExtensionsService = service.NewIPHPExtensionsService() + + hostToolService = service.NewIHostToolService() + + recycleBinService = service.NewIRecycleBinService() + favoriteService = service.NewIFavoriteService() + + websiteCAService = service.NewIWebsiteCAService() + taskService = service.NewITaskService() + groupService = service.NewIGroupService() + alertService = service.NewIAlertService() + + diskService = service.NewIDiskService() +) diff --git a/agent/app/api/v2/fail2ban.go b/agent/app/api/v2/fail2ban.go new file mode 100644 index 0000000..30b6fd8 --- /dev/null +++ b/agent/app/api/v2/fail2ban.go @@ -0,0 +1,154 @@ +package v2 + +import ( + "os" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Fail2ban +// @Summary Load fail2ban base info +// @Success 200 {object} dto.Fail2BanBaseInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/fail2ban/base [get] +func (b *BaseApi) LoadFail2BanBaseInfo(c *gin.Context) { + data, err := fail2banService.LoadBaseInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Fail2ban +// @Summary Page fail2ban ip list +// @Accept json +// @Param request body dto.Fail2BanSearch true "request" +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/fail2ban/search [post] +func (b *BaseApi) SearchFail2Ban(c *gin.Context) { + var req dto.Fail2BanSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := fail2banService.Search(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Fail2ban +// @Summary Operate fail2ban +// @Accept json +// @Param request body dto.Operate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/fail2ban/operate [post] +// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] Fail2ban","formatEN":"[operation] Fail2ban"} +func (b *BaseApi) OperateFail2Ban(c *gin.Context) { + var req dto.Operate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := fail2banService.Operate(req.Operation); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Fail2ban +// @Summary Operate sshd of fail2ban +// @Accept json +// @Param request body dto.Fail2BanSet true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/fail2ban/operate/sshd [post] +func (b *BaseApi) OperateSSHD(c *gin.Context) { + var req dto.Fail2BanSet + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := fail2banService.OperateSSHD(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Fail2ban +// @Summary Update fail2ban conf +// @Accept json +// @Param request body dto.Fail2BanUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/fail2ban/update [post] +// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 Fail2ban 配置 [key] => [value]","formatEN":"update fail2ban conf [key] => [value]"} +func (b *BaseApi) UpdateFail2BanConf(c *gin.Context) { + var req dto.Fail2BanUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := fail2banService.UpdateConf(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Fail2ban +// @Summary Load fail2ban conf +// @Accept json +// @Success 200 {string} file +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/fail2ban/load/conf [get] +func (b *BaseApi) LoadFail2BanConf(c *gin.Context) { + path := "/etc/fail2ban/jail.local" + file, err := os.ReadFile(path) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, string(file)) +} + +// @Tags Fail2ban +// @Summary Update fail2ban conf by file +// @Accept json +// @Param request body dto.UpdateByFile true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/fail2ban/update/byconf [post] +func (b *BaseApi) UpdateFail2BanConfByFile(c *gin.Context) { + var req dto.UpdateByFile + if err := helper.CheckBind(&req, c); err != nil { + return + } + if err := fail2banService.UpdateConfByFile(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} diff --git a/agent/app/api/v2/favorite.go b/agent/app/api/v2/favorite.go new file mode 100644 index 0000000..63f0992 --- /dev/null +++ b/agent/app/api/v2/favorite.go @@ -0,0 +1,75 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags File +// @Summary List favorites +// @Accept json +// @Param request body dto.PageInfo true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/favorite/search [post] +func (b *BaseApi) SearchFavorite(c *gin.Context) { + var req dto.PageInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, list, err := favoriteService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: list, + }) +} + +// @Tags File +// @Summary Create favorite +// @Accept json +// @Param request body request.FavoriteCreate true "request" +// @Success 200 {object} model.Favorite +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/favorite [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"收藏文件/文件夹 [path]","formatEN":"收藏文件/文件夹 [path]"} +func (b *BaseApi) CreateFavorite(c *gin.Context) { + var req request.FavoriteCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + favorite, err := favoriteService.Create(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, favorite) +} + +// @Tags File +// @Summary Delete favorite +// @Accept json +// @Param request body request.FavoriteDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/favorite/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"favorites","output_column":"path","output_value":"path"}],"formatZH":"删除收藏 [path]","formatEN":"delete avorite [path]"} +func (b *BaseApi) DeleteFavorite(c *gin.Context) { + var req request.FavoriteDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := favoriteService.Delete(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/file.go b/agent/app/api/v2/file.go new file mode 100644 index 0000000..277630c --- /dev/null +++ b/agent/app/api/v2/file.go @@ -0,0 +1,1017 @@ +package v2 + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "syscall" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/files" + websocket2 "github.com/1Panel-dev/1Panel/agent/utils/websocket" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" +) + +// @Tags File +// @Summary List files +// @Accept json +// @Param request body request.FileOption true "request" +// @Success 200 {object} response.FileInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/search [post] +func (b *BaseApi) ListFiles(c *gin.Context) { + var req request.FileOption + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + fileList, err := fileService.GetFileList(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, fileList) +} + +// @Tags File +// @Summary Page file +// @Accept json +// @Param request body request.SearchUploadWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/upload/search [post] +func (b *BaseApi) SearchUploadWithPage(c *gin.Context) { + var req request.SearchUploadWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, files, err := fileService.SearchUploadWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: files, + Total: total, + }) +} + +// @Tags File +// @Summary Load files tree +// @Accept json +// @Param request body request.FileOption true "request" +// @Success 200 {array} response.FileTree +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/tree [post] +func (b *BaseApi) GetFileTree(c *gin.Context) { + var req request.FileOption + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + tree, err := fileService.GetFileTree(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithDataGzipped(c, tree) +} + +// @Tags File +// @Summary Create file +// @Accept json +// @Param request body request.FileCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建文件/文件夹 [path]","formatEN":"Create dir or file [path]"} +func (b *BaseApi) CreateFile(c *gin.Context) { + var req request.FileCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := fileService.Create(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Delete file +// @Accept json +// @Param request body request.FileDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/del [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除文件/文件夹 [path]","formatEN":"Delete dir or file [path]"} +func (b *BaseApi) DeleteFile(c *gin.Context) { + var req request.FileDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := fileService.Delete(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Batch delete file +// @Accept json +// @Param request body request.FileBatchDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/batch/del [post] +// @x-panel-log {"bodyKeys":["paths"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"批量删除文件/文件夹 [paths]","formatEN":"Batch delete dir or file [paths]"} +func (b *BaseApi) BatchDeleteFile(c *gin.Context) { + var req request.FileBatchDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := fileService.BatchDelete(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Change file mode +// @Accept json +// @Param request body request.FileCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/mode [post] +// @x-panel-log {"bodyKeys":["path","mode"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改权限 [paths] => [mode]","formatEN":"Change mode [paths] => [mode]"} +func (b *BaseApi) ChangeFileMode(c *gin.Context) { + var req request.FileCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := fileService.ChangeMode(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Change file owner +// @Accept json +// @Param request body request.FileRoleUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/owner [post] +// @x-panel-log {"bodyKeys":["path","user","group"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改用户/组 [paths] => [user]/[group]","formatEN":"Change owner [paths] => [user]/[group]"} +func (b *BaseApi) ChangeFileOwner(c *gin.Context) { + var req request.FileRoleUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := fileService.ChangeOwner(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Compress file +// @Accept json +// @Param request body request.FileCompress true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/compress [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"压缩文件 [name]","formatEN":"Compress file [name]"} +func (b *BaseApi) CompressFile(c *gin.Context) { + var req request.FileCompress + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := fileService.Compress(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Decompress file +// @Accept json +// @Param request body request.FileDeCompress true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/decompress [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"解压 [path]","formatEN":"Decompress file [path]"} +func (b *BaseApi) DeCompressFile(c *gin.Context) { + var req request.FileDeCompress + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := fileService.DeCompress(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Load file content +// @Accept json +// @Param request body request.FileContentReq true "request" +// @Success 200 {object} response.FileInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/content [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"获取文件内容 [path]","formatEN":"Load file content [path]"} +func (b *BaseApi) GetContent(c *gin.Context) { + var req request.FileContentReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + info, err := fileService.GetContent(req) + if err != nil { + helper.InternalServer(c, err) + return + } + if info.Size > 2*1024 && info.Size < 5*1024*1024 { + helper.SuccessWithDataGzipped(c, info) + } else { + helper.SuccessWithData(c, info) + } +} + +// @Tags File +// @Summary Preview file content +// @Accept json +// @Param request body request.FileContentReq true "request" +// @Success 200 {object} response.FileInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/preview [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"预览文件内容 [path]","formatEN":"Preview file content [path]"} +func (b *BaseApi) PreviewContent(c *gin.Context) { + var req request.FileContentReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + info, err := fileService.GetPreviewContent(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, info) +} + +// @Tags File +// @Summary Update file content +// @Accept json +// @Param request body request.FileEdit true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/save [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新文件内容 [path]","formatEN":"Update file content [path]"} +func (b *BaseApi) SaveContent(c *gin.Context) { + var req request.FileEdit + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := fileService.SaveContent(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Upload file +// @Param file formData file true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/upload [post] +// @x-panel-log {"bodyKeys":["path", "file"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传文件 [path]/[file]","formatEN":"Upload file [path]/[file]"} +func (b *BaseApi) UploadFiles(c *gin.Context) { + form, err := c.MultipartForm() + if err != nil { + helper.BadRequest(c, err) + return + } + uploadFiles := form.File["file"] + paths := form.Value["path"] + + overwrite := true + if ow, ok := form.Value["overwrite"]; ok { + if len(ow) != 0 { + parseBool, _ := strconv.ParseBool(ow[0]) + overwrite = parseBool + } + } + + if len(paths) == 0 || !strings.Contains(paths[0], "/") { + helper.BadRequest(c, errors.New("error paths in request")) + return + } + dir := path.Dir(paths[0]) + + _, err = os.Stat(dir) + if err != nil && os.IsNotExist(err) { + mode, err := files.GetParentMode(dir) + if err != nil { + helper.InternalServer(c, err) + return + } + if err = os.MkdirAll(dir, mode); err != nil { + helper.BadRequest(c, fmt.Errorf("mkdir %s failed, err: %v", dir, err)) + return + } + } + info, err := os.Stat(dir) + if err != nil { + helper.InternalServer(c, err) + return + } + mode := info.Mode() + + fileOp := files.NewFileOp() + stat, ok := info.Sys().(*syscall.Stat_t) + uid, gid := -1, -1 + if ok { + uid, gid = int(stat.Uid), int(stat.Gid) + } + success := 0 + failures := make(buserr.MultiErr) + for _, file := range uploadFiles { + dstFilename := path.Join(paths[0], file.Filename) + dstDir := path.Dir(dstFilename) + if !fileOp.Stat(dstDir) { + if err = fileOp.CreateDir(dstDir, mode); err != nil { + e := fmt.Errorf("create dir [%s] failed, err: %v", path.Dir(dstFilename), err) + failures[file.Filename] = e + global.LOG.Error(e) + continue + } + _ = os.Chown(dstDir, uid, gid) + } + tmpFilename := dstFilename + ".tmp" + if err := c.SaveUploadedFile(file, tmpFilename); err != nil { + _ = os.Remove(tmpFilename) + e := fmt.Errorf("upload [%s] file failed, err: %v", file.Filename, err) + failures[file.Filename] = e + global.LOG.Error(e) + continue + } + dstInfo, statErr := os.Stat(dstFilename) + if overwrite { + _ = os.Remove(dstFilename) + } + + err = os.Rename(tmpFilename, dstFilename) + if err != nil { + _ = os.Remove(tmpFilename) + e := fmt.Errorf("upload [%s] file failed, err: %v", file.Filename, err) + failures[file.Filename] = e + global.LOG.Error(e) + continue + } + if statErr == nil { + _ = os.Chmod(dstFilename, dstInfo.Mode()) + } else { + _ = os.Chmod(dstFilename, mode) + } + if uid != -1 && gid != -1 { + _ = os.Chown(dstFilename, uid, gid) + } + success++ + } + if success == 0 { + helper.InternalServer(c, failures) + } else { + helper.SuccessWithMsg(c, fmt.Sprintf("%d files upload success", success)) + } +} + +// @Tags File +// @Summary Check file exist +// @Accept json +// @Param request body request.FilePathCheck true "request" +// @Success 200 {boolean} isOk +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/check [post] +func (b *BaseApi) CheckFile(c *gin.Context) { + var req request.FilePathCheck + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + fileOp := files.NewFileOp() + if fileOp.Stat(req.Path) { + helper.SuccessWithData(c, true) + return + } + if req.WithInit { + if err := fileOp.CreateDir(req.Path, 0644); err != nil { + helper.SuccessWithData(c, false) + return + } + helper.SuccessWithData(c, true) + return + } + helper.SuccessWithData(c, false) +} + +// @Tags File +// @Summary Batch check file exist +// @Accept json +// @Param request body request.FilePathsCheck true "request" +// @Success 200 {array} response.ExistFileInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/batch/check [post] +func (b *BaseApi) BatchCheckFiles(c *gin.Context) { + var req request.FilePathsCheck + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + fileList := fileService.BatchCheckFiles(req) + helper.SuccessWithData(c, fileList) +} + +// @Tags File +// @Summary Change file name +// @Accept json +// @Param request body request.FileRename true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/rename [post] +// @x-panel-log {"bodyKeys":["oldName","newName"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"重命名 [oldName] => [newName]","formatEN":"Rename [oldName] => [newName]"} +func (b *BaseApi) ChangeFileName(c *gin.Context) { + var req request.FileRename + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := fileService.ChangeName(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Wget file +// @Accept json +// @Param request body request.FileWget true "request" +// @Success 200 {object} response.FileWgetRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/wget [post] +// @x-panel-log {"bodyKeys":["url","path","name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载 url => [path]/[name]","formatEN":"Download url => [path]/[name]"} +func (b *BaseApi) WgetFile(c *gin.Context) { + var req request.FileWget + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + key, err := fileService.Wget(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, response.FileWgetRes{ + Key: key, + }) +} + +// @Tags File +// @Summary Move file +// @Accept json +// @Param request body request.FileMove true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/move [post] +// @x-panel-log {"bodyKeys":["oldPaths","newPath"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"移动文件 [oldPaths] => [newPath]","formatEN":"Move [oldPaths] => [newPath]"} +func (b *BaseApi) MoveFile(c *gin.Context) { + var req request.FileMove + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := fileService.MvFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Download file +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/download [get] +func (b *BaseApi) Download(c *gin.Context) { + filePath := c.Query("path") + file, err := os.Open(filePath) + if err != nil { + helper.InternalServer(c, err) + return + } + defer file.Close() + info, err := file.Stat() + if err != nil { + helper.InternalServer(c, err) + return + } + c.Header("Content-Length", strconv.FormatInt(info.Size(), 10)) + c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) + http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file) +} + +// @Tags File +// @Summary Chunk Download file +// @Accept json +// @Param request body request.FileDownload true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/chunkdownload [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"下载文件 [name]","formatEN":"Download file [name]"} +func (b *BaseApi) DownloadChunkFiles(c *gin.Context) { + var req request.FileChunkDownload + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + fileOp := files.NewFileOp() + if !fileOp.Stat(req.Path) { + helper.ErrorWithDetail(c, http.StatusInternalServerError, "ErrPathNotFound", nil) + return + } + filePath := req.Path + fstFile, err := fileOp.OpenFile(filePath) + if err != nil { + helper.InternalServer(c, err) + return + } + info, err := fstFile.Stat() + if err != nil { + helper.InternalServer(c, err) + return + } + if info.IsDir() { + helper.ErrorWithDetail(c, http.StatusInternalServerError, "ErrFileDownloadDir", err) + return + } + + c.Writer.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s", req.Name)) + c.Writer.Header().Set("Content-Type", "application/octet-stream") + c.Writer.Header().Set("Content-Length", strconv.FormatInt(info.Size(), 10)) + c.Writer.Header().Set("Accept-Ranges", "bytes") + + if c.Request.Header.Get("Range") != "" { + rangeHeader := c.Request.Header.Get("Range") + rangeArr := strings.Split(rangeHeader, "=")[1] + rangeParts := strings.Split(rangeArr, "-") + + startPos, _ := strconv.ParseInt(rangeParts[0], 10, 64) + + var endPos int64 + if rangeParts[1] == "" { + endPos = info.Size() - 1 + } else { + endPos, _ = strconv.ParseInt(rangeParts[1], 10, 64) + } + + c.Writer.Header().Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", startPos, endPos, info.Size())) + c.Writer.WriteHeader(http.StatusPartialContent) + + buffer := make([]byte, 1024*1024) + file, err := os.Open(filePath) + if err != nil { + helper.InternalServer(c, err) + return + } + defer file.Close() + + _, _ = file.Seek(startPos, 0) + reader := io.LimitReader(file, endPos-startPos+1) + _, err = io.CopyBuffer(c.Writer, reader, buffer) + if err != nil { + helper.InternalServer(c, err) + return + } + } else { + c.File(filePath) + } +} + +// @Tags File +// @Summary Load file size +// @Accept json +// @Param request body request.DirSizeReq true "request" +// @Success 200 {object} response.DirSizeRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/size [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"获取文件夹大小 [path]","formatEN":"Load file size [path]"} +func (b *BaseApi) Size(c *gin.Context) { + var req request.DirSizeReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := fileService.DirSize(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags File +// @Summary Multi file size +// @Accept json +// @Param request body request.DirSizeReq true "request" +// @Success 200 {array} response.DepthDirSizeRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/depth/size [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"获取目录及其第一层子目录文件夹大小 [path]","formatEN":"Multi file size [path]"} +func (b *BaseApi) DepthDirSize(c *gin.Context) { + var req request.DirSizeReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := fileService.DepthDirSize(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +func mergeChunks(fileName string, fileDir string, dstDir string, chunkCount int, overwrite bool) error { + defer func() { + _ = os.RemoveAll(fileDir) + }() + + op := files.NewFileOp() + dstDir = strings.TrimSpace(dstDir) + mode, _ := files.GetParentMode(dstDir) + if mode == 0 { + mode = constant.DirPerm + } + if _, err := os.Stat(dstDir); err != nil && os.IsNotExist(err) { + if err = op.CreateDir(dstDir, mode); err != nil { + return err + } + } + dstFileName := filepath.Join(dstDir, fileName) + dstInfo, statErr := os.Stat(dstFileName) + if statErr == nil { + mode = dstInfo.Mode() + } else { + mode = 0644 + } + if overwrite { + _ = os.Remove(dstFileName) + } + targetFile, err := os.OpenFile(dstFileName, os.O_RDWR|os.O_CREATE, mode) + if err != nil { + return err + } + defer targetFile.Close() + for i := 0; i < chunkCount; i++ { + chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", fileName, i)) + chunkData, err := os.ReadFile(chunkPath) + if err != nil { + return err + } + _, err = targetFile.Write(chunkData) + if err != nil { + return err + } + _ = os.Remove(chunkPath) + } + + return nil +} + +// @Tags File +// @Summary ChunkUpload file +// @Param file formData file true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/chunkupload [post] +func (b *BaseApi) UploadChunkFiles(c *gin.Context) { + var err error + fileForm, err := c.FormFile("chunk") + if err != nil { + helper.BadRequest(c, err) + return + } + uploadFile, err := fileForm.Open() + if err != nil { + helper.BadRequest(c, err) + return + } + defer uploadFile.Close() + chunkIndex, err := strconv.Atoi(c.PostForm("chunkIndex")) + if err != nil { + helper.BadRequest(c, err) + return + } + chunkCount, err := strconv.Atoi(c.PostForm("chunkCount")) + if err != nil { + helper.BadRequest(c, err) + return + } + fileOp := files.NewFileOp() + tmpDir := path.Join(global.Dir.TmpDir, "upload") + if !fileOp.Stat(tmpDir) { + if err := fileOp.CreateDir(tmpDir, constant.DirPerm); err != nil { + helper.BadRequest(c, err) + return + } + } + filename := c.PostForm("filename") + fileDir := filepath.Join(tmpDir, filename) + if chunkIndex == 0 { + if fileOp.Stat(fileDir) { + _ = fileOp.DeleteDir(fileDir) + } + _ = os.MkdirAll(fileDir, constant.DirPerm) + } + filePath := filepath.Join(fileDir, filename) + + defer func() { + if err != nil { + _ = os.Remove(fileDir) + } + }() + var ( + emptyFile *os.File + chunkData []byte + ) + + emptyFile, err = os.Create(filePath) + if err != nil { + helper.BadRequest(c, err) + return + } + defer emptyFile.Close() + + chunkData, err = io.ReadAll(uploadFile) + if err != nil { + helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err)) + return + } + + chunkPath := filepath.Join(fileDir, fmt.Sprintf("%s.%d", filename, chunkIndex)) + err = os.WriteFile(chunkPath, chunkData, constant.DirPerm) + if err != nil { + helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err)) + return + } + + if chunkIndex+1 == chunkCount { + overwrite := true + if ow := c.PostForm("overwrite"); ow != "" { + overwrite, _ = strconv.ParseBool(ow) + } + err = mergeChunks(filename, fileDir, c.PostForm("path"), chunkCount, overwrite) + if err != nil { + helper.InternalServer(c, buserr.WithMap("ErrFileUpload", map[string]interface{}{"name": filename, "detail": err.Error()}, err)) + return + } + helper.SuccessWithData(c, true) + } else { + return + } +} + +var wsUpgrade = websocket.Upgrader{ + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func (b *BaseApi) WgetProcess(c *gin.Context) { + ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil) + if err != nil { + return + } + wsClient := websocket2.NewWsClient("fileClient", ws) + go wsClient.Read() + go wsClient.Write() +} + +func (b *BaseApi) ProcessKeys(c *gin.Context) { + res := &response.FileProcessKeys{} + keys := global.CACHE.PrefixScanKey("file-wget-") + for _, key := range keys { + value := global.CACHE.Get(key) + if value == "" { + continue + } + var process files.Process + if err := json.Unmarshal([]byte(value), &process); err != nil { + continue + } + res.Keys = append(res.Keys, key) + } + helper.SuccessWithData(c, res) +} + +// @Tags File +// @Summary Read file by Line +// @Param request body request.FileReadByLineReq true "request" +// @Success 200 {object} response.FileLineContent +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/read [post] +func (b *BaseApi) ReadFileByLine(c *gin.Context) { + var req request.FileReadByLineReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := fileService.ReadLogByLine(req) + if err != nil { + helper.InternalServer(c, err) + return + } + if res.TotalLines > 100 { + helper.SuccessWithDataGzipped(c, res) + } else { + helper.SuccessWithData(c, res) + } +} + +// @Tags File +// @Summary Batch change file mode and owner +// @Accept json +// @Param request body request.FileRoleReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/batch/role [post] +// @x-panel-log {"bodyKeys":["paths","mode","user","group"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"批量修改文件权限和用户/组 [paths] => [mode]/[user]/[group]","formatEN":"Batch change file mode and owner [paths] => [mode]/[user]/[group]"} +func (b *BaseApi) BatchChangeModeAndOwner(c *gin.Context) { + var req request.FileRoleReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := fileService.BatchChangeModeAndOwner(req); err != nil { + helper.InternalServer(c, err) + } + helper.Success(c) +} + +func (b *BaseApi) GetPathByType(c *gin.Context) { + pathType, ok := c.Params.Get("type") + if !ok { + helper.BadRequest(c, errors.New("error pathType id in path")) + return + } + resPath := fileService.GetPathByType(pathType) + helper.SuccessWithData(c, resPath) +} + +// @Tags File +// @Summary system mount +// @Accept json +// @Success 200 {object} dto.DiskInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/mount [post] +func (b *BaseApi) GetHostMount(c *gin.Context) { + disks := fileService.GetHostMount() + helper.SuccessWithData(c, disks) +} + +// @Tags File +// @Summary system user and group +// @Accept json +// @Success 200 {object} response.UserGroupResponse +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/user/group [post] +func (b *BaseApi) GetUsersAndGroups(c *gin.Context) { + res, err := fileService.GetUsersAndGroups() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags File +// @Summary Convert file +// @Accept json +// @Param request body request.FileConvert true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/convert [post] +func (b *BaseApi) ConvertFile(c *gin.Context) { + var req request.FileConvertRequest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + fileService.Convert(req) + helper.SuccessWithData(c, nil) +} + +// @Tags File +// @Summary Convert file +// @Accept json +// @Param request body dto.PageInfo true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/convert/log [post] +func (b *BaseApi) ConvertLog(c *gin.Context) { + var req dto.PageInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, logs, err := fileService.ConvertLog(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: logs, + Total: total, + }) +} + +// @Tags File +// @Summary Batch get file remarks +// @Accept json +// @Param request body request.FileRemarkBatch true "request" +// @Success 200 {object} response.FileRemarksRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/remarks [post] +func (b *BaseApi) BatchGetFileRemarks(c *gin.Context) { + var req request.FileRemarkBatch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + remarks := fileService.BatchGetRemarks(req) + helper.SuccessWithData(c, response.FileRemarksRes{Remarks: remarks}) +} + +// @Tags File +// @Summary Set file remark +// @Accept json +// @Param request body request.FileRemarkUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/remark [post] +func (b *BaseApi) SetFileRemark(c *gin.Context) { + var req request.FileRemarkUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := fileService.SetRemark(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/firewall.go b/agent/app/api/v2/firewall.go new file mode 100644 index 0000000..b36ddb8 --- /dev/null +++ b/agent/app/api/v2/firewall.go @@ -0,0 +1,339 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Firewall +// @Summary Load firewall base info +// @Accept json +// @Param request body dto.OperationWithName true "request" +// @Success 200 {object} dto.FirewallBaseInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/base [post] +func (b *BaseApi) LoadFirewallBaseInfo(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := firewallService.LoadBaseInfo(req.Name) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Firewall +// @Summary Page firewall rules +// @Accept json +// @Param request body dto.RuleSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/search [post] +func (b *BaseApi) SearchFirewallRule(c *gin.Context) { + var req dto.RuleSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := firewallService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Firewall +// @Summary Operate firewall +// @Accept json +// @Param request body dto.FirewallOperation true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/operate [post] +// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] 防火墙","formatEN":"[operation] firewall"} +func (b *BaseApi) OperateFirewall(c *gin.Context) { + var req dto.FirewallOperation + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := firewallService.OperateFirewall(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Firewall +// @Summary Create group +// @Accept json +// @Param request body dto.PortRuleOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/port [post] +// @x-panel-log {"bodyKeys":["port","strategy"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加端口规则 [strategy] [port]","formatEN":"create port rules [strategy][port]"} +func (b *BaseApi) OperatePortRule(c *gin.Context) { + var req dto.PortRuleOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := firewallService.OperatePortRule(req, true); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// OperateForwardRule +// @Tags Firewall +// @Summary Operate forward rule +// @Accept json +// @Param request body dto.ForwardRuleOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/forward [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新端口转发规则","formatEN":"update port forward rules"} +func (b *BaseApi) OperateForwardRule(c *gin.Context) { + var req dto.ForwardRuleOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := firewallService.OperateForwardRule(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Firewall +// @Summary Operate Ip rule +// @Accept json +// @Param request body dto.AddrRuleOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/ip [post] +// @x-panel-log {"bodyKeys":["strategy","address"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加 ip 规则 [strategy] [address]","formatEN":"create address rules [strategy][address]"} +func (b *BaseApi) OperateIPRule(c *gin.Context) { + var req dto.AddrRuleOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := firewallService.OperateAddressRule(req, true); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Firewall +// @Summary Batch operate rule +// @Accept json +// @Param request body dto.BatchRuleOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/batch [post] +func (b *BaseApi) BatchOperateRule(c *gin.Context) { + var req dto.BatchRuleOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := firewallService.BatchOperateRule(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Firewall +// @Summary Update rule description +// @Accept json +// @Param request body dto.UpdateFirewallDescription true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/update/description [post] +func (b *BaseApi) UpdateFirewallDescription(c *gin.Context) { + var req dto.UpdateFirewallDescription + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := firewallService.UpdateDescription(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Firewall +// @Summary Update port rule +// @Accept json +// @Param request body dto.PortRuleUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/update/port [post] +func (b *BaseApi) UpdatePortRule(c *gin.Context) { + var req dto.PortRuleUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := firewallService.UpdatePortRule(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Firewall +// @Summary Update Ip rule +// @Accept json +// @Param request body dto.AddrRuleUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/update/addr [post] +func (b *BaseApi) UpdateAddrRule(c *gin.Context) { + var req dto.AddrRuleUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := firewallService.UpdateAddrRule(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Firewall +// @Summary search iptables filter rules +// @Accept json +// @Param request body dto.SearchPageWithType true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/filter/search [post] +func (b *BaseApi) SearchFilterRules(c *gin.Context) { + var req dto.SearchPageWithType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := iptablesService.Search(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Firewall +// @Summary Operate iptables filter rule +// @Accept json +// @Param request body dto.IptablesRuleOp true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/filter/rule/operate [post] +// @x-panel-log {"bodyKeys":["operation","chain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] filter规则到 [chain]","formatEN":"[operation] filter rule to [chain]"} +func (b *BaseApi) OperateFilterRule(c *gin.Context) { + var req dto.IptablesRuleOp + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := iptablesService.OperateRule(req, true); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Firewall +// @Summary Batch operate iptables filter rules +// @Accept json +// @Param request body dto.IptablesBatchOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/filter/rule/batch [post] +func (b *BaseApi) BatchOperateFilterRule(c *gin.Context) { + var req dto.IptablesBatchOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := iptablesService.BatchOperate(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Firewall +// @Summary Apply/Unload/Init iptables filter +// @Accept json +// @Param request body dto.IptablesOp true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/filter/operate [post] +// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] iptables filter 防火墙","formatEN":"[operate] iptables filter firewall"} +func (b *BaseApi) OperateFilterChain(c *gin.Context) { + var req dto.IptablesOp + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := iptablesService.Operate(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Firewall +// @Summary load chain status with name +// @Accept json +// @Param request body dto.OperationWithName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/firewall/filter/chain/status [post] +func (b *BaseApi) LoadChainStatus(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + helper.SuccessWithData(c, iptablesService.LoadChainStatus(req)) +} diff --git a/agent/app/api/v2/ftp.go b/agent/app/api/v2/ftp.go new file mode 100644 index 0000000..a8e83cf --- /dev/null +++ b/agent/app/api/v2/ftp.go @@ -0,0 +1,199 @@ +package v2 + +import ( + "encoding/base64" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags FTP +// @Summary Load FTP base info +// @Success 200 {object} dto.FtpBaseInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/ftp/base [get] +func (b *BaseApi) LoadFtpBaseInfo(c *gin.Context) { + data, err := ftpService.LoadBaseInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags FTP +// @Summary Load FTP operation log +// @Accept json +// @Param request body dto.FtpLogSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/ftp/log/search [post] +func (b *BaseApi) LoadFtpLogInfo(c *gin.Context) { + var req dto.FtpLogSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := ftpService.LoadLog(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags FTP +// @Summary Operate FTP +// @Accept json +// @Param request body dto.Operate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/ftp/operate [post] +// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] FTP","formatEN":"[operation] FTP"} +func (b *BaseApi) OperateFtp(c *gin.Context) { + var req dto.Operate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := ftpService.Operate(req.Operation); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags FTP +// @Summary Page FTP user +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/ftp/search [post] +func (b *BaseApi) SearchFtp(c *gin.Context) { + var req dto.SearchWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := ftpService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags FTP +// @Summary Create FTP user +// @Accept json +// @Param request body dto.FtpCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/ftp [post] +// @x-panel-log {"bodyKeys":["user", "path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 FTP 账户 [user][path]","formatEN":"create FTP [user][path]"} +func (b *BaseApi) CreateFtp(c *gin.Context) { + var req dto.FtpCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if len(req.Password) != 0 { + pass, err := base64.StdEncoding.DecodeString(req.Password) + if err != nil { + helper.BadRequest(c, err) + return + } + req.Password = string(pass) + } + if _, err := ftpService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags FTP +// @Summary Delete FTP user +// @Accept json +// @Param request body dto.BatchDeleteReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/ftp/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"ftps","output_column":"user","output_value":"users"}],"formatZH":"删除 FTP 账户 [users]","formatEN":"delete FTP users [users]"} +func (b *BaseApi) DeleteFtp(c *gin.Context) { + var req dto.BatchDeleteReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := ftpService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags FTP +// @Summary Sync FTP user +// @Accept json +// @Param request body dto.BatchDeleteReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/ftp/sync [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 FTP 账户","formatEN":"sync FTP users"} +func (b *BaseApi) SyncFtp(c *gin.Context) { + if err := ftpService.Sync(); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags FTP +// @Summary Update FTP user +// @Accept json +// @Param request body dto.FtpUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /toolbox/ftp/update [post] +// @x-panel-log {"bodyKeys":["user", "path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 FTP 账户 [user][path]","formatEN":"update FTP [user][path]"} +func (b *BaseApi) UpdateFtp(c *gin.Context) { + var req dto.FtpUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if len(req.Password) != 0 { + pass, err := base64.StdEncoding.DecodeString(req.Password) + if err != nil { + helper.BadRequest(c, err) + return + } + req.Password = string(pass) + } + if err := ftpService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/group.go b/agent/app/api/v2/group.go new file mode 100644 index 0000000..45c54c8 --- /dev/null +++ b/agent/app/api/v2/group.go @@ -0,0 +1,96 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags System Group +// @Summary Create group +// @Accept json +// @Param request body dto.GroupCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /groups [post] +// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"} +func (b *BaseApi) CreateGroup(c *gin.Context) { + var req dto.GroupCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := groupService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Group +// @Summary Delete group +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /groups/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"name","output_value":"name"},{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"} +func (b *BaseApi) DeleteGroup(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := groupService.Delete(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Group +// @Summary Update group +// @Accept json +// @Param request body dto.GroupUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /groups/update [post] +// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"} +func (b *BaseApi) UpdateGroup(c *gin.Context) { + var req dto.GroupUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := groupService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Group +// @Summary List groups +// @Accept json +// @Param request body dto.GroupSearch true "request" +// @Success 200 {array} dto.OperateByType +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /groups/search [post] +func (b *BaseApi) ListGroup(c *gin.Context) { + var req dto.OperateByType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := groupService.List(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} diff --git a/agent/app/api/v2/health.go b/agent/app/api/v2/health.go new file mode 100644 index 0000000..8b693c9 --- /dev/null +++ b/agent/app/api/v2/health.go @@ -0,0 +1,10 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) CheckHealth(c *gin.Context) { + helper.Success(c) +} diff --git a/agent/app/api/v2/helper/helper.go b/agent/app/api/v2/helper/helper.go new file mode 100644 index 0000000..3439a98 --- /dev/null +++ b/agent/app/api/v2/helper/helper.go @@ -0,0 +1,166 @@ +package helper + +import ( + "compress/gzip" + "context" + "encoding/json" + "fmt" + "net/http" + "strconv" + "strings" + "sync" + + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" +) + +func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) { + res := dto.Response{ + Code: code, + Message: "", + } + res.Message = i18n.GetMsgWithDetail(msgKey, err.Error()) + ctx.JSON(http.StatusOK, res) + ctx.Abort() +} + +func InternalServer(ctx *gin.Context, err error) { + ErrorWithDetail(ctx, http.StatusInternalServerError, "ErrInternalServer", err) +} + +func BadRequest(ctx *gin.Context, err error) { + ErrorWithDetail(ctx, http.StatusBadRequest, "ErrInvalidParams", err) +} + +func SuccessWithData(ctx *gin.Context, data interface{}) { + if data == nil { + data = gin.H{} + } + res := dto.Response{ + Code: http.StatusOK, + Data: data, + } + ctx.JSON(http.StatusOK, res) + ctx.Abort() +} + +var gzipWriterPool = sync.Pool{ + New: func() interface{} { + return gzip.NewWriter(nil) + }, +} + +func SuccessWithDataGzipped(ctx *gin.Context, data interface{}) { + if !strings.Contains(ctx.GetHeader("Accept-Encoding"), "gzip") { + SuccessWithData(ctx, data) + return + } + if data == nil { + data = gin.H{} + } + res := dto.Response{ + Code: http.StatusOK, + Data: data, + } + jsonBytes, err := json.Marshal(res) + if err != nil { + ErrorWithDetail(ctx, http.StatusInternalServerError, "ErrInternalServer", err) + return + } + + ctx.Header("Content-Encoding", "gzip") + ctx.Header("Content-Type", "application/json; charset=utf-8") + ctx.Status(http.StatusOK) + + gz := gzipWriterPool.Get().(*gzip.Writer) + gz.Reset(ctx.Writer) + _, _ = gz.Write(jsonBytes) + _ = gz.Close() + gzipWriterPool.Put(gz) + ctx.Abort() +} + +func Success(ctx *gin.Context) { + res := dto.Response{ + Code: http.StatusOK, + Message: "success", + } + ctx.JSON(http.StatusOK, res) + ctx.Abort() +} + +func SuccessWithMsg(ctx *gin.Context, msg string) { + res := dto.Response{ + Code: http.StatusOK, + Message: msg, + } + ctx.JSON(http.StatusOK, res) + ctx.Abort() +} + +func GetParamID(c *gin.Context) (uint, error) { + idParam, ok := c.Params.Get("id") + if !ok { + return 0, errors.New("error id in path") + } + intNum, _ := strconv.Atoi(idParam) + return uint(intNum), nil +} + +func GetIntParamByKey(c *gin.Context, key string) (uint, error) { + idParam, ok := c.Params.Get(key) + if !ok { + return 0, fmt.Errorf("error %s in path", key) + } + intNum, _ := strconv.Atoi(idParam) + return uint(intNum), nil +} + +func GetStrParamByKey(c *gin.Context, key string) (string, error) { + idParam, ok := c.Params.Get(key) + if !ok { + return "", fmt.Errorf("error %s in path", key) + } + return idParam, nil +} + +func GetTxAndContext() (tx *gorm.DB, ctx context.Context) { + tx = global.DB.Begin() + ctx = context.WithValue(context.Background(), constant.DB, tx) + return +} + +func CheckBindAndValidate(req interface{}, c *gin.Context) error { + if err := c.ShouldBindJSON(req); err != nil { + ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", err) + return err + } + if err := global.VALID.Struct(req); err != nil { + ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", err) + return err + } + return nil +} + +func CheckBind(req interface{}, c *gin.Context) error { + if err := c.ShouldBindJSON(&req); err != nil { + ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", err) + return err + } + return nil +} + +func GetParamInt32(paramName string, c *gin.Context) (int32, error) { + idParam, ok := c.Params.Get(paramName) + if !ok { + return 0, errors.New("error id in path") + } + intNum, _ := strconv.Atoi(idParam) + return int32(intNum), nil +} diff --git a/agent/app/api/v2/host_tool.go b/agent/app/api/v2/host_tool.go new file mode 100644 index 0000000..24f977e --- /dev/null +++ b/agent/app/api/v2/host_tool.go @@ -0,0 +1,157 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags Host tool +// @Summary Get tool status +// @Accept json +// @Param request body request.HostToolReq true "request" +// @Success 200 {object} response.HostToolRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/tool [post] +func (b *BaseApi) GetToolStatus(c *gin.Context) { + var req request.HostToolReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + config, err := hostToolService.GetToolStatus(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, config) +} + +// @Tags Host tool +// @Summary Create Host tool Config +// @Accept json +// @Param request body request.HostToolCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/tool/init [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建 [type] 配置","formatEN":"create [type] config"} +func (b *BaseApi) InitToolConfig(c *gin.Context) { + var req request.HostToolCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := hostToolService.CreateToolConfig(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Host tool +// @Summary Operate tool +// @Accept json +// @Param request body request.HostToolReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/tool/operate [post] +// @x-panel-log {"bodyKeys":["operate","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] [type] ","formatEN":"[operate] [type]"} +func (b *BaseApi) OperateTool(c *gin.Context) { + var req request.HostToolReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := hostToolService.OperateTool(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Host tool +// @Summary Get tool config +// @Accept json +// @Param request body request.HostToolConfig true "request" +// @Success 200 {object} response.HostToolConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/tool/config [post] +// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 主机工具配置文件 ","formatEN":"[operate] tool config"} +func (b *BaseApi) OperateToolConfig(c *gin.Context) { + var req request.HostToolConfig + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + config, err := hostToolService.OperateToolConfig(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, config) +} + +// @Tags Host tool +// @Summary Create Supervisor process +// @Accept json +// @Param request body request.SupervisorProcessConfig true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/tool/supervisor/process [post] +// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] 守护进程 ","formatEN":"[operate] process"} +func (b *BaseApi) OperateProcess(c *gin.Context) { + var req request.SupervisorProcessConfig + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + err := hostToolService.OperateSupervisorProcess(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Host tool +// @Summary Get Supervisor process config +// @Accept json +// @Success 200 {object} response.SupervisorProcessConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/tool/supervisor/process [get] +func (b *BaseApi) GetProcess(c *gin.Context) { + configs, err := hostToolService.GetSupervisorProcessConfig() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, configs) +} + +// @Tags Host tool +// @Summary Get Supervisor process config file +// @Accept json +// @Param request body request.SupervisorProcessFileReq true "request" +// @Success 200 {string} content +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/tool/supervisor/process/file [post] +// @x-panel-log {"bodyKeys":["operate"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operate] Supervisor 进程文件 ","formatEN":"[operate] Supervisor Process Config file"} +func (b *BaseApi) GetProcessFile(c *gin.Context) { + var req request.SupervisorProcessFileReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + content, err := hostToolService.OperateSupervisorProcessFile(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, content) +} diff --git a/agent/app/api/v2/image.go b/agent/app/api/v2/image.go new file mode 100644 index 0000000..52633bf --- /dev/null +++ b/agent/app/api/v2/image.go @@ -0,0 +1,227 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Container Image +// @Summary Page images +// @Accept json +// @Param request body dto.PageImage true "request" +// @Produce json +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image/search [post] +func (b *BaseApi) SearchImage(c *gin.Context) { + var req dto.PageImage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := imageService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Container Image +// @Summary List all images +// @Produce json +// @Success 200 {array} dto.ImageInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image/all [get] +func (b *BaseApi) ListAllImage(c *gin.Context) { + list, err := imageService.ListAll() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags Container Image +// @Summary load images options +// @Produce json +// @Success 200 {array} dto.Options +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image [get] +func (b *BaseApi) ListImage(c *gin.Context) { + list, err := imageService.List() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags Container Image +// @Summary Build image +// @Accept json +// @Param request body dto.ImageBuild true "request" +// @Success 200 {string} log +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image/build [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"构建镜像 [name]","formatEN":"build image [name]"} +func (b *BaseApi) ImageBuild(c *gin.Context) { + var req dto.ImageBuild + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageService.ImageBuild(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Image +// @Summary Pull image +// @Accept json +// @Param request body dto.ImagePull true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image/pull [post] +// @x-panel-log {"bodyKeys":["repoID","imageName"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"repoID","isList":false,"db":"image_repos","output_column":"name","output_value":"reponame"}],"formatZH":"镜像拉取 [reponame][imageName]","formatEN":"image pull [reponame][imageName]"} +func (b *BaseApi) ImagePull(c *gin.Context) { + var req dto.ImagePull + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageService.ImagePull(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Image +// @Summary Push image +// @Accept json +// @Param request body dto.ImagePush true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image/push [post] +// @x-panel-log {"bodyKeys":["repoID","tagName","name"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"repoID","isList":false,"db":"image_repos","output_column":"name","output_value":"reponame"}],"formatZH":"[tagName] 推送到 [reponame][name]","formatEN":"push [tagName] to [reponame][name]"} +func (b *BaseApi) ImagePush(c *gin.Context) { + var req dto.ImagePush + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageService.ImagePush(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Image +// @Summary Delete image +// @Accept json +// @Param request body dto.BatchDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image/remove [post] +// @x-panel-log {"bodyKeys":["names"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"移除镜像 [names]","formatEN":"remove image [names]"} +func (b *BaseApi) ImageRemove(c *gin.Context) { + var req dto.BatchDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageService.ImageRemove(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Image +// @Summary Save image +// @Accept json +// @Param request body dto.ImageSave true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image/save [post] +// @x-panel-log {"bodyKeys":["tagName","path","name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"保留 [tagName] 为 [path]/[name]","formatEN":"save [tagName] as [path]/[name]"} +func (b *BaseApi) ImageSave(c *gin.Context) { + var req dto.ImageSave + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageService.ImageSave(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Image +// @Summary Tag image +// @Accept json +// @Param request body dto.ImageTag true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image/tag [post] +// @x-panel-log {"bodyKeys":["repoID","targetName"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"repoID","isList":false,"db":"image_repos","output_column":"name","output_value":"reponame"}],"formatZH":"tag 镜像 [reponame][targetName]","formatEN":"tag image [reponame][targetName]"} +func (b *BaseApi) ImageTag(c *gin.Context) { + var req dto.ImageTag + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageService.ImageTag(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Container Image +// @Summary Load image +// @Accept json +// @Param request body dto.ImageLoad true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/image/load [post] +// @x-panel-log {"bodyKeys":["path"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"从 [path] 加载镜像","formatEN":"load image from [path]"} +func (b *BaseApi) ImageLoad(c *gin.Context) { + var req dto.ImageLoad + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageService.ImageLoad(req); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} diff --git a/agent/app/api/v2/image_repo.go b/agent/app/api/v2/image_repo.go new file mode 100644 index 0000000..a8f3df2 --- /dev/null +++ b/agent/app/api/v2/image_repo.go @@ -0,0 +1,142 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Container Image-repo +// @Summary Page image repos +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Produce json +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/repo/search [post] +func (b *BaseApi) SearchRepo(c *gin.Context) { + var req dto.SearchWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := imageRepoService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Container Image-repo +// @Summary List image repos +// @Produce json +// @Success 200 {array} dto.ImageRepoOption +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/repo [get] +func (b *BaseApi) ListRepo(c *gin.Context) { + list, err := imageRepoService.List() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Container Image-repo +// @Summary Load repo status +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Produce json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/repo/status [post] +func (b *BaseApi) CheckRepoStatus(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageRepoService.Login(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Image-repo +// @Summary Create image repo +// @Accept json +// @Param request body dto.ImageRepoDelete true "request" +// @Produce json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/repo [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建镜像仓库 [name]","formatEN":"create image repo [name]"} +func (b *BaseApi) CreateRepo(c *gin.Context) { + var req dto.ImageRepoCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageRepoService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Image-repo +// @Summary Delete image repo +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Produce json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/repo/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_column":"name","output_value":"name"}],"formatZH":"删除镜像仓库 [name]","formatEN":"delete image repo [name]"} +func (b *BaseApi) DeleteRepo(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageRepoService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Container Image-repo +// @Summary Update image repo +// @Accept json +// @Param request body dto.ImageRepoUpdate true "request" +// @Produce json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /containers/repo/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"image_repos","output_column":"name","output_value":"name"}],"formatZH":"更新镜像仓库 [name]","formatEN":"update image repo information [name]"} +func (b *BaseApi) UpdateRepo(c *gin.Context) { + var req dto.ImageRepoUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := imageRepoService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/logs.go b/agent/app/api/v2/logs.go new file mode 100644 index 0000000..1058a79 --- /dev/null +++ b/agent/app/api/v2/logs.go @@ -0,0 +1,22 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/gin-gonic/gin" +) + +// @Tags Logs +// @Summary Load system log files +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /logs/system/files [get] +func (b *BaseApi) GetSystemFiles(c *gin.Context) { + data, err := logService.ListSystemLogFile() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} diff --git a/agent/app/api/v2/mcp_server.go b/agent/app/api/v2/mcp_server.go new file mode 100644 index 0000000..19d0522 --- /dev/null +++ b/agent/app/api/v2/mcp_server.go @@ -0,0 +1,166 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags McpServer +// @Summary List mcp servers +// @Accept json +// @Param request body request.McpServerSearch true "request" +// @Success 200 {object} response.McpServersRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/mcp/search [post] +func (b *BaseApi) PageMcpServers(c *gin.Context) { + var req request.McpServerSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + list := mcpServerService.Page(req) + helper.SuccessWithData(c, list) +} + +// @Tags McpServer +// @Summary Create mcp server +// @Accept json +// @Param request body request.McpServerCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/mcp/server [post] +func (b *BaseApi) CreateMcpServer(c *gin.Context) { + var req request.McpServerCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := mcpServerService.Create(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags McpServer +// @Summary Update mcp server +// @Accept json +// @Param request body request.McpServerUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/mcp/server/update [post] +func (b *BaseApi) UpdateMcpServer(c *gin.Context) { + var req request.McpServerUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := mcpServerService.Update(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags McpServer +// @Summary Delete mcp server +// @Accept json +// @Param request body request.McpServerDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/mcp/server/del [post] +func (b *BaseApi) DeleteMcpServer(c *gin.Context) { + var req request.McpServerDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := mcpServerService.Delete(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags McpServer +// @Summary Operate mcp server +// @Accept json +// @Param request body request.McpServerOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/mcp/server/op [post] +func (b *BaseApi) OperateMcpServer(c *gin.Context) { + var req request.McpServerOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := mcpServerService.Operate(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags McpServer +// @Summary Bind Domain for mcp server +// @Accept json +// @Param request body request.McpBindDomain true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/mcp/domain/bind [post] +func (b *BaseApi) BindMcpDomain(c *gin.Context) { + var req request.McpBindDomain + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := mcpServerService.BindDomain(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags McpServer +// @Summary Update bind Domain for mcp server +// @Accept json +// @Param request body request.McpBindDomainUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/mcp/domain/update [post] +func (b *BaseApi) UpdateMcpBindDomain(c *gin.Context) { + var req request.McpBindDomainUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := mcpServerService.UpdateBindDomain(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags McpServer +// @Summary Get bin Domain for mcp server +// @Accept json +// @Success 200 {object} response.McpBindDomainRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /ai/mcp/domain/get [get] +func (b *BaseApi) GetMcpBindDomain(c *gin.Context) { + res, err := mcpServerService.GetBindDomain() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} diff --git a/agent/app/api/v2/monitor.go b/agent/app/api/v2/monitor.go new file mode 100644 index 0000000..6543fc0 --- /dev/null +++ b/agent/app/api/v2/monitor.go @@ -0,0 +1,132 @@ +package v2 + +import ( + "sort" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" + "github.com/shirou/gopsutil/v4/disk" + "github.com/shirou/gopsutil/v4/net" +) + +// @Tags Monitor +// @Summary Load monitor data +// @Param request body dto.MonitorSearch true "request" +// @Success 200 {array} dto.MonitorData +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/monitor/search [post] +func (b *BaseApi) LoadMonitor(c *gin.Context) { + var req dto.MonitorSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := monitorService.LoadMonitorData(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithDataGzipped(c, data) +} + +// @Tags Monitor +// @Summary Load monitor data +// @Param request body dto.MonitorGPUSearch true "request" +// @Success 200 {object} dto.MonitorGPUData +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/monitor/gpu/search [post] +func (b *BaseApi) LoadGPUMonitor(c *gin.Context) { + var req dto.MonitorGPUSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := monitorService.LoadGPUMonitorData(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Monitor +// @Summary Clean monitor data +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/monitor/clean [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空监控数据","formatEN":"clean monitor datas"} +func (b *BaseApi) CleanMonitor(c *gin.Context) { + if err := monitorService.CleanData(); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags Monitor +// @Summary Load monitor setting +// @Success 200 {object} dto.MonitorSetting +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/monitor/setting [get] +func (b *BaseApi) LoadMonitorSetting(c *gin.Context) { + setting, err := monitorService.LoadSetting() + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, setting) +} + +// @Tags Monitor +// @Summary Update monitor setting +// @Param request body dto.MonitorSettingUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/monitor/setting/update [post] +// @x-panel-log {"bodyKeys":["key", "value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改默认监控网卡 [name]-[value]","formatEN":"update default monitor [name]-[value]"} +func (b *BaseApi) UpdateMonitorSetting(c *gin.Context) { + var req dto.MonitorSettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := monitorService.UpdateSetting(req.Key, req.Value); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +func (b *BaseApi) GetNetworkOptions(c *gin.Context) { + netStat, _ := net.IOCounters(true) + var options []string + options = append(options, "all") + for _, net := range netStat { + options = append(options, net.Name) + } + sort.Strings(options) + helper.SuccessWithData(c, options) +} + +func (b *BaseApi) GetIOOptions(c *gin.Context) { + diskStat, _ := disk.IOCounters() + var options []string + options = append(options, "all") + for _, net := range diskStat { + options = append(options, net.Name) + } + sort.Strings(options) + helper.SuccessWithData(c, options) +} + +func (b *BaseApi) GetCPUOptions(c *gin.Context) { + helper.SuccessWithData(c, monitorService.LoadGPUOptions()) +} diff --git a/agent/app/api/v2/nginx.go b/agent/app/api/v2/nginx.go new file mode 100644 index 0000000..e9b5f2e --- /dev/null +++ b/agent/app/api/v2/nginx.go @@ -0,0 +1,195 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags OpenResty +// @Summary Load OpenResty conf +// @Success 200 {object} response.NginxFile +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty [get] +func (b *BaseApi) GetNginx(c *gin.Context) { + fileInfo, err := nginxService.GetNginxConfig() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, fileInfo) +} + +// @Tags OpenResty +// @Summary Load partial OpenResty conf +// @Accept json +// @Param request body request.NginxScopeReq true "request" +// @Success 200 {array} response.NginxParam +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty/scope [post] +func (b *BaseApi) GetNginxConfigByScope(c *gin.Context) { + var req request.NginxScopeReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + params, err := nginxService.GetConfigByScope(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, params) +} + +// @Tags OpenResty +// @Summary Update OpenResty conf +// @Accept json +// @Param request body request.NginxConfigUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty/update [post] +// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新 nginx 配置 [domain]","formatEN":"Update nginx conf [domain]"} +func (b *BaseApi) UpdateNginxConfigByScope(c *gin.Context) { + var req request.NginxConfigUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := nginxService.UpdateConfigByScope(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags OpenResty +// @Summary Load OpenResty status info +// @Success 200 {object} response.NginxStatus +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty/status [get] +func (b *BaseApi) GetNginxStatus(c *gin.Context) { + res, err := nginxService.GetStatus() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags OpenResty +// @Summary Update OpenResty conf by upload file +// @Accept json +// @Param request body request.NginxConfigFileUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty/file [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 nginx 配置","formatEN":"Update nginx conf"} +func (b *BaseApi) UpdateNginxFile(c *gin.Context) { + var req request.NginxConfigFileUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := nginxService.UpdateConfigFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags OpenResty +// @Summary Build OpenResty +// @Accept json +// @Param request body request.NginxBuildReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty/build [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"构建 OpenResty","formatEN":"Build OpenResty"} +func (b *BaseApi) BuildNginx(c *gin.Context) { + var req request.NginxBuildReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := nginxService.Build(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags OpenResty +// @Summary Update OpenResty module +// @Accept json +// @Param request body request.NginxModuleUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty/modules/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 OpenResty 模块","formatEN":"Update OpenResty module"} +func (b *BaseApi) UpdateNginxModule(c *gin.Context) { + var req request.NginxModuleUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := nginxService.UpdateModule(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags OpenResty +// @Summary Get OpenResty modules +// @Success 200 {object} response.NginxBuildConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty/modules [get] +func (b *BaseApi) GetNginxModules(c *gin.Context) { + modules, err := nginxService.GetModules() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, modules) +} + +// @Tags OpenResty +// @Summary Operate default HTTPs +// @Accept json +// @Param request body request.NginxDefaultHTTPSUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty/https [post] +func (b *BaseApi) OperateDefaultHTTPs(c *gin.Context) { + var req request.NginxDefaultHTTPSUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := nginxService.OperateDefaultHTTPs(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags OpenResty +// @Summary Get default HTTPs status +// @Success 200 {object} response.NginxConfigRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /openresty/https [get] +func (b *BaseApi) GetDefaultHTTPsStatus(c *gin.Context) { + res, err := nginxService.GetDefaultHttpsStatus() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} diff --git a/agent/app/api/v2/php_extensions.go b/agent/app/api/v2/php_extensions.go new file mode 100644 index 0000000..842ea43 --- /dev/null +++ b/agent/app/api/v2/php_extensions.go @@ -0,0 +1,105 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags PHP Extensions +// @Summary Page Extensions +// @Accept json +// @Param request body request.PHPExtensionsSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/extensions/search [post] +func (b *BaseApi) PagePHPExtensions(c *gin.Context) { + var req request.PHPExtensionsSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if req.All { + list, err := phpExtensionsService.List() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: int64(len(list)), + Items: list, + }) + } else { + total, list, err := phpExtensionsService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: list, + }) + } + +} + +// @Tags PHP Extensions +// @Summary Create Extensions +// @Accept json +// @Param request body request.PHPExtensionsCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/extensions [post] +func (b *BaseApi) CreatePHPExtensions(c *gin.Context) { + var req request.PHPExtensionsCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := phpExtensionsService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags PHP Extensions +// @Summary Update Extensions +// @Accept json +// @Param request body request.PHPExtensionsUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/extensions/update [post] +func (b *BaseApi) UpdatePHPExtensions(c *gin.Context) { + var req request.PHPExtensionsUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := phpExtensionsService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags PHP Extensions +// @Summary Delete Extensions +// @Accept json +// @Param request body request.PHPExtensionsDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/extensions/del [post] +func (b *BaseApi) DeletePHPExtensions(c *gin.Context) { + var req request.PHPExtensionsDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := phpExtensionsService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/process.go b/agent/app/api/v2/process.go new file mode 100644 index 0000000..b84fb45 --- /dev/null +++ b/agent/app/api/v2/process.go @@ -0,0 +1,74 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + websocket2 "github.com/1Panel-dev/1Panel/agent/utils/websocket" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) ProcessWs(c *gin.Context) { + ws, err := wsUpgrade.Upgrade(c.Writer, c.Request, nil) + if err != nil { + return + } + wsClient := websocket2.NewWsClient("processClient", ws) + go wsClient.Read() + go wsClient.Write() +} + +// @Tags Process +// @Summary Stop Process +// @Param request body request.ProcessReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /process/stop [post] +// @x-panel-log {"bodyKeys":["PID"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"结束进程 [PID]","formatEN":"结束进程 [PID]"} +func (b *BaseApi) StopProcess(c *gin.Context) { + var req request.ProcessReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := processService.StopProcess(req); err != nil { + helper.BadRequest(c, err) + return + } + helper.Success(c) +} + +// @Tags Process +// @Summary Get Process Info By PID +// @Param pid path int true "PID" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /process/{pid} [get] +func (b *BaseApi) GetProcessInfoByPID(c *gin.Context) { + pid, err := helper.GetParamInt32("pid", c) + if err != nil { + helper.BadRequest(c, err) + return + } + data, err := processService.GetProcessInfoByPID(pid) + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Process +// @Summary Get Listening Process +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /process/listening [post] +func (b *BaseApi) GetListeningProcess(c *gin.Context) { + procs, err := processService.GetListeningProcess(c) + if err != nil { + helper.BadRequest(c, err) + return + } + helper.SuccessWithData(c, procs) +} diff --git a/agent/app/api/v2/recycle_bin.go b/agent/app/api/v2/recycle_bin.go new file mode 100644 index 0000000..5089f64 --- /dev/null +++ b/agent/app/api/v2/recycle_bin.go @@ -0,0 +1,85 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags File +// @Summary List RecycleBin files +// @Accept json +// @Param request body dto.PageInfo true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/recycle/search [post] +func (b *BaseApi) SearchRecycleBinFile(c *gin.Context) { + var req dto.PageInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, list, err := recycleBinService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags File +// @Summary Reduce RecycleBin files +// @Accept json +// @Param request body request.RecycleBinReduce true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/recycle/reduce [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"还原回收站文件 [name]","formatEN":"Reduce RecycleBin file [name]"} +func (b *BaseApi) ReduceRecycleBinFile(c *gin.Context) { + var req request.RecycleBinReduce + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := recycleBinService.Reduce(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Clear RecycleBin files +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/recycle/clear [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空回收站","formatEN":"清空回收站"} +func (b *BaseApi) ClearRecycleBinFile(c *gin.Context) { + if err := recycleBinService.Clear(); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags File +// @Summary Get RecycleBin status +// @Accept json +// @Success 200 {string} content +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /files/recycle/status [get] +func (b *BaseApi) GetRecycleStatus(c *gin.Context) { + settingInfo, err := settingService.GetSettingInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, settingInfo.FileRecycleBin) +} diff --git a/agent/app/api/v2/runtime.go b/agent/app/api/v2/runtime.go new file mode 100644 index 0000000..d5c8e47 --- /dev/null +++ b/agent/app/api/v2/runtime.go @@ -0,0 +1,579 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags Runtime +// @Summary List runtimes +// @Accept json +// @Param request body request.RuntimeSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/search [post] +func (b *BaseApi) SearchRuntimes(c *gin.Context) { + var req request.RuntimeSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, items, err := runtimeService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: items, + }) +} + +// @Tags Runtime +// @Summary Create runtime +// @Accept json +// @Param request body request.RuntimeCreate true "request" +// @Success 200 {object} model.Runtime +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建运行环境 [name]","formatEN":"Create runtime [name]"} +func (b *BaseApi) CreateRuntime(c *gin.Context) { + var req request.RuntimeCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + ssl, err := runtimeService.Create(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, ssl) +} + +// @Tags Website +// @Summary Delete runtime +// @Accept json +// @Param request body request.RuntimeDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除运行环境 [name]","formatEN":"Delete runtime [name]"} +func (b *BaseApi) DeleteRuntime(c *gin.Context) { + var req request.RuntimeDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := runtimeService.Delete(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Delete runtime +// @Accept json +// @Success 200 {array} dto.AppResource +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/installed/delete/check/:id [get] +func (b *BaseApi) DeleteRuntimeCheck(c *gin.Context) { + runTimeId, err := helper.GetIntParamByKey(c, "id") + if err != nil { + helper.BadRequest(c, err) + return + } + checkData, err := runtimeService.DeleteCheck(runTimeId) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, checkData) +} + +// @Tags Runtime +// @Summary Update runtime +// @Accept json +// @Param request body request.RuntimeUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/update [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新运行环境 [name]","formatEN":"Update runtime [name]"} +func (b *BaseApi) UpdateRuntime(c *gin.Context) { + var req request.RuntimeUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := runtimeService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Get runtime +// @Accept json +// @Param id path string true "request" +// @Success 200 {object} response.RuntimeDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/:id [get] +func (b *BaseApi) GetRuntime(c *gin.Context) { + id, err := helper.GetIntParamByKey(c, "id") + if err != nil { + helper.BadRequest(c, err) + return + } + res, err := runtimeService.Get(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Runtime +// @Summary Get Node package scripts +// @Accept json +// @Param request body request.NodePackageReq true "request" +// @Success 200 {array} response.PackageScripts +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/node/package [post] +func (b *BaseApi) GetNodePackageRunScript(c *gin.Context) { + var req request.NodePackageReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := runtimeService.GetNodePackageRunScript(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Runtime +// @Summary Operate runtime +// @Accept json +// @Param request body request.RuntimeOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/operate [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"操作运行环境 [name]","formatEN":"Operate runtime [name]"} +func (b *BaseApi) OperateRuntime(c *gin.Context) { + var req request.RuntimeOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := runtimeService.OperateRuntime(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Get Node modules +// @Accept json +// @Param request body request.NodeModuleReq true "request" +// @Success 200 {array} response.NodeModule +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/node/modules [post] +func (b *BaseApi) GetNodeModules(c *gin.Context) { + var req request.NodeModuleReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := runtimeService.GetNodeModules(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Runtime +// @Summary Operate Node modules +// @Accept json +// @Param request body request.NodeModuleReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/node/modules/operate [post] +func (b *BaseApi) OperateNodeModules(c *gin.Context) { + var req request.NodeModuleOperateReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := runtimeService.OperateNodeModules(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Sync runtime status +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Router /runtimes/sync [post] +func (b *BaseApi) SyncStatus(c *gin.Context) { + err := runtimeService.SyncRuntimeStatus() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Get php runtime extension +// @Accept json +// @Param id path string true "request" +// @Success 200 {object} response.PHPExtensionRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/:id/extensions [get] +func (b *BaseApi) GetRuntimeExtension(c *gin.Context) { + id, err := helper.GetIntParamByKey(c, "id") + if err != nil { + helper.BadRequest(c, err) + return + } + res, err := runtimeService.GetPHPExtensions(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Runtime +// @Summary Install php extension +// @Accept json +// @Param request body request.PHPExtensionInstallReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/extensions/install [post] +func (b *BaseApi) InstallPHPExtension(c *gin.Context) { + var req request.PHPExtensionInstallReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := runtimeService.InstallPHPExtension(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary UnInstall php extension +// @Accept json +// @Param request body request.PHPExtensionInstallReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/extensions/uninstall [post] +func (b *BaseApi) UnInstallPHPExtension(c *gin.Context) { + var req request.PHPExtensionInstallReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := runtimeService.UnInstallPHPExtension(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Load php runtime conf +// @Accept json +// @Param id path integer true "request" +// @Success 200 {object} response.PHPConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/config/:id [get] +func (b *BaseApi) GetPHPConfig(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + data, err := runtimeService.GetPHPConfig(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Runtime +// @Summary Update runtime php conf +// @Accept json +// @Param request body request.PHPConfigUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/config [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain] PHP 配置修改","formatEN":"[domain] PHP conf update"} +func (b *BaseApi) UpdatePHPConfig(c *gin.Context) { + var req request.PHPConfigUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := runtimeService.UpdatePHPConfig(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Update php conf file +// @Accept json +// @Param request body request.PHPFileUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/update [post] +// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"php 配置修改 [domain]","formatEN":"Nginx conf update [domain]"} +func (b *BaseApi) UpdatePHPFile(c *gin.Context) { + var req request.PHPFileUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := runtimeService.UpdatePHPConfigFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Get php conf file +// @Accept json +// @Param request body request.PHPFileReq true "request" +// @Success 200 {object} response.FileInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/file [post] +func (b *BaseApi) GetPHPConfigFile(c *gin.Context) { + var req request.PHPFileReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + data, err := runtimeService.GetPHPConfigFile(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Runtime +// @Summary Update fpm config +// @Accept json +// @Param request body request.FPMConfig true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/fpm/config [post] +func (b *BaseApi) UpdateFPMConfig(c *gin.Context) { + var req request.FPMConfig + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := runtimeService.UpdateFPMConfig(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Get fpm config +// @Accept json +// @Param id path integer true "request" +// @Success 200 {object} request.FPMConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/fpm/config/:id [get] +func (b *BaseApi) GetFPMConfig(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + data, err := runtimeService.GetFPMConfig(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Runtime +// @Summary Get supervisor process +// @Accept json +// @Param id path integer true "request" +// @Success 200 {array} response.SupervisorProcessConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/supervisor/process/:id [get] +func (b *BaseApi) GetSupervisorProcess(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + data, err := runtimeService.GetSupervisorProcess(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Runtime +// @Summary Operate supervisor process +// @Accept json +// @Param request body request.PHPSupervisorProcessConfig true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/supervisor/process [post] +func (b *BaseApi) OperateSupervisorProcess(c *gin.Context) { + var req request.PHPSupervisorProcessConfig + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := runtimeService.OperateSupervisorProcess(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Operate supervisor process file +// @Accept json +// @Param request body request.PHPSupervisorProcessFileReq true "request" +// @Success 200 {string} content +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/supervisor/process/file [post] +func (b *BaseApi) OperateSupervisorProcessFile(c *gin.Context) { + var req request.PHPSupervisorProcessFileReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := runtimeService.OperateSupervisorProcessFile(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Runtime +// @Summary Update PHP container config +// @Accept json +// @Param request body request.PHPContainerConfig true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/container/update [post] +func (b *BaseApi) UpdatePHPContainer(c *gin.Context) { + var req request.PHPContainerConfig + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := runtimeService.UpdatePHPContainer(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Get PHP container config +// @Accept json +// @Param id path integer true "request" +// @Success 200 {object} request.PHPContainerConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/container/:id [get] +func (b *BaseApi) GetPHPContainerConfig(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + data, err := runtimeService.GetPHPContainerConfig(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Runtime +// @Summary Update runtime remark +// @Accept json +// @Param request body request.RuntimeRemark true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/remark [post] +func (b *BaseApi) UpdateRuntimeRemark(c *gin.Context) { + var req request.RuntimeRemark + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := runtimeService.UpdateRemark(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Runtime +// @Summary Get PHP runtime status +// @Accept json +// @Param id path integer true "request" +// @Success 200 {object} map[string]interface{} +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /runtimes/php/fpm/status/:id [get] +func (b *BaseApi) GetFPMStatus(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + data, err := runtimeService.GetFPMStatus(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} diff --git a/agent/app/api/v2/setting.go b/agent/app/api/v2/setting.go new file mode 100644 index 0000000..604dcf7 --- /dev/null +++ b/agent/app/api/v2/setting.go @@ -0,0 +1,200 @@ +package v2 + +import ( + "encoding/json" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/ssh" + "github.com/gin-gonic/gin" + "github.com/pkg/errors" +) + +// @Tags System Setting +// @Summary Load system setting info +// @Success 200 {object} dto.SettingInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/search [post] +func (b *BaseApi) GetSettingInfo(c *gin.Context) { + setting, err := settingService.GetSettingInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, setting) +} + +// @Tags System Setting +// @Summary Load system available status +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/search/available [get] +func (b *BaseApi) GetSystemAvailable(c *gin.Context) { + helper.Success(c) +} + +// @Tags System Setting +// @Summary Update system setting +// @Accept json +// @Param request body dto.SettingUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/update [post] +// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统配置 [key] => [value]","formatEN":"update system setting [key] => [value]"} +func (b *BaseApi) UpdateSetting(c *gin.Context) { + var req dto.SettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.Update(req.Key, req.Value); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Load local backup dir +// @Success 200 {string} path +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/basedir [get] +func (b *BaseApi) LoadBaseDir(c *gin.Context) { + helper.SuccessWithData(c, global.Dir.DataDir) +} + +// @Tags System Setting +// @Summary Load local conn +// @Success 200 {object} dto.SSHConnData +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/ssh/conn [get] +func (b *BaseApi) LoadLocalConn(c *gin.Context) { + helper.SuccessWithData(c, settingService.GetLocalConn()) +} + +func (b *BaseApi) CheckLocalConn(c *gin.Context) { + _, err := loadLocalConn() + helper.SuccessWithData(c, err == nil) +} + +// @Tags System Setting +// @Summary Update local is conn +// @Accept json +// @Param request body dto.SSHDefaultConn true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/ssh/conn/default [post] +// @x-panel-log {"bodyKeys":["defaultConn"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"本地终端默认连接 [defaultConn]","formatEN":"update system default conn [defaultConn]"} +func (b *BaseApi) SetDefaultIsConn(c *gin.Context) { + var req dto.SSHDefaultConn + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.SetDefaultIsConn(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Check local conn info +// @Success 200 {boolean} isOk +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/ssh/check/info [post] +func (b *BaseApi) CheckLocalConnByInfo(c *gin.Context) { + var req dto.SSHConnData + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + helper.SuccessWithData(c, settingService.TestConnByInfo(req)) +} + +// @Tags System Setting +// @Summary Save local conn info +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/ssh [post] +func (b *BaseApi) SaveLocalConn(c *gin.Context) { + var req dto.SSHConnData + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.SaveConnInfo(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func loadLocalConn() (*ssh.SSHClient, error) { + connInfoInDB := settingService.GetSettingByKey("LocalSSHConn") + if len(connInfoInDB) == 0 { + return nil, errors.New("no such ssh conn info in db!") + } + var connInDB model.LocalConnInfo + if err := json.Unmarshal([]byte(connInfoInDB), &connInDB); err != nil { + return nil, err + } + sshInfo := ssh.ConnInfo{ + Addr: connInDB.Addr, + Port: int(connInDB.Port), + User: connInDB.User, + AuthMode: connInDB.AuthMode, + Password: connInDB.Password, + PrivateKey: []byte(connInDB.PrivateKey), + PassPhrase: []byte(connInDB.PassPhrase), + } + return ssh.NewClient(sshInfo) +} + +// @Tags System Setting +// @Summary Load system setting by key +// @Param key path string true "key" +// @Success 200 {object} dto.SettingInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/get/{key} [get] +func (b *BaseApi) GetSettingByKey(c *gin.Context) { + key := c.Param("key") + if len(key) == 0 { + helper.BadRequest(c, errors.New("key is empty")) + return + } + value := settingService.GetSettingByKey(key) + helper.SuccessWithData(c, value) +} + +// @Tags System Setting +// @Summary Save common description +// @Accept json +// @Param request body dto.CommonDescription true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/description/save [post] +func (b *BaseApi) SaveDescription(c *gin.Context) { + var req dto.CommonDescription + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.SaveDescription(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/snapshot.go b/agent/app/api/v2/snapshot.go new file mode 100644 index 0000000..16d7603 --- /dev/null +++ b/agent/app/api/v2/snapshot.go @@ -0,0 +1,201 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags System Setting +// @Summary Load system snapshot data +// @Success 200 {object} dto.SnapshotData +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/snapshot/load [get] +func (b *BaseApi) LoadSnapshotData(c *gin.Context) { + data, err := snapshotService.LoadSnapshotData() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags System Setting +// @Summary Create system snapshot +// @Accept json +// @Param request body dto.SnapshotCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/snapshot [post] +// @x-panel-log {"bodyKeys":["from", "description"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建系统快照 [description] 到 [from]","formatEN":"Create system backup [description] to [from]"} +func (b *BaseApi) CreateSnapshot(c *gin.Context) { + var req dto.SnapshotCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := snapshotService.SnapshotCreate(nil, req, 0, 3); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Recreate system snapshot +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/snapshot/recreate [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"重试创建快照 [name]","formatEN":"recrete the snapshot [name]"} +func (b *BaseApi) RecreateSnapshot(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := snapshotService.SnapshotReCreate(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Import system snapshot +// @Accept json +// @Param request body dto.SnapshotImport true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/snapshot/import [post] +// @x-panel-log {"bodyKeys":["from", "names"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"从 [from] 同步系统快照 [names]","formatEN":"Sync system snapshots [names] from [from]"} +func (b *BaseApi) ImportSnapshot(c *gin.Context) { + var req dto.SnapshotImport + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := snapshotService.SnapshotImport(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Update snapshot description +// @Accept json +// @Param request body dto.UpdateDescription true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/snapshot/description/update [post] +// @x-panel-log {"bodyKeys":["id","description"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"快照 [name] 描述信息修改 [description]","formatEN":"The description of the snapshot [name] is modified => [description]"} +func (b *BaseApi) UpdateSnapDescription(c *gin.Context) { + var req dto.UpdateDescription + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := snapshotService.UpdateDescription(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Page system snapshot +// @Accept json +// @Param request body dto.PageSnapshot true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/snapshot/search [post] +func (b *BaseApi) SearchSnapshot(c *gin.Context) { + var req dto.PageSnapshot + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, accounts, err := snapshotService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: accounts, + }) +} + +// @Tags System Setting +// @Summary Recover system backup +// @Accept json +// @Param request body dto.SnapshotRecover true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/snapshot/recover [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 恢复","formatEN":"Recover from system backup [name]"} +func (b *BaseApi) RecoverSnapshot(c *gin.Context) { + var req dto.SnapshotRecover + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := snapshotService.SnapshotRecover(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Rollback system backup +// @Accept json +// @Param request body dto.SnapshotRecover true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/snapshot/rollback [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"从系统快照 [name] 回滚","formatEN":"Rollback from system backup [name]"} +func (b *BaseApi) RollbackSnapshot(c *gin.Context) { + var req dto.SnapshotRecover + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := snapshotService.SnapshotRollback(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Delete system backup +// @Accept json +// @Param request body dto.SnapshotBatchDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /settings/snapshot/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"snapshots","output_column":"name","output_value":"name"}],"formatZH":"删除系统快照 [name]","formatEN":"Delete system backup [name]"} +func (b *BaseApi) DeleteSnapshot(c *gin.Context) { + var req dto.SnapshotBatchDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := snapshotService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/ssh.go b/agent/app/api/v2/ssh.go new file mode 100644 index 0000000..a9c5bb5 --- /dev/null +++ b/agent/app/api/v2/ssh.go @@ -0,0 +1,294 @@ +package v2 + +import ( + "encoding/base64" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags SSH +// @Summary Load host SSH setting info +// @Success 200 {object} dto.SSHInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/search [post] +func (b *BaseApi) GetSSHInfo(c *gin.Context) { + info, err := sshService.GetSSHInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, info) +} + +// @Tags SSH +// @Summary Operate SSH +// @Accept json +// @Param request body dto.Operate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/operate [post] +// @x-panel-log {"bodyKeys":["operation"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"[operation] SSH ","formatEN":"[operation] SSH"} +func (b *BaseApi) OperateSSH(c *gin.Context) { + var req dto.Operate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := sshService.OperateSSH(req.Operation); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags SSH +// @Summary Update host SSH setting +// @Accept json +// @Param request body dto.SSHUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/update [post] +// @x-panel-log {"bodyKeys":["key","newValue"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置 [key] => [newValue]","formatEN":"update SSH setting [key] => [newValue]"} +func (b *BaseApi) UpdateSSH(c *gin.Context) { + var req dto.SSHUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := sshService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags SSH +// @Summary Generate host SSH secret +// @Accept json +// @Param request body dto.RootCertOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/cert [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"} +func (b *BaseApi) CreateRootCert(c *gin.Context) { + var req dto.RootCertOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := loadCertAfterDecrypt(&req); err != nil { + helper.BadRequest(c, err) + } + if err := sshService.CreateRootCert(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags SSH +// @Summary Update host SSH secret +// @Accept json +// @Param request body dto.RootCertOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/cert/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 SSH 密钥 ","formatEN":"generate SSH secret"} +func (b *BaseApi) EditRootCert(c *gin.Context) { + var req dto.RootCertOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := loadCertAfterDecrypt(&req); err != nil { + helper.BadRequest(c, err) + } + if err := sshService.EditRootCert(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags SSH +// @Summary Sycn host SSH secret +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/cert/sync [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步 SSH 密钥 ","formatEN":"sync SSH secret"} +func (b *BaseApi) SyncRootCert(c *gin.Context) { + if err := sshService.SyncRootCert(); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags SSH +// @Summary Load host SSH secret +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/cert/search [post] +func (b *BaseApi) SearchRootCert(c *gin.Context) { + var req dto.SearchWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, data, err := sshService.SearchRootCerts(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: data, + }) +} + +// @Tags SSH +// @Summary Delete host SSH secret +// @Accept json +// @Param request body dto.ForceDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/cert/delete [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除 SSH 密钥 ","formatEN":"delete SSH secret"} +func (b *BaseApi) DeleteRootCert(c *gin.Context) { + var req dto.ForceDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := sshService.DeleteRootCerts(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags SSH +// @Summary Load host SSH logs +// @Accept json +// @Param request body dto.SearchSSHLog true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/log [post] +func (b *BaseApi) LoadSSHLogs(c *gin.Context) { + var req dto.SearchSSHLog + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, data, err := sshService.LoadLog(c, req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: data, + }) +} + +// @Tags SSH +// @Summary Export host SSH logs +// @Accept json +// @Param request body dto.SearchSSHLog true "request" +// @Success 200 {string} path +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/log/export [post] +func (b *BaseApi) ExportSSHLogs(c *gin.Context) { + var req dto.SearchSSHLog + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + tmpFile, err := sshService.ExportLog(c, req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, tmpFile) +} + +// @Tags SSH +// @Summary Load host SSH conf +// @Accept json +// @Param request body dto.OperationWithName true "request" +// @Success 200 {string} conf +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/file [post] +func (b *BaseApi) LoadSSHFile(c *gin.Context) { + var req dto.OperationWithName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := sshService.LoadSSHFile(req.Name) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags SSH +// @Summary Update host SSH setting by file +// @Accept json +// @Param request body dto.SSHConf true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/ssh/file/update [post] +// @x-panel-log {"bodyKeys":["key"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改 SSH 配置文件 [key]","formatEN":"update SSH conf [key]"} +func (b *BaseApi) UpdateSSHByFile(c *gin.Context) { + var req dto.SettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := sshService.UpdateByFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func loadCertAfterDecrypt(req *dto.RootCertOperate) error { + if len(req.PassPhrase) != 0 { + passPhrase, err := base64.StdEncoding.DecodeString(req.PassPhrase) + if err != nil { + return err + } + req.PassPhrase = string(passPhrase) + } + if len(req.PrivateKey) != 0 { + privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) + if err != nil { + return err + } + req.PrivateKey = string(privateKey) + } + if len(req.PublicKey) != 0 { + publicKey, err := base64.StdEncoding.DecodeString(req.PublicKey) + if err != nil { + return err + } + req.PublicKey = string(publicKey) + } + return nil +} diff --git a/agent/app/api/v2/system.go b/agent/app/api/v2/system.go new file mode 100644 index 0000000..f113ab5 --- /dev/null +++ b/agent/app/api/v2/system.go @@ -0,0 +1,26 @@ +package v2 + +import ( + "errors" + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/gin-gonic/gin" +) + +// @Tags Host +// @Summary Check if a system component exists +// @Accept json +// @Param name path string true "Component name to check (e.g., rsync, docker)" +// @Success 200 {object} response.ComponentInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /hosts/components/{name} [get] +func (b *BaseApi) CheckComponentExistence(c *gin.Context) { + name := c.Param("name") + if name == "" { + helper.BadRequest(c, errors.New("empty component name")) + return + } + + info := systemService.IsComponentExist(name) + helper.SuccessWithData(c, info) +} diff --git a/agent/app/api/v2/task.go b/agent/app/api/v2/task.go new file mode 100644 index 0000000..6886ebf --- /dev/null +++ b/agent/app/api/v2/task.go @@ -0,0 +1,46 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags TaskLog +// @Summary Page task logs +// @Accept json +// @Param request body dto.SearchTaskLogReq true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /logs/tasks/search [post] +func (b *BaseApi) PageTasks(c *gin.Context) { + var req dto.SearchTaskLogReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, list, err := taskService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags TaskLog +// @Summary Get the number of executing tasks +// @Success 200 {object} int64 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /logs/tasks/executing/count [get] +func (b *BaseApi) CountExecutingTasks(c *gin.Context) { + count, err := taskService.CountExecutingTask() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, count) +} diff --git a/agent/app/api/v2/tensorrt_llm.go b/agent/app/api/v2/tensorrt_llm.go new file mode 100644 index 0000000..1af5100 --- /dev/null +++ b/agent/app/api/v2/tensorrt_llm.go @@ -0,0 +1,68 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +func (b *BaseApi) PageTensorRTLLMs(c *gin.Context) { + var req request.TensorRTLLMSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + list := tensorrtLLMService.Page(req) + helper.SuccessWithData(c, list) +} + +func (b *BaseApi) CreateTensorRTLLM(c *gin.Context) { + var req request.TensorRTLLMCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := tensorrtLLMService.Create(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) UpdateTensorRTLLM(c *gin.Context) { + var req request.TensorRTLLMUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := tensorrtLLMService.Update(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) DeleteTensorRTLLM(c *gin.Context) { + var req request.TensorRTLLMDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := tensorrtLLMService.Delete(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) OperateTensorRTLLM(c *gin.Context) { + var req request.TensorRTLLMOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := tensorrtLLMService.Operate(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/terminal.go b/agent/app/api/v2/terminal.go new file mode 100644 index 0000000..16685ed --- /dev/null +++ b/agent/app/api/v2/terminal.go @@ -0,0 +1,236 @@ +package v2 + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/terminal" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/pkg/errors" +) + +func (b *BaseApi) WsSSH(c *gin.Context) { + wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + global.LOG.Errorf("gin context http handler failed, err: %v", err) + return + } + defer wsConn.Close() + + if global.CONF.Base.IsDemo { + if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) { + return + } + } + + cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { + return + } + rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { + return + } + + client, err := loadLocalConn() + if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) { + return + } + defer client.Close() + command := c.DefaultQuery("command", "") + sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, command) + if wshandleError(wsConn, err) { + return + } + defer sws.Close() + + quitChan := make(chan bool, 3) + sws.Start(quitChan) + go sws.Wait(quitChan) + + <-quitChan + + dt := time.Now().Add(time.Second) + _ = wsConn.WriteControl(websocket.CloseMessage, nil, dt) +} + +func (b *BaseApi) ContainerWsSSH(c *gin.Context) { + wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + global.LOG.Errorf("gin context http handler failed, err: %v", err) + return + } + defer wsConn.Close() + + if global.CONF.Base.IsDemo { + if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) { + return + } + } + + cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { + return + } + rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { + return + } + source := c.Query("source") + var initCmd []string + switch source { + case "redis", "redis-cluster": + initCmd, err = loadRedisInitCmd(c, source) + case "ollama": + initCmd, err = loadOllamaInitCmd(c) + case "container": + initCmd, err = loadContainerInitCmd(c) + case "database": + initCmd, err = loadDatabaseInitCmd(c) + default: + if wshandleError(wsConn, fmt.Errorf("not support such source %s", source)) { + return + } + } + if wshandleError(wsConn, err) { + return + } + slave, err := terminal.NewCommand("docker", initCmd...) + if wshandleError(wsConn, err) { + return + } + defer slave.Close() + + tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, false) + if wshandleError(wsConn, err) { + return + } + + quitChan := make(chan bool, 3) + tty.Start(quitChan) + go slave.Wait(quitChan) + + <-quitChan + + global.LOG.Info("websocket finished") + dt := time.Now().Add(time.Second) + _ = wsConn.WriteControl(websocket.CloseMessage, nil, dt) +} + +func loadRedisInitCmd(c *gin.Context, redisType string) ([]string, error) { + name := c.Query("name") + from := c.Query("from") + commands := []string{"exec", "-it"} + database, err := databaseService.Get(name) + if err != nil { + return nil, fmt.Errorf("no such database in db, err: %v", err) + } + if from == "local" { + redisInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: name, Type: redisType}) + if err != nil { + return nil, fmt.Errorf("no such app in db, err: %v", err) + } + name = redisInfo.ContainerName + commands = append(commands, []string{name, "redis-cli"}...) + if len(database.Password) != 0 { + commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...) + } + } else { + name = "1Panel-redis-cli-tools" + commands = append(commands, []string{name, "redis-cli", "-h", database.Address, "-p", fmt.Sprintf("%v", database.Port)}...) + if len(database.Password) != 0 { + commands = append(commands, []string{"-a", database.Password, "--no-auth-warning"}...) + } + } + return commands, nil +} + +func loadOllamaInitCmd(c *gin.Context) ([]string, error) { + name := c.Query("name") + if cmd.CheckIllegal(name) { + return nil, fmt.Errorf("ollama model %s contains illegal characters", name) + } + ollamaInfo, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Name: "", Type: "ollama"}) + if err != nil { + return nil, fmt.Errorf("no such app in db, err: %v", err) + } + containerName := ollamaInfo.ContainerName + return []string{"exec", "-it", containerName, "ollama", "run", name}, nil +} + +func loadContainerInitCmd(c *gin.Context) ([]string, error) { + containerID := c.Query("containerid") + command := c.Query("command") + user := c.Query("user") + if cmd.CheckIllegal(user, containerID, command) { + return nil, fmt.Errorf("the command contains illegal characters. command: %s, user: %s, containerID: %s", command, user, containerID) + } + if len(command) == 0 || len(containerID) == 0 { + return nil, fmt.Errorf("error param of command: %s or containerID: %s", command, containerID) + } + commands := []string{"exec", "-it", containerID, command} + if len(user) != 0 { + commands = []string{"exec", "-it", "-u", user, containerID, command} + } + + return commands, nil +} + +func loadDatabaseInitCmd(c *gin.Context) ([]string, error) { + database := c.Query("database") + databaseType := c.Query("databaseType") + if len(database) == 0 || len(databaseType) == 0 { + return nil, fmt.Errorf("error param of database: %s or database type: %s", database, databaseType) + } + databaseConn, err := appInstallService.LoadConnInfo(dto.OperationWithNameAndType{Type: databaseType, Name: database}) + if err != nil { + return nil, fmt.Errorf("no such database in db, err: %v", err) + } + commands := []string{"exec", "-it", databaseConn.ContainerName} + switch databaseType { + case "mysql", "mysql-cluster": + commands = append(commands, []string{"mysql", "-uroot", "-p" + databaseConn.Password}...) + case "mariadb": + commands = append(commands, []string{"mariadb", "-uroot", "-p" + databaseConn.Password}...) + case "postgresql", "postgresql-cluster": + commands = []string{"exec", "-e", fmt.Sprintf("PGPASSWORD=%s", databaseConn.Password), "-it", databaseConn.ContainerName, "psql", "-t", "-U", databaseConn.Username} + } + + return commands, nil +} + +func wshandleError(ws *websocket.Conn, err error) bool { + if err != nil { + global.LOG.Errorf("handler ws faled:, err: %v", err) + dt := time.Now().Add(time.Second) + if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil { + wsData, err := json.Marshal(terminal.WsMsg{ + Type: terminal.WsMsgCmd, + Data: base64.StdEncoding.EncodeToString([]byte(err.Error())), + }) + if err != nil { + _ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}")) + } else { + _ = ws.WriteMessage(websocket.TextMessage, wsData) + } + } + return true + } + return false +} + +var upGrader = websocket.Upgrader{ + ReadBufferSize: 4096, + WriteBufferSize: 16384, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} diff --git a/agent/app/api/v2/website.go b/agent/app/api/v2/website.go new file mode 100644 index 0000000..2db6b61 --- /dev/null +++ b/agent/app/api/v2/website.go @@ -0,0 +1,1265 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags Website +// @Summary Page websites +// @Accept json +// @Param request body request.WebsiteSearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/search [post] +func (b *BaseApi) PageWebsite(c *gin.Context) { + var req request.WebsiteSearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, websites, err := websiteService.PageWebsite(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: websites, + }) +} + +// @Tags Website +// @Summary List websites +// @Success 200 {array} response.WebsiteDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/list [get] +func (b *BaseApi) GetWebsites(c *gin.Context) { + websites, err := websiteService.GetWebsites() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, websites) +} + +// @Tags Website +// @Summary List website names +// @Success 200 {array} response.WebsiteOption +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/options [post] +func (b *BaseApi) GetWebsiteOptions(c *gin.Context) { + var req request.WebsiteOptionReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + websites, err := websiteService.GetWebsiteOptions(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, websites) +} + +// @Tags Website +// @Summary Create website +// @Accept json +// @Param request body request.WebsiteCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites [post] +// @x-panel-log {"bodyKeys":["alias"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 [alias]","formatEN":"Create website [alias]"} +func (b *BaseApi) CreateWebsite(c *gin.Context) { + var req request.WebsiteCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := websiteService.CreateWebsite(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Operate website +// @Accept json +// @Param request body request.WebsiteOp true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/operate [post] +// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[operate] 网站 [domain]","formatEN":"[operate] website [domain]"} +func (b *BaseApi) OpWebsite(c *gin.Context) { + var req request.WebsiteOp + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := websiteService.OpWebsite(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Delete website +// @Accept json +// @Param request body request.WebsiteDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"删除网站 [domain]","formatEN":"Delete website [domain]"} +func (b *BaseApi) DeleteWebsite(c *gin.Context) { + var req request.WebsiteDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := websiteService.DeleteWebsite(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Update website +// @Accept json +// @Param request body request.WebsiteUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/update [post] +// @x-panel-log {"bodyKeys":["primaryDomain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新网站 [primaryDomain]","formatEN":"Update website [primaryDomain]"} +func (b *BaseApi) UpdateWebsite(c *gin.Context) { + var req request.WebsiteUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateWebsite(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Search website by id +// @Accept json +// @Param id path integer true "request" +// @Success 200 {object} response.WebsiteDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/:id [get] +func (b *BaseApi) GetWebsite(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + website, err := websiteService.GetWebsite(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, website) +} + +// @Tags Website Nginx +// @Summary Search website nginx by id +// @Accept json +// @Param id path integer true "request" +// @Success 200 {object} response.FileInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/:id/config/:type [get] +func (b *BaseApi) GetWebsiteNginx(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + configType := c.Param("type") + + fileInfo, err := websiteService.GetWebsiteNginxConfig(id, configType) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, fileInfo) +} + +// @Tags Website Nginx +// @Summary Load nginx conf +// @Accept json +// @Param request body request.NginxScopeReq true "request" +// @Success 200 {object} response.WebsiteNginxConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/config [post] +func (b *BaseApi) GetNginxConfig(c *gin.Context) { + var req request.NginxScopeReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + config, err := websiteService.GetNginxConfigByScope(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, config) +} + +// @Tags Website Nginx +// @Summary Update nginx conf +// @Accept json +// @Param request body request.NginxConfigUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/config/update [post] +// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"nginx 配置修改 [domain]","formatEN":"Nginx conf update [domain]"} +func (b *BaseApi) UpdateNginxConfig(c *gin.Context) { + var req request.NginxConfigUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateNginxConfigByScope(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website HTTPS +// @Summary Load https conf +// @Accept json +// @Param id path integer true "request" +// @Success 200 {object} response.WebsiteHTTPS +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/:id/https [get] +func (b *BaseApi) GetHTTPSConfig(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + res, err := websiteService.GetWebsiteHTTPS(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website HTTPS +// @Summary Update https conf +// @Accept json +// @Param request body request.WebsiteHTTPSOp true "request" +// @Success 200 {object} response.WebsiteHTTPS +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/:id/https [post] +// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] https 配置","formatEN":"Update website https [domain] conf"} +func (b *BaseApi) UpdateHTTPSConfig(c *gin.Context) { + var req request.WebsiteHTTPSOp + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + tx, ctx := helper.GetTxAndContext() + res, err := websiteService.OpWebsiteHTTPS(ctx, req) + if err != nil { + tx.Rollback() + helper.InternalServer(c, err) + return + } + tx.Commit() + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Check before create website +// @Accept json +// @Param request body request.WebsiteInstallCheckReq true "request" +// @Success 200 {array} response.WebsitePreInstallCheck +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/check [post] +func (b *BaseApi) CreateWebsiteCheck(c *gin.Context) { + var req request.WebsiteInstallCheckReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + data, err := websiteService.PreInstallCheck(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Website Nginx +// @Summary Update website nginx conf +// @Accept json +// @Param request body request.WebsiteNginxUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/nginx/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain] Nginx 配置修改","formatEN":"[domain] Nginx conf update"} +func (b *BaseApi) UpdateWebsiteNginxConfig(c *gin.Context) { + var req request.WebsiteNginxUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateNginxConfigFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Operate website log +// @Accept json +// @Param request body request.WebsiteLogReq true "request" +// @Success 200 {object} response.WebsiteLog +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/log [post] +// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"[domain][operate] 日志","formatEN":"[domain][operate] logs"} +func (b *BaseApi) OpWebsiteLog(c *gin.Context) { + var req request.WebsiteLogReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteService.OpWebsiteLog(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Change default server +// @Accept json +// @Param request body request.WebsiteDefaultUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/default/server [post] +// @x-panel-log {"bodyKeys":["id", "operate"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改默认 server => [domain]","formatEN":"Change default server => [domain]"} +func (b *BaseApi) ChangeDefaultServer(c *gin.Context) { + var req request.WebsiteDefaultUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.ChangeDefaultServer(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website PHP +// @Summary Update php version +// @Accept json +// @Param request body request.WebsitePHPVersionReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/php/version [post] +// @x-panel-log {"bodyKeys":["websiteId"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteId","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"php 版本变更 [domain]","formatEN":"php version update [domain]"} +func (b *BaseApi) ChangePHPVersion(c *gin.Context) { + var req request.WebsitePHPVersionReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.ChangePHPVersion(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get rewrite conf +// @Accept json +// @Param request body request.NginxRewriteReq true "request" +// @Success 200 {object} response.NginxRewriteRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/rewrite [post] +func (b *BaseApi) GetRewriteConfig(c *gin.Context) { + var req request.NginxRewriteReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteService.GetRewriteConfig(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Update rewrite conf +// @Accept json +// @Param request body request.NginxRewriteUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/rewrite/update [post] +// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"伪静态配置修改 [domain]","formatEN":"Nginx conf rewrite update [domain]"} +func (b *BaseApi) UpdateRewriteConfig(c *gin.Context) { + var req request.NginxRewriteUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateRewriteConfig(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Update Site Dir +// @Accept json +// @Param request body request.WebsiteUpdateDir true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/dir/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录","formatEN":"Update domain [domain] dir"} +func (b *BaseApi) UpdateSiteDir(c *gin.Context) { + var req request.WebsiteUpdateDir + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateSiteDir(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Update Site Dir permission +// @Accept json +// @Param request body request.WebsiteUpdateDirPermission true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/dir/permission [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新网站 [domain] 目录权限","formatEN":"Update domain [domain] dir permission"} +func (b *BaseApi) UpdateSiteDirPermission(c *gin.Context) { + var req request.WebsiteUpdateDirPermission + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateSitePermission(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get proxy conf +// @Accept json +// @Param request body request.WebsiteProxyReq true "request" +// @Success 200 {array} request.WebsiteProxyConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/proxies [post] +func (b *BaseApi) GetProxyConfig(c *gin.Context) { + var req request.WebsiteProxyReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteService.GetProxies(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Update proxy conf +// @Accept json +// @Param request body request.WebsiteProxyConfig true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/proxies/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 反向代理配置 ","formatEN":"Update domain [domain] proxy config"} +func (b *BaseApi) UpdateProxyConfig(c *gin.Context) { + var req request.WebsiteProxyConfig + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := websiteService.OperateProxy(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Update proxy file +// @Accept json +// @Param request body request.NginxProxyUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/proxies/file [post] +// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新反向代理文件 [domain]","formatEN":"Nginx conf proxy file update [domain]"} +func (b *BaseApi) UpdateProxyConfigFile(c *gin.Context) { + var req request.NginxProxyUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateProxyFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get AuthBasic conf +// @Accept json +// @Param request body request.NginxAuthReq true "request" +// @Success 200 {object} response.NginxAuthRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/auths [post] +func (b *BaseApi) GetAuthConfig(c *gin.Context) { + var req request.NginxAuthReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteService.GetAuthBasics(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Get AuthBasic conf +// @Accept json +// @Param request body request.NginxAuthUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/auths/update [post] +func (b *BaseApi) UpdateAuthConfig(c *gin.Context) { + var req request.NginxAuthUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateAuthBasic(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get AuthBasic conf +// @Accept json +// @Param request body request.NginxAuthReq true "request" +// @Success 200 {object} response.NginxPathAuthRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/auths/path [post] +func (b *BaseApi) GetPathAuthConfig(c *gin.Context) { + var req request.NginxAuthReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteService.GetPathAuthBasics(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Get AuthBasic conf +// @Accept json +// @Param request body request.NginxPathAuthUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/auths/path/update [post] +func (b *BaseApi) UpdatePathAuthConfig(c *gin.Context) { + var req request.NginxPathAuthUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdatePathAuthBasic(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get AntiLeech conf +// @Accept json +// @Param request body request.NginxCommonReq true "request" +// @Success 200 {object} response.NginxAntiLeechRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/leech [post] +func (b *BaseApi) GetAntiLeech(c *gin.Context) { + var req request.NginxCommonReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteService.GetAntiLeech(req.WebsiteID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Update AntiLeech +// @Accept json +// @Param request body request.NginxAntiLeechUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/leech/update [post] +func (b *BaseApi) UpdateAntiLeech(c *gin.Context) { + var req request.NginxAntiLeechUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateAntiLeech(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Update redirect conf +// @Accept json +// @Param request body request.NginxRedirectReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/redirect/update [post] +// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改网站 [domain] 重定向配置 ","formatEN":"Update domain [domain] redirect config"} +func (b *BaseApi) UpdateRedirectConfig(c *gin.Context) { + var req request.NginxRedirectReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := websiteService.OperateRedirect(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get redirect conf +// @Accept json +// @Param request body request.WebsiteProxyReq true "request" +// @Success 200 {array} response.NginxRedirectConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/redirect [post] +func (b *BaseApi) GetRedirectConfig(c *gin.Context) { + var req request.WebsiteRedirectReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteService.GetRedirect(req.WebsiteID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Update redirect file +// @Accept json +// @Param request body request.NginxRedirectUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/redirect/file [post] +// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新重定向文件 [domain]","formatEN":"Nginx conf redirect file update [domain]"} +func (b *BaseApi) UpdateRedirectConfigFile(c *gin.Context) { + var req request.NginxRedirectUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateRedirectFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get website dir +// @Accept json +// @Param request body request.WebsiteCommonReq true "request" +// @Success 200 {object} response.WebsiteDirConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/dir [post] +func (b *BaseApi) GetDirConfig(c *gin.Context) { + var req request.WebsiteCommonReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteService.LoadWebsiteDirConfig(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Get default html +// @Accept json +// @Success 200 {object} response.WebsiteHtmlRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/default/html/:type [get] +func (b *BaseApi) GetDefaultHtml(c *gin.Context) { + resourceType, err := helper.GetStrParamByKey(c, "type") + if err != nil { + helper.BadRequest(c, err) + return + } + fileInfo, err := websiteService.GetDefaultHtml(resourceType) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, fileInfo) +} + +// @Tags Website +// @Summary Update default html +// @Accept json +// @Param request body request.WebsiteHtmlUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/default/html/update [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新默认 html","formatEN":"Update default html"} +func (b *BaseApi) UpdateDefaultHtml(c *gin.Context) { + var req request.WebsiteHtmlUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateDefaultHtml(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get website upstreams +// @Accept json +// @Param request body request.WebsiteCommonReq true "request" +// @Success 200 {array} dto.NginxUpstream +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/lbs [get] +func (b *BaseApi) GetLoadBalances(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + res, err := websiteService.GetLoadBalances(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Create website upstream +// @Accept json +// @Param request body request.WebsiteLBCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/lbs/create [post] +func (b *BaseApi) CreateLoadBalance(c *gin.Context) { + var req request.WebsiteLBCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.CreateLoadBalance(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Delete website upstream +// @Accept json +// @Param request body request.WebsiteLBDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/lbs/del [post] +func (b *BaseApi) DeleteLoadBalance(c *gin.Context) { + var req request.WebsiteLBDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.DeleteLoadBalance(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Update website upstream +// @Accept json +// @Param request body request.WebsiteLBUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/lbs/update [post] +func (b *BaseApi) UpdateLoadBalance(c *gin.Context) { + var req request.WebsiteLBUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateLoadBalance(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Update website upstream file +// @Accept json +// @Param request body request.WebsiteLBUpdateFile true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/lbs/file [post] +func (b *BaseApi) UpdateLoadBalanceFile(c *gin.Context) { + var req request.WebsiteLBUpdateFile + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateLoadBalanceFile(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) ChangeWebsiteGroup(c *gin.Context) { + var req dto.UpdateGroup + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.ChangeGroup(req.Group, req.NewGroup); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary update website proxy cache config +// @Accept json +// @Param request body request.NginxProxyCacheUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/proxy/config [post] +func (b *BaseApi) UpdateProxyCache(c *gin.Context) { + var req request.NginxProxyCacheUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateProxyCache(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Summary Get website proxy cache config +// @Accept json +// @Param id path int true "id" +// @Success 200 {object} response.NginxProxyCache +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/proxy/config/{id} [get] +func (b *BaseApi) GetProxyCache(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + res, err := websiteService.GetProxyCache(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Set Real IP +// @Accept json +// @Param request body request.WebsiteRealIP true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/realip/config [post] +// @x-panel-log {"bodyKeys":["websiteID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"websiteID","isList":false,"db":"websites","output_column":"primary_domain","output_value":"domain"}],"formatZH":"修改 [domain] 网站真实IP配置 ","formatEN":"Modify the real IP configuration of [domain] website"} +func (b *BaseApi) SetRealIPConfig(c *gin.Context) { + var req request.WebsiteRealIP + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.SetRealIPConfig(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get Real IP Config +// @Accept json +// @Param id path int true "id" +// @Success 200 {object} response.WebsiteRealIP +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/realip/config/{id} [get] +func (b *BaseApi) GetRealIPConfig(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + res, err := websiteService.GetRealIPConfig(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Get website resource +// @Accept json +// @Param id path int true "id" +// @Success 200 {object} response.Resource +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/resource/{id} [get] +func (b *BaseApi) GetWebsiteResource(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + res, err := websiteService.GetWebsiteResource(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Get databases +// @Accept json +// @Success 200 {object} response.Database +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/databases [get] +func (b *BaseApi) GetWebsiteDatabase(c *gin.Context) { + res, err := websiteService.ListDatabases() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Change website database +// @Accept json +// @Param request body request.ChangeDatabase true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/databases [post] +func (b *BaseApi) ChangeWebsiteDatabase(c *gin.Context) { + var req request.ChangeDatabase + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.ChangeDatabase(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Operate custom rewrite +// @Accept json +// @Param request body request.CustomRewriteOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/rewrite/custom [post] +func (b *BaseApi) OperateCustomRewrite(c *gin.Context) { + var req request.CustomRewriteOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.OperateCustomRewrite(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary List custom rewrite +// @Accept json +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/rewrite/custom [get] +func (b *BaseApi) ListCustomRewrite(c *gin.Context) { + res, err := websiteService.ListCustomRewrite() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Clear Website proxy cache +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/proxy/clear [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清理 Openresty 代理缓存","formatEN":"Clear nginx proxy cache"} +func (b *BaseApi) ClearProxyCache(c *gin.Context) { + var req request.NginxCommonReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.ClearProxyCache(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Operate Cross Site Access +// @Accept json +// @Param request body request.CrossSiteAccessOp true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/crosssite [post] +func (b *BaseApi) OperateCrossSiteAccess(c *gin.Context) { + var req request.CrossSiteAccessOp + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.OperateCrossSiteAccess(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Exec Composer +// @Accept json +// @Param request body request.ExecComposerReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/exec/composer [post] +func (b *BaseApi) ExecComposer(c *gin.Context) { + var req request.ExecComposerReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.ExecComposer(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Batch operate websites +// @Accept json +// @Param request body request.BatchWebsiteOp true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/batch/operate [post] +func (b *BaseApi) BatchOpWebsites(c *gin.Context) { + var req request.BatchWebsiteOp + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.BatchOpWebsite(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Batch set website group +// @Accept json +// @Param request body request.BatchWebsiteGroup true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/batch/group [post] +func (b *BaseApi) BatchSetWebsiteGroup(c *gin.Context) { + var req request.BatchWebsiteGroup + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.BatchSetGroup(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Get CORS Config +// @Accept json +// @Param id path int true "id" +// @Success 200 {object} request.CorsConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/cors/{id} [get] +func (b *BaseApi) GetCORSConfig(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + res, err := websiteService.GetCors(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website +// @Summary Update CORS Config +// @Accept json +// @Param request body request.CorsConfigReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/cors/update [post] +func (b *BaseApi) UpdateCORSConfig(c *gin.Context) { + var req request.CorsConfigReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateCors(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Update Stream Config +// @Accept json +// @Param request body request.StreamUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/stream/update [post] +func (b *BaseApi) UpdateStreamConfig(c *gin.Context) { + var req request.StreamUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateStream(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website +// @Summary Batch set HTTPS for websites +// @Accept json +// @Param request body request.BatchWebsiteHttps true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/batch/https [post] +func (b *BaseApi) BatchSetHttps(c *gin.Context) { + var req request.BatchWebsiteHttps + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.BatchSetHttps(c, req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/website_acme_account.go b/agent/app/api/v2/website_acme_account.go new file mode 100644 index 0000000..79fa4de --- /dev/null +++ b/agent/app/api/v2/website_acme_account.go @@ -0,0 +1,97 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags Website Acme +// @Summary Page website acme accounts +// @Accept json +// @Param request body dto.PageInfo true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/acme/search [post] +func (b *BaseApi) PageWebsiteAcmeAccount(c *gin.Context) { + var req dto.PageInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, accounts, err := websiteAcmeAccountService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: accounts, + }) +} + +// @Tags Website Acme +// @Summary Create website acme account +// @Accept json +// @Param request body request.WebsiteAcmeAccountCreate true "request" +// @Success 200 {object} response.WebsiteAcmeAccountDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/acme [post] +// @x-panel-log {"bodyKeys":["email"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 acme [email]","formatEN":"Create website acme [email]"} +func (b *BaseApi) CreateWebsiteAcmeAccount(c *gin.Context) { + var req request.WebsiteAcmeAccountCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteAcmeAccountService.Create(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website Acme +// @Summary Delete website acme account +// @Accept json +// @Param request body request.WebsiteResourceReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/acme/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_acme_accounts","output_column":"email","output_value":"email"}],"formatZH":"删除网站 acme [email]","formatEN":"Delete website acme [email]"} +func (b *BaseApi) DeleteWebsiteAcmeAccount(c *gin.Context) { + var req request.WebsiteResourceReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteAcmeAccountService.Delete(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website Acme +// @Summary Update website acme account +// @Accept json +// @Param request body request.WebsiteAcmeAccountUpdate true "request" +// @Success 200 {object} response.WebsiteAcmeAccountDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/acme/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_acme_accounts","output_column":"email","output_value":"email"}],"formatZH":"更新 acme [email]","formatEN":"Update acme [email]"} +func (b *BaseApi) UpdateWebsiteAcmeAccount(c *gin.Context) { + var req request.WebsiteAcmeAccountUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteAcmeAccountService.Update(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} diff --git a/agent/app/api/v2/website_ca.go b/agent/app/api/v2/website_ca.go new file mode 100644 index 0000000..183517b --- /dev/null +++ b/agent/app/api/v2/website_ca.go @@ -0,0 +1,178 @@ +package v2 + +import ( + "net/http" + "net/url" + "strconv" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags Website CA +// @Summary Page website ca +// @Accept json +// @Param request body request.WebsiteCASearch true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ca/search [post] +func (b *BaseApi) PageWebsiteCA(c *gin.Context) { + var req request.WebsiteCASearch + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, cas, err := websiteCAService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: cas, + }) +} + +// @Tags Website CA +// @Summary Create website ca +// @Accept json +// @Param request body request.WebsiteCACreate true "request" +// @Success 200 {object} request.WebsiteCACreate +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ca [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 ca [name]","formatEN":"Create website ca [name]"} +func (b *BaseApi) CreateWebsiteCA(c *gin.Context) { + var req request.WebsiteCACreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteCAService.Create(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website CA +// @Summary Get website ca +// @Accept json +// @Param id path int true "id" +// @Success 200 {object} response.WebsiteCADTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ca/{id} [get] +func (b *BaseApi) GetWebsiteCA(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + return + } + res, err := websiteCAService.GetCA(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website CA +// @Summary Delete website ca +// @Accept json +// @Param request body request.WebsiteCommonReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ca/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_cas","output_column":"name","output_value":"name"}],"formatZH":"删除网站 ca [name]","formatEN":"Delete website ca [name]"} +func (b *BaseApi) DeleteWebsiteCA(c *gin.Context) { + var req request.WebsiteCommonReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteCAService.Delete(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website CA +// @Summary Obtain SSL +// @Accept json +// @Param request body request.WebsiteCAObtain true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ca/obtain [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_cas","output_column":"name","output_value":"name"}],"formatZH":"自签 SSL 证书 [name]","formatEN":"Obtain SSL [name]"} +func (b *BaseApi) ObtainWebsiteCA(c *gin.Context) { + var req request.WebsiteCAObtain + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteCAService.ObtainSSL(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website CA +// @Summary Obtain SSL +// @Accept json +// @Param request body request.WebsiteCAObtain true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ca/renew [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_cas","output_column":"name","output_value":"name"}],"formatZH":"自签 SSL 证书 [name]","formatEN":"Obtain SSL [name]"} +func (b *BaseApi) RenewWebsiteCA(c *gin.Context) { + var req request.WebsiteCARenew + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if _, err := websiteCAService.ObtainSSL(request.WebsiteCAObtain{ + SSLID: req.SSLID, + Renew: true, + Unit: "year", + Time: 1, + }); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website CA +// @Summary Download CA file +// @Accept json +// @Param request body request.WebsiteResourceReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ca/download [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_cas","output_column":"name","output_value":"name"}],"formatZH":"下载 CA 证书文件 [name]","formatEN":"download ca file [name]"} +func (b *BaseApi) DownloadCAFile(c *gin.Context) { + var req request.WebsiteResourceReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + file, err := websiteCAService.DownloadFile(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + defer file.Close() + info, err := file.Stat() + if err != nil { + helper.InternalServer(c, err) + return + } + c.Header("Content-Length", strconv.FormatInt(info.Size(), 10)) + c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) + http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file) +} diff --git a/agent/app/api/v2/website_dns_account.go b/agent/app/api/v2/website_dns_account.go new file mode 100644 index 0000000..2101012 --- /dev/null +++ b/agent/app/api/v2/website_dns_account.go @@ -0,0 +1,95 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags Website DNS +// @Summary Page website dns accounts +// @Accept json +// @Param request body dto.PageInfo true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/dns/search [post] +func (b *BaseApi) PageWebsiteDnsAccount(c *gin.Context) { + var req dto.PageInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + total, accounts, err := websiteDnsAccountService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, dto.PageResult{ + Total: total, + Items: accounts, + }) +} + +// @Tags Website DNS +// @Summary Create website dns account +// @Accept json +// @Param request body request.WebsiteDnsAccountCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/dns [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 dns [name]","formatEN":"Create website dns [name]"} +func (b *BaseApi) CreateWebsiteDnsAccount(c *gin.Context) { + var req request.WebsiteDnsAccountCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if _, err := websiteDnsAccountService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website DNS +// @Summary Update website dns account +// @Accept json +// @Param request body request.WebsiteDnsAccountUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/dns/update [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新网站 dns [name]","formatEN":"Update website dns [name]"} +func (b *BaseApi) UpdateWebsiteDnsAccount(c *gin.Context) { + var req request.WebsiteDnsAccountUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if _, err := websiteDnsAccountService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website DNS +// @Summary Delete website dns account +// @Accept json +// @Param request body request.WebsiteResourceReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/dns/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_dns_accounts","output_column":"name","output_value":"name"}],"formatZH":"删除网站 dns [name]","formatEN":"Delete website dns [name]"} +func (b *BaseApi) DeleteWebsiteDnsAccount(c *gin.Context) { + var req request.WebsiteResourceReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteDnsAccountService.Delete(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/website_domain.go b/agent/app/api/v2/website_domain.go new file mode 100644 index 0000000..99c8c89 --- /dev/null +++ b/agent/app/api/v2/website_domain.go @@ -0,0 +1,93 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags Website Domain +// @Summary Delete website domain +// @Accept json +// @Param request body request.WebsiteDomainDelete true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/domains/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_domains","output_column":"domain","output_value":"domain"}],"formatZH":"删除域名 [domain]","formatEN":"Delete domain [domain]"} +func (b *BaseApi) DeleteWebDomain(c *gin.Context) { + var req request.WebsiteDomainDelete + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.DeleteWebsiteDomain(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website Domain +// @Summary Create website domain +// @Accept json +// @Param request body request.WebsiteDomainCreate true "request" +// @Success 200 {object} model.WebsiteDomain +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/domains [post] +// @x-panel-log {"bodyKeys":["domain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建域名 [domain]","formatEN":"Create domain [domain]"} +func (b *BaseApi) CreateWebDomain(c *gin.Context) { + var req request.WebsiteDomainCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + domain, err := websiteService.CreateWebsiteDomain(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, domain) +} + +// @Tags Website Domain +// @Summary Search website domains by websiteId +// @Accept json +// @Param websiteId path integer true "request" +// @Success 200 {array} model.WebsiteDomain +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/domains/:websiteId [get] +func (b *BaseApi) GetWebDomains(c *gin.Context) { + websiteId, err := helper.GetIntParamByKey(c, "websiteId") + if err != nil { + helper.BadRequest(c, err) + return + } + list, err := websiteService.GetWebsiteDomain(websiteId) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags Website Domain +// @Summary Update website domain +// @Accept json +// @Param request body request.WebsiteDomainUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/domains/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_domains","output_column":"domain","output_value":"domain"}],"formatZH":"更新域名 [domain]","formatEN":"Update domain [domain]"} +func (b *BaseApi) UpdateWebDomain(c *gin.Context) { + var req request.WebsiteDomainUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteService.UpdateWebsiteDomain(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/api/v2/website_ssl.go b/agent/app/api/v2/website_ssl.go new file mode 100644 index 0000000..b00d71f --- /dev/null +++ b/agent/app/api/v2/website_ssl.go @@ -0,0 +1,335 @@ +package v2 + +import ( + "io" + "mime/multipart" + "net/http" + "net/url" + "reflect" + "strconv" + + "github.com/1Panel-dev/1Panel/agent/app/model" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/gin-gonic/gin" +) + +// @Tags Website SSL +// @Summary Page website ssl +// @Accept json +// @Param request body request.WebsiteSSLSearch true "request" +// @Success 200 {array} response.WebsiteSSLDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/search [post] +func (b *BaseApi) PageWebsiteSSL(c *gin.Context) { + var req request.WebsiteSSLSearch + if err := helper.CheckBind(&req, c); err != nil { + return + } + if !reflect.DeepEqual(req.PageInfo, dto.PageInfo{}) { + total, accounts, err := websiteSSLService.Page(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithDataGzipped(c, dto.PageResult{ + Total: total, + Items: accounts, + }) + } else { + list, err := websiteSSLService.Search(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithDataGzipped(c, list) + } +} + +// @Tags Website SSL +// @Summary Create website ssl +// @Accept json +// @Param request body request.WebsiteSSLCreate true "request" +// @Success 200 {object} request.WebsiteSSLCreate +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl [post] +// @x-panel-log {"bodyKeys":["primaryDomain"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建网站 ssl [primaryDomain]","formatEN":"Create website ssl [primaryDomain]"} +func (b *BaseApi) CreateWebsiteSSL(c *gin.Context) { + var req request.WebsiteSSLCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteSSLService.Create(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website SSL +// @Summary Apply ssl +// @Accept json +// @Param request body request.WebsiteSSLApply true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/obtain [post] +// @x-panel-log {"bodyKeys":["ID"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ID","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"申请证书 [domain]","formatEN":"apply ssl [domain]"} +func (b *BaseApi) ApplyWebsiteSSL(c *gin.Context) { + var req request.WebsiteSSLApply + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteSSLService.ObtainSSL(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website SSL +// @Summary Resolve website ssl +// @Accept json +// @Param request body request.WebsiteDNSReq true "request" +// @Success 200 {array} response.WebsiteDNSRes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/resolve [post] +func (b *BaseApi) GetDNSResolve(c *gin.Context) { + var req request.WebsiteDNSReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, err := websiteSSLService.GetDNSResolve(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Website SSL +// @Summary Delete website ssl +// @Accept json +// @Param request body request.WebsiteBatchDelReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"删除 ssl [domain]","formatEN":"Delete ssl [domain]"} +func (b *BaseApi) DeleteWebsiteSSL(c *gin.Context) { + var req request.WebsiteBatchDelReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteSSLService.Delete(req.IDs); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website SSL +// @Summary Search website ssl by website id +// @Accept json +// @Param websiteId path integer true "request" +// @Success 200 {object} response.WebsiteSSLDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/website/:websiteId [get] +func (b *BaseApi) GetWebsiteSSLByWebsiteId(c *gin.Context) { + websiteId, err := helper.GetIntParamByKey(c, "websiteId") + if err != nil { + helper.BadRequest(c, err) + return + } + websiteSSL, err := websiteSSLService.GetWebsiteSSL(websiteId) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, websiteSSL) +} + +// @Tags Website SSL +// @Summary Search website ssl by id +// @Accept json +// @Param id path integer true "request" +// @Success 200 {object} response.WebsiteSSLDTO +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/:id [get] +func (b *BaseApi) GetWebsiteSSLById(c *gin.Context) { + id, err := helper.GetParamID(c) + if err != nil { + helper.BadRequest(c, err) + return + } + websiteSSL, err := websiteSSLService.GetSSL(id) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, websiteSSL) +} + +// @Tags Website SSL +// @Summary Update ssl +// @Accept json +// @Param request body request.WebsiteSSLUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"更新证书设置 [domain]","formatEN":"Update ssl config [domain]"} +func (b *BaseApi) UpdateWebsiteSSL(c *gin.Context) { + var req request.WebsiteSSLUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteSSLService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website SSL +// @Summary Upload ssl +// @Accept json +// @Param request body request.WebsiteSSLUpload true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/upload [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传 ssl [type]","formatEN":"Upload ssl [type]"} +func (b *BaseApi) UploadWebsiteSSL(c *gin.Context) { + var req request.WebsiteSSLUpload + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteSSLService.Upload(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Website SSL +// @Summary Upload SSL file +// @Accept multipart/form-data +// @Param type formData string true "type" +// @Param description formData string false "description" +// @Param sslID formData string false "sslID" +// @Param privateKeyFile formData file true "privateKeyFile" +// @Param certificateFile formData file true "certificateFile" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/upload/file [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传 ssl 文件 [type]","formatEN":"Upload ssl file [type]"} +func (b *BaseApi) UploadSSLFile(c *gin.Context) { + var req request.WebsiteSSLFileUpload + + req.Description = c.PostForm("description") + sslID := c.PostForm("sslID") + if sslID != "" { + req.SSLID, _ = strconv.ParseUint(sslID, 10, 64) + } + + privateKeyFile, err := c.FormFile("privateKeyFile") + if err != nil { + helper.InternalServer(c, err) + return + } + + certificateFile, err := c.FormFile("certificateFile") + if err != nil { + helper.InternalServer(c, err) + return + } + + privateKeyContent, err := readUploadedFile(privateKeyFile) + if err != nil { + helper.InternalServer(c, err) + return + } + + certificateContent, err := readUploadedFile(certificateFile) + if err != nil { + helper.InternalServer(c, err) + return + } + + uploadReq := request.WebsiteSSLUpload{ + Type: "paste", + PrivateKey: string(privateKeyContent), + Certificate: string(certificateContent), + Description: req.Description, + SSLID: uint(req.SSLID), + } + + if err := websiteSSLService.Upload(uploadReq); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +func readUploadedFile(fileHeader *multipart.FileHeader) ([]byte, error) { + file, err := fileHeader.Open() + if err != nil { + return nil, err + } + defer file.Close() + + return io.ReadAll(file) +} + +// @Tags Website SSL +// @Summary Download SSL file +// @Accept json +// @Param request body request.WebsiteResourceReq true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /websites/ssl/download [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"website_ssls","output_column":"primary_domain","output_value":"domain"}],"formatZH":"下载证书文件 [domain]","formatEN":"download ssl file [domain]"} +func (b *BaseApi) DownloadWebsiteSSL(c *gin.Context) { + var req request.WebsiteResourceReq + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + file, err := websiteSSLService.DownloadFile(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + defer file.Close() + info, err := file.Stat() + if err != nil { + helper.InternalServer(c, err) + return + } + c.Header("Content-Length", strconv.FormatInt(info.Size(), 10)) + c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) + http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), file) +} + +func (b *BaseApi) ImportMasterSSL(c *gin.Context) { + var req model.WebsiteSSL + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := websiteSSLService.ImportMasterSSL(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/agent/app/dto/ai.go b/agent/app/dto/ai.go new file mode 100644 index 0000000..9e9525f --- /dev/null +++ b/agent/app/dto/ai.go @@ -0,0 +1,46 @@ +package dto + +import "time" + +type OllamaModelInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Size string `json:"size"` + From string `json:"from"` + LogFileExist bool `json:"logFileExist"` + + Status string `json:"status"` + Message string `json:"message"` + CreatedAt time.Time `json:"createdAt"` +} + +type OllamaModelDropList struct { + ID uint `json:"id"` + Name string `json:"name"` +} + +type OllamaModelName struct { + Name string `json:"name"` + TaskID string `json:"taskID"` +} + +type OllamaBindDomain struct { + Domain string `json:"domain" validate:"required"` + AppInstallID uint `json:"appInstallID" validate:"required"` + SSLID uint `json:"sslID"` + WebsiteID uint `json:"websiteID"` + IPList string `json:"ipList"` +} + +type OllamaBindDomainReq struct { + AppInstallID uint `json:"appInstallID" validate:"required"` +} + +type OllamaBindDomainRes struct { + Domain string `json:"domain"` + SSLID uint `json:"sslID"` + AllowIPs []string `json:"allowIPs"` + WebsiteID uint `json:"websiteID"` + ConnUrl string `json:"connUrl"` + AcmeAccountID uint `json:"acmeAccountID"` +} diff --git a/agent/app/dto/alert.go b/agent/app/dto/alert.go new file mode 100644 index 0000000..e9695b4 --- /dev/null +++ b/agent/app/dto/alert.go @@ -0,0 +1,337 @@ +package dto + +import ( + "encoding/json" + "github.com/1Panel-dev/1Panel/agent/app/model" + "time" +) + +type CreateOrUpdateAlert struct { + AlertTitle string `json:"alertTitle"` + AlertType string `json:"alertType"` + AlertMethod string `json:"alertMethod"` + AlertCount uint `json:"alertCount"` + EntryID uint `json:"entryID"` +} + +type AlertBase struct { + AlertType string `json:"alertType"` + EntryID uint `json:"entryID"` +} + +type PushAlert struct { + TaskName string `json:"taskName"` + AlertType string `json:"alertType"` + EntryID uint `json:"entryID"` + Param string `json:"param"` +} + +type AlertSearch struct { + PageInfo + OrderBy string `json:"orderBy" validate:"required,oneof=created_at"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` + Type string `json:"type"` + Status string `json:"status"` + Method string `json:"method"` +} + +type AlertDTO struct { + ID uint `json:"id"` + Type string `json:"type"` + Cycle uint `json:"cycle"` + Count uint `json:"count"` + Method string `json:"method"` + Title string `json:"title"` + Project string `json:"project"` + Status string `json:"status"` + SendCount uint `json:"sendCount"` + AdvancedParams string `json:"advancedParams"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type AlertCreate struct { + Type string `json:"type" validate:"required"` + Cycle uint `json:"cycle"` + Count uint `json:"count"` + Method string `json:"method" validate:"required"` + Title string `json:"title"` + Project string `json:"project"` + Status string `json:"status"` + SendCount uint `json:"sendCount"` + AdvancedParams string `json:"advancedParams"` +} + +type AlertUpdate struct { + ID uint `json:"id" validate:"required"` + Type string `json:"type"` + Cycle uint `json:"cycle"` + Count uint `json:"count"` + Method string `json:"method"` + Title string `json:"title"` + Project string `json:"project"` + Status string `json:"status"` + SendCount uint `json:"sendCount"` + AdvancedParams string `json:"advancedParams"` +} + +type DeleteRequest struct { + ID uint `json:"id" validate:"required"` +} + +type AlertUpdateStatus struct { + ID uint `json:"id" validate:"required"` + Status string `json:"status" validate:"required"` +} + +type DiskDTO struct { + Path string `json:"path"` + Type string `json:"type"` + Device string `json:"device"` + Total uint64 `json:"total"` + Free uint64 `json:"free"` + Used uint64 `json:"used"` + UsedPercent float64 `json:"usedPercent"` + + InodesTotal uint64 `json:"inodesTotal"` + InodesUsed uint64 `json:"inodesUsed"` + InodesFree uint64 `json:"inodesFree"` + InodesUsedPercent float64 `json:"inodesUsedPercent"` +} + +type AlertLogSearch struct { + PageInfo + Count uint `json:"count"` + Status string `json:"status"` +} + +type AlertLogDTO struct { + ID uint `json:"id"` + Type string `json:"type"` + Count uint `json:"count"` + AlertId uint `json:"alertId"` + AlertDetail AlertDetail `json:"alertDetail"` + AlertRule AlertRule `json:"alertRule"` + Status string `json:"status"` + Method string `json:"method"` + Message string `json:"message"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type AlertLogCreate struct { + Type string `json:"type" validate:"required"` + Count uint `json:"count" validate:"required"` + AlertId uint `json:"alertId" validate:"required"` + AlertDetail string `json:"alertDetail" validate:"required"` + AlertRule string `json:"alertRule" validate:"required"` + Status string `json:"status" validate:"required"` + Method string `json:"method" validate:"required"` + Message string `json:"message"` + RecordId uint `json:"recordId"` + LicenseId string `json:"licenseId" validate:"required"` +} + +type AlertLog struct { + ID uint `json:"id" validate:"required"` +} + +type AlertDetail struct { + LicenseId string `json:"licenseId"` + Type string `json:"type"` + SubType string `json:"subType"` + Title string `json:"title"` + Method string `json:"method"` + LicenseCode string `json:"licenseCode"` + DeviceId string `json:"deviceId"` + Project string `json:"project"` + Params []Param `json:"params"` + Phone string `json:"phone"` +} + +type AlertRule struct { + ID uint `json:"id"` + Type string `json:"type"` + Cycle uint `json:"cycle"` + Count uint `json:"count"` + Method string `json:"method"` + Title string `json:"title"` + Project string `json:"project"` + Status string `json:"status"` + SendCount uint `json:"sendCount"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type Param struct { + Index string `json:"index"` + Key string `json:"key"` + Value string `json:"value"` +} + +type ClamDTO struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Name string `json:"name"` + Status string `json:"status"` + Path string `json:"path"` +} + +type CronJobDTO struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Name string `json:"name"` + Status string `json:"status"` + Type string `json:"type"` +} + +type CronJobReq struct { + Name string `json:"name"` + Status string `json:"status"` + Type string `json:"type"` +} + +type UpgradeInfo struct { + TestVersion string `json:"testVersion"` + NewVersion string `json:"newVersion"` + LatestVersion string `json:"latestVersion"` + ReleaseNote string `json:"releaseNote"` +} + +type AlertDiskInfo struct { + Type string + Mount string + Device string +} + +type SyncResult struct { + ID uint `json:"id"` + LicenseID string `json:"licenseId"` + SendStatus string `json:"sendStatus"` + CreateTime string `json:"createTime"` + UpdateTime string `json:"updateTime"` + Remarks string `json:"remarks"` + MsgCount int `json:"msgCount"` + MsgCountMax int `json:"msgCountMax"` +} + +type QueryRequest struct { + QueryIds []uint `json:"queryIds"` + LicenseId string `json:"licenseId"` +} + +type AlertResponse struct { + Result bool `json:"result"` + Data json.RawMessage `json:"data"` + Message string `json:"message"` +} + +type PushResult struct { + RecordId uint `json:"recordId"` + LicenseId string `json:"licenseId"` +} + +type UpdateOfflineAlertLog struct { + ID uint `json:"id"` + Status string `json:"status"` + Message string `json:"message"` + RecordId uint `json:"recordId"` + LicenseId string `json:"licenseId"` +} + +type SyncOfflineAlertLogDTO struct { + QueryRequest string `json:"queryRequest"` + Ids []uint `json:"ids"` + AlertLogs []model.AlertLog `json:"alertLogs"` + LicenseId string `json:"licenseId"` +} + +type OfflineAlertResponse struct { + ID uint `json:"id"` + Type string `json:"type"` + RemoteErr error `json:"remoteErr"` + ResponseStruct AlertResponse `json:"responseStruct"` + AlertLogs []model.AlertLog `json:"alertLogs"` + Ids []uint `json:"ids"` + LicenseId string `json:"licenseId"` +} + +type OfflineAlertLogDTO struct { + ID uint `json:"id"` + Type string `json:"type"` + Count uint `json:"count"` + AlertId uint `json:"alertId"` + AlertDetail string `json:"alertDetail"` + AlertRule string `json:"alertRule"` + Status string `json:"status"` + Method string `json:"method"` + Message string `json:"message"` + RecordId uint `json:"recordId"` + LicenseId string `json:"licenseId"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} + +type OfflineQueryRequest struct { + Ids []uint `json:"ids"` + QueryIds []uint `json:"queryIds"` + LicenseId string `json:"licenseId"` +} + +type AlertConfigUpdate struct { + ID uint `json:"id"` + Type string `json:"type"` + Title string `json:"title"` + Status string `json:"status"` + Config string `json:"config"` +} + +type AlertConfigTest struct { + Host string `json:"host"` + Port int `json:"port"` + Sender string `json:"sender"` + UserName string `json:"userName"` + Password string `json:"password"` + DisplayName string `json:"displayName"` + Encryption string `json:"encryption"` // "ssl" / "tls" / "none" + Recipient string `json:"recipient"` +} + +type AlertSendTimeRange struct { + NoticeAlert struct { + SendTimeRange string `json:"sendTimeRange"` + Type []string `json:"type"` + } `json:"noticeAlert"` + ResourceAlert struct { + SendTimeRange string `json:"sendTimeRange"` + Type []string `json:"type"` + } `json:"resourceAlert"` +} + +type AlertCommonConfig struct { + IsOffline string `json:"isOffline"` + AlertSendTimeRange AlertSendTimeRange `json:"alertSendTimeRange"` +} + +type AlertSmsConfig struct { + Phone string `json:"phone"` + AlertDailyNum string `json:"alertDailyNum"` +} + +type AlertEmailConfig struct { + Host string `json:"host"` + Port int `json:"port"` + Sender string `json:"sender"` + UserName string `json:"userName"` + Password string `json:"password"` + DisplayName string `json:"displayName"` + Encryption string `json:"encryption"` // "ssl" / "tls" / "none" + Recipient string `json:"recipient"` +} + +type AgentInfo struct { + NodeName string `json:"nodeName"` + NodeAddr string `json:"nodeAddr"` +} diff --git a/agent/app/dto/app.go b/agent/app/dto/app.go new file mode 100644 index 0000000..e013976 --- /dev/null +++ b/agent/app/dto/app.go @@ -0,0 +1,191 @@ +package dto + +import ( + "context" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/task" +) + +type AppDatabase struct { + ServiceName string `json:"PANEL_DB_HOST"` + DbName string `json:"PANEL_DB_NAME"` + DbUser string `json:"PANEL_DB_USER"` + Password string `json:"PANEL_DB_USER_PASSWORD"` + DatabaseName string `json:"DATABASE_NAME"` + Format string `json:"format"` + Collation string `json:"collation"` +} + +type AuthParam struct { + RootPassword string `json:"PANEL_DB_ROOT_PASSWORD"` + RootUser string `json:"PANEL_DB_ROOT_USER"` +} + +type RedisAuthParam struct { + RootPassword string `json:"PANEL_REDIS_ROOT_PASSWORD"` +} + +type MinioAuthParam struct { + RootPassword string `json:"PANEL_MINIO_ROOT_PASSWORD"` + RootUser string `json:"PANEL_MINIO_ROOT_USER"` +} + +type ContainerExec struct { + ContainerName string `json:"containerName"` + DbParam AppDatabase `json:"dbParam"` + Auth AuthParam `json:"auth"` +} + +type AppOssConfig struct { + Version string `json:"version"` + Package string `json:"package"` +} + +type AppVersion struct { + Version string `json:"version"` + DetailId uint `json:"detailId"` + DockerCompose string `json:"dockerCompose"` +} + +type AppList struct { + Valid bool `json:"valid"` + Violations []string `json:"violations"` + LastModified int `json:"lastModified"` + + Apps []AppDefine `json:"apps"` + Extra ExtraProperties `json:"additionalProperties"` +} + +type AppDefine struct { + Icon string `json:"icon"` + Name string `json:"name"` + ReadMe string `json:"readMe"` + LastModified int `json:"lastModified"` + + AppProperty AppProperty `json:"additionalProperties"` + Versions []AppConfigVersion `json:"versions"` +} + +type LocalAppAppDefine struct { + AppProperty AppProperty `json:"additionalProperties" yaml:"additionalProperties"` +} + +type LocalAppParam struct { + AppParams LocalAppInstallDefine `json:"additionalProperties" yaml:"additionalProperties"` +} + +type LocalAppInstallDefine struct { + FormFields interface{} `json:"formFields" yaml:"formFields"` +} + +type ExtraProperties struct { + Tags []Tag `json:"tags"` + Version string `json:"version"` +} + +type AppProperty struct { + Name string `json:"name"` + Type string `json:"type"` + Tags []string `json:"tags"` + ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"` + ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"` + Description Locale `json:"description"` + Key string `json:"key"` + Required []string `json:"Required"` + CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"` + Limit int `json:"limit" yaml:"limit"` + Recommend int `json:"recommend" yaml:"recommend"` + Website string `json:"website"` + Github string `json:"github"` + Document string `json:"document"` + Architectures []string `json:"architectures"` + MemoryRequired int `json:"memoryRequired" yaml:"memoryRequired"` + GpuSupport bool `json:"gpuSupport" yaml:"gpuSupport"` + Version float64 `json:"version"` + Deprecated float64 `json:"deprecated"` + BatchInstallSupport bool `json:"batchInstallSupport"` +} + +type AppConfigVersion struct { + Name string `json:"name"` + LastModified int `json:"lastModified"` + DownloadUrl string `json:"downloadUrl"` + DownloadCallBackUrl string `json:"downloadCallBackUrl"` + AppForm interface{} `json:"additionalProperties"` +} + +type Tag struct { + Key string `json:"key"` + Name string `json:"name"` + Sort int `json:"sort"` + Locales Locale `json:"locales"` +} + +type Locale struct { + En string `json:"en"` + Ja string `json:"ja"` + Ms string `json:"ms"` + PtBr string `json:"pt-br" yaml:"pt-br"` + Ru string `json:"ru"` + ZhHant string `json:"zh-hant" yaml:"zh-hant"` + Zh string `json:"zh"` + Ko string `json:"ko"` + Tr string `json:"tr"` + Es string `json:"es-es" yaml:"es-es"` +} + +type AppForm struct { + FormFields []AppFormFields `json:"formFields"` + SupportVersion float64 `json:"supportVersion"` +} + +type AppFormFields struct { + Type string `json:"type"` + LabelZh string `json:"labelZh"` + LabelEn string `json:"labelEn"` + Label Locale `json:"label"` + Description Locale `json:"description"` + Required bool `json:"required"` + Default interface{} `json:"default"` + EnvKey string `json:"envKey"` + Disabled bool `json:"disabled"` + Edit bool `json:"edit"` + Rule string `json:"rule"` + Multiple bool `json:"multiple"` + Child interface{} `json:"child"` + Values []AppFormValue `json:"values"` +} + +type AppFormValue struct { + Label string `json:"label"` + Value string `json:"value"` +} + +type AppResource struct { + Type string `json:"type"` + Name string `json:"name"` +} + +var AppToolMap = map[string]string{ + "mysql": "phpmyadmin", + "redis": "redis-commander", +} + +type AppInstallInfo struct { + ID uint `json:"id"` + Key string `json:"key"` + Name string `json:"name"` +} + +type DelAppLink struct { + Ctx context.Context + Task *task.Task + Install *model.AppInstall + ForceDelete bool +} + +type PHPForm struct { + AdditionalProperties struct { + FormFields []interface{} `yaml:"formFields"` + } `yaml:"additionalProperties"` +} diff --git a/agent/app/dto/backup.go b/agent/app/dto/backup.go new file mode 100644 index 0000000..c08c12d --- /dev/null +++ b/agent/app/dto/backup.go @@ -0,0 +1,136 @@ +package dto + +import ( + "time" +) + +type BackupOperate struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type" validate:"required"` + IsPublic bool `json:"isPublic"` + Bucket string `json:"bucket"` + AccessKey string `json:"accessKey"` + Credential string `json:"credential"` + BackupPath string `json:"backupPath"` + Vars string `json:"vars" validate:"required"` + + RememberAuth bool `json:"rememberAuth"` +} + +type BackupInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + IsPublic bool `json:"isPublic"` + Bucket string `json:"bucket"` + AccessKey string `json:"accessKey"` + Credential string `json:"credential"` + BackupPath string `json:"backupPath"` + Vars string `json:"vars"` + CreatedAt time.Time `json:"createdAt"` + + RememberAuth bool `json:"rememberAuth"` +} + +type BackupCheckRes struct { + IsOk bool `json:"isOk"` + Msg string `json:"msg"` + Token string `json:"token"` +} + +type ForBuckets struct { + Type string `json:"type" validate:"required"` + AccessKey string `json:"accessKey"` + Credential string `json:"credential" validate:"required"` + Vars string `json:"vars" validate:"required"` +} + +type SyncFromMaster struct { + Name string `json:"name" validate:"required"` + Operation string `json:"operation" validate:"required,oneof=create delete update"` + Data string `json:"data"` +} + +type BackupOption struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + IsPublic bool `json:"isPublic"` +} + +type UploadForRecover struct { + FilePath string `json:"filePath"` + TargetDir string `json:"targetDir"` +} + +type CommonBackup struct { + Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"` + Name string `json:"name"` + DetailName string `json:"detailName"` + Secret string `json:"secret"` + TaskID string `json:"taskID"` + FileName string `json:"fileName"` + Args []string `json:"args"` + + Description string `json:"description"` +} +type CommonRecover struct { + DownloadAccountID uint `json:"downloadAccountID" validate:"required"` + Type string `json:"type" validate:"required,oneof=app mysql mariadb redis website postgresql mysql-cluster postgresql-cluster redis-cluster"` + Name string `json:"name"` + DetailName string `json:"detailName"` + File string `json:"file"` + Secret string `json:"secret"` + TaskID string `json:"taskID"` + BackupRecordID uint `json:"backupRecordID"` + Timeout int `json:"timeout"` +} + +type RecordSearch struct { + PageInfo + Type string `json:"type" validate:"required"` + Name string `json:"name"` + DetailName string `json:"detailName"` +} + +type RecordSearchByCronjob struct { + PageInfo + CronjobID uint `json:"cronjobID" validate:"required"` +} + +type BackupRecords struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + AccountType string `json:"accountType"` + AccountName string `json:"accountName"` + DownloadAccountID uint `json:"downloadAccountID"` + FileDir string `json:"fileDir"` + FileName string `json:"fileName"` + TaskID string `json:"taskID"` + Status string `json:"status"` + Message string `json:"message"` + Description string `json:"description"` +} + +type DownloadRecord struct { + DownloadAccountID uint `json:"downloadAccountID" validate:"required"` + FileDir string `json:"fileDir" validate:"required"` + FileName string `json:"fileName" validate:"required"` +} + +type SearchForSize struct { + PageInfo + Type string `json:"type" validate:"required"` + Name string `json:"name"` + DetailName string `json:"detailName"` + Info string `json:"info"` + CronjobID uint `json:"cronjobID"` + OrderBy string `json:"orderBy"` + Order string `json:"order"` +} +type RecordFileSize struct { + ID uint `json:"id"` + Name string `json:"name"` + Size int64 `json:"size"` +} diff --git a/agent/app/dto/clam.go b/agent/app/dto/clam.go new file mode 100644 index 0000000..c69f72b --- /dev/null +++ b/agent/app/dto/clam.go @@ -0,0 +1,111 @@ +package dto + +import ( + "time" +) + +type SearchClamWithPage struct { + PageInfo + Info string `json:"info"` + OrderBy string `json:"orderBy" validate:"required,oneof=name status createdAt"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` +} + +type ClamBaseInfo struct { + Version string `json:"version"` + IsActive bool `json:"isActive"` + IsExist bool `json:"isExist"` + + FreshVersion string `json:"freshVersion"` + FreshIsActive bool `json:"freshIsActive"` + FreshIsExist bool `json:"freshIsExist"` +} + +type ClamInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + + Name string `json:"name"` + Status string `json:"status"` + Path string `json:"path"` + InfectedStrategy string `json:"infectedStrategy"` + InfectedDir string `json:"infectedDir"` + LastRecordStatus string `json:"lastRecordStatus"` + LastRecordTime string `json:"lastRecordTime"` + Spec string `json:"spec"` + Timeout uint `json:"timeout"` + Description string `json:"description"` + AlertCount uint `json:"alertCount"` + AlertMethod string `json:"alertMethod"` +} + +type ClamLogSearch struct { + PageInfo + + ClamID uint `json:"clamID"` + Status string `json:"status"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` +} + +type ClamLogReq struct { + Tail string `json:"tail"` + ClamName string `json:"clamName"` + RecordName string `json:"recordName"` +} + +type ClamFileReq struct { + Tail string `json:"tail"` + Name string `json:"name" validate:"required"` +} + +type ClamRecord struct { + ID uint `json:"id"` + TaskID string `json:"taskID"` + StartTime time.Time `json:"startTime"` + ScanTime string `json:"scanTime"` + InfectedFiles string `json:"infectedFiles"` + TotalError string `json:"totalError"` + + Status string `json:"status"` + Message string `json:"message"` +} + +type ClamCreate struct { + Name string `json:"name"` + Status string `json:"status"` + Path string `json:"path"` + InfectedStrategy string `json:"infectedStrategy"` + InfectedDir string `json:"infectedDir"` + Spec string `json:"spec"` + Timeout uint `json:"timeout"` + Description string `json:"description"` + AlertCount uint `json:"alertCount"` + AlertTitle string `json:"alertTitle"` + AlertMethod string `json:"alertMethod"` +} + +type ClamUpdate struct { + ID uint `json:"id"` + + Name string `json:"name"` + Path string `json:"path"` + InfectedStrategy string `json:"infectedStrategy"` + InfectedDir string `json:"infectedDir"` + Spec string `json:"spec"` + Timeout uint `json:"timeout"` + Description string `json:"description"` + AlertCount uint `json:"alertCount"` + AlertTitle string `json:"alertTitle"` + AlertMethod string `json:"alertMethod"` +} + +type ClamUpdateStatus struct { + ID uint `json:"id"` + Status string `json:"status"` +} + +type ClamDelete struct { + RemoveInfected bool `json:"removeInfected"` + Ids []uint `json:"ids" validate:"required"` +} diff --git a/agent/app/dto/command.go b/agent/app/dto/command.go new file mode 100644 index 0000000..2cc14d9 --- /dev/null +++ b/agent/app/dto/command.go @@ -0,0 +1,38 @@ +package dto + +type SearchCommandWithPage struct { + PageInfo + OrderBy string `json:"orderBy" validate:"required,oneof=name command createdAt"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` + GroupID uint `json:"groupID"` + Info string `json:"info"` + Name string `json:"name"` +} + +type CommandOperate struct { + ID uint `json:"id"` + GroupID uint `json:"groupID"` + GroupBelong string `json:"groupBelong"` + Name string `json:"name" validate:"required"` + Command string `json:"command" validate:"required"` +} + +type CommandInfo struct { + ID uint `json:"id"` + GroupID uint `json:"groupID"` + Name string `json:"name"` + Command string `json:"command"` + GroupBelong string `json:"groupBelong"` +} + +type CommandTree struct { + ID uint `json:"id"` + Label string `json:"label"` + Children []CommandInfo `json:"children"` +} + +type RedisCommand struct { + ID uint `json:"id"` + Name string `json:"name"` + Command string `json:"command"` +} diff --git a/agent/app/dto/common_req.go b/agent/app/dto/common_req.go new file mode 100644 index 0000000..576bb44 --- /dev/null +++ b/agent/app/dto/common_req.go @@ -0,0 +1,86 @@ +package dto + +type SearchWithPage struct { + PageInfo + Info string `json:"info"` +} + +type SearchPageWithType struct { + PageInfo + Info string `json:"info"` + Type string `json:"type"` +} + +type PageInfo struct { + Page int `json:"page" validate:"required,number"` + PageSize int `json:"pageSize" validate:"required,number"` +} + +type UpdateDescription struct { + ID uint `json:"id" validate:"required"` + Description string `json:"description" validate:"max=256"` +} + +type OperationWithName struct { + Name string `json:"name" validate:"required"` +} + +type OperateByID struct { + ID uint `json:"id" validate:"required"` +} +type OperateByIDs struct { + IDs []uint `json:"ids"` +} + +type Operate struct { + Operation string `json:"operation" validate:"required"` +} + +type SearchByFilter struct { + Filter string `json:"filter"` +} + +type BatchDeleteReq struct { + Ids []uint `json:"ids" validate:"required"` +} + +type FilePath struct { + Path string `json:"path" validate:"required"` +} + +type DeleteByName struct { + Name string `json:"name" validate:"required"` +} + +type UpdateByFile struct { + File string `json:"file"` +} + +type UpdateByNameAndFile struct { + Name string `json:"name"` + File string `json:"file"` +} + +type OperationWithNameAndType struct { + Name string `json:"name"` + Type string `json:"type" validate:"required"` +} + +type UpdateGroup struct { + Group uint `json:"group"` + NewGroup uint `json:"newGroup"` +} + +type OperateWithTask struct { + TaskID string `json:"taskID"` +} + +type ForceDelete struct { + IDs []uint `json:"ids"` + ForceDelete bool `json:"forceDelete"` +} + +type ChangeGroup struct { + ID uint `json:"id" validate:"required"` + GroupID uint `json:"groupID" validate:"required"` +} diff --git a/agent/app/dto/common_res.go b/agent/app/dto/common_res.go new file mode 100644 index 0000000..5c7c111 --- /dev/null +++ b/agent/app/dto/common_res.go @@ -0,0 +1,16 @@ +package dto + +type PageResult struct { + Total int64 `json:"total"` + Items interface{} `json:"items"` +} + +type Response struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +type Options struct { + Option string `json:"option"` +} diff --git a/agent/app/dto/compose_template.go b/agent/app/dto/compose_template.go new file mode 100644 index 0000000..f936e13 --- /dev/null +++ b/agent/app/dto/compose_template.go @@ -0,0 +1,27 @@ +package dto + +import "time" + +type ComposeTemplateCreate struct { + Name string `json:"name" validate:"required"` + Description string `json:"description"` + Content string `json:"content"` +} + +type ComposeTemplateBatch struct { + Templates []ComposeTemplateCreate `json:"templates" validate:"required"` +} + +type ComposeTemplateUpdate struct { + ID uint `json:"id"` + Description string `json:"description"` + Content string `json:"content"` +} + +type ComposeTemplateInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + Description string `json:"description"` + Content string `json:"content"` +} diff --git a/agent/app/dto/container.go b/agent/app/dto/container.go new file mode 100644 index 0000000..aaa6caf --- /dev/null +++ b/agent/app/dto/container.go @@ -0,0 +1,318 @@ +package dto + +import ( + "time" +) + +type PageContainer struct { + PageInfo + Name string `json:"name"` + State string `json:"state" validate:"required,oneof=all created running paused restarting removing exited dead"` + OrderBy string `json:"orderBy" validate:"required,oneof=name createdAt state"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` + Filters string `json:"filters"` + ExcludeAppStore bool `json:"excludeAppStore"` +} + +type InspectReq struct { + ID string `json:"id" validate:"required"` + Type string `json:"type" validate:"required"` + Detail string `json:"detail"` +} + +type ContainerInfo struct { + ContainerID string `json:"containerID"` + Name string `json:"name"` + ImageId string `json:"imageID"` + ImageName string `json:"imageName"` + CreateTime string `json:"createTime"` + State string `json:"state"` + RunTime string `json:"runTime"` + + Network []string `json:"network"` + Ports []string `json:"ports"` + + IsFromApp bool `json:"isFromApp"` + IsFromCompose bool `json:"isFromCompose"` + + AppName string `json:"appName"` + AppInstallName string `json:"appInstallName"` + Websites []string `json:"websites"` + + IsPinned bool `json:"isPinned"` + Description string `json:"description"` +} + +type ContainerOptions struct { + Name string `json:"name"` + State string `json:"state"` +} + +type ContainerStatus struct { + Created int `json:"created"` + Running int `json:"running"` + Paused int `json:"paused"` + Restarting int `json:"restarting"` + Removing int `json:"removing"` + Exited int `json:"exited"` + Dead int `json:"dead"` + + ContainerCount int `json:"containerCount"` + ComposeCount int `json:"composeCount"` + ComposeTemplateCount int `json:"composeTemplateCount"` + ImageCount int `json:"imageCount"` + NetworkCount int `json:"networkCount"` + VolumeCount int `json:"volumeCount"` + RepoCount int `json:"repoCount"` +} +type ResourceLimit struct { + CPU int `json:"cpu"` + Memory uint64 `json:"memory"` +} + +type ContainerOperate struct { + TaskID string `json:"taskID"` + ForcePull bool `json:"forcePull"` + Name string `json:"name" validate:"required"` + Image string `json:"image" validate:"required"` + + Hostname string `json:"hostname"` + DomainName string `json:"domainName"` + DNS []string `json:"dns"` + Networks []ContainerNetwork `json:"networks"` + + PublishAllPorts bool `json:"publishAllPorts"` + ExposedPorts []PortHelper `json:"exposedPorts"` + Tty bool `json:"tty"` + OpenStdin bool `json:"openStdin"` + WorkingDir string `json:"workingDir"` + User string `json:"user"` + Cmd []string `json:"cmd"` + Entrypoint []string `json:"entrypoint"` + CPUShares int64 `json:"cpuShares"` + NanoCPUs float64 `json:"nanoCPUs"` + Memory float64 `json:"memory"` + Privileged bool `json:"privileged"` + AutoRemove bool `json:"autoRemove"` + Volumes []VolumeHelper `json:"volumes"` + Labels []string `json:"labels"` + Env []string `json:"env"` + RestartPolicy string `json:"restartPolicy"` +} +type ContainerNetwork struct { + Network string `json:"network"` + Ipv4 string `json:"ipv4"` + Ipv6 string `json:"ipv6"` + MacAddr string `json:"macAddr"` +} + +type ContainerCreateByCommand struct { + TaskID string `json:"taskID"` + Command string `json:"command"` +} + +type ContainerUpgrade struct { + TaskID string `json:"taskID"` + Names []string `json:"names" validate:"required"` + Image string `json:"image" validate:"required"` + ForcePull bool `json:"forcePull"` +} + +type ContainerItemStats struct { + SizeRw int64 `json:"sizeRw"` + SizeRootFs int64 `json:"sizeRootFs"` + + ContainerUsage int64 `json:"containerUsage"` + ContainerReclaimable int64 `json:"containerReclaimable"` + ImageUsage int64 `json:"imageUsage"` + ImageReclaimable int64 `json:"imageReclaimable"` + VolumeUsage int64 `json:"volumeUsage"` + VolumeReclaimable int64 `json:"volumeReclaimable"` + BuildCacheUsage int64 `json:"buildCacheUsage"` + BuildCacheReclaimable int64 `json:"buildCacheReclaimable"` +} +type ContainerListStats struct { + ContainerID string `json:"containerID"` + + CPUTotalUsage uint64 `json:"cpuTotalUsage"` + SystemUsage uint64 `json:"systemUsage"` + CPUPercent float64 `json:"cpuPercent"` + PercpuUsage int `json:"percpuUsage"` + + MemoryCache uint64 `json:"memoryCache"` + MemoryUsage uint64 `json:"memoryUsage"` + MemoryLimit uint64 `json:"memoryLimit"` + MemoryPercent float64 `json:"memoryPercent"` +} + +type ContainerStats struct { + CPUPercent float64 `json:"cpuPercent"` + Memory float64 `json:"memory"` + Cache float64 `json:"cache"` + IORead float64 `json:"ioRead"` + IOWrite float64 `json:"ioWrite"` + NetworkRX float64 `json:"networkRX"` + NetworkTX float64 `json:"networkTX"` + + ShotTime time.Time `json:"shotTime"` +} + +type VolumeHelper struct { + Type string `json:"type"` + SourceDir string `json:"sourceDir"` + ContainerDir string `json:"containerDir"` + Mode string `json:"mode"` + Shared string `json:"shared"` +} +type PortHelper struct { + HostIP string `json:"hostIP"` + HostPort string `json:"hostPort"` + ContainerPort string `json:"containerPort"` + Protocol string `json:"protocol"` +} + +type ContainerOperation struct { + TaskID string `json:"taskID"` + Names []string `json:"names" validate:"required"` + Operation string `json:"operation" validate:"required,oneof=up start stop restart kill pause unpause remove"` +} + +type ContainerRename struct { + Name string `json:"name" validate:"required"` + NewName string `json:"newName" validate:"required"` +} + +type ContainerCommit struct { + ContainerId string `json:"containerID" validate:"required"` + ContainerName string `json:"containerName"` + NewImageName string `json:"newImageName"` + Comment string `json:"comment"` + Author string `json:"author"` + Pause bool `json:"pause"` + + TaskID string `json:"taskID"` +} + +type ContainerPrune struct { + TaskID string `json:"taskID"` + PruneType string `json:"pruneType" validate:"required,oneof=container image volume network buildcache"` + WithTagAll bool `json:"withTagAll"` +} + +type Network struct { + ID string `json:"id"` + Name string `json:"name"` + Labels []string `json:"labels"` + Driver string `json:"driver"` + IPAMDriver string `json:"ipamDriver"` + Subnet string `json:"subnet"` + Gateway string `json:"gateway"` + CreatedAt time.Time `json:"createdAt"` + Attachable bool `json:"attachable"` +} +type NetworkCreate struct { + Name string `json:"name" validate:"required"` + Driver string `json:"driver" validate:"required"` + Options []string `json:"options"` + Ipv4 bool `json:"ipv4"` + Subnet string `json:"subnet"` + Gateway string `json:"gateway"` + IPRange string `json:"ipRange"` + AuxAddress []SettingUpdate `json:"auxAddress"` + + Ipv6 bool `json:"ipv6"` + SubnetV6 string `json:"subnetV6"` + GatewayV6 string `json:"gatewayV6"` + IPRangeV6 string `json:"ipRangeV6"` + AuxAddressV6 []SettingUpdate `json:"auxAddressV6"` + Labels []string `json:"labels"` +} + +type Volume struct { + Name string `json:"name"` + Labels []VolumeOption `json:"labels"` + Driver string `json:"driver"` + Mountpoint string `json:"mountpoint"` + CreatedAt time.Time `json:"createdAt"` + Options []VolumeOption `json:"options"` +} +type VolumeCreate struct { + Name string `json:"name" validate:"required"` + Driver string `json:"driver" validate:"required"` + Options []string `json:"options"` + Labels []string `json:"labels"` +} +type VolumeOption struct { + Key string `json:"key"` + Value string `json:"value"` +} +type BatchDelete struct { + TaskID string `json:"taskID"` + Force bool `json:"force"` + Names []string `json:"names" validate:"required"` +} + +type ComposeInfo struct { + Name string `json:"name"` + CreatedAt string `json:"createdAt"` + CreatedBy string `json:"createdBy"` + ContainerCount int `json:"containerCount"` + RunningCount int `json:"runningCount"` + ConfigFile string `json:"configFile"` + Workdir string `json:"workdir"` + Path string `json:"path"` + Containers []ComposeContainer `json:"containers"` + Env string `json:"env"` +} +type ComposeContainer struct { + ContainerID string `json:"containerID"` + Name string `json:"name"` + CreateTime string `json:"createTime"` + State string `json:"state"` + Ports []string `json:"ports"` +} +type ComposeCreate struct { + TaskID string `json:"taskID"` + Name string `json:"name"` + From string `json:"from" validate:"required,oneof=edit path template"` + File string `json:"file"` + Path string `json:"path"` + Template uint `json:"template"` + Env string `json:"env"` + PullImage *bool `json:"pullImage,omitempty"` +} +type ComposeOperation struct { + Name string `json:"name" validate:"required"` + Path string `json:"path"` + Operation string `json:"operation" validate:"required,oneof=up start restart stop down delete"` + WithFile bool `json:"withFile"` + Force bool `json:"force"` +} +type ComposeUpdate struct { + Name string `json:"name" validate:"required"` + Path string `json:"path" validate:"required"` + DetailPath string `json:"detailPath"` + Content string `json:"content" validate:"required"` + Env string `json:"env"` +} +type ComposeLogClean struct { + Name string `json:"name" validate:"required"` + Path string `json:"path" validate:"required"` + DetailPath string `json:"detailPath"` +} + +type ContainerLog struct { + Container string `json:"container" validate:"required"` + Since string `json:"since"` + Tail uint `json:"tail"` + ContainerType string `json:"containerType"` +} + +type StreamLog struct { + Compose string + Container string + Since string + Follow bool + Tail string + Type string +} diff --git a/agent/app/dto/cronjob.go b/agent/app/dto/cronjob.go new file mode 100644 index 0000000..5d2250c --- /dev/null +++ b/agent/app/dto/cronjob.go @@ -0,0 +1,208 @@ +package dto + +import ( + "time" +) + +type PageCronjob struct { + PageInfo + Info string `json:"info"` + GroupIDs []uint `json:"groupIDs"` + OrderBy string `json:"orderBy" validate:"required,oneof=name status createdAt"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` +} + +type CronjobSpec struct { + Spec string `json:"spec" validate:"required"` +} + +type CronjobOperate struct { + ID uint `json:"id"` + Name string `json:"name" validate:"required"` + Type string `json:"type" validate:"required"` + GroupID uint `json:"groupID"` + SpecCustom bool `json:"specCustom"` + Spec string `json:"spec" validate:"required"` + + Executor string `json:"executor"` + ScriptMode string `json:"scriptMode"` + Script string `json:"script"` + Command string `json:"command"` + ContainerName string `json:"containerName"` + User string `json:"user"` + + ScriptID uint `json:"scriptID"` + AppID string `json:"appID"` + Website string `json:"website"` + ExclusionRules string `json:"exclusionRules"` + DBType string `json:"dbType"` + DBName string `json:"dbName"` + URL string `json:"url"` + IsDir bool `json:"isDir"` + SourceDir string `json:"sourceDir"` + SnapshotRule SnapshotRule `json:"snapshotRule"` + + SourceAccountIDs string `json:"sourceAccountIDs"` + DownloadAccountID uint `json:"downloadAccountID"` + RetainCopies int `json:"retainCopies" validate:"number,min=1"` + RetryTimes int `json:"retryTimes" validate:"number,min=0"` + Timeout uint `json:"timeout" validate:"number,min=1"` + IgnoreErr bool `json:"ignoreErr"` + Secret string `json:"secret"` + Args string `json:"args"` + + AlertCount uint `json:"alertCount"` + AlertTitle string `json:"alertTitle"` + AlertMethod string `json:"alertMethod"` + + CleanLogConfig +} + +type CleanLogConfig struct { + Scopes []string `json:"scopes"` +} + +type SnapshotRule struct { + WithImage bool `json:"withImage"` + IgnoreAppIDs []uint `json:"ignoreAppIDs"` +} + +type CronjobUpdateStatus struct { + ID uint `json:"id" validate:"required"` + Status string `json:"status" validate:"required"` +} + +type CronjobClean struct { + IsDelete bool `json:"isDelete"` + CleanData bool `json:"cleanData"` + CronjobID uint `json:"cronjobID" validate:"required"` + CleanRemoteData bool `json:"cleanRemoteData"` +} + +type CronjobBatchDelete struct { + CleanData bool `json:"cleanData"` + CleanRemoteData bool `json:"cleanRemoteData"` + IDs []uint `json:"ids" validate:"required"` +} + +type CronjobInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + GroupID uint `json:"groupID"` + SpecCustom bool `json:"specCustom"` + Spec string `json:"spec"` + + Executor string `json:"executor"` + ScriptMode string `json:"scriptMode"` + Script string `json:"script"` + Command string `json:"command"` + ContainerName string `json:"containerName"` + User string `json:"user"` + + ScriptID uint `json:"scriptID"` + AppID string `json:"appID"` + Website string `json:"website"` + ExclusionRules string `json:"exclusionRules"` + DBType string `json:"dbType"` + DBName string `json:"dbName"` + URL string `json:"url"` + IsDir bool `json:"isDir"` + SourceDir string `json:"sourceDir"` + RetainCopies int `json:"retainCopies"` + RetryTimes int `json:"retryTimes"` + Timeout uint `json:"timeout"` + IgnoreErr bool `json:"ignoreErr"` + SnapshotRule SnapshotRule `json:"snapshotRule"` + + SourceAccounts []string `json:"sourceAccounts"` + DownloadAccount string `json:"downloadAccount"` + SourceAccountIDs string `json:"sourceAccountIDs"` + DownloadAccountID uint `json:"downloadAccountID"` + + LastRecordStatus string `json:"lastRecordStatus"` + LastRecordTime string `json:"lastRecordTime"` + Status string `json:"status"` + Secret string `json:"secret"` + Args string `json:"args"` + + AlertCount uint `json:"alertCount"` +} + +type CronjobImport struct { + Cronjobs []CronjobTrans `json:"cronjobs"` +} +type CronjobTrans struct { + Name string `json:"name"` + Type string `json:"type"` + GroupID uint `json:"groupID"` + SpecCustom bool `json:"specCustom"` + Spec string `json:"spec"` + + Executor string `json:"executor"` + ScriptMode string `json:"scriptMode"` + Script string `json:"script"` + Command string `json:"command"` + ContainerName string `json:"containerName"` + User string `json:"user"` + URL string `json:"url"` + + ScriptName string `json:"scriptName"` + Apps []TransHelper `json:"apps"` + Websites []string `json:"websites"` + DBType string `json:"dbType"` + DBNames []TransHelper `json:"dbName"` + + ExclusionRules string `json:"exclusionRules"` + + IsDir bool `json:"isDir"` + SourceDir string `json:"sourceDir"` + + RetainCopies uint64 `json:"retainCopies"` + RetryTimes uint `json:"retryTimes"` + Timeout uint `json:"timeout"` + IgnoreErr bool `json:"ignoreErr"` + SnapshotRule SnapshotTransHelper `json:"snapshotRule"` + Secret string `json:"secret"` + Args string `json:"args"` + + SourceAccounts []string `json:"sourceAccounts"` + DownloadAccount string `json:"downloadAccount"` + + AlertCount uint `json:"alertCount"` + AlertTitle string `json:"alertTitle"` + AlertMethod string `json:"alertMethod"` +} +type TransHelper struct { + Name string `json:"name"` + DetailName string `json:"detailName"` +} +type SnapshotTransHelper struct { + WithImage bool `json:"withImage"` + IgnoreApps []TransHelper `json:"ignoreApps"` +} + +type ScriptOptions struct { + ID uint `json:"id"` + Name string `json:"name"` +} + +type SearchRecord struct { + PageInfo + CronjobID int `json:"cronjobID"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` + Status string `json:"status"` +} + +type Record struct { + ID uint `json:"id"` + TaskID string `json:"taskID"` + StartTime string `json:"startTime"` + Records string `json:"records"` + Status string `json:"status"` + Message string `json:"message"` + TargetPath string `json:"targetPath"` + Interval int `json:"interval"` + File string `json:"file"` +} diff --git a/agent/app/dto/dashboard.go b/agent/app/dto/dashboard.go new file mode 100644 index 0000000..2fb4fd9 --- /dev/null +++ b/agent/app/dto/dashboard.go @@ -0,0 +1,203 @@ +package dto + +import "time" + +type DashboardBase struct { + WebsiteNumber int `json:"websiteNumber"` + DatabaseNumber int `json:"databaseNumber"` + CronjobNumber int `json:"cronjobNumber"` + AppInstalledNumber int `json:"appInstalledNumber"` + + Hostname string `json:"hostname"` + OS string `json:"os"` + Platform string `json:"platform"` + PlatformFamily string `json:"platformFamily"` + PlatformVersion string `json:"platformVersion"` + PrettyDistro string `json:"prettyDistro"` + KernelArch string `json:"kernelArch"` + KernelVersion string `json:"kernelVersion"` + VirtualizationSystem string `json:"virtualizationSystem"` + IpV4Addr string `json:"ipV4Addr"` + SystemProxy string `json:"systemProxy"` + + CPUCores int `json:"cpuCores"` + CPULogicalCores int `json:"cpuLogicalCores"` + CPUModelName string `json:"cpuModelName"` + CPUMhz float64 `json:"cpuMhz"` + + QuickJumps []QuickJump `json:"quickJump"` + CurrentInfo DashboardCurrent `json:"currentInfo"` +} + +type ChangeQuicks struct { + Quicks []QuickJump `json:"quicks"` +} + +type QuickJump struct { + ID uint `json:"id"` + Name string `json:"name"` + Alias string `json:"alias"` + Title string `json:"title"` + Detail string `json:"detail"` + Recommend int `json:"recommend"` + IsShow bool `json:"isShow"` + Router string `json:"router"` +} + +type OsInfo struct { + OS string `json:"os"` + Platform string `json:"platform"` + PlatformFamily string `json:"platformFamily"` + KernelArch string `json:"kernelArch"` + KernelVersion string `json:"kernelVersion"` + PrettyDistro string `json:"prettyDistro"` + + DiskSize int64 `json:"diskSize"` +} + +type NodeCurrent struct { + Load1 float64 `json:"load1"` + Load5 float64 `json:"load5"` + Load15 float64 `json:"load15"` + LoadUsagePercent float64 `json:"loadUsagePercent"` + + CPUUsedPercent float64 `json:"cpuUsedPercent"` + CPUUsed float64 `json:"cpuUsed"` + CPUTotal int `json:"cpuTotal"` + CPUDetailedPercent []float64 `json:"cpuDetailedPercent"` + + MemoryTotal uint64 `json:"memoryTotal"` + MemoryAvailable uint64 `json:"memoryAvailable"` + MemoryUsed uint64 `json:"memoryUsed"` + MemoryUsedPercent float64 `json:"memoryUsedPercent"` + + SwapMemoryTotal uint64 `json:"swapMemoryTotal"` + SwapMemoryAvailable uint64 `json:"swapMemoryAvailable"` + SwapMemoryUsed uint64 `json:"swapMemoryUsed"` + SwapMemoryUsedPercent float64 `json:"swapMemoryUsedPercent"` +} + +type DashboardCurrent struct { + Uptime uint64 `json:"uptime"` + TimeSinceUptime string `json:"timeSinceUptime"` + + Procs uint64 `json:"procs"` + + Load1 float64 `json:"load1"` + Load5 float64 `json:"load5"` + Load15 float64 `json:"load15"` + LoadUsagePercent float64 `json:"loadUsagePercent"` + + CPUPercent []float64 `json:"cpuPercent"` + CPUUsedPercent float64 `json:"cpuUsedPercent"` + CPUUsed float64 `json:"cpuUsed"` + CPUTotal int `json:"cpuTotal"` + CPUDetailedPercent []float64 `json:"cpuDetailedPercent"` + + MemoryTotal uint64 `json:"memoryTotal"` + MemoryUsed uint64 `json:"memoryUsed"` + MemoryFree uint64 `json:"memoryFree"` + MemoryShard uint64 `json:"memoryShard"` + MemoryCache uint64 `json:"memoryCache"` + MemoryAvailable uint64 `json:"memoryAvailable"` + MemoryUsedPercent float64 `json:"memoryUsedPercent"` + + SwapMemoryTotal uint64 `json:"swapMemoryTotal"` + SwapMemoryAvailable uint64 `json:"swapMemoryAvailable"` + SwapMemoryUsed uint64 `json:"swapMemoryUsed"` + SwapMemoryUsedPercent float64 `json:"swapMemoryUsedPercent"` + + IOReadBytes uint64 `json:"ioReadBytes"` + IOWriteBytes uint64 `json:"ioWriteBytes"` + IOCount uint64 `json:"ioCount"` + IOReadTime uint64 `json:"ioReadTime"` + IOWriteTime uint64 `json:"ioWriteTime"` + + DiskData []DiskInfo `json:"diskData"` + + NetBytesSent uint64 `json:"netBytesSent"` + NetBytesRecv uint64 `json:"netBytesRecv"` + + GPUData []GPUInfo `json:"gpuData"` + XPUData []XPUInfo `json:"xpuData"` + + TopCPUItems []Process `json:"topCPUItems"` + TopMemItems []Process `json:"topMemItems"` + + ShotTime time.Time `json:"shotTime"` +} + +type AppLauncherSync struct { + Keys []string `json:"keys"` +} + +type DiskInfo struct { + Path string `json:"path"` + Type string `json:"type"` + Device string `json:"device"` + Total uint64 `json:"total"` + Free uint64 `json:"free"` + Used uint64 `json:"used"` + UsedPercent float64 `json:"usedPercent"` + + InodesTotal uint64 `json:"inodesTotal"` + InodesUsed uint64 `json:"inodesUsed"` + InodesFree uint64 `json:"inodesFree"` + InodesUsedPercent float64 `json:"inodesUsedPercent"` +} + +type GPUInfo struct { + Index uint `json:"index"` + ProductName string `json:"productName"` + GPUUtil string `json:"gpuUtil"` + Temperature string `json:"temperature"` + PerformanceState string `json:"performanceState"` + PowerUsage string `json:"powerUsage"` + PowerDraw string `json:"powerDraw"` + MaxPowerLimit string `json:"maxPowerLimit"` + MemoryUsage string `json:"memoryUsage"` + MemUsed string `json:"memUsed"` + MemTotal string `json:"memTotal"` + FanSpeed string `json:"fanSpeed"` +} + +type AppLauncher struct { + Key string `json:"key"` + Type string `json:"type"` + Name string `json:"name"` + Icon string `json:"icon"` + Limit int `json:"limit"` + Description string `json:"description"` + Recommend int `json:"recommend"` + + IsInstall bool `json:"isInstall"` + IsRecommend bool `json:"isRecommend"` + Detail []InstallDetail `json:"detail"` +} + +type InstallDetail struct { + InstallID uint `json:"installID"` + DetailID uint `json:"detailID"` + Name string `json:"name"` + Version string `json:"version"` + Path string `json:"path"` + Status string `json:"status"` + WebUI string `json:"webUI"` + HttpPort int `json:"httpPort"` + HttpsPort int `json:"httpsPort"` +} + +type LauncherOption struct { + Key string `json:"key"` + IsShow bool `json:"isShow"` +} + +type XPUInfo struct { + DeviceID int `json:"deviceID"` + DeviceName string `json:"deviceName"` + Memory string `json:"memory"` + Temperature string `json:"temperature"` + MemoryUsed string `json:"memoryUsed"` + Power string `json:"power"` + MemoryUtil string `json:"memoryUtil"` +} diff --git a/agent/app/dto/database.go b/agent/app/dto/database.go new file mode 100644 index 0000000..fb6bf37 --- /dev/null +++ b/agent/app/dto/database.go @@ -0,0 +1,346 @@ +package dto + +import "time" + +// common +type DBConfUpdateByFile struct { + Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis mysql-cluster postgresql-cluster redis-cluster"` + Database string `json:"database" validate:"required"` + File string `json:"file"` +} +type ChangeDBInfo struct { + ID uint `json:"id"` + From string `json:"from" validate:"required,oneof=local remote"` + Type string `json:"type" validate:"required,oneof=mysql mariadb postgresql redis mysql-cluster postgresql-cluster redis-cluster"` + Database string `json:"database" validate:"required"` + Value string `json:"value" validate:"required"` +} + +type DBBaseInfo struct { + Name string `json:"name"` + ContainerName string `json:"containerName"` + Port int64 `json:"port"` +} + +// mysql +type MysqlDBSearch struct { + PageInfo + Info string `json:"info"` + Database string `json:"database" validate:"required"` + OrderBy string `json:"orderBy" validate:"required,oneof=name createdAt"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` +} + +type MysqlDBInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + From string `json:"from"` + MysqlName string `json:"mysqlName"` + Format string `json:"format"` + Collation string `json:"collation"` + Username string `json:"username"` + Password string `json:"password"` + Permission string `json:"permission"` + IsDelete bool `json:"isDelete"` + Description string `json:"description"` +} + +type MysqlOption struct { + ID uint `json:"id"` + From string `json:"from"` + Type string `json:"type"` + Database string `json:"database"` + Name string `json:"name"` +} + +type MysqlDBCreate struct { + Name string `json:"name" validate:"required"` + From string `json:"from" validate:"required,oneof=local remote"` + Database string `json:"database" validate:"required"` + Format string `json:"format" validate:"required"` + Collation string `json:"collation"` + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + Permission string `json:"permission" validate:"required"` + Description string `json:"description"` +} + +type MysqlFormatCollationOption struct { + Format string `json:"format"` + Collations []string `json:"collations"` +} + +type BindUser struct { + Database string `json:"database" validate:"required"` + DB string `json:"db" validate:"required"` + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + Permission string `json:"permission" validate:"required"` +} + +type MysqlLoadDB struct { + From string `json:"from" validate:"required,oneof=local remote"` + Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"` + Database string `json:"database" validate:"required"` +} + +type MysqlDBDeleteCheck struct { + ID uint `json:"id" validate:"required"` + Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"` + Database string `json:"database" validate:"required"` +} + +type MysqlDBDelete struct { + ID uint `json:"id" validate:"required"` + Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"` + Database string `json:"database" validate:"required"` + ForceDelete bool `json:"forceDelete"` + DeleteBackup bool `json:"deleteBackup"` +} + +type MysqlStatus struct { + AbortedClients string `json:"Aborted_clients"` + AbortedConnects string `json:"Aborted_connects"` + BytesReceived string `json:"Bytes_received"` + BytesSent string `json:"Bytes_sent"` + ComCommit string `json:"Com_commit"` + ComRollback string `json:"Com_rollback"` + Connections string `json:"Connections"` + CreatedTmpDiskTables string `json:"Created_tmp_disk_tables"` + CreatedTmpTables string `json:"Created_tmp_tables"` + InnodbBufferPoolPagesDirty string `json:"Innodb_buffer_pool_pages_dirty"` + InnodbBufferPoolReadRequests string `json:"Innodb_buffer_pool_read_requests"` + InnodbBufferPoolReads string `json:"Innodb_buffer_pool_reads"` + KeyReadRequests string `json:"Key_read_requests"` + KeyReads string `json:"Key_reads"` + KeyWriteEequests string `json:"Key_write_requests"` + KeyWrites string `json:"Key_writes"` + MaxUsedConnections string `json:"Max_used_connections"` + OpenTables string `json:"Open_tables"` + OpenedFiles string `json:"Opened_files"` + OpenedTables string `json:"Opened_tables"` + QcacheHits string `json:"Qcache_hits"` + QcacheInserts string `json:"Qcache_inserts"` + Questions string `json:"Questions"` + SelectFullJoin string `json:"Select_full_join"` + SelectRangeCheck string `json:"Select_range_check"` + SortMergePasses string `json:"Sort_merge_passes"` + TableLocksWaited string `json:"Table_locks_waited"` + ThreadsCached string `json:"Threads_cached"` + ThreadsConnected string `json:"Threads_connected"` + ThreadsCreated string `json:"Threads_created"` + ThreadsRunning string `json:"Threads_running"` + Uptime string `json:"Uptime"` + Run string `json:"Run"` + File string `json:"File"` + Position string `json:"Position"` +} + +type MysqlVariables struct { + BinlogCacheSize string `json:"binlog_cache_size"` + InnodbBufferPoolSize string `json:"innodb_buffer_pool_size"` + InnodbLogBufferSize string `json:"innodb_log_buffer_size"` + JoinBufferSize string `json:"join_buffer_size"` + KeyBufferSize string `json:"key_buffer_size"` + MaxConnections string `json:"max_connections"` + MaxHeapTableSize string `json:"max_heap_table_size"` + QueryCacheSize string `json:"query_cache_size"` + QueryCacheType string `json:"query_cache_type"` + ReadBufferSize string `json:"read_buffer_size"` + ReadRndBufferSize string `json:"read_rnd_buffer_size"` + SortBufferSize string `json:"sort_buffer_size"` + TableOpenCache string `json:"table_open_cache"` + ThreadCacheSize string `json:"thread_cache_size"` + ThreadStack string `json:"thread_stack"` + TmpTableSize string `json:"tmp_table_size"` + + SlowQueryLog string `json:"slow_query_log"` + LongQueryTime string `json:"long_query_time"` +} + +type MysqlVariablesUpdate struct { + Type string `json:"type" validate:"required,oneof=mysql mariadb mysql-cluster"` + Database string `json:"database" validate:"required"` + Variables []MysqlVariablesUpdateHelper `json:"variables"` +} + +type MysqlVariablesUpdateHelper struct { + Param string `json:"param"` + Value interface{} `json:"value"` +} + +// redis +type ChangeRedisPass struct { + Database string `json:"database" validate:"required"` + Value string `json:"value"` +} + +type RedisConfUpdate struct { + Database string `json:"database" validate:"required"` + Timeout string `json:"timeout"` + Maxclients string `json:"maxclients"` + Maxmemory string `json:"maxmemory"` + DBType string `json:"dbType" validate:"required,oneof=redis redis-cluster"` +} +type RedisConfPersistenceUpdate struct { + Database string `json:"database" validate:"required"` + Type string `json:"type" validate:"required,oneof=aof rbd"` + Appendonly string `json:"appendonly"` + Appendfsync string `json:"appendfsync"` + Save string `json:"save"` + DBType string `json:"dbType" validate:"required,oneof=redis redis-cluster"` +} + +type RedisConf struct { + Database string `json:"database" validate:"required"` + Name string `json:"name"` + Port int64 `json:"port"` + ContainerName string `json:"containerName"` + Timeout string `json:"timeout"` + Maxclients string `json:"maxclients"` + Requirepass string `json:"requirepass"` + Maxmemory string `json:"maxmemory"` +} + +type RedisPersistence struct { + Database string `json:"database" validate:"required"` + Appendonly string `json:"appendonly"` + Appendfsync string `json:"appendfsync"` + Save string `json:"save"` +} + +type RedisStatus struct { + Database string `json:"database" validate:"required"` + TcpPort string `json:"tcp_port"` + UptimeInDays string `json:"uptime_in_days"` + ConnectedClients string `json:"connected_clients"` + UsedMemory string `json:"used_memory"` + UsedMemoryRss string `json:"used_memory_rss"` + UsedMemoryPeak string `json:"used_memory_peak"` + MemFragmentationRatio string `json:"mem_fragmentation_ratio"` + TotalConnectionsReceived string `json:"total_connections_received"` + TotalCommandsProcessed string `json:"total_commands_processed"` + InstantaneousOpsPerSec string `json:"instantaneous_ops_per_sec"` + KeyspaceHits string `json:"keyspace_hits"` + KeyspaceMisses string `json:"keyspace_misses"` + LatestForkUsec string `json:"latest_fork_usec"` +} + +type DatabaseFileRecords struct { + Database string `json:"database" validate:"required"` + FileName string `json:"fileName"` + FileDir string `json:"fileDir"` + CreatedAt string `json:"createdAt"` + Size int `json:"size"` +} +type RedisBackupRecover struct { + Database string `json:"database" validate:"required"` + FileName string `json:"fileName"` + FileDir string `json:"fileDir"` +} + +// database +type DatabaseSearch struct { + PageInfo + Info string `json:"info"` + Type string `json:"type"` + OrderBy string `json:"orderBy" validate:"required,oneof=name createdAt"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` +} + +type DatabaseInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name" validate:"max=256"` + From string `json:"from"` + Type string `json:"type"` + Version string `json:"version"` + Address string `json:"address"` + Port uint `json:"port"` + InitialDB string `json:"initialDB"` + Username string `json:"username"` + Password string `json:"password"` + + SSL bool `json:"ssl"` + RootCert string `json:"rootCert"` + ClientKey string `json:"clientKey"` + ClientCert string `json:"clientCert"` + SkipVerify bool `json:"skipVerify"` + + Timeout uint `json:"timeout"` + Description string `json:"description"` +} + +type DatabaseOption struct { + ID uint `json:"id"` + Type string `json:"type"` + From string `json:"from"` + Database string `json:"database"` + Version string `json:"version"` + Address string `json:"address"` +} + +type DatabaseItem struct { + ID uint `json:"id"` + From string `json:"from"` + Database string `json:"database"` + Name string `json:"name"` +} + +type DatabaseCreate struct { + Name string `json:"name" validate:"required,max=256"` + Type string `json:"type" validate:"required"` + From string `json:"from" validate:"required,oneof=local remote"` + Version string `json:"version" validate:"required"` + Address string `json:"address"` + Port uint `json:"port"` + InitialDB string `json:"initialDB"` + Username string `json:"username" validate:"required"` + Password string `json:"password"` + + SSL bool `json:"ssl"` + RootCert string `json:"rootCert"` + ClientKey string `json:"clientKey"` + ClientCert string `json:"clientCert"` + SkipVerify bool `json:"skipVerify"` + + Timeout uint `json:"timeout"` + Description string `json:"description"` +} + +type DatabaseUpdate struct { + ID uint `json:"id"` + Type string `json:"type" validate:"required"` + Version string `json:"version" validate:"required"` + Address string `json:"address"` + Port uint `json:"port"` + InitialDB string `json:"initialDB"` + Username string `json:"username" validate:"required"` + Password string `json:"password"` + + SSL bool `json:"ssl"` + RootCert string `json:"rootCert"` + ClientKey string `json:"clientKey"` + ClientCert string `json:"clientCert"` + SkipVerify bool `json:"skipVerify"` + + Timeout uint `json:"timeout"` + Description string `json:"description"` +} + +type DatabaseDelete struct { + ID uint `json:"id" validate:"required"` + ForceDelete bool `json:"forceDelete"` + DeleteBackup bool `json:"deleteBackup"` +} + +type LoadRedisStatus struct { + Name string `json:"name" validate:"required"` + Type string `json:"type" validate:"required"` +} + +type DBResource struct { + Type string `json:"type"` + Name string `json:"name"` +} diff --git a/agent/app/dto/database_postgresql.go b/agent/app/dto/database_postgresql.go new file mode 100644 index 0000000..6d4c219 --- /dev/null +++ b/agent/app/dto/database_postgresql.go @@ -0,0 +1,79 @@ +package dto + +import "time" + +type PostgresqlDBSearch struct { + PageInfo + Info string `json:"info"` + Database string `json:"database" validate:"required"` + OrderBy string `json:"orderBy" validate:"required,oneof=name createdAt"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` +} + +type PostgresqlDBInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + From string `json:"from"` + PostgresqlName string `json:"postgresqlName"` + Format string `json:"format"` + Username string `json:"username"` + Password string `json:"password"` + SuperUser bool `json:"superUser"` + IsDelete bool `json:"isDelete"` + Description string `json:"description"` +} + +type PostgresqlOption struct { + ID uint `json:"id"` + From string `json:"from"` + Type string `json:"type"` + Database string `json:"database"` + Name string `json:"name"` +} + +type PostgresqlDBCreate struct { + Name string `json:"name" validate:"required"` + From string `json:"from" validate:"required,oneof=local remote"` + Database string `json:"database" validate:"required"` + Format string `json:"format"` + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + SuperUser bool `json:"superUser"` + Description string `json:"description"` +} + +type PostgresqlBindUser struct { + Name string `json:"name" validate:"required"` + Database string `json:"database" validate:"required"` + Username string `json:"username" validate:"required"` + Password string `json:"password" validate:"required"` + SuperUser bool `json:"superUser"` +} + +type PostgresqlPrivileges struct { + Name string `json:"name" validate:"required"` + Database string `json:"database" validate:"required"` + Username string `json:"username" validate:"required"` + SuperUser bool `json:"superUser"` +} + +type PostgresqlLoadDB struct { + From string `json:"from" validate:"required,oneof=local remote"` + Type string `json:"type" validate:"required,oneof=postgresql postgresql-cluster"` + Database string `json:"database" validate:"required"` +} + +type PostgresqlDBDeleteCheck struct { + ID uint `json:"id" validate:"required"` + Type string `json:"type" validate:"required,oneof=postgresql postgresql-cluster"` + Database string `json:"database" validate:"required"` +} + +type PostgresqlDBDelete struct { + ID uint `json:"id" validate:"required"` + Type string `json:"type" validate:"required,oneof=postgresql postgresql-cluster"` + Database string `json:"database" validate:"required"` + ForceDelete bool `json:"forceDelete"` + DeleteBackup bool `json:"deleteBackup"` +} diff --git a/agent/app/dto/device.go b/agent/app/dto/device.go new file mode 100644 index 0000000..276961b --- /dev/null +++ b/agent/app/dto/device.go @@ -0,0 +1,42 @@ +package dto + +type DeviceBaseInfo struct { + DNS []string `json:"dns"` + Hosts []HostHelper `json:"hosts"` + Hostname string `json:"hostname"` + TimeZone string `json:"timeZone"` + LocalTime string `json:"localTime"` + Ntp string `json:"ntp"` + User string `json:"user"` + + SwapMemoryTotal uint64 `json:"swapMemoryTotal"` + SwapMemoryAvailable uint64 `json:"swapMemoryAvailable"` + SwapMemoryUsed uint64 `json:"swapMemoryUsed"` + MaxSize uint64 `json:"maxSize"` + + SwapDetails []SwapHelper `json:"swapDetails"` +} + +type HostHelper struct { + IP string `json:"ip"` + Host string `json:"host"` +} + +type SwapHelper struct { + Path string `json:"path" validate:"required"` + Size uint64 `json:"size"` + Used string `json:"used"` + + IsNew bool `json:"isNew"` + TaskID string `json:"taskID"` +} + +type TimeZoneOptions struct { + From string `json:"from"` + Zones []string `json:"zones"` +} + +type ChangePasswd struct { + User string `json:"user"` + Passwd string `json:"passwd"` +} diff --git a/agent/app/dto/disk.go b/agent/app/dto/disk.go new file mode 100644 index 0000000..3fa6afe --- /dev/null +++ b/agent/app/dto/disk.go @@ -0,0 +1,25 @@ +package dto + +type LsblkDevice struct { + Name string `json:"name"` + Size string `json:"size"` + Type string `json:"type"` + MountPoint *string `json:"mountpoint"` + FsType *string `json:"fstype"` + Model *string `json:"model"` + Serial string `json:"serial"` + Tran string `json:"tran"` + Rota bool `json:"rota"` + Children []LsblkDevice `json:"children,omitempty"` +} + +type LsblkOutput struct { + BlockDevices []LsblkDevice `json:"blockdevices"` +} + +type DiskFormatRequest struct { + Device string `json:"device" ` + Filesystem string `json:"filesystem" ` + Label string `json:"label,omitempty" ` + QuickFormat bool `json:"quickFormat,omitempty"` +} diff --git a/agent/app/dto/docker.go b/agent/app/dto/docker.go new file mode 100644 index 0000000..4d593fe --- /dev/null +++ b/agent/app/dto/docker.go @@ -0,0 +1,43 @@ +package dto + +type DaemonJsonUpdateByFile struct { + File string `json:"file"` +} + +type DockerStatus struct { + IsActive bool `json:"isActive"` + IsExist bool `json:"isExist"` +} + +type DaemonJsonConf struct { + IsSwarm bool `json:"isSwarm"` + Version string `json:"version"` + Mirrors []string `json:"registryMirrors"` + Registries []string `json:"insecureRegistries"` + LiveRestore bool `json:"liveRestore"` + IPTables bool `json:"iptables"` + CgroupDriver string `json:"cgroupDriver"` + + Ipv6 bool `json:"ipv6"` + FixedCidrV6 string `json:"fixedCidrV6"` + Ip6Tables bool `json:"ip6Tables"` + Experimental bool `json:"experimental"` + + LogMaxSize string `json:"logMaxSize"` + LogMaxFile string `json:"logMaxFile"` +} + +type LogOption struct { + LogMaxSize string `json:"logMaxSize"` + LogMaxFile string `json:"logMaxFile"` +} + +type Ipv6Option struct { + FixedCidrV6 string `json:"fixedCidrV6"` + Ip6Tables bool `json:"ip6Tables" validate:"required"` + Experimental bool `json:"experimental"` +} + +type DockerOperation struct { + Operation string `json:"operation" validate:"required,oneof=start restart stop"` +} diff --git a/agent/app/dto/fail2ban.go b/agent/app/dto/fail2ban.go new file mode 100644 index 0000000..8c8351b --- /dev/null +++ b/agent/app/dto/fail2ban.go @@ -0,0 +1,29 @@ +package dto + +type Fail2BanBaseInfo struct { + IsEnable bool `json:"isEnable"` + IsActive bool `json:"isActive"` + IsExist bool `json:"isExist"` + Version string `json:"version"` + + Port int `json:"port"` + MaxRetry int `json:"maxRetry"` + BanTime string `json:"banTime"` + FindTime string `json:"findTime"` + BanAction string `json:"banAction"` + LogPath string `json:"logPath"` +} + +type Fail2BanSearch struct { + Status string `json:"status" validate:"required,oneof=banned ignore"` +} + +type Fail2BanUpdate struct { + Key string `json:"key" validate:"required,oneof=port bantime findtime maxretry banaction logpath port"` + Value string `json:"value"` +} + +type Fail2BanSet struct { + IPs []string `json:"ips"` + Operate string `json:"operate" validate:"required,oneof=banned ignore"` +} diff --git a/agent/app/dto/file.go b/agent/app/dto/file.go new file mode 100644 index 0000000..f7fe337 --- /dev/null +++ b/agent/app/dto/file.go @@ -0,0 +1,8 @@ +package dto + +type LogFileRes struct { + Lines []string `json:"lines"` + IsEndOfFile bool `json:"isEndOfFile"` + TotalPages int `json:"totalPages"` + TotalLines int `json:"totalLines"` +} diff --git a/agent/app/dto/firewall.go b/agent/app/dto/firewall.go new file mode 100644 index 0000000..d12a965 --- /dev/null +++ b/agent/app/dto/firewall.go @@ -0,0 +1,113 @@ +package dto + +type FirewallBaseInfo struct { + Name string `json:"name"` + IsExist bool `json:"isExist"` + IsActive bool `json:"isActive"` + IsInit bool `json:"isInit"` + IsBind bool `json:"isBind"` + Version string `json:"version"` + PingStatus string `json:"pingStatus"` +} + +type RuleSearch struct { + PageInfo + Info string `json:"info"` + Status string `json:"status"` + Strategy string `json:"strategy"` + Type string `json:"type" validate:"required"` +} + +type FirewallOperation struct { + Operation string `json:"operation" validate:"required,oneof=start stop restart disableBanPing enableBanPing"` + WithDockerRestart bool `json:"withDockerRestart"` +} + +type PortRuleOperate struct { + ID uint `json:"id"` + Operation string `json:"operation" validate:"required,oneof=add remove"` + Chain string `json:"chain"` + Address string `json:"address"` + Port string `json:"port" validate:"required"` + Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop"` + + Description string `json:"description"` +} + +type ForwardRuleOperate struct { + ForceDelete bool `json:"forceDelete"` + Rules []struct { + Operation string `json:"operation" validate:"required,oneof=add remove"` + Num string `json:"num"` + Protocol string `json:"protocol" validate:"required,oneof=tcp udp tcp/udp"` + Interface string `json:"interface"` + Port string `json:"port" validate:"required"` + TargetIP string `json:"targetIP"` + TargetPort string `json:"targetPort" validate:"required"` + } `json:"rules"` +} + +type UpdateFirewallDescription struct { + Type string `json:"type"` + Chain string `json:"chain"` + SrcIP string `json:"srcIP"` + DstIP string `json:"dstIP"` + SrcPort string `json:"srcPort"` + DstPort string `json:"dstPort"` + Protocol string `json:"protocol"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop"` + + Description string `json:"description"` +} + +type AddrRuleOperate struct { + ID uint `json:"id"` + Operation string `json:"operation" validate:"required,oneof=add remove"` + Address string `json:"address" validate:"required"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop"` + + Description string `json:"description"` +} + +type PortRuleUpdate struct { + OldRule PortRuleOperate `json:"oldRule"` + NewRule PortRuleOperate `json:"newRule"` +} + +type AddrRuleUpdate struct { + OldRule AddrRuleOperate `json:"oldRule"` + NewRule AddrRuleOperate `json:"newRule"` +} + +type BatchRuleOperate struct { + Type string `json:"type" validate:"required"` + Rules []PortRuleOperate `json:"rules"` +} + +type IptablesOp struct { + Name string `json:"name" validate:"required,oneof=1PANEL_INPUT 1PANEL_OUTPUT 1PANEL_BASIC"` + Operate string `json:"operate" validate:"required,oneof=init-base init-forward init-advance bind-base unbind-base bind unbind"` +} + +type IptablesRuleOp struct { + Operation string `json:"operation" validate:"required,oneof=add remove"` + ID uint `json:"id"` + Chain string `json:"chain" validate:"required,oneof=1PANEL_BASIC 1PANEL_BASIC_BEFORE 1PANEL_INPUT 1PANEL_OUTPUT"` + Protocol string `json:"protocol"` + SrcIP string `json:"srcIP"` + SrcPort uint `json:"srcPort"` + DstIP string `json:"dstIP"` + DstPort uint `json:"dstPort"` + Strategy string `json:"strategy" validate:"required,oneof=accept drop reject"` + Description string `json:"description"` +} + +type IptablesBatchOperate struct { + Rules []IptablesRuleOp `json:"rules"` +} + +type IptablesChainStatus struct { + IsBind bool `json:"isBind"` + DefaultStrategy string `json:"defaultStrategy"` +} diff --git a/agent/app/dto/ftp.go b/agent/app/dto/ftp.go new file mode 100644 index 0000000..fad9376 --- /dev/null +++ b/agent/app/dto/ftp.go @@ -0,0 +1,43 @@ +package dto + +import ( + "time" +) + +type FtpInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + + User string `json:"user"` + Password string `json:"password"` + Path string `json:"path"` + Status string `json:"status"` + Description string `json:"description"` +} + +type FtpBaseInfo struct { + IsActive bool `json:"isActive"` + IsExist bool `json:"isExist"` +} + +type FtpLogSearch struct { + PageInfo + User string `json:"user"` + Operation string `json:"operation"` +} + +type FtpCreate struct { + User string `json:"user" validate:"required"` + Password string `json:"password" validate:"required"` + Path string `json:"path" validate:"required"` + Description string `json:"description"` +} + +type FtpUpdate struct { + ID uint `json:"id"` + + Password string `json:"password" validate:"required"` + Path string `json:"path" validate:"required"` + Status string `json:"status"` + Description string `json:"description"` +} diff --git a/agent/app/dto/group.go b/agent/app/dto/group.go new file mode 100644 index 0000000..6e0e6d0 --- /dev/null +++ b/agent/app/dto/group.go @@ -0,0 +1,29 @@ +package dto + +type GroupCreate struct { + ID uint `json:"id"` + Name string `json:"name" validate:"required"` + Type string `json:"type" validate:"required"` +} + +type GroupSearch struct { + Type string `json:"type" validate:"required"` +} + +type GroupUpdate struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type" validate:"required"` + IsDefault bool `json:"isDefault"` +} + +type GroupInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + IsDefault bool `json:"isDefault"` +} + +type OperateByType struct { + Type string `json:"type"` +} diff --git a/agent/app/dto/image.go b/agent/app/dto/image.go new file mode 100644 index 0000000..e8c1daa --- /dev/null +++ b/agent/app/dto/image.go @@ -0,0 +1,60 @@ +package dto + +import "time" + +type PageImage struct { + PageInfo + Name string `json:"name"` + OrderBy string `json:"orderBy" validate:"required,oneof=size tags createdAt isUsed"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` +} + +type ImageInfo struct { + ID string `json:"id"` + CreatedAt time.Time `json:"createdAt"` + IsUsed bool `json:"isUsed"` + Tags []string `json:"tags"` + Size int64 `json:"size"` + + IsPinned bool `json:"isPinned"` + Description string `json:"description"` +} + +type ImageLoad struct { + TaskID string `json:"taskID"` + Paths []string `json:"paths" validate:"required,dive,required"` +} + +type ImageBuild struct { + TaskID string `json:"taskID"` + From string `json:"from" validate:"required"` + Name string `json:"name" validate:"required"` + Dockerfile string `json:"dockerfile" validate:"required"` + Tags []string `json:"tags"` + Args []string `json:"args"` +} + +type ImagePull struct { + TaskID string `json:"taskID"` + RepoID uint `json:"repoID"` + ImageName []string `json:"imageName" validate:"required"` +} + +type ImageTag struct { + SourceID string `json:"sourceID" validate:"required"` + Tags []string `json:"tags" validate:"required"` +} + +type ImagePush struct { + TaskID string `json:"taskID"` + RepoID uint `json:"repoID" validate:"required"` + TagName string `json:"tagName" validate:"required"` + Name string `json:"name" validate:"required"` +} + +type ImageSave struct { + TaskID string `json:"taskID"` + TagName string `json:"tagName" validate:"required"` + Path string `json:"path" validate:"required"` + Name string `json:"name" validate:"required"` +} diff --git a/agent/app/dto/image_repo.go b/agent/app/dto/image_repo.go new file mode 100644 index 0000000..a6bea3f --- /dev/null +++ b/agent/app/dto/image_repo.go @@ -0,0 +1,44 @@ +package dto + +import "time" + +type ImageRepoCreate struct { + Name string `json:"name" validate:"required"` + DownloadUrl string `json:"downloadUrl"` + Protocol string `json:"protocol"` + Username string `json:"username"` + Password string `json:"password"` + Auth bool `json:"auth"` +} + +type ImageRepoUpdate struct { + ID uint `json:"id"` + DownloadUrl string `json:"downloadUrl"` + Protocol string `json:"protocol"` + Username string `json:"username"` + Password string `json:"password"` + Auth bool `json:"auth"` +} + +type ImageRepoInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + DownloadUrl string `json:"downloadUrl"` + Protocol string `json:"protocol"` + Username string `json:"username"` + Auth bool `json:"auth"` + + Status string `json:"status"` + Message string `json:"message"` +} + +type ImageRepoOption struct { + ID uint `json:"id"` + Name string `json:"name"` + DownloadUrl string `json:"downloadUrl"` +} + +type ImageRepoDelete struct { + Ids []uint `json:"ids" validate:"required"` +} diff --git a/agent/app/dto/logs.go b/agent/app/dto/logs.go new file mode 100644 index 0000000..3159855 --- /dev/null +++ b/agent/app/dto/logs.go @@ -0,0 +1,15 @@ +package dto + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type SearchTaskLogReq struct { + Status string `json:"status"` + Type string `json:"type"` + PageInfo +} + +type TaskDTO struct { + model.Task +} diff --git a/agent/app/dto/monitor.go b/agent/app/dto/monitor.go new file mode 100644 index 0000000..636bfdd --- /dev/null +++ b/agent/app/dto/monitor.go @@ -0,0 +1,81 @@ +package dto + +import "time" + +type MonitorSearch struct { + Param string `json:"param" validate:"required,oneof=all cpu memory load io network"` + IO string `json:"io"` + Network string `json:"network"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` +} + +type MonitorData struct { + Param string `json:"param"` + Date []time.Time `json:"date"` + Value []interface{} `json:"value"` +} + +type Process struct { + Name string `json:"name"` + Pid int32 `json:"pid"` + Percent float64 `json:"percent"` + Memory uint64 `json:"memory"` + Cmd string `json:"cmd"` + User string `json:"user"` +} + +type MonitorSetting struct { + MonitorStatus string `json:"monitorStatus"` + MonitorStoreDays string `json:"monitorStoreDays"` + MonitorInterval string `json:"monitorInterval"` + DefaultNetwork string `json:"defaultNetwork"` + DefaultIO string `json:"defaultIO"` +} + +type MonitorSettingUpdate struct { + Key string `json:"key" validate:"required,oneof=MonitorStatus MonitorStoreDays MonitorInterval DefaultNetwork DefaultIO"` + Value string `json:"value"` +} + +type MonitorGPUOptions struct { + GPUType string `json:"gpuType"` + ChartHide []GPUChartHide `json:"chartHide"` + Options []string `json:"options"` +} +type GPUChartHide struct { + ProductName string `json:"productName"` + Process bool `json:"process"` + GPU bool `json:"gpu"` + Memory bool `json:"memory"` + Power bool `json:"power"` + Temperature bool `json:"temperature"` + Speed bool `json:"speed"` +} +type MonitorGPUSearch struct { + ProductName string `json:"productName"` + StartTime time.Time `json:"startTime"` + EndTime time.Time `json:"endTime"` +} +type MonitorGPUData struct { + Date []time.Time `json:"date"` + GPUValue []float64 `json:"gpuValue"` + TemperatureValue []float64 `json:"temperatureValue"` + PowerTotal []float64 `json:"powerTotal"` + PowerUsed []float64 `json:"powerUsed"` + PowerPercent []float64 `json:"powerPercent"` + MemoryTotal []float64 `json:"memoryTotal"` + MemoryUsed []float64 `json:"memoryUsed"` + MemoryPercent []float64 `json:"memoryPercent"` + SpeedValue []int `json:"speedValue"` + + ProcessCount []int `json:"processCount"` + GPUProcesses [][]GPUProcess `json:"gpuProcesses"` +} + +type GPUProcess struct { + Pid string `json:"pid"` + Type string `json:"type"` + ProcessName string `json:"processName"` + UsedMemory string `json:"usedMemory"` +} diff --git a/agent/app/dto/nginx.go b/agent/app/dto/nginx.go new file mode 100644 index 0000000..3630810 --- /dev/null +++ b/agent/app/dto/nginx.go @@ -0,0 +1,95 @@ +package dto + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" +) + +type NginxFull struct { + Install model.AppInstall + Website model.Website + ConfigDir string + ConfigFile string + SiteDir string + Dir string + RootConfig NginxConfig + SiteConfig NginxConfig +} + +type NginxConfig struct { + FilePath string + Config *components.Config + OldContent string +} + +type NginxParam struct { + UpdateScope string + Name string + Params []string +} + +type NginxAuth struct { + Username string `json:"username"` + Remark string `json:"remark"` +} + +type NginxPathAuth struct { + Username string `json:"username"` + Remark string `json:"remark"` + Path string `json:"path"` + Name string `json:"name"` +} + +type NginxKey string + +const ( + Index NginxKey = "index" + LimitConn NginxKey = "limit-conn" + SSL NginxKey = "ssl" + CACHE NginxKey = "cache" + HttpPer NginxKey = "http-per" + ProxyCache NginxKey = "proxy-cache" +) + +var ScopeKeyMap = map[NginxKey][]string{ + Index: {"index"}, + LimitConn: {"limit_conn", "limit_rate", "limit_conn_zone"}, + SSL: {"ssl_certificate", "ssl_certificate_key"}, + HttpPer: {"server_names_hash_bucket_size", "client_header_buffer_size", "client_max_body_size", "keepalive_timeout", "gzip", "gzip_min_length", "gzip_comp_level"}, +} + +var StaticFileKeyMap = map[NginxKey]struct { +}{ + SSL: {}, + CACHE: {}, + ProxyCache: {}, +} + +type NginxUpstream struct { + Name string `json:"name"` + Algorithm string `json:"algorithm"` + Servers []NginxUpstreamServer `json:"servers"` + Content string `json:"content"` +} + +type NginxUpstreamServer struct { + Server string `json:"server"` + Weight int `json:"weight"` + FailTimeout int `json:"failTimeout"` + FailTimeoutUnit string `json:"failTimeoutUnit"` + MaxFails int `json:"maxFails"` + MaxConns int `json:"maxConns"` + Flag string `json:"flag"` +} + +var LBAlgorithms = map[string]struct{}{"ip_hash": {}, "least_conn": {}} + +var RealIPKeys = map[string]struct{}{"X-Forwarded-For": {}, "X-Real-IP": {}, "CF-Connecting-IP": {}} + +type NginxModule struct { + Name string `json:"name"` + Script string `json:"script"` + Packages []string `json:"packages"` + Params string `json:"params"` + Enable bool `json:"enable"` +} diff --git a/agent/app/dto/request/app.go b/agent/app/dto/request/app.go new file mode 100644 index 0000000..145168e --- /dev/null +++ b/agent/app/dto/request/app.go @@ -0,0 +1,140 @@ +package request + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" +) + +type AppSearch struct { + dto.PageInfo + Name string `json:"name"` + Tags []string `json:"tags"` + Type string `json:"type"` + Recommend bool `json:"recommend"` + Resource string `json:"resource"` + ShowCurrentArch bool `json:"showCurrentArch"` +} + +type AppInstallCreate struct { + AppDetailId uint `json:"appDetailId" validate:"required"` + Params map[string]interface{} `json:"params"` + Name string `json:"name" validate:"required"` + Services map[string]string `json:"services"` + TaskID string `json:"taskID"` + + AppContainerConfig + NodePushConfig +} + +type NodePushConfig struct { + Nodes []string `json:"nodes"` + PushNode bool `json:"pushNode"` + AppKey string `json:"appKey"` + Version string `json:"version"` +} + +type AppContainerConfig struct { + Advanced bool `json:"advanced"` + CpuQuota float64 `json:"cpuQuota"` + MemoryLimit float64 `json:"memoryLimit"` + MemoryUnit string `json:"memoryUnit"` + ContainerName string `json:"containerName"` + AllowPort bool `json:"allowPort"` + EditCompose bool `json:"editCompose"` + DockerCompose string `json:"dockerCompose"` + HostMode bool `json:"hostMode"` + PullImage bool `json:"pullImage"` + GpuConfig bool `json:"gpuConfig"` + WebUI string `json:"webUI"` + Type string `json:"type"` + SpecifyIP string `json:"specifyIP"` + RestartPolicy string `json:"restartPolicy" validate:"omitempty,oneof=always unless-stopped no on-failure"` +} + +type AppInstalledSearch struct { + dto.PageInfo + Type string `json:"type"` + Name string `json:"name"` + Tags []string `json:"tags"` + Update bool `json:"update"` + Unused bool `json:"unused"` + All bool `json:"all"` + Sync bool `json:"sync"` + CheckUpdate bool `json:"checkUpdate"` +} + +type AppInstalledInfo struct { + Key string `json:"key" validate:"required"` + Name string `json:"name"` +} + +type AppBackupSearch struct { + dto.PageInfo + AppInstallID uint `json:"appInstallID"` +} + +type AppBackupDelete struct { + Ids []uint `json:"ids"` +} + +type AppInstalledOperate struct { + InstallId uint `json:"installId" validate:"required"` + BackupId uint `json:"backupId"` + DetailId uint `json:"detailId"` + Operate constant.AppOperate `json:"operate" validate:"required"` + ForceDelete bool `json:"forceDelete"` + DeleteBackup bool `json:"deleteBackup"` + DeleteDB bool `json:"deleteDB"` + Backup bool `json:"backup"` + PullImage bool `json:"pullImage"` + DockerCompose string `json:"dockerCompose"` + TaskID string `json:"taskID"` + DeleteImage bool `json:"deleteImage"` + Favorite bool `json:"favorite"` +} + +type AppInstallUpgrade struct { + InstallID uint `json:"installId"` + DetailID uint `json:"detailId"` + Backup bool `json:"backup"` + PullImage bool `json:"pullImage"` + DockerCompose string `json:"dockerCompose"` + TaskID string `json:"taskID"` +} + +type AppInstallDelete struct { + Install model.AppInstall + DeleteBackup bool `json:"deleteBackup"` + ForceDelete bool `json:"forceDelete"` + DeleteDB bool `json:"deleteDB"` + DeleteImage bool `json:"deleteImage"` + TaskID string `json:"taskID"` +} + +type AppInstalledUpdate struct { + InstallId uint `json:"installId" validate:"required"` + Params map[string]interface{} `json:"params" validate:"required"` + AppContainerConfig +} + +type AppConfigUpdate struct { + InstallID uint `json:"installID" validate:"required"` + WebUI string `json:"webUI"` +} + +type AppInstalledIgnoreUpgrade struct { + DetailID uint `json:"detailID" validate:"required"` + Operate string `json:"operate" validate:"required,oneof=cancel ignore"` +} + +type PortUpdate struct { + Key string `json:"key"` + Name string `json:"name"` + Port int64 `json:"port"` +} + +type AppUpdateVersion struct { + AppInstallID uint `json:"appInstallID" validate:"required"` + UpdateVersion string `json:"updateVersion"` +} diff --git a/agent/app/dto/request/app_ignore_upgrade.go b/agent/app/dto/request/app_ignore_upgrade.go new file mode 100644 index 0000000..5fc3850 --- /dev/null +++ b/agent/app/dto/request/app_ignore_upgrade.go @@ -0,0 +1,7 @@ +package request + +type AppIgnoreUpgradeReq struct { + AppID uint `json:"appID" validate:"required"` + AppDetailID uint `json:"appDetailID"` + Scope string `json:"scope" validate:"required,oneof=all version"` +} diff --git a/agent/app/dto/request/common.go b/agent/app/dto/request/common.go new file mode 100644 index 0000000..b7b102c --- /dev/null +++ b/agent/app/dto/request/common.go @@ -0,0 +1,5 @@ +package request + +type ReqWithID struct { + ID uint `json:"id" validate:"required"` +} diff --git a/agent/app/dto/request/disk.go b/agent/app/dto/request/disk.go new file mode 100644 index 0000000..6232a93 --- /dev/null +++ b/agent/app/dto/request/disk.go @@ -0,0 +1,21 @@ +package request + +type DiskPartitionRequest struct { + Device string `json:"device" validate:"required"` + Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"` + Label string `json:"label"` + AutoMount bool `json:"autoMount"` + MountPoint string `json:"mountPoint" validate:"required"` +} + +type DiskMountRequest struct { + Device string `json:"device" validate:"required"` + MountPoint string `json:"mountPoint" validate:"required"` + Filesystem string `json:"filesystem" validate:"required,oneof=ext4 xfs"` + AutoMount bool `json:"autoMount"` + NoFail bool `json:"noFail"` +} + +type DiskUnmountRequest struct { + MountPoint string `json:"mountPoint" validate:"required"` +} diff --git a/agent/app/dto/request/favorite.go b/agent/app/dto/request/favorite.go new file mode 100644 index 0000000..7028092 --- /dev/null +++ b/agent/app/dto/request/favorite.go @@ -0,0 +1,9 @@ +package request + +type FavoriteCreate struct { + Path string `json:"path" validate:"required"` +} + +type FavoriteDelete struct { + ID uint `json:"id" validate:"required"` +} diff --git a/agent/app/dto/request/file.go b/agent/app/dto/request/file.go new file mode 100644 index 0000000..78f8a86 --- /dev/null +++ b/agent/app/dto/request/file.go @@ -0,0 +1,174 @@ +package request + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +type FileOption struct { + files.FileOption +} + +type FileContentReq struct { + Path string `json:"path" validate:"required"` + IsDetail bool `json:"isDetail"` +} +type SearchUploadWithPage struct { + dto.PageInfo + Path string `json:"path" validate:"required"` +} + +type FileCreate struct { + Path string `json:"path" validate:"required"` + Content string `json:"content"` + IsDir bool `json:"isDir"` + Mode int64 `json:"mode"` + IsLink bool `json:"isLink"` + IsSymlink bool `json:"isSymlink"` + LinkPath string `json:"linkPath"` + Sub bool `json:"sub"` +} + +type FileRoleReq struct { + Paths []string `json:"paths" validate:"required"` + Mode int64 `json:"mode" validate:"required"` + User string `json:"user" validate:"required"` + Group string `json:"group" validate:"required"` + Sub bool `json:"sub"` +} + +type FileDelete struct { + Path string `json:"path" validate:"required"` + IsDir bool `json:"isDir"` + ForceDelete bool `json:"forceDelete"` +} + +type FileBatchDelete struct { + Paths []string `json:"paths" validate:"required"` + IsDir bool `json:"isDir"` +} + +type FileCompress struct { + Files []string `json:"files" validate:"required"` + Dst string `json:"dst" validate:"required"` + Type string `json:"type" validate:"required"` + Name string `json:"name" validate:"required"` + Replace bool `json:"replace"` + Secret string `json:"secret"` +} + +type FileDeCompress struct { + Dst string `json:"dst" validate:"required"` + Type string `json:"type" validate:"required"` + Path string `json:"path" validate:"required"` + Secret string `json:"secret"` +} + +type FileEdit struct { + Path string `json:"path" validate:"required"` + Content string `json:"content"` +} + +type FileRename struct { + OldName string `json:"oldName" validate:"required"` + NewName string `json:"newName" validate:"required"` +} + +type FilePathCheck struct { + Path string `json:"path" validate:"required"` + WithInit bool `json:"withInit"` +} + +type FilePathsCheck struct { + Paths []string `json:"paths" validate:"required"` +} + +type FileWget struct { + Url string `json:"url" validate:"required"` + Path string `json:"path" validate:"required"` + Name string `json:"name" validate:"required"` + IgnoreCertificate bool `json:"ignoreCertificate"` +} + +type FileMove struct { + Type string `json:"type" validate:"required"` + OldPaths []string `json:"oldPaths" validate:"required"` + NewPath string `json:"newPath" validate:"required"` + Name string `json:"name"` + Cover bool `json:"cover"` + CoverPaths []string `json:"coverPaths"` +} + +type FileDownload struct { + Paths []string `json:"paths" validate:"required"` + Type string `json:"type" validate:"required"` + Name string `json:"name" validate:"required"` + Compress bool `json:"compress"` +} + +type FileChunkDownload struct { + Path string `json:"path" validate:"required"` + Name string `json:"name" validate:"required"` +} + +type DirSizeReq struct { + Path string `json:"path" validate:"required"` +} + +type FileProcessReq struct { + Key string `json:"key"` +} + +type FileRoleUpdate struct { + Path string `json:"path" validate:"required"` + User string `json:"user" validate:"required"` + Group string `json:"group" validate:"required"` + Sub bool `json:"sub"` +} + +type FileReadByLineReq struct { + Page int `json:"page" validate:"required"` + PageSize int `json:"pageSize" validate:"required"` + Type string `json:"type" validate:"required"` + ID uint `json:"ID"` + Name string `json:"name"` + Latest bool `json:"latest"` + TaskReq +} + +type TaskReq struct { + TaskID string `json:"taskID"` + TaskType string `json:"taskType"` + TaskOperate string `json:"taskOperate"` + ResourceID uint `json:"resourceID"` +} + +type FileExistReq struct { + Name string `json:"name" validate:"required"` + Dir string `json:"dir" validate:"required"` +} + +type FileConvert struct { + Path string `json:"path" validate:"required"` + Type string `json:"type" validate:"required"` + InputFile string `json:"inputFile" validate:"required"` + Extension string `json:"extension" validate:"required"` + OutputFormat string `json:"outputFormat" validate:"required"` + Status string `json:"status"` +} + +type FileConvertRequest struct { + Files []FileConvert `json:"files" validate:"required"` + OutputPath string `json:"outputPath" validate:"required"` + DeleteSource bool `json:"deleteSource"` + TaskID string `json:"taskID"` +} + +type FileRemarkBatch struct { + Paths []string `json:"paths" validate:"required"` +} + +type FileRemarkUpdate struct { + Path string `json:"path" validate:"required"` + Remark string `json:"remark"` +} diff --git a/agent/app/dto/request/host_tool.go b/agent/app/dto/request/host_tool.go new file mode 100644 index 0000000..a784a1a --- /dev/null +++ b/agent/app/dto/request/host_tool.go @@ -0,0 +1,45 @@ +package request + +type HostToolReq struct { + Type string `json:"type" validate:"required,oneof=supervisord"` + Operate string `json:"operate" validate:"oneof=status restart start stop"` +} + +type HostToolCreate struct { + Type string `json:"type" validate:"required"` + SupervisorConfig +} + +type SupervisorConfig struct { + ConfigPath string `json:"configPath"` + ServiceName string `json:"serviceName"` +} + +type HostToolLogReq struct { + Type string `json:"type" validate:"required,oneof=supervisord"` +} + +type HostToolConfig struct { + Type string `json:"type" validate:"required,oneof=supervisord"` + Operate string `json:"operate" validate:"oneof=get set"` + Content string `json:"content"` +} + +type SupervisorProcessConfig struct { + Name string `json:"name"` + Operate string `json:"operate"` + Command string `json:"command"` + User string `json:"user"` + Dir string `json:"dir"` + Numprocs string `json:"numprocs"` + AutoRestart string `json:"autoRestart"` + AutoStart string `json:"autoStart"` + Environment string `json:"environment"` +} + +type SupervisorProcessFileReq struct { + Name string `json:"name" validate:"required"` + Operate string `json:"operate" validate:"required,oneof=get clear update" ` + Content string `json:"content"` + File string `json:"file" validate:"required,oneof=out.log err.log config"` +} diff --git a/agent/app/dto/request/mcp_server.go b/agent/app/dto/request/mcp_server.go new file mode 100644 index 0000000..721cfb0 --- /dev/null +++ b/agent/app/dto/request/mcp_server.go @@ -0,0 +1,50 @@ +package request + +import "github.com/1Panel-dev/1Panel/agent/app/dto" + +type McpServerSearch struct { + dto.PageInfo + Name string `json:"name"` + Sync bool `json:"sync"` +} + +type McpServerCreate struct { + Name string `json:"name" validate:"required"` + Command string `json:"command" validate:"required"` + Environments []Environment `json:"environments"` + Volumes []Volume `json:"volumes"` + Port int `json:"port" validate:"required"` + ContainerName string `json:"containerName"` + BaseURL string `json:"baseUrl"` + SsePath string `json:"ssePath"` + HostIP string `json:"hostIP"` + StreamableHttpPath string `json:"streamableHttpPath"` + OutputTransport string `json:"outputTransport" validate:"required"` + Type string `json:"type" validate:"required"` +} + +type McpServerUpdate struct { + ID uint `json:"id" validate:"required"` + McpServerCreate +} + +type McpServerDelete struct { + ID uint `json:"id" validate:"required"` +} + +type McpServerOperate struct { + ID uint `json:"id" validate:"required"` + Operate string `json:"operate" validate:"required"` +} + +type McpBindDomain struct { + Domain string `json:"domain" validate:"required"` + SSLID uint `json:"sslID"` + IPList string `json:"ipList"` +} + +type McpBindDomainUpdate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + SSLID uint `json:"sslID"` + IPList string `json:"ipList"` +} diff --git a/agent/app/dto/request/nginx.go b/agent/app/dto/request/nginx.go new file mode 100644 index 0000000..7c70613 --- /dev/null +++ b/agent/app/dto/request/nginx.go @@ -0,0 +1,137 @@ +package request + +import "github.com/1Panel-dev/1Panel/agent/app/dto" + +type NginxConfigFileUpdate struct { + Content string `json:"content" validate:"required"` + Backup bool `json:"backup"` +} + +type NginxScopeReq struct { + Scope dto.NginxKey `json:"scope" validate:"required"` + WebsiteID uint `json:"websiteId"` +} + +type NginxConfigUpdate struct { + Scope dto.NginxKey `json:"scope"` + Operate string `json:"operate" validate:"required,oneof=add update delete"` + WebsiteID uint `json:"websiteId"` + Params interface{} `json:"params"` +} + +type NginxRewriteReq struct { + WebsiteID uint `json:"websiteId" validate:"required"` + Name string `json:"name" validate:"required"` +} + +type CustomRewriteOperate struct { + Operate string `json:"operate" validate:"required,oneof=create delete"` + Content string `json:"content"` + Name string `json:"name"` +} + +type NginxRewriteUpdate struct { + WebsiteID uint `json:"websiteId" validate:"required"` + Name string `json:"name" validate:"required"` + Content string `json:"content"` +} + +type NginxProxyUpdate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Content string `json:"content" validate:"required"` + Name string `json:"name" validate:"required"` +} + +type NginxProxyCacheUpdate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Open bool `json:"open"` + CacheLimit int `json:"cacheLimit" validate:"required"` + CacheLimitUnit string `json:"cacheLimitUnit" validate:"required"` + ShareCache int `json:"shareCache" validate:"required"` + ShareCacheUnit string `json:"shareCacheUnit" validate:"required"` + CacheExpire int `json:"cacheExpire" validate:"required"` + CacheExpireUnit string `json:"cacheExpireUnit" validate:"required"` +} + +type NginxAuthUpdate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Operate string `json:"operate" validate:"required"` + Username string `json:"username"` + Password string `json:"password"` + Remark string `json:"remark"` +} + +type NginxPathAuthUpdate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Operate string `json:"operate" validate:"required"` + Name string `json:"name"` + Username string `json:"username"` + Password string `json:"password"` + Path string `json:"path"` + Remark string `json:"remark"` +} + +type NginxAuthReq struct { + WebsiteID uint `json:"websiteID" validate:"required"` +} + +type NginxCommonReq struct { + WebsiteID uint `json:"websiteID" validate:"required"` +} + +type NginxAntiLeechUpdate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Extends string `json:"extends"` + Return string `json:"return"` + Enable bool `json:"enable" ` + ServerNames []string `json:"serverNames"` + Cache bool `json:"cache"` + CacheTime int `json:"cacheTime"` + CacheUint string `json:"cacheUint"` + NoneRef bool `json:"noneRef"` + LogEnable bool `json:"logEnable"` + Blocked bool `json:"blocked"` +} + +type NginxRedirectReq struct { + Name string `json:"name" validate:"required"` + WebsiteID uint `json:"websiteID" validate:"required"` + Domains []string `json:"domains"` + KeepPath bool `json:"keepPath"` + Enable bool `json:"enable"` + Type string `json:"type" validate:"required"` + Redirect string `json:"redirect" validate:"required"` + Path string `json:"path"` + Target string `json:"target" validate:"required"` + Operate string `json:"operate" validate:"required"` + RedirectRoot bool `json:"redirectRoot"` +} + +type NginxRedirectUpdate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Content string `json:"content" validate:"required"` + Name string `json:"name" validate:"required"` +} + +type NginxBuildReq struct { + TaskID string `json:"taskID" validate:"required"` + Mirror string `json:"mirror" validate:"required"` +} + +type NginxModuleUpdate struct { + Operate string `json:"operate" validate:"required,oneof=create delete update"` + Name string `json:"name" validate:"required"` + Script string `json:"script"` + Packages string `json:"packages"` + Enable bool `json:"enable"` + Params string `json:"params"` +} + +type NginxOperateReq struct { + Operate string `json:"operate" validate:"required,oneof=enable disable"` +} + +type NginxDefaultHTTPSUpdate struct { + Operate string `json:"operate" validate:"required,oneof=enable disable"` + SSLRejectHandshake bool `json:"sslRejectHandshake"` +} diff --git a/agent/app/dto/request/php_extensions.go b/agent/app/dto/request/php_extensions.go new file mode 100644 index 0000000..2046f2b --- /dev/null +++ b/agent/app/dto/request/php_extensions.go @@ -0,0 +1,22 @@ +package request + +import "github.com/1Panel-dev/1Panel/agent/app/dto" + +type PHPExtensionsSearch struct { + dto.PageInfo + All bool `json:"all"` +} + +type PHPExtensionsCreate struct { + Name string `json:"name" validate:"required"` + Extensions string `json:"extensions" validate:"required"` +} + +type PHPExtensionsUpdate struct { + ID uint `json:"id" validate:"required"` + Extensions string `json:"extensions" validate:"required"` +} + +type PHPExtensionsDelete struct { + ID uint `json:"id" validate:"required"` +} diff --git a/agent/app/dto/request/process.go b/agent/app/dto/request/process.go new file mode 100644 index 0000000..9269c4c --- /dev/null +++ b/agent/app/dto/request/process.go @@ -0,0 +1,5 @@ +package request + +type ProcessReq struct { + PID int32 `json:"PID" validate:"required"` +} diff --git a/agent/app/dto/request/recycle_bin.go b/agent/app/dto/request/recycle_bin.go new file mode 100644 index 0000000..4870a73 --- /dev/null +++ b/agent/app/dto/request/recycle_bin.go @@ -0,0 +1,11 @@ +package request + +type RecycleBinCreate struct { + SourcePath string `json:"sourcePath" validate:"required"` +} + +type RecycleBinReduce struct { + From string `json:"from" validate:"required"` + RName string `json:"rName" validate:"required"` + Name string `json:"name"` +} diff --git a/agent/app/dto/request/runtime.go b/agent/app/dto/request/runtime.go new file mode 100644 index 0000000..3d7a844 --- /dev/null +++ b/agent/app/dto/request/runtime.go @@ -0,0 +1,150 @@ +package request + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" +) + +type RuntimeSearch struct { + dto.PageInfo + Type string `json:"type"` + Name string `json:"name"` + Status string `json:"status"` +} + +type RuntimeCreate struct { + AppDetailID uint `json:"appDetailId"` + Name string `json:"name"` + Resource string `json:"resource"` + Image string `json:"image"` + Type string `json:"type"` + Version string `json:"version"` + Source string `json:"source"` + CodeDir string `json:"codeDir"` + Remark string `json:"remark"` + + Params map[string]interface{} `json:"params"` + NodeConfig +} + +type NodeConfig struct { + Install bool `json:"install"` + Clean bool `json:"clean"` + ExposedPorts []ExposedPort `json:"exposedPorts"` + Environments []Environment `json:"environments"` + Volumes []Volume `json:"volumes"` + ExtraHosts []ExtraHost `json:"extraHosts"` +} + +type Environment struct { + Key string `json:"key"` + Value string `json:"value"` +} +type Volume struct { + Source string `json:"source"` + Target string `json:"target"` +} + +type ExposedPort struct { + HostPort int `json:"hostPort"` + ContainerPort int `json:"containerPort"` + HostIP string `json:"hostIP"` +} + +type ExtraHost struct { + Hostname string `json:"hostname"` + IP string `json:"ip"` +} + +type RuntimeDelete struct { + ID uint `json:"id"` + ForceDelete bool `json:"forceDelete"` +} + +type RuntimeUpdate struct { + Name string `json:"name"` + ID uint `json:"id"` + Image string `json:"image"` + Version string `json:"version"` + Rebuild bool `json:"rebuild"` + Source string `json:"source"` + CodeDir string `json:"codeDir"` + Remark string `json:"remark"` + + Params map[string]interface{} `json:"params"` + NodeConfig +} + +type NodePackageReq struct { + CodeDir string `json:"codeDir"` +} + +type RuntimeOperate struct { + Operate string `json:"operate"` + ID uint `json:"ID"` +} + +type NodeModuleOperateReq struct { + Operate string `json:"operate" validate:"oneof=install uninstall update"` + ID uint `json:"ID" validate:"required"` + Module string `json:"module"` + PkgManager string `json:"pkgManager" validate:"oneof=npm yarn"` +} + +type NodeModuleReq struct { + ID uint `json:"ID" validate:"required"` +} + +type PHPExtensionInstallReq struct { + ID uint `json:"ID" validate:"required"` + Name string `json:"name" validate:"required"` + TaskID string `json:"taskID"` +} + +type PHPConfigUpdate struct { + ID uint `json:"id" validate:"required"` + Params map[string]string `json:"params"` + Scope string `json:"scope" validate:"required"` + DisableFunctions []string `json:"disableFunctions"` + UploadMaxSize string `json:"uploadMaxSize"` + MaxExecutionTime string `json:"maxExecutionTime"` +} + +type PHPFileUpdate struct { + ID uint `json:"id" validate:"required"` + Type string `json:"type" validate:"required"` + Content string `json:"content" validate:"required"` +} + +type PHPFileReq struct { + ID uint `json:"id" validate:"required"` + Type string `json:"type" validate:"required"` +} + +type FPMConfig struct { + ID uint `json:"id" validate:"required"` + Params map[string]interface{} `json:"params" validate:"required"` +} + +type PHPSupervisorProcessConfig struct { + ID uint `json:"id" validate:"required"` + SupervisorProcessConfig +} + +type PHPSupervisorProcessFileReq struct { + ID uint `json:"id" validate:"required"` + SupervisorProcessFileReq +} + +type PHPContainerConfig struct { + ID uint `json:"id" validate:"required"` + ContainerName string `json:"containerName"` + ExposedPorts []ExposedPort `json:"exposedPorts"` + Environments []Environment `json:"environments"` + Volumes []Volume `json:"volumes"` + ExtraHosts []ExtraHost `json:"extraHosts"` +} + +type RuntimeRemark struct { + ID uint `json:"id" validate:"required"` + Remark string `json:"remark"` +} diff --git a/agent/app/dto/request/task.go b/agent/app/dto/request/task.go new file mode 100644 index 0000000..725b8fc --- /dev/null +++ b/agent/app/dto/request/task.go @@ -0,0 +1 @@ +package request diff --git a/agent/app/dto/request/tensorrt_llm.go b/agent/app/dto/request/tensorrt_llm.go new file mode 100644 index 0000000..7a9604d --- /dev/null +++ b/agent/app/dto/request/tensorrt_llm.go @@ -0,0 +1,40 @@ +package request + +import "github.com/1Panel-dev/1Panel/agent/app/dto" + +type TensorRTLLMSearch struct { + dto.PageInfo + Name string `json:"name"` +} + +type TensorRTLLMCreate struct { + Name string `json:"name" validate:"required"` + ContainerName string `json:"containerName" validate:"required"` + Version string `json:"version" validate:"required"` + ModelDir string `json:"modelDir" validate:"required"` + Image string `json:"image" validate:"required"` + Command string `json:"command" validate:"required"` + ModelType string `json:"modelType"` + ModelSpeedup bool `json:"modelSpeedup"` + DockerConfig +} + +type TensorRTLLMUpdate struct { + ID uint `json:"id" validate:"required"` + TensorRTLLMCreate +} + +type TensorRTLLMDelete struct { + ID uint `json:"id" validate:"required"` +} + +type TensorRTLLMOperate struct { + ID uint `json:"id" validate:"required"` + Operate string `json:"operate" validate:"required"` +} + +type DockerConfig struct { + ExposedPorts []ExposedPort `json:"exposedPorts"` + Environments []Environment `json:"environments"` + Volumes []Volume `json:"volumes"` +} diff --git a/agent/app/dto/request/website.go b/agent/app/dto/request/website.go new file mode 100644 index 0000000..8b747a4 --- /dev/null +++ b/agent/app/dto/request/website.go @@ -0,0 +1,374 @@ +package request + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" +) + +type WebsiteSearch struct { + dto.PageInfo + Name string `json:"name"` + OrderBy string `json:"orderBy" validate:"required,oneof=primary_domain type status createdAt expire_date created_at favorite"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` + WebsiteGroupID uint `json:"websiteGroupId"` + Type string `json:"type"` +} + +type WebsiteCreate struct { + Type string `json:"type" validate:"required"` + Alias string `json:"alias" validate:"required"` + Remark string `json:"remark"` + Proxy string `json:"proxy"` + WebsiteGroupID uint `json:"webSiteGroupID" validate:"required"` + IPV6 bool `json:"IPV6"` + + Domains []WebsiteDomain `json:"domains"` + + AppType string `json:"appType" validate:"oneof=new installed"` + AppInstall NewAppInstall `json:"appInstall"` + AppID uint `json:"appID"` + AppInstallID uint `json:"appInstallID"` + + RuntimeID uint `json:"runtimeID"` + TaskID string `json:"taskID"` + ParentWebsiteID uint `json:"parentWebsiteID"` + + SiteDir string `json:"siteDir"` + + RuntimeConfig + FtpConfig + DataBaseConfig + SSLConfig + StreamConfig +} + +type StreamConfig struct { + StreamPorts string `json:"streamPorts"` + Name string `json:"name"` + Algorithm string `json:"algorithm"` + UDP bool `json:"udp"` + + Servers []dto.NginxUpstreamServer `json:"servers"` +} + +type StreamUpdate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + + StreamConfig +} + +type WebsiteOptionReq struct { + Types []string `json:"types"` +} + +type RuntimeConfig struct { + ProxyType string `json:"proxyType"` + Port int `json:"port"` +} + +type FtpConfig struct { + FtpUser string `json:"ftpUser"` + FtpPassword string `json:"ftpPassword"` +} + +type DataBaseConfig struct { + CreateDb bool `json:"createDb"` + DbName string `json:"dbName"` + DbUser string `json:"dbUser"` + DbPassword string `json:"dbPassword"` + DbHost string `json:"dbHost"` + DBFormat string `json:"dbFormat"` +} + +type SSLConfig struct { + EnableSSL bool `json:"enableSSL"` + WebsiteSSLID uint `json:"websiteSSLID"` +} + +type NewAppInstall struct { + Name string `json:"name"` + AppDetailId uint `json:"appDetailID"` + Params map[string]interface{} `json:"params"` + + AppContainerConfig +} + +type WebsiteInstallCheckReq struct { + InstallIds []uint `json:"InstallIds"` +} + +type WebsiteUpdate struct { + ID uint `json:"id" validate:"required"` + PrimaryDomain string `json:"primaryDomain" validate:"required"` + Remark string `json:"remark"` + WebsiteGroupID uint `json:"webSiteGroupID"` + ExpireDate string `json:"expireDate"` + IPV6 bool `json:"IPV6"` + Favorite bool `json:"favorite"` +} + +type WebsiteDelete struct { + ID uint `json:"id" validate:"required"` + DeleteApp bool `json:"deleteApp"` + DeleteBackup bool `json:"deleteBackup"` + ForceDelete bool `json:"forceDelete"` + DeleteDB bool `json:"deleteDB"` +} + +type WebsiteOp struct { + ID uint `json:"id" validate:"required"` + Operate string `json:"operate"` +} + +type BatchWebsiteOp struct { + IDs []uint `json:"ids" validate:"required"` + Operate string `json:"operate" validate:"required"` + TaskID string `json:"taskID" validate:"required"` +} + +type BatchWebsiteGroup struct { + IDs []uint `json:"ids" validate:"required"` + GroupID uint `json:"groupID" validate:"required"` +} + +type BatchWebsiteHttps struct { + IDs []uint `json:"ids" validate:"required"` + TaskID string `json:"taskID" validate:"required"` + WebsiteSSLID uint `json:"websiteSSLId"` + Type string `json:"type" validate:"oneof=existed auto manual"` + PrivateKey string `json:"privateKey"` + Certificate string `json:"certificate"` + PrivateKeyPath string `json:"privateKeyPath"` + CertificatePath string `json:"certificatePath"` + ImportType string `json:"importType"` + HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` + SSLProtocol []string `json:"SSLProtocol"` + Algorithm string `json:"algorithm"` + Hsts bool `json:"hsts"` + HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"` + HttpsPorts []int `json:"httpsPorts"` + Http3 bool `json:"http3"` +} + +type WebsiteRedirectUpdate struct { + WebsiteID uint `json:"websiteId" validate:"required"` + Key string `json:"key" validate:"required"` + Enable bool `json:"enable"` +} + +type WebsiteRecover struct { + WebsiteName string `json:"websiteName" validate:"required"` + Type string `json:"type" validate:"required"` + BackupName string `json:"backupName" validate:"required"` +} + +type WebsiteRecoverByFile struct { + WebsiteName string `json:"websiteName" validate:"required"` + Type string `json:"type" validate:"required"` + FileDir string `json:"fileDir" validate:"required"` + FileName string `json:"fileName" validate:"required"` +} + +type WebsiteGroupCreate struct { + Name string `json:"name" validate:"required"` +} + +type WebsiteGroupUpdate struct { + ID uint `json:"id" validate:"required"` + Name string `json:"name"` + Default bool `json:"default"` +} + +type WebsiteDomainCreate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Domains []WebsiteDomain `json:"domains" validate:"required"` +} + +type WebsiteDomainUpdate struct { + ID uint `json:"id" validate:"required"` + SSL bool `json:"ssl"` +} + +type WebsiteDomain struct { + Domain string `json:"domain" validate:"required"` + Port int `json:"port"` + SSL bool `json:"ssl"` +} + +type WebsiteDomainDelete struct { + ID uint `json:"id" validate:"required"` +} + +type WebsiteHTTPSOp struct { + WebsiteID uint `json:"websiteId" validate:"required"` + Enable bool `json:"enable"` + WebsiteSSLID uint `json:"websiteSSLId"` + Type string `json:"type" validate:"oneof=existed auto manual"` + PrivateKey string `json:"privateKey"` + Certificate string `json:"certificate"` + PrivateKeyPath string `json:"privateKeyPath"` + CertificatePath string `json:"certificatePath"` + ImportType string `json:"importType"` + HttpConfig string `json:"httpConfig" validate:"oneof=HTTPSOnly HTTPAlso HTTPToHTTPS"` + SSLProtocol []string `json:"SSLProtocol"` + Algorithm string `json:"algorithm"` + Hsts bool `json:"hsts"` + HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"` + HttpsPorts []int `json:"httpsPorts"` + Http3 bool `json:"http3"` +} + +type WebsiteNginxUpdate struct { + ID uint `json:"id" validate:"required"` + Content string `json:"content" validate:"required"` +} + +type WebsiteLogReq struct { + ID uint `json:"id" validate:"required"` + Operate string `json:"operate" validate:"required"` + LogType string `json:"logType" validate:"required"` + Page int `json:"page"` + PageSize int `json:"pageSize"` +} + +type WebsiteDefaultUpdate struct { + ID uint `json:"id"` +} + +type WebsitePHPVersionReq struct { + WebsiteID uint `json:"websiteID" validate:"required"` + RuntimeID uint `json:"runtimeID"` +} + +type WebsiteUpdateDir struct { + ID uint `json:"id" validate:"required"` + SiteDir string `json:"siteDir" validate:"required"` +} + +type WebsiteUpdateDirPermission struct { + ID uint `json:"id" validate:"required"` + User string `json:"user" validate:"required"` + Group string `json:"group" validate:"required"` +} + +type WebsiteProxyConfig struct { + ID uint `json:"id" validate:"required"` + Operate string `json:"operate" validate:"required"` + Enable bool `json:"enable" ` + Cache bool `json:"cache" ` + CacheTime int `json:"cacheTime"` + CacheUnit string `json:"cacheUnit"` + ServerCacheTime int `json:"serverCacheTime"` + ServerCacheUnit string `json:"serverCacheUnit"` + Name string `json:"name" validate:"required"` + Modifier string `json:"modifier"` + Match string `json:"match" validate:"required"` + ProxyPass string `json:"proxyPass" validate:"required"` + ProxyHost string `json:"proxyHost" validate:"required"` + Content string `json:"content"` + FilePath string `json:"filePath"` + Replaces map[string]string `json:"replaces"` + SNI bool `json:"sni"` + ProxySSLName string `json:"proxySSLName"` + CorsConfig +} + +type CorsConfig struct { + Cors bool `json:"cors"` + AllowOrigins string `json:"allowOrigins"` + AllowMethods string `json:"allowMethods"` + AllowHeaders string `json:"allowHeaders"` + AllowCredentials bool `json:"allowCredentials"` + Preflight bool `json:"preflight"` +} + +type CorsConfigReq struct { + WebsiteID uint `json:"websiteID" validate:"required"` + CorsConfig +} + +type WebsiteProxyReq struct { + ID uint `json:"id" validate:"required"` +} + +type WebsiteRedirectReq struct { + WebsiteID uint `json:"websiteId" validate:"required"` +} + +type WebsiteCommonReq struct { + ID uint `json:"id" validate:"required"` +} + +type WafWebsite struct { + Key string `json:"key"` + Domains []string `json:"domains"` + Host []string `json:"host"` +} + +type WebsiteHtmlReq struct { + Type string `json:"type" validate:"required"` +} + +type WebsiteHtmlUpdate struct { + Type string `json:"type" validate:"required"` + Content string `json:"content" validate:"required"` + Sync bool `json:"sync"` +} + +type WebsiteLBCreate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Name string `json:"name" validate:"required"` + Algorithm string `json:"algorithm"` + Servers []dto.NginxUpstreamServer `json:"servers"` +} + +type WebsiteLBUpdate struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Name string `json:"name" validate:"required"` + Algorithm string `json:"algorithm"` + Servers []dto.NginxUpstreamServer `json:"servers"` +} + +type WebsiteLBDelete struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Name string `json:"name" validate:"required"` +} + +type WebsiteLBUpdateFile struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Name string `json:"name" validate:"required"` + Content string `json:"content" validate:"required"` +} + +type WebsiteRealIP struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Open bool `json:"open"` + IPFrom string `json:"ipFrom"` + IPHeader string `json:"ipHeader"` + IPOther string `json:"ipOther"` +} + +type ChangeDatabase struct { + WebsiteID uint `json:"websiteID" validate:"required"` + DatabaseID uint `json:"databaseID" ` + DatabaseType string `json:"databaseType" ` +} + +type WebsiteProxyDel struct { + ID uint `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` +} + +type CrossSiteAccessOp struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Operation string `json:"operation" validate:"required,oneof=Enable Disable"` +} + +type ExecComposerReq struct { + Command string `json:"command" validate:"required"` + ExtCommand string `json:"extCommand"` + Mirror string `json:"mirror" validate:"required"` + Dir string `json:"dir" validate:"required"` + User string `json:"user" validate:"required"` + WebsiteID uint `json:"websiteID" validate:"required"` + TaskID string `json:"taskID" validate:"required"` +} diff --git a/agent/app/dto/request/website_ssl.go b/agent/app/dto/request/website_ssl.go new file mode 100644 index 0000000..2e96309 --- /dev/null +++ b/agent/app/dto/request/website_ssl.go @@ -0,0 +1,167 @@ +package request + +import "github.com/1Panel-dev/1Panel/agent/app/dto" + +type WebsiteSSLSearch struct { + dto.PageInfo + AcmeAccountID string `json:"acmeAccountID"` + Domain string `json:"domain"` + OrderBy string `json:"orderBy" validate:"required,oneof=expire_date"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` +} + +type WebsiteSSLCreate struct { + PrimaryDomain string `json:"primaryDomain" validate:"required"` + OtherDomains string `json:"otherDomains"` + Provider string `json:"provider" validate:"required"` + AcmeAccountID uint `json:"acmeAccountId" validate:"required"` + DnsAccountID uint `json:"dnsAccountId"` + AutoRenew bool `json:"autoRenew"` + KeyType string `json:"keyType"` + Apply bool `json:"apply"` + PushDir bool `json:"pushDir"` + Dir string `json:"dir"` + ID uint `json:"id"` + Description string `json:"description"` + DisableCNAME bool `json:"disableCNAME"` + SkipDNS bool `json:"skipDNS"` + Nameserver1 string `json:"nameserver1"` + Nameserver2 string `json:"nameserver2"` + ExecShell bool `json:"execShell"` + Shell string `json:"shell"` + PushNode bool `json:"pushNode"` + Nodes string `json:"nodes"` + IsIp bool `json:"isIp"` +} + +type WebsiteDNSReq struct { + AcmeAccountID uint `json:"acmeAccountId" validate:"required"` + WebsiteSSLID uint `json:"websiteSSLId" validate:"required"` +} + +type WebsiteSSLRenew struct { + SSLID uint `json:"SSLId" validate:"required"` +} + +type WebsiteSSLApply struct { + ID uint `json:"ID" validate:"required"` + SkipDNSCheck bool `json:"skipDNSCheck"` + Nameservers []string `json:"nameservers"` + DisableLog bool `json:"disableLog"` +} + +type WebsiteSSLObtain struct { + ID uint `json:"ID" validate:"required"` + TXTRecords map[string]string +} + +type WebsiteAcmeAccountCreate struct { + Email string `json:"email" validate:"required"` + Type string `json:"type" validate:"required,oneof=letsencrypt zerossl buypass google custom"` + KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"` + EabKid string `json:"eabKid"` + EabHmacKey string `json:"eabHmacKey"` + UseProxy bool `json:"useProxy"` + CaDirURL string `json:"caDirURL"` + UseEAB bool `json:"useEAB"` +} + +type WebsiteAcmeAccountUpdate struct { + ID uint `json:"id" validate:"required"` + UseProxy bool `json:"useProxy"` +} + +type WebsiteDnsAccountCreate struct { + Name string `json:"name" validate:"required"` + Type string `json:"type" validate:"required"` + Authorization map[string]string `json:"authorization" validate:"required"` +} + +type WebsiteDnsAccountUpdate struct { + ID uint `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Type string `json:"type" validate:"required"` + Authorization map[string]string `json:"authorization" validate:"required"` +} + +type WebsiteResourceReq struct { + ID uint `json:"id" validate:"required"` +} + +type WebsiteBatchDelReq struct { + IDs []uint `json:"ids" validate:"required"` +} + +type WebsiteSSLUpdate struct { + ID uint `json:"id" validate:"required"` + AutoRenew bool `json:"autoRenew"` + Description string `json:"description"` + PrimaryDomain string `json:"primaryDomain" validate:"required"` + OtherDomains string `json:"otherDomains"` + Provider string `json:"provider" validate:"required"` + AcmeAccountID uint `json:"acmeAccountId"` + DnsAccountID uint `json:"dnsAccountId"` + KeyType string `json:"keyType"` + Apply bool `json:"apply"` + PushDir bool `json:"pushDir"` + Dir string `json:"dir"` + DisableCNAME bool `json:"disableCNAME"` + SkipDNS bool `json:"skipDNS"` + Nameserver1 string `json:"nameserver1"` + Nameserver2 string `json:"nameserver2"` + ExecShell bool `json:"execShell"` + Shell string `json:"shell"` + PushNode bool `json:"pushNode"` + Nodes string `json:"nodes"` +} + +type WebsiteSSLUpload struct { + PrivateKey string `json:"privateKey"` + Certificate string `json:"certificate"` + PrivateKeyPath string `json:"privateKeyPath"` + CertificatePath string `json:"certificatePath"` + Type string `json:"type" validate:"required,oneof=paste local"` + SSLID uint `json:"sslID"` + Description string `json:"description"` +} + +type WebsiteCASearch struct { + dto.PageInfo +} + +type WebsiteCACreate struct { + CommonName string `json:"commonName" validate:"required"` + Country string `json:"country" validate:"required"` + Organization string `json:"organization" validate:"required"` + OrganizationUint string `json:"organizationUint"` + Name string `json:"name" validate:"required"` + KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"` + Province string `json:"province" ` + City string `json:"city"` +} + +type WebsiteCAObtain struct { + ID uint `json:"id" validate:"required"` + Domains string `json:"domains" validate:"required"` + KeyType string `json:"keyType" validate:"required,oneof=P256 P384 2048 3072 4096 8192"` + Time int `json:"time" validate:"required"` + Unit string `json:"unit" validate:"required"` + PushDir bool `json:"pushDir"` + Dir string `json:"dir"` + AutoRenew bool `json:"autoRenew"` + Renew bool `json:"renew"` + SSLID uint `json:"sslID"` + Description string `json:"description"` + ExecShell bool `json:"execShell"` + Shell string `json:"shell"` +} + +type WebsiteCARenew struct { + SSLID uint `json:"SSLID" validate:"required"` +} + +type WebsiteSSLFileUpload struct { + Type string `json:"type"` + Description string `json:"description"` + SSLID uint64 `json:"sslID"` +} diff --git a/agent/app/dto/response/app.go b/agent/app/dto/response/app.go new file mode 100644 index 0000000..42fc3ba --- /dev/null +++ b/agent/app/dto/response/app.go @@ -0,0 +1,187 @@ +package response + +import ( + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type AppRes struct { + Items []*AppItem `json:"items"` + Total int64 `json:"total"` +} + +type AppUpdateRes struct { + CanUpdate bool `json:"canUpdate"` + IsSyncing bool `json:"isSyncing"` + AppStoreLastModified int `json:"appStoreLastModified"` + AppList *dto.AppList `json:"appList"` +} + +type AppDTO struct { + model.App + Installed bool `json:"installed"` + Versions []string `json:"versions"` + Tags []TagDTO `json:"tags"` +} + +type AppItem struct { + Name string `json:"name"` + Key string `json:"key"` + ID uint `json:"id"` + Description string `json:"description"` + Status string `json:"status"` + Installed bool `json:"installed"` + Limit int `json:"limit"` + Tags []string `json:"tags"` + GpuSupport bool `json:"gpuSupport"` + Recommend int `json:"recommend"` + Type string `json:"type"` + BatchInstallSupport bool `json:"batchInstallSupport"` +} + +type TagDTO struct { + ID uint `json:"id"` + Key string `json:"key"` + Name string `json:"name"` +} + +type AppInstalledCheck struct { + IsExist bool `json:"isExist"` + Name string `json:"name"` + App string `json:"app"` + Version string `json:"version"` + Status string `json:"status"` + CreatedAt time.Time `json:"createdAt"` + LastBackupAt string `json:"lastBackupAt"` + AppInstallID uint `json:"appInstallId"` + ContainerName string `json:"containerName"` + InstallPath string `json:"installPath"` + HttpPort int `json:"httpPort"` + HttpsPort int `json:"httpsPort"` + WebsiteDir string `json:"websiteDir"` +} + +type AppDetailDTO struct { + model.AppDetail + Enable bool `json:"enable"` + Params interface{} `json:"params"` + Image string `json:"image"` + HostMode bool `json:"hostMode"` + Architectures string `json:"architectures"` + MemoryRequired int `json:"memoryRequired"` + GpuSupport bool `json:"gpuSupport"` +} + +type AppDetailSimpleDTO struct { + ID uint `json:"id"` +} + +type IgnoredApp struct { + Icon string `json:"icon"` + Name string `json:"name"` + Version string `json:"version"` + DetailID uint `json:"detailID"` +} + +type AppInstalledDTO struct { + model.AppInstall + Total int `json:"total"` + Ready int `json:"ready"` + AppName string `json:"appName"` + Icon string `json:"icon"` + CanUpdate bool `json:"canUpdate"` + Path string `json:"path"` +} + +type AppDetail struct { + Website string `json:"website"` + Document string `json:"document"` + Github string `json:"github"` +} + +type AppInstallDTO struct { + ID uint `json:"id"` + Name string `json:"name"` + AppID uint `json:"appID"` + AppDetailID uint `json:"appDetailID"` + Version string `json:"version"` + Status string `json:"status"` + Message string `json:"message"` + HttpPort int `json:"httpPort"` + HttpsPort int `json:"httpsPort"` + Path string `json:"path"` + CanUpdate bool `json:"canUpdate"` + Icon string `json:"icon"` + AppName string `json:"appName"` + Ready int `json:"ready"` + Total int `json:"total"` + AppKey string `json:"appKey"` + AppType string `json:"appType"` + AppStatus string `json:"appStatus"` + DockerCompose string `json:"dockerCompose"` + WebUI string `json:"webUI"` + CreatedAt time.Time `json:"createdAt"` + Favorite bool `json:"favorite"` + App AppDetail `json:"app"` + Container string `json:"container"` + IsEdit bool `json:"isEdit"` + LinkDB bool `json:"linkDB"` + ServiceName string `json:"serviceName"` +} + +type AppInstallInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Version string `json:"version"` + Status string `json:"status"` + Message string `json:"message"` + HttpPort int `json:"HttpPort"` + Container string `json:"container"` + ComposePath string `json:"composePath"` + AppKey string `json:"appKey"` + AppPorts []int `json:"appPorts"` + + Env map[string]interface{} `json:"env"` +} + +type DatabaseConn struct { + Status string `json:"status"` + Username string `json:"username"` + Password string `json:"password"` + ContainerName string `json:"containerName"` + ServiceName string `json:"serviceName"` + Port int64 `json:"port"` +} + +type AppService struct { + Label string `json:"label"` + Value string `json:"value"` + Config interface{} `json:"config"` + From string `json:"from"` + Status string `json:"status"` +} + +type AppParam struct { + Value interface{} `json:"value"` + Edit bool `json:"edit"` + Key string `json:"key"` + Rule string `json:"rule"` + LabelZh string `json:"labelZh"` + LabelEn string `json:"labelEn"` + Type string `json:"type"` + Values interface{} `json:"values"` + ShowValue string `json:"showValue"` + Required bool `json:"required"` + Multiple bool `json:"multiple"` + Label dto.Locale `json:"label"` +} + +type AppConfig struct { + Params []AppParam `json:"params"` + RawCompose string `json:"rawCompose"` + request.AppContainerConfig +} diff --git a/agent/app/dto/response/app_ignore_upgrade.go b/agent/app/dto/response/app_ignore_upgrade.go new file mode 100644 index 0000000..570fc55 --- /dev/null +++ b/agent/app/dto/response/app_ignore_upgrade.go @@ -0,0 +1,10 @@ +package response + +type AppIgnoreUpgradeDTO struct { + ID uint `json:"ID"` + AppID uint `json:"appID"` + AppDetailID uint `json:"appDetailID"` + Scope string `json:"scope"` + Version string `json:"version"` + Name string `json:"name"` +} diff --git a/agent/app/dto/response/disk.go b/agent/app/dto/response/disk.go new file mode 100644 index 0000000..518dac7 --- /dev/null +++ b/agent/app/dto/response/disk.go @@ -0,0 +1,37 @@ +package response + +type DiskInfo struct { + DiskBasicInfo + Partitions []DiskBasicInfo `json:"partitions"` +} + +type DiskBasicInfo struct { + Device string `json:"device"` + Size string `json:"size"` + Model string `json:"model"` + DiskType string `json:"diskType"` + IsRemovable bool `json:"isRemovable"` + IsSystem bool `json:"isSystem"` + Filesystem string `json:"filesystem"` + Used string `json:"used"` + Avail string `json:"avail"` + UsePercent int `json:"usePercent"` + MountPoint string `json:"mountPoint"` + IsMounted bool `json:"isMounted"` + Serial string `json:"serial"` +} + +type CompleteDiskInfo struct { + Disks []DiskInfo `json:"disks"` + UnpartitionedDisks []DiskBasicInfo `json:"unpartitionedDisks"` + SystemDisks []DiskInfo `json:"systemDisks"` + TotalDisks int `json:"totalDisks"` + TotalCapacity int64 `json:"totalCapacity"` +} + +type MountInfo struct { + Device string `json:"device"` + MountPoint string `json:"mountPoint"` + Filesystem string `json:"filesystem"` + Options string `json:"options"` +} diff --git a/agent/app/dto/response/favorite.go b/agent/app/dto/response/favorite.go new file mode 100644 index 0000000..9998978 --- /dev/null +++ b/agent/app/dto/response/favorite.go @@ -0,0 +1,7 @@ +package response + +import "github.com/1Panel-dev/1Panel/agent/app/model" + +type FavoriteDTO struct { + model.Favorite +} diff --git a/agent/app/dto/response/file.go b/agent/app/dto/response/file.go new file mode 100644 index 0000000..14add3d --- /dev/null +++ b/agent/app/dto/response/file.go @@ -0,0 +1,87 @@ +package response + +import ( + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +type FileInfo struct { + files.FileInfo +} + +type UploadInfo struct { + Name string `json:"name"` + Size int `json:"size"` + CreatedAt string `json:"createdAt"` +} + +type FileTree struct { + ID string `json:"id"` + Name string `json:"name"` + Path string `json:"path"` + IsDir bool `json:"isDir"` + Extension string `json:"extension"` + Children []FileTree `json:"children"` +} + +type DirSizeRes struct { + Size int64 `json:"size" validate:"required"` +} + +type FileProcessKeys struct { + Keys []string `json:"keys"` +} + +type FileWgetRes struct { + Key string `json:"key"` +} + +type FileLineContent struct { + End bool `json:"end"` + Path string `json:"path"` + Total int `json:"total"` + TaskStatus string `json:"taskStatus"` + Lines []string `json:"lines"` + Scope string `json:"scope"` + TotalLines int `json:"totalLines"` +} + +type FileExist struct { + Exist bool `json:"exist"` +} + +type ExistFileInfo struct { + Name string `json:"name"` + Path string `json:"path"` + Size int64 `json:"size"` + ModTime time.Time `json:"modTime"` + IsDir bool `json:"isDir"` +} + +type UserInfo struct { + Username string `json:"username"` + Group string `json:"group"` +} + +type UserGroupResponse struct { + Users []UserInfo `json:"users"` + Groups []string `json:"groups"` +} + +type DepthDirSizeRes struct { + Path string `json:"path"` + Size int64 `json:"size"` +} + +type FileConvertLog struct { + Date string `json:"date"` + Type string `json:"type"` + Log string `json:"log"` + Status string `json:"status"` + Message string `json:"message"` +} + +type FileRemarksRes struct { + Remarks map[string]string `json:"remarks"` +} diff --git a/agent/app/dto/response/host_tool.go b/agent/app/dto/response/host_tool.go new file mode 100644 index 0000000..59ad0b2 --- /dev/null +++ b/agent/app/dto/response/host_tool.go @@ -0,0 +1,44 @@ +package response + +type HostToolRes struct { + Type string `json:"type"` + Config interface{} `json:"config"` +} + +type Supervisor struct { + ConfigPath string `json:"configPath"` + IncludeDir string `json:"includeDir"` + LogPath string `json:"logPath"` + IsExist bool `json:"isExist"` + Init bool `json:"init"` + Msg string `json:"msg"` + Version string `json:"version"` + Status string `json:"status"` + CtlExist bool `json:"ctlExist"` + ServiceName string `json:"serviceName"` +} + +type HostToolConfig struct { + Content string `json:"content"` +} + +type SupervisorProcessConfig struct { + Name string `json:"name"` + Command string `json:"command"` + User string `json:"user"` + Dir string `json:"dir"` + Numprocs string `json:"numprocs"` + Msg string `json:"msg"` + Status []ProcessStatus `json:"status"` + AutoRestart string `json:"autoRestart"` + AutoStart string `json:"autoStart"` + Environment string `json:"environment"` +} + +type ProcessStatus struct { + Name string `json:"name"` + Status string `json:"status"` + PID string `json:"PID"` + Uptime string `json:"uptime"` + Msg string `json:"msg"` +} diff --git a/agent/app/dto/response/mcp_server.go b/agent/app/dto/response/mcp_server.go new file mode 100644 index 0000000..29cf71f --- /dev/null +++ b/agent/app/dto/response/mcp_server.go @@ -0,0 +1,26 @@ +package response + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type McpServersRes struct { + Items []McpServerDTO `json:"items"` + Total int64 `json:"total"` +} + +type McpServerDTO struct { + model.McpServer + Environments []request.Environment `json:"environments"` + Volumes []request.Volume `json:"volumes"` +} + +type McpBindDomainRes struct { + Domain string `json:"domain"` + SSLID uint `json:"sslID"` + AcmeAccountID uint `json:"acmeAccountID"` + AllowIPs []string `json:"allowIPs"` + WebsiteID uint `json:"websiteID"` + ConnUrl string `json:"connUrl"` +} diff --git a/agent/app/dto/response/nginx.go b/agent/app/dto/response/nginx.go new file mode 100644 index 0000000..1e0b0e0 --- /dev/null +++ b/agent/app/dto/response/nginx.go @@ -0,0 +1,87 @@ +package response + +import "github.com/1Panel-dev/1Panel/agent/app/dto" + +type NginxStatus struct { + Active int `json:"active"` + Accepts int `json:"accepts"` + Handled int `json:"handled"` + Requests int `json:"requests"` + Reading int `json:"reading"` + Writing int `json:"writing"` + Waiting int `json:"waiting"` +} + +type NginxParam struct { + Name string `json:"name"` + Params []string `json:"params"` +} + +type NginxAuthRes struct { + Enable bool `json:"enable"` + Items []dto.NginxAuth `json:"items"` +} + +type NginxPathAuthRes struct { + dto.NginxPathAuth +} + +type NginxAntiLeechRes struct { + Enable bool `json:"enable"` + Extends string `json:"extends"` + Return string `json:"return"` + ServerNames []string `json:"serverNames"` + Cache bool `json:"cache"` + CacheTime int `json:"cacheTime"` + CacheUint string `json:"cacheUint"` + NoneRef bool `json:"noneRef"` + LogEnable bool `json:"logEnable"` + Blocked bool `json:"blocked"` +} + +type NginxRedirectConfig struct { + WebsiteID uint `json:"websiteID"` + Name string `json:"name"` + Domains []string `json:"domains"` + KeepPath bool `json:"keepPath"` + Enable bool `json:"enable"` + Type string `json:"type"` + Redirect string `json:"redirect"` + Path string `json:"path"` + Target string `json:"target"` + FilePath string `json:"filePath"` + Content string `json:"content"` + RedirectRoot bool `json:"redirectRoot"` +} + +type NginxFile struct { + Content string `json:"content"` +} + +type NginxProxyCache struct { + Open bool `json:"open"` + CacheLimit float64 `json:"cacheLimit"` + CacheLimitUnit string `json:"cacheLimitUnit" ` + ShareCache int `json:"shareCache" ` + ShareCacheUnit string `json:"shareCacheUnit" ` + CacheExpire int `json:"cacheExpire" ` + CacheExpireUnit string `json:"cacheExpireUnit" ` +} + +type NginxModule struct { + Name string `json:"name"` + Script string `json:"script"` + Packages string `json:"packages"` + Params string `json:"params"` + Enable bool `json:"enable"` +} + +type NginxBuildConfig struct { + Mirror string `json:"mirror"` + Modules []NginxModule `json:"modules"` +} + +type NginxConfigRes struct { + Https bool `json:"https"` + SSLRejectHandshake bool `json:"sslRejectHandshake"` +} diff --git a/agent/app/dto/response/php_extensions.go b/agent/app/dto/response/php_extensions.go new file mode 100644 index 0000000..f3d11b3 --- /dev/null +++ b/agent/app/dto/response/php_extensions.go @@ -0,0 +1,7 @@ +package response + +import "github.com/1Panel-dev/1Panel/agent/app/model" + +type PHPExtensionsDTO struct { + model.PHPExtensions +} diff --git a/agent/app/dto/response/recycle_bin.go b/agent/app/dto/response/recycle_bin.go new file mode 100644 index 0000000..b5a0403 --- /dev/null +++ b/agent/app/dto/response/recycle_bin.go @@ -0,0 +1,14 @@ +package response + +import "time" + +type RecycleBinDTO struct { + Name string `json:"name"` + Size int `json:"size"` + Type string `json:"type"` + DeleteTime time.Time `json:"deleteTime"` + RName string `json:"rName"` + SourcePath string `json:"sourcePath"` + IsDir bool `json:"isDir"` + From string `json:"from"` +} diff --git a/agent/app/dto/response/runtime.go b/agent/app/dto/response/runtime.go new file mode 100644 index 0000000..059b1b0 --- /dev/null +++ b/agent/app/dto/response/runtime.go @@ -0,0 +1,86 @@ +package response + +import ( + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type RuntimeDTO struct { + ID uint `json:"id"` + Name string `json:"name"` + Resource string `json:"resource"` + AppDetailID uint `json:"appDetailID"` + AppID uint `json:"appID"` + Source string `json:"source"` + Status string `json:"status"` + Type string `json:"type"` + Image string `json:"image"` + Params map[string]interface{} `json:"params"` + Message string `json:"message"` + Version string `json:"version"` + CreatedAt time.Time `json:"createdAt"` + CodeDir string `json:"codeDir"` + AppParams []AppParam `json:"appParams"` + Port string `json:"port"` + Path string `json:"path"` + ExposedPorts []request.ExposedPort `json:"exposedPorts"` + Environments []request.Environment `json:"environments"` + Volumes []request.Volume `json:"volumes"` + ExtraHosts []request.ExtraHost `json:"extraHosts"` + ContainerStatus string `json:"containerStatus"` + Container string `json:"container"` + Remark string `json:"remark"` +} + +type PackageScripts struct { + Name string `json:"name"` + Script string `json:"script"` +} + +func NewRuntimeDTO(runtime model.Runtime) RuntimeDTO { + return RuntimeDTO{ + ID: runtime.ID, + Name: runtime.Name, + Resource: runtime.Resource, + AppDetailID: runtime.AppDetailID, + Status: runtime.Status, + Type: runtime.Type, + Image: runtime.Image, + Message: runtime.Message, + CreatedAt: runtime.CreatedAt, + CodeDir: runtime.CodeDir, + Version: runtime.Version, + Port: runtime.Port, + Path: runtime.GetPath(), + Container: runtime.ContainerName, + Remark: runtime.Remark, + } +} + +type NodeModule struct { + Name string `json:"name"` + Version string `json:"version"` + License string `json:"license"` + Description string `json:"description"` +} + +type SupportExtension struct { + Name string `json:"name"` + Description string `json:"description"` + Installed bool `json:"installed"` + Check string `json:"check"` + Versions []string `json:"versions"` + File string `json:"file"` +} + +type PHPExtensionRes struct { + Extensions []string `json:"extensions"` + SupportExtensions []SupportExtension `json:"supportExtensions"` +} + +type FpmStatusItem struct { + Key string `json:"key"` + Value interface{} `json:"value"` +} diff --git a/agent/app/dto/response/system.go b/agent/app/dto/response/system.go new file mode 100644 index 0000000..8088f18 --- /dev/null +++ b/agent/app/dto/response/system.go @@ -0,0 +1,8 @@ +package response + +type ComponentInfo struct { + Exists bool `json:"exists"` + Version string `json:"version"` + Path string `json:"path"` + Error string `json:"error"` +} diff --git a/agent/app/dto/response/task.go b/agent/app/dto/response/task.go new file mode 100644 index 0000000..a467149 --- /dev/null +++ b/agent/app/dto/response/task.go @@ -0,0 +1 @@ +package response diff --git a/agent/app/dto/response/tensorrt_llm.go b/agent/app/dto/response/tensorrt_llm.go new file mode 100644 index 0000000..fdedaf0 --- /dev/null +++ b/agent/app/dto/response/tensorrt_llm.go @@ -0,0 +1,25 @@ +package response + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type TensorRTLLMsRes struct { + Items []TensorRTLLMDTO `json:"items"` + Total int64 `json:"total"` +} + +type TensorRTLLMDTO struct { + model.TensorRTLLM + Version string `json:"version"` + Model string `json:"model"` + Dir string `json:"dir"` + ModelDir string `json:"modelDir"` + Image string `json:"image"` + Command string `json:"command"` + + ExposedPorts []request.ExposedPort `json:"exposedPorts"` + Environments []request.Environment `json:"environments"` + Volumes []request.Volume `json:"volumes"` +} diff --git a/agent/app/dto/response/website.go b/agent/app/dto/response/website.go new file mode 100644 index 0000000..8e059c7 --- /dev/null +++ b/agent/app/dto/response/website.go @@ -0,0 +1,130 @@ +package response + +import ( + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type WebsiteDTO struct { + model.Website + ErrorLogPath string `json:"errorLogPath"` + AccessLogPath string `json:"accessLogPath"` + SitePath string `json:"sitePath"` + AppName string `json:"appName"` + RuntimeName string `json:"runtimeName"` + RuntimeType string `json:"runtimeType"` + SiteDir string `json:"siteDir"` + OpenBaseDir bool `json:"openBaseDir"` + Algorithm string `json:"algorithm"` + UDP bool `json:"udp"` + + Servers []dto.NginxUpstreamServer `json:"servers"` +} + +type WebsiteRes struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Protocol string `json:"protocol"` + PrimaryDomain string `json:"primaryDomain"` + Type string `json:"type"` + Alias string `json:"alias"` + Remark string `json:"remark"` + Status string `json:"status"` + ExpireDate time.Time `json:"expireDate"` + SitePath string `json:"sitePath"` + AppName string `json:"appName"` + RuntimeName string `json:"runtimeName"` + SSLExpireDate time.Time `json:"sslExpireDate"` + SSLStatus string `json:"sslStatus"` + AppInstallID uint `json:"appInstallId"` + ChildSites []string `json:"childSites"` + ParentSite string `json:"parentSite"` + RuntimeType string `json:"runtimeType"` + Favorite bool `json:"favorite"` + IPV6 bool `json:"IPV6"` +} + +type WebsiteOption struct { + ID uint `json:"id"` + PrimaryDomain string `json:"primaryDomain"` + Alias string `json:"alias"` +} + +type WebsitePreInstallCheck struct { + Name string `json:"name"` + Status string `json:"status"` + Version string `json:"version"` + AppName string `json:"appName"` +} + +type WebsiteNginxConfig struct { + Enable bool `json:"enable"` + Params []NginxParam `json:"params"` +} + +type WebsiteHTTPS struct { + Enable bool `json:"enable"` + HttpConfig string `json:"httpConfig"` + SSL model.WebsiteSSL `json:"SSL"` + SSLProtocol []string `json:"SSLProtocol"` + Algorithm string `json:"algorithm"` + Hsts bool `json:"hsts"` + HstsIncludeSubDomains bool `json:"hstsIncludeSubDomains"` + HttpsPorts []int `json:"httpsPorts"` + HttpsPort string `json:"httpsPort"` + Http3 bool `json:"http3"` +} + +type WebsiteLog struct { + Enable bool `json:"enable"` + Content string `json:"content"` + End bool `json:"end"` + Path string `json:"path"` +} + +type PHPConfig struct { + Params map[string]string `json:"params"` + DisableFunctions []string `json:"disableFunctions"` + UploadMaxSize string `json:"uploadMaxSize"` + MaxExecutionTime string `json:"maxExecutionTime"` +} + +type NginxRewriteRes struct { + Content string `json:"content"` +} + +type WebsiteDirConfig struct { + Dirs []string `json:"dirs"` + User string `json:"user"` + UserGroup string `json:"userGroup"` + Msg string `json:"msg"` +} + +type WebsiteHtmlRes struct { + Content string `json:"content"` +} + +type WebsiteRealIP struct { + WebsiteID uint `json:"websiteID" validate:"required"` + Open bool `json:"open"` + IPFrom string `json:"ipFrom"` + IPHeader string `json:"ipHeader"` + IPOther string `json:"ipOther"` +} + +type Resource struct { + Name string `json:"name"` + Type string `json:"type"` + ResourceID uint `json:"resourceID"` + Detail interface{} `json:"detail"` +} + +type Database struct { + Name string `json:"name"` + DatabaseName string `json:"databaseName"` + Type string `json:"type"` + From string `json:"from"` + ID uint `json:"id"` +} diff --git a/agent/app/dto/response/website_ssl.go b/agent/app/dto/response/website_ssl.go new file mode 100644 index 0000000..8afb488 --- /dev/null +++ b/agent/app/dto/response/website_ssl.go @@ -0,0 +1,34 @@ +package response + +import "github.com/1Panel-dev/1Panel/agent/app/model" + +type WebsiteSSLDTO struct { + model.WebsiteSSL + LogPath string `json:"logPath"` +} + +type WebsiteDNSRes struct { + Key string `json:"resolve"` + Value string `json:"value"` + Domain string `json:"domain"` + Err string `json:"err"` +} + +type WebsiteAcmeAccountDTO struct { + model.WebsiteAcmeAccount +} + +type WebsiteDnsAccountDTO struct { + model.WebsiteDnsAccount + Authorization map[string]string `json:"authorization"` +} + +type WebsiteCADTO struct { + model.WebsiteCA + CommonName string `json:"commonName" ` + Country string `json:"country"` + Organization string `json:"organization"` + OrganizationUint string `json:"organizationUint"` + Province string `json:"province" ` + City string `json:"city"` +} diff --git a/agent/app/dto/setting.go b/agent/app/dto/setting.go new file mode 100644 index 0000000..090ef8f --- /dev/null +++ b/agent/app/dto/setting.go @@ -0,0 +1,96 @@ +package dto + +type SettingInfo struct { + DockerSockPath string `json:"dockerSockPath"` + SystemVersion string `json:"systemVersion"` + SystemIP string `json:"systemIP"` + + LocalTime string `json:"localTime"` + TimeZone string `json:"timeZone"` + NtpSite string `json:"ntpSite"` + + DefaultNetwork string `json:"defaultNetwork"` + DefaultIO string `json:"defaultIO"` + LastCleanTime string `json:"lastCleanTime"` + LastCleanSize string `json:"lastCleanSize"` + LastCleanData string `json:"lastCleanData"` + + MonitorStatus string `json:"monitorStatus"` + MonitorInterval string `json:"monitorInterval"` + MonitorStoreDays string `json:"monitorStoreDays"` + + AppStoreVersion string `json:"appStoreVersion"` + AppStoreLastModified string `json:"appStoreLastModified"` + AppStoreSyncStatus string `json:"appStoreSyncStatus"` + + FileRecycleBin string `json:"fileRecycleBin"` +} + +type SettingUpdate struct { + Key string `json:"key" validate:"required"` + Value string `json:"value"` +} + +type SyncTime struct { + NtpSite string `json:"ntpSite" validate:"required"` +} + +type CleanData struct { + SystemClean []CleanTree `json:"systemClean"` + BackupClean []CleanTree `json:"backupClean"` + UploadClean []CleanTree `json:"uploadClean"` + DownloadClean []CleanTree `json:"downloadClean"` + SystemLogClean []CleanTree `json:"systemLogClean"` + ContainerClean []CleanTree `json:"containerClean"` +} + +type CleanTree struct { + ID string `json:"id"` + Label string `json:"label"` + Children []CleanTree `json:"children"` + + Type string `json:"type"` + Name string `json:"name"` + + Size uint64 `json:"size"` + IsCheck bool `json:"isCheck"` + IsRecommend bool `json:"isRecommend"` +} + +type Clean struct { + TreeType string `json:"treeType"` + Name string `json:"name"` + Size uint64 `json:"size"` +} + +type SSHDefaultConn struct { + WithReset bool `json:"withReset"` + DefaultConn string `json:"defaultConn"` +} +type SSHConnData struct { + Addr string `json:"addr" validate:"required"` + Port uint `json:"port" validate:"required,number,max=65535,min=1"` + User string `json:"user" validate:"required"` + AuthMode string `json:"authMode" validate:"oneof=password key"` + Password string `json:"password"` + PrivateKey string `json:"privateKey"` + PassPhrase string `json:"passPhrase"` + + LocalSSHConnShow string `json:"localSSHConnShow"` +} + +type SystemProxy struct { + Type string `json:"type"` + URL string `json:"url"` + Port string `json:"port"` + User string `json:"user"` + Password string `json:"password"` +} + +type CommonDescription struct { + ID string `json:"id" validate:"required"` + Type string `json:"type" validate:"required"` + DetailType string `json:"detailType"` + IsPinned bool `json:"isPinned"` + Description string `json:"description"` +} diff --git a/agent/app/dto/snapshot.go b/agent/app/dto/snapshot.go new file mode 100644 index 0000000..dd962c8 --- /dev/null +++ b/agent/app/dto/snapshot.go @@ -0,0 +1,106 @@ +package dto + +import "time" + +type PageSnapshot struct { + PageInfo + Info string `json:"info"` + OrderBy string `json:"orderBy" validate:"required,oneof=name createdAt"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` +} + +type SnapshotCreate struct { + ID uint `json:"id"` + Name string `json:"name"` + TaskID string `json:"taskID"` + SourceAccountIDs string `json:"sourceAccountIDs" validate:"required"` + DownloadAccountID uint `json:"downloadAccountID" validate:"required"` + Description string `json:"description" validate:"max=256"` + Secret string `json:"secret"` + InterruptStep string `json:"interruptStep"` + Timeout uint `json:"timeout"` + + AppData []DataTree `json:"appData"` + BackupData []DataTree `json:"backupData"` + PanelData []DataTree `json:"panelData"` + + WithDockerConf bool `json:"withDockerConf"` + WithMonitorData bool `json:"withMonitorData"` + WithLoginLog bool `json:"withLoginLog"` + WithOperationLog bool `json:"withOperationLog"` + WithSystemLog bool `json:"withSystemLog"` + WithTaskLog bool `json:"withTaskLog"` + + IgnoreFiles []string `json:"ignoreFiles"` +} + +type SnapshotData struct { + AppData []DataTree `json:"appData"` + BackupData []DataTree `json:"backupData"` + PanelData []DataTree `json:"panelData"` + + WithDockerConf bool `json:"withDockerConf"` + WithMonitorData bool `json:"withMonitorData"` + WithLoginLog bool `json:"withLoginLog"` + WithOperationLog bool `json:"withOperationLog"` + WithSystemLog bool `json:"withSystemLog"` + WithTaskLog bool `json:"withTaskLog"` + IgnoreFiles []string `json:"ignoreFiles"` +} +type DataTree struct { + ID string `json:"id"` + Label string `json:"label"` + Key string `json:"key"` + Name string `json:"name"` + IsLocal bool `json:"isLocal"` + Size uint64 `json:"size"` + IsCheck bool `json:"isCheck"` + IsDisable bool `json:"isDisable"` + + Path string `json:"path"` + + RelationItemID string `json:"relationItemID"` + Children []DataTree `json:"children"` +} +type SnapshotRecover struct { + IsNew bool `json:"isNew"` + ReDownload bool `json:"reDownload"` + ID uint `json:"id" validate:"required"` + TaskID string `json:"taskID"` + Secret string `json:"secret"` +} +type SnapshotBatchDelete struct { + DeleteWithFile bool `json:"deleteWithFile"` + Ids []uint `json:"ids" validate:"required"` +} + +type SnapshotImport struct { + BackupAccountID uint `json:"backupAccountID"` + Names []string `json:"names"` + Description string `json:"description" validate:"max=256"` +} + +type SnapshotInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description" validate:"max=256"` + SourceAccounts []string `json:"sourceAccounts"` + DownloadAccount string `json:"downloadAccount"` + Status string `json:"status"` + Message string `json:"message"` + CreatedAt time.Time `json:"createdAt"` + Version string `json:"version"` + Size int64 `json:"size"` + + TaskID string `json:"taskID"` + TaskRecoverID string `json:"taskRecoverID"` + TaskRollbackID string `json:"taskRollbackID"` + + InterruptStep string `json:"interruptStep"` + RecoverStatus string `json:"recoverStatus"` + RecoverMessage string `json:"recoverMessage"` + LastRecoveredAt string `json:"lastRecoveredAt"` + RollbackStatus string `json:"rollbackStatus"` + RollbackMessage string `json:"rollbackMessage"` + LastRollbackedAt string `json:"lastRollbackedAt"` +} diff --git a/agent/app/dto/ssh.go b/agent/app/dto/ssh.go new file mode 100644 index 0000000..29f11d4 --- /dev/null +++ b/agent/app/dto/ssh.go @@ -0,0 +1,69 @@ +package dto + +import "time" + +type SSHUpdate struct { + Key string `json:"key" validate:"required"` + OldValue string `json:"oldValue"` + NewValue string `json:"newValue"` +} + +type SSHInfo struct { + AutoStart bool `json:"autoStart"` + IsExist bool `json:"isExist"` + IsActive bool `json:"isActive"` + Message string `json:"message"` + Port string `json:"port"` + ListenAddress string `json:"listenAddress"` + PasswordAuthentication string `json:"passwordAuthentication"` + PubkeyAuthentication string `json:"pubkeyAuthentication"` + PermitRootLogin string `json:"permitRootLogin"` + UseDNS string `json:"useDNS"` + CurrentUser string `json:"currentUser"` +} + +type RootCertOperate struct { + ID uint `json:"id"` + Name string `json:"name"` + Mode string `json:"mode"` + EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"` + PassPhrase string `json:"passPhrase"` + PublicKey string `json:"publicKey"` + PrivateKey string `json:"privateKey"` + Description string `json:"description"` +} +type RootCert struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + Name string `json:"name"` + EncryptionMode string `json:"encryptionMode"` + PassPhrase string `json:"passPhrase"` + PublicKey string `json:"publicKey"` + PrivateKey string `json:"privateKey"` + Description string `json:"description"` +} + +type GenerateLoad struct { + EncryptionMode string `json:"encryptionMode" validate:"required,oneof=rsa ed25519 ecdsa dsa"` +} + +type SSHConf struct { + File string `json:"file"` +} +type SearchSSHLog struct { + PageInfo + Info string `json:"info"` + Status string `json:"Status" validate:"required,oneof=Success Failed All"` +} + +type SSHHistory struct { + Date time.Time `json:"date"` + DateStr string `json:"dateStr"` + Area string `json:"area"` + User string `json:"user"` + AuthMode string `json:"authMode"` + Address string `json:"address"` + Port string `json:"port"` + Status string `json:"status"` + Message string `json:"message"` +} diff --git a/agent/app/model/ai.go b/agent/app/model/ai.go new file mode 100644 index 0000000..2165e96 --- /dev/null +++ b/agent/app/model/ai.go @@ -0,0 +1,11 @@ +package model + +type OllamaModel struct { + BaseModel + + Name string `json:"name"` + Size string `json:"size"` + From string `json:"from"` + Status string `json:"status"` + Message string `json:"message"` +} diff --git a/agent/app/model/alert.go b/agent/app/model/alert.go new file mode 100644 index 0000000..1676b35 --- /dev/null +++ b/agent/app/model/alert.go @@ -0,0 +1,55 @@ +package model + +type Alert struct { + BaseModel + + Title string `gorm:"type:varchar(256);not null" json:"title"` + Type string `gorm:"type:varchar(64);not null" json:"type"` + Cycle uint `gorm:"type:integer;not null" json:"cycle"` + Count uint `gorm:"type:integer;not null" json:"count"` + Project string `gorm:"type:varchar(64)" json:"project"` + Status string `gorm:"type:varchar(64);not null" json:"status"` + Method string `gorm:"type:varchar(64);not null" json:"method"` + SendCount uint `gorm:"type:integer" json:"sendCount"` + AdvancedParams string `gorm:"type:longText" json:"advancedParam"` +} + +type AlertTask struct { + BaseModel + Type string `gorm:"type:varchar(64);not null" json:"type"` + Quota string `gorm:"type:varchar(64)" json:"quota"` + QuotaType string `gorm:"type:varchar(64)" json:"quotaType"` + Method string `gorm:"type:varchar(64);not null;default:'sms'" json:"method"` +} + +type AlertLog struct { + BaseModel + + Type string `gorm:"type:varchar(64);not null" json:"type"` + Status string `gorm:"type:varchar(64);not null" json:"status"` + AlertId uint `gorm:"type:integer;not null" json:"alertId"` + AlertDetail string `gorm:"type:varchar(256);not null" json:"alertDetail"` + AlertRule string `gorm:"type:varchar(256);not null" json:"alertRule"` + Count uint `gorm:"type:integer;not null" json:"count"` + Message string `gorm:"type:varchar(256);" json:"message"` + RecordId uint `gorm:"type:integer;" json:"recordId"` + LicenseId string `gorm:"type:varchar(256);not null;" json:"licenseId" ` + Method string `gorm:"type:varchar(64);not null;default:'sms'" json:"method"` +} + +type AlertConfig struct { + BaseModel + Type string `gorm:"type:varchar(64);not null" json:"type"` + Title string `gorm:"type:varchar(64);not null" json:"title"` + Status string `gorm:"type:varchar(64);not null" json:"status"` + Config string `gorm:"type:varchar(256);not null" json:"config"` +} + +type LoginLog struct { + BaseModel + IP string `json:"ip"` + Address string `json:"address"` + Agent string `json:"agent"` + Status string `json:"status"` + Message string `json:"message"` +} diff --git a/agent/app/model/app.go b/agent/app/model/app.go new file mode 100644 index 0000000..572bbc2 --- /dev/null +++ b/agent/app/model/app.go @@ -0,0 +1,83 @@ +package model + +import ( + "encoding/json" + "github.com/gin-gonic/gin" + "path/filepath" + "strings" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type App struct { + BaseModel + Name string `json:"name" gorm:"not null"` + Key string `json:"key" gorm:"not null;"` + ShortDescZh string `json:"shortDescZh" yaml:"shortDescZh"` + ShortDescEn string `json:"shortDescEn" yaml:"shortDescEn"` + Description string `json:"description"` + Icon string `json:"icon"` + Type string `json:"type" gorm:"not null"` + Status string `json:"status" gorm:"not null"` + Required string `json:"required"` + CrossVersionUpdate bool `json:"crossVersionUpdate" yaml:"crossVersionUpdate"` + Limit int `json:"limit" gorm:"not null"` + Website string `json:"website" gorm:"not null"` + Github string `json:"github" gorm:"not null"` + Document string `json:"document" gorm:"not null"` + Recommend int `json:"recommend" gorm:"not null"` + Resource string `json:"resource" gorm:"not null;default:remote"` + ReadMe string `json:"readMe"` + LastModified int `json:"lastModified"` + Architectures string `json:"architectures"` + MemoryRequired int `json:"memoryRequired"` + GpuSupport bool `json:"gpuSupport"` + RequiredPanelVersion float64 `json:"requiredPanelVersion"` + BatchInstallSupport bool `json:"batchInstallSupport" yaml:"batchInstallSupport"` + + Details []AppDetail `json:"-" gorm:"-:migration"` + TagsKey []string `json:"tags" yaml:"tags" gorm:"-"` + AppTags []AppTag `json:"-" gorm:"-:migration"` +} + +func (i *App) IsLocalApp() bool { + return i.Resource == constant.ResourceLocal +} +func (i *App) IsCustomApp() bool { + return i.Resource == constant.AppResourceCustom +} + +func (i *App) GetAppResourcePath() string { + if i.IsLocalApp() { + return filepath.Join(global.Dir.LocalAppResourceDir, strings.TrimPrefix(i.Key, "local")) + } + if i.IsCustomApp() { + return filepath.Join(global.Dir.CustomAppResourceDir, i.Key) + } + return filepath.Join(global.Dir.RemoteAppResourceDir, i.Key) +} + +func getLang(c *gin.Context) string { + lang := c.GetHeader("Accept-Language") + if lang == "" { + lang = "en" + } + return lang +} + +func (i *App) GetDescription(ctx *gin.Context) string { + var translations = make(map[string]string) + _ = json.Unmarshal([]byte(i.Description), &translations) + lang := strings.ToLower(getLang(ctx)) + if desc, ok := translations[lang]; ok && desc != "" { + return desc + } + if lang == "zh" && i.ShortDescZh != "" { + return i.ShortDescZh + } + if desc, ok := translations["en"]; ok && desc != "" { + return desc + } + return i.ShortDescEn +} diff --git a/agent/app/model/app_detail.go b/agent/app/model/app_detail.go new file mode 100644 index 0000000..dd94d8c --- /dev/null +++ b/agent/app/model/app_detail.go @@ -0,0 +1,15 @@ +package model + +type AppDetail struct { + BaseModel + AppId uint `json:"appId" gorm:"not null"` + Version string `json:"version" gorm:"not null"` + Params string `json:"-"` + DockerCompose string `json:"dockerCompose"` + Status string `json:"status" gorm:"not null"` + LastVersion string `json:"lastVersion"` + LastModified int `json:"lastModified"` + DownloadUrl string `json:"downloadUrl"` + DownloadCallBackUrl string `json:"downloadCallBackUrl"` + Update bool `json:"update"` +} diff --git a/agent/app/model/app_ignore_upgrade.go b/agent/app/model/app_ignore_upgrade.go new file mode 100644 index 0000000..c8b7c63 --- /dev/null +++ b/agent/app/model/app_ignore_upgrade.go @@ -0,0 +1,8 @@ +package model + +type AppIgnoreUpgrade struct { + BaseModel + AppID uint `json:"appID"` + AppDetailID uint `json:"appDetailID"` + Scope string `json:"scope"` +} diff --git a/agent/app/model/app_install.go b/agent/app/model/app_install.go new file mode 100644 index 0000000..cd0904b --- /dev/null +++ b/agent/app/model/app_install.go @@ -0,0 +1,51 @@ +package model + +import ( + "path" + "strings" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type AppInstall struct { + BaseModel + Name string `json:"name" gorm:"not null;UNIQUE"` + AppId uint `json:"appId" gorm:"not null"` + AppDetailId uint `json:"appDetailId" gorm:"not null"` + Version string `json:"version" gorm:"not null"` + Param string `json:"param"` + Env string `json:"env"` + DockerCompose string `json:"dockerCompose" ` + Status string `json:"status" gorm:"not null"` + Description string `json:"description"` + Message string `json:"message"` + ContainerName string `json:"containerName" gorm:"not null"` + ServiceName string `json:"serviceName" gorm:"not null"` + HttpPort int `json:"httpPort"` + HttpsPort int `json:"httpsPort"` + WebUI string `json:"webUI"` + Favorite bool `json:"favorite"` + + App App `json:"app" gorm:"-:migration"` +} + +func (i *AppInstall) GetPath() string { + return path.Join(i.GetAppPath(), i.Name) +} + +func (i *AppInstall) GetComposePath() string { + return path.Join(i.GetAppPath(), i.Name, "docker-compose.yml") +} + +func (i *AppInstall) GetEnvPath() string { + return path.Join(i.GetAppPath(), i.Name, ".env") +} + +func (i *AppInstall) GetAppPath() string { + if i.App.Resource == constant.AppResourceLocal { + return path.Join(global.Dir.LocalAppInstallDir, strings.TrimPrefix(i.App.Key, constant.AppResourceLocal)) + } else { + return path.Join(global.Dir.AppInstallDir, i.App.Key) + } +} diff --git a/agent/app/model/app_install_resource.go b/agent/app/model/app_install_resource.go new file mode 100644 index 0000000..5ada945 --- /dev/null +++ b/agent/app/model/app_install_resource.go @@ -0,0 +1,10 @@ +package model + +type AppInstallResource struct { + BaseModel + AppInstallId uint `json:"appInstallId" gorm:"not null;"` + LinkId uint `json:"linkId" gorm:"not null;"` + ResourceId uint `json:"resourceId"` + Key string `json:"key" gorm:"not null"` + From string `json:"from" gorm:"not null;default:local"` +} diff --git a/agent/app/model/app_launcher.go b/agent/app/model/app_launcher.go new file mode 100644 index 0000000..a53a8f8 --- /dev/null +++ b/agent/app/model/app_launcher.go @@ -0,0 +1,17 @@ +package model + +type AppLauncher struct { + BaseModel + Key string `json:"key"` +} + +type QuickJump struct { + BaseModel + Name string `json:"name"` + Alias string `json:"alias"` + Title string `json:"title"` + Detail string `json:"detail"` + Recommend int `json:"recommend"` + IsShow bool `json:"isShow"` + Router string `json:"router"` +} diff --git a/agent/app/model/app_tag.go b/agent/app/model/app_tag.go new file mode 100644 index 0000000..6e4b13c --- /dev/null +++ b/agent/app/model/app_tag.go @@ -0,0 +1,7 @@ +package model + +type AppTag struct { + BaseModel + AppId uint `json:"appId" gorm:"not null"` + TagId uint `json:"tagId" gorm:"not null"` +} diff --git a/agent/app/model/backup.go b/agent/app/model/backup.go new file mode 100644 index 0000000..392bd7e --- /dev/null +++ b/agent/app/model/backup.go @@ -0,0 +1,34 @@ +package model + +type BackupAccount struct { + BaseModel + Name string `gorm:"not null;default:''" json:"name"` + Type string `gorm:"not null;default:''" json:"type"` + IsPublic bool `json:"isPublic"` + Bucket string `json:"bucket"` + AccessKey string `json:"accessKey"` + Credential string `json:"credential"` + BackupPath string `json:"backupPath"` + Vars string `json:"vars"` + + RememberAuth bool `json:"rememberAuth"` +} + +type BackupRecord struct { + BaseModel + From string `json:"from"` + CronjobID uint `json:"cronjobID"` + SourceAccountIDs string `json:"sourceAccountIDs"` + DownloadAccountID uint `json:"downloadAccountID"` + + Type string `gorm:"not null;default:''" json:"type"` + Name string `gorm:"not null;default:''" json:"name"` + DetailName string `json:"detailName"` + FileDir string `json:"fileDir"` + FileName string `json:"fileName"` + + TaskID string `json:"taskID"` + Status string `json:"status"` + Message string `json:"message"` + Description string `json:"description"` +} diff --git a/agent/app/model/base.go b/agent/app/model/base.go new file mode 100644 index 0000000..69921d4 --- /dev/null +++ b/agent/app/model/base.go @@ -0,0 +1,9 @@ +package model + +import "time" + +type BaseModel struct { + ID uint `gorm:"primarykey;AUTO_INCREMENT" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/agent/app/model/clam.go b/agent/app/model/clam.go new file mode 100644 index 0000000..8980b4e --- /dev/null +++ b/agent/app/model/clam.go @@ -0,0 +1,33 @@ +package model + +import "time" + +type Clam struct { + BaseModel + + Name string `gorm:"not null" json:"name"` + Path string `gorm:"not null" json:"path"` + InfectedStrategy string `json:"infectedStrategy"` + InfectedDir string `json:"infectedDir"` + Spec string `json:"spec"` + RetryTimes uint `json:"retryTimes"` + Timeout uint `json:"timeout"` + EntryID int `json:"entryID"` + Description string `json:"description"` + + Status string `json:"status"` + IsExecuting bool `json:"isExecuting"` +} + +type ClamRecord struct { + BaseModel + + ClamID uint `json:"clamID"` + TaskID string `json:"taskID"` + StartTime time.Time `json:"startTime"` + ScanTime string `json:"scanTime"` + InfectedFiles string `json:"infectedFiles"` + TotalError string `json:"totalError"` + Status string `json:"status"` + Message string `json:"message"` +} diff --git a/agent/app/model/compose_template.go b/agent/app/model/compose_template.go new file mode 100644 index 0000000..aa127f2 --- /dev/null +++ b/agent/app/model/compose_template.go @@ -0,0 +1,16 @@ +package model + +type ComposeTemplate struct { + BaseModel + + Name string `gorm:"not null;unique" json:"name"` + Description string `json:"description"` + Content string `json:"content"` +} + +type Compose struct { + BaseModel + + Name string `json:"name"` + Path string `json:"path"` +} diff --git a/agent/app/model/cronjob.go b/agent/app/model/cronjob.go new file mode 100644 index 0000000..c39b688 --- /dev/null +++ b/agent/app/model/cronjob.go @@ -0,0 +1,69 @@ +package model + +import ( + "time" +) + +type Cronjob struct { + BaseModel + + Name string `gorm:"not null" json:"name"` + Type string `gorm:"not null" json:"type"` + GroupID uint `json:"groupID"` + SpecCustom bool `json:"specCustom"` + Spec string `gorm:"not null" json:"spec"` + + Executor string `json:"executor"` + Command string `json:"command"` + ContainerName string `json:"containerName"` + ScriptMode string `json:"scriptMode"` + Script string `json:"script"` + User string `json:"user"` + + ScriptID uint `json:"scriptID"` + Website string `json:"website"` + AppID string `json:"appID"` + DBType string `json:"dbType"` + DBName string `json:"dbName"` + URL string `json:"url"` + IsDir bool `json:"isDir"` + SourceDir string `json:"sourceDir"` + SnapshotRule string `json:"snapshotRule"` + ExclusionRules string `json:"exclusionRules"` + + SourceAccountIDs string `json:"sourceAccountIDs"` + DownloadAccountID uint `json:"downloadAccountID"` + RetryTimes uint `json:"retryTimes"` + Timeout uint `json:"timeout"` + IgnoreErr bool `json:"ignoreErr"` + RetainCopies uint64 `json:"retainCopies"` + Args string `json:"args"` + + IsExecuting bool `json:"isExecuting"` + Status string `json:"status"` + EntryIDs string `json:"entryIDs"` + Records []JobRecords `json:"records"` + Secret string `json:"secret"` + + Config string `json:"config"` +} + +type JobRecords struct { + BaseModel + + CronjobID uint `json:"cronjobID"` + TaskID string `json:"taskID"` + StartTime time.Time `json:"startTime"` + Interval float64 `json:"interval"` + Records string `json:"records"` + FromLocal bool `json:"source"` + File string `json:"file"` + Status string `json:"status"` + Message string `json:"message"` +} + +type ScriptLibrary struct { + BaseModel + Name string `json:"name" gorm:"not null;"` + Script string `json:"script" gorm:"not null;"` +} diff --git a/agent/app/model/database.go b/agent/app/model/database.go new file mode 100644 index 0000000..9770c7e --- /dev/null +++ b/agent/app/model/database.go @@ -0,0 +1,24 @@ +package model + +type Database struct { + BaseModel + AppInstallID uint `json:"appInstallID"` + Name string `json:"name" gorm:"not null;unique"` + Type string `json:"type" gorm:"not null"` + Version string `json:"version" gorm:"not null"` + From string `json:"from" gorm:"not null"` + Address string `json:"address" gorm:"not null"` + Port uint `json:"port" gorm:"not null"` + InitialDB string `json:"initialDB"` + Username string `json:"username"` + Password string `json:"password"` + + SSL bool `json:"ssl"` + RootCert string `json:"rootCert"` + ClientKey string `json:"clientKey"` + ClientCert string `json:"clientCert"` + SkipVerify bool `json:"skipVerify"` + + Timeout uint `json:"timeout"` + Description string `json:"description"` +} diff --git a/agent/app/model/database_mysql.go b/agent/app/model/database_mysql.go new file mode 100644 index 0000000..412e091 --- /dev/null +++ b/agent/app/model/database_mysql.go @@ -0,0 +1,15 @@ +package model + +type DatabaseMysql struct { + BaseModel + Name string `json:"name" gorm:"not null"` + From string `json:"from" gorm:"not null;default:local"` + MysqlName string `json:"mysqlName" gorm:"not null"` + Format string `json:"format" gorm:"not null"` + Collation string `json:"collation"` + Username string `json:"username" gorm:"not null"` + Password string `json:"password" gorm:"not null"` + Permission string `json:"permission" gorm:"not null"` + IsDelete bool `json:"isDelete"` + Description string `json:"description"` +} diff --git a/agent/app/model/database_postgresql.go b/agent/app/model/database_postgresql.go new file mode 100644 index 0000000..3568fd5 --- /dev/null +++ b/agent/app/model/database_postgresql.go @@ -0,0 +1,14 @@ +package model + +type DatabasePostgresql struct { + BaseModel + Name string `json:"name" gorm:"not null"` + From string `json:"from" gorm:"not null;default:local"` + PostgresqlName string `json:"postgresqlName" gorm:"not null"` + Format string `json:"format" gorm:"not null"` + Username string `json:"username" gorm:"not null"` + Password string `json:"password" gorm:"not null"` + SuperUser bool `json:"superUser"` + IsDelete bool `json:"isDelete"` + Description string `json:"description"` +} diff --git a/agent/app/model/favorite.go b/agent/app/model/favorite.go new file mode 100644 index 0000000..ee17b55 --- /dev/null +++ b/agent/app/model/favorite.go @@ -0,0 +1,10 @@ +package model + +type Favorite struct { + BaseModel + Name string `gorm:"not null;" json:"name" ` + Path string `gorm:"not null;unique" json:"path"` + Type string `json:"type"` + IsDir bool `json:"isDir"` + IsTxt bool `json:"isTxt"` +} diff --git a/agent/app/model/firewall.go b/agent/app/model/firewall.go new file mode 100644 index 0000000..fc79023 --- /dev/null +++ b/agent/app/model/firewall.go @@ -0,0 +1,18 @@ +package model + +type Firewall struct { + BaseModel + + Type string `json:"type"` + Port string `json:"port"` // Deprecated + Address string `json:"address"` // Deprecated + + Chain string `json:"chain"` + Protocol string `json:"protocol"` + SrcIP string `json:"srcIP"` + SrcPort string `json:"srcPort"` + DstIP string `json:"dstIP"` + DstPort string `json:"dstPort"` + Strategy string `gorm:"not null" json:"strategy"` + Description string `json:"description"` +} diff --git a/agent/app/model/ftp.go b/agent/app/model/ftp.go new file mode 100644 index 0000000..6e88a4a --- /dev/null +++ b/agent/app/model/ftp.go @@ -0,0 +1,11 @@ +package model + +type Ftp struct { + BaseModel + + User string `gorm:"not null" json:"user"` + Password string `gorm:"not null" json:"password"` + Status string `gorm:"not null" json:"status"` + Path string `gorm:"not null" json:"path"` + Description string `gorm:"not null" json:"description"` +} diff --git a/agent/app/model/group.go b/agent/app/model/group.go new file mode 100644 index 0000000..c8999b7 --- /dev/null +++ b/agent/app/model/group.go @@ -0,0 +1,8 @@ +package model + +type Group struct { + BaseModel + IsDefault bool `json:"isDefault"` + Name string `json:"name"` + Type string `json:"type"` +} diff --git a/agent/app/model/image_repo.go b/agent/app/model/image_repo.go new file mode 100644 index 0000000..438774e --- /dev/null +++ b/agent/app/model/image_repo.go @@ -0,0 +1,15 @@ +package model + +type ImageRepo struct { + BaseModel + + Name string `gorm:"not null" json:"name"` + DownloadUrl string `json:"downloadUrl"` + Protocol string `json:"protocol"` + Username string `json:"username"` + Password string `json:"password"` + Auth bool `json:"auth"` + + Status string `json:"status"` + Message string `json:"message"` +} diff --git a/agent/app/model/mcp_server.go b/agent/app/model/mcp_server.go new file mode 100644 index 0000000..133bc8f --- /dev/null +++ b/agent/app/model/mcp_server.go @@ -0,0 +1,21 @@ +package model + +type McpServer struct { + BaseModel + Name string `json:"name"` + DockerCompose string `json:"dockerCompose"` + Command string `json:"command"` + ContainerName string `json:"containerName"` + Message string `json:"message"` + Port int `json:"port"` + Status string `json:"status"` + Env string `json:"env"` + BaseURL string `json:"baseUrl"` + SsePath string `json:"ssePath"` + WebsiteID int `json:"websiteID"` + Dir string `json:"dir"` + HostIP string `json:"hostIP"` + StreamableHttpPath string `json:"streamableHttpPath"` + OutputTransport string `json:"outputTransport"` + Type string `json:"type"` +} diff --git a/agent/app/model/monitor.go b/agent/app/model/monitor.go new file mode 100644 index 0000000..6ec42ab --- /dev/null +++ b/agent/app/model/monitor.go @@ -0,0 +1,46 @@ +package model + +type MonitorBase struct { + BaseModel + Cpu float64 `json:"cpu"` + TopCPU string `json:"topCPU"` + TopCPUItems interface{} `gorm:"-" json:"topCPUItems"` + + Memory float64 `json:"memory"` + TopMem string `json:"topMem"` + TopMemItems interface{} `gorm:"-" json:"topMemItems"` + + LoadUsage float64 `json:"loadUsage"` + CpuLoad1 float64 `json:"cpuLoad1"` + CpuLoad5 float64 `json:"cpuLoad5"` + CpuLoad15 float64 `json:"cpuLoad15"` +} + +type MonitorIO struct { + BaseModel + Name string `json:"name"` + Read uint64 `json:"read"` + Write uint64 `json:"write"` + Count uint64 `json:"count"` + Time uint64 `json:"time"` +} + +type MonitorNetwork struct { + BaseModel + Name string `json:"name"` + Up float64 `json:"up"` + Down float64 `json:"down"` +} + +type MonitorGPU struct { + BaseModel + ProductName string `json:"productName"` + GPUUtil float64 `json:"gpuUtil"` + Temperature float64 `json:"temperature"` + PowerDraw float64 `json:"powerDraw"` + MaxPowerLimit float64 `json:"maxPowerLimit"` + MemUsed float64 `json:"memUsed"` + MemTotal float64 `json:"memTotal"` + FanSpeed int `json:"fanSpeed"` + Processes string `json:"processes"` +} diff --git a/agent/app/model/php_extensions.go b/agent/app/model/php_extensions.go new file mode 100644 index 0000000..732b1e0 --- /dev/null +++ b/agent/app/model/php_extensions.go @@ -0,0 +1,7 @@ +package model + +type PHPExtensions struct { + BaseModel + Name string `json:"name" gorm:"not null"` + Extensions string `json:"extensions" gorm:"not null"` +} diff --git a/agent/app/model/runtime.go b/agent/app/model/runtime.go new file mode 100644 index 0000000..f32ce31 --- /dev/null +++ b/agent/app/model/runtime.go @@ -0,0 +1,55 @@ +package model + +import ( + "path" + + "github.com/1Panel-dev/1Panel/agent/global" +) + +type Runtime struct { + BaseModel + Name string `gorm:"not null" json:"name"` + AppDetailID uint `json:"appDetailID"` + Image string `json:"image"` + WorkDir string `json:"workDir"` + DockerCompose string `json:"dockerCompose"` + Env string `json:"env"` + Params string `json:"params"` + Version string `gorm:"not null" json:"version"` + Type string `gorm:"not null" json:"type"` + Status string `gorm:"not null" json:"status"` + Resource string `gorm:"not null" json:"resource"` + Port string `json:"port"` + Message string `json:"message"` + CodeDir string `json:"codeDir"` + ContainerName string `json:"containerName"` + Remark string `json:"remark"` +} + +func (r *Runtime) GetComposePath() string { + return path.Join(r.GetPath(), "docker-compose.yml") +} + +func (r *Runtime) GetEnvPath() string { + return path.Join(r.GetPath(), ".env") +} + +func (r *Runtime) GetPath() string { + return path.Join(global.Dir.RuntimeDir, r.Type, r.Name) +} + +func (r *Runtime) GetFPMPath() string { + return path.Join(global.Dir.RuntimeDir, r.Type, r.Name, "conf", "php-fpm.conf") +} + +func (r *Runtime) GetPHPPath() string { + return path.Join(global.Dir.RuntimeDir, r.Type, r.Name, "conf", "php.ini") +} + +func (r *Runtime) GetLogPath() string { + return path.Join(r.GetPath(), "build.log") +} + +func (r *Runtime) GetSlowLogPath() string { + return path.Join(r.GetPath(), "log", "fpm.slow.log") +} diff --git a/agent/app/model/setting.go b/agent/app/model/setting.go new file mode 100644 index 0000000..22f12cf --- /dev/null +++ b/agent/app/model/setting.go @@ -0,0 +1,35 @@ +package model + +type Setting struct { + BaseModel + Key string `json:"key" gorm:"not null;"` + Value string `json:"value"` + About string `json:"about"` +} + +type CommonDescription struct { + ID string `json:"id"` + Type string `json:"type"` + DetailType string `json:"detailType"` + IsPinned bool `json:"isPinned"` + Description string `json:"description"` +} + +type NodeInfo struct { + Scope string `json:"scope"` + BaseDir string `json:"baseDir"` + NodePort uint `json:"nodePort"` + Version string `json:"version"` + ServerCrt string `json:"serverCrt"` + ServerKey string `json:"serverKey"` +} + +type LocalConnInfo struct { + Addr string `json:"addr"` + Port uint `json:"port"` + User string `json:"user"` + AuthMode string `json:"authMode"` + Password string `json:"password"` + PrivateKey string `json:"privateKey"` + PassPhrase string `json:"passPhrase"` +} diff --git a/agent/app/model/snapshot.go b/agent/app/model/snapshot.go new file mode 100644 index 0000000..eeb28b0 --- /dev/null +++ b/agent/app/model/snapshot.go @@ -0,0 +1,36 @@ +package model + +type Snapshot struct { + BaseModel + Name string `json:"name" gorm:"not null;unique"` + Secret string `json:"secret"` + Description string `json:"description"` + SourceAccountIDs string `json:"sourceAccountIDs"` + DownloadAccountID uint `json:"downloadAccountID"` + Status string `json:"status"` + Message string `json:"message"` + Version string `json:"version"` + Timeout uint `json:"timeout"` + + TaskID string `json:"taskID"` + TaskRecoverID string `json:"taskRecoverID"` + TaskRollbackID string `json:"taskRollbackID"` + + AppData string `json:"appData"` + PanelData string `json:"panelData"` + BackupData string `json:"backupData"` + + WithDockerConf bool `json:"withDockerConf"` + WithMonitorData bool `json:"withMonitorData"` + WithLoginLog bool `json:"withLoginLog"` + WithOperationLog bool `json:"withOperationLog"` + WithSystemLog bool `json:"withSystemLog"` + WithTaskLog bool `json:"withTaskLog"` + IgnoreFiles string `json:"ignoreFiles"` + + InterruptStep string `json:"interruptStep"` + RecoverStatus string `json:"recoverStatus"` + RecoverMessage string `json:"recoverMessage"` + RollbackStatus string `json:"rollbackStatus"` + RollbackMessage string `json:"rollbackMessage"` +} diff --git a/agent/app/model/ssh.go b/agent/app/model/ssh.go new file mode 100644 index 0000000..04831d2 --- /dev/null +++ b/agent/app/model/ssh.go @@ -0,0 +1,11 @@ +package model + +type RootCert struct { + BaseModel + Name string `json:"name" gorm:"not null;"` + EncryptionMode string `json:"encryptionMode"` + PassPhrase string `json:"passPhrase"` + PublicKeyPath string `json:"publicKeyPath"` + PrivateKeyPath string `json:"privateKeyPath"` + Description string `json:"description"` +} diff --git a/agent/app/model/tag.go b/agent/app/model/tag.go new file mode 100644 index 0000000..c4812b5 --- /dev/null +++ b/agent/app/model/tag.go @@ -0,0 +1,9 @@ +package model + +type Tag struct { + BaseModel + Key string `json:"key" gorm:"not null"` + Name string `json:"name" gorm:"not null"` + Sort int `json:"sort" gorm:"not null;default:1"` + Translations string `json:"translations"` +} diff --git a/agent/app/model/task.go b/agent/app/model/task.go new file mode 100644 index 0000000..46d0032 --- /dev/null +++ b/agent/app/model/task.go @@ -0,0 +1,18 @@ +package model + +import "time" + +type Task struct { + ID string `gorm:"primarykey;" json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Operate string `json:"operate"` + LogFile string `json:"logFile"` + Status string `json:"status"` + ErrorMsg string `json:"errorMsg"` + OperationLogID uint `json:"operationLogID"` + ResourceID uint `json:"resourceID"` + CurrentStep string `json:"currentStep"` + EndAt time.Time `json:"endAt"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/agent/app/model/tensorrt_llm.go b/agent/app/model/tensorrt_llm.go new file mode 100644 index 0000000..e53873f --- /dev/null +++ b/agent/app/model/tensorrt_llm.go @@ -0,0 +1,14 @@ +package model + +type TensorRTLLM struct { + BaseModel + Name string `json:"name"` + DockerCompose string `json:"dockerCompose"` + ContainerName string `json:"containerName"` + Message string `json:"message"` + Status string `json:"status"` + Env string `json:"env"` + TaskID string `json:"taskID"` + ModelType string `json:"modelType"` + ModelSpeedup bool `json:"modelSpeedup"` +} diff --git a/agent/app/model/website.go b/agent/app/model/website.go new file mode 100644 index 0000000..10b2b58 --- /dev/null +++ b/agent/app/model/website.go @@ -0,0 +1,48 @@ +package model + +import "time" + +type Website struct { + BaseModel + Protocol string `gorm:"not null" json:"protocol"` + PrimaryDomain string `gorm:"not null" json:"primaryDomain"` + Type string `gorm:"not null" json:"type"` + Alias string `gorm:"not null" json:"alias"` + Remark string `json:"remark"` + Status string `gorm:"not null" json:"status"` + HttpConfig string `gorm:"not null" json:"httpConfig"` + ExpireDate time.Time `json:"expireDate"` + + Proxy string `json:"proxy"` + ProxyType string `json:"proxyType"` + SiteDir string `json:"siteDir"` + ErrorLog bool `json:"errorLog"` + AccessLog bool `json:"accessLog"` + DefaultServer bool `json:"defaultServer"` + IPV6 bool `json:"IPV6"` + Rewrite string `json:"rewrite"` + + WebsiteGroupID uint `json:"webSiteGroupId"` + WebsiteSSLID uint `json:"webSiteSSLId"` + RuntimeID uint `json:"runtimeID"` + AppInstallID uint `json:"appInstallId"` + FtpID uint `json:"ftpId"` + ParentWebsiteID uint `json:"parentWebsiteID"` + + User string `json:"user"` + Group string `json:"group"` + + DbType string `json:"dbType"` + DbID uint `json:"dbID"` + + Favorite bool `json:"favorite"` + + StreamPorts string `json:"streamPorts"` + + Domains []WebsiteDomain `json:"domains" gorm:"-:migration"` + WebsiteSSL WebsiteSSL `json:"webSiteSSL" gorm:"-:migration"` +} + +func (w Website) TableName() string { + return "websites" +} diff --git a/agent/app/model/website_acme_account.go b/agent/app/model/website_acme_account.go new file mode 100644 index 0000000..0dfe2bd --- /dev/null +++ b/agent/app/model/website_acme_account.go @@ -0,0 +1,19 @@ +package model + +type WebsiteAcmeAccount struct { + BaseModel + Email string `gorm:"not null" json:"email"` + URL string `gorm:"not null" json:"url"` + PrivateKey string `gorm:"not null" json:"-"` + Type string `gorm:"not null;default:letsencrypt" json:"type"` + EabKid string `json:"eabKid"` + EabHmacKey string `json:"eabHmacKey"` + KeyType string `gorm:"not null;default:2048" json:"keyType"` + UseProxy bool `gorm:"default:false" json:"useProxy"` + CaDirURL string `json:"caDirURL"` + UseEAB bool `json:"useEAB"` +} + +func (w WebsiteAcmeAccount) TableName() string { + return "website_acme_accounts" +} diff --git a/agent/app/model/website_ca.go b/agent/app/model/website_ca.go new file mode 100644 index 0000000..74b0296 --- /dev/null +++ b/agent/app/model/website_ca.go @@ -0,0 +1,9 @@ +package model + +type WebsiteCA struct { + BaseModel + CSR string `gorm:"not null;" json:"csr"` + Name string `gorm:"not null;" json:"name"` + PrivateKey string `gorm:"not null" json:"privateKey"` + KeyType string `gorm:"not null;default:2048" json:"keyType"` +} diff --git a/agent/app/model/website_dns_account.go b/agent/app/model/website_dns_account.go new file mode 100644 index 0000000..0dfce6e --- /dev/null +++ b/agent/app/model/website_dns_account.go @@ -0,0 +1,12 @@ +package model + +type WebsiteDnsAccount struct { + BaseModel + Name string `gorm:"not null" json:"name"` + Type string `gorm:"not null" json:"type"` + Authorization string `gorm:"not null" json:"-"` +} + +func (w WebsiteDnsAccount) TableName() string { + return "website_dns_accounts" +} diff --git a/agent/app/model/website_domain.go b/agent/app/model/website_domain.go new file mode 100644 index 0000000..155d164 --- /dev/null +++ b/agent/app/model/website_domain.go @@ -0,0 +1,13 @@ +package model + +type WebsiteDomain struct { + BaseModel + WebsiteID uint `gorm:"column:website_id;not null;" json:"websiteId"` + Domain string `gorm:"not null" json:"domain"` + SSL bool `json:"ssl"` + Port int `json:"port"` +} + +func (w WebsiteDomain) TableName() string { + return "website_domains" +} diff --git a/agent/app/model/website_ssl.go b/agent/app/model/website_ssl.go new file mode 100644 index 0000000..31338d4 --- /dev/null +++ b/agent/app/model/website_ssl.go @@ -0,0 +1,57 @@ +package model + +import ( + "fmt" + "path" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" +) + +type WebsiteSSL struct { + BaseModel + PrimaryDomain string `json:"primaryDomain"` + PrivateKey string `json:"privateKey"` + Pem string `json:"pem"` + Domains string `json:"domains"` + CertURL string `json:"certURL"` + Type string `json:"type"` + Provider string `json:"provider"` + Organization string `json:"organization"` + DnsAccountID uint `json:"dnsAccountId"` + AcmeAccountID uint `gorm:"column:acme_account_id" json:"acmeAccountId" ` + CaID uint `json:"caId"` + AutoRenew bool `json:"autoRenew"` + ExpireDate time.Time `json:"expireDate"` + StartDate time.Time `json:"startDate"` + Status string `json:"status"` + Message string `json:"message"` + KeyType string `json:"keyType"` + PushDir bool `json:"pushDir"` + Dir string `json:"dir"` + Description string `json:"description"` + SkipDNS bool `json:"skipDNS"` + Nameserver1 string `json:"nameserver1"` + Nameserver2 string `json:"nameserver2"` + DisableCNAME bool `json:"disableCNAME"` + ExecShell bool `json:"execShell"` + Shell string `json:"shell"` + MasterSSLID uint `json:"masterSslId"` + Nodes string `json:"nodes"` + PushNode bool `json:"pushNode"` + PrivateKeyPath string `json:"privateKeyPath"` + CertPath string `json:"certPath"` + IsIp bool `json:"isIP"` + + AcmeAccount WebsiteAcmeAccount `json:"acmeAccount" gorm:"-:migration"` + DnsAccount WebsiteDnsAccount `json:"dnsAccount" gorm:"-:migration"` + Websites []Website `json:"websites" gorm:"-:migration"` +} + +func (w WebsiteSSL) TableName() string { + return "website_ssls" +} + +func (w WebsiteSSL) GetLogPath() string { + return path.Join(global.Dir.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", w.PrimaryDomain, w.ID)) +} diff --git a/agent/app/repo/ai.go b/agent/app/repo/ai.go new file mode 100644 index 0000000..f0368f8 --- /dev/null +++ b/agent/app/repo/ai.go @@ -0,0 +1,69 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type AiRepo struct{} + +type IAiRepo interface { + Get(opts ...DBOption) (model.OllamaModel, error) + List(opts ...DBOption) ([]model.OllamaModel, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.OllamaModel, error) + Create(cronjob *model.OllamaModel) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error +} + +func NewIAiRepo() IAiRepo { + return &AiRepo{} +} + +func (u *AiRepo) Get(opts ...DBOption) (model.OllamaModel, error) { + var item model.OllamaModel + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&item).Error + return item, err +} + +func (u *AiRepo) List(opts ...DBOption) ([]model.OllamaModel, error) { + var list []model.OllamaModel + db := global.DB.Model(&model.OllamaModel{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&list).Error + return list, err +} + +func (u *AiRepo) Page(page, size int, opts ...DBOption) (int64, []model.OllamaModel, error) { + var list []model.OllamaModel + db := global.DB.Model(&model.OllamaModel{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&list).Error + return count, list, err +} + +func (u *AiRepo) Create(item *model.OllamaModel) error { + return global.DB.Create(item).Error +} + +func (u *AiRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.OllamaModel{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *AiRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.OllamaModel{}).Error +} diff --git a/agent/app/repo/alert.go b/agent/app/repo/alert.go new file mode 100644 index 0000000..c1ea1c2 --- /dev/null +++ b/agent/app/repo/alert.go @@ -0,0 +1,337 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "google.golang.org/genproto/googleapis/type/date" + "gorm.io/gorm" + "time" +) + +type AlertRepo struct{} + +type IAlertRepo interface { + WithByType(alertType string) DBOption + WithByStatusIn(status []string) DBOption + WithByProject(project string) DBOption + WithByCount(count uint) DBOption + WithByAlertId(alertId uint) DBOption + WithByCreateAt(date *date.Date) DBOption + WithByLicenseId(licenseId string) DBOption + WithByRecordId(recordId uint) DBOption + WithByMethod(method string) DBOption + + Create(alert *model.Alert) error + Get(opts ...DBOption) (model.Alert, error) + Page(page, size int, opts ...DBOption) (int64, []model.Alert, error) + List(opts ...DBOption) ([]model.Alert, error) + Delete(opts ...DBOption) error + Save(alert *model.Alert) error + Update(maps map[string]interface{}, opts ...DBOption) error + + GetLog(opts ...DBOption) (model.AlertLog, error) + CreateLog(alertLog *model.AlertLog) error + PageLog(limit, offset int, opts ...DBOption) (int64, []model.AlertLog, error) + ListLog(opts ...DBOption) ([]model.AlertLog, error) + UpdateLog(id uint, maps map[string]interface{}) error + BatchUpdateLogBy(maps map[string]interface{}, opts ...DBOption) error + DeleteLog(opts ...DBOption) error + CleanAlertLogs() error + + CreateAlertTask(alertTaskBase *model.AlertTask) error + DeleteAlertTask(opts ...DBOption) error + GetAlertTask(opts ...DBOption) (model.AlertTask, error) + LoadTaskCount(alertType string, project string, method string) (uint, uint, error) + GetTaskLog(alertType string, alertId uint) (time.Time, error) + GetLicensePushCount(method string) (uint, error) + + GetConfig(opts ...DBOption) (model.AlertConfig, error) + AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error) + UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error + CreateAlertConfig(config *model.AlertConfig) error + DeleteAlertConfig(opts ...DBOption) error + + SyncAll(data []model.AlertConfig) error +} + +func NewIAlertRepo() IAlertRepo { + return &AlertRepo{} +} + +func (a *AlertRepo) WithByType(alertType string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`type` = ?", alertType) + } +} + +func (a *AlertRepo) WithByStatusIn(status []string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("status in (?)", status) + } +} + +func (a *AlertRepo) WithByCount(count uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("count = ?", count) + } +} + +func (a *AlertRepo) WithByProject(project string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("project = ?", project) + } +} + +func (a *AlertRepo) WithByAlertId(alertId uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("alert_id = ?", alertId) + } +} + +func (a *AlertRepo) WithByLicenseId(licenseId string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("license_id = ?", licenseId) + } +} + +func (a *AlertRepo) WithByRecordId(recordId uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("record_id = ?", recordId) + } +} + +func (a *AlertRepo) WithByMethod(method string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("method = ?", method) + } +} + +func (a *AlertRepo) WithByCreateAt(createAt *date.Date) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("DATE(created_at) = DATE(?)", createAt) + } +} + +func (a *AlertRepo) Create(alert *model.Alert) error { + return global.AlertDB.Model(&model.Alert{}).Create(alert).Error +} + +func (a *AlertRepo) Save(alert *model.Alert) error { + return global.AlertDB.Save(alert).Error +} + +func (a *AlertRepo) Get(opts ...DBOption) (model.Alert, error) { + var alert model.Alert + db, _ := getAlertDB(opts...) + err := db.First(&alert).Error + return alert, err +} + +func (a *AlertRepo) Page(page, size int, opts ...DBOption) (int64, []model.Alert, error) { + var alerts []model.Alert + alertDb, _ := getAlertDB(opts...) + db := alertDb.Model(&model.Alert{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&alerts).Error + return count, alerts, err +} + +func (a *AlertRepo) List(opts ...DBOption) ([]model.Alert, error) { + var alert []model.Alert + db, _ := getAlertDB(opts...) + err := db.Find(&alert).Error + return alert, err +} + +func (a *AlertRepo) Update(maps map[string]interface{}, opts ...DBOption) error { + db, _ := getAlertDB(opts...) + return db.Model(&model.Alert{}).Updates(maps).Error +} + +func (a *AlertRepo) Delete(opts ...DBOption) error { + db, _ := getAlertDB(opts...) + return db.Delete(&model.Alert{}).Error +} + +func (a *AlertRepo) GetLog(opts ...DBOption) (model.AlertLog, error) { + var alertLog model.AlertLog + db, _ := getAlertDB(opts...) + err := db.First(&alertLog).Error + return alertLog, err +} + +func (a *AlertRepo) CreateLog(log *model.AlertLog) error { + return global.AlertDB.Model(&model.AlertLog{}).Create(&log).Error +} + +func (a *AlertRepo) UpdateLog(id uint, maps map[string]interface{}) error { + return global.AlertDB.Model(&model.AlertLog{}).Where("id = ?", id).Updates(maps).Error +} + +func (a *AlertRepo) BatchUpdateLogBy(maps map[string]interface{}, opts ...DBOption) error { + db, _ := getAlertDB(opts...) + if len(opts) == 0 { + db = db.Where("1=1") + } + return db.Model(&model.AlertLog{}).Updates(&maps).Error +} + +func (a *AlertRepo) PageLog(page, size int, opts ...DBOption) (int64, []model.AlertLog, error) { + var alerts []model.AlertLog + db := global.AlertDB.Model(&model.AlertLog{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Order("created_at desc").Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&alerts).Error + return count, alerts, err +} + +func (a *AlertRepo) ListLog(opts ...DBOption) ([]model.AlertLog, error) { + var alertLog []model.AlertLog + db, _ := getAlertDB(opts...) + err := db.Find(&alertLog).Error + return alertLog, err +} + +func (a *AlertRepo) DeleteLog(opts ...DBOption) error { + db, _ := getAlertDB(opts...) + return db.Delete(&model.AlertLog{}).Error +} + +func (a *AlertRepo) CleanAlertLogs() error { + return global.AlertDB.Where("1 = 1").Delete(&model.AlertLog{}).Error +} + +func (a *AlertRepo) CreateAlertTask(alertTaskBase *model.AlertTask) error { + return global.AlertDB.Model(&model.AlertTask{}).Create(&alertTaskBase).Error +} + +func (a *AlertRepo) DeleteAlertTask(opts ...DBOption) error { + db, _ := getAlertDB(opts...) + return db.Delete(&model.AlertTask{}).Error +} + +func (a *AlertRepo) GetAlertTask(opts ...DBOption) (model.AlertTask, error) { + var data model.AlertTask + db, _ := getAlertDB(opts...) + err := db.First(&data).Error + return data, err +} + +func (a *AlertRepo) LoadTaskCount(alertType string, project string, method string) (uint, uint, error) { + var ( + todayCount int64 + totalCount int64 + ) + _ = global.AlertDB.Model(&model.AlertTask{}).Where("type = ? AND quota_type = ? AND method = ?", alertType, project, method).Count(&totalCount).Error + + now := time.Now() + todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + tomorrowMidnight := todayMidnight.Add(24 * time.Hour) + err := global.AlertDB.Model(&model.AlertTask{}).Where("type = ? AND quota_type = ? AND method = ? AND created_at > ? AND created_at < ?", alertType, project, method, todayMidnight, tomorrowMidnight).Count(&todayCount).Error + return uint(todayCount), uint(totalCount), err +} + +func (a *AlertRepo) GetTaskLog(alertType string, alertId uint) (time.Time, error) { + var newDate time.Time + status := []string{constant.AlertSuccess, constant.AlertPushSuccess, constant.AlertSyncError, constant.AlertPushing} + now := time.Now() + todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + tomorrowMidnight := todayMidnight.Add(24 * time.Hour) + err := global.AlertDB.Model(&model.AlertLog{}). + Where("type = ? AND alert_id = ? AND status in ? AND created_at > ? AND created_at < ?", alertType, alertId, status, todayMidnight, tomorrowMidnight). + Order("created_at DESC"). + Limit(1). + Pluck("created_at", &newDate).Error + if err != nil { + return time.Time{}, err + } + + if newDate.IsZero() { + return time.Time{}, nil + } + + return newDate, nil +} + +func getAlertDB(opts ...DBOption) (*gorm.DB, error) { + var db *gorm.DB + db = global.AlertDB + for _, opt := range opts { + db = opt(db) + } + return db, nil +} + +func (a *AlertRepo) GetLicensePushCount(method string) (uint, error) { + var ( + todayCount int64 + ) + now := time.Now() + todayMidnight := time.Date(now.Year(), now.Month(), now.Day(), 0, 0, 0, 0, now.Location()) + tomorrowMidnight := todayMidnight.Add(24 * time.Hour) + err := global.AlertDB.Model(&model.AlertTask{}).Where("created_at > ? AND created_at < ? AND method = ?", todayMidnight, tomorrowMidnight, method).Count(&todayCount).Error + return uint(todayCount), err +} + +func (a *AlertRepo) AlertConfigList(opts ...DBOption) ([]model.AlertConfig, error) { + var config []model.AlertConfig + db, _ := getAlertDB(opts...) + err := db.Find(&config).Error + return config, err +} + +func (a *AlertRepo) UpdateAlertConfig(maps map[string]interface{}, opts ...DBOption) error { + db, _ := getAlertDB(opts...) + return db.Model(&model.AlertConfig{}).Updates(maps).Error +} + +func (a *AlertRepo) CreateAlertConfig(config *model.AlertConfig) error { + return global.AlertDB.Model(&model.AlertConfig{}).Create(config).Error +} + +func (a *AlertRepo) DeleteAlertConfig(opts ...DBOption) error { + db, _ := getAlertDB(opts...) + return db.Delete(&model.AlertConfig{}).Error +} + +func (a *AlertRepo) GetConfig(opts ...DBOption) (model.AlertConfig, error) { + var alertConfig model.AlertConfig + db, _ := getAlertDB(opts...) + err := db.First(&alertConfig).Error + return alertConfig, err +} + +func (a *AlertRepo) SyncAll(data []model.AlertConfig) error { + tx := global.AlertDB.Begin() + var oldConfigs []model.AlertConfig + _ = tx.Find(&oldConfigs).Error + oldConfigMap := make(map[string]uint) + for _, item := range oldConfigs { + oldConfigMap[item.Type] = item.ID + } + for _, item := range data { + if val, ok := oldConfigMap[item.Type]; ok { + item.ID = val + delete(oldConfigMap, item.Type) + } else { + item.ID = 0 + } + if err := tx.Model(model.AlertConfig{}).Where("id = ?", item.ID).Save(&item).Error; err != nil { + tx.Rollback() + return err + } + } + for _, val := range oldConfigMap { + if err := tx.Where("id = ?", val).Delete(&model.AlertConfig{}).Error; err != nil { + tx.Rollback() + return err + } + } + tx.Commit() + return nil +} diff --git a/agent/app/repo/app.go b/agent/app/repo/app.go new file mode 100644 index 0000000..c7bbc7c --- /dev/null +++ b/agent/app/repo/app.go @@ -0,0 +1,164 @@ +package repo + +import ( + "context" + "fmt" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type AppRepo struct { +} + +type IAppRepo interface { + WithKey(key string) DBOption + WithType(typeStr string) DBOption + OrderByRecommend() DBOption + GetRecommend() DBOption + WithResource(resource string) DBOption + WithNotLocal() DBOption + WithByLikeName(name string) DBOption + WithArch(arch string) DBOption + WithPanelVersion(panelVersion string) DBOption + + Page(page, size int, opts ...DBOption) (int64, []model.App, error) + GetFirst(opts ...DBOption) (model.App, error) + GetBy(opts ...DBOption) ([]model.App, error) + BatchCreate(ctx context.Context, apps []model.App) error + Create(ctx context.Context, app *model.App) error + Save(ctx context.Context, app *model.App) error + BatchDelete(ctx context.Context, apps []model.App) error + DeleteByIDs(ctx context.Context, ids []uint) error + DeleteBy(opts ...DBOption) error + + GetTopRecommend() ([]string, error) +} + +func NewIAppRepo() IAppRepo { + return &AppRepo{} +} + +func (a AppRepo) WithByLikeName(name string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(name) == 0 { + return g + } + return g.Where("name like ? or description like ? or short_desc_zh like ? or short_desc_en like ?", "%"+name+"%", "%"+name+"%", "%"+name+"%", "%"+name+"%") + } +} + +func (a AppRepo) WithKey(key string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("key = ?", key) + } +} + +func (a AppRepo) WithType(typeStr string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("type = ?", typeStr) + } +} + +func (a AppRepo) OrderByRecommend() DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Order("recommend asc") + } +} + +func (a AppRepo) GetRecommend() DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("recommend < 9999") + } +} + +func (a AppRepo) WithResource(resource string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("resource = ?", resource) + } +} + +func (a AppRepo) WithNotLocal() DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("resource != 'local'") + } +} + +func (a AppRepo) WithArch(arch string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("architectures like ?", fmt.Sprintf("%%%s%%", arch)) + } +} + +func (a AppRepo) WithPanelVersion(panelVersion string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("required_panel_version >= ?", panelVersion) + } +} + +func (a AppRepo) Page(page, size int, opts ...DBOption) (int64, []model.App, error) { + var apps []model.App + db := getDb(opts...).Model(&model.App{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Preload("AppTags").Find(&apps).Error + return count, apps, err +} + +func (a AppRepo) GetFirst(opts ...DBOption) (model.App, error) { + var app model.App + db := getDb(opts...).Model(&model.App{}) + if err := db.Preload("AppTags").First(&app).Error; err != nil { + return app, err + } + return app, nil +} + +func (a AppRepo) GetBy(opts ...DBOption) ([]model.App, error) { + var apps []model.App + db := getDb(opts...).Model(&model.App{}) + if err := db.Preload("Details").Preload("AppTags").Find(&apps).Error; err != nil { + return apps, err + } + return apps, nil +} + +func (a AppRepo) GetTopRecommend() ([]string, error) { + var ( + apps []model.App + names []string + ) + db := getDb().Model(&model.App{}) + if err := db.Order("recommend asc").Limit(6).Find(&apps).Error; err != nil { + return names, err + } + for _, item := range apps { + names = append(names, item.Key) + } + return names, nil +} + +func (a AppRepo) BatchCreate(ctx context.Context, apps []model.App) error { + return getTx(ctx).Omit(clause.Associations).Create(&apps).Error +} + +func (a AppRepo) Create(ctx context.Context, app *model.App) error { + return getTx(ctx).Omit(clause.Associations).Create(app).Error +} + +func (a AppRepo) Save(ctx context.Context, app *model.App) error { + return getTx(ctx).Omit(clause.Associations).Save(app).Error +} + +func (a AppRepo) BatchDelete(ctx context.Context, apps []model.App) error { + return getTx(ctx).Omit(clause.Associations).Delete(&apps).Error +} + +func (a AppRepo) DeleteByIDs(ctx context.Context, ids []uint) error { + return getTx(ctx).Where("id in (?)", ids).Delete(&model.App{}).Error +} + +func (a AppRepo) DeleteBy(opts ...DBOption) error { + return getDb().Delete(&model.App{}, opts).Error +} diff --git a/agent/app/repo/app_detail.go b/agent/app/repo/app_detail.go new file mode 100644 index 0000000..84e5477 --- /dev/null +++ b/agent/app/repo/app_detail.go @@ -0,0 +1,88 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type AppDetailRepo struct { +} + +type IAppDetailRepo interface { + WithVersion(version string) DBOption + WithAppId(id uint) DBOption + WithIgnored() DBOption + GetFirst(opts ...DBOption) (model.AppDetail, error) + Update(ctx context.Context, detail model.AppDetail) error + BatchCreate(ctx context.Context, details []model.AppDetail) error + DeleteByAppIds(ctx context.Context, appIds []uint) error + DeleteByIDs(ctx context.Context, appIds []uint) error + GetBy(opts ...DBOption) ([]model.AppDetail, error) + BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error + BatchDelete(ctx context.Context, appDetails []model.AppDetail) error +} + +func NewIAppDetailRepo() IAppDetailRepo { + return &AppDetailRepo{} +} + +func (a AppDetailRepo) WithVersion(version string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("version = ?", version) + } +} + +func (a AppDetailRepo) WithAppId(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_id = ?", id) + } +} + +func (a AppDetailRepo) WithIgnored() DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("ignore_upgrade = 1") + } +} + +func (a AppDetailRepo) GetFirst(opts ...DBOption) (model.AppDetail, error) { + var detail model.AppDetail + err := getDb(opts...).Model(&model.AppDetail{}).Find(&detail).Error + return detail, err +} + +func (a AppDetailRepo) Update(ctx context.Context, detail model.AppDetail) error { + return getTx(ctx).Save(&detail).Error +} + +func (a AppDetailRepo) BatchCreate(ctx context.Context, details []model.AppDetail) error { + return getTx(ctx).Model(&model.AppDetail{}).Create(&details).Error +} + +func (a AppDetailRepo) DeleteByAppIds(ctx context.Context, appIds []uint) error { + return getTx(ctx).Where("app_id in (?)", appIds).Delete(&model.AppDetail{}).Error +} + +func (a AppDetailRepo) DeleteByIDs(ctx context.Context, appIds []uint) error { + return getTx(ctx).Where("id in (?)", appIds).Delete(&model.AppDetail{}).Error +} + +func (a AppDetailRepo) GetBy(opts ...DBOption) ([]model.AppDetail, error) { + var details []model.AppDetail + err := getDb(opts...).Find(&details).Error + return details, err +} + +func (a AppDetailRepo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error { + db := getDb(opts...).Model(&model.AppDetail{}) + if len(opts) == 0 { + db = db.Where("1=1") + } + return db.Updates(&maps).Error +} + +func (a AppDetailRepo) BatchDelete(ctx context.Context, appDetails []model.AppDetail) error { + return getTx(ctx).Omit(clause.Associations).Delete(&appDetails).Error +} diff --git a/agent/app/repo/app_ignore_upgrade.go b/agent/app/repo/app_ignore_upgrade.go new file mode 100644 index 0000000..cc35b21 --- /dev/null +++ b/agent/app/repo/app_ignore_upgrade.go @@ -0,0 +1,48 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type AppIgnoreUpgradeRepo struct { +} + +type IAppIgnoreUpgradeRepo interface { + WithScope(scope string) DBOption + WithAppID(appID uint) DBOption + List(opts ...DBOption) ([]model.AppIgnoreUpgrade, error) + Create(appIgnoreUpgrade *model.AppIgnoreUpgrade) error + Delete(opts ...DBOption) error +} + +func NewIAppIgnoreUpgradeRepo() IAppIgnoreUpgradeRepo { + return &AppIgnoreUpgradeRepo{} +} + +func (a AppIgnoreUpgradeRepo) WithScope(scope string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("scope = ?", scope) + } +} + +func (a AppIgnoreUpgradeRepo) WithAppID(appID uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_id = ?", appID) + } +} + +func (a AppIgnoreUpgradeRepo) List(opts ...DBOption) ([]model.AppIgnoreUpgrade, error) { + var appIgnoreUpgradeList []model.AppIgnoreUpgrade + err := getDb(opts...).Find(&appIgnoreUpgradeList).Error + return appIgnoreUpgradeList, err +} + +func (a AppIgnoreUpgradeRepo) Create(appIgnoreUpgrade *model.AppIgnoreUpgrade) error { + return global.DB.Create(appIgnoreUpgrade).Error +} + +func (a AppIgnoreUpgradeRepo) Delete(opts ...DBOption) error { + return getDb(opts...).Delete(&model.AppIgnoreUpgrade{}).Error +} diff --git a/agent/app/repo/app_install.go b/agent/app/repo/app_install.go new file mode 100644 index 0000000..20ea770 --- /dev/null +++ b/agent/app/repo/app_install.go @@ -0,0 +1,269 @@ +package repo + +import ( + "context" + "encoding/json" + + "github.com/1Panel-dev/1Panel/agent/constant" + + "gorm.io/gorm/clause" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type AppInstallRepo struct{} + +type IAppInstallRepo interface { + WithDetailIdsIn(detailIds []uint) DBOption + WithDetailIdNotIn(detailIds []uint) DBOption + WithAppId(appId uint) DBOption + WithAppIdsIn(appIds []uint) DBOption + WithAppIdsNotIn(appIds []uint) DBOption + WithStatus(status string) DBOption + WithServiceName(serviceName string) DBOption + WithContainerName(containerName string) DBOption + WithPort(port int) DBOption + WithIdNotInWebsite() DBOption + WithIDNotIs(id uint) DBOption + ListBy(ctx context.Context, opts ...DBOption) ([]model.AppInstall, error) + GetFirst(opts ...DBOption) (model.AppInstall, error) + Create(ctx context.Context, install *model.AppInstall) error + Save(ctx context.Context, install *model.AppInstall) error + DeleteBy(opts ...DBOption) error + Delete(ctx context.Context, install model.AppInstall) error + Page(page, size int, opts ...DBOption) (int64, []model.AppInstall, error) + BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error + LoadBaseInfo(key string, name string) (*RootInfo, error) + LoadInstallAppByKeyAndName(key string, name string) (*model.AppInstall, error) + GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error) +} + +func NewIAppInstallRepo() IAppInstallRepo { + return &AppInstallRepo{} +} + +func (a *AppInstallRepo) WithDetailIdsIn(detailIds []uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_detail_id in (?)", detailIds) + } +} + +func (a *AppInstallRepo) WithDetailIdNotIn(detailIds []uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_detail_id not in (?)", detailIds) + } +} + +func (a *AppInstallRepo) WithAppId(appId uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_id = ?", appId) + } +} + +func (a *AppInstallRepo) WithIDNotIs(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id != ?", id) + } +} + +func (a *AppInstallRepo) WithAppIdsIn(appIds []uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_id in (?)", appIds) + } +} + +func (a *AppInstallRepo) WithAppIdsNotIn(appIds []uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_id not in (?)", appIds) + } +} + +func (a *AppInstallRepo) WithStatus(status string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("status = ?", status) + } +} + +func (a *AppInstallRepo) WithServiceName(serviceName string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("service_name = ?", serviceName) + } +} + +func (a *AppInstallRepo) WithContainerName(containerName string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("container_name = ?", containerName) + } +} + +func (a *AppInstallRepo) WithPort(port int) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("https_port = ? or http_port = ?", port, port) + } +} + +func (a *AppInstallRepo) WithIdNotInWebsite() DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("id not in (select app_install_id from websites)") + } +} + +func (a *AppInstallRepo) ListBy(ctx context.Context, opts ...DBOption) ([]model.AppInstall, error) { + var install []model.AppInstall + db := getTx(ctx, opts...).Model(&model.AppInstall{}) + err := db.Preload("App").Find(&install).Error + return install, err +} + +func (a *AppInstallRepo) GetFirst(opts ...DBOption) (model.AppInstall, error) { + var install model.AppInstall + db := getDb(opts...).Model(&model.AppInstall{}) + err := db.Preload("App").First(&install).Error + return install, err +} + +func (a *AppInstallRepo) GetFirstByCtx(ctx context.Context, opts ...DBOption) (model.AppInstall, error) { + var install model.AppInstall + db := getTx(ctx, opts...).Model(&model.AppInstall{}) + err := db.Preload("App").First(&install).Error + return install, err +} + +func (a *AppInstallRepo) Create(ctx context.Context, install *model.AppInstall) error { + db := getTx(ctx).Model(&model.AppInstall{}) + return db.Omit(clause.Associations).Create(&install).Error +} + +func (a *AppInstallRepo) Save(ctx context.Context, install *model.AppInstall) error { + return getTx(ctx).Omit("App").Save(&install).Error +} + +func (a *AppInstallRepo) DeleteBy(opts ...DBOption) error { + return getDb(opts...).Delete(&model.AppInstall{}).Error +} + +func (a *AppInstallRepo) Delete(ctx context.Context, install model.AppInstall) error { + db := getTx(ctx).Model(&model.AppInstall{}) + return db.Delete(&install).Error +} + +func (a *AppInstallRepo) Page(page, size int, opts ...DBOption) (int64, []model.AppInstall, error) { + var apps []model.AppInstall + db := getDb(opts...).Model(&model.AppInstall{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Preload("App").Find(&apps).Error + return count, apps, err +} + +func (a *AppInstallRepo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error { + db := getDb(opts...).Model(&model.AppInstall{}) + if len(opts) == 0 { + db = db.Where("1=1") + } + return db.Updates(&maps).Error +} + +type RootInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Status string `json:"status"` + Port int64 `json:"port"` + HttpsPort int64 `json:"httpsPort"` + UserName string `json:"userName"` + Password string `json:"password"` + UserPassword string `json:"userPassword"` + ContainerName string `json:"containerName"` + ServiceName string `json:"serviceName"` + Param string `json:"param"` + Env string `json:"env"` + Key string `json:"key"` + Version string `json:"version"` + AppPath string `json:"app_path"` +} + +func (a *AppInstallRepo) LoadBaseInfo(key string, name string) (*RootInfo, error) { + var ( + app model.App + appInstall model.AppInstall + info RootInfo + ) + if err := global.DB.Where("key = ?", key).First(&app).Error; err != nil { + return nil, err + } + if len(name) == 0 { + if err := global.DB.Where("app_id = ?", app.ID).First(&appInstall).Error; err != nil { + return nil, err + } + } else { + if err := global.DB.Where("app_id = ? AND name = ?", app.ID, name).First(&appInstall).Error; err != nil { + return nil, err + } + } + envMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(appInstall.Env), &envMap); err != nil { + return nil, err + } + switch app.Key { + case "mysql", "mariadb", constant.AppMysqlCluster: + password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string) + if ok { + info.Password = password + } + case "redis", constant.AppRedisCluster: + password, ok := envMap["PANEL_REDIS_ROOT_PASSWORD"].(string) + if ok { + info.Password = password + } + case "mongodb", constant.AppPostgresql, constant.AppPostgresqlCluster: + user, ok := envMap["PANEL_DB_ROOT_USER"].(string) + if ok { + info.UserName = user + } + password, ok := envMap["PANEL_DB_ROOT_PASSWORD"].(string) + if ok { + info.Password = password + } + } + + userPassword, ok := envMap["PANEL_DB_USER_PASSWORD"].(string) + if ok { + info.UserPassword = userPassword + } + info.Port = int64(appInstall.HttpPort) + info.HttpsPort = int64(appInstall.HttpsPort) + info.ID = appInstall.ID + info.ServiceName = appInstall.ServiceName + info.ContainerName = appInstall.ContainerName + info.Name = appInstall.Name + info.Env = appInstall.Env + info.Param = appInstall.Param + info.Version = appInstall.Version + info.Key = app.Key + appInstall.App = app + info.AppPath = appInstall.GetAppPath() + info.Status = appInstall.Status + return &info, nil +} + +func (a *AppInstallRepo) LoadInstallAppByKeyAndName(key string, name string) (*model.AppInstall, error) { + var ( + app model.App + appInstall model.AppInstall + ) + if err := global.DB.Where("key = ?", key).First(&app).Error; err != nil { + return nil, err + } + if len(name) == 0 { + if err := global.DB.Where("app_id = ?", app.ID).First(&appInstall).Error; err != nil { + return nil, err + } + } else { + if err := global.DB.Where("app_id = ? AND name = ?", app.ID, name).First(&appInstall).Error; err != nil { + return nil, err + } + } + return &appInstall, nil +} diff --git a/agent/app/repo/app_install_resource.go b/agent/app/repo/app_install_resource.go new file mode 100644 index 0000000..2211a41 --- /dev/null +++ b/agent/app/repo/app_install_resource.go @@ -0,0 +1,82 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type AppInstallResourceRpo struct { +} + +type IAppInstallResourceRpo interface { + WithAppInstallId(appInstallId uint) DBOption + WithLinkId(linkId uint) DBOption + WithResourceId(resourceId uint) DBOption + GetBy(opts ...DBOption) ([]model.AppInstallResource, error) + GetFirst(opts ...DBOption) (model.AppInstallResource, error) + Create(ctx context.Context, resource *model.AppInstallResource) error + DeleteBy(ctx context.Context, opts ...DBOption) error + BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error +} + +func NewIAppInstallResourceRpo() IAppInstallResourceRpo { + return &AppInstallResourceRpo{} +} + +func (a AppInstallResourceRpo) WithAppInstallId(appInstallId uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("app_install_id = ?", appInstallId) + } +} + +func (a AppInstallResourceRpo) WithLinkId(linkId uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("link_id = ?", linkId) + } +} + +func (a AppInstallResourceRpo) WithResourceId(resourceId uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("resource_id = ?", resourceId) + } +} + +func (a AppInstallResourceRpo) GetBy(opts ...DBOption) ([]model.AppInstallResource, error) { + db := global.DB.Model(&model.AppInstallResource{}) + var resources []model.AppInstallResource + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&resources).Error + return resources, err +} + +func (a AppInstallResourceRpo) GetFirst(opts ...DBOption) (model.AppInstallResource, error) { + db := global.DB.Model(&model.AppInstallResource{}) + var resources model.AppInstallResource + for _, opt := range opts { + db = opt(db) + } + err := db.First(&resources).Error + return resources, err +} + +func (a AppInstallResourceRpo) Create(ctx context.Context, resource *model.AppInstallResource) error { + db := getTx(ctx).Model(&model.AppInstallResource{}) + return db.Create(&resource).Error +} + +func (a AppInstallResourceRpo) DeleteBy(ctx context.Context, opts ...DBOption) error { + return getTx(ctx, opts...).Delete(&model.AppInstallResource{}).Error +} + +func (a AppInstallResourceRpo) BatchUpdateBy(maps map[string]interface{}, opts ...DBOption) error { + db := getDb(opts...).Model(&model.AppInstallResource{}) + if len(opts) == 0 { + db = db.Where("1=1") + } + return db.Updates(&maps).Error +} diff --git a/agent/app/repo/app_launcher.go b/agent/app/repo/app_launcher.go new file mode 100644 index 0000000..291ef68 --- /dev/null +++ b/agent/app/repo/app_launcher.go @@ -0,0 +1,106 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type LauncherRepo struct{} + +type ILauncherRepo interface { + Get(opts ...DBOption) (model.AppLauncher, error) + ListName(opts ...DBOption) ([]string, error) + Create(launcher *model.AppLauncher) error + Save(launcher *model.AppLauncher) error + Delete(opts ...DBOption) error + + GetQuickJump(opts ...DBOption) (model.QuickJump, error) + ListQuickJump(withAll bool) []model.QuickJump + UpdateQuicks(quicks []model.QuickJump) error +} + +func NewILauncherRepo() ILauncherRepo { + return &LauncherRepo{} +} + +func (u *LauncherRepo) Get(opts ...DBOption) (model.AppLauncher, error) { + var launcher model.AppLauncher + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&launcher).Error + return launcher, err +} +func (u *LauncherRepo) ListName(opts ...DBOption) ([]string, error) { + var ops []model.AppLauncher + db := global.DB.Model(&model.AppLauncher{}) + for _, opt := range opts { + db = opt(db) + } + _ = db.Find(&ops).Error + var names []string + for i := 0; i < len(ops); i++ { + names = append(names, ops[i].Key) + } + return names, nil +} + +func (u *LauncherRepo) Create(launcher *model.AppLauncher) error { + return global.DB.Create(launcher).Error +} + +func (u *LauncherRepo) Save(launcher *model.AppLauncher) error { + return global.DB.Save(launcher).Error +} + +func (u *LauncherRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.AppLauncher{}).Error +} + +func (u *LauncherRepo) GetQuickJump(opts ...DBOption) (model.QuickJump, error) { + var launcher model.QuickJump + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&launcher).Error + return launcher, err +} +func (u *LauncherRepo) ListQuickJump(withAll bool) []model.QuickJump { + var quicks []model.QuickJump + if withAll { + _ = global.DB.Find(&quicks).Error + } else { + _ = global.DB.Where("is_show = ?", true).Find(&quicks).Error + } + if !withAll && len(quicks) == 0 { + return []model.QuickJump{ + {Name: "Website", Title: "menu.website", Recommend: 10, IsShow: true, Router: "/websites"}, + {Name: "Database", Title: "menu.database", Recommend: 30, IsShow: true, Router: "/databases"}, + {Name: "Cronjob", Title: "menu.cronjob", Recommend: 50, IsShow: true, Router: "/cronjobs"}, + {Name: "AppInstalled", Title: "home.appInstalled", Recommend: 70, IsShow: true, Router: "/apps/installed"}, + } + } + + return quicks +} +func (u *LauncherRepo) UpdateQuicks(quicks []model.QuickJump) error { + tx := global.DB.Begin() + for _, item := range quicks { + if err := tx.Model(&model.QuickJump{}).Where("id = ?", item.ID).Updates(map[string]interface{}{ + "is_show": item.IsShow, + "detail": item.Detail, + "alias": item.Alias, + }).Error; err != nil { + tx.Rollback() + return err + } + } + tx.Commit() + return nil +} diff --git a/agent/app/repo/app_tag.go b/agent/app/repo/app_tag.go new file mode 100644 index 0000000..215777d --- /dev/null +++ b/agent/app/repo/app_tag.go @@ -0,0 +1,81 @@ +package repo + +import ( + "context" + "gorm.io/gorm" + + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type AppTagRepo struct { +} + +type IAppTagRepo interface { + BatchCreate(ctx context.Context, tags []*model.AppTag) error + DeleteByAppIds(ctx context.Context, appIds []uint) error + DeleteAll(ctx context.Context) error + GetByAppId(appId uint) ([]model.AppTag, error) + GetByTagIds(tagIds []uint) ([]model.AppTag, error) + DeleteBy(ctx context.Context, opts ...DBOption) error + GetFirst(ctx context.Context, opts ...DBOption) (*model.AppTag, error) + + WithByTagID(tagID uint) DBOption + WithByAppID(appId uint) DBOption +} + +func NewIAppTagRepo() IAppTagRepo { + return &AppTagRepo{} +} + +func (a AppTagRepo) WithByTagID(tagID uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("tag_id = ?", tagID) + } +} + +func (a AppTagRepo) WithByAppID(appId uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_id = ?", appId) + } +} + +func (a AppTagRepo) BatchCreate(ctx context.Context, tags []*model.AppTag) error { + return getTx(ctx).Create(&tags).Error +} + +func (a AppTagRepo) DeleteByAppIds(ctx context.Context, appIds []uint) error { + return getTx(ctx).Where("app_id in (?)", appIds).Delete(&model.AppTag{}).Error +} + +func (a AppTagRepo) DeleteAll(ctx context.Context) error { + return getTx(ctx).Where("1 = 1").Delete(&model.AppTag{}).Error +} + +func (a AppTagRepo) GetByAppId(appId uint) ([]model.AppTag, error) { + var appTags []model.AppTag + if err := getDb().Where("app_id = ?", appId).Find(&appTags).Error; err != nil { + return nil, err + } + return appTags, nil +} + +func (a AppTagRepo) GetByTagIds(tagIds []uint) ([]model.AppTag, error) { + var appTags []model.AppTag + if err := getDb().Where("tag_id in (?)", tagIds).Find(&appTags).Error; err != nil { + return nil, err + } + return appTags, nil +} + +func (a AppTagRepo) DeleteBy(ctx context.Context, opts ...DBOption) error { + return getTx(ctx, opts...).Delete(&model.AppTag{}).Error +} + +func (a AppTagRepo) GetFirst(ctx context.Context, opts ...DBOption) (*model.AppTag, error) { + var appTag model.AppTag + if err := getTx(ctx, opts...).First(&appTag).Error; err != nil { + return nil, err + } + return &appTag, nil + +} diff --git a/agent/app/repo/backup.go b/agent/app/repo/backup.go new file mode 100644 index 0000000..2f72600 --- /dev/null +++ b/agent/app/repo/backup.go @@ -0,0 +1,201 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type BackupRepo struct{} + +type IBackupRepo interface { + Get(opts ...DBOption) (model.BackupAccount, error) + List(opts ...DBOption) ([]model.BackupAccount, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.BackupAccount, error) + Create(backup *model.BackupAccount) error + Save(backup *model.BackupAccount) error + Delete(opts ...DBOption) error + WithByPublic(isPublic bool) DBOption + + ListRecord(opts ...DBOption) ([]model.BackupRecord, error) + GetRecord(opts ...DBOption) (*model.BackupRecord, error) + PageRecord(page, size int, opts ...DBOption) (int64, []model.BackupRecord, error) + CreateRecord(record *model.BackupRecord) error + DeleteRecord(ctx context.Context, opts ...DBOption) error + UpdateRecord(record *model.BackupRecord) error + UpdateRecordByMap(id uint, upMap map[string]interface{}) error + WithByDetailName(detailName string) DBOption + WithByFileName(fileName string) DBOption + WithByCronID(cronjobID uint) DBOption + WithFileNameStartWith(filePrefix string) DBOption + + SyncAll(data []model.BackupAccount) error +} + +func NewIBackupRepo() IBackupRepo { + return &BackupRepo{} +} + +func (u *BackupRepo) WithByPublic(isPublic bool) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("is_public = ?", isPublic) + } +} + +func (u *BackupRepo) Get(opts ...DBOption) (model.BackupAccount, error) { + var backup model.BackupAccount + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&backup).Error + return backup, err +} + +func (u *BackupRepo) Page(page, size int, opts ...DBOption) (int64, []model.BackupAccount, error) { + var ops []model.BackupAccount + db := global.DB.Model(&model.BackupAccount{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error + return count, ops, err +} + +func (u *BackupRepo) List(opts ...DBOption) ([]model.BackupAccount, error) { + var ops []model.BackupAccount + db := global.DB.Model(&model.BackupAccount{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&ops).Error + return ops, err +} + +func (u *BackupRepo) Create(backup *model.BackupAccount) error { + return global.DB.Create(backup).Error +} + +func (u *BackupRepo) Save(backup *model.BackupAccount) error { + return global.DB.Save(backup).Error +} + +func (u *BackupRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.BackupAccount{}).Error +} + +func (u *BackupRepo) ListRecord(opts ...DBOption) ([]model.BackupRecord, error) { + var users []model.BackupRecord + db := global.DB.Model(&model.BackupRecord{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&users).Error + return users, err +} + +func (u *BackupRepo) PageRecord(page, size int, opts ...DBOption) (int64, []model.BackupRecord, error) { + var users []model.BackupRecord + db := global.DB.Model(&model.BackupRecord{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (u *BackupRepo) WithByFileName(fileName string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(fileName) == 0 { + return g + } + return g.Where("file_name = ?", fileName) + } +} + +func (u *BackupRepo) WithByDetailName(detailName string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(detailName) == 0 { + return g + } + return g.Where("detail_name = ?", detailName) + } +} + +func (u *BackupRepo) WithFileNameStartWith(filePrefix string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("file_name LIKE ?", filePrefix+"%") + } +} + +func (u *BackupRepo) CreateRecord(record *model.BackupRecord) error { + return global.DB.Create(record).Error +} + +func (u *BackupRepo) UpdateRecord(record *model.BackupRecord) error { + return global.DB.Save(record).Error +} + +func (u *BackupRepo) UpdateRecordByMap(id uint, upMap map[string]interface{}) error { + return global.DB.Model(&model.BackupRecord{}).Where("id = ?", id).Updates(upMap).Error +} + +func (u *BackupRepo) DeleteRecord(ctx context.Context, opts ...DBOption) error { + return getTx(ctx, opts...).Delete(&model.BackupRecord{}).Error +} + +func (u *BackupRepo) WithByCronID(cronjobID uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("cronjob_id = ?", cronjobID) + } +} + +func (u *BackupRepo) GetRecord(opts ...DBOption) (*model.BackupRecord, error) { + record := &model.BackupRecord{} + db := global.DB.Model(&model.BackupRecord{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(record).Error + return record, err +} + +func (u *BackupRepo) SyncAll(data []model.BackupAccount) error { + tx := global.DB.Begin() + var oldAccounts []model.BackupAccount + _ = tx.Where("is_public = ?", 1).Find(&oldAccounts).Error + oldAccountMap := make(map[string]uint) + for _, item := range oldAccounts { + oldAccountMap[item.Name] = item.ID + } + for _, item := range data { + if val, ok := oldAccountMap[item.Name]; ok { + item.ID = val + delete(oldAccountMap, item.Name) + } else { + item.ID = 0 + } + if err := tx.Model(model.BackupAccount{}).Where("id = ?", item.ID).Save(&item).Error; err != nil { + tx.Rollback() + return err + } + } + for _, val := range oldAccountMap { + if err := tx.Where("id = ?", val).Delete(&model.BackupAccount{}).Error; err != nil { + tx.Rollback() + return err + } + } + tx.Commit() + return nil +} diff --git a/agent/app/repo/clam.go b/agent/app/repo/clam.go new file mode 100644 index 0000000..9322993 --- /dev/null +++ b/agent/app/repo/clam.go @@ -0,0 +1,148 @@ +package repo + +import ( + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type ClamRepo struct{} + +type IClamRepo interface { + Page(limit, offset int, opts ...DBOption) (int64, []model.Clam, error) + Create(clam *model.Clam) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error + Get(opts ...DBOption) (model.Clam, error) + List(opts ...DBOption) ([]model.Clam, error) + + WithByClamID(id uint) DBOption + ListRecord(opts ...DBOption) ([]model.ClamRecord, error) + RecordFirst(id uint) (model.ClamRecord, error) + DeleteRecord(opts ...DBOption) error + StartRecords(clamID uint) model.ClamRecord + EndRecords(record model.ClamRecord, status, message string) + PageRecords(page, size int, opts ...DBOption) (int64, []model.ClamRecord, error) +} + +func NewIClamRepo() IClamRepo { + return &ClamRepo{} +} + +func (u *ClamRepo) Get(opts ...DBOption) (model.Clam, error) { + var clam model.Clam + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&clam).Error + return clam, err +} + +func (u *ClamRepo) List(opts ...DBOption) ([]model.Clam, error) { + var clam []model.Clam + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&clam).Error + return clam, err +} + +func (u *ClamRepo) Page(page, size int, opts ...DBOption) (int64, []model.Clam, error) { + var users []model.Clam + db := global.DB.Model(&model.Clam{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (u *ClamRepo) Create(clam *model.Clam) error { + return global.DB.Create(clam).Error +} + +func (u *ClamRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Clam{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *ClamRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Clam{}).Error +} + +func (c *ClamRepo) WithByClamID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("clam_id = ?", id) + } +} + +func (u *ClamRepo) ListRecord(opts ...DBOption) ([]model.ClamRecord, error) { + var record []model.ClamRecord + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&record).Error + return record, err +} + +func (u *ClamRepo) RecordFirst(id uint) (model.ClamRecord, error) { + var record model.ClamRecord + err := global.DB.Where("clam_id = ?", id).Order("created_at desc").First(&record).Error + return record, err +} + +func (u *ClamRepo) PageRecords(page, size int, opts ...DBOption) (int64, []model.ClamRecord, error) { + var records []model.ClamRecord + db := global.DB.Model(&model.ClamRecord{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Order("created_at desc").Limit(size).Offset(size * (page - 1)).Find(&records).Error + return count, records, err +} +func (u *ClamRepo) StartRecords(clamID uint) model.ClamRecord { + var record model.ClamRecord + record.StartTime = time.Now() + record.ClamID = clamID + record.TaskID = uuid.New().String() + record.Status = constant.StatusWaiting + if err := global.DB.Create(&record).Error; err != nil { + global.LOG.Errorf("create record status failed, err: %v", err) + } + _ = u.Update(clamID, map[string]interface{}{"is_executing": true}) + return record +} +func (u *ClamRepo) EndRecords(record model.ClamRecord, status, message string) { + upMap := make(map[string]interface{}) + upMap["status"] = status + upMap["message"] = message + upMap["task_id"] = record.TaskID + upMap["scan_time"] = record.ScanTime + upMap["infected_files"] = record.InfectedFiles + upMap["total_error"] = record.TotalError + if err := global.DB.Model(&model.ClamRecord{}).Where("id = ?", record.ID).Updates(upMap).Error; err != nil { + global.LOG.Errorf("update record status failed, err: %v", err) + } + _ = u.Update(record.ClamID, map[string]interface{}{"is_executing": false}) +} +func (u *ClamRepo) DeleteRecord(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.ClamRecord{}).Error +} diff --git a/agent/app/repo/common.go b/agent/app/repo/common.go new file mode 100644 index 0000000..36c2d4f --- /dev/null +++ b/agent/app/repo/common.go @@ -0,0 +1,170 @@ +package repo + +import ( + "context" + "fmt" + "time" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type DBOption func(*gorm.DB) *gorm.DB + +func WithByID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} + +func WithByNOTID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id != ?", id) + } +} + +func WithByIDs(ids []uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id in (?)", ids) + } +} + +func WithByIDNotIn(ids []uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id not in (?)", ids) + } +} + +func WithByName(name string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("name = ?", name) + } +} + +func WithByKey(key string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("key = ?", key) + } +} + +func WithByLowerName(name string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("LOWER(name) = LOWER(?)", name) + } +} + +func WithByLikeName(name string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(name) == 0 { + return g + } + return g.Where("name like ?", "%"+name+"%") + } +} + +func WithByDetailName(detailName string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(detailName) == 0 { + return g + } + return g.Where("detail_name = ?", detailName) + } +} + +func WithByType(tp string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`type` = ?", tp) + } +} +func WithByDetailType(tp string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`detail_type` = ?", tp) + } +} + +func WithTypes(types []string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("`type` in (?)", types) + } +} + +func WithByStatus(status string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(status) == 0 { + return g + } + return g.Where("status = ?", status) + } +} +func WithByFrom(from string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`from` = ?", from) + } +} + +func WithByDate(startTime, endTime time.Time) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("start_time > ? AND start_time < ?", startTime, endTime) + } +} + +func WithByGroups(groupIDs []uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(groupIDs) == 0 { + return g + } + return g.Where("group_id in (?)", groupIDs) + } +} + +func WithByCreatedAt(startTime, endTime time.Time) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("created_at > ? AND created_at < ?", startTime, endTime) + } +} + +func WithOrderBy(orderStr string) DBOption { + if orderStr == "createdAt" { + orderStr = "created_at" + } + return func(g *gorm.DB) *gorm.DB { + return g.Order(orderStr) + } +} +func WithOrderRuleBy(orderBy, order string) DBOption { + if orderBy == "createdAt" { + orderBy = "created_at" + } + switch order { + case constant.OrderDesc: + order = "desc" + case constant.OrderAsc: + order = "asc" + default: + orderBy = "created_at" + order = "desc" + } + return func(g *gorm.DB) *gorm.DB { + return g.Order(fmt.Sprintf("%s %s", orderBy, order)) + } +} + +func getTx(ctx context.Context, opts ...DBOption) *gorm.DB { + tx, ok := ctx.Value(constant.DB).(*gorm.DB) + if ok { + for _, opt := range opts { + tx = opt(tx) + } + return tx + } + return getDb(opts...) +} + +func getDb(opts ...DBOption) *gorm.DB { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db +} diff --git a/agent/app/repo/compose_template.go b/agent/app/repo/compose_template.go new file mode 100644 index 0000000..1864668 --- /dev/null +++ b/agent/app/repo/compose_template.go @@ -0,0 +1,108 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type ComposeTemplateRepo struct{} + +type IComposeTemplateRepo interface { + Get(opts ...DBOption) (model.ComposeTemplate, error) + List(opts ...DBOption) ([]model.ComposeTemplate, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.ComposeTemplate, error) + Create(compose *model.ComposeTemplate) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error + + GetRecord(opts ...DBOption) (model.Compose, error) + CreateRecord(compose *model.Compose) error + DeleteRecord(opts ...DBOption) error + ListRecord() ([]model.Compose, error) + UpdateRecord(name string, vars map[string]interface{}) error +} + +func NewIComposeTemplateRepo() IComposeTemplateRepo { + return &ComposeTemplateRepo{} +} + +func (u *ComposeTemplateRepo) Get(opts ...DBOption) (model.ComposeTemplate, error) { + var compose model.ComposeTemplate + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&compose).Error + return compose, err +} + +func (u *ComposeTemplateRepo) Page(page, size int, opts ...DBOption) (int64, []model.ComposeTemplate, error) { + var users []model.ComposeTemplate + db := global.DB.Model(&model.ComposeTemplate{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (u *ComposeTemplateRepo) List(opts ...DBOption) ([]model.ComposeTemplate, error) { + var composes []model.ComposeTemplate + db := global.DB.Model(&model.ComposeTemplate{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&composes).Error + return composes, err +} + +func (u *ComposeTemplateRepo) Create(compose *model.ComposeTemplate) error { + return global.DB.Create(compose).Error +} + +func (u *ComposeTemplateRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.ComposeTemplate{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *ComposeTemplateRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.ComposeTemplate{}).Error +} + +func (u *ComposeTemplateRepo) GetRecord(opts ...DBOption) (model.Compose, error) { + var compose model.Compose + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&compose).Error + return compose, err +} + +func (u *ComposeTemplateRepo) ListRecord() ([]model.Compose, error) { + var composes []model.Compose + if err := global.DB.Find(&composes).Error; err != nil { + return nil, err + } + return composes, nil +} + +func (u *ComposeTemplateRepo) CreateRecord(compose *model.Compose) error { + return global.DB.Create(compose).Error +} + +func (u *ComposeTemplateRepo) DeleteRecord(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Compose{}).Error +} +func (u *ComposeTemplateRepo) UpdateRecord(name string, vars map[string]interface{}) error { + return global.DB.Model(&model.Compose{}).Where("name = ?", name).Updates(vars).Error +} diff --git a/agent/app/repo/cronjob.go b/agent/app/repo/cronjob.go new file mode 100644 index 0000000..5098702 --- /dev/null +++ b/agent/app/repo/cronjob.go @@ -0,0 +1,207 @@ +package repo + +import ( + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/google/uuid" + "gorm.io/gorm" +) + +type CronjobRepo struct{} + +type ICronjobRepo interface { + Get(opts ...DBOption) (model.Cronjob, error) + GetRecord(opts ...DBOption) (model.JobRecords, error) + RecordFirst(id uint) (model.JobRecords, error) + ListRecord(opts ...DBOption) ([]model.JobRecords, error) + List(opts ...DBOption) ([]model.Cronjob, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.Cronjob, error) + Create(cronjob *model.Cronjob) error + WithByJobID(id int) DBOption + WithByDbName(name string) DBOption + WithByDownloadAccountID(id uint) DBOption + WithByRecordDropID(id int) DBOption + WithByRecordFile(file string) DBOption + Save(id uint, cronjob model.Cronjob) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error + DeleteRecord(opts ...DBOption) error + StartRecords(cronjobID uint) model.JobRecords + UpdateRecords(id uint, vars map[string]interface{}) error + EndRecords(record model.JobRecords, status, message, records string) + AddFailedRecord(cronjobID uint, message string) + PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error) +} + +func NewICronjobRepo() ICronjobRepo { + return &CronjobRepo{} +} + +func (u *CronjobRepo) Get(opts ...DBOption) (model.Cronjob, error) { + var cronjob model.Cronjob + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&cronjob).Error + return cronjob, err +} + +func (u *CronjobRepo) GetRecord(opts ...DBOption) (model.JobRecords, error) { + var record model.JobRecords + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&record).Error + return record, err +} + +func (u *CronjobRepo) List(opts ...DBOption) ([]model.Cronjob, error) { + var cronjobs []model.Cronjob + db := global.DB.Model(&model.Cronjob{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&cronjobs).Error + return cronjobs, err +} + +func (u *CronjobRepo) ListRecord(opts ...DBOption) ([]model.JobRecords, error) { + var cronjobs []model.JobRecords + db := global.DB.Model(&model.JobRecords{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&cronjobs).Error + return cronjobs, err +} + +func (u *CronjobRepo) Page(page, size int, opts ...DBOption) (int64, []model.Cronjob, error) { + var cronjobs []model.Cronjob + db := global.DB.Model(&model.Cronjob{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error + return count, cronjobs, err +} + +func (u *CronjobRepo) RecordFirst(id uint) (model.JobRecords, error) { + var record model.JobRecords + err := global.DB.Where("cronjob_id = ?", id).Order("created_at desc").First(&record).Error + return record, err +} + +func (u *CronjobRepo) PageRecords(page, size int, opts ...DBOption) (int64, []model.JobRecords, error) { + var cronjobs []model.JobRecords + db := global.DB.Model(&model.JobRecords{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Order("created_at desc").Limit(size).Offset(size * (page - 1)).Find(&cronjobs).Error + return count, cronjobs, err +} + +func (u *CronjobRepo) Create(cronjob *model.Cronjob) error { + return global.DB.Create(cronjob).Error +} + +func (c *CronjobRepo) WithByJobID(id int) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("cronjob_id = ?", id) + } +} + +func (c *CronjobRepo) WithByDbName(name string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("db_name = ?", name) + } +} + +func (c *CronjobRepo) WithByDownloadAccountID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("download_account_id = ?", id) + } +} + +func (c *CronjobRepo) WithByRecordFile(file string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("records = ?", file) + } +} + +func (c *CronjobRepo) WithByRecordDropID(id int) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id < ?", id) + } +} + +func (u *CronjobRepo) StartRecords(cronjobID uint) model.JobRecords { + var record model.JobRecords + record.StartTime = time.Now() + record.CronjobID = cronjobID + record.TaskID = uuid.New().String() + record.Status = constant.StatusWaiting + if err := global.DB.Create(&record).Error; err != nil { + global.LOG.Errorf("create record status failed, err: %v", err) + } + _ = u.Update(cronjobID, map[string]interface{}{"is_executing": true}) + return record +} +func (u *CronjobRepo) EndRecords(record model.JobRecords, status, message, records string) { + errMap := make(map[string]interface{}) + errMap["records"] = records + errMap["status"] = status + errMap["file"] = record.File + errMap["message"] = message + errMap["task_id"] = record.TaskID + errMap["interval"] = time.Since(record.StartTime).Milliseconds() + if err := global.DB.Model(&model.JobRecords{}).Where("id = ?", record.ID).Updates(errMap).Error; err != nil { + global.LOG.Errorf("update record status failed, err: %v", err) + } + _ = u.Update(record.CronjobID, map[string]interface{}{"is_executing": false}) +} +func (u *CronjobRepo) AddFailedRecord(cronjobID uint, message string) { + var record model.JobRecords + record.StartTime = time.Now() + record.CronjobID = cronjobID + record.Status = constant.StatusUnexecuted + record.Message = message + if err := global.DB.Create(&record).Error; err != nil { + global.LOG.Errorf("create record status failed, err: %v", err) + } +} + +func (u *CronjobRepo) Save(id uint, cronjob model.Cronjob) error { + return global.DB.Model(&model.Cronjob{}).Where("id = ?", id).Save(&cronjob).Error +} +func (u *CronjobRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Cronjob{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *CronjobRepo) UpdateRecords(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.JobRecords{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *CronjobRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Cronjob{}).Error +} +func (u *CronjobRepo) DeleteRecord(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.JobRecords{}).Error +} diff --git a/agent/app/repo/database.go b/agent/app/repo/database.go new file mode 100644 index 0000000..47aa097 --- /dev/null +++ b/agent/app/repo/database.go @@ -0,0 +1,136 @@ +package repo + +import ( + "context" + "fmt" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "gorm.io/gorm" +) + +type DatabaseRepo struct{} + +type IDatabaseRepo interface { + GetList(opts ...DBOption) ([]model.Database, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.Database, error) + Create(ctx context.Context, database *model.Database) error + Update(id uint, vars map[string]interface{}) error + Delete(ctx context.Context, opts ...DBOption) error + Get(opts ...DBOption) (model.Database, error) + WithoutByFrom(from string) DBOption + WithAppInstallID(appInstallID uint) DBOption + WithTypeList(dbType string) DBOption +} + +func NewIDatabaseRepo() IDatabaseRepo { + return &DatabaseRepo{} +} + +func (d *DatabaseRepo) Get(opts ...DBOption) (model.Database, error) { + var database model.Database + db := global.DB + for _, opt := range opts { + db = opt(db) + } + if err := db.First(&database).Error; err != nil { + return database, err + } + pass, err := encrypt.StringDecrypt(database.Password) + if err != nil { + global.LOG.Errorf("decrypt database %s password failed, err: %v", database.Name, err) + } + database.Password = pass + return database, nil +} + +func (d *DatabaseRepo) Page(page, size int, opts ...DBOption) (int64, []model.Database, error) { + var databases []model.Database + db := global.DB.Model(&model.Database{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + if err := db.Limit(size).Offset(size * (page - 1)).Find(&databases).Error; err != nil { + return count, databases, err + } + for i := 0; i < len(databases); i++ { + pass, err := encrypt.StringDecrypt(databases[i].Password) + if err != nil { + global.LOG.Errorf("decrypt database db %s password failed, err: %v", databases[i].Name, err) + } + databases[i].Password = pass + } + return count, databases, nil +} + +func (d *DatabaseRepo) GetList(opts ...DBOption) ([]model.Database, error) { + var databases []model.Database + db := global.DB.Model(&model.Database{}) + for _, opt := range opts { + db = opt(db) + } + if err := db.Find(&databases).Error; err != nil { + return databases, err + } + for i := 0; i < len(databases); i++ { + pass, err := encrypt.StringDecrypt(databases[i].Password) + if err != nil { + global.LOG.Errorf("decrypt database db %s password failed, err: %v", databases[i].Name, err) + } + databases[i].Password = pass + } + return databases, nil +} + +func (d *DatabaseRepo) WithoutByFrom(from string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`from` != ?", from) + } +} + +func (d *DatabaseRepo) WithTypeList(dbType string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if !strings.Contains(dbType, ",") { + return g.Where("`type` = ?", dbType) + } + types := strings.Split(dbType, ",") + var ( + rules []string + values []interface{} + ) + for _, ty := range types { + if len(ty) != 0 { + rules = append(rules, "`type` = ?") + values = append(values, ty) + } + } + return g.Where(strings.Join(rules, " OR "), values...) + } +} + +func (d *DatabaseRepo) WithAppInstallID(appInstallID uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_install_id = ?", appInstallID) + } +} + +func (d *DatabaseRepo) Create(ctx context.Context, database *model.Database) error { + pass, err := encrypt.StringEncrypt(database.Password) + if err != nil { + return fmt.Errorf("decrypt database db %s password failed, err: %v", database.Name, err) + } + database.Password = pass + return getTx(ctx).Create(database).Error +} + +func (d *DatabaseRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Database{}).Where("id = ?", id).Updates(vars).Error +} + +func (d *DatabaseRepo) Delete(ctx context.Context, opts ...DBOption) error { + return getTx(ctx, opts...).Delete(&model.Database{}).Error +} diff --git a/agent/app/repo/database_mysql.go b/agent/app/repo/database_mysql.go new file mode 100644 index 0000000..60c6a4d --- /dev/null +++ b/agent/app/repo/database_mysql.go @@ -0,0 +1,113 @@ +package repo + +import ( + "context" + "fmt" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "gorm.io/gorm" +) + +type MysqlRepo struct{} + +type IMysqlRepo interface { + Get(opts ...DBOption) (model.DatabaseMysql, error) + WithByMysqlName(mysqlName string) DBOption + List(opts ...DBOption) ([]model.DatabaseMysql, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.DatabaseMysql, error) + Create(ctx context.Context, mysql *model.DatabaseMysql) error + Delete(ctx context.Context, opts ...DBOption) error + Update(id uint, vars map[string]interface{}) error + DeleteLocal(ctx context.Context) error +} + +func NewIMysqlRepo() IMysqlRepo { + return &MysqlRepo{} +} + +func (u *MysqlRepo) Get(opts ...DBOption) (model.DatabaseMysql, error) { + var mysql model.DatabaseMysql + db := global.DB + for _, opt := range opts { + db = opt(db) + } + if err := db.First(&mysql).Error; err != nil { + return mysql, err + } + + pass, err := encrypt.StringDecrypt(mysql.Password) + if err != nil { + global.LOG.Errorf("decrypt database db %s password failed, err: %v", mysql.Name, err) + } + mysql.Password = pass + return mysql, err +} + +func (u *MysqlRepo) List(opts ...DBOption) ([]model.DatabaseMysql, error) { + var mysqls []model.DatabaseMysql + db := global.DB.Model(&model.DatabaseMysql{}) + for _, opt := range opts { + db = opt(db) + } + if err := db.Find(&mysqls).Error; err != nil { + return mysqls, err + } + for i := 0; i < len(mysqls); i++ { + pass, err := encrypt.StringDecrypt(mysqls[i].Password) + if err != nil { + global.LOG.Errorf("decrypt database db %s password failed, err: %v", mysqls[i].Name, err) + } + mysqls[i].Password = pass + } + return mysqls, nil +} + +func (u *MysqlRepo) Page(page, size int, opts ...DBOption) (int64, []model.DatabaseMysql, error) { + var mysqls []model.DatabaseMysql + db := global.DB.Model(&model.DatabaseMysql{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + if err := db.Limit(size).Offset(size * (page - 1)).Find(&mysqls).Error; err != nil { + return count, mysqls, err + } + for i := 0; i < len(mysqls); i++ { + pass, err := encrypt.StringDecrypt(mysqls[i].Password) + if err != nil { + global.LOG.Errorf("decrypt database db %s password failed, err: %v", mysqls[i].Name, err) + } + mysqls[i].Password = pass + } + return count, mysqls, nil +} + +func (u *MysqlRepo) Create(ctx context.Context, mysql *model.DatabaseMysql) error { + pass, err := encrypt.StringEncrypt(mysql.Password) + if err != nil { + return fmt.Errorf("decrypt database db %s password failed, err: %v", mysql.Name, err) + } + mysql.Password = pass + return getTx(ctx).Create(mysql).Error +} + +func (u *MysqlRepo) Delete(ctx context.Context, opts ...DBOption) error { + return getTx(ctx, opts...).Delete(&model.DatabaseMysql{}).Error +} + +func (u *MysqlRepo) DeleteLocal(ctx context.Context) error { + return getTx(ctx).Where("`from` = ?", "local").Delete(&model.DatabaseMysql{}).Error +} + +func (u *MysqlRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.DatabaseMysql{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *MysqlRepo) WithByMysqlName(mysqlName string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("mysql_name = ?", mysqlName) + } +} diff --git a/agent/app/repo/database_postgresql.go b/agent/app/repo/database_postgresql.go new file mode 100644 index 0000000..4affa1f --- /dev/null +++ b/agent/app/repo/database_postgresql.go @@ -0,0 +1,113 @@ +package repo + +import ( + "context" + "fmt" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "gorm.io/gorm" +) + +type PostgresqlRepo struct{} + +type IPostgresqlRepo interface { + Get(opts ...DBOption) (model.DatabasePostgresql, error) + WithByPostgresqlName(postgresqlName string) DBOption + List(opts ...DBOption) ([]model.DatabasePostgresql, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.DatabasePostgresql, error) + Create(ctx context.Context, postgresql *model.DatabasePostgresql) error + Delete(ctx context.Context, opts ...DBOption) error + Update(id uint, vars map[string]interface{}) error + DeleteLocal(ctx context.Context) error +} + +func NewIPostgresqlRepo() IPostgresqlRepo { + return &PostgresqlRepo{} +} + +func (u *PostgresqlRepo) Get(opts ...DBOption) (model.DatabasePostgresql, error) { + var postgresql model.DatabasePostgresql + db := global.DB + for _, opt := range opts { + db = opt(db) + } + if err := db.First(&postgresql).Error; err != nil { + return postgresql, err + } + + pass, err := encrypt.StringDecrypt(postgresql.Password) + if err != nil { + global.LOG.Errorf("decrypt database db %s password failed, err: %v", postgresql.Name, err) + } + postgresql.Password = pass + return postgresql, err +} + +func (u *PostgresqlRepo) List(opts ...DBOption) ([]model.DatabasePostgresql, error) { + var postgresqls []model.DatabasePostgresql + db := global.DB.Model(&model.DatabasePostgresql{}) + for _, opt := range opts { + db = opt(db) + } + if err := db.Find(&postgresqls).Error; err != nil { + return postgresqls, err + } + for i := 0; i < len(postgresqls); i++ { + pass, err := encrypt.StringDecrypt(postgresqls[i].Password) + if err != nil { + global.LOG.Errorf("decrypt database db %s password failed, err: %v", postgresqls[i].Name, err) + } + postgresqls[i].Password = pass + } + return postgresqls, nil +} + +func (u *PostgresqlRepo) Page(page, size int, opts ...DBOption) (int64, []model.DatabasePostgresql, error) { + var postgresqls []model.DatabasePostgresql + db := global.DB.Model(&model.DatabasePostgresql{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + if err := db.Limit(size).Offset(size * (page - 1)).Find(&postgresqls).Error; err != nil { + return count, postgresqls, err + } + for i := 0; i < len(postgresqls); i++ { + pass, err := encrypt.StringDecrypt(postgresqls[i].Password) + if err != nil { + global.LOG.Errorf("decrypt database db %s password failed, err: %v", postgresqls[i].Name, err) + } + postgresqls[i].Password = pass + } + return count, postgresqls, nil +} + +func (u *PostgresqlRepo) Create(ctx context.Context, postgresql *model.DatabasePostgresql) error { + pass, err := encrypt.StringEncrypt(postgresql.Password) + if err != nil { + return fmt.Errorf("decrypt database db %s password failed, err: %v", postgresql.Name, err) + } + postgresql.Password = pass + return getTx(ctx).Create(postgresql).Error +} + +func (u *PostgresqlRepo) Delete(ctx context.Context, opts ...DBOption) error { + return getTx(ctx, opts...).Delete(&model.DatabasePostgresql{}).Error +} + +func (u *PostgresqlRepo) DeleteLocal(ctx context.Context) error { + return getTx(ctx).Where("`from` = ?", "local").Delete(&model.DatabasePostgresql{}).Error +} + +func (u *PostgresqlRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.DatabasePostgresql{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *PostgresqlRepo) WithByPostgresqlName(postgresqlName string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("postgresql_name = ?", postgresqlName) + } +} diff --git a/agent/app/repo/favorite.go b/agent/app/repo/favorite.go new file mode 100644 index 0000000..9fd394f --- /dev/null +++ b/agent/app/repo/favorite.go @@ -0,0 +1,66 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type FavoriteRepo struct{} + +type IFavoriteRepo interface { + Page(page, size int, opts ...DBOption) (int64, []model.Favorite, error) + Create(group *model.Favorite) error + Delete(opts ...DBOption) error + GetFirst(opts ...DBOption) (model.Favorite, error) + All() ([]model.Favorite, error) + WithByPath(path string) DBOption +} + +func NewIFavoriteRepo() IFavoriteRepo { + return &FavoriteRepo{} +} + +func (f *FavoriteRepo) WithByPath(path string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("path = ?", path) + } +} + +func (f *FavoriteRepo) Page(page, size int, opts ...DBOption) (int64, []model.Favorite, error) { + var ( + favorites []model.Favorite + count int64 + ) + count = int64(0) + db := getDb(opts...).Model(&model.Favorite{}) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&favorites).Error + return count, favorites, err +} + +func (f *FavoriteRepo) Create(favorite *model.Favorite) error { + return global.DB.Create(favorite).Error +} + +func (f *FavoriteRepo) GetFirst(opts ...DBOption) (model.Favorite, error) { + var favorite model.Favorite + db := getDb(opts...).Model(&model.Favorite{}) + if err := db.First(&favorite).Error; err != nil { + return favorite, err + } + return favorite, nil +} + +func (f *FavoriteRepo) Delete(opts ...DBOption) error { + db := getDb(opts...).Model(&model.Favorite{}) + return db.Delete(&model.Favorite{}).Error +} + +func (f *FavoriteRepo) All() ([]model.Favorite, error) { + var favorites []model.Favorite + if err := getDb().Find(&favorites).Error; err != nil { + return nil, err + } + return favorites, nil +} diff --git a/agent/app/repo/ftp.go b/agent/app/repo/ftp.go new file mode 100644 index 0000000..35e071e --- /dev/null +++ b/agent/app/repo/ftp.go @@ -0,0 +1,87 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type FtpRepo struct{} + +type IFtpRepo interface { + Get(opts ...DBOption) (model.Ftp, error) + GetList(opts ...DBOption) ([]model.Ftp, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.Ftp, error) + Create(ftp *model.Ftp) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error + WithLikeUser(user string) DBOption + WithByUser(user string) DBOption +} + +func NewIFtpRepo() IFtpRepo { + return &FtpRepo{} +} + +func (u *FtpRepo) Get(opts ...DBOption) (model.Ftp, error) { + var ftp model.Ftp + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&ftp).Error + return ftp, err +} + +func (h *FtpRepo) WithByUser(user string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("user = ?", user) + } +} + +func (h *FtpRepo) WithLikeUser(user string) DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(user) == 0 { + return g + } + return g.Where("user like ?", "%"+user+"%") + } +} + +func (u *FtpRepo) GetList(opts ...DBOption) ([]model.Ftp, error) { + var ftps []model.Ftp + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&ftps).Error + return ftps, err +} + +func (h *FtpRepo) Page(page, size int, opts ...DBOption) (int64, []model.Ftp, error) { + var users []model.Ftp + db := global.DB.Model(&model.Ftp{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (h *FtpRepo) Create(ftp *model.Ftp) error { + return global.DB.Create(ftp).Error +} + +func (h *FtpRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Ftp{}).Where("id = ?", id).Updates(vars).Error +} + +func (h *FtpRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Ftp{}).Error +} diff --git a/agent/app/repo/group.go b/agent/app/repo/group.go new file mode 100644 index 0000000..9a33e53 --- /dev/null +++ b/agent/app/repo/group.go @@ -0,0 +1,78 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type GroupRepo struct{} + +type IGroupRepo interface { + Get(opts ...DBOption) (model.Group, error) + GetList(opts ...DBOption) ([]model.Group, error) + Create(group *model.Group) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error + CancelDefault(groupType string) error + WithByDefault(isDefault bool) DBOption + WithByWebsiteDefault() DBOption +} + +func NewIGroupRepo() IGroupRepo { + return &GroupRepo{} +} + +func (g *GroupRepo) WithByDefault(isDefault bool) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("is_default = ?", isDefault) + } +} + +func (g *GroupRepo) Get(opts ...DBOption) (model.Group, error) { + var group model.Group + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&group).Error + return group, err +} + +func (g *GroupRepo) GetList(opts ...DBOption) ([]model.Group, error) { + var groups []model.Group + db := global.DB.Model(&model.Group{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&groups).Error + return groups, err +} + +func (g *GroupRepo) Create(group *model.Group) error { + return global.DB.Create(group).Error +} + +func (g *GroupRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Group{}).Where("id = ?", id).Updates(vars).Error +} + +func (g *GroupRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Group{}).Error +} + +func (g *GroupRepo) WithByWebsiteDefault() DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("is_default = ? AND type = ?", 1, "website") + } +} + +func (g *GroupRepo) CancelDefault(groupType string) error { + return global.DB.Model(&model.Group{}). + Where("is_default = ? AND type = ?", 1, groupType). + Updates(map[string]interface{}{"is_default": 0}).Error +} diff --git a/agent/app/repo/host.go b/agent/app/repo/host.go new file mode 100644 index 0000000..374dbcd --- /dev/null +++ b/agent/app/repo/host.go @@ -0,0 +1,172 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "gorm.io/gorm" +) + +type HostRepo struct{} + +type IHostRepo interface { + GetFirewallRecord(opts ...DBOption) (model.Firewall, error) + ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error) + SaveFirewallRecord(firewall *model.Firewall) error + DeleteFirewallRecordByID(id uint) error + + SyncCert(data []model.RootCert) error + GetCert(opts ...DBOption) (model.RootCert, error) + PageCert(limit, offset int, opts ...DBOption) (int64, []model.RootCert, error) + ListCert(opts ...DBOption) ([]model.RootCert, error) + SaveCert(cert *model.RootCert) error + UpdateCert(id uint, vars map[string]interface{}) error + DeleteCert(opts ...DBOption) error + + WithByChain(chain string) DBOption +} + +func NewIHostRepo() IHostRepo { + return &HostRepo{} +} + +func (h *HostRepo) GetFirewallRecord(opts ...DBOption) (model.Firewall, error) { + var firewall model.Firewall + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&firewall).Error + return firewall, err +} + +func (h *HostRepo) ListFirewallRecord(opts ...DBOption) ([]model.Firewall, error) { + var firewalls []model.Firewall + db := global.DB + for _, opt := range opts { + db = opt(db) + } + if err := global.DB.Find(&firewalls).Error; err != nil { + return firewalls, nil + } + return firewalls, nil +} + +func (h *HostRepo) SaveFirewallRecord(firewall *model.Firewall) error { + if firewall.ID != 0 { + return global.DB.Save(firewall).Error + } + var data model.Firewall + switch firewall.Type { + case "port": + _ = global.DB.Where("type = ? AND dst_port = ? AND protocol = ? AND src_ip = ? AND strategy = ?", "port", + firewall.DstPort, + firewall.Protocol, + firewall.SrcIP, + firewall.Strategy, + ).First(&data).Error + case "ip": + _ = global.DB.Where("type = ? AND src_ip = ? AND strategy = ?", "address", firewall.SrcIP, firewall.Strategy).First(&data) + default: + _ = global.DB.Where("type = ? AND chain = ? AND src_port = ? AND dst_port = ? AND protocol = ? AND src_ip = ? AND dst_ip = ? AND strategy = ?", + firewall.Type, + firewall.Chain, + firewall.SrcPort, + firewall.DstPort, + firewall.Protocol, + firewall.SrcIP, + firewall.DstIP, + firewall.Strategy, + ).First(&data).Error + } + return global.DB.Save(firewall).Error +} + +func (h *HostRepo) DeleteFirewallRecordByID(id uint) error { + return global.DB.Where("id = ?", id).Delete(&model.Firewall{}).Error +} + +func (u *HostRepo) GetCert(opts ...DBOption) (model.RootCert, error) { + var cert model.RootCert + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&cert).Error + return cert, err +} + +func (u *HostRepo) PageCert(page, size int, opts ...DBOption) (int64, []model.RootCert, error) { + var ops []model.RootCert + db := global.DB.Model(&model.RootCert{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error + return count, ops, err +} + +func (u *HostRepo) ListCert(opts ...DBOption) ([]model.RootCert, error) { + var ops []model.RootCert + db := global.DB.Model(&model.RootCert{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Find(&ops).Error + return ops, err +} + +func (u *HostRepo) SaveCert(cert *model.RootCert) error { + return global.DB.Save(cert).Error +} + +func (u *HostRepo) UpdateCert(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.RootCert{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *HostRepo) DeleteCert(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.RootCert{}).Error +} + +func (u *HostRepo) SyncCert(data []model.RootCert) error { + tx := global.DB.Begin() + var oldCerts []model.RootCert + _ = tx.Where("1 = ?", 1).Find(&oldCerts).Error + oldCertsMap := make(map[string]uint) + for _, item := range oldCerts { + oldCertsMap[item.Name] = item.ID + } + for _, item := range data { + if _, ok := oldCertsMap[item.Name]; ok { + delete(oldCertsMap, item.Name) + continue + } + item.PassPhrase, _ = encrypt.StringEncrypt("") + if err := tx.Model(model.RootCert{}).Create(&item).Error; err != nil { + tx.Rollback() + return err + } + } + for _, val := range oldCertsMap { + if err := tx.Where("id = ?", val).Delete(&model.RootCert{}).Error; err != nil { + tx.Rollback() + return err + } + } + tx.Commit() + return nil +} + +func (u *HostRepo) WithByChain(chain string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("chain = ?", chain) + } +} diff --git a/agent/app/repo/image_repo.go b/agent/app/repo/image_repo.go new file mode 100644 index 0000000..423fedf --- /dev/null +++ b/agent/app/repo/image_repo.go @@ -0,0 +1,71 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type ImageRepoRepo struct{} + +type IImageRepoRepo interface { + Get(opts ...DBOption) (model.ImageRepo, error) + Page(limit, offset int, opts ...DBOption) (int64, []model.ImageRepo, error) + List(opts ...DBOption) ([]model.ImageRepo, error) + Create(imageRepo *model.ImageRepo) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...DBOption) error +} + +func NewIImageRepoRepo() IImageRepoRepo { + return &ImageRepoRepo{} +} + +func (u *ImageRepoRepo) Get(opts ...DBOption) (model.ImageRepo, error) { + var imageRepo model.ImageRepo + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&imageRepo).Error + return imageRepo, err +} + +func (u *ImageRepoRepo) Page(page, size int, opts ...DBOption) (int64, []model.ImageRepo, error) { + var ops []model.ImageRepo + db := global.DB.Model(&model.ImageRepo{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error + return count, ops, err +} + +func (u *ImageRepoRepo) List(opts ...DBOption) ([]model.ImageRepo, error) { + var ops []model.ImageRepo + db := global.DB.Model(&model.ImageRepo{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Find(&ops).Error + return ops, err +} + +func (u *ImageRepoRepo) Create(imageRepo *model.ImageRepo) error { + return global.DB.Create(imageRepo).Error +} + +func (u *ImageRepoRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.ImageRepo{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *ImageRepoRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.ImageRepo{}).Error +} diff --git a/agent/app/repo/mcp_server.go b/agent/app/repo/mcp_server.go new file mode 100644 index 0000000..775efa6 --- /dev/null +++ b/agent/app/repo/mcp_server.go @@ -0,0 +1,56 @@ +package repo + +import "github.com/1Panel-dev/1Panel/agent/app/model" + +type McpServerRepo struct { +} + +type IMcpServerRepo interface { + Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error) + GetFirst(opts ...DBOption) (*model.McpServer, error) + Create(mcpServer *model.McpServer) error + Save(mcpServer *model.McpServer) error + DeleteBy(opts ...DBOption) error + List(opts ...DBOption) ([]model.McpServer, error) +} + +func NewIMcpServerRepo() IMcpServerRepo { + return &McpServerRepo{} +} + +func (m McpServerRepo) Page(page, size int, opts ...DBOption) (int64, []model.McpServer, error) { + var servers []model.McpServer + db := getDb(opts...).Model(&model.McpServer{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&servers).Error + return count, servers, err +} + +func (m McpServerRepo) GetFirst(opts ...DBOption) (*model.McpServer, error) { + var mcpServer model.McpServer + if err := getDb(opts...).First(&mcpServer).Error; err != nil { + return nil, err + } + return &mcpServer, nil +} + +func (m McpServerRepo) List(opts ...DBOption) ([]model.McpServer, error) { + var mcpServers []model.McpServer + if err := getDb(opts...).Find(&mcpServers).Error; err != nil { + return nil, err + } + return mcpServers, nil +} + +func (m McpServerRepo) Create(mcpServer *model.McpServer) error { + return getDb().Create(mcpServer).Error +} + +func (m McpServerRepo) Save(mcpServer *model.McpServer) error { + return getDb().Save(mcpServer).Error +} + +func (m McpServerRepo) DeleteBy(opts ...DBOption) error { + return getDb(opts...).Delete(&model.McpServer{}).Error +} diff --git a/agent/app/repo/monitor.go b/agent/app/repo/monitor.go new file mode 100644 index 0000000..64d9c0e --- /dev/null +++ b/agent/app/repo/monitor.go @@ -0,0 +1,101 @@ +package repo + +import ( + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type MonitorRepo struct{} + +type IMonitorRepo interface { + GetBase(opts ...DBOption) ([]model.MonitorBase, error) + GetGPU(opts ...DBOption) ([]model.MonitorGPU, error) + GetIO(opts ...DBOption) ([]model.MonitorIO, error) + GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error) + + CreateMonitorBase(model model.MonitorBase) error + BatchCreateMonitorGPU(list []model.MonitorGPU) error + BatchCreateMonitorIO(ioList []model.MonitorIO) error + BatchCreateMonitorNet(ioList []model.MonitorNetwork) error + DelMonitorBase(timeForDelete time.Time) error + DelMonitorGPU(timeForDelete time.Time) error + DelMonitorIO(timeForDelete time.Time) error + DelMonitorNet(timeForDelete time.Time) error + + WithByProductName(name string) DBOption +} + +func NewIMonitorRepo() IMonitorRepo { + return &MonitorRepo{} +} + +func (u *MonitorRepo) GetBase(opts ...DBOption) ([]model.MonitorBase, error) { + var data []model.MonitorBase + db := global.MonitorDB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&data).Error + return data, err +} +func (u *MonitorRepo) GetIO(opts ...DBOption) ([]model.MonitorIO, error) { + var data []model.MonitorIO + db := global.MonitorDB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&data).Error + return data, err +} +func (u *MonitorRepo) GetNetwork(opts ...DBOption) ([]model.MonitorNetwork, error) { + var data []model.MonitorNetwork + db := global.MonitorDB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&data).Error + return data, err +} +func (u *MonitorRepo) GetGPU(opts ...DBOption) ([]model.MonitorGPU, error) { + var data []model.MonitorGPU + db := global.GPUMonitorDB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&data).Error + return data, err +} + +func (u *MonitorRepo) CreateMonitorBase(model model.MonitorBase) error { + return global.MonitorDB.Create(&model).Error +} +func (s *MonitorRepo) BatchCreateMonitorGPU(list []model.MonitorGPU) error { + return global.GPUMonitorDB.CreateInBatches(&list, len(list)).Error +} +func (u *MonitorRepo) BatchCreateMonitorIO(ioList []model.MonitorIO) error { + return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error +} +func (u *MonitorRepo) BatchCreateMonitorNet(ioList []model.MonitorNetwork) error { + return global.MonitorDB.CreateInBatches(ioList, len(ioList)).Error +} +func (u *MonitorRepo) DelMonitorBase(timeForDelete time.Time) error { + return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorBase{}).Error +} +func (u *MonitorRepo) DelMonitorIO(timeForDelete time.Time) error { + return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorIO{}).Error +} +func (u *MonitorRepo) DelMonitorNet(timeForDelete time.Time) error { + return global.MonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorNetwork{}).Error +} +func (s *MonitorRepo) DelMonitorGPU(timeForDelete time.Time) error { + return global.GPUMonitorDB.Where("created_at < ?", timeForDelete).Delete(&model.MonitorGPU{}).Error +} + +func (s *MonitorRepo) WithByProductName(name string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("product_name = ?", name) + } +} diff --git a/agent/app/repo/php_extensions.go b/agent/app/repo/php_extensions.go new file mode 100644 index 0000000..651cf7d --- /dev/null +++ b/agent/app/repo/php_extensions.go @@ -0,0 +1,59 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type PHPExtensionsRepo struct { +} + +type IPHPExtensionsRepo interface { + Page(page, size int, opts ...DBOption) (int64, []model.PHPExtensions, error) + Save(extension *model.PHPExtensions) error + Create(extension *model.PHPExtensions) error + GetFirst(opts ...DBOption) (model.PHPExtensions, error) + DeleteBy(opts ...DBOption) error + List() ([]model.PHPExtensions, error) +} + +func NewIPHPExtensionsRepo() IPHPExtensionsRepo { + return &PHPExtensionsRepo{} +} + +func (p *PHPExtensionsRepo) Page(page, size int, opts ...DBOption) (int64, []model.PHPExtensions, error) { + var ( + phpExtensions []model.PHPExtensions + ) + db := getDb(opts...).Model(&model.PHPExtensions{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&phpExtensions).Error + return count, phpExtensions, err +} + +func (p *PHPExtensionsRepo) List() ([]model.PHPExtensions, error) { + var ( + phpExtensions []model.PHPExtensions + ) + err := getDb().Model(&model.PHPExtensions{}).Find(&phpExtensions).Error + return phpExtensions, err +} + +func (p *PHPExtensionsRepo) Save(extension *model.PHPExtensions) error { + return getDb().Save(&extension).Error +} + +func (p *PHPExtensionsRepo) Create(extension *model.PHPExtensions) error { + return getDb().Create(&extension).Error +} + +func (p *PHPExtensionsRepo) GetFirst(opts ...DBOption) (model.PHPExtensions, error) { + var extension model.PHPExtensions + db := getDb(opts...).Model(&model.PHPExtensions{}) + err := db.First(&extension).Error + return extension, err +} + +func (p *PHPExtensionsRepo) DeleteBy(opts ...DBOption) error { + return getDb(opts...).Delete(&model.PHPExtensions{}).Error +} diff --git a/agent/app/repo/runtime.go b/agent/app/repo/runtime.go new file mode 100644 index 0000000..c32dd30 --- /dev/null +++ b/agent/app/repo/runtime.go @@ -0,0 +1,122 @@ +package repo + +import ( + "context" + "fmt" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type RuntimeRepo struct { +} + +type IRuntimeRepo interface { + WithImage(image string) DBOption + WithNotId(id uint) DBOption + WithStatus(status string) DBOption + WithDetailId(id uint) DBOption + WithDetailIdsIn(ids []uint) DBOption + WithPort(port int) DBOption + WithNormalStatus(status string) DBOption + Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) + Create(ctx context.Context, runtime *model.Runtime) error + Save(runtime *model.Runtime) error + DeleteBy(opts ...DBOption) error + GetFirst(ctx context.Context, opts ...DBOption) (*model.Runtime, error) + List(opts ...DBOption) ([]model.Runtime, error) +} + +func NewIRunTimeRepo() IRuntimeRepo { + return &RuntimeRepo{} +} + +func (r *RuntimeRepo) WithStatus(status string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("status = ?", status) + } +} + +func (r *RuntimeRepo) WithNormalStatus(status string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("status = ? or status = 'Normal'", status) + } +} + +func (r *RuntimeRepo) WithImage(image string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("image = ?", image) + } +} + +func (r *RuntimeRepo) WithDetailId(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_detail_id = ?", id) + } +} + +func (r *RuntimeRepo) WithDetailIdsIn(ids []uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("app_detail_id in(?) ", ids) + } +} + +func (r *RuntimeRepo) WithNotId(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id != ?", id) + } +} + +func (r *RuntimeRepo) WithPort(port int) DBOption { + return func(g *gorm.DB) *gorm.DB { + portStr := fmt.Sprintf("%d", port) + return g.Debug().Where( + "port = ? OR port LIKE ? OR port LIKE ? OR port LIKE ?", + portStr, + portStr+",%", + "%,"+portStr, + "%,"+portStr+",%", + ) + } +} + +func (r *RuntimeRepo) Page(page, size int, opts ...DBOption) (int64, []model.Runtime, error) { + var runtimes []model.Runtime + db := getDb(opts...).Model(&model.Runtime{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&runtimes).Error + return count, runtimes, err +} + +func (r *RuntimeRepo) List(opts ...DBOption) ([]model.Runtime, error) { + var runtimes []model.Runtime + db := global.DB.Model(&model.Runtime{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&runtimes).Error + return runtimes, err +} + +func (r *RuntimeRepo) Create(ctx context.Context, runtime *model.Runtime) error { + db := getTx(ctx).Model(&model.Runtime{}) + return db.Create(&runtime).Error +} + +func (r *RuntimeRepo) Save(runtime *model.Runtime) error { + return getDb().Save(&runtime).Error +} + +func (r *RuntimeRepo) DeleteBy(opts ...DBOption) error { + return getDb(opts...).Delete(&model.Runtime{}).Error +} + +func (r *RuntimeRepo) GetFirst(ctx context.Context, opts ...DBOption) (*model.Runtime, error) { + var runtime model.Runtime + if err := getTx(ctx, opts...).First(&runtime).Error; err != nil { + return nil, err + } + return &runtime, nil +} diff --git a/agent/app/repo/script.go b/agent/app/repo/script.go new file mode 100644 index 0000000..fb80997 --- /dev/null +++ b/agent/app/repo/script.go @@ -0,0 +1,80 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type ScriptRepo struct{} + +type IScriptRepo interface { + Get(opts ...DBOption) (model.ScriptLibrary, error) + List(opts ...DBOption) ([]model.ScriptLibrary, error) + + SyncAll(data []model.ScriptLibrary) error +} + +func NewIScriptRepo() IScriptRepo { + return &ScriptRepo{} +} + +func (u *ScriptRepo) Get(opts ...DBOption) (model.ScriptLibrary, error) { + var script model.ScriptLibrary + db := global.DB + if global.IsMaster { + db = global.CoreDB + } + for _, opt := range opts { + db = opt(db) + } + err := db.First(&script).Error + return script, err +} + +func (u *ScriptRepo) List(opts ...DBOption) ([]model.ScriptLibrary, error) { + var ops []model.ScriptLibrary + if global.IsMaster { + db := global.CoreDB.Model(&model.ScriptLibrary{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Where("is_interactive = ?", false).Find(&ops).Error + return ops, err + } + db := global.DB.Model(&model.ScriptLibrary{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&ops).Error + return ops, err +} + +func (u *ScriptRepo) SyncAll(data []model.ScriptLibrary) error { + tx := global.DB.Begin() + var oldScripts []model.ScriptLibrary + _ = tx.Where("1 = 1").Find(&oldScripts).Error + oldScriptMap := make(map[string]uint) + for _, item := range oldScripts { + oldScriptMap[item.Name] = item.ID + } + for _, item := range data { + if val, ok := oldScriptMap[item.Name]; ok { + item.ID = val + delete(oldScriptMap, item.Name) + } else { + item.ID = 0 + } + if err := tx.Model(model.ScriptLibrary{}).Where("id = ?", item.ID).Save(&item).Error; err != nil { + tx.Rollback() + return err + } + } + for _, val := range oldScriptMap { + if err := tx.Where("id = ?", val).Delete(&model.ScriptLibrary{}).Error; err != nil { + tx.Rollback() + return err + } + } + tx.Commit() + return nil +} diff --git a/agent/app/repo/setting.go b/agent/app/repo/setting.go new file mode 100644 index 0000000..429d787 --- /dev/null +++ b/agent/app/repo/setting.go @@ -0,0 +1,124 @@ +package repo + +import ( + "errors" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type SettingRepo struct{} + +type ISettingRepo interface { + GetList(opts ...DBOption) ([]model.Setting, error) + Get(opts ...DBOption) (model.Setting, error) + GetValueByKey(key string) (string, error) + Create(key, value string) error + Update(key, value string) error + WithByKey(key string) DBOption + + UpdateOrCreate(key, value string) error + + GetDescription(opts ...DBOption) (model.CommonDescription, error) + GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error) + CreateDescription(data *model.CommonDescription) error + UpdateDescription(id string, val map[string]interface{}) error + DelDescription(id string) error + WithByDescriptionID(id string) DBOption +} + +func NewISettingRepo() ISettingRepo { + return &SettingRepo{} +} + +func (s *SettingRepo) GetList(opts ...DBOption) ([]model.Setting, error) { + var settings []model.Setting + db := global.DB.Model(&model.Setting{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&settings).Error + return settings, err +} + +func (s *SettingRepo) Create(key, value string) error { + setting := &model.Setting{ + Key: key, + Value: value, + } + return global.DB.Create(setting).Error +} + +func (s *SettingRepo) Get(opts ...DBOption) (model.Setting, error) { + var settings model.Setting + db := global.DB.Model(&model.Setting{}) + for _, opt := range opts { + db = opt(db) + } + err := db.First(&settings).Error + return settings, err +} + +func (s *SettingRepo) GetValueByKey(key string) (string, error) { + var setting model.Setting + if err := global.DB.Model(&model.Setting{}).Where("key = ?", key).First(&setting).Error; err != nil { + return "", err + } + return setting.Value, nil +} + +func (s *SettingRepo) WithByKey(key string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("key = ?", key) + } +} + +func (s *SettingRepo) Update(key, value string) error { + return global.DB.Model(&model.Setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error +} + +func (s *SettingRepo) UpdateOrCreate(key, value string) error { + var setting model.Setting + result := global.DB.Where("key = ?", key).First(&setting) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return global.DB.Create(&model.Setting{Key: key, Value: value}).Error + } + return result.Error + } + return global.DB.Model(&setting).UpdateColumn("value", value).Error +} + +func (s *SettingRepo) GetDescriptionList(opts ...DBOption) ([]model.CommonDescription, error) { + var lists []model.CommonDescription + db := global.DB.Model(&model.CommonDescription{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&lists).Error + return lists, err +} +func (s *SettingRepo) GetDescription(opts ...DBOption) (model.CommonDescription, error) { + var data model.CommonDescription + db := global.DB.Model(&model.CommonDescription{}) + for _, opt := range opts { + db = opt(db) + } + err := db.First(&data).Error + return data, err +} +func (s *SettingRepo) CreateDescription(data *model.CommonDescription) error { + return global.DB.Create(data).Error +} +func (s *SettingRepo) UpdateDescription(id string, val map[string]interface{}) error { + return global.DB.Model(&model.CommonDescription{}).Where("id = ?", id).Updates(val).Error +} +func (s *SettingRepo) DelDescription(id string) error { + return global.DB.Where("id = ?", id).Delete(&model.CommonDescription{}).Error +} +func (s *SettingRepo) WithByDescriptionID(id string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} diff --git a/agent/app/repo/snapshot.go b/agent/app/repo/snapshot.go new file mode 100644 index 0000000..2894701 --- /dev/null +++ b/agent/app/repo/snapshot.go @@ -0,0 +1,69 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type ISnapshotRepo interface { + Get(opts ...DBOption) (model.Snapshot, error) + GetList(opts ...DBOption) ([]model.Snapshot, error) + Create(snap *model.Snapshot) error + Update(id uint, vars map[string]interface{}) error + Page(limit, offset int, opts ...DBOption) (int64, []model.Snapshot, error) + Delete(opts ...DBOption) error +} + +func NewISnapshotRepo() ISnapshotRepo { + return &SnapshotRepo{} +} + +type SnapshotRepo struct{} + +func (u *SnapshotRepo) Get(opts ...DBOption) (model.Snapshot, error) { + var Snapshot model.Snapshot + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&Snapshot).Error + return Snapshot, err +} + +func (u *SnapshotRepo) GetList(opts ...DBOption) ([]model.Snapshot, error) { + var snaps []model.Snapshot + db := global.DB.Model(&model.Snapshot{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&snaps).Error + return snaps, err +} + +func (u *SnapshotRepo) Page(page, size int, opts ...DBOption) (int64, []model.Snapshot, error) { + var users []model.Snapshot + db := global.DB.Model(&model.Snapshot{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (u *SnapshotRepo) Create(Snapshot *model.Snapshot) error { + return global.DB.Create(Snapshot).Error +} + +func (u *SnapshotRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Snapshot{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *SnapshotRepo) Delete(opts ...DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Snapshot{}).Error +} diff --git a/agent/app/repo/tag.go b/agent/app/repo/tag.go new file mode 100644 index 0000000..f5342cb --- /dev/null +++ b/agent/app/repo/tag.go @@ -0,0 +1,87 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type TagRepo struct { +} + +type ITagRepo interface { + BatchCreate(ctx context.Context, tags []*model.Tag) error + DeleteAll(ctx context.Context) error + All() ([]model.Tag, error) + GetByIds(ids []uint) ([]model.Tag, error) + GetByKeys(keys []string) ([]model.Tag, error) + GetByAppId(appId uint) ([]model.Tag, error) + DeleteByID(ctx context.Context, id uint) error + Create(ctx context.Context, tag *model.Tag) error + Save(ctx context.Context, tag *model.Tag) error + GetByKey(key string) (*model.Tag, error) +} + +func NewITagRepo() ITagRepo { + return &TagRepo{} +} + +func (t TagRepo) BatchCreate(ctx context.Context, tags []*model.Tag) error { + return getTx(ctx).Create(&tags).Error +} + +func (t TagRepo) DeleteAll(ctx context.Context) error { + return getTx(ctx).Where("1 = 1 ").Delete(&model.Tag{}).Error +} + +func (t TagRepo) All() ([]model.Tag, error) { + var tags []model.Tag + if err := getDb().Where("1 = 1 ").Order("sort asc").Find(&tags).Error; err != nil { + return nil, err + } + return tags, nil +} + +func (t TagRepo) GetByKey(key string) (*model.Tag, error) { + var tag model.Tag + if err := getDb().Where("key = ?", key).First(&tag).Error; err != nil { + return nil, err + } + return &tag, nil +} + +func (t TagRepo) GetByIds(ids []uint) ([]model.Tag, error) { + var tags []model.Tag + if err := getDb().Where("id in (?)", ids).Find(&tags).Error; err != nil { + return nil, err + } + return tags, nil +} + +func (t TagRepo) GetByKeys(keys []string) ([]model.Tag, error) { + var tags []model.Tag + if err := getDb().Where("key in (?)", keys).Find(&tags).Error; err != nil { + return nil, err + } + return tags, nil +} + +func (t TagRepo) GetByAppId(appId uint) ([]model.Tag, error) { + var tags []model.Tag + if err := getDb().Where("id in (select tag_id from app_tags where app_id = ?)", appId).Find(&tags).Error; err != nil { + return nil, err + } + return tags, nil +} + +func (t TagRepo) DeleteByID(ctx context.Context, id uint) error { + return getTx(ctx).Where("id = ?", id).Delete(&model.Tag{}).Error +} + +func (t TagRepo) Create(ctx context.Context, tag *model.Tag) error { + return getTx(ctx).Create(tag).Error +} + +func (t TagRepo) Save(ctx context.Context, tag *model.Tag) error { + return getTx(ctx).Save(tag).Error +} diff --git a/agent/app/repo/task.go b/agent/app/repo/task.go new file mode 100644 index 0000000..b5dea01 --- /dev/null +++ b/agent/app/repo/task.go @@ -0,0 +1,132 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "gorm.io/gorm" +) + +type TaskRepo struct { +} + +type ITaskRepo interface { + Save(ctx context.Context, task *model.Task) error + GetFirst(opts ...DBOption) (model.Task, error) + Page(page, size int, opts ...DBOption) (int64, []model.Task, error) + Update(ctx context.Context, task *model.Task) error + UpdateRunningTaskToFailed() error + CountExecutingTask() (int64, error) + Delete(opts ...DBOption) error + DeleteAll() error + + WithByID(id string) DBOption + WithByIDNotIn(ids []string) DBOption + WithResourceID(id uint) DBOption + WithOperate(taskOperate string) DBOption + WithByStatus(status string) DBOption +} + +func NewITaskRepo() ITaskRepo { + return &TaskRepo{} +} + +func getTaskDb(opts ...DBOption) *gorm.DB { + db := global.TaskDB + for _, opt := range opts { + db = opt(db) + } + return db +} + +func getTaskTx(ctx context.Context, opts ...DBOption) *gorm.DB { + tx, ok := ctx.Value(constant.DB).(*gorm.DB) + if ok { + for _, opt := range opts { + tx = opt(tx) + } + return tx + } + return getTaskDb(opts...) +} + +func (t TaskRepo) WithByID(id string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} + +func (t TaskRepo) WithByIDNotIn(ids []string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id not in (?)", ids) + } +} + +func (t TaskRepo) WithOperate(taskOperate string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("operate = ?", taskOperate) + } +} + +func (t TaskRepo) WithResourceID(id uint) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("resource_id = ?", id) + } +} + +func (t TaskRepo) WithByStatus(status string) DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("status = ?", status) + } +} + +func (t TaskRepo) Save(ctx context.Context, task *model.Task) error { + return getTaskTx(ctx).Save(&task).Error +} + +func (t TaskRepo) GetFirst(opts ...DBOption) (model.Task, error) { + var task model.Task + db := getTaskDb(opts...).Model(&model.Task{}) + if err := db.First(&task).Error; err != nil { + return task, err + } + return task, nil +} + +func (t TaskRepo) Page(page, size int, opts ...DBOption) (int64, []model.Task, error) { + var tasks []model.Task + db := getTaskDb(opts...).Model(&model.Task{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&tasks).Error + return count, tasks, err +} + +func (t TaskRepo) Update(ctx context.Context, task *model.Task) error { + return getTaskTx(ctx).Save(&task).Error +} + +func (t TaskRepo) UpdateRunningTaskToFailed() error { + return getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Updates(map[string]interface{}{"status": constant.StatusFailed, "error_msg": "1Panel restart causes failure"}).Error +} + +func (t TaskRepo) CountExecutingTask() (int64, error) { + var count int64 + err := getTaskDb(t.WithByStatus(constant.StatusExecuting)).Model(&model.Task{}).Count(&count).Error + return count, err +} + +func (t TaskRepo) Delete(opts ...DBOption) error { + db := global.TaskDB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Task{}).Error +} + +func (t TaskRepo) DeleteAll() error { + return global.TaskDB.Where("1 = 1").Delete(&model.Task{}).Error +} diff --git a/agent/app/repo/tensorrt_llm.go b/agent/app/repo/tensorrt_llm.go new file mode 100644 index 0000000..6df2b43 --- /dev/null +++ b/agent/app/repo/tensorrt_llm.go @@ -0,0 +1,56 @@ +package repo + +import "github.com/1Panel-dev/1Panel/agent/app/model" + +type TensorRTLLMRepo struct { +} + +type ITensorRTLLMRepo interface { + Page(page, size int, opts ...DBOption) (int64, []model.TensorRTLLM, error) + GetFirst(opts ...DBOption) (*model.TensorRTLLM, error) + Create(tensorrtLLM *model.TensorRTLLM) error + Save(tensorrtLLM *model.TensorRTLLM) error + DeleteBy(opts ...DBOption) error + List(opts ...DBOption) ([]model.TensorRTLLM, error) +} + +func NewITensorRTLLMRepo() ITensorRTLLMRepo { + return &TensorRTLLMRepo{} +} + +func (t TensorRTLLMRepo) Page(page, size int, opts ...DBOption) (int64, []model.TensorRTLLM, error) { + var servers []model.TensorRTLLM + db := getDb(opts...).Model(&model.TensorRTLLM{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&servers).Error + return count, servers, err +} + +func (t TensorRTLLMRepo) GetFirst(opts ...DBOption) (*model.TensorRTLLM, error) { + var tensorrtLLM model.TensorRTLLM + if err := getDb(opts...).First(&tensorrtLLM).Error; err != nil { + return nil, err + } + return &tensorrtLLM, nil +} + +func (t TensorRTLLMRepo) List(opts ...DBOption) ([]model.TensorRTLLM, error) { + var tensorrtLLMs []model.TensorRTLLM + if err := getDb(opts...).Find(&tensorrtLLMs).Error; err != nil { + return nil, err + } + return tensorrtLLMs, nil +} + +func (t TensorRTLLMRepo) Create(tensorrtLLM *model.TensorRTLLM) error { + return getDb().Create(tensorrtLLM).Error +} + +func (t TensorRTLLMRepo) Save(tensorrtLLM *model.TensorRTLLM) error { + return getDb().Save(tensorrtLLM).Error +} + +func (t TensorRTLLMRepo) DeleteBy(opts ...DBOption) error { + return getDb(opts...).Delete(&model.TensorRTLLM{}).Error +} diff --git a/agent/app/repo/website.go b/agent/app/repo/website.go new file mode 100644 index 0000000..3f49817 --- /dev/null +++ b/agent/app/repo/website.go @@ -0,0 +1,172 @@ +package repo + +import ( + "context" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type IWebsiteRepo interface { + WithAppInstallId(appInstallId uint) DBOption + WithDomain(domain string) DBOption + WithAlias(alias string) DBOption + WithWebsiteSSLID(sslId uint) DBOption + WithGroupID(groupId uint) DBOption + WithDefaultServer() DBOption + WithDomainLike(domain string) DBOption + WithRuntimeID(runtimeID uint) DBOption + WithParentID(websiteID uint) DBOption + WithType(websiteType string) DBOption + WithDBType(dbType string) DBOption + WithDBID(dbID uint) DBOption + + Page(page, size int, opts ...DBOption) (int64, []model.Website, error) + List(opts ...DBOption) ([]model.Website, error) + GetFirst(opts ...DBOption) (model.Website, error) + GetBy(opts ...DBOption) ([]model.Website, error) + Save(ctx context.Context, app *model.Website) error + SaveWithoutCtx(app *model.Website) error + DeleteBy(ctx context.Context, opts ...DBOption) error + Create(ctx context.Context, app *model.Website) error + DeleteAll(ctx context.Context) error + + UpdateGroup(group, newGroup uint) error +} + +func NewIWebsiteRepo() IWebsiteRepo { + return &WebsiteRepo{} +} + +type WebsiteRepo struct { +} + +func (w *WebsiteRepo) WithAppInstallId(appInstallID uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("app_install_id = ?", appInstallID) + } +} + +func (w *WebsiteRepo) WithRuntimeID(runtimeID uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("runtime_id = ?", runtimeID) + } +} + +func (w *WebsiteRepo) WithDomain(domain string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("primary_domain = ?", domain) + } +} + +func (w *WebsiteRepo) WithDomainLike(domain string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("primary_domain like ?", "%"+domain+"%") + } +} + +func (w *WebsiteRepo) WithAlias(alias string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("alias = ?", alias) + } +} + +func (w *WebsiteRepo) WithWebsiteSSLID(sslId uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("website_ssl_id = ?", sslId) + } +} + +func (w *WebsiteRepo) WithParentID(websiteID uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("parent_website_id = ?", websiteID) + } +} + +func (w *WebsiteRepo) WithGroupID(groupId uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("website_group_id = ?", groupId) + } +} + +func (w *WebsiteRepo) WithDefaultServer() DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("default_server = 1") + } +} + +func (w *WebsiteRepo) WithType(websiteType string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("type = ?", websiteType) + } +} + +func (w *WebsiteRepo) WithDBType(dbType string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("db_type = ?", dbType) + } +} + +func (w *WebsiteRepo) WithDBID(dbID uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("db_id = ?", dbID) + } +} + +func (w *WebsiteRepo) Page(page, size int, opts ...DBOption) (int64, []model.Website, error) { + var websites []model.Website + db := getDb(opts...).Model(&model.Website{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Preload("WebsiteSSL").Find(&websites).Error + return count, websites, err +} + +func (w *WebsiteRepo) List(opts ...DBOption) ([]model.Website, error) { + var websites []model.Website + err := getDb(opts...).Model(&model.Website{}).Preload("Domains").Preload("WebsiteSSL").Find(&websites).Error + return websites, err +} + +func (w *WebsiteRepo) GetFirst(opts ...DBOption) (model.Website, error) { + var website model.Website + db := getDb(opts...).Model(&model.Website{}) + if err := db.Preload("Domains").First(&website).Error; err != nil { + return website, err + } + return website, nil +} + +func (w *WebsiteRepo) GetBy(opts ...DBOption) ([]model.Website, error) { + var websites []model.Website + db := getDb(opts...).Model(&model.Website{}) + if err := db.Find(&websites).Error; err != nil { + return websites, err + } + return websites, nil +} + +func (w *WebsiteRepo) Create(ctx context.Context, app *model.Website) error { + return getTx(ctx).Omit(clause.Associations).Create(app).Error +} + +func (w *WebsiteRepo) Save(ctx context.Context, app *model.Website) error { + return getTx(ctx).Omit(clause.Associations).Save(app).Error +} + +func (w *WebsiteRepo) SaveWithoutCtx(website *model.Website) error { + return global.DB.Save(website).Error +} + +func (w *WebsiteRepo) DeleteBy(ctx context.Context, opts ...DBOption) error { + return getTx(ctx, opts...).Delete(&model.Website{}).Error +} + +func (w *WebsiteRepo) DeleteAll(ctx context.Context) error { + return getTx(ctx).Where("1 = 1 ").Delete(&model.Website{}).Error +} + +func (w *WebsiteRepo) UpdateGroup(group, newGroup uint) error { + return global.DB.Model(&model.Website{}).Where("website_group_id = ?", group).Updates(map[string]interface{}{"website_group_id": newGroup}).Error +} diff --git a/agent/app/repo/website_acme_account.go b/agent/app/repo/website_acme_account.go new file mode 100644 index 0000000..34cdec1 --- /dev/null +++ b/agent/app/repo/website_acme_account.go @@ -0,0 +1,64 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" + "gorm.io/gorm" +) + +type IAcmeAccountRepo interface { + Page(page, size int, opts ...DBOption) (int64, []model.WebsiteAcmeAccount, error) + GetFirst(opts ...DBOption) (*model.WebsiteAcmeAccount, error) + Create(account model.WebsiteAcmeAccount) error + Save(account model.WebsiteAcmeAccount) error + DeleteBy(opts ...DBOption) error + WithEmail(email string) DBOption + WithType(acType string) DBOption +} + +func NewIAcmeAccountRepo() IAcmeAccountRepo { + return &WebsiteAcmeAccountRepo{} +} + +type WebsiteAcmeAccountRepo struct { +} + +func (w *WebsiteAcmeAccountRepo) WithEmail(email string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("email = ?", email) + } +} +func (w *WebsiteAcmeAccountRepo) WithType(acType string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("type = ?", acType) + } +} + +func (w *WebsiteAcmeAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteAcmeAccount, error) { + var accounts []model.WebsiteAcmeAccount + db := getDb(opts...).Model(&model.WebsiteAcmeAccount{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&accounts).Error + return count, accounts, err +} + +func (w *WebsiteAcmeAccountRepo) GetFirst(opts ...DBOption) (*model.WebsiteAcmeAccount, error) { + var account model.WebsiteAcmeAccount + db := getDb(opts...).Model(&model.WebsiteAcmeAccount{}) + if err := db.First(&account).Error; err != nil { + return nil, err + } + return &account, nil +} + +func (w *WebsiteAcmeAccountRepo) Create(account model.WebsiteAcmeAccount) error { + return getDb().Create(&account).Error +} + +func (w *WebsiteAcmeAccountRepo) Save(account model.WebsiteAcmeAccount) error { + return getDb().Save(&account).Error +} + +func (w *WebsiteAcmeAccountRepo) DeleteBy(opts ...DBOption) error { + return getDb(opts...).Delete(&model.WebsiteAcmeAccount{}).Error +} diff --git a/agent/app/repo/website_ca.go b/agent/app/repo/website_ca.go new file mode 100644 index 0000000..dd8a8f3 --- /dev/null +++ b/agent/app/repo/website_ca.go @@ -0,0 +1,55 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type WebsiteCARepo struct { +} + +func NewIWebsiteCARepo() IWebsiteCARepo { + return &WebsiteCARepo{} +} + +type IWebsiteCARepo interface { + Page(page, size int, opts ...DBOption) (int64, []model.WebsiteCA, error) + GetFirst(opts ...DBOption) (model.WebsiteCA, error) + List(opts ...DBOption) ([]model.WebsiteCA, error) + Create(ctx context.Context, ca *model.WebsiteCA) error + DeleteBy(opts ...DBOption) error +} + +func (w WebsiteCARepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteCA, error) { + var caList []model.WebsiteCA + db := getDb(opts...).Model(&model.WebsiteCA{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&caList).Error + return count, caList, err +} + +func (w WebsiteCARepo) GetFirst(opts ...DBOption) (model.WebsiteCA, error) { + var ca model.WebsiteCA + db := getDb(opts...).Model(&model.WebsiteCA{}) + if err := db.First(&ca).Error; err != nil { + return ca, err + } + return ca, nil +} + +func (w WebsiteCARepo) List(opts ...DBOption) ([]model.WebsiteCA, error) { + var caList []model.WebsiteCA + db := getDb(opts...).Model(&model.WebsiteCA{}) + err := db.Find(&caList).Error + return caList, err +} + +func (w WebsiteCARepo) Create(ctx context.Context, ca *model.WebsiteCA) error { + return getTx(ctx).Create(ca).Error +} + +func (w WebsiteCARepo) DeleteBy(opts ...DBOption) error { + return getDb(opts...).Delete(&model.WebsiteCA{}).Error +} diff --git a/agent/app/repo/website_dns_account.go b/agent/app/repo/website_dns_account.go new file mode 100644 index 0000000..b945903 --- /dev/null +++ b/agent/app/repo/website_dns_account.go @@ -0,0 +1,60 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/agent/app/model" +) + +type WebsiteDnsAccountRepo struct { +} + +type IWebsiteDnsAccountRepo interface { + Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDnsAccount, error) + GetFirst(opts ...DBOption) (*model.WebsiteDnsAccount, error) + List(opts ...DBOption) ([]model.WebsiteDnsAccount, error) + Create(account model.WebsiteDnsAccount) error + Save(account model.WebsiteDnsAccount) error + DeleteBy(opts ...DBOption) error +} + +func NewIWebsiteDnsAccountRepo() IWebsiteDnsAccountRepo { + return &WebsiteDnsAccountRepo{} +} + +func (w WebsiteDnsAccountRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDnsAccount, error) { + var accounts []model.WebsiteDnsAccount + db := getDb(opts...).Model(&model.WebsiteDnsAccount{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&accounts).Error + return count, accounts, err +} + +func (w WebsiteDnsAccountRepo) GetFirst(opts ...DBOption) (*model.WebsiteDnsAccount, error) { + var account model.WebsiteDnsAccount + db := getDb(opts...).Model(&model.WebsiteDnsAccount{}) + if err := db.First(&account).Error; err != nil { + return nil, err + } + return &account, nil +} + +func (w WebsiteDnsAccountRepo) List(opts ...DBOption) ([]model.WebsiteDnsAccount, error) { + var accounts []model.WebsiteDnsAccount + db := getDb(opts...).Model(&model.WebsiteDnsAccount{}) + if err := db.Find(&accounts).Error; err != nil { + return nil, err + } + return accounts, nil +} + +func (w WebsiteDnsAccountRepo) Create(account model.WebsiteDnsAccount) error { + return getDb().Create(&account).Error +} + +func (w WebsiteDnsAccountRepo) Save(account model.WebsiteDnsAccount) error { + return getDb().Save(&account).Error +} + +func (w WebsiteDnsAccountRepo) DeleteBy(opts ...DBOption) error { + return getDb(opts...).Delete(&model.WebsiteDnsAccount{}).Error +} diff --git a/agent/app/repo/website_domain.go b/agent/app/repo/website_domain.go new file mode 100644 index 0000000..40258aa --- /dev/null +++ b/agent/app/repo/website_domain.go @@ -0,0 +1,99 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "gorm.io/gorm" + "gorm.io/gorm/clause" +) + +type WebsiteDomainRepo struct { +} + +type IWebsiteDomainRepo interface { + WithWebsiteId(websiteId uint) DBOption + WithPort(port int) DBOption + WithDomain(domain string) DBOption + WithDomainLike(domain string) DBOption + Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error) + GetFirst(opts ...DBOption) (model.WebsiteDomain, error) + GetBy(opts ...DBOption) ([]model.WebsiteDomain, error) + BatchCreate(ctx context.Context, domains []model.WebsiteDomain) error + Create(ctx context.Context, app *model.WebsiteDomain) error + Save(ctx context.Context, app *model.WebsiteDomain) error + DeleteBy(ctx context.Context, opts ...DBOption) error + DeleteAll(ctx context.Context) error +} + +func NewIWebsiteDomainRepo() IWebsiteDomainRepo { + return &WebsiteDomainRepo{} +} + +func (w WebsiteDomainRepo) WithWebsiteId(websiteId uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("website_id = ?", websiteId) + } +} + +func (w WebsiteDomainRepo) WithPort(port int) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("port = ?", port) + } +} +func (w WebsiteDomainRepo) WithDomain(domain string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("domain = ?", domain) + } +} +func (w WebsiteDomainRepo) WithDomainLike(domain string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("domain like ?", "%"+domain+"%") + } +} +func (w WebsiteDomainRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteDomain, error) { + var domains []model.WebsiteDomain + db := getDb(opts...).Model(&model.WebsiteDomain{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&domains).Error + return count, domains, err +} + +func (w WebsiteDomainRepo) GetFirst(opts ...DBOption) (model.WebsiteDomain, error) { + var domain model.WebsiteDomain + db := getDb(opts...).Model(&model.WebsiteDomain{}) + if err := db.First(&domain).Error; err != nil { + return domain, err + } + return domain, nil +} + +func (w WebsiteDomainRepo) GetBy(opts ...DBOption) ([]model.WebsiteDomain, error) { + var domains []model.WebsiteDomain + db := getDb(opts...).Model(&model.WebsiteDomain{}) + if err := db.Find(&domains).Error; err != nil { + return domains, err + } + return domains, nil +} + +func (w WebsiteDomainRepo) BatchCreate(ctx context.Context, domains []model.WebsiteDomain) error { + return getTx(ctx).Model(&model.WebsiteDomain{}).Create(&domains).Error +} + +func (w WebsiteDomainRepo) Create(ctx context.Context, app *model.WebsiteDomain) error { + return getTx(ctx).Omit(clause.Associations).Create(app).Error +} + +func (w WebsiteDomainRepo) Save(ctx context.Context, app *model.WebsiteDomain) error { + return getTx(ctx).Omit(clause.Associations).Save(app).Error +} + +func (w WebsiteDomainRepo) DeleteBy(ctx context.Context, opts ...DBOption) error { + return getTx(ctx, opts...).Delete(&model.WebsiteDomain{}).Error +} + +func (w WebsiteDomainRepo) DeleteAll(ctx context.Context) error { + return getTx(ctx).Where("1 = 1 ").Delete(&model.WebsiteDomain{}).Error +} diff --git a/agent/app/repo/website_ssl.go b/agent/app/repo/website_ssl.go new file mode 100644 index 0000000..63fe27a --- /dev/null +++ b/agent/app/repo/website_ssl.go @@ -0,0 +1,114 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "gorm.io/gorm" +) + +func NewISSLRepo() ISSLRepo { + return &WebsiteSSLRepo{} +} + +type ISSLRepo interface { + WithByAlias(alias string) DBOption + WithByAcmeAccountId(acmeAccountId uint) DBOption + WithByDnsAccountId(dnsAccountId uint) DBOption + WithByCAID(caID uint) DBOption + WithByDomain(domain string) DBOption + WithByMasterSSLID(sslID uint) DBOption + Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) + GetFirst(opts ...DBOption) (*model.WebsiteSSL, error) + List(opts ...DBOption) ([]model.WebsiteSSL, error) + Create(ctx context.Context, ssl *model.WebsiteSSL) error + Save(ssl *model.WebsiteSSL) error + DeleteBy(opts ...DBOption) error + SaveByMap(ssl *model.WebsiteSSL, params map[string]interface{}) error +} + +type WebsiteSSLRepo struct { +} + +func (w WebsiteSSLRepo) WithByAlias(alias string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("alias = ?", alias) + } +} + +func (w WebsiteSSLRepo) WithByAcmeAccountId(acmeAccountId uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("acme_account_id = ?", acmeAccountId) + } +} + +func (w WebsiteSSLRepo) WithByDnsAccountId(dnsAccountId uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("dns_account_id = ?", dnsAccountId) + } +} + +func (w WebsiteSSLRepo) WithByCAID(caID uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("ca_id = ?", caID) + } +} + +func (w WebsiteSSLRepo) WithByDomain(domain string) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("primary_domain Like ? or domains Like ?", "%"+domain+"%", "%"+domain+"%") + } +} + +func (w WebsiteSSLRepo) WithByMasterSSLID(sslID uint) DBOption { + return func(db *gorm.DB) *gorm.DB { + return db.Where("master_ssl_id = ?", sslID) + } +} + +func (w WebsiteSSLRepo) Page(page, size int, opts ...DBOption) (int64, []model.WebsiteSSL, error) { + var sslList []model.WebsiteSSL + db := getDb(opts...).Model(&model.WebsiteSSL{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Preload("AcmeAccount").Preload("DnsAccount").Preload("Websites").Find(&sslList).Error + return count, sslList, err +} + +func (w WebsiteSSLRepo) GetFirst(opts ...DBOption) (*model.WebsiteSSL, error) { + var website *model.WebsiteSSL + db := getDb(opts...).Model(&model.WebsiteSSL{}) + if err := db.Preload("AcmeAccount").Preload("DnsAccount").First(&website).Error; err != nil { + return website, err + } + return website, nil +} + +func (w WebsiteSSLRepo) List(opts ...DBOption) ([]model.WebsiteSSL, error) { + var websites []model.WebsiteSSL + db := getDb(opts...).Model(&model.WebsiteSSL{}) + if err := db.Preload("AcmeAccount").Preload("DnsAccount").Find(&websites).Error; err != nil { + return websites, err + } + return websites, nil +} + +func (w WebsiteSSLRepo) Create(ctx context.Context, ssl *model.WebsiteSSL) error { + return getTx(ctx).Create(ssl).Error +} + +func (w WebsiteSSLRepo) Save(ssl *model.WebsiteSSL) error { + return getDb().Model(&model.WebsiteSSL{BaseModel: model.BaseModel{ + ID: ssl.ID, + }}).Save(&ssl).Error +} + +func (w WebsiteSSLRepo) SaveByMap(ssl *model.WebsiteSSL, params map[string]interface{}) error { + return getDb().Model(&model.WebsiteSSL{BaseModel: model.BaseModel{ + ID: ssl.ID, + }}).Updates(params).Error +} + +func (w WebsiteSSLRepo) DeleteBy(opts ...DBOption) error { + return getDb(opts...).Delete(&model.WebsiteSSL{}).Error +} diff --git a/agent/app/service/ai.go b/agent/app/service/ai.go new file mode 100644 index 0000000..07d7912 --- /dev/null +++ b/agent/app/service/ai.go @@ -0,0 +1,396 @@ +package service + +import ( + "context" + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/jinzhu/copier" +) + +type AIToolService struct{} + +type IAIToolService interface { + Search(search dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error) + Create(req dto.OllamaModelName) error + Close(name string) error + Recreate(req dto.OllamaModelName) error + Delete(req dto.ForceDelete) error + Sync() ([]dto.OllamaModelDropList, error) + LoadDetail(name string) (string, error) + BindDomain(req dto.OllamaBindDomain) error + GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error) + UpdateBindDomain(req dto.OllamaBindDomain) error +} + +func NewIAIToolService() IAIToolService { + return &AIToolService{} +} + +func (u *AIToolService) Search(req dto.SearchWithPage) (int64, []dto.OllamaModelInfo, error) { + var options []repo.DBOption + if len(req.Info) != 0 { + options = append(options, repo.WithByLikeName(req.Info)) + } + total, list, err := aiRepo.Page(req.Page, req.PageSize, options...) + if err != nil { + return 0, nil, err + } + var dtoLists []dto.OllamaModelInfo + for _, itemModel := range list { + var item dto.OllamaModelInfo + if err := copier.Copy(&item, &itemModel); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + taskModel, _ := taskRepo.GetFirst(taskRepo.WithResourceID(item.ID), repo.WithByType(task.TaskScopeAI)) + if len(taskModel.ID) != 0 { + item.LogFileExist = true + } + dtoLists = append(dtoLists, item) + } + return total, dtoLists, err +} + +func (u *AIToolService) LoadDetail(name string) (string, error) { + if cmd.CheckIllegal(name) { + return "", buserr.New("ErrCmdIllegal") + } + containerName, err := LoadContainerName() + if err != nil { + return "", err + } + stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s ollama show %s", containerName, name) + if err != nil { + return "", err + } + return stdout, err +} + +func (u *AIToolService) Create(req dto.OllamaModelName) error { + if cmd.CheckIllegal(req.Name) { + return buserr.New("ErrCmdIllegal") + } + modelInfo, _ := aiRepo.Get(repo.WithByName(req.Name)) + if modelInfo.ID != 0 { + return buserr.New("ErrRecordExist") + } + containerName, err := LoadContainerName() + if err != nil { + return err + } + info := model.OllamaModel{ + Name: req.Name, + From: "local", + Status: constant.StatusWaiting, + } + if err := aiRepo.Create(&info); err != nil { + return err + } + taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, info.ID) + if err != nil { + global.LOG.Errorf("new task for exec shell failed, err: %v", err) + return err + } + go func() { + taskItem.AddSubTask(i18n.GetWithName("OllamaModelPull", req.Name), func(t *task.Task) error { + cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem), cmd.WithTimeout(time.Hour)) + return cmdMgr.Run("docker", "exec", containerName, "ollama", "pull", info.Name) + }, nil) + taskItem.AddSubTask(i18n.GetWithName("OllamaModelSize", req.Name), func(t *task.Task) error { + itemSize, err := loadModelSize(info.Name, containerName) + if len(itemSize) != 0 { + _ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize}) + } else { + _ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + } + return nil + }, nil) + if err := taskItem.Execute(); err != nil { + _ = aiRepo.Update(info.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + } + }() + return nil +} + +func (u *AIToolService) Close(name string) error { + if cmd.CheckIllegal(name) { + return buserr.New("ErrCmdIllegal") + } + containerName, err := LoadContainerName() + if err != nil { + return err + } + if err := cmd.RunDefaultBashCf("docker exec %s ollama stop %s", containerName, name); err != nil { + return fmt.Errorf("handle ollama stop %s failed, %v", name, err) + } + return nil +} + +func (u *AIToolService) Recreate(req dto.OllamaModelName) error { + if cmd.CheckIllegal(req.Name) { + return buserr.New("ErrCmdIllegal") + } + modelInfo, _ := aiRepo.Get(repo.WithByName(req.Name)) + if modelInfo.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + containerName, err := LoadContainerName() + if err != nil { + return err + } + if err := aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusWaiting, "from": "local"}); err != nil { + return err + } + taskItem, err := task.NewTaskWithOps(fmt.Sprintf("ollama-model-%s", req.Name), task.TaskPull, task.TaskScopeAI, req.TaskID, modelInfo.ID) + if err != nil { + global.LOG.Errorf("new task for exec shell failed, err: %v", err) + return err + } + go func() { + taskItem.AddSubTask(i18n.GetWithName("OllamaModelPull", req.Name), func(t *task.Task) error { + cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem), cmd.WithTimeout(time.Hour)) + return cmdMgr.Run("docker", "exec", containerName, "ollama", "pull", req.Name) + }, nil) + taskItem.AddSubTask(i18n.GetWithName("OllamaModelSize", req.Name), func(t *task.Task) error { + itemSize, err := loadModelSize(modelInfo.Name, containerName) + if len(itemSize) != 0 { + _ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusSuccess, "size": itemSize}) + } else { + _ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + } + return nil + }, nil) + if err := taskItem.Execute(); err != nil { + _ = aiRepo.Update(modelInfo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + } + }() + return nil +} + +func (u *AIToolService) Delete(req dto.ForceDelete) error { + ollamaList, _ := aiRepo.List(repo.WithByIDs(req.IDs)) + if len(ollamaList) == 0 { + return buserr.New("ErrRecordNotFound") + } + containerName, err := LoadContainerName() + if err != nil && !req.ForceDelete { + return err + } + for _, item := range ollamaList { + if item.Status != constant.StatusDeleted { + if err := cmd.RunDefaultBashCf("docker exec %s ollama rm %s", containerName, item.Name); err != nil && !req.ForceDelete { + return fmt.Errorf("handle ollama rm %s failed, %v", item.Name, err) + } + } + _ = aiRepo.Delete(repo.WithByID(item.ID)) + logItem := path.Join(global.Dir.DataDir, "log", "AITools", item.Name) + _ = os.Remove(logItem) + } + return nil +} + +func (u *AIToolService) Sync() ([]dto.OllamaModelDropList, error) { + containerName, err := LoadContainerName() + if err != nil { + return nil, err + } + stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s ollama list", containerName) + if err != nil { + return nil, err + } + var list []model.OllamaModel + lines := strings.Split(stdout, "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) < 5 { + continue + } + if parts[0] == "NAME" { + continue + } + list = append(list, model.OllamaModel{Name: parts[0], Size: parts[2] + " " + parts[3]}) + } + listInDB, _ := aiRepo.List() + var dropList []dto.OllamaModelDropList + for _, itemModel := range listInDB { + isExit := false + for i := 0; i < len(list); i++ { + if list[i].Name == itemModel.Name { + _ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusSuccess, "message": "", "size": list[i].Size}) + list = append(list[:i], list[(i+1):]...) + isExit = true + break + } + } + if !isExit && itemModel.Status != constant.StatusWaiting { + _ = aiRepo.Update(itemModel.ID, map[string]interface{}{"status": constant.StatusDeleted, "message": "not exist", "size": ""}) + dropList = append(dropList, dto.OllamaModelDropList{ID: itemModel.ID, Name: itemModel.Name}) + continue + } + } + for _, item := range list { + item.Status = constant.StatusSuccess + item.From = "remote" + _ = aiRepo.Create(&item) + } + + return dropList, nil +} + +func (u *AIToolService) BindDomain(req dto.OllamaBindDomain) error { + nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) + if nginxInstall.ID == 0 { + return buserr.New("ErrOpenrestyInstall") + } + var ( + ipList []string + err error + ) + if len(req.IPList) > 0 { + ipList, err = common.HandleIPList(req.IPList) + if err != nil { + return err + } + } + createWebsiteReq := request.WebsiteCreate{ + Domains: []request.WebsiteDomain{{Domain: req.Domain, Port: 80}}, + Alias: strings.ToLower(req.Domain), + Type: constant.Deployment, + AppType: constant.InstalledApp, + AppInstallID: req.AppInstallID, + } + if req.SSLID > 0 { + createWebsiteReq.WebsiteSSLID = req.SSLID + createWebsiteReq.EnableSSL = true + } + res, _ := NewIGroupService().GetDefault() + createWebsiteReq.WebsiteGroupID = res.ID + websiteService := NewIWebsiteService() + if err = websiteService.CreateWebsite(createWebsiteReq); err != nil { + return err + } + website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain))) + if err != nil { + return err + } + if len(ipList) > 0 { + if err = ConfigAllowIPs(ipList, website); err != nil { + return err + } + } + if err = ConfigAIProxy(website); err != nil { + return err + } + return nil +} + +func (u *AIToolService) GetBindDomain(req dto.OllamaBindDomainReq) (*dto.OllamaBindDomainRes, error) { + install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID)) + if err != nil { + return nil, err + } + res := &dto.OllamaBindDomainRes{} + website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(install.ID)) + if website.ID == 0 { + return res, nil + } + res.WebsiteID = website.ID + res.Domain = website.PrimaryDomain + if website.WebsiteSSLID > 0 { + res.SSLID = website.WebsiteSSLID + ssl, _ := websiteSSLRepo.GetFirst(repo.WithByID(website.WebsiteSSLID)) + res.AcmeAccountID = ssl.AcmeAccountID + } + res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain) + res.AllowIPs = GetAllowIps(website) + return res, nil +} + +func (u *AIToolService) UpdateBindDomain(req dto.OllamaBindDomain) error { + nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) + if nginxInstall.ID == 0 { + return buserr.New("ErrOpenrestyInstall") + } + var ( + ipList []string + err error + ) + if len(req.IPList) > 0 { + ipList, err = common.HandleIPList(req.IPList) + if err != nil { + return err + } + } + websiteService := NewIWebsiteService() + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + if err = ConfigAllowIPs(ipList, website); err != nil { + return err + } + if req.SSLID > 0 { + sslReq := request.WebsiteHTTPSOp{ + WebsiteID: website.ID, + Enable: true, + Type: constant.SSLExisted, + WebsiteSSLID: req.SSLID, + HttpConfig: constant.HTTPToHTTPS, + } + if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil { + return err + } + return nil + } + if website.WebsiteSSLID > 0 && req.SSLID == 0 { + sslReq := request.WebsiteHTTPSOp{ + WebsiteID: website.ID, + Enable: false, + } + if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil { + return err + } + } + return nil +} + +func LoadContainerName() (string, error) { + ollamaBaseInfo, err := appInstallRepo.LoadBaseInfo("ollama", "") + if err != nil { + return "", fmt.Errorf("ollama service is not found, err: %v", err) + } + if ollamaBaseInfo.Status != constant.StatusRunning { + return "", fmt.Errorf("container %s of ollama is not running, please check and retry!", ollamaBaseInfo.ContainerName) + } + return ollamaBaseInfo.ContainerName, nil +} + +func loadModelSize(name string, containerName string) (string, error) { + stdout, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s ollama list | grep %s", containerName, name) + if err != nil { + return "", err + } + lines := strings.Split(stdout, "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) < 5 { + continue + } + return parts[2] + " " + parts[3], nil + } + return "", fmt.Errorf("no such model %s in ollama list, std: %s", name, stdout) +} diff --git a/agent/app/service/alert.go b/agent/app/service/alert.go new file mode 100644 index 0000000..4c01f34 --- /dev/null +++ b/agent/app/service/alert.go @@ -0,0 +1,560 @@ +package service + +import ( + "encoding/json" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/copier" + "github.com/1Panel-dev/1Panel/agent/utils/email" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "github.com/shirou/gopsutil/v4/disk" + "sort" + "strings" + "sync" + "time" +) + +type AlertService struct{} + +type IAlertService interface { + PageAlert(req dto.AlertSearch) (int64, []dto.AlertDTO, error) + GetAlerts() ([]dto.AlertDTO, error) + CreateAlert(create dto.AlertCreate) error + UpdateAlert(req dto.AlertUpdate) error + DeleteAlert(id uint) error + GetAlert(id uint) (dto.AlertDTO, error) + UpdateStatus(id uint, status string) error + ExternalUpdateAlert(req dto.AlertCreate) error + + GetDisks() ([]dto.DiskDTO, error) + PageAlertLogs(req dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error) + CleanAlertLogs() error + GetClams() ([]dto.ClamDTO, error) + GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error) + + GetAlertConfig() ([]model.AlertConfig, error) + UpdateAlertConfig(req dto.AlertConfigUpdate) error + DeleteAlertConfig(id uint) error + TestAlertConfig(req dto.AlertConfigTest) (bool, error) +} + +func NewIAlertService() IAlertService { + return &AlertService{} +} + +func (a AlertService) PageAlert(search dto.AlertSearch) (int64, []dto.AlertDTO, error) { + var ( + opts []repo.DBOption + result []dto.AlertDTO + ) + if search.Status != "" { + opts = append(opts, repo.WithByStatus(search.Status)) + } + if search.Type != "" { + opts = append(opts, alertRepo.WithByType(search.Type)) + } + opts = append(opts, repo.WithOrderBy("created_at desc")) + + total, alerts, err := alertRepo.Page(search.Page, search.PageSize, opts...) + if err != nil { + return 0, nil, err + } + + for _, item := range alerts { + + result = append(result, dto.AlertDTO{ + ID: item.ID, + Type: item.Type, + Cycle: item.Cycle, + Count: item.Count, + Method: item.Method, + Title: item.Title, + Project: item.Project, + Status: item.Status, + SendCount: item.SendCount, + AdvancedParams: item.AdvancedParams, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + }) + } + + return total, result, err +} + +func (a AlertService) GetAlerts() ([]dto.AlertDTO, error) { + var ( + opts []repo.DBOption + result []dto.AlertDTO + ) + opts = append(opts, repo.WithByStatus(constant.AlertEnable)) + alerts, err := alertRepo.List(opts...) + if err != nil { + return nil, err + } + for _, item := range alerts { + + result = append(result, dto.AlertDTO{ + ID: item.ID, + Type: item.Type, + Cycle: item.Cycle, + Count: item.Count, + Method: item.Method, + Title: item.Title, + Project: item.Project, + Status: item.Status, + SendCount: item.SendCount, + AdvancedParams: item.AdvancedParams, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + }) + } + + return result, err +} + +func (a AlertService) CreateAlert(create dto.AlertCreate) error { + var alertID uint + var alertInfo model.Alert + if create.Project != "" { + alertInfo, _ := alertRepo.Get(alertRepo.WithByType(create.Type), alertRepo.WithByProject(create.Project)) + alertID = alertInfo.ID + } else { + alertInfo, _ := alertRepo.Get(alertRepo.WithByType(create.Type)) + alertID = alertInfo.ID + } + + if alertID != 0 { + var upAlert dto.AlertUpdate + if err := copier.Copy(&upAlert, &create); err != nil { + return buserr.WithErr("ErrStructTransform", err) + } + upAlert.ID = alertID + err := a.UpdateAlert(upAlert) + if err != nil { + return err + } + } else { + alertInfo.Status = constant.AlertEnable + if err := copier.Copy(&alertInfo, &create); err != nil { + return buserr.WithErr("ErrStructTransform", err) + } + + if err := alertRepo.Create(&alertInfo); err != nil { + return err + } + NewIAlertTaskHelper().InitTask(alertInfo.Type) + } + + return nil +} + +func (a AlertService) UpdateAlert(req dto.AlertUpdate) error { + + upMap := make(map[string]interface{}) + upMap["id"] = req.ID + upMap["type"] = req.Type + upMap["cycle"] = req.Cycle + upMap["count"] = req.Count + upMap["method"] = req.Method + upMap["title"] = req.Title + upMap["project"] = req.Project + upMap["status"] = req.Status + upMap["send_count"] = req.SendCount + upMap["advanced_params"] = req.AdvancedParams + + if err := alertRepo.Update(upMap, repo.WithByID(req.ID)); err != nil { + return err + } + NewIAlertTaskHelper().InitTask(req.Type) + return nil +} + +func (a AlertService) DeleteAlert(id uint) error { + alertInfo, _ := alertRepo.Get(repo.WithByID(id)) + if alertInfo.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + err := alertRepo.Delete(repo.WithByID(id)) + if err != nil { + return err + } + alerts, err := a.GetAlerts() + if err != nil { + return err + } + if len(alerts) > 0 { + NewIAlertTaskHelper().InitTask(alertInfo.Type) + } else { + NewIAlertTaskHelper().StopTask() + } + return nil +} + +func (a AlertService) GetAlert(id uint) (dto.AlertDTO, error) { + var res dto.AlertDTO + alertInfo, err := alertRepo.Get(repo.WithByID(id)) + if err != nil { + return res, err + } + _ = copier.Copy(&res, &alertInfo) + return res, nil +} + +func (a AlertService) UpdateStatus(id uint, status string) error { + alertInfo, _ := alertRepo.Get(repo.WithByID(id)) + if alertInfo.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + err := alertRepo.Update(map[string]interface{}{"status": status}, repo.WithByID(alertInfo.ID)) + if err != nil { + return err + } + alerts, err := a.GetAlerts() + if err != nil { + return err + } + if len(alerts) > 0 { + NewIAlertTaskHelper().InitTask(alertInfo.Type) + } else { + NewIAlertTaskHelper().StopTask() + } + return nil +} + +func (a AlertService) GetDisks() ([]dto.DiskDTO, error) { + var disks []dto.DiskDTO + excludes := map[string]struct{}{ + "/mnt/cdrom": {}, "/boot": {}, "/boot/efi": {}, "/dev": {}, "/dev/shm": {}, + "/run/lock": {}, "/run": {}, "/run/shm": {}, "/run/user": {}, + } + stdout, err := executeDiskCommand() + if err != nil { + return disks, nil + } + + lines := strings.Split(stdout, "\n") + var mounts []dto.AlertDiskInfo + + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) < 7 { + continue + } + mountPoint := strings.Join(fields[6:], " ") + if shouldExclude(fields, mountPoint, excludes) { + continue + } + mounts = append(mounts, dto.AlertDiskInfo{Type: fields[1], Device: fields[0], Mount: mountPoint}) + + } + + var ( + wg sync.WaitGroup + mu sync.Mutex + ) + wg.Add(len(mounts)) + for i := 0; i < len(mounts); i++ { + go func(timeoutCh <-chan time.Time, mount dto.AlertDiskInfo) { + defer wg.Done() + + var itemData dto.DiskDTO + itemData.Path = mount.Mount + itemData.Type = mount.Type + itemData.Device = mount.Device + select { + case <-timeoutCh: + mu.Lock() + disks = append(disks, itemData) + mu.Unlock() + global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount) + default: + state, err := disk.Usage(mount.Mount) + if err != nil { + mu.Lock() + disks = append(disks, itemData) + mu.Unlock() + global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, err) + return + } + itemData.Total = state.Total + itemData.Free = state.Free + itemData.Used = state.Used + itemData.UsedPercent = state.UsedPercent + itemData.InodesTotal = state.InodesTotal + itemData.InodesUsed = state.InodesUsed + itemData.InodesFree = state.InodesFree + itemData.InodesUsedPercent = state.InodesUsedPercent + mu.Lock() + disks = append(disks, itemData) + mu.Unlock() + } + }(time.After(5*time.Second), mounts[i]) + } + wg.Wait() + + sort.Slice(disks, func(i, j int) bool { + return disks[i].Path < disks[j].Path + }) + return disks, nil +} + +func executeDiskCommand() (string, error) { + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second)) + stdout, err := cmdMgr.RunWithStdoutBashC("df -hT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev") + if err != nil { + cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second)) + stdout, err = cmdMgr2.RunWithStdoutBashC("df -lhT -P | grep '/' | grep -v tmpfs | grep -v 'snap/core' | grep -v udev") + } + return stdout, err +} + +func shouldExclude(fields []string, mountPoint string, excludes map[string]struct{}) bool { + if strings.HasPrefix(mountPoint, "/snap") || len(strings.Split(mountPoint, "/")) > 10 { + return true + } + if strings.TrimSpace(fields[1]) == "tmpfs" { + return true + } + if strings.Contains(fields[2], "K") { + return true + } + if strings.Contains(mountPoint, "docker") { + return true + } + _, excluded := excludes[mountPoint] + return excluded +} + +func (a AlertService) PageAlertLogs(search dto.AlertLogSearch) (int64, []dto.AlertLogDTO, error) { + var ( + opts []repo.DBOption + result []dto.AlertLogDTO + ) + if search.Status != "" { + opts = append(opts, repo.WithByStatus(search.Status)) + } + if search.Count != 0 { + opts = append(opts, alertRepo.WithByCount(search.Count)) + } + opts = append(opts, repo.WithOrderBy("created_at desc")) + + total, alerts, err := alertRepo.PageLog(search.Page, search.PageSize, opts...) + if err != nil { + return 0, nil, err + } + + for _, item := range alerts { + alertLogDTO, err := a.parseAlertLog(item) + if err != nil { + return 0, nil, err + } + result = append(result, alertLogDTO) + } + + return total, result, err +} + +func (a AlertService) parseAlertLog(item model.AlertLog) (dto.AlertLogDTO, error) { + var alertDetail dto.AlertDetail + var alertRule dto.AlertRule + + if err := unmarshalAlertInfo(item.AlertDetail, &alertDetail); err != nil { + return dto.AlertLogDTO{}, err + } + if err := unmarshalAlertInfo(item.AlertRule, &alertRule); err != nil { + return dto.AlertLogDTO{}, err + } + return dto.AlertLogDTO{ + ID: item.ID, + Count: item.Count, + Type: item.Type, + Status: item.Status, + Method: item.Method, + Message: item.Message, + AlertId: item.AlertId, + AlertDetail: alertDetail, + AlertRule: alertRule, + CreatedAt: item.CreatedAt, + UpdatedAt: item.UpdatedAt, + }, nil +} + +func unmarshalAlertInfo(data string, v interface{}) error { + if err := json.Unmarshal([]byte(data), v); err != nil { + return fmt.Errorf("unmarshal alert info vars failed, err: %v", err) + } + return nil +} + +func (a AlertService) CleanAlertLogs() error { + return alertRepo.CleanAlertLogs() +} + +func (a AlertService) GetClams() ([]dto.ClamDTO, error) { + var clams []dto.ClamDTO + clamList, err := clamRepo.List() + + for _, clam := range clamList { + var clamDTO dto.ClamDTO + clamDTO.ID = clam.ID + clamDTO.Name = clam.Name + clamDTO.Path = clam.Path + clamDTO.Status = clam.Status + clamDTO.UpdatedAt = clam.UpdatedAt + clamDTO.CreatedAt = clam.CreatedAt + clams = append(clams, clamDTO) + } + return clams, err +} + +func (a AlertService) GetCronJobs(req dto.CronJobReq) ([]dto.CronJobDTO, error) { + var cronJobs []dto.CronJobDTO + var ( + opts []repo.DBOption + ) + if req.Status != "" { + opts = append(opts, repo.WithByStatus(req.Status)) + } + if req.Type != "" { + opts = append(opts, repo.WithByType(req.Type)) + } + cronjobList, err := cronjobRepo.List(opts...) + + for _, cronJob := range cronjobList { + var cronJobDTO dto.CronJobDTO + cronJobDTO.ID = cronJob.ID + cronJobDTO.Name = cronJob.Name + cronJobDTO.Status = cronJob.Status + cronJobDTO.Type = cronJob.Type + cronJobDTO.UpdatedAt = cronJob.UpdatedAt + cronJobDTO.CreatedAt = cronJob.CreatedAt + cronJobs = append(cronJobs, cronJobDTO) + } + return cronJobs, err +} + +func (a AlertService) GetAlertConfig() ([]model.AlertConfig, error) { + var ( + opts []repo.DBOption + configs []model.AlertConfig + ) + opts = append(opts, repo.WithByStatus(constant.AlertEnable)) + configs, err := alertRepo.AlertConfigList(opts...) + return configs, err +} + +func (a AlertService) UpdateAlertConfig(req dto.AlertConfigUpdate) error { + if req.ID != 0 { + upMap := make(map[string]interface{}) + upMap["id"] = req.ID + upMap["type"] = req.Type + upMap["title"] = req.Title + upMap["status"] = req.Status + upMap["config"] = req.Config + if err := alertRepo.UpdateAlertConfig(upMap, repo.WithByID(req.ID)); err != nil { + return err + } + } else { + var alertConfig model.AlertConfig + if err := copier.Copy(&alertConfig, &req); err != nil { + return buserr.WithErr("ErrStructTransform", err) + } + if err := alertRepo.CreateAlertConfig(&alertConfig); err != nil { + return err + } + } + + return nil +} + +func (a AlertService) DeleteAlertConfig(id uint) error { + return alertRepo.DeleteAlertConfig(repo.WithByID(id)) +} + +func (a AlertService) TestAlertConfig(req dto.AlertConfigTest) (bool, error) { + username := req.UserName + if username == "" { + username = req.Sender + } + cfg := email.SMTPConfig{ + Host: req.Host, + Port: req.Port, + Sender: req.Sender, + Username: username, + Password: req.Password, + From: fmt.Sprintf(`"%s" <%s>`, req.DisplayName, req.Sender), + Encryption: req.Encryption, + Recipient: req.Recipient, + } + + msg := email.EmailMessage{ + Subject: i18n.GetMsgByKey("TestAlertTitle"), + Body: i18n.GetMsgByKey("TestAlert"), + IsHTML: false, + } + transport := xpack.LoadRequestTransport() + if err := email.SendMail(cfg, msg, transport); err != nil { + return false, err + } + return true, nil +} + +func (a AlertService) ExternalUpdateAlert(updateAlert dto.AlertCreate) error { + upMap := make(map[string]interface{}) + var newStatus string + if updateAlert.SendCount == 0 { + newStatus = constant.AlertDisable + } else { + newStatus = constant.AlertEnable + upMap["send_count"] = updateAlert.SendCount + if updateAlert.Method != "" { + upMap["method"] = updateAlert.Method + } + } + upMap["status"] = newStatus + + alertInfo, _ := alertRepo.Get( + alertRepo.WithByType(updateAlert.Type), + alertRepo.WithByProject(updateAlert.Project), + ) + + if alertInfo.ID > 0 { + shouldUpdate := false + + if alertInfo.Status != newStatus { + shouldUpdate = true + } + if val, ok := upMap["send_count"]; ok && val != alertInfo.SendCount { + shouldUpdate = true + } + if val, ok := upMap["method"]; ok && val != "" && val != alertInfo.Method { + shouldUpdate = true + } + + if shouldUpdate { + if err := alertRepo.Update( + upMap, + alertRepo.WithByProject(updateAlert.Project), + alertRepo.WithByType(updateAlert.Type), + ); err != nil { + return err + } + } + } else { + if updateAlert.Method != "" && updateAlert.Title != "" { + updateAlert.Status = newStatus + if err := a.CreateAlert(updateAlert); err != nil { + return err + } + } + } + + return nil +} diff --git a/agent/app/service/alert_helper.go b/agent/app/service/alert_helper.go new file mode 100644 index 0000000..9dcd08d --- /dev/null +++ b/agent/app/service/alert_helper.go @@ -0,0 +1,899 @@ +package service + +import ( + "encoding/json" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/psutil" + versionUtil "github.com/1Panel-dev/1Panel/agent/utils/version" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/disk" + "github.com/shirou/gopsutil/v4/load" + "github.com/shirou/gopsutil/v4/mem" + "github.com/shirou/gopsutil/v4/net" + "math" + "sort" + "strconv" + "strings" + "time" +) + +const ( + ResourceAlertInterval = 30 + CheckIntervalSec = 3 + LoadCheckIntervalMin = 5 +) + +type AlertTaskHelper struct { + DiskIO chan []disk.IOCountersStat + NetIO chan []net.IOCountersStat +} + +type IAlertTaskHelper interface { + StopTask() + StartTask() + ResetTask() + InitTask(alertType string) +} + +var cpuLoad1, cpuLoad5, cpuLoad15 []float64 +var memoryLoad1, memoryLoad5, memoryLoad15 []float64 + +var baseTypes = map[string]bool{"ssl": true, "siteEndTime": true, "panelPwdEndTime": true, "panelUpdate": true} +var resourceTypes = map[string]bool{"cpu": true, "memory": true, "disk": true, "load": true, "panelLogin": true, "sshLogin": true, "nodeException": true, "licenseException": true} + +func NewIAlertTaskHelper() IAlertTaskHelper { + return &AlertTaskHelper{ + DiskIO: make(chan []disk.IOCountersStat, 1), + NetIO: make(chan []net.IOCountersStat, 1), + } +} +func (m *AlertTaskHelper) StartTask() { + baseAlert, resourceAlert := m.getClassifiedAlerts() + if len(baseAlert) == 0 && len(resourceAlert) == 0 { + return + } + handleBaseAlerts(baseAlert) + handleResourceAlerts(resourceAlert) +} + +func (m *AlertTaskHelper) StopTask() { + stopBaseJob() + stopResourceJob() +} + +func (m *AlertTaskHelper) ResetTask() { + m.StopTask() + m.StartTask() +} + +func (m *AlertTaskHelper) InitTask(alertType string) { + resetAlertState(alertType) + if baseTypes[alertType] { + stopBaseJob() + } else if resourceTypes[alertType] { + stopResourceJob() + } + m.StartTask() +} + +func resetAlertState(alertType string) { + switch alertType { + case "cpu": + cpuLoad1 = []float64{} + cpuLoad5 = []float64{} + cpuLoad15 = []float64{} + case "memory": + memoryLoad1 = []float64{} + memoryLoad5 = []float64{} + memoryLoad15 = []float64{} + } +} + +func (m *AlertTaskHelper) getClassifiedAlerts() (baseAlerts, resourceAlerts []dto.AlertDTO) { + alertList, _ := NewIAlertService().GetAlerts() + for _, alert := range alertList { + if baseTypes[alert.Type] { + baseAlerts = append(baseAlerts, alert) + } else if resourceTypes[alert.Type] { + resourceAlerts = append(resourceAlerts, alert) + } + } + return +} + +func handleBaseAlerts(baseAlerts []dto.AlertDTO) { + if len(baseAlerts) == 0 { + stopResourceJob() + return + } + if global.AlertBaseJobID == 0 { + baseTask(baseAlerts) + jobID, err := global.Cron.AddFunc("*/30 * * * *", func() { + baseTask(baseAlerts) + }) + if err != nil { + global.LOG.Errorf("alert base job start failed: %v", err) + return + } + global.AlertBaseJobID = jobID + global.LOG.Info("start alert base job") + } +} + +func handleResourceAlerts(resourceAlerts []dto.AlertDTO) { + if len(resourceAlerts) == 0 { + stopResourceJob() + return + } + if global.AlertResourceJobID == 0 { + jobID, err := global.Cron.AddFunc("*/1 * * * *", func() { + resourceTask(resourceAlerts) + }) + if err != nil { + global.LOG.Errorf("alert resource job start failed: %v", err) + return + } + global.AlertResourceJobID = jobID + global.LOG.Info("start alert resource job") + } +} + +func stopBaseJob() { + if global.AlertBaseJobID != 0 { + global.Cron.Remove(global.AlertBaseJobID) + global.AlertBaseJobID = 0 + global.LOG.Info("stop alert base job") + } +} + +func stopResourceJob() { + if global.AlertResourceJobID != 0 { + global.Cron.Remove(global.AlertResourceJobID) + global.AlertResourceJobID = 0 + global.LOG.Info("stop alert resource job") + } +} + +func baseTask(baseAlert []dto.AlertDTO) { + for _, alert := range baseAlert { + if !alertUtil.CheckSendTimeRange(alert.Type) { + continue + } + switch alert.Type { + case "ssl": + loadSSLInfo(alert) + case "siteEndTime": + loadWebsiteInfo(alert) + case "panelPwdEndTime": + if global.IsMaster { + loadPanelPwd(alert) + } + case "panelUpdate": + if global.IsMaster { + loadPanelUpdate(alert) + } + } + } +} + +func resourceTask(resourceAlert []dto.AlertDTO) { + minute := time.Now().Minute() + for _, alert := range resourceAlert { + if !alertUtil.CheckSendTimeRange(alert.Type) { + continue + } + execute := minute%LoadCheckIntervalMin == 0 + switch alert.Type { + case "cpu": + loadCPUUsage(alert) + case "memory": + loadMemUsage(alert) + case "load": + loadLoadInfo(alert) + case "disk": + loadDiskUsage(alert) + case "panelLogin": + loadPanelLogin(alert) + case "sshLogin": + loadSSHLogin(alert) + case "nodeException": + if execute && global.IsMaster { + loadNodeException(alert) + } + case "licenseException": + if execute && global.IsMaster { + loadLicenseException(alert) + } + } + } +} + +func loadSSLInfo(alert dto.AlertDTO) { + opts := getRepoOptionsByProject(alert.Project) + sslList, _ := repo.NewISSLRepo().List(opts...) + if len(sslList) == 0 { + return + } + daysDiffMap, projectMap := calculateSSLExpiryDays(sslList, alert.Cycle) + projectJSON := serializeAndSortProjects(projectMap) + if projectJSON == "" || len(daysDiffMap) == 0 { + return + } + sender := NewAlertSender(alert, projectJSON) + for daysDiff, domains := range daysDiffMap { + domainStr := strings.Join(domains, ",") + params := createAlertBaseParams(strconv.Itoa(len(domains)), strconv.Itoa(daysDiff)) + sender.Send(domainStr, params) + } +} + +func loadWebsiteInfo(alert dto.AlertDTO) { + opts := getRepoOptionsByProject(alert.Project) + websiteList, _ := websiteRepo.List(opts...) + if len(websiteList) == 0 { + return + } + + daysDiffMap, projectMap := calculateWebsiteExpiryDays(websiteList, alert.Cycle) + projectJSON := serializeAndSortProjects(projectMap) + if projectJSON == "" || len(daysDiffMap) == 0 { + return + } + sender := NewAlertSender(alert, projectJSON) + for daysDiff, domains := range daysDiffMap { + domainStr := strings.Join(domains, ",") + params := createAlertBaseParams(strconv.Itoa(len(domains)), strconv.Itoa(daysDiff)) + sender.Send(domainStr, params) + } +} + +func loadPanelPwd(alert dto.AlertDTO) { + // only master alert + expDays, err := getSettingValue("ExpirationDays") + if err != nil || expDays == "0" { + global.LOG.Info("panel password expiration setting not enabled, skip") + return + } + + expTimeStr, err := getSettingValue("ExpirationTime") + if err != nil { + return + } + expTime, _ := time.Parse(constant.DateTimeLayout, expTimeStr) + daysDiff := calculateDaysDifference(expTime) + if daysDiff >= 0 && int(alert.Cycle) >= daysDiff { + params := createAlertPwdParams(strconv.Itoa(daysDiff)) + sender := NewAlertSender(alert, expTimeStr) + sender.Send(strconv.Itoa(daysDiff), params) + } +} + +func loadPanelUpdate(alert dto.AlertDTO) { + // only master alert + info, err := versionUtil.GetUpgradeVersionInfo() + if err != nil { + global.LOG.Errorf("error getting version info: %s", err) + return + } + + version := getValidVersion(info) + if version == "" { + return + } + + sender := NewAlertSender(alert, version) + sender.Send(version, []dto.Param{}) +} + +// 获取 CPU 使用率数据并发送到通道 +func loadCPUUsage(alert dto.AlertDTO) { + percent, err := cpu.Percent(time.Duration(CheckIntervalSec)*time.Second, false) + if err != nil { + global.LOG.Errorf("error getting cpu usage, err: %v", err) + return + } + + if len(percent) > 0 { + var usageLoad *[]float64 + var threshold int + + switch alert.Cycle { + case 1: + usageLoad = &cpuLoad1 + threshold = 1 + case 5: + usageLoad = &cpuLoad5 + threshold = 5 + case 15: + usageLoad = &cpuLoad15 + threshold = 15 + } + shouldSendResourceAlert(alert, percent[0], usageLoad, threshold) + } +} + +// 获取内存使用情况数据并发送到通道 +func loadMemUsage(alert dto.AlertDTO) { + memStat, err := mem.VirtualMemory() + if err != nil { + global.LOG.Errorf("error getting memory usage, err: %v", err) + return + } + + percent := memStat.UsedPercent + var memoryLoad *[]float64 + var threshold int + + switch alert.Cycle { + case 1: + memoryLoad = &memoryLoad1 + threshold = 1 + case 5: + memoryLoad = &memoryLoad5 + threshold = 5 + case 15: + memoryLoad = &memoryLoad15 + threshold = 15 + } + shouldSendResourceAlert(alert, percent, memoryLoad, threshold) +} + +// 获取系统负载数据并发送到通道 +func loadLoadInfo(alert dto.AlertDTO) { + avgStat, err := load.Avg() + if err != nil { + global.LOG.Errorf("error getting load usage, err: %v", err) + return + } + var loadValue float64 + CPUTotal, _ := psutil.CPUInfo.GetLogicalCores(false) + switch alert.Cycle { + case 1: + loadValue = avgStat.Load1 / (float64(CPUTotal*2) * 0.75) * 100 + case 5: + loadValue = avgStat.Load5 / (float64(CPUTotal*2) * 0.75) * 100 + case 15: + loadValue = avgStat.Load15 / (float64(CPUTotal*2) * 0.75) * 100 + default: + return + } + if loadValue < float64(alert.Count) { + return + } + newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID) + if err != nil { + global.LOG.Errorf("task log record not found, err: %v", err) + } + if isAlertDue(newDate) { + sendResourceAlert(alert, loadValue) + } +} + +func loadDiskUsage(alert dto.AlertDTO) { + newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID) + if err != nil { + global.LOG.Errorf("record not found, err: %v", err) + return + } + if isAlertDue(newDate) { + if strings.Contains(alert.Project, "all") { + err = processAllDisks(alert) + } else { + err = processSingleDisk(alert) + } + } +} + +func loadPanelLogin(alert dto.AlertDTO) { + count, isAlert, err := alertUtil.CountRecentFailedLoginLogs(alert.Cycle, alert.Count) + alertType := alert.Type + quota := strconv.Itoa(count) + quotaType := strconv.Itoa(int(alert.Cycle)) + if err != nil { + global.LOG.Errorf("Failed to count recent failed login logs: %v", err) + } + if isAlert { + alertType = "panelLogin" + quota = strconv.Itoa(count) + quotaType = "panelLogin" + params := []dto.Param{ + { + Index: "1", + Key: "cycle", + Value: "", + }, + { + Index: "2", + Key: "project", + Value: "", + }, + } + sendAlerts(alert, alertType, quota, quotaType, params) + } + + whitelist := strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n") + records, err := alertUtil.FindRecentSuccessLoginsNotInWhitelist(30, whitelist) + if err != nil { + global.LOG.Errorf("Failed to check recent failed ip login logs: %v", err) + } + if len(records) > 0 { + quota = strings.Join(func() []string { + var ips []string + for _, r := range records { + ips = append(ips, r.IP) + } + return ips + }(), "\n") + alertType = "panelIpLogin" + quotaType = "panelIpLogin" + params := []dto.Param{ + { + Index: "1", + Key: "cycle", + Value: "", + }, + { + Index: "2", + Key: "project", + Value: " IP ", + }, + } + sendAlerts(alert, alertType, quota, quotaType, params) + } +} + +func loadSSHLogin(alert dto.AlertDTO) { + count, isAlert, err := alertUtil.CountRecentFailedSSHLog(alert.Cycle, alert.Count) + alertType := alert.Type + quota := strconv.Itoa(count) + quotaType := strconv.Itoa(int(alert.Cycle)) + if err != nil { + global.LOG.Errorf("Failed to count recent failed ssh login logs: %v", err) + } + if isAlert { + alertType = "sshLogin" + quota = strconv.Itoa(count) + quotaType = "sshLogin" + params := []dto.Param{ + { + Index: "1", + Key: "cycle", + Value: " SSH ", + }, + { + Index: "2", + Key: "project", + Value: "", + }, + } + sendAlerts(alert, alertType, quota, quotaType, params) + } + whitelist := strings.Split(strings.TrimSpace(alert.AdvancedParams), "\n") + records, err := alertUtil.FindRecentSuccessLoginNotInWhitelist(30, whitelist) + if err != nil { + global.LOG.Errorf("Failed to check recent failed ip ssh login logs: %v", err) + } + if len(records) > 0 { + quota = strings.Join(records, "\n") + alertType = "sshIpLogin" + quotaType = "sshIpLogin" + params := []dto.Param{ + { + Index: "1", + Key: "cycle", + Value: " SSH ", + }, + { + Index: "2", + Key: "project", + Value: " IP ", + }, + } + sendAlerts(alert, alertType, quota, quotaType, params) + } +} + +func loadNodeException(alert dto.AlertDTO) { + // only master alert + failCount, err := xpack.GetNodeErrorAlert() + if err != nil { + global.LOG.Errorf("error getting node, err: %s", err) + return + } + if failCount > 0 { + quotaType := "node-error" + params := []dto.Param{ + { + Index: "1", + Key: "cycle", + Value: strconv.Itoa(int(failCount)), + }, + } + newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID) + if err != nil { + global.LOG.Errorf("record not found, err: %v", err) + return + } + if isAlertDue(newDate) { + sender := NewAlertSender(alert, quotaType) + sender.ResourceSend(strconv.Itoa(int(failCount)), params) + } + } + +} + +func loadLicenseException(alert dto.AlertDTO) { + // only master alert + failCount, err := xpack.GetLicenseErrorAlert() + if err != nil { + global.LOG.Errorf("error getting license, err: %s", err) + return + } + if failCount > 0 { + quotaType := "license-error" + params := []dto.Param{ + { + Index: "1", + Key: "cycle", + Value: strconv.Itoa(int(failCount)), + }, + } + newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID) + if err != nil { + global.LOG.Errorf("record not found, err: %v", err) + return + } + if isAlertDue(newDate) { + sender := NewAlertSender(alert, quotaType) + sender.ResourceSend(strconv.Itoa(int(failCount)), params) + } + } +} + +func sendAlerts(alert dto.AlertDTO, alertType, quota, quotaType string, params []dto.Param) { + methods := strings.Split(alert.Method, ",") + newDate, err := alertRepo.GetTaskLog(alertType, alert.ID) + if err != nil { + global.LOG.Errorf("task log record not found, err: %v", err) + } + if newDate.IsZero() || calculateMinutesDifference(newDate) > ResourceAlertInterval { + for _, m := range methods { + m = strings.TrimSpace(m) + switch m { + case constant.SMS: + if !alertUtil.CheckSMSSendLimit(constant.SMS) { + continue + } + todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, constant.SMS) + if !isValid { + continue + } + create := dto.AlertLogCreate{ + Type: alertType, + AlertId: alert.ID, + Count: todayCount + 1, + } + alertErr := xpack.CreateSMSAlertLog(alertType, alert, create, quotaType, params, constant.SMS) + if alertErr != nil { + global.LOG.Infof("%s alert sms push faild, err: %v", alertType, alertErr.Error()) + continue + } + alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.SMS) + global.LOG.Infof("%s alert sms push successful", alertType) + + case constant.Email: + todayCount, isValid := canSendAlertToday(alertType, quotaType, alert.SendCount, constant.Email) + if !isValid { + continue + } + create := dto.AlertLogCreate{ + Type: alertType, + AlertId: alert.ID, + Count: todayCount + 1, + } + alertInfo := alert + alertInfo.Type = alertType + create.AlertRule = alertUtil.ProcessAlertRule(alert) + create.AlertDetail = alertUtil.ProcessAlertDetail(alertInfo, quotaType, params, constant.Email) + transport := xpack.LoadRequestTransport() + agentInfo, _ := xpack.GetAgentInfo() + alertErr := alertUtil.CreateEmailAlertLog(create, alertInfo, params, transport, agentInfo) + if alertErr != nil { + global.LOG.Infof("%s alert email push faild, err: %v", alertType, alertErr.Error()) + continue + } + alertUtil.CreateNewAlertTask(quota, alertType, quotaType, constant.Email) + global.LOG.Infof("%s alert email push successful", alertType) + } + } + } +} + +// ------------------------------ +func getRepoOptionsByProject(project string) []repo.DBOption { + var opts []repo.DBOption + if project != "all" { + itemID, _ := strconv.Atoi(project) + opts = append(opts, repo.WithByID(uint(itemID))) + } + return opts +} + +func serializeAndSortProjects(projectMap map[uint][]time.Time) string { + if len(projectMap) == 0 { + return "" + } + keys := make([]int, 0, len(projectMap)) + for k := range projectMap { + keys = append(keys, int(k)) + } + sort.Ints(keys) + projectJSON, err := json.Marshal(projectMap) + if err != nil { + global.LOG.Errorf("Failed to serialize projectMap: %v", err) + return "" + } + + return string(projectJSON) +} + +func calculateSSLExpiryDays(sslList []model.WebsiteSSL, cycle uint) (map[int][]string, map[uint][]time.Time) { + currentDate := time.Now() + daysDiffMap := make(map[int][]string) + projectMap := make(map[uint][]time.Time) + + for _, ssl := range sslList { + daysDiff := int(ssl.ExpireDate.Sub(currentDate).Hours() / 24) + if daysDiff > 0 && int(cycle) >= daysDiff { + daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], ssl.PrimaryDomain) + projectMap[ssl.ID] = append(projectMap[ssl.ID], ssl.ExpireDate) + } + } + return daysDiffMap, projectMap +} + +func calculateWebsiteExpiryDays(websites []model.Website, cycle uint) (map[int][]string, map[uint][]time.Time) { + currentDate := time.Now() + daysDiffMap := make(map[int][]string) + projectMap := make(map[uint][]time.Time) + + for _, website := range websites { + daysDiff := int(website.ExpireDate.Sub(currentDate).Hours() / 24) + if daysDiff > 0 && int(cycle) >= daysDiff { + daysDiffMap[daysDiff] = append(daysDiffMap[daysDiff], website.PrimaryDomain) + projectMap[website.ID] = append(projectMap[website.ID], website.ExpireDate) + } + } + return daysDiffMap, projectMap +} + +func getSettingValue(key string) (string, error) { + var setting model.Setting + if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", key).First(&setting).Error; err != nil { + global.LOG.Errorf("load %s from db setting failed: %v", key, err) + return "", err + } + return setting.Value, nil +} + +func getValidVersion(info *dto.UpgradeInfo) string { + if info.NewVersion != "" { + return info.NewVersion + } else if info.TestVersion != "" { + return info.TestVersion + } else if info.LatestVersion != "" { + return info.LatestVersion + } + return "" +} + +func shouldSendResourceAlert(alert dto.AlertDTO, currentUsage float64, usageLoad *[]float64, threshold int) { + newDate, err := alertRepo.GetTaskLog(alert.Type, alert.ID) + if err != nil { + global.LOG.Errorf("record not found, err: %v", err) + } + if isAlertDue(newDate) { + *usageLoad = append(*usageLoad, currentUsage) + if len(*usageLoad) > threshold { + *usageLoad = (*usageLoad)[1:] + } + if len(*usageLoad) == threshold { + avgUsage := average(*usageLoad) + if avgUsage >= float64(alert.Count) { + sendResourceAlert(alert, avgUsage) + } + } + } +} + +func isAlertDue(lastAlertTime time.Time) bool { + if lastAlertTime.IsZero() { + return true + } + return calculateMinutesDifference(lastAlertTime) > ResourceAlertInterval +} + +func sendResourceAlert(alert dto.AlertDTO, value float64) { + valueStr := common.FormatPercent(value) + module := getModuleName(alert.Type) + params := createAlertAvgParams(strconv.Itoa(int(alert.Cycle)), module, valueStr) + sender := NewAlertSender(alert, strconv.Itoa(int(alert.Cycle))) + sender.ResourceSend(valueStr, params) +} + +func getModuleName(alertType string) string { + var module string + switch alertType { + case "cpu": + module = " CPU " + case "memory": + module = "内存" + case "load": + module = "负载" + default: + } + return module +} + +func canSendAlertToday(alertType, quotaType string, sendCount uint, method string) (uint, bool) { + todayCount, _, err := alertRepo.LoadTaskCount(alertType, quotaType, method) + if err != nil { + global.LOG.Errorf("error getting task info, err: %v", err) + return todayCount, false + } + if todayCount >= sendCount { + return todayCount, false + } + + return todayCount, true +} + +func average(arr []float64) float64 { + total := 0.0 + for _, v := range arr { + total += v + } + return total / float64(len(arr)) +} + +func createAlertBaseParams(project, cycle string) []dto.Param { + return []dto.Param{ + { + Index: "1", + Key: "project", + Value: project, + }, + { + Index: "2", + Key: "cycle", + Value: cycle, + }, + } +} + +func createAlertPwdParams(cycle string) []dto.Param { + return []dto.Param{ + { + Index: "1", + Key: "cycle", + Value: cycle, + }, + } +} + +func createAlertAvgParams(cycle, module, count string) []dto.Param { + return []dto.Param{ + { + Index: "1", + Key: "cycle", + Value: cycle, + }, + { + Index: "2", + Key: "module", + Value: module, + }, + { + Index: "3", + Key: "count", + Value: count, + }, + } +} + +func createAlertDiskParams(project, count string) []dto.Param { + return []dto.Param{ + { + Index: "1", + Key: "project", + Value: project, + }, + { + Index: "2", + Key: "count", + Value: count, + }, + } +} + +func processAllDisks(alert dto.AlertDTO) error { + diskList, err := NewIAlertService().GetDisks() + if err != nil { + global.LOG.Errorf("error getting disk list, err: %v", err) + return err + } + for _, item := range diskList { + if success, err := checkAndCreateDiskAlert(alert, item.Path); err == nil && success { + global.LOG.Infof("disk alert pushed successfully for %s", item.Path) + } + } + return nil +} + +func processSingleDisk(alert dto.AlertDTO) error { + success, err := checkAndCreateDiskAlert(alert, alert.Project) + if err != nil { + return err + } + if success { + global.LOG.Infof("disk alert pushed successfully for %s", alert.Project) + } + return nil +} + +func checkAndCreateDiskAlert(alert dto.AlertDTO, path string) (bool, error) { + usageStat, err := psutil.DISK.GetUsage(path, false) + if err != nil { + global.LOG.Errorf("error getting disk usage for %s, err: %v", path, err) + return false, err + } + + usedTotal, usedStr := calculateUsedTotal(alert.Cycle, usageStat) + commonTotal := float64(alert.Count) + if alert.Cycle == 1 { + commonTotal *= 1024 * 1024 * 1024 + } + if usedTotal < commonTotal { + return false, nil + } + global.LOG.Infof("disk「 %s 」usage: %s", path, usedStr) + params := createAlertDiskParams(path, usedStr) + sender := NewAlertSender(alert, alert.Project) + sender.ResourceSend(path, params) + return true, nil +} + +func calculateUsedTotal(cycle uint, usageStat *disk.UsageStat) (float64, string) { + if cycle == 1 { + return float64(usageStat.Used), common.FormatBytes(usageStat.Used) + } + return usageStat.UsedPercent, common.FormatPercent(usageStat.UsedPercent) +} + +func calculateDaysDifference(expirationTime time.Time) int { + currentDate := time.Now() + formattedTime := currentDate.Format(constant.DateTimeLayout) + parsedTime, _ := time.Parse(constant.DateTimeLayout, formattedTime) + timeGap := expirationTime.Sub(parsedTime).Milliseconds() + if timeGap < 0 { + return -1 + } + daysDifference := int(math.Floor(float64(timeGap) / (3600 * 1000 * 24))) + return daysDifference +} + +func calculateMinutesDifference(newDate time.Time) int { + now := time.Now() + if newDate.After(now) { + return -1 + } + minutesDifference := int(now.Sub(newDate).Minutes()) + return minutesDifference +} diff --git a/agent/app/service/alert_sender.go b/agent/app/service/alert_sender.go new file mode 100644 index 0000000..228d4b4 --- /dev/null +++ b/agent/app/service/alert_sender.go @@ -0,0 +1,167 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "strings" +) + +type AlertSender struct { + alert dto.AlertDTO + quotaType string +} + +func NewAlertSender(alert dto.AlertDTO, quotaType string) *AlertSender { + return &AlertSender{ + alert: alert, + quotaType: quotaType, + } +} + +func (s *AlertSender) Send(quota string, params []dto.Param) { + methods := strings.Split(s.alert.Method, ",") + for _, method := range methods { + method = strings.TrimSpace(method) + switch method { + case constant.SMS: + s.sendSMS(quota, params) + case constant.Email: + s.sendEmail(quota, params) + } + } +} + +func (s *AlertSender) ResourceSend(quota string, params []dto.Param) { + methods := strings.Split(s.alert.Method, ",") + for _, method := range methods { + method = strings.TrimSpace(method) + switch method { + case constant.SMS: + s.sendResourceSMS(quota, params) + case constant.Email: + s.sendResourceEmail(quota, params) + } + } +} + +func (s *AlertSender) sendSMS(quota string, params []dto.Param) { + if !alertUtil.CheckSMSSendLimit(constant.SMS) { + return + } + + totalCount, isValid := s.canSendAlert(constant.SMS) + if !isValid { + return + } + + create := dto.AlertLogCreate{ + Status: constant.AlertSuccess, + Count: totalCount + 1, + AlertId: s.alert.ID, + Type: s.alert.Type, + } + + _ = xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS) + alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS) + global.LOG.Infof("%s alert sms push successful", s.alert.Type) +} + +func (s *AlertSender) sendEmail(quota string, params []dto.Param) { + totalCount, isValid := s.canSendAlert(constant.Email) + if !isValid { + return + } + + create := dto.AlertLogCreate{ + Status: constant.AlertSuccess, + Count: totalCount + 1, + AlertId: s.alert.ID, + Type: s.alert.Type, + AlertRule: alertUtil.ProcessAlertRule(s.alert), + AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email), + } + + transport := xpack.LoadRequestTransport() + agentInfo, _ := xpack.GetAgentInfo() + _ = alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo) + alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email) + global.LOG.Infof("%s alert email push successful", s.alert.Type) +} + +func (s *AlertSender) sendResourceSMS(quota string, params []dto.Param) { + if !alertUtil.CheckSMSSendLimit(constant.SMS) { + return + } + + todayCount, isValid := s.canResourceSendAlert(constant.SMS) + if !isValid { + return + } + + create := dto.AlertLogCreate{ + Status: constant.AlertSuccess, + Count: todayCount + 1, + AlertId: s.alert.ID, + Type: s.alert.Type, + } + + if err := xpack.CreateSMSAlertLog(s.alert.Type, s.alert, create, quota, params, constant.SMS); err != nil { + global.LOG.Errorf("failed to send SMS alert: %v", err) + return + } + alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.SMS) + global.LOG.Infof("%s alert sms push successful", s.alert.Type) +} + +func (s *AlertSender) sendResourceEmail(quota string, params []dto.Param) { + todayCount, isValid := s.canResourceSendAlert(constant.Email) + if !isValid { + return + } + + create := dto.AlertLogCreate{ + Status: constant.AlertSuccess, + Count: todayCount + 1, + AlertId: s.alert.ID, + Type: s.alert.Type, + AlertRule: alertUtil.ProcessAlertRule(s.alert), + AlertDetail: alertUtil.ProcessAlertDetail(s.alert, quota, params, constant.Email), + } + + transport := xpack.LoadRequestTransport() + agentInfo, _ := xpack.GetAgentInfo() + if err := alertUtil.CreateEmailAlertLog(create, s.alert, params, transport, agentInfo); err != nil { + global.LOG.Errorf("failed to send Email alert: %v", err) + return + } + alertUtil.CreateNewAlertTask(quota, s.alert.Type, s.quotaType, constant.Email) + global.LOG.Infof("%s alert email push successful", s.alert.Type) +} + +func (s *AlertSender) canSendAlert(method string) (uint, bool) { + todayCount, totalCount, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method) + if err != nil { + global.LOG.Errorf("error getting task count: %v", err) + return totalCount, false + } + + if todayCount >= 1 || s.alert.SendCount <= totalCount { + return totalCount, false + } + return totalCount, true +} + +func (s *AlertSender) canResourceSendAlert(method string) (uint, bool) { + todayCount, _, err := alertRepo.LoadTaskCount(s.alert.Type, s.quotaType, method) + if err != nil { + global.LOG.Errorf("error getting task count: %v", err) + return todayCount, false + } + if s.alert.SendCount <= todayCount { + return todayCount, false + } + return todayCount, true +} diff --git a/agent/app/service/app.go b/agent/app/service/app.go new file mode 100644 index 0000000..65b9f76 --- /dev/null +++ b/agent/app/service/app.go @@ -0,0 +1,950 @@ +package service + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "os" + "path" + "path/filepath" + "reflect" + "strconv" + "strings" + + "github.com/gin-gonic/gin" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/req_helper" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "gopkg.in/yaml.v3" +) + +type AppService struct { +} + +type IAppService interface { + PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error) + GetAppTags(ctx *gin.Context) ([]response.TagDTO, error) + GetApp(ctx *gin.Context, key string) (*response.AppDTO, error) + GetAppDetail(appId uint, version, appType string) (response.AppDetailDTO, error) + Install(req request.AppInstallCreate) (*model.AppInstall, error) + SyncAppListFromRemote(taskID string) error + GetAppUpdate() (*response.AppUpdateRes, error) + GetAppDetailByID(id uint) (*response.AppDetailDTO, error) + SyncAppListFromLocal(taskID string) + GetAppIcon(key string) ([]byte, error) + GetAppDetailByKey(appKey, version string) (response.AppDetailSimpleDTO, error) +} + +func NewIAppService() IAppService { + return &AppService{} +} + +func (a AppService) PageApp(ctx *gin.Context, req request.AppSearch) (*response.AppRes, error) { + var opts []repo.DBOption + opts = append(opts, appRepo.OrderByRecommend()) + if req.Name != "" { + opts = append(opts, appRepo.WithByLikeName(strings.TrimSpace(req.Name))) + } + if req.Type != "" { + opts = append(opts, appRepo.WithType(req.Type)) + } + if req.Recommend { + opts = append(opts, appRepo.GetRecommend()) + } + if req.Resource != "" && req.Resource != "all" { + opts = append(opts, appRepo.WithResource(req.Resource)) + } + + if req.ShowCurrentArch { + info, err := NewIDashboardService().LoadOsInfo() + if err != nil { + return nil, err + } + kernelArch := info.KernelArch + if kernelArch == "aarch64" { + kernelArch = "arm64" + } + opts = append(opts, appRepo.WithArch(kernelArch)) + } + if len(req.Tags) != 0 { + tags, err := tagRepo.GetByKeys(req.Tags) + if err != nil { + return nil, err + } + var tagIds []uint + for _, t := range tags { + tagIds = append(tagIds, t.ID) + } + appTags, err := appTagRepo.GetByTagIds(tagIds) + if err != nil { + return nil, err + } + var appIds []uint + for _, t := range appTags { + appIds = append(appIds, t.AppId) + } + opts = append(opts, repo.WithByIDs(appIds)) + } + res := &response.AppRes{} + + total, apps, err := appRepo.Page(req.Page, req.PageSize, opts...) + if err != nil { + return nil, err + } + appDTOs := make([]*response.AppItem, 0) + info := &dto.SettingInfo{} + if req.Type == "php" { + info, _ = NewISettingService().GetSettingInfo() + } + lang := strings.ToLower(common.GetLang(ctx)) + for _, ap := range apps { + if req.Type == "php" { + if !global.CONF.Base.IsOffLine && (ap.RequiredPanelVersion == 0 || !common.CompareAppVersion(common.GetSystemVersion(info.SystemVersion), fmt.Sprintf("%f", ap.RequiredPanelVersion))) { + continue + } + } + appDTO := &response.AppItem{ + ID: ap.ID, + Name: ap.Name, + Key: ap.Key, + Limit: ap.Limit, + GpuSupport: ap.GpuSupport, + Recommend: ap.Recommend, + Description: ap.GetDescription(ctx), + Type: ap.Type, + BatchInstallSupport: ap.BatchInstallSupport, + } + appDTOs = append(appDTOs, appDTO) + tags, err := getAppTags(ap.ID, lang) + if err != nil { + continue + } + for _, tag := range tags { + appDTO.Tags = append(appDTO.Tags, tag.Name) + } + if ap.Type == constant.RuntimePHP || ap.Type == constant.RuntimeGo || ap.Type == constant.RuntimeNode || ap.Type == constant.RuntimePython || ap.Type == constant.RuntimeJava || ap.Type == constant.RuntimeDotNet { + details, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(ap.ID)) + var ids []uint + if len(details) == 0 { + continue + } + for _, d := range details { + ids = append(ids, d.ID) + } + runtimes, _ := runtimeRepo.List(runtimeRepo.WithDetailIdsIn(ids)) + appDTO.Installed = len(runtimes) > 0 + } else { + installs, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(ap.ID)) + appDTO.Installed = len(installs) > 0 + } + } + res.Items = appDTOs + res.Total = total + + return res, nil +} + +func (a AppService) GetAppTags(ctx *gin.Context) ([]response.TagDTO, error) { + tags, err := tagRepo.All() + if err != nil { + return nil, err + } + var res []response.TagDTO + lang := strings.ToLower(common.GetLang(ctx)) + for _, tag := range tags { + tagDTO := response.TagDTO{ + ID: tag.ID, + Key: tag.Key, + } + var translations = make(map[string]string) + _ = json.Unmarshal([]byte(tag.Translations), &translations) + if name, ok := translations[lang]; ok { + tagDTO.Name = name + } + res = append(res, tagDTO) + } + return res, nil +} + +func (a AppService) GetApp(ctx *gin.Context, key string) (*response.AppDTO, error) { + var appDTO response.AppDTO + if key == "postgres" { + key = "postgresql" + } + app, err := appRepo.GetFirst(appRepo.WithKey(key)) + if err != nil { + return nil, err + } + appDTO.App = app + appDTO.App.Description = app.GetDescription(ctx) + details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID)) + if err != nil { + return nil, err + } + appDTO.Versions = getAppVersions(key, details) + tags, err := getAppTags(app.ID, strings.ToLower(common.GetLang(ctx))) + if err != nil { + return nil, err + } + appDTO.Tags = tags + return &appDTO, nil +} + +func (a AppService) GetAppDetailByKey(appKey, version string) (response.AppDetailSimpleDTO, error) { + var appDetailDTO response.AppDetailSimpleDTO + app, err := appRepo.GetFirst(appRepo.WithKey(appKey)) + if err != nil { + return appDetailDTO, err + } + appDetail, err := appDetailRepo.GetFirst(appDetailRepo.WithAppId(app.ID), appDetailRepo.WithVersion(version)) + if err != nil { + return appDetailDTO, err + } + appDetailDTO.ID = appDetail.ID + return appDetailDTO, nil +} + +func (a AppService) GetAppDetail(appID uint, version, appType string) (response.AppDetailDTO, error) { + var ( + appDetailDTO response.AppDetailDTO + opts []repo.DBOption + ) + opts = append(opts, appDetailRepo.WithAppId(appID), appDetailRepo.WithVersion(version)) + detail, err := appDetailRepo.GetFirst(opts...) + if err != nil { + return appDetailDTO, err + } + appDetailDTO.AppDetail = detail + appDetailDTO.Enable = true + + if appType == "runtime" { + app, err := appRepo.GetFirst(repo.WithByID(appID)) + if err != nil { + return appDetailDTO, err + } + fileOp := files.NewFileOp() + + versionPath := filepath.Join(app.GetAppResourcePath(), detail.Version) + if !fileOp.Stat(versionPath) || detail.Update { + if err = downloadApp(app, detail, nil, nil); err != nil && !fileOp.Stat(versionPath) { + return appDetailDTO, err + } + } + switch app.Type { + case constant.RuntimePHP: + paramsPath := filepath.Join(versionPath, "data.yml") + if !fileOp.Stat(paramsPath) { + return appDetailDTO, buserr.WithDetail("ErrFileNotExist", paramsPath, nil) + } + param, err := fileOp.GetContent(paramsPath) + if err != nil { + return appDetailDTO, err + } + paramMap := make(map[string]interface{}) + if err = yaml.Unmarshal(param, ¶mMap); err != nil { + return appDetailDTO, err + } + appDetailDTO.Params = paramMap["additionalProperties"] + composePath := filepath.Join(versionPath, "docker-compose.yml") + if !fileOp.Stat(composePath) { + return appDetailDTO, buserr.WithDetail("ErrFileNotExist", composePath, nil) + } + compose, err := fileOp.GetContent(composePath) + if err != nil { + return appDetailDTO, err + } + composeMap := make(map[string]interface{}) + if err := yaml.Unmarshal(compose, &composeMap); err != nil { + return appDetailDTO, err + } + if service, ok := composeMap["services"]; ok { + servicesMap := service.(map[string]interface{}) + for k := range servicesMap { + appDetailDTO.Image = k + } + } + } + } else { + paramMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(detail.Params), ¶mMap); err != nil { + return appDetailDTO, err + } + appDetailDTO.Params = paramMap + } + + if appDetailDTO.DockerCompose == "" { + filename := filepath.Base(appDetailDTO.DownloadUrl) + dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(appDetailDTO.DownloadUrl, filename), "docker-compose.yml") + statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s) + if err != nil { + return appDetailDTO, buserr.WithDetail("ErrGetCompose", err.Error(), err) + } + if statusCode > 200 { + return appDetailDTO, buserr.WithDetail("ErrGetCompose", string(composeRes), err) + } + detail.DockerCompose = string(composeRes) + _ = appDetailRepo.Update(context.Background(), detail) + appDetailDTO.DockerCompose = string(composeRes) + } + + appDetailDTO.HostMode = isHostModel(appDetailDTO.DockerCompose) + + app, err := appRepo.GetFirst(repo.WithByID(detail.AppId)) + if err != nil { + return appDetailDTO, err + } + if err := checkLimit(app); err != nil { + appDetailDTO.Enable = false + } + appDetailDTO.Architectures = app.Architectures + appDetailDTO.MemoryRequired = app.MemoryRequired + appDetailDTO.GpuSupport = app.GpuSupport + return appDetailDTO, nil +} +func (a AppService) GetAppDetailByID(id uint) (*response.AppDetailDTO, error) { + res := &response.AppDetailDTO{} + appDetail, err := appDetailRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return nil, err + } + res.AppDetail = appDetail + paramMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(appDetail.Params), ¶mMap); err != nil { + return nil, err + } + res.Params = paramMap + res.HostMode = isHostModel(appDetail.DockerCompose) + return res, nil +} + +func (a AppService) Install(req request.AppInstallCreate) (appInstall *model.AppInstall, err error) { + if err = docker.CreateDefaultDockerNetwork(); err != nil { + err = buserr.WithDetail("Err1PanelNetworkFailed", err.Error(), nil) + return + } + if list, _ := appInstallRepo.ListBy(context.Background(), repo.WithByLowerName(req.Name)); len(list) > 0 { + err = buserr.New("ErrAppNameExist") + return + } + var ( + httpPort int + httpsPort int + appDetail model.AppDetail + app model.App + ) + appDetail, err = appDetailRepo.GetFirst(repo.WithByID(req.AppDetailId)) + if err != nil { + return + } + app, err = appRepo.GetFirst(repo.WithByID(appDetail.AppId)) + if err != nil { + return + } + if DatabaseKeys[app.Key] > 0 { + if existDatabases, _ := databaseRepo.GetList(repo.WithByName(req.Name)); len(existDatabases) > 0 { + err = buserr.New("ErrRemoteExist") + return + } + } + if hostName, ok := req.Params["PANEL_DB_HOST"]; ok { + database, _ := databaseRepo.Get(repo.WithByName(hostName.(string))) + if database.AppInstallID > 0 { + databaseInstall, _ := appInstallRepo.GetFirst(repo.WithByID(database.AppInstallID)) + if databaseInstall.Status != constant.StatusRunning { + return nil, buserr.WithName("ErrAppIsDown", databaseInstall.Name) + } + } + } + for key := range req.Params { + if !strings.Contains(key, "PANEL_APP_PORT") { + continue + } + var port int + port, err = checkPort(key, req.Params) + if err != nil { + return + } + if key == "PANEL_APP_PORT_HTTP" { + httpPort = port + } + if key == "PANEL_APP_PORT_HTTPS" { + httpsPort = port + } + } + + if err = checkRequiredAndLimit(app); err != nil { + return + } + + appInstall = &model.AppInstall{ + Name: req.Name, + AppId: appDetail.AppId, + AppDetailId: appDetail.ID, + Version: appDetail.Version, + Status: constant.StatusInstalling, + HttpPort: httpPort, + HttpsPort: httpsPort, + App: app, + } + composeMap := make(map[string]interface{}) + var composeRes []byte + if req.EditCompose { + if err = yaml.Unmarshal([]byte(req.DockerCompose), &composeMap); err != nil { + return + } + } else { + if appDetail.DockerCompose == "" { + dockerComposeUrl := fmt.Sprintf("%s/%s/1panel/%s/%s/docker-compose.yml", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode, app.Key, appDetail.Version) + _, composeRes, err = req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s) + if err != nil { + return + } + appDetail.DockerCompose = string(composeRes) + _ = appDetailRepo.Update(context.Background(), appDetail) + } + if err = yaml.Unmarshal([]byte(appDetail.DockerCompose), &composeMap); err != nil { + return + } + } + + value, ok := composeMap["services"] + if !ok || value == nil { + err = buserr.New("ErrFileParse") + return + } + servicesMap := value.(map[string]interface{}) + containerName := constant.ContainerPrefix + app.Key + "-" + common.RandStr(4) + if req.Advanced && req.ContainerName != "" { + containerName = req.ContainerName + appInstalls, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithContainerName(containerName)) + if len(appInstalls) > 0 { + err = buserr.New("ErrContainerName") + return + } + containerExist := false + containerExist, err = checkContainerNameIsExist(req.ContainerName, appInstall.GetPath()) + if err != nil { + return + } + if containerExist { + err = buserr.New("ErrContainerName") + return + } + } + req.Params[constant.ContainerName] = containerName + appInstall.ContainerName = containerName + + index := 0 + serviceName := "" + for k := range servicesMap { + serviceName = k + if index > 0 { + continue + } + index++ + } + newServiceName := strings.ToLower(appInstall.Name) + if app.Limit == 0 && newServiceName != serviceName && len(servicesMap) == 1 { + servicesMap[newServiceName] = servicesMap[serviceName] + delete(servicesMap, serviceName) + serviceName = newServiceName + } + appInstall.ServiceName = serviceName + + if err = addDockerComposeCommonParam(composeMap, appInstall.ServiceName, req.AppContainerConfig, req.Params); err != nil { + return + } + var ( + composeByte []byte + paramByte []byte + ) + + composeByte, err = yaml.Marshal(composeMap) + if err != nil { + return + } + appInstall.DockerCompose = string(composeByte) + + if hostName, ok := req.Params["PANEL_DB_HOST"]; ok { + database, _ := databaseRepo.Get(repo.WithByName(hostName.(string))) + if !reflect.DeepEqual(database, model.Database{}) { + req.Params["PANEL_DB_HOST"] = database.Address + req.Params["PANEL_DB_PORT"] = database.Port + req.Params["PANEL_DB_HOST_NAME"] = hostName + req.Params["DATABASE_NAME"] = database.Name + } + } + if app.Key == "openresty" { + req.Params["CONTAINER_PACKAGE_URL"] = "http://archive.ubuntu.com/ubuntu/" + req.Params["RESTY_ADD_PACKAGE_BUILDDEPS"] = "" + req.Params["RESTY_CONFIG_OPTIONS_MORE"] = "" + } + if app.Key == "openresty" && (app.Resource == "remote" || app.Resource == "custom") && common.CompareVersion(appDetail.Version, "1.27") { + if dir, ok := req.Params["WEBSITE_DIR"]; ok { + siteDir := dir.(string) + if siteDir == "" || !strings.HasPrefix(siteDir, "/") { + siteDir = path.Join(global.Dir.DataDir, dir.(string)) + } + req.Params["WEBSITE_DIR"] = siteDir + } + } + paramByte, err = json.Marshal(req.Params) + if err != nil { + return + } + appInstall.Env = string(paramByte) + + if err = appInstallRepo.Create(context.Background(), appInstall); err != nil { + return + } + + installTask, err := task.NewTaskWithOps(appInstall.Name, task.TaskInstall, task.TaskScopeApp, req.TaskID, appInstall.ID) + if err != nil { + return + } + + if err = createLink(context.Background(), installTask, app, appInstall, req.Params); err != nil { + return + } + + installApp := func(t *task.Task) error { + if err = copyData(t, app, appDetail, appInstall, req); err != nil { + return err + } + if err = runScript(t, appInstall, "init"); err != nil { + return err + } + if app.Key == "openresty" { + if err = handleSiteDir(app, appDetail, req, t); err != nil { + return err + } + if err = handleOpenrestyFile(appInstall); err != nil { + return err + } + } + if err = upApp(t, appInstall, req.PullImage); err != nil { + return err + } + updateToolApp(appInstall) + return nil + } + + handleAppStatus := func(t *task.Task) { + appInstall.Status = constant.StatusUpErr + appInstall.Message = installTask.Task.ErrorMsg + _ = appInstallRepo.Save(context.Background(), appInstall) + } + + installTask.AddSubTask(task.GetTaskName(appInstall.Name, task.TaskInstall, task.TaskScopeApp), installApp, handleAppStatus) + + go func() { + if taskErr := installTask.Execute(); taskErr != nil { + appInstall.Status = constant.StatusInstallErr + appInstall.Message = taskErr.Error() + if strings.Contains(taskErr.Error(), "Timeout") && strings.Contains(taskErr.Error(), "Pulling") { + appInstall.Message = buserr.New("PullImageTimeout").Error() + appInstall.Message + } + _ = appInstallRepo.Save(context.Background(), appInstall) + } + }() + + return +} + +func (a AppService) SyncAppListFromLocal(TaskID string) { + var ( + err error + dirEntries []os.DirEntry + localApps []model.App + ) + + syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("LocalApp"), task.TaskSync, task.TaskScopeAppStore, TaskID, 0) + if err != nil { + global.LOG.Errorf("Create sync task failed %v", err) + return + } + + syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("LocalApp"), task.TaskSync, task.TaskScopeAppStore), func(t *task.Task) (err error) { + fileOp := files.NewFileOp() + localAppDir := global.Dir.LocalAppResourceDir + if !fileOp.Stat(localAppDir) { + return nil + } + dirEntries, err = os.ReadDir(localAppDir) + if err != nil { + return + } + for _, dirEntry := range dirEntries { + if dirEntry.IsDir() { + appDir := filepath.Join(localAppDir, dirEntry.Name()) + appDirEntries, err := os.ReadDir(appDir) + if err != nil { + t.Log(i18n.GetWithNameAndErr("ErrAppDirNull", dirEntry.Name(), err)) + continue + } + app, err := handleLocalApp(appDir) + if err != nil { + t.Log(i18n.GetWithNameAndErr("LocalAppErr", dirEntry.Name(), err)) + continue + } + var appDetails []model.AppDetail + for _, appDirEntry := range appDirEntries { + if appDirEntry.IsDir() { + appDetail := model.AppDetail{ + Version: appDirEntry.Name(), + Status: constant.AppNormal, + } + versionDir := filepath.Join(appDir, appDirEntry.Name()) + if err = handleLocalAppDetail(versionDir, &appDetail); err != nil { + t.Log(i18n.GetMsgWithMap("LocalAppVersionErr", map[string]interface{}{"name": app.Name, "version": appDetail.Version, "err": err.Error()})) + continue + } + appDetails = append(appDetails, appDetail) + } + } + if len(appDetails) > 0 { + app.Details = appDetails + localApps = append(localApps, *app) + } else { + t.Log(i18n.GetWithName("LocalAppVersionNull", app.Name)) + } + } + } + + var ( + newApps []model.App + deleteApps []model.App + updateApps []model.App + oldAppIds []uint + + deleteAppIds []uint + deleteAppDetails []model.AppDetail + newAppDetails []model.AppDetail + updateDetails []model.AppDetail + + appTags []*model.AppTag + ) + + oldApps, _ := appRepo.GetBy(appRepo.WithResource(constant.AppResourceLocal)) + apps := make(map[string]model.App, len(oldApps)) + for _, old := range oldApps { + old.Status = constant.AppTakeDown + apps[old.Key] = old + } + for _, app := range localApps { + if oldApp, ok := apps[app.Key]; ok { + app.ID = oldApp.ID + appDetails := make(map[string]model.AppDetail, len(oldApp.Details)) + for _, old := range oldApp.Details { + old.Status = constant.AppTakeDown + appDetails[old.Version] = old + } + for i, newDetail := range app.Details { + version := newDetail.Version + newDetail.Status = constant.AppNormal + newDetail.AppId = app.ID + oldDetail, exist := appDetails[version] + if exist { + newDetail.ID = oldDetail.ID + delete(appDetails, version) + } + app.Details[i] = newDetail + } + for _, v := range appDetails { + app.Details = append(app.Details, v) + } + } + app.TagsKey = append(app.TagsKey, constant.AppResourceLocal) + apps[app.Key] = app + } + + for _, app := range apps { + if app.ID == 0 { + newApps = append(newApps, app) + } else { + oldAppIds = append(oldAppIds, app.ID) + if app.Status == constant.AppTakeDown { + installs, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(app.ID)) + if len(installs) > 0 { + updateApps = append(updateApps, app) + continue + } + deleteAppIds = append(deleteAppIds, app.ID) + deleteApps = append(deleteApps, app) + deleteAppDetails = append(deleteAppDetails, app.Details...) + } else { + updateApps = append(updateApps, app) + } + } + + } + + tags, _ := tagRepo.All() + tagMap := make(map[string]uint, len(tags)) + for _, tag := range tags { + tagMap[tag.Key] = tag.ID + } + + tx, ctx := getTxAndContext() + defer tx.Rollback() + if len(newApps) > 0 { + if err = appRepo.BatchCreate(ctx, newApps); err != nil { + return + } + } + for _, update := range updateApps { + if err = appRepo.Save(ctx, &update); err != nil { + return + } + } + if len(deleteApps) > 0 { + if err = appRepo.BatchDelete(ctx, deleteApps); err != nil { + return + } + if err = appDetailRepo.DeleteByAppIds(ctx, deleteAppIds); err != nil { + return + } + } + + if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil { + return + } + for _, newApp := range newApps { + if newApp.ID > 0 { + for _, detail := range newApp.Details { + detail.AppId = newApp.ID + newAppDetails = append(newAppDetails, detail) + } + } + } + for _, update := range updateApps { + for _, detail := range update.Details { + if detail.ID == 0 { + detail.AppId = update.ID + newAppDetails = append(newAppDetails, detail) + } else { + if detail.Status == constant.AppNormal { + updateDetails = append(updateDetails, detail) + } else { + deleteAppDetails = append(deleteAppDetails, detail) + } + } + } + } + + allApps := append(newApps, updateApps...) + for _, app := range allApps { + for _, t := range app.TagsKey { + tagId, ok := tagMap[t] + if ok { + appTags = append(appTags, &model.AppTag{ + AppId: app.ID, + TagId: tagId, + }) + } + } + } + + if len(newAppDetails) > 0 { + if err = appDetailRepo.BatchCreate(ctx, newAppDetails); err != nil { + return + } + } + + for _, updateAppDetail := range updateDetails { + if err = appDetailRepo.Update(ctx, updateAppDetail); err != nil { + return + } + } + + if len(deleteAppDetails) > 0 { + if err = appDetailRepo.BatchDelete(ctx, deleteAppDetails); err != nil { + return + } + } + + if len(oldAppIds) > 0 { + if err = appTagRepo.DeleteByAppIds(ctx, oldAppIds); err != nil { + return + } + } + + if len(appTags) > 0 { + if err = appTagRepo.BatchCreate(ctx, appTags); err != nil { + return + } + } + tx.Commit() + global.LOG.Infof("Synchronization of local applications completed") + return nil + }, nil) + go func() { + _ = syncTask.Execute() + }() +} + +func (a AppService) GetAppUpdate() (*response.AppUpdateRes, error) { + res := &response.AppUpdateRes{ + CanUpdate: false, + } + mysql, _ := appRepo.GetFirst(appRepo.WithKey("mysql")) + if !mysql.BatchInstallSupport { + res.CanUpdate = true + return res, nil + } + + versionUrl := fmt.Sprintf("%s/%s/1panel.json.version.txt", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode) + _, versionRes, err := req_helper.HandleRequest(versionUrl, http.MethodGet, constant.TimeOut20s) + if err != nil { + return nil, err + } + lastModifiedStr := string(versionRes) + lastModified, err := strconv.Atoi(lastModifiedStr) + if err != nil { + return nil, err + } + setting, err := NewISettingService().GetSettingInfo() + if err != nil { + return nil, err + } + if setting.AppStoreSyncStatus == constant.StatusSyncing { + res.IsSyncing = true + return res, nil + } + + appStoreLastModified, _ := strconv.Atoi(setting.AppStoreLastModified) + res.AppStoreLastModified = appStoreLastModified + if setting.AppStoreLastModified == "" || lastModified != appStoreLastModified { + res.CanUpdate = true + return res, err + } + apps, _ := appRepo.GetBy(appRepo.WithResource(constant.AppResourceRemote)) + for _, app := range apps { + if app.Icon == "" { + res.CanUpdate = true + return res, err + } + } + + list, err := getAppList() + if err != nil { + return res, err + } + if list.Extra.Version != "" && setting.SystemVersion != list.Extra.Version && !common.CompareVersion(setting.SystemVersion, list.Extra.Version) { + global.LOG.Errorf("The current version %s is too low to synchronize with the App Store. The minimum required version is %s", setting.SystemVersion, list.Extra.Version) + return nil, buserr.New("ErrVersionTooLow") + } + res.AppList = list + return res, nil +} + +func getAppFromRepo(downloadPath string) error { + downloadUrl := downloadPath + global.LOG.Infof("[AppStore] download file from %s", downloadUrl) + fileOp := files.NewFileOp() + packagePath := filepath.Join(global.Dir.ResourceDir, filepath.Base(downloadUrl)) + if err := files.DownloadFileWithProxy(downloadUrl, packagePath); err != nil { + return err + } + + if err := fileOp.Decompress(packagePath, global.Dir.ResourceDir, files.SdkZip, ""); err != nil { + return err + } + defer func() { + _ = fileOp.DeleteFile(packagePath) + }() + return nil +} + +func getAppList() (*dto.AppList, error) { + list := &dto.AppList{} + if err := getAppFromRepo(fmt.Sprintf("%s/%s/1panel.json.zip", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode)); err != nil { + return nil, err + } + listFile := filepath.Join(global.Dir.ResourceDir, "1panel.json") + file, err := os.Open(listFile) + if err != nil { + return nil, err + } + defer file.Close() + if err = json.NewDecoder(file).Decode(list); err != nil { + return nil, err + } + + return list, nil +} + +var InitTypes = map[string]struct{}{ + "runtime": {}, + "php": {}, + "node": {}, +} + +func deleteCustomApp() { + var appIDS []uint + installs, _ := appInstallRepo.ListBy(context.Background()) + for _, install := range installs { + appIDS = append(appIDS, install.AppId) + } + var ops []repo.DBOption + ops = append(ops, repo.WithByIDNotIn(appIDS)) + if len(appIDS) > 0 { + ops = append(ops, repo.WithByIDNotIn(appIDS)) + } + apps, _ := appRepo.GetBy(ops...) + var deleteIDS []uint + for _, app := range apps { + if app.Resource == constant.AppResourceCustom { + deleteIDS = append(deleteIDS, app.ID) + } + } + _ = appRepo.DeleteByIDs(context.Background(), deleteIDS) + _ = appDetailRepo.DeleteByAppIds(context.Background(), deleteIDS) +} + +func (a AppService) SyncAppListFromRemote(taskID string) (err error) { + if xpack.IsUseCustomApp() { + return nil + } + syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore, taskID, 0) + if err != nil { + return err + } + syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("App"), task.TaskSync, task.TaskScopeAppStore), a.syncAppStoreTask, nil) + + go func() { + if err := syncTask.Execute(); err != nil { + _ = NewISettingService().Update("AppStoreLastModified", "0") + _ = NewISettingService().Update("AppStoreSyncStatus", constant.StatusError) + } + }() + + return nil +} + +func (a AppService) GetAppIcon(key string) ([]byte, error) { + app, err := appRepo.GetFirst(appRepo.WithKey(key)) + if err != nil { + return nil, err + } + iconBytes, err := base64.StdEncoding.DecodeString(app.Icon) + if err != nil { + return nil, err + } + return iconBytes, nil +} diff --git a/agent/app/service/app_ingore_upgrade.go b/agent/app/service/app_ingore_upgrade.go new file mode 100644 index 0000000..720b6a0 --- /dev/null +++ b/agent/app/service/app_ingore_upgrade.go @@ -0,0 +1,76 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +type AppIgnoreUpgradeService struct { +} + +type IAppIgnoreUpgradeService interface { + List() ([]response.AppIgnoreUpgradeDTO, error) + CreateAppIgnore(req request.AppIgnoreUpgradeReq) error + Delete(req request.ReqWithID) error +} + +func NewIAppIgnoreUpgradeService() IAppIgnoreUpgradeService { + return AppIgnoreUpgradeService{} +} + +func (a AppIgnoreUpgradeService) List() ([]response.AppIgnoreUpgradeDTO, error) { + var res []response.AppIgnoreUpgradeDTO + ignores, err := appIgnoreUpgradeRepo.List() + if err != nil { + return nil, err + } + for _, ignore := range ignores { + dto := response.AppIgnoreUpgradeDTO{ + ID: ignore.ID, + AppID: ignore.AppID, + AppDetailID: ignore.AppDetailID, + Scope: ignore.Scope, + } + app, err := appRepo.GetFirst(repo.WithByID(ignore.AppID)) + if errors.Is(err, gorm.ErrRecordNotFound) { + _ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID)) + continue + } + dto.Name = app.Name + if ignore.Scope == "version" { + appDetail, err := appDetailRepo.GetFirst(repo.WithByID(ignore.AppDetailID)) + if errors.Is(err, gorm.ErrRecordNotFound) { + _ = appIgnoreUpgradeRepo.Delete(repo.WithByID(ignore.ID)) + continue + } + dto.Version = appDetail.Version + } + res = append(res, dto) + } + return res, nil +} + +func (a AppIgnoreUpgradeService) CreateAppIgnore(req request.AppIgnoreUpgradeReq) error { + appIgnoreUpgrade := model.AppIgnoreUpgrade{ + AppID: req.AppID, + Scope: req.Scope, + } + if req.Scope == "version" { + appIgnoreUpgrade.AppDetailID = req.AppDetailID + } + if req.Scope == "all" { + _ = appIgnoreUpgradeRepo.Delete(appInstallRepo.WithAppId(req.AppID)) + } + if err := appIgnoreUpgradeRepo.Create(&appIgnoreUpgrade); err != nil { + return err + } + return nil +} + +func (a AppIgnoreUpgradeService) Delete(req request.ReqWithID) error { + return appIgnoreUpgradeRepo.Delete(repo.WithByID(req.ID)) +} diff --git a/agent/app/service/app_install.go b/agent/app/service/app_install.go new file mode 100644 index 0000000..61d9c1c --- /dev/null +++ b/agent/app/service/app_install.go @@ -0,0 +1,969 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "math" + "net/http" + "os" + "path" + "path/filepath" + "reflect" + "sort" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/env" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/req_helper" + "github.com/docker/docker/api/types/container" + "github.com/joho/godotenv" + "github.com/pkg/errors" + "gopkg.in/yaml.v3" +) + +type AppInstallService struct { +} + +type IAppInstallService interface { + Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error) + CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error) + LoadPort(req dto.OperationWithNameAndType) (int64, error) + LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error) + SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error) + Operate(req request.AppInstalledOperate) error + Update(req request.AppInstalledUpdate) error + SyncAll(systemInit bool) error + GetServices(key string) ([]response.AppService, error) + GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error) + GetParams(id uint) (*response.AppConfig, error) + ChangeAppPort(req request.PortUpdate) error + GetDefaultConfigByKey(key, name string) (string, error) + DeleteCheck(installId uint) ([]dto.AppResource, error) + + UpdateAppConfig(req request.AppConfigUpdate) error + GetInstallList() ([]dto.AppInstallInfo, error) + GetAppInstallInfo(appInstallID uint) (*response.AppInstallInfo, error) +} + +func NewIAppInstalledService() IAppInstallService { + return &AppInstallService{} +} + +func (a *AppInstallService) GetInstallList() ([]dto.AppInstallInfo, error) { + var datas []dto.AppInstallInfo + appInstalls, err := appInstallRepo.ListBy(context.Background()) + if err != nil { + return nil, err + } + for _, install := range appInstalls { + datas = append(datas, dto.AppInstallInfo{ID: install.ID, Key: install.App.Key, Name: install.Name}) + } + return datas, nil +} + +func (a *AppInstallService) Page(req request.AppInstalledSearch) (int64, []response.AppInstallDTO, error) { + var ( + opts []repo.DBOption + total int64 + installs []model.AppInstall + err error + ) + opts = append(opts, repo.WithOrderRuleBy("favorite", "descending")) + + if req.Name != "" { + opts = append(opts, repo.WithByLikeName(req.Name)) + } + + if len(req.Tags) != 0 { + tags, err := tagRepo.GetByKeys(req.Tags) + if err != nil { + return 0, nil, err + } + var tagIds []uint + for _, t := range tags { + tagIds = append(tagIds, t.ID) + } + appTags, err := appTagRepo.GetByTagIds(tagIds) + if err != nil { + return 0, nil, err + } + var appIds []uint + for _, t := range appTags { + appIds = append(appIds, t.AppId) + } + + opts = append(opts, appInstallRepo.WithAppIdsIn(appIds)) + } + + if req.Update { + installs, err = appInstallRepo.ListBy(context.Background(), opts...) + if err != nil { + return 0, nil, err + } + } else { + total, installs, err = appInstallRepo.Page(req.Page, req.PageSize, opts...) + if err != nil { + return 0, nil, err + } + } + + installDTOs, _ := handleInstalled(installs, req.Update, req.Sync, req.CheckUpdate) + if req.Update { + total = int64(len(installDTOs)) + } + return total, installDTOs, nil +} + +func (a *AppInstallService) CheckExist(req request.AppInstalledInfo) (*response.AppInstalledCheck, error) { + res := &response.AppInstalledCheck{ + IsExist: false, + } + + app, err := appRepo.GetFirst(appRepo.WithKey(req.Key)) + if err != nil { + return res, nil + } + res.App = app.Name + + var appInstall model.AppInstall + if len(req.Name) == 0 { + appInstall, _ = appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID)) + } else { + appInstall, _ = appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID), repo.WithByName(req.Name)) + } + + if reflect.DeepEqual(appInstall, model.AppInstall{}) { + return res, nil + } + if err = syncAppInstallStatus(&appInstall, false); err != nil { + return nil, err + } + + res.ContainerName = appInstall.ContainerName + res.Name = appInstall.Name + res.Version = appInstall.Version + res.CreatedAt = appInstall.CreatedAt + res.Status = appInstall.Status + res.AppInstallID = appInstall.ID + res.IsExist = true + res.InstallPath = path.Join(global.Dir.AppInstallDir, appInstall.App.Key, appInstall.Name) + res.HttpPort = appInstall.HttpPort + res.HttpsPort = appInstall.HttpsPort + + if appInstall.App.Key == "openresty" { + websiteDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR") + res.WebsiteDir = websiteDir + } + + return res, nil +} + +func (a *AppInstallService) LoadPort(req dto.OperationWithNameAndType) (int64, error) { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return int64(0), nil + } + return app.Port, nil +} + +func (a *AppInstallService) LoadConnInfo(req dto.OperationWithNameAndType) (response.DatabaseConn, error) { + var data response.DatabaseConn + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return data, nil + } + data.Status = app.Status + data.Username = app.UserName + data.Password = app.Password + data.ServiceName = app.ServiceName + data.Port = app.Port + data.ContainerName = app.ContainerName + return data, nil +} + +func (a *AppInstallService) SearchForWebsite(req request.AppInstalledSearch) ([]response.AppInstallDTO, error) { + var ( + installs []model.AppInstall + err error + opts []repo.DBOption + ) + if req.Type != "" { + if req.Type == "proxy" { + phpApps, _ := appRepo.GetBy(appRepo.WithType("php")) + var ids []uint + for _, app := range phpApps { + ids = append(ids, app.ID) + } + runtimeApps, _ := appRepo.GetBy(appRepo.WithType("runtime")) + for _, app := range runtimeApps { + ids = append(ids, app.ID) + } + opts = append(opts, appInstallRepo.WithAppIdsNotIn(ids)) + } else { + apps, err := appRepo.GetBy(appRepo.WithType(req.Type)) + if err != nil { + return nil, err + } + var ids []uint + for _, app := range apps { + ids = append(ids, app.ID) + } + if req.Unused { + opts = append(opts, appInstallRepo.WithIdNotInWebsite()) + } + opts = append(opts, appInstallRepo.WithAppIdsIn(ids)) + } + installs, err = appInstallRepo.ListBy(context.Background(), opts...) + if err != nil { + return nil, err + } + } else { + installs, err = appInstallRepo.ListBy(context.Background()) + if err != nil { + return nil, err + } + } + + return handleInstalled(installs, false, true, false) +} + +func (a *AppInstallService) Operate(req request.AppInstalledOperate) error { + install, err := appInstallRepo.GetFirstByCtx(context.Background(), repo.WithByID(req.InstallId)) + if err != nil { + return err + } + if !req.ForceDelete && !files.NewFileOp().Stat(install.GetPath()) { + return buserr.New("ErrInstallDirNotFound") + } + dockerComposePath := install.GetComposePath() + switch req.Operate { + case constant.Rebuild: + return rebuildApp(install) + case constant.Start: + out, err := compose.Up(dockerComposePath) + if err != nil { + return handleErr(install, err, out) + } + return syncAppInstallStatus(&install, false) + case constant.Stop: + out, err := compose.Stop(dockerComposePath) + if err != nil { + return handleErr(install, err, out) + } + return syncAppInstallStatus(&install, false) + case constant.Restart: + out, err := compose.Restart(dockerComposePath) + if err != nil { + return handleErr(install, err, out) + } + return syncAppInstallStatus(&install, false) + case constant.Delete: + deleteReq := request.AppInstallDelete{ + Install: install, + DeleteBackup: req.DeleteBackup, + ForceDelete: req.ForceDelete, + DeleteDB: req.DeleteDB, + DeleteImage: req.DeleteImage, + TaskID: req.TaskID, + } + if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete { + return err + } + return nil + case constant.Sync: + return syncAppInstallStatus(&install, true) + case constant.Upgrade: + upgradeReq := request.AppInstallUpgrade{ + InstallID: install.ID, + DetailID: req.DetailId, + Backup: req.Backup, + PullImage: req.PullImage, + DockerCompose: req.DockerCompose, + TaskID: req.TaskID, + } + return upgradeInstall(upgradeReq) + case constant.Reload: + return opNginx(install.ContainerName, constant.NginxReload) + case constant.Favorite: + install.Favorite = req.Favorite + return appInstallRepo.Save(context.Background(), &install) + default: + return errors.New("operate not support") + } +} + +func (a *AppInstallService) UpdateAppConfig(req request.AppConfigUpdate) error { + installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID)) + if err != nil { + return err + } + installed.WebUI = "" + if req.WebUI != "" { + installed.WebUI = req.WebUI + } + return appInstallRepo.Save(context.Background(), &installed) +} + +func (a *AppInstallService) Update(req request.AppInstalledUpdate) error { + installed, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallId)) + if err != nil { + return err + } + changePort := false + port, ok := req.Params["PANEL_APP_PORT_HTTP"] + if ok { + portN := int(math.Ceil(port.(float64))) + if portN != installed.HttpPort { + changePort = true + httpPort, err := checkPort("PANEL_APP_PORT_HTTP", req.Params) + if err != nil { + return err + } + installed.HttpPort = httpPort + } + } + ports, ok := req.Params["PANEL_APP_PORT_HTTPS"] + if ok { + portN := int(math.Ceil(ports.(float64))) + if portN != installed.HttpsPort { + httpsPort, err := checkPort("PANEL_APP_PORT_HTTPS", req.Params) + if err != nil { + return err + } + installed.HttpsPort = httpsPort + } + } + + backupDockerCompose := installed.DockerCompose + if req.Advanced { + composeMap := make(map[string]interface{}) + if req.EditCompose { + if err = yaml.Unmarshal([]byte(req.DockerCompose), &composeMap); err != nil { + return err + } + } else { + if err = yaml.Unmarshal([]byte(installed.DockerCompose), &composeMap); err != nil { + return err + } + } + if err = addDockerComposeCommonParam(composeMap, installed.ServiceName, req.AppContainerConfig, req.Params); err != nil { + return err + } + composeByte, err := yaml.Marshal(composeMap) + if err != nil { + return err + } + installed.DockerCompose = string(composeByte) + if req.ContainerName == "" { + req.Params[constant.ContainerName] = installed.ContainerName + } else { + req.Params[constant.ContainerName] = req.ContainerName + if installed.ContainerName != req.ContainerName { + exist, _ := appInstallRepo.GetFirst(appInstallRepo.WithContainerName(req.ContainerName), appInstallRepo.WithIDNotIs(installed.ID)) + if exist.ID > 0 { + return buserr.New("ErrContainerName") + } + containerExist, err := checkContainerNameIsExist(req.ContainerName, installed.GetPath()) + if err != nil { + return err + } + if containerExist { + return buserr.New("ErrContainerName") + } + installed.ContainerName = req.ContainerName + } + } + } + + envPath := path.Join(installed.GetPath(), ".env") + oldEnvMaps, err := godotenv.Read(envPath) + if err != nil { + return err + } + backupEnvMaps := oldEnvMaps + handleMap(req.Params, oldEnvMaps) + paramByte, err := json.Marshal(oldEnvMaps) + if err != nil { + return err + } + installed.Env = string(paramByte) + if err := env.Write(oldEnvMaps, envPath); err != nil { + return err + } + fileOp := files.NewFileOp() + _ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(installed.DockerCompose), constant.DirPerm) + if err := rebuildApp(installed); err != nil { + _ = env.Write(backupEnvMaps, envPath) + _ = fileOp.WriteFile(installed.GetComposePath(), strings.NewReader(backupDockerCompose), constant.DirPerm) + return err + } + installed.Status = constant.StatusRunning + _ = appInstallRepo.Save(context.Background(), &installed) + + website, _ := websiteRepo.GetFirst(websiteRepo.WithAppInstallId(installed.ID)) + if changePort && website.ID != 0 && website.Status == constant.StatusRunning { + go func() { + nginxInstall, err := getNginxFull(&website) + if err != nil { + global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", err).Error()) + return + } + config := nginxInstall.SiteConfig.Config + servers := config.FindServers() + if len(servers) == 0 { + global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", errors.New("nginx config is not valid")).Error()) + return + } + server := servers[0] + proxy := fmt.Sprintf("http://127.0.0.1:%d", installed.HttpPort) + server.UpdateRootProxy([]string{proxy}) + + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", err).Error()) + return + } + if err := nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName); err != nil { + global.LOG.Error(buserr.WithErr("ErrUpdateBuWebsite", err).Error()) + return + } + }() + } + return nil +} + +func (a *AppInstallService) SyncAll(systemInit bool) error { + allList, err := appInstallRepo.ListBy(context.Background()) + if err != nil { + return err + } + for _, i := range allList { + if i.Status == constant.StatusInstalling || i.Status == constant.StatusUpgrading || i.Status == constant.StatusRebuilding || i.Status == constant.StatusUninstalling { + if systemInit { + i.Status = constant.StatusError + i.Message = "1Panel restart causes the task to terminate" + _ = appInstallRepo.Save(context.Background(), &i) + } + continue + } + if i.Status == constant.StatusWaitingRestart { + dockerComposePath := i.GetComposePath() + _, _ = compose.Up(dockerComposePath) + i.Status = constant.StatusRunning + _ = appInstallRepo.Save(context.Background(), &i) + continue + } + if !systemInit { + if err = syncAppInstallStatus(&i, false); err != nil { + global.LOG.Errorf("sync install app[%s] error,mgs: %s", i.Name, err.Error()) + } + } + } + return nil +} + +func (a *AppInstallService) GetServices(key string) ([]response.AppService, error) { + var res []response.AppService + if DatabaseKeys[key] > 0 { + types := []string{key} + if key == constant.AppPostgres { + key = constant.AppPostgresql + } + switch key { + case constant.AppMysql: + types = []string{constant.AppMysql, constant.AppMysqlCluster} + case constant.AppPostgresql: + types = []string{constant.AppPostgresql, constant.AppPostgresqlCluster} + case constant.AppRedis: + types = []string{constant.AppRedis, constant.AppRedisCluster} + } + + dbs, _ := databaseRepo.GetList(repo.WithTypes(types)) + if len(dbs) == 0 { + return res, nil + } + for _, db := range dbs { + service := response.AppService{ + Label: db.Name, + Value: db.Name, + } + if db.AppInstallID > 0 { + install, err := appInstallRepo.GetFirst(repo.WithByID(db.AppInstallID)) + if err != nil { + return nil, err + } + paramMap := make(map[string]string) + if install.Param != "" { + _ = json.Unmarshal([]byte(install.Param), ¶mMap) + } + service.Config = paramMap + service.From = constant.AppResourceLocal + service.Status = install.Status + } else { + service.From = constant.AppResourceRemote + service.Status = constant.StatusRunning + service.Config = map[string]string{"PANEL_DB_ROOT_PASSWORD": db.Password, "PANEL_DB_ROOT_USER": db.Username} + } + res = append(res, service) + } + } else { + app, err := appRepo.GetFirst(appRepo.WithKey(key)) + if err != nil { + return nil, err + } + installs, err := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(app.ID), appInstallRepo.WithStatus(constant.StatusRunning)) + if err != nil { + return nil, err + } + for _, install := range installs { + paramMap := make(map[string]string) + if install.Param != "" { + _ = json.Unmarshal([]byte(install.Param), ¶mMap) + } + res = append(res, response.AppService{ + Label: install.Name, + Value: install.ServiceName, + Config: paramMap, + Status: install.Status, + }) + } + } + return res, nil +} + +func (a *AppInstallService) GetUpdateVersions(req request.AppUpdateVersion) ([]dto.AppVersion, error) { + install, err := appInstallRepo.GetFirst(repo.WithByID(req.AppInstallID)) + var versions []dto.AppVersion + if err != nil { + return versions, err + } + app, err := appRepo.GetFirst(repo.WithByID(install.AppId)) + if err != nil { + return versions, err + } + details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID)) + if err != nil { + return versions, err + } + for _, detail := range details { + ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(detail.ID), appIgnoreUpgradeRepo.WithScope("version")) + if len(ignores) > 0 { + continue + } + if common.IsCrossVersion(install.Version, detail.Version) && !app.CrossVersionUpdate { + continue + } + if common.CompareVersion(detail.Version, install.Version) { + var newCompose string + if req.UpdateVersion != "" && req.UpdateVersion == detail.Version && detail.DockerCompose == "" && !app.IsLocalApp() { + filename := filepath.Base(detail.DownloadUrl) + dockerComposeUrl := fmt.Sprintf("%s%s", strings.TrimSuffix(detail.DownloadUrl, filename), "docker-compose.yml") + statusCode, composeRes, err := req_helper.HandleRequest(dockerComposeUrl, http.MethodGet, constant.TimeOut20s) + if err != nil { + return versions, err + } + if statusCode > 200 { + return versions, err + } + detail.DockerCompose = string(composeRes) + _ = appDetailRepo.Update(context.Background(), detail) + } + newCompose, err = getUpgradeCompose(install, detail) + if err != nil { + return versions, err + } + if app.Key == constant.AppMysql { + majorVersion := getMajorVersion(install.Version) + if !strings.HasPrefix(detail.Version, majorVersion) { + continue + } + } + versions = append(versions, dto.AppVersion{ + Version: detail.Version, + DetailId: detail.ID, + DockerCompose: newCompose, + }) + } + } + sort.Slice(versions, func(i, j int) bool { + return common.CompareVersion(versions[i].Version, versions[j].Version) + }) + return versions, nil +} + +func (a *AppInstallService) ChangeAppPort(req request.PortUpdate) error { + if common.ScanPort(int(req.Port)) { + return buserr.WithDetail("ErrPortInUsed", req.Port, nil) + } + + appInstall, err := appInstallRepo.LoadBaseInfo(req.Key, req.Name) + if err != nil { + return nil + } + + if err := updateInstallInfoInDB(req.Key, req.Name, "port", strconv.FormatInt(req.Port, 10)); err != nil { + return nil + } + + appRess, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(appInstall.ID)) + for _, appRes := range appRess { + appInstall, err := appInstallRepo.GetFirst(repo.WithByID(appRes.AppInstallId)) + if err != nil { + return err + } + if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, appInstall.App.Key, appInstall.Name)); err != nil { + global.LOG.Errorf("docker-compose restart %s[%s] failed, err: %v", appInstall.App.Key, appInstall.Name, err) + } + } + + return nil +} + +func (a *AppInstallService) DeleteCheck(installID uint) ([]dto.AppResource, error) { + var res []dto.AppResource + appInstall, err := appInstallRepo.GetFirst(repo.WithByID(installID)) + if err != nil { + return nil, err + } + websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(appInstall.ID)) + for _, website := range websites { + res = append(res, dto.AppResource{ + Type: "website", + Name: website.PrimaryDomain, + }) + } + resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(appInstall.ID), repo.WithByFrom(constant.AppResourceLocal)) + for _, resource := range resources { + linkInstall, _ := appInstallRepo.GetFirst(repo.WithByID(resource.AppInstallId)) + if linkInstall.ID > 0 { + res = append(res, dto.AppResource{ + Type: "app", + Name: linkInstall.Name, + }) + } else { + _ = appInstallResourceRepo.DeleteBy(context.Background(), appInstallResourceRepo.WithAppInstallId(resource.AppInstallId)) + } + } + return res, nil +} + +func (a *AppInstallService) GetDefaultConfigByKey(key, name string) (string, error) { + baseInfo, err := appInstallRepo.LoadBaseInfo(key, name) + if err != nil { + return "", err + } + + fileOp := files.NewFileOp() + filePath := path.Join(global.Dir.AppResourceDir, "remote", baseInfo.Key, baseInfo.Version, "conf") + if !fileOp.Stat(filePath) { + filePath = path.Join(global.Dir.AppResourceDir, baseInfo.Key, "versions", baseInfo.Version, "conf") + } + if !fileOp.Stat(filePath) { + return "", buserr.New("ErrPathNotFound") + } + + if key == constant.AppMysql || key == constant.AppMariaDB || key == constant.AppMysqlCluster { + filePath = path.Join(filePath, "my.cnf") + } + if key == constant.AppRedis || key == constant.AppRedisCluster { + filePath = path.Join(filePath, "redis.conf") + } + if key == constant.AppOpenresty { + filePath = path.Join(filePath, "nginx.conf") + } + contentByte, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + return string(contentByte), nil +} + +func (a *AppInstallService) GetParams(id uint) (*response.AppConfig, error) { + var ( + params []response.AppParam + appForm dto.AppForm + envs = make(map[string]interface{}) + res response.AppConfig + ) + install, err := appInstallRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return nil, err + } + detail, err := appDetailRepo.GetFirst(repo.WithByID(install.AppDetailId)) + if err != nil { + return nil, err + } + if err = json.Unmarshal([]byte(detail.Params), &appForm); err != nil { + return nil, err + } + if err = json.Unmarshal([]byte(install.Env), &envs); err != nil { + return nil, err + } + for _, form := range appForm.FormFields { + if v, ok := envs[form.EnvKey]; ok { + appParam := response.AppParam{ + Edit: false, + Key: form.EnvKey, + Rule: form.Rule, + Type: form.Type, + Multiple: form.Multiple, + Required: form.Required, + } + if form.Edit { + appParam.Edit = true + } + appParam.LabelZh = form.LabelZh + appParam.LabelEn = form.LabelEn + appParam.Label = form.Label + appParam.Value = v + if form.Type == "service" { + appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(v.(string))) + appParam.ShowValue = appInstall.Name + } else if form.Type == "select" { + if form.Multiple { + if v == "" { + appParam.Value = []string{} + } else { + if str, ok := v.(string); ok { + appParam.Value = strings.Split(str, ",") + } + } + } else { + for _, fv := range form.Values { + if fv.Value == v { + appParam.ShowValue = fv.Label + break + } + } + } + appParam.Values = form.Values + } else if form.Type == "apps" { + if m, ok := form.Child.(map[string]interface{}); ok { + result := make(map[string]string) + for key, value := range m { + if strVal, ok := value.(string); ok { + result[key] = strVal + } + } + if envKey, ok := result["envKey"]; ok { + serviceName := envs[envKey] + if serviceName != nil { + appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithServiceName(serviceName.(string))) + appParam.ShowValue = appInstall.Name + } + } + } + } + + params = append(params, appParam) + } else { + params = append(params, response.AppParam{ + Edit: form.Edit, + Key: form.EnvKey, + Rule: form.Rule, + Type: form.Type, + LabelZh: form.LabelZh, + LabelEn: form.LabelEn, + Value: form.Default, + Values: form.Values, + Multiple: form.Multiple, + Required: form.Required, + Label: form.Label, + }) + } + } + + config := getAppCommonConfig(envs) + config.DockerCompose = install.DockerCompose + res.Params = params + if config.ContainerName == "" { + config.ContainerName = install.ContainerName + } + res.AppContainerConfig = config + res.HostMode = isHostModel(install.DockerCompose) + res.RestartPolicy = getRestartPolicy(install.DockerCompose) + res.WebUI = install.WebUI + res.Type = install.App.Type + if rawCompose, err := getUpgradeCompose(install, detail); err == nil { + res.RawCompose = rawCompose + } + return &res, nil +} + +func syncAppInstallStatus(appInstall *model.AppInstall, force bool) error { + if appInstall.Status == constant.StatusInstalling || appInstall.Status == constant.StatusRebuilding || appInstall.Status == constant.StatusUpgrading || appInstall.Status == constant.StatusUninstalling { + return nil + } + cli, err := docker.NewClient() + if err != nil { + return err + } + defer cli.Close() + + var ( + containers []container.Summary + containersMap map[string]container.Summary + containerNames = strings.Split(appInstall.ContainerName, ",") + ) + containers, err = cli.ListContainersByName(containerNames) + if err != nil { + return err + } + containersMap = make(map[string]container.Summary) + for _, con := range containers { + containersMap[con.Names[0]] = con + } + synAppInstall(containersMap, appInstall, force) + return nil +} + +func updateInstallInfoInDB(appKey, appName, param string, value interface{}) error { + if param != "password" && param != "port" && param != "user-password" { + return nil + } + appInstall, err := appInstallRepo.LoadBaseInfo(appKey, appName) + if err != nil { + return nil + } + envPath := fmt.Sprintf("%s/%s/.env", appInstall.AppPath, appInstall.Name) + lineBytes, err := os.ReadFile(envPath) + if err != nil { + return err + } + + envKey := "" + switch param { + case "password": + if appKey == "mysql" || appKey == "mariadb" || appKey == "postgresql" { + envKey = "PANEL_DB_ROOT_PASSWORD=" + } else { + envKey = "PANEL_REDIS_ROOT_PASSWORD=" + } + case "port": + envKey = "PANEL_APP_PORT_HTTP=" + default: + envKey = "PANEL_DB_USER_PASSWORD=" + } + files := strings.Split(string(lineBytes), "\n") + var newFiles []string + for _, line := range files { + if strings.HasPrefix(line, envKey) { + newFiles = append(newFiles, fmt.Sprintf("%s%v", envKey, value)) + } else { + newFiles = append(newFiles, line) + } + } + file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(strings.Join(newFiles, "\n")) + if err != nil { + return err + } + + oldVal, newVal := "", "" + if param == "password" { + oldVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", appInstall.Password) + newVal = fmt.Sprintf("\"PANEL_DB_ROOT_PASSWORD\":\"%v\"", value) + if appKey == "redis" { + oldVal = fmt.Sprintf("\"PANEL_REDIS_ROOT_PASSWORD\":\"%v\"", appInstall.Password) + newVal = fmt.Sprintf("\"PANEL_REDIS_ROOT_PASSWORD\":\"%v\"", value) + } + _ = appInstallRepo.BatchUpdateBy(map[string]interface{}{ + "param": strings.ReplaceAll(appInstall.Param, oldVal, newVal), + "env": strings.ReplaceAll(appInstall.Env, oldVal, newVal), + }, repo.WithByID(appInstall.ID)) + if appKey == "mysql" || appKey == "postgresql" { + return nil + } + } + if param == "user-password" { + oldVal = fmt.Sprintf("\"PANEL_DB_USER_PASSWORD\":\"%v\"", appInstall.UserPassword) + newVal = fmt.Sprintf("\"PANEL_DB_USER_PASSWORD\":\"%v\"", value) + _ = appInstallRepo.BatchUpdateBy(map[string]interface{}{ + "param": strings.ReplaceAll(appInstall.Param, oldVal, newVal), + "env": strings.ReplaceAll(appInstall.Env, oldVal, newVal), + }, repo.WithByID(appInstall.ID)) + } + if param == "port" { + oldVal = fmt.Sprintf("\"PANEL_APP_PORT_HTTP\":%v", appInstall.Port) + newVal = fmt.Sprintf("\"PANEL_APP_PORT_HTTP\":%v", value) + _ = appInstallRepo.BatchUpdateBy(map[string]interface{}{ + "param": strings.ReplaceAll(appInstall.Param, oldVal, newVal), + "env": strings.ReplaceAll(appInstall.Env, oldVal, newVal), + "http_port": value, + }, repo.WithByID(appInstall.ID)) + } + + ComposeFile := fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, appKey, appInstall.Name) + stdout, err := compose.Down(ComposeFile) + if err != nil { + return errors.New(stdout) + } + stdout, err = compose.Up(ComposeFile) + if err != nil { + return errors.New(stdout) + } + return nil +} + +func (a *AppInstallService) GetAppInstallInfo(installID uint) (*response.AppInstallInfo, error) { + appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(installID)) + if appInstall.ID == 0 { + return &response.AppInstallInfo{ + Status: constant.StatusDeleted, + }, nil + } + _ = syncAppInstallStatus(&appInstall, false) + appInstall, _ = appInstallRepo.GetFirst(repo.WithByID(installID)) + var envMap map[string]interface{} + err := json.Unmarshal([]byte(appInstall.Env), &envMap) + if err != nil { + return nil, err + } + res := &response.AppInstallInfo{ + ID: appInstall.ID, + Name: appInstall.Name, + Version: appInstall.Version, + Container: appInstall.ContainerName, + HttpPort: appInstall.HttpPort, + Status: appInstall.Status, + Message: appInstall.Message, + AppKey: appInstall.App.Key, + Env: envMap, + ComposePath: appInstall.GetComposePath(), + } + for k, v := range envMap { + if strings.Contains(strings.ToUpper(k), "PANEL_APP_PORT") { + var port int + switch val := v.(type) { + case int: + port = val + case string: + port, err = strconv.Atoi(val) + if err != nil { + continue + } + default: + continue + } + res.AppPorts = append(res.AppPorts, port) + } + } + return res, nil +} diff --git a/agent/app/service/app_sync_task.go b/agent/app/service/app_sync_task.go new file mode 100644 index 0000000..7e584c9 --- /dev/null +++ b/agent/app/service/app_sync_task.go @@ -0,0 +1,347 @@ +package service + +import ( + "context" + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "strconv" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/req_helper" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" +) + +type appSyncContext struct { + task *task.Task + httpClient http.Client + baseRemoteUrl string + systemVersion string + appsMap map[string]model.App + settingService ISettingService + list *dto.AppList + oldAppIds []uint + appTags []*model.AppTag +} + +func (a AppService) syncAppStoreTask(t *task.Task) (err error) { + updateRes, err := a.GetAppUpdate() + if err != nil { + return err + } + if !updateRes.CanUpdate { + if updateRes.IsSyncing { + t.Log(i18n.GetMsgByKey("AppStoreIsSyncing")) + return nil + } + global.LOG.Infof("[AppStore] Appstore is up to date") + t.Log(i18n.GetMsgByKey("AppStoreIsUpToDate")) + return nil + } + + list := &dto.AppList{} + if updateRes.AppList == nil { + list, err = getAppList() + if err != nil { + return err + } + } else { + list = updateRes.AppList + } + + settingService := NewISettingService() + _ = settingService.Update("AppStoreSyncStatus", constant.StatusSyncing) + + setting, err := settingService.GetSettingInfo() + if err != nil { + return err + } + + ctx := &appSyncContext{ + task: t, + httpClient: http.Client{Timeout: time.Duration(constant.TimeOut20s) * time.Second, Transport: xpack.LoadRequestTransport()}, + baseRemoteUrl: fmt.Sprintf("%s/%s/1panel", global.CONF.RemoteURL.AppRepo, global.CONF.Base.Mode), + systemVersion: setting.SystemVersion, + settingService: settingService, + list: list, + appTags: make([]*model.AppTag, 0), + } + + if err = SyncTags(list.Extra); err != nil { + return err + } + deleteCustomApp() + + oldApps, err := appRepo.GetBy(appRepo.WithNotLocal()) + if err != nil { + return err + } + ctx.oldAppIds = make([]uint, 0, len(oldApps)) + for _, old := range oldApps { + ctx.oldAppIds = append(ctx.oldAppIds, old.ID) + } + + ctx.appsMap = getApps(oldApps, list.Apps, setting.SystemVersion, t) + + if err = ctx.syncAppIconsAndDetails(); err != nil { + return err + } + + if err = ctx.classifyAndPersistApps(); err != nil { + return err + } + + _ = settingService.Update("AppStoreSyncStatus", constant.StatusSyncSuccess) + _ = settingService.Update("AppStoreLastModified", strconv.Itoa(list.LastModified)) + global.LOG.Infof("[AppStore] Appstore sync completed") + return nil +} + +func (c *appSyncContext) syncAppIconsAndDetails() error { + c.task.LogStart(i18n.GetMsgByKey("SyncAppDetail")) + global.LOG.Infof("[AppStore] sync app detail start, total: %d", len(c.list.Apps)) + + downloadIconNum := 0 + total := len(c.list.Apps) + + for _, l := range c.list.Apps { + downloadIconNum++ + if downloadIconNum%10 == 0 { + c.task.LogWithProgress(i18n.GetMsgByKey("SyncAppDetail"), downloadIconNum, total) + } + + app, ok := c.appsMap[l.AppProperty.Key] + if !ok { + continue + } + + iconStr := c.downloadAppIcon(l.Icon) + if iconStr == "" { + global.LOG.Infof("[AppStore] save failed url=%s", l.Icon) + } + app.Icon = iconStr + + app.TagsKey = l.AppProperty.Tags + if l.AppProperty.Recommend > 0 { + app.Recommend = l.AppProperty.Recommend + } else { + app.Recommend = 9999 + } + app.ReadMe = l.ReadMe + app.LastModified = l.LastModified + + versions := l.Versions + detailsMap := getAppDetails(app.Details, versions) + for _, v := range versions { + version := v.Name + detail := detailsMap[version] + versionUrl := fmt.Sprintf("%s/%s/%s", c.baseRemoteUrl, app.Key, version) + + paramByte, _ := json.Marshal(v.AppForm) + var appForm dto.AppForm + _ = json.Unmarshal(paramByte, &appForm) + + if appForm.SupportVersion > 0 && common.CompareVersion(strconv.FormatFloat(appForm.SupportVersion, 'f', -1, 64), c.systemVersion) { + delete(detailsMap, version) + continue + } + + if _, ok := InitTypes[app.Type]; ok { + dockerComposeUrl := fmt.Sprintf("%s/%s", versionUrl, "docker-compose.yml") + _, composeRes, err := req_helper.HandleRequestWithClient(&c.httpClient, dockerComposeUrl, http.MethodGet, constant.TimeOut20s) + if err == nil { + detail.DockerCompose = string(composeRes) + } + } else { + detail.DockerCompose = "" + } + + detail.Params = string(paramByte) + detail.DownloadUrl = fmt.Sprintf("%s/%s", versionUrl, app.Key+"-"+version+".tar.gz") + detail.DownloadCallBackUrl = v.DownloadCallBackUrl + detail.Update = true + detail.LastModified = v.LastModified + detailsMap[version] = detail + } + + var newDetails []model.AppDetail + for _, detail := range detailsMap { + newDetails = append(newDetails, detail) + } + app.Details = newDetails + c.appsMap[l.AppProperty.Key] = app + } + + global.LOG.Infof("[AppStore] download icon success: %d, total: %d", + downloadIconNum, total) + + c.task.LogSuccess(i18n.GetMsgByKey("SyncAppDetail")) + return nil +} + +func (c *appSyncContext) downloadAppIcon(iconUrl string) string { + iconStr := "" + + code, iconRes, err := req_helper.HandleRequestWithClient(&c.httpClient, iconUrl, http.MethodGet, constant.TimeOut20s) + if err == nil { + if code == http.StatusOK { + if len(iconRes) > 0 { + if iconRes[0] != '<' { + iconStr = base64.StdEncoding.EncodeToString(iconRes) + } + } + } else { + global.LOG.Infof("[AppStore] download failed status=%d", code) + } + } + return iconStr +} + +func (c *appSyncContext) classifyAndPersistApps() (err error) { + tags, _ := tagRepo.All() + var ( + addAppArray []model.App + updateAppArray []model.App + deleteAppArray []model.App + deleteIds []uint + tagMap = make(map[string]uint, len(tags)) + ) + + for _, v := range c.appsMap { + if v.ID == 0 { + addAppArray = append(addAppArray, v) + } else { + if v.Status == constant.AppTakeDown { + installs, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(v.ID)) + if len(installs) > 0 { + updateAppArray = append(updateAppArray, v) + continue + } + deleteAppArray = append(deleteAppArray, v) + deleteIds = append(deleteIds, v.ID) + } else { + updateAppArray = append(updateAppArray, v) + } + } + } + + tx, ctx := getTxAndContext() + defer func() { + if err != nil { + tx.Rollback() + return + } + }() + + if len(addAppArray) > 0 { + if err = appRepo.BatchCreate(ctx, addAppArray); err != nil { + return + } + } + + if len(deleteAppArray) > 0 { + if err = appRepo.BatchDelete(ctx, deleteAppArray); err != nil { + return + } + if err = appDetailRepo.DeleteByAppIds(ctx, deleteIds); err != nil { + return + } + } + + for _, tag := range tags { + tagMap[tag.Key] = tag.ID + } + + for _, update := range updateAppArray { + if err = appRepo.Save(ctx, &update); err != nil { + return + } + } + + apps := append(addAppArray, updateAppArray...) + + var ( + addDetails []model.AppDetail + updateDetails []model.AppDetail + deleteDetails []model.AppDetail + ) + + for _, app := range apps { + for _, tag := range app.TagsKey { + tagId, ok := tagMap[tag] + if ok { + exist, _ := appTagRepo.GetFirst(ctx, appTagRepo.WithByTagID(tagId), appTagRepo.WithByAppID(app.ID)) + if exist == nil { + c.appTags = append(c.appTags, &model.AppTag{ + AppId: app.ID, + TagId: tagId, + }) + } + } + } + + for _, d := range app.Details { + d.AppId = app.ID + if d.ID == 0 { + addDetails = append(addDetails, d) + } else { + if d.Status == constant.AppTakeDown { + runtime, _ := runtimeRepo.GetFirst(ctx, runtimeRepo.WithDetailId(d.ID)) + if runtime != nil { + updateDetails = append(updateDetails, d) + continue + } + installs, _ := appInstallRepo.ListBy(ctx, appInstallRepo.WithDetailIdsIn([]uint{d.ID})) + if len(installs) > 0 { + updateDetails = append(updateDetails, d) + continue + } + deleteDetails = append(deleteDetails, d) + } else { + updateDetails = append(updateDetails, d) + } + } + } + } + + if len(addDetails) > 0 { + if err = appDetailRepo.BatchCreate(ctx, addDetails); err != nil { + return + } + } + + if len(deleteDetails) > 0 { + if err = appDetailRepo.BatchDelete(ctx, deleteDetails); err != nil { + return + } + } + + for _, u := range updateDetails { + if err = appDetailRepo.Update(ctx, u); err != nil { + return + } + } + + if len(c.oldAppIds) > 0 { + if err = appTagRepo.DeleteByAppIds(ctx, deleteIds); err != nil { + return + } + } + + if len(c.appTags) > 0 { + if err = appTagRepo.BatchCreate(ctx, c.appTags); err != nil { + return + } + } + + tx.Commit() + return nil +} diff --git a/agent/app/service/app_utils.go b/agent/app/service/app_utils.go new file mode 100644 index 0000000..90fd651 --- /dev/null +++ b/agent/app/service/app_utils.go @@ -0,0 +1,2253 @@ +package service + +import ( + "bufio" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "maps" + "math" + "net/http" + "os" + "os/exec" + "path" + "path/filepath" + "reflect" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + composeV2 "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/env" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "github.com/1Panel-dev/1Panel/agent/utils/req_helper" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "github.com/compose-spec/compose-go/v2/types" + "github.com/docker/docker/api/types/container" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/subosito/gotenv" + "gopkg.in/yaml.v3" +) + +type DatabaseOp string + +var ( + Add DatabaseOp = "add" + Delete DatabaseOp = "delete" +) + +func checkPort(key string, params map[string]interface{}) (int, error) { + port, ok := params[key] + if ok { + portN := 0 + var err error + switch p := port.(type) { + case string: + portN, err = strconv.Atoi(p) + if err != nil { + return portN, nil + } + case float64: + portN = int(math.Ceil(p)) + case int: + portN = p + } + + oldInstalled, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithPort(portN)) + if len(oldInstalled) > 0 { + var apps []string + for _, install := range oldInstalled { + apps = append(apps, install.App.Name) + } + return portN, buserr.WithMap("ErrPortInOtherApp", map[string]interface{}{"port": portN, "apps": apps}, nil) + } + if common.ScanPort(portN) { + return portN, buserr.WithDetail("ErrPortInUsed", portN, nil) + } else { + return portN, nil + } + } + return 0, nil +} + +func checkPortExist(port int) error { + errMap := make(map[string]interface{}) + errMap["port"] = port + appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port)) + if appInstall.ID > 0 { + errMap["type"] = i18n.GetMsgByKey("TYPE_APP") + errMap["name"] = appInstall.Name + return buserr.WithMap("ErrPortExist", errMap, nil) + } + runtime, _ := runtimeRepo.GetFirst(context.Background(), runtimeRepo.WithPort(port)) + if runtime != nil { + errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME") + errMap["name"] = runtime.Name + return buserr.WithMap("ErrPortExist", errMap, nil) + } + domain, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithPort(port)) + if domain.ID > 0 { + errMap["type"] = i18n.GetMsgByKey("TYPE_DOMAIN") + errMap["name"] = domain.Domain + return buserr.WithMap("ErrPortExist", errMap, nil) + } + if common.ScanPort(port) { + return buserr.WithDetail("ErrPortInUsed", port, nil) + } + return nil +} + +var DatabaseKeys = map[string]uint{ + constant.AppMysql: 3306, + constant.AppMariaDB: 3306, + constant.AppPostgresql: 5432, + constant.AppPostgres: 5432, + constant.AppMongodb: 27017, + constant.AppRedis: 6379, + constant.AppMemcached: 11211, + constant.AppMysqlCluster: 3306, + constant.AppPostgresqlCluster: 5432, + constant.AppRedisCluster: 6379, +} + +var ToolKeys = map[string]uint{ + "minio": 9001, +} + +func CreateDB(ctx context.Context, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error { + database := &model.Database{ + AppInstallID: appInstall.ID, + Name: appInstall.Name, + Type: app.Key, + Version: appInstall.Version, + From: "local", + Address: appInstall.ServiceName, + Port: DatabaseKeys[app.Key], + } + detail, err := appDetailRepo.GetFirst(repo.WithByID(appInstall.AppDetailId)) + if err != nil { + return err + } + formFields := &dto.AppForm{} + if err := json.Unmarshal([]byte(detail.Params), formFields); err != nil { + return err + } + for _, form := range formFields.FormFields { + if form.EnvKey == "PANEL_APP_PORT_HTTP" { + portFloat, ok := form.Default.(float64) + if ok { + database.Port = uint(int(portFloat)) + } + break + } + } + + switch app.Key { + case constant.AppMysql, constant.AppMariaDB, constant.AppPostgresql, constant.AppMongodb, constant.AppMysqlCluster, constant.AppPostgresqlCluster: + if password, ok := params["PANEL_DB_ROOT_PASSWORD"]; ok { + if password != "" { + database.Password = password.(string) + if app.Key == "mysql" || app.Key == "mariadb" || app.Key == constant.AppMysqlCluster { + database.Username = "root" + } + if rootUser, ok := params["PANEL_DB_ROOT_USER"]; ok { + database.Username = rootUser.(string) + } + authParam := dto.AuthParam{ + RootPassword: password.(string), + RootUser: database.Username, + } + authByte, err := json.Marshal(authParam) + if err != nil { + return err + } + appInstall.Param = string(authByte) + + } + } + case constant.AppRedis, constant.AppRedisCluster: + if password, ok := params["PANEL_REDIS_ROOT_PASSWORD"]; ok { + authParam := dto.RedisAuthParam{ + RootPassword: "", + } + if password != "" { + authParam.RootPassword = password.(string) + database.Password = password.(string) + } + authByte, err := json.Marshal(authParam) + if err != nil { + return err + } + appInstall.Param = string(authByte) + } + } + return databaseRepo.Create(ctx, database) +} + +func createLink(ctx context.Context, installTask *task.Task, app model.App, appInstall *model.AppInstall, params map[string]interface{}) error { + deleteAppLink := func(t *task.Task) { + del := dto.DelAppLink{ + Ctx: ctx, + Install: appInstall, + ForceDelete: true, + } + _ = deleteLink(del) + } + if DatabaseKeys[app.Key] > 0 { + handleDataBaseApp := func(task *task.Task) error { + return CreateDB(ctx, app, appInstall, params) + } + installTask.AddSubTask(i18n.GetMsgByKey("HandleDatabaseApp"), handleDataBaseApp, deleteAppLink) + } + if ToolKeys[app.Key] > 0 { + if app.Key == "minio" { + authParam := dto.MinioAuthParam{} + if password, ok := params["PANEL_MINIO_ROOT_PASSWORD"]; ok { + authParam.RootPassword = password.(string) + } + if rootUser, ok := params["PANEL_MINIO_ROOT_USER"]; ok { + authParam.RootUser = rootUser.(string) + } + authByte, err := json.Marshal(authParam) + if err != nil { + return err + } + appInstall.Param = string(authByte) + } + } + + var dbConfig dto.AppDatabase + if app.Type == "website" || app.Type == "tool" { + paramByte, err := json.Marshal(params) + if err != nil { + return err + } + if err = json.Unmarshal(paramByte, &dbConfig); err != nil { + return err + } + } + + if !reflect.DeepEqual(dbConfig, dto.AppDatabase{}) && dbConfig.ServiceName != "" { + createAppDataBase := func(rootTask *task.Task) error { + hostName := params["PANEL_DB_HOST_NAME"] + if hostName == nil || hostName.(string) == "" { + return nil + } + database, _ := databaseRepo.Get(repo.WithByName(hostName.(string))) + if database.ID == 0 { + return nil + } + var resourceId uint + if dbConfig.DbName != "" && dbConfig.DbUser != "" && dbConfig.Password != "" { + switch database.Type { + case constant.AppPostgresql, constant.AppPostgres, constant.AppPostgresqlCluster: + oldPostgresqlDb, _ := postgresqlRepo.Get(repo.WithByName(dbConfig.DbName), repo.WithByFrom(constant.ResourceLocal)) + if oldPostgresqlDb.ID == 0 { + oldPostgresqlDb, _ = postgresqlRepo.Get(repo.WithByName(dbConfig.DbName), postgresqlRepo.WithByPostgresqlName(dbConfig.DatabaseName), repo.WithByFrom(constant.AppResourceRemote)) + } + resourceId = oldPostgresqlDb.ID + if oldPostgresqlDb.ID > 0 { + if oldPostgresqlDb.Username != dbConfig.DbUser || oldPostgresqlDb.Password != dbConfig.Password { + return buserr.New("ErrDbUserNotValid") + } + } else { + var createPostgresql dto.PostgresqlDBCreate + createPostgresql.Name = dbConfig.DbName + createPostgresql.Username = dbConfig.DbUser + createPostgresql.Database = database.Name + createPostgresql.Format = "UTF8" + createPostgresql.Password = dbConfig.Password + createPostgresql.From = database.From + createPostgresql.SuperUser = true + pgdb, err := NewIPostgresqlService().Create(ctx, createPostgresql) + if err != nil { + return err + } + resourceId = pgdb.ID + } + case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster: + oldMysqlDb, _ := mysqlRepo.Get(repo.WithByName(dbConfig.DbName), repo.WithByFrom(constant.ResourceLocal)) + if oldMysqlDb.ID == 0 { + oldMysqlDb, _ = mysqlRepo.Get(repo.WithByName(dbConfig.DbName), mysqlRepo.WithByMysqlName(dbConfig.DatabaseName), repo.WithByFrom(constant.AppResourceRemote)) + } + resourceId = oldMysqlDb.ID + if oldMysqlDb.ID > 0 { + if oldMysqlDb.Username != dbConfig.DbUser || oldMysqlDb.Password != dbConfig.Password { + return buserr.New("ErrDbUserNotValid") + } + } else { + var createMysql dto.MysqlDBCreate + createMysql.Name = dbConfig.DbName + createMysql.Username = dbConfig.DbUser + createMysql.Database = database.Name + createMysql.Format = dbConfig.Format + createMysql.Permission = "%" + createMysql.Password = dbConfig.Password + createMysql.From = database.From + createMysql.Collation = dbConfig.Collation + mysqldb, err := NewIMysqlService().Create(ctx, createMysql) + if err != nil { + return err + } + resourceId = mysqldb.ID + } + } + } + var installResource model.AppInstallResource + installResource.ResourceId = resourceId + installResource.AppInstallId = appInstall.ID + if database.AppInstallID > 0 { + installResource.LinkId = database.AppInstallID + } else { + installResource.LinkId = database.ID + } + installResource.Key = database.Type + installResource.From = database.From + return appInstallResourceRepo.Create(ctx, &installResource) + } + installTask.AddSubTask(task.GetTaskName(dbConfig.DbName, task.TaskCreate, task.TaskScopeDatabase), createAppDataBase, deleteAppLink) + } + return nil +} + +func deleteAppInstall(deleteReq request.AppInstallDelete) error { + install := deleteReq.Install + op := files.NewFileOp() + appDir := install.GetPath() + + uninstallTask, err := task.NewTaskWithOps(install.Name, task.TaskUninstall, task.TaskScopeApp, deleteReq.TaskID, install.ID) + if err != nil { + return err + } + + uninstall := func(t *task.Task) error { + install.Status = constant.StatusUninstalling + _ = appInstallRepo.Save(context.Background(), &install) + dir, _ := os.Stat(appDir) + if dir != nil { + logStr := i18n.GetMsgByKey("Stop") + i18n.GetMsgByKey("App") + t.Log(logStr) + + out, err := compose.Down(install.GetComposePath()) + if err != nil && !deleteReq.ForceDelete { + return handleErr(install, err, out) + } + t.LogSuccess(logStr) + if err = runScript(t, &install, "uninstall"); err != nil { + _, _ = compose.Up(install.GetComposePath()) + return err + } + if deleteReq.DeleteImage { + delImageStr := i18n.GetMsgByKey("TaskDelete") + i18n.GetMsgByKey("Image") + content, err := op.GetContent(install.GetEnvPath()) + if err != nil { + return err + } + images, err := composeV2.GetImagesFromDockerCompose(content, []byte(install.DockerCompose)) + if err != nil { + return err + } + client, err := docker.NewClient() + if err != nil { + return err + } + defer client.Close() + for _, image := range images { + imageID, err := client.GetImageIDByName(image) + if err == nil { + imgStr := delImageStr + image + t.Log(imgStr) + + if err = client.DeleteImage(imageID); err != nil { + t.LogFailedWithErr(imgStr, err) + continue + } + t.LogSuccess(delImageStr + image) + } + } + } + } + tx, ctx := helper.GetTxAndContext() + defer tx.Rollback() + if err = appInstallRepo.Delete(ctx, install); err != nil { + return err + } + + resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID)) + if len(resources) > 0 { + if deleteReq.DeleteDB { + del := dto.DelAppLink{ + Ctx: ctx, + Install: &install, + ForceDelete: deleteReq.ForceDelete, + Task: uninstallTask, + } + t.LogWithOps(task.TaskDelete, i18n.GetMsgByKey("Database")) + if err = deleteLink(del); err != nil { + t.LogFailedWithOps(task.TaskDelete, i18n.GetMsgByKey("Database"), err) + if !deleteReq.ForceDelete { + return err + } + } + t.LogSuccessWithOps(task.TaskDelete, i18n.GetMsgByKey("Database")) + } else { + _ = appInstallResourceRepo.DeleteBy(ctx, appInstallResourceRepo.WithAppInstallId(install.ID)) + } + } + + if DatabaseKeys[install.App.Key] > 0 { + _ = databaseRepo.Delete(ctx, databaseRepo.WithAppInstallID(install.ID)) + } + + switch install.App.Key { + case constant.AppMysql, constant.AppMariaDB, constant.AppMysqlCluster: + _ = mysqlRepo.Delete(ctx, mysqlRepo.WithByMysqlName(install.Name)) + case constant.AppPostgresql, constant.AppPostgresqlCluster: + _ = postgresqlRepo.Delete(ctx, postgresqlRepo.WithByPostgresqlName(install.Name)) + } + + _ = backupRepo.DeleteRecord(ctx, repo.WithByType("app"), repo.WithByName(install.App.Key), repo.WithByDetailName(install.Name)) + uploadDir := path.Join(global.Dir.BaseDir, fmt.Sprintf("1panel/uploads/app/%s/%s", install.App.Key, install.Name)) + if _, err := os.Stat(uploadDir); err == nil { + _ = os.RemoveAll(uploadDir) + } + if deleteReq.DeleteBackup { + backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("app/%s/%s", install.App.Key, install.Name)) + if _, err = os.Stat(backupDir); err == nil { + t.LogWithOps(task.TaskDelete, i18n.GetMsgByKey("TaskBackup")) + _ = os.RemoveAll(backupDir) + t.LogSuccessWithOps(task.TaskDelete, i18n.GetMsgByKey("TaskBackup")) + } + } + _ = op.DeleteDir(appDir) + parentDir := filepath.Dir(appDir) + entries, err := os.ReadDir(parentDir) + if err == nil && len(entries) == 0 { + _ = op.DeleteDir(parentDir) + } + tx.Commit() + + existApps, _ := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(install.AppId)) + if len(existApps) == 0 { + _ = appIgnoreUpgradeRepo.Delete(appIgnoreUpgradeRepo.WithAppID(install.AppId)) + } + return nil + } + uninstallTask.AddSubTask(task.GetTaskName(install.Name, task.TaskUninstall, task.TaskScopeApp), uninstall, nil) + go func() { + if err := uninstallTask.Execute(); err != nil && !deleteReq.ForceDelete { + install.Status = constant.StatusError + _ = appInstallRepo.Save(context.Background(), &install) + } + }() + return nil +} + +func deleteLink(del dto.DelAppLink) error { + install := del.Install + resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID)) + if len(resources) == 0 { + return nil + } + for _, re := range resources { + switch re.Key { + case constant.AppMysql, constant.AppMariaDB: + mysqlService := NewIMysqlService() + database, _ := mysqlRepo.Get(repo.WithByID(re.ResourceId)) + if reflect.DeepEqual(database, model.DatabaseMysql{}) { + continue + } + if err := mysqlService.Delete(del.Ctx, dto.MysqlDBDelete{ + ID: database.ID, + ForceDelete: del.ForceDelete, + DeleteBackup: true, + Type: re.Key, + Database: database.MysqlName, + }); err != nil && !del.ForceDelete { + return err + } + case constant.AppPostgresql: + pgsqlService := NewIPostgresqlService() + database, _ := postgresqlRepo.Get(repo.WithByID(re.ResourceId)) + if reflect.DeepEqual(database, model.DatabasePostgresql{}) { + continue + } + if err := pgsqlService.Delete(del.Ctx, dto.PostgresqlDBDelete{ + ID: database.ID, + ForceDelete: del.ForceDelete, + DeleteBackup: true, + Type: re.Key, + Database: database.PostgresqlName, + }); err != nil { + return err + } + } + } + return appInstallResourceRepo.DeleteBy(del.Ctx, appInstallResourceRepo.WithAppInstallId(install.ID)) +} + +func handleUpgradeCompose(install model.AppInstall, detail model.AppDetail) (map[string]interface{}, error) { + composeMap := make(map[string]interface{}) + if err := yaml.Unmarshal([]byte(detail.DockerCompose), &composeMap); err != nil { + return nil, err + } + value, ok := composeMap["services"] + if !ok || value == nil { + return nil, buserr.New("ErrFileParse") + } + servicesMap := value.(map[string]interface{}) + if len(servicesMap) == 1 { + index := 0 + oldServiceName := "" + for k := range servicesMap { + oldServiceName = k + index++ + if index > 0 { + break + } + } + servicesMap[install.ServiceName] = servicesMap[oldServiceName] + if install.ServiceName != oldServiceName { + delete(servicesMap, oldServiceName) + } + } + serviceValue := servicesMap[install.ServiceName].(map[string]interface{}) + + oldComposeMap := make(map[string]interface{}) + if err := yaml.Unmarshal([]byte(install.DockerCompose), &oldComposeMap); err != nil { + return nil, err + } + oldValue, ok := oldComposeMap["services"] + if !ok || oldValue == nil { + return nil, buserr.New("ErrFileParse") + } + oldValueMap, ok := oldValue.(map[string]interface{}) + if !ok { + return nil, buserr.New("ErrFileParse") + } + oldServiceValueInterface, ok := oldValueMap[install.ServiceName] + if !ok || oldServiceValueInterface == nil { + return nil, buserr.New("ErrFileParse") + } + oldServiceValue, ok := oldServiceValueInterface.(map[string]interface{}) + if !ok { + return nil, buserr.New("ErrFileParse") + } + if oldServiceValue["deploy"] != nil { + serviceValue["deploy"] = oldServiceValue["deploy"] + } + if oldServiceValue["restart"] != nil { + serviceValue["restart"] = oldServiceValue["restart"] + } + servicesMap[install.ServiceName] = serviceValue + composeMap["services"] = servicesMap + return composeMap, nil +} + +func getUpgradeCompose(install model.AppInstall, detail model.AppDetail) (string, error) { + if detail.DockerCompose == "" { + return "", nil + } + composeMap, err := handleUpgradeCompose(install, detail) + if err != nil { + return "", err + } + envs := make(map[string]interface{}) + if err := json.Unmarshal([]byte(install.Env), &envs); err != nil { + return "", err + } + config := getAppCommonConfig(envs) + if config.ContainerName == "" { + config.ContainerName = install.ContainerName + envs[constant.ContainerName] = install.ContainerName + } + config.Advanced = true + if err := addDockerComposeCommonParam(composeMap, install.ServiceName, config, envs); err != nil { + return "", err + } + paramByte, err := json.Marshal(envs) + if err != nil { + return "", err + } + install.Env = string(paramByte) + composeByte, err := yaml.Marshal(composeMap) + if err != nil { + return "", err + } + return string(composeByte), nil +} + +func buildNginx(parentTask *task.Task) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + fileOp := files.NewFileOp() + buildPath := path.Join(nginxInstall.GetPath(), "build") + if !fileOp.Stat(buildPath) { + return buserr.New("ErrBuildDirNotFound") + } + moduleConfigPath := path.Join(buildPath, "module.json") + moduleContent, err := fileOp.GetContent(moduleConfigPath) + if err != nil { + return err + } + var ( + modules []dto.NginxModule + addModuleParams []string + addPackages []string + ) + if len(moduleContent) > 0 { + _ = json.Unmarshal(moduleContent, &modules) + bashFile, err := os.OpenFile(path.Join(buildPath, "tmp", "pre.sh"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.DirPerm) + if err != nil { + return err + } + defer bashFile.Close() + bashFileWriter := bufio.NewWriter(bashFile) + for _, module := range modules { + if !module.Enable { + continue + } + _, err = bashFileWriter.WriteString(module.Script + "\n") + if err != nil { + return err + } + addModuleParams = append(addModuleParams, module.Params) + addPackages = append(addPackages, module.Packages...) + } + err = bashFileWriter.Flush() + if err != nil { + return err + } + } + envs, err := gotenv.Read(nginxInstall.GetEnvPath()) + if err != nil { + return err + } + envs["RESTY_CONFIG_OPTIONS_MORE"] = "" + envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = "" + if len(addModuleParams) > 0 { + envs["RESTY_CONFIG_OPTIONS_MORE"] = strings.Join(addModuleParams, " ") + } + if len(addPackages) > 0 { + envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = strings.Join(addPackages, " ") + } + _ = gotenv.Write(envs, nginxInstall.GetEnvPath()) + if len(addModuleParams) == 0 && len(addPackages) == 0 { + return nil + } + logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("TaskBuild"), i18n.GetMsgByKey("Image")) + parentTask.LogStart(logStr) + cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*parentTask), cmd.WithTimeout(60*time.Minute)) + if err = cmdMgr.RunBashCf("docker compose -f %s build", nginxInstall.GetComposePath()); err != nil { + return err + } + parentTask.LogSuccess(logStr) + return nil +} + +func upgradeInstall(req request.AppInstallUpgrade) error { + install, err := appInstallRepo.GetFirst(repo.WithByID(req.InstallID)) + if err != nil { + return err + } + detail, err := appDetailRepo.GetFirst(repo.WithByID(req.DetailID)) + if err != nil { + return err + } + if install.Version == detail.Version { + return errors.New("two version is same") + } + + upgradeTask, err := task.NewTaskWithOps(install.Name, task.TaskUpgrade, task.TaskScopeApp, req.TaskID, install.ID) + if err != nil { + return err + } + install.Status = constant.StatusUpgrading + + var ( + upErr error + backupFile string + ) + backUpApp := func(t *task.Task) error { + backupService := NewIBackupService() + backupRecordService := NewIBackupRecordService() + fileName := fmt.Sprintf("upgrade_backup_%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)) + backupRecord, err := backupService.AppBackup(dto.CommonBackup{Name: install.App.Key, DetailName: install.Name, FileName: fileName}) + if err == nil { + backups, _ := backupRecordService.ListAppRecords(install.App.Key, install.Name, "upgrade_backup") + if len(backups) > 3 { + backupsToDelete := backups[:len(backups)-3] + var deleteIDs []uint + for _, backup := range backupsToDelete { + deleteIDs = append(deleteIDs, backup.ID) + } + _ = backupRecordService.BatchDeleteRecord(deleteIDs) + } + backupFile = path.Join(global.Dir.LocalBackupDir, backupRecord.FileDir, backupRecord.FileName) + } else { + return buserr.WithNameAndErr("ErrAppBackup", install.Name, err) + } + return nil + } + if req.Backup { + upgradeTask.AddSubTask(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeApp), backUpApp, nil) + } + + upgradeApp := func(t *task.Task) error { + fileOp := files.NewFileOp() + detailDir := path.Join(global.Dir.ResourceDir, "apps", install.App.Resource, install.App.Key, detail.Version) + if install.App.Resource == constant.AppResourceRemote { + if err = downloadApp(install.App, detail, &install, t.Logger); err != nil { + return err + } + if detail.DockerCompose == "" { + composeDetail, err := fileOp.GetContent(path.Join(detailDir, "docker-compose.yml")) + if err != nil { + return err + } + detail.DockerCompose = string(composeDetail) + _ = appDetailRepo.Update(context.Background(), detail) + } + go func() { + RequestDownloadCallBack(detail.DownloadCallBackUrl) + }() + } + if install.App.Resource == constant.AppResourceLocal { + detailDir = path.Join(global.Dir.ResourceDir, "apps", "local", strings.TrimPrefix(install.App.Key, "local"), detail.Version) + } + + content, err := fileOp.GetContent(install.GetEnvPath()) + if err != nil { + return err + } + dockerCLi, _ := docker.NewClient() + if req.PullImage { + composeContent := []byte(detail.DockerCompose) + if req.DockerCompose != "" { + composeContent = []byte(req.DockerCompose) + } + images, err := composeV2.GetImagesFromDockerCompose(content, composeContent) + if err != nil { + return err + } + for _, image := range images { + t.Log(i18n.GetWithName("PullImageStart", image)) + if err = dockerCLi.PullImageWithProcess(t, image); err != nil { + err = buserr.WithNameAndErr("ErrDockerPullImage", "", err) + return err + } + t.LogSuccess(i18n.GetMsgByKey("PullImage")) + } + } + + command := exec.Command("/bin/bash", "-c", fmt.Sprintf("cp -rn %s/* %s || true", detailDir, install.GetPath())) + _, _ = command.CombinedOutput() + if install.App.Key == constant.AppOpenresty { + installBuildDir := path.Join(install.GetPath(), "build") + detailBuildDir := path.Join(detailDir, "build") + if !fileOp.Stat(installBuildDir) { + if err := fileOp.CreateDir(installBuildDir, constant.DirPerm); err != nil { + return err + } + } + if err := fileOp.DeleteDir(path.Join(installBuildDir, "tmp")); err != nil { + return err + } + if err := fileOp.CopyDir(path.Join(detailBuildDir, "tmp"), installBuildDir); err != nil { + return err + } + if err := fileOp.CopyFile(path.Join(detailBuildDir, "Dockerfile"), installBuildDir); err != nil { + return err + } + if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.conf"), installBuildDir); err != nil { + return err + } + if err := fileOp.CopyFile(path.Join(detailBuildDir, "nginx.vh.default.conf"), installBuildDir); err != nil { + return err + } + } + sourceScripts := path.Join(detailDir, "scripts") + if fileOp.Stat(sourceScripts) { + dstScripts := path.Join(install.GetPath(), "scripts") + _ = fileOp.DeleteDir(dstScripts) + _ = fileOp.CreateDir(dstScripts, constant.DirPerm) + scriptCmd := exec.Command("cp", "-rf", sourceScripts+"/.", dstScripts+"/") + _, _ = scriptCmd.CombinedOutput() + } + + var newCompose string + if req.DockerCompose == "" { + newCompose, err = getUpgradeCompose(install, detail) + if err != nil { + return err + } + } else { + newCompose = req.DockerCompose + } + + install.DockerCompose = newCompose + install.Version = detail.Version + install.AppDetailId = req.DetailID + + if out, err := compose.Down(install.GetComposePath()); err != nil { + if out != "" { + upErr = errors.New(out) + return upErr + } + return err + } + envs := make(map[string]interface{}) + if err = json.Unmarshal([]byte(install.Env), &envs); err != nil { + return err + } + envParams := make(map[string]string, len(envs)) + if install.App.Key == constant.AppOpenresty { + packageUrl, _ := env.GetEnvValueByKey(install.GetEnvPath(), "CONTAINER_PACKAGE_URL") + addPackage, _ := env.GetEnvValueByKey(install.GetEnvPath(), "RESTY_ADD_PACKAGE_BUILDDEPS") + options, _ := env.GetEnvValueByKey(install.GetEnvPath(), "RESTY_CONFIG_OPTIONS_MORE") + envParams["CONTAINER_PACKAGE_URL"] = packageUrl + envParams["RESTY_ADD_PACKAGE_BUILDDEPS"] = addPackage + envParams["RESTY_CONFIG_OPTIONS_MORE"] = options + } + handleMap(envs, envParams) + if err = env.Write(envParams, install.GetEnvPath()); err != nil { + return err + } + + if err = runScript(t, &install, "upgrade"); err != nil { + return err + } + + if err = fileOp.WriteFile(install.GetComposePath(), strings.NewReader(install.DockerCompose), constant.FilePerm); err != nil { + return err + } + + if install.App.Key == constant.AppOpenresty { + if err = buildNginx(t); err != nil { + t.Log(err.Error()) + return err + } + } + + logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App")) + t.Log(logStr) + if out, err := compose.Up(install.GetComposePath()); err != nil { + if out != "" { + return errors.New(out) + } + return err + } + t.LogSuccess(logStr) + install.Status = constant.StatusRunning + return appInstallRepo.Save(context.Background(), &install) + } + + rollBackApp := func(t *task.Task) { + if req.Backup { + t.Log(i18n.GetWithName("AppRecover", install.Name)) + if err := NewIBackupService().AppRecover(dto.CommonRecover{Name: install.App.Key, DetailName: install.Name, Type: "app", DownloadAccountID: 1, File: backupFile}); err != nil { + t.LogFailedWithErr(i18n.GetWithName("AppRecover", install.Name), err) + return + } + t.LogSuccess(i18n.GetWithName("AppRecover", install.Name)) + return + } + } + + upgradeTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskScopeApp, task.TaskUpgrade), upgradeApp, rollBackApp, 0, 1*time.Hour) + + go func() { + err = upgradeTask.Execute() + if err != nil { + existInstall, _ := appInstallRepo.GetFirst(repo.WithByID(req.InstallID)) + if existInstall.ID > 0 && existInstall.Status != constant.StatusRunning { + existInstall.Status = constant.StatusUpgradeErr + existInstall.Message = err.Error() + _ = appInstallRepo.Save(context.Background(), &existInstall) + } + } + }() + + return appInstallRepo.Save(context.Background(), &install) +} + +func skipCheckStatus(service types.ServiceConfig) bool { + for key := range service.Labels { + if key == "skipStatusCheck" { + return true + } + } + return false +} + +func getContainerNames(install model.AppInstall) ([]string, error) { + envStr, err := coverEnvJsonToStr(install.Env) + if err != nil { + return nil, err + } + project, err := composeV2.GetComposeProject(install.Name, install.GetPath(), []byte(install.DockerCompose), []byte(envStr), true) + if err != nil { + return nil, err + } + containerMap := make(map[string]struct{}) + for _, service := range project.AllServices() { + if service.ContainerName == "${CONTAINER_NAME}" || service.ContainerName == "" || skipCheckStatus(service) { + continue + } + containerMap[service.ContainerName] = struct{}{} + } + var containerNames []string + for k := range containerMap { + containerNames = append(containerNames, k) + } + if len(containerNames) == 0 { + containerNames = append(containerNames, install.ContainerName) + } + return containerNames, nil +} + +func coverEnvJsonToStr(envJson string) (string, error) { + envMap := make(map[string]interface{}) + _ = json.Unmarshal([]byte(envJson), &envMap) + newEnvMap := make(map[string]string, len(envMap)) + handleMap(envMap, newEnvMap) + envStr, err := gotenv.Marshal(newEnvMap) + if err != nil { + return "", err + } + return envStr, nil +} + +func checkLimit(app model.App) error { + if app.Limit > 0 { + installs, err := appInstallRepo.ListBy(context.Background(), appInstallRepo.WithAppId(app.ID)) + if err != nil { + return err + } + if len(installs) >= app.Limit { + return buserr.New("ErrAppLimit") + } + } + return nil +} + +func checkRequiredAndLimit(app model.App) error { + if err := checkLimit(app); err != nil { + return err + } + return nil +} + +func handleMap(params map[string]interface{}, envParams map[string]string) { + for k, v := range params { + switch t := v.(type) { + case string: + envParams[k] = t + case float64: + envParams[k] = strconv.FormatFloat(t, 'f', -1, 32) + case uint: + envParams[k] = strconv.Itoa(int(t)) + case int: + envParams[k] = strconv.Itoa(t) + case []interface{}: + strArray := make([]string, len(t)) + for i := range t { + strArray[i] = strings.ToLower(fmt.Sprintf("%v", t[i])) + } + envParams[k] = strings.Join(strArray, ",") + case map[string]interface{}: + handleMap(t, envParams) + } + } +} + +func downloadApp(app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, logger *logrus.Logger) (err error) { + if app.IsLocalApp() || app.IsCustomApp() { + return nil + } + appResourceDir := path.Join(global.Dir.AppResourceDir, app.Resource) + appDownloadDir := app.GetAppResourcePath() + appVersionDir := path.Join(appDownloadDir, appDetail.Version) + fileOp := files.NewFileOp() + if !appDetail.Update && fileOp.Stat(appVersionDir) { + return + } + if !fileOp.Stat(appDownloadDir) { + _ = fileOp.CreateDir(appDownloadDir, constant.DirPerm) + } + if !fileOp.Stat(appVersionDir) { + _ = fileOp.CreateDir(appVersionDir, constant.DirPerm) + } + if logger == nil { + global.LOG.Infof("download app [%s] from %s", app.Name, appDetail.DownloadUrl) + } else { + logger.Printf("download app [%s] from %s", app.Name, appDetail.DownloadUrl) + } + + filePath := path.Join(appVersionDir, app.Key+"-"+appDetail.Version+".tar.gz") + + defer func() { + if err != nil { + if appInstall != nil { + appInstall.Status = constant.StatusDownloadErr + appInstall.Message = err.Error() + } + } + }() + + if err = files.DownloadFileWithProxy(appDetail.DownloadUrl, filePath); err != nil { + if logger == nil { + global.LOG.Errorf("download app [%s] error %v", app.Name, err) + } else { + logger.Printf("download app [%s] error %v", app.Name, err) + } + return + } + if err = fileOp.Decompress(filePath, appResourceDir, files.SdkTarGz, ""); err != nil { + if logger == nil { + global.LOG.Errorf("decompress app[%s] error %v", app.Name, err) + } else { + logger.Printf("decompress app[%s] error %v", app.Name, err) + } + return + } + _ = fileOp.DeleteFile(filePath) + appDetail.Update = false + _ = appDetailRepo.Update(context.Background(), appDetail) + return +} + +func copyData(task *task.Task, app model.App, appDetail model.AppDetail, appInstall *model.AppInstall, req request.AppInstallCreate) (err error) { + fileOp := files.NewFileOp() + appResourceDir := path.Join(global.Dir.AppResourceDir, app.Resource) + + if app.Resource == constant.AppResourceRemote { + err = downloadApp(app, appDetail, appInstall, task.Logger) + if err != nil { + return + } + go func() { + RequestDownloadCallBack(appDetail.DownloadCallBackUrl) + }() + } + appKey := app.Key + installAppDir := path.Join(global.Dir.AppInstallDir, app.Key) + if app.Resource == constant.AppResourceLocal { + appResourceDir = global.Dir.LocalAppResourceDir + appKey = strings.TrimPrefix(app.Key, "local") + installAppDir = path.Join(global.Dir.LocalAppInstallDir, appKey) + } + if app.Resource == constant.AppResourceCustom { + appResourceDir = path.Join(global.Dir.AppResourceDir, "custom") + } + resourceDir := path.Join(appResourceDir, appKey, appDetail.Version) + + if !fileOp.Stat(installAppDir) { + if err = fileOp.CreateDir(installAppDir, constant.DirPerm); err != nil { + return + } + } + appDir := path.Join(installAppDir, req.Name) + if fileOp.Stat(appDir) { + if err = fileOp.DeleteDir(appDir); err != nil { + return + } + } + if err = fileOp.Copy(resourceDir, installAppDir); err != nil { + return + } + versionDir := path.Join(installAppDir, appDetail.Version) + if err = fileOp.Rename(versionDir, appDir); err != nil { + return + } + envPath := path.Join(appDir, ".env") + envParams := make(map[string]string) + if fileOp.Stat(envPath) { + envs, _ := gotenv.Read(envPath) + if envParams = maps.Clone(envs); envParams == nil { + envParams = make(map[string]string) + } + } + handleMap(req.Params, envParams) + if err = env.Write(envParams, envPath); err != nil { + return + } + if err := fileOp.WriteFile(appInstall.GetComposePath(), strings.NewReader(appInstall.DockerCompose), constant.DirPerm); err != nil { + return err + } + return +} + +func runScript(task *task.Task, appInstall *model.AppInstall, operate string) error { + workDir := appInstall.GetPath() + scriptPath := "" + switch operate { + case "init": + scriptPath = path.Join(workDir, "scripts", "init.sh") + case "upgrade": + scriptPath = path.Join(workDir, "scripts", "upgrade.sh") + case "uninstall": + scriptPath = path.Join(workDir, "scripts", "uninstall.sh") + } + fileOp := files.NewFileOp() + if !fileOp.Stat(scriptPath) { + return nil + } + _ = fileOp.ChmodR(scriptPath, constant.DirPerm, false) + logStr := i18n.GetWithName("ExecShell", operate) + task.LogStart(logStr) + + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute), cmd.WithWorkDir(workDir)) + if err := cmdMgr.RunBashC(scriptPath); err != nil { + task.LogFailedWithErr(logStr, err) + return err + } + task.LogSuccess(logStr) + return nil +} + +func checkContainerNameIsExist(containerName, appDir string) (bool, error) { + client, err := composeV2.NewDockerClient() + if err != nil { + return false, err + } + defer client.Close() + var options container.ListOptions + list, err := client.ContainerList(context.Background(), options) + if err != nil { + return false, err + } + for _, container := range list { + if containerName == container.Names[0][1:] { + if workDir, ok := container.Labels[composeWorkdirLabel]; ok { + if workDir != appDir { + return true, nil + } + } else { + return true, nil + } + } + + } + return false, nil +} + +func upApp(task *task.Task, appInstall *model.AppInstall, pullImages bool) error { + upProject := func(appInstall *model.AppInstall) (err error) { + var ( + out string + errMsg string + ) + if pullImages && appInstall.App.Type != "php" { + envByte, err := files.NewFileOp().GetContent(appInstall.GetEnvPath()) + if err != nil { + return err + } + images, err := composeV2.GetImagesFromDockerCompose(envByte, []byte(appInstall.DockerCompose)) + if err != nil { + return err + } + imagePrefix := xpack.GetImagePrefix() + dockerCLi, err := docker.NewClient() + if err != nil { + return err + } + for _, image := range images { + if imagePrefix != "" { + lastSlashIndex := strings.LastIndex(image, "/") + if lastSlashIndex != -1 { + image = image[lastSlashIndex+1:] + } + image = imagePrefix + "/" + image + } + + task.Log(i18n.GetWithName("PullImageStart", image)) + if err = dockerCLi.PullImageWithProcess(task, image); err != nil { + errOur := err.Error() + if errOur != "" { + if strings.Contains(errOur, "no such host") { + errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":" + } + if strings.Contains(errOur, "Error response from daemon") { + errMsg = i18n.GetMsgByKey("PullImageTimeout") + ":" + } + } + appInstall.Message = errMsg + errOur + installErr := errors.New(appInstall.Message) + task.LogFailedWithErr(i18n.GetMsgByKey("PullImage"), installErr) + if exist, _ := dockerCLi.ImageExists(image); !exist { + return installErr + } else { + task.Log(i18n.GetMsgByKey("UseExistImage")) + } + } else { + task.Log(i18n.GetMsgByKey("PullImageSuccess")) + } + } + } + logStr := fmt.Sprintf("%s %s", i18n.GetMsgByKey("Run"), i18n.GetMsgByKey("App")) + task.Log(logStr) + out, err = compose.Up(appInstall.GetComposePath()) + if err != nil { + if out != "" { + appInstall.Message = errMsg + out + err = errors.New(out) + } + task.LogFailedWithErr(logStr, err) + return err + } + task.LogSuccess(logStr) + return + } + exist, _ := appInstallRepo.GetFirst(repo.WithByID(appInstall.ID)) + if exist.ID > 0 { + containerNames, err := getContainerNames(*appInstall) + if err == nil { + if len(containerNames) > 0 { + appInstall.ContainerName = strings.Join(containerNames, ",") + } + _ = appInstallRepo.Save(context.Background(), appInstall) + } + } + if err := upProject(appInstall); err != nil { + if appInstall.Message == "" { + appInstall.Message = err.Error() + } + appInstall.Status = constant.StatusUpErr + _ = appInstallRepo.Save(context.Background(), appInstall) + return err + } else { + appInstall.Status = constant.StatusRunning + _ = appInstallRepo.Save(context.Background(), appInstall) + return nil + } +} + +func rebuildApp(appInstall model.AppInstall) error { + appInstall.Status = constant.StatusRebuilding + _ = appInstallRepo.Save(context.Background(), &appInstall) + go func() { + dockerComposePath := appInstall.GetComposePath() + out, err := compose.Down(dockerComposePath) + if err != nil { + _ = handleErr(appInstall, err, out) + return + } + out, err = compose.Up(appInstall.GetComposePath()) + if err != nil { + _ = handleErr(appInstall, err, out) + return + } + containerNames, err := getContainerNames(appInstall) + if err != nil { + _ = handleErr(appInstall, err, out) + return + } + appInstall.ContainerName = strings.Join(containerNames, ",") + + appInstall.Status = constant.StatusRunning + _ = appInstallRepo.Save(context.Background(), &appInstall) + }() + return nil +} + +func getAppDetails(details []model.AppDetail, versions []dto.AppConfigVersion) map[string]model.AppDetail { + appDetails := make(map[string]model.AppDetail, len(details)) + for _, old := range details { + old.Status = constant.AppTakeDown + appDetails[old.Version] = old + } + for _, v := range versions { + version := v.Name + detail, ok := appDetails[version] + if ok { + detail.Status = constant.AppNormal + appDetails[version] = detail + } else { + appDetails[version] = model.AppDetail{ + Version: version, + Status: constant.AppNormal, + } + } + } + return appDetails +} + +func getApps(oldApps []model.App, items []dto.AppDefine, systemVersion string, task *task.Task) map[string]model.App { + apps := make(map[string]model.App, len(oldApps)) + for _, old := range oldApps { + old.Status = constant.AppTakeDown + apps[old.Key] = old + } + for _, item := range items { + config := item.AppProperty + if config.Version > 0 && common.CompareVersion(strconv.FormatFloat(config.Version, 'f', -1, 64), systemVersion) { + task.Log(i18n.GetWithName("AppVersionNotMatch", item.Name)) + continue + } + if config.Deprecated > 0 && common.CompareAppVersion(systemVersion, strconv.FormatFloat(config.Deprecated, 'f', -1, 64)) { + continue + } + + key := config.Key + app, ok := apps[key] + if !ok { + app = model.App{} + } + app.RequiredPanelVersion = config.Version + app.Resource = constant.AppResourceRemote + app.Name = item.Name + app.Limit = config.Limit + app.Key = key + app.ShortDescZh = config.ShortDescZh + app.ShortDescEn = config.ShortDescEn + description, _ := json.Marshal(config.Description) + app.Description = string(description) + app.Website = config.Website + app.Document = config.Document + app.Github = config.Github + app.Type = config.Type + app.CrossVersionUpdate = config.CrossVersionUpdate + app.Status = constant.AppNormal + app.LastModified = item.LastModified + app.ReadMe = item.ReadMe + app.MemoryRequired = config.MemoryRequired + app.Architectures = strings.Join(config.Architectures, ",") + app.GpuSupport = config.GpuSupport + app.BatchInstallSupport = config.BatchInstallSupport + apps[key] = app + } + return apps +} + +func handleLocalAppDetail(versionDir string, appDetail *model.AppDetail) error { + fileOp := files.NewFileOp() + dockerComposePath := path.Join(versionDir, "docker-compose.yml") + if !fileOp.Stat(dockerComposePath) { + return buserr.WithName("ErrFileNotFound", "docker-compose.yml") + } + dockerComposeByte, _ := fileOp.GetContent(dockerComposePath) + if dockerComposeByte == nil { + return buserr.WithName("ErrFileParseApp", "docker-compose.yml") + } + appDetail.DockerCompose = string(dockerComposeByte) + paramPath := path.Join(versionDir, "data.yml") + if !fileOp.Stat(paramPath) { + return buserr.WithName("ErrFileNotFound", "data.yml") + } + paramByte, _ := fileOp.GetContent(paramPath) + if paramByte == nil { + return buserr.WithName("ErrFileNotFound", "data.yml") + } + appParamConfig := dto.LocalAppParam{} + if err := yaml.Unmarshal(paramByte, &appParamConfig); err != nil { + return buserr.WithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()}, err) + } + dataJson, err := json.Marshal(appParamConfig.AppParams) + if err != nil { + return buserr.WithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()}, err) + } + var appParam dto.AppForm + if err = json.Unmarshal(dataJson, &appParam); err != nil { + return buserr.WithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()}, err) + } + for _, formField := range appParam.FormFields { + if strings.Contains(formField.EnvKey, " ") { + return buserr.WithName("ErrAppParamKey", formField.EnvKey) + } + } + + var dataMap map[string]interface{} + err = yaml.Unmarshal(paramByte, &dataMap) + if err != nil { + return buserr.WithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()}, err) + } + + additionalProperties, _ := dataMap["additionalProperties"].(map[string]interface{}) + formFieldsInterface, ok := additionalProperties["formFields"] + if ok { + formFields, ok := formFieldsInterface.([]interface{}) + if !ok { + return buserr.WithName("ErrAppParamKey", "formFields") + } + for _, item := range formFields { + field := item.(map[string]interface{}) + for key, value := range field { + if value == nil { + return buserr.WithName("ErrAppParamKey", key) + } + } + } + } + + appDetail.Params = string(dataJson) + return nil +} + +func handleLocalApp(appDir string) (app *model.App, err error) { + fileOp := files.NewFileOp() + configYamlPath := path.Join(appDir, "data.yml") + if !fileOp.Stat(configYamlPath) { + err = buserr.WithName("ErrFileNotFound", "data.yml") + return + } + iconPath := path.Join(appDir, "logo.png") + if !fileOp.Stat(iconPath) { + err = buserr.WithName("ErrFileNotFound", "logo.png") + return + } + configYamlByte, err := fileOp.GetContent(configYamlPath) + if err != nil { + err = buserr.WithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()}, err) + return + } + localAppDefine := dto.LocalAppAppDefine{} + if err = yaml.Unmarshal(configYamlByte, &localAppDefine); err != nil { + err = buserr.WithMap("ErrFileParseApp", map[string]interface{}{"name": "data.yml", "err": err.Error()}, err) + return + } + appDefine := localAppDefine.AppProperty + app = &model.App{} + app.Name = appDefine.Name + app.TagsKey = append(appDefine.Tags, "Local") + app.Type = appDefine.Type + app.CrossVersionUpdate = appDefine.CrossVersionUpdate + app.Limit = appDefine.Limit + app.Recommend = appDefine.Recommend + app.Website = appDefine.Website + app.Github = appDefine.Github + app.Document = appDefine.Document + if appDefine.ShortDescZh != "" { + appDefine.Description.Zh = appDefine.ShortDescZh + } + if appDefine.ShortDescEn != "" { + appDefine.Description.En = appDefine.ShortDescEn + } + desc, _ := json.Marshal(appDefine.Description) + app.Description = string(desc) + app.Key = "local" + appDefine.Key + app.Architectures = strings.Join(appDefine.Architectures, ",") + app.GpuSupport = appDefine.GpuSupport + app.MemoryRequired = appDefine.MemoryRequired + + app.Resource = constant.AppResourceLocal + app.Status = constant.AppNormal + app.Recommend = 9999 + readMeByte, err := fileOp.GetContent(path.Join(appDir, "README.md")) + if err == nil { + app.ReadMe = string(readMeByte) + } + iconByte, _ := fileOp.GetContent(iconPath) + if iconByte != nil { + iconStr := base64.StdEncoding.EncodeToString(iconByte) + app.Icon = iconStr + } + return +} + +func handleErr(install model.AppInstall, err error, out string) error { + reErr := err + install.Message = err.Error() + if out != "" { + install.Message = out + reErr = errors.New(out) + } + install.Status = constant.StatusUpErr + _ = appInstallRepo.Save(context.Background(), &install) + return reErr +} + +func doNotNeedSync(installed model.AppInstall) bool { + return installed.Status == constant.StatusInstalling || installed.Status == constant.StatusRebuilding || installed.Status == constant.StatusUpgrading || + installed.Status == constant.StatusSyncing || installed.Status == constant.StatusUninstalling || installed.Status == constant.StatusInstallErr +} + +func synAppInstall(containers map[string]container.Summary, appInstall *model.AppInstall, force bool) { + oldStatus := appInstall.Status + containerNames := strings.Split(appInstall.ContainerName, ",") + if len(containers) == 0 { + if appInstall.Status == constant.StatusUpErr && !force { + return + } + appInstall.Status = constant.StatusError + appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(containerNames, ",")).Error() + _ = appInstallRepo.Save(context.Background(), appInstall) + return + } + notFoundNames := make([]string, 0) + exitNames := make([]string, 0) + exitedCount := 0 + pausedCount := 0 + runningCount := 0 + restartingCount := 0 + total := len(containerNames) + for _, name := range containerNames { + if con, ok := containers["/"+name]; ok { + switch con.State { + case "exited": + exitedCount++ + exitNames = append(exitNames, name) + case "running": + runningCount++ + case "restarting": + restartingCount++ + case "paused": + pausedCount++ + } + } else { + notFoundNames = append(notFoundNames, name) + } + } + switch { + case exitedCount == total: + appInstall.Status = constant.StatusStopped + case runningCount == total: + appInstall.Status = constant.StatusRunning + if oldStatus == constant.StatusRunning { + return + } + case restartingCount == total: + appInstall.Status = constant.StatusRestarting + case pausedCount == total: + appInstall.Status = constant.StatusPaused + case len(notFoundNames) == total: + if appInstall.Status == constant.StatusUpErr && !force { + return + } + appInstall.Status = constant.StatusError + appInstall.Message = buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error() + default: + var msg string + if exitedCount > 0 { + msg = buserr.WithName("ErrContainerMsg", strings.Join(exitNames, ",")).Error() + } + if len(notFoundNames) > 0 { + msg += buserr.WithName("ErrContainerNotFound", strings.Join(notFoundNames, ",")).Error() + } + if msg == "" { + msg = buserr.New("ErrAppWarn").Error() + } + appInstall.Message = msg + appInstall.Status = constant.StatusUnHealthy + } + _ = appInstallRepo.Save(context.Background(), appInstall) +} + +func handleInstalled(appInstallList []model.AppInstall, updated, sync, checkUpdate bool) ([]response.AppInstallDTO, error) { + var ( + res []response.AppInstallDTO + containersMap map[string]container.Summary + ) + if sync { + cli, err := docker.NewClient() + if err == nil { + defer cli.Close() + containers, err := cli.ListAllContainers() + if err == nil { + containersMap = make(map[string]container.Summary, len(containers)) + for _, contain := range containers { + containersMap[contain.Names[0]] = contain + } + } + } + } + + for _, installed := range appInstallList { + if updated && ignoreUpdate(installed) { + continue + } + + if sync && !doNotNeedSync(installed) { + synAppInstall(containersMap, &installed, false) + } + + installDTO := response.AppInstallDTO{ + ID: installed.ID, + Name: installed.Name, + AppID: installed.AppId, + AppDetailID: installed.AppDetailId, + Version: installed.Version, + Status: installed.Status, + Message: installed.Message, + HttpPort: installed.HttpPort, + HttpsPort: installed.HttpsPort, + AppName: installed.App.Name, + AppKey: installed.App.Key, + AppType: installed.App.Type, + Path: installed.GetPath(), + CreatedAt: installed.CreatedAt, + WebUI: installed.WebUI, + App: response.AppDetail{ + Github: installed.App.Github, + Website: installed.App.Website, + Document: installed.App.Document, + }, + Favorite: installed.Favorite, + Container: installed.ContainerName, + ServiceName: strings.ToLower(installed.ServiceName), + } + + if !updated && !checkUpdate { + installDTO.LinkDB = hasLinkDB(installed.ID) + res = append(res, installDTO) + continue + } + + if installed.Version == "latest" { + if checkUpdate { + installDTO.CanUpdate = false + installDTO.LinkDB = hasLinkDB(installed.ID) + res = append(res, installDTO) + } + continue + } + + installDTO.DockerCompose = installed.DockerCompose + installDTO.IsEdit = isEditCompose(installed) + + details, err := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID)) + if err != nil { + return nil, err + } + + var versions []string + for _, appDetail := range details { + ignores, _ := appIgnoreUpgradeRepo.List(runtimeRepo.WithDetailId(appDetail.ID), appIgnoreUpgradeRepo.WithScope("version")) + if len(ignores) > 0 { + continue + } + if common.IsCrossVersion(installed.Version, appDetail.Version) && !installed.App.CrossVersionUpdate { + continue + } + versions = append(versions, appDetail.Version) + } + + if len(versions) == 0 { + if checkUpdate { + installDTO.CanUpdate = false + installDTO.LinkDB = hasLinkDB(installed.ID) + res = append(res, installDTO) + } + continue + } + + versions = common.GetSortedVersions(versions) + lastVersion := versions[0] + + if installed.App.Key == constant.AppMysql || installed.App.Key == constant.AppMysqlCluster { + for _, version := range versions { + majorVersion := getMajorVersion(installed.Version) + if !strings.HasPrefix(version, majorVersion) { + continue + } else { + lastVersion = version + break + } + } + } + + if common.IsCrossVersion(installed.Version, lastVersion) { + installDTO.CanUpdate = installed.App.CrossVersionUpdate + } else { + installDTO.CanUpdate = common.CompareVersion(lastVersion, installed.Version) + } + + if updated { + if installDTO.CanUpdate { + res = append(res, installDTO) + } + } else if checkUpdate { + installDTO.LinkDB = hasLinkDB(installed.ID) + res = append(res, installDTO) + } + } + + return res, nil +} + +func getAppInstallByKey(key string) (model.AppInstall, error) { + app, err := appRepo.GetFirst(appRepo.WithKey(key)) + if err != nil { + return model.AppInstall{}, err + } + appInstall, err := appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID)) + if err != nil { + return model.AppInstall{}, err + } + return appInstall, nil +} + +func getAppInstallPort(key string) (httpPort, httpsPort int, err error) { + install, err := getAppInstallByKey(key) + if err != nil { + return + } + httpPort = install.HttpPort + httpsPort = install.HttpsPort + return +} + +func updateToolApp(installed *model.AppInstall) { + tooKey, ok := dto.AppToolMap[installed.App.Key] + if !ok { + return + } + toolInstall, _ := getAppInstallByKey(tooKey) + if reflect.DeepEqual(toolInstall, model.AppInstall{}) { + return + } + paramMap := make(map[string]string) + _ = json.Unmarshal([]byte(installed.Param), ¶mMap) + envMap := make(map[string]interface{}) + _ = json.Unmarshal([]byte(toolInstall.Env), &envMap) + if password, ok := paramMap["PANEL_DB_ROOT_PASSWORD"]; ok { + envMap["PANEL_DB_ROOT_PASSWORD"] = password + } + if _, ok := envMap["PANEL_REDIS_HOST"]; ok { + envMap["PANEL_REDIS_HOST"] = installed.ServiceName + } + if _, ok := envMap["PANEL_DB_HOST"]; ok { + envMap["PANEL_DB_HOST"] = installed.ServiceName + } + + envPath := path.Join(toolInstall.GetPath(), ".env") + contentByte, err := json.Marshal(envMap) + if err != nil { + global.LOG.Errorf("update tool app [%s] error : %s", toolInstall.Name, err.Error()) + return + } + envFileMap := make(map[string]string) + handleMap(envMap, envFileMap) + if err = env.Write(envFileMap, envPath); err != nil { + global.LOG.Errorf("update tool app [%s] error : %s", toolInstall.Name, err.Error()) + return + } + toolInstall.Env = string(contentByte) + if err := appInstallRepo.Save(context.Background(), &toolInstall); err != nil { + global.LOG.Errorf("update tool app [%s] error : %s", toolInstall.Name, err.Error()) + return + } + if out, err := compose.Down(toolInstall.GetComposePath()); err != nil { + global.LOG.Errorf("update tool app [%s] error : %s", toolInstall.Name, out) + return + } + if out, err := compose.Up(toolInstall.GetComposePath()); err != nil { + global.LOG.Errorf("update tool app [%s] error : %s", toolInstall.Name, out) + return + } +} + +func addDockerComposeCommonParam(composeMap map[string]interface{}, serviceName string, req request.AppContainerConfig, params map[string]interface{}) error { + services, serviceValid := composeMap["services"].(map[string]interface{}) + if !serviceValid { + return buserr.New("ErrFileParse") + } + imagePreFix := xpack.GetImagePrefix() + if imagePreFix != "" { + for _, service := range services { + serviceValue := service.(map[string]interface{}) + if image, ok := serviceValue["image"]; ok { + imageStr := image.(string) + lastSlashIndex := strings.LastIndex(imageStr, "/") + if lastSlashIndex != -1 { + imageStr = imageStr[lastSlashIndex+1:] + } + imageStr = imagePreFix + "/" + imageStr + serviceValue["image"] = imageStr + } + } + } + + service, serviceExist := services[serviceName] + if !serviceExist { + return buserr.New("ErrFileParse") + } + serviceValue := service.(map[string]interface{}) + + deploy := map[string]interface{}{} + if de, ok := serviceValue["deploy"]; ok { + deploy = de.(map[string]interface{}) + } + resource := map[string]interface{}{} + if res, ok := deploy["resources"]; ok { + resource = res.(map[string]interface{}) + } + resource["limits"] = map[string]interface{}{ + "cpus": "${CPUS}", + "memory": "${MEMORY_LIMIT}", + } + if req.GpuConfig { + resource["reservations"] = map[string]interface{}{ + "devices": []map[string]interface{}{ + { + "driver": "nvidia", + "count": "all", + "capabilities": []string{"gpu"}, + }, + }, + } + } + deploy["resources"] = resource + serviceValue["deploy"] = deploy + + if req.RestartPolicy != "" { + if req.RestartPolicy == "on-failure" { + serviceValue["restart"] = "on-failure:5" + } else { + serviceValue["restart"] = req.RestartPolicy + } + } + + ports, ok := serviceValue["ports"].([]interface{}) + if ok { + for i, port := range ports { + portStr, portOK := port.(string) + if !portOK { + continue + } + portArray := strings.Split(portStr, ":") + if len(portArray) == 2 { + portArray = append([]string{"${HOST_IP}"}, portArray...) + } + ports[i] = strings.Join(portArray, ":") + } + serviceValue["ports"] = ports + } + + params[constant.CPUS] = "0" + params[constant.MemoryLimit] = "0" + if req.Advanced { + if req.CpuQuota > 0 { + params[constant.CPUS] = req.CpuQuota + } + if req.MemoryLimit > 0 { + params[constant.MemoryLimit] = strconv.FormatFloat(req.MemoryLimit, 'f', -1, 32) + req.MemoryUnit + } + } + _, portExist := serviceValue["ports"].([]interface{}) + if portExist { + allowHost := "127.0.0.1" + if req.Advanced && req.AllowPort { + allowHost = req.SpecifyIP + } + params[constant.HostIP] = allowHost + } + services[serviceName] = serviceValue + return nil +} + +func getAppCommonConfig(envs map[string]interface{}) request.AppContainerConfig { + config := request.AppContainerConfig{} + + if hostIp, ok := envs[constant.HostIP]; ok { + config.AllowPort = hostIp.(string) != "127.0.0.1" + config.SpecifyIP = hostIp.(string) + } else { + config.AllowPort = true + } + if cpuCore, ok := envs[constant.CPUS]; ok { + numStr, ok := cpuCore.(string) + if ok { + num, err := strconv.ParseFloat(numStr, 64) + if err == nil { + config.CpuQuota = num + } + } else { + num64, flOk := cpuCore.(float64) + if flOk { + config.CpuQuota = num64 + } + } + } else { + config.CpuQuota = 0 + } + if memLimit, ok := envs[constant.MemoryLimit]; ok { + matches := re.GetRegex(re.NumberAlphaPattern).FindStringSubmatch(memLimit.(string)) + if len(matches) == 3 { + num, err := strconv.ParseFloat(matches[1], 64) + if err == nil { + unit := matches[2] + config.MemoryLimit = num + config.MemoryUnit = unit + } + } + } else { + config.MemoryLimit = 0 + config.MemoryUnit = "M" + } + + if containerName, ok := envs[constant.ContainerName]; ok { + config.ContainerName = containerName.(string) + } + + return config +} + +func isHostModel(dockerCompose string) bool { + composeMap := make(map[string]interface{}) + _ = yaml.Unmarshal([]byte(dockerCompose), &composeMap) + services, serviceValid := composeMap["services"].(map[string]interface{}) + if !serviceValid { + return false + } + for _, service := range services { + serviceValue := service.(map[string]interface{}) + if value, ok := serviceValue["network_mode"]; ok && value == "host" { + return true + } + } + return false +} + +func getRestartPolicy(yml string) string { + var project docker.ComposeProject + if err := yaml.Unmarshal([]byte(yml), &project); err != nil { + return "" + } + for _, service := range project.Services { + return service.Restart + } + return "" +} + +func getMajorVersion(version string) string { + parts := strings.Split(version, ".") + if len(parts) >= 2 { + return parts[0] + "." + parts[1] + } + return version +} + +func ignoreUpdate(installed model.AppInstall) bool { + if installed.App.Type == "php" || installed.Status == constant.StatusInstalling { + return true + } + ignores, _ := appIgnoreUpgradeRepo.List(appDetailRepo.WithAppId(installed.AppId), appIgnoreUpgradeRepo.WithScope("all")) + if len(ignores) > 0 { + return true + } + if installed.App.Key == constant.AppMysql { + majorVersion := getMajorVersion(installed.Version) + appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(installed.App.ID)) + for _, appDetail := range appDetails { + if strings.HasPrefix(appDetail.Version, majorVersion) && common.CompareVersion(appDetail.Version, installed.Version) { + return false + } + } + return true + } + if installed.App.Key == "sqlbot" { + if common.CompareVersion("1.1.0", installed.Version) { + return true + } + } + return false +} + +func RequestDownloadCallBack(downloadCallBackUrl string) { + if downloadCallBackUrl == "" { + return + } + _, _, _ = req_helper.HandleRequest(downloadCallBackUrl, http.MethodGet, constant.TimeOut5s) +} + +func getAppTags(appID uint, lang string) ([]response.TagDTO, error) { + appTags, err := appTagRepo.GetByAppId(appID) + if err != nil { + return nil, err + } + var tagIds []uint + for _, at := range appTags { + tagIds = append(tagIds, at.TagId) + } + tags, err := tagRepo.GetByIds(tagIds) + if err != nil { + return nil, err + } + var res []response.TagDTO + for _, t := range tags { + var translations = make(map[string]string) + _ = json.Unmarshal([]byte(t.Translations), &translations) + if name, ok := translations[lang]; ok { + tagDTO := response.TagDTO{ + ID: t.ID, + Key: t.Key, + Name: name, + } + res = append(res, tagDTO) + } + } + return res, nil +} + +func handleSiteDir(app model.App, appDetail model.AppDetail, req request.AppInstallCreate, t *task.Task) error { + if app.Key == "openresty" && (app.Resource == "remote" || app.Resource == "custom") && common.CompareVersion(appDetail.Version, "1.27") { + if dir, ok := req.Params["WEBSITE_DIR"]; ok { + siteDir := dir.(string) + if siteDir == "" || !strings.HasPrefix(siteDir, "/") { + siteDir = path.Join(global.Dir.DataDir, dir.(string)) + } + req.Params["WEBSITE_DIR"] = siteDir + oldWebStePath, _ := settingRepo.GetValueByKey("WEBSITE_DIR") + fileOp := files.NewFileOp() + if oldWebStePath != "" && oldWebStePath != siteDir && fileOp.Stat(oldWebStePath) { + t.Log(i18n.GetWithName("MoveSiteDir", siteDir)) + if fileOp.Stat(siteDir) { + if fileOp.Stat(path.Join(siteDir, "conf.d")) { + _ = fileOp.Rename(path.Join(siteDir, "conf.d"), path.Join(siteDir, "conf.d.bak")) + } + if fileOp.Stat(path.Join(siteDir, "sites")) { + _ = fileOp.Rename(path.Join(siteDir, "sites"), path.Join(siteDir, "sites.bak")) + } + if err := fileOp.Rename(path.Join(oldWebStePath, "sites"), path.Join(siteDir, "sites")); err != nil { + return err + } + if err := fileOp.Rename(path.Join(oldWebStePath, "conf.d"), path.Join(siteDir, "conf.d")); err != nil { + return err + } + } else { + err := fileOp.Rename(oldWebStePath, siteDir) + if err != nil { + return err + } + } + t.Log(i18n.GetMsgByKey("MoveSiteDirSuccess")) + } + if !fileOp.Stat(siteDir) { + _ = fileOp.CreateDir(siteDir, constant.DirPerm) + _ = fileOp.CreateDir(path.Join(siteDir, "conf.d"), constant.DirPerm) + } + err := settingRepo.UpdateOrCreate("WEBSITE_DIR", siteDir) + if err != nil { + return err + } + go RestartPHPRuntime() + } + } + return nil +} + +func handleOpenrestyFile(appInstall *model.AppInstall) error { + websites, _ := websiteRepo.List() + hasDefaultWebsite := false + for _, website := range websites { + if website.DefaultServer { + hasDefaultWebsite = true + break + } + } + if err := handleSSLConfig(appInstall, hasDefaultWebsite, true); err != nil { + return err + } + if len(websites) == 0 { + return nil + } + if hasDefaultWebsite { + if err := handleDefaultServer(appInstall); err != nil { + return err + } + } + return createAllWebsitesWAFConfig(websites) +} + +func handleDefaultServer(appInstall *model.AppInstall) error { + installDir := appInstall.GetPath() + defaultConfigPath := path.Join(installDir, "conf", "default", "00.default.conf") + fileOp := files.NewFileOp() + content, err := fileOp.GetContent(defaultConfigPath) + if err != nil { + return err + } + newContent := strings.ReplaceAll(string(content), "default_server", "") + if err := fileOp.WriteFile(defaultConfigPath, strings.NewReader(newContent), constant.FilePerm); err != nil { + return err + } + return nil +} + +func handleSSLConfig(appInstall *model.AppInstall, hasDefaultWebsite bool, sslRejectHandshake bool) error { + sslDir := path.Join(appInstall.GetPath(), "conf", "ssl") + fileOp := files.NewFileOp() + if !fileOp.Stat(sslDir) { + return errors.New("ssl dir not found") + } + hasDefaultSSL := fileOp.Stat(path.Join(sslDir, "fullchain.pem")) && fileOp.Stat(path.Join(sslDir, "privkey.pem")) && fileOp.Stat(path.Join(sslDir, "root_ssl.conf")) + if !hasDefaultSSL { + ca, _ := websiteCARepo.GetFirst(repo.WithByName("1Panel")) + if ca.ID == 0 { + global.LOG.Errorf("create openresty default ssl failed ca not found") + return nil + } + caService := NewIWebsiteCAService() + caRequest := request.WebsiteCAObtain{ + ID: ca.ID, + Domains: "localhost", + KeyType: "4096", + Time: 99, + Unit: "year", + Dir: sslDir, + PushDir: true, + } + websiteSSL, err := caService.ObtainSSL(caRequest) + if err != nil { + return err + } + defer func() { + _ = NewIWebsiteSSLService().Delete([]uint{websiteSSL.ID}) + }() + } + defaultConfigPath := path.Join(appInstall.GetPath(), "conf", "default", "00.default.conf") + content, err := os.ReadFile(defaultConfigPath) + if err != nil { + return err + } + defaultConfig, err := parser.NewStringParser(string(content)).Parse() + if err != nil { + return err + } + defaultConfig.FilePath = defaultConfigPath + defaultServer := defaultConfig.FindServers()[0] + updateDefaultServer(defaultServer, appInstall.HttpPort, appInstall.HttpsPort, !hasDefaultWebsite, true) + defaultServer.UpdateDirective("include", []string{"/usr/local/openresty/nginx/conf/ssl/root_ssl.conf"}) + defaultServer.UpdateDirective("http2", []string{"on"}) + if sslRejectHandshake { + defaultServer.UpdateDirective("ssl_reject_handshake", []string{"on"}) + } else { + defaultServer.RemoveDirective("ssl_reject_handshake", []string{}) + } + if err = nginx.WriteConfig(defaultConfig, nginx.IndentedStyle); err != nil { + return err + } + return nil +} + +func SyncTags(remoteProperties dto.ExtraProperties) error { + tx, ctx := getTxAndContext() + defer tx.Rollback() + localTags, _ := tagRepo.All() + localTagsMap := make(map[string]*model.Tag) + for i := range localTags { + localTagsMap[localTags[i].Key] = &localTags[i] + } + var err error + remoteTagsMap := make(map[string]*dto.Tag) + for i := range remoteProperties.Tags { + remoteTagsMap[remoteProperties.Tags[i].Key] = &remoteProperties.Tags[i] + } + + for key, localTag := range localTagsMap { + if _, exists := remoteTagsMap[key]; !exists { + _ = tagRepo.DeleteByID(ctx, localTag.ID) + } + } + + for _, remoteTag := range remoteProperties.Tags { + translations, _ := json.Marshal(remoteTag.Locales) + + if existTag, exists := localTagsMap[remoteTag.Key]; exists { + if needsUpdate(existTag, remoteTag, string(translations)) { + existTag.Name = remoteTag.Name + existTag.Sort = remoteTag.Sort + existTag.Translations = string(translations) + + if err = tagRepo.Save(ctx, existTag); err != nil { + return err + } + } + } else { + newTag := &model.Tag{ + Key: remoteTag.Key, + Name: remoteTag.Name, + Sort: remoteTag.Sort, + Translations: string(translations), + } + if err = tagRepo.Create(ctx, newTag); err != nil { + return err + } + } + } + + tx.Commit() + return nil +} + +func needsUpdate(localTag *model.Tag, remoteTag dto.Tag, translations string) bool { + return localTag.Name != remoteTag.Name || + localTag.Sort != remoteTag.Sort || + localTag.Translations != translations +} + +func hasLinkDB(installID uint) bool { + resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(installID)) + hasDB := false + if len(resources) > 0 { + for _, resource := range resources { + if resource.Key == constant.AppPostgres || resource.Key == constant.AppMysql || + resource.Key == constant.AppMariaDB || resource.Key == constant.AppMysqlCluster || + resource.Key == constant.AppPostgresql || resource.Key == constant.AppPostgresqlCluster { + hasDB = true + break + } + } + } + return hasDB +} + +func isEditCompose(installed model.AppInstall) bool { + detail, _ := appDetailRepo.GetFirst(repo.WithByID(installed.AppDetailId)) + rawCompose, err := getUpgradeCompose(installed, detail) + if rawCompose == "" || err != nil { + return false + } + if rawCompose != installed.DockerCompose { + return true + } + return false +} + +func getAppVersions(key string, details []model.AppDetail) []string { + var ( + versionsRaw []string + versions []string + ) + hasLatest := false + latestVersion := "" + for _, detail := range details { + if key != "mssql" && strings.Contains(detail.Version, "latest") { + hasLatest = true + latestVersion = detail.Version + continue + } + if key == "openresty" && !common.CompareAppVersion(detail.Version, "1.27") { + continue + } + versionsRaw = append(versionsRaw, detail.Version) + } + versions = common.GetSortedVersions(versionsRaw) + if hasLatest { + versions = append([]string{latestVersion}, versions...) + } + return versions +} diff --git a/agent/app/service/backup.go b/agent/app/service/backup.go new file mode 100644 index 0000000..942c020 --- /dev/null +++ b/agent/app/service/backup.go @@ -0,0 +1,632 @@ +package service + +import ( + "bufio" + "encoding/base64" + "encoding/json" + "fmt" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cloud_storage" + "github.com/1Panel-dev/1Panel/agent/utils/cloud_storage/client" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/jinzhu/copier" +) + +type BackupService struct{} + +type IBackupService interface { + CheckUsed(name string, isPublic bool) error + + LoadBackupOptions() ([]dto.BackupOption, error) + SearchWithPage(search dto.SearchPageWithType) (int64, interface{}, error) + Create(backupDto dto.BackupOperate) error + CheckConn(req dto.BackupOperate) dto.BackupCheckRes + GetBuckets(backupDto dto.ForBuckets) ([]interface{}, error) + Update(req dto.BackupOperate) error + Delete(id uint) error + RefreshToken(req dto.OperateByID) error + GetLocalDir() (string, error) + UploadForRecover(req dto.UploadForRecover) error + + MysqlBackup(db dto.CommonBackup) error + PostgresqlBackup(db dto.CommonBackup) error + MysqlRecover(db dto.CommonRecover) error + PostgresqlRecover(db dto.CommonRecover) error + MysqlRecoverByUpload(req dto.CommonRecover) error + PostgresqlRecoverByUpload(req dto.CommonRecover) error + + RedisBackup(db dto.CommonBackup) error + RedisRecover(db dto.CommonRecover) error + + WebsiteBackup(db dto.CommonBackup) error + WebsiteRecover(req dto.CommonRecover) error + + AppBackup(db dto.CommonBackup) (*model.BackupRecord, error) + AppRecover(req dto.CommonRecover) error +} + +func NewIBackupService() IBackupService { + return &BackupService{} +} + +func (u *BackupService) GetLocalDir() (string, error) { + account, err := backupRepo.Get(repo.WithByType(constant.Local)) + if err != nil { + return "", err + } + return account.BackupPath, nil +} + +func (u *BackupService) SearchWithPage(req dto.SearchPageWithType) (int64, interface{}, error) { + options := []repo.DBOption{repo.WithOrderBy("created_at desc")} + if len(req.Type) != 0 { + options = append(options, repo.WithByType(req.Type)) + } + if len(req.Info) != 0 { + options = append(options, repo.WithByType(req.Info)) + } + count, accounts, err := backupRepo.Page(req.Page, req.PageSize, options...) + if err != nil { + return 0, nil, err + } + var data []dto.BackupInfo + for _, account := range accounts { + var item dto.BackupInfo + if err := copier.Copy(&item, &account); err != nil { + global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err) + } + if item.Type != constant.Sftp && item.Type != constant.Local { + item.BackupPath = path.Join("/", strings.TrimPrefix(item.BackupPath, "/")) + } + if !item.RememberAuth { + item.AccessKey = "" + item.Credential = "" + if account.Type == constant.Sftp { + varMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil { + continue + } + delete(varMap, "passPhrase") + itemVars, _ := json.Marshal(varMap) + item.Vars = string(itemVars) + } + } else { + item.AccessKey, _ = encrypt.StringDecryptWithBase64(item.AccessKey) + item.Credential, _ = encrypt.StringDecryptWithBase64(item.Credential) + } + + if account.Type == constant.OneDrive || account.Type == constant.ALIYUN || account.Type == constant.GoogleDrive { + varMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(item.Vars), &varMap); err != nil { + continue + } + delete(varMap, "refresh_token") + delete(varMap, "drive_id") + itemVars, _ := json.Marshal(varMap) + item.Vars = string(itemVars) + } + data = append(data, item) + } + return count, data, nil +} + +func (u *BackupService) CheckConn(req dto.BackupOperate) dto.BackupCheckRes { + var res dto.BackupCheckRes + var backup model.BackupAccount + if err := copier.Copy(&backup, &req); err != nil { + res.Msg = i18n.GetMsgWithDetail("ErrStructTransform", err.Error()) + return res + } + itemAccessKey, err := base64.StdEncoding.DecodeString(backup.AccessKey) + if err != nil { + res.Msg = err.Error() + return res + } + backup.AccessKey = string(itemAccessKey) + itemCredential, err := base64.StdEncoding.DecodeString(backup.Credential) + if err != nil { + res.Msg = err.Error() + return res + } + backup.Credential = string(itemCredential) + + if req.Type == constant.OneDrive || req.Type == constant.GoogleDrive { + refreshToken, err := loadRefreshTokenByCode(&backup) + if err != nil { + res.Msg = err.Error() + return res + } + res.Token = base64.StdEncoding.EncodeToString([]byte(refreshToken)) + } + isOk, err := u.checkBackupConn(&backup) + if err != nil { + res.Msg = err.Error() + return res + } + res.IsOk = isOk + return res +} + +func (u *BackupService) Create(req dto.BackupOperate) error { + if req.Type == constant.Local { + return buserr.New("ErrBackupLocalCreate") + } + if req.Type != constant.Sftp { + req.BackupPath = strings.TrimPrefix(req.BackupPath, "/") + } + backup, _ := backupRepo.Get(repo.WithByName(req.Name)) + if backup.ID != 0 { + return buserr.New("ErrRecordExist") + } + if err := copier.Copy(&backup, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + itemAccessKey, err := base64.StdEncoding.DecodeString(backup.AccessKey) + if err != nil { + return err + } + backup.AccessKey = string(itemAccessKey) + itemCredential, err := base64.StdEncoding.DecodeString(backup.Credential) + if err != nil { + return err + } + backup.Credential = string(itemCredential) + backup.AccessKey, err = encrypt.StringEncrypt(backup.AccessKey) + if err != nil { + return err + } + backup.Credential, err = encrypt.StringEncrypt(backup.Credential) + if err != nil { + return err + } + if err := backupRepo.Create(&backup); err != nil { + return err + } + return nil +} + +func (u *BackupService) GetBuckets(req dto.ForBuckets) ([]interface{}, error) { + itemAccessKey, err := base64.StdEncoding.DecodeString(req.AccessKey) + if err != nil { + return nil, err + } + req.AccessKey = string(itemAccessKey) + itemCredential, err := base64.StdEncoding.DecodeString(req.Credential) + if err != nil { + return nil, err + } + req.Credential = string(itemCredential) + + varMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(req.Vars), &varMap); err != nil { + return nil, err + } + switch req.Type { + case constant.Sftp, constant.WebDAV: + varMap["username"] = req.AccessKey + varMap["password"] = req.Credential + case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: + varMap["accessKey"] = req.AccessKey + varMap["secretKey"] = req.Credential + } + client, err := cloud_storage.NewCloudStorageClient(req.Type, varMap) + if err != nil { + return nil, err + } + return client.ListBuckets() +} + +func (u *BackupService) Delete(id uint) error { + backup, _ := backupRepo.Get(repo.WithByID(id)) + if backup.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if backup.Type == constant.Local { + return buserr.New("ErrBackupLocalDelete") + } + if err := u.CheckUsed(backup.Name, false); err != nil { + return err + } + return backupRepo.Delete(repo.WithByID(id)) +} + +func (u *BackupService) Update(req dto.BackupOperate) error { + backup, _ := backupRepo.Get(repo.WithByID(req.ID)) + if backup.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if req.Type != constant.Sftp && req.Type != constant.Local && req.BackupPath != "/" { + req.BackupPath = strings.TrimPrefix(req.BackupPath, "/") + } + var newBackup model.BackupAccount + if err := copier.Copy(&newBackup, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + itemAccessKey, err := base64.StdEncoding.DecodeString(newBackup.AccessKey) + if err != nil { + return err + } + newBackup.AccessKey = string(itemAccessKey) + itemCredential, err := base64.StdEncoding.DecodeString(newBackup.Credential) + if err != nil { + return err + } + newBackup.Credential = string(itemCredential) + if backup.Type == constant.Local { + if err := changeLocalBackup(backup.BackupPath, newBackup.BackupPath); err != nil { + return err + } + global.Dir.LocalBackupDir = newBackup.BackupPath + } + + if backup.Type != constant.Local { + newBackup.AccessKey, err = encrypt.StringEncrypt(newBackup.AccessKey) + if err != nil { + return err + } + newBackup.Credential, err = encrypt.StringEncrypt(newBackup.Credential) + if err != nil { + return err + } + } + + newBackup.ID = backup.ID + newBackup.CreatedAt = backup.CreatedAt + newBackup.UpdatedAt = backup.UpdatedAt + if err := backupRepo.Save(&newBackup); err != nil { + return err + } + return nil +} + +func (u *BackupService) RefreshToken(req dto.OperateByID) error { + backup, _ := backupRepo.Get(repo.WithByID(req.ID)) + if backup.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + varMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { + return fmt.Errorf("failed to refresh %s - %s token, please retry, err: %v", backup.Type, backup.Name, err) + } + var ( + refreshToken string + err error + ) + switch backup.Type { + case constant.OneDrive: + refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap) + case constant.ALIYUN: + refreshToken, err = client.RefreshALIToken(varMap) + } + if err != nil { + varMap["refresh_status"] = constant.StatusFailed + varMap["refresh_msg"] = err.Error() + return fmt.Errorf("failed to refresh %s-%s token, please retry, err: %v", backup.Type, backup.Name, err) + } + varMap["refresh_status"] = constant.StatusSuccess + varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout) + varMap["refresh_token"] = refreshToken + + varsItem, _ := json.Marshal(varMap) + backup.Vars = string(varsItem) + return backupRepo.Save(&backup) +} + +func (u *BackupService) UploadForRecover(req dto.UploadForRecover) error { + fileOp := files.NewFileOp() + if !fileOp.Stat(req.TargetDir) { + if err := fileOp.CreateDir(req.TargetDir, constant.DirPerm); err != nil { + return err + } + } + return fileOp.Copy(req.FilePath, req.TargetDir) +} + +func (u *BackupService) checkBackupConn(backup *model.BackupAccount) (bool, error) { + client, err := newClient(backup, false) + if err != nil { + return false, err + } + fileItem := path.Join(global.Dir.BaseDir, "1panel/tmp/test/1panel") + if _, err := os.Stat(path.Dir(fileItem)); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(path.Dir(fileItem), os.ModePerm); err != nil { + return false, err + } + } + file, err := os.OpenFile(fileItem, os.O_WRONLY|os.O_CREATE, constant.FilePerm) + if err != nil { + return false, err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString("1Panel 备份账号测试文件。\n") + _, _ = write.WriteString("1Panel 備份賬號測試文件。\n") + _, _ = write.WriteString("1Panel Backs up account test files.\n") + _, _ = write.WriteString("1Panelアカウントのテストファイルをバックアップします。\n") + write.Flush() + + targetPath := path.Join(backup.BackupPath, "test/1panel") + if backup.Type != constant.Sftp && backup.Type != constant.Local && targetPath != "/" { + targetPath = strings.TrimPrefix(targetPath, "/") + } + + if _, err := client.Upload(fileItem, targetPath); err != nil { + return false, err + } + _, _ = client.Delete(path.Join(backup.BackupPath, "test/1panel")) + return true, nil +} + +func (u *BackupService) LoadBackupOptions() ([]dto.BackupOption, error) { + accounts, err := backupRepo.List(repo.WithOrderBy("created_at desc")) + if err != nil { + return nil, err + } + var data []dto.BackupOption + for _, account := range accounts { + var item dto.BackupOption + if err := copier.Copy(&item, &account); err != nil { + global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err) + } + data = append(data, item) + } + return data, nil +} + +func (u *BackupService) CheckUsed(name string, isPublic bool) error { + account, _ := backupRepo.Get(repo.WithByName(name), backupRepo.WithByPublic(isPublic)) + if account.ID == 0 { + return nil + } + cronjobs, _ := cronjobRepo.List() + for _, job := range cronjobs { + if job.DownloadAccountID == account.ID { + return buserr.New("ErrBackupInUsed") + } + ids := strings.Split(job.SourceAccountIDs, ",") + for _, idItem := range ids { + if idItem == fmt.Sprintf("%v", account.ID) { + return buserr.New("ErrBackupInUsed") + } + } + } + return nil +} + +func NewBackupClientWithID(id uint) (*model.BackupAccount, cloud_storage.CloudStorageClient, error) { + account, _ := backupRepo.Get(repo.WithByID(id)) + backClient, err := newClient(&account, true) + if err != nil { + return nil, nil, err + } + return &account, backClient, nil +} + +type backupClientHelper struct { + id uint + accountType string + name string + backupPath string + client cloud_storage.CloudStorageClient + + isOk bool + hasBackup bool + message string +} + +func NewBackupClientMap(ids []string) map[string]backupClientHelper { + var accounts []model.BackupAccount + var idItems []uint + for i := 0; i < len(ids); i++ { + item, _ := strconv.Atoi(ids[i]) + idItems = append(idItems, uint(item)) + } + accounts, _ = backupRepo.List(repo.WithByIDs(idItems)) + clientMap := make(map[string]backupClientHelper) + for _, item := range accounts { + backClient, err := newClient(&item, true) + itemHelper := backupClientHelper{ + client: backClient, + name: item.Name, + backupPath: item.BackupPath, + accountType: item.Type, + id: item.ID, + isOk: err == nil, + } + if err != nil { + itemHelper.message = err.Error() + } + clientMap[fmt.Sprintf("%v", item.ID)] = itemHelper + } + return clientMap +} + +func uploadWithMap(taskItem task.Task, accountMap map[string]backupClientHelper, src, dst, accountIDs string, downloadAccountID, retry uint) error { + accounts := strings.Split(accountIDs, ",") + for _, account := range accounts { + if len(account) == 0 { + continue + } + itemBackup, ok := accountMap[account] + if !ok { + continue + } + if itemBackup.hasBackup { + continue + } + if !itemBackup.isOk { + taskItem.LogFailed(i18n.GetMsgWithDetail("LoadBackupFailed", itemBackup.message)) + continue + } + name := itemBackup.name + if itemBackup.name == "localhost" { + name = i18n.GetMsgByKey("Localhost") + } + taskItem.LogStart(i18n.GetMsgWithMap("UploadFile", map[string]interface{}{ + "file": path.Join(itemBackup.backupPath, dst), + "backup": name, + })) + for i := 0; i < int(retry)+1; i++ { + _, err := itemBackup.client.Upload(src, path.Join(itemBackup.backupPath, dst)) + taskItem.LogWithStatus(i18n.GetMsgByKey("Upload"), err) + if err != nil { + if account == fmt.Sprintf("%d", downloadAccountID) { + return err + } + } else { + break + } + } + itemBackup.hasBackup = true + accountMap[account] = itemBackup + } + os.RemoveAll(src) + return nil +} + +func newClient(account *model.BackupAccount, isEncrypt bool) (cloud_storage.CloudStorageClient, error) { + varMap := make(map[string]interface{}) + if len(account.Vars) != 0 { + if err := json.Unmarshal([]byte(account.Vars), &varMap); err != nil { + return nil, err + } + } + varMap["bucket"] = account.Bucket + varMap["backupPath"] = account.BackupPath + if isEncrypt { + account.AccessKey, _ = encrypt.StringDecrypt(account.AccessKey) + account.Credential, _ = encrypt.StringDecrypt(account.Credential) + } + switch account.Type { + case constant.Sftp, constant.WebDAV: + varMap["username"] = account.AccessKey + varMap["password"] = account.Credential + case constant.OSS, constant.S3, constant.MinIo, constant.Cos, constant.Kodo: + varMap["accessKey"] = account.AccessKey + varMap["secretKey"] = account.Credential + case constant.UPYUN: + varMap["operator"] = account.AccessKey + varMap["password"] = account.Credential + } + + client, err := cloud_storage.NewCloudStorageClient(account.Type, varMap) + if err != nil { + return nil, err + } + return client, nil +} + +func loadRefreshTokenByCode(backup *model.BackupAccount) (string, error) { + varMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { + return "", fmt.Errorf("unmarshal backup vars failed, err: %v", err) + } + if _, ok := varMap["refresh_token"]; ok { + return "", nil + } + refreshToken := "" + var err error + switch backup.Type { + case constant.GoogleDrive: + refreshToken, err = client.RefreshGoogleToken("authorization_code", "refreshToken", varMap) + if err != nil { + return "", err + } + case constant.OneDrive: + refreshToken, err = client.RefreshToken("authorization_code", "refreshToken", varMap) + if err != nil { + return "", err + } + } + if backup.Type != constant.ALIYUN { + varMap["refresh_token"] = refreshToken + } + itemVars, _ := json.Marshal(varMap) + backup.Vars = string(itemVars) + return refreshToken, nil +} + +func loadBackupNamesByID(accountIDs string, downloadID uint) ([]string, string, error) { + accountIDList := strings.Split(accountIDs, ",") + var ids []uint + for _, item := range accountIDList { + if len(item) != 0 { + itemID, _ := strconv.Atoi(item) + ids = append(ids, uint(itemID)) + } + } + list, err := backupRepo.List(repo.WithByIDs(ids)) + if err != nil { + return nil, "", err + } + var accounts []string + var downloadAccount string + for _, item := range list { + accounts = append(accounts, item.Name) + if item.ID == downloadID { + downloadAccount = item.Name + } + } + return accounts, downloadAccount, nil +} + +func changeLocalBackup(oldPath, newPath string) error { + fileOp := files.NewFileOp() + if fileOp.Stat(path.Join(oldPath, "app")) { + if err := fileOp.CopyDir(path.Join(oldPath, "app"), newPath); err != nil { + return err + } + } + if fileOp.Stat(path.Join(oldPath, "database")) { + if err := fileOp.CopyDir(path.Join(oldPath, "database"), newPath); err != nil { + return err + } + } + if fileOp.Stat(path.Join(oldPath, "directory")) { + if err := fileOp.CopyDir(path.Join(oldPath, "directory"), newPath); err != nil { + return err + } + } + if fileOp.Stat(path.Join(oldPath, "system_snapshot")) { + if err := fileOp.CopyDir(path.Join(oldPath, "system_snapshot"), newPath); err != nil { + return err + } + } + if fileOp.Stat(path.Join(oldPath, "website")) { + if err := fileOp.CopyDir(path.Join(oldPath, "website"), newPath); err != nil { + return err + } + } + if fileOp.Stat(path.Join(oldPath, "log")) { + if err := fileOp.CopyDir(path.Join(oldPath, "log"), newPath); err != nil { + return err + } + } + if fileOp.Stat(path.Join(oldPath, "master")) { + if err := fileOp.CopyDir(path.Join(oldPath, "master"), newPath); err != nil { + return err + } + } + _ = fileOp.RmRf(path.Join(oldPath, "app")) + _ = fileOp.RmRf(path.Join(oldPath, "database")) + _ = fileOp.RmRf(path.Join(oldPath, "directory")) + _ = fileOp.RmRf(path.Join(oldPath, "system_snapshot")) + _ = fileOp.RmRf(path.Join(oldPath, "website")) + _ = fileOp.RmRf(path.Join(oldPath, "log")) + _ = fileOp.RmRf(path.Join(oldPath, "master")) + return nil +} diff --git a/agent/app/service/backup_app.go b/agent/app/service/backup_app.go new file mode 100644 index 0000000..fef58e2 --- /dev/null +++ b/agent/app/service/backup_app.go @@ -0,0 +1,405 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/i18n" + + "github.com/1Panel-dev/1Panel/agent/buserr" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/pkg/errors" +) + +func (u *BackupService) AppBackup(req dto.CommonBackup) (*model.BackupRecord, error) { + app, err := appRepo.GetFirst(appRepo.WithKey(req.Name)) + if err != nil { + return nil, err + } + install, err := appInstallRepo.GetFirst(repo.WithByName(req.DetailName), appInstallRepo.WithAppId(app.ID)) + if err != nil { + return nil, err + } + timeNow := time.Now().Format(constant.DateTimeSlimLayout) + itemDir := fmt.Sprintf("app/%s/%s", req.Name, req.DetailName) + backupDir := path.Join(global.Dir.LocalBackupDir, itemDir) + + fileName := req.FileName + if req.FileName == "" { + fileName = fmt.Sprintf("%s_%s.tar.gz", req.DetailName, timeNow+common.RandStrAndNum(5)) + } + + record := &model.BackupRecord{ + Type: "app", + Name: req.Name, + DetailName: req.DetailName, + SourceAccountIDs: "1", + DownloadAccountID: 1, + FileDir: itemDir, + FileName: fileName, + TaskID: req.TaskID, + Status: constant.StatusWaiting, + Description: req.Description, + } + if err := backupRepo.CreateRecord(record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return nil, err + } + + if err = handleAppBackup(&install, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil { + global.LOG.Errorf("backup app %s failed, err: %v", req.DetailName, err) + return nil, err + } + + return record, nil +} + +func (u *BackupService) AppRecover(req dto.CommonRecover) error { + app, err := appRepo.GetFirst(appRepo.WithKey(req.Name)) + if err != nil { + return err + } + install, err := appInstallRepo.GetFirst(repo.WithByName(req.DetailName), appInstallRepo.WithAppId(app.ID)) + if err != nil { + return err + } + + fileOp := files.NewFileOp() + if !fileOp.Stat(req.File) { + return buserr.WithName("ErrFileNotFound", req.File) + } + if _, err := compose.Down(install.GetComposePath()); err != nil { + return err + } + if err := handleAppRecover(&install, nil, req.File, false, req.Secret, req.TaskID); err != nil { + global.LOG.Errorf("recover app %s failed, err: %v", req.DetailName, err) + } + return nil +} + +func backupDatabaseWithTask(parentTask *task.Task, resourceKey, tmpDir, name string, databaseID uint) error { + switch resourceKey { + case constant.AppMysql, constant.AppMariaDB: + db, err := mysqlRepo.Get(repo.WithByID(databaseID)) + if err != nil { + return err + } + parentTask.LogStart(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase)) + databaseHelper := DatabaseHelper{Database: db.MysqlName, DBType: resourceKey, Name: db.Name} + if err := handleMysqlBackup(databaseHelper, parentTask, 0, tmpDir, fmt.Sprintf("%s.sql.gz", name), "", ""); err != nil { + return err + } + parentTask.LogSuccess(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase)) + case constant.AppPostgresql: + db, err := postgresqlRepo.Get(repo.WithByID(databaseID)) + if err != nil { + return err + } + parentTask.LogStart(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase)) + databaseHelper := DatabaseHelper{Database: db.PostgresqlName, DBType: resourceKey, Name: db.Name} + if err := handlePostgresqlBackup(databaseHelper, parentTask, 0, tmpDir, fmt.Sprintf("%s.sql.gz", name), "", ""); err != nil { + return err + } + parentTask.LogSuccess(task.GetTaskName(db.Name, task.TaskBackup, task.TaskScopeDatabase)) + } + return nil +} + +func handleAppBackup(install *model.AppInstall, parentTask *task.Task, recordID uint, backupDir, fileName, excludes, secret, taskID string) error { + var ( + err error + backupTask *task.Task + ) + backupTask = parentTask + if parentTask == nil { + backupTask, err = task.NewTaskWithOps(install.Name, task.TaskBackup, task.TaskScopeBackup, taskID, install.ID) + if err != nil { + return err + } + } + + itemHandler := func() error { return doAppBackup(install, backupTask, backupDir, fileName, excludes, secret) } + if parentTask != nil { + return itemHandler() + } + + backupTask.AddSubTaskWithOps(task.GetTaskName(install.Name, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour) + go func() { + if err := backupTask.Execute(); err != nil { + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + return + } + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess}) + }() + + return nil +} + +func handleAppRecover(install *model.AppInstall, parentTask *task.Task, recoverFile string, isRollback bool, secret, taskID string) error { + var ( + err error + recoverTask *task.Task + isOk = false + rollbackFile string + ) + recoverTask = parentTask + if parentTask == nil { + recoverTask, err = task.NewTaskWithOps(install.Name, task.TaskRecover, task.TaskScopeBackup, taskID, install.ID) + if err != nil { + return err + } + } + + recoverApp := func(t *task.Task) error { + fileOp := files.NewFileOp() + if !isRollback { + rollbackFile = path.Join(global.Dir.TmpDir, fmt.Sprintf("app/%s_%s.tar.gz", install.Name, time.Now().Format(constant.DateTimeSlimLayout))) + if err := handleAppBackup(install, recoverTask, 0, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", taskID); err != nil { + t.Log(fmt.Sprintf("backup app %s for rollback before recover failed, err: %v", install.Name, err)) + } + } + + if err := fileOp.TarGzExtractPro(recoverFile, path.Dir(recoverFile), secret); err != nil { + return err + } + tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "") + defer func() { + _, _ = compose.Up(install.GetComposePath()) + _ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", "")) + }() + + if !fileOp.Stat(tmpPath+"/app.json") || !fileOp.Stat(tmpPath+"/app.tar.gz") { + return errors.New(i18n.GetMsgByKey("AppBackupFileIncomplete")) + } + var oldInstall model.AppInstall + appJson, err := os.ReadFile(tmpPath + "/app.json") + if err != nil { + return err + } + if err := json.Unmarshal(appJson, &oldInstall); err != nil { + return fmt.Errorf("unmarshal app.json failed, err: %v", err) + } + if oldInstall.App.Key != install.App.Key || oldInstall.Name != install.Name { + return errors.New(i18n.GetMsgByKey("AppAttributesNotMatch")) + } + + newEnvFile := "" + resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID)) + for _, resource := range resources { + var database model.Database + switch resource.From { + case constant.AppResourceRemote: + database, err = databaseRepo.Get(repo.WithByID(resource.LinkId)) + if err != nil { + return err + } + case constant.AppResourceLocal: + resourceApp, err := appInstallRepo.GetFirst(repo.WithByID(resource.LinkId)) + if err != nil { + return err + } + database, err = databaseRepo.Get(databaseRepo.WithAppInstallID(resourceApp.ID), repo.WithByType(resource.Key), repo.WithByFrom(constant.AppResourceLocal), repo.WithByName(resourceApp.Name)) + if err != nil { + return err + } + } + switch database.Type { + case constant.AppPostgresql: + db, err := postgresqlRepo.Get(repo.WithByID(resource.ResourceId)) + if err != nil { + return err + } + taskName := task.GetTaskName(db.Name, task.TaskRecover, task.TaskScopeDatabase) + t.LogStart(taskName) + if err := handlePostgresqlRecover(dto.CommonRecover{ + Name: database.Name, + DetailName: db.Name, + File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name), + }, parentTask, true); err != nil { + t.LogFailedWithErr(taskName, err) + return err + } + t.LogSuccess(taskName) + case constant.AppMysql, constant.AppMariaDB: + db, err := mysqlRepo.Get(repo.WithByID(resource.ResourceId)) + if err != nil { + return err + } + newDB, envMap, err := reCreateDB(db.ID, database, oldInstall.Env) + if err != nil { + return err + } + oldHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", envMap["PANEL_DB_HOST"].(string)) + newHost := fmt.Sprintf("\"PANEL_DB_HOST\":\"%v\"", database.Address) + oldInstall.Env = strings.ReplaceAll(oldInstall.Env, oldHost, newHost) + envMap["PANEL_DB_HOST"] = database.Address + newEnvFile, err = coverEnvJsonToStr(oldInstall.Env) + if err != nil { + return err + } + _ = appInstallResourceRepo.BatchUpdateBy(map[string]interface{}{"resource_id": newDB.ID}, repo.WithByID(resource.ID)) + taskName := task.GetTaskName(db.Name, task.TaskRecover, task.TaskScopeDatabase) + t.LogStart(taskName) + if err := handleMysqlRecover(dto.CommonRecover{ + Name: newDB.MysqlName, + DetailName: newDB.Name, + File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, install.Name), + }, parentTask, true); err != nil { + t.LogFailedWithErr(taskName, err) + return err + } + t.LogSuccess(taskName) + } + } + + appDir := install.GetPath() + backPath := fmt.Sprintf("%s_bak", appDir) + _ = fileOp.Rename(appDir, backPath) + _ = fileOp.CreateDir(appDir, constant.DirPerm) + + deCompressName := i18n.GetWithName("DeCompressFile", "app.tar.gz") + t.LogStart(deCompressName) + if err := fileOp.TarGzExtractPro(tmpPath+"/app.tar.gz", install.GetAppPath(), ""); err != nil { + t.LogFailedWithErr(deCompressName, err) + _ = fileOp.DeleteDir(appDir) + _ = fileOp.Rename(backPath, appDir) + return err + } + t.LogSuccess(deCompressName) + _ = fileOp.DeleteDir(backPath) + + if len(newEnvFile) != 0 { + envPath := fmt.Sprintf("%s/%s/.env", install.GetAppPath(), install.Name) + file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + _, _ = file.WriteString(newEnvFile) + } + + oldInstall.ID = install.ID + oldInstall.Status = constant.StatusRunning + oldInstall.AppId = install.AppId + oldInstall.AppDetailId = install.AppDetailId + oldInstall.App.ID = install.AppId + if err := appInstallRepo.Save(context.Background(), &oldInstall); err != nil { + global.LOG.Errorf("save db app install failed, err: %v", err) + return err + } + isOk = true + + return nil + } + + if parentTask != nil { + return recoverApp(parentTask) + } + + rollBackApp := func(t *task.Task) { + if isRollback { + return + } + if !isOk { + t.Log(i18n.GetMsgByKey("RecoverFailedStartRollBack")) + if err := handleAppRecover(install, t, rollbackFile, true, "", ""); err != nil { + t.LogFailedWithErr(i18n.GetMsgByKey("Rollback"), err) + return + } + t.LogSuccess(i18n.GetMsgByKey("Rollback")) + _ = os.RemoveAll(rollbackFile) + } else { + _ = os.RemoveAll(rollbackFile) + } + } + recoverTask.AddSubTask(task.GetTaskName(install.Name, task.TaskRecover, task.TaskScopeBackup), recoverApp, rollBackApp) + go func() { + _ = recoverTask.Execute() + }() + return nil +} + +func doAppBackup(install *model.AppInstall, parentTask *task.Task, backupDir, fileName, excludes, secret string) error { + fileOp := files.NewFileOp() + tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", "")) + if !fileOp.Stat(tmpDir) { + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err) + } + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + remarkInfo, _ := json.Marshal(install) + remarkInfoPath := fmt.Sprintf("%s/app.json", tmpDir) + if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil { + return err + } + + appPath := install.GetPath() + parentTask.LogStart(i18n.GetMsgByKey("TaskBackup")) + if err := fileOp.TarGzCompressPro(true, appPath, path.Join(tmpDir, "app.tar.gz"), "", excludes); err != nil { + return err + } + + resources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID)) + for _, resource := range resources { + if err := backupDatabaseWithTask(parentTask, resource.Key, tmpDir, install.Name, resource.ResourceId); err != nil { + return err + } + } + parentTask.LogStart(i18n.GetMsgByKey("CompressDir")) + if err := fileOp.TarGzCompressPro(true, tmpDir, path.Join(backupDir, fileName), secret, ""); err != nil { + return err + } + parentTask.Log(i18n.GetWithName("CompressFileSuccess", fileName)) + return nil +} + +func reCreateDB(dbID uint, database model.Database, oldEnv string) (*model.DatabaseMysql, map[string]interface{}, error) { + mysqlService := NewIMysqlService() + ctx := context.Background() + _ = mysqlService.Delete(ctx, dto.MysqlDBDelete{ID: dbID, Database: database.Name, Type: database.Type, DeleteBackup: false, ForceDelete: true}) + + envMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(oldEnv), &envMap); err != nil { + return nil, envMap, err + } + oldName, _ := envMap["PANEL_DB_NAME"].(string) + oldUser, _ := envMap["PANEL_DB_USER"].(string) + oldPassword, _ := envMap["PANEL_DB_USER_PASSWORD"].(string) + createDB, err := mysqlService.Create(context.Background(), dto.MysqlDBCreate{ + Name: oldName, + From: database.From, + Database: database.Name, + Format: "utf8mb4", + Username: oldUser, + Password: oldPassword, + Permission: "%", + }) + cronjobs, _ := cronjobRepo.List(cronjobRepo.WithByDbName(fmt.Sprintf("%v", dbID))) + for _, job := range cronjobs { + _ = cronjobRepo.Update(job.ID, map[string]interface{}{"db_name": fmt.Sprintf("%v", createDB.ID)}) + } + if err != nil { + return nil, envMap, err + } + return createDB, envMap, nil +} diff --git a/agent/app/service/backup_mysql.go b/agent/app/service/backup_mysql.go new file mode 100644 index 0000000..80166cb --- /dev/null +++ b/agent/app/service/backup_mysql.go @@ -0,0 +1,294 @@ +package service + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/mysql/client" +) + +func (u *BackupService) MysqlBackup(req dto.CommonBackup) error { + timeNow := time.Now().Format(constant.DateTimeSlimLayout) + itemDir := fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName) + targetDir := path.Join(global.Dir.LocalBackupDir, itemDir) + fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5)) + + record := &model.BackupRecord{ + Type: req.Type, + Name: req.Name, + DetailName: req.DetailName, + SourceAccountIDs: "1", + DownloadAccountID: 1, + FileDir: itemDir, + FileName: fileName, + TaskID: req.TaskID, + Status: constant.StatusWaiting, + Description: req.Description, + } + if err := backupRepo.CreateRecord(record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return err + } + + databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName, Args: req.Args} + if err := handleMysqlBackup(databaseHelper, nil, record.ID, targetDir, fileName, req.TaskID, req.Secret); err != nil { + backupRepo.UpdateRecordByMap(record.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + return err + } + return nil +} + +func (u *BackupService) MysqlRecover(req dto.CommonRecover) error { + return handleMysqlRecover(req, nil, false) +} + +func (u *BackupService) MysqlRecoverByUpload(req dto.CommonRecover) error { + recoverFile, err := loadSqlFile(req.File) + if err != nil { + return err + } + req.File = recoverFile + + if err := handleMysqlRecover(req, nil, false); err != nil { + return err + } + return nil +} + +func handleMysqlBackup(db DatabaseHelper, parentTask *task.Task, recordID uint, targetDir, fileName, taskID, secret string) error { + var ( + err error + backupTask *task.Task + ) + backupTask = parentTask + dbInfo, err := mysqlRepo.Get(repo.WithByName(db.Name), mysqlRepo.WithByMysqlName(db.Database)) + if err != nil { + return err + } + itemName := fmt.Sprintf("%s[%s] - %s", db.Database, db.DBType, db.Name) + if parentTask == nil { + backupTask, err = task.NewTaskWithOps(itemName, task.TaskBackup, task.TaskScopeBackup, taskID, dbInfo.ID) + if err != nil { + return err + } + } + + itemHandler := func() error { + if len(db.Args) != 0 { + backupTask.Logf("%s: %v", i18n.GetMsgByKey("Arg"), db.Args) + } + return doMysqlBackup(db, targetDir, fileName, secret) + } + if parentTask != nil { + return itemHandler() + } + backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 0, 3*time.Hour) + go func() { + if err := backupTask.Execute(); err != nil { + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + return + } + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess}) + }() + return nil +} + +func handleMysqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback bool) error { + var ( + err error + itemTask *task.Task + ) + itemTask = parentTask + dbInfo, err := mysqlRepo.Get(repo.WithByName(req.DetailName), mysqlRepo.WithByMysqlName(req.Name)) + if err != nil { + return err + } + itemName := fmt.Sprintf("%s[%s] - %s", req.Name, req.Type, req.DetailName) + if parentTask == nil { + itemTask, err = task.NewTaskWithOps(itemName, task.TaskRecover, task.TaskScopeBackup, req.TaskID, dbInfo.ID) + if err != nil { + return err + } + } + + recoverDatabase := func(t *task.Task) error { + isOk := false + fileOp := files.NewFileOp() + if !fileOp.Stat(req.File) { + return buserr.WithName("ErrFileNotFound", req.File) + } + dbInfo, err := mysqlRepo.Get(repo.WithByName(req.DetailName), mysqlRepo.WithByMysqlName(req.Name)) + if err != nil { + return err + } + cli, version, err := LoadMysqlClientByFrom(req.Name) + if err != nil { + return err + } + + if !isRollback { + rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format(constant.DateTimeSlimLayout))) + if err := cli.Backup(client.BackupInfo{ + Name: req.DetailName, + Type: req.Type, + Version: version, + Format: dbInfo.Format, + TargetDir: path.Dir(rollbackFile), + FileName: path.Base(rollbackFile), + }); err != nil { + return fmt.Errorf("backup mysql db %s for rollback before recover failed, err: %v", req.DetailName, err) + } + defer func() { + if !isOk { + global.LOG.Info("recover failed, start to rollback now") + if err := cli.Recover(client.RecoverInfo{ + Name: req.DetailName, + Type: req.Type, + Version: version, + Format: dbInfo.Format, + SourceFile: rollbackFile, + }); err != nil { + global.LOG.Errorf("rollback mysql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err) + } else { + global.LOG.Infof("rollback mysql db %s from %s successful", req.DetailName, rollbackFile) + } + _ = os.RemoveAll(rollbackFile) + } else { + _ = os.RemoveAll(rollbackFile) + } + }() + } + if len(req.Secret) != 0 { + err = files.OpensslDecrypt(req.File, req.Secret) + if err != nil { + return err + } + req.File = path.Join(path.Dir(req.File), "tmp_"+path.Base(req.File)) + defer os.Remove(req.File) + t.LogWithStatus(i18n.GetMsgByKey("Decrypt"), err) + } + if err := cli.Recover(client.RecoverInfo{ + Name: req.DetailName, + Type: req.Type, + Version: version, + Format: dbInfo.Format, + SourceFile: req.File, + }); err != nil { + global.LOG.Errorf("recover mysql db %s from %s failed, err: %v", req.DetailName, req.File, err) + return err + } + isOk = true + return nil + } + if parentTask != nil { + return recoverDatabase(parentTask) + } + + var timeout time.Duration + switch req.Timeout { + case -1: + timeout = 0 + case 0: + timeout = 3 * time.Hour + default: + timeout = time.Duration(req.Timeout) * time.Second + } + itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 0, timeout) + go func() { + _ = itemTask.Execute() + }() + return nil +} + +func doMysqlBackup(db DatabaseHelper, targetDir, fileName, secret string) error { + dbInfo, err := mysqlRepo.Get(repo.WithByName(db.Name), mysqlRepo.WithByMysqlName(db.Database)) + if err != nil { + return err + } + cli, version, err := LoadMysqlClientByFrom(db.Database) + if err != nil { + return err + } + backupInfo := client.BackupInfo{ + Name: db.Name, + Type: db.DBType, + Version: version, + Format: dbInfo.Format, + TargetDir: targetDir, + FileName: fileName, + Args: db.Args, + } + if err := cli.Backup(backupInfo); err != nil { + return err + } + if len(secret) != 0 { + return files.OpensslEncrypt(path.Join(targetDir, fileName), secret) + } + return nil +} + +func loadSqlFile(file string) (string, error) { + if !strings.HasSuffix(file, ".tar.gz") && !strings.HasSuffix(file, ".zip") { + return file, nil + } + fileName := path.Base(file) + fileDir := path.Dir(file) + fileNameItem := time.Now().Format(constant.DateTimeSlimLayout) + dstDir := fmt.Sprintf("%s/%s", fileDir, fileNameItem) + _ = os.Mkdir(dstDir, constant.DirPerm) + if strings.HasSuffix(fileName, ".tar.gz") { + fileOp := files.NewFileOp() + if err := fileOp.TarGzExtractPro(file, dstDir, ""); err != nil { + _ = os.RemoveAll(dstDir) + return "", err + } + } + if strings.HasSuffix(fileName, ".zip") { + archiver, err := files.NewShellArchiver(files.Zip) + if err != nil { + _ = os.RemoveAll(dstDir) + return "", err + } + if err := archiver.Extract(file, dstDir, ""); err != nil { + _ = os.RemoveAll(dstDir) + return "", err + } + } + global.LOG.Infof("decompress file %s successful, now start to check test.sql is exist", file) + var sqlFiles []string + hasTestSql := false + _ = filepath.Walk(dstDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + if !info.IsDir() && strings.HasSuffix(info.Name(), ".sql") { + sqlFiles = append(sqlFiles, path) + if info.Name() == "test.sql" { + hasTestSql = true + } + } + return nil + }) + if len(sqlFiles) == 1 { + return sqlFiles[0], nil + } + if !hasTestSql { + _ = os.RemoveAll(dstDir) + return "", fmt.Errorf("no such file named test.sql in %s", fileName) + } + return "", nil +} diff --git a/agent/app/service/backup_postgresql.go b/agent/app/service/backup_postgresql.go new file mode 100644 index 0000000..ea8f7a7 --- /dev/null +++ b/agent/app/service/backup_postgresql.go @@ -0,0 +1,228 @@ +package service + +import ( + "fmt" + "os" + "path" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/postgresql/client" +) + +func (u *BackupService) PostgresqlBackup(req dto.CommonBackup) error { + timeNow := time.Now().Format(constant.DateTimeSlimLayout) + itemDir := fmt.Sprintf("database/%s/%s/%s", req.Type, req.Name, req.DetailName) + targetDir := path.Join(global.Dir.LocalBackupDir, itemDir) + fileName := fmt.Sprintf("%s_%s.sql.gz", req.DetailName, timeNow+common.RandStrAndNum(5)) + + record := &model.BackupRecord{ + Type: req.Type, + Name: req.Name, + DetailName: req.DetailName, + SourceAccountIDs: "1", + DownloadAccountID: 1, + FileDir: itemDir, + FileName: fileName, + TaskID: req.TaskID, + Status: constant.StatusWaiting, + Description: req.Description, + } + if err := backupRepo.CreateRecord(record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + } + + databaseHelper := DatabaseHelper{Database: req.Name, DBType: req.Type, Name: req.DetailName} + if err := handlePostgresqlBackup(databaseHelper, nil, record.ID, targetDir, fileName, req.TaskID, req.Secret); err != nil { + return err + } + return nil +} +func (u *BackupService) PostgresqlRecover(req dto.CommonRecover) error { + if err := handlePostgresqlRecover(req, nil, false); err != nil { + return err + } + return nil +} + +func (u *BackupService) PostgresqlRecoverByUpload(req dto.CommonRecover) error { + recoverFile, err := loadSqlFile(req.File) + if err != nil { + return err + } + req.File = recoverFile + if err := handlePostgresqlRecover(req, nil, false); err != nil { + return err + } + global.LOG.Info("recover from uploads successful!") + return nil +} + +func handlePostgresqlBackup(db DatabaseHelper, parentTask *task.Task, recordID uint, targetDir, fileName, taskID, secret string) error { + var ( + err error + backupTask *task.Task + ) + backupTask = parentTask + itemName := fmt.Sprintf("%s - %s", db.Database, db.Name) + if parentTask == nil { + backupTask, err = task.NewTaskWithOps(itemName, task.TaskBackup, task.TaskScopeBackup, taskID, db.ID) + if err != nil { + return err + } + } + + itemHandler := func() error { return doPostgresqlBackup(db, targetDir, fileName, secret, backupTask) } + if parentTask != nil { + return itemHandler() + } + backupTask.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 0, 3*time.Hour) + go func() { + if err := backupTask.Execute(); err != nil { + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + return + } + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess}) + }() + return nil +} + +func handlePostgresqlRecover(req dto.CommonRecover, parentTask *task.Task, isRollback bool) error { + var ( + err error + itemTask *task.Task + ) + dbInfo, err := postgresqlRepo.Get(repo.WithByName(req.DetailName), postgresqlRepo.WithByPostgresqlName(req.Name)) + if err != nil { + return err + } + itemTask = parentTask + if parentTask == nil { + itemTask, err = task.NewTaskWithOps(req.Name, task.TaskRecover, task.TaskScopeBackup, req.TaskID, dbInfo.ID) + if err != nil { + return err + } + } + + recoverDatabase := func(t *task.Task) error { + isOk := false + fileOp := files.NewFileOp() + if !fileOp.Stat(req.File) { + return buserr.WithName("ErrFileNotFound", req.File) + } + + cli, err := LoadPostgresqlClientByFrom(req.Name) + if err != nil { + return err + } + defer cli.Close() + + if !isRollback { + rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/%s/%s_%s.sql.gz", req.Type, req.DetailName, time.Now().Format(constant.DateTimeSlimLayout))) + if err := cli.Backup(client.BackupInfo{ + Database: req.Name, + Name: req.DetailName, + TargetDir: path.Dir(rollbackFile), + FileName: path.Base(rollbackFile), + + Task: t, + Timeout: 300, + }); err != nil { + return fmt.Errorf("backup postgresql db %s for rollback before recover failed, err: %v", req.DetailName, err) + } + defer func() { + if !isOk { + global.LOG.Info("recover failed, start to rollback now") + if err := cli.Recover(client.RecoverInfo{ + Database: req.Name, + Name: req.DetailName, + SourceFile: rollbackFile, + + Task: t, + Timeout: 300, + }); err != nil { + global.LOG.Errorf("rollback postgresql db %s from %s failed, err: %v", req.DetailName, rollbackFile, err) + } else { + global.LOG.Infof("rollback postgresql db %s from %s successful", req.DetailName, rollbackFile) + } + _ = os.RemoveAll(rollbackFile) + } else { + _ = os.RemoveAll(rollbackFile) + } + }() + } + if len(req.Secret) != 0 { + err = files.OpensslDecrypt(req.File, req.Secret) + if err != nil { + return err + } + req.File = path.Join(path.Dir(req.File), "tmp_"+path.Base(req.File)) + defer os.Remove(req.File) + t.LogWithStatus(i18n.GetMsgByKey("Decrypt"), err) + } + if err := cli.Recover(client.RecoverInfo{ + Database: req.Name, + Name: req.DetailName, + SourceFile: req.File, + Username: dbInfo.Username, + Task: t, + Timeout: 300, + }); err != nil { + global.LOG.Errorf("recover postgresql db %s from %s failed, err: %v", req.DetailName, req.File, err) + return err + } + isOk = true + return nil + } + if parentTask != nil { + return recoverDatabase(parentTask) + } + + var timeout time.Duration + switch req.Timeout { + case -1: + timeout = 0 + case 0: + timeout = 3 * time.Hour + default: + timeout = time.Duration(req.Timeout) * time.Second + } + itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil, 0, timeout) + go func() { + _ = itemTask.Execute() + }() + return nil +} + +func doPostgresqlBackup(db DatabaseHelper, targetDir, fileName, secret string, task *task.Task) error { + cli, err := LoadPostgresqlClientByFrom(db.Database) + if err != nil { + return err + } + defer cli.Close() + backupInfo := client.BackupInfo{ + Database: db.Database, + Name: db.Name, + TargetDir: targetDir, + FileName: fileName, + + Task: task, + Timeout: 300, + } + if err := cli.Backup(backupInfo); err != nil { + return err + } + if len(secret) != 0 { + return files.OpensslEncrypt(path.Join(targetDir, fileName), secret) + } + return nil +} diff --git a/agent/app/service/backup_record.go b/agent/app/service/backup_record.go new file mode 100644 index 0000000..28baee4 --- /dev/null +++ b/agent/app/service/backup_record.go @@ -0,0 +1,292 @@ +package service + +import ( + "context" + "fmt" + "os" + "path" + "sync" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/jinzhu/copier" +) + +type BackupRecordService struct{} + +type IBackupRecordService interface { + SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) + SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) + DownloadRecord(info dto.DownloadRecord) (string, error) + DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error + BatchDeleteRecord(ids []uint) error + ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) + + ListFiles(req dto.OperateByID) []string + LoadRecordSize(req dto.SearchForSize) ([]dto.RecordFileSize, error) + UpdateDescription(req dto.UpdateDescription) error +} + +func NewIBackupRecordService() IBackupRecordService { + return &BackupRecordService{} +} + +func (u *BackupRecordService) SearchRecordsWithPage(search dto.RecordSearch) (int64, []dto.BackupRecords, error) { + total, records, err := backupRepo.PageRecord( + search.Page, search.PageSize, + repo.WithOrderBy("created_at desc"), + repo.WithByName(search.Name), + repo.WithByType(search.Type), + repo.WithByDetailName(search.DetailName), + ) + if err != nil { + return 0, nil, err + } + accounts, _ := backupRepo.List() + var data []dto.BackupRecords + for _, record := range records { + var item dto.BackupRecords + if err := copier.Copy(&item, &record); err != nil { + global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err) + } + for _, account := range accounts { + if account.ID == record.DownloadAccountID { + item.DownloadAccountID = account.ID + item.AccountName = account.Name + item.AccountType = account.Type + break + } + } + data = append(data, item) + } + return total, data, err +} + +func (u *BackupRecordService) SearchRecordsByCronjobWithPage(search dto.RecordSearchByCronjob) (int64, []dto.BackupRecords, error) { + total, records, err := backupRepo.PageRecord( + search.Page, search.PageSize, + repo.WithOrderBy("created_at desc"), + backupRepo.WithByCronID(search.CronjobID), + ) + if err != nil { + return 0, nil, err + } + accounts, _ := backupRepo.List() + var data []dto.BackupRecords + for _, record := range records { + var item dto.BackupRecords + if err := copier.Copy(&item, &record); err != nil { + global.LOG.Errorf("copy backup account to dto backup info failed, err: %v", err) + } + for _, account := range accounts { + if account.ID == record.DownloadAccountID { + item.DownloadAccountID = account.ID + item.AccountName = account.Name + item.AccountType = account.Type + break + } + } + data = append(data, item) + } + return total, data, err +} + +func (u *BackupRecordService) DownloadRecord(info dto.DownloadRecord) (string, error) { + account, client, err := NewBackupClientWithID(info.DownloadAccountID) + if err != nil { + return "", fmt.Errorf("new cloud storage client failed, err: %v", err) + } + if account.Type == "LOCAL" { + return path.Join(global.Dir.LocalBackupDir, info.FileDir, info.FileName), nil + } + targetPath := fmt.Sprintf("%s/download/%s/%s", global.Dir.DataDir, info.FileDir, info.FileName) + if _, err := os.Stat(path.Dir(targetPath)); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(path.Dir(targetPath), os.ModePerm); err != nil { + global.LOG.Errorf("mkdir %s failed, err: %v", path.Dir(targetPath), err) + } + } + srcPath := fmt.Sprintf("%s/%s", info.FileDir, info.FileName) + if len(account.BackupPath) != 0 { + srcPath = path.Join(account.BackupPath, srcPath) + } + if exist, _ := client.Exist(srcPath); exist { + isOK, err := client.Download(srcPath, targetPath) + if !isOK || err != nil { + return "", fmt.Errorf("cloud storage download failed, err: %v", err) + } + } + return targetPath, nil +} + +func (u *BackupRecordService) DeleteRecordByName(backupType, name, detailName string, withDeleteFile bool) error { + if !withDeleteFile { + return backupRepo.DeleteRecord(context.Background(), repo.WithByType(backupType), repo.WithByName(name), repo.WithByDetailName(detailName)) + } + + records, err := backupRepo.ListRecord(repo.WithByType(backupType), repo.WithByName(name), repo.WithByDetailName(detailName)) + if err != nil { + return err + } + + for _, record := range records { + backup, client, err := NewBackupClientWithID(record.DownloadAccountID) + if err != nil { + global.LOG.Errorf("new client for backup account failed, err: %v", err) + continue + } + if _, err = client.Delete(path.Join(backup.BackupPath, record.FileDir, record.FileName)); err != nil { + global.LOG.Errorf("remove file %s failed, err: %v", path.Join(record.FileDir, record.FileName), err) + } + _ = backupRepo.DeleteRecord(context.Background(), repo.WithByID(record.ID)) + } + return nil +} + +func (u *BackupRecordService) BatchDeleteRecord(ids []uint) error { + records, err := backupRepo.ListRecord(repo.WithByIDs(ids)) + if err != nil { + return err + } + for _, record := range records { + backup, client, err := NewBackupClientWithID(record.DownloadAccountID) + if err != nil { + global.LOG.Errorf("new client for backup account failed, err: %v", err) + continue + } + if _, err = client.Delete(path.Join(backup.BackupPath, record.FileDir, record.FileName)); err != nil { + global.LOG.Errorf("remove file %s failed, err: %v", path.Join(record.FileDir, record.FileName), err) + } + } + return backupRepo.DeleteRecord(context.Background(), repo.WithByIDs(ids)) +} + +func (u *BackupRecordService) ListAppRecords(name, detailName, fileName string) ([]model.BackupRecord, error) { + records, err := backupRepo.ListRecord( + repo.WithOrderBy("created_at asc"), + repo.WithByName(name), + repo.WithByType("app"), + backupRepo.WithFileNameStartWith(fileName), + backupRepo.WithByDetailName(detailName), + ) + if err != nil { + return nil, err + } + return records, err +} + +func (u *BackupRecordService) ListFiles(req dto.OperateByID) []string { + backupItem, client, err := NewBackupClientWithID(req.ID) + if err != nil { + return []string{} + } + prefix := "system_snapshot" + if len(backupItem.BackupPath) != 0 { + prefix = path.Join(backupItem.BackupPath, prefix) + } + files, err := client.ListObjects(prefix) + if err != nil { + global.LOG.Debugf("load files failed, err: %v", err) + return []string{} + } + var datas []string + for _, file := range files { + fileName := path.Base(file) + if len(file) != 0 && checkSnapshotIsOk(fileName) { + datas = append(datas, path.Base(file)) + } + } + return datas +} + +type backupSizeHelper struct { + ID uint `json:"id"` + DownloadID uint `json:"downloadID"` + FilePath string `json:"filePath"` + Size uint `json:"size"` +} + +func (u *BackupRecordService) LoadRecordSize(req dto.SearchForSize) ([]dto.RecordFileSize, error) { + var list []backupSizeHelper + switch req.Type { + case "snapshot": + _, records, err := snapshotRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order)) + if err != nil { + return nil, err + } + for _, item := range records { + list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: fmt.Sprintf("system_snapshot/%s.tar.gz", item.Name)}) + } + case "cronjob": + _, records, err := backupRepo.PageRecord(req.Page, req.PageSize, repo.WithOrderBy("created_at desc"), backupRepo.WithByCronID(req.CronjobID)) + if err != nil { + return nil, err + } + for _, item := range records { + list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: path.Join(item.FileDir, item.FileName)}) + } + default: + _, records, err := backupRepo.PageRecord( + req.Page, req.PageSize, + repo.WithOrderBy("created_at desc"), + repo.WithByName(req.Name), + repo.WithByType(req.Type), + repo.WithByDetailName(req.DetailName), + ) + if err != nil { + return nil, err + } + for _, item := range records { + if item.Status == constant.StatusWaiting { + continue + } + list = append(list, backupSizeHelper{ID: item.ID, DownloadID: item.DownloadAccountID, FilePath: path.Join(item.FileDir, item.FileName)}) + } + } + recordMap := make(map[uint]struct{}) + var recordIds []string + for _, record := range list { + if _, ok := recordMap[record.DownloadID]; !ok { + recordMap[record.DownloadID] = struct{}{} + recordIds = append(recordIds, fmt.Sprintf("%v", record.DownloadID)) + } + } + + clientMap := NewBackupClientMap(recordIds) + var datas []dto.RecordFileSize + var wg sync.WaitGroup + ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + for i := 0; i < len(list); i++ { + datas = append(datas, dto.RecordFileSize{ID: list[i].ID}) + if val, ok := clientMap[fmt.Sprintf("%v", list[i].DownloadID)]; ok { + if !val.isOk { + continue + } + wg.Add(1) + go func(index int) { + defer wg.Done() + done := make(chan struct{}, 1) + go func() { + datas[index].Size, _ = val.client.Size(path.Join(val.backupPath, list[i].FilePath)) + defer close(done) + }() + select { + case <-ctx.Done(): + return + case <-done: + return + } + }(i) + } + } + wg.Wait() + return datas, nil +} + +func (u *BackupRecordService) UpdateDescription(req dto.UpdateDescription) error { + return backupRepo.UpdateRecordByMap(req.ID, map[string]interface{}{"description": req.Description}) +} diff --git a/agent/app/service/backup_redis.go b/agent/app/service/backup_redis.go new file mode 100644 index 0000000..7133976 --- /dev/null +++ b/agent/app/service/backup_redis.go @@ -0,0 +1,242 @@ +package service + +import ( + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +func (u *BackupService) RedisBackup(req dto.CommonBackup) error { + redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return err + } + appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly") + if err != nil { + return err + } + global.LOG.Infof("appendonly in redis conf is %s", appendonly) + + timeNow := time.Now().Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5) + fileName := fmt.Sprintf("%s.rdb", timeNow) + if appendonly == "yes" { + if strings.HasPrefix(redisInfo.Version, "6.") { + fileName = fmt.Sprintf("%s.aof", timeNow) + } else { + fileName = fmt.Sprintf("%s.tar.gz", timeNow) + } + } + itemDir := fmt.Sprintf("database/redis/%s", redisInfo.Name) + backupDir := path.Join(global.Dir.LocalBackupDir, itemDir) + record := &model.BackupRecord{ + Type: req.Type, + Name: req.Name, + SourceAccountIDs: "1", + DownloadAccountID: 1, + FileDir: itemDir, + FileName: fileName, + TaskID: req.TaskID, + Status: constant.StatusWaiting, + Description: req.Description, + } + if err := backupRepo.CreateRecord(record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + } + + if err := handleRedisBackup(redisInfo, nil, record.ID, backupDir, fileName, req.Secret, req.TaskID); err != nil { + return err + } + return nil +} + +func (u *BackupService) RedisRecover(req dto.CommonRecover) error { + redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return err + } + global.LOG.Infof("recover redis from backup file %s", req.File) + if err := handleRedisRecover(redisInfo, nil, req.File, false, req.Secret, req.TaskID); err != nil { + return err + } + return nil +} + +func handleRedisBackup(redisInfo *repo.RootInfo, parentTask *task.Task, recordID uint, backupDir, fileName, secret, taskID string) error { + var ( + err error + itemTask *task.Task + ) + itemTask = parentTask + if parentTask == nil { + itemTask, err = task.NewTaskWithOps("Redis", task.TaskBackup, task.TaskScopeBackup, taskID, redisInfo.ID) + if err != nil { + return err + } + } + + backupDatabase := func(t *task.Task) error { + fileOp := files.NewFileOp() + if !fileOp.Stat(backupDir) { + if err := os.MkdirAll(backupDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err) + } + } + + if err := cmd.RunDefaultBashCf("docker exec %s redis-cli -a %s --no-auth-warning save", redisInfo.ContainerName, redisInfo.Password); err != nil { + return err + } + + if strings.HasSuffix(fileName, ".tar.gz") { + redisDataDir := fmt.Sprintf("%s/%s/%s/data/appendonlydir", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name) + if err := fileOp.TarGzCompressPro(true, redisDataDir, path.Join(backupDir, fileName), secret, ""); err != nil { + return err + } + return nil + } + if strings.HasSuffix(fileName, ".aof") { + if err := cmd.RunDefaultBashCf("docker cp %s:/data/appendonly.aof %s/%s", redisInfo.ContainerName, backupDir, fileName); err != nil { + return err + } + return nil + } + + if err := cmd.RunDefaultBashCf("docker cp %s:/data/dump.rdb %s/%s", redisInfo.ContainerName, backupDir, fileName); err != nil { + return err + } + return nil + } + itemTask.AddSubTask(i18n.GetMsgByKey("TaskBackup"), backupDatabase, nil) + if parentTask != nil { + return backupDatabase(parentTask) + } + + itemTask.AddSubTaskWithOps(i18n.GetMsgByKey("TaskBackup"), backupDatabase, nil, 3, time.Hour) + go func() { + if err := itemTask.Execute(); err != nil { + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + return + } + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess}) + }() + return itemTask.Execute() +} + +func handleRedisRecover(redisInfo *repo.RootInfo, parentTask *task.Task, recoverFile string, isRollback bool, secret, taskID string) error { + var ( + err error + itemTask *task.Task + ) + itemTask = parentTask + if parentTask == nil { + itemTask, err = task.NewTaskWithOps("Redis", task.TaskRecover, task.TaskScopeBackup, taskID, redisInfo.ID) + if err != nil { + return err + } + } + + recoverDatabase := func(t *task.Task) error { + fileOp := files.NewFileOp() + if !fileOp.Stat(recoverFile) { + return buserr.WithName("ErrFileNotFound", recoverFile) + } + + appendonly, err := configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly") + if err != nil { + return err + } + + if appendonly == "yes" { + if strings.HasPrefix(redisInfo.Version, "6.") && !strings.HasSuffix(recoverFile, ".aof") { + return buserr.New("ErrTypeOfRedis") + } + if strings.HasPrefix(redisInfo.Version, "7.") && !strings.HasSuffix(recoverFile, ".tar.gz") { + return buserr.New("ErrTypeOfRedis") + } + } else { + if !strings.HasSuffix(recoverFile, ".rdb") { + return buserr.New("ErrTypeOfRedis") + } + } + + global.LOG.Infof("appendonly in redis conf is %s", appendonly) + isOk := false + if !isRollback { + suffix := "rdb" + if appendonly == "yes" { + if strings.HasPrefix(redisInfo.Version, "6.") { + suffix = "aof" + } else { + suffix = "tar.gz" + } + } + rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("database/redis/%s_%s.%s", redisInfo.Name, time.Now().Format(constant.DateTimeSlimLayout), suffix)) + if err := handleRedisBackup(redisInfo, nil, 0, path.Dir(rollbackFile), path.Base(rollbackFile), secret, ""); err != nil { + return fmt.Errorf("backup database %s for rollback before recover failed, err: %v", redisInfo.Name, err) + } + defer func() { + if !isOk { + global.LOG.Info("recover failed, start to rollback now") + if err := handleRedisRecover(redisInfo, itemTask, rollbackFile, true, secret, ""); err != nil { + global.LOG.Errorf("rollback redis from %s failed, err: %v", rollbackFile, err) + return + } + global.LOG.Infof("rollback redis from %s successful", rollbackFile) + _ = os.RemoveAll(rollbackFile) + } else { + _ = os.RemoveAll(rollbackFile) + } + }() + } + composeDir := fmt.Sprintf("%s/%s/%s", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name) + if _, err := compose.Down(composeDir + "/docker-compose.yml"); err != nil { + return err + } + if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "7.") { + redisDataDir := fmt.Sprintf("%s/%s/%s/data", global.Dir.AppInstallDir, redisInfo.Key, redisInfo.Name) + if err := fileOp.TarGzExtractPro(recoverFile, redisDataDir, secret); err != nil { + return err + } + } else { + itemName := "dump.rdb" + if appendonly == "yes" && strings.HasPrefix(redisInfo.Version, "6.") { + itemName = "appendonly.aof" + } + input, err := os.ReadFile(recoverFile) + if err != nil { + return err + } + if err = os.WriteFile(composeDir+"/data/"+itemName, input, 0640); err != nil { + return err + } + } + if _, err := compose.Up(composeDir + "/docker-compose.yml"); err != nil { + return err + } + isOk = true + return nil + } + itemTask.AddSubTask(i18n.GetMsgByKey("TaskRecover"), recoverDatabase, nil) + if parentTask != nil { + return recoverDatabase(parentTask) + } + + go func() { + _ = itemTask.Execute() + }() + return nil +} diff --git a/agent/app/service/backup_runtime.go b/agent/app/service/backup_runtime.go new file mode 100644 index 0000000..dab7d67 --- /dev/null +++ b/agent/app/service/backup_runtime.go @@ -0,0 +1,128 @@ +package service + +import ( + "encoding/json" + "errors" + "fmt" + "io/fs" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +func handleRuntimeBackup(runtime *model.Runtime, backupDir, fileName string, excludes string, secret string) error { + fileOp := files.NewFileOp() + tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", "")) + if !fileOp.Stat(tmpDir) { + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err) + } + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + remarkInfo, _ := json.Marshal(runtime) + remarkInfoPath := fmt.Sprintf("%s/runtime.json", tmpDir) + if err := fileOp.SaveFile(remarkInfoPath, string(remarkInfo), fs.ModePerm); err != nil { + return err + } + + appPath := runtime.GetPath() + if err := fileOp.TarGzCompressPro(true, appPath, path.Join(tmpDir, "runtime.tar.gz"), secret, excludes); err != nil { + return err + } + if err := fileOp.TarGzCompressPro(true, tmpDir, path.Join(backupDir, fileName), secret, ""); err != nil { + return err + } + return nil +} + +func handleRuntimeRecover(runtime *model.Runtime, recoverFile string, isRollback bool, secret string) error { + isOk := false + if !isRollback { + rollbackFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("runtime/%s_%s.tar.gz", runtime.Name, time.Now().Format(constant.DateTimeSlimLayout))) + if err := handleRuntimeBackup(runtime, path.Dir(rollbackFile), path.Base(rollbackFile), "", secret); err != nil { + return fmt.Errorf("backup runtime %s for rollback before recover failed, err: %v", runtime.Name, err) + } + defer func() { + if !isOk { + global.LOG.Info("recover failed, start to rollback now") + if err := handleRuntimeRecover(runtime, rollbackFile, true, ""); err != nil { + global.LOG.Errorf("rollback runtime %s from %s failed, err: %v", runtime.Name, rollbackFile, err) + return + } + global.LOG.Infof("rollback runtime %s from %s successful", runtime.Name, rollbackFile) + _ = os.RemoveAll(rollbackFile) + } else { + _ = os.RemoveAll(rollbackFile) + } + }() + } + fileOp := files.NewFileOp() + if err := fileOp.TarGzExtractPro(recoverFile, path.Dir(recoverFile), secret); err != nil { + return err + } + tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "") + defer func() { + go startRuntime(runtime) + _ = os.RemoveAll(strings.ReplaceAll(recoverFile, ".tar.gz", "")) + }() + + if !fileOp.Stat(tmpPath+"/runtime.json") || !fileOp.Stat(tmpPath+"/runtime.tar.gz") { + return errors.New("the wrong recovery package does not have runtime.json or runtime.tar.gz files") + } + var oldRuntime model.Runtime + runtimeJson, err := os.ReadFile(tmpPath + "/runtime.json") + if err != nil { + return err + } + if err := json.Unmarshal(runtimeJson, &oldRuntime); err != nil { + return fmt.Errorf("unmarshal runtime.json failed, err: %v", err) + } + if oldRuntime.Type != runtime.Type || oldRuntime.Name != runtime.Name { + return errors.New("the current backup file does not match the application") + } + + newEnvFile, err := coverEnvJsonToStr(runtime.Env) + if err != nil { + return err + } + runtimeDir := runtime.GetPath() + backPath := fmt.Sprintf("%s_bak", runtimeDir) + _ = fileOp.Rename(runtimeDir, backPath) + _ = fileOp.CreateDir(runtimeDir, constant.DirPerm) + + if err := fileOp.TarGzExtractPro(tmpPath+"/runtime.tar.gz", fmt.Sprintf("%s/%s", global.Dir.RuntimeDir, runtime.Type), secret); err != nil { + global.LOG.Errorf("handle recover from runtime.tar.gz failed, err: %v", err) + _ = fileOp.DeleteDir(runtimeDir) + _ = fileOp.Rename(backPath, runtimeDir) + return err + } + _ = fileOp.DeleteDir(backPath) + + if len(newEnvFile) != 0 { + envPath := fmt.Sprintf("%s/%s/%s/.env", global.Dir.RuntimeDir, runtime.Type, runtime.Name) + file, err := os.OpenFile(envPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + _, _ = file.WriteString(newEnvFile) + } + + oldRuntime.ID = runtime.ID + oldRuntime.Status = constant.StatusStarting + if err := runtimeRepo.Save(&oldRuntime); err != nil { + global.LOG.Errorf("save db app install failed, err: %v", err) + return err + } + isOk = true + return nil +} diff --git a/agent/app/service/backup_website.go b/agent/app/service/backup_website.go new file mode 100644 index 0000000..b0adfd4 --- /dev/null +++ b/agent/app/service/backup_website.go @@ -0,0 +1,373 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "io/fs" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +func (u *BackupService) WebsiteBackup(req dto.CommonBackup) error { + website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName)) + if err != nil { + return err + } + + timeNow := time.Now().Format(constant.DateTimeSlimLayout) + itemDir := fmt.Sprintf("website/%s", website.Alias) + backupDir := path.Join(global.Dir.LocalBackupDir, itemDir) + fileName := fmt.Sprintf("%s_%s.tar.gz", website.Alias, timeNow+common.RandStrAndNum(5)) + + record := &model.BackupRecord{ + Type: "website", + Name: website.Alias, + DetailName: website.Alias, + SourceAccountIDs: "1", + DownloadAccountID: 1, + FileDir: itemDir, + FileName: fileName, + TaskID: req.TaskID, + Status: constant.StatusWaiting, + Description: req.Description, + } + if err = backupRepo.CreateRecord(record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return err + } + if err = handleWebsiteBackup(&website, nil, record.ID, backupDir, fileName, "", req.Secret, req.TaskID); err != nil { + global.LOG.Errorf("backup website %s failed, err: %v", website.Alias, err) + return err + } + return nil +} + +func (u *BackupService) WebsiteRecover(req dto.CommonRecover) error { + website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(req.DetailName)) + if err != nil { + return err + } + if err := handleWebsiteRecover(&website, nil, req.File, false, req.Secret, req.TaskID); err != nil { + global.LOG.Errorf("recover website %s failed, err: %v", website.Alias, err) + } + return nil +} + +func handleWebsiteRecover(website *model.Website, parentTask *task.Task, recoverFile string, isRollback bool, secret, taskID string) error { + var ( + err error + recoverTask *task.Task + isOk = false + rollbackFile string + ) + recoverTask = parentTask + if parentTask == nil { + recoverTask, err = task.NewTaskWithOps(website.PrimaryDomain, task.TaskRecover, task.TaskScopeBackup, taskID, website.ID) + if err != nil { + return err + } + } + recoverWebsite := func(t *task.Task) error { + if !isRollback { + rollbackFile = path.Join(global.Dir.TmpDir, fmt.Sprintf("website/%s_%s.tar.gz", website.Alias, time.Now().Format(constant.DateTimeSlimLayout))) + if err := handleWebsiteBackup(website, recoverTask, 0, path.Dir(rollbackFile), path.Base(rollbackFile), "", "", taskID); err != nil { + return fmt.Errorf("backup website %s for rollback before recover failed, err: %v", website.Alias, err) + } + } + + fileOp := files.NewFileOp() + tmpPath := strings.ReplaceAll(recoverFile, ".tar.gz", "") + t.Log(i18n.GetWithName("DeCompressFile", recoverFile)) + if err = fileOp.TarGzExtractPro(recoverFile, path.Dir(recoverFile), secret); err != nil { + return err + } + defer func() { + _ = os.RemoveAll(tmpPath) + }() + + var oldWebsite model.Website + websiteJson, err := os.ReadFile(tmpPath + "/website.json") + if err != nil { + return err + } + if err = json.Unmarshal(websiteJson, &oldWebsite); err != nil { + return fmt.Errorf("unmarshal app.json failed, err: %v", err) + } + + if err = checkValidOfWebsite(&oldWebsite, website); err != nil { + t.Log(i18n.GetWithName("ErrCheckValid", err.Error())) + return err + } + + temPathWithName := tmpPath + "/" + website.Alias + if !fileOp.Stat(tmpPath+"/website.json") || !fileOp.Stat(temPathWithName+".conf") || !fileOp.Stat(temPathWithName+".web.tar.gz") { + return buserr.WithDetail("ErrBackupExist", ".conf or .web.tar.gz", nil) + } + if website.Type == constant.Deployment { + if !fileOp.Stat(temPathWithName + ".app.tar.gz") { + return buserr.WithDetail("ErrBackupExist", ".app.tar.gz", nil) + } + } + + nginxInfo, err := appInstallRepo.LoadBaseInfo(constant.AppOpenresty, "") + if err != nil { + return err + } + if err = fileOp.CopyFile(fmt.Sprintf("%s/%s.conf", tmpPath, website.Alias), GetConfDir(*website)); err != nil { + return err + } + + switch website.Type { + case constant.Deployment: + app, err := appInstallRepo.GetFirst(repo.WithByID(website.AppInstallID)) + if err != nil { + return err + } + taskName := task.GetTaskName(app.Name, task.TaskRecover, task.TaskScopeApp) + t.LogStart(taskName) + if err := handleAppRecover(&app, recoverTask, fmt.Sprintf("%s/%s.app.tar.gz", tmpPath, website.Alias), true, "", ""); err != nil { + return err + } + t.LogSuccess(taskName) + if _, err = compose.DownAndUp(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, app.App.Key, app.Name)); err != nil { + return err + } + case constant.Runtime: + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + taskName := task.GetTaskName(runtime.Name, task.TaskRecover, task.TaskScopeRuntime) + t.LogStart(taskName) + if err := handleRuntimeRecover(runtime, fmt.Sprintf("%s/%s.runtime.tar.gz", tmpPath, website.Alias), true, ""); err != nil { + return err + } + t.LogSuccess(taskName) + if oldWebsite.DbID > 0 { + if err := recoverWebsiteDatabase(t, oldWebsite.DbID, oldWebsite.DbType, tmpPath, website.Alias); err != nil { + return err + } + } + case constant.Static: + if oldWebsite.DbID > 0 { + if err := recoverWebsiteDatabase(t, oldWebsite.DbID, oldWebsite.DbType, tmpPath, website.Alias); err != nil { + return err + } + } + } + taskName := i18n.GetMsgByKey("TaskRecover") + i18n.GetMsgByKey("websiteDir") + t.Log(taskName) + if err = fileOp.TarGzExtractPro(fmt.Sprintf("%s/%s.web.tar.gz", tmpPath, website.Alias), GetOpenrestyDir(SitesRootDir), ""); err != nil { + return err + } + if err := cmd.RunDefaultBashCf("docker exec -i %s nginx -s reload", nginxInfo.ContainerName); err != nil { + return err + } + oldWebsite.ID = website.ID + if err := websiteRepo.SaveWithoutCtx(&oldWebsite); err != nil { + return err + } + isOk = true + return nil + } + + if parentTask != nil { + return recoverWebsite(parentTask) + } + + rollBackWebsite := func(t *task.Task) { + if isRollback { + return + } + if !isOk { + t.Log(i18n.GetMsgByKey("RecoverFailedStartRollBack")) + if err := handleWebsiteRecover(website, t, rollbackFile, true, "", ""); err != nil { + t.LogFailedWithErr(i18n.GetMsgByKey("Rollback"), err) + return + } + t.LogSuccess(i18n.GetMsgByKey("Rollback")) + _ = os.RemoveAll(rollbackFile) + } else { + _ = os.RemoveAll(rollbackFile) + } + } + recoverTask.AddSubTask(task.GetTaskName(website.PrimaryDomain, task.TaskRecover, task.TaskScopeBackup), recoverWebsite, rollBackWebsite) + go func() { + _ = recoverTask.Execute() + }() + return nil +} + +func handleWebsiteBackup(website *model.Website, parentTask *task.Task, recordID uint, backupDir, fileName, excludes, secret, taskID string) error { + var ( + err error + backupTask *task.Task + ) + backupTask = parentTask + if parentTask == nil { + backupTask, err = task.NewTaskWithOps(website.Alias, task.TaskBackup, task.TaskScopeBackup, taskID, website.ID) + if err != nil { + return err + } + } + itemHandler := func() error { return doWebsiteBackup(website, backupTask, backupDir, fileName, excludes, secret) } + if parentTask != nil { + return itemHandler() + } + backupTask.AddSubTaskWithOps(task.GetTaskName(website.Alias, task.TaskBackup, task.TaskScopeBackup), func(t *task.Task) error { return itemHandler() }, nil, 3, time.Hour) + go func() { + if err := backupTask.Execute(); err != nil { + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + return + } + backupRepo.UpdateRecordByMap(recordID, map[string]interface{}{"status": constant.StatusSuccess}) + }() + return nil +} + +func doWebsiteBackup(website *model.Website, parentTask *task.Task, backupDir, fileName, excludes, secret string) error { + fileOp := files.NewFileOp() + tmpDir := fmt.Sprintf("%s/%s", backupDir, strings.ReplaceAll(fileName, ".tar.gz", "")) + if !fileOp.Stat(tmpDir) { + if err := os.MkdirAll(tmpDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", backupDir, err) + } + } + defer func() { + _ = os.RemoveAll(tmpDir) + }() + + remarkInfo, _ := json.Marshal(website) + if err := fileOp.SaveFile(tmpDir+"/website.json", string(remarkInfo), fs.ModePerm); err != nil { + return err + } + nginxConfFile := GetWebsiteConfigPath(*website) + if err := fileOp.CopyFile(nginxConfFile, tmpDir); err != nil { + return err + } + parentTask.Log(i18n.GetMsgByKey("BackupNginxConfig")) + + switch website.Type { + case constant.Deployment: + app, err := appInstallRepo.GetFirst(repo.WithByID(website.AppInstallID)) + if err != nil { + return err + } + parentTask.LogStart(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeApp)) + if err = handleAppBackup(&app, parentTask, 0, tmpDir, fmt.Sprintf("%s.app.tar.gz", website.Alias), excludes, "", ""); err != nil { + return err + } + parentTask.LogSuccess(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeApp)) + case constant.Runtime: + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + parentTask.LogStart(task.GetTaskName(runtime.Name, task.TaskBackup, task.TaskScopeRuntime)) + if err = handleRuntimeBackup(runtime, tmpDir, fmt.Sprintf("%s.runtime.tar.gz", website.Alias), excludes, ""); err != nil { + return err + } + parentTask.LogSuccess(task.GetTaskName(runtime.Name, task.TaskBackup, task.TaskScopeRuntime)) + if website.DbID > 0 { + if err = backupDatabaseWithTask(parentTask, website.DbType, tmpDir, website.Alias, website.DbID); err != nil { + return err + } + } + case constant.Static: + if website.DbID > 0 { + if err := backupDatabaseWithTask(parentTask, website.DbType, tmpDir, website.Alias, website.DbID); err != nil { + return err + } + } + } + + websiteDir := GetSitePath(*website, SiteDir) + parentTask.LogStart(i18n.GetMsgByKey("CompressDir")) + if err := fileOp.TarGzCompressPro(true, websiteDir, path.Join(tmpDir, fmt.Sprintf("%s.web.tar.gz", website.Alias)), "", excludes); err != nil { + return err + } + if err := fileOp.TarGzCompressPro(true, tmpDir, path.Join(backupDir, fileName), secret, ""); err != nil { + return err + } + parentTask.Log(i18n.GetWithName("CompressFileSuccess", fileName)) + return nil +} + +func checkValidOfWebsite(oldWebsite, website *model.Website) error { + if oldWebsite.Alias != website.Alias || oldWebsite.Type != website.Type { + return buserr.WithDetail("ErrBackupMatch", fmt.Sprintf("oldName: %s, oldType: %v", oldWebsite.Alias, oldWebsite.Type), nil) + } + if oldWebsite.AppInstallID != 0 { + _, err := appInstallRepo.GetFirst(repo.WithByID(oldWebsite.AppInstallID)) + if err != nil { + return buserr.WithDetail("ErrBackupMatch", "app", nil) + } + } + if oldWebsite.RuntimeID != 0 { + if _, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(oldWebsite.RuntimeID)); err != nil { + return buserr.WithDetail("ErrBackupMatch", "runtime", nil) + } + } + if oldWebsite.WebsiteSSLID != 0 { + if _, err := websiteSSLRepo.GetFirst(repo.WithByID(oldWebsite.WebsiteSSLID)); err != nil { + return buserr.WithDetail("ErrBackupMatch", "ssl", nil) + } + } + return nil +} + +func recoverWebsiteDatabase(t *task.Task, dbID uint, dbType, tmpPath, websiteKey string) error { + switch dbType { + case constant.AppPostgresql: + db, err := postgresqlRepo.Get(repo.WithByID(dbID)) + if err != nil { + return err + } + taskName := task.GetTaskName(db.Name, task.TaskRecover, task.TaskScopeDatabase) + t.LogStart(taskName) + if err := handlePostgresqlRecover(dto.CommonRecover{ + Name: db.PostgresqlName, + DetailName: db.Name, + File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, websiteKey), + }, t, true); err != nil { + t.LogFailedWithErr(taskName, err) + return err + } + t.LogSuccess(taskName) + case constant.AppMysql, constant.AppMariaDB: + db, err := mysqlRepo.Get(repo.WithByID(dbID)) + if err != nil { + return err + } + taskName := task.GetTaskName(db.Name, task.TaskRecover, task.TaskScopeDatabase) + t.LogStart(taskName) + if err := handleMysqlRecover(dto.CommonRecover{ + Name: db.MysqlName, + DetailName: db.Name, + File: fmt.Sprintf("%s/%s.sql.gz", tmpPath, websiteKey), + }, t, true); err != nil { + t.LogFailedWithErr(taskName, err) + return err + } + t.LogSuccess(taskName) + } + return nil +} diff --git a/agent/app/service/clam.go b/agent/app/service/clam.go new file mode 100644 index 0000000..6142528 --- /dev/null +++ b/agent/app/service/clam.go @@ -0,0 +1,506 @@ +package service + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/alert_push" + "github.com/1Panel-dev/1Panel/agent/utils/clam" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "github.com/jinzhu/copier" + "github.com/robfig/cron/v3" +) + +type ClamService struct { + serviceName string + freshClamService string +} + +type IClamService interface { + LoadBaseInfo() (dto.ClamBaseInfo, error) + Operate(operate string) error + SearchWithPage(search dto.SearchClamWithPage) (int64, interface{}, error) + Create(req dto.ClamCreate) error + Update(req dto.ClamUpdate) error + UpdateStatus(id uint, status string) error + Delete(req dto.ClamDelete) error + HandleOnce(id uint) error + + LoadFile(req dto.ClamFileReq) (string, error) + UpdateFile(req dto.UpdateByNameAndFile) error + + SearchRecords(req dto.ClamLogSearch) (int64, interface{}, error) + CleanRecord(id uint) error +} + +func NewIClamService() IClamService { + return &ClamService{} +} + +func (c *ClamService) LoadBaseInfo() (dto.ClamBaseInfo, error) { + var baseInfo dto.ClamBaseInfo + baseInfo.Version = "-" + baseInfo.FreshVersion = "-" + + clamSvc, err := controller.LoadServiceName("clam") + if err != nil { + baseInfo.IsExist = false + return baseInfo, nil + } + c.serviceName = clamSvc + exist, _ := controller.CheckExist(clamSvc) + if exist { + baseInfo.IsExist = true + baseInfo.IsActive, _ = controller.CheckActive(clamSvc) + } + + freshSvc, err := controller.LoadServiceName("freshclam") + if err != nil { + baseInfo.FreshIsExist = false + return baseInfo, nil + } + c.freshClamService = freshSvc + freshExist, _ := controller.CheckExist(freshSvc) + if freshExist { + baseInfo.FreshIsExist = true + baseInfo.FreshIsActive, _ = controller.CheckActive(freshSvc) + } + + if !cmd.Which("clamdscan") { + baseInfo.IsActive = false + } + + if baseInfo.IsActive { + version, err := cmd.RunDefaultWithStdoutBashC("clamdscan --version") + if err == nil { + if strings.Contains(version, "/") { + baseInfo.Version = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ") + } else { + baseInfo.Version = strings.TrimPrefix(version, "ClamAV ") + } + } + } else { + _ = clam.StopAllClamJob(false, clamRepo) + } + if baseInfo.FreshIsActive { + version, err := cmd.RunDefaultWithStdoutBashC("freshclam --version") + if err == nil { + if strings.Contains(version, "/") { + baseInfo.FreshVersion = strings.TrimPrefix(strings.Split(version, "/")[0], "ClamAV ") + } else { + baseInfo.FreshVersion = strings.TrimPrefix(version, "ClamAV ") + } + } + } + return baseInfo, nil +} + +func (c *ClamService) Operate(operate string) error { + switch operate { + case "start", "restart", "stop": + if err := controller.Handle(operate, c.serviceName); err != nil { + return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, err) + } + return nil + case "fresh-start", "fresh-restart", "fresh-stop": + if err := controller.Handle(strings.TrimPrefix(operate, "fresh-"), c.freshClamService); err != nil { + return fmt.Errorf("%s the %s failed, err: %s", operate, c.serviceName, err) + } + return nil + default: + return fmt.Errorf("not support such operation: %v", operate) + } +} + +func (c *ClamService) SearchWithPage(req dto.SearchClamWithPage) (int64, interface{}, error) { + total, clams, err := clamRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order)) + if err != nil { + return 0, nil, err + } + var datas []dto.ClamInfo + for _, clam := range clams { + var item dto.ClamInfo + if err := copier.Copy(&item, &clam); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + datas = append(datas, item) + } + for i := 0; i < len(datas); i++ { + record, _ := clamRepo.RecordFirst(datas[i].ID) + if record.ID != 0 { + datas[i].LastRecordStatus = record.Status + datas[i].LastRecordTime = record.StartTime.Format(constant.DateTimeLayout) + } else { + datas[i].LastRecordTime = "-" + } + alertBase := dto.AlertBase{ + AlertType: "clams", + EntryID: datas[i].ID, + } + alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable)) + datas[i].AlertMethod = alertInfo.Method + if alertInfo.SendCount != 0 { + datas[i].AlertCount = alertInfo.SendCount + } else { + datas[i].AlertCount = 0 + } + } + return total, datas, err +} + +func (c *ClamService) Create(req dto.ClamCreate) error { + clam, _ := clamRepo.Get(repo.WithByName(req.Name)) + if clam.ID != 0 { + return buserr.New("ErrRecordExist") + } + if cmd.CheckIllegal(req.Path) { + return buserr.New("ErrCmdIllegal") + } + if err := copier.Copy(&clam, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if clam.InfectedStrategy == "none" || clam.InfectedStrategy == "remove" { + clam.InfectedDir = "" + } + if len(req.Spec) != 0 { + entryID, err := xpack.StartClam(&clam, false) + if err != nil { + return err + } + clam.EntryID = entryID + clam.Status = constant.StatusEnable + } + if err := clamRepo.Create(&clam); err != nil { + return err + } + if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" { + createAlert := dto.AlertCreate{ + Title: req.AlertTitle, + SendCount: req.AlertCount, + Method: req.AlertMethod, + Type: "clams", + Project: strconv.Itoa(int(clam.ID)), + Status: constant.AlertEnable, + } + err := NewIAlertService().CreateAlert(createAlert) + if err != nil { + return err + } + } + return nil +} + +func (c *ClamService) Update(req dto.ClamUpdate) error { + if cmd.CheckIllegal(req.Path) { + return buserr.New("ErrCmdIllegal") + } + clam, _ := clamRepo.Get(repo.WithByName(req.Name)) + if clam.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if req.InfectedStrategy == "none" || req.InfectedStrategy == "remove" { + req.InfectedDir = "" + } + var clamItem model.Clam + if err := copier.Copy(&clamItem, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + clamItem.EntryID = clam.EntryID + upMap := map[string]interface{}{} + if len(clam.Spec) != 0 && clam.EntryID != 0 { + global.Cron.Remove(cron.EntryID(clamItem.EntryID)) + upMap["entry_id"] = 0 + } + if len(req.Spec) == 0 { + upMap["status"] = "" + upMap["entry_id"] = 0 + } + if len(req.Spec) != 0 && clam.Status != constant.StatusDisable { + newEntryID, err := xpack.StartClam(&clamItem, true) + if err != nil { + return err + } + upMap["entry_id"] = newEntryID + } + if len(clam.Spec) == 0 && len(req.Spec) != 0 { + upMap["status"] = constant.StatusEnable + } + + upMap["name"] = req.Name + upMap["path"] = req.Path + upMap["infected_dir"] = req.InfectedDir + upMap["infected_strategy"] = req.InfectedStrategy + upMap["spec"] = req.Spec + upMap["timeout"] = req.Timeout + upMap["description"] = req.Description + if err := clamRepo.Update(req.ID, upMap); err != nil { + return err + } + updateAlert := dto.AlertCreate{ + Title: req.AlertTitle, + SendCount: req.AlertCount, + Method: req.AlertMethod, + Type: "clams", + Project: strconv.Itoa(int(clam.ID)), + } + err := NewIAlertService().ExternalUpdateAlert(updateAlert) + if err != nil { + return err + } + return nil +} + +func (c *ClamService) UpdateStatus(id uint, status string) error { + clam, _ := clamRepo.Get(repo.WithByID(id)) + if clam.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + var ( + entryID int + err error + ) + if status == constant.StatusEnable { + entryID, err = xpack.StartClam(&clam, true) + if err != nil { + return err + } + } else { + global.Cron.Remove(cron.EntryID(clam.EntryID)) + global.LOG.Infof("stop cronjob entryID: %v", clam.EntryID) + } + + return clamRepo.Update(clam.ID, map[string]interface{}{"status": status, "entry_id": entryID}) +} + +func (c *ClamService) Delete(req dto.ClamDelete) error { + for _, id := range req.Ids { + clam, _ := clamRepo.Get(repo.WithByID(id)) + if clam.ID == 0 { + continue + } + if len(clam.Spec) != 0 { + global.Cron.Remove(cron.EntryID(clam.EntryID)) + } + _ = c.CleanRecord(clam.ID) + if req.RemoveInfected { + _ = os.RemoveAll(path.Join(clam.InfectedDir, "1panel-infected", clam.Name)) + } + if err := clamRepo.Delete(repo.WithByID(id)); err != nil { + return err + } + err := alertRepo.Delete(alertRepo.WithByProject(strconv.Itoa(int(clam.ID))), alertRepo.WithByType("clams")) + if err != nil { + return err + } + } + return nil +} + +func (c *ClamService) HandleOnce(id uint) error { + if active := clam.StopAllClamJob(true, clamRepo); !active { + return buserr.New("ErrClamdscanNotFound") + } + clamItem, _ := clamRepo.Get(repo.WithByID(id)) + if clamItem.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + record := clamRepo.StartRecords(clamItem.ID) + taskItem, err := task.NewTaskWithOps("clam-"+clamItem.Name, task.TaskScan, task.TaskScopeClam, record.TaskID, clamItem.ID) + if err != nil { + return fmt.Errorf("new task for exec shell failed, err: %v", err) + } + clam.AddScanTask(taskItem, clamItem, record.StartTime.Format(constant.DateTimeSlimLayout)) + go func() { + err := taskItem.Execute() + taskRepo := repo.NewITaskRepo() + taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID)) + if len(taskItem.ID) == 0 { + record.TaskID = "" + } + if err != nil { + clamRepo.EndRecords(record, constant.StatusFailed, err.Error()) + return + } + handleAlert(record.InfectedFiles, clamItem.Name, clamItem.ID) + clam.AnalysisFromLog(taskItem.LogFile, &record) + clamRepo.EndRecords(record, constant.StatusDone, "") + }() + return nil +} + +func (c *ClamService) SearchRecords(req dto.ClamLogSearch) (int64, interface{}, error) { + clam, _ := clamRepo.Get(repo.WithByID(req.ClamID)) + if clam.ID == 0 { + return 0, nil, buserr.New("ErrRecordNotFound") + } + loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + req.StartTime = req.StartTime.In(loc) + req.EndTime = req.EndTime.In(loc) + + total, records, err := clamRepo.PageRecords(req.Page, req.PageSize, clamRepo.WithByClamID(req.ClamID), repo.WithByStatus(req.Status), repo.WithByCreatedAt(req.StartTime, req.EndTime)) + if err != nil { + return 0, nil, err + } + var datas []dto.ClamRecord + for _, record := range records { + var item dto.ClamRecord + if err := copier.Copy(&item, &record); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + datas = append(datas, item) + } + return int64(total), datas, nil +} + +func (c *ClamService) CleanRecord(id uint) error { + record, _ := clamRepo.ListRecord() + for _, item := range record { + if len(item.TaskID) != 0 { + continue + } + taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(item.TaskID)) + if len(taskItem.LogFile) != 0 { + _ = os.Remove(taskItem.LogFile) + } + } + return clamRepo.DeleteRecord(clamRepo.WithByClamID(id)) +} + +func (c *ClamService) LoadFile(req dto.ClamFileReq) (string, error) { + filePath := "" + switch req.Name { + case "clamd": + filePath = c.loadConfigPath("clamd") + case "clamd-log": + filePath = c.loadLogPath("clamd-log") + case "freshclam": + filePath = c.loadConfigPath("freshclam") + case "freshclam-log": + filePath = c.loadLogPath("freshclam-log") + default: + return "", fmt.Errorf("not support such type") + } + if _, err := os.Stat(filePath); err != nil { + return "", buserr.New("ErrHttpReqNotFound") + } + var tail string + if req.Tail != "0" { + tail = req.Tail + } else { + tail = "+1" + } + cmd := exec.Command("tail", "-n", tail, filePath) + stdout, err := cmd.CombinedOutput() + if err != nil { + return "", fmt.Errorf("tail -n %v failed, err: %v", req.Tail, err) + } + return string(stdout), nil +} + +func (c *ClamService) UpdateFile(req dto.UpdateByNameAndFile) error { + filePath := "" + switch req.Name { + case "clamd": + filePath = c.loadConfigPath("clamd") + case "freshclam": + filePath = c.loadConfigPath("freshclam") + default: + return fmt.Errorf("not support such type") + } + file, err := os.OpenFile(filePath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(req.File) + write.Flush() + + _ = controller.HandleRestart(c.serviceName) + return nil +} + +func (c *ClamService) loadLogPath(name string) string { + configKey := "clamd" + searchPrefix := "LogFile " + if name != "clamd-log" { + configKey = "freshclam" + searchPrefix = "UpdateLogFile " + } + confPath := c.loadConfigPath(configKey) + content, err := os.ReadFile(confPath) + if err != nil { + global.LOG.Debugf("read config of %s failed, err: %v", configKey, err) + return "" + } + lines := strings.Split(string(content), "\n") + for _, line := range lines { + if strings.HasPrefix(line, searchPrefix) { + return strings.Trim(strings.ReplaceAll(line, searchPrefix, ""), " ") + } + } + if configKey == "clamd" { + if _, err := os.Stat("/var/log/clamav/clamav.log"); err == nil { + return "/var/log/clamav/clamav.log" + } + if _, err := os.Stat("/var/log/clamd.scan"); err == nil { + return "/var/log/clamd.scan" + } + } + if configKey == "freshclam" { + if _, err := os.Stat("/var/log/clamav/freshclam.log"); err == nil { + return "/var/log/clamav/freshclam.log" + } + if _, err := os.Stat("/var/log/freshclam.log"); err == nil { + return "/var/log/freshclam.log" + } + } + return "" +} + +func (c *ClamService) loadConfigPath(confType string) string { + switch confType { + case "clamd": + if _, err := os.Stat("/etc/clamav/clamd.conf"); err == nil { + return "/etc/clamav/clamd.conf" + } + return "/etc/clamd.d/scan.conf" + case "freshclam": + if _, err := os.Stat("/etc/clamav/freshclam.conf"); err == nil { + return "/etc/clamav/freshclam.conf" + } + return "/etc/freshclam.conf" + default: + return "" + } +} + +func handleAlert(infectedFiles, clamName string, clamId uint) { + itemInfected, _ := strconv.Atoi(strings.TrimSpace(infectedFiles)) + if itemInfected < 0 { + return + } + pushAlert := dto.PushAlert{ + TaskName: clamName, + AlertType: "clams", + EntryID: clamId, + Param: strconv.Itoa(itemInfected), + } + _ = alert_push.PushAlert(pushAlert) +} diff --git a/agent/app/service/compose_template.go b/agent/app/service/compose_template.go new file mode 100644 index 0000000..a5d6b6b --- /dev/null +++ b/agent/app/service/compose_template.go @@ -0,0 +1,98 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/jinzhu/copier" +) + +type ComposeTemplateService struct{} + +type IComposeTemplateService interface { + List() ([]dto.ComposeTemplateInfo, error) + SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) + Create(req dto.ComposeTemplateCreate) error + Update(id uint, upMap map[string]interface{}) error + Batch(req dto.ComposeTemplateBatch) error + Delete(ids []uint) error +} + +func NewIComposeTemplateService() IComposeTemplateService { + return &ComposeTemplateService{} +} + +func (u *ComposeTemplateService) List() ([]dto.ComposeTemplateInfo, error) { + composes, err := composeRepo.List() + if err != nil { + return nil, buserr.New("ErrRecordNotFound") + } + var dtoLists []dto.ComposeTemplateInfo + for _, compose := range composes { + var item dto.ComposeTemplateInfo + if err := copier.Copy(&item, &compose); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + dtoLists = append(dtoLists, item) + } + return dtoLists, err +} + +func (u *ComposeTemplateService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) { + total, composes, err := composeRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info)) + var dtoComposeTemplates []dto.ComposeTemplateInfo + for _, compose := range composes { + var item dto.ComposeTemplateInfo + if err := copier.Copy(&item, &compose); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + dtoComposeTemplates = append(dtoComposeTemplates, item) + } + return total, dtoComposeTemplates, err +} + +func (u *ComposeTemplateService) Create(composeDto dto.ComposeTemplateCreate) error { + compose, _ := composeRepo.Get(repo.WithByName(composeDto.Name)) + if compose.ID != 0 { + return buserr.New("ErrRecordExist") + } + if err := copier.Copy(&compose, &composeDto); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if err := composeRepo.Create(&compose); err != nil { + return err + } + return nil +} + +func (u *ComposeTemplateService) Batch(req dto.ComposeTemplateBatch) error { + for _, item := range req.Templates { + template, _ := composeRepo.Get(repo.WithByName(item.Name)) + if template.ID == 0 { + if err := composeRepo.Create(&model.ComposeTemplate{Name: item.Name, Content: item.Content, Description: item.Description}); err != nil { + return err + } + } else { + if err := composeRepo.Update(template.ID, map[string]interface{}{"content": item.Content, "description": item.Description}); err != nil { + return err + } + } + } + return nil +} + +func (u *ComposeTemplateService) Delete(ids []uint) error { + if len(ids) == 1 { + compose, _ := composeRepo.Get(repo.WithByID(ids[0])) + if compose.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + return composeRepo.Delete(repo.WithByID(ids[0])) + } + return composeRepo.Delete(repo.WithByIDs(ids)) +} + +func (u *ComposeTemplateService) Update(id uint, upMap map[string]interface{}) error { + return composeRepo.Update(id, upMap) +} diff --git a/agent/app/service/container.go b/agent/app/service/container.go new file mode 100644 index 0000000..7dce670 --- /dev/null +++ b/agent/app/service/container.go @@ -0,0 +1,1812 @@ +package service + +import ( + "bufio" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "os" + "os/exec" + "path/filepath" + "sort" + "strconv" + "strings" + "sync" + "syscall" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/build" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/mount" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/api/types/volume" + "github.com/docker/docker/client" + "github.com/docker/go-connections/nat" + "github.com/gin-gonic/gin" + v1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/mem" +) + +type ContainerService struct{} + +type IContainerService interface { + Page(req dto.PageContainer) (int64, interface{}, error) + List() []dto.ContainerOptions + ListByImage(imageName string) []dto.ContainerOptions + LoadStatus() (dto.ContainerStatus, error) + PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) + ListNetwork() ([]dto.Options, error) + PageVolume(req dto.SearchWithPage) (int64, interface{}, error) + ListVolume() ([]dto.Options, error) + PageCompose(req dto.SearchWithPage) (int64, interface{}, error) + CreateCompose(req dto.ComposeCreate) error + ComposeOperation(req dto.ComposeOperation) error + ContainerCreate(req dto.ContainerOperate, inThread bool) error + ContainerCreateByCommand(req dto.ContainerCreateByCommand) error + ContainerUpdate(req dto.ContainerOperate) error + ContainerUpgrade(req dto.ContainerUpgrade) error + ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) + ContainerListStats() ([]dto.ContainerListStats, error) + ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error) + LoadResourceLimit() (*dto.ResourceLimit, error) + ContainerRename(req dto.ContainerRename) error + ContainerCommit(req dto.ContainerCommit) error + ContainerLogClean(req dto.OperationWithName) error + ContainerOperation(req dto.ContainerOperation) error + DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error + ContainerStats(id string) (*dto.ContainerStats, error) + Inspect(req dto.InspectReq) (string, error) + DeleteNetwork(req dto.BatchDelete) error + CreateNetwork(req dto.NetworkCreate) error + DeleteVolume(req dto.BatchDelete) error + CreateVolume(req dto.VolumeCreate) error + TestCompose(req dto.ComposeCreate) (bool, error) + ComposeUpdate(req dto.ComposeUpdate) error + ComposeLogClean(req dto.ComposeLogClean) error + Prune(req dto.ContainerPrune) error + + LoadUsers(req dto.OperationWithName) []string + + StreamLogs(ctx *gin.Context, params dto.StreamLog) +} + +func NewIContainerService() IContainerService { + return &ContainerService{} +} + +func (u *ContainerService) Page(req dto.PageContainer) (int64, interface{}, error) { + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + defer client.Close() + options := container.ListOptions{All: true} + if len(req.Filters) != 0 { + options.Filters = filters.NewArgs() + options.Filters.Add("label", req.Filters) + } + containers, err := client.ContainerList(context.Background(), options) + if err != nil { + return 0, nil, err + } + records := searchWithFilter(req, containers) + + var backData []dto.ContainerInfo + total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + backData = make([]dto.ContainerInfo, 0) + } else { + if end >= total { + end = total + } + backData = records[start:end] + } + + for i := 0; i < len(backData); i++ { + install, _ := appInstallRepo.GetFirst(appInstallRepo.WithContainerName(backData[i].Name)) + if install.ID > 0 { + backData[i].AppInstallName = install.Name + backData[i].AppName = install.App.Name + websites, _ := websiteRepo.GetBy(websiteRepo.WithAppInstallId(install.ID)) + for _, website := range websites { + backData[i].Websites = append(backData[i].Websites, website.PrimaryDomain) + } + } + } + + return int64(total), backData, nil +} + +func (u *ContainerService) List() []dto.ContainerOptions { + var options []dto.ContainerOptions + client, err := docker.NewDockerClient() + if err != nil { + global.LOG.Errorf("load docker client for contianer list failed, err: %v", err) + return nil + } + defer client.Close() + containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true}) + if err != nil { + global.LOG.Errorf("load container list failed, err: %v", err) + return nil + } + for _, container := range containers { + for _, name := range container.Names { + if len(name) != 0 { + options = append(options, dto.ContainerOptions{Name: strings.TrimPrefix(name, "/"), State: container.State}) + } + } + } + + return options +} + +func (u *ContainerService) ListByImage(imageName string) []dto.ContainerOptions { + var options []dto.ContainerOptions + client, err := docker.NewDockerClient() + if err != nil { + global.LOG.Errorf("load docker client for contianer list failed, err: %v", err) + return nil + } + defer client.Close() + containers, err := client.ContainerList(context.Background(), container.ListOptions{All: true}) + if err != nil { + global.LOG.Errorf("load container list failed, err: %v", err) + return nil + } + for _, container := range containers { + if container.Image != imageName { + continue + } + for _, name := range container.Names { + if len(name) != 0 { + options = append(options, dto.ContainerOptions{Name: strings.TrimPrefix(name, "/"), State: container.State}) + } + } + } + + return options +} + +func (u *ContainerService) LoadStatus() (dto.ContainerStatus, error) { + var data dto.ContainerStatus + client, err := docker.NewDockerClient() + if err != nil { + return data, err + } + defer client.Close() + c := context.Background() + + images, _ := client.ImageList(c, image.ListOptions{All: true}) + data.ImageCount = len(images) + repo, _ := imageRepoRepo.List() + data.RepoCount = len(repo) + templates, _ := composeRepo.List() + data.ComposeTemplateCount = len(templates) + networks, _ := client.NetworkList(c, network.ListOptions{}) + data.NetworkCount = len(networks) + volumes, _ := client.VolumeList(c, volume.ListOptions{}) + data.VolumeCount = len(volumes.Volumes) + data.ComposeCount = loadComposeCount(client) + containers, _ := client.ContainerList(c, container.ListOptions{All: true}) + data.ContainerCount = len(containers) + for _, item := range containers { + switch item.State { + case "created": + data.Created++ + case "running": + data.Running++ + case "paused": + data.Paused++ + case "restarting": + data.Restarting++ + case "dead": + data.Dead++ + case "exited": + data.Exited++ + case "removing": + data.Removing++ + } + } + return data, nil +} +func (u *ContainerService) ContainerItemStats(req dto.OperationWithName) (dto.ContainerItemStats, error) { + var data dto.ContainerItemStats + client, err := docker.NewDockerClient() + if err != nil { + return data, err + } + if req.Name != "system" { + defer client.Close() + containerInfo, _, err := client.ContainerInspectWithRaw(context.Background(), req.Name, true) + if err != nil { + return data, err + } + data.SizeRw = *containerInfo.SizeRw + data.SizeRootFs = *containerInfo.SizeRootFs + return data, nil + } + + usage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{}) + if err != nil { + return data, err + } + for _, item := range usage.Images { + data.ImageUsage += item.Size + if item.Containers < 1 { + data.ImageReclaimable += item.Size + } + } + for _, item := range usage.Containers { + data.ContainerUsage += item.SizeRw + if item.State != "running" { + data.ContainerReclaimable += item.SizeRw + } + } + for _, item := range usage.Volumes { + data.VolumeUsage += item.UsageData.Size + if item.UsageData.RefCount == 0 { + data.VolumeReclaimable += item.UsageData.Size + } + } + for _, item := range usage.BuildCache { + data.BuildCacheUsage += item.Size + } + return data, nil +} +func (u *ContainerService) ContainerListStats() ([]dto.ContainerListStats, error) { + client, err := docker.NewDockerClient() + if err != nil { + return nil, err + } + defer client.Close() + list, err := client.ContainerList(context.Background(), container.ListOptions{All: true}) + if err != nil { + return nil, err + } + var datas []dto.ContainerListStats + var wg sync.WaitGroup + wg.Add(len(list)) + for i := 0; i < len(list); i++ { + go func(item container.Summary) { + datas = append(datas, loadCpuAndMem(client, item.ID)) + wg.Done() + }(list[i]) + } + wg.Wait() + return datas, nil +} + +func (u *ContainerService) ContainerCreateByCommand(req dto.ContainerCreateByCommand) error { + if cmd.CheckIllegal(req.Command) { + return buserr.New("ErrCmdIllegal") + } + if !strings.HasPrefix(strings.TrimSpace(req.Command), "docker run ") { + return errors.New("error command format") + } + containerName := "" + commands := strings.Split(req.Command, " ") + for index, val := range commands { + if val == "--name" && len(commands) > index+1 { + containerName = commands[index+1] + } + } + if !strings.Contains(req.Command, " -d ") { + req.Command = strings.ReplaceAll(req.Command, "docker run", "docker run -d") + } + if len(containerName) == 0 { + containerName = fmt.Sprintf("1Panel-%s-%s", common.RandStr(5), common.RandStrAndNum(4)) + req.Command += fmt.Sprintf(" --name %s", containerName) + } + taskItem, err := task.NewTaskWithOps(containerName, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1) + if err != nil { + global.LOG.Errorf("new task for create container failed, err: %v", err) + return err + } + go func() { + taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", containerName), func(t *task.Task) error { + cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem), cmd.WithTimeout(5*time.Minute)) + return cmdMgr.RunBashC(req.Command) + }, nil) + _ = taskItem.Execute() + }() + return nil +} + +func (u *ContainerService) Inspect(req dto.InspectReq) (string, error) { + client, err := docker.NewDockerClient() + if err != nil { + return "", err + } + defer client.Close() + var inspectInfo interface{} + switch req.Type { + case "container": + inspectInfo, err = client.ContainerInspect(context.Background(), req.ID) + case "compose": + filePath := "" + cli, err := docker.NewDockerClient() + if err != nil { + return "", err + } + defer cli.Close() + options := container.ListOptions{All: true} + options.Filters = filters.NewArgs() + options.Filters.Add("label", fmt.Sprintf("%s=%s", composeProjectLabel, req.ID)) + containers, err := cli.ContainerList(context.Background(), options) + if err != nil { + return "", err + } + for _, container := range containers { + config := container.Labels[composeConfigLabel] + if len(req.Detail) != 0 && strings.Contains(config, req.Detail) { + config = req.Detail + } + workdir := container.Labels[composeWorkdirLabel] + if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) { + filePath = config + break + } else { + filePath = workdir + break + } + } + if len(containers) == 0 { + composeItem, _ := composeRepo.GetRecord(repo.WithByName(req.ID)) + filePath = composeItem.Path + } + if _, err := os.Stat(filePath); err != nil { + return "", err + } + content, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + return string(content), nil + case "image": + inspectInfo, _, err = client.ImageInspectWithRaw(context.Background(), req.ID) + case "network": + inspectInfo, err = client.NetworkInspect(context.TODO(), req.ID, network.InspectOptions{}) + case "volume": + inspectInfo, err = client.VolumeInspect(context.TODO(), req.ID) + } + if err != nil { + return "", err + } + bytes, err := json.Marshal(inspectInfo) + if err != nil { + return "", err + } + return string(bytes), nil +} + +func (u *ContainerService) Prune(req dto.ContainerPrune) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + name := "" + switch req.PruneType { + case "container": + name = "Container" + case "image": + name = "Image" + case "volume": + name = "Volume" + case "buildcache": + name = "BuildCache" + case "network": + name = "Network" + } + taskItem, err := task.NewTaskWithOps(i18n.GetMsgByKey(name), task.TaskClean, task.TaskScopeContainer, req.TaskID, 1) + if err != nil { + global.LOG.Errorf("new task for create container failed, err: %v", err) + return err + } + + taskItem.AddSubTask(i18n.GetMsgByKey("TaskClean"), func(t *task.Task) error { + pruneFilters := filters.NewArgs() + if req.WithTagAll { + pruneFilters.Add("dangling", "false") + if req.PruneType != "image" { + pruneFilters.Add("until", "24h") + } + } + taskItem.Log(i18n.GetMsgByKey("PruneStart")) + SpaceReclaimed := 0 + switch req.PruneType { + case "container": + rep, err := client.ContainersPrune(context.Background(), pruneFilters) + if err != nil { + return err + } + SpaceReclaimed = int(rep.SpaceReclaimed) + case "image": + rep, err := client.ImagesPrune(context.Background(), pruneFilters) + if err != nil { + return err + } + SpaceReclaimed = int(rep.SpaceReclaimed) + case "network": + _, err := client.NetworksPrune(context.Background(), pruneFilters) + if err != nil { + return err + } + case "volume": + versions, err := client.ServerVersion(context.Background()) + if err != nil { + return err + } + if common.ComparePanelVersion(versions.APIVersion, "1.42") { + pruneFilters.Add("all", "true") + } + rep, err := client.VolumesPrune(context.Background(), pruneFilters) + if err != nil { + return err + } + SpaceReclaimed = int(rep.SpaceReclaimed) + case "buildcache": + opts := build.CachePruneOptions{} + opts.All = true + rep, err := client.BuildCachePrune(context.Background(), opts) + if err != nil { + return err + } + SpaceReclaimed = int(rep.SpaceReclaimed) + } + taskItem.Log(i18n.GetMsgWithMap("PruneHelper", map[string]interface{}{"name": i18n.GetMsgByKey(name), "size": common.LoadSizeUnit2F(float64(SpaceReclaimed))})) + return nil + }, nil) + go func() { + _ = taskItem.Execute() + }() + return nil +} + +func (u *ContainerService) LoadResourceLimit() (*dto.ResourceLimit, error) { + cpuCounts, err := cpu.Counts(true) + if err != nil { + return nil, fmt.Errorf("load cpu limit failed, err: %v", err) + } + memoryInfo, err := mem.VirtualMemory() + if err != nil { + return nil, fmt.Errorf("load memory limit failed, err: %v", err) + } + + data := dto.ResourceLimit{ + CPU: cpuCounts, + Memory: memoryInfo.Total, + } + return &data, nil +} + +func (u *ContainerService) ContainerCreate(req dto.ContainerOperate, inThread bool) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + ctx := context.Background() + newContainer, _ := client.ContainerInspect(ctx, req.Name) + if newContainer.ContainerJSONBase != nil { + return buserr.New("ErrContainerName") + } + + taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeContainer, req.TaskID, 1) + if err != nil { + global.LOG.Errorf("new task for create container failed, err: %v", err) + return err + } + + taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error { + if !checkImageExist(client, req.Image) || req.ForcePull { + if err := pullImages(taskItem, client, req.Image); err != nil { + if !req.ForcePull { + return err + } + } + } + return nil + }, nil) + + taskItem.AddSubTask(i18n.GetMsgByKey("ContainerImageCheck"), func(t *task.Task) error { + imageInfo, _, err := client.ImageInspectWithRaw(ctx, req.Image) + if err != nil { + return err + } + if len(req.Entrypoint) == 0 { + req.Entrypoint = imageInfo.Config.Entrypoint + } + if len(req.Cmd) == 0 { + req.Cmd = imageInfo.Config.Cmd + } + return nil + }, nil) + + taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error { + config, hostConf, networkConf, err := loadConfigInfo(true, req, nil) + taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err) + if err != nil { + return err + } + con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name) + if err != nil { + taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed")) + _ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true}) + return err + } + err = client.ContainerStart(ctx, con.ID, container.StartOptions{}) + taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err) + if err != nil { + taskItem.Log(i18n.GetMsgByKey("ContainerCreateFailed")) + _ = client.ContainerRemove(ctx, req.Name, container.RemoveOptions{RemoveVolumes: true, Force: true}) + return fmt.Errorf("create successful but start failed, err: %v", err) + } + return nil + }, nil) + + if inThread { + go func() { + if err := taskItem.Execute(); err != nil { + global.LOG.Error(err.Error()) + } + }() + return nil + } + return taskItem.Execute() +} + +func (u *ContainerService) ContainerInfo(req dto.OperationWithName) (*dto.ContainerOperate, error) { + client, err := docker.NewDockerClient() + if err != nil { + return nil, err + } + defer client.Close() + ctx := context.Background() + oldContainer, err := client.ContainerInspect(ctx, req.Name) + if err != nil { + return nil, err + } + + var data dto.ContainerOperate + data.Name = strings.ReplaceAll(oldContainer.Name, "/", "") + data.Image = oldContainer.Config.Image + if oldContainer.NetworkSettings != nil { + for net, val := range oldContainer.NetworkSettings.Networks { + netItem := dto.ContainerNetwork{ + Network: net, + MacAddr: val.MacAddress, + } + if val.IPAMConfig != nil { + if netItem.Network != "bridge" { + netItem.Ipv4 = val.IPAMConfig.IPv4Address + netItem.Ipv6 = val.IPAMConfig.IPv6Address + } + } else { + if netItem.Network != "bridge" { + netItem.Ipv4 = val.IPAddress + } + } + data.Networks = append(data.Networks, netItem) + } + } + + exposePorts, _ := loadPortByInspect(oldContainer.ID, client) + data.ExposedPorts = loadContainerPortForInfo(exposePorts) + data.Hostname = oldContainer.Config.Hostname + data.DNS = oldContainer.HostConfig.DNS + data.DomainName = oldContainer.Config.Domainname + + data.Cmd = oldContainer.Config.Cmd + data.WorkingDir = oldContainer.Config.WorkingDir + data.User = oldContainer.Config.User + data.OpenStdin = oldContainer.Config.OpenStdin + data.Tty = oldContainer.Config.Tty + data.Entrypoint = oldContainer.Config.Entrypoint + data.Env = oldContainer.Config.Env + data.CPUShares = oldContainer.HostConfig.CPUShares + for key, val := range oldContainer.Config.Labels { + data.Labels = append(data.Labels, fmt.Sprintf("%s=%s", key, val)) + } + data.AutoRemove = oldContainer.HostConfig.AutoRemove + data.Privileged = oldContainer.HostConfig.Privileged + data.PublishAllPorts = oldContainer.HostConfig.PublishAllPorts + data.RestartPolicy = string(oldContainer.HostConfig.RestartPolicy.Name) + if oldContainer.HostConfig.NanoCPUs != 0 { + data.NanoCPUs = float64(oldContainer.HostConfig.NanoCPUs) / 1000000000 + } + if oldContainer.HostConfig.Memory != 0 { + data.Memory = float64(oldContainer.HostConfig.Memory) / 1024 / 1024 + } + data.Volumes = loadVolumeBinds(oldContainer.Mounts) + + return &data, nil +} + +func (u *ContainerService) ContainerUpdate(req dto.ContainerOperate) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + ctx := context.Background() + oldContainer, err := client.ContainerInspect(ctx, req.Name) + if err != nil { + return err + } + + taskItem, err := task.NewTaskWithOps(req.Name, task.TaskUpdate, task.TaskScopeContainer, req.TaskID, 1) + if err != nil { + global.LOG.Errorf("new task for create container failed, err: %v", err) + return err + } + go func() { + taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error { + if !checkImageExist(client, req.Image) || req.ForcePull { + if err := pullImages(taskItem, client, req.Image); err != nil { + if !req.ForcePull { + return err + } + return fmt.Errorf("pull image %s failed, err: %v", req.Image, err) + } + } + return nil + }, nil) + + taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", req.Name), func(t *task.Task) error { + err := client.ContainerRemove(ctx, req.Name, container.RemoveOptions{Force: true}) + taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", req.Name), err) + if err != nil { + return err + } + + config, hostConf, networkConf, err := loadConfigInfo(false, req, &oldContainer) + taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerLoadInfo"), err) + if err != nil { + taskItem.Log(i18n.GetMsgByKey("ContainerRecreate")) + reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) + return err + } + + con, err := client.ContainerCreate(ctx, config, hostConf, networkConf, &v1.Platform{}, req.Name) + if err != nil { + taskItem.Log(i18n.GetMsgByKey("ContainerRecreate")) + reCreateAfterUpdate(req.Name, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) + return fmt.Errorf("update container failed, err: %v", err) + } + err = client.ContainerStart(ctx, con.ID, container.StartOptions{}) + taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err) + if err != nil { + return fmt.Errorf("update successful but start failed, err: %v", err) + } + return nil + }, nil) + + if err := taskItem.Execute(); err != nil { + global.LOG.Error(err.Error()) + } + }() + + return nil +} + +func (u *ContainerService) ContainerUpgrade(req dto.ContainerUpgrade) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + ctx := context.Background() + taskItem, err := task.NewTaskWithOps(req.Image, task.TaskUpgrade, task.TaskScopeImage, req.TaskID, 1) + if err != nil { + global.LOG.Errorf("new task for create container failed, err: %v", err) + return err + } + go func() { + taskItem.AddSubTask(i18n.GetWithName("ContainerImagePull", req.Image), func(t *task.Task) error { + taskItem.LogStart(i18n.GetWithName("ContainerImagePull", req.Image)) + if !checkImageExist(client, req.Image) || req.ForcePull { + if err := pullImages(taskItem, client, req.Image); err != nil { + if !req.ForcePull { + return err + } + return fmt.Errorf("pull image %s failed, err: %v", req.Image, err) + } + } + return nil + }, nil) + for _, item := range req.Names { + var oldContainer container.InspectResponse + taskItem.AddSubTask(i18n.GetWithName("ContainerLoadInfo", item), func(t *task.Task) error { + taskItem.Logf("----------------- %s -----------------", item) + oldContainer, err = client.ContainerInspect(ctx, item) + if err != nil { + return err + } + return nil + }, nil) + + taskItem.AddSubTask(i18n.GetWithName("ContainerCreate", item), func(t *task.Task) error { + config := oldContainer.Config + config.Image = req.Image + hostConf := oldContainer.HostConfig + var networkConf network.NetworkingConfig + if oldContainer.NetworkSettings != nil { + for networkKey := range oldContainer.NetworkSettings.Networks { + networkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}} + break + } + } + err := client.ContainerRemove(ctx, item, container.RemoveOptions{Force: true}) + taskItem.LogWithStatus(i18n.GetWithName("ContainerRemoveOld", item), err) + if err != nil { + return err + } + + con, err := client.ContainerCreate(ctx, config, hostConf, &networkConf, &v1.Platform{}, item) + if err != nil { + taskItem.Log(i18n.GetMsgByKey("ContainerRecreate")) + reCreateAfterUpdate(item, client, oldContainer.Config, oldContainer.HostConfig, oldContainer.NetworkSettings) + return fmt.Errorf("upgrade container failed, err: %v", err) + } + err = client.ContainerStart(ctx, con.ID, container.StartOptions{}) + taskItem.LogWithStatus(i18n.GetMsgByKey("ContainerStartCheck"), err) + if err != nil { + return fmt.Errorf("upgrade successful but start failed, err: %v", err) + } + return nil + }, nil) + + } + if err := taskItem.Execute(); err != nil { + global.LOG.Error(err.Error()) + } + }() + + return nil +} + +func (u *ContainerService) ContainerRename(req dto.ContainerRename) error { + ctx := context.Background() + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + + newContainer, _ := client.ContainerInspect(ctx, req.NewName) + if newContainer.ContainerJSONBase != nil { + return buserr.New("ErrContainerName") + } + return client.ContainerRename(ctx, req.Name, req.NewName) +} + +func (u *ContainerService) ContainerCommit(req dto.ContainerCommit) error { + ctx := context.Background() + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + options := container.CommitOptions{ + Reference: req.NewImageName, + Comment: req.Comment, + Author: req.Author, + Changes: nil, + Pause: req.Pause, + Config: nil, + } + + taskItem, err := task.NewTaskWithOps(req.NewImageName, task.TaskCommit, task.TaskScopeContainer, req.TaskID, 1) + if err != nil { + return fmt.Errorf("new task for container commit failed, err: %v", err) + } + + go func() { + taskItem.AddSubTask(i18n.GetWithName("TaskCommit", req.NewImageName), func(t *task.Task) error { + res, err := client.ContainerCommit(ctx, req.ContainerId, options) + if err != nil { + return fmt.Errorf("failed to commit container, err: %v", err) + } + taskItem.Log(res.ID) + return nil + }, nil) + _ = taskItem.Execute() + }() + return nil +} + +func (u *ContainerService) ContainerOperation(req dto.ContainerOperation) error { + var err error + ctx := context.Background() + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + taskItem, err := task.NewTaskWithOps(strings.Join(req.Names, " "), req.Operation, task.TaskScopeContainer, req.TaskID, 1) + if err != nil { + return fmt.Errorf("new task for container commit failed, err: %v", err) + } + + for _, item := range req.Names { + taskItem.AddSubTask(item, func(t *task.Task) error { + switch req.Operation { + case constant.ContainerOpStart: + err = client.ContainerStart(ctx, item, container.StartOptions{}) + case constant.ContainerOpStop: + err = client.ContainerStop(ctx, item, container.StopOptions{}) + case constant.ContainerOpRestart: + err = client.ContainerRestart(ctx, item, container.StopOptions{}) + case constant.ContainerOpKill: + err = client.ContainerKill(ctx, item, "SIGKILL") + case constant.ContainerOpPause: + err = client.ContainerPause(ctx, item) + case constant.ContainerOpUnpause: + err = client.ContainerUnpause(ctx, item) + case constant.ContainerOpRemove: + err = client.ContainerRemove(ctx, item, container.RemoveOptions{RemoveVolumes: true, Force: true}) + } + return err + }, nil) + } + + go func() { + _ = taskItem.Execute() + }() + return err +} + +func (u *ContainerService) ContainerLogClean(req dto.OperationWithName) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + ctx := context.Background() + containerItem, err := client.ContainerInspect(ctx, req.Name) + if err != nil { + return err + } + if err := client.ContainerStop(ctx, containerItem.ID, container.StopOptions{}); err != nil { + return err + } + file, err := os.OpenFile(containerItem.LogPath, os.O_RDWR|os.O_CREATE, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + if err = file.Truncate(0); err != nil { + return err + } + _, _ = file.Seek(0, 0) + + files, _ := filepath.Glob(fmt.Sprintf("%s.*", containerItem.LogPath)) + for _, file := range files { + _ = os.Remove(file) + } + + if err := client.ContainerStart(ctx, containerItem.ID, container.StartOptions{}); err != nil { + return err + } + return nil +} + +func (u *ContainerService) StreamLogs(ctx *gin.Context, params dto.StreamLog) { + messageChan := make(chan string, 1024) + errorChan := make(chan error, 1) + doneChan := make(chan struct{}) + + go func() { + <-ctx.Request.Context().Done() + close(doneChan) + }() + go collectLogs(doneChan, params, messageChan, errorChan) + ctx.Stream(func(w io.Writer) bool { + select { + case msg, ok := <-messageChan: + if !ok { + return false + } + _, err := fmt.Fprintf(w, "data: %s\n\n", msg) + if err != nil { + return false + } + return true + case err := <-errorChan: + if err != nil { + _, _ = fmt.Fprintf(w, "event: error\ndata: %v\n\n", err.Error()) + } + return false + } + }) +} + +func collectLogs(done <-chan struct{}, params dto.StreamLog, messageChan chan<- string, errorChan chan<- error) { + defer close(messageChan) + defer close(errorChan) + var cmdArgs []string + cmdArgs = append(cmdArgs, "logs") + if params.Follow { + cmdArgs = append(cmdArgs, "-f") + } + if params.Tail != "0" { + cmdArgs = append(cmdArgs, "--tail", params.Tail) + } + if params.Since != "all" { + cmdArgs = append(cmdArgs, "--since", params.Since) + } + if params.Container != "" { + cmdArgs = append(cmdArgs, params.Container) + } + + var dockerCmd *exec.Cmd + if params.Type == "compose" { + dockerComposeCmd := common.GetDockerComposeCommand() + var yamlFiles []string + for _, item := range strings.Split(params.Compose, ",") { + if len(item) != 0 { + yamlFiles = append(yamlFiles, "-f", item) + } + } + if dockerComposeCmd == "docker-compose" { + newCmdArgs := append(yamlFiles, cmdArgs...) + dockerCmd = exec.Command(dockerComposeCmd, newCmdArgs...) + } else { + newCmdArgs := append(append([]string{"compose"}, yamlFiles...), cmdArgs...) + dockerCmd = exec.Command("docker", newCmdArgs...) + } + } else { + dockerCmd = exec.Command("docker", cmdArgs...) + } + + dockerCmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} + + stdout, err := dockerCmd.StdoutPipe() + if err != nil { + errorChan <- fmt.Errorf("failed to get stdout pipe: %v", err) + return + } + + dockerCmd.Stderr = dockerCmd.Stdout + + if err = dockerCmd.Start(); err != nil { + errorChan <- fmt.Errorf("failed to start docker logs command: %v", err) + return + } + + defer func() { + if dockerCmd.Process != nil { + if pgid, err := syscall.Getpgid(dockerCmd.Process.Pid); err == nil { + _ = syscall.Kill(-pgid, syscall.SIGKILL) + } + _ = dockerCmd.Process.Kill() + _ = dockerCmd.Wait() + } + }() + + reader := bufio.NewReader(stdout) + + processKilled := false + go func() { + <-done + if !processKilled && dockerCmd.Process != nil { + processKilled = true + if pgid, err := syscall.Getpgid(dockerCmd.Process.Pid); err == nil { + _ = syscall.Kill(-pgid, syscall.SIGKILL) + } + _ = dockerCmd.Process.Kill() + } + }() + + for { + line, err := reader.ReadString('\n') + if err != nil { + if err == io.EOF { + if len(line) > 0 { + line = strings.TrimSuffix(line, "\n") + select { + case messageChan <- line: + case <-done: + return + } + } + break + } + errorChan <- fmt.Errorf("reader error: %v", err) + return + } + line = strings.TrimSuffix(line, "\n") + select { + case messageChan <- line: + case <-done: + return + } + } + _ = dockerCmd.Wait() +} + +func (u *ContainerService) DownloadContainerLogs(containerType, container, since, tail string, c *gin.Context) error { + if cmd.CheckIllegal(container, since, tail) { + return buserr.New("ErrCmdIllegal") + } + commandArg := []string{"logs", container} + dockerCommand := global.CONF.DockerConfig.Command + if containerType == "compose" { + var yamlFiles []string + for _, item := range strings.Split(container, ",") { + if len(item) != 0 { + yamlFiles = append(yamlFiles, "-f", item) + } + } + if dockerCommand == "docker-compose" { + commandArg = append(yamlFiles, "logs") + } else { + commandArg = append(append([]string{"compose"}, yamlFiles...), "logs") + } + } + + if tail != "0" { + commandArg = append(commandArg, "--tail") + commandArg = append(commandArg, tail) + } + if since != "all" { + commandArg = append(commandArg, "--since") + commandArg = append(commandArg, since) + } + var dockerCmd *exec.Cmd + if containerType == "compose" && dockerCommand == "docker-compose" { + dockerCmd = exec.Command("docker-compose", commandArg...) + } else { + dockerCmd = exec.Command("docker", commandArg...) + } + stdout, err := dockerCmd.StdoutPipe() + if err != nil { + _ = dockerCmd.Process.Signal(syscall.SIGTERM) + return err + } + dockerCmd.Stderr = dockerCmd.Stdout + if err := dockerCmd.Start(); err != nil { + _ = dockerCmd.Process.Signal(syscall.SIGTERM) + return err + } + + tempFile, err := os.CreateTemp("", "cmd_output_*.txt") + if err != nil { + return err + } + defer tempFile.Close() + defer func() { + if err := os.Remove(tempFile.Name()); err != nil { + global.LOG.Errorf("os.Remove() failed: %v", err) + } + }() + errCh := make(chan error) + go func() { + scanner := bufio.NewScanner(stdout) + var ansiRegex = re.GetRegex(re.AnsiEscapePattern) + for scanner.Scan() { + line := scanner.Text() + cleanLine := ansiRegex.ReplaceAllString(line, "") + if _, err := tempFile.WriteString(cleanLine + "\n"); err != nil { + errCh <- err + return + } + } + if err := scanner.Err(); err != nil { + errCh <- err + return + } + errCh <- nil + }() + select { + case err := <-errCh: + if err != nil { + global.LOG.Errorf("Error: %v", err) + } + case <-time.After(3 * time.Second): + global.LOG.Errorf("Timeout reached") + } + info, _ := tempFile.Stat() + + c.Header("Content-Length", strconv.FormatInt(info.Size(), 10)) + c.Header("Content-Disposition", "attachment; filename*=utf-8''"+url.PathEscape(info.Name())) + http.ServeContent(c.Writer, c.Request, info.Name(), info.ModTime(), tempFile) + return nil +} + +func (u *ContainerService) ContainerStats(id string) (*dto.ContainerStats, error) { + client, err := docker.NewDockerClient() + if err != nil { + return nil, err + } + defer client.Close() + res, err := client.ContainerStats(context.TODO(), id, false) + if err != nil { + return nil, err + } + defer res.Body.Close() + + body, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + var stats *container.StatsResponse + if err := json.Unmarshal(body, &stats); err != nil { + return nil, err + } + var data dto.ContainerStats + data.CPUPercent = calculateCPUPercentUnix(stats) + data.IORead, data.IOWrite = calculateBlockIO(stats.BlkioStats) + data.Memory = float64(stats.MemoryStats.Usage) / 1024 / 1024 + if cache, ok := stats.MemoryStats.Stats["cache"]; ok { + data.Cache = float64(cache) / 1024 / 1024 + } + data.NetworkRX, data.NetworkTX = calculateNetwork(stats.Networks) + data.ShotTime = stats.Read + return &data, nil +} + +func (u *ContainerService) LoadUsers(req dto.OperationWithName) []string { + var users []string + std, err := cmd.RunDefaultWithStdoutBashCf("docker exec %s cat /etc/passwd", req.Name) + if err != nil { + return users + } + lines := strings.Split(string(std), "\n") + for _, line := range lines { + if strings.Contains(line, ":") { + users = append(users, strings.Split(line, ":")[0]) + } + } + return users +} + +func stringsToMap(list []string) map[string]string { + var labelMap = make(map[string]string) + for _, label := range list { + if strings.Contains(label, "=") { + sps := strings.SplitN(label, "=", 2) + labelMap[sps[0]] = sps[1] + } + } + return labelMap +} +func stringsToMap2(list []string) map[string]*string { + var labelMap = make(map[string]*string) + for _, label := range list { + if strings.Contains(label, "=") { + sps := strings.SplitN(label, "=", 2) + labelMap[sps[0]] = &sps[1] + } + } + return labelMap +} + +func calculateCPUPercentUnix(stats *container.StatsResponse) float64 { + cpuPercent := 0.0 + cpuDelta := float64(stats.CPUStats.CPUUsage.TotalUsage) - float64(stats.PreCPUStats.CPUUsage.TotalUsage) + systemDelta := float64(stats.CPUStats.SystemUsage) - float64(stats.PreCPUStats.SystemUsage) + + if systemDelta > 0.0 && cpuDelta > 0.0 { + cpuPercent = (cpuDelta / systemDelta) * 100.0 + if len(stats.CPUStats.CPUUsage.PercpuUsage) != 0 { + cpuPercent = cpuPercent * float64(len(stats.CPUStats.CPUUsage.PercpuUsage)) + } + } + return cpuPercent +} +func calculateMemPercentUnix(memStats container.MemoryStats) float64 { + memPercent := 0.0 + memUsage := calculateMemUsageUnixNoCache(memStats) + memLimit := float64(memStats.Limit) + if memUsage > 0.0 && memLimit > 0.0 { + memPercent = (float64(memUsage) / float64(memLimit)) * 100.0 + } + return memPercent +} +func calculateMemUsageUnixNoCache(mem container.MemoryStats) uint64 { + if v, isCgroup1 := mem.Stats["total_inactive_file"]; isCgroup1 && v < mem.Usage { + return mem.Usage - v + } + if v := mem.Stats["inactive_file"]; v < mem.Usage { + return mem.Usage - v + } + return mem.Usage +} +func calculateBlockIO(blkio container.BlkioStats) (blkRead float64, blkWrite float64) { + for _, bioEntry := range blkio.IoServiceBytesRecursive { + switch strings.ToLower(bioEntry.Op) { + case "read": + blkRead = (blkRead + float64(bioEntry.Value)) / 1024 / 1024 + case "write": + blkWrite = (blkWrite + float64(bioEntry.Value)) / 1024 / 1024 + } + } + return +} +func calculateNetwork(network map[string]container.NetworkStats) (float64, float64) { + var rx, tx float64 + + for _, v := range network { + rx += float64(v.RxBytes) / 1024 + tx += float64(v.TxBytes) / 1024 + } + return rx, tx +} + +func checkImageExist(client *client.Client, imageItem string) bool { + if client == nil { + var err error + client, err = docker.NewDockerClient() + if err != nil { + return false + } + } + images, err := client.ImageList(context.Background(), image.ListOptions{}) + if err != nil { + return false + } + + for _, img := range images { + for _, tag := range img.RepoTags { + if tag == imageItem || tag == imageItem+":latest" { + return true + } + } + } + return false +} + +func checkImageLike(client *client.Client, imageName string) bool { + if client == nil { + var err error + client, err = docker.NewDockerClient() + if err != nil { + return false + } + } + images, err := client.ImageList(context.Background(), image.ListOptions{}) + if err != nil { + return false + } + + for _, img := range images { + for _, tag := range img.RepoTags { + parts := strings.Split(tag, "/") + imageNameWithTag := parts[len(parts)-1] + + if imageNameWithTag == imageName { + return true + } + } + } + return false +} + +func pullImages(task *task.Task, client *client.Client, imageName string) error { + dockerCli := docker.NewClientWithExist(client) + options := image.PullOptions{} + repos, _ := imageRepoRepo.List() + if len(repos) != 0 { + for _, repo := range repos { + if strings.HasPrefix(imageName, repo.DownloadUrl) && repo.Auth { + authConfig := registry.AuthConfig{ + Username: repo.Username, + Password: repo.Password, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return err + } + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + options.RegistryAuth = authStr + } + } + } else { + hasAuth, authStr := loadAuthInfo(imageName) + if hasAuth { + options.RegistryAuth = authStr + } + } + return dockerCli.PullImageWithProcessAndOptions(task, imageName, options) +} + +func loadCpuAndMem(client *client.Client, containerItem string) dto.ContainerListStats { + data := dto.ContainerListStats{ + ContainerID: containerItem, + } + res, err := client.ContainerStats(context.Background(), containerItem, false) + if err != nil { + return data + } + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + if err != nil { + return data + } + var stats *container.StatsResponse + if err := json.Unmarshal(body, &stats); err != nil { + return data + } + + data.CPUTotalUsage = stats.CPUStats.CPUUsage.TotalUsage - stats.PreCPUStats.CPUUsage.TotalUsage + data.SystemUsage = stats.CPUStats.SystemUsage - stats.PreCPUStats.SystemUsage + data.CPUPercent = calculateCPUPercentUnix(stats) + data.PercpuUsage = len(stats.CPUStats.CPUUsage.PercpuUsage) + + data.MemoryCache = stats.MemoryStats.Stats["cache"] + data.MemoryUsage = calculateMemUsageUnixNoCache(stats.MemoryStats) + data.MemoryLimit = stats.MemoryStats.Limit + + data.MemoryPercent = calculateMemPercentUnix(stats.MemoryStats) + return data +} + +func checkPortStats(ports []dto.PortHelper) (nat.PortMap, error) { + portMap := make(nat.PortMap) + if len(ports) == 0 { + return portMap, nil + } + for _, port := range ports { + if strings.Contains(port.ContainerPort, "-") { + if !strings.Contains(port.HostPort, "-") { + return portMap, buserr.New("ErrPortRules") + } + hostStart, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[0]) + hostEnd, _ := strconv.Atoi(strings.Split(port.HostPort, "-")[1]) + containerStart, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[0]) + containerEnd, _ := strconv.Atoi(strings.Split(port.ContainerPort, "-")[1]) + if (hostEnd-hostStart) <= 0 || (containerEnd-containerStart) <= 0 { + return portMap, buserr.New("ErrPortRules") + } + if (containerEnd - containerStart) != (hostEnd - hostStart) { + return portMap, buserr.New("ErrPortRules") + } + for i := 0; i <= hostEnd-hostStart; i++ { + bindItem := nat.PortBinding{HostPort: strconv.Itoa(hostStart + i), HostIP: port.HostIP} + portMap[nat.Port(fmt.Sprintf("%d/%s", containerStart+i, port.Protocol))] = []nat.PortBinding{bindItem} + } + for i := hostStart; i <= hostEnd; i++ { + if common.ScanPortWithIP(port.HostIP, i) { + return portMap, buserr.WithDetail("ErrPortInUsed", i, nil) + } + } + } else { + portItem := 0 + if strings.Contains(port.HostPort, "-") { + portItem, _ = strconv.Atoi(strings.Split(port.HostPort, "-")[0]) + } else { + portItem, _ = strconv.Atoi(port.HostPort) + } + if common.ScanPortWithIP(port.HostIP, portItem) { + return portMap, buserr.WithDetail("ErrPortInUsed", portItem, nil) + } + bindItem := nat.PortBinding{HostPort: strconv.Itoa(portItem), HostIP: port.HostIP} + portMap[nat.Port(fmt.Sprintf("%s/%s", port.ContainerPort, port.Protocol))] = []nat.PortBinding{bindItem} + } + } + return portMap, nil +} + +func loadConfigInfo(isCreate bool, req dto.ContainerOperate, oldContainer *container.InspectResponse) (*container.Config, *container.HostConfig, *network.NetworkingConfig, error) { + var config container.Config + var hostConf container.HostConfig + if !isCreate { + config = *oldContainer.Config + hostConf = *oldContainer.HostConfig + } + var networkConf network.NetworkingConfig + + portMap, err := checkPortStats(req.ExposedPorts) + if err != nil { + return nil, nil, nil, err + } + exposed := make(nat.PortSet) + for port := range portMap { + exposed[port] = struct{}{} + } + config.Image = req.Image + config.Cmd = req.Cmd + config.Entrypoint = req.Entrypoint + config.Env = req.Env + config.Labels = stringsToMap(req.Labels) + config.ExposedPorts = exposed + config.OpenStdin = req.OpenStdin + config.Tty = req.Tty + config.Hostname = req.Hostname + config.Domainname = req.DomainName + config.User = req.User + config.WorkingDir = req.WorkingDir + + if len(req.Networks) != 0 { + networkConf.EndpointsConfig = make(map[string]*network.EndpointSettings) + for _, item := range req.Networks { + switch item.Network { + case "host", "none", "bridge": + hostConf.NetworkMode = container.NetworkMode(item.Network) + } + if item.Ipv4 != "" || item.Ipv6 != "" { + networkConf.EndpointsConfig[item.Network] = &network.EndpointSettings{ + IPAMConfig: &network.EndpointIPAMConfig{ + IPv4Address: item.Ipv4, + IPv6Address: item.Ipv6, + }, MacAddress: item.MacAddr} + } else { + networkConf.EndpointsConfig[item.Network] = &network.EndpointSettings{} + } + } + } else { + return nil, nil, nil, fmt.Errorf("please set up the network") + } + + hostConf.Privileged = req.Privileged + hostConf.AutoRemove = req.AutoRemove + hostConf.CPUShares = req.CPUShares + hostConf.PublishAllPorts = req.PublishAllPorts + hostConf.RestartPolicy = container.RestartPolicy{Name: container.RestartPolicyMode(req.RestartPolicy)} + if req.RestartPolicy == "on-failure" { + hostConf.RestartPolicy.MaximumRetryCount = 5 + } + hostConf.NanoCPUs = int64(req.NanoCPUs * 1000000000) + hostConf.Memory = int64(req.Memory * 1024 * 1024) + hostConf.MemorySwap = 0 + hostConf.PortBindings = portMap + hostConf.Binds = []string{} + hostConf.Mounts = []mount.Mount{} + hostConf.DNS = req.DNS + config.Volumes = make(map[string]struct{}) + for _, volume := range req.Volumes { + item := mount.Mount{ + Type: mount.Type(volume.Type), + Source: volume.SourceDir, + Target: volume.ContainerDir, + ReadOnly: volume.Mode == "ro", + } + if volume.Type == "bind" { + item.BindOptions = &mount.BindOptions{ + Propagation: mount.Propagation(volume.Shared), + } + } + hostConf.Mounts = append(hostConf.Mounts, item) + config.Volumes[volume.ContainerDir] = struct{}{} + } + return &config, &hostConf, &networkConf, nil +} + +func reCreateAfterUpdate(name string, client *client.Client, config *container.Config, hostConf *container.HostConfig, networkConf *container.NetworkSettings) { + ctx := context.Background() + + var oldNetworkConf network.NetworkingConfig + if networkConf != nil { + for networkKey := range networkConf.Networks { + oldNetworkConf.EndpointsConfig = map[string]*network.EndpointSettings{networkKey: {}} + break + } + } + + oldContainer, err := client.ContainerCreate(ctx, config, hostConf, &oldNetworkConf, &v1.Platform{}, name) + if err != nil { + global.LOG.Errorf("recreate after container update failed, err: %v", err) + return + } + if err := client.ContainerStart(ctx, oldContainer.ID, container.StartOptions{}); err != nil { + global.LOG.Errorf("restart after container update failed, err: %v", err) + } + global.LOG.Info("recreate after container update successful") +} + +func loadVolumeBinds(binds []container.MountPoint) []dto.VolumeHelper { + var datas []dto.VolumeHelper + for _, bind := range binds { + var volumeItem dto.VolumeHelper + volumeItem.Type = string(bind.Type) + if bind.Type == "volume" { + volumeItem.SourceDir = bind.Name + } else { + volumeItem.SourceDir = bind.Source + } + volumeItem.ContainerDir = bind.Destination + volumeItem.Mode = "ro" + if bind.RW { + volumeItem.Mode = "rw" + } + volumeItem.Shared = string(bind.Propagation) + datas = append(datas, volumeItem) + } + return datas +} + +func loadPortByInspect(id string, client *client.Client) ([]container.Port, error) { + containerItem, err := client.ContainerInspect(context.Background(), id) + if err != nil { + return nil, err + } + var itemPorts []container.Port + for key, val := range containerItem.ContainerJSONBase.HostConfig.PortBindings { + if !strings.Contains(string(key), "/") { + continue + } + item := strings.Split(string(key), "/") + itemPort, _ := strconv.ParseUint(item[0], 10, 16) + + for _, itemVal := range val { + publicPort, _ := strconv.ParseUint(itemVal.HostPort, 10, 16) + itemPorts = append(itemPorts, container.Port{PrivatePort: uint16(itemPort), Type: item[1], PublicPort: uint16(publicPort), IP: itemVal.HostIP}) + } + } + return itemPorts, nil +} +func transPortToStr(ports []container.Port) []string { + var ( + ipv4Ports []container.Port + ipv6Ports []container.Port + ) + for _, port := range ports { + if strings.Contains(port.IP, ":") { + ipv6Ports = append(ipv6Ports, port) + } else { + ipv4Ports = append(ipv4Ports, port) + } + } + list1 := simplifyPort(ipv4Ports) + list2 := simplifyPort(ipv6Ports) + return append(list1, list2...) +} +func simplifyPort(ports []container.Port) []string { + var datas []string + if len(ports) == 0 { + return datas + } + if len(ports) == 1 { + ip := "" + if len(ports[0].IP) != 0 { + ip = ports[0].IP + ":" + } + itemPortStr := fmt.Sprintf("%s%v/%s", ip, ports[0].PrivatePort, ports[0].Type) + if ports[0].PublicPort != 0 { + itemPortStr = fmt.Sprintf("%s%v->%v/%s", ip, ports[0].PublicPort, ports[0].PrivatePort, ports[0].Type) + } + datas = append(datas, itemPortStr) + return datas + } + + sort.Slice(ports, func(i, j int) bool { + return ports[i].PrivatePort < ports[j].PrivatePort + }) + start := ports[0] + + for i := 1; i < len(ports); i++ { + if ports[i].PrivatePort != ports[i-1].PrivatePort+1 || ports[i].IP != ports[i-1].IP || ports[i].PublicPort != ports[i-1].PublicPort+1 || ports[i].Type != ports[i-1].Type { + if ports[i-1].PrivatePort == start.PrivatePort { + itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type) + if start.PublicPort != 0 { + itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type) + } + if len(start.IP) == 0 { + itemPortStr = strings.TrimPrefix(itemPortStr, ":") + } + datas = append(datas, itemPortStr) + } else { + itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i-1].PrivatePort, start.Type) + if start.PublicPort != 0 { + itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i-1].PublicPort, start.PrivatePort, ports[i-1].PrivatePort, start.Type) + } + if len(start.IP) == 0 { + itemPortStr = strings.TrimPrefix(itemPortStr, ":") + } + datas = append(datas, itemPortStr) + } + start = ports[i] + } + if i == len(ports)-1 { + if ports[i].PrivatePort == start.PrivatePort { + itemPortStr := fmt.Sprintf("%s:%v/%s", start.IP, start.PrivatePort, start.Type) + if start.PublicPort != 0 { + itemPortStr = fmt.Sprintf("%s:%v->%v/%s", start.IP, start.PublicPort, start.PrivatePort, start.Type) + } + if len(start.IP) == 0 { + itemPortStr = strings.TrimPrefix(itemPortStr, ":") + } + datas = append(datas, itemPortStr) + } else { + itemPortStr := fmt.Sprintf("%s:%v-%v/%s", start.IP, start.PrivatePort, ports[i].PrivatePort, start.Type) + if start.PublicPort != 0 { + itemPortStr = fmt.Sprintf("%s:%v-%v->%v-%v/%s", start.IP, start.PublicPort, ports[i].PublicPort, start.PrivatePort, ports[i].PrivatePort, start.Type) + } + if len(start.IP) == 0 { + itemPortStr = strings.TrimPrefix(itemPortStr, ":") + } + datas = append(datas, itemPortStr) + } + } + } + return datas +} + +func loadComposeCount(client *client.Client) int { + options := container.ListOptions{All: true} + options.Filters = filters.NewArgs() + options.Filters.Add("label", composeProjectLabel) + list, err := client.ContainerList(context.Background(), options) + if err != nil { + return 0 + } + composeCreatedByLocal, _ := composeRepo.ListRecord() + composeMap := make(map[string]struct{}) + for _, container := range list { + if name, ok := container.Labels[composeProjectLabel]; ok { + if _, has := composeMap[name]; !has { + composeMap[name] = struct{}{} + } + } + } + for _, compose := range composeCreatedByLocal { + if _, has := composeMap[compose.Name]; !has { + composeMap[compose.Name] = struct{}{} + } + } + + return len(composeMap) +} +func loadContainerPortForInfo(itemPorts []container.Port) []dto.PortHelper { + var exposedPorts []dto.PortHelper + samePortMap := make(map[string]dto.PortHelper) + ports := transPortToStr(itemPorts) + for _, item := range ports { + itemStr := strings.Split(item, "->") + if len(itemStr) < 2 { + continue + } + var itemPort dto.PortHelper + lastIndex := strings.LastIndex(itemStr[0], ":") + if lastIndex == -1 { + itemPort.HostPort = itemStr[0] + } else { + itemPort.HostIP = itemStr[0][0:lastIndex] + itemPort.HostPort = itemStr[0][lastIndex+1:] + } + itemContainer := strings.Split(itemStr[1], "/") + if len(itemContainer) != 2 { + continue + } + itemPort.ContainerPort = itemContainer[0] + itemPort.Protocol = itemContainer[1] + keyItem := fmt.Sprintf("%s->%s/%s", itemPort.HostPort, itemPort.ContainerPort, itemPort.Protocol) + if val, ok := samePortMap[keyItem]; ok { + val.HostIP = "" + samePortMap[keyItem] = val + } else { + samePortMap[keyItem] = itemPort + } + } + for _, val := range samePortMap { + exposedPorts = append(exposedPorts, val) + } + return exposedPorts +} + +func searchWithFilter(req dto.PageContainer, containers []container.Summary) []dto.ContainerInfo { + var ( + records []dto.ContainerInfo + list []container.Summary + ) + + if req.ExcludeAppStore { + for _, item := range containers { + if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" { + continue + } + list = append(list, item) + } + } else { + list = containers + } + + if len(req.Name) != 0 { + length, count := len(list), 0 + for count < length { + if !strings.Contains(list[count].Names[0][1:], req.Name) && !strings.Contains(list[count].Image, req.Name) { + list = append(list[:count], list[(count+1):]...) + length-- + } else { + count++ + } + } + } + if req.State != "all" { + length, count := len(list), 0 + for count < length { + if list[count].State != req.State { + list = append(list[:count], list[(count+1):]...) + length-- + } else { + count++ + } + } + } + switch req.OrderBy { + case "name": + sort.Slice(list, func(i, j int) bool { + if req.Order == constant.OrderAsc { + return list[i].Names[0][1:] < list[j].Names[0][1:] + } + return list[i].Names[0][1:] > list[j].Names[0][1:] + }) + default: + sort.Slice(list, func(i, j int) bool { + if req.Order == constant.OrderAsc { + return list[i].Created < list[j].Created + } + return list[i].Created > list[j].Created + }) + } + for _, item := range list { + IsFromCompose := false + if _, ok := item.Labels[composeProjectLabel]; ok { + IsFromCompose = true + } + IsFromApp := false + if created, ok := item.Labels[composeCreatedBy]; ok && created == "Apps" { + IsFromApp = true + } + exposePorts := transPortToStr(item.Ports) + info := dto.ContainerInfo{ + ContainerID: item.ID, + CreateTime: time.Unix(item.Created, 0).Format(constant.DateTimeLayout), + Name: item.Names[0][1:], + Ports: exposePorts, + ImageId: strings.Split(item.ImageID, ":")[1], + ImageName: item.Image, + State: item.State, + RunTime: item.Status, + IsFromApp: IsFromApp, + IsFromCompose: IsFromCompose, + } + if item.NetworkSettings != nil && len(item.NetworkSettings.Networks) > 0 { + networks := make([]string, 0, len(item.NetworkSettings.Networks)) + for key := range item.NetworkSettings.Networks { + networks = append(networks, item.NetworkSettings.Networks[key].IPAddress) + } + sort.Strings(networks) + info.Network = networks + } + records = append(records, info) + } + descriptions, _ := settingRepo.GetDescriptionList(repo.WithByType("container")) + for i := 0; i < len(records); i++ { + for _, desc := range descriptions { + if desc.ID == records[i].ContainerID { + records[i].Description = desc.Description + records[i].IsPinned = desc.IsPinned + } + } + } + sort.Slice(records, func(i, j int) bool { + return records[i].IsPinned && !records[j].IsPinned + }) + return records +} diff --git a/agent/app/service/container_compose.go b/agent/app/service/container_compose.go new file mode 100644 index 0000000..f4f5cfa --- /dev/null +++ b/agent/app/service/container_compose.go @@ -0,0 +1,437 @@ +package service + +import ( + "bufio" + "context" + "errors" + "fmt" + "os" + "path" + "path/filepath" + "sort" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" +) + +const composeProjectLabel = "com.docker.compose.project" +const composeConfigLabel = "com.docker.compose.project.config_files" +const composeWorkdirLabel = "com.docker.compose.project.working_dir" +const composeCreatedBy = "createdBy" + +func (u *ContainerService) PageCompose(req dto.SearchWithPage) (int64, interface{}, error) { + var ( + records []dto.ComposeInfo + BackDatas []dto.ComposeInfo + ) + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + defer client.Close() + + options := container.ListOptions{All: true} + options.Filters = filters.NewArgs() + options.Filters.Add("label", composeProjectLabel) + + list, err := client.ContainerList(context.Background(), options) + if err != nil { + return 0, nil, err + } + + composeCreatedByLocal, _ := composeRepo.ListRecord() + composeLocalMap := make(map[string]dto.ComposeInfo) + for _, localItem := range composeCreatedByLocal { + composeItemLocal := dto.ComposeInfo{ + ContainerCount: 0, + CreatedAt: localItem.CreatedAt.Format(constant.DateTimeLayout), + ConfigFile: localItem.Path, + Workdir: strings.TrimSuffix(localItem.Path, "/docker-compose.yml"), + } + composeItemLocal.CreatedBy = "1Panel" + composeItemLocal.Path = localItem.Path + composeLocalMap[localItem.Name] = composeItemLocal + } + + composeMap := make(map[string]dto.ComposeInfo) + for _, container := range list { + if name, ok := container.Labels[composeProjectLabel]; ok { + containerItem := dto.ComposeContainer{ + ContainerID: container.ID, + Name: container.Names[0][1:], + State: container.State, + CreateTime: time.Unix(container.Created, 0).Format(constant.DateTimeLayout), + Ports: transPortToStr(container.Ports), + } + if compose, has := composeMap[name]; has { + compose.ContainerCount++ + if strings.ToLower(containerItem.State) == "running" { + compose.RunningCount++ + } + compose.Containers = append(compose.Containers, containerItem) + composeMap[name] = compose + } else { + config := container.Labels[composeConfigLabel] + workdir := container.Labels[composeWorkdirLabel] + composeItem := dto.ComposeInfo{ + ContainerCount: 1, + CreatedAt: time.Unix(container.Created, 0).Format(constant.DateTimeLayout), + ConfigFile: config, + Workdir: workdir, + Containers: []dto.ComposeContainer{containerItem}, + } + if strings.ToLower(containerItem.State) == "running" { + composeItem.RunningCount = 1 + } + createdBy, ok := container.Labels[composeCreatedBy] + if ok { + composeItem.CreatedBy = createdBy + } + if len(config) != 0 && len(workdir) != 0 && strings.Contains(config, workdir) { + composeItem.Path = config + } else { + composeItem.Path = workdir + } + for i := 0; i < len(composeCreatedByLocal); i++ { + if composeCreatedByLocal[i].Name == name { + composeItem.CreatedBy = "1Panel" + composeCreatedByLocal = append(composeCreatedByLocal[:i], composeCreatedByLocal[i+1:]...) + break + } + } + composeMap[name] = composeItem + } + } + } + + mergedMap := make(map[string]dto.ComposeInfo) + for key, localItem := range composeLocalMap { + mergedMap[key] = localItem + } + for key, item := range composeMap { + if existingItem, exists := mergedMap[key]; exists { + if item.ContainerCount > 0 { + if existingItem.ContainerCount <= 0 { + mergedMap[key] = item + } + } + } else { + mergedMap[key] = item + } + } + + for key, value := range mergedMap { + value.Name = key + records = append(records, value) + } + if len(req.Info) != 0 { + length, count := len(records), 0 + for count < length { + if !strings.Contains(records[count].Name, req.Info) { + records = append(records[:count], records[(count+1):]...) + length-- + } else { + count++ + } + } + } + sort.Slice(records, func(i, j int) bool { + return records[i].CreatedAt > records[j].CreatedAt + }) + total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + BackDatas = make([]dto.ComposeInfo, 0) + } else { + if end >= total { + end = total + } + BackDatas = records[start:end] + } + listItem := loadEnv(BackDatas) + return int64(total), listItem, nil +} + +func (u *ContainerService) TestCompose(req dto.ComposeCreate) (bool, error) { + if cmd.CheckIllegal(req.Path) { + return false, buserr.New("ErrCmdIllegal") + } + composeItem, _ := composeRepo.GetRecord(repo.WithByName(req.Name)) + if composeItem.ID != 0 { + return false, buserr.New("ErrRecordExist") + } + if err := u.loadPath(&req); err != nil { + return false, err + } + if err := newComposeEnv(req.Path, req.Env); err != nil { + return false, err + } + cmd := getComposeCmd(req.Path, "config") + stdout, err := cmd.CombinedOutput() + if err != nil { + return false, fmt.Errorf("docker-compose config failed, std: %s, err: %v", string(stdout), err) + } + return true, nil +} + +func (u *ContainerService) CreateCompose(req dto.ComposeCreate) error { + if cmd.CheckIllegal(req.Name, req.Path) { + return buserr.New("ErrCmdIllegal") + } + if err := u.loadPath(&req); err != nil { + return err + } + if req.From == "path" { + req.Name = path.Base(path.Dir(req.Path)) + } + taskItem, err := task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeCompose, req.TaskID, 1) + if err != nil { + return fmt.Errorf("new task for image build failed, err: %v", err) + } + if err := newComposeEnv(req.Path, req.Env); err != nil { + return err + } + pullImages := true + if req.PullImage != nil { + pullImages = *req.PullImage + } + go func() { + taskItem.AddSubTask(i18n.GetMsgByKey("ComposeCreate"), func(t *task.Task) error { + err := compose.UpWithTask(req.Path, t, pullImages) + t.LogWithStatus(i18n.GetMsgByKey("ComposeCreate"), err) + if err != nil { + _, _ = compose.Down(req.Path) + return err + } + _ = composeRepo.CreateRecord(&model.Compose{Name: strings.ToLower(req.Name), Path: req.Path}) + return nil + }, nil) + _ = taskItem.Execute() + }() + + return nil +} + +func (u *ContainerService) ComposeOperation(req dto.ComposeOperation) error { + if len(req.Path) == 0 && req.Operation == "delete" { + _ = composeRepo.DeleteRecord(repo.WithByName(req.Name)) + return nil + } + if cmd.CheckIllegal(req.Path, req.Operation) { + return buserr.New("ErrCmdIllegal") + } + if req.Operation == "delete" { + if err := removeContainerForCompose(req.Name, req.Path); err != nil && !req.Force { + return err + } + if req.WithFile { + for _, item := range strings.Split(req.Path, ",") { + if len(item) != 0 { + _ = os.RemoveAll(path.Dir(item)) + } + } + } + _ = composeRepo.DeleteRecord(repo.WithByName(req.Name)) + return nil + } + if req.Operation == "up" { + if stdout, err := compose.Up(req.Path); err != nil { + return fmt.Errorf("docker-compose up failed, std: %s, err: %v", stdout, err) + } + } else { + if stdout, err := compose.Operate(req.Path, req.Operation); err != nil { + return fmt.Errorf("docker-compose %s failed, std: %s, err: %v", req.Operation, stdout, err) + } + } + return nil +} + +func (u *ContainerService) ComposeUpdate(req dto.ComposeUpdate) error { + if cmd.CheckIllegal(req.Name, req.Path) { + return buserr.New("ErrCmdIllegal") + } + oldFile, err := os.ReadFile(req.DetailPath) + if err != nil { + return fmt.Errorf("load file with path %s failed, %v", req.DetailPath, err) + } + file, err := os.OpenFile(req.DetailPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(req.Content) + write.Flush() + + global.LOG.Infof("docker-compose.yml %s has been replaced, now start to docker-compose restart", req.DetailPath) + if err := newComposeEnv(req.DetailPath, req.Env); err != nil { + return err + } + + if stdout, err := compose.Up(req.Path); err != nil { + global.LOG.Errorf("update failed when handle compose up, std: %s, err: %s, now try to recreate the old compose file", stdout, err) + if err := recreateCompose(string(oldFile), req.Path); err != nil { + return fmt.Errorf("update failed and recreate old compose file also failed, err: %v", err) + } + return fmt.Errorf("update failed when handle compose up, std: %v, err: %s", stdout, err) + } + + return nil +} + +func (u *ContainerService) ComposeLogClean(req dto.ComposeLogClean) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + + options := container.ListOptions{All: true} + options.Filters = filters.NewArgs() + options.Filters.Add("label", composeProjectLabel) + + list, err := client.ContainerList(context.Background(), options) + if err != nil { + return err + } + ctx := context.Background() + for _, item := range list { + if name, ok := item.Labels[composeProjectLabel]; ok { + if name != req.Name { + continue + } + containerItem, err := client.ContainerInspect(ctx, item.ID) + if err != nil { + return err + } + if err := client.ContainerStop(ctx, containerItem.ID, container.StopOptions{}); err != nil { + return err + } + file, err := os.OpenFile(containerItem.LogPath, os.O_RDWR|os.O_CREATE, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + if err = file.Truncate(0); err != nil { + return err + } + _, _ = file.Seek(0, 0) + + files, _ := filepath.Glob(fmt.Sprintf("%s.*", containerItem.LogPath)) + for _, file := range files { + _ = os.Remove(file) + } + } + } + return u.ComposeOperation(dto.ComposeOperation{ + Name: req.Name, + Path: req.Path, + Operation: "restart", + }) +} + +func (u *ContainerService) loadPath(req *dto.ComposeCreate) error { + if req.From == "template" || req.From == "edit" { + dir := fmt.Sprintf("%s/docker/compose/%s", global.Dir.DataDir, req.Name) + if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + } + + path := fmt.Sprintf("%s/docker-compose.yml", dir) + file, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(string(req.File)) + write.Flush() + req.Path = path + } + return nil +} + +func removeContainerForCompose(composeName, composePath string) error { + if stdout, err := compose.Operate(composePath, "down"); err != nil { + return errors.New(stdout) + } + var options container.ListOptions + options.All = true + options.Filters = filters.NewArgs() + options.Filters.Add("label", "com.docker.compose.project="+composeName) + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + ctx := context.Background() + containers, err := client.ContainerList(ctx, options) + if err != nil { + return err + } + for _, c := range containers { + _ = client.ContainerRemove(ctx, c.ID, container.RemoveOptions{RemoveVolumes: true, Force: true}) + } + return nil +} + +func recreateCompose(content, path string) error { + file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(content) + write.Flush() + + if stdout, err := compose.Up(path); err != nil { + return errors.New(string(stdout)) + } + return nil +} + +func loadEnv(list []dto.ComposeInfo) []dto.ComposeInfo { + for i := 0; i < len(list); i++ { + tmpPath := list[i].Path + if strings.Contains(list[i].Path, ",") { + tmpPath = strings.Split(list[i].Path, ",")[0] + } + envFilePath := path.Join(path.Dir(tmpPath), ".env") + file, err := os.ReadFile(envFilePath) + if err != nil { + continue + } + list[i].Env = string(file) + } + return list +} + +func newComposeEnv(pathItem string, env string) error { + envFilePath := path.Join(path.Dir(pathItem), ".env") + file, err := os.OpenFile(envFilePath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm) + if err != nil { + global.LOG.Errorf("failed to create env file: %v", err) + return err + } + defer file.Close() + if _, err := file.WriteString(env); err != nil { + global.LOG.Errorf("failed to write env to file: %v", err) + return err + } + return nil +} diff --git a/agent/app/service/container_network.go b/agent/app/service/container_network.go new file mode 100644 index 0000000..2c07225 --- /dev/null +++ b/agent/app/service/container_network.go @@ -0,0 +1,174 @@ +package service + +import ( + "context" + "fmt" + "sort" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/docker/docker/api/types/network" +) + +func (u *ContainerService) PageNetwork(req dto.SearchWithPage) (int64, interface{}, error) { + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + defer client.Close() + list, err := client.NetworkList(context.TODO(), network.ListOptions{}) + if err != nil { + return 0, nil, err + } + if len(req.Info) != 0 { + length, count := len(list), 0 + for count < length { + if !strings.Contains(list[count].Name, req.Info) { + list = append(list[:count], list[(count+1):]...) + length-- + } else { + count++ + } + } + } + var ( + data []dto.Network + records []network.Inspect + ) + sort.Slice(list, func(i, j int) bool { + return list[i].Created.Before(list[j].Created) + }) + total, start, end := len(list), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + records = make([]network.Inspect, 0) + } else { + if end >= total { + end = total + } + records = list[start:end] + } + + for _, item := range records { + tag := make([]string, 0) + for key, val := range item.Labels { + tag = append(tag, fmt.Sprintf("%s=%s", key, val)) + } + var ipam network.IPAMConfig + if len(item.IPAM.Config) > 0 { + ipam = item.IPAM.Config[0] + } + data = append(data, dto.Network{ + ID: item.ID, + CreatedAt: item.Created, + Name: item.Name, + Driver: item.Driver, + IPAMDriver: item.IPAM.Driver, + Subnet: ipam.Subnet, + Gateway: ipam.Gateway, + Attachable: item.Attachable, + Labels: tag, + }) + } + + return int64(total), data, nil +} + +func (u *ContainerService) ListNetwork() ([]dto.Options, error) { + client, err := docker.NewDockerClient() + if err != nil { + return nil, err + } + defer client.Close() + list, err := client.NetworkList(context.TODO(), network.ListOptions{}) + if err != nil { + return nil, err + } + var datas []dto.Options + for _, item := range list { + datas = append(datas, dto.Options{Option: item.Name}) + } + sort.Slice(datas, func(i, j int) bool { + return datas[i].Option < datas[j].Option + }) + return datas, nil +} + +func (u *ContainerService) DeleteNetwork(req dto.BatchDelete) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + for _, id := range req.Names { + if err := client.NetworkRemove(context.TODO(), id); err != nil { + if strings.Contains(err.Error(), "has active endpoints") { + return buserr.WithDetail("ErrInUsed", id, nil) + } + return err + } + } + return nil +} +func (u *ContainerService) CreateNetwork(req dto.NetworkCreate) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + var ipams []network.IPAMConfig + + if req.Ipv4 { + var itemIpam network.IPAMConfig + if len(req.AuxAddress) != 0 { + itemIpam.AuxAddress = make(map[string]string) + } + if len(req.Subnet) != 0 { + itemIpam.Subnet = req.Subnet + } + if len(req.Gateway) != 0 { + itemIpam.Gateway = req.Gateway + } + if len(req.IPRange) != 0 { + itemIpam.IPRange = req.IPRange + } + for _, addr := range req.AuxAddress { + itemIpam.AuxAddress[addr.Key] = addr.Value + } + ipams = append(ipams, itemIpam) + } + if req.Ipv6 { + var itemIpam network.IPAMConfig + if len(req.AuxAddress) != 0 { + itemIpam.AuxAddress = make(map[string]string) + } + if len(req.SubnetV6) != 0 { + itemIpam.Subnet = req.SubnetV6 + } + if len(req.GatewayV6) != 0 { + itemIpam.Gateway = req.GatewayV6 + } + if len(req.IPRangeV6) != 0 { + itemIpam.IPRange = req.IPRangeV6 + } + for _, addr := range req.AuxAddressV6 { + itemIpam.AuxAddress[addr.Key] = addr.Value + } + ipams = append(ipams, itemIpam) + } + + options := network.CreateOptions{ + EnableIPv6: &req.Ipv6, + Driver: req.Driver, + Options: stringsToMap(req.Options), + Labels: stringsToMap(req.Labels), + } + if len(ipams) != 0 { + options.IPAM = &network.IPAM{Config: ipams} + } + if _, err := client.NetworkCreate(context.TODO(), req.Name, options); err != nil { + return err + } + return nil +} diff --git a/agent/app/service/container_volume.go b/agent/app/service/container_volume.go new file mode 100644 index 0000000..5df9442 --- /dev/null +++ b/agent/app/service/container_volume.go @@ -0,0 +1,145 @@ +package service + +import ( + "context" + "sort" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/volume" +) + +func (u *ContainerService) PageVolume(req dto.SearchWithPage) (int64, interface{}, error) { + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + defer client.Close() + list, err := client.VolumeList(context.TODO(), volume.ListOptions{}) + if err != nil { + return 0, nil, err + } + if len(req.Info) != 0 { + length, count := len(list.Volumes), 0 + for count < length { + if !strings.Contains(list.Volumes[count].Name, req.Info) { + list.Volumes = append(list.Volumes[:count], list.Volumes[(count+1):]...) + length-- + } else { + count++ + } + } + } + var ( + data []dto.Volume + records []*volume.Volume + ) + sort.Slice(list.Volumes, func(i, j int) bool { + return list.Volumes[i].CreatedAt > list.Volumes[j].CreatedAt + }) + total, start, end := len(list.Volumes), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + records = make([]*volume.Volume, 0) + } else { + if end >= total { + end = total + } + records = list.Volumes[start:end] + } + + nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + for _, item := range records { + var volume dto.Volume + volume.Driver = item.Driver + volume.Mountpoint = item.Mountpoint + volume.Name = item.Name + for key, val := range item.Labels { + volume.Labels = append(volume.Labels, dto.VolumeOption{Key: key, Value: val}) + } + for key, val := range item.Options { + volume.Options = append(volume.Options, dto.VolumeOption{Key: key, Value: val}) + } + sort.Slice(volume.Options, func(i, j int) bool { + return volume.Options[i].Key < volume.Options[j].Key + }) + if strings.Contains(item.CreatedAt, "Z") { + volume.CreatedAt, _ = time.ParseInLocation("2006-01-02T15:04:05Z", item.CreatedAt, nyc) + } else if strings.Contains(item.CreatedAt, "+") { + volume.CreatedAt, _ = time.ParseInLocation("2006-01-02T15:04:05+08:00", item.CreatedAt, nyc) + } else { + volume.CreatedAt, _ = time.ParseInLocation("2006-01-02T15:04:05", item.CreatedAt, nyc) + } + data = append(data, volume) + } + + return int64(total), data, nil +} +func (u *ContainerService) ListVolume() ([]dto.Options, error) { + client, err := docker.NewDockerClient() + if err != nil { + return nil, err + } + defer client.Close() + list, err := client.VolumeList(context.TODO(), volume.ListOptions{}) + if err != nil { + return nil, err + } + var datas []dto.Options + for _, item := range list.Volumes { + datas = append(datas, dto.Options{ + Option: item.Name, + }) + } + sort.Slice(datas, func(i, j int) bool { + return datas[i].Option < datas[j].Option + }) + return datas, nil +} +func (u *ContainerService) DeleteVolume(req dto.BatchDelete) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + for _, id := range req.Names { + if err := client.VolumeRemove(context.TODO(), id, true); err != nil { + if strings.Contains(err.Error(), "volume is in use") { + return buserr.WithDetail("ErrInUsed", id, nil) + } + return err + } + } + return nil +} +func (u *ContainerService) CreateVolume(req dto.VolumeCreate) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + arg := filters.NewArgs() + arg.Add("name", req.Name) + vos, _ := client.VolumeList(context.TODO(), volume.ListOptions{Filters: arg}) + if len(vos.Volumes) != 0 { + for _, v := range vos.Volumes { + if v.Name == req.Name { + return buserr.New("ErrRecordExist") + } + } + } + options := volume.CreateOptions{ + Name: req.Name, + Driver: req.Driver, + DriverOpts: stringsToMap(req.Options), + Labels: stringsToMap(req.Labels), + } + if _, err := client.VolumeCreate(context.TODO(), options); err != nil { + return err + } + return nil +} diff --git a/agent/app/service/cronjob.go b/agent/app/service/cronjob.go new file mode 100644 index 0000000..4cce0a8 --- /dev/null +++ b/agent/app/service/cronjob.go @@ -0,0 +1,802 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/jinzhu/copier" + "github.com/pkg/errors" + "github.com/robfig/cron/v3" +) + +type CronjobService struct{} + +type ICronjobService interface { + SearchWithPage(search dto.PageCronjob) (int64, interface{}, error) + SearchRecords(search dto.SearchRecord) (int64, interface{}, error) + Create(cronjobDto dto.CronjobOperate) error + LoadNextHandle(spec string) ([]string, error) + HandleOnce(id uint) error + Update(id uint, req dto.CronjobOperate) error + UpdateStatus(id uint, status string) error + UpdateGroup(req dto.ChangeGroup) error + Delete(req dto.CronjobBatchDelete) error + StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error) + CleanRecord(req dto.CronjobClean) error + HandleStop(id uint) error + + Export(req dto.OperateByIDs) (string, error) + Import(req []dto.CronjobTrans) error + LoadScriptOptions() []dto.ScriptOptions + + LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error) + LoadRecordLog(req dto.OperateByID) string +} + +func NewICronjobService() ICronjobService { + return &CronjobService{} +} + +func (u *CronjobService) SearchWithPage(search dto.PageCronjob) (int64, interface{}, error) { + total, cronjobs, err := cronjobRepo.Page(search.Page, + search.PageSize, + repo.WithByGroups(search.GroupIDs), + repo.WithByLikeName(search.Info), + repo.WithOrderRuleBy(search.OrderBy, search.Order)) + var dtoCronjobs []dto.CronjobInfo + for _, cronjob := range cronjobs { + var item dto.CronjobInfo + if err := copier.Copy(&item, &cronjob); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + record, _ := cronjobRepo.RecordFirst(cronjob.ID) + if record.ID != 0 { + item.LastRecordStatus = record.Status + item.LastRecordTime = record.StartTime.Format(constant.DateTimeLayout) + } else { + item.LastRecordTime = "-" + } + item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID) + alertBase := dto.AlertBase{ + AlertType: cronjob.Type, + EntryID: cronjob.ID, + } + alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable)) + if alertInfo.SendCount != 0 { + item.AlertCount = alertInfo.SendCount + } else { + item.AlertCount = 0 + } + if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 { + _ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule) + } + dtoCronjobs = append(dtoCronjobs, item) + } + return total, dtoCronjobs, err +} + +func (u *CronjobService) LoadInfo(req dto.OperateByID) (*dto.CronjobOperate, error) { + cronjob, err := cronjobRepo.Get(repo.WithByID(req.ID)) + var item dto.CronjobOperate + if err := copier.Copy(&item, &cronjob); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + alertBase := dto.AlertBase{ + AlertType: cronjob.Type, + EntryID: cronjob.ID, + } + alertInfo, _ := alertRepo.Get(alertRepo.WithByType(alertBase.AlertType), alertRepo.WithByProject(strconv.Itoa(int(alertBase.EntryID))), repo.WithByStatus(constant.AlertEnable)) + item.AlertMethod = alertInfo.Method + if alertInfo.SendCount != 0 { + item.AlertCount = alertInfo.SendCount + } else { + item.AlertCount = 0 + } + if cronjob.Type == "snapshot" && len(cronjob.SnapshotRule) != 0 { + _ = json.Unmarshal([]byte(cronjob.SnapshotRule), &item.SnapshotRule) + } + if cronjob.Type == "cleanLog" { + _ = json.Unmarshal([]byte(cronjob.Config), &item.CleanLogConfig) + } + return &item, err +} + +func (u *CronjobService) Export(req dto.OperateByIDs) (string, error) { + cronjobs, err := cronjobRepo.List(repo.WithByIDs(req.IDs)) + if err != nil { + return "", err + } + var data []dto.CronjobTrans + for _, cronjob := range cronjobs { + item := dto.CronjobTrans{ + Name: cronjob.Name, + Type: cronjob.Type, + GroupID: cronjob.GroupID, + SpecCustom: cronjob.SpecCustom, + Spec: cronjob.Spec, + Executor: cronjob.Executor, + ScriptMode: cronjob.ScriptMode, + Script: cronjob.Script, + Command: cronjob.Command, + ContainerName: cronjob.ContainerName, + User: cronjob.User, + URL: cronjob.URL, + DBType: cronjob.DBType, + ExclusionRules: cronjob.ExclusionRules, + IsDir: cronjob.IsDir, + SourceDir: cronjob.SourceDir, + RetainCopies: cronjob.RetainCopies, + RetryTimes: cronjob.RetryTimes, + Timeout: cronjob.Timeout, + IgnoreErr: cronjob.IgnoreErr, + Secret: cronjob.Secret, + Args: cronjob.Args, + } + switch cronjob.Type { + case "app": + if cronjob.AppID == "all" { + break + } + apps := loadAppsForJob(cronjob) + for _, app := range apps { + item.Apps = append(item.Apps, dto.TransHelper{Name: app.App.Key, DetailName: app.Name}) + } + case "website", "cutWebsiteLog": + if cronjob.Website == "all" { + break + } + websites := loadWebsForJob(cronjob) + for _, website := range websites { + item.Websites = append(item.Websites, website.Alias) + } + case "database": + if cronjob.DBName == "all" { + break + } + databases := loadDbsForJob(cronjob) + for _, db := range databases { + item.DBNames = append(item.DBNames, dto.TransHelper{Name: db.Database, DetailName: db.Name}) + } + case "shell": + if cronjob.ScriptMode == "library" { + script, err := scriptRepo.Get(repo.WithByID(cronjob.ScriptID)) + if err != nil { + return "", err + } + item.ScriptName = script.Name + } + case "snapshot": + if len(cronjob.SnapshotRule) == 0 { + break + } + var snapRule dto.SnapshotRule + if err := json.Unmarshal([]byte(cronjob.SnapshotRule), &snapRule); err != nil { + return "", err + } + item.SnapshotRule.WithImage = snapRule.WithImage + if len(snapRule.IgnoreAppIDs) != 0 { + ignoreApps, _ := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(snapRule.IgnoreAppIDs)) + for _, app := range ignoreApps { + item.SnapshotRule.IgnoreApps = append(item.SnapshotRule.IgnoreApps, dto.TransHelper{Name: app.App.Key, DetailName: app.Name}) + } + } + } + item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(cronjob.SourceAccountIDs, cronjob.DownloadAccountID) + alertInfo, _ := alertRepo.Get(alertRepo.WithByType(cronjob.Type), alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), repo.WithByStatus(constant.AlertEnable)) + if alertInfo.SendCount != 0 { + item.AlertCount = alertInfo.SendCount + item.AlertTitle = alertInfo.Title + item.AlertMethod = alertInfo.Method + } else { + item.AlertCount = 0 + } + data = append(data, item) + } + itemJson, err := json.Marshal(&data) + if err != nil { + return "", err + } + return string(itemJson), nil +} + +func (u *CronjobService) Import(req []dto.CronjobTrans) error { + for _, item := range req { + cronjobItem, _ := cronjobRepo.Get(repo.WithByName(item.Name)) + if cronjobItem.ID != 0 { + continue + } + cronjob := model.Cronjob{ + Name: item.Name, + Type: item.Type, + GroupID: item.GroupID, + SpecCustom: item.SpecCustom, + Spec: item.Spec, + Executor: item.Executor, + ScriptMode: item.ScriptMode, + Command: item.Command, + ContainerName: item.ContainerName, + User: item.User, + URL: item.URL, + DBType: item.DBType, + ExclusionRules: item.ExclusionRules, + IsDir: item.IsDir, + RetainCopies: item.RetainCopies, + RetryTimes: item.RetryTimes, + Timeout: item.Timeout, + IgnoreErr: item.IgnoreErr, + Secret: item.Secret, + Args: item.Args, + } + hasNotFound := false + switch item.Type { + case "app": + if len(item.Apps) == 0 { + cronjob.AppID = "all" + break + } + var appIDs []string + for _, app := range item.Apps { + appItem, err := appInstallRepo.LoadInstallAppByKeyAndName(app.Name, app.DetailName) + if err != nil { + hasNotFound = true + continue + } + appIDs = append(appIDs, fmt.Sprintf("%v", appItem.ID)) + } + cronjob.AppID = strings.Join(appIDs, ",") + case "website", "cutWebsiteLog": + if len(item.Websites) == 0 { + cronjob.Website = "all" + break + } + var webIDs []string + for _, web := range item.Websites { + webItem, err := websiteRepo.GetFirst(websiteRepo.WithAlias(web)) + if err != nil { + hasNotFound = true + continue + } + webIDs = append(webIDs, fmt.Sprintf("%v", webItem.ID)) + } + cronjob.Website = strings.Join(webIDs, ",") + case "database": + if len(item.DBNames) == 0 { + cronjob.DBName = "all" + break + } + var dbIDs []string + if strings.Contains(cronjob.DBType, "postgresql") { + for _, db := range item.DBNames { + dbItem, err := postgresqlRepo.Get(postgresqlRepo.WithByPostgresqlName(db.Name), repo.WithByName(db.DetailName)) + if err != nil { + hasNotFound = true + continue + } + dbIDs = append(dbIDs, fmt.Sprintf("%v", dbItem.ID)) + } + } else { + for _, db := range item.DBNames { + dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(db.Name), repo.WithByName(db.DetailName)) + if err != nil { + hasNotFound = true + continue + } + dbIDs = append(dbIDs, fmt.Sprintf("%v", dbItem.ID)) + } + } + cronjob.DBName = strings.Join(dbIDs, ",") + case "shell": + if len(item.ContainerName) != 0 { + cronjob.Script = item.Script + client, err := docker.NewDockerClient() + if err != nil { + hasNotFound = true + break + } + defer client.Close() + if _, err := client.ContainerStats(context.Background(), item.ContainerName, false); err != nil { + hasNotFound = true + break + } + } + switch item.ScriptMode { + case "library": + library, _ := scriptRepo.Get(repo.WithByName(item.ScriptName)) + if library.ID == 0 { + hasNotFound = true + break + } + cronjob.ScriptID = library.ID + case "select": + if _, err := os.Stat(item.Script); err != nil { + hasNotFound = true + break + } + cronjob.Script = item.Script + case "input": + cronjob.Script = item.Script + } + case "directory": + if item.IsDir { + if _, err := os.Stat(item.SourceDir); err != nil { + hasNotFound = true + break + } + cronjob.SourceDir = item.SourceDir + } else { + fileList := strings.Split(item.SourceDir, ",") + var newFiles []string + for _, item := range fileList { + if len(item) == 0 { + continue + } + if _, err := os.Stat(item); err != nil { + hasNotFound = true + continue + } + newFiles = append(newFiles, item) + } + cronjob.SourceDir = strings.Join(newFiles, ",") + } + case "snapshot": + if len(item.SnapshotRule.IgnoreApps) == 0 && !item.SnapshotRule.WithImage { + break + } + var itemRules dto.SnapshotRule + itemRules.WithImage = item.SnapshotRule.WithImage + for _, app := range item.SnapshotRule.IgnoreApps { + appItem, err := appInstallRepo.LoadInstallAppByKeyAndName(app.Name, app.DetailName) + if err != nil { + hasNotFound = true + continue + } + itemRules.IgnoreAppIDs = append(itemRules.IgnoreAppIDs, appItem.ID) + } + itemRulesStr, _ := json.Marshal(itemRules) + cronjob.SnapshotRule = string(itemRulesStr) + } + var acIDs []string + for _, ac := range item.SourceAccounts { + backup, err := backupRepo.Get(repo.WithByName(ac)) + if err != nil { + hasNotFound = true + continue + } + if ac == item.DownloadAccount { + cronjob.DownloadAccountID = backup.ID + } + acIDs = append(acIDs, fmt.Sprintf("%v", backup.ID)) + } + cronjob.SourceAccountIDs = strings.Join(acIDs, ",") + if hasNotFound { + cronjob.Status = constant.StatusPending + } else { + cronjob.Status = constant.StatusDisable + } + _ = cronjobRepo.Create(&cronjob) + if item.AlertCount != 0 && item.AlertTitle != "" && item.AlertMethod != "" { + createAlert := dto.AlertCreate{ + Title: item.AlertTitle, + SendCount: item.AlertCount, + Method: item.AlertMethod, + Type: cronjob.Type, + Project: strconv.Itoa(int(cronjob.ID)), + Status: constant.AlertEnable, + } + _ = NewIAlertService().CreateAlert(createAlert) + } + } + return nil +} + +func (u *CronjobService) LoadScriptOptions() []dto.ScriptOptions { + scripts, err := scriptRepo.List() + if err != nil { + return nil + } + lang, _ := settingRepo.GetValueByKey("Language") + if len(lang) == 0 { + lang = "en" + } + var options []dto.ScriptOptions + for _, script := range scripts { + var item dto.ScriptOptions + item.ID = script.ID + var translations = make(map[string]string) + _ = json.Unmarshal([]byte(script.Name), &translations) + if name, ok := translations[lang]; ok { + item.Name = strings.ReplaceAll(name, " ", "_") + } else { + item.Name = strings.ReplaceAll(script.Name, " ", "_") + } + options = append(options, item) + } + return options +} + +func (u *CronjobService) SearchRecords(search dto.SearchRecord) (int64, interface{}, error) { + total, records, err := cronjobRepo.PageRecords( + search.Page, + search.PageSize, + repo.WithByStatus(search.Status), + cronjobRepo.WithByJobID(search.CronjobID), + repo.WithByDate(search.StartTime, search.EndTime)) + var dtoCronjobs []dto.Record + for _, record := range records { + var item dto.Record + if err := copier.Copy(&item, &record); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + item.StartTime = record.StartTime.Format(constant.DateTimeLayout) + dtoCronjobs = append(dtoCronjobs, item) + } + return total, dtoCronjobs, err +} + +func (u *CronjobService) LoadNextHandle(specStr string) ([]string, error) { + spec := cron.NewParser(cron.Minute | cron.Hour | cron.Dom | cron.Month | cron.Dow) + now := time.Now() + var next [5]string + if strings.HasPrefix(specStr, "@every ") { + duration := time.Minute + if strings.HasSuffix(specStr, "s") { + duration = time.Second + } + interval := strings.ReplaceAll(specStr, "@every ", "") + interval = strings.ReplaceAll(interval, "s", "") + interval = strings.ReplaceAll(interval, "m", "") + durationItem, err := strconv.Atoi(interval) + if err != nil { + return nil, err + } + for i := 0; i < 5; i++ { + nextTime := now.Add(time.Duration(durationItem) * duration) + next[i] = nextTime.Format(constant.DateTimeLayout) + now = nextTime + } + return next[:], nil + } + sched, err := spec.Parse(specStr) + if err != nil { + return nil, err + } + for i := 0; i < 5; i++ { + nextTime := sched.Next(now) + next[i] = nextTime.Format(constant.DateTimeLayout) + now = nextTime + } + return next[:], nil +} + +func (u *CronjobService) LoadRecordLog(req dto.OperateByID) string { + record, err := cronjobRepo.GetRecord(repo.WithByID(req.ID)) + if err != nil { + return "" + } + if _, err := os.Stat(record.Records); err != nil { + return "" + } + content, err := os.ReadFile(record.Records) + if err != nil { + return "" + } + return string(content) +} + +func (u *CronjobService) CleanRecord(req dto.CronjobClean) error { + cronjob, err := cronjobRepo.Get(repo.WithByID(req.CronjobID)) + if err != nil { + return err + } + if req.CleanData { + if hasBackup(cronjob.Type) { + accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ",")) + if !req.CleanRemoteData { + for key, val := range accountMap { + if val.accountType != constant.Local { + delete(accountMap, key) + } + } + } + cronjob.RetainCopies = 0 + if len(accountMap) != 0 { + u.removeExpiredBackup(cronjob, accountMap, model.BackupRecord{}) + } + } + } + if req.IsDelete { + records, _ := backupRepo.ListRecord(backupRepo.WithByCronID(cronjob.ID)) + for _, records := range records { + records.CronjobID = 0 + _ = backupRepo.UpdateRecord(&records) + } + } + delRecords, err := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(req.CronjobID))) + if err != nil { + return err + } + for _, del := range delRecords { + _ = os.RemoveAll(del.Records) + } + if err := cronjobRepo.DeleteRecord(cronjobRepo.WithByJobID(int(req.CronjobID))); err != nil { + return err + } + return nil +} + +func (u *CronjobService) HandleOnce(id uint) error { + cronjob, _ := cronjobRepo.Get(repo.WithByID(id)) + if cronjob.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if cronjob.IsExecuting { + return buserr.New("InExecuting") + } + u.HandleJob(&cronjob) + return nil +} + +func (u *CronjobService) Create(req dto.CronjobOperate) error { + cronjob, _ := cronjobRepo.Get(repo.WithByName(req.Name)) + if cronjob.ID != 0 { + return buserr.New("ErrRecordExist") + } + cronjob.Secret = req.Secret + if err := copier.Copy(&cronjob, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if cronjob.Type == "snapshot" { + rule, err := json.Marshal(req.SnapshotRule) + if err != nil { + return err + } + cronjob.SnapshotRule = string(rule) + } + if cronjob.Type == "cutWebsiteLog" { + backupAccount, err := backupRepo.Get(repo.WithByType(constant.Local)) + if backupAccount.ID == 0 { + return fmt.Errorf("load local backup dir failed, err: %v", err) + } + cronjob.DownloadAccountID, cronjob.SourceAccountIDs = backupAccount.ID, fmt.Sprintf("%v", backupAccount.ID) + } + cronjob.Status = constant.StatusEnable + + global.LOG.Infof("create cronjob %s successful, spec: %s", cronjob.Name, cronjob.Spec) + spec := cronjob.Spec + entryIDs, err := u.StartJob(&cronjob, false) + if err != nil { + return err + } + cronjob.Spec = spec + cronjob.EntryIDs = entryIDs + if req.Type == "cleanLog" { + config, _ := json.Marshal(req.CleanLogConfig) + cronjob.Config = string(config) + } + if err := cronjobRepo.Create(&cronjob); err != nil { + return err + } + if req.AlertCount != 0 && req.AlertTitle != "" && req.AlertMethod != "" { + createAlert := dto.AlertCreate{ + Title: req.AlertTitle, + SendCount: req.AlertCount, + Method: req.AlertMethod, + Type: cronjob.Type, + Project: strconv.Itoa(int(cronjob.ID)), + Status: constant.AlertEnable, + } + err := NewIAlertService().CreateAlert(createAlert) + if err != nil { + return err + } + } + return nil +} + +func (u *CronjobService) StartJob(cronjob *model.Cronjob, isUpdate bool) (string, error) { + if len(cronjob.EntryIDs) != 0 && isUpdate { + ids := strings.Split(cronjob.EntryIDs, ",") + for _, id := range ids { + idItem, _ := strconv.Atoi(id) + global.Cron.Remove(cron.EntryID(idItem)) + } + } + specs := strings.Split(cronjob.Spec, "&&") + var ids []string + for _, spec := range specs { + cronjob.Spec = spec + entryID, err := u.AddCronJob(cronjob) + if err != nil { + return "", err + } + ids = append(ids, fmt.Sprintf("%v", entryID)) + } + return strings.Join(ids, ","), nil +} + +func (u *CronjobService) HandleStop(id uint) error { + record, _ := cronjobRepo.GetRecord(repo.WithByID(id)) + if record.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if len(record.TaskID) == 0 { + return nil + } + if cancel, ok := global.TaskCtxMap[record.TaskID]; ok { + cancel() + } + return nil +} + +func (u *CronjobService) Delete(req dto.CronjobBatchDelete) error { + for _, id := range req.IDs { + cronjob, _ := cronjobRepo.Get(repo.WithByID(id)) + if cronjob.ID == 0 { + return errors.New("find cronjob in db failed") + } + _ = os.RemoveAll(path.Join(global.Dir.DataDir, "task/shell", cronjob.Name)) + ids := strings.Split(cronjob.EntryIDs, ",") + for _, id := range ids { + idItem, _ := strconv.Atoi(id) + global.Cron.Remove(cron.EntryID(idItem)) + } + global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs) + if err := u.CleanRecord(dto.CronjobClean{CronjobID: id, CleanData: req.CleanData, CleanRemoteData: req.CleanRemoteData, IsDelete: true}); err != nil { + return err + } + if err := cronjobRepo.Delete(repo.WithByID(id)); err != nil { + return err + } + err := alertRepo.Delete(alertRepo.WithByProject(strconv.Itoa(int(cronjob.ID))), alertRepo.WithByType(cronjob.Type)) + if err != nil { + return err + } + } + + return nil +} + +func (u *CronjobService) Update(id uint, req dto.CronjobOperate) error { + var cronjob model.Cronjob + if err := copier.Copy(&cronjob, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if req.Type == "snapshot" { + itemRule, err := json.Marshal(req.SnapshotRule) + if err != nil { + return err + } + cronjob.SnapshotRule = string(itemRule) + } + cronModel, err := cronjobRepo.Get(repo.WithByID(id)) + if err != nil { + return buserr.New("ErrRecordNotFound") + } + upMap := make(map[string]interface{}) + cronjob.EntryIDs = cronModel.EntryIDs + cronjob.Type = cronModel.Type + spec := cronjob.Spec + if cronModel.Status == constant.StatusEnable { + newEntryIDs, err := u.StartJob(&cronjob, true) + if err != nil { + return err + } + upMap["entry_ids"] = newEntryIDs + } else { + ids := strings.Split(cronjob.EntryIDs, ",") + for _, id := range ids { + idItem, _ := strconv.Atoi(id) + global.Cron.Remove(cron.EntryID(idItem)) + } + } + + if cronModel.Status == constant.StatusPending { + upMap["status"] = constant.StatusEnable + } + upMap["name"] = req.Name + upMap["group_id"] = req.GroupID + upMap["spec_custom"] = req.SpecCustom + upMap["spec"] = spec + upMap["script"] = req.Script + upMap["script_mode"] = req.ScriptMode + upMap["command"] = req.Command + upMap["container_name"] = req.ContainerName + upMap["executor"] = req.Executor + upMap["user"] = req.User + + upMap["script_id"] = req.ScriptID + upMap["app_id"] = req.AppID + upMap["website"] = req.Website + upMap["exclusion_rules"] = req.ExclusionRules + upMap["db_type"] = req.DBType + upMap["db_name"] = req.DBName + upMap["url"] = req.URL + upMap["is_dir"] = req.IsDir + upMap["source_dir"] = req.SourceDir + upMap["snapshot_rule"] = cronjob.SnapshotRule + + upMap["source_account_ids"] = req.SourceAccountIDs + upMap["download_account_id"] = req.DownloadAccountID + upMap["retain_copies"] = req.RetainCopies + upMap["retry_times"] = req.RetryTimes + upMap["timeout"] = req.Timeout + upMap["ignore_err"] = req.IgnoreErr + upMap["secret"] = req.Secret + upMap["args"] = req.Args + err = cronjobRepo.Update(id, upMap) + if err != nil { + return err + } + updateAlert := dto.AlertCreate{ + Title: req.AlertTitle, + SendCount: req.AlertCount, + Method: req.AlertMethod, + Type: cronjob.Type, + Project: strconv.Itoa(int(cronModel.ID)), + } + err = NewIAlertService().ExternalUpdateAlert(updateAlert) + if err != nil { + return err + } + return nil +} + +func (u *CronjobService) UpdateStatus(id uint, status string) error { + cronjob, _ := cronjobRepo.Get(repo.WithByID(id)) + if cronjob.ID == 0 { + return errors.WithMessage(buserr.New("ErrRecordNotFound"), "record not found") + } + var ( + entryIDs string + err error + ) + + if status == constant.StatusEnable { + entryIDs, err = u.StartJob(&cronjob, false) + if err != nil { + return err + } + } else { + ids := strings.Split(cronjob.EntryIDs, ",") + for _, id := range ids { + idItem, _ := strconv.Atoi(id) + global.Cron.Remove(cron.EntryID(idItem)) + } + global.LOG.Infof("stop cronjob entryID: %s", cronjob.EntryIDs) + } + return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"status": status, "entry_ids": entryIDs}) +} + +func (u *CronjobService) UpdateGroup(req dto.ChangeGroup) error { + cronjob, _ := cronjobRepo.Get(repo.WithByID(req.ID)) + if cronjob.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + return cronjobRepo.Update(cronjob.ID, map[string]interface{}{"group_id": req.GroupID}) +} + +func (u *CronjobService) AddCronJob(cronjob *model.Cronjob) (int, error) { + addFunc := func() { + u.HandleJob(cronjob) + } + global.LOG.Infof("add %s job %s successful", cronjob.Type, cronjob.Name) + entryID, err := global.Cron.AddFunc(cronjob.Spec, addFunc) + if err != nil { + return 0, err + } + global.LOG.Infof("start cronjob entryID: %d", entryID) + return int(entryID), nil +} diff --git a/agent/app/service/cronjob_backup.go b/agent/app/service/cronjob_backup.go new file mode 100644 index 0000000..ef3c6f6 --- /dev/null +++ b/agent/app/service/cronjob_backup.go @@ -0,0 +1,620 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/i18n" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +func (u *CronjobService) handleApp(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error { + apps := loadAppsForJob(cronjob) + if len(apps) == 0 { + addSkipTask("App", taskItem) + return nil + } + accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ",")) + if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk { + return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message)) + } + for _, app := range apps { + retry := 0 + taskItem.AddSubTaskWithOps(task.GetTaskName(app.Name, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error { + var record model.BackupRecord + record.Status = constant.StatusSuccess + record.From = "cronjob" + record.Type = "app" + record.CronjobID = cronjob.ID + record.Name = app.App.Key + record.DetailName = app.Name + record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs + backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/app/%s/%s", app.App.Key, app.Name)) + record.FileName = simplifiedFileName(fmt.Sprintf("app_%s_%s.tar.gz", app.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))) + if err := doAppBackup(&app, task, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil { + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } else { + task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error())) + cleanAccountMap(accountMap) + return nil + } + } + + src := path.Join(backupDir, record.FileName) + dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/") + if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil { + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } + task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error())) + cleanAccountMap(accountMap) + return nil + } + record.FileDir = path.Dir(dst) + if err := backupRepo.CreateRecord(&record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return err + } + u.removeExpiredBackup(cronjob, accountMap, record) + cleanAccountMap(accountMap) + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) + } + return nil +} + +func (u *CronjobService) handleWebsite(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error { + webs := loadWebsForJob(cronjob) + if len(webs) == 0 { + addSkipTask("Website", taskItem) + return nil + } + accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ",")) + if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk { + return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message)) + } + for _, web := range webs { + retry := 0 + taskItem.AddSubTaskWithOps(task.GetTaskName(web.Alias, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error { + var record model.BackupRecord + record.Status = constant.StatusSuccess + record.From = "cronjob" + record.Type = "website" + record.CronjobID = cronjob.ID + record.Name = web.Alias + record.DetailName = web.Alias + record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs + backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/website/%s", web.Alias)) + record.FileName = simplifiedFileName(fmt.Sprintf("website_%s_%s.tar.gz", web.Alias, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))) + + if err := doWebsiteBackup(&web, taskItem, backupDir, record.FileName, cronjob.ExclusionRules, cronjob.Secret); err != nil { + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } else { + task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error())) + cleanAccountMap(accountMap) + return nil + } + } + + src := path.Join(backupDir, record.FileName) + dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/") + if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil { + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } + task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error())) + cleanAccountMap(accountMap) + return nil + } + record.FileDir = path.Dir(dst) + if err := backupRepo.CreateRecord(&record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return err + } + u.removeExpiredBackup(cronjob, accountMap, record) + cleanAccountMap(accountMap) + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) + } + return nil +} + +func (u *CronjobService) handleDatabase(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error { + dbs := loadDbsForJob(cronjob) + if len(dbs) == 0 { + addSkipTask("Database", taskItem) + return nil + } + accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ",")) + if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk { + return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message)) + } + for _, dbInfo := range dbs { + retry := 0 + itemName := fmt.Sprintf("%s[%s] - %s", dbInfo.Database, dbInfo.DBType, dbInfo.Name) + taskItem.AddSubTaskWithOps(task.GetTaskName(itemName, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error { + var record model.BackupRecord + record.Status = constant.StatusSuccess + record.From = "cronjob" + record.Type = dbInfo.DBType + record.CronjobID = cronjob.ID + record.Name = dbInfo.Database + record.DetailName = dbInfo.Name + record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs + + backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/database/%s/%s/%s", dbInfo.DBType, record.Name, dbInfo.Name)) + record.FileName = simplifiedFileName(fmt.Sprintf("db_%s_%s.sql.gz", dbInfo.Name, startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5))) + if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" { + if err := doMysqlBackup(dbInfo, backupDir, record.FileName, cronjob.Secret); err != nil { + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } else { + task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error())) + cleanAccountMap(accountMap) + return nil + } + } + } else { + if err := doPostgresqlBackup(dbInfo, backupDir, record.FileName, cronjob.Secret, taskItem); err != nil { + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } else { + task.Log(i18n.GetMsgWithDetail("IgnoreBackupErr", err.Error())) + cleanAccountMap(accountMap) + return nil + } + } + } + + src := path.Join(backupDir, record.FileName) + dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/") + if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil { + if retry < int(cronjob.RetryTimes) || !cronjob.IgnoreErr { + retry++ + return err + } + task.Log(i18n.GetMsgWithDetail("IgnoreUploadErr", err.Error())) + cleanAccountMap(accountMap) + return nil + } + record.FileDir = path.Dir(dst) + if err := backupRepo.CreateRecord(&record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return err + } + u.removeExpiredBackup(cronjob, accountMap, record) + cleanAccountMap(accountMap) + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) + } + return nil +} + +func (u *CronjobService) handleDirectory(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error { + accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ",")) + if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk { + return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message)) + } + taskItem.AddSubTaskWithOps(task.GetTaskName(cronjob.SourceDir, task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error { + fileName := fmt.Sprintf("%s.tar.gz", startTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(2)) + if cronjob.IsDir || len(strings.Split(cronjob.SourceDir, ",")) == 1 { + fileName = loadFileName(cronjob.SourceDir) + } + fileName = simplifiedFileName(fileName) + backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("tmp/%s/%s", cronjob.Type, cronjob.Name)) + + fileOp := files.NewFileOp() + if cronjob.IsDir { + taskItem.Logf("Dir: %s, Excludes: %s", cronjob.SourceDir, cronjob.ExclusionRules) + if err := fileOp.TarGzCompressPro(true, cronjob.SourceDir, path.Join(backupDir, fileName), cronjob.Secret, cronjob.ExclusionRules); err != nil { + return err + } + } else { + taskItem.Logf("Files: %s", cronjob.SourceDir) + fileLists := strings.Split(cronjob.SourceDir, ",") + if err := fileOp.TarGzFilesWithCompressPro(fileLists, path.Join(backupDir, fileName), cronjob.Secret); err != nil { + return err + } + } + var record model.BackupRecord + record.Status = constant.StatusSuccess + record.From = "cronjob" + record.Type = "directory" + record.CronjobID = cronjob.ID + record.Name = cronjob.Name + record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs + + src := path.Join(backupDir, fileName) + dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/") + if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil { + return err + } + record.FileDir = path.Dir(dst) + record.FileName = fileName + if err := backupRepo.CreateRecord(&record); err != nil { + return err + } + u.removeExpiredBackup(cronjob, accountMap, record) + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) + return nil +} + +func (u *CronjobService) handleSystemLog(cronjob model.Cronjob, startTime time.Time, taskItem *task.Task) error { + accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ",")) + if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk { + return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message)) + } + taskItem.AddSubTaskWithOps(task.GetTaskName(i18n.GetMsgByKey("SystemLog"), task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error { + nameItem := startTime.Format(constant.DateTimeSlimLayout) + common.RandStrAndNum(5) + fileName := fmt.Sprintf("system_log_%s.tar.gz", nameItem) + backupDir := path.Join(global.Dir.LocalBackupDir, "tmp/log", nameItem) + if err := handleBackupLogs(taskItem, backupDir, fileName, cronjob.Secret); err != nil { + return err + } + var record model.BackupRecord + record.Status = constant.StatusSuccess + record.From = "cronjob" + record.Type = "log" + record.CronjobID = cronjob.ID + record.Name = cronjob.Name + record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs + + src := path.Join(path.Dir(backupDir), fileName) + dst := strings.TrimPrefix(src, global.Dir.LocalBackupDir+"/tmp/") + if err := uploadWithMap(*task, accountMap, src, dst, cronjob.SourceAccountIDs, cronjob.DownloadAccountID, cronjob.RetryTimes); err != nil { + return err + } + record.FileDir = path.Dir(dst) + record.FileName = fileName + if err := backupRepo.CreateRecord(&record); err != nil { + return err + } + u.removeExpiredBackup(cronjob, accountMap, record) + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) + return nil +} + +func (u *CronjobService) handleSnapshot(cronjob model.Cronjob, jobRecord model.JobRecords, taskItem *task.Task) error { + accountMap := NewBackupClientMap(strings.Split(cronjob.SourceAccountIDs, ",")) + if !accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk { + return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message)) + } + var record model.BackupRecord + record.Status = constant.StatusSuccess + record.From = "cronjob" + record.Type = "snapshot" + record.CronjobID = cronjob.ID + record.Name = cronjob.Name + record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs + record.FileDir = "system_snapshot" + + versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion")) + scope := "core" + if !global.IsMaster { + scope = "agent" + } + + itemData, err := loadSnapWithRule(cronjob) + if err != nil { + return err + } + req := dto.SnapshotCreate{ + Name: fmt.Sprintf("snapshot-1panel-%s-%s-linux-%s-%s", scope, versionItem.Value, loadOs(), jobRecord.StartTime.Format(constant.DateTimeSlimLayout)+common.RandStrAndNum(5)), + Secret: cronjob.Secret, + TaskID: jobRecord.TaskID, + Timeout: cronjob.Timeout, + + SourceAccountIDs: record.SourceAccountIDs, + DownloadAccountID: cronjob.DownloadAccountID, + AppData: itemData.AppData, + PanelData: itemData.PanelData, + BackupData: itemData.BackupData, + WithDockerConf: true, + WithMonitorData: true, + WithLoginLog: true, + WithOperationLog: true, + WithSystemLog: true, + WithTaskLog: true, + IgnoreFiles: strings.Split(cronjob.ExclusionRules, ","), + } + + if err := NewISnapshotService().SnapshotCreate(taskItem, req, jobRecord.ID, cronjob.RetryTimes); err != nil { + return err + } + record.FileName = req.Name + ".tar.gz" + + if err := backupRepo.CreateRecord(&record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return err + } + u.removeExpiredBackup(cronjob, accountMap, record) + return nil +} + +func loadAppsForJob(cronjob model.Cronjob) []model.AppInstall { + var apps []model.AppInstall + if cronjob.AppID == "all" { + apps, _ = appInstallRepo.ListBy(context.Background()) + } else { + appIds := strings.Split(cronjob.AppID, ",") + var idItems []uint + for i := 0; i < len(appIds); i++ { + itemID, _ := strconv.Atoi(appIds[i]) + idItems = append(idItems, uint(itemID)) + } + appItems, _ := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(idItems)) + apps = appItems + } + return apps +} + +type DatabaseHelper struct { + ID uint + DBType string + Database string + Name string + Args []string +} + +func addSkipTask(source string, taskItem *task.Task) { + taskItem.AddSubTask(task.GetTaskName(i18n.GetMsgByKey(source), task.TaskBackup, task.TaskScopeCronjob), func(task *task.Task) error { + taskItem.Log(i18n.GetMsgByKey("NoSuchResource")) + return nil + }, nil) +} + +func loadDbsForJob(cronjob model.Cronjob) []DatabaseHelper { + var dbs []DatabaseHelper + if cronjob.DBName == "all" { + if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" { + databaseService := NewIDatabaseService() + mysqlItems, _ := databaseService.LoadItems(cronjob.DBType) + for _, mysql := range mysqlItems { + dbs = append(dbs, DatabaseHelper{ + ID: mysql.ID, + DBType: cronjob.DBType, + Database: mysql.Database, + Name: mysql.Name, + Args: strings.Split(cronjob.Args, ","), + }) + } + } else { + pgItems, _ := postgresqlRepo.List() + for _, pg := range pgItems { + dbs = append(dbs, DatabaseHelper{ + ID: pg.ID, + DBType: cronjob.DBType, + Database: pg.PostgresqlName, + Name: pg.Name, + Args: strings.Split(cronjob.Args, ","), + }) + } + } + return dbs + } + dbNames := strings.Split(cronjob.DBName, ",") + for _, name := range dbNames { + itemID, _ := strconv.Atoi(name) + if cronjob.DBType == "mysql" || cronjob.DBType == "mariadb" || cronjob.DBType == "mysql-cluster" { + mysqlItem, _ := mysqlRepo.Get(repo.WithByID(uint(itemID))) + dbs = append(dbs, DatabaseHelper{ + ID: mysqlItem.ID, + DBType: cronjob.DBType, + Database: mysqlItem.MysqlName, + Name: mysqlItem.Name, + Args: strings.Split(cronjob.Args, ","), + }) + } else { + pgItem, _ := postgresqlRepo.Get(repo.WithByID(uint(itemID))) + dbs = append(dbs, DatabaseHelper{ + ID: pgItem.ID, + DBType: cronjob.DBType, + Database: pgItem.PostgresqlName, + Name: pgItem.Name, + Args: strings.Split(cronjob.Args, ","), + }) + } + } + return dbs +} + +func loadWebsForJob(cronjob model.Cronjob) []model.Website { + var list []model.Website + if cronjob.Website == "all" { + list, _ = websiteRepo.List() + return list + } + websites := strings.Split(cronjob.Website, ",") + var idItems []uint + for i := 0; i < len(websites); i++ { + itemID, _ := strconv.Atoi(websites[i]) + idItems = append(idItems, uint(itemID)) + } + list, _ = websiteRepo.GetBy(repo.WithByIDs(idItems)) + return list +} + +func handleBackupLogs(taskItem *task.Task, targetDir, fileName string, secret string) error { + fileOp := files.NewFileOp() + websites, err := websiteRepo.List() + if err != nil { + return err + } + if len(websites) != 0 { + webItem := GetOpenrestyDir(SitesRootDir) + for _, website := range websites { + taskItem.Logf("%s Website logs %s...", i18n.GetMsgByKey("TaskBackup"), website.Alias) + dirItem := path.Join(targetDir, "website", website.Alias) + if _, err := os.Stat(dirItem); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(dirItem, os.ModePerm); err != nil { + return err + } + } + itemDir := path.Join(webItem, website.Alias, "log") + logFiles, _ := os.ReadDir(itemDir) + if len(logFiles) != 0 { + for i := 0; i < len(logFiles); i++ { + if !logFiles[i].IsDir() { + _ = fileOp.CopyFile(path.Join(itemDir, logFiles[i].Name()), dirItem) + } + } + } + itemDir2 := path.Join(global.Dir.LocalBackupDir, "tmp/log/website", website.Alias) + logFiles2, _ := os.ReadDir(itemDir2) + if len(logFiles2) != 0 { + for i := 0; i < len(logFiles2); i++ { + if !logFiles2[i].IsDir() { + _ = fileOp.CopyFile(path.Join(itemDir2, logFiles2[i].Name()), dirItem) + } + } + } + } + } + taskItem.Logf("%s Website logs...", i18n.GetMsgByKey("TaskBackup")) + + systemDir := path.Join(targetDir, "system") + if _, err := os.Stat(systemDir); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(systemDir, os.ModePerm); err != nil { + return err + } + } + + systemLogFiles, _ := os.ReadDir(global.Dir.LogDir) + if len(systemLogFiles) != 0 { + for i := 0; i < len(systemLogFiles); i++ { + if !systemLogFiles[i].IsDir() { + _ = fileOp.CopyFile(path.Join(global.Dir.LogDir, systemLogFiles[i].Name()), systemDir) + } + } + } + taskItem.Logf("%s System logs...", i18n.GetMsgByKey("TaskBackup")) + + loginLogFiles, _ := os.ReadDir("/var/log") + loginDir := path.Join(targetDir, "login") + if _, err := os.Stat(loginDir); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(loginDir, os.ModePerm); err != nil { + return err + } + } + if len(loginLogFiles) != 0 { + for i := 0; i < len(loginLogFiles); i++ { + if !loginLogFiles[i].IsDir() && (strings.HasPrefix(loginLogFiles[i].Name(), "secure") || strings.HasPrefix(loginLogFiles[i].Name(), "auth.log")) { + _ = fileOp.CopyFile(path.Join("/var/log", loginLogFiles[i].Name()), loginDir) + } + } + } + taskItem.Logf("%s SSH logs...", i18n.GetMsgByKey("TaskBackup")) + + if err := fileOp.TarGzCompressPro(true, targetDir, path.Join(path.Dir(targetDir), fileName), secret, ""); err != nil { + return err + } + defer func() { + _ = os.RemoveAll(targetDir) + }() + return nil +} + +func loadSnapWithRule(cronjob model.Cronjob) (dto.SnapshotData, error) { + itemData, err := NewISnapshotService().LoadSnapshotData() + if err != nil { + return itemData, err + } + + if len(cronjob.SnapshotRule) == 0 { + return itemData, nil + } + var snapRule dto.SnapshotRule + if err := json.Unmarshal([]byte(cronjob.SnapshotRule), &snapRule); err != nil { + return itemData, err + } + if len(snapRule.IgnoreAppIDs) == 0 && !snapRule.WithImage { + return itemData, nil + } + + var ignoreApps []model.AppInstall + if len(snapRule.IgnoreAppIDs) != 0 { + ignoreApps, _ = appInstallRepo.ListBy(context.Background(), repo.WithByIDs(snapRule.IgnoreAppIDs)) + } + if len(ignoreApps) == 0 && !snapRule.WithImage { + return itemData, nil + } + for i := 0; i < len(itemData.AppData); i++ { + isIgnore := false + for _, ignore := range ignoreApps { + if ignore.App.Key == itemData.AppData[i].Key && ignore.Name == itemData.AppData[i].Name { + isIgnore = true + itemData.AppData[i].IsCheck = false + for j := 0; j < len(itemData.AppData[i].Children); j++ { + if itemData.AppData[i].Children[j].Label == "appData" { + itemData.AppData[i].Children[j].IsCheck = false + } + } + break + } + } + if snapRule.WithImage && !isIgnore { + for j := 0; j < len(itemData.AppData[i].Children); j++ { + if itemData.AppData[i].Children[j].Label == "appImage" { + itemData.AppData[i].Children[j].IsCheck = true + } + } + } + } + return itemData, nil +} + +func loadFileName(src string) string { + dirs := strings.Split(filepath.ToSlash(src), "/") + var keyPart string + if len(dirs) >= 3 { + keyPart = filepath.Join(dirs[len(dirs)-3], dirs[len(dirs)-2], dirs[len(dirs)-1]) + } + cleanName := strings.ReplaceAll(keyPart, string(filepath.Separator), "_") + timestamp := time.Now().Format(constant.DateTimeSlimLayout) + return fmt.Sprintf("%s_%s_%s.tar.gz", cleanName, timestamp, common.RandStrAndNum(2)) +} + +func simplifiedFileName(name string) string { + name = strings.ReplaceAll(name, "/", "_") + name = strings.ReplaceAll(name, ":", "_") + name = strings.ReplaceAll(name, "*", "_") + name = strings.ReplaceAll(name, "?", "_") + name = strings.ReplaceAll(name, "\"", "_") + name = strings.ReplaceAll(name, "<", "_") + name = strings.ReplaceAll(name, ">", "_") + name = strings.ReplaceAll(name, "|", "_") + return name +} + +func cleanAccountMap(accountMap map[string]backupClientHelper) { + for key, val := range accountMap { + val.hasBackup = false + accountMap[key] = val + } +} diff --git a/agent/app/service/cronjob_helper.go b/agent/app/service/cronjob_helper.go new file mode 100644 index 0000000..308e83c --- /dev/null +++ b/agent/app/service/cronjob_helper.go @@ -0,0 +1,499 @@ +package service + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + pathUtils "path" + "path/filepath" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/alert_push" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/ntp" +) + +func (u *CronjobService) HandleJob(cronjob *model.Cronjob) { + cronjobItem, _ := cronjobRepo.Get(repo.WithByID(cronjob.ID)) + if cronjobItem.IsExecuting { + cronjobRepo.AddFailedRecord(cronjob.ID, i18n.GetMsgByKey("InExecuting")) + return + } + record := cronjobRepo.StartRecords(cronjob.ID) + taskItem, err := task.NewTaskWithOps(fmt.Sprintf("cronjob-%s", cronjob.Name), task.TaskHandle, task.TaskScopeCronjob, record.TaskID, cronjob.ID) + if err != nil { + global.LOG.Errorf("new task for exec shell failed, err: %v", err) + return + } + if cronjob.Type == "snapshot" { + go func() { + _ = cronjobRepo.UpdateRecords(record.ID, map[string]interface{}{"records": record.Records}) + if err := taskRepo.Save(context.Background(), taskItem.Task); err != nil { + global.LOG.Errorf("save task for snapshot cronjob failed, err: %v", err) + return + } + if err = u.handleSnapshot(*cronjob, record, taskItem); err != nil { + if len(taskItem.Task.CurrentStep) == 0 { + taskItem.Log(err.Error()) + taskItem.Task.Status = constant.StatusFailed + taskItem.Task.ErrorMsg = err.Error() + taskItem.Task.EndAt = time.Now() + _ = taskRepo.Save(context.Background(), taskItem.Task) + } + cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records) + handleCronJobAlert(cronjob) + return + } + cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) + }() + return + } + if err = u.loadTask(cronjob, &record, taskItem); err != nil { + global.LOG.Debugf("prepare to handle cron job [%s] %s failed, err: %v", cronjob.Type, cronjob.Name, err) + item, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID)) + if len(item.ID) == 0 { + record.TaskID = "" + } + cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records) + handleCronJobAlert(cronjob) + return + } + go func() { + if err := taskItem.Execute(); err != nil { + taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(record.TaskID)) + if len(taskItem.ID) == 0 { + record.TaskID = "" + } + cronjobRepo.EndRecords(record, constant.StatusFailed, err.Error(), record.Records) + handleCronJobAlert(cronjob) + } else { + cronjobRepo.EndRecords(record, constant.StatusSuccess, "", record.Records) + } + }() +} + +func (u *CronjobService) loadTask(cronjob *model.Cronjob, record *model.JobRecords, taskItem *task.Task) error { + var err error + switch cronjob.Type { + case "shell": + if cronjob.ScriptMode == "library" { + scriptItem, _ := scriptRepo.Get(repo.WithByID(cronjob.ScriptID)) + if scriptItem.ID == 0 { + return fmt.Errorf("load script from db failed, err: %v", err) + } + cronjob.Script = scriptItem.Script + cronjob.ScriptMode = "input" + } + if len(cronjob.Script) == 0 { + return fmt.Errorf("the script content is empty and is skipped") + } + u.handleShell(*cronjob, taskItem) + u.removeExpiredLog(*cronjob) + case "curl": + if len(cronjob.URL) == 0 { + return fmt.Errorf("the url is empty and is skipped") + } + u.handleCurl(*cronjob, taskItem) + u.removeExpiredLog(*cronjob) + case "ntp": + u.handleNtpSync(*cronjob, taskItem) + u.removeExpiredLog(*cronjob) + case "cutWebsiteLog": + err = u.handleCutWebsiteLog(cronjob, record.StartTime, taskItem) + case "clean": + u.handleSystemClean(*cronjob, taskItem) + u.removeExpiredLog(*cronjob) + case "website": + err = u.handleWebsite(*cronjob, record.StartTime, taskItem) + case "app": + err = u.handleApp(*cronjob, record.StartTime, taskItem) + case "database": + err = u.handleDatabase(*cronjob, record.StartTime, taskItem) + case "directory": + if len(cronjob.SourceDir) == 0 { + return fmt.Errorf("the source dir is empty and is skipped") + } + err = u.handleDirectory(*cronjob, record.StartTime, taskItem) + case "log": + err = u.handleSystemLog(*cronjob, record.StartTime, taskItem) + case "syncIpGroup": + u.handleSyncIpGroup(*cronjob, taskItem) + case "cleanLog": + u.handleCleanLog(*cronjob, taskItem) + } + return err +} + +func (u *CronjobService) handleShell(cronjob model.Cronjob, taskItem *task.Task) { + cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem), cmd.WithContext(taskItem.TaskCtx)) + + taskItem.AddSubTaskWithOps(i18n.GetWithName("HandleShell", cronjob.Name), func(t *task.Task) error { + if len(cronjob.ContainerName) != 0 { + scriptItem := cronjob.Script + if cronjob.ScriptMode == "select" { + scriptItem = pathUtils.Join("/tmp", pathUtils.Base(cronjob.Script)) + if err := cmdMgr.Run("docker", "cp", cronjob.Script, cronjob.ContainerName+":"+scriptItem); err != nil { + return err + } + } + command := "sh" + if len(cronjob.Command) != 0 { + command = cronjob.Command + } + if len(cronjob.User) != 0 { + return cmdMgr.Run("docker", "exec", "-u", cronjob.User, cronjob.ContainerName, command, "-c", scriptItem) + } + return cmdMgr.Run("docker", "exec", cronjob.ContainerName, command, "-c", scriptItem) + } + if len(cronjob.Executor) == 0 { + cronjob.Executor = "bash" + } + if cronjob.ScriptMode == "input" { + suffix := ".sh" + if strings.HasPrefix(cronjob.Executor, "python") { + suffix = ".py" + } + fileItem := pathUtils.Join(global.Dir.DataDir, "task", "shell", cronjob.Name, cronjob.Name+suffix) + _ = os.MkdirAll(pathUtils.Dir(fileItem), os.ModePerm) + shellFile, err := os.OpenFile(fileItem, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.DirPerm) + if err != nil { + return err + } + defer shellFile.Close() + if _, err := shellFile.WriteString(cronjob.Script); err != nil { + return err + } + if len(cronjob.User) == 0 { + return cmdMgr.Run(cronjob.Executor, fileItem) + } + return cmdMgr.Run("sudo", "-u", cronjob.User, cronjob.Executor, fileItem) + } + if len(cronjob.User) == 0 { + return cmdMgr.Run(cronjob.Executor, cronjob.Script) + } + if err := cmdMgr.Run("sudo", "-u", cronjob.User, cronjob.Executor, cronjob.Script); err != nil { + return err + } + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) +} + +func (u *CronjobService) handleCurl(cronjob model.Cronjob, taskItem *task.Task) { + urls := strings.Split(cronjob.URL, ",") + for _, url := range urls { + if len(strings.TrimSpace(url)) == 0 { + continue + } + taskItem.AddSubTaskWithOps(i18n.GetWithName("HandleCurl", url), func(t *task.Task) error { + taskItem.LogStart(i18n.GetWithName("HandleCurl", url)) + cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem)) + return cmdMgr.Run("curl", url) + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) + } +} + +func (u *CronjobService) handleNtpSync(cronjob model.Cronjob, taskItem *task.Task) { + taskItem.AddSubTaskWithOps(i18n.GetMsgByKey("HandleNtpSync"), func(t *task.Task) error { + ntpServer, err := settingRepo.Get(settingRepo.WithByKey("NtpSite")) + if err != nil { + return err + } + taskItem.Logf("ntp server: %s", ntpServer.Value) + ntime, err := ntp.GetRemoteTime(ntpServer.Value) + if err != nil { + return err + } + if err := ntp.UpdateSystemTime(ntime.Format(constant.DateTimeLayout)); err != nil { + return err + } + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) +} + +func (u *CronjobService) handleCleanLog(cronjob model.Cronjob, taskItem *task.Task) { + taskItem.AddSubTaskWithOps(i18n.GetWithName("CleanLog", cronjob.Name), func(t *task.Task) error { + config := GetCleanLogConfig(cronjob) + for _, scope := range config.Scopes { + switch scope { + case "website": + websites, _ := websiteRepo.List() + for _, website := range websites { + curStr := i18n.GetWithName("CleanLogByName", website.PrimaryDomain) + t.LogStart(curStr) + accessLogPath := GetSitePath(website, SiteAccessLog) + if err := os.Truncate(accessLogPath, 0); err != nil { + t.LogFailedWithErr(curStr, err) + continue + } + errLogPath := GetSitePath(website, SiteErrorLog) + if err := os.Truncate(errLogPath, 0); err != nil { + t.LogFailedWithErr(curStr, err) + continue + } + t.LogSuccess(curStr) + } + appInstall, _ := getAppInstallByKey(constant.AppOpenresty) + if appInstall.ID > 0 { + curStr := i18n.GetWithName("CleanLogByName", "OpenResty") + t.LogStart(curStr) + accessLogPath := pathUtils.Join(appInstall.GetPath(), "log", "access.log") + if err := os.Truncate(accessLogPath, 0); err != nil { + t.LogFailedWithErr(curStr, err) + } + errLogPath := pathUtils.Join(appInstall.GetPath(), "log", "error.log") + if err := os.Truncate(errLogPath, 0); err != nil { + t.LogFailedWithErr(curStr, err) + } + t.LogSuccess(curStr) + } + } + } + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) +} + +func (u *CronjobService) handleSyncIpGroup(cronjob model.Cronjob, taskItem *task.Task) { + taskItem.AddSubTaskWithOps(i18n.GetWithName("SyncIpGroup", cronjob.Name), func(t *task.Task) error { + appInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + ipGroupDir := pathUtils.Join(appInstall.GetPath(), "1pwaf", "data", "rules", "ip_group") + urlDir := pathUtils.Join(ipGroupDir, "ip_group_url") + _, err = os.Stat(urlDir) + if err != nil && os.IsNotExist(err) { + return buserr.New("WafIpGroupNotFound") + } + + urlsFiles, err := os.ReadDir(urlDir) + if err != nil { + return err + } + for _, file := range urlsFiles { + if file.IsDir() || !strings.HasSuffix(file.Name(), "_url") { + continue + } + urlFilePath := filepath.Join(urlDir, file.Name()) + + urlContent, err := os.ReadFile(urlFilePath) + if err != nil { + continue + } + remoteURL := strings.TrimSpace(string(urlContent)) + if remoteURL == "" { + continue + } + resp, err := http.Get(remoteURL) + if err != nil { + continue + } + + ipGroupFile := strings.TrimSuffix(file.Name(), "_url") + ipGroupPath := filepath.Join(ipGroupDir, ipGroupFile) + + if resp.StatusCode != http.StatusOK { + resp.Body.Close() + taskItem.Logf("get remote ip group %s failed %s status code %d", ipGroupFile, remoteURL, resp.StatusCode) + continue + } + + outFile, err := os.OpenFile(ipGroupPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) + if err != nil { + resp.Body.Close() + taskItem.Logf("sync %s failed %v", ipGroupFile, err) + continue + } + + writer := bufio.NewWriter(outFile) + _, err = io.Copy(writer, resp.Body) + if err != nil { + outFile.Close() + resp.Body.Close() + taskItem.Logf("sync %s failed , write file failed %v", ipGroupFile, err) + continue + } + writer.Flush() + outFile.Close() + resp.Body.Close() + taskItem.LogSuccess(i18n.Get("TaskSync") + " " + ipGroupFile) + } + if err := opNginx(appInstall.ContainerName, constant.NginxReload); err != nil { + return err + } + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) +} + +func (u *CronjobService) handleCutWebsiteLog(cronjob *model.Cronjob, startTime time.Time, taskItem *task.Task) error { + clientMap := NewBackupClientMap([]string{fmt.Sprintf("%v", cronjob.DownloadAccountID)}) + if !clientMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].isOk { + return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", clientMap[fmt.Sprintf("%d", cronjob.DownloadAccountID)].message)) + } + taskItem.AddSubTaskWithOps(i18n.GetWithName("CutWebsiteLog", cronjob.Name), func(t *task.Task) error { + websites := loadWebsForJob(*cronjob) + fileOp := files.NewFileOp() + baseDir := GetOpenrestyDir(SitesRootDir) + for _, website := range websites { + taskItem.Log(website.Alias) + var record model.BackupRecord + record.Status = constant.StatusSuccess + record.From = "cronjob" + record.Type = "cut-website-log" + record.CronjobID = cronjob.ID + record.Name = website.Alias + record.DetailName = website.Alias + record.DownloadAccountID, record.SourceAccountIDs = cronjob.DownloadAccountID, cronjob.SourceAccountIDs + backupDir := pathUtils.Join(global.Dir.LocalBackupDir, "log", "website", website.Alias) + if !fileOp.Stat(backupDir) { + _ = os.MkdirAll(backupDir, constant.DirPerm) + } + record.FileDir = strings.TrimPrefix(backupDir, global.Dir.LocalBackupDir+"/") + record.FileName = fmt.Sprintf("%s_log_%s.gz", website.PrimaryDomain, startTime.Format(constant.DateTimeSlimLayout)) + if err := backupRepo.CreateRecord(&record); err != nil { + global.LOG.Errorf("save backup record failed, err: %v", err) + return err + } + + websiteLogDir := pathUtils.Join(baseDir, website.Alias, "log") + srcAccessLogPath := pathUtils.Join(websiteLogDir, "access.log") + srcErrorLogPath := pathUtils.Join(websiteLogDir, "error.log") + + dstFilePath := pathUtils.Join(backupDir, record.FileName) + if err := backupLogFile(dstFilePath, websiteLogDir, fileOp); err != nil { + taskItem.LogFailedWithErr("CutWebsiteLog", err) + continue + } else { + _ = fileOp.WriteFile(srcAccessLogPath, strings.NewReader(""), constant.DirPerm) + _ = fileOp.WriteFile(srcErrorLogPath, strings.NewReader(""), constant.DirPerm) + } + taskItem.Log(i18n.GetMsgWithMap("CutWebsiteLogSuccess", map[string]interface{}{"name": website.PrimaryDomain, "path": dstFilePath})) + u.removeExpiredBackup(*cronjob, clientMap, record) + } + return nil + }, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) + return nil +} + +func backupLogFile(dstFilePath, websiteLogDir string, fileOp files.FileOp) error { + cmdMgr := cmd.NewCommandMgr() + if err := cmdMgr.RunBashCf("tar -czf %s -C %s %s", dstFilePath, websiteLogDir, strings.Join([]string{"access.log", "error.log"}, " ")); err != nil { + dstDir := pathUtils.Dir(dstFilePath) + if err = fileOp.Copy(pathUtils.Join(websiteLogDir, "access.log"), dstDir); err != nil { + return err + } + if err = fileOp.Copy(pathUtils.Join(websiteLogDir, "error.log"), dstDir); err != nil { + return err + } + if err = cmdMgr.RunBashCf("tar -czf %s -C %s %s", dstFilePath, dstDir, strings.Join([]string{"access.log", "error.log"}, " ")); err != nil { + return err + } + _ = fileOp.DeleteFile(pathUtils.Join(dstDir, "access.log")) + _ = fileOp.DeleteFile(pathUtils.Join(dstDir, "error.log")) + return nil + } + return nil +} + +func (u *CronjobService) handleSystemClean(cronjob model.Cronjob, taskItem *task.Task) { + cleanTask := doSystemClean(taskItem) + taskItem.AddSubTaskWithOps(i18n.GetMsgByKey("HandleSystemClean"), cleanTask, nil, int(cronjob.RetryTimes), time.Duration(cronjob.Timeout)*time.Second) +} + +func (u *CronjobService) removeExpiredBackup(cronjob model.Cronjob, accountMap map[string]backupClientHelper, record model.BackupRecord) { + var opts []repo.DBOption + opts = append(opts, repo.WithByFrom("cronjob")) + opts = append(opts, backupRepo.WithByCronID(cronjob.ID)) + opts = append(opts, repo.WithOrderBy("created_at desc")) + if record.ID != 0 { + opts = append(opts, repo.WithByType(record.Type)) + opts = append(opts, repo.WithByName(record.Name)) + opts = append(opts, repo.WithByDetailName(record.DetailName)) + } + records, _ := backupRepo.ListRecord(opts...) + if len(records) <= int(cronjob.RetainCopies) { + return + } + for i := int(cronjob.RetainCopies); i < len(records); i++ { + accounts := strings.Split(cronjob.SourceAccountIDs, ",") + if cronjob.Type == "snapshot" { + for _, account := range accounts { + if len(account) != 0 { + if _, ok := accountMap[account]; !ok { + continue + } + if !accountMap[account].isOk { + continue + } + _, _ = accountMap[account].client.Delete(pathUtils.Join(accountMap[account].backupPath, "system_snapshot", records[i].FileName)) + } + } + _ = snapshotRepo.Delete(repo.WithByName(strings.TrimSuffix(records[i].FileName, ".tar.gz"))) + } else { + for _, account := range accounts { + if len(account) != 0 { + if _, ok := accountMap[account]; !ok { + continue + } + if !accountMap[account].isOk { + continue + } + _, _ = accountMap[account].client.Delete(pathUtils.Join(accountMap[account].backupPath, records[i].FileDir, records[i].FileName)) + } + } + } + _ = backupRepo.DeleteRecord(context.Background(), repo.WithByID(records[i].ID)) + } +} + +func (u *CronjobService) removeExpiredLog(cronjob model.Cronjob) { + records, _ := cronjobRepo.ListRecord(cronjobRepo.WithByJobID(int(cronjob.ID)), repo.WithOrderBy("created_at desc")) + if len(records) <= int(cronjob.RetainCopies) { + return + } + for i := int(cronjob.RetainCopies); i < len(records); i++ { + if len(records[i].File) != 0 { + files := strings.Split(records[i].File, ",") + for _, file := range files { + _ = os.Remove(file) + } + } + _ = cronjobRepo.DeleteRecord(repo.WithByID(records[i].ID)) + _ = taskRepo.Delete(taskRepo.WithByID(records[i].TaskID)) + _ = os.Remove(pathUtils.Join(global.CONF.Base.InstallDir, "1panel/log/task/Cronjob", records[i].TaskID+".log")) + } +} + +func hasBackup(cronjobType string) bool { + return cronjobType == "app" || cronjobType == "database" || cronjobType == "website" || cronjobType == "directory" || cronjobType == "snapshot" || cronjobType == "log" || cronjobType == "cutWebsiteLog" +} + +func handleCronJobAlert(cronjob *model.Cronjob) { + pushAlert := dto.PushAlert{ + TaskName: cronjob.Name, + AlertType: cronjob.Type, + EntryID: cronjob.ID, + Param: cronjob.Type, + } + _ = alert_push.PushAlert(pushAlert) +} + +func GetCleanLogConfig(cronJob model.Cronjob) dto.CleanLogConfig { + config := &dto.CleanLogConfig{} + _ = json.Unmarshal([]byte(cronJob.Config), config) + return *config +} diff --git a/agent/app/service/dashboard.go b/agent/app/service/dashboard.go new file mode 100644 index 0000000..9a41c62 --- /dev/null +++ b/agent/app/service/dashboard.go @@ -0,0 +1,644 @@ +package service + +import ( + "cmp" + "context" + "encoding/json" + "fmt" + network "net" + "os" + "sort" + "strings" + "sync" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/copier" + "github.com/1Panel-dev/1Panel/agent/utils/psutil" + "github.com/gin-gonic/gin" + "github.com/shirou/gopsutil/v4/disk" + "github.com/shirou/gopsutil/v4/load" + "github.com/shirou/gopsutil/v4/mem" + "github.com/shirou/gopsutil/v4/net" +) + +type DashboardService struct{} + +type IDashboardService interface { + LoadOsInfo() (*dto.OsInfo, error) + LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) + LoadCurrentInfoForNode() *dto.NodeCurrent + LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent + LoadTopCPU() []dto.Process + LoadTopMem() []dto.Process + + LoadQuickOptions() []dto.QuickJump + ChangeQuick(req dto.ChangeQuicks) error + + LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error) + ChangeShow(req dto.SettingUpdate) error + ListLauncherOption(filter string) ([]dto.LauncherOption, error) + Restart(operation string) error +} + +func NewIDashboardService() IDashboardService { + return &DashboardService{} +} + +func (u *DashboardService) Restart(operation string) error { + switch operation { + case "system": + { + go func() { + if err := cmd.RunDefaultBashCf("%s reboot", cmd.SudoHandleCmd()); err != nil { + global.LOG.Errorf("handle reboot failed, %v", err) + } + }() + return nil + } + case "1panel-agent": + controller.RestartPanel(false, true, false) + return nil + case "1panel": + controller.RestartPanel(true, true, false) + return nil + default: + return fmt.Errorf("handle restart operation %s failed, err: nonsupport such operation", operation) + } +} + +func (u *DashboardService) LoadOsInfo() (*dto.OsInfo, error) { + var baseInfo dto.OsInfo + hostInfo, err := psutil.HOST.GetHostInfo(false) + if err != nil { + return nil, err + } + baseInfo.OS = hostInfo.OS + baseInfo.Platform = hostInfo.Platform + baseInfo.PlatformFamily = hostInfo.PlatformFamily + baseInfo.KernelArch = hostInfo.KernelArch + baseInfo.KernelVersion = hostInfo.KernelVersion + baseInfo.PrettyDistro = psutil.HOST.GetDistro() + + diskInfo, err := psutil.DISK.GetUsage(global.Dir.BaseDir, false) + if err == nil { + baseInfo.DiskSize = int64(diskInfo.Free) + } + + if baseInfo.KernelArch == "armv7l" { + baseInfo.KernelArch = "armv7" + } + if baseInfo.KernelArch == "x86_64" { + baseInfo.KernelArch = "amd64" + } + return &baseInfo, nil +} + +func (u *DashboardService) LoadCurrentInfoForNode() *dto.NodeCurrent { + var currentInfo dto.NodeCurrent + + currentInfo.CPUTotal, _ = psutil.CPUInfo.GetLogicalCores(false) + + cpuUsedPercent, perCore, cpuDetailedPercent := psutil.CPU.GetCPUUsage() + if len(perCore) == 0 { + currentInfo.CPUTotal = psutil.CPU.NumCPU() + } else { + currentInfo.CPUTotal = len(perCore) + } + currentInfo.CPUUsedPercent = cpuUsedPercent + currentInfo.CPUUsed = cpuUsedPercent * 0.01 * float64(currentInfo.CPUTotal) + currentInfo.CPUDetailedPercent = cpuDetailedPercent + + loadInfo, _ := load.Avg() + currentInfo.Load1 = loadInfo.Load1 + currentInfo.Load5 = loadInfo.Load5 + currentInfo.Load15 = loadInfo.Load15 + currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100 + + memoryInfo, _ := mem.VirtualMemory() + currentInfo.MemoryTotal = memoryInfo.Total + currentInfo.MemoryAvailable = memoryInfo.Available + currentInfo.MemoryUsed = memoryInfo.Used + currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent + + swapInfo, _ := mem.SwapMemory() + currentInfo.SwapMemoryTotal = swapInfo.Total + currentInfo.SwapMemoryAvailable = swapInfo.Free + currentInfo.SwapMemoryUsed = swapInfo.Used + currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent + + return ¤tInfo +} + +func (u *DashboardService) LoadBaseInfo(ioOption string, netOption string) (*dto.DashboardBase, error) { + var baseInfo dto.DashboardBase + hostInfo, err := psutil.HOST.GetHostInfo(false) + if err != nil { + return nil, err + } + ss, _ := json.Marshal(hostInfo) + baseInfo = dto.DashboardBase{ + Hostname: hostInfo.Hostname, + OS: hostInfo.OS, + Platform: hostInfo.Platform, + PlatformFamily: hostInfo.PlatformFamily, + PlatformVersion: hostInfo.PlatformVersion, + PrettyDistro: psutil.HOST.GetDistro(), + KernelArch: hostInfo.KernelArch, + KernelVersion: hostInfo.KernelVersion, + VirtualizationSystem: string(ss), + IpV4Addr: loadOutboundIP(), + SystemProxy: "noProxy", + } + + if proxy := cmp.Or(os.Getenv("http_proxy"), os.Getenv("HTTP_PROXY")); proxy != "" { + baseInfo.SystemProxy = proxy + } + + loadQuickJump(&baseInfo) + + cpuInfo, err := psutil.CPUInfo.GetCPUInfo(false) + if err == nil && len(cpuInfo) > 0 { + baseInfo.CPUModelName = cpuInfo[0].ModelName + } + + baseInfo.CPUCores, _ = psutil.CPUInfo.GetPhysicalCores(false) + baseInfo.CPULogicalCores, _ = psutil.CPUInfo.GetLogicalCores(false) + baseInfo.CPUMhz = cpuInfo[0].Mhz + + baseInfo.CurrentInfo = *u.LoadCurrentInfo(ioOption, netOption) + return &baseInfo, nil +} + +func (u *DashboardService) LoadCurrentInfo(ioOption string, netOption string) *dto.DashboardCurrent { + var currentInfo dto.DashboardCurrent + hostInfo, _ := psutil.HOST.GetHostInfo(false) + currentInfo.Uptime = hostInfo.Uptime + currentInfo.TimeSinceUptime = time.Unix(int64(hostInfo.BootTime), 0).Format(constant.DateTimeLayout) + currentInfo.Procs = hostInfo.Procs + currentInfo.CPUTotal, _ = psutil.CPUInfo.GetLogicalCores(false) + + cpuUsedPercent, perCore, cpuDetailedPercent := psutil.CPU.GetCPUUsage() + if len(perCore) == 0 { + currentInfo.CPUTotal = psutil.CPU.NumCPU() + } else { + currentInfo.CPUTotal = len(perCore) + } + currentInfo.CPUPercent = perCore + currentInfo.CPUUsedPercent = cpuUsedPercent + currentInfo.CPUUsed = cpuUsedPercent * 0.01 * float64(currentInfo.CPUTotal) + currentInfo.CPUDetailedPercent = cpuDetailedPercent + + loadInfo, _ := load.Avg() + currentInfo.Load1 = loadInfo.Load1 + currentInfo.Load5 = loadInfo.Load5 + currentInfo.Load15 = loadInfo.Load15 + currentInfo.LoadUsagePercent = loadInfo.Load1 / (float64(currentInfo.CPUTotal*2) * 0.75) * 100 + + memoryInfo, _ := mem.VirtualMemory() + currentInfo.MemoryTotal = memoryInfo.Total + currentInfo.MemoryUsed = memoryInfo.Used + currentInfo.MemoryFree = memoryInfo.Free + currentInfo.MemoryCache = memoryInfo.Cached + memoryInfo.Buffers + currentInfo.MemoryShard = memoryInfo.Shared + currentInfo.MemoryAvailable = memoryInfo.Available + currentInfo.MemoryUsedPercent = memoryInfo.UsedPercent + + swapInfo, _ := mem.SwapMemory() + currentInfo.SwapMemoryTotal = swapInfo.Total + currentInfo.SwapMemoryAvailable = swapInfo.Free + currentInfo.SwapMemoryUsed = swapInfo.Used + currentInfo.SwapMemoryUsedPercent = swapInfo.UsedPercent + + currentInfo.DiskData = loadDiskInfo() + currentInfo.GPUData = loadGPUInfo() + currentInfo.XPUData = loadXpuInfo() + + if ioOption == "all" { + diskInfo, _ := disk.IOCounters() + for _, state := range diskInfo { + currentInfo.IOReadBytes += state.ReadBytes + currentInfo.IOWriteBytes += state.WriteBytes + currentInfo.IOCount += (state.ReadCount + state.WriteCount) + currentInfo.IOReadTime += state.ReadTime + currentInfo.IOWriteTime += state.WriteTime + } + } else { + diskInfo, _ := disk.IOCounters(ioOption) + for _, state := range diskInfo { + currentInfo.IOReadBytes += state.ReadBytes + currentInfo.IOWriteBytes += state.WriteBytes + currentInfo.IOCount += (state.ReadCount + state.WriteCount) + currentInfo.IOReadTime += state.ReadTime + currentInfo.IOWriteTime += state.WriteTime + } + } + + if netOption == "all" { + netInfo, _ := net.IOCounters(false) + if len(netInfo) != 0 { + currentInfo.NetBytesSent = netInfo[0].BytesSent + currentInfo.NetBytesRecv = netInfo[0].BytesRecv + } + } else { + netInfo, _ := net.IOCounters(true) + for _, state := range netInfo { + if state.Name == netOption { + currentInfo.NetBytesSent = state.BytesSent + currentInfo.NetBytesRecv = state.BytesRecv + break + } + } + } + + currentInfo.ShotTime = time.Now() + return ¤tInfo +} + +func (u *DashboardService) LoadTopCPU() []dto.Process { + return loadTopCPU() +} + +func (u *DashboardService) LoadTopMem() []dto.Process { + return loadTopMem() +} + +func (u *DashboardService) LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error) { + var data []dto.AppLauncher + appInstalls, err := appInstallRepo.ListBy(context.Background()) + if err != nil { + return data, err + } + apps, err := appRepo.GetBy() + if err != nil { + return data, err + } + + showList, err := launcherRepo.ListName() + defaultList, err := appRepo.GetTopRecommend() + if err != nil { + return data, nil + } + allList := common.RemoveRepeatStr(append(defaultList, showList...)) + for _, showItem := range allList { + var itemData dto.AppLauncher + for _, app := range apps { + if showItem == app.Key { + itemData.Key = app.Key + itemData.Type = app.Type + itemData.Name = app.Name + itemData.Icon = app.Icon + itemData.Limit = app.Limit + itemData.Recommend = app.Recommend + itemData.Description = app.GetDescription(ctx) + break + } + } + if len(itemData.Icon) == 0 { + continue + } + for _, install := range appInstalls { + if install.App.Key == showItem { + itemData.IsInstall = true + itemData.Detail = append(itemData.Detail, dto.InstallDetail{ + InstallID: install.ID, + DetailID: install.AppDetailId, + Name: install.Name, + Version: install.Version, + Status: install.Status, + Path: install.GetPath(), + WebUI: install.WebUI, + HttpPort: install.HttpPort, + HttpsPort: install.HttpsPort, + }) + } + } + if (ArryContains(showList, showItem) && len(itemData.Detail) != 0) || + (ArryContains(defaultList, showItem) && len(itemData.Detail) == 0) { + data = append(data, itemData) + } + } + + sort.Slice(data, func(i, j int) bool { + if data[i].IsInstall != data[j].IsInstall { + return data[i].IsInstall + } + return data[i].Recommend < data[j].Recommend + }) + return data, nil +} + +func (u *DashboardService) ChangeShow(req dto.SettingUpdate) error { + launcher, _ := launcherRepo.Get(repo.WithByKey(req.Key)) + if req.Value == constant.StatusEnable && launcher.ID == 0 { + if err := launcherRepo.Create(&model.AppLauncher{Key: req.Key}); err != nil { + return err + } + } + if req.Value == constant.StatusDisable && launcher.ID != 0 { + if err := launcherRepo.Delete(repo.WithByKey(req.Key)); err != nil { + return err + } + } + return nil +} + +func (u *DashboardService) LoadQuickOptions() []dto.QuickJump { + quicks := launcherRepo.ListQuickJump(true) + var list []dto.QuickJump + for _, quick := range quicks { + var item dto.QuickJump + _ = copier.Copy(&item, &quick) + list = append(list, item) + } + return list +} +func (u *DashboardService) ChangeQuick(req dto.ChangeQuicks) error { + showCount := 0 + var quicks []model.QuickJump + for _, item := range req.Quicks { + var quick model.QuickJump + if item.IsShow { + showCount++ + } + if err := copier.Copy(&quick, &item); err != nil { + return err + } + quicks = append(quicks, quick) + } + if showCount == 0 { + return buserr.New("ErrMinQuickJump") + } + if showCount > 4 { + return buserr.New("ErrMaxQuickJump") + } + + return launcherRepo.UpdateQuicks(quicks) +} + +func (u *DashboardService) ListLauncherOption(filter string) ([]dto.LauncherOption, error) { + showList, _ := launcherRepo.ListName() + var data []dto.LauncherOption + optionMap := make(map[string]bool) + appInstalls, err := appInstallRepo.ListBy(context.Background()) + if err != nil { + return data, err + } + + for _, install := range appInstalls { + isShow := false + for _, item := range showList { + if install.App.Key == item { + isShow = true + break + } + } + optionMap[install.App.Key] = isShow + } + for key, val := range optionMap { + if len(filter) != 0 && !strings.Contains(key, filter) { + continue + } + data = append(data, dto.LauncherOption{Key: key, IsShow: val}) + } + sort.Slice(data, func(i, j int) bool { + return data[i].Key < data[j].Key + }) + return data, nil +} + +type diskInfo struct { + Type string + Mount string + Device string +} + +func loadDiskInfo() []dto.DiskInfo { + var datas []dto.DiskInfo + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(2 * time.Second)) + format := `awk 'NR>1 && !/tmpfs|snap\/core|udev/ {printf "%s\t%s\t%s\t%s\t%s\t%s\t%s\n", $1, $2, $3, $4, $5, $6, $7}'` + stdout, err := cmdMgr.RunWithStdout("bash", "-c", `timeout 2 df -hT -P | `+format) + if err != nil { + cmdMgr2 := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second)) + stdout, err = cmdMgr2.RunWithStdout("bash", "-c", `timeout 1 df -lhT -P | `+format) + if err != nil { + return datas + } + } + lines := strings.Split(stdout, "\n") + + var mounts []diskInfo + var excludes = []string{"/mnt/cdrom", "/boot", "/boot/efi", "/dev", "/dev/shm", "/run/lock", "/run", "/run/shm", "/run/user"} + for _, line := range lines { + fields := strings.Split(line, "\t") + if len(fields) < 7 { + continue + } + if strings.HasPrefix(fields[6], "/snap") || len(strings.Split(fields[6], "/")) > 10 { + continue + } + if strings.TrimSpace(fields[1]) == "tmpfs" || strings.TrimSpace(fields[1]) == "overlay" { + continue + } + if strings.Contains(fields[2], "K") { + continue + } + if strings.Contains(fields[6], "docker") || strings.Contains(fields[6], "podman") || strings.Contains(fields[6], "containerd") || strings.HasPrefix(fields[6], "/var/lib/containers") { + continue + } + isExclude := false + for _, exclude := range excludes { + if exclude == fields[6] { + isExclude = true + } + } + if isExclude { + continue + } + mounts = append(mounts, diskInfo{Type: fields[1], Device: fields[0], Mount: strings.Join(fields[6:], " ")}) + } + + var ( + wg sync.WaitGroup + mu sync.Mutex + ) + wg.Add(len(mounts)) + for i := 0; i < len(mounts); i++ { + go func(mount diskInfo) { + defer wg.Done() + + var itemData dto.DiskInfo + itemData.Path = mount.Mount + itemData.Type = mount.Type + itemData.Device = mount.Device + + type diskResult struct { + state *disk.UsageStat + err error + } + resultCh := make(chan diskResult, 1) + + go func() { + state, err := psutil.DISK.GetUsage(mount.Mount, false) + resultCh <- diskResult{state: state, err: err} + }() + + select { + case <-time.After(5 * time.Second): + mu.Lock() + datas = append(datas, itemData) + mu.Unlock() + global.LOG.Errorf("load disk info from %s failed, err: timeout", mount.Mount) + case result := <-resultCh: + if result.err != nil { + mu.Lock() + datas = append(datas, itemData) + mu.Unlock() + global.LOG.Errorf("load disk info from %s failed, err: %v", mount.Mount, result.err) + return + } + itemData.Total = result.state.Total + itemData.Free = result.state.Free + itemData.Used = result.state.Used + itemData.UsedPercent = result.state.UsedPercent + itemData.InodesTotal = result.state.InodesTotal + itemData.InodesUsed = result.state.InodesUsed + itemData.InodesFree = result.state.InodesFree + itemData.InodesUsedPercent = result.state.InodesUsedPercent + mu.Lock() + datas = append(datas, itemData) + mu.Unlock() + } + }(mounts[i]) + } + wg.Wait() + + sort.Slice(datas, func(i, j int) bool { + return datas[i].Path < datas[j].Path + }) + return datas +} + +func loadGPUInfo() []dto.GPUInfo { + ok, client := gpu.New() + var list []interface{} + if ok { + info, err := client.LoadGpuInfo() + if err != nil || len(info.GPUs) == 0 { + return nil + } + for _, item := range info.GPUs { + list = append(list, item) + } + } + if len(list) == 0 { + return nil + } + var data []dto.GPUInfo + for _, gpu := range list { + var dataItem dto.GPUInfo + if err := copier.Copy(&dataItem, &gpu); err != nil { + continue + } + dataItem.PowerUsage = dataItem.PowerDraw + " / " + dataItem.MaxPowerLimit + dataItem.MemoryUsage = dataItem.MemUsed + " / " + dataItem.MemTotal + data = append(data, dataItem) + } + return data +} + +type AppLauncher struct { + Key string `json:"key"` +} + +func ArryContains(arr []string, element string) bool { + for _, v := range arr { + if v == element { + return true + } + } + return false +} + +func loadXpuInfo() []dto.XPUInfo { + var list []interface{} + ok, xpuClient := xpu.New() + if ok { + xpus, err := xpuClient.LoadDashData() + if err != nil || len(xpus) == 0 { + return nil + } + for _, item := range xpus { + list = append(list, item) + } + } + if len(list) == 0 { + return nil + } + var data []dto.XPUInfo + for _, gpu := range list { + var dataItem dto.XPUInfo + if err := copier.Copy(&dataItem, &gpu); err != nil { + continue + } + data = append(data, dataItem) + } + return data +} + +func loadOutboundIP() string { + conn, err := network.Dial("udp", "8.8.8.8:80") + + if err != nil { + return "IPNotFound" + } + defer conn.Close() + + localAddr := conn.LocalAddr().(*network.UDPAddr) + return localAddr.IP.String() +} + +func loadQuickJump(base *dto.DashboardBase) { + website, _ := websiteRepo.GetBy() + base.WebsiteNumber = len(website) + + postgresqlDbs, _ := postgresqlRepo.List() + mysqlDbs, _ := mysqlRepo.List() + base.DatabaseNumber = len(mysqlDbs) + len(postgresqlDbs) + + cronjobs, _ := cronjobRepo.List() + base.CronjobNumber = len(cronjobs) + + appInstall, _ := appInstallRepo.ListBy(context.Background()) + base.AppInstalledNumber = len(appInstall) + + quicks := launcherRepo.ListQuickJump(false) + for i := 0; i < len(quicks); i++ { + switch quicks[i].Name { + case "Website": + quicks[i].Detail = fmt.Sprintf("%d", base.WebsiteNumber) + case "Database": + quicks[i].Detail = fmt.Sprintf("%d", base.DatabaseNumber) + case "Cronjob": + quicks[i].Detail = fmt.Sprintf("%d", base.CronjobNumber) + case "AppInstalled": + quicks[i].Detail = fmt.Sprintf("%d", base.AppInstalledNumber) + } + var item dto.QuickJump + _ = copier.Copy(&item, quicks[i]) + base.QuickJumps = append(base.QuickJumps, item) + } + sort.Slice(quicks, func(i, j int) bool { + return quicks[i].Recommend < quicks[j].Recommend + }) +} diff --git a/agent/app/service/database.go b/agent/app/service/database.go new file mode 100644 index 0000000..476df7d --- /dev/null +++ b/agent/app/service/database.go @@ -0,0 +1,342 @@ +package service + +import ( + "context" + "fmt" + "os" + "path" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/mysql" + "github.com/1Panel-dev/1Panel/agent/utils/mysql/client" + "github.com/1Panel-dev/1Panel/agent/utils/postgresql" + pgclient "github.com/1Panel-dev/1Panel/agent/utils/postgresql/client" + redisclient "github.com/1Panel-dev/1Panel/agent/utils/redis" + "github.com/jinzhu/copier" + "github.com/pkg/errors" +) + +type DatabaseService struct{} + +type IDatabaseService interface { + Get(name string) (dto.DatabaseInfo, error) + SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error) + CheckDatabase(req dto.DatabaseCreate) bool + Create(req dto.DatabaseCreate) error + Update(req dto.DatabaseUpdate) error + DeleteCheck(id uint) ([]string, error) + Delete(req dto.DatabaseDelete) error + List(dbType string) ([]dto.DatabaseOption, error) + LoadItems(dbType string) ([]dto.DatabaseItem, error) +} + +func NewIDatabaseService() IDatabaseService { + return &DatabaseService{} +} + +func (u *DatabaseService) SearchWithPage(search dto.DatabaseSearch) (int64, interface{}, error) { + total, dbs, err := databaseRepo.Page(search.Page, search.PageSize, + databaseRepo.WithTypeList(search.Type), + repo.WithByLikeName(search.Info), + repo.WithOrderRuleBy(search.OrderBy, search.Order), + databaseRepo.WithoutByFrom("local"), + ) + var datas []dto.DatabaseInfo + for _, db := range dbs { + var item dto.DatabaseInfo + if err := copier.Copy(&item, &db); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + datas = append(datas, item) + } + return total, datas, err +} + +func (u *DatabaseService) Get(name string) (dto.DatabaseInfo, error) { + var data dto.DatabaseInfo + remote, err := databaseRepo.Get(repo.WithByName(name)) + if err != nil { + return data, err + } + if err := copier.Copy(&data, &remote); err != nil { + return data, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + return data, nil +} + +func (u *DatabaseService) List(dbType string) ([]dto.DatabaseOption, error) { + dbs, err := databaseRepo.GetList(databaseRepo.WithTypeList(dbType)) + if err != nil { + return nil, err + } + var datas []dto.DatabaseOption + for _, db := range dbs { + var item dto.DatabaseOption + if err := copier.Copy(&item, &db); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + item.Database = db.Name + datas = append(datas, item) + } + return datas, err +} + +func (u *DatabaseService) LoadItems(dbType string) ([]dto.DatabaseItem, error) { + dbs, err := databaseRepo.GetList(databaseRepo.WithTypeList(dbType)) + var datas []dto.DatabaseItem + for _, db := range dbs { + if dbType == constant.AppPostgresql || dbType == constant.AppPostgresqlCluster { + items, _ := postgresqlRepo.List(postgresqlRepo.WithByPostgresqlName(db.Name)) + for _, item := range items { + var dItem dto.DatabaseItem + if err := copier.Copy(&dItem, &item); err != nil { + continue + } + dItem.Database = db.Name + datas = append(datas, dItem) + } + } else { + items, _ := mysqlRepo.List(mysqlRepo.WithByMysqlName(db.Name)) + for _, item := range items { + var dItem dto.DatabaseItem + if err := copier.Copy(&dItem, &item); err != nil { + continue + } + dItem.Database = db.Name + datas = append(datas, dItem) + } + } + } + return datas, err +} + +func (u *DatabaseService) CheckDatabase(req dto.DatabaseCreate) bool { + if req.Timeout == 0 { + req.Timeout = 30 + } + var err error + switch req.Type { + case constant.AppPostgresql: + _, err = postgresql.NewPostgresqlClient(pgclient.DBInfo{ + From: "remote", + Address: req.Address, + Port: req.Port, + InitialDB: req.InitialDB, + Username: req.Username, + Password: req.Password, + Timeout: req.Timeout, + }) + case constant.AppRedis: + _, err = redisclient.NewRedisClient(redisclient.DBInfo{ + Address: req.Address, + Port: req.Port, + Password: req.Password, + Timeout: req.Timeout, + }) + case "mysql", "mariadb": + _, err = mysql.NewMysqlClient(client.DBInfo{ + From: "remote", + Address: req.Address, + Port: req.Port, + Username: req.Username, + Password: req.Password, + + SSL: req.SSL, + RootCert: req.RootCert, + ClientKey: req.ClientKey, + ClientCert: req.ClientCert, + SkipVerify: req.SkipVerify, + Timeout: req.Timeout, + }) + } + if err != nil { + global.LOG.Errorf("check database connection failed, err: %v", err) + return false + } + + return true +} + +func (u *DatabaseService) Create(req dto.DatabaseCreate) error { + if req.Timeout == 0 { + req.Timeout = 30 + } + db, _ := databaseRepo.Get(repo.WithByName(req.Name)) + if db.ID != 0 { + if db.From == "local" { + return buserr.New("ErrLocalExist") + } + return buserr.New("ErrRecordExist") + } + switch req.Type { + case constant.AppPostgresql: + if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{ + From: "remote", + Address: req.Address, + Port: req.Port, + InitialDB: req.InitialDB, + Username: req.Username, + Password: req.Password, + Timeout: req.Timeout, + }); err != nil { + return err + } + case constant.AppRedis: + if _, err := redisclient.NewRedisClient(redisclient.DBInfo{ + Address: req.Address, + Port: req.Port, + Password: req.Password, + Timeout: req.Timeout, + }); err != nil { + return err + } + case "mysql", "mariadb": + if _, err := mysql.NewMysqlClient(client.DBInfo{ + From: "remote", + Address: req.Address, + Port: req.Port, + Username: req.Username, + Password: req.Password, + + SSL: req.SSL, + RootCert: req.RootCert, + ClientKey: req.ClientKey, + ClientCert: req.ClientCert, + SkipVerify: req.SkipVerify, + Timeout: req.Timeout, + }); err != nil { + return err + } + default: + return errors.New("database type not supported") + } + + if err := copier.Copy(&db, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if err := databaseRepo.Create(context.Background(), &db); err != nil { + return err + } + return nil +} + +func (u *DatabaseService) DeleteCheck(id uint) ([]string, error) { + var appInUsed []string + apps, _ := appInstallResourceRepo.GetBy(repo.WithByFrom("remote"), appInstallResourceRepo.WithLinkId(id)) + for _, app := range apps { + appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId)) + if appInstall.ID != 0 { + appInUsed = append(appInUsed, appInstall.Name) + } + } + + return appInUsed, nil +} + +func (u *DatabaseService) Delete(req dto.DatabaseDelete) error { + db, _ := databaseRepo.Get(repo.WithByID(req.ID)) + if db.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + + if req.DeleteBackup { + uploadDir := path.Join(global.Dir.DataDir, fmt.Sprintf("uploads/database/%s/%s", db.Type, db.Name)) + if _, err := os.Stat(uploadDir); err == nil { + _ = os.RemoveAll(uploadDir) + } + backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("database/%s/%s", db.Type, db.Name)) + if _, err := os.Stat(backupDir); err == nil { + _ = os.RemoveAll(backupDir) + } + _ = backupRepo.DeleteRecord(context.Background(), repo.WithByType(db.Type), repo.WithByName(db.Name)) + global.LOG.Infof("delete database %s-%s backups successful", db.Type, db.Name) + } + + if err := databaseRepo.Delete(context.Background(), repo.WithByID(req.ID)); err != nil && !req.ForceDelete { + return err + } + if db.From != "local" { + if db.Type == "mysql" || db.Type == "mariadb" { + if err := mysqlRepo.Delete(context.Background(), mysqlRepo.WithByMysqlName(db.Name)); err != nil && !req.ForceDelete { + return err + } + } else { + if err := postgresqlRepo.Delete(context.Background(), postgresqlRepo.WithByPostgresqlName(db.Name)); err != nil && !req.ForceDelete { + return err + } + } + } + return nil +} + +func (u *DatabaseService) Update(req dto.DatabaseUpdate) error { + switch req.Type { + case constant.AppPostgresql: + if _, err := postgresql.NewPostgresqlClient(pgclient.DBInfo{ + From: "remote", + Address: req.Address, + Port: req.Port, + InitialDB: req.InitialDB, + Username: req.Username, + Password: req.Password, + Timeout: req.Timeout, + }); err != nil { + return err + } + case constant.AppRedis: + if _, err := redisclient.NewRedisClient(redisclient.DBInfo{ + Address: req.Address, + Port: req.Port, + Password: req.Password, + Timeout: req.Timeout, + }); err != nil { + return err + } + case "mysql", "mariadb": + if _, err := mysql.NewMysqlClient(client.DBInfo{ + From: "remote", + Address: req.Address, + Port: req.Port, + Username: req.Username, + Password: req.Password, + + SSL: req.SSL, + RootCert: req.RootCert, + ClientKey: req.ClientKey, + ClientCert: req.ClientCert, + SkipVerify: req.SkipVerify, + Timeout: req.Timeout, + }); err != nil { + return err + } + default: + return errors.New("database type not supported") + } + + pass, err := encrypt.StringEncrypt(req.Password) + if err != nil { + return fmt.Errorf("decrypt database password failed, err: %v", err) + } + + upMap := make(map[string]interface{}) + upMap["type"] = req.Type + upMap["version"] = req.Version + upMap["address"] = req.Address + upMap["port"] = req.Port + upMap["username"] = req.Username + upMap["password"] = pass + upMap["initial_db"] = req.InitialDB + upMap["timeout"] = req.Timeout + upMap["description"] = req.Description + upMap["ssl"] = req.SSL + upMap["client_key"] = req.ClientKey + upMap["client_cert"] = req.ClientCert + upMap["root_cert"] = req.RootCert + upMap["skip_verify"] = req.SkipVerify + return databaseRepo.Update(req.ID, upMap) +} diff --git a/agent/app/service/database_common.go b/agent/app/service/database_common.go new file mode 100644 index 0000000..a303434 --- /dev/null +++ b/agent/app/service/database_common.go @@ -0,0 +1,110 @@ +package service + +import ( + "bufio" + "fmt" + "os" + "path" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/compose" +) + +type DBCommonService struct{} + +type IDBCommonService interface { + LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error) + LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) + + UpdateConfByFile(req dto.DBConfUpdateByFile) error +} + +func NewIDBCommonService() IDBCommonService { + return &DBCommonService{} +} + +func (u *DBCommonService) LoadBaseInfo(req dto.OperationWithNameAndType) (*dto.DBBaseInfo, error) { + var data dto.DBBaseInfo + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return nil, err + } + data.ContainerName = app.ContainerName + data.Name = app.Name + data.Port = app.Port + + return &data, nil +} + +func (u *DBCommonService) LoadDatabaseFile(req dto.OperationWithNameAndType) (string, error) { + filePath := "" + switch req.Type { + case "mysql-cluster-conf": + filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql-cluster/%s/conf/my.cnf", req.Name)) + case "postgresql-cluster-conf": + filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql-cluster/%s/data/postgresql.conf", req.Name)) + case "mysql-conf": + filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/conf/my.cnf", req.Name)) + case "mariadb-conf": + filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mariadb/%s/conf/my.cnf", req.Name)) + case "postgresql-conf": + database, err := databaseRepo.Get(repo.WithByName(req.Name)) + if err != nil { + return "", err + } + filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql/%s/data/postgresql.conf", req.Name)) + if strings.HasPrefix(database.Version, "18.") { + filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/postgresql/%s/data/18/docker/postgresql.conf", req.Name)) + } + case "redis-conf": + filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/redis/%s/conf/redis.conf", req.Name)) + case "redis-cluster-conf": + filePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/redis-cluster/%s/conf/redis.conf", req.Name)) + } + if _, err := os.Stat(filePath); err != nil { + return "", buserr.New("ErrHttpReqNotFound") + } + content, err := os.ReadFile(filePath) + if err != nil { + return "", err + } + return string(content), nil +} + +func (u *DBCommonService) UpdateConfByFile(req dto.DBConfUpdateByFile) error { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database) + if err != nil { + return err + } + path := "" + switch req.Type { + case constant.AppMariaDB, constant.AppMysql, constant.AppMysqlCluster: + path = fmt.Sprintf("%s/%s/%s/conf/my.cnf", global.Dir.AppInstallDir, req.Type, app.Name) + case constant.AppPostgresqlCluster: + path = fmt.Sprintf("%s/%s/%s/data/postgresql.conf", global.Dir.AppInstallDir, req.Type, app.Name) + case constant.AppPostgresql: + path = fmt.Sprintf("%s/%s/%s/data/postgresql.conf", global.Dir.AppInstallDir, req.Type, app.Name) + if strings.HasPrefix(app.Version, "18.") { + path = fmt.Sprintf("%s/%s/%s/data/18/docker/postgresql.conf", global.Dir.AppInstallDir, req.Type, app.Name) + } + case constant.AppRedis, constant.AppRedisCluster: + path = fmt.Sprintf("%s/%s/%s/conf/redis.conf", global.Dir.AppInstallDir, req.Type, app.Name) + } + file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(req.File) + write.Flush() + if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, req.Type, app.Name)); err != nil { + return err + } + return nil +} diff --git a/agent/app/service/database_mysql.go b/agent/app/service/database_mysql.go new file mode 100644 index 0000000..5790a71 --- /dev/null +++ b/agent/app/service/database_mysql.go @@ -0,0 +1,721 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/mysql" + "github.com/1Panel-dev/1Panel/agent/utils/mysql/client" + "github.com/1Panel-dev/1Panel/agent/utils/re" + _ "github.com/go-sql-driver/mysql" + "github.com/jinzhu/copier" + "github.com/pkg/errors" +) + +type MysqlService struct{} + +type IMysqlService interface { + SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error) + ListDBOption() ([]dto.MysqlOption, error) + Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) + BindUser(req dto.BindUser) error + LoadFromRemote(req dto.MysqlLoadDB) error + ChangeAccess(info dto.ChangeDBInfo) error + ChangePassword(info dto.ChangeDBInfo) error + UpdateVariables(req dto.MysqlVariablesUpdate) error + UpdateDescription(req dto.UpdateDescription) error + DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error) + Delete(ctx context.Context, req dto.MysqlDBDelete) error + + LoadFormatOption(req dto.OperationWithName) []dto.MysqlFormatCollationOption + LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlStatus, error) + LoadVariables(req dto.OperationWithNameAndType) (*dto.MysqlVariables, error) + LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error) +} + +func NewIMysqlService() IMysqlService { + return &MysqlService{} +} + +func (u *MysqlService) SearchWithPage(search dto.MysqlDBSearch) (int64, interface{}, error) { + total, mysqls, err := mysqlRepo.Page(search.Page, search.PageSize, + mysqlRepo.WithByMysqlName(search.Database), + repo.WithByLikeName(search.Info), + repo.WithOrderRuleBy(search.OrderBy, search.Order), + ) + var dtoMysqls []dto.MysqlDBInfo + for _, mysql := range mysqls { + var item dto.MysqlDBInfo + if err := copier.Copy(&item, &mysql); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + dtoMysqls = append(dtoMysqls, item) + } + return total, dtoMysqls, err +} + +func (u *MysqlService) ListDBOption() ([]dto.MysqlOption, error) { + mysqls, err := mysqlRepo.List() + if err != nil { + return nil, err + } + + databases, err := databaseRepo.GetList(databaseRepo.WithTypeList("mysql,mariadb")) + if err != nil { + return nil, err + } + var dbs []dto.MysqlOption + for _, mysql := range mysqls { + var item dto.MysqlOption + if err := copier.Copy(&item, &mysql); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + item.Database = mysql.MysqlName + for _, database := range databases { + if database.Name == item.Database { + item.Type = database.Type + } + } + dbs = append(dbs, item) + } + return dbs, err +} + +func (u *MysqlService) Create(ctx context.Context, req dto.MysqlDBCreate) (*model.DatabaseMysql, error) { + if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format, req.Collation, req.Permission) { + return nil, buserr.New("ErrCmdIllegal") + } + + mysql, _ := mysqlRepo.Get(repo.WithByName(req.Name), mysqlRepo.WithByMysqlName(req.Database), repo.WithByFrom(req.From)) + if mysql.ID != 0 { + return nil, buserr.New("ErrRecordExist") + } + + var createItem model.DatabaseMysql + if err := copier.Copy(&createItem, &req); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + + if req.From == "local" && req.Username == "root" { + return nil, errors.New("cannot set root as user name") + } + + cli, version, err := LoadMysqlClientByFrom(req.Database) + if err != nil { + return nil, err + } + createItem.MysqlName = req.Database + defer cli.Close() + if err := cli.Create(client.CreateInfo{ + Name: req.Name, + Format: req.Format, + Collation: req.Collation, + Username: req.Username, + Password: req.Password, + Permission: req.Permission, + Version: version, + Timeout: 300, + }); err != nil { + return nil, err + } + + global.LOG.Infof("create database %s successful!", req.Name) + if err := mysqlRepo.Create(ctx, &createItem); err != nil { + return nil, err + } + return &createItem, nil +} + +func (u *MysqlService) BindUser(req dto.BindUser) error { + if cmd.CheckIllegal(req.Username, req.Password, req.Permission) { + return buserr.New("ErrCmdIllegal") + } + + dbItem, err := mysqlRepo.Get(mysqlRepo.WithByMysqlName(req.Database), repo.WithByName(req.DB)) + if err != nil { + return err + } + cli, version, err := LoadMysqlClientByFrom(req.Database) + if err != nil { + return err + } + defer cli.Close() + + if err := cli.CreateUser(client.CreateInfo{ + Name: dbItem.Name, + Format: dbItem.Format, + Username: req.Username, + Password: req.Password, + Permission: req.Permission, + Version: version, + Timeout: 300, + }, false); err != nil { + return err + } + pass, err := encrypt.StringEncrypt(req.Password) + if err != nil { + return fmt.Errorf("decrypt database db password failed, err: %v", err) + } + if err := mysqlRepo.Update(dbItem.ID, map[string]interface{}{ + "username": req.Username, + "password": pass, + "permission": req.Permission, + }); err != nil { + return err + } + return nil +} + +func (u *MysqlService) LoadFromRemote(req dto.MysqlLoadDB) error { + client, version, err := LoadMysqlClientByFrom(req.Database) + if err != nil { + return err + } + + databases, err := mysqlRepo.List(mysqlRepo.WithByMysqlName(req.Database)) + if err != nil { + return err + } + datas, err := client.SyncDB(version) + if err != nil { + return err + } + deleteList := databases + for _, data := range datas { + hasOld := false + for i := 0; i < len(databases); i++ { + if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].MysqlName, data.MysqlName) { + hasOld = true + if databases[i].IsDelete { + _ = mysqlRepo.Update(databases[i].ID, map[string]interface{}{"is_delete": false}) + } + deleteList = append(deleteList[:i], deleteList[i+1:]...) + break + } + } + if !hasOld { + var createItem model.DatabaseMysql + if err := copier.Copy(&createItem, &data); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if err := mysqlRepo.Create(context.Background(), &createItem); err != nil { + return err + } + } + } + for _, delItem := range deleteList { + _ = mysqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true}) + } + return nil +} + +func (u *MysqlService) UpdateDescription(req dto.UpdateDescription) error { + return mysqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description}) +} + +func (u *MysqlService) DeleteCheck(req dto.MysqlDBDeleteCheck) ([]dto.DBResource, error) { + var res []dto.DBResource + db, err := mysqlRepo.Get(repo.WithByID(req.ID)) + if err != nil { + return res, err + } + + website, _ := websiteRepo.GetFirst(websiteRepo.WithDBType(constant.AppMysql), websiteRepo.WithDBID(req.ID)) + if website.ID != 0 { + res = append(res, dto.DBResource{ + Type: constant.TypeWebsite, + Name: website.PrimaryDomain, + }) + } + website, _ = websiteRepo.GetFirst(websiteRepo.WithDBType(constant.AppMysqlCluster), websiteRepo.WithDBID(req.ID)) + if website.ID != 0 { + res = append(res, dto.DBResource{ + Type: constant.TypeWebsite, + Name: website.PrimaryDomain, + }) + } + + if db.From == "local" { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database) + if err != nil { + return res, err + } + apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID)) + for _, app := range apps { + appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId)) + if appInstall.ID != 0 { + res = append(res, dto.DBResource{ + Type: constant.TypeApp, + Name: appInstall.Name, + }) + } + } + } else { + apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID), appRepo.WithKey(req.Type)) + for _, app := range apps { + appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId)) + if appInstall.ID != 0 { + res = append(res, dto.DBResource{ + Type: constant.TypeApp, + Name: appInstall.Name, + }) + } + } + } + + return res, nil +} + +func (u *MysqlService) Delete(ctx context.Context, req dto.MysqlDBDelete) error { + db, err := mysqlRepo.Get(repo.WithByID(req.ID)) + if err != nil && !req.ForceDelete { + return err + } + cli, version, err := LoadMysqlClientByFrom(req.Database) + if err != nil { + return err + } + defer cli.Close() + if err := cli.Delete(client.DeleteInfo{ + Name: db.Name, + Version: version, + Username: db.Username, + Permission: db.Permission, + Timeout: 300, + }); err != nil && !req.ForceDelete { + return err + } + + if req.DeleteBackup { + uploadDir := filepath.Join(global.Dir.DataDir, fmt.Sprintf("uploads/database/%s/%s/%s", req.Type, req.Database, db.Name)) + if _, err := os.Stat(uploadDir); err == nil { + _ = os.RemoveAll(uploadDir) + } + backupDir := filepath.Join(global.Dir.LocalBackupDir, fmt.Sprintf("database/%s/%s/%s", req.Type, db.MysqlName, db.Name)) + if _, err := os.Stat(backupDir); err == nil { + _ = os.RemoveAll(backupDir) + } + _ = backupRepo.DeleteRecord(ctx, repo.WithByType(req.Type), repo.WithByName(req.Database), repo.WithByDetailName(db.Name)) + global.LOG.Infof("delete database %s-%s backups successful", req.Database, db.Name) + } + + _ = mysqlRepo.Delete(ctx, repo.WithByID(db.ID)) + return nil +} + +func (u *MysqlService) ChangePassword(req dto.ChangeDBInfo) error { + if cmd.CheckIllegal(req.Value) { + return buserr.New("ErrCmdIllegal") + } + cli, version, err := LoadMysqlClientByFrom(req.Database) + if err != nil { + return err + } + defer cli.Close() + var ( + mysqlData model.DatabaseMysql + passwordInfo client.PasswordChangeInfo + ) + passwordInfo.Password = req.Value + passwordInfo.Timeout = 300 + passwordInfo.Version = version + + if req.ID != 0 { + mysqlData, err = mysqlRepo.Get(repo.WithByID(req.ID)) + if err != nil { + return err + } + passwordInfo.Name = mysqlData.Name + passwordInfo.Username = mysqlData.Username + passwordInfo.Permission = mysqlData.Permission + } else { + passwordInfo.Username = "root" + } + if err := cli.ChangePassword(passwordInfo); err != nil { + return err + } + + if req.ID != 0 { + var appRess []model.AppInstallResource + if req.From == "local" { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database) + if err != nil { + return err + } + appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(mysqlData.ID)) + } else { + appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(mysqlData.ID)) + } + for _, appRes := range appRess { + appInstall, err := appInstallRepo.GetFirst(repo.WithByID(appRes.AppInstallId)) + if err != nil { + return err + } + appModel, err := appRepo.GetFirst(repo.WithByID(appInstall.AppId)) + if err != nil { + return err + } + + global.LOG.Infof("start to update mysql password used by app %s-%s", appModel.Key, appInstall.Name) + if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil { + return err + } + } + global.LOG.Info("execute password change sql successful") + pass, err := encrypt.StringEncrypt(req.Value) + if err != nil { + return fmt.Errorf("decrypt database db password failed, err: %v", err) + } + _ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"password": pass}) + return nil + } + + if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil { + return err + } + if req.From == "local" { + remote, err := databaseRepo.Get(repo.WithByName(req.Database)) + if err != nil { + return err + } + pass, err := encrypt.StringEncrypt(req.Value) + if err != nil { + return fmt.Errorf("decrypt database password failed, err: %v", err) + } + _ = databaseRepo.Update(remote.ID, map[string]interface{}{"password": pass}) + } + return nil +} + +func (u *MysqlService) ChangeAccess(req dto.ChangeDBInfo) error { + if cmd.CheckIllegal(req.Value) { + return buserr.New("ErrCmdIllegal") + } + cli, version, err := LoadMysqlClientByFrom(req.Database) + if err != nil { + return err + } + defer cli.Close() + var ( + mysqlData model.DatabaseMysql + accessInfo client.AccessChangeInfo + ) + accessInfo.Permission = req.Value + accessInfo.Timeout = 300 + accessInfo.Version = version + + if req.ID != 0 { + mysqlData, err = mysqlRepo.Get(repo.WithByID(req.ID)) + if err != nil { + return err + } + accessInfo.Name = mysqlData.Name + accessInfo.Username = mysqlData.Username + accessInfo.Password = mysqlData.Password + accessInfo.OldPermission = mysqlData.Permission + } else { + accessInfo.Username = "root" + } + if err := cli.ChangeAccess(accessInfo); err != nil { + return err + } + + if mysqlData.ID != 0 { + _ = mysqlRepo.Update(mysqlData.ID, map[string]interface{}{"permission": req.Value}) + } + + return nil +} + +func (u *MysqlService) UpdateVariables(req dto.MysqlVariablesUpdate) error { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database) + if err != nil { + return err + } + var files []string + + path := fmt.Sprintf("%s/%s/%s/conf/my.cnf", global.Dir.AppInstallDir, req.Type, app.Name) + lineBytes, err := os.ReadFile(path) + if err != nil { + return err + } + files = strings.Split(string(lineBytes), "\n") + + group := "[mysqld]" + for _, info := range req.Variables { + if info.Param == "slow_query_log" && info.Value == "ON" { + logFilePath := filepath.Join(global.Dir.DataDir, fmt.Sprintf("apps/%s/%s/data/1Panel-slow.log", app.Key, app.Name)) + if req.Type == "mariadb" { + logFilePath = filepath.Join(global.Dir.DataDir, fmt.Sprintf("apps/%s/%s/db/data/1Panel-slow.log", app.Key, app.Name)) + } + file, err := os.OpenFile(logFilePath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0644) + if err != nil { + return err + } + defer file.Close() + } + if !strings.HasPrefix(app.Version, "5.7") && !strings.HasPrefix(app.Version, "5.6") { + if info.Param == "query_cache_size" { + continue + } + } + + if _, ok := info.Value.(float64); ok { + files = updateMyCnf(files, group, info.Param, common.LoadSizeUnit(info.Value.(float64))) + } else { + files = updateMyCnf(files, group, info.Param, info.Value) + } + } + file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(strings.Join(files, "\n")) + if err != nil { + return err + } + + if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, req.Type, app.Name)); err != nil { + return err + } + + return nil +} + +func (u *MysqlService) LoadRemoteAccess(req dto.OperationWithNameAndType) (bool, error) { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return false, err + } + hosts, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, "select host from mysql.user where user='root';") + if err != nil { + return false, err + } + for _, host := range hosts { + if host == "%" { + return true, nil + } + } + + return false, nil +} + +func (u *MysqlService) LoadVariables(req dto.OperationWithNameAndType) (*dto.MysqlVariables, error) { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return nil, err + } + variableMap, err := executeSqlForMaps(app.ContainerName, app.Key, app.Password, "show global variables;") + if err != nil { + return nil, err + } + var info dto.MysqlVariables + arr, err := json.Marshal(variableMap) + if err != nil { + return nil, err + } + _ = json.Unmarshal(arr, &info) + return &info, nil +} + +func (u *MysqlService) LoadStatus(req dto.OperationWithNameAndType) (*dto.MysqlStatus, error) { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return nil, err + } + + statusMap, err := executeSqlForMaps(app.ContainerName, app.Key, app.Password, "show global status;") + if err != nil { + return nil, err + } + + var info dto.MysqlStatus + arr, err := json.Marshal(statusMap) + if err != nil { + return nil, err + } + _ = json.Unmarshal(arr, &info) + + if value, ok := statusMap["Run"]; ok { + uptime, _ := strconv.Atoi(value) + info.Run = time.Unix(time.Now().Unix()-int64(uptime), 0).Format(constant.DateTimeLayout) + } else { + if value, ok := statusMap["Uptime"]; ok { + uptime, _ := strconv.Atoi(value) + info.Run = time.Unix(time.Now().Unix()-int64(uptime), 0).Format(constant.DateTimeLayout) + } + } + + info.File = "OFF" + info.Position = "OFF" + masterStatus := "show master status;" + if common.CompareAppVersion(app.Version, "8.4.0") && (req.Type == constant.AppMysql || req.Type == constant.AppMysqlCluster) { + masterStatus = "show binary log status;" + } + rows, err := executeSqlForRows(app.ContainerName, app.Key, app.Password, masterStatus) + if err != nil { + return nil, err + } + if len(rows) > 2 { + itemValue := strings.Split(rows[1], "\t") + if len(itemValue) > 2 { + info.File = itemValue[0] + info.Position = itemValue[1] + } + } + + return &info, nil +} + +func (u *MysqlService) LoadFormatOption(req dto.OperationWithName) []dto.MysqlFormatCollationOption { + defaultList := []dto.MysqlFormatCollationOption{{Format: "utf8mb4"}, {Format: "utf8mb3"}, {Format: "gbk"}, {Format: "big5"}} + client, _, err := LoadMysqlClientByFrom(req.Name) + if err != nil { + return defaultList + } + options, err := client.LoadFormatCollation(3) + if err != nil { + return defaultList + } + return options +} + +func executeSqlForMaps(containerName, dbType, password, command string) (map[string]string, error) { + if dbType == "mysql-cluster" { + dbType = "mysql" + } + cmd := exec.Command("docker", "exec", containerName, dbType, "-uroot", "-p"+password, "-e", command) + stdout, err := cmd.CombinedOutput() + stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "") + if err != nil || strings.HasPrefix(stdStr, "ERROR ") { + return nil, errors.New(stdStr) + } + + rows := strings.Split(stdStr, "\n") + rowMap := make(map[string]string) + for _, v := range rows { + itemRow := strings.Split(v, "\t") + if len(itemRow) == 2 { + rowMap[itemRow[0]] = itemRow[1] + } + } + return rowMap, nil +} + +func executeSqlForRows(containerName, dbType, password, command string) ([]string, error) { + if dbType == "mysql-cluster" { + dbType = "mysql" + } + cmd := exec.Command("docker", "exec", containerName, dbType, "-uroot", "-p"+password, "-e", command) + stdout, err := cmd.CombinedOutput() + stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "") + if err != nil || strings.HasPrefix(stdStr, "ERROR ") { + return nil, errors.New(stdStr) + } + return strings.Split(stdStr, "\n"), nil +} + +func updateMyCnf(oldFiles []string, group string, param string, value interface{}) []string { + isOn := false + hasGroup := false + hasKey := false + regItem := re.GetRegex(re.MysqlGroupPattern) + var newFiles []string + i := 0 + for _, line := range oldFiles { + i++ + if strings.HasPrefix(line, group) { + isOn = true + hasGroup = true + newFiles = append(newFiles, line) + continue + } + if !isOn { + newFiles = append(newFiles, line) + continue + } + if strings.HasPrefix(line, param+"=") || strings.HasPrefix(line, "# "+param+"=") { + newFiles = append(newFiles, fmt.Sprintf("%s=%v", param, value)) + hasKey = true + continue + } + if regItem.Match([]byte(line)) || i == len(oldFiles) { + isOn = false + if !hasKey { + newFiles = append(newFiles, fmt.Sprintf("%s=%v", param, value)) + } + newFiles = append(newFiles, line) + continue + } + newFiles = append(newFiles, line) + } + if !hasGroup { + newFiles = append(newFiles, group+"\n") + newFiles = append(newFiles, fmt.Sprintf("%s=%v\n", param, value)) + } + return newFiles +} + +func LoadMysqlClientByFrom(database string) (mysql.MysqlClient, string, error) { + var ( + dbInfo client.DBInfo + version string + err error + ) + + dbInfo.Timeout = 300 + databaseItem, err := databaseRepo.Get(repo.WithByName(database)) + if err != nil { + return nil, "", err + } + dbInfo.Type = databaseItem.Type + dbInfo.From = databaseItem.From + dbInfo.Database = database + if dbInfo.From != "local" { + dbInfo.Address = databaseItem.Address + dbInfo.Port = databaseItem.Port + dbInfo.Username = databaseItem.Username + dbInfo.Password = databaseItem.Password + dbInfo.SSL = databaseItem.SSL + dbInfo.ClientKey = databaseItem.ClientKey + dbInfo.ClientCert = databaseItem.ClientCert + dbInfo.RootCert = databaseItem.RootCert + dbInfo.SkipVerify = databaseItem.SkipVerify + version = databaseItem.Version + + } else { + app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database) + if err != nil { + return nil, "", err + } + dbInfo.Address = app.ContainerName + dbInfo.Username = "root" + dbInfo.Password = app.Password + version = app.Version + } + + cli, err := mysql.NewMysqlClient(dbInfo) + if err != nil { + return nil, "", err + } + return cli, version, nil +} diff --git a/agent/app/service/database_postgresql.go b/agent/app/service/database_postgresql.go new file mode 100644 index 0000000..9bf6eca --- /dev/null +++ b/agent/app/service/database_postgresql.go @@ -0,0 +1,451 @@ +package service + +import ( + "context" + "fmt" + "os" + "path" + "strings" + + "github.com/1Panel-dev/1Panel/agent/constant" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/postgresql" + "github.com/1Panel-dev/1Panel/agent/utils/postgresql/client" + _ "github.com/jackc/pgx/v5/stdlib" + "github.com/jinzhu/copier" +) + +type PostgresqlService struct{} + +type IPostgresqlService interface { + SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error) + ListDBOption() ([]dto.PostgresqlOption, error) + BindUser(req dto.PostgresqlBindUser) error + Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error) + LoadFromRemote(database string) error + ChangePrivileges(req dto.PostgresqlPrivileges) error + ChangePassword(info dto.ChangeDBInfo) error + UpdateDescription(req dto.UpdateDescription) error + DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]dto.DBResource, error) + Delete(ctx context.Context, req dto.PostgresqlDBDelete) error +} + +func NewIPostgresqlService() IPostgresqlService { + return &PostgresqlService{} +} + +func (u *PostgresqlService) SearchWithPage(search dto.PostgresqlDBSearch) (int64, interface{}, error) { + total, postgresqls, err := postgresqlRepo.Page(search.Page, search.PageSize, + postgresqlRepo.WithByPostgresqlName(search.Database), + repo.WithByLikeName(search.Info), + repo.WithOrderRuleBy(search.OrderBy, search.Order), + ) + var dtoPostgresqls []dto.PostgresqlDBInfo + for _, pg := range postgresqls { + var item dto.PostgresqlDBInfo + if err := copier.Copy(&item, &pg); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + dtoPostgresqls = append(dtoPostgresqls, item) + } + return total, dtoPostgresqls, err +} + +func (u *PostgresqlService) ListDBOption() ([]dto.PostgresqlOption, error) { + postgresqls, err := postgresqlRepo.List() + if err != nil { + return nil, err + } + + databases, err := databaseRepo.GetList(databaseRepo.WithTypeList("postgresql,mariadb")) + if err != nil { + return nil, err + } + var dbs []dto.PostgresqlOption + for _, pg := range postgresqls { + var item dto.PostgresqlOption + if err := copier.Copy(&item, &pg); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + item.Database = pg.PostgresqlName + for _, database := range databases { + if database.Name == item.Database { + item.Type = database.Type + } + } + dbs = append(dbs, item) + } + return dbs, err +} + +func (u *PostgresqlService) BindUser(req dto.PostgresqlBindUser) error { + if cmd.CheckIllegal(req.Name, req.Username, req.Password) { + return buserr.New("ErrCmdIllegal") + } + dbItem, err := postgresqlRepo.Get(postgresqlRepo.WithByPostgresqlName(req.Database), repo.WithByName(req.Name)) + if err != nil { + return err + } + + pgClient, err := LoadPostgresqlClientByFrom(req.Database) + if err != nil { + return err + } + if err := pgClient.CreateUser(client.CreateInfo{ + Name: req.Name, + Username: req.Username, + Password: req.Password, + SuperUser: req.SuperUser, + Timeout: 300, + }, false); err != nil { + return err + } + pass, err := encrypt.StringEncrypt(req.Password) + if err != nil { + return fmt.Errorf("decrypt database db password failed, err: %v", err) + } + if err := postgresqlRepo.Update(dbItem.ID, map[string]interface{}{ + "username": req.Username, + "password": pass, + "super_user": req.SuperUser, + }); err != nil { + return err + } + return nil +} + +func (u *PostgresqlService) Create(ctx context.Context, req dto.PostgresqlDBCreate) (*model.DatabasePostgresql, error) { + if cmd.CheckIllegal(req.Name, req.Username, req.Password, req.Format) { + return nil, buserr.New("ErrCmdIllegal") + } + + pgsql, _ := postgresqlRepo.Get(repo.WithByName(req.Name), postgresqlRepo.WithByPostgresqlName(req.Database), repo.WithByFrom(req.From)) + if pgsql.ID != 0 { + return nil, buserr.New("ErrRecordExist") + } + + var createItem model.DatabasePostgresql + if err := copier.Copy(&createItem, &req); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + + cli, err := LoadPostgresqlClientByFrom(req.Database) + if err != nil { + return nil, err + } + defer cli.Close() + + createItem.PostgresqlName = req.Database + defer cli.Close() + if err := cli.Create(client.CreateInfo{ + Name: req.Name, + Username: req.Username, + Password: req.Password, + SuperUser: req.SuperUser, + Timeout: 300, + }); err != nil { + return nil, err + } + + global.LOG.Infof("create database %s successful!", req.Name) + if err := postgresqlRepo.Create(ctx, &createItem); err != nil { + return nil, err + } + return &createItem, nil +} + +func LoadPostgresqlClientByFrom(database string) (postgresql.PostgresqlClient, error) { + var ( + dbInfo client.DBInfo + err error + ) + + dbInfo.Timeout = 300 + databaseItem, err := databaseRepo.Get(repo.WithByName(database)) + if err != nil { + return nil, err + } + dbInfo.From = databaseItem.From + dbInfo.Database = database + if dbInfo.From != "local" { + dbInfo.Address = databaseItem.Address + dbInfo.Port = databaseItem.Port + dbInfo.Username = databaseItem.Username + dbInfo.Password = databaseItem.Password + dbInfo.InitialDB = databaseItem.InitialDB + } else { + app, err := appInstallRepo.LoadBaseInfo(databaseItem.Type, database) + if err != nil { + return nil, err + } + dbInfo.From = "local" + dbInfo.Address = app.ContainerName + dbInfo.Username = app.UserName + dbInfo.Password = app.Password + dbInfo.Port = uint(app.Port) + } + + cli, err := postgresql.NewPostgresqlClient(dbInfo) + if err != nil { + return nil, err + } + return cli, nil +} + +func (u *PostgresqlService) LoadFromRemote(database string) error { + client, err := LoadPostgresqlClientByFrom(database) + if err != nil { + return err + } + defer client.Close() + + databases, err := postgresqlRepo.List(postgresqlRepo.WithByPostgresqlName(database)) + if err != nil { + return err + } + datas, err := client.SyncDB() + if err != nil { + return err + } + deleteList := databases + for _, data := range datas { + hasOld := false + for i := 0; i < len(databases); i++ { + if strings.EqualFold(databases[i].Name, data.Name) && strings.EqualFold(databases[i].PostgresqlName, data.PostgresqlName) { + hasOld = true + if databases[i].IsDelete { + _ = postgresqlRepo.Update(databases[i].ID, map[string]interface{}{"is_delete": false}) + } + deleteList = append(deleteList[:i], deleteList[i+1:]...) + break + } + } + if !hasOld { + var createItem model.DatabasePostgresql + if err := copier.Copy(&createItem, &data); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if err := postgresqlRepo.Create(context.Background(), &createItem); err != nil { + return err + } + } + } + for _, delItem := range deleteList { + _ = postgresqlRepo.Update(delItem.ID, map[string]interface{}{"is_delete": true}) + } + return nil +} + +func (u *PostgresqlService) UpdateDescription(req dto.UpdateDescription) error { + return postgresqlRepo.Update(req.ID, map[string]interface{}{"description": req.Description}) +} + +func (u *PostgresqlService) DeleteCheck(req dto.PostgresqlDBDeleteCheck) ([]dto.DBResource, error) { + var res []dto.DBResource + db, err := postgresqlRepo.Get(repo.WithByID(req.ID)) + if err != nil { + return res, err + } + + website, _ := websiteRepo.GetFirst(websiteRepo.WithDBType(constant.AppPostgresql), websiteRepo.WithDBID(req.ID)) + if website.ID != 0 { + res = append(res, dto.DBResource{ + Type: constant.TypeWebsite, + Name: website.PrimaryDomain, + }) + } + website, _ = websiteRepo.GetFirst(websiteRepo.WithDBType(constant.AppPostgresqlCluster), websiteRepo.WithDBID(req.ID)) + if website.ID != 0 { + res = append(res, dto.DBResource{ + Type: constant.TypeWebsite, + Name: website.PrimaryDomain, + }) + } + + if db.From == "local" { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database) + if err != nil { + return res, err + } + apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(db.ID)) + for _, app := range apps { + appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId)) + if appInstall.ID != 0 { + res = append(res, dto.DBResource{ + Type: constant.TypeApp, + Name: appInstall.Name, + }) + } + } + } else { + apps, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(db.ID), appRepo.WithKey(req.Type)) + for _, app := range apps { + appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(app.AppInstallId)) + if appInstall.ID != 0 { + res = append(res, dto.DBResource{ + Type: constant.TypeApp, + Name: appInstall.Name, + }) + } + } + } + + return res, nil +} + +func (u *PostgresqlService) Delete(ctx context.Context, req dto.PostgresqlDBDelete) error { + db, err := postgresqlRepo.Get(repo.WithByID(req.ID)) + if err != nil && !req.ForceDelete { + return err + } + cli, err := LoadPostgresqlClientByFrom(req.Database) + if err != nil { + return err + } + defer cli.Close() + if err := cli.Delete(client.DeleteInfo{ + Name: db.Name, + Username: db.Username, + ForceDelete: req.ForceDelete, + Timeout: 300, + }); err != nil && !req.ForceDelete { + if strings.HasPrefix(err.Error(), "drop user") { + _ = postgresqlRepo.Update(db.ID, map[string]interface{}{"is_delete": true}) + } + return err + } + + if req.DeleteBackup { + uploadDir := path.Join(global.Dir.DataDir, fmt.Sprintf("uploads/database/%s/%s/%s", req.Type, req.Database, db.Name)) + if _, err := os.Stat(uploadDir); err == nil { + _ = os.RemoveAll(uploadDir) + } + backupDir := path.Join(global.Dir.LocalBackupDir, fmt.Sprintf("database/%s/%s/%s", req.Type, db.PostgresqlName, db.Name)) + if _, err := os.Stat(backupDir); err == nil { + _ = os.RemoveAll(backupDir) + } + _ = backupRepo.DeleteRecord(ctx, repo.WithByType(req.Type), repo.WithByName(req.Database), repo.WithByDetailName(db.Name)) + global.LOG.Infof("delete database %s-%s backups successful", req.Database, db.Name) + } + + _ = postgresqlRepo.Delete(ctx, repo.WithByID(db.ID)) + return nil +} + +func (u *PostgresqlService) ChangePrivileges(req dto.PostgresqlPrivileges) error { + if cmd.CheckIllegal(req.Database, req.Username) { + return buserr.New("ErrCmdIllegal") + } + dbItem, err := postgresqlRepo.Get(postgresqlRepo.WithByPostgresqlName(req.Database), repo.WithByName(req.Name)) + if err != nil { + return err + } + cli, err := LoadPostgresqlClientByFrom(req.Database) + if err != nil { + return err + } + defer cli.Close() + + if err := cli.ChangePrivileges(client.Privileges{Username: req.Username, SuperUser: req.SuperUser, Timeout: 300}); err != nil { + return err + } + if err := postgresqlRepo.Update(dbItem.ID, map[string]interface{}{ + "super_user": req.SuperUser, + }); err != nil { + return err + } + return nil +} + +func (u *PostgresqlService) ChangePassword(req dto.ChangeDBInfo) error { + if cmd.CheckIllegal(req.Value) { + return buserr.New("ErrCmdIllegal") + } + cli, err := LoadPostgresqlClientByFrom(req.Database) + if err != nil { + return err + } + defer cli.Close() + var ( + postgresqlData model.DatabasePostgresql + passwordInfo client.PasswordChangeInfo + ) + passwordInfo.Password = req.Value + passwordInfo.Timeout = 300 + + if req.ID != 0 { + postgresqlData, err = postgresqlRepo.Get(repo.WithByID(req.ID)) + if err != nil { + return err + } + passwordInfo.Username = postgresqlData.Username + } else { + dbItem, err := databaseRepo.Get(repo.WithByType(req.Type), repo.WithByFrom(req.From)) + if err != nil { + return err + } + passwordInfo.Username = dbItem.Username + } + if err := cli.ChangePassword(passwordInfo); err != nil { + return err + } + + if req.ID != 0 { + var appRess []model.AppInstallResource + if req.From == "local" { + app, err := appInstallRepo.LoadBaseInfo(req.Type, req.Database) + if err != nil { + return err + } + appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithLinkId(app.ID), appInstallResourceRepo.WithResourceId(postgresqlData.ID)) + } else { + appRess, _ = appInstallResourceRepo.GetBy(appInstallResourceRepo.WithResourceId(postgresqlData.ID)) + } + for _, appRes := range appRess { + appInstall, err := appInstallRepo.GetFirst(repo.WithByID(appRes.AppInstallId)) + if err != nil { + return err + } + appModel, err := appRepo.GetFirst(repo.WithByID(appInstall.AppId)) + if err != nil { + return err + } + + global.LOG.Infof("start to update postgresql password used by app %s-%s", appModel.Key, appInstall.Name) + if err := updateInstallInfoInDB(appModel.Key, appInstall.Name, "user-password", req.Value); err != nil { + return err + } + } + global.LOG.Info("execute password change sql successful") + pass, err := encrypt.StringEncrypt(req.Value) + if err != nil { + return fmt.Errorf("decrypt database db password failed, err: %v", err) + } + _ = postgresqlRepo.Update(postgresqlData.ID, map[string]interface{}{"password": pass}) + return nil + } + + if err := updateInstallInfoInDB(req.Type, req.Database, "password", req.Value); err != nil { + return err + } + if req.From == "local" { + remote, err := databaseRepo.Get(repo.WithByName(req.Database)) + if err != nil { + return err + } + pass, err := encrypt.StringEncrypt(req.Value) + if err != nil { + return fmt.Errorf("decrypt database password failed, err: %v", err) + } + _ = databaseRepo.Update(remote.ID, map[string]interface{}{"password": pass}) + } + return nil +} diff --git a/agent/app/service/database_redis.go b/agent/app/service/database_redis.go new file mode 100644 index 0000000..c895cfa --- /dev/null +++ b/agent/app/service/database_redis.go @@ -0,0 +1,301 @@ +package service + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/global" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/docker/docker/api/types/container" + _ "github.com/go-sql-driver/mysql" +) + +type RedisService struct{} + +type IRedisService interface { + UpdateConf(req dto.RedisConfUpdate) error + UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error + ChangePassword(info dto.ChangeRedisPass) error + + LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error) + LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error) + LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error) + + CheckHasCli() bool + InstallCli() error +} + +func NewIRedisService() IRedisService { + return &RedisService{} +} + +func (u *RedisService) UpdateConf(req dto.RedisConfUpdate) error { + redisInfo, err := appInstallRepo.LoadBaseInfo(req.DBType, req.Database) + if err != nil { + return err + } + + var confs []redisConfig + confs = append(confs, redisConfig{key: "timeout", value: req.Timeout}) + confs = append(confs, redisConfig{key: "maxclients", value: req.Maxclients}) + confs = append(confs, redisConfig{key: "maxmemory", value: req.Maxmemory}) + if err := confSet(redisInfo.Name, req.DBType, "", confs); err != nil { + return err + } + if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, req.DBType, redisInfo.Name)); err != nil { + return err + } + + return nil +} + +func (u *RedisService) CheckHasCli() bool { + client, err := docker.NewDockerClient() + if err != nil { + return false + } + defer client.Close() + containerLists, err := client.ContainerList(context.Background(), container.ListOptions{}) + if err != nil { + return false + } + for _, item := range containerLists { + if strings.ReplaceAll(item.Names[0], "/", "") == "1Panel-redis-cli-tools" { + return true + } + } + return false +} + +func (u *RedisService) InstallCli() error { + item := dto.ContainerOperate{ + Name: "1Panel-redis-cli-tools", + Image: "redis:7.4.4", + Networks: []dto.ContainerNetwork{{Network: "1panel-network"}}, + } + return NewIContainerService().ContainerCreate(item, false) +} + +func (u *RedisService) ChangePassword(req dto.ChangeRedisPass) error { + if err := updateInstallInfoInDB("redis", req.Database, "password", req.Value); err != nil { + return err + } + remote, err := databaseRepo.Get(repo.WithByName(req.Database)) + if err != nil { + return err + } + if remote.From == "local" { + pass, err := encrypt.StringEncrypt(req.Value) + if err != nil { + return fmt.Errorf("decrypt database password failed, err: %v", err) + } + _ = databaseRepo.Update(remote.ID, map[string]interface{}{"password": pass}) + } + + return nil +} + +func (u *RedisService) UpdatePersistenceConf(req dto.RedisConfPersistenceUpdate) error { + redisInfo, err := appInstallRepo.LoadBaseInfo(req.DBType, req.Database) + if err != nil { + return err + } + + var confs []redisConfig + if req.Type == "rbd" { + confs = append(confs, redisConfig{key: "save", value: req.Save}) + } else { + confs = append(confs, redisConfig{key: "appendonly", value: req.Appendonly}) + confs = append(confs, redisConfig{key: "appendfsync", value: req.Appendfsync}) + } + if err := confSet(redisInfo.Name, req.DBType, req.Type, confs); err != nil { + return err + } + if _, err := compose.Restart(fmt.Sprintf("%s/%s/%s/docker-compose.yml", global.Dir.AppInstallDir, req.DBType, redisInfo.Name)); err != nil { + return err + } + + return nil +} + +func (u *RedisService) LoadStatus(req dto.LoadRedisStatus) (*dto.RedisStatus, error) { + redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return nil, err + } + commands := append(redisExec(redisInfo.ContainerName, redisInfo.Password), "info") + cmd := exec.Command("docker", commands...) + stdout, err := cmd.CombinedOutput() + if err != nil { + return nil, errors.New(string(stdout)) + } + rows := strings.Split(string(stdout), "\r\n") + rowMap := make(map[string]string) + for _, v := range rows { + itemRow := strings.Split(v, ":") + if len(itemRow) == 2 { + rowMap[itemRow[0]] = itemRow[1] + } + } + var info dto.RedisStatus + arr, err := json.Marshal(rowMap) + if err != nil { + return nil, err + } + _ = json.Unmarshal(arr, &info) + return &info, nil +} + +func (u *RedisService) LoadConf(req dto.LoadRedisStatus) (*dto.RedisConf, error) { + redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return nil, err + } + + var item dto.RedisConf + item.ContainerName = redisInfo.ContainerName + item.Name = redisInfo.Name + item.Port = redisInfo.Port + item.Requirepass = redisInfo.Password + item.Timeout, _ = configGetStr(redisInfo.ContainerName, redisInfo.Password, "timeout") + item.Maxclients, _ = configGetStr(redisInfo.ContainerName, redisInfo.Password, "maxclients") + item.Maxmemory, _ = configGetStr(redisInfo.ContainerName, redisInfo.Password, "maxmemory") + return &item, nil +} + +func (u *RedisService) LoadPersistenceConf(req dto.LoadRedisStatus) (*dto.RedisPersistence, error) { + redisInfo, err := appInstallRepo.LoadBaseInfo(req.Type, req.Name) + if err != nil { + return nil, err + } + var item dto.RedisPersistence + if item.Appendonly, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendonly"); err != nil { + return nil, err + } + if item.Appendfsync, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "appendfsync"); err != nil { + return nil, err + } + if item.Save, err = configGetStr(redisInfo.ContainerName, redisInfo.Password, "save"); err != nil { + return nil, err + } + return &item, nil +} + +func configGetStr(containerName, password, param string) (string, error) { + commands := append(redisExec(containerName, password), []string{"config", "get", param}...) + cmd := exec.Command("docker", commands...) + stdout, err := cmd.CombinedOutput() + if err != nil { + return "", errors.New(string(stdout)) + } + rows := strings.Split(string(stdout), "\r\n") + for _, v := range rows { + itemRow := strings.Split(v, "\n") + if len(itemRow) == 3 { + return itemRow[1], nil + } + } + return "", nil +} + +type redisConfig struct { + key string + value string +} + +func confSet(redisName string, redisType string, updateType string, changeConf []redisConfig) error { + path := fmt.Sprintf("%s/%s/%s/conf/redis.conf", global.Dir.AppInstallDir, redisType, redisName) + lineBytes, err := os.ReadFile(path) + if err != nil { + return err + } + files := strings.Split(string(lineBytes), "\n") + + startIndex, endIndex, emptyLine := 0, 0, 0 + var newFiles []string + for i := 0; i < len(files); i++ { + if files[i] == "# Redis configuration rewrite by 1Panel" { + startIndex = i + newFiles = append(newFiles, files[i]) + continue + } + if files[i] == "# End Redis configuration rewrite by 1Panel" { + endIndex = i + break + } + if startIndex == 0 && strings.HasPrefix(files[i], "save ") { + newFiles = append(newFiles, "# "+files[i]) + continue + } + if startIndex != 0 && endIndex == 0 && (len(files[i]) == 0 || (updateType == "rbd" && strings.HasPrefix(files[i], "save "))) { + emptyLine++ + continue + } + newFiles = append(newFiles, files[i]) + } + if startIndex == 0 { + newFiles = append(newFiles, "# Redis configuration rewrite by 1Panel") + startIndex = len(newFiles) - 1 + } + endIndex = endIndex - emptyLine + for _, item := range changeConf { + if item.key == "save" { + saveVal := strings.Split(item.value, ",") + for i := 0; i < len(saveVal); i++ { + newFiles = append(newFiles, "save "+saveVal[i]) + } + continue + } + + isExist := false + for i := startIndex; i < endIndex; i++ { + if strings.HasPrefix(newFiles[i], item.key) || strings.HasPrefix(newFiles[i], "# "+item.key) { + if item.value == "0" || len(item.value) == 0 { + newFiles[i] = fmt.Sprintf("# %s %s", item.key, item.value) + } else { + newFiles[i] = fmt.Sprintf("%s %s", item.key, item.value) + } + isExist = true + break + } + } + if !isExist { + if item.value == "0" || len(item.value) == 0 { + newFiles = append(newFiles, fmt.Sprintf("# %s %s", item.key, item.value)) + } else { + newFiles = append(newFiles, fmt.Sprintf("%s %s", item.key, item.value)) + } + } + } + newFiles = append(newFiles, "# End Redis configuration rewrite by 1Panel") + + file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(strings.Join(newFiles, "\n")) + if err != nil { + return err + } + return nil +} + +func redisExec(containerName, password string) []string { + cmds := []string{"exec", containerName, "redis-cli", "-a", password, "--no-auth-warning"} + if len(password) == 0 { + cmds = []string{"exec", containerName, "redis-cli"} + } + return cmds +} diff --git a/agent/app/service/device.go b/agent/app/service/device.go new file mode 100644 index 0000000..5795098 --- /dev/null +++ b/agent/app/service/device.go @@ -0,0 +1,461 @@ +package service + +import ( + "bufio" + "fmt" + "net" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/ntp" + "github.com/1Panel-dev/1Panel/agent/utils/psutil" + "github.com/shirou/gopsutil/v4/mem" +) + +const defaultDNSPath = "/etc/resolv.conf" +const defaultHostPath = "/etc/hosts" +const defaultFstab = "/etc/fstab" + +type DeviceService struct{} + +type IDeviceService interface { + LoadBaseInfo() (dto.DeviceBaseInfo, error) + Update(key, value string) error + UpdateHosts(req []dto.HostHelper) error + UpdatePasswd(req dto.ChangePasswd) error + UpdateSwap(req dto.SwapHelper) error + UpdateByConf(req dto.UpdateByNameAndFile) error + LoadTimeZone() ([]string, error) + CheckDNS(key, value string) (bool, error) + LoadConf(name string) (string, error) + LoadUsers() ([]string, error) + + Scan() dto.CleanData + Clean(req []dto.Clean) +} + +func NewIDeviceService() IDeviceService { + return &DeviceService{} +} + +func (u *DeviceService) LoadBaseInfo() (dto.DeviceBaseInfo, error) { + var baseInfo dto.DeviceBaseInfo + baseInfo.LocalTime = time.Now().Format("2006-01-02 15:04:05 MST -0700") + baseInfo.TimeZone = common.LoadTimeZoneByCmd() + baseInfo.DNS = loadDNS() + baseInfo.Hosts = loadHosts() + baseInfo.Hostname = loadHostname() + baseInfo.User = loadUser() + ntp, err := settingRepo.Get(settingRepo.WithByKey("NtpSite")) + if err == nil { + baseInfo.Ntp = ntp.Value + } + + swapInfo, err := mem.SwapMemory() + if err != nil { + return baseInfo, err + } + baseInfo.SwapMemoryTotal = swapInfo.Total + baseInfo.SwapMemoryAvailable = swapInfo.Free + baseInfo.SwapMemoryUsed = swapInfo.Used + if baseInfo.SwapMemoryTotal != 0 { + baseInfo.SwapDetails = loadSwap() + } + disks := loadDiskInfo() + for _, item := range disks { + baseInfo.MaxSize += item.Free + } + + return baseInfo, nil +} + +func (u *DeviceService) LoadTimeZone() ([]string, error) { + std, err := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Minute)).RunWithStdoutBashC("timedatectl list-timezones") + if err != nil { + return []string{}, err + } + return strings.Split(std, "\n"), nil +} + +func (u *DeviceService) CheckDNS(key, value string) (bool, error) { + content, err := os.ReadFile(defaultDNSPath) + if err != nil { + return false, err + } + defer func() { _ = u.UpdateByConf(dto.UpdateByNameAndFile{Name: "DNS", File: string(content)}) }() + if key == "form" { + if err := u.Update("DNS", value); err != nil { + return false, err + } + } else { + if err := u.UpdateByConf(dto.UpdateByNameAndFile{Name: "DNS", File: value}); err != nil { + return false, err + } + } + + conn, err := net.DialTimeout("ip4:icmp", "www.baidu.com", time.Second*2) + if err != nil { + return false, err + } + defer conn.Close() + + return true, nil +} + +func (u *DeviceService) Update(key, value string) error { + switch key { + case "TimeZone": + if cmd.CheckIllegal(value) { + return buserr.New("ErrCmdIllegal") + } + if err := ntp.UpdateSystemTimeZone(value); err != nil { + return err + } + go controller.RestartPanel(global.IsMaster, true, false) + case "DNS": + if err := updateDNS(strings.Split(value, ",")); err != nil { + return err + } + case "Hostname": + if cmd.CheckIllegal(value) { + return buserr.New("ErrCmdIllegal") + } + if err := cmd.RunDefaultBashCf("%s hostnamectl set-hostname %s", cmd.SudoHandleCmd(), value); err != nil { + return err + } + _, _ = psutil.HOST.GetHostInfo(true) + case "Ntp", "LocalTime": + if cmd.CheckIllegal(value) { + return buserr.New("ErrCmdIllegal") + } + ntpValue := value + if key == "LocalTime" { + ntpItem, err := settingRepo.Get(settingRepo.WithByKey("NtpSite")) + if err != nil { + return err + } + ntpValue = ntpItem.Value + } else { + if err := settingRepo.Update("NtpSite", ntpValue); err != nil { + return err + } + } + ntime, err := ntp.GetRemoteTime(ntpValue) + if err != nil { + return err + } + ts := ntime.Format(constant.DateTimeLayout) + if err := ntp.UpdateSystemTime(ts); err != nil { + return err + } + default: + return fmt.Errorf("not support such key %s", key) + } + return nil +} + +func (u *DeviceService) LoadUsers() ([]string, error) { + file, err := os.ReadFile("/etc/passwd") + if err != nil { + return nil, err + } + var users []string + lines := strings.Split(string(file), "\n") + for _, line := range lines { + if strings.Contains(line, ":") { + users = append(users, strings.Split(line, ":")[0]) + } + } + return users, nil +} + +func (u *DeviceService) UpdateHosts(req []dto.HostHelper) error { + conf, err := os.ReadFile(defaultHostPath) + if err != nil { + return fmt.Errorf("read namesever conf of %s failed, err: %v", defaultHostPath, err) + } + lines := strings.Split(string(conf), "\n") + newFile := "" + for _, line := range lines { + if len(line) == 0 { + continue + } + parts := strings.Fields(line) + if len(parts) < 2 { + newFile += line + "\n" + continue + } + for index, item := range req { + if item.IP == parts[0] && item.Host == strings.Join(parts[1:], " ") { + newFile += line + "\n" + req = append(req[:index], req[index+1:]...) + break + } + } + } + for _, item := range req { + newFile += fmt.Sprintf("%s %s \n", item.IP, item.Host) + } + file, err := os.OpenFile(defaultHostPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(newFile) + write.Flush() + return nil +} + +func (u *DeviceService) UpdatePasswd(req dto.ChangePasswd) error { + if cmd.CheckIllegal(req.User, req.Passwd) { + return buserr.New("ErrCmdIllegal") + } + if err := cmd.RunDefaultBashCf("%s echo '%s:%s' | %s chpasswd", cmd.SudoHandleCmd(), req.User, req.Passwd, cmd.SudoHandleCmd()); err != nil { + if strings.Contains(err.Error(), "does not exist") { + return buserr.New("ErrNotExistUser") + } + return err + } + return nil +} + +func (u *DeviceService) UpdateSwap(req dto.SwapHelper) error { + taskItem, err := task.NewTaskWithOps(req.Path, task.TaskSwapSet, task.TaskScopeSystem, req.TaskID, 1) + if err != nil { + global.LOG.Errorf("new task for create container failed, err: %v", err) + return err + } + + taskItem.AddSubTask(i18n.GetMsgWithMap("SetSwap", map[string]interface{}{"path": req.Path, "size": common.LoadSizeUnit2F(float64(req.Size * 1024))}), func(t *task.Task) error { + if cmd.CheckIllegal(req.Path) { + return buserr.New("ErrCmdIllegal") + } + cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*taskItem)) + if !req.IsNew { + if err := cmdMgr.RunBashCf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path); err != nil { + return fmt.Errorf("handle swapoff %s failed, %v", req.Path, err) + } + } + if req.Size == 0 { + if req.Path == path.Join(global.Dir.BaseDir, ".1panel_swap") { + _ = os.Remove(path.Join(global.Dir.BaseDir, ".1panel_swap")) + } + return operateSwapWithFile(true, req) + } + taskItem.LogStart(i18n.GetMsgByKey("CreateSwap")) + if err := cmdMgr.RunBashCf("%s dd if=/dev/zero of=%s bs=1024 count=%d", cmd.SudoHandleCmd(), req.Path, req.Size); err != nil { + return fmt.Errorf("handle dd %s failed, %v", req.Path, err) + } + + taskItem.Log("chmod 0600 " + req.Path) + if err := cmdMgr.RunBashCf("%s chmod 0600 %s", cmd.SudoHandleCmd(), req.Path); err != nil { + return fmt.Errorf("handle chmod 0600 %s failed, %v", req.Path, err) + } + taskItem.LogStart(i18n.GetMsgByKey("FormatSwap")) + if err := cmdMgr.RunBashCf("%s mkswap -f %s", cmd.SudoHandleCmd(), req.Path); err != nil { + return fmt.Errorf("handle mkswap -f %s failed, %v", req.Path, err) + } + + taskItem.LogStart(i18n.GetMsgByKey("EnableSwap")) + if err := cmdMgr.RunBashCf("%s swapon %s", cmd.SudoHandleCmd(), req.Path); err != nil { + _ = cmdMgr.RunBashCf("%s swapoff %s", cmd.SudoHandleCmd(), req.Path) + return fmt.Errorf("handle swapoff %s failed, %v", req.Path, err) + } + return operateSwapWithFile(false, req) + }, nil) + go func() { + _ = taskItem.Execute() + }() + return nil +} + +func (u *DeviceService) LoadConf(name string) (string, error) { + pathItem := "" + switch name { + case "DNS": + pathItem = defaultDNSPath + case "Hosts": + pathItem = defaultHostPath + default: + return "", fmt.Errorf("not support such name %s", name) + } + if _, err := os.Stat(pathItem); err != nil { + return "", err + } + content, err := os.ReadFile(pathItem) + if err != nil { + return "", err + } + return string(content), nil +} + +func (u *DeviceService) UpdateByConf(req dto.UpdateByNameAndFile) error { + if req.Name != "DNS" && req.Name != "Hosts" { + return fmt.Errorf("not support such name %s", req.Name) + } + path := defaultDNSPath + if req.Name == "Hosts" { + path = defaultHostPath + } + file, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(req.File) + write.Flush() + return nil +} + +func loadDNS() []string { + var list []string + dnsConf, err := os.ReadFile(defaultDNSPath) + if err == nil { + lines := strings.Split(string(dnsConf), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "nameserver ") { + list = append(list, strings.TrimPrefix(line, "nameserver ")) + } + } + } + return list +} + +func updateDNS(list []string) error { + conf, err := os.ReadFile(defaultDNSPath) + if err != nil { + return fmt.Errorf("read nameserver conf of %s failed, err: %v", defaultDNSPath, err) + } + lines := strings.Split(string(conf), "\n") + newFile := "" + for _, line := range lines { + if len(line) == 0 { + continue + } + parts := strings.Fields(line) + if len(parts) < 2 || parts[0] != "nameserver" { + newFile += line + "\n" + continue + } + itemNs := strings.Join(parts[1:], " ") + for index, item := range list { + if item == itemNs { + newFile += line + "\n" + list = append(list[:index], list[index+1:]...) + break + } + } + } + for _, item := range list { + if len(item) != 0 { + newFile += fmt.Sprintf("nameserver %s \n", item) + } + } + file, err := os.OpenFile(defaultDNSPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(newFile) + write.Flush() + return nil +} + +func loadHosts() []dto.HostHelper { + var list []dto.HostHelper + hostConf, err := os.ReadFile(defaultHostPath) + if err == nil { + lines := strings.Split(string(hostConf), "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) < 2 || strings.HasPrefix(strings.TrimPrefix(line, " "), "#") { + continue + } + list = append(list, dto.HostHelper{IP: parts[0], Host: strings.Join(parts[1:], " ")}) + } + } + return list +} + +func loadHostname() string { + std, err := cmd.RunDefaultWithStdoutBashC("hostname") + if err != nil { + return "" + } + return strings.ReplaceAll(std, "\n", "") +} + +func loadUser() string { + std, err := cmd.RunDefaultWithStdoutBashC("whoami") + if err != nil { + return "" + } + return strings.ReplaceAll(std, "\n", "") +} + +func loadSwap() []dto.SwapHelper { + var data []dto.SwapHelper + std, err := cmd.RunDefaultWithStdoutBashCf("%s swapon --summary", cmd.SudoHandleCmd()) + if err != nil { + return data + } + lines := strings.Split(std, "\n") + for index, line := range lines { + if index == 0 { + continue + } + parts := strings.Fields(line) + if len(parts) < 5 { + continue + } + sizeItem, _ := strconv.Atoi(parts[2]) + data = append(data, dto.SwapHelper{Path: parts[0], Size: uint64(sizeItem), Used: parts[3]}) + } + return data +} + +func operateSwapWithFile(delete bool, req dto.SwapHelper) error { + conf, err := os.ReadFile(defaultFstab) + if err != nil { + return fmt.Errorf("read file %s failed, err: %v", defaultFstab, err) + } + lines := strings.Split(string(conf), "\n") + newFile := "" + for _, line := range lines { + if len(line) == 0 { + continue + } + parts := strings.Fields(line) + if len(parts) == 6 && parts[0] == req.Path { + continue + } + newFile += line + "\n" + } + if !delete { + newFile += fmt.Sprintf("%s swap swap defaults 0 0\n", req.Path) + } + file, err := os.OpenFile(defaultFstab, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(newFile) + write.Flush() + return nil +} diff --git a/agent/app/service/device_clean.go b/agent/app/service/device_clean.go new file mode 100644 index 0000000..f5ea1fb --- /dev/null +++ b/agent/app/service/device_clean.go @@ -0,0 +1,1137 @@ +package service + +import ( + "context" + "fmt" + "os" + "path" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + fileUtils "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/build" + "github.com/docker/docker/api/types/filters" + "github.com/google/uuid" +) + +const ( + rollbackPath = "1panel/tmp" + upgradePath = "1panel/tmp/upgrade" + uploadPath = "1panel/uploads" + downloadPath = "1panel/download" +) + +func (u *DeviceService) Scan() dto.CleanData { + var ( + SystemClean dto.CleanData + treeData []dto.CleanTree + ) + fileOp := fileUtils.NewFileOp() + + originalPath := path.Join(global.Dir.BaseDir, "1panel_original") + originalSize, _ := fileOp.GetDirSize(originalPath) + treeData = append(treeData, dto.CleanTree{ + ID: uuid.NewString(), + Label: "1panel_original", + Size: uint64(originalSize), + IsCheck: originalSize > 0, + IsRecommend: true, + Type: "1panel_original", + Children: loadTreeWithDir(true, "1panel_original", originalPath, fileOp), + }) + treeData = append(treeData, loadUpgradeTree(fileOp)) + treeData = append(treeData, loadAgentPackage(fileOp)) + + SystemClean.BackupClean = loadBackupTree(fileOp) + + rollBackTree := loadRollBackTree(fileOp) + rollbackSize := uint64(0) + for _, rollback := range rollBackTree { + rollbackSize += rollback.Size + } + treeData = append(treeData, dto.CleanTree{ + ID: uuid.NewString(), + Label: "rollback", + Size: rollbackSize, + IsCheck: rollbackSize > 0, + IsRecommend: true, + Type: "rollback", + Children: rollBackTree, + }) + SystemClean.SystemClean = treeData + + uploadTreeData := loadUploadTree(fileOp) + SystemClean.UploadClean = append(SystemClean.UploadClean, uploadTreeData...) + + downloadTreeData := loadDownloadTree(fileOp) + SystemClean.DownloadClean = append(SystemClean.DownloadClean, downloadTreeData...) + + logTree := loadLogTree(fileOp) + SystemClean.SystemLogClean = append(SystemClean.SystemLogClean, logTree...) + + containerTree := loadContainerTree() + SystemClean.ContainerClean = append(SystemClean.ContainerClean, containerTree...) + + return SystemClean +} + +func (u *DeviceService) Clean(req []dto.Clean) { + size := uint64(0) + for _, item := range req { + size += item.Size + switch item.TreeType { + case "1panel_original": + dropFileOrDir(path.Join(global.Dir.BaseDir, "1panel_original", item.Name)) + + case "upgrade": + dropFileOrDir(path.Join(global.Dir.BaseDir, upgradePath, item.Name)) + + case "agent": + dropFileOrDir(path.Join(global.Dir.BaseDir, "1panel/agent/package", item.Name)) + + case "tmp_backup": + dropFileOrDir(path.Join(global.Dir.LocalBackupDir, "tmp")) + case "unknown_backup": + if strings.HasPrefix(item.Name, path.Join(global.Dir.LocalBackupDir, "log/website")) { + dropFileOrDir(item.Name) + } else { + dropFile(item.Name) + } + + case "rollback": + dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "app")) + dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "database")) + dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "website")) + case "rollback_app": + dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "app", item.Name)) + case "rollback_database": + dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "database", item.Name)) + case "rollback_website": + dropFileOrDir(path.Join(global.Dir.BaseDir, rollbackPath, "website", item.Name)) + + case "upload": + dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, item.Name)) + case "upload_app": + dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "app", item.Name)) + case "upload_database": + dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "database", item.Name)) + case "upload_website": + dropFileOrDir(path.Join(global.Dir.BaseDir, uploadPath, "website", item.Name)) + case "download": + dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, item.Name)) + case "download_app": + dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, "app", item.Name)) + case "download_database": + dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, "database", item.Name)) + case "download_website": + dropFileOrDir(path.Join(global.Dir.BaseDir, downloadPath, "website", item.Name)) + + case "system_log": + if len(item.Name) == 0 { + files, _ := os.ReadDir(global.Dir.LogDir) + if len(files) == 0 { + continue + } + for _, file := range files { + if file.Name() == "1Panel-Core.log" || file.Name() == "1Panel.log" || file.IsDir() { + continue + } + dropFileOrDir(path.Join(global.Dir.LogDir, file.Name())) + } + } else { + dropFileOrDir(path.Join(global.Dir.LogDir, item.Name)) + } + case "task_log": + if len(item.Name) == 0 { + files, _ := os.ReadDir(global.Dir.TaskDir) + if len(files) == 0 { + continue + } + for _, file := range files { + if file.Name() == "ssl" || !file.IsDir() { + continue + } + dropTaskLog(path.Join(global.Dir.TaskDir, file.Name())) + } + } else { + dropTaskLog(path.Join(global.Dir.TaskDir, item.Name)) + } + case "website_log": + dropWebsiteLog(item.Name) + case "script": + dropFileOrDir(path.Join(global.Dir.TmpDir, "script", item.Name)) + case "images": + _, _ = dropImages() + case "containers": + _, _ = dropContainers() + case "volumes": + _, _ = dropVolumes() + case "build_cache": + _, _ = dropBuildCache() + case "app_tmp_download_version": + dropFileOrDir(path.Join(global.Dir.RemoteAppResourceDir, item.Name)) + } + } + + _ = cleanEmptyDirs(global.Dir.LocalBackupDir) + _ = settingRepo.Update("LastCleanTime", time.Now().Format(constant.DateTimeLayout)) + _ = settingRepo.Update("LastCleanSize", fmt.Sprintf("%v", size)) + _ = settingRepo.Update("LastCleanData", fmt.Sprintf("%v", len(req))) +} + +func doSystemClean(taskItem *task.Task) func(t *task.Task) error { + return func(t *task.Task) error { + size := int64(0) + fileCount := 0 + dropWithTask(path.Join(global.Dir.BaseDir, "1panel_original"), taskItem, &size, &fileCount) + + upgradePath := path.Join(global.Dir.BaseDir, upgradePath) + upgradeFiles, _ := os.ReadDir(upgradePath) + if len(upgradeFiles) != 0 { + sort.Slice(upgradeFiles, func(i, j int) bool { + return upgradeFiles[i].Name() > upgradeFiles[j].Name() + }) + for i := 0; i < len(upgradeFiles); i++ { + if i != 0 { + dropWithTask(path.Join(upgradePath, upgradeFiles[i].Name()), taskItem, &size, &fileCount) + } + } + } + + dropWithTask(path.Join(global.Dir.LocalBackupDir, "tmp/system"), taskItem, &size, &fileCount) + + dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "app"), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "website"), taskItem, &size, &fileCount) + dropWithTask(path.Join(global.Dir.BaseDir, rollbackPath, "database"), taskItem, &size, &fileCount) + + upgrades := path.Join(global.Dir.BaseDir, upgradePath) + oldUpgradeFiles, _ := os.ReadDir(upgrades) + if len(oldUpgradeFiles) != 0 { + for i := 0; i < len(oldUpgradeFiles); i++ { + dropWithTask(path.Join(upgrades, oldUpgradeFiles[i].Name()), taskItem, &size, &fileCount) + } + } + + dropWithExclude(path.Join(global.Dir.BaseDir, uploadPath), []string{"theme"}, taskItem, &size, &fileCount) + dropWithTask(path.Join(global.Dir.BaseDir, downloadPath), taskItem, &size, &fileCount) + + logFiles, _ := os.ReadDir(global.Dir.LogDir) + if len(logFiles) != 0 { + for i := 0; i < len(logFiles); i++ { + if logFiles[i].IsDir() { + continue + } + if logFiles[i].Name() != "1Panel.log" && logFiles[i].Name() != "1Panel-Core.log" { + dropWithTask(path.Join(global.Dir.LogDir, logFiles[i].Name()), taskItem, &size, &fileCount) + } + } + } + + count1, size1 := dropVolumes() + size += int64(size1) + fileCount += count1 + count2, size2 := dropBuildCache() + size += int64(size2) + fileCount += count2 + + timeNow := time.Now().Format(constant.DateTimeLayout) + if fileCount != 0 { + taskItem.Log(i18n.GetMsgWithMap("FileDropSum", map[string]interface{}{"size": common.LoadSizeUnit2F(float64(size)), "count": fileCount})) + } + + _ = settingRepo.Update("LastCleanTime", timeNow) + _ = settingRepo.Update("LastCleanSize", fmt.Sprintf("%v", size)) + _ = settingRepo.Update("LastCleanData", fmt.Sprintf("%v", fileCount)) + + return nil + } +} + +func loadUpgradeTree(fileOp fileUtils.FileOp) dto.CleanTree { + upgradePath := path.Join(global.Dir.BaseDir, upgradePath) + upgradeSize, _ := fileOp.GetDirSize(upgradePath) + upgradeTree := dto.CleanTree{ + ID: uuid.NewString(), + Label: "upgrade", + Size: uint64(upgradeSize), + IsCheck: false, + IsRecommend: true, + Type: "upgrade", + Children: loadTreeWithDir(true, "upgrade", upgradePath, fileOp), + } + if len(upgradeTree.Children) != 0 { + sort.Slice(upgradeTree.Children, func(i, j int) bool { + return common.CompareVersion(upgradeTree.Children[i].Label, upgradeTree.Children[j].Label) + }) + if global.IsMaster { + var copiesSetting model.Setting + _ = global.CoreDB.Where("key = ?", "UpgradeBackupCopies").First(&copiesSetting).Error + copies, _ := strconv.Atoi(copiesSetting.Value) + if copies == 0 || copies > len(upgradeTree.Children) { + copies = len(upgradeTree.Children) + } + for i := 0; i < copies; i++ { + upgradeTree.Children[i].IsCheck = false + upgradeTree.Children[i].IsRecommend = false + } + } else { + upgradeTree.Children[0].IsCheck = false + upgradeTree.Children[0].IsRecommend = false + } + } + return upgradeTree +} + +func loadAgentPackage(fileOp fileUtils.FileOp) dto.CleanTree { + pathItem := path.Join(global.Dir.BaseDir, "1panel/agent/package") + itemTree := dto.CleanTree{ + ID: uuid.NewString(), + Label: "agent_packages", + IsCheck: false, + IsRecommend: true, + Type: "agent", + } + files, _ := os.ReadDir(pathItem) + for _, file := range files { + if file.IsDir() { + itemSize, _ := fileOp.GetDirSize(path.Join(pathItem, file.Name())) + itemTree.Size += uint64(itemSize) + itemTree.Children = append(itemTree.Children, dto.CleanTree{ + ID: uuid.NewString(), + Label: file.Name(), + Name: file.Name(), + Size: uint64(itemSize), + IsCheck: true, + IsRecommend: true, + Type: "agent", + }) + } else { + itemSize, _ := file.Info() + itemName := file.Name() + isCurrentVersion := strings.HasPrefix(itemName, fmt.Sprintf("1panel-agent_%s_", global.CONF.Base.Version)) + if isCurrentVersion { + continue + } + itemTree.Size += uint64(itemSize.Size()) + itemTree.Children = append(itemTree.Children, dto.CleanTree{ + ID: uuid.NewString(), + Label: itemName, + Name: itemName, + Size: uint64(itemSize.Size()), + IsCheck: !isCurrentVersion, + IsRecommend: true, + Type: "agent", + }) + } + } + if itemTree.Size == 0 { + itemTree.IsCheck = false + } + return itemTree +} + +func loadBackupTree(fileOp fileUtils.FileOp) []dto.CleanTree { + var treeData []dto.CleanTree + + tmpSize, _ := fileOp.GetDirSize(path.Join(global.Dir.LocalBackupDir, "tmp")) + treeData = append(treeData, dto.CleanTree{ + ID: uuid.NewString(), + Label: "tmp_backup", + Size: uint64(tmpSize), + IsCheck: tmpSize != 0, + IsRecommend: true, + Type: "tmp_backup", + }) + backupRecords, _ := backupRepo.ListRecord() + var recordMap = make(map[string][]string) + for _, record := range backupRecords { + if val, ok := recordMap[record.FileDir]; ok { + val = append(val, record.FileName) + recordMap[record.FileDir] = val + } else { + recordMap[record.FileDir] = []string{record.FileName} + } + } + + treeData = append(treeData, loadUnknownApps(fileOp, recordMap)) + treeData = append(treeData, loadUnknownDbs(fileOp, recordMap)) + treeData = append(treeData, loadUnknownWebsites(fileOp, recordMap)) + treeData = append(treeData, loadUnknownSnapshot(fileOp)) + treeData = append(treeData, loadUnknownWebsiteLog(fileOp)) + return treeData +} + +func loadUnknownApps(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree { + apps, _ := appInstallRepo.ListBy(context.Background()) + var excludePaths []string + for _, app := range apps { + itemName := fmt.Sprintf("app/%s/%s", app.App.Key, app.Name) + if val, ok := recordMap[itemName]; ok { + for _, item := range val { + excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item)) + } + } + } + backupPath := path.Join(global.Dir.LocalBackupDir, "app") + treeData := dto.CleanTree{ + ID: uuid.NewString(), + Label: "unknown_app", + IsCheck: false, + IsRecommend: false, + Name: backupPath, + Type: "unknown_backup", + } + _ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths) + return treeData +} +func loadUnknownDbs(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree { + dbs, _ := databaseRepo.GetList() + var excludePaths []string + dbMap := make(map[string]struct{}) + for _, db := range dbs { + dbMap[fmt.Sprintf("database/%s/%s", db.Type, db.Name)] = struct{}{} + } + for key, val := range recordMap { + itemName := path.Dir(key) + if _, ok := dbMap[itemName]; ok { + for _, item := range val { + excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, key, item)) + } + } + } + backupPath := path.Join(global.Dir.LocalBackupDir, "database") + treeData := dto.CleanTree{ + ID: uuid.NewString(), + Label: "unknown_database", + Name: backupPath, + IsCheck: false, + IsRecommend: false, + Type: "unknown_backup", + } + _ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths) + return treeData +} +func loadUnknownWebsites(fileOp fileUtils.FileOp, recordMap map[string][]string) dto.CleanTree { + websites, _ := websiteRepo.List() + var excludePaths []string + for _, website := range websites { + itemName := fmt.Sprintf("website/%s", website.Alias) + if val, ok := recordMap[itemName]; ok { + for _, item := range val { + excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, itemName, item)) + } + } + } + backupPath := path.Join(global.Dir.LocalBackupDir, "website") + treeData := dto.CleanTree{ + ID: uuid.NewString(), + Label: "unknown_website", + Name: backupPath, + IsCheck: false, + IsRecommend: false, + Type: "unknown_backup", + } + _ = loadFileOrDirWithExclude(fileOp, 0, backupPath, &treeData, excludePaths) + return treeData +} +func loadUnknownSnapshot(fileOp fileUtils.FileOp) dto.CleanTree { + snaps, _ := snapshotRepo.GetList() + var excludePaths []string + for _, item := range snaps { + excludePaths = append(excludePaths, path.Join(global.Dir.LocalBackupDir, "system_snapshot", item.Name+".tar.gz")) + } + backupPath := path.Join(global.Dir.LocalBackupDir, "system_snapshot") + treeData := dto.CleanTree{ + ID: uuid.NewString(), + Label: "unknown_snapshot", + Name: backupPath, + IsCheck: false, + IsRecommend: false, + Type: "unknown_backup", + } + entries, _ := os.ReadDir(backupPath) + for _, entry := range entries { + childPath := filepath.Join(backupPath, entry.Name()) + if isExactPathMatch(childPath, excludePaths) { + continue + } + childNode := dto.CleanTree{ + ID: uuid.NewString(), + Label: entry.Name(), + IsCheck: false, + IsRecommend: false, + Name: childPath, + Type: "unknown_backup", + } + if entry.IsDir() { + itemSize, _ := fileOp.GetDirSize(childPath) + childNode.Size = uint64(itemSize) + childNode.IsCheck = true + childNode.IsRecommend = true + treeData.Size += childNode.Size + } else { + info, _ := entry.Info() + childNode.Size = uint64(info.Size()) + treeData.Size += childNode.Size + } + + treeData.Children = append(treeData.Children, childNode) + } + return treeData +} + +func loadUnknownWebsiteLog(fileOp fileUtils.FileOp) dto.CleanTree { + treeData := dto.CleanTree{ + ID: uuid.NewString(), + Label: "unknown_website_log", + IsCheck: false, + IsRecommend: true, + Type: "unknown_backup", + } + dir := path.Join(global.Dir.LocalBackupDir, "log/website") + websites, _ := websiteRepo.List() + websiteMap := make(map[string]struct{}) + for _, website := range websites { + websiteMap[website.Alias] = struct{}{} + } + + entries, _ := os.ReadDir(dir) + for _, entry := range entries { + if !entry.IsDir() { + continue + } + dirName := entry.Name() + if _, ok := websiteMap[dirName]; !ok { + dirPath := path.Join(dir, dirName) + itemSize, _ := fileOp.GetDirSize(dirPath) + childData := dto.CleanTree{ + ID: uuid.NewString(), + Label: dirName, + IsCheck: true, + IsRecommend: true, + Name: dirPath, + Type: "unknown_backup", + Size: uint64(itemSize), + } + treeData.Size += uint64(itemSize) + treeData.Children = append(treeData.Children, childData) + } + } + if treeData.Size > 0 { + treeData.IsCheck = true + } + return treeData +} + +func loadFileOrDirWithExclude(fileOp fileUtils.FileOp, index uint, dir string, rootTree *dto.CleanTree, excludes []string) error { + index++ + entries, err := os.ReadDir(dir) + if err != nil { + return err + } + for _, entry := range entries { + childPath := filepath.Join(dir, entry.Name()) + if isExactPathMatch(childPath, excludes) { + continue + } + childNode := dto.CleanTree{ + ID: uuid.NewString(), + Label: entry.Name(), + IsCheck: false, + IsRecommend: false, + Name: childPath, + Type: "unknown_backup", + } + if entry.IsDir() { + if index < 4 { + if err = loadFileOrDirWithExclude(fileOp, index, childPath, &childNode, excludes); err != nil { + return err + } + childNode.Size = 0 + for _, child := range childNode.Children { + childNode.Size += child.Size + } + rootTree.Size += childNode.Size + } else { + itemSize, _ := fileOp.GetDirSize(childPath) + childNode.Size = uint64(itemSize) + rootTree.Size += childNode.Size + } + } else { + info, _ := entry.Info() + childNode.Size = uint64(info.Size()) + rootTree.Size += childNode.Size + } + + rootTree.Children = append(rootTree.Children, childNode) + } + return nil +} + +func isExactPathMatch(path string, excludePaths []string) bool { + cleanPath := filepath.Clean(path) + + for _, excludePath := range excludePaths { + cleanExclude := filepath.Clean(excludePath) + if cleanPath == cleanExclude { + return true + } + } + + return false +} + +func loadRollBackTree(fileOp fileUtils.FileOp) []dto.CleanTree { + var treeData []dto.CleanTree + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "app"), "rollback_app", fileOp) + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "website"), "rollback_website", fileOp) + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, rollbackPath, "database"), "rollback_database", fileOp) + + return treeData +} + +func loadUploadTree(fileOp fileUtils.FileOp) []dto.CleanTree { + var treeData []dto.CleanTree + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "app"), "upload_app", fileOp) + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "website"), "upload_website", fileOp) + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, uploadPath, "database"), "upload_database", fileOp) + + path5 := path.Join(global.Dir.BaseDir, uploadPath) + uploadTreeData := loadTreeWithAllFile(true, path5, "upload", path5, fileOp) + treeData = append(treeData, uploadTreeData...) + + return treeData +} + +func loadDownloadTree(fileOp fileUtils.FileOp) []dto.CleanTree { + var treeData []dto.CleanTree + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, downloadPath, "app"), "download_app", fileOp) + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, downloadPath, "website"), "download_website", fileOp) + treeData = loadTreeWithCheck(treeData, path.Join(global.Dir.BaseDir, downloadPath, "database"), "download_database", fileOp) + + path5 := path.Join(global.Dir.BaseDir, downloadPath) + uploadTreeData := loadTreeWithAllFile(true, path5, "download", path5, fileOp) + treeData = append(treeData, uploadTreeData...) + + appTmpDownloadTree := loadAppTmpDownloadTree(fileOp) + if len(appTmpDownloadTree) > 0 { + parentTree := dto.CleanTree{ + ID: uuid.NewString(), + Label: "app_tmp_download", + IsCheck: true, + IsRecommend: true, + Type: "app_tmp_download", + Name: "apps", + } + for _, child := range appTmpDownloadTree { + parentTree.Size += child.Size + } + parentTree.Children = appTmpDownloadTree + treeData = append(treeData, parentTree) + } + return treeData +} + +func loadLogTree(fileOp fileUtils.FileOp) []dto.CleanTree { + var treeData []dto.CleanTree + path1 := path.Join(global.Dir.LogDir) + list1 := loadTreeWithAllFile(true, path1, "system_log", path1, fileOp) + size := uint64(0) + for _, file := range list1 { + size += file.Size + } + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "system_log", Size: size, Children: list1, Type: "system_log", IsRecommend: true}) + + path2 := path.Join(global.Dir.TaskDir) + list2 := loadTreeWithDir(false, "task_log", path2, fileOp) + size2, _ := fileOp.GetDirSize(path2) + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "task_log", Size: uint64(size2), Children: list2, Type: "task_log"}) + + websiteLogList := loadWebsiteLogTree(fileOp) + logTotalSize := uint64(0) + for _, websiteLog := range websiteLogList { + logTotalSize += websiteLog.Size + } + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "website_log", Size: logTotalSize, Children: websiteLogList, Type: "website_log", IsRecommend: false}) + + return treeData +} + +func loadWebsiteLogTree(fileOp fileUtils.FileOp) []dto.CleanTree { + websites, _ := websiteRepo.List() + if len(websites) == 0 { + return nil + } + var res []dto.CleanTree + for _, website := range websites { + size3, _ := fileOp.GetDirSize(path.Join(GetSiteDir(website.Alias), "log")) + res = append(res, dto.CleanTree{ + ID: uuid.NewString(), + Label: website.PrimaryDomain, + Size: uint64(size3), + Type: "website_log", + Name: website.Alias, + }) + } + return res +} + +func loadAppTmpDownloadTree(fileOp fileUtils.FileOp) []dto.CleanTree { + appDirs, err := os.ReadDir(global.Dir.RemoteAppResourceDir) + if err != nil { + return nil + } + var res []dto.CleanTree + for _, appDir := range appDirs { + if !appDir.IsDir() { + continue + } + appKey := appDir.Name() + app, _ := appRepo.GetFirst(appRepo.WithKey(appKey)) + if app.ID == 0 { + continue + } + appPath := filepath.Join(global.Dir.RemoteAppResourceDir, appKey) + versionDirs, err := os.ReadDir(appPath) + if err != nil { + continue + } + appDetails, _ := appDetailRepo.GetBy(appDetailRepo.WithAppId(app.ID)) + existingVersions := make(map[string]bool) + for _, appDetail := range appDetails { + existingVersions[appDetail.Version] = true + } + var missingVersions []string + for _, versionDir := range versionDirs { + if !versionDir.IsDir() { + continue + } + + version := versionDir.Name() + if !existingVersions[version] { + missingVersions = append(missingVersions, version) + } + } + if len(missingVersions) > 0 { + var appTree dto.CleanTree + appTree.ID = uuid.NewString() + appTree.Label = app.Name + appTree.Type = "app_tmp_download" + appTree.Name = appKey + appTree.IsRecommend = true + appTree.IsCheck = true + for _, version := range missingVersions { + versionPath := filepath.Join(appPath, version) + size, _ := fileOp.GetDirSize(versionPath) + appTree.Size += uint64(size) + appTree.Children = append(appTree.Children, dto.CleanTree{ + ID: uuid.NewString(), + Label: version, + Size: uint64(size), + IsCheck: true, + IsRecommend: true, + Type: "app_tmp_download_version", + Name: path.Join(appKey, version), + }) + } + res = append(res, appTree) + } + } + return res +} + +func loadContainerTree() []dto.CleanTree { + var treeData []dto.CleanTree + client, err := docker.NewDockerClient() + if err != nil { + return treeData + } + defer client.Close() + diskUsage, err := client.DiskUsage(context.Background(), types.DiskUsageOptions{}) + if err != nil { + return treeData + } + imageSize := uint64(0) + for _, file := range diskUsage.Images { + if file.Containers == 0 { + imageSize += uint64(file.Size) + } + } + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "container_images", Size: imageSize, Children: nil, Type: "images", IsRecommend: true}) + + containerSize := uint64(0) + for _, file := range diskUsage.Containers { + if file.State != "running" { + containerSize += uint64(file.SizeRw) + } + } + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "container_containers", Size: containerSize, Children: nil, Type: "containers", IsRecommend: true}) + + volumeSize := uint64(0) + for _, file := range diskUsage.Volumes { + if file.UsageData.RefCount <= 0 { + volumeSize += uint64(file.UsageData.Size) + } + } + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "container_volumes", Size: volumeSize, IsCheck: volumeSize > 0, Children: nil, Type: "volumes", IsRecommend: true}) + + var buildCacheTotalSize int64 + for _, cache := range diskUsage.BuildCache { + if cache.Type == "source.local" { + buildCacheTotalSize += cache.Size + } + } + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: "build_cache", Size: uint64(buildCacheTotalSize), IsCheck: buildCacheTotalSize > 0, Type: "build_cache", IsRecommend: true}) + return treeData +} + +func loadTreeWithCheck(treeData []dto.CleanTree, pathItem, treeType string, fileOp fileUtils.FileOp) []dto.CleanTree { + size, _ := fileOp.GetDirSize(pathItem) + if size == 0 { + return treeData + } + list := loadTreeWithAllFile(true, pathItem, treeType, pathItem, fileOp) + treeData = append(treeData, dto.CleanTree{ID: uuid.NewString(), Label: treeType, Size: uint64(size), IsCheck: size > 0, Children: list, Type: treeType, IsRecommend: true}) + return treeData +} + +func loadTreeWithDir(isCheck bool, treeType, pathItem string, fileOp fileUtils.FileOp) []dto.CleanTree { + var lists []dto.CleanTree + files, err := os.ReadDir(pathItem) + if err != nil { + return lists + } + for _, file := range files { + if file.Name() == "ssl" { + continue + } + if file.IsDir() { + size, err := fileOp.GetDirSize(path.Join(pathItem, file.Name())) + if err != nil { + continue + } + item := dto.CleanTree{ + ID: uuid.NewString(), + Label: file.Name(), + Type: treeType, + Size: uint64(size), + Name: strings.TrimPrefix(file.Name(), "/"), + IsCheck: isCheck, + IsRecommend: isCheck, + } + lists = append(lists, item) + } + } + return lists +} + +func loadTreeWithAllFile(isCheck bool, originalPath, treeType, pathItem string, fileOp fileUtils.FileOp) []dto.CleanTree { + var lists []dto.CleanTree + + files, err := os.ReadDir(pathItem) + if err != nil { + return lists + } + for _, file := range files { + if treeType == "upload" && (file.Name() == "theme" && file.IsDir()) { + continue + } + if treeType == "system_log" && (file.Name() == "1Panel-Core.log" || file.Name() == "1Panel.log" || file.IsDir()) { + continue + } + if (treeType == "upload" || treeType == "download") && file.IsDir() && (file.Name() == "app" || file.Name() == "database" || file.Name() == "website" || file.Name() == "directory") { + continue + } + size := uint64(0) + name := strings.TrimPrefix(path.Join(pathItem, file.Name()), originalPath+"/") + if file.IsDir() { + sizeItem, err := fileOp.GetDirSize(path.Join(pathItem, file.Name())) + if err != nil { + continue + } + size = uint64(sizeItem) + } else { + fileInfo, err := file.Info() + if err != nil { + continue + } + size = uint64(fileInfo.Size()) + } + item := dto.CleanTree{ + ID: uuid.NewString(), + Label: file.Name(), + Type: treeType, + Size: size, + Name: name, + IsCheck: isCheck, + IsRecommend: isCheck, + } + if file.IsDir() { + item.Children = loadTreeWithAllFile(isCheck, originalPath, treeType, path.Join(pathItem, file.Name()), fileOp) + } + lists = append(lists, item) + } + return lists +} + +func dropFileOrDir(itemPath string) { + if err := os.RemoveAll(itemPath); err != nil { + global.LOG.Errorf("drop file %s failed, err %v", itemPath, err) + } +} +func dropFile(itemPath string) { + info, err := os.Stat(itemPath) + if err != nil { + return + } + if info.IsDir() { + return + } + if err := os.Remove(itemPath); err != nil { + global.LOG.Errorf("drop file %s failed, err %v", itemPath, err) + } +} + +func dropBuildCache() (int, int) { + client, err := docker.NewDockerClient() + if err != nil { + global.LOG.Errorf("do not get docker client") + return 0, 0 + } + defer client.Close() + opts := build.CachePruneOptions{} + opts.All = true + res, err := client.BuildCachePrune(context.Background(), opts) + if err != nil { + global.LOG.Errorf("drop build cache failed, err %v", err) + return 0, 0 + } + return len(res.CachesDeleted), int(res.SpaceReclaimed) +} + +func dropImages() (int, int) { + client, err := docker.NewDockerClient() + if err != nil { + global.LOG.Errorf("do not get docker client") + return 0, 0 + } + defer client.Close() + pruneFilters := filters.NewArgs() + pruneFilters.Add("dangling", "false") + res, err := client.ImagesPrune(context.Background(), pruneFilters) + if err != nil { + global.LOG.Errorf("drop images failed, err %v", err) + return 0, 0 + } + return len(res.ImagesDeleted), int(res.SpaceReclaimed) +} + +func dropContainers() (int, int) { + client, err := docker.NewDockerClient() + if err != nil { + global.LOG.Errorf("do not get docker client") + return 0, 0 + } + defer client.Close() + pruneFilters := filters.NewArgs() + res, err := client.ContainersPrune(context.Background(), pruneFilters) + if err != nil { + global.LOG.Errorf("drop containers failed, err %v", err) + return 0, 0 + } + return len(res.ContainersDeleted), int(res.SpaceReclaimed) +} + +func dropVolumes() (int, int) { + client, err := docker.NewDockerClient() + if err != nil { + global.LOG.Errorf("do not get docker client") + return 0, 0 + } + defer client.Close() + pruneFilters := filters.NewArgs() + versions, err := client.ServerVersion(context.Background()) + if err != nil { + global.LOG.Errorf("do not get docker api versions") + return 0, 0 + } + if common.ComparePanelVersion(versions.APIVersion, "1.42") { + pruneFilters.Add("all", "true") + } + res, err := client.VolumesPrune(context.Background(), pruneFilters) + if err != nil { + global.LOG.Errorf("drop volumes failed, err %v", err) + return 0, 0 + } + return len(res.VolumesDeleted), int(res.SpaceReclaimed) +} + +func dropWebsiteLog(alias string) { + accessLogPath := path.Join(GetSiteDir(alias), "log", "access.log") + errorLogPath := path.Join(GetSiteDir(alias), "log", "error.log") + if err := os.Truncate(accessLogPath, 0); err != nil { + global.LOG.Errorf("truncate access log %s failed, err %v", accessLogPath, err) + } + + if err := os.Truncate(errorLogPath, 0); err != nil { + global.LOG.Errorf("truncate error log %s failed, err %v", errorLogPath, err) + } +} + +func dropTaskLog(logDir string) { + files, err := os.ReadDir(logDir) + if err != nil { + return + } + taskType := path.Base(logDir) + var usedTasks []string + switch taskType { + case "Cronjob": + _ = global.DB.Model(&model.JobRecords{}).Where("task_id != ?", "").Select("task_id").Find(&usedTasks).Error + case "Snapshot": + var ( + snapIDs []string + recoverIDs []string + rollbackIDs []string + ) + _ = global.DB.Model(&model.Snapshot{}).Where("task_id != ?", "").Select("task_id").Find(&snapIDs).Error + _ = global.DB.Model(&model.Snapshot{}).Where("task_recover_id != ", "").Select("task_id").Find(&recoverIDs).Error + _ = global.DB.Model(&model.Snapshot{}).Where("task_rollback_id != ?", "").Select("task_id").Find(&rollbackIDs).Error + usedTasks = append(usedTasks, snapIDs...) + usedTasks = append(usedTasks, recoverIDs...) + usedTasks = append(usedTasks, rollbackIDs...) + case "Backup": + _ = global.DB.Model(&model.BackupRecord{}).Where("task_id != ?", "").Select("task_id").Find(&usedTasks).Error + case "Clam": + _ = global.DB.Model(&model.ClamRecord{}).Where("task_id != ?", "").Select("task_id").Find(&usedTasks).Error + case "Tamper": + xpackDB, err := common.LoadDBConnByPathWithErr(path.Join(global.CONF.Base.InstallDir, "1panel/db/xpack.db"), "xpack.db") + if err == nil { + _ = xpackDB.Table("tampers").Where("task_id != ?", "").Select("task_id").Find(&usedTasks).Error + } + defer common.CloseDB(xpackDB) + case "System": + xpackDB, err := common.LoadDBConnByPathWithErr(path.Join(global.CONF.Base.InstallDir, "1panel/db/xpack.db"), "xpack.db") + if err == nil { + _ = xpackDB.Model("nodes").Where("task_id != ?", "").Select("task_id").Find(&usedTasks).Error + } + defer common.CloseDB(xpackDB) + default: + dropFileOrDir(logDir) + _ = taskRepo.Delete(repo.WithByType(taskType)) + return + } + usedMap := make(map[string]struct{}) + for _, item := range usedTasks { + if _, ok := usedMap[item]; !ok { + usedMap[item] = struct{}{} + } + } + for _, item := range files { + if _, ok := usedMap[strings.TrimSuffix(item.Name(), ".log")]; ok { + continue + } + _ = os.Remove(logDir + "/" + item.Name()) + } + _ = taskRepo.Delete(repo.WithByType(taskType), taskRepo.WithByIDNotIn(usedTasks)) +} + +func dropWithExclude(pathToDelete string, excludeSubDirs []string, taskItem *task.Task, size *int64, count *int) { + entries, err := os.ReadDir(pathToDelete) + if err != nil { + return + } + + for _, entry := range entries { + name := entry.Name() + fullPath := filepath.Join(pathToDelete, name) + excluded := false + for _, ex := range excludeSubDirs { + if name == ex { + excluded = true + break + } + } + if excluded { + continue + } + dropWithTask(fullPath, taskItem, size, count) + } +} + +func dropWithTask(itemPath string, taskItem *task.Task, size *int64, count *int) { + itemSize := int64(0) + itemCount := 0 + scanFile(itemPath, &itemSize, &itemCount) + *size += itemSize + *count += itemCount + if err := os.RemoveAll(itemPath); err != nil { + taskItem.Log(i18n.GetWithNameAndErr("FileDropFailed", itemPath, err)) + return + } + if itemCount != 0 { + taskItem.Log(i18n.GetMsgWithMap("FileDropSuccess", map[string]interface{}{"name": itemPath, "count": itemCount, "size": common.LoadSizeUnit2F(float64(itemSize))})) + } +} + +func scanFile(pathItem string, size *int64, count *int) { + files, _ := os.ReadDir(pathItem) + for _, f := range files { + if f.IsDir() { + scanFile(path.Join(pathItem, f.Name()), size, count) + } else { + fileInfo, err := f.Info() + if err != nil { + continue + } + *count++ + *size += fileInfo.Size() + } + } +} + +func cleanEmptyDirs(root string) error { + dirsToCheck := make([]string, 0) + err := filepath.WalkDir(root, func(path string, d os.DirEntry, err error) error { + if err != nil { + return nil + } + if d.IsDir() { + dirsToCheck = append(dirsToCheck, path) + } + return nil + }) + if err != nil { + return err + } + + for i := len(dirsToCheck) - 1; i >= 0; i-- { + dir := dirsToCheck[i] + if dir == root { + continue + } + + entries, err := os.ReadDir(dir) + if err != nil { + continue + } + + if len(entries) == 0 { + _ = os.Remove(dir) + } + } + return nil +} diff --git a/agent/app/service/disk.go b/agent/app/service/disk.go new file mode 100644 index 0000000..2a6b9d1 --- /dev/null +++ b/agent/app/service/disk.go @@ -0,0 +1,157 @@ +package service + +import ( + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "os" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" +) + +type DiskService struct{} + +type IDiskService interface { + GetCompleteDiskInfo() (*response.CompleteDiskInfo, error) + PartitionDisk(req request.DiskPartitionRequest) (string, error) + MountDisk(req request.DiskMountRequest) error + UnmountDisk(req request.DiskUnmountRequest) error +} + +func NewIDiskService() IDiskService { + return &DiskService{} +} + +func (s *DiskService) GetCompleteDiskInfo() (*response.CompleteDiskInfo, error) { + var diskInfos []response.DiskBasicInfo + output, err := cmd.RunDefaultWithStdoutBashC("lsblk -J -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA") + if err == nil { + diskInfos, err = parseLsblkJsonOutput(output) + if err == nil { + result := organizeDiskInfo(diskInfos) + return &result, nil + } + } + output, err = cmd.RunDefaultWithStdoutBashC("lsblk -P -o NAME,SIZE,TYPE,MOUNTPOINT,FSTYPE,MODEL,SERIAL,TRAN,ROTA") + if err != nil { + return nil, fmt.Errorf("failed to run lsblk command: %v", err) + } + diskInfos, err = parseLsblkOutput(output) + if err != nil { + return nil, fmt.Errorf("failed to parse lsblk output: %v", err) + } + result := organizeDiskInfo(diskInfos) + return &result, nil +} + +func (s *DiskService) PartitionDisk(req request.DiskPartitionRequest) (string, error) { + if !strings.HasPrefix("/dev", req.Device) { + req.Device = "/dev/" + req.Device + } + if !deviceExists(req.Device) { + return "", buserr.WithName("DeviceNotFound", req.Device) + } + + if isDeviceMounted(req.Device) { + return "", buserr.WithName("DeviceIsMounted", req.Device) + } + + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second)) + if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil { + return "", buserr.WithErr("PartitionDiskErr", err) + } + + if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mklabel gpt", req.Device)); err != nil { + return "", buserr.WithErr("PartitionDiskErr", err) + } + + if err := cmdMgr.RunBashC(fmt.Sprintf("parted -s %s mkpart primary 1MiB 100%%", req.Device)); err != nil { + return "", buserr.WithErr("PartitionDiskErr", err) + } + + if err := cmdMgr.RunBashC(fmt.Sprintf("partprobe %s", req.Device)); err != nil { + return "", buserr.WithErr("PartitionDiskErr", err) + } + partition := req.Device + "1" + + formatReq := dto.DiskFormatRequest{ + Device: partition, + Filesystem: req.Filesystem, + Label: req.Label, + } + if err := formatDisk(formatReq); err != nil { + return "", buserr.WithErr("FormatDiskErr", err) + } + + if req.MountPoint != "" { + mountReq := request.DiskMountRequest{ + Device: partition, + MountPoint: req.MountPoint, + Filesystem: req.Filesystem, + AutoMount: req.AutoMount, + } + if err := s.MountDisk(mountReq); err != nil { + return "", buserr.WithErr("MountDiskErr", err) + } + } + + return partition, nil +} + +func (s *DiskService) MountDisk(req request.DiskMountRequest) error { + if !deviceExists(req.Device) { + return buserr.WithName("DeviceNotFound", req.Device) + } + + if err := os.MkdirAll(req.MountPoint, 0755); err != nil { + return err + } + + fileSystem, err := getFilesystemType(req.Device) + if err != nil { + return err + } + if fileSystem == "" { + formatReq := dto.DiskFormatRequest{ + Device: req.Device, + Filesystem: req.Filesystem, + } + if err := formatDisk(formatReq); err != nil { + return buserr.WithErr("FormatDiskErr", err) + } + } + + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Minute)) + if err := cmdMgr.RunBashC(fmt.Sprintf("mount -t %s %s %s", req.Filesystem, req.Device, req.MountPoint)); err != nil { + return buserr.WithErr("MountDiskErr", err) + } + if req.AutoMount { + options := "" + if req.NoFail { + options = "defaults,nofail" + } + if err := addToFstabWithOptions(req.Device, req.MountPoint, req.Filesystem, options); err != nil { + return buserr.WithErr("MountDiskErr", err) + } + } + + return nil +} + +func (s *DiskService) UnmountDisk(req request.DiskUnmountRequest) error { + if !isPointMounted(req.MountPoint) { + return buserr.New("MountDiskErr") + } + if err := cmd.RunDefaultBashC(fmt.Sprintf("umount -f %s", req.MountPoint)); err != nil { + return buserr.WithErr("MountDiskErr", err) + } + if err := removeFromFstab(req.MountPoint); err != nil { + global.LOG.Errorf("remove %s mountPoint err: %v", req.MountPoint, err) + } + return nil +} diff --git a/agent/app/service/disk_utils.go b/agent/app/service/disk_utils.go new file mode 100644 index 0000000..e237cfa --- /dev/null +++ b/agent/app/service/disk_utils.go @@ -0,0 +1,587 @@ +package service + +import ( + "bufio" + "encoding/json" + "fmt" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "os" + "os/exec" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type LsblkRaw struct { + Blockdevices []LsblkDevice `json:"blockdevices"` +} + +type LsblkDevice struct { + Name string `json:"name"` + Size string `json:"size"` + Type string `json:"type"` + Mountpoint string `json:"mountpoint"` + Fstype string `json:"fstype"` + Model string `json:"model"` + Serial string `json:"serial"` + Tran string `json:"tran"` + Rota bool `json:"rota"` + Children []LsblkDevice `json:"children"` +} + +func parseDevice(dev LsblkDevice) []response.DiskBasicInfo { + var list []response.DiskBasicInfo + + if strings.HasPrefix(dev.Name, "loop") || strings.HasPrefix(dev.Name, "dm-") || dev.Type == "rom" { + return list + } + + if dev.Type == "lvm" { + return list + } + + diskType := "Unknown" + if dev.Type == "disk" || dev.Type == "part" { + if dev.Rota { + diskType = "HDD" + } else { + diskType = "SSD" + } + } + + mountPoint := dev.Mountpoint + filesystem := dev.Fstype + size := dev.Size + + var used, avail, totalSize string + var usePercent int + isMounted := mountPoint != "" + isSystem := false + + if dev.Fstype == "LVM2_member" && len(dev.Children) > 0 { + for _, child := range dev.Children { + if child.Type == "lvm" && child.Mountpoint != "" { + devicePath := "/dev/mapper/" + child.Name + totalSize, used, avail, usePercent, _ := getDiskUsageInfo(devicePath) + + childInfo := response.DiskBasicInfo{ + Device: dev.Name, + Size: totalSize, + Model: dev.Model, + DiskType: diskType, + Filesystem: child.Fstype, + MountPoint: child.Mountpoint, + IsMounted: true, + UsePercent: usePercent, + Used: used, + Avail: avail, + Serial: dev.Serial, + IsRemovable: dev.Tran == "usb", + IsSystem: isSystemDisk(child.Mountpoint), + } + list = append(list, childInfo) + } + } + return list + } else if isMounted { + isSystem = isSystemDisk(mountPoint) + devicePath := "/dev/" + dev.Name + totalSize, used, avail, usePercent, _ = getDiskUsageInfo(devicePath) + if totalSize != "" { + size = totalSize + } + } + + info := response.DiskBasicInfo{ + Device: dev.Name, + Size: size, + Model: dev.Model, + DiskType: diskType, + Filesystem: filesystem, + MountPoint: mountPoint, + IsMounted: isMounted, + UsePercent: usePercent, + Used: used, + Avail: avail, + Serial: dev.Serial, + IsRemovable: dev.Tran == "usb", + IsSystem: isSystem, + } + + list = append(list, info) + + for _, child := range dev.Children { + childList := parseDevice(child) + list = append(list, childList...) + } + + return list +} + +func parseLsblkJsonOutput(output string) ([]response.DiskBasicInfo, error) { + raw := &LsblkRaw{} + if err := json.Unmarshal([]byte(output), raw); err != nil { + return nil, fmt.Errorf("failed to parse lsblk json output: %v", err) + } + var disks []response.DiskBasicInfo + + for _, dev := range raw.Blockdevices { + if strings.HasPrefix(dev.Name, "loop") || + strings.HasPrefix(dev.Name, "dm-") { + continue + } + devList := parseDevice(dev) + disks = append(disks, devList...) + } + return disks, nil +} + +func organizeDiskInfo(diskInfos []response.DiskBasicInfo) response.CompleteDiskInfo { + var result response.CompleteDiskInfo + diskMap := make(map[string]*response.DiskInfo) + partitions := make(map[string][]response.DiskBasicInfo) + + for _, info := range diskInfos { + isPartition := isPartitionDevice(info.Device) + + if isPartition { + parentDevice := getParentDevice(info.Device) + partitions[parentDevice] = append(partitions[parentDevice], info) + } else { + disk := &response.DiskInfo{ + DiskBasicInfo: info, + Partitions: []response.DiskBasicInfo{}, + } + diskMap[info.Device] = disk + } + } + + for parentDevice, partList := range partitions { + if disk, exists := diskMap[parentDevice]; exists { + for index, part := range partList { + part.Device = fmt.Sprintf("/dev/%s", part.Device) + if part.IsSystem { + disk.IsSystem = true + } + partList[index] = part + } + disk.Partitions = partList + } + } + + var totalCapacity int64 + for _, disk := range diskMap { + capacity := parseSizeToBytes(disk.Size) + totalCapacity += capacity + + if disk.IsSystem { + result.SystemDisks = append(result.SystemDisks, *disk) + } else if len(disk.Partitions) == 0 { + result.UnpartitionedDisks = append(result.UnpartitionedDisks, disk.DiskBasicInfo) + } else { + result.Disks = append(result.Disks, *disk) + } + } + + result.TotalDisks = len(diskMap) + result.TotalCapacity = totalCapacity + + return result +} + +func parseLsblkOutput(output string) ([]response.DiskBasicInfo, error) { + lines := strings.Split(strings.TrimSpace(output), "\n") + if len(lines) == 0 { + return nil, fmt.Errorf("invalid lsblk output") + } + + var diskInfos []response.DiskBasicInfo + lvmMap := make(map[string]response.DiskBasicInfo) + var pendingDevices []map[string]string + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + + fields := parseKeyValuePairs(line) + + name, ok := fields["NAME"] + if !ok { + continue + } + + if strings.HasPrefix(name, "loop") || + strings.HasPrefix(name, "dm-") { + continue + } + + diskType := fields["TYPE"] + mountPoint := fields["MOUNTPOINT"] + fsType := fields["FSTYPE"] + size := fields["SIZE"] + + if diskType == "lvm" { + total, used, avail, usePercent, _ := getDiskUsageInfo("/dev/mapper/" + name) + if total != "" && fsType != "" { + size = total + } + + lvmInfo := response.DiskBasicInfo{ + Device: name, + Size: size, + Model: fields["MODEL"], + DiskType: "LVM", + IsRemovable: false, + IsSystem: isSystemDisk(mountPoint), + Filesystem: fsType, + Used: used, + Avail: avail, + UsePercent: usePercent, + MountPoint: mountPoint, + IsMounted: mountPoint != "" && mountPoint != "-", + Serial: fields["SERIAL"], + } + lvmMap[name] = lvmInfo + } else if diskType == "disk" || diskType == "part" { + pendingDevices = append(pendingDevices, fields) + } + } + + for _, fields := range pendingDevices { + name := fields["NAME"] + size := fields["SIZE"] + mountPoint := fields["MOUNTPOINT"] + fsType := fields["FSTYPE"] + model := fields["MODEL"] + serial := fields["SERIAL"] + tran := fields["TRAN"] + rota := fields["ROTA"] + + var ( + used, avail, totalSize string + usePercent int + ) + if mountPoint != "" { + totalSize, used, avail, usePercent, _ = getDiskUsageInfo("/dev/" + name) + if totalSize != "" { + size = totalSize + } + } + + actualMountPoint := mountPoint + actualFsType := fsType + actualUsed := used + actualAvail := avail + actualUsePercent := usePercent + isMounted := mountPoint != "" && mountPoint != "-" + isSystemPartition := isSystemDisk(mountPoint) + + if fsType == "LVM2_member" { + for _, lvmInfo := range lvmMap { + if lvmInfo.IsMounted { + lvmDiskInfo := response.DiskBasicInfo{ + Device: name, + Size: lvmInfo.Size, + Model: model, + DiskType: "LVM", + IsRemovable: tran == "usb", + IsSystem: lvmInfo.IsSystem, + Filesystem: lvmInfo.Filesystem, + Used: lvmInfo.Used, + Avail: lvmInfo.Avail, + UsePercent: lvmInfo.UsePercent, + MountPoint: lvmInfo.MountPoint, + IsMounted: true, + Serial: serial, + } + diskInfos = append(diskInfos, lvmDiskInfo) + } + } + continue + } + + info := response.DiskBasicInfo{ + Device: name, + Size: size, + Model: model, + DiskType: getDiskType(rota), + IsRemovable: tran == "usb", + IsSystem: isSystemPartition, + Filesystem: actualFsType, + Used: actualUsed, + Avail: actualAvail, + UsePercent: actualUsePercent, + MountPoint: actualMountPoint, + IsMounted: isMounted, + Serial: serial, + } + + diskInfos = append(diskInfos, info) + } + + return diskInfos, nil +} + +func getDiskType(rota string) string { + if rota == "0" { + return "SSD" + } else if rota == "1" { + return "HDD" + } + return "Unknown" +} + +func parseKeyValuePairs(line string) map[string]string { + fields := make(map[string]string) + + matches := re.GetRegex(re.DiskKeyValuePattern).FindAllStringSubmatch(line, -1) + for _, m := range matches { + key := m[1] + raw := m[2] + + val := raw + if len(val) >= 2 && val[0] == '"' && val[len(val)-1] == '"' { + if unq, err := strconv.Unquote(val); err == nil { + val = unq + } else { + val = val[1 : len(val)-1] + } + } + fields[key] = val + } + return fields +} + +func isPartitionDevice(device string) bool { + if strings.Contains(device, "nvme") { + return strings.Contains(device, "p") && + strings.ContainsAny(device[strings.LastIndex(device, "p")+1:], "0123456789") + } else if strings.HasPrefix(device, "sd") || strings.HasPrefix(device, "hd") { + return len(device) > 3 && + strings.ContainsAny(device[len(device)-1:], "0123456789") + } else if strings.HasPrefix(device, "vd") { + return len(device) > 3 && + strings.ContainsAny(device[len(device)-1:], "0123456789") + } + + return false +} + +func getParentDevice(device string) string { + if strings.Contains(device, "nvme") { + if idx := strings.LastIndex(device, "p"); idx != -1 { + return device[:idx] + } + } else { + return strings.TrimRight(device, "0123456789") + } + + return device +} + +func getDiskUsageInfo(device string) (size, used, avail string, usePercent int, err error) { + output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("df -h %s | tail -1", device)) + if err != nil { + return "", "", "", 0, nil + } + + fields := strings.Fields(output) + if len(fields) >= 5 { + size = fields[1] + used = fields[2] + avail = fields[3] + usePercentStr := strings.TrimSuffix(fields[4], "%") + usePercent, _ = strconv.Atoi(usePercentStr) + } + + return size, used, avail, usePercent, nil +} + +func formatDisk(req dto.DiskFormatRequest) error { + var mkfsCmd *exec.Cmd + + switch req.Filesystem { + case "ext4": + mkfsCmd = exec.Command("mkfs.ext4", "-F", req.Device) + case "xfs": + if !cmd.Which("mkfs.xfs") { + return buserr.New("XfsNotFound") + } + mkfsCmd = exec.Command("mkfs.xfs", "-f", req.Device) + default: + return fmt.Errorf("unsupport type: %s", req.Filesystem) + } + if err := mkfsCmd.Run(); err != nil { + return err + } + return nil +} + +func deviceExists(device string) bool { + _, err := os.Stat(device) + return err == nil +} + +func isDeviceMounted(device string) bool { + file, err := os.Open("/proc/mounts") + if err != nil { + return false + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) >= 2 && fields[0] == device { + return true + } + } + return false +} + +func isPointMounted(mountPoint string) bool { + file, err := os.Open("/proc/mounts") + if err != nil { + return false + } + defer file.Close() + + scanner := bufio.NewScanner(file) + for scanner.Scan() { + fields := strings.Fields(scanner.Text()) + if len(fields) >= 2 && fields[1] == mountPoint { + return true + } + } + return false +} + +func addToFstabWithOptions(device, mountPoint, filesystem, options string) error { + uuid, err := getDeviceUUID(device) + if err != nil { + return fmt.Errorf("failed to get UUID for device %s: %v", device, err) + } + + if filesystem == "" { + fsType, err := getFilesystemType(device) + if err != nil { + filesystem = "auto" + } else { + filesystem = fsType + } + } + + if options == "" { + options = "defaults" + } + + entry := fmt.Sprintf("UUID=%s %s %s %s 0 2\n", uuid, mountPoint, filesystem, options) + + file, err := os.OpenFile("/etc/fstab", os.O_APPEND|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer file.Close() + + _, err = file.WriteString(entry) + return err +} + +func removeFromFstab(mountPoint string) error { + file, err := os.Open("/etc/fstab") + if err != nil { + return err + } + defer file.Close() + + var lines []string + scanner := bufio.NewScanner(file) + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) >= 2 && fields[1] == mountPoint { + continue + } + lines = append(lines, line) + } + + return os.WriteFile("/etc/fstab", []byte(strings.Join(lines, "\n")+"\n"), 0644) +} + +func getFilesystemType(device string) (string, error) { + output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -o value -s TYPE %s", device)) + if err != nil { + return "", err + } + return strings.TrimSpace(output), nil +} + +func isSystemDisk(mountPoint string) bool { + systemMountPoints := []string{ + "/", + "/boot", + "/boot/efi", + "/usr", + "/var", + "/etc", + } + + for _, sysMount := range systemMountPoints { + if mountPoint == sysMount { + return true + } + } + + return false +} + +func parseSizeToBytes(sizeStr string) int64 { + if sizeStr == "" || sizeStr == "-" { + return 0 + } + + sizeStr = strings.TrimSpace(sizeStr) + if len(sizeStr) < 2 { + return 0 + } + + unit := strings.ToUpper(sizeStr[len(sizeStr)-1:]) + valueStr := sizeStr[:len(sizeStr)-1] + + value, err := strconv.ParseFloat(valueStr, 64) + if err != nil { + return 0 + } + + switch unit { + case "K": + return int64(value * 1024) + case "M": + return int64(value * 1024 * 1024) + case "G": + return int64(value * 1024 * 1024 * 1024) + case "T": + return int64(value * 1024 * 1024 * 1024 * 1024) + default: + val, _ := strconv.ParseInt(sizeStr, 10, 64) + return val + } +} + +func getDeviceUUID(device string) (string, error) { + output, err := cmd.RunDefaultWithStdoutBashC(fmt.Sprintf("blkid -s UUID -o value %s", device)) + if err != nil { + return "", err + } + uuid := strings.TrimSpace(output) + if uuid == "" { + return "", fmt.Errorf("no UUID found for device %s", device) + } + return uuid, nil +} diff --git a/agent/app/service/docker.go b/agent/app/service/docker.go new file mode 100644 index 0000000..78952d5 --- /dev/null +++ b/agent/app/service/docker.go @@ -0,0 +1,434 @@ +package service + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "os" + "path" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/docker" +) + +type DockerService struct{} + +type IDockerService interface { + UpdateConf(req dto.SettingUpdate, withRestart bool) error + UpdateLogOption(req dto.LogOption) error + UpdateIpv6Option(req dto.Ipv6Option) error + UpdateConfByFile(info dto.DaemonJsonUpdateByFile) error + LoadDockerStatus() *dto.DockerStatus + LoadDockerConf() (*dto.DaemonJsonConf, error) + OperateDocker(req dto.DockerOperation) error +} + +func NewIDockerService() IDockerService { + return &DockerService{} +} + +type daemonJsonItem struct { + Status string `json:"status"` + Mirrors []string `json:"registry-mirrors"` + Registries []string `json:"insecure-registries"` + LiveRestore bool `json:"live-restore"` + Ipv6 bool `json:"ipv6"` + FixedCidrV6 string `json:"fixed-cidr-v6"` + Ip6Tables bool `json:"ip6tables"` + Experimental bool `json:"experimental"` + IPTables bool `json:"iptables"` + ExecOpts []string `json:"exec-opts"` + LogOption logOption `json:"log-opts"` +} +type logOption struct { + LogMaxSize string `json:"max-size"` + LogMaxFile string `json:"max-file"` +} + +func (u *DockerService) LoadDockerStatus() *dto.DockerStatus { + ctx := context.Background() + var data dto.DockerStatus + if !cmd.Which("docker") { + data.IsExist = false + return &data + } + data.IsExist = true + data.IsActive = true + client, err := docker.NewDockerClient() + if err != nil { + global.LOG.Errorf("load docker client failed, err: %v", err) + data.IsActive = false + return &data + } + defer client.Close() + if _, err := client.Ping(ctx); err != nil { + global.LOG.Errorf("ping docker client failed, err: %v", err) + data.IsActive = false + } + + return &data +} + +func (u *DockerService) LoadDockerConf() (*dto.DaemonJsonConf, error) { + ctx := context.Background() + var data dto.DaemonJsonConf + data.IPTables = true + data.Version = "-" + client, err := docker.NewDockerClient() + if err != nil { + return &data, err + } + itemVersion, err := client.ServerVersion(ctx) + if err == nil { + data.Version = itemVersion.Version + } + data.IsSwarm = false + stdout2, _ := cmd.RunDefaultWithStdoutBashC("docker info | grep Swarm") + if string(stdout2) == " Swarm: active\n" { + data.IsSwarm = true + } + if _, err := os.Stat(constant.DaemonJsonPath); err != nil { + return &data, nil + } + file, err := os.ReadFile(constant.DaemonJsonPath) + if err != nil { + return &data, nil + } + var conf daemonJsonItem + daemonMap := make(map[string]interface{}) + if err := json.Unmarshal(file, &daemonMap); err != nil { + return &data, nil + } + arr, err := json.Marshal(daemonMap) + if err != nil { + return &data, err + } + if err := json.Unmarshal(arr, &conf); err != nil { + return &data, err + } + if _, ok := daemonMap["iptables"]; !ok { + conf.IPTables = true + } + data.CgroupDriver = "cgroupfs" + for _, opt := range conf.ExecOpts { + if strings.HasPrefix(opt, "native.cgroupdriver=") { + data.CgroupDriver = strings.ReplaceAll(opt, "native.cgroupdriver=", "") + break + } + } + data.Ipv6 = conf.Ipv6 + data.FixedCidrV6 = conf.FixedCidrV6 + data.Ip6Tables = conf.Ip6Tables + data.Experimental = conf.Experimental + data.LogMaxSize = conf.LogOption.LogMaxSize + data.LogMaxFile = conf.LogOption.LogMaxFile + data.Mirrors = conf.Mirrors + data.Registries = conf.Registries + data.IPTables = conf.IPTables + data.LiveRestore = conf.LiveRestore + return &data, nil +} + +func (u *DockerService) UpdateConf(req dto.SettingUpdate, withRestart bool) error { + err := createIfNotExistDaemonJsonFile() + if err != nil { + return err + } + file, err := os.ReadFile(constant.DaemonJsonPath) + if err != nil { + return err + } + daemonMap := make(map[string]interface{}) + _ = json.Unmarshal(file, &daemonMap) + switch req.Key { + case "Registries": + req.Value = strings.TrimSuffix(req.Value, ",") + if len(req.Value) == 0 { + delete(daemonMap, "insecure-registries") + } else { + daemonMap["insecure-registries"] = strings.Split(req.Value, ",") + } + case "Mirrors": + req.Value = strings.TrimSuffix(req.Value, ",") + if len(req.Value) == 0 { + delete(daemonMap, "registry-mirrors") + } else { + daemonMap["registry-mirrors"] = strings.Split(req.Value, ",") + } + case "Ipv6": + if req.Value == "disable" { + delete(daemonMap, "ipv6") + delete(daemonMap, "fixed-cidr-v6") + delete(daemonMap, "ip6tables") + delete(daemonMap, "experimental") + } + case "LogOption": + if req.Value == "disable" { + delete(daemonMap, "log-opts") + } + case "LiveRestore": + if req.Value == "disable" { + delete(daemonMap, "live-restore") + } else { + daemonMap["live-restore"] = true + } + case "IPtables": + if req.Value == "enable" { + delete(daemonMap, "iptables") + } else { + daemonMap["iptables"] = false + } + case "Driver": + if opts, ok := daemonMap["exec-opts"]; ok { + if optsValue, isArray := opts.([]interface{}); isArray { + for i := 0; i < len(optsValue); i++ { + if opt, isStr := optsValue[i].(string); isStr { + if strings.HasPrefix(opt, "native.cgroupdriver=") { + optsValue[i] = "native.cgroupdriver=" + req.Value + break + } + } + } + } + } else { + if req.Value == "systemd" { + daemonMap["exec-opts"] = []string{"native.cgroupdriver=systemd"} + } + } + case "http-proxy", "https-proxy": + delete(daemonMap, "proxies") + if len(req.Value) > 0 { + proxies := map[string]interface{}{ + req.Key: req.Value, + } + daemonMap["proxies"] = proxies + } + case "socks5-proxy", "close-proxy": + delete(daemonMap, "proxies") + if len(req.Value) > 0 { + proxies := map[string]interface{}{ + "http-proxy": req.Value, + "https-proxy": req.Value, + } + daemonMap["proxies"] = proxies + } + } + newJson, err := json.MarshalIndent(daemonMap, "", "\t") + if err != nil { + return err + } + if string(newJson) == string(file) { + return nil + } + if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil { + return err + } + if err := validateDockerConfig(); err != nil { + return err + } + + if withRestart { + if err := controller.HandleRestart("docker"); err != nil { + return fmt.Errorf("failed to restart Docker: %v", err) + } + } + return nil +} +func createIfNotExistDaemonJsonFile() error { + if _, err := os.Stat(constant.DaemonJsonPath); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(path.Dir(constant.DaemonJsonPath), os.ModePerm); err != nil { + return err + } + var daemonFile *os.File + daemonFile, err = os.Create(constant.DaemonJsonPath) + if err != nil { + return err + } + defer daemonFile.Close() + } + return nil +} + +func (u *DockerService) UpdateLogOption(req dto.LogOption) error { + err := createIfNotExistDaemonJsonFile() + if err != nil { + return err + } + file, err := os.ReadFile(constant.DaemonJsonPath) + if err != nil { + return err + } + daemonMap := make(map[string]interface{}) + _ = json.Unmarshal(file, &daemonMap) + + changeLogOption(daemonMap, req.LogMaxFile, req.LogMaxSize) + newJson, err := json.MarshalIndent(daemonMap, "", "\t") + if err != nil { + return err + } + if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil { + return err + } + + if err := validateDockerConfig(); err != nil { + return err + } + + if err := controller.HandleRestart("docker"); err != nil { + return fmt.Errorf("failed to restart Docker: %v", err) + } + return nil +} + +func (u *DockerService) UpdateIpv6Option(req dto.Ipv6Option) error { + err := createIfNotExistDaemonJsonFile() + if err != nil { + return err + } + + file, err := os.ReadFile(constant.DaemonJsonPath) + if err != nil { + return err + } + daemonMap := make(map[string]interface{}) + _ = json.Unmarshal(file, &daemonMap) + + daemonMap["ipv6"] = true + daemonMap["fixed-cidr-v6"] = req.FixedCidrV6 + if req.Ip6Tables { + daemonMap["ip6tables"] = req.Ip6Tables + } + if req.Experimental { + daemonMap["experimental"] = req.Experimental + } + newJson, err := json.MarshalIndent(daemonMap, "", "\t") + if err != nil { + return err + } + if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil { + return err + } + + if err := validateDockerConfig(); err != nil { + return err + } + + if err := controller.HandleRestart("docker"); err != nil { + return fmt.Errorf("failed to restart Docker: %v", err) + } + return nil +} + +func (u *DockerService) UpdateConfByFile(req dto.DaemonJsonUpdateByFile) error { + err := createIfNotExistDaemonJsonFile() + if err != nil { + return err + } + file, err := os.OpenFile(constant.DaemonJsonPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(req.File) + write.Flush() + + if err := validateDockerConfig(); err != nil { + return err + } + + if err := controller.HandleRestart("docker"); err != nil { + return fmt.Errorf("failed to restart Docker: %v", err) + } + return nil +} + +func (u *DockerService) OperateDocker(req dto.DockerOperation) error { + service := "docker" + if req.Operation == "stop" { + isSocketActive, _ := controller.CheckExist("docker.socket") + if isSocketActive { + if err := controller.HandleStop("docker.socket"); err != nil { + global.LOG.Errorf("handle stop docker.socket failed, err: %v", err) + } + } + } + if req.Operation == "restart" { + if err := validateDockerConfig(); err != nil { + return err + } + } + if err := controller.Handle(req.Operation, service); err != nil { + return err + } + return nil +} + +func changeLogOption(daemonMap map[string]interface{}, logMaxFile, logMaxSize string) { + if opts, ok := daemonMap["log-opts"]; ok { + if len(logMaxFile) != 0 || len(logMaxSize) != 0 { + daemonMap["log-driver"] = "json-file" + } + optsMap, isMap := opts.(map[string]interface{}) + if isMap { + if len(logMaxFile) != 0 { + optsMap["max-file"] = logMaxFile + } else { + delete(optsMap, "max-file") + } + if len(logMaxSize) != 0 { + optsMap["max-size"] = logMaxSize + } else { + delete(optsMap, "max-size") + } + if len(optsMap) == 0 { + delete(daemonMap, "log-opts") + } + } else { + optsMap := make(map[string]interface{}) + if len(logMaxFile) != 0 { + optsMap["max-file"] = logMaxFile + } + if len(logMaxSize) != 0 { + optsMap["max-size"] = logMaxSize + } + if len(optsMap) != 0 { + daemonMap["log-opts"] = optsMap + } + } + } else { + if len(logMaxFile) != 0 || len(logMaxSize) != 0 { + daemonMap["log-driver"] = "json-file" + } + optsMap := make(map[string]interface{}) + if len(logMaxFile) != 0 { + optsMap["max-file"] = logMaxFile + } + if len(logMaxSize) != 0 { + optsMap["max-size"] = logMaxSize + } + if len(optsMap) != 0 { + daemonMap["log-opts"] = optsMap + } + } +} + +func validateDockerConfig() error { + if !cmd.Which("dockerd") { + return nil + } + stdout, err := cmd.RunDefaultWithStdoutBashC("dockerd --validate") + if strings.Contains(stdout, "unknown flag: --validate") { + return nil + } + if err != nil || (stdout != "" && strings.TrimSpace(stdout) != "configuration OK") { + return fmt.Errorf("Docker configuration validation failed, %v", err) + } + return nil +} diff --git a/agent/app/service/entry.go b/agent/app/service/entry.go new file mode 100644 index 0000000..90213e7 --- /dev/null +++ b/agent/app/service/entry.go @@ -0,0 +1,55 @@ +package service + +import "github.com/1Panel-dev/1Panel/agent/app/repo" + +var ( + appRepo = repo.NewIAppRepo() + appTagRepo = repo.NewIAppTagRepo() + appDetailRepo = repo.NewIAppDetailRepo() + tagRepo = repo.NewITagRepo() + appInstallRepo = repo.NewIAppInstallRepo() + launcherRepo = repo.NewILauncherRepo() + appInstallResourceRepo = repo.NewIAppInstallResourceRpo() + appIgnoreUpgradeRepo = repo.NewIAppIgnoreUpgradeRepo() + + aiRepo = repo.NewIAiRepo() + mcpServerRepo = repo.NewIMcpServerRepo() + tensorrtLLMRepo = repo.NewITensorRTLLMRepo() + + mysqlRepo = repo.NewIMysqlRepo() + postgresqlRepo = repo.NewIPostgresqlRepo() + databaseRepo = repo.NewIDatabaseRepo() + + imageRepoRepo = repo.NewIImageRepoRepo() + composeRepo = repo.NewIComposeTemplateRepo() + + scriptRepo = repo.NewIScriptRepo() + cronjobRepo = repo.NewICronjobRepo() + + hostRepo = repo.NewIHostRepo() + ftpRepo = repo.NewIFtpRepo() + clamRepo = repo.NewIClamRepo() + monitorRepo = repo.NewIMonitorRepo() + + settingRepo = repo.NewISettingRepo() + backupRepo = repo.NewIBackupRepo() + + websiteRepo = repo.NewIWebsiteRepo() + websiteDomainRepo = repo.NewIWebsiteDomainRepo() + websiteDnsRepo = repo.NewIWebsiteDnsAccountRepo() + websiteSSLRepo = repo.NewISSLRepo() + websiteAcmeRepo = repo.NewIAcmeAccountRepo() + websiteCARepo = repo.NewIWebsiteCARepo() + + snapshotRepo = repo.NewISnapshotRepo() + + runtimeRepo = repo.NewIRunTimeRepo() + phpExtensionsRepo = repo.NewIPHPExtensionsRepo() + + favoriteRepo = repo.NewIFavoriteRepo() + + taskRepo = repo.NewITaskRepo() + + groupRepo = repo.NewIGroupRepo() + alertRepo = repo.NewIAlertRepo() +) diff --git a/agent/app/service/fail2ban.go b/agent/app/service/fail2ban.go new file mode 100644 index 0000000..4876f54 --- /dev/null +++ b/agent/app/service/fail2ban.go @@ -0,0 +1,251 @@ +package service + +import ( + "bufio" + "fmt" + "os" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/firewall" + "github.com/1Panel-dev/1Panel/agent/utils/toolbox" +) + +const defaultFail2BanPath = "/etc/fail2ban/jail.local" + +type Fail2BanService struct{} + +type IFail2BanService interface { + LoadBaseInfo() (dto.Fail2BanBaseInfo, error) + Search(search dto.Fail2BanSearch) ([]string, error) + Operate(operation string) error + OperateSSHD(req dto.Fail2BanSet) error + UpdateConf(req dto.Fail2BanUpdate) error + UpdateConfByFile(req dto.UpdateByFile) error +} + +func NewIFail2BanService() IFail2BanService { + return &Fail2BanService{} +} + +func (u *Fail2BanService) LoadBaseInfo() (dto.Fail2BanBaseInfo, error) { + var baseInfo dto.Fail2BanBaseInfo + client, err := toolbox.NewFail2Ban() + if err != nil { + return baseInfo, err + } + baseInfo.IsEnable, baseInfo.IsActive, baseInfo.IsExist = client.Status() + if !baseInfo.IsActive { + baseInfo.Version = "-" + } else { + baseInfo.Version = client.Version() + } + conf, err := os.ReadFile(defaultFail2BanPath) + if err != nil { + if baseInfo.IsActive { + return baseInfo, fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err) + } else { + return baseInfo, nil + } + } + lines := strings.Split(string(conf), "\n") + + block := "" + for _, line := range lines { + if strings.HasPrefix(strings.ToLower(line), "[default]") { + block = "default" + continue + } + if strings.HasPrefix(line, "[sshd]") { + block = "sshd" + continue + } + if strings.HasPrefix(line, "[") { + block = "" + continue + } + if block != "default" && block != "sshd" { + continue + } + loadFailValue(line, &baseInfo) + } + + return baseInfo, nil +} + +func (u *Fail2BanService) Search(req dto.Fail2BanSearch) ([]string, error) { + var list []string + client, err := toolbox.NewFail2Ban() + if err != nil { + return nil, err + } + if req.Status == "banned" { + list, err = client.ListBanned() + + } else { + list, err = client.ListIgnore() + } + if err != nil { + return nil, err + } + + return list, nil +} + +func (u *Fail2BanService) Operate(operation string) error { + client, err := toolbox.NewFail2Ban() + if err != nil { + return err + } + return client.Operate(operation) +} + +func (u *Fail2BanService) UpdateConf(req dto.Fail2BanUpdate) error { + if req.Key == "banaction" { + if req.Value == "firewallcmd-ipset" || req.Value == "ufw" { + itemName := "ufw" + if req.Value == "firewallcmd-ipset" { + itemName = "firewalld" + } + client, err := firewall.NewFirewallClient() + if err != nil { + return err + } + if client.Name() != itemName { + return buserr.WithName("ErrBanAction", itemName) + } + isActive, _ := client.Status() + if !isActive { + return buserr.WithName("ErrBanAction", itemName) + } + } + } + if req.Key == "logpath" { + if _, err := os.Stat(req.Value); err != nil { + return err + } + } + conf, err := os.ReadFile(defaultFail2BanPath) + if err != nil { + return fmt.Errorf("read fail2ban conf of %s failed, err: %v", defaultFail2BanPath, err) + } + lines := strings.Split(string(conf), "\n") + + isStart, isEnd, hasKey := false, false, false + newFile := "" + for index, line := range lines { + if !isStart && strings.HasPrefix(line, "[sshd]") { + isStart = true + newFile += fmt.Sprintf("%s\n", line) + continue + } + if !isStart || isEnd { + newFile += fmt.Sprintf("%s\n", line) + continue + } + if strings.HasPrefix(line, req.Key) { + hasKey = true + newFile += fmt.Sprintf("%s = %s\n", req.Key, req.Value) + continue + } + if strings.HasPrefix(line, "[") || index == len(lines)-1 { + isEnd = true + if !hasKey { + newFile += fmt.Sprintf("%s = %s\n", req.Key, req.Value) + } + } + newFile += line + if index != len(lines)-1 { + newFile += "\n" + } + } + file, err := os.OpenFile(defaultFail2BanPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(newFile) + write.Flush() + + client, err := toolbox.NewFail2Ban() + if err != nil { + return err + } + if err := client.Operate("restart"); err != nil { + return err + } + return nil +} + +func (u *Fail2BanService) UpdateConfByFile(req dto.UpdateByFile) error { + file, err := os.OpenFile(defaultFail2BanPath, os.O_WRONLY|os.O_TRUNC, 0640) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(req.File) + write.Flush() + + client, err := toolbox.NewFail2Ban() + if err != nil { + return err + } + if err := client.Operate("restart"); err != nil { + return err + } + return nil +} + +func (u *Fail2BanService) OperateSSHD(req dto.Fail2BanSet) error { + if req.Operate == "ignore" { + if err := u.UpdateConf(dto.Fail2BanUpdate{Key: "ignoreip", Value: strings.Join(req.IPs, ",")}); err != nil { + return err + } + return nil + } + client, err := toolbox.NewFail2Ban() + if err != nil { + return err + } + if err := client.ReBanIPs(req.IPs); err != nil { + return err + } + return nil +} + +func loadFailValue(line string, baseInfo *dto.Fail2BanBaseInfo) { + if strings.HasPrefix(line, "port") { + itemValue := strings.ReplaceAll(line, "port", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.Port, _ = strconv.Atoi(strings.TrimSpace(itemValue)) + } + if strings.HasPrefix(line, "maxretry") { + itemValue := strings.ReplaceAll(line, "maxretry", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.MaxRetry, _ = strconv.Atoi(strings.TrimSpace(itemValue)) + } + if strings.HasPrefix(line, "findtime") { + itemValue := strings.ReplaceAll(line, "findtime", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.FindTime = strings.TrimSpace(itemValue) + } + if strings.HasPrefix(line, "bantime") { + itemValue := strings.ReplaceAll(line, "bantime", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.BanTime = strings.TrimSpace(itemValue) + } + if strings.HasPrefix(line, "banaction") { + itemValue := strings.ReplaceAll(line, "banaction", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.BanAction = strings.TrimSpace(itemValue) + } + if strings.HasPrefix(line, "logpath") { + itemValue := strings.ReplaceAll(line, "logpath", "") + itemValue = strings.ReplaceAll(itemValue, "=", "") + baseInfo.LogPath = strings.TrimSpace(itemValue) + } +} diff --git a/agent/app/service/favorite.go b/agent/app/service/favorite.go new file mode 100644 index 0000000..95c04de --- /dev/null +++ b/agent/app/service/favorite.go @@ -0,0 +1,83 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/spf13/afero" +) + +type FavoriteService struct { +} + +type IFavoriteService interface { + Page(req dto.PageInfo) (int64, []response.FavoriteDTO, error) + Create(req request.FavoriteCreate) (*model.Favorite, error) + Delete(id uint) error +} + +func NewIFavoriteService() IFavoriteService { + return &FavoriteService{} +} + +func (f *FavoriteService) Page(req dto.PageInfo) (int64, []response.FavoriteDTO, error) { + total, favorites, err := favoriteRepo.Page(req.Page, req.PageSize) + if err != nil { + return 0, nil, err + } + var dtoFavorites []response.FavoriteDTO + for _, favorite := range favorites { + dtoFavorites = append(dtoFavorites, response.FavoriteDTO{ + Favorite: favorite, + }) + } + return total, dtoFavorites, nil +} + +func (f *FavoriteService) Create(req request.FavoriteCreate) (*model.Favorite, error) { + exist, _ := favoriteRepo.GetFirst(favoriteRepo.WithByPath(req.Path)) + if exist.ID > 0 { + return nil, buserr.New("ErrFavoriteExist") + } + op := files.NewFileOp() + if !op.Stat(req.Path) { + return nil, buserr.New("ErrLinkPathNotFound") + } + openFile, err := op.OpenFile(req.Path) + if err != nil { + return nil, err + } + fileInfo, err := openFile.Stat() + if err != nil { + return nil, err + } + favorite := &model.Favorite{ + Name: fileInfo.Name(), + IsDir: fileInfo.IsDir(), + Path: req.Path, + } + if fileInfo.Size() <= 10*1024*1024 { + afs := &afero.Afero{Fs: op.Fs} + cByte, err := afs.ReadFile(req.Path) + if err == nil { + if len(cByte) > 0 && !files.DetectBinary(cByte) { + favorite.IsTxt = true + } + } + } + if err := favoriteRepo.Create(favorite); err != nil { + return nil, err + } + return favorite, nil +} + +func (f *FavoriteService) Delete(id uint) error { + if err := favoriteRepo.Delete(repo.WithByID(id)); err != nil { + return err + } + return nil +} diff --git a/agent/app/service/file.go b/agent/app/service/file.go new file mode 100644 index 0000000..5b64500 --- /dev/null +++ b/agent/app/service/file.go @@ -0,0 +1,1017 @@ +package service + +import ( + "bufio" + "context" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "io/fs" + "os" + "os/user" + "path" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + "unicode/utf8" + + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/convert" + "github.com/1Panel-dev/1Panel/agent/utils/ini_conf" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/jinzhu/copier" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/simplifiedchinese" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "golang.org/x/net/html/charset" + "golang.org/x/sys/unix" + "golang.org/x/text/transform" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/pkg/errors" +) + +type FileService struct { +} + +type IFileService interface { + GetFileList(op request.FileOption) (response.FileInfo, error) + SearchUploadWithPage(req request.SearchUploadWithPage) (int64, interface{}, error) + GetFileTree(op request.FileOption) ([]response.FileTree, error) + Create(op request.FileCreate) error + Delete(op request.FileDelete) error + BatchDelete(op request.FileBatchDelete) error + Compress(c request.FileCompress) error + DeCompress(c request.FileDeCompress) error + GetContent(op request.FileContentReq) (response.FileInfo, error) + GetPreviewContent(op request.FileContentReq) (response.FileInfo, error) + SaveContent(edit request.FileEdit) error + FileDownload(d request.FileDownload) (string, error) + DirSize(req request.DirSizeReq) (response.DirSizeRes, error) + DepthDirSize(req request.DirSizeReq) ([]response.DepthDirSizeRes, error) + ChangeName(req request.FileRename) error + Wget(w request.FileWget) (string, error) + MvFile(m request.FileMove) error + ChangeOwner(req request.FileRoleUpdate) error + ChangeMode(op request.FileCreate) error + BatchChangeModeAndOwner(op request.FileRoleReq) error + ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error) + + GetPathByType(pathType string) string + BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo + GetHostMount() []dto.DiskInfo + GetUsersAndGroups() (*response.UserGroupResponse, error) + Convert(req request.FileConvertRequest) + ConvertLog(req dto.PageInfo) (int64, []response.FileConvertLog, error) + BatchGetRemarks(req request.FileRemarkBatch) map[string]string + SetRemark(req request.FileRemarkUpdate) error +} + +var filteredPaths = []string{ + "/.1panel_clash", +} + +const ( + fileRemarkXattr = "user.1panel.remark" + fileRemarkEncodedMaxLen = 256 +) + +func NewIFileService() IFileService { + return &FileService{} +} + +func (f *FileService) GetFileList(op request.FileOption) (response.FileInfo, error) { + var fileInfo response.FileInfo + data, err := os.Stat(op.Path) + if err != nil && os.IsNotExist(err) { + return fileInfo, nil + } + if !data.IsDir() { + op.FileOption.Path = filepath.Dir(op.FileOption.Path) + } + info, err := files.NewFileInfo(op.FileOption) + if err != nil { + return fileInfo, err + } + fileInfo.FileInfo = *info + return fileInfo, nil +} + +func (f *FileService) SearchUploadWithPage(req request.SearchUploadWithPage) (int64, interface{}, error) { + var ( + files []response.UploadInfo + backData []response.UploadInfo + ) + fileList, err := os.ReadDir(req.Path) + if err != nil { + return 0, files, nil + } + for _, item := range fileList { + if item.IsDir() { + continue + } + fileItem, err := item.Info() + if err != nil { + continue + } + files = append(files, response.UploadInfo{ + CreatedAt: fileItem.ModTime().Format(constant.DateTimeLayout), + Size: int(fileItem.Size()), + Name: item.Name(), + }) + } + + total, start, end := len(files), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + backData = make([]response.UploadInfo, 0) + } else { + if end >= total { + end = total + } + backData = files[start:end] + } + return int64(total), backData, nil +} + +func (f *FileService) GetFileTree(op request.FileOption) ([]response.FileTree, error) { + var treeArray []response.FileTree + if _, err := os.Stat(op.Path); err != nil && os.IsNotExist(err) { + return treeArray, nil + } + info, err := files.NewFileInfo(op.FileOption) + if err != nil { + return nil, err + } + node := response.FileTree{ + ID: common.GetUuid(), + Name: info.Name, + Path: info.Path, + IsDir: info.IsDir, + Extension: info.Extension, + } + err = f.buildFileTree(&node, info.Items, op, 2) + if err != nil { + return nil, err + } + return append(treeArray, node), nil +} + +func shouldFilterPath(path string) bool { + cleanedPath := filepath.Clean(path) + for _, filteredPath := range filteredPaths { + cleanedFilteredPath := filepath.Clean(filteredPath) + if cleanedFilteredPath == cleanedPath || strings.HasPrefix(cleanedPath, cleanedFilteredPath+"/") { + return true + } + } + return false +} + +func (f *FileService) buildFileTree(node *response.FileTree, items []*files.FileInfo, op request.FileOption, level int) error { + for _, v := range items { + if shouldFilterPath(v.Path) { + global.LOG.Infof("File Tree: Skipping %s due to filter\n", v.Path) + continue + } + childNode := response.FileTree{ + ID: common.GetUuid(), + Name: v.Name, + Path: v.Path, + IsDir: v.IsDir, + Extension: v.Extension, + } + if level > 1 && v.IsDir { + if err := f.buildChildNode(&childNode, v, op, level); err != nil { + return err + } + } + + node.Children = append(node.Children, childNode) + } + return nil +} + +func (f *FileService) buildChildNode(childNode *response.FileTree, fileInfo *files.FileInfo, op request.FileOption, level int) error { + op.Path = fileInfo.Path + subInfo, err := files.NewFileInfo(op.FileOption) + if err != nil { + if os.IsPermission(err) || errors.Is(err, unix.EACCES) { + global.LOG.Infof("File Tree: Skipping %s due to permission denied\n", fileInfo.Path) + return nil + } + global.LOG.Errorf("File Tree: Skipping %s due to error: %s\n", fileInfo.Path, err.Error()) + return nil + } + + return f.buildFileTree(childNode, subInfo.Items, op, level-1) +} + +func (f *FileService) Create(op request.FileCreate) error { + if files.IsInvalidChar(op.Path) { + return buserr.New("ErrInvalidChar") + } + fo := files.NewFileOp() + if fo.Stat(op.Path) { + return buserr.New("ErrFileIsExist") + } + mode := op.Mode + if mode == 0 { + fileInfo, err := os.Stat(filepath.Dir(op.Path)) + if err == nil { + mode = int64(fileInfo.Mode().Perm()) + } else { + mode = constant.DirPerm + } + } + if op.IsDir { + if err := fo.CreateDirWithMode(op.Path, fs.FileMode(mode)); err != nil { + return err + } + handleDefaultOwn(op.Path) + return nil + } + if op.IsLink { + if !fo.Stat(op.LinkPath) { + return buserr.New("ErrLinkPathNotFound") + } + if err := fo.LinkFile(op.LinkPath, op.Path, op.IsSymlink); err != nil { + return err + } + handleDefaultOwn(op.Path) + return nil + } + if err := fo.CreateFileWithMode(op.Path, fs.FileMode(mode)); err != nil { + return err + } + handleDefaultOwn(op.Path) + return nil +} + +func (f *FileService) Delete(op request.FileDelete) error { + if op.IsDir { + excludeDir := global.Dir.DataDir + if filepath.Base(op.Path) == ".1panel_clash" || op.Path == excludeDir { + return buserr.New("ErrPathNotDelete") + } + } + fo := files.NewFileOp() + recycleBinStatus, _ := settingRepo.Get(settingRepo.WithByKey("FileRecycleBin")) + if recycleBinStatus.Value == "Disable" { + op.ForceDelete = true + } + if op.ForceDelete { + if op.IsDir { + return fo.DeleteDir(op.Path) + } else { + return fo.DeleteFile(op.Path) + } + } + info, _ := fo.Fs.Stat(op.Path) + if info == nil || files.IsSymlink(info.Mode()) { + return os.Remove(op.Path) + } + + if err := NewIRecycleBinService().Create(request.RecycleBinCreate{SourcePath: op.Path}); err != nil { + return err + } + return favoriteRepo.Delete(favoriteRepo.WithByPath(op.Path)) +} + +func (f *FileService) BatchDelete(op request.FileBatchDelete) error { + fo := files.NewFileOp() + if op.IsDir { + for _, file := range op.Paths { + if err := fo.DeleteDir(file); err != nil { + return err + } + } + } else { + for _, file := range op.Paths { + if err := fo.DeleteFile(file); err != nil { + return err + } + } + } + return nil +} + +func (f *FileService) ChangeMode(op request.FileCreate) error { + fo := files.NewFileOp() + return fo.ChmodR(op.Path, op.Mode, op.Sub) +} + +func (f *FileService) BatchChangeModeAndOwner(op request.FileRoleReq) error { + fo := files.NewFileOp() + for _, path := range op.Paths { + if !fo.Stat(path) { + return buserr.New("ErrPathNotFound") + } + if err := fo.ChownR(path, op.User, op.Group, op.Sub); err != nil { + return err + } + if err := fo.ChmodR(path, op.Mode, op.Sub); err != nil { + return err + } + } + return nil + +} + +func (f *FileService) ChangeOwner(req request.FileRoleUpdate) error { + fo := files.NewFileOp() + return fo.ChownR(req.Path, req.User, req.Group, req.Sub) +} + +func (f *FileService) Compress(c request.FileCompress) error { + fo := files.NewFileOp() + if !c.Replace && fo.Stat(filepath.Join(c.Dst, c.Name)) { + return buserr.New("ErrFileIsExist") + } + return fo.Compress(c.Files, c.Dst, c.Name, files.CompressType(c.Type), c.Secret) +} + +func (f *FileService) DeCompress(c request.FileDeCompress) error { + fo := files.NewFileOp() + if c.Type == "tar" && len(c.Secret) != 0 { + c.Type = "tar.gz" + } + return fo.Decompress(c.Path, c.Dst, files.CompressType(c.Type), c.Secret) +} + +func (f *FileService) GetContent(op request.FileContentReq) (response.FileInfo, error) { + info, err := files.NewFileInfo(files.FileOption{ + Path: op.Path, + Expand: true, + IsDetail: op.IsDetail, + }) + if err != nil { + return response.FileInfo{}, err + } + + content := []byte(info.Content) + if len(content) > 1024 { + content = content[:1024] + } + if !utf8.Valid(content) { + _, decodeName, _ := charset.DetermineEncoding(content, "") + decoder := files.GetDecoderByName(decodeName) + if decoder != nil { + reader := strings.NewReader(info.Content) + var dec *encoding.Decoder + if decodeName == "windows-1252" { + dec = simplifiedchinese.GBK.NewDecoder() + } else { + dec = decoder.NewDecoder() + } + decodedReader := transform.NewReader(reader, dec) + contents, err := io.ReadAll(decodedReader) + if err != nil { + return response.FileInfo{}, err + } + info.Content = string(contents) + } + } + return response.FileInfo{FileInfo: *info}, nil +} + +func (f *FileService) GetPreviewContent(op request.FileContentReq) (response.FileInfo, error) { + info, err := files.NewFileInfo(files.FileOption{ + Path: op.Path, + Expand: false, + IsDetail: op.IsDetail, + }) + if err != nil { + return response.FileInfo{}, err + } + + if files.IsBlockDevice(info.FileMode) { + return response.FileInfo{FileInfo: *info}, nil + } + + file, err := os.Open(op.Path) + if err != nil { + return response.FileInfo{}, err + } + defer file.Close() + + headBuf := make([]byte, 1024) + n, err := file.Read(headBuf) + if err != nil && err != io.EOF { + return response.FileInfo{}, err + } + headBuf = headBuf[:n] + + if len(headBuf) > 0 && files.DetectBinary(headBuf) { + return response.FileInfo{FileInfo: *info}, nil + } + + const maxSize = 10 * 1024 * 1024 + if info.Size <= maxSize { + if _, err := file.Seek(0, 0); err != nil { + return response.FileInfo{}, err + } + content, err := io.ReadAll(file) + if err != nil { + return response.FileInfo{}, err + } + info.Content = string(content) + } else { + lines, err := files.TailFromEnd(op.Path, 300) + if err != nil { + return response.FileInfo{}, err + } + info.Content = strings.Join(lines, "\n") + } + + content := []byte(info.Content) + if len(content) > 1024 { + content = content[:1024] + } + if !utf8.Valid(content) { + _, decodeName, _ := charset.DetermineEncoding(content, "") + decoder := files.GetDecoderByName(decodeName) + if decoder != nil { + reader := strings.NewReader(info.Content) + var dec *encoding.Decoder + if decodeName == "windows-1252" { + dec = simplifiedchinese.GBK.NewDecoder() + } else { + dec = decoder.NewDecoder() + } + decodedReader := transform.NewReader(reader, dec) + contents, err := io.ReadAll(decodedReader) + if err != nil { + return response.FileInfo{}, err + } + info.Content = string(contents) + } + } + + return response.FileInfo{FileInfo: *info}, nil +} + +func (f *FileService) SaveContent(edit request.FileEdit) error { + info, err := files.NewFileInfo(files.FileOption{ + Path: edit.Path, + Expand: false, + }) + if err != nil { + return err + } + + fo := files.NewFileOp() + return fo.WriteFile(edit.Path, strings.NewReader(edit.Content), info.FileMode) +} + +func (f *FileService) ChangeName(req request.FileRename) error { + if files.IsInvalidChar(req.NewName) { + return buserr.New("ErrInvalidChar") + } + fo := files.NewFileOp() + return fo.Rename(req.OldName, req.NewName) +} + +func (f *FileService) Wget(w request.FileWget) (string, error) { + fo := files.NewFileOp() + key := "file-wget-" + common.GetUuid() + return key, fo.DownloadFileWithProcess(w.Url, filepath.Join(w.Path, w.Name), key, w.IgnoreCertificate) +} + +func (f *FileService) MvFile(m request.FileMove) error { + fo := files.NewFileOp() + if !fo.Stat(m.NewPath) { + return buserr.New("ErrPathNotFound") + } + for _, oldPath := range m.OldPaths { + if !fo.Stat(oldPath) { + return buserr.WithName("ErrFileNotFound", oldPath) + } + if oldPath == m.NewPath || strings.Contains(m.NewPath, filepath.Clean(oldPath)+"/") { + return buserr.New("ErrMovePathFailed") + } + } + var errs []error + if m.Type == "cut" { + if len(m.CoverPaths) > 0 { + for _, src := range m.CoverPaths { + if err := fo.CopyAndReName(src, m.NewPath, "", true); err != nil { + errs = append(errs, err) + global.LOG.Errorf("cut copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error()) + } + } + } + return fo.Cut(m.OldPaths, m.NewPath, m.Name, m.Cover) + } + if m.Type == "copy" { + for _, src := range m.OldPaths { + if err := fo.CopyAndReName(src, m.NewPath, m.Name, m.Cover); err != nil { + errs = append(errs, err) + global.LOG.Errorf("copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error()) + } + } + if len(m.CoverPaths) > 0 { + for _, src := range m.CoverPaths { + if err := fo.CopyAndReName(src, m.NewPath, "", true); err != nil { + errs = append(errs, err) + global.LOG.Errorf("copy file [%s] to [%s] failed, err: %s", src, m.NewPath, err.Error()) + } + } + } + } + + var errString string + for _, err := range errs { + errString += err.Error() + "\n" + } + if errString != "" { + return errors.New(errString) + } + return nil +} + +func (f *FileService) FileDownload(d request.FileDownload) (string, error) { + filePath := d.Paths[0] + if d.Compress { + tempPath := filepath.Join(os.TempDir(), fmt.Sprintf("%d", time.Now().UnixNano())) + if err := os.MkdirAll(tempPath, os.ModePerm); err != nil { + return "", err + } + fo := files.NewFileOp() + if err := fo.Compress(d.Paths, tempPath, d.Name, files.CompressType(d.Type), ""); err != nil { + return "", err + } + filePath = filepath.Join(tempPath, d.Name) + } + return filePath, nil +} + +func (f *FileService) DirSize(req request.DirSizeReq) (response.DirSizeRes, error) { + var ( + res response.DirSizeRes + ) + if req.Path == "/proc" { + return res, nil + } + fo := files.NewFileOp() + size, err := fo.GetDirSize(req.Path) + if err != nil { + return res, err + } + res.Size = size + return res, nil +} + +func (f *FileService) DepthDirSize(req request.DirSizeReq) ([]response.DepthDirSizeRes, error) { + var ( + res []response.DepthDirSizeRes + ) + if req.Path == "/proc" { + return res, nil + } + fo := files.NewFileOp() + dirSizes, err := fo.GetDepthDirSize(req.Path) + _ = copier.Copy(&res, &dirSizes) + if err != nil { + return res, err + } + return res, nil +} + +func (f *FileService) ReadLogByLine(req request.FileReadByLineReq) (*response.FileLineContent, error) { + logFilePath := "" + taskStatus := "" + switch req.Type { + case constant.TypeWebsite: + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return nil, err + } + logFilePath = GetSitePath(website, req.Name) + case constant.TypePhp: + php, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return nil, err + } + logFilePath = php.GetLogPath() + case constant.TypeSSL: + ssl, err := websiteSSLRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return nil, err + } + logFilePath = ssl.GetLogPath() + case constant.TypeSystem: + fileName := "" + if len(req.Name) == 0 { + fileName = "1Panel.log" + } else { + if strings.HasSuffix(req.Name, time.Now().Format("2006-01-02")) { + fileName = "1Panel.log" + if strings.HasPrefix(req.Name, "Core-") { + fileName = "1Panel-Core.log" + } + } else { + fileName = "1Panel-" + req.Name + ".log" + } + } + logFilePath = path.Join(global.Dir.DataDir, "log", fileName) + if _, err := os.Stat(logFilePath); err != nil { + fileGzPath := path.Join(global.Dir.DataDir, "log", fileName+".gz") + if _, err := os.Stat(fileGzPath); err != nil { + return nil, buserr.New("ErrHttpReqNotFound") + } + if err := handleGunzip(fileGzPath); err != nil { + return nil, fmt.Errorf("handle ungzip file %s failed, err: %v", fileGzPath, err) + } + } + case constant.TypeTask: + var opts []repo.DBOption + if req.TaskID != "" { + opts = append(opts, taskRepo.WithByID(req.TaskID)) + } else { + opts = append(opts, repo.WithOrderRuleBy("created_at", "desc"), repo.WithByType(req.TaskType), taskRepo.WithOperate(req.TaskOperate), taskRepo.WithResourceID(req.ResourceID)) + } + taskModel, err := taskRepo.GetFirst(opts...) + if err != nil { + return nil, err + } + logFilePath = taskModel.LogFile + taskStatus = taskModel.Status + case "mysql-slow-logs": + logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mysql/%s/data/1Panel-slow.log", req.Name)) + case "mariadb-slow-logs": + logFilePath = path.Join(global.Dir.DataDir, fmt.Sprintf("apps/mariadb/%s/db/data/1Panel-slow.log", req.Name)) + case "php-fpm-slow-logs": + php, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return nil, err + } + logFilePath = php.GetSlowLogPath() + case constant.Supervisord: + configPath := "/etc/supervisord.conf" + pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)) + if pathSet.ID != 0 || pathSet.Value != "" { + configPath = pathSet.Value + } + logFilePath, _ = ini_conf.GetIniValue(configPath, "supervisord", "logfile") + case constant.Supervisor: + logDir := path.Join(global.Dir.DataDir, "tools", "supervisord", "log") + logFilePath = path.Join(logDir, req.Name) + } + + file, err := os.Open(logFilePath) + if err != nil { + return nil, err + } + defer file.Close() + stat, err := file.Stat() + if err != nil { + return nil, err + } + var ( + lines []string + isEndOfFile bool + scope string + logFileRes *dto.LogFileRes + ) + if stat.Size() > files.MaxReadFileSize { + lines, err = files.TailFromEnd(logFilePath, req.PageSize) + isEndOfFile = true + scope = "tail" + } else { + logFileRes, err = files.ReadFileByLine(logFilePath, req.Page, req.PageSize, req.Latest) + if err != nil { + return nil, err + } + scope = "page" + lines = logFileRes.Lines + } + + res := &response.FileLineContent{ + End: isEndOfFile, + Path: logFilePath, + TaskStatus: taskStatus, + Lines: lines, + Scope: scope, + } + if logFileRes != nil { + res.TotalLines = logFileRes.TotalLines + res.Total = logFileRes.TotalPages + res.End = logFileRes.IsEndOfFile + } + return res, nil +} + +func (f *FileService) GetPathByType(pathType string) string { + if pathType == "websiteDir" { + value, _ := settingRepo.GetValueByKey("WEBSITE_DIR") + if value == "" { + return path.Join(global.Dir.BaseDir, "1panel", "www") + } + return value + } + return "" +} + +func (f *FileService) BatchCheckFiles(req request.FilePathsCheck) []response.ExistFileInfo { + fileList := make([]response.ExistFileInfo, 0, len(req.Paths)) + for _, filePath := range req.Paths { + if info, err := os.Stat(filePath); err == nil { + fileList = append(fileList, response.ExistFileInfo{ + Size: info.Size(), + Name: info.Name(), + Path: filePath, + ModTime: info.ModTime(), + IsDir: info.IsDir(), + }) + } + } + return fileList +} + +func (f *FileService) GetHostMount() []dto.DiskInfo { + return loadDiskInfo() +} + +func (f *FileService) GetUsersAndGroups() (*response.UserGroupResponse, error) { + groupMap, err := getValidGroups() + if err != nil { + return nil, err + } + + users, groupSet, err := getValidUsers(groupMap) + if err != nil { + return nil, err + } + + var groups []string + for group := range groupSet { + groups = append(groups, group) + } + sort.Strings(groups) + + return &response.UserGroupResponse{ + Users: users, + Groups: groups, + }, nil +} + +func (f *FileService) BatchGetRemarks(req request.FileRemarkBatch) map[string]string { + remarks := make(map[string]string) + for _, filePath := range req.Paths { + remark, err := getFileRemark(filePath) + if err != nil { + if isXattrNotSupported(err) { + return map[string]string{} + } + continue + } + if remark == "" { + continue + } + remarks[filePath] = remark + } + + return remarks +} + +func (f *FileService) SetRemark(req request.FileRemarkUpdate) error { + if req.Remark == "" { + if err := unix.Lremovexattr(req.Path, fileRemarkXattr); err != nil { + if isXattrNotFound(err) { + return nil + } + if isXattrNotSupported(err) { + return buserr.WithDetail("ErrInvalidParams", "xattr not supported", err) + } + return err + } + return nil + } + + encoded := base64.StdEncoding.EncodeToString([]byte(req.Remark)) + if len(encoded) >= fileRemarkEncodedMaxLen { + return buserr.WithDetail("ErrInvalidParams", "remark length must be less than 256", nil) + } + if err := unix.Lsetxattr(req.Path, fileRemarkXattr, []byte(encoded), 0); err != nil { + if isXattrNotSupported(err) { + return buserr.WithDetail("ErrInvalidParams", "xattr not supported", err) + } + return err + } + return nil +} + +func getValidGroups() (map[string]bool, error) { + groupFile, err := os.Open("/etc/group") + if err != nil { + return nil, fmt.Errorf("failed to open /etc/group: %w", err) + } + defer groupFile.Close() + + groupMap := make(map[string]bool) + scanner := bufio.NewScanner(groupFile) + for scanner.Scan() { + parts := strings.Split(scanner.Text(), ":") + if len(parts) < 3 { + continue + } + groupName := parts[0] + gid, _ := strconv.Atoi(parts[2]) + if groupName == "root" || gid >= 1000 { + groupMap[groupName] = true + } + } + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("failed to scan /etc/group: %w", err) + } + return groupMap, nil +} + +func getFileRemark(filePath string) (string, error) { + size, err := unix.Lgetxattr(filePath, fileRemarkXattr, nil) + if err != nil { + if isXattrNotFound(err) { + return "", nil + } + return "", err + } + if size == 0 { + return "", nil + } + buf := make([]byte, size) + n, err := unix.Lgetxattr(filePath, fileRemarkXattr, buf) + if err != nil { + return "", err + } + decoded, err := base64.StdEncoding.DecodeString(string(buf[:n])) + if err != nil { + return "", err + } + return string(decoded), nil +} + +func isXattrNotSupported(err error) bool { + return errors.Is(err, unix.ENOTSUP) || errors.Is(err, unix.EOPNOTSUPP) +} + +func isXattrNotFound(err error) bool { + return errors.Is(err, unix.ENODATA) +} + +func getValidUsers(validGroups map[string]bool) ([]response.UserInfo, map[string]struct{}, error) { + passwdFile, err := os.Open("/etc/passwd") + if err != nil { + return nil, nil, fmt.Errorf("failed to open /etc/passwd: %w", err) + } + defer passwdFile.Close() + + var users []response.UserInfo + groupSet := make(map[string]struct{}) + scanner := bufio.NewScanner(passwdFile) + for scanner.Scan() { + parts := strings.Split(scanner.Text(), ":") + if len(parts) < 4 { + continue + } + + username := parts[0] + uid, _ := strconv.Atoi(parts[2]) + gid := parts[3] + + if username != "root" && uid < 1000 { + continue + } + + groupName := gid + if g, err := user.LookupGroupId(gid); err == nil { + groupName = g.Name + } + + if !validGroups[groupName] { + continue + } + + users = append(users, response.UserInfo{ + Username: username, + Group: groupName, + }) + groupSet[groupName] = struct{}{} + } + if err := scanner.Err(); err != nil { + return nil, nil, fmt.Errorf("failed to scan /etc/passwd: %w", err) + } + return users, groupSet, nil +} + +func (f *FileService) Convert(req request.FileConvertRequest) { + convertTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("FileConvert"), task.TaskExec, task.TaskScopeFileConvert, req.TaskID, 1) + if err != nil { + global.LOG.Errorf("Create convert task failed %v", err) + return + } + convertTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("FileConvert"), task.TaskExec, task.TaskScopeFileConvert), func(t *task.Task) (err error) { + for _, file := range req.Files { + input := filepath.Join(file.Path, file.InputFile) + nameOnly := file.InputFile[0 : len(file.InputFile)-len(file.Extension)] + output := filepath.Join(req.OutputPath, nameOnly+"."+file.OutputFormat) + status, errMsg := convert.MediaFile(input, output, file.OutputFormat, req.DeleteSource) + if status == "FAILED" { + convertTask.Log(fmt.Sprintf("%s -> %s [%s]: %s\n", + input, output, status, errMsg)) + } else { + convertTask.Log(fmt.Sprintf("%s -> %s [%s]: %s\n", + input, output, status, "SUCCESS")) + } + } + return nil + }, nil) + go func() { + _ = convertTask.Execute() + }() +} + +func (f *FileService) ConvertLog(req dto.PageInfo) (total int64, data []response.FileConvertLog, err error) { + logFilePath := filepath.Join(global.Dir.ConvertLogDir, "convert.log") + file, err := os.Open(logFilePath) + if err != nil { + return 0, nil, err + } + defer file.Close() + + const chunkSize = 64 * 1024 + stat, err := file.Stat() + if err != nil { + return 0, nil, err + } + fileSize := stat.Size() + + var ( + buf []byte + remainder []byte + offset = fileSize + lines []string + ) + + pageStart := int64((req.Page - 1) * req.PageSize) + pageEnd := pageStart + int64(req.PageSize) + + for offset > 0 { + readSize := chunkSize + if offset < int64(readSize) { + readSize = int(offset) + } + offset -= int64(readSize) + + tmp := make([]byte, readSize) + if _, err := file.ReadAt(tmp, offset); err != nil && err != io.EOF { + return 0, nil, err + } + + buf = append(tmp, remainder...) + linesSplit := strings.Split(string(buf), "\n") + + if offset > 0 { + remainder = []byte(linesSplit[0]) + linesSplit = linesSplit[1:] + } + + for i := len(linesSplit) - 1; i >= 0; i-- { + line := strings.TrimSpace(linesSplit[i]) + if line == "" { + continue + } + total++ + if total > pageStart && total <= pageEnd { + lines = append(lines, line) + } + if total >= pageEnd { + break + } + } + if total >= pageEnd { + break + } + } + + for _, line := range lines { + var entry response.FileConvertLog + if err := json.Unmarshal([]byte(line), &entry); err == nil { + data = append(data, entry) + } + } + + return total, data, nil +} diff --git a/agent/app/service/firewall.go b/agent/app/service/firewall.go new file mode 100644 index 0000000..f54c3ee --- /dev/null +++ b/agent/app/service/firewall.go @@ -0,0 +1,728 @@ +package service + +import ( + "context" + "fmt" + "sort" + "strconv" + "strings" + "sync" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/firewall" + fireClient "github.com/1Panel-dev/1Panel/agent/utils/firewall/client" + "github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables" + "github.com/jinzhu/copier" +) + +type FirewallService struct{} + +type IFirewallService interface { + LoadBaseInfo(tab string) (dto.FirewallBaseInfo, error) + SearchWithPage(search dto.RuleSearch) (int64, interface{}, error) + OperateFirewall(req dto.FirewallOperation) error + OperatePortRule(req dto.PortRuleOperate, reload bool) error + OperateForwardRule(req dto.ForwardRuleOperate) error + OperateAddressRule(req dto.AddrRuleOperate, reload bool) error + UpdatePortRule(req dto.PortRuleUpdate) error + UpdateAddrRule(req dto.AddrRuleUpdate) error + UpdateDescription(req dto.UpdateFirewallDescription) error + BatchOperateRule(req dto.BatchRuleOperate) error +} + +func NewIFirewallService() IFirewallService { + return &FirewallService{} +} + +func (u *FirewallService) LoadBaseInfo(tab string) (dto.FirewallBaseInfo, error) { + var baseInfo dto.FirewallBaseInfo + baseInfo.Version = "-" + baseInfo.Name = "-" + client, err := firewall.NewFirewallClient() + if err != nil { + global.LOG.Errorf("load firewall failed, err: %v", err) + baseInfo.IsExist = false + return baseInfo, nil + } + baseInfo.IsExist = true + baseInfo.Name = client.Name() + + var wg sync.WaitGroup + wg.Add(2) + go func() { + defer wg.Done() + baseInfo.PingStatus = firewall.LoadPingStatus() + baseInfo.Version, _ = client.Version() + }() + go func() { + defer wg.Done() + baseInfo.IsActive, _ = client.Status() + baseInfo.IsInit, baseInfo.IsBind = iptables.LoadInitStatus(baseInfo.Name, tab) + }() + wg.Wait() + return baseInfo, nil +} + +func (u *FirewallService) SearchWithPage(req dto.RuleSearch) (int64, interface{}, error) { + var ( + datas []fireClient.FireInfo + backDatas []fireClient.FireInfo + ) + + client, err := firewall.NewFirewallClient() + if err != nil { + return 0, nil, err + } + + var rules []fireClient.FireInfo + switch req.Type { + case "port": + rules, err = client.ListPort() + case "forward": + rules, err = client.ListForward() + case "address": + rules, err = client.ListAddress() + } + if err != nil { + return 0, nil, err + } + + if len(req.Info) != 0 { + for _, addr := range rules { + if strings.Contains(addr.Address, req.Info) || + strings.Contains(addr.Port, req.Info) || + strings.Contains(addr.TargetPort, req.Info) || + strings.Contains(addr.TargetIP, req.Info) { + datas = append(datas, addr) + } + } + } else { + datas = rules + } + if req.Type == "port" { + apps := u.loadPortByApp() + for i := 0; i < len(datas); i++ { + datas[i].UsedStatus = checkPortUsed(datas[i].Port, datas[i].Protocol, apps) + } + } + + var datasFilterStatus []fireClient.FireInfo + if len(req.Status) != 0 { + for _, data := range datas { + if req.Status == "free" && len(data.UsedStatus) == 0 { + datasFilterStatus = append(datasFilterStatus, data) + } + if req.Status == "used" && len(data.UsedStatus) != 0 { + datasFilterStatus = append(datasFilterStatus, data) + } + } + } else { + datasFilterStatus = datas + } + + var datasFilterStrategy []fireClient.FireInfo + if len(req.Strategy) != 0 { + for _, data := range datasFilterStatus { + if req.Strategy == data.Strategy { + datasFilterStrategy = append(datasFilterStrategy, data) + } + } + } else { + datasFilterStrategy = datasFilterStatus + } + + total, start, end := len(datasFilterStrategy), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + backDatas = make([]fireClient.FireInfo, 0) + } else { + if end >= total { + end = total + } + backDatas = datasFilterStrategy[start:end] + } + + datasFromDB, _ := hostRepo.ListFirewallRecord() + for i := 0; i < len(backDatas); i++ { + for _, des := range datasFromDB { + if req.Type != des.Type { + continue + } + if backDatas[i].Port == des.DstPort && + req.Type == "port" && + backDatas[i].Protocol == des.Protocol && + backDatas[i].Strategy == des.Strategy && + backDatas[i].Address == des.SrcIP { + backDatas[i].ID = des.ID + backDatas[i].Description = des.Description + break + } + if req.Type == "address" && backDatas[i].Strategy == des.Strategy && backDatas[i].Address == des.SrcIP { + backDatas[i].ID = des.ID + backDatas[i].Description = des.Description + break + } + } + } + + go u.cleanUnUsedData(client) + + return int64(total), backDatas, nil +} + +func (u *FirewallService) OperateFirewall(req dto.FirewallOperation) error { + client, err := firewall.NewFirewallClient() + if err != nil { + return err + } + needRestartDocker := false + switch req.Operation { + case "start": + if err := client.Start(); err != nil { + return err + } + if err := u.addPortsBeforeStart(client); err != nil { + _ = client.Stop() + return err + } + needRestartDocker = true + case "stop": + if err := client.Stop(); err != nil { + return err + } + needRestartDocker = true + case "restart": + if err := client.Restart(); err != nil { + return err + } + needRestartDocker = true + case "disableBanPing": + if err := firewall.UpdatePingStatus("0"); err != nil { + return err + } + _ = settingRepo.Update("BanPing", constant.StatusDisable) + return nil + case "enableBanPing": + if err := firewall.UpdatePingStatus("1"); err != nil { + return err + } + _ = settingRepo.Update("BanPing", constant.StatusEnable) + return nil + default: + return fmt.Errorf("not supported operation: %s", req.Operation) + } + if needRestartDocker && req.WithDockerRestart { + if err := controller.HandleRestart("docker"); err != nil { + return fmt.Errorf("failed to restart Docker: %v", err) + } + } + return nil +} + +func (u *FirewallService) OperatePortRule(req dto.PortRuleOperate, reload bool) error { + client, err := firewall.NewFirewallClient() + if err != nil { + return err + } + if len(req.Chain) == 0 && client.Name() == "iptables" { + req.Chain = iptables.Chain1PanelBasic + } + protos := strings.Split(req.Protocol, "/") + itemAddress := strings.Split(strings.TrimSuffix(req.Address, ","), ",") + + if client.Name() == "ufw" { + if strings.Contains(req.Port, ",") || strings.Contains(req.Port, "-") { + for _, proto := range protos { + for _, addr := range itemAddress { + if len(addr) == 0 { + addr = "Anywhere" + } + req.Address = addr + req.Port = strings.ReplaceAll(req.Port, "-", ":") + req.Protocol = proto + if err := u.operatePort(client, req); err != nil { + return err + } + req.Port = strings.ReplaceAll(req.Port, ":", "-") + if err := u.addPortRecord(req); err != nil { + return err + } + } + } + return nil + } + for _, addr := range itemAddress { + if len(addr) == 0 { + addr = "Anywhere" + } + if req.Protocol == "tcp/udp" { + req.Protocol = "" + } + req.Address = addr + if err := u.operatePort(client, req); err != nil { + return err + } + if len(req.Protocol) == 0 { + req.Protocol = "tcp/udp" + } + if err := u.addPortRecord(req); err != nil { + return err + } + } + return nil + } + + itemPorts := req.Port + for _, proto := range protos { + if strings.Contains(req.Port, "-") { + for _, addr := range itemAddress { + req.Protocol = proto + req.Address = addr + if err := u.operatePort(client, req); err != nil { + return err + } + if err := u.addPortRecord(req); err != nil { + return err + } + } + } else { + ports := strings.Split(itemPorts, ",") + for _, port := range ports { + if len(port) == 0 { + continue + } + for _, addr := range itemAddress { + req.Address = addr + req.Port = port + req.Protocol = proto + if err := u.operatePort(client, req); err != nil { + return err + } + if err := u.addPortRecord(req); err != nil { + return err + } + } + } + } + } + + if reload { + return client.Reload() + } + return nil +} + +func (u *FirewallService) OperateForwardRule(req dto.ForwardRuleOperate) error { + client, err := firewall.NewFirewallClient() + if err != nil { + return err + } + + rules, _ := client.ListForward() + i := 0 + for _, rule := range rules { + shouldKeep := true + for i := range req.Rules { + reqRule := &req.Rules[i] + if reqRule.TargetIP == "" { + reqRule.TargetIP = "127.0.0.1" + } + + if reqRule.Operation == "remove" { + for _, proto := range strings.Split(reqRule.Protocol, "/") { + if reqRule.Port == rule.Port && + reqRule.TargetPort == rule.TargetPort && + reqRule.TargetIP == rule.TargetIP && + proto == rule.Protocol && + reqRule.Interface == rule.Interface { + shouldKeep = false + break + } + } + } + } + if shouldKeep { + rules[i] = rule + i++ + } + } + rules = rules[:i] + + for _, rule := range rules { + for _, reqRule := range req.Rules { + if reqRule.Operation == "remove" { + continue + } + + for _, proto := range strings.Split(reqRule.Protocol, "/") { + if reqRule.Port == rule.Port && + reqRule.TargetPort == rule.TargetPort && + reqRule.TargetIP == rule.TargetIP && + proto == rule.Protocol && + reqRule.Interface == rule.Interface { + return buserr.New("ErrRecordExist") + } + } + } + } + + sort.SliceStable(req.Rules, func(i, j int) bool { + if req.Rules[i].Operation == "remove" && req.Rules[j].Operation != "remove" { + return true + } + if req.Rules[i].Operation != "remove" && req.Rules[j].Operation == "remove" { + return false + } + n1, _ := strconv.Atoi(req.Rules[i].Num) + n2, _ := strconv.Atoi(req.Rules[j].Num) + return n1 > n2 + }) + + for _, r := range req.Rules { + for _, p := range strings.Split(r.Protocol, "/") { + if r.TargetIP == "" { + r.TargetIP = "127.0.0.1" + } + if err = client.PortForward(fireClient.Forward{ + Num: r.Num, + Protocol: p, + Port: r.Port, + TargetIP: r.TargetIP, + TargetPort: r.TargetPort, + Interface: r.Interface, + }, r.Operation); err != nil { + if req.ForceDelete { + global.LOG.Error(err) + continue + } + return err + } + } + } + return nil +} + +func (u *FirewallService) OperateAddressRule(req dto.AddrRuleOperate, reload bool) error { + client, err := firewall.NewFirewallClient() + if err != nil { + return err + } + chain := "" + if client.Name() == "iptables" { + chain = iptables.Chain1PanelBasic + } + var fireInfo fireClient.FireInfo + if err := copier.Copy(&fireInfo, &req); err != nil { + return err + } + + addressList := strings.Split(req.Address, ",") + for i := 0; i < len(addressList); i++ { + if len(addressList[i]) == 0 { + continue + } + fireInfo.Address = addressList[i] + if err := client.RichRules(fireInfo, req.Operation); err != nil { + return err + } + req.Address = addressList[i] + if err := u.addAddressRecord(chain, req); err != nil { + return err + } + } + if reload { + return client.Reload() + } + return nil +} + +func (u *FirewallService) UpdatePortRule(req dto.PortRuleUpdate) error { + client, err := firewall.NewFirewallClient() + if err != nil { + return err + } + if err := u.OperatePortRule(req.OldRule, false); err != nil { + return err + } + if err := u.OperatePortRule(req.NewRule, false); err != nil { + return err + } + return client.Reload() +} + +func (u *FirewallService) UpdateAddrRule(req dto.AddrRuleUpdate) error { + client, err := firewall.NewFirewallClient() + if err != nil { + return err + } + if err := u.OperateAddressRule(req.OldRule, false); err != nil { + return err + } + if err := u.OperateAddressRule(req.NewRule, false); err != nil { + return err + } + return client.Reload() +} + +func (u *FirewallService) UpdateDescription(req dto.UpdateFirewallDescription) error { + firewall := model.Firewall{ + Type: req.Type, + Chain: req.Chain, + SrcIP: req.SrcIP, + DstIP: req.DstIP, + SrcPort: req.SrcPort, + DstPort: req.DstPort, + Protocol: req.Protocol, + Strategy: req.Strategy, + Description: req.Description, + } + + return hostRepo.SaveFirewallRecord(&firewall) +} + +func (u *FirewallService) BatchOperateRule(req dto.BatchRuleOperate) error { + client, err := firewall.NewFirewallClient() + if err != nil { + return err + } + if req.Type == "port" { + for _, rule := range req.Rules { + _ = u.OperatePortRule(rule, false) + } + return client.Reload() + } + for _, rule := range req.Rules { + itemRule := dto.AddrRuleOperate{Operation: rule.Operation, Address: rule.Address, Strategy: rule.Strategy} + _ = u.OperateAddressRule(itemRule, false) + } + return client.Reload() +} + +func OperateFirewallPort(oldPorts, newPorts []int) error { + client, err := firewall.NewFirewallClient() + if err != nil { + return err + } + for _, port := range newPorts { + if err := client.Port(fireClient.FireInfo{Port: strconv.Itoa(port), Protocol: "tcp", Strategy: "accept"}, "add"); err != nil { + return err + } + } + for _, port := range oldPorts { + if err := client.Port(fireClient.FireInfo{Port: strconv.Itoa(port), Protocol: "tcp", Strategy: "accept"}, "remove"); err != nil { + return err + } + } + return client.Reload() +} + +func (u *FirewallService) operatePort(client firewall.FirewallClient, req dto.PortRuleOperate) error { + var fireInfo fireClient.FireInfo + if err := copier.Copy(&fireInfo, &req); err != nil { + return err + } + + if client.Name() == "ufw" { + if len(fireInfo.Address) != 0 && !strings.EqualFold(fireInfo.Address, "Anywhere") { + return client.RichRules(fireInfo, req.Operation) + } + return client.Port(fireInfo, req.Operation) + } + + if len(fireInfo.Address) != 0 || fireInfo.Strategy == "drop" { + return client.RichRules(fireInfo, req.Operation) + } + return client.Port(fireInfo, req.Operation) +} + +type portOfApp struct { + AppName string + HttpPort string + HttpsPort string +} + +func (u *FirewallService) loadPortByApp() []portOfApp { + var datas []portOfApp + apps, err := appInstallRepo.ListBy(context.Background()) + if err != nil { + return datas + } + for i := 0; i < len(apps); i++ { + datas = append(datas, portOfApp{ + AppName: apps[i].App.Key, + HttpPort: strconv.Itoa(apps[i].HttpPort), + HttpsPort: strconv.Itoa(apps[i].HttpsPort), + }) + } + systemPort, err := settingRepo.Get(settingRepo.WithByKey("ServerPort")) + if err != nil { + return datas + } + datas = append(datas, portOfApp{AppName: "1panel", HttpPort: systemPort.Value}) + + return datas +} + +func (u *FirewallService) cleanUnUsedData(client firewall.FirewallClient) { + list, _ := client.ListPort() + addressList, _ := client.ListAddress() + list = append(list, addressList...) + if len(list) == 0 { + return + } + records, _ := hostRepo.ListFirewallRecord() + if len(records) == 0 { + return + } + for _, item := range list { + for i := 0; i < len(records); i++ { + if records[i].DstPort == item.Port && records[i].Protocol == item.Protocol && records[i].Strategy == item.Strategy && records[i].SrcIP == item.Address { + records = append(records[:i], records[i+1:]...) + } + } + } + + for _, record := range records { + _ = hostRepo.DeleteFirewallRecordByID(record.ID) + } +} + +func (u *FirewallService) addPortsBeforeStart(client firewall.FirewallClient) error { + if !global.IsMaster { + if err := client.Port(fireClient.FireInfo{Port: global.CONF.Base.Port, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil { + return err + } + } else { + var portSetting model.Setting + _ = global.CoreDB.Where("key = ?", "ServerPort").First(&portSetting).Error + if len(portSetting.Value) != 0 { + if err := client.Port(fireClient.FireInfo{Port: portSetting.Value, Protocol: "tcp", Strategy: "accept"}, "add"); err != nil { + return err + } + } + } + if err := client.Port(fireClient.FireInfo{Port: loadSSHPort(), Protocol: "tcp", Strategy: "accept"}, "add"); err != nil { + return err + } + if err := client.Port(fireClient.FireInfo{Port: "80", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil { + return err + } + if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "tcp", Strategy: "accept"}, "add"); err != nil { + return err + } + if err := client.Port(fireClient.FireInfo{Port: "443", Protocol: "udp", Strategy: "accept"}, "add"); err != nil { + return err + } + + return client.Reload() +} + +func (u *FirewallService) addPortRecord(req dto.PortRuleOperate) error { + if req.Operation == "remove" { + if req.ID != 0 { + return hostRepo.DeleteFirewallRecordByID(req.ID) + } + return nil + } + + if len(req.Description) == 0 { + return nil + } + if err := hostRepo.SaveFirewallRecord(&model.Firewall{ + Type: "port", + Chain: req.Chain, + DstPort: req.Port, + Protocol: req.Protocol, + SrcIP: req.Address, + Strategy: req.Strategy, + Description: req.Description, + }); err != nil { + return fmt.Errorf("add record %s/%s failed (strategy: %s, address: %s), err: %v", req.Port, req.Protocol, req.Strategy, req.Address, err) + } + + return nil +} + +func (u *FirewallService) addAddressRecord(chain string, req dto.AddrRuleOperate) error { + if req.Operation == "remove" { + if req.ID != 0 { + return hostRepo.DeleteFirewallRecordByID(req.ID) + } + return nil + } + + if err := hostRepo.SaveFirewallRecord(&model.Firewall{ + Type: "address", + Chain: chain, + SrcIP: req.Address, + Strategy: req.Strategy, + Description: req.Description, + }); err != nil { + return fmt.Errorf("add record failed (strategy: %s, address: %s), err: %v", req.Strategy, req.Address, err) + } + return nil +} + +func checkPortUsed(ports, proto string, apps []portOfApp) string { + var portList []int + rangeSplit := "" + if strings.Contains(ports, "-") { + rangeSplit = "-" + } + if strings.Contains(ports, ":") { + rangeSplit = ":" + } + if len(rangeSplit) != 0 { + port1, err := strconv.Atoi(strings.Split(ports, rangeSplit)[0]) + if err != nil { + global.LOG.Errorf(" convert string %s to int failed, err: %v", strings.Split(ports, rangeSplit)[0], err) + return "" + } + port2, err := strconv.Atoi(strings.Split(ports, rangeSplit)[1]) + if err != nil { + global.LOG.Errorf(" convert string %s to int failed, err: %v", strings.Split(ports, rangeSplit)[1], err) + return "" + } + for i := port1; i <= port2; i++ { + portList = append(portList, i) + } + } + if strings.Contains(ports, ",") { + portLists := strings.Split(ports, ",") + for _, item := range portLists { + portItem, _ := strconv.Atoi(item) + portList = append(portList, portItem) + } + } + if len(portList) != 0 { + var usedPorts []string + for _, port := range portList { + portItem := fmt.Sprintf("%v", port) + isUsedByApp := false + for _, app := range apps { + if app.HttpPort == portItem || app.HttpsPort == portItem { + isUsedByApp = true + usedPorts = append(usedPorts, fmt.Sprintf("%s (%s)", portItem, app.AppName)) + break + } + } + if !isUsedByApp && common.ScanPortWithProto(port, proto) { + usedPorts = append(usedPorts, fmt.Sprintf("%v", port)) + } + } + return strings.Join(usedPorts, ",") + } + + for _, app := range apps { + if app.HttpPort == ports || app.HttpsPort == ports { + return app.AppName + } + } + + return "" +} diff --git a/agent/app/service/ftp.go b/agent/app/service/ftp.go new file mode 100644 index 0000000..368cd4f --- /dev/null +++ b/agent/app/service/ftp.go @@ -0,0 +1,249 @@ +package service + +import ( + "os" + "sort" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/toolbox" + "github.com/jinzhu/copier" +) + +type FtpService struct{} + +type IFtpService interface { + LoadBaseInfo() (dto.FtpBaseInfo, error) + SearchWithPage(search dto.SearchWithPage) (int64, interface{}, error) + Operate(operation string) error + Create(req dto.FtpCreate) (uint, error) + Delete(req dto.BatchDeleteReq) error + Update(req dto.FtpUpdate) error + Sync() error + LoadLog(req dto.FtpLogSearch) (int64, interface{}, error) +} + +func NewIFtpService() IFtpService { + return &FtpService{} +} + +func (f *FtpService) LoadBaseInfo() (dto.FtpBaseInfo, error) { + var baseInfo dto.FtpBaseInfo + client, err := toolbox.NewFtpClient() + if err != nil { + return baseInfo, err + } + baseInfo.IsActive, baseInfo.IsExist = client.Status() + return baseInfo, nil +} + +func (f *FtpService) LoadLog(req dto.FtpLogSearch) (int64, interface{}, error) { + client, err := toolbox.NewFtpClient() + if err != nil { + return 0, nil, err + } + logItem, err := client.LoadLogs(req.User, req.Operation) + if err != nil { + return 0, nil, err + } + sort.Slice(logItem, func(i, j int) bool { + return logItem[i].Time > logItem[j].Time + }) + var logs []toolbox.FtpLog + total, start, end := len(logItem), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + logs = make([]toolbox.FtpLog, 0) + } else { + if end >= total { + end = total + } + logs = logItem[start:end] + } + return int64(total), logs, nil +} + +func (u *FtpService) Operate(operation string) error { + client, err := toolbox.NewFtpClient() + if err != nil { + return err + } + return client.Operate(operation) +} + +func (f *FtpService) SearchWithPage(req dto.SearchWithPage) (int64, interface{}, error) { + total, lists, err := ftpRepo.Page(req.Page, req.PageSize, ftpRepo.WithLikeUser(req.Info), repo.WithOrderBy("created_at desc")) + if err != nil { + return 0, nil, err + } + var users []dto.FtpInfo + for _, user := range lists { + var item dto.FtpInfo + if err := copier.Copy(&item, &user); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + item.Password, _ = encrypt.StringDecrypt(item.Password) + users = append(users, item) + } + return total, users, err +} + +func (f *FtpService) Sync() error { + client, err := toolbox.NewFtpClient() + if err != nil { + return err + } + lists, err := client.LoadList() + if err != nil { + return nil + } + listsInDB, err := ftpRepo.GetList() + if err != nil { + return err + } + currentData := make(map[string]model.Ftp) + for _, item := range listsInDB { + currentData[item.User] = item + } + sameData := make(map[string]struct{}) + for _, item := range lists { + if itemInDB, ok := currentData[item.User]; ok { + sameData[item.User] = struct{}{} + if item.Path != itemInDB.Path || item.Status != itemInDB.Status { + if err := ftpRepo.Update(itemInDB.ID, map[string]interface{}{"path": item.Path, "status": item.Status}); err != nil { + return err + } + } + } else { + if err := ftpRepo.Create(&model.Ftp{User: item.User, Path: item.Path, Status: item.Status}); err != nil { + return err + } + } + } + for _, item := range listsInDB { + if _, ok := sameData[item.User]; !ok { + _ = ftpRepo.Update(item.ID, map[string]interface{}{"status": constant.StatusDeleted}) + } + } + return nil +} + +func (f *FtpService) Create(req dto.FtpCreate) (uint, error) { + if _, err := os.Stat(req.Path); err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(req.Path, os.ModePerm); err != nil { + return 0, err + } + } else { + return 0, err + } + } + pass, err := encrypt.StringEncrypt(req.Password) + if err != nil { + return 0, err + } + userInDB, _ := ftpRepo.Get(ftpRepo.WithByUser(req.User)) + if userInDB.ID != 0 { + return 0, buserr.New("ErrRecordExist") + } + client, err := toolbox.NewFtpClient() + if err != nil { + return 0, err + } + if err := client.UserAdd(req.User, req.Password, req.Path); err != nil { + return 0, err + } + var ftp model.Ftp + if err := copier.Copy(&ftp, &req); err != nil { + return 0, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + ftp.Status = constant.StatusEnable + ftp.Password = pass + if err := ftpRepo.Create(&ftp); err != nil { + return 0, err + } + return ftp.ID, nil +} + +func (f *FtpService) Delete(req dto.BatchDeleteReq) error { + client, err := toolbox.NewFtpClient() + if err != nil { + return err + } + for _, id := range req.Ids { + ftpItem, err := ftpRepo.Get(repo.WithByID(id)) + if err != nil { + return err + } + _ = client.UserDel(ftpItem.User) + _ = ftpRepo.Delete(repo.WithByID(id)) + } + return nil +} + +func (f *FtpService) Update(req dto.FtpUpdate) error { + if _, err := os.Stat(req.Path); err != nil { + if os.IsNotExist(err) { + if err := os.MkdirAll(req.Path, os.ModePerm); err != nil { + return err + } + } else { + return err + } + } + + pass, err := encrypt.StringEncrypt(req.Password) + if err != nil { + return err + } + ftpItem, _ := ftpRepo.Get(repo.WithByID(req.ID)) + if ftpItem.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + passItem, err := encrypt.StringDecrypt(ftpItem.Password) + if err != nil { + return err + } + + client, err := toolbox.NewFtpClient() + if err != nil { + return err + } + needReload := false + updates := make(map[string]interface{}) + if req.Password != passItem { + if err := client.SetPasswd(ftpItem.User, req.Password); err != nil { + return err + } + updates["password"] = pass + needReload = true + } + if req.Status != ftpItem.Status { + if err := client.SetStatus(ftpItem.User, req.Status); err != nil { + return err + } + updates["status"] = req.Status + needReload = true + } + if req.Path != ftpItem.Path { + if err := client.SetPath(ftpItem.User, req.Path); err != nil { + return err + } + updates["path"] = req.Path + needReload = true + } + if req.Description != ftpItem.Description { + updates["description"] = req.Description + } + if needReload { + _ = client.Reload() + } + if len(updates) != 0 { + return ftpRepo.Update(ftpItem.ID, updates) + } + return nil +} diff --git a/agent/app/service/group.go b/agent/app/service/group.go new file mode 100644 index 0000000..025035f --- /dev/null +++ b/agent/app/service/group.go @@ -0,0 +1,97 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/jinzhu/copier" +) + +type GroupService struct{} + +type IGroupService interface { + List(req dto.OperateByType) ([]dto.GroupInfo, error) + Create(req dto.GroupCreate) error + Update(req dto.GroupUpdate) error + Delete(id uint) error + GetDefault() (model.Group, error) +} + +func NewIGroupService() IGroupService { + return &GroupService{} +} + +func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) { + options := []repo.DBOption{ + repo.WithOrderBy("is_default desc"), + repo.WithOrderBy("created_at desc"), + } + if len(req.Type) != 0 { + options = append(options, repo.WithByType(req.Type)) + } + var ( + groups []model.Group + err error + ) + groups, err = groupRepo.GetList(options...) + if err != nil { + return nil, buserr.New("ErrRecordNotFound") + } + var dtoUsers []dto.GroupInfo + for _, group := range groups { + var item dto.GroupInfo + if err := copier.Copy(&item, &group); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + dtoUsers = append(dtoUsers, item) + } + return dtoUsers, err +} + +func (u *GroupService) Create(req dto.GroupCreate) error { + group, _ := groupRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type)) + if group.ID != 0 { + return buserr.New("ErrRecordExist") + } + if err := copier.Copy(&group, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if err := groupRepo.Create(&group); err != nil { + return err + } + return nil +} + +func (u *GroupService) Delete(id uint) error { + group, _ := groupRepo.Get(repo.WithByID(id)) + if group.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if group.IsDefault { + return buserr.New("ErrGroupIsDefault") + } + if group.Type == "website" { + websites, _ := websiteRepo.List(websiteRepo.WithGroupID(group.ID)) + if len(websites) > 0 { + return buserr.New("ErrGroupIsInWebsiteUse") + } + } + return groupRepo.Delete(repo.WithByID(id)) +} + +func (u *GroupService) Update(req dto.GroupUpdate) error { + if req.IsDefault { + if err := groupRepo.CancelDefault(req.Type); err != nil { + return err + } + } + upMap := make(map[string]interface{}) + upMap["name"] = req.Name + upMap["is_default"] = req.IsDefault + return groupRepo.Update(req.ID, upMap) +} + +func (u *GroupService) GetDefault() (model.Group, error) { + return groupRepo.Get(groupRepo.WithByDefault(true)) +} diff --git a/agent/app/service/helper.go b/agent/app/service/helper.go new file mode 100644 index 0000000..725e8f7 --- /dev/null +++ b/agent/app/service/helper.go @@ -0,0 +1,15 @@ +package service + +import ( + "context" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +func getTxAndContext() (tx *gorm.DB, ctx context.Context) { + tx = global.DB.Begin() + ctx = context.WithValue(context.Background(), constant.DB, tx) + return +} diff --git a/agent/app/service/host_tool.go b/agent/app/service/host_tool.go new file mode 100644 index 0000000..d9ba896 --- /dev/null +++ b/agent/app/service/host_tool.go @@ -0,0 +1,621 @@ +package service + +import ( + "bytes" + "fmt" + "os" + "os/exec" + "os/user" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/ini_conf" + "github.com/pkg/errors" + "gopkg.in/ini.v1" +) + +type HostToolService struct{} + +type IHostToolService interface { + GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) + CreateToolConfig(req request.HostToolCreate) error + OperateTool(req request.HostToolReq) error + OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) + OperateSupervisorProcess(req request.SupervisorProcessConfig) error + GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error) + OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error) +} + +func NewIHostToolService() IHostToolService { + return &HostToolService{} +} + +func (h *HostToolService) GetToolStatus(req request.HostToolReq) (*response.HostToolRes, error) { + res := &response.HostToolRes{} + res.Type = req.Type + switch req.Type { + case constant.Supervisord: + supervisorConfig := &response.Supervisor{} + if !cmd.Which(constant.Supervisord) { + supervisorConfig.IsExist = false + res.Config = supervisorConfig + return res, nil + } + supervisorConfig.IsExist = true + serviceExist, _ := controller.CheckExist(constant.Supervisord) + if !serviceExist { + serviceExist, _ = controller.CheckExist(constant.Supervisor) + if !serviceExist { + supervisorConfig.IsExist = false + res.Config = supervisorConfig + return res, nil + } else { + supervisorConfig.ServiceName = constant.Supervisor + } + } else { + supervisorConfig.ServiceName = constant.Supervisord + } + + serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)) + if serviceNameSet.ID != 0 || serviceNameSet.Value != "" { + supervisorConfig.ServiceName = serviceNameSet.Value + } + + versionRes, _ := cmd.RunDefaultWithStdoutBashC("supervisord -v") + supervisorConfig.Version = strings.TrimSuffix(versionRes, "\n") + _, ctlRrr := exec.LookPath("supervisorctl") + supervisorConfig.CtlExist = ctlRrr == nil + + active, _ := controller.CheckActive(supervisorConfig.ServiceName) + if active { + supervisorConfig.Status = "running" + } else { + supervisorConfig.Status = "stopped" + } + + pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)) + if pathSet.ID != 0 || pathSet.Value != "" { + supervisorConfig.ConfigPath = pathSet.Value + res.Config = supervisorConfig + return res, nil + } else { + supervisorConfig.Init = true + } + + servicePath := "/usr/lib/systemd/system/supervisor.service" + fileOp := files.NewFileOp() + if !fileOp.Stat(servicePath) { + servicePath = "/usr/lib/systemd/system/supervisord.service" + } + if fileOp.Stat(servicePath) { + startCmd, _ := ini_conf.GetIniValue(servicePath, "Service", "ExecStart") + if startCmd != "" { + args := strings.Fields(startCmd) + cIndex := -1 + for i, arg := range args { + if arg == "-c" { + cIndex = i + break + } + } + if cIndex != -1 && cIndex+1 < len(args) { + supervisorConfig.ConfigPath = args[cIndex+1] + } + } + } + if supervisorConfig.ConfigPath == "" { + configPath := "/etc/supervisord.conf" + if !fileOp.Stat(configPath) { + configPath = "/etc/supervisor/supervisord.conf" + if fileOp.Stat(configPath) { + supervisorConfig.ConfigPath = configPath + } + } + } + + res.Config = supervisorConfig + } + return res, nil +} + +func (h *HostToolService) CreateToolConfig(req request.HostToolCreate) error { + switch req.Type { + case constant.Supervisord: + fileOp := files.NewFileOp() + if !fileOp.Stat(req.ConfigPath) { + return buserr.New("ErrConfigNotFound") + } + cfg, err := ini.Load(req.ConfigPath) + if err != nil { + return err + } + service, err := cfg.GetSection("include") + if err != nil { + return err + } + targetKey, err := service.GetKey("files") + if err != nil { + return err + } + if targetKey != nil { + _, err = service.NewKey(";files", targetKey.Value()) + if err != nil { + return err + } + } + supervisorDir := path.Join(global.Dir.DataDir, "tools", "supervisord") + includeDir := path.Join(supervisorDir, "supervisor.d") + if !fileOp.Stat(includeDir) { + if err = fileOp.CreateDir(includeDir, constant.DirPerm); err != nil { + return err + } + } + logDir := path.Join(supervisorDir, "log") + if !fileOp.Stat(logDir) { + if err = fileOp.CreateDir(logDir, constant.DirPerm); err != nil { + return err + } + } + includePath := path.Join(includeDir, "*.ini") + targetKey.SetValue(includePath) + if err = cfg.SaveTo(req.ConfigPath); err != nil { + return err + } + + serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)) + if serviceNameSet.ID != 0 { + if err = settingRepo.Update(constant.SupervisorServiceName, req.ServiceName); err != nil { + return err + } + } else { + if err = settingRepo.Create(constant.SupervisorServiceName, req.ServiceName); err != nil { + return err + } + } + + configPathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)) + if configPathSet.ID != 0 { + if err = settingRepo.Update(constant.SupervisorConfigPath, req.ConfigPath); err != nil { + return err + } + } else { + if err = settingRepo.Create(constant.SupervisorConfigPath, req.ConfigPath); err != nil { + return err + } + } + if err = controller.HandleRestart(req.ServiceName); err != nil { + global.LOG.Errorf("[init] restart %s failed err %s", req.ServiceName, err.Error()) + return err + } + } + return nil +} + +func (h *HostToolService) OperateTool(req request.HostToolReq) error { + serviceName := req.Type + if req.Type == constant.Supervisord { + serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)) + if serviceNameSet.ID != 0 || serviceNameSet.Value != "" { + serviceName = serviceNameSet.Value + } + } + return controller.Handle(req.Operate, serviceName) +} + +func (h *HostToolService) OperateToolConfig(req request.HostToolConfig) (*response.HostToolConfig, error) { + fileOp := files.NewFileOp() + res := &response.HostToolConfig{} + configPath := "" + serviceName := "supervisord" + switch req.Type { + case constant.Supervisord: + pathSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorConfigPath)) + if pathSet.ID != 0 || pathSet.Value != "" { + configPath = pathSet.Value + } + serviceNameSet, _ := settingRepo.Get(settingRepo.WithByKey(constant.SupervisorServiceName)) + if serviceNameSet.ID != 0 || serviceNameSet.Value != "" { + serviceName = serviceNameSet.Value + } + } + switch req.Operate { + case "get": + content, err := fileOp.GetContent(configPath) + if err != nil { + return nil, err + } + res.Content = string(content) + case "set": + file, err := fileOp.OpenFile(configPath) + if err != nil { + return nil, err + } + oldContent, err := fileOp.GetContent(configPath) + if err != nil { + return nil, err + } + fileInfo, err := file.Stat() + if err != nil { + return nil, err + } + if err = fileOp.WriteFile(configPath, strings.NewReader(req.Content), fileInfo.Mode()); err != nil { + return nil, err + } + if err = controller.HandleRestart(serviceName); err != nil { + _ = fileOp.WriteFile(configPath, bytes.NewReader(oldContent), fileInfo.Mode()) + return nil, err + } + } + + return res, nil +} + +func (h *HostToolService) OperateSupervisorProcess(req request.SupervisorProcessConfig) error { + var ( + supervisordDir = path.Join(global.Dir.DataDir, "tools", "supervisord") + fileOp = files.NewFileOp() + ) + if req.Operate == "update" || req.Operate == "create" { + if !fileOp.Stat(req.Dir) { + return buserr.New("ErrConfigDirNotFound") + } + _, err := user.Lookup(req.User) + if err != nil { + return buserr.WithMap("ErrUserFindErr", map[string]interface{}{"name": req.User, "err": err.Error()}, err) + } + } + return handleProcess(supervisordDir, req, "") +} + +func handleProcess(supervisordDir string, req request.SupervisorProcessConfig, containerName string) error { + var ( + fileOp = files.NewFileOp() + logDir = path.Join(supervisordDir, "log") + includeDir = path.Join(supervisordDir, "supervisor.d") + outLog = path.Join(logDir, fmt.Sprintf("%s.out.log", req.Name)) + errLog = path.Join(logDir, fmt.Sprintf("%s.err.log", req.Name)) + iniPath = path.Join(includeDir, fmt.Sprintf("%s.ini", req.Name)) + ) + if containerName != "" { + outLog = path.Join("/var/log/supervisor", fmt.Sprintf("%s.out.log", req.Name)) + errLog = path.Join("/var/log/supervisor", fmt.Sprintf("%s.err.log", req.Name)) + } + switch req.Operate { + case "create": + if fileOp.Stat(iniPath) { + return buserr.New("ErrConfigAlreadyExist") + } + configFile := ini.Empty() + section, err := configFile.NewSection(fmt.Sprintf("program:%s", req.Name)) + if err != nil { + return err + } + _, _ = section.NewKey("command", strings.TrimSpace(req.Command)) + _, _ = section.NewKey("directory", req.Dir) + _, _ = section.NewKey("autorestart", req.AutoRestart) + _, _ = section.NewKey("autostart", req.AutoStart) + _, _ = section.NewKey("startsecs", "3") + _, _ = section.NewKey("stdout_logfile", outLog) + _, _ = section.NewKey("stderr_logfile", errLog) + _, _ = section.NewKey("stdout_logfile_maxbytes", "2MB") + _, _ = section.NewKey("stderr_logfile_maxbytes", "2MB") + _, _ = section.NewKey("user", req.User) + _, _ = section.NewKey("priority", "999") + _, _ = section.NewKey("numprocs", req.Numprocs) + _, _ = section.NewKey("process_name", "%(program_name)s_%(process_num)02d") + if req.Environment != "" { + _, _ = section.NewKey("environment", req.Environment) + } + + if err = configFile.SaveTo(iniPath); err != nil { + return err + } + if err := operateSupervisorCtl("reread", "", "", includeDir, containerName); err != nil { + return err + } + return operateSupervisorCtl("update", "", "", includeDir, containerName) + case "update": + configFile, err := ini.Load(iniPath) + if err != nil { + return err + } + section, err := configFile.GetSection(fmt.Sprintf("program:%s", req.Name)) + if err != nil { + return err + } + + commandKey := section.Key("command") + commandKey.SetValue(strings.TrimSpace(req.Command)) + directoryKey := section.Key("directory") + directoryKey.SetValue(req.Dir) + userKey := section.Key("user") + userKey.SetValue(req.User) + numprocsKey := section.Key("numprocs") + numprocsKey.SetValue(req.Numprocs) + autoRestart := section.Key("autorestart") + autoRestart.SetValue(req.AutoRestart) + autoStart := section.Key("autostart") + autoStart.SetValue(req.AutoStart) + environment := section.Key("environment") + environment.SetValue(req.Environment) + + if err = configFile.SaveTo(iniPath); err != nil { + return err + } + if err := operateSupervisorCtl("reread", "", "", includeDir, containerName); err != nil { + return err + } + return operateSupervisorCtl("update", "", "", includeDir, containerName) + case "restart": + return operateSupervisorCtl("restart", req.Name, "", includeDir, containerName) + case "start": + return operateSupervisorCtl("start", req.Name, "", includeDir, containerName) + case "stop": + return operateSupervisorCtl("stop", req.Name, "", includeDir, containerName) + case "delete": + _ = operateSupervisorCtl("remove", "", req.Name, includeDir, containerName) + _ = fileOp.DeleteFile(iniPath) + _ = fileOp.DeleteFile(outLog) + _ = fileOp.DeleteFile(errLog) + if err := operateSupervisorCtl("reread", "", "", includeDir, containerName); err != nil { + return err + } + return operateSupervisorCtl("update", "", "", includeDir, containerName) + } + return nil +} + +func handleProcessConfig(configDir, containerName string) ([]response.SupervisorProcessConfig, error) { + var ( + result []response.SupervisorProcessConfig + ) + entries, err := os.ReadDir(configDir) + if err != nil { + return nil, err + } + for _, entry := range entries { + if entry.IsDir() { + continue + } + fileName := entry.Name() + f, err := ini.Load(path.Join(configDir, fileName)) + if err != nil { + global.LOG.Errorf("get %s file err %s", fileName, err.Error()) + continue + } + if strings.HasSuffix(fileName, ".ini") { + config := response.SupervisorProcessConfig{} + name := strings.TrimSuffix(fileName, ".ini") + config.Name = name + section, err := f.GetSection(fmt.Sprintf("program:%s", name)) + if err != nil { + global.LOG.Errorf("get %s file section err %s", fileName, err.Error()) + continue + } + if command, _ := section.GetKey("command"); command != nil { + config.Command = command.Value() + } + if directory, _ := section.GetKey("directory"); directory != nil { + config.Dir = directory.Value() + } + if user, _ := section.GetKey("user"); user != nil { + config.User = user.Value() + } + if numprocs, _ := section.GetKey("numprocs"); numprocs != nil { + config.Numprocs = numprocs.Value() + } + if autoRestart, _ := section.GetKey("autorestart"); autoRestart != nil { + config.AutoRestart = autoRestart.Value() + } + if autoStart, _ := section.GetKey("autostart"); autoStart != nil { + config.AutoStart = autoStart.Value() + } + if environment, _ := section.GetKey("environment"); environment != nil { + config.Environment = environment.Value() + } + _ = getProcessStatus(&config, containerName) + result = append(result, config) + } + } + return result, nil +} + +func (h *HostToolService) GetSupervisorProcessConfig() ([]response.SupervisorProcessConfig, error) { + configDir := path.Join(global.Dir.DataDir, "tools", "supervisord", "supervisor.d") + fileOp := files.NewFileOp() + if !fileOp.Stat(configDir) { + _ = fileOp.CreateDir(configDir, constant.DirPerm) + return []response.SupervisorProcessConfig{}, nil + } + return handleProcessConfig(configDir, "") +} + +func (h *HostToolService) OperateSupervisorProcessFile(req request.SupervisorProcessFileReq) (string, error) { + var ( + includeDir = path.Join(global.Dir.DataDir, "tools", "supervisord", "supervisor.d") + ) + return handleSupervisorFile(req, includeDir, "", "") +} + +func handleSupervisorFile(req request.SupervisorProcessFileReq, includeDir, containerName, logFile string) (string, error) { + var ( + fileOp = files.NewFileOp() + group = fmt.Sprintf("program:%s", req.Name) + configPath = path.Join(includeDir, fmt.Sprintf("%s.ini", req.Name)) + err error + ) + switch req.File { + case "err.log": + if logFile == "" { + logFile, err = ini_conf.GetIniValue(configPath, group, "stderr_logfile") + if err != nil { + return "", err + } + } + switch req.Operate { + case "get": + content, err := fileOp.GetContent(logFile) + if err != nil { + return "", err + } + return string(content), nil + case "clear": + if err = fileOp.WriteFile(logFile, strings.NewReader(""), constant.DirPerm); err != nil { + return "", err + } + } + + case "out.log": + if logFile == "" { + logFile, err = ini_conf.GetIniValue(configPath, group, "stdout_logfile") + if err != nil { + return "", err + } + } + switch req.Operate { + case "get": + content, err := fileOp.GetContent(logFile) + if err != nil { + return "", err + } + return string(content), nil + case "clear": + if err = fileOp.WriteFile(logFile, strings.NewReader(""), constant.DirPerm); err != nil { + return "", err + } + } + + case "config": + switch req.Operate { + case "get": + content, err := fileOp.GetContent(configPath) + if err != nil { + return "", err + } + return string(content), nil + case "update": + if req.Content == "" { + return "", buserr.New("ErrConfigIsNull") + } + if err := fileOp.WriteFile(configPath, strings.NewReader(req.Content), constant.DirPerm); err != nil { + return "", err + } + return "", operateSupervisorCtl("update", "", req.Name, includeDir, containerName) + } + + } + return "", nil +} + +func operateSupervisorCtl(operate, name, group, includeDir, containerName string) error { + processNames := []string{operate} + if name != "" { + f, err := ini.Load(path.Join(includeDir, fmt.Sprintf("%s.ini", name))) + if err != nil { + return err + } + section, err := f.GetSection(fmt.Sprintf("program:%s", name)) + if err != nil { + return err + } + numprocsNum := "" + if numprocs, _ := section.GetKey("numprocs"); numprocs != nil { + numprocsNum = numprocs.Value() + } + if numprocsNum == "" { + return buserr.New("ErrConfigParse") + } + processNames = append(processNames, getProcessName(name, numprocsNum)...) + } + if group != "" { + processNames = append(processNames, group) + } + + var ( + output string + err error + ) + if containerName != "" { + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30 * time.Second)) + output, err = cmdMgr.RunWithStdoutBashCf("docker exec %s supervisorctl %s", containerName, strings.Join(processNames, " ")) + } else { + var out []byte + out, err = exec.Command("supervisorctl", processNames...).Output() + output = string(out) + } + if err != nil && output != "" { + return errors.New(output) + } + return err +} + +func getProcessName(name, numprocs string) []string { + var ( + processNames []string + ) + num, err := strconv.Atoi(numprocs) + if err != nil { + return processNames + } + if num == 1 { + processNames = append(processNames, fmt.Sprintf("%s:%s_00", name, name)) + } else { + for i := 0; i < num; i++ { + processName := fmt.Sprintf("%s:%s_0%s", name, name, strconv.Itoa(i)) + if i >= 10 { + processName = fmt.Sprintf("%s:%s_%s", name, name, strconv.Itoa(i)) + } + processNames = append(processNames, processName) + } + } + return processNames +} + +func getProcessStatus(config *response.SupervisorProcessConfig, containerName string) error { + var ( + processNames = []string{"status"} + output string + err error + ) + processNames = append(processNames, getProcessName(config.Name, config.Numprocs)...) + if containerName != "" { + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(3 * time.Second)) + output, err = cmdMgr.RunWithStdoutBashCf("docker exec %s supervisorctl %s", containerName, strings.Join(processNames, " ")) + } else { + var out []byte + out, err = exec.Command("supervisorctl", processNames...).Output() + output = string(out) + } + if output == "" && err != nil { + return err + } + lines := strings.Split(output, "\n") + for _, line := range lines { + line = strings.TrimPrefix(line, "stdout:") + fields := strings.Fields(line) + if len(fields) >= 4 { + status := response.ProcessStatus{ + Name: fields[0], + Status: fields[1], + } + if fields[1] == "RUNNING" { + status.PID = strings.TrimSuffix(fields[3], ",") + status.Uptime = fields[5] + } else { + status.Msg = strings.Join(fields[2:], " ") + } + config.Status = append(config.Status, status) + } + } + return nil +} diff --git a/agent/app/service/image.go b/agent/app/service/image.go new file mode 100644 index 0000000..2f1e433 --- /dev/null +++ b/agent/app/service/image.go @@ -0,0 +1,595 @@ +package service + +import ( + "bufio" + "context" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "path" + "sort" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/docker/docker/api/types/build" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/registry" + "github.com/docker/docker/pkg/archive" + "github.com/docker/docker/pkg/homedir" +) + +type ImageService struct{} + +type IImageService interface { + Page(req dto.PageImage) (int64, interface{}, error) + List() ([]dto.Options, error) + ListAll() ([]dto.ImageInfo, error) + ImageBuild(req dto.ImageBuild) error + ImagePull(req dto.ImagePull) error + ImageLoad(req dto.ImageLoad) error + ImageSave(req dto.ImageSave) error + ImagePush(req dto.ImagePush) error + ImageRemove(req dto.BatchDelete) error + ImageTag(req dto.ImageTag) error +} + +func NewIImageService() IImageService { + return &ImageService{} +} +func (u *ImageService) Page(req dto.PageImage) (int64, interface{}, error) { + var ( + list []image.Summary + records []dto.ImageInfo + backDatas []dto.ImageInfo + ) + client, err := docker.NewDockerClient() + if err != nil { + return 0, nil, err + } + defer client.Close() + list, err = client.ImageList(context.Background(), image.ListOptions{}) + if err != nil { + return 0, nil, err + } + containers, _ := client.ContainerList(context.Background(), container.ListOptions{All: true}) + if len(req.Name) != 0 { + length, count := len(list), 0 + for count < length { + hasTag := false + for _, tag := range list[count].RepoTags { + if strings.Contains(tag, req.Name) { + hasTag = true + break + } + } + if !hasTag { + list = append(list[:count], list[(count+1):]...) + length-- + } else { + count++ + } + } + } + + for _, image := range list { + records = append(records, dto.ImageInfo{ + ID: image.ID, + Tags: image.RepoTags, + IsUsed: checkUsed(image.ID, containers), + CreatedAt: time.Unix(image.Created, 0), + Size: image.Size, + }) + } + switch req.OrderBy { + case "size": + sort.Slice(records, func(i, j int) bool { + if req.Order == constant.OrderAsc { + return records[i].Size < records[j].Size + } + return records[i].Size > records[j].Size + }) + case "isUsed": + sort.Slice(records, func(i, j int) bool { + if req.Order == constant.OrderAsc { + return !records[i].IsUsed + } + return records[i].IsUsed + }) + case "tags": + sort.Slice(records, func(i, j int) bool { + if len(records[i].Tags) == 0 || len(records[j].Tags) == 0 { + return true + } + if req.Order == constant.OrderAsc { + return records[i].Tags[0] < records[j].Tags[0] + } + return records[i].Tags[0] > records[j].Tags[0] + }) + default: + sort.Slice(records, func(i, j int) bool { + if req.Order == constant.OrderAsc { + return records[i].CreatedAt.Before(records[j].CreatedAt) + } + return records[i].CreatedAt.After(records[j].CreatedAt) + }) + } + + imageDescriptions, _ := settingRepo.GetDescriptionList(repo.WithByType("image")) + for i := 0; i < len(list); i++ { + for _, desc := range imageDescriptions { + if "sha256:"+desc.ID == records[i].ID { + records[i].Description = desc.Description + records[i].IsPinned = desc.IsPinned + } + } + } + sort.Slice(records, func(i, j int) bool { + return records[i].IsPinned && !records[j].IsPinned + }) + total, start, end := len(records), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + backDatas = make([]dto.ImageInfo, 0) + } else { + if end >= total { + end = total + } + backDatas = records[start:end] + } + + return int64(total), backDatas, nil +} + +func (u *ImageService) ListAll() ([]dto.ImageInfo, error) { + var records []dto.ImageInfo + client, err := docker.NewDockerClient() + if err != nil { + return nil, err + } + defer client.Close() + list, err := client.ImageList(context.Background(), image.ListOptions{}) + if err != nil { + return nil, err + } + containers, _ := client.ContainerList(context.Background(), container.ListOptions{All: true}) + for _, image := range list { + records = append(records, dto.ImageInfo{ + ID: image.ID, + Tags: image.RepoTags, + IsUsed: checkUsed(image.ID, containers), + CreatedAt: time.Unix(image.Created, 0), + Size: image.Size, + }) + } + return records, nil +} + +func (u *ImageService) List() ([]dto.Options, error) { + var ( + list []image.Summary + backDatas []dto.Options + ) + client, err := docker.NewDockerClient() + if err != nil { + return nil, err + } + defer client.Close() + list, err = client.ImageList(context.Background(), image.ListOptions{}) + if err != nil { + return nil, err + } + for _, image := range list { + for _, tag := range image.RepoTags { + backDatas = append(backDatas, dto.Options{ + Option: tag, + }) + } + } + return backDatas, nil +} + +func (u *ImageService) ImageBuild(req dto.ImageBuild) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + fileName := "Dockerfile" + if req.From == "edit" { + dir := fmt.Sprintf("%s/docker/build/%s", global.Dir.DataDir, strings.ReplaceAll(req.Name, ":", "_")) + if _, err := os.Stat(dir); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(dir, os.ModePerm); err != nil { + return err + } + } + + pathItem := fmt.Sprintf("%s/Dockerfile", dir) + file, err := os.OpenFile(pathItem, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(string(req.Dockerfile)) + write.Flush() + req.Dockerfile = dir + } else { + fileName = path.Base(req.Dockerfile) + req.Dockerfile = path.Dir(req.Dockerfile) + } + tar, err := archive.TarWithOptions(req.Dockerfile+"/", &archive.TarOptions{}) + if err != nil { + return err + } + + opts := build.ImageBuildOptions{ + Dockerfile: fileName, + Tags: []string{req.Name}, + Remove: true, + Labels: stringsToMap(req.Tags), + BuildArgs: stringsToMap2(req.Args), + } + taskItem, err := task.NewTaskWithOps(req.Name, task.TaskBuild, task.TaskScopeImage, req.TaskID, 1) + if err != nil { + return fmt.Errorf("new task for image build failed, err: %v", err) + } + + go func() { + defer tar.Close() + taskItem.AddSubTask(i18n.GetMsgByKey("ImageBuild"), func(t *task.Task) error { + dockerCli := docker.NewClientWithExist(client) + if err := dockerCli.BuildImageWithProcessAndOptions(taskItem, tar, opts); err != nil { + return err + } + return nil + }, nil) + + _ = taskItem.Execute() + }() + + return nil +} + +func (u *ImageService) ImagePull(req dto.ImagePull) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + taskItem, err := task.NewTaskWithOps(strings.Join(req.ImageName, ","), task.TaskPull, task.TaskScopeImage, req.TaskID, 1) + if err != nil { + return fmt.Errorf("new task for image pull failed, err: %v", err) + } + + for _, item := range req.ImageName { + itemName := strings.ReplaceAll(path.Base(item), ":", "_") + taskItem.AddSubTask(i18n.GetWithName("ImagePull", itemName), func(t *task.Task) error { + taskItem.Logf("----------------- %s -----------------", itemName) + options := image.PullOptions{} + imageName := item + if req.RepoID == 0 { + hasAuth, authStr := loadAuthInfo(item) + if hasAuth { + options.RegistryAuth = authStr + } + } else { + repo, err := imageRepoRepo.Get(repo.WithByID(req.RepoID)) + taskItem.LogWithStatus(i18n.GetMsgByKey("ImageRepoAuthFromDB"), err) + if err != nil { + return err + } + if repo.Auth { + authConfig := registry.AuthConfig{ + Username: repo.Username, + Password: repo.Password, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return err + } + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + options.RegistryAuth = authStr + } + imageName = repo.DownloadUrl + "/" + item + } + dockerCli := docker.NewClientWithExist(client) + err = dockerCli.PullImageWithProcessAndOptions(taskItem, imageName, options) + taskItem.LogWithStatus(i18n.GetMsgByKey("TaskPull"), err) + if err != nil { + return err + } + return nil + }, nil) + } + go func() { + _ = taskItem.Execute() + }() + return nil +} + +func (u *ImageService) ImageLoad(req dto.ImageLoad) error { + taskItem, err := task.NewTaskWithOps(strings.Join(req.Paths, ","), task.TaskImport, task.TaskScopeImage, req.TaskID, 1) + if err != nil { + return fmt.Errorf("new task for image import failed, err: %v", err) + } + + go func() { + client, err := docker.NewDockerClient() + if err != nil { + taskItem.Log("Failed to create Docker client: " + err.Error()) + return + } + defer client.Close() + + for _, itemPath := range req.Paths { + currentPath := itemPath + itemName := path.Base(currentPath) + taskItem.AddSubTask(i18n.GetWithName("TaskImport", itemName), func(t *task.Task) error { + taskItem.Logf("----------------- %s -----------------", itemName) + file, err := os.Open(currentPath) + if err != nil { + return err + } + defer file.Close() + res, err := client.ImageLoad(context.TODO(), file) + if err != nil { + return err + } + defer res.Body.Close() + content, err := io.ReadAll(res.Body) + if err != nil { + return err + } + if strings.Contains(string(content), "Error") { + return errors.New(string(content)) + } + return nil + }, nil) + } + _ = taskItem.Execute() + }() + return nil +} + +func (u *ImageService) ImageSave(req dto.ImageSave) error { + taskItem, err := task.NewTaskWithOps(req.Name, task.TaskExport, task.TaskScopeImage, req.TaskID, 1) + if err != nil { + return fmt.Errorf("new task for image export failed, err: %v", err) + } + taskItem.AddSubTask(i18n.GetWithName("TaskExport", req.Name), func(t *task.Task) error { + taskItem.Log(req.TagName) + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + + out, err := client.ImageSave(context.TODO(), []string{req.TagName}) + if err != nil { + return err + } + defer out.Close() + file, err := os.OpenFile(fmt.Sprintf("%s/%s.tar", req.Path, req.Name), os.O_WRONLY|os.O_CREATE|os.O_EXCL, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + if _, err = io.Copy(file, out); err != nil { + return err + } + return nil + }, nil) + go func() { + _ = taskItem.Execute() + }() + return nil +} + +func (u *ImageService) ImageTag(req dto.ImageTag) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + + imageItem, err := client.ImageInspect(context.Background(), req.SourceID) + if err != nil { + return err + } + + for _, tag := range req.Tags { + isNew := true + for _, tagOld := range imageItem.RepoTags { + if tag == tagOld { + isNew = false + break + } + } + if isNew { + if err := client.ImageTag(context.TODO(), req.SourceID, tag); err != nil { + return err + } + } + } + for _, tagOld := range imageItem.RepoTags { + isDel := true + for _, tag := range req.Tags { + if tag == tagOld { + isDel = false + break + } + } + if isDel { + if _, err := client.ImageRemove(context.TODO(), tagOld, image.RemoveOptions{}); err != nil { + return err + } + } + } + return nil +} + +func (u *ImageService) ImagePush(req dto.ImagePush) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + + taskItem, err := task.NewTaskWithOps(req.Name, task.TaskPush, task.TaskScopeImage, req.TaskID, 1) + if err != nil { + return fmt.Errorf("new task for image push failed, err: %v", err) + } + + go func() { + options := image.PushOptions{All: true} + var imageRepo model.ImageRepo + newName := "" + taskItem.AddSubTask(i18n.GetMsgByKey("ImagePush"), func(t *task.Task) error { + imageRepo, err = imageRepoRepo.Get(repo.WithByID(req.RepoID)) + newName = fmt.Sprintf("%s/%s", imageRepo.DownloadUrl, req.Name) + taskItem.LogWithStatus(i18n.GetMsgByKey("ImageRepoAuthFromDB"), err) + if err != nil { + return err + } + options = image.PushOptions{All: true} + authConfig := registry.AuthConfig{ + Username: imageRepo.Username, + Password: imageRepo.Password, + } + encodedJSON, _ := json.Marshal(authConfig) + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + options.RegistryAuth = authStr + return nil + }, nil) + taskItem.AddSubTask(i18n.GetMsgByKey("ImageRenameTag"), func(t *task.Task) error { + taskItem.Log(i18n.GetWithName("ImageNewTag", newName)) + if newName != req.TagName { + if err := client.ImageTag(context.TODO(), req.TagName, newName); err != nil { + return err + } + } + return nil + }, nil) + taskItem.AddSubTask(i18n.GetMsgByKey("TaskPush"), func(t *task.Task) error { + dockerCli := docker.NewClientWithExist(client) + if err := dockerCli.PushImageWithProcessAndOptions(taskItem, newName, options); err != nil { + return err + } + return nil + }, nil) + _ = taskItem.Execute() + }() + + return nil +} + +func (u *ImageService) ImageRemove(req dto.BatchDelete) error { + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + taskItem, err := task.NewTaskWithOps(task.TaskScopeImage, task.TaskDelete, task.TaskScopeContainer, req.TaskID, 1) + if err != nil { + global.LOG.Errorf("new task for create container failed, err: %v", err) + return err + } + + for _, id := range req.Names { + taskItem.AddSubTask(i18n.GetMsgByKey("TaskDelete")+id, func(t *task.Task) error { + imageItem, err := client.ImageInspect(context.TODO(), id) + if err != nil { + return err + } + if _, err := client.ImageRemove(context.TODO(), id, image.RemoveOptions{Force: req.Force, PruneChildren: true}); err != nil { + if strings.Contains(err.Error(), "image is being used") || strings.Contains(err.Error(), "is using") { + if strings.Contains(id, "sha256:") { + return buserr.New("ErrObjectInUsed") + } + return buserr.WithDetail("ErrInUsed", id, nil) + } + if strings.Contains(err.Error(), "image has dependent") { + return buserr.New("ErrObjectBeDependent") + } + return err + } + taskItem.Log(i18n.GetMsgWithMap("ImageRemoveHelper", map[string]interface{}{"name": id, "size": common.LoadSizeUnit2F(float64(imageItem.Size))})) + return nil + }, nil) + } + if len(req.TaskID) == 0 { + return taskItem.Execute() + } + go func() { + _ = taskItem.Execute() + }() + return nil +} + +func checkUsed(imageID string, containers []container.Summary) bool { + for _, container := range containers { + if container.ImageID == imageID { + return true + } + } + return false +} + +func loadAuthInfo(image string) (bool, string) { + if !strings.Contains(image, "/") { + return false, "" + } + homeDir := homedir.Get() + confPath := path.Join(homeDir, ".docker/config.json") + configFileBytes, err := os.ReadFile(confPath) + if err != nil { + return false, "" + } + var config dockerConfig + if err = json.Unmarshal(configFileBytes, &config); err != nil { + return false, "" + } + var ( + user string + passwd string + ) + imagePrefix := strings.Split(image, "/")[0] + if val, ok := config.Auths[imagePrefix]; ok { + itemByte, _ := base64.StdEncoding.DecodeString(val.Auth) + itemStr := string(itemByte) + if strings.Contains(itemStr, ":") { + user = strings.Split(itemStr, ":")[0] + passwd = strings.Split(itemStr, ":")[1] + } + } + authConfig := registry.AuthConfig{ + Username: user, + Password: passwd, + } + encodedJSON, err := json.Marshal(authConfig) + if err != nil { + return false, "" + } + authStr := base64.URLEncoding.EncodeToString(encodedJSON) + return true, authStr +} + +type dockerConfig struct { + Auths map[string]authConfig `json:"auths"` +} +type authConfig struct { + Auth string `json:"auth"` +} diff --git a/agent/app/service/image_repo.go b/agent/app/service/image_repo.go new file mode 100644 index 0000000..fca1925 --- /dev/null +++ b/agent/app/service/image_repo.go @@ -0,0 +1,299 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/jinzhu/copier" + "github.com/pkg/errors" +) + +type ImageRepoService struct{} + +type IImageRepoService interface { + Page(search dto.SearchWithPage) (int64, interface{}, error) + List() ([]dto.ImageRepoOption, error) + Login(req dto.OperateByID) error + Create(req dto.ImageRepoCreate) error + Update(req dto.ImageRepoUpdate) error + Delete(req dto.OperateByID) error +} + +func NewIImageRepoService() IImageRepoService { + return &ImageRepoService{} +} + +func (u *ImageRepoService) Page(req dto.SearchWithPage) (int64, interface{}, error) { + total, ops, err := imageRepoRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderBy("created_at desc")) + var dtoOps []dto.ImageRepoInfo + for _, op := range ops { + var item dto.ImageRepoInfo + if err := copier.Copy(&item, &op); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + dtoOps = append(dtoOps, item) + } + return total, dtoOps, err +} + +func (u *ImageRepoService) Login(req dto.OperateByID) error { + repo, err := imageRepoRepo.Get(repo.WithByID(req.ID)) + if err != nil { + return err + } + if repo.Auth { + if err := u.CheckConn(repo.DownloadUrl, repo.Username, repo.Password); err != nil { + _ = imageRepoRepo.Update(repo.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error()}) + return err + } + } + _ = imageRepoRepo.Update(repo.ID, map[string]interface{}{"status": constant.StatusSuccess}) + return nil +} + +func (u *ImageRepoService) List() ([]dto.ImageRepoOption, error) { + ops, err := imageRepoRepo.List(repo.WithOrderBy("created_at desc")) + var dtoOps []dto.ImageRepoOption + for _, op := range ops { + if op.Status == constant.StatusSuccess { + var item dto.ImageRepoOption + if err := copier.Copy(&item, &op); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + dtoOps = append(dtoOps, item) + } + } + return dtoOps, err +} + +func (u *ImageRepoService) Create(req dto.ImageRepoCreate) error { + if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) { + return buserr.New("ErrCmdIllegal") + } + imageRepo, _ := imageRepoRepo.Get(repo.WithByName(req.Name)) + if imageRepo.ID != 0 { + return buserr.New("ErrRecordExist") + } + + if req.Protocol == "http" { + if err := u.handleRegistries(req.DownloadUrl, "", "create"); err != nil { + return fmt.Errorf("create registry %s failed, err: %v", req.DownloadUrl, err) + } + if err := stopBeforeUpdateRepo(); err != nil { + return err + } + } + + if req.Auth { + if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil { + return err + } + } + + if err := copier.Copy(&imageRepo, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + + imageRepo.Status = constant.StatusSuccess + return imageRepoRepo.Create(&imageRepo) +} + +func (u *ImageRepoService) Delete(req dto.OperateByID) error { + if req.ID == 1 { + return errors.New("The default value cannot be edit !") + } + itemRepo, _ := imageRepoRepo.Get(repo.WithByID(req.ID)) + if itemRepo.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if itemRepo.Auth { + _, _ = cmd.NewCommandMgr().RunWithStdout("docker", "logout", "-i", itemRepo.DownloadUrl) + } + if itemRepo.Protocol == "https" { + return imageRepoRepo.Delete(repo.WithByID(req.ID)) + } + if err := u.handleRegistries("", itemRepo.DownloadUrl, "delete"); err != nil { + return fmt.Errorf("delete registry %s failed, err: %v", itemRepo.DownloadUrl, err) + } + if err := validateDockerConfig(); err != nil { + return err + } + if err := imageRepoRepo.Delete(repo.WithByID(req.ID)); err != nil { + return err + } + go func() { + _ = controller.HandleRestart("docker") + }() + return nil +} + +func (u *ImageRepoService) Update(req dto.ImageRepoUpdate) error { + if req.ID == 1 { + return errors.New("The default value cannot be deleted !") + } + if cmd.CheckIllegal(req.Username, req.Password, req.DownloadUrl) { + return buserr.New("ErrCmdIllegal") + } + repo, err := imageRepoRepo.Get(repo.WithByID(req.ID)) + if err != nil { + return err + } + needRestart := false + if repo.Protocol == "http" && req.Protocol == "https" { + if err := u.handleRegistries("", repo.DownloadUrl, "delete"); err != nil { + return fmt.Errorf("delete registry %s failed, err: %v", repo.DownloadUrl, err) + } + needRestart = true + } + if repo.Protocol == "http" && req.Protocol == "http" { + if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil { + return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err) + } + needRestart = repo.DownloadUrl == req.DownloadUrl + } + if repo.Protocol == "https" && req.Protocol == "http" { + if req.DownloadUrl != repo.DownloadUrl { + if err := u.handleRegistries(req.DownloadUrl, repo.DownloadUrl, "update"); err != nil { + return fmt.Errorf("update registry %s => %s failed, err: %v", repo.DownloadUrl, req.DownloadUrl, err) + } + needRestart = true + } + } + if needRestart { + if err := stopBeforeUpdateRepo(); err != nil { + return err + } + } + if repo.Auth { + _, _ = cmd.NewCommandMgr().RunWithStdout("docker", "logout", "-i", repo.DownloadUrl) + } + if req.Auth { + if err := u.CheckConn(req.DownloadUrl, req.Username, req.Password); err != nil { + return err + } + } + + upMap := make(map[string]interface{}) + upMap["download_url"] = req.DownloadUrl + upMap["protocol"] = req.Protocol + upMap["username"] = req.Username + upMap["password"] = req.Password + upMap["auth"] = req.Auth + upMap["status"] = constant.StatusSuccess + upMap["message"] = "" + return imageRepoRepo.Update(req.ID, upMap) +} + +func (u *ImageRepoService) CheckConn(host, user, password string) error { + cmdMgr := cmd.NewCommandMgr() + stdout, err := cmdMgr.RunWithStdout("docker", "login", "-u", user, "-p", password, host) + if err != nil { + return fmt.Errorf("stdout: %s, stderr: %v", stdout, err) + } + if strings.Contains(string(stdout), "Login Succeeded") { + return nil + } + return errors.New(string(stdout)) +} + +func (u *ImageRepoService) handleRegistries(newHost, delHost, handle string) error { + err := createIfNotExistDaemonJsonFile() + if err != nil { + return err + } + daemonMap := make(map[string]interface{}) + file, err := os.ReadFile(constant.DaemonJsonPath) + if err != nil { + return err + } + if len(file) != 0 { + if err := json.Unmarshal(file, &daemonMap); err != nil { + return err + } + } + + iRegistries := daemonMap["insecure-registries"] + registries, _ := iRegistries.([]interface{}) + switch handle { + case "create": + registries = common.RemoveRepeatElement(append(registries, newHost)) + case "update": + for i, regi := range registries { + if regi == delHost { + registries = append(registries[:i], registries[i+1:]...) + } + } + registries = common.RemoveRepeatElement(append(registries, newHost)) + case "delete": + for i, regi := range registries { + if regi == delHost { + registries = append(registries[:i], registries[i+1:]...) + } + } + } + if len(registries) == 0 { + delete(daemonMap, "insecure-registries") + } else { + daemonMap["insecure-registries"] = registries + } + newJson, err := json.MarshalIndent(daemonMap, "", "\t") + if err != nil { + return err + } + if err := os.WriteFile(constant.DaemonJsonPath, newJson, 0640); err != nil { + return err + } + return nil +} + +func stopBeforeUpdateRepo() error { + if err := validateDockerConfig(); err != nil { + return err + } + dockerService, _ := controller.LoadServiceName("docker") + if len(dockerService) == 0 { + return errors.New("docker service not found") + } + client, err := controller.New() + if err != nil { + return err + } + if err := client.Operate("restart", dockerService); err != nil { + return fmt.Errorf("failed to restart Docker: %v", err) + } + ticker := time.NewTicker(3 * time.Second) + defer ticker.Stop() + ctx, cancel := context.WithTimeout(context.Background(), time.Second*30) + if err := func() error { + for range ticker.C { + select { + case <-ctx.Done(): + cancel() + return errors.New("the docker service cannot be restarted") + default: + active, _ := client.IsActive(dockerService) + if active { + global.LOG.Info("docker restart with new conf successful!") + return nil + } + } + } + return nil + }(); err != nil { + return err + } + return nil +} diff --git a/agent/app/service/iptables.go b/agent/app/service/iptables.go new file mode 100644 index 0000000..77b91c1 --- /dev/null +++ b/agent/app/service/iptables.go @@ -0,0 +1,401 @@ +package service + +import ( + "errors" + "fmt" + "net" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/firewall/client" + "github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables" +) + +type IIptablesService interface { + Search(req dto.SearchPageWithType) (int64, interface{}, error) + OperateRule(req dto.IptablesRuleOp, withSave bool) error + BatchOperate(req dto.IptablesBatchOperate) error + LoadChainStatus(req dto.OperationWithName) dto.IptablesChainStatus + + Operate(req dto.IptablesOp) error +} + +type IptablesService struct{} + +func NewIIptablesService() IIptablesService { + return &IptablesService{} +} + +func (s *IptablesService) Search(req dto.SearchPageWithType) (int64, interface{}, error) { + rules, err := iptables.ReadFilterRulesByChain(req.Type) + if err != nil { + return 0, nil, fmt.Errorf("failed to read iptables rules: %w", err) + } + var records []iptables.FilterRules + total, start, end := len(rules), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + records = make([]iptables.FilterRules, 0) + } else { + if end >= total { + end = total + } + records = rules[start:end] + } + + rulesInDB, _ := hostRepo.ListFirewallRecord(hostRepo.WithByChain(req.Type)) + + for i := 0; i < len(records); i++ { + for _, item := range rulesInDB { + if records[i].Strategy == item.Strategy && + records[i].DstIP == item.DstIP && + fmt.Sprintf("%v", records[i].DstPort) == item.DstPort && + records[i].Protocol == item.Protocol && + records[i].SrcIP == item.SrcIP && + fmt.Sprintf("%v", records[i].SrcPort) == item.SrcPort { + records[i].ID = item.ID + records[i].Description = item.Description + } + } + } + return int64(total), records, nil +} + +func (s *IptablesService) OperateRule(req dto.IptablesRuleOp, withSave bool) error { + action := "ACCEPT" + if req.Strategy == "drop" { + action = "DROP" + } + policy := iptables.FilterRules{ + Protocol: req.Protocol, + SrcIP: req.SrcIP, + DstIP: req.DstIP, + Strategy: action, + } + if req.SrcPort != 0 { + policy.SrcPort = fmt.Sprintf("%v", req.SrcPort) + } + if req.DstPort != 0 { + policy.DstPort = fmt.Sprintf("%v", req.DstPort) + } + + name := iptables.InputFileName + if req.Chain == iptables.Chain1PanelOutput { + name = iptables.OutputFileName + } + switch req.Operation { + case "add": + if err := s.validateRuleInput(&req); err != nil { + return err + } + + if err := iptables.AddFilterRule(req.Chain, policy); err != nil { + return fmt.Errorf("failed to add iptables rule: %w", err) + } + + if len(req.Description) != 0 { + rule := &model.Firewall{ + Chain: req.Chain, + Protocol: req.Protocol, + SrcIP: req.SrcIP, + SrcPort: policy.SrcPort, + DstIP: req.DstIP, + DstPort: policy.DstPort, + Strategy: req.Strategy, + Description: req.Description, + } + + if err := hostRepo.SaveFirewallRecord(rule); err != nil { + return fmt.Errorf("failed to save rule to database: %w", err) + } + } + case "remove": + if err := iptables.DeleteFilterRule(req.Chain, policy); err != nil { + return fmt.Errorf("failed to remove iptables rule: %w", err) + } + if req.ID != 0 { + if err := hostRepo.DeleteFirewallRecordByID(req.ID); err != nil { + return fmt.Errorf("failed to delete rule from database: %w", err) + } + } + } + + if !withSave { + return nil + } + if err := iptables.SaveRulesToFile(iptables.FilterTab, req.Chain, name); err != nil { + global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err) + } + return nil +} + +func (s *IptablesService) BatchOperate(req dto.IptablesBatchOperate) error { + if len(req.Rules) == 0 { + return errors.New("no rules to operate") + } + for _, rule := range req.Rules { + if err := s.OperateRule(rule, false); err != nil { + return err + } + } + chain := iptables.Chain1PanelInput + fileName := iptables.InputFileName + if req.Rules[0].Chain == iptables.Chain1PanelOutput { + chain = iptables.Chain1PanelOutput + fileName = iptables.OutputFileName + } + if err := iptables.SaveRulesToFile(iptables.FilterTab, chain, fileName); err != nil { + global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err) + } + return nil +} + +func (s *IptablesService) Operate(req dto.IptablesOp) error { + targetChain := iptables.ChainInput + if req.Name == iptables.Chain1PanelOutput { + targetChain = iptables.ChainOutput + } + switch req.Operate { + case "init-base": + if ok := cmd.Which("iptables"); !ok { + return fmt.Errorf("failed to find iptables") + } + if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelBasicBefore); err != nil { + return err + } + if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelBasic); err != nil { + return err + } + if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelBasicAfter); err != nil { + return err + } + if err := initPreRules(); err != nil { + return err + } + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore, 1); err != nil { + return err + } + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic, 2); err != nil { + return err + } + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter, 3); err != nil { + return err + } + if err := iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.BasicBeforeFileName); err != nil { + return err + } + if err := iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.BasicAfterFileName); err != nil { + return err + } + _ = settingRepo.Update("IptablesStatus", constant.StatusEnable) + return nil + case "init-forward": + if err := client.EnableIptablesForward(); err != nil { + return err + } + _ = settingRepo.Update("IptablesForwardStatus", constant.StatusEnable) + return nil + case "init-advance": + if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelInput); err != nil { + return err + } + if err := iptables.AddChain(iptables.FilterTab, iptables.Chain1PanelOutput); err != nil { + return err + } + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainOutput, iptables.Chain1PanelOutput, 1); err != nil { + return err + } + number := loadBindNumber(iptables.Chain1PanelInput) + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelInput, number); err != nil { + return err + } + _ = settingRepo.Update("IptablesInputStatus", constant.StatusEnable) + _ = settingRepo.Update("IptablesOutputStatus", constant.StatusEnable) + return nil + case "bind-base": + if err := initPreRules(); err != nil { + return err + } + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore, 1); err != nil { + return err + } + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic, 2); err != nil { + return err + } + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter, 3); err != nil { + return err + } + _ = settingRepo.Update("IptablesStatus", constant.StatusEnable) + return nil + case "bind-base-without-init": + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore, 1); err != nil { + return err + } + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic, 2); err != nil { + return err + } + if err := iptables.BindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter, 3); err != nil { + return err + } + _ = settingRepo.Update("IptablesStatus", constant.StatusEnable) + return nil + case "unbind-base": + if err := iptables.UnbindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicAfter); err != nil { + return err + } + if err := iptables.UnbindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasicBefore); err != nil { + return err + } + if err := iptables.UnbindChain(iptables.FilterTab, iptables.ChainInput, iptables.Chain1PanelBasic); err != nil { + return err + } + _ = settingRepo.Update("IptablesStatus", constant.StatusDisable) + return nil + case "bind": + if err := iptables.BindChain(iptables.FilterTab, targetChain, req.Name, loadBindNumber(req.Name)); err != nil { + return err + } + if req.Name == iptables.Chain1PanelInput { + _ = settingRepo.Update("IptablesInputStatus", constant.StatusEnable) + } + if req.Name == iptables.Chain1PanelOutput { + _ = settingRepo.Update("IptablesOutputStatus", constant.StatusEnable) + } + return nil + case "unbind": + if err := iptables.UnbindChain(iptables.FilterTab, targetChain, req.Name); err != nil { + return err + } + if req.Name == iptables.Chain1PanelInput { + _ = settingRepo.Update("IptablesInputStatus", constant.StatusDisable) + } + if req.Name == iptables.Chain1PanelOutput { + _ = settingRepo.Update("IptablesOutputStatus", constant.StatusDisable) + } + return nil + } + return nil +} + +func (s *IptablesService) LoadChainStatus(req dto.OperationWithName) dto.IptablesChainStatus { + var data dto.IptablesChainStatus + var err error + data.DefaultStrategy, err = iptables.LoadDefaultStrategy(req.Name) + if err != nil { + global.LOG.Error(err) + } + switch req.Name { + case iptables.Chain1PanelBasic: + data.IsBind, err = iptables.CheckChainBind(iptables.FilterTab, iptables.ChainInput, req.Name) + case iptables.Chain1PanelInput: + data.IsBind, err = iptables.CheckChainBind(iptables.FilterTab, iptables.ChainInput, req.Name) + case iptables.Chain1PanelOutput: + data.IsBind, err = iptables.CheckChainBind(iptables.FilterTab, iptables.ChainOutput, req.Name) + } + return data +} + +func (s *IptablesService) validateRuleInput(req *dto.IptablesRuleOp) error { + if req.Protocol != "" { + validProtocols := map[string]bool{"tcp": true, "udp": true, "icmp": true, "all": true} + if !validProtocols[strings.ToLower(req.Protocol)] { + return fmt.Errorf("invalid protocol: %s, must be tcp, udp, icmp or all", req.Protocol) + } + } + if req.SrcIP != "" { + if err := s.validateIPOrCIDR(req.SrcIP); err != nil { + return fmt.Errorf("invalid source IP: %w", err) + } + } + if req.DstIP != "" { + if err := s.validateIPOrCIDR(req.DstIP); err != nil { + return fmt.Errorf("invalid destination IP: %w", err) + } + } + if req.SrcPort > 65535 { + return fmt.Errorf("invalid source port: %d, must be between 1 and 65535", req.SrcPort) + } + if req.DstPort > 65535 { + return fmt.Errorf("invalid destination port: %d, must be between 1 and 65535", req.DstPort) + } + if (req.SrcPort > 0 || req.DstPort > 0) && req.Protocol == "" { + return fmt.Errorf("port specification requires protocol (tcp/udp)") + } + + return nil +} + +func (s *IptablesService) validateIPOrCIDR(ipStr string) error { + if strings.Contains(ipStr, "/") { + _, _, err := net.ParseCIDR(ipStr) + if err != nil { + return fmt.Errorf("invalid CIDR format: %w", err) + } + return nil + } + ip := net.ParseIP(ipStr) + if ip == nil { + return fmt.Errorf("invalid IP address format") + } + + return nil +} + +func loadBindNumber(chain string) int { + if chain == iptables.Chain1PanelOutput { + return 1 + } + number := 1 + if exist, _ := iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelBasicBefore); exist { + number++ + } + if exist, _ := iptables.CheckChainExist(iptables.FilterTab, iptables.Chain1PanelBasic); exist { + number++ + } + return number +} + +func initPreRules() error { + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.IoRuleIn); err != nil { + return err + } + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.EstablishedRule); err != nil { + return err + } + panelPort := LoadPanelPort() + if len(panelPort) == 0 { + return errors.New("find 1panel service port failed") + } + ports := []string{"80", "443", panelPort, loadSSHPort()} + for _, item := range ports { + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, fmt.Sprintf("-p tcp -m tcp --dport %v -j ACCEPT", item)); err != nil { + return err + } + } + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, "-p udp -m udp --dport 443 -j ACCEPT"); err != nil { + return err + } + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.DropAllTcp); err != nil { + return err + } + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.DropAllUdp); err != nil { + return err + } + return nil +} + +func LoadPanelPort() string { + if !global.IsMaster { + return global.CONF.Base.Port + } else { + var portSetting model.Setting + _ = global.CoreDB.Where("key = ?", "ServerPort").First(&portSetting).Error + if len(portSetting.Value) != 0 { + return portSetting.Value + } + } + return "" +} diff --git a/agent/app/service/logs.go b/agent/app/service/logs.go new file mode 100644 index 0000000..de27b8b --- /dev/null +++ b/agent/app/service/logs.go @@ -0,0 +1,63 @@ +package service + +import ( + "os" + "sort" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" +) + +type LogService struct{} + +type ILogService interface { + ListSystemLogFile() ([]string, error) +} + +func NewILogService() ILogService { + return &LogService{} +} + +func (u *LogService) ListSystemLogFile() ([]string, error) { + var listFile []string + files, err := os.ReadDir(global.Dir.LogDir) + if err != nil { + return nil, err + } + listMap := make(map[string]struct{}) + for _, item := range files { + if item.IsDir() || !strings.HasPrefix(item.Name(), "1Panel") { + continue + } + if item.Name() == "1Panel.log" || item.Name() == "1Panel-Core.log" { + itemName := time.Now().Format("2006-01-02") + if _, ok := listMap[itemName]; ok { + continue + } + listMap[itemName] = struct{}{} + listFile = append(listFile, itemName) + continue + } + itemFileName := strings.TrimPrefix(item.Name(), "1Panel-Core-") + itemFileName = strings.TrimPrefix(itemFileName, "1Panel-") + itemFileName = strings.TrimSuffix(itemFileName, ".gz") + itemFileName = strings.TrimSuffix(itemFileName, ".log") + if len(itemFileName) == 0 { + continue + } + if _, ok := listMap[itemFileName]; ok { + continue + } + listMap[itemFileName] = struct{}{} + listFile = append(listFile, itemFileName) + } + if len(listFile) < 2 { + return listFile, nil + } + sort.Slice(listFile, func(i, j int) bool { + return listFile[i] > listFile[j] + }) + + return listFile, nil +} diff --git a/agent/app/service/mcp_server.go b/agent/app/service/mcp_server.go new file mode 100644 index 0000000..efb6cf1 --- /dev/null +++ b/agent/app/service/mcp_server.go @@ -0,0 +1,695 @@ +package service + +import ( + "context" + "errors" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/cmd/server/ai" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" + "github.com/subosito/gotenv" + "gopkg.in/yaml.v3" + "path" + "strconv" + "strings" +) + +type McpServerService struct{} + +type IMcpServerService interface { + Page(req request.McpServerSearch) response.McpServersRes + Create(create request.McpServerCreate) error + Update(req request.McpServerUpdate) error + Delete(id uint) error + Operate(req request.McpServerOperate) error + GetBindDomain() (response.McpBindDomainRes, error) + BindDomain(req request.McpBindDomain) error + UpdateBindDomain(req request.McpBindDomainUpdate) error +} + +func NewIMcpServerService() IMcpServerService { + return &McpServerService{} +} + +func (m McpServerService) Page(req request.McpServerSearch) response.McpServersRes { + var ( + res response.McpServersRes + items []response.McpServerDTO + ) + + total, data, _ := mcpServerRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize) + for _, item := range data { + _ = syncMcpServerContainerStatus(&item) + serverDTO := response.McpServerDTO{ + McpServer: item, + Environments: make([]request.Environment, 0), + Volumes: make([]request.Volume, 0), + } + project, err := docker.GetComposeProject(item.Name, path.Join(global.Dir.McpDir, item.Name), []byte(item.DockerCompose), []byte(item.Env), true) + if err != nil { + global.LOG.Errorf("get mcp compose project error: %s", err.Error()) + continue + } + for _, service := range project.Services { + if service.Environment != nil { + for key, value := range service.Environment { + serverDTO.Environments = append(serverDTO.Environments, request.Environment{ + Key: key, + Value: *value, + }) + } + } + if service.Volumes != nil { + for _, volume := range service.Volumes { + serverDTO.Volumes = append(serverDTO.Volumes, request.Volume{ + Source: volume.Source, + Target: volume.Target, + }) + } + } + } + items = append(items, serverDTO) + } + res.Total = total + res.Items = items + return res +} + +func (m McpServerService) Update(req request.McpServerUpdate) error { + go pullImage(req.Type) + mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + if mcpServer.Port != req.Port { + if err := checkPortExist(req.Port); err != nil { + return err + } + } + if mcpServer.ContainerName != req.ContainerName { + if err := checkContainerName(req.ContainerName); err != nil { + return err + } + } + req.Command = strings.TrimSuffix(req.Command, "\n") + mcpServer.Name = req.Name + mcpServer.ContainerName = req.ContainerName + mcpServer.Port = req.Port + mcpServer.Command = req.Command + mcpServer.BaseURL = req.BaseURL + mcpServer.HostIP = req.HostIP + mcpServer.OutputTransport = req.OutputTransport + mcpServer.Type = req.Type + if req.OutputTransport == "sse" { + mcpServer.SsePath = req.SsePath + } else { + mcpServer.StreamableHttpPath = req.StreamableHttpPath + } + if err := handleCreateParams(mcpServer, req.Environments, req.Volumes); err != nil { + return err + } + env := handleEnv(mcpServer) + mcpDir := path.Join(global.Dir.McpDir, mcpServer.Name) + envPath := path.Join(mcpDir, ".env") + if err := gotenv.Write(env, envPath); err != nil { + return err + } + dockerComposePath := path.Join(mcpDir, "docker-compose.yml") + if err := files.NewFileOp().SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil { + return err + } + mcpServer.Status = constant.StatusStarting + if err := mcpServerRepo.Save(mcpServer); err != nil { + return err + } + go startMcp(mcpServer) + return nil +} + +func (m McpServerService) Create(create request.McpServerCreate) error { + go pullImage(create.Type) + servers, _ := mcpServerRepo.List() + for _, server := range servers { + if server.Port == create.Port { + return buserr.New("ErrPortInUsed") + } + if server.ContainerName == create.ContainerName { + return buserr.New("ErrContainerName") + } + if server.Name == create.Name { + return buserr.New("ErrNameIsExist") + } + if server.SsePath == create.SsePath { + return buserr.New("ErrSsePath") + } + } + create.Command = strings.TrimSuffix(create.Command, "\n") + if err := checkPortExist(create.Port); err != nil { + return err + } + if err := checkContainerName(create.ContainerName); err != nil { + return err + } + mcpDir := path.Join(global.Dir.McpDir, create.Name) + mcpServer := &model.McpServer{ + Name: create.Name, + ContainerName: create.ContainerName, + Port: create.Port, + Command: create.Command, + Status: constant.StatusStarting, + BaseURL: create.BaseURL, + Dir: mcpDir, + HostIP: create.HostIP, + OutputTransport: create.OutputTransport, + Type: create.Type, + } + if create.OutputTransport == "sse" { + mcpServer.SsePath = create.SsePath + } else { + mcpServer.StreamableHttpPath = create.StreamableHttpPath + } + + if err := handleCreateParams(mcpServer, create.Environments, create.Volumes); err != nil { + return err + } + + env := handleEnv(mcpServer) + filesOP := files.NewFileOp() + if !filesOP.Stat(mcpDir) { + _ = filesOP.CreateDir(mcpDir, 0644) + } + envPath := path.Join(mcpDir, ".env") + if err := gotenv.Write(env, envPath); err != nil { + return err + } + dockerComposePath := path.Join(mcpDir, "docker-compose.yml") + if err := filesOP.SaveFile(dockerComposePath, mcpServer.DockerCompose, 0644); err != nil { + return err + } + if err := mcpServerRepo.Create(mcpServer); err != nil { + return err + } + addProxy(mcpServer) + go startMcp(mcpServer) + return nil +} + +func (m McpServerService) Delete(id uint) error { + mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return err + } + composePath := path.Join(global.Dir.McpDir, mcpServer.Name, "docker-compose.yml") + _, _ = compose.Down(composePath) + _ = files.NewFileOp().DeleteDir(path.Join(global.Dir.McpDir, mcpServer.Name)) + + websiteID := GetWebsiteID() + if websiteID > 0 { + websiteService := NewIWebsiteService() + delProxyReq := request.WebsiteProxyDel{ + ID: websiteID, + Name: mcpServer.Name, + } + _ = websiteService.DeleteProxy(delProxyReq) + } + return mcpServerRepo.DeleteBy(repo.WithByID(id)) +} + +func (m McpServerService) Operate(req request.McpServerOperate) error { + mcpServer, err := mcpServerRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + composePath := path.Join(mcpServer.Dir, "docker-compose.yml") + var out string + switch req.Operate { + case "start": + out, err = compose.Up(composePath) + mcpServer.Status = constant.StatusRunning + case "stop": + out, err = compose.Down(composePath) + mcpServer.Status = constant.StatusStopped + case "restart": + out, err = compose.Restart(composePath) + mcpServer.Status = constant.StatusRunning + } + if err != nil { + mcpServer.Status = constant.StatusError + mcpServer.Message = out + } + return mcpServerRepo.Save(mcpServer) +} + +func (m McpServerService) GetBindDomain() (response.McpBindDomainRes, error) { + var res response.McpBindDomainRes + websiteID := GetWebsiteID() + if websiteID == 0 { + return res, nil + } + website, err := websiteRepo.GetFirst(repo.WithByID(websiteID)) + if err != nil { + return res, nil + } + res.WebsiteID = website.ID + res.Domain = website.PrimaryDomain + if website.WebsiteSSLID > 0 { + res.SSLID = website.WebsiteSSLID + ssl, _ := websiteSSLRepo.GetFirst(repo.WithByID(website.WebsiteSSLID)) + res.AcmeAccountID = ssl.AcmeAccountID + } + res.ConnUrl = fmt.Sprintf("%s://%s", strings.ToLower(website.Protocol), website.PrimaryDomain) + res.AllowIPs = GetAllowIps(website) + return res, nil + +} + +func (m McpServerService) BindDomain(req request.McpBindDomain) error { + nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) + if nginxInstall.ID == 0 { + return buserr.New("ErrOpenrestyInstall") + } + var ( + ipList []string + err error + ) + if len(req.IPList) > 0 { + ipList, err = common.HandleIPList(req.IPList) + if err != nil { + return err + } + } + if req.SSLID > 0 { + ssl, err := websiteSSLRepo.GetFirst(repo.WithByID(req.SSLID)) + if err != nil { + return err + } + if ssl.Pem == "" { + return buserr.New("ErrSSL") + } + } + group, _ := groupRepo.Get(groupRepo.WithByWebsiteDefault()) + + domain, err := ParseDomain(req.Domain) + if err != nil { + return err + } + if domain.Port == 0 { + domain.Port = nginxInstall.HttpPort + } + createWebsiteReq := request.WebsiteCreate{ + Domains: []request.WebsiteDomain{ + { + Domain: domain.Domain, + Port: domain.Port, + }, + }, + Alias: strings.ToLower(req.Domain), + Type: constant.Static, + WebsiteGroupID: group.ID, + } + if req.SSLID > 0 { + createWebsiteReq.WebsiteSSLID = req.SSLID + createWebsiteReq.EnableSSL = true + } + websiteService := NewIWebsiteService() + if err := websiteService.CreateWebsite(createWebsiteReq); err != nil { + return err + } + website, err := websiteRepo.GetFirst(websiteRepo.WithAlias(strings.ToLower(req.Domain))) + if err != nil { + return err + } + _ = settingRepo.UpdateOrCreate("MCP_WEBSITE_ID", fmt.Sprintf("%d", website.ID)) + if len(ipList) > 0 { + if err = ConfigAllowIPs(ipList, website); err != nil { + return err + } + } + if err = addMCPProxy(website.ID); err != nil { + return err + } + return nil +} + +func (m McpServerService) UpdateBindDomain(req request.McpBindDomainUpdate) error { + nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) + if nginxInstall.ID == 0 { + return buserr.New("ErrOpenrestyInstall") + } + var ( + ipList []string + err error + ) + if len(req.IPList) > 0 { + ipList, err = common.HandleIPList(req.IPList) + if err != nil { + return err + } + } + if req.SSLID > 0 { + ssl, err := websiteSSLRepo.GetFirst(repo.WithByID(req.SSLID)) + if err != nil { + return err + } + if ssl.Pem == "" { + return buserr.New("ErrSSL") + } + } + websiteService := NewIWebsiteService() + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + if err = ConfigAllowIPs(ipList, website); err != nil { + return err + } + if req.SSLID > 0 { + sslReq := request.WebsiteHTTPSOp{ + WebsiteID: website.ID, + Enable: true, + Type: "existed", + WebsiteSSLID: req.SSLID, + HttpConfig: "HTTPSOnly", + } + if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil { + return err + } + } + if website.WebsiteSSLID > 0 && req.SSLID == 0 { + sslReq := request.WebsiteHTTPSOp{ + WebsiteID: website.ID, + Enable: false, + } + if _, err = websiteService.OpWebsiteHTTPS(context.Background(), sslReq); err != nil { + return err + } + } + go updateMcpConfig(website.ID) + return nil +} + +func updateMcpConfig(websiteID uint) { + servers, _ := mcpServerRepo.List() + if len(servers) == 0 { + return + } + website, _ := websiteRepo.GetFirst(repo.WithByID(websiteID)) + websiteDomain := website.Domains[0] + var baseUrl string + if website.Protocol == constant.ProtocolHTTP { + baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain) + } else { + baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain) + } + + go func() { + for _, server := range servers { + if server.BaseURL != baseUrl { + server.BaseURL = baseUrl + server.HostIP = "127.0.0.1" + _ = updateMcpServer(&server) + } + } + }() +} + +func addProxy(server *model.McpServer) { + websiteID := GetWebsiteID() + website, err := websiteRepo.GetFirst(repo.WithByID(websiteID)) + if err != nil { + global.LOG.Errorf("[mcp] add proxy failed, err: %v", err) + return + } + fileOp := files.NewFileOp() + includeDir := GetSitePath(website, SiteProxyDir) + if !fileOp.Stat(includeDir) { + if err = fileOp.CreateDir(includeDir, 0644); err != nil { + return + } + } + config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse() + if err != nil { + return + } + includePath := path.Join(includeDir, server.Name+".conf") + config.FilePath = includePath + directives := config.Directives + location, ok := directives[0].(*components.Location) + if !ok { + return + } + var proxyPath string + if server.OutputTransport == "sse" { + proxyPath = server.SsePath + } else { + proxyPath = server.StreamableHttpPath + } + location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)}) + location.ChangePath("^~", proxyPath) + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + global.LOG.Errorf("write config failed, err: %v", buserr.WithErr("ErrUpdateBuWebsite", err)) + return + } + nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias) + if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil { + global.LOG.Errorf("update nginx config failed, err: %v", err) + return + } +} + +func addMCPProxy(websiteID uint) error { + servers, _ := mcpServerRepo.List() + if len(servers) == 0 { + return nil + } + website, err := websiteRepo.GetFirst(repo.WithByID(websiteID)) + if err != nil { + return err + } + fileOp := files.NewFileOp() + includeDir := GetSitePath(website, SiteProxyDir) + if !fileOp.Stat(includeDir) { + if err = fileOp.CreateDir(includeDir, 0644); err != nil { + return err + } + } + config, err := parser.NewStringParser(string(nginx_conf.SSE)).Parse() + if err != nil { + return err + } + websiteDomain := website.Domains[0] + var baseUrl string + if website.Protocol == constant.ProtocolHTTP { + baseUrl = fmt.Sprintf("http://%s", websiteDomain.Domain) + } else { + baseUrl = fmt.Sprintf("https://%s", websiteDomain.Domain) + } + if websiteDomain.Port != 80 && websiteDomain.Port != 443 { + baseUrl = fmt.Sprintf("%s:%d", baseUrl, websiteDomain.Port) + } + for _, server := range servers { + includePath := path.Join(includeDir, server.Name+".conf") + config.FilePath = includePath + directives := config.Directives + location, ok := directives[0].(*components.Location) + if !ok { + err = errors.New("error") + return err + } + var proxyPath string + if server.OutputTransport == "sse" { + proxyPath = server.SsePath + } else { + proxyPath = server.StreamableHttpPath + } + location.UpdateDirective("proxy_pass", []string{fmt.Sprintf("http://127.0.0.1:%d%s", server.Port, proxyPath)}) + location.ChangePath("^~", proxyPath) + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return buserr.WithErr("ErrUpdateBuWebsite", err) + } + server.BaseURL = baseUrl + server.HostIP = "127.0.0.1" + go updateMcpServer(&server) + } + nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias) + if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil { + return err + } + return nil +} + +func updateMcpServer(mcpServer *model.McpServer) error { + env := handleEnv(mcpServer) + if err := gotenv.Write(env, path.Join(mcpServer.Dir, ".env")); err != nil { + return err + } + _ = mcpServerRepo.Save(mcpServer) + composePath := path.Join(global.Dir.McpDir, mcpServer.Name, "docker-compose.yml") + _, _ = compose.Down(composePath) + if _, err := compose.Up(composePath); err != nil { + mcpServer.Status = constant.StatusError + mcpServer.Message = err.Error() + } + return mcpServerRepo.Save(mcpServer) +} + +func handleEnv(mcpServer *model.McpServer) gotenv.Env { + env := make(gotenv.Env) + env["CONTAINER_NAME"] = mcpServer.ContainerName + env["COMMAND"] = mcpServer.Command + env["PANEL_APP_PORT_HTTP"] = strconv.Itoa(mcpServer.Port) + env["BASE_URL"] = mcpServer.BaseURL + env["SSE_PATH"] = mcpServer.SsePath + env["HOST_IP"] = mcpServer.HostIP + env["STREAMABLE_HTTP_PATH"] = mcpServer.StreamableHttpPath + env["OUTPUT_TRANSPORT"] = mcpServer.OutputTransport + envStr, _ := gotenv.Marshal(env) + mcpServer.Env = envStr + return env +} + +func handleCreateParams(mcpServer *model.McpServer, environments []request.Environment, volumes []request.Volume) error { + var composeContent []byte + if mcpServer.ID == 0 { + composeContent = ai.DefaultMcpCompose + } else { + composeContent = []byte(mcpServer.DockerCompose) + } + composeMap := make(map[string]interface{}) + if err := yaml.Unmarshal(composeContent, &composeMap); err != nil { + return err + } + services, serviceValid := composeMap["services"].(map[string]interface{}) + if !serviceValid { + return buserr.New("ErrFileParse") + } + serviceName := "" + serviceValue := make(map[string]interface{}) + + if mcpServer.ID > 0 { + serviceName = mcpServer.Name + serviceValue = services[serviceName].(map[string]interface{}) + } else { + for name, service := range services { + serviceName = name + serviceValue = service.(map[string]interface{}) + break + } + delete(services, serviceName) + } + delete(serviceValue, "environment") + if len(environments) > 0 { + envMap := make(map[string]string) + for _, env := range environments { + envMap[env.Key] = env.Value + } + serviceValue["environment"] = envMap + } + delete(serviceValue, "volumes") + if len(volumes) > 0 { + volumeList := make([]string, 0) + for _, volume := range volumes { + volumeList = append(volumeList, fmt.Sprintf("%s:%s", volume.Source, volume.Target)) + } + serviceValue["volumes"] = volumeList + } + if mcpServer.Type == "npx" { + serviceValue["image"] = "supercorp/supergateway:latest" + } else { + serviceValue["image"] = "supercorp/supergateway:uvx" + } + + services[mcpServer.Name] = serviceValue + composeByte, err := yaml.Marshal(composeMap) + if err != nil { + return err + } + mcpServer.DockerCompose = string(composeByte) + return nil +} + +func startMcp(mcpServer *model.McpServer) { + composePath := path.Join(global.Dir.McpDir, mcpServer.Name, "docker-compose.yml") + if mcpServer.Status != constant.StatusNormal { + _, _ = compose.Down(composePath) + } + if out, err := compose.Up(composePath); err != nil { + mcpServer.Status = constant.StatusError + mcpServer.Message = out + } else { + mcpServer.Status = constant.StatusRunning + mcpServer.Message = "" + } + _ = syncMcpServerContainerStatus(mcpServer) +} + +func syncMcpServerContainerStatus(mcpServer *model.McpServer) error { + containerNames := []string{mcpServer.ContainerName} + cli, err := docker.NewClient() + if err != nil { + return err + } + defer cli.Close() + containers, err := cli.ListContainersByName(containerNames) + if err != nil { + return err + } + if len(containers) == 0 { + mcpServer.Status = constant.StatusStopped + return mcpServerRepo.Save(mcpServer) + } + container := containers[0] + switch container.State { + case "exited": + mcpServer.Status = constant.StatusError + case "running": + mcpServer.Status = constant.StatusRunning + case "paused": + mcpServer.Status = constant.StatusStopped + case "restarting": + mcpServer.Status = constant.StatusRestarting + default: + if mcpServer.Status != constant.StatusStarting { + mcpServer.Status = constant.StatusStopped + } + } + return mcpServerRepo.Save(mcpServer) +} + +func GetWebsiteID() uint { + websiteID, _ := settingRepo.Get(settingRepo.WithByKey("MCP_WEBSITE_ID")) + if websiteID.Value == "" { + return 0 + } + websiteIDUint, _ := strconv.ParseUint(websiteID.Value, 10, 64) + return uint(websiteIDUint) +} + +func pullImage(imageType string) { + if global.CONF.Base.IsOffLine { + return + } + if imageType == "npx" { + if err := docker.PullImage("supercorp/supergateway:latest"); err != nil { + global.LOG.Errorf("docker pull mcp image error: %s", err.Error()) + } + } else { + if err := docker.PullImage("supercorp/supergateway:uvx"); err != nil { + global.LOG.Errorf("docker pull mcp image error: %s", err.Error()) + } + } +} diff --git a/agent/app/service/monitor.go b/agent/app/service/monitor.go new file mode 100644 index 0000000..a619c6c --- /dev/null +++ b/agent/app/service/monitor.go @@ -0,0 +1,681 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "sort" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/xpu" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/psutil" + "github.com/robfig/cron/v3" + "github.com/shirou/gopsutil/v4/cpu" + "github.com/shirou/gopsutil/v4/disk" + "github.com/shirou/gopsutil/v4/load" + "github.com/shirou/gopsutil/v4/mem" + "github.com/shirou/gopsutil/v4/net" + "github.com/shirou/gopsutil/v4/process" +) + +type MonitorService struct { + DiskIO chan ([]disk.IOCountersStat) + NetIO chan ([]net.IOCountersStat) +} + +var monitorCancel context.CancelFunc + +type IMonitorService interface { + Run() + LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorData, error) + LoadGPUOptions() dto.MonitorGPUOptions + LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) + LoadSetting() (*dto.MonitorSetting, error) + UpdateSetting(key, value string) error + CleanData() error + + saveIODataToDB(ctx context.Context, interval float64) + saveNetDataToDB(ctx context.Context, interval float64) +} + +func NewIMonitorService() IMonitorService { + return &MonitorService{ + DiskIO: make(chan []disk.IOCountersStat, 2), + NetIO: make(chan []net.IOCountersStat, 2), + } +} + +func (m *MonitorService) LoadMonitorData(req dto.MonitorSearch) ([]dto.MonitorData, error) { + loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + req.StartTime = req.StartTime.In(loc) + req.EndTime = req.EndTime.In(loc) + + var data []dto.MonitorData + if req.Param == "all" || req.Param == "cpu" || req.Param == "memory" || req.Param == "load" { + bases, err := monitorRepo.GetBase(repo.WithByCreatedAt(req.StartTime, req.EndTime)) + if err != nil { + return nil, err + } + + var itemData dto.MonitorData + itemData.Param = "base" + for _, base := range bases { + itemData.Date = append(itemData.Date, base.CreatedAt) + if req.Param == "all" || req.Param == "cpu" { + var processes []dto.Process + _ = json.Unmarshal([]byte(base.TopCPU), &processes) + base.TopCPUItems = processes + base.TopCPU = "" + } + if req.Param == "all" || req.Param == "mem" { + var processes []dto.Process + _ = json.Unmarshal([]byte(base.TopMem), &processes) + base.TopMemItems = processes + base.TopMem = "" + } + itemData.Value = append(itemData.Value, base) + } + data = append(data, itemData) + } + if req.Param == "all" || req.Param == "io" { + bases, err := monitorRepo.GetIO(repo.WithByName(req.IO), repo.WithByCreatedAt(req.StartTime, req.EndTime)) + if err != nil { + return nil, err + } + + var itemData dto.MonitorData + itemData.Param = "io" + for _, base := range bases { + itemData.Date = append(itemData.Date, base.CreatedAt) + itemData.Value = append(itemData.Value, base) + } + data = append(data, itemData) + } + if req.Param == "all" || req.Param == "network" { + bases, err := monitorRepo.GetNetwork(repo.WithByName(req.Network), repo.WithByCreatedAt(req.StartTime, req.EndTime)) + if err != nil { + return nil, err + } + + var itemData dto.MonitorData + itemData.Param = "network" + for _, base := range bases { + itemData.Date = append(itemData.Date, base.CreatedAt) + itemData.Value = append(itemData.Value, base) + } + data = append(data, itemData) + } + return data, nil +} + +func (m *MonitorService) LoadGPUOptions() dto.MonitorGPUOptions { + var data dto.MonitorGPUOptions + gpuExist, gpuClient := gpu.New() + xpuExist, xpuClient := xpu.New() + if !gpuExist && !xpuExist { + return data + } + if gpuExist { + data.GPUType = "gpu" + gpuInfo, err := gpuClient.LoadGpuInfo() + if err != nil || len(gpuInfo.GPUs) == 0 { + global.LOG.Error("Load GPU info failed or no GPU found, err: ", err) + return data + } + sort.Slice(gpuInfo.GPUs, func(i, j int) bool { + return gpuInfo.GPUs[i].Index < gpuInfo.GPUs[j].Index + }) + for _, item := range gpuInfo.GPUs { + var chartHide dto.GPUChartHide + chartHide.ProductName = fmt.Sprintf("%d - %s", item.Index, item.ProductName) + chartHide.GPU = item.GPUUtil == "" || item.GPUUtil == "N/A" + if (item.MemTotal == "" || item.MemTotal == "N/A") && (item.MemUsed == "" || item.MemUsed == "N/A") { + chartHide.Memory = true + } + if (item.MaxPowerLimit == "" || item.MaxPowerLimit == "N/A") && (item.PowerDraw == "" || item.PowerDraw == "N/A") { + chartHide.Power = true + } + chartHide.Temperature = item.Temperature == "" || item.Temperature == "N/A" + chartHide.Speed = item.FanSpeed == "" || item.FanSpeed == "N/A" + data.ChartHide = append(data.ChartHide, chartHide) + data.Options = append(data.Options, fmt.Sprintf("%d - %s", item.Index, item.ProductName)) + } + return data + } else { + data.GPUType = "xpu" + xpu, err := xpuClient.LoadGpuInfo() + if err != nil || len(xpu.Xpu) == 0 { + global.LOG.Error("Load XPU info failed or no XPU found, err: ", err) + } + sort.Slice(xpu.Xpu, func(i, j int) bool { + return xpu.Xpu[i].Basic.DeviceID < xpu.Xpu[j].Basic.DeviceID + }) + for _, item := range xpu.Xpu { + var chartHide dto.GPUChartHide + chartHide.GPU = true + chartHide.Speed = true + chartHide.ProductName = fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName) + if (item.Stats.MemoryUsed == "" || item.Stats.MemoryUsed == "N/A") && (item.Basic.Memory == "" || item.Basic.FreeMemory == "N/A") { + chartHide.Memory = true + } + if item.Stats.Power == "" || item.Stats.Power == "N/A" { + chartHide.Power = true + } + chartHide.Temperature = item.Stats.Temperature == "" || item.Stats.Temperature == "N/A" + data.ChartHide = append(data.ChartHide, chartHide) + data.Options = append(data.Options, fmt.Sprintf("%d - %s", item.Basic.DeviceID, item.Basic.DeviceName)) + } + return data + } +} + +func (m *MonitorService) LoadGPUMonitorData(req dto.MonitorGPUSearch) (dto.MonitorGPUData, error) { + loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + req.StartTime = req.StartTime.In(loc) + req.EndTime = req.EndTime.In(loc) + var data dto.MonitorGPUData + gpuList, err := monitorRepo.GetGPU(repo.WithByCreatedAt(req.StartTime, req.EndTime), monitorRepo.WithByProductName(req.ProductName)) + if err != nil { + return data, err + } + + for _, gpu := range gpuList { + data.Date = append(data.Date, gpu.CreatedAt) + data.GPUValue = append(data.GPUValue, gpu.GPUUtil) + data.TemperatureValue = append(data.TemperatureValue, gpu.Temperature) + data.PowerUsed = append(data.PowerUsed, gpu.PowerDraw) + data.PowerTotal = append(data.PowerTotal, gpu.MaxPowerLimit) + if gpu.MaxPowerLimit != 0 { + data.PowerPercent = append(data.PowerPercent, gpu.PowerDraw/gpu.MaxPowerLimit*100) + } else { + data.PowerPercent = append(data.PowerPercent, float64(0)) + } + + data.MemoryTotal = append(data.MemoryTotal, gpu.MemTotal) + data.MemoryUsed = append(data.MemoryUsed, gpu.MemUsed) + if gpu.MemTotal != 0 { + data.MemoryPercent = append(data.MemoryPercent, gpu.MemUsed/gpu.MemTotal*100) + } else { + data.MemoryPercent = append(data.MemoryPercent, float64(0)) + } + var process []dto.GPUProcess + if err := json.Unmarshal([]byte(gpu.Processes), &process); err == nil { + data.ProcessCount = append(data.ProcessCount, len(process)) + data.GPUProcesses = append(data.GPUProcesses, process) + } else { + data.ProcessCount = append(data.ProcessCount, 0) + data.GPUProcesses = append(data.GPUProcesses, []dto.GPUProcess{}) + } + data.SpeedValue = append(data.SpeedValue, gpu.FanSpeed) + } + return data, nil +} + +func (m *MonitorService) LoadSetting() (*dto.MonitorSetting, error) { + setting, err := settingRepo.GetList() + if err != nil { + return nil, buserr.New("ErrRecordNotFound") + } + settingMap := make(map[string]string) + for _, set := range setting { + settingMap[set.Key] = set.Value + } + var info dto.MonitorSetting + arr, err := json.Marshal(settingMap) + if err != nil { + return nil, err + } + if err := json.Unmarshal(arr, &info); err != nil { + return nil, err + } + return &info, err +} + +func (m *MonitorService) UpdateSetting(key, value string) error { + switch key { + case "MonitorStatus": + if value == constant.StatusEnable && global.MonitorCronID == 0 { + interval, err := settingRepo.Get(settingRepo.WithByKey("MonitorInterval")) + if err != nil { + return err + } + if err := StartMonitor(false, interval.Value); err != nil { + return err + } + } + if value == constant.StatusDisable && global.MonitorCronID != 0 { + monitorCancel() + global.Cron.Remove(cron.EntryID(global.MonitorCronID)) + global.MonitorCronID = 0 + } + case "MonitorInterval": + status, err := settingRepo.Get(settingRepo.WithByKey("MonitorStatus")) + if err != nil { + return err + } + if status.Value == constant.StatusEnable && global.MonitorCronID != 0 { + if err := StartMonitor(true, value); err != nil { + return err + } + } + } + return settingRepo.Update(key, value) +} + +func (m *MonitorService) CleanData() error { + if err := global.MonitorDB.Exec("DELETE FROM monitor_bases").Error; err != nil { + return err + } + if err := global.MonitorDB.Exec("DELETE FROM monitor_ios").Error; err != nil { + return err + } + if err := global.MonitorDB.Exec("DELETE FROM monitor_networks").Error; err != nil { + return err + } + _ = global.GPUMonitorDB.Exec("DELETE FROM monitor_gpus").Error + return nil +} + +func (m *MonitorService) Run() { + saveGPUDataToDB() + saveXPUDataToDB() + var itemModel model.MonitorBase + totalPercent, _ := cpu.Percent(3*time.Second, false) + if len(totalPercent) == 1 { + itemModel.Cpu = totalPercent[0] + } + topCPU := loadTopCPU() + if len(topCPU) != 0 { + topItemCPU, err := json.Marshal(topCPU) + if err == nil { + itemModel.TopCPU = string(topItemCPU) + } + } + cpuCount, _ := psutil.CPUInfo.GetPhysicalCores(false) + loadInfo, _ := load.Avg() + itemModel.CpuLoad1 = loadInfo.Load1 + itemModel.CpuLoad5 = loadInfo.Load5 + itemModel.CpuLoad15 = loadInfo.Load15 + itemModel.LoadUsage = loadInfo.Load1 / (float64(cpuCount*2) * 0.75) * 100 + + memoryInfo, _ := mem.VirtualMemory() + itemModel.Memory = memoryInfo.UsedPercent + topMem := loadTopMem() + if len(topMem) != 0 { + topMemItem, err := json.Marshal(topMem) + if err == nil { + itemModel.TopMem = string(topMemItem) + } + } + + if err := monitorRepo.CreateMonitorBase(itemModel); err != nil { + global.LOG.Errorf("Insert basic monitoring data failed, err: %v", err) + } + + m.loadDiskIO() + m.loadNetIO() + + MonitorStoreDays, err := settingRepo.Get(settingRepo.WithByKey("MonitorStoreDays")) + if err != nil { + return + } + storeDays, _ := strconv.Atoi(MonitorStoreDays.Value) + timeForDelete := time.Now().AddDate(0, 0, -storeDays) + _ = monitorRepo.DelMonitorBase(timeForDelete) + _ = monitorRepo.DelMonitorIO(timeForDelete) + _ = monitorRepo.DelMonitorNet(timeForDelete) +} + +func (m *MonitorService) loadDiskIO() { + ioStat, _ := disk.IOCounters() + var diskIOList []disk.IOCountersStat + var ioStatAll disk.IOCountersStat + for _, io := range ioStat { + ioStatAll.Name = "all" + ioStatAll.ReadBytes += io.ReadBytes + ioStatAll.WriteBytes += io.WriteBytes + ioStatAll.ReadTime += io.ReadTime + ioStatAll.WriteTime += io.WriteTime + ioStatAll.WriteCount += io.WriteCount + ioStatAll.ReadCount += io.ReadCount + diskIOList = append(diskIOList, io) + } + diskIOList = append(diskIOList, ioStatAll) + m.DiskIO <- diskIOList +} + +func (m *MonitorService) loadNetIO() { + netStat, _ := net.IOCounters(true) + netStatAll, _ := net.IOCounters(false) + var netList []net.IOCountersStat + netList = append(netList, netStat...) + netList = append(netList, netStatAll...) + m.NetIO <- netList +} + +func (m *MonitorService) saveIODataToDB(ctx context.Context, interval float64) { + defer close(m.DiskIO) + for { + select { + case <-ctx.Done(): + return + case ioStat := <-m.DiskIO: + select { + case <-ctx.Done(): + return + case ioStat2 := <-m.DiskIO: + var ioList []model.MonitorIO + for _, io2 := range ioStat2 { + for _, io1 := range ioStat { + if io2.Name == io1.Name { + var itemIO model.MonitorIO + itemIO.Name = io1.Name + if io2.ReadBytes != 0 && io1.ReadBytes != 0 && io2.ReadBytes > io1.ReadBytes { + itemIO.Read = uint64(float64(io2.ReadBytes-io1.ReadBytes) / interval) + } + if io2.WriteBytes != 0 && io1.WriteBytes != 0 && io2.WriteBytes > io1.WriteBytes { + itemIO.Write = uint64(float64(io2.WriteBytes-io1.WriteBytes) / interval) + } + + if io2.ReadCount != 0 && io1.ReadCount != 0 && io2.ReadCount > io1.ReadCount { + itemIO.Count = uint64(float64(io2.ReadCount-io1.ReadCount) / interval) + } + writeCount := uint64(0) + if io2.WriteCount != 0 && io1.WriteCount != 0 && io2.WriteCount > io1.WriteCount { + writeCount = uint64(float64(io2.WriteCount-io1.WriteCount) / interval) + } + if writeCount > itemIO.Count { + itemIO.Count = writeCount + } + + if io2.ReadTime != 0 && io1.ReadTime != 0 && io2.ReadTime > io1.ReadTime { + itemIO.Time = uint64(float64(io2.ReadTime-io1.ReadTime) / interval) + } + writeTime := uint64(0) + if io2.WriteTime != 0 && io1.WriteTime != 0 && io2.WriteTime > io1.WriteTime { + writeTime = uint64(float64(io2.WriteTime-io1.WriteTime) / interval) + } + if writeTime > itemIO.Time { + itemIO.Time = writeTime + } + ioList = append(ioList, itemIO) + break + } + } + } + _ = monitorRepo.BatchCreateMonitorIO(ioList) + m.DiskIO <- ioStat2 + } + } + } +} + +func (m *MonitorService) saveNetDataToDB(ctx context.Context, interval float64) { + defer close(m.NetIO) + for { + select { + case <-ctx.Done(): + return + case netStat := <-m.NetIO: + select { + case <-ctx.Done(): + return + case netStat2 := <-m.NetIO: + var netList []model.MonitorNetwork + for _, net2 := range netStat2 { + for _, net1 := range netStat { + if net2.Name == net1.Name { + var itemNet model.MonitorNetwork + itemNet.Name = net1.Name + + if net2.BytesSent != 0 && net1.BytesSent != 0 && net2.BytesSent > net1.BytesSent { + itemNet.Up = float64(net2.BytesSent-net1.BytesSent) / 1024 / interval + } + if net2.BytesRecv != 0 && net1.BytesRecv != 0 && net2.BytesRecv > net1.BytesRecv { + itemNet.Down = float64(net2.BytesRecv-net1.BytesRecv) / 1024 / interval + } + netList = append(netList, itemNet) + break + } + } + } + + _ = monitorRepo.BatchCreateMonitorNet(netList) + m.NetIO <- netStat2 + } + } + } +} + +func loadTopCPU() []dto.Process { + processes, err := process.Processes() + if err != nil { + return nil + } + + top5 := make([]dto.Process, 0, 5) + for _, p := range processes { + percent, err := p.CPUPercent() + if err != nil { + continue + } + minIndex := 0 + if len(top5) >= 5 { + minCPU := top5[0].Percent + for i := 1; i < len(top5); i++ { + if top5[i].Percent < minCPU { + minCPU = top5[i].Percent + minIndex = i + } + } + if percent < minCPU { + continue + } + } + name, err := p.Name() + if err != nil { + name = "undefined" + } + cmd, err := p.Cmdline() + if err != nil { + cmd = "undefined" + } + user, err := p.Username() + if err != nil { + user = "undefined" + } + if len(top5) == 5 { + top5[minIndex] = dto.Process{Percent: percent, Pid: p.Pid, User: user, Name: name, Cmd: cmd} + } else { + top5 = append(top5, dto.Process{Percent: percent, Pid: p.Pid, User: user, Name: name, Cmd: cmd}) + } + } + sort.Slice(top5, func(i, j int) bool { + return top5[i].Percent > top5[j].Percent + }) + + return top5 +} + +func loadTopMem() []dto.Process { + processes, err := process.Processes() + if err != nil { + return nil + } + + top5 := make([]dto.Process, 0, 5) + for _, p := range processes { + stat, err := p.MemoryInfo() + if err != nil { + continue + } + memItem := stat.RSS + minIndex := 0 + if len(top5) >= 5 { + min := top5[0].Memory + for i := 1; i < len(top5); i++ { + if top5[i].Memory < min { + min = top5[i].Memory + minIndex = i + } + } + if memItem < min { + continue + } + } + name, err := p.Name() + if err != nil { + name = "undefined" + } + cmd, err := p.Cmdline() + if err != nil { + cmd = "undefined" + } + user, err := p.Username() + if err != nil { + user = "undefined" + } + percent, _ := p.MemoryPercent() + if len(top5) == 5 { + top5[minIndex] = dto.Process{Percent: float64(percent), Pid: p.Pid, User: user, Name: name, Cmd: cmd, Memory: memItem} + } else { + top5 = append(top5, dto.Process{Percent: float64(percent), Pid: p.Pid, User: user, Name: name, Cmd: cmd, Memory: memItem}) + } + } + + sort.Slice(top5, func(i, j int) bool { + return top5[i].Memory > top5[j].Memory + }) + return top5 +} + +func StartMonitor(removeBefore bool, interval string) error { + if removeBefore { + monitorCancel() + global.Cron.Remove(cron.EntryID(global.MonitorCronID)) + } + intervalItem, err := strconv.Atoi(interval) + if err != nil { + return err + } + + service := NewIMonitorService() + ctx, cancel := context.WithCancel(context.Background()) + monitorCancel = cancel + now := time.Now() + nextMinute := now.Truncate(time.Minute).Add(time.Minute) + time.AfterFunc(time.Until(nextMinute), func() { + monitorID, err := global.Cron.AddJob(fmt.Sprintf("@every %ss", interval), service) + if err != nil { + return + } + global.MonitorCronID = monitorID + }) + + service.Run() + + go service.saveIODataToDB(ctx, float64(intervalItem)) + go service.saveNetDataToDB(ctx, float64(intervalItem)) + + return nil +} + +func saveGPUDataToDB() { + exist, client := gpu.New() + if !exist { + return + } + gpuInfo, err := client.LoadGpuInfo() + if err != nil { + return + } + var list []model.MonitorGPU + for _, gpuItem := range gpuInfo.GPUs { + item := model.MonitorGPU{ + ProductName: fmt.Sprintf("%d - %s", gpuItem.Index, gpuItem.ProductName), + GPUUtil: loadGPUInfoFloat(gpuItem.GPUUtil), + Temperature: loadGPUInfoFloat(gpuItem.Temperature), + PowerDraw: loadGPUInfoFloat(gpuItem.PowerDraw), + MaxPowerLimit: loadGPUInfoFloat(gpuItem.MaxPowerLimit), + MemUsed: loadGPUInfoFloat(gpuItem.MemUsed), + MemTotal: loadGPUInfoFloat(gpuItem.MemTotal), + FanSpeed: loadGPUInfoInt(gpuItem.FanSpeed), + } + process, _ := json.Marshal(gpuItem.Processes) + if len(process) != 0 { + item.Processes = string(process) + } + list = append(list, item) + } + if err := repo.NewIMonitorRepo().BatchCreateMonitorGPU(list); err != nil { + global.LOG.Errorf("batch create gpu monitor data failed, err: %v", err) + return + } +} +func saveXPUDataToDB() { + exist, client := xpu.New() + if !exist { + return + } + xpuInfo, err := client.LoadGpuInfo() + if err != nil { + return + } + var list []model.MonitorGPU + for _, xpuItem := range xpuInfo.Xpu { + item := model.MonitorGPU{ + ProductName: fmt.Sprintf("%d - %s", xpuItem.Basic.DeviceID, xpuItem.Basic.DeviceName), + Temperature: loadGPUInfoFloat(xpuItem.Stats.Temperature), + PowerDraw: loadGPUInfoFloat(xpuItem.Stats.Power), + MemUsed: loadGPUInfoFloat(xpuItem.Stats.MemoryUsed), + MemTotal: loadGPUInfoFloat(xpuItem.Basic.Memory), + } + if len(xpuItem.Processes) != 0 { + var processItem []dto.GPUProcess + for _, ps := range xpuItem.Processes { + processItem = append(processItem, dto.GPUProcess{ + Pid: fmt.Sprintf("%v", ps.PID), + Type: ps.SHR, + ProcessName: ps.Command, + UsedMemory: ps.Memory, + }) + } + process, _ := json.Marshal(processItem) + if len(process) != 0 { + item.Processes = string(process) + } + } + list = append(list, item) + } + if err := repo.NewIMonitorRepo().BatchCreateMonitorGPU(list); err != nil { + global.LOG.Errorf("batch create gpu monitor data failed, err: %v", err) + return + } +} +func loadGPUInfoInt(val string) int { + val = strings.TrimSuffix(val, "%") + val = strings.TrimSpace(val) + data, _ := strconv.Atoi(val) + return data +} +func loadGPUInfoFloat(val string) float64 { + val = strings.TrimSpace(val) + suffixes := []string{"W", "MB", "MiB", "°C", "C", "%"} + for _, suffix := range suffixes { + val = strings.TrimSuffix(val, suffix) + } + val = strings.TrimSpace(val) + data, _ := strconv.ParseFloat(val, 64) + return data +} diff --git a/agent/app/service/nginx.go b/agent/app/service/nginx.go new file mode 100644 index 0000000..eb425bd --- /dev/null +++ b/agent/app/service/nginx.go @@ -0,0 +1,432 @@ +package service + +import ( + "bufio" + "encoding/json" + "fmt" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" + "io" + "net/http" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/subosito/gotenv" + + "github.com/1Panel-dev/1Panel/agent/utils/compose" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +type NginxService struct { +} + +type INginxService interface { + GetNginxConfig() (*response.NginxFile, error) + GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error) + UpdateConfigByScope(req request.NginxConfigUpdate) error + GetStatus() (response.NginxStatus, error) + UpdateConfigFile(req request.NginxConfigFileUpdate) error + + Build(req request.NginxBuildReq) error + GetModules() (*response.NginxBuildConfig, error) + UpdateModule(req request.NginxModuleUpdate) error + + OperateDefaultHTTPs(req request.NginxDefaultHTTPSUpdate) error + GetDefaultHttpsStatus() (*response.NginxConfigRes, error) +} + +func NewINginxService() INginxService { + return &NginxService{} +} + +func (n NginxService) GetNginxConfig() (*response.NginxFile, error) { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + configPath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf") + byteContent, err := files.NewFileOp().GetContent(configPath) + if err != nil { + return nil, err + } + return &response.NginxFile{Content: string(byteContent)}, nil +} + +func (n NginxService) GetConfigByScope(req request.NginxScopeReq) ([]response.NginxParam, error) { + keys, ok := dto.ScopeKeyMap[req.Scope] + if !ok || len(keys) == 0 { + return nil, nil + } + return getNginxParamsByKeys(constant.NginxScopeHttp, keys, nil) +} + +func (n NginxService) UpdateConfigByScope(req request.NginxConfigUpdate) error { + keys, ok := dto.ScopeKeyMap[req.Scope] + if !ok || len(keys) == 0 { + return nil + } + return updateNginxConfig(constant.NginxScopeHttp, getNginxParams(req.Params, keys), nil) +} + +func (n NginxService) GetStatus() (response.NginxStatus, error) { + httpPort, _, err := getAppInstallPort(constant.AppOpenresty) + if err != nil { + return response.NginxStatus{}, err + } + url := "http://127.0.0.1/nginx_status" + if httpPort != 80 { + url = fmt.Sprintf("http://127.0.0.1:%v/nginx_status", httpPort) + } + res, err := http.Get(url) + if err != nil || res.StatusCode > 300 { + return response.NginxStatus{}, err + } + defer res.Body.Close() + content, err := io.ReadAll(res.Body) + if err != nil { + return response.NginxStatus{}, err + } + var status response.NginxStatus + resArray := strings.Split(string(content), " ") + active, err := strconv.Atoi(resArray[2]) + if err == nil { + status.Active = active + } + accepts, err := strconv.Atoi(resArray[7]) + if err == nil { + status.Accepts = accepts + } + handled, err := strconv.Atoi(resArray[8]) + if err == nil { + status.Handled = handled + } + requests, err := strconv.Atoi(resArray[9]) + if err == nil { + status.Requests = requests + } + reading, err := strconv.Atoi(resArray[11]) + if err == nil { + status.Reading = reading + } + writing, err := strconv.Atoi(resArray[13]) + if err == nil { + status.Writing = writing + } + waiting, err := strconv.Atoi(resArray[15]) + if err == nil { + status.Waiting = waiting + } + return status, nil +} + +func (n NginxService) UpdateConfigFile(req request.NginxConfigFileUpdate) error { + fileOp := files.NewFileOp() + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + filePath := path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name, "conf", "nginx.conf") + if req.Backup { + backupPath := path.Join(path.Dir(filePath), "bak") + if !fileOp.Stat(backupPath) { + if err := fileOp.CreateDir(backupPath, constant.DirPerm); err != nil { + return err + } + } + newFile := path.Join(backupPath, "nginx.bak"+"-"+time.Now().Format("2006-01-02-15-04-05")) + if err := fileOp.Copy(filePath, backupPath); err != nil { + return err + } + if err := fileOp.Rename(path.Join(backupPath, "nginx.conf"), newFile); err != nil { + return err + } + } + oldContent, err := os.ReadFile(filePath) + if err != nil { + return err + } + if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), constant.DirPerm); err != nil { + return err + } + if status, err := checkContainerStatus(nginxInstall.ContainerName); err == nil && status != "running" { + if out, err := compose.DownAndUp(nginxInstall.GetComposePath()); err != nil { + _ = fileOp.SaveFile(filePath, string(oldContent), constant.DirPerm) + return fmt.Errorf("nginx restart failed: %v", out) + } else { + return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName) + } + } + return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName) +} + +func (n NginxService) Build(req request.NginxBuildReq) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + taskName := task.GetTaskName(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp) + if err = task.CheckTaskIsExecuting(taskName); err != nil { + return err + } + fileOp := files.NewFileOp() + buildPath := path.Join(nginxInstall.GetPath(), "build") + if !fileOp.Stat(buildPath) { + return buserr.New("ErrBuildDirNotFound") + } + moduleConfigPath := path.Join(buildPath, "module.json") + moduleContent, err := fileOp.GetContent(moduleConfigPath) + if err != nil { + return err + } + var ( + modules []dto.NginxModule + addModuleParams []string + addPackages []string + ) + if len(moduleContent) > 0 { + _ = json.Unmarshal(moduleContent, &modules) + bashFile, err := os.OpenFile(path.Join(buildPath, "tmp", "pre.sh"), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.DirPerm) + if err != nil { + return err + } + defer bashFile.Close() + bashFileWriter := bufio.NewWriter(bashFile) + for _, module := range modules { + if !module.Enable { + continue + } + _, err = bashFileWriter.WriteString(module.Script + "\n") + if err != nil { + return err + } + addModuleParams = append(addModuleParams, module.Params) + addPackages = append(addPackages, module.Packages...) + } + err = bashFileWriter.Flush() + if err != nil { + return err + } + } + envs, err := gotenv.Read(nginxInstall.GetEnvPath()) + if err != nil { + return err + } + envs["CONTAINER_PACKAGE_URL"] = req.Mirror + envs["RESTY_CONFIG_OPTIONS_MORE"] = "" + envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = "" + if len(addModuleParams) > 0 { + envs["RESTY_CONFIG_OPTIONS_MORE"] = strings.Join(addModuleParams, " ") + } + if len(addPackages) > 0 { + envs["RESTY_ADD_PACKAGE_BUILDDEPS"] = strings.Join(addPackages, " ") + } + _ = gotenv.Write(envs, nginxInstall.GetEnvPath()) + + buildTask, err := task.NewTaskWithOps(nginxInstall.Name, task.TaskBuild, task.TaskScopeApp, req.TaskID, nginxInstall.ID) + if err != nil { + return err + } + buildTask.AddSubTaskWithOps("", func(t *task.Task) error { + cmdMgr := cmd2.NewCommandMgr(cmd.WithTask(*buildTask), cmd.WithTimeout(120*time.Minute)) + if err = cmdMgr.RunBashCf("docker compose -f %s build", nginxInstall.GetComposePath()); err != nil { + return err + } + _, err = compose.DownAndUp(nginxInstall.GetComposePath()) + return err + }, nil, 0, 120*time.Minute) + + go func() { + _ = buildTask.Execute() + }() + return nil +} + +func (n NginxService) GetModules() (*response.NginxBuildConfig, error) { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + fileOp := files.NewFileOp() + var modules []dto.NginxModule + moduleConfigPath := path.Join(nginxInstall.GetPath(), "build", "module.json") + if !fileOp.Stat(moduleConfigPath) { + return nil, nil + } + moduleContent, err := fileOp.GetContent(moduleConfigPath) + if err != nil { + return nil, err + } + if len(moduleContent) > 0 { + _ = json.Unmarshal(moduleContent, &modules) + } + var resList []response.NginxModule + for _, module := range modules { + resList = append(resList, response.NginxModule{ + Name: module.Name, + Script: module.Script, + Packages: strings.Join(module.Packages, ","), + Params: module.Params, + Enable: module.Enable, + }) + } + envs, err := gotenv.Read(nginxInstall.GetEnvPath()) + if err != nil { + return nil, err + } + + return &response.NginxBuildConfig{ + Mirror: envs["CONTAINER_PACKAGE_URL"], + Modules: resList, + }, nil +} + +func (n NginxService) UpdateModule(req request.NginxModuleUpdate) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + fileOp := files.NewFileOp() + var ( + modules []dto.NginxModule + ) + moduleConfigPath := path.Join(nginxInstall.GetPath(), "build", "module.json") + if !fileOp.Stat(moduleConfigPath) { + _ = fileOp.CreateFile(moduleConfigPath) + } + moduleContent, err := fileOp.GetContent(moduleConfigPath) + if err != nil { + return err + } + if len(moduleContent) > 0 { + _ = json.Unmarshal(moduleContent, &modules) + } + + switch req.Operate { + case "create": + for _, module := range modules { + if module.Name == req.Name { + return buserr.New("ErrNameIsExist") + } + } + modules = append(modules, dto.NginxModule{ + Name: req.Name, + Script: req.Script, + Packages: strings.Split(req.Packages, ","), + Params: req.Params, + Enable: true, + }) + case "update": + for i, module := range modules { + if module.Name == req.Name { + modules[i].Script = req.Script + modules[i].Packages = strings.Split(req.Packages, ",") + modules[i].Params = req.Params + modules[i].Enable = req.Enable + break + } + } + case "delete": + for i, module := range modules { + if module.Name == req.Name { + modules = append(modules[:i], modules[i+1:]...) + break + } + } + } + moduleByte, err := json.Marshal(modules) + if err != nil { + return err + } + return fileOp.SaveFileWithByte(moduleConfigPath, moduleByte, constant.DirPerm) +} + +func (n NginxService) OperateDefaultHTTPs(req request.NginxDefaultHTTPSUpdate) error { + appInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + websites, _ := websiteRepo.List() + hasDefaultWebsite := false + for _, website := range websites { + if website.DefaultServer { + hasDefaultWebsite = true + break + } + } + defaultConfigPath := path.Join(appInstall.GetPath(), "conf", "default", "00.default.conf") + content, err := os.ReadFile(defaultConfigPath) + if err != nil { + return err + } + switch req.Operate { + case "enable": + if req.SSLRejectHandshake { + defaultWebsite, _ := websiteRepo.GetFirst(websiteRepo.WithDefaultServer()) + if defaultWebsite.ID > 0 { + return buserr.New("ErrDefaultWebsite") + } + } + if err := handleSSLConfig(&appInstall, hasDefaultWebsite, req.SSLRejectHandshake); err != nil { + return err + } + case "disable": + defaultConfig, err := parser.NewStringParser(string(content)).Parse() + if err != nil { + return err + } + defaultConfig.FilePath = defaultConfigPath + defaultServer := defaultConfig.FindServers()[0] + defaultServer.RemoveListen(fmt.Sprintf("%d", appInstall.HttpsPort)) + defaultServer.RemoveListen(fmt.Sprintf("[::]:%d", appInstall.HttpsPort)) + defaultServer.RemoveDirective("include", []string{"/usr/local/openresty/nginx/conf/ssl/root_ssl.conf"}) + defaultServer.RemoveDirective("http2", []string{"on"}) + defaultServer.RemoveDirective("ssl_reject_handshake", []string{"on"}) + if err = nginx.WriteConfig(defaultConfig, nginx.IndentedStyle); err != nil { + return err + } + } + return nginxCheckAndReload(string(content), defaultConfigPath, appInstall.ContainerName) +} + +func (n NginxService) GetDefaultHttpsStatus() (*response.NginxConfigRes, error) { + appInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + defaultConfigPath := path.Join(appInstall.GetPath(), "conf", "default", "00.default.conf") + content, err := os.ReadFile(defaultConfigPath) + if err != nil { + return nil, err + } + defaultConfig, err := parser.NewStringParser(string(content)).Parse() + if err != nil { + return nil, err + } + defaultConfig.FilePath = defaultConfigPath + defaultServer := defaultConfig.FindServers()[0] + res := &response.NginxConfigRes{} + for _, directive := range defaultServer.GetDirectives() { + if directive.GetName() == "include" && directive.GetParameters()[0] == "/usr/local/openresty/nginx/conf/ssl/root_ssl.conf" { + res.Https = true + } + if directive.GetName() == "ssl_reject_handshake" && directive.GetParameters()[0] == "on" { + res.Https = true + res.SSLRejectHandshake = true + } + } + return res, nil +} diff --git a/agent/app/service/nginx_utils.go b/agent/app/service/nginx_utils.go new file mode 100644 index 0000000..8e665ef --- /dev/null +++ b/agent/app/service/nginx_utils.go @@ -0,0 +1,276 @@ +package service + +import ( + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" +) + +func getNginxFull(website *model.Website) (dto.NginxFull, error) { + var nginxFull dto.NginxFull + nginxInstall, err := getAppInstallByKey("openresty") + if err != nil { + return nginxFull, err + } + nginxFull.Install = nginxInstall + nginxFull.Dir = path.Join(global.Dir.AppInstallDir, constant.AppOpenresty, nginxInstall.Name) + nginxFull.ConfigDir = path.Join(nginxFull.Dir, "conf") + nginxFull.ConfigFile = "nginx.conf" + nginxFull.SiteDir = path.Join(nginxFull.Dir, "www") + + var nginxConfig dto.NginxConfig + nginxConfig.FilePath = path.Join(nginxFull.Dir, "conf", "nginx.conf") + content, err := os.ReadFile(path.Join(nginxFull.ConfigDir, nginxFull.ConfigFile)) + if err != nil { + return nginxFull, err + } + config, err := parser.NewStringParser(string(content)).Parse() + if err != nil { + return dto.NginxFull{}, err + } + config.FilePath = nginxConfig.FilePath + nginxConfig.OldContent = string(content) + nginxConfig.Config = config + + nginxFull.RootConfig = nginxConfig + + if website != nil { + nginxFull.Website = *website + var siteNginxConfig dto.NginxConfig + siteConfigPath := GetWebsiteConfigPath(*website) + siteNginxConfig.FilePath = siteConfigPath + siteNginxContent, err := os.ReadFile(siteConfigPath) + if err != nil { + return nginxFull, err + } + siteConfig, err := parser.NewStringParser(string(siteNginxContent)).Parse() + if err != nil { + return dto.NginxFull{}, err + } + siteConfig.FilePath = siteConfigPath + siteNginxConfig.Config = siteConfig + siteNginxConfig.OldContent = string(siteNginxContent) + nginxFull.SiteConfig = siteNginxConfig + } + + return nginxFull, nil +} + +func getNginxParamsByKeys(scope string, keys []string, website *model.Website) ([]response.NginxParam, error) { + nginxFull, err := getNginxFull(website) + if err != nil { + return nil, err + } + var res []response.NginxParam + var block components.IBlock + if scope == constant.NginxScopeHttp { + block = nginxFull.RootConfig.Config.FindHttp() + } else { + block = nginxFull.SiteConfig.Config.FindServers()[0] + } + for _, key := range keys { + dirs := block.FindDirectives(key) + for _, dir := range dirs { + nginxParam := response.NginxParam{ + Name: dir.GetName(), + Params: dir.GetParameters(), + } + res = append(res, nginxParam) + } + if len(dirs) == 0 { + nginxParam := response.NginxParam{ + Name: key, + Params: []string{}, + } + res = append(res, nginxParam) + } + } + return res, nil +} + +func updateDefaultServerConfig(enable bool) error { + nginxInstall, err := getAppInstallByKey("openresty") + if err != nil { + return err + } + defaultConfigPath := path.Join(nginxInstall.GetPath(), "conf", "default", "00.default.conf") + content, err := os.ReadFile(defaultConfigPath) + if err != nil { + return err + } + defaultConfig, err := parser.NewStringParser(string(content)).Parse() + if err != nil { + return err + } + defaultConfig.FilePath = defaultConfigPath + defaultServer := defaultConfig.FindServers()[0] + + includeSSL := false + for _, dir := range defaultServer.GetDirectives() { + if dir.GetName() == "ssl_reject_handshake" && dir.GetParameters()[0] == "on" { + defaultServer.RemoveDirective("ssl_reject_handshake", []string{"on"}) + } + if dir.GetName() == "include" && dir.GetParameters()[0] == "/usr/local/openresty/nginx/conf/ssl/root_ssl.conf" { + includeSSL = true + } + } + updateDefaultServer(defaultServer, nginxInstall.HttpPort, nginxInstall.HttpsPort, enable, includeSSL) + + if err = nginx.WriteConfig(defaultConfig, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(string(content), defaultConfigPath, nginxInstall.ContainerName) +} + +func updateNginxConfig(scope string, params []dto.NginxParam, website *model.Website) error { + nginxFull, err := getNginxFull(website) + if err != nil { + return err + } + var block components.IBlock + var config dto.NginxConfig + if scope == constant.NginxScopeHttp { + config = nginxFull.RootConfig + block = nginxFull.RootConfig.Config.FindHttp() + } else if scope == constant.NginxScopeServer { + config = nginxFull.SiteConfig + block = nginxFull.SiteConfig.Config.FindServers()[0] + } else { + config = nginxFull.SiteConfig + block = config.Config.Block + } + + for _, p := range params { + if p.UpdateScope == constant.NginxScopeOut { + config.Config.UpdateDirective(p.Name, p.Params) + } else { + block.UpdateDirective(p.Name, p.Params) + } + } + if err := nginx.WriteConfig(config.Config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(config.OldContent, config.FilePath, nginxFull.Install.ContainerName) +} + +func deleteNginxConfig(scope string, params []dto.NginxParam, website *model.Website) error { + nginxFull, err := getNginxFull(website) + if err != nil { + return err + } + var block components.IBlock + var config dto.NginxConfig + if scope == constant.NginxScopeHttp { + config = nginxFull.RootConfig + block = nginxFull.RootConfig.Config.FindHttp() + } else if scope == constant.NginxScopeServer { + config = nginxFull.SiteConfig + block = nginxFull.SiteConfig.Config.FindServers()[0] + } else { + config = nginxFull.SiteConfig + block = config.Config.Block + } + + for _, param := range params { + block.RemoveDirective(param.Name, param.Params) + } + + if err := nginx.WriteConfig(config.Config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(config.OldContent, config.FilePath, nginxFull.Install.ContainerName) +} + +func getNginxParamsFromStaticFile(scope dto.NginxKey, newParams []dto.NginxParam) []dto.NginxParam { + var ( + newConfig = &components.Config{} + err error + ) + + updateScope := "in" + switch scope { + case dto.SSL: + newConfig, err = parser.NewStringParser(string(nginx_conf.SSL)).Parse() + case dto.CACHE: + newConfig, err = parser.NewStringParser(string(nginx_conf.Cache)).Parse() + case dto.ProxyCache: + newConfig, err = parser.NewStringParser(string(nginx_conf.ProxyCache)).Parse() + } + if err != nil { + return nil + } + for _, dir := range newConfig.GetDirectives() { + addParam := dto.NginxParam{ + Name: dir.GetName(), + Params: dir.GetParameters(), + UpdateScope: updateScope, + } + isExist := false + for _, newParam := range newParams { + if newParam.Name == dir.GetName() { + if components.IsRepeatKey(newParam.Name) { + if len(newParam.Params) > 0 && newParam.Params[0] == dir.GetParameters()[0] { + isExist = true + } + } else { + isExist = true + } + } + } + if !isExist { + newParams = append(newParams, addParam) + } + } + return newParams +} + +func opNginx(containerName, operate string) error { + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)) + cmdStr := fmt.Sprintf("docker exec -i %s nginx ", containerName) + if operate == constant.NginxCheck { + cmdStr = cmdStr + "-t" + } else { + cmdStr = cmdStr + "-s reload" + } + if err := cmdMgr.RunBashC(cmdStr); err != nil { + return err + } + return nil +} + +func nginxCheckAndReload(oldContent string, filePath string, containerName string) error { + if err := opNginx(containerName, constant.NginxCheck); err != nil { + _ = files.NewFileOp().WriteFile(filePath, strings.NewReader(oldContent), constant.DirPerm) + return err + } + if err := opNginx(containerName, constant.NginxReload); err != nil { + _ = files.NewFileOp().WriteFile(filePath, strings.NewReader(oldContent), constant.DirPerm) + return err + } + return nil +} + +func updateDefaultServer(server *components.Server, httpPort int, httpsPort int, defaultServer bool, ssl bool) { + server.UpdateListen(fmt.Sprintf("%d", httpPort), defaultServer) + server.UpdateListen(fmt.Sprintf("[::]:%d", httpPort), defaultServer) + if ssl { + server.UpdateListen(fmt.Sprintf("%d", httpsPort), defaultServer, "ssl") + server.UpdateListen(fmt.Sprintf("[::]:%d", httpsPort), defaultServer, "ssl") + server.UpdateListen(fmt.Sprintf("%d", httpsPort), defaultServer, "quic", "reuseport") + server.UpdateListen(fmt.Sprintf("[::]:%d", httpsPort), defaultServer, "quic", "reuseport") + } +} diff --git a/agent/app/service/php_extensions.go b/agent/app/service/php_extensions.go new file mode 100644 index 0000000..6698150 --- /dev/null +++ b/agent/app/service/php_extensions.go @@ -0,0 +1,86 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" +) + +type PHPExtensionsService struct { +} + +type IPHPExtensionsService interface { + Page(req request.PHPExtensionsSearch) (int64, []response.PHPExtensionsDTO, error) + List() ([]response.PHPExtensionsDTO, error) + Create(req request.PHPExtensionsCreate) error + Update(req request.PHPExtensionsUpdate) error + Delete(req request.PHPExtensionsDelete) error +} + +func NewIPHPExtensionsService() IPHPExtensionsService { + return &PHPExtensionsService{} +} + +func (p PHPExtensionsService) Page(req request.PHPExtensionsSearch) (int64, []response.PHPExtensionsDTO, error) { + var ( + total int64 + extensions []model.PHPExtensions + err error + result []response.PHPExtensionsDTO + ) + total, extensions, err = phpExtensionsRepo.Page(req.Page, req.PageSize) + if err != nil { + return 0, nil, err + } + for _, extension := range extensions { + result = append(result, response.PHPExtensionsDTO{ + PHPExtensions: extension, + }) + } + return total, result, nil +} + +func (p PHPExtensionsService) List() ([]response.PHPExtensionsDTO, error) { + var ( + extensions []model.PHPExtensions + err error + result []response.PHPExtensionsDTO + ) + extensions, err = phpExtensionsRepo.List() + if err != nil { + return nil, err + } + for _, extension := range extensions { + result = append(result, response.PHPExtensionsDTO{ + PHPExtensions: extension, + }) + } + return result, nil +} + +func (p PHPExtensionsService) Create(req request.PHPExtensionsCreate) error { + exist, _ := phpExtensionsRepo.GetFirst(repo.WithByName(req.Name)) + if exist.ID > 0 { + return buserr.New("ErrNameIsExist") + } + extension := model.PHPExtensions{ + Name: req.Name, + Extensions: req.Extensions, + } + return phpExtensionsRepo.Create(&extension) +} + +func (p PHPExtensionsService) Update(req request.PHPExtensionsUpdate) error { + exist, err := phpExtensionsRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + exist.Extensions = req.Extensions + return phpExtensionsRepo.Save(&exist) +} + +func (p PHPExtensionsService) Delete(req request.PHPExtensionsDelete) error { + return phpExtensionsRepo.DeleteBy(repo.WithByID(req.ID)) +} diff --git a/agent/app/service/process.go b/agent/app/service/process.go new file mode 100644 index 0000000..7b26867 --- /dev/null +++ b/agent/app/service/process.go @@ -0,0 +1,338 @@ +package service + +import ( + "bufio" + "context" + "fmt" + "os" + "strconv" + "strings" + "syscall" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/websocket" + "github.com/shirou/gopsutil/v4/net" + "github.com/shirou/gopsutil/v4/process" +) + +type ProcessService struct{} + +type IProcessService interface { + StopProcess(req request.ProcessReq) error + GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error) + GetListeningProcess(c context.Context) ([]ListeningProcess, error) +} + +func NewIProcessService() IProcessService { + return &ProcessService{} +} + +func (ps *ProcessService) StopProcess(req request.ProcessReq) error { + proc, err := process.NewProcess(req.PID) + if err != nil { + return err + } + if err := proc.Kill(); err != nil { + return err + } + return nil +} + +type ListeningProcess struct { + PID int32 + Port map[uint32]struct{} + Protocol uint32 + Name string +} + +func (ps *ProcessService) GetListeningProcess(c context.Context) ([]ListeningProcess, error) { + conn, err := net.ConnectionsMaxWithContext(c, "inet", 32768) + if err != nil { + return nil, err + } + procCache := make(map[int32]ListeningProcess, 64) + + for _, conn := range conn { + if conn.Pid == 0 { + continue + } + + if (conn.Status == "LISTEN" && conn.Type == syscall.SOCK_STREAM) || (conn.Type == syscall.SOCK_DGRAM && conn.Raddr.Port == 0) { + if _, exists := procCache[conn.Pid]; !exists { + proc, err := process.NewProcess(conn.Pid) + if err != nil { + continue + } + procData := ListeningProcess{ + PID: conn.Pid, + } + procData.Name, _ = proc.Name() + procData.Port = make(map[uint32]struct{}) + procData.Port[conn.Laddr.Port] = struct{}{} + procData.Protocol = conn.Type + procCache[conn.Pid] = procData + } else { + p := procCache[conn.Pid] + p.Port[conn.Laddr.Port] = struct{}{} + procCache[conn.Pid] = p + } + } + } + + procs := make([]ListeningProcess, 0, len(procCache)) + for _, proc := range procCache { + procs = append(procs, proc) + } + + return procs, nil +} + +func (ps *ProcessService) GetProcessInfoByPID(pid int32) (*websocket.PsProcessData, error) { + p, err := process.NewProcess(pid) + if err != nil { + return nil, fmt.Errorf("get process info by pid %v: %v", pid, err) + } + + exists, err := p.IsRunning() + if err != nil || !exists { + return nil, fmt.Errorf("process %v is not running", pid) + } + + data := &websocket.PsProcessData{ + PID: pid, + } + + if name, err := p.Name(); err == nil { + data.Name = name + } + + if ppid, err := p.Ppid(); err == nil { + data.PPID = ppid + } + + if username, err := p.Username(); err == nil { + data.Username = username + } + + if status, err := p.Status(); err == nil { + if len(status) > 0 { + data.Status = status[0] + } + } + + if createTime, err := p.CreateTime(); err == nil { + data.StartTime = time.Unix(createTime/1000, 0).Format("2006-01-02 15:04:05") + } + + if numThreads, err := p.NumThreads(); err == nil { + data.NumThreads = numThreads + } + + if connections, err := p.Connections(); err == nil { + data.NumConnections = len(connections) + + var connects []websocket.ProcessConnect + for _, conn := range connections { + pc := websocket.ProcessConnect{ + Status: conn.Status, + Laddr: conn.Laddr, + Raddr: conn.Raddr, + PID: pid, + Name: data.Name, + } + connects = append(connects, pc) + } + data.Connects = connects + } + + if cpuPercent, err := p.CPUPercent(); err == nil { + data.CpuValue = cpuPercent + data.CpuPercent = fmt.Sprintf("%.2f%%", cpuPercent) + } + + if ioCounters, err := p.IOCounters(); err == nil { + data.DiskRead = common.FormatBytes(ioCounters.ReadBytes) + data.DiskWrite = common.FormatBytes(ioCounters.WriteBytes) + } + + if cmdline, err := p.Cmdline(); err == nil { + data.CmdLine = cmdline + } + + if memDetail, err := getMemoryDetail(p.Pid); err == nil { + data.Rss = common.FormatBytes(memDetail.RSS) + data.VMS = common.FormatBytes(memDetail.VMS) + data.HWM = common.FormatBytes(memDetail.HWM) + data.Data = common.FormatBytes(memDetail.Data) + data.Stack = common.FormatBytes(memDetail.Stack) + data.Locked = common.FormatBytes(memDetail.Locked) + data.Swap = common.FormatBytes(memDetail.Swap) + data.Dirty = common.FormatBytes(memDetail.Dirty) + data.RssValue = memDetail.RSS + data.PSS = common.FormatBytes(memDetail.PSS) + data.USS = common.FormatBytes(memDetail.USS) + data.Shared = common.FormatBytes(memDetail.Shared) + data.Text = common.FormatBytes(memDetail.Text) + } + + if envs, err := p.Environ(); err == nil { + data.Envs = envs + } + + if openFiles, err := p.OpenFiles(); err == nil { + data.OpenFiles = openFiles + } + + return data, nil +} + +type MemoryDetail struct { + RSS uint64 + VMS uint64 + HWM uint64 + Data uint64 + Stack uint64 + Locked uint64 + Swap uint64 + + PSS uint64 + USS uint64 + Shared uint64 + Text uint64 + Dirty uint64 +} + +func getMemoryDetail(pid int32) (*MemoryDetail, error) { + mem := &MemoryDetail{} + + if err := readStatus(pid, mem); err != nil { + return nil, err + } + + if err := readSmapsRollup(pid, mem); err != nil { + if err := readSmaps(pid, mem); err != nil { + return nil, err + } + } + return mem, nil +} + +func readStatus(pid int32, mem *MemoryDetail) error { + filePath := fmt.Sprintf("/proc/%d/status", pid) + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + + key := strings.TrimSuffix(fields[0], ":") + value, _ := strconv.ParseUint(fields[1], 10, 64) + value *= 1024 + + switch key { + case "VmRSS": + mem.RSS = value + case "VmSize": + mem.VMS = value + case "VmData": + mem.Data = value + case "VmSwap": + mem.Swap = value + case "VmExe": + mem.Text = value + case "RssShmem": + mem.Shared = value + case "VmHWM": + mem.HWM = value + case "VmStk": + mem.Stack = value + case "VmLck": + mem.Locked = value + } + } + + return scanner.Err() +} + +func readSmapsRollup(pid int32, mem *MemoryDetail) error { + filePath := fmt.Sprintf("/proc/%d/smaps_rollup", pid) + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + + key := strings.TrimSuffix(fields[0], ":") + value, _ := strconv.ParseUint(fields[1], 10, 64) + value *= 1024 + + switch key { + case "Pss": + mem.PSS = value + case "Private_Clean", "Private_Dirty": + mem.USS += value + case "Shared_Clean", "Shared_Dirty": + if mem.Shared == 0 { + mem.Shared = value + } + } + } + + return scanner.Err() +} + +func readSmaps(pid int32, mem *MemoryDetail) error { + filePath := fmt.Sprintf("/proc/%d/smaps", pid) + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + + scanner := bufio.NewScanner(file) + + for scanner.Scan() { + line := scanner.Text() + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + + key := strings.TrimSuffix(fields[0], ":") + value, _ := strconv.ParseUint(fields[1], 10, 64) + value *= 1024 + + switch key { + case "Pss": + mem.PSS += value + case "Private_Clean", "Private_Dirty": + mem.USS += value + case "Shared_Clean", "Shared_Dirty": + if mem.Shared == 0 { + mem.Shared += value + } + } + } + + return scanner.Err() +} diff --git a/agent/app/service/recycle_bin.go b/agent/app/service/recycle_bin.go new file mode 100644 index 0000000..9af3559 --- /dev/null +++ b/agent/app/service/recycle_bin.go @@ -0,0 +1,215 @@ +package service + +import ( + "fmt" + "math" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "github.com/shirou/gopsutil/v4/disk" +) + +type RecycleBinService struct { +} + +type IRecycleBinService interface { + Page(search dto.PageInfo) (int64, []response.RecycleBinDTO, error) + Create(create request.RecycleBinCreate) error + Reduce(reduce request.RecycleBinReduce) error + Clear() error +} + +func NewIRecycleBinService() IRecycleBinService { + return &RecycleBinService{} +} + +func (r RecycleBinService) Page(search dto.PageInfo) (int64, []response.RecycleBinDTO, error) { + var ( + result []response.RecycleBinDTO + ) + partitions, err := disk.Partitions(false) + if err != nil { + return 0, nil, err + } + op := files.NewFileOp() + for _, p := range partitions { + dir := path.Join(p.Mountpoint, ".1panel_clash") + if !op.Stat(dir) { + continue + } + clashFiles, err := os.ReadDir(dir) + if err != nil { + return 0, nil, err + } + for _, file := range clashFiles { + if strings.HasPrefix(file.Name(), "_1p_") { + recycleDTO, err := getRecycleBinDTOFromName(file.Name()) + recycleDTO.IsDir = file.IsDir() + recycleDTO.From = dir + if err == nil { + result = append(result, *recycleDTO) + } + } + } + } + startIndex := (search.Page - 1) * search.PageSize + endIndex := startIndex + search.PageSize + + if startIndex > len(result) { + return int64(len(result)), result, nil + } + if endIndex > len(result) { + endIndex = len(result) + } + return int64(len(result)), result[startIndex:endIndex], nil +} + +func (r RecycleBinService) Create(create request.RecycleBinCreate) error { + if files.IsProtected(create.SourcePath) { + return buserr.New("ErrPathNotDelete") + } + op := files.NewFileOp() + if !op.Stat(create.SourcePath) { + return buserr.New("ErrLinkPathNotFound") + } + clashDir, err := getClashDir(create.SourcePath) + if err != nil { + return err + } + paths := strings.Split(create.SourcePath, "/") + rNamePre := strings.Join(paths, "_1p_") + deleteTime := time.Now() + openFile, err := op.OpenFile(create.SourcePath) + if err != nil { + return err + } + fileInfo, err := openFile.Stat() + if err != nil { + return err + } + size := 0 + if fileInfo.IsDir() { + sizeF, err := op.GetDirSize(create.SourcePath) + if err != nil { + return err + } + size = int(sizeF) + } else { + size = int(fileInfo.Size()) + } + + rName := fmt.Sprintf("_1p_%s%s_p_%d_%d", "file", rNamePre, size, deleteTime.Unix()) + return op.Mv(create.SourcePath, path.Join(clashDir, rName)) +} + +func (r RecycleBinService) Reduce(reduce request.RecycleBinReduce) error { + filePath := path.Join(reduce.From, reduce.RName) + op := files.NewFileOp() + if !op.Stat(filePath) { + return buserr.New("ErrLinkPathNotFound") + } + recycleBinDTO, err := getRecycleBinDTOFromName(reduce.RName) + if err != nil { + return err + } + if !op.Stat(path.Dir(recycleBinDTO.SourcePath)) { + return buserr.New("ErrSourcePathNotFound") + } + if op.Stat(recycleBinDTO.SourcePath) { + if err = op.RmRf(recycleBinDTO.SourcePath); err != nil { + return err + } + } + return op.Mv(filePath, recycleBinDTO.SourcePath) +} + +func (r RecycleBinService) Clear() error { + partitions, err := disk.Partitions(false) + if err != nil { + return err + } + op := files.NewFileOp() + for _, p := range partitions { + dir := path.Join(p.Mountpoint, ".1panel_clash") + if !op.Stat(dir) { + continue + } + newDir := path.Join(p.Mountpoint, "1panel_clash") + if err := op.Mv(dir, newDir); err != nil { + return err + } + go func() { + _ = op.DeleteDir(newDir) + }() + } + return nil +} + +func getClashDir(realPath string) (string, error) { + partitions, err := disk.Partitions(false) + if err != nil { + return "", err + } + for _, p := range partitions { + if p.Mountpoint == "/" { + continue + } + if strings.HasPrefix(realPath, p.Mountpoint) { + clashDir := path.Join(p.Mountpoint, ".1panel_clash") + if err = createClashDir(path.Join(p.Mountpoint, ".1panel_clash")); err != nil { + return "", err + } + return clashDir, nil + } + } + return global.Dir.RecycleBinDir, createClashDir(global.Dir.RecycleBinDir) +} + +func createClashDir(clashDir string) error { + op := files.NewFileOp() + if !op.Stat(clashDir) { + if err := op.CreateDir(clashDir, constant.DirPerm); err != nil { + return err + } + } + return nil +} + +func getRecycleBinDTOFromName(filename string) (*response.RecycleBinDTO, error) { + matches := re.GetRegex(re.RecycleBinFilePattern).FindStringSubmatch(filename) + if len(matches) != 4 { + return nil, fmt.Errorf("invalid filename format") + } + sourcePath := "/" + strings.ReplaceAll(matches[1], "_1p_", "/") + size, err := strconv.ParseInt(matches[2], 10, 64) + if err != nil { + return nil, err + } + if size < math.MinInt || size > math.MaxInt { + return nil, fmt.Errorf("size out of int range") + } + + deleteTime, err := strconv.ParseInt(matches[3], 10, 64) + if err != nil { + return nil, err + } + return &response.RecycleBinDTO{ + Name: path.Base(sourcePath), + Size: int(size), + Type: "file", + DeleteTime: time.Unix(deleteTime, 0), + SourcePath: sourcePath, + RName: filename, + }, nil +} diff --git a/agent/app/service/runtime.go b/agent/app/service/runtime.go new file mode 100644 index 0000000..57d861f --- /dev/null +++ b/agent/app/service/runtime.go @@ -0,0 +1,1242 @@ +package service + +import ( + "bufio" + "context" + "encoding/json" + "fmt" + "maps" + "os" + "os/exec" + "path" + "path/filepath" + "sort" + "strconv" + "strings" + "time" + + fcgiclient "github.com/tomasen/fcgi_client" + + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "gopkg.in/ini.v1" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/env" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "github.com/pkg/errors" + "github.com/subosito/gotenv" +) + +type RuntimeService struct { +} + +type IRuntimeService interface { + Page(req request.RuntimeSearch) (int64, []response.RuntimeDTO, error) + Create(create request.RuntimeCreate) (*model.Runtime, error) + Delete(delete request.RuntimeDelete) error + Update(req request.RuntimeUpdate) error + Get(id uint) (res *response.RuntimeDTO, err error) + GetNodePackageRunScript(req request.NodePackageReq) ([]response.PackageScripts, error) + OperateRuntime(req request.RuntimeOperate) error + GetNodeModules(req request.NodeModuleReq) ([]response.NodeModule, error) + OperateNodeModules(req request.NodeModuleOperateReq) error + SyncForRestart() error + SyncRuntimeStatus() error + DeleteCheck(installID uint) ([]dto.AppResource, error) + UpdateRemark(req request.RuntimeRemark) error + + GetPHPExtensions(runtimeID uint) (response.PHPExtensionRes, error) + InstallPHPExtension(req request.PHPExtensionInstallReq) error + UnInstallPHPExtension(req request.PHPExtensionInstallReq) error + + GetPHPConfig(id uint) (*response.PHPConfig, error) + UpdatePHPConfig(req request.PHPConfigUpdate) (err error) + UpdatePHPConfigFile(req request.PHPFileUpdate) error + GetPHPConfigFile(req request.PHPFileReq) (*response.FileInfo, error) + UpdateFPMConfig(req request.FPMConfig) error + GetFPMConfig(id uint) (*request.FPMConfig, error) + + UpdatePHPContainer(req request.PHPContainerConfig) error + GetPHPContainerConfig(id uint) (*request.PHPContainerConfig, error) + + GetSupervisorProcess(id uint) ([]response.SupervisorProcessConfig, error) + OperateSupervisorProcess(req request.PHPSupervisorProcessConfig) error + OperateSupervisorProcessFile(req request.PHPSupervisorProcessFileReq) (string, error) + + GetFPMStatus(runtimeID uint) ([]response.FpmStatusItem, error) +} + +func NewRuntimeService() IRuntimeService { + return &RuntimeService{} +} + +func (r *RuntimeService) Create(create request.RuntimeCreate) (*model.Runtime, error) { + var ( + opts []repo.DBOption + ) + if create.Name != "" { + opts = append(opts, repo.WithByName(create.Name)) + } + if create.Type != "" { + opts = append(opts, repo.WithByType(create.Type)) + } + exist, _ := runtimeRepo.GetFirst(context.Background(), opts...) + if exist != nil { + return nil, buserr.New("ErrNameIsExist") + } + fileOp := files.NewFileOp() + + runtimeDir := path.Join(global.Dir.RuntimeDir, create.Type) + if !fileOp.Stat(runtimeDir) { + if err := fileOp.CreateDir(runtimeDir, constant.DirPerm); err != nil { + return nil, err + } + } + var hostPorts []string + switch create.Type { + case constant.RuntimePHP: + if create.Resource == constant.ResourceLocal { + runtime := &model.Runtime{ + Name: create.Name, + Resource: create.Resource, + Type: create.Type, + Version: create.Version, + Status: constant.StatusNormal, + Remark: create.Remark, + } + return nil, runtimeRepo.Create(context.Background(), runtime) + } + exist, _ = runtimeRepo.GetFirst(context.Background(), runtimeRepo.WithImage(create.Image)) + if exist != nil { + return nil, buserr.New("ErrImageExist") + } + fpmPort, ok := create.Params["PANEL_APP_PORT_HTTP"] + if !ok { + return nil, buserr.New("ErrPortNotFound") + } + hostPorts = append(hostPorts, fmt.Sprintf("%.0f", fpmPort.(float64))) + if err := checkPortExist(int(fpmPort.(float64))); err != nil { + return nil, err + } + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet: + if !fileOp.Stat(create.CodeDir) { + return nil, buserr.New("ErrPathNotFound") + } + create.Install = true + for _, export := range create.ExposedPorts { + hostPorts = append(hostPorts, strconv.Itoa(export.HostPort)) + if err := checkPortExist(export.HostPort); err != nil { + return nil, err + } + } + } + containerName, ok := create.Params["CONTAINER_NAME"] + if !ok { + return nil, buserr.New("ErrContainerNameIsNull") + } + if err := checkContainerName(containerName.(string)); err != nil { + return nil, err + } + + appDetail, err := appDetailRepo.GetFirst(repo.WithByID(create.AppDetailID)) + if err != nil { + return nil, err + } + app, err := appRepo.GetFirst(repo.WithByID(appDetail.AppId)) + if err != nil { + return nil, err + } + + appVersionDir := filepath.Join(app.GetAppResourcePath(), appDetail.Version) + if !fileOp.Stat(appVersionDir) { + if err = downloadApp(app, appDetail, nil, nil); err != nil { + return nil, err + } + } + + runtime := &model.Runtime{ + Name: create.Name, + AppDetailID: create.AppDetailID, + Type: create.Type, + Image: create.Image, + Resource: create.Resource, + Version: create.Version, + ContainerName: containerName.(string), + Port: strings.Join(hostPorts, ","), + Remark: create.Remark, + } + + switch create.Type { + case constant.RuntimePHP: + if err = handlePHP(create, runtime, fileOp, appVersionDir); err != nil { + return nil, err + } + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet: + if err = handleRuntime(create, runtime, fileOp, appVersionDir); err != nil { + return nil, err + } + } + if err := runtimeRepo.Create(context.Background(), runtime); err != nil { + return nil, err + } + return runtime, nil +} + +func (r *RuntimeService) Page(req request.RuntimeSearch) (int64, []response.RuntimeDTO, error) { + var ( + opts []repo.DBOption + res []response.RuntimeDTO + ) + if req.Name != "" { + opts = append(opts, repo.WithByLikeName(req.Name)) + } + if req.Status != "" { + if req.Type == constant.TypePhp { + opts = append(opts, runtimeRepo.WithNormalStatus(req.Status)) + } else { + opts = append(opts, runtimeRepo.WithStatus(req.Status)) + } + } + if req.Type != "" { + opts = append(opts, repo.WithByType(req.Type)) + } + total, runtimes, err := runtimeRepo.Page(req.Page, req.PageSize, opts...) + if err != nil { + return 0, nil, err + } + if len(runtimes) == 0 { + return 0, res, nil + } + if err = SyncRuntimesStatus(runtimes); err != nil { + return 0, nil, err + } + for _, runtime := range runtimes { + if runtime.Resource == constant.ResourceLocal { + runtime.Status = constant.StatusNormal + } + runtimeDTO := response.NewRuntimeDTO(runtime) + runtimeDTO.Params = make(map[string]interface{}) + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + return 0, nil, err + } + detail, _ := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID)) + if detail.AppId == 0 { + appID, appDetailID := handleRuntimeDetailID(runtime) + runtimeDTO.AppDetailID = appDetailID + runtimeDTO.AppID = appID + } else { + runtimeDTO.AppID = detail.AppId + } + for k, v := range envs { + runtimeDTO.Params[k] = v + if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") { + if strings.Contains(k, "CONTAINER_PORT") { + matches := re.GetRegex(re.TrailingDigitsPattern).FindStringSubmatch(k) + if len(matches) < 2 { + continue + } + containerPort, err := strconv.Atoi(v) + if err != nil { + continue + } + hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])]) + if err != nil { + continue + } + hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])] + runtimeDTO.ExposedPorts = append(runtimeDTO.ExposedPorts, request.ExposedPort{ + ContainerPort: containerPort, + HostPort: hostPort, + HostIP: hostIP, + }) + } + } + } + res = append(res, runtimeDTO) + } + return total, res, nil +} + +func (r *RuntimeService) DeleteCheck(runTimeId uint) ([]dto.AppResource, error) { + var res []dto.AppResource + websites, _ := websiteRepo.GetBy(websiteRepo.WithRuntimeID(runTimeId)) + for _, website := range websites { + res = append(res, dto.AppResource{ + Type: "website", + Name: website.PrimaryDomain, + }) + } + return res, nil +} + +func (r *RuntimeService) Delete(runtimeDelete request.RuntimeDelete) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(runtimeDelete.ID)) + if err != nil { + return err + } + website, _ := websiteRepo.GetFirst(websiteRepo.WithRuntimeID(runtimeDelete.ID)) + if website.ID > 0 { + return buserr.New("ErrDelWithWebsite") + } + if runtime.Resource != constant.ResourceAppstore { + return runtimeRepo.DeleteBy(repo.WithByID(runtimeDelete.ID)) + } + projectDir := runtime.GetPath() + if out, err := compose.Down(runtime.GetComposePath()); err != nil && !runtimeDelete.ForceDelete { + if out != "" { + return errors.New(out) + } + return err + } + if runtime.Type == constant.RuntimePHP { + client, err := docker.NewClient() + if err != nil { + return err + } + defer client.Close() + imageID, err := client.GetImageIDByName(runtime.Image) + if err != nil { + return err + } + if imageID != "" { + if err := client.DeleteImage(imageID); err != nil { + global.LOG.Errorf("delete image id [%s] error %v", imageID, err) + } + } + } + if err := files.NewFileOp().DeleteDir(projectDir); err != nil && !runtimeDelete.ForceDelete { + return err + } + return runtimeRepo.DeleteBy(repo.WithByID(runtimeDelete.ID)) +} + +func (r *RuntimeService) Get(id uint) (*response.RuntimeDTO, error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id)) + if err != nil { + return nil, err + } + + res := response.NewRuntimeDTO(*runtime) + if runtime.Resource == constant.ResourceLocal { + return &res, nil + } + appDetail, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID)) + if err != nil { + return nil, err + } + res.AppID = appDetail.AppId + switch runtime.Type { + case constant.RuntimePHP: + var ( + appForm dto.AppForm + appParams []response.AppParam + ) + if err := json.Unmarshal([]byte(runtime.Params), &appForm); err != nil { + return nil, err + } + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + return nil, err + } + if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok { + res.Source = v + } + res.Params = make(map[string]interface{}) + for k, v := range envs { + if k == "PANEL_APP_PORT_HTTP" { + port, err := strconv.Atoi(v) + if err != nil { + return nil, err + } + res.Params[k] = port + continue + } + res.Params[k] = v + } + + for _, form := range appForm.FormFields { + if v, ok := envs[form.EnvKey]; ok { + appParam := response.AppParam{ + Edit: false, + Key: form.EnvKey, + Rule: form.Rule, + Type: form.Type, + Required: form.Required, + } + if form.Edit { + appParam.Edit = true + } + appParam.LabelZh = form.LabelZh + appParam.LabelEn = form.LabelEn + appParam.Multiple = form.Multiple + appParam.Value = v + if form.Type == "select" { + if form.Multiple { + if v == "" { + appParam.Value = []string{} + } else { + appParam.Value = strings.Split(v, ",") + if strSlice, ok := appParam.Value.([]string); ok && len(strSlice) > 0 && strSlice[0] == "" { + appParam.Value = strSlice[1:] + } + } + } else { + for _, fv := range form.Values { + if fv.Value == v { + appParam.ShowValue = fv.Label + break + } + } + } + appParam.Values = form.Values + } + appParams = append(appParams, appParam) + } + } + res.AppParams = appParams + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet: + if err := handleRuntimeDTO(&res, *runtime); err != nil { + return nil, err + } + } + + return &res, nil +} + +func (r *RuntimeService) Update(req request.RuntimeUpdate) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + if runtime.Resource == constant.ResourceLocal { + runtime.Version = req.Version + return runtimeRepo.Save(runtime) + } + oldImage := runtime.Image + oldEnv := runtime.Env + var hostPorts []string + switch runtime.Type { + case constant.RuntimePHP: + exist, _ := runtimeRepo.GetFirst(context.Background(), runtimeRepo.WithImage(req.Name), runtimeRepo.WithNotId(req.ID)) + if exist != nil { + return buserr.New("ErrImageExist") + } + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet: + for _, export := range req.ExposedPorts { + hostPorts = append(hostPorts, strconv.Itoa(export.HostPort)) + if err = checkRuntimePortExist(export.HostPort, false, runtime.ID); err != nil { + return err + } + } + + appDetail, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID)) + if err != nil { + return err + } + app, err := appRepo.GetFirst(repo.WithByID(appDetail.AppId)) + if err != nil { + return err + } + fileOp := files.NewFileOp() + appVersionDir := path.Join(global.Dir.AppResourceDir, app.Resource, app.Key, appDetail.Version) + if !fileOp.Stat(appVersionDir) || appDetail.Update { + if err := downloadApp(app, appDetail, nil, nil); err != nil { + return err + } + _ = fileOp.Rename(path.Join(runtime.GetPath(), "run.sh"), path.Join(runtime.GetPath(), "run.sh.bak")) + _ = fileOp.CopyFile(path.Join(appVersionDir, "run.sh"), runtime.GetPath()) + } + } + + if containerName, ok := req.Params["CONTAINER_NAME"]; ok && containerName != getRuntimeEnv(runtime.Env, "CONTAINER_NAME") { + if err := checkContainerName(containerName.(string)); err != nil { + return err + } + runtime.ContainerName = containerName.(string) + } + + projectDir := path.Join(global.Dir.RuntimeDir, runtime.Type, runtime.Name) + create := request.RuntimeCreate{ + Image: req.Image, + Type: runtime.Type, + Source: req.Source, + Params: req.Params, + CodeDir: req.CodeDir, + Version: req.Version, + Remark: req.Remark, + NodeConfig: request.NodeConfig{ + Install: true, + ExposedPorts: req.ExposedPorts, + Environments: req.Environments, + Volumes: req.Volumes, + ExtraHosts: req.ExtraHosts, + }, + } + composeContent, envContent, _, err := handleParams(create, projectDir) + if err != nil { + return err + } + runtime.Remark = req.Remark + runtime.Env = string(envContent) + runtime.DockerCompose = string(composeContent) + + switch runtime.Type { + case constant.RuntimePHP: + runtime.Image = req.Image + runtime.Status = constant.StatusBuilding + _ = runtimeRepo.Save(runtime) + client, err := docker.NewClient() + if err != nil { + return err + } + defer client.Close() + imageID, err := client.GetImageIDByName(oldImage) + if err != nil { + return err + } + go buildRuntime(runtime, imageID, oldEnv, req.Rebuild) + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet: + runtime.Version = req.Version + runtime.CodeDir = req.CodeDir + runtime.Port = strings.Join(hostPorts, ",") + runtime.Status = constant.StatusReCreating + runtime.ContainerName = req.Params["CONTAINER_NAME"].(string) + _ = runtimeRepo.Save(runtime) + go reCreateRuntime(runtime) + } + return nil +} + +func (r *RuntimeService) GetNodePackageRunScript(req request.NodePackageReq) ([]response.PackageScripts, error) { + fileOp := files.NewFileOp() + if !fileOp.Stat(req.CodeDir) { + return nil, buserr.New("ErrPathNotFound") + } + if !fileOp.Stat(path.Join(req.CodeDir, "package.json")) { + return nil, buserr.New("ErrPackageJsonNotFound") + } + content, err := fileOp.GetContent(path.Join(req.CodeDir, "package.json")) + if err != nil { + return nil, err + } + var packageMap map[string]interface{} + err = json.Unmarshal(content, &packageMap) + if err != nil { + return nil, err + } + scripts, ok := packageMap["scripts"] + if !ok { + return nil, buserr.New("ErrScriptsNotFound") + } + var packageScripts []response.PackageScripts + for k, v := range scripts.(map[string]interface{}) { + packageScripts = append(packageScripts, response.PackageScripts{ + Name: k, + Script: v.(string), + }) + } + return packageScripts, nil +} + +func (r *RuntimeService) OperateRuntime(req request.RuntimeOperate) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + defer func() { + if err != nil { + runtime.Status = constant.StatusError + runtime.Message = err.Error() + _ = runtimeRepo.Save(runtime) + } + }() + switch req.Operate { + case constant.RuntimeUp: + if err = runComposeCmdWithLog(req.Operate, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + return err + } + if err = SyncRuntimeContainerStatus(runtime); err != nil { + return err + } + case constant.RuntimeDown: + if err = runComposeCmdWithLog(req.Operate, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + return err + } + runtime.Status = constant.StatusStopped + case constant.RuntimeRestart: + if err = restartRuntime(runtime); err != nil { + return err + } + if err = SyncRuntimeContainerStatus(runtime); err != nil { + return err + } + } + return runtimeRepo.Save(runtime) +} + +func (r *RuntimeService) GetNodeModules(req request.NodeModuleReq) ([]response.NodeModule, error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return nil, err + } + var res []response.NodeModule + nodeModulesPath := path.Join(runtime.CodeDir, "node_modules") + fileOp := files.NewFileOp() + if !fileOp.Stat(nodeModulesPath) { + return res, nil + } + moduleDirs, err := os.ReadDir(nodeModulesPath) + if err != nil { + return nil, err + } + for _, moduleDir := range moduleDirs { + packagePath := path.Join(nodeModulesPath, moduleDir.Name(), "package.json") + if !fileOp.Stat(packagePath) { + continue + } + content, err := fileOp.GetContent(packagePath) + if err != nil { + continue + } + module := response.NodeModule{} + if err := json.Unmarshal(content, &module); err != nil { + continue + } + res = append(res, module) + } + return res, nil +} + +func (r *RuntimeService) OperateNodeModules(req request.NodeModuleOperateReq) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + containerName, err := env.GetEnvValueByKey(runtime.GetEnvPath(), "CONTAINER_NAME") + if err != nil { + return err + } + operation := getOperation(req.Operate, req.PkgManager) + execScript := fmt.Sprintf("%s %s %s", req.PkgManager, operation, req.Module) + + ctx, cancel := context.WithTimeout(context.Background(), 20*time.Minute) + defer cancel() + installCmd := exec.CommandContext(ctx, "docker", "exec", "-i", containerName, "bash", "-c", execScript) + output, err := installCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("failed to execute command: %s, error: %w", string(output), err) + } + return nil +} + +func (r *RuntimeService) SyncForRestart() error { + runtimes, err := runtimeRepo.List() + if err != nil { + return err + } + for _, runtime := range runtimes { + if runtime.Status == constant.StatusBuilding || runtime.Status == constant.StatusReCreating || runtime.Status == constant.StatusStarting || runtime.Status == constant.StatusCreating { + runtime.Status = constant.SystemRestart + runtime.Message = "System restart causing interrupt" + _ = runtimeRepo.Save(&runtime) + } + } + return nil +} + +func (r *RuntimeService) SyncRuntimeStatus() error { + runtimes, err := runtimeRepo.List() + if err != nil { + return err + } + for _, runtime := range runtimes { + if runtime.Type == constant.RuntimeNode || runtime.Type == constant.RuntimeJava || runtime.Type == constant.RuntimeGo || runtime.Type == constant.RuntimePython || runtime.Type == constant.RuntimeDotNet { + _ = SyncRuntimeContainerStatus(&runtime) + } + } + return nil +} + +func (r *RuntimeService) GetPHPExtensions(runtimeID uint) (response.PHPExtensionRes, error) { + var res response.PHPExtensionRes + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(runtimeID)) + if err != nil { + return res, err + } + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Second)) + out, err := cmdMgr.RunWithStdoutBashCf("docker exec -i %s php -m", runtime.ContainerName) + if err != nil { + return res, err + } + extensions := strings.Split(out, "\n") + exitExtensions := make(map[string]string) + for _, ext := range extensions { + extStr := strings.TrimSpace(ext) + if extStr != "" && extStr != "[Zend Modules]" && extStr != "[PHP Modules]" { + exitExtensions[strings.ToLower(extStr)] = extStr + } + } + var phpExtensions []response.SupportExtension + if err = json.Unmarshal(nginx_conf.GetWebsiteFile("php_extensions.json"), &phpExtensions); err != nil { + return res, err + } + for _, ext := range phpExtensions { + if _, ok := exitExtensions[strings.ToLower(ext.Check)]; ok { + ext.Installed = true + } + res.SupportExtensions = append(res.SupportExtensions, ext) + } + for _, name := range exitExtensions { + res.Extensions = append(res.Extensions, name) + } + sort.Slice(res.Extensions, func(i, j int) bool { + return strings.ToLower(res.Extensions[i]) < strings.ToLower(res.Extensions[j]) + }) + return res, nil +} + +func (r *RuntimeService) InstallPHPExtension(req request.PHPExtensionInstallReq) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + if task.CheckResourceTaskIsExecuting(task.TaskInstall, task.TaskScopeRuntimeExtension, runtime.ID) { + return buserr.New("ErrInstallExtension") + } + installTask, err := task.NewTaskWithOps(req.Name, task.TaskInstall, task.TaskScopeRuntimeExtension, req.TaskID, runtime.ID) + if err != nil { + return err + } + installTask.AddSubTask("", func(t *task.Task) error { + err = cmd.NewCommandMgr(cmd.WithTask(*installTask), cmd.WithTimeout(20*time.Minute)). + RunBashCf("docker exec -i %s install-ext %s", runtime.ContainerName, req.Name) + if err != nil { + return err + } + client, err := docker.NewClient() + if err != nil { + return err + } + defer client.Close() + oldImageID, err := client.GetImageIDByName(runtime.Image) + if err != nil { + return err + } + err = cmd.NewCommandMgr(cmd.WithTask(*installTask), cmd.WithTimeout(15*time.Minute)). + RunBashCf("docker commit %s %s", runtime.ContainerName, runtime.Image) + if err != nil { + return err + } + handlePHPDir(*runtime) + if err = restartRuntime(runtime); err != nil { + return err + } + newImageID, err := client.GetImageIDByName(runtime.Image) + if err == nil && newImageID != oldImageID { + if err := client.DeleteImage(oldImageID); err != nil { + t.Log(fmt.Sprintf("delete old image %s failed %v", oldImageID, err)) + } else { + t.Log("delete old image success") + } + } + return nil + }, nil) + go func() { + err = installTask.Execute() + if err == nil { + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + global.LOG.Errorf("get runtime env error %v", err) + return + } + extensions, ok := envs["PHP_EXTENSIONS"] + exist := false + var extensionArray []string + if ok { + extensions = strings.TrimPrefix(extensions, ",") + extensionArray = strings.Split(extensions, ",") + for _, ext := range extensionArray { + if ext == req.Name { + exist = true + break + } + } + } + if !exist { + extensionArray = append(extensionArray, req.Name) + envs["PHP_EXTENSIONS"] = strings.Join(extensionArray, ",") + if err = gotenv.Write(envs, runtime.GetEnvPath()); err != nil { + global.LOG.Errorf("write runtime env error %v", err) + return + } + envStr, err := gotenv.Marshal(envs) + if err != nil { + global.LOG.Errorf("marshal runtime env error %v", err) + return + } + runtime.Env = envStr + _ = runtimeRepo.Save(runtime) + } + } + }() + return nil +} + +func (r *RuntimeService) UnInstallPHPExtension(req request.PHPExtensionInstallReq) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + if err = unInstallPHPExtension(runtime, []string{req.Name}); err != nil { + return err + } + if err = restartRuntime(runtime); err != nil { + return err + } + return runtimeRepo.Save(runtime) +} + +func (r *RuntimeService) GetPHPConfig(id uint) (*response.PHPConfig, error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id)) + if err != nil { + return nil, err + } + phpConfigPath := path.Join(runtime.GetPath(), "conf", "php.ini") + fileOp := files.NewFileOp() + if !fileOp.Stat(phpConfigPath) { + return nil, buserr.WithName("ErrFileNotFound", "php.ini") + } + params := make(map[string]string) + configFile, err := fileOp.OpenFile(phpConfigPath) + if err != nil { + return nil, err + } + defer configFile.Close() + scanner := bufio.NewScanner(configFile) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if strings.HasPrefix(line, ";") { + continue + } + matches := re.GetRegex(re.PhpAssignmentPattern).FindStringSubmatch(line) + if len(matches) == 3 { + params[matches[1]] = matches[2] + } + } + cfg, err := ini.Load(phpConfigPath) + if err != nil { + return nil, err + } + phpConfig, err := cfg.GetSection("PHP") + if err != nil { + return nil, err + } + disableFunctionStr := phpConfig.Key("disable_functions").Value() + res := &response.PHPConfig{Params: params} + if disableFunctionStr != "" { + disableFunctions := strings.Split(disableFunctionStr, ",") + if len(disableFunctions) > 0 { + res.DisableFunctions = disableFunctions + } + } + uploadMaxSize := phpConfig.Key("upload_max_filesize").Value() + if uploadMaxSize != "" { + res.UploadMaxSize = uploadMaxSize + } + timeout := phpConfig.Key("max_execution_time").Value() + if timeout != "" { + res.MaxExecutionTime = timeout + } + return res, nil +} + +func (r *RuntimeService) UpdatePHPConfig(req request.PHPConfigUpdate) (err error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + phpConfigPath := path.Join(runtime.GetPath(), "conf", "php.ini") + fileOp := files.NewFileOp() + if !fileOp.Stat(phpConfigPath) { + return buserr.WithName("ErrFileNotFound", "php.ini") + } + configFile, err := fileOp.OpenFile(phpConfigPath) + if err != nil { + return err + } + defer configFile.Close() + + contentBytes, err := fileOp.GetContent(phpConfigPath) + if err != nil { + return err + } + + content := string(contentBytes) + lines := strings.Split(content, "\n") + for i, line := range lines { + if strings.HasPrefix(line, ";") { + continue + } + switch req.Scope { + case "params": + for key, value := range req.Params { + if phpConfigLineMatchesKey(line, key) { + lines[i] = fmt.Sprintf("%s = %s", key, value) + } + } + case "disable_functions": + if phpConfigLineMatchesKey(line, "disable_functions") { + lines[i] = fmt.Sprintf("disable_functions = %s", strings.Join(req.DisableFunctions, ",")) + } + case "upload_max_filesize": + if phpConfigLineMatchesKey(line, "post_max_size") { + lines[i] = fmt.Sprintf("post_max_size = %s", req.UploadMaxSize) + } + if phpConfigLineMatchesKey(line, "upload_max_filesize") { + lines[i] = fmt.Sprintf("upload_max_filesize = %s", req.UploadMaxSize) + } + case "max_execution_time": + if phpConfigLineMatchesKey(line, "max_execution_time") { + lines[i] = fmt.Sprintf("max_execution_time = %s", req.MaxExecutionTime) + } + if phpConfigLineMatchesKey(line, "max_input_time") { + lines[i] = fmt.Sprintf("max_input_time = %s", req.MaxExecutionTime) + } + } + } + updatedContent := strings.Join(lines, "\n") + if err := fileOp.WriteFile(phpConfigPath, strings.NewReader(updatedContent), constant.DirPerm); err != nil { + return err + } + + err = restartRuntime(runtime) + if err != nil { + _ = fileOp.WriteFile(phpConfigPath, strings.NewReader(string(contentBytes)), constant.DirPerm) + return err + } + return +} + +func (r *RuntimeService) GetPHPConfigFile(req request.PHPFileReq) (*response.FileInfo, error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return nil, err + } + configPath := "" + switch req.Type { + case constant.ConfigFPM: + configPath = path.Join(runtime.GetPath(), "conf", "php-fpm.conf") + case constant.ConfigPHP: + configPath = path.Join(runtime.GetPath(), "conf", "php.ini") + } + info, err := files.NewFileInfo(files.FileOption{ + Path: configPath, + Expand: true, + }) + if err != nil { + return nil, err + } + return &response.FileInfo{FileInfo: *info}, nil +} + +func phpConfigLineMatchesKey(line, key string) bool { + trim := strings.TrimSpace(line) + + idx := strings.Index(trim, "=") + if idx == -1 { + return false + } + currentKey := strings.TrimSpace(trim[:idx]) + return currentKey == key +} + +func (r *RuntimeService) UpdatePHPConfigFile(req request.PHPFileUpdate) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + configPath := "" + if req.Type == constant.ConfigFPM { + configPath = path.Join(runtime.GetPath(), "conf", "php-fpm.conf") + } else { + configPath = path.Join(runtime.GetPath(), "conf", "php.ini") + } + if err := files.NewFileOp().WriteFile(configPath, strings.NewReader(req.Content), constant.DirPerm); err != nil { + return err + } + if _, err := compose.Restart(runtime.GetComposePath()); err != nil { + return err + } + return nil +} + +func (r *RuntimeService) UpdateFPMConfig(req request.FPMConfig) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + cfg, err := ini.Load(runtime.GetFPMPath()) + if err != nil { + return err + } + for k, v := range req.Params { + var valueStr string + switch v := v.(type) { + case string: + valueStr = v + case int: + valueStr = fmt.Sprintf("%d", v) + case float64: + valueStr = fmt.Sprintf("%.f", v) + default: + continue + } + cfg.Section("www").Key(k).SetValue(valueStr) + } + if err := cfg.SaveTo(runtime.GetFPMPath()); err != nil { + return err + } + if _, err := compose.Restart(runtime.GetComposePath()); err != nil { + return err + } + return nil +} + +var PmKeys = map[string]struct { +}{ + "pm": {}, + "pm.max_children": {}, + "pm.start_servers": {}, + "pm.min_spare_servers": {}, + "pm.max_spare_servers": {}, +} + +func (r *RuntimeService) GetFPMConfig(id uint) (*request.FPMConfig, error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id)) + if err != nil { + return nil, err + } + fileOp := files.NewFileOp() + if !fileOp.Stat(runtime.GetFPMPath()) { + return nil, buserr.WithName("ErrFileNotFound", "php-fpm.conf") + } + params := make(map[string]interface{}) + cfg, err := ini.Load(runtime.GetFPMPath()) + if err != nil { + return nil, err + } + for _, key := range cfg.Section("www").Keys() { + if _, ok := PmKeys[key.Name()]; ok { + params[key.Name()] = key.Value() + } + } + res := &request.FPMConfig{Params: params} + return res, nil +} + +func (r *RuntimeService) UpdatePHPContainer(req request.PHPContainerConfig) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + var ( + composeContent []byte + ) + for _, export := range req.ExposedPorts { + if strconv.Itoa(export.HostPort) == runtime.Port { + return buserr.WithName("ErrPHPRuntimePortFailed", strconv.Itoa(export.HostPort)) + } + if export.ContainerPort == 9000 { + return buserr.New("ErrPHPPortIsDefault") + } + if err = checkRuntimePortExist(export.HostPort, false, runtime.ID); err != nil { + return err + } + } + if req.ContainerName != "" && req.ContainerName != getRuntimeEnv(runtime.Env, "CONTAINER_NAME") { + if err := checkContainerName(req.ContainerName); err != nil { + return err + } + runtime.ContainerName = req.ContainerName + } + fileOp := files.NewFileOp() + projectDir := path.Join(global.Dir.RuntimeDir, runtime.Type, runtime.Name) + composeContent, err = fileOp.GetContent(path.Join(projectDir, "docker-compose.yml")) + if err != nil { + return err + } + envPath := path.Join(projectDir, ".env") + if !fileOp.Stat(envPath) { + _ = fileOp.CreateFile(envPath) + } + envs, err := gotenv.Read(envPath) + if err != nil { + return err + } + for k := range envs { + if strings.HasPrefix(k, "CONTAINER_PORT_") || strings.HasPrefix(k, "HOST_PORT_") || strings.HasPrefix(k, "HOST_IP_") || strings.Contains(k, "APP_PORT") { + delete(envs, k) + } + } + create := request.RuntimeCreate{ + Image: runtime.Image, + Type: runtime.Type, + Params: make(map[string]interface{}), + NodeConfig: request.NodeConfig{ + ExposedPorts: req.ExposedPorts, + Environments: req.Environments, + Volumes: req.Volumes, + }, + } + composeContent, err = handleCompose(envs, composeContent, create, projectDir) + if err != nil { + return err + } + newMap := make(map[string]string) + handleMap(create.Params, newMap) + maps.Copy(envs, newMap) + envs["PANEL_APP_PORT_HTTP"] = runtime.Port + envStr, err := gotenv.Marshal(envs) + if err != nil { + return err + } + if err = gotenv.Write(envs, envPath); err != nil { + return err + } + envContent := []byte(envStr) + runtime.Env = string(envContent) + runtime.DockerCompose = string(composeContent) + runtime.Status = constant.StatusReCreating + _ = runtimeRepo.Save(runtime) + go reCreateRuntime(runtime) + return nil +} + +func (r *RuntimeService) GetPHPContainerConfig(id uint) (*request.PHPContainerConfig, error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id)) + if err != nil { + return nil, err + } + runtimeDTO := response.NewRuntimeDTO(*runtime) + if err := handleRuntimeDTO(&runtimeDTO, *runtime); err != nil { + return nil, err + } + res := &request.PHPContainerConfig{ + ID: runtime.ID, + ContainerName: runtime.ContainerName, + ExposedPorts: runtimeDTO.ExposedPorts, + Environments: runtimeDTO.Environments, + Volumes: runtimeDTO.Volumes, + ExtraHosts: runtimeDTO.ExtraHosts, + } + return res, nil +} + +func (r *RuntimeService) GetSupervisorProcess(id uint) ([]response.SupervisorProcessConfig, error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(id)) + if err != nil { + return nil, err + } + configDir := path.Join(global.Dir.RuntimeDir, "php", runtime.Name, "supervisor", "supervisor.d") + return handleProcessConfig(configDir, runtime.ContainerName) +} + +func (r *RuntimeService) OperateSupervisorProcess(req request.PHPSupervisorProcessConfig) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + configDir := path.Join(global.Dir.RuntimeDir, "php", runtime.Name, "supervisor") + return handleProcess(configDir, req.SupervisorProcessConfig, runtime.ContainerName) +} + +func (r *RuntimeService) OperateSupervisorProcessFile(req request.PHPSupervisorProcessFileReq) (string, error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return "", err + } + supervisorDir := path.Join(global.Dir.RuntimeDir, "php", runtime.Name, "supervisor") + configDir := path.Join(supervisorDir, "supervisor.d") + logFile := path.Join(supervisorDir, "log", fmt.Sprintf("%s.out.log", req.SupervisorProcessFileReq.Name)) + return handleSupervisorFile(req.SupervisorProcessFileReq, configDir, runtime.ContainerName, logFile) +} + +func (r *RuntimeService) UpdateRemark(req request.RuntimeRemark) error { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.ID)) + if err != nil { + return err + } + runtime.Remark = req.Remark + return runtimeRepo.Save(runtime) +} + +func (r *RuntimeService) GetFPMStatus(runtimeID uint) ([]response.FpmStatusItem, error) { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(runtimeID)) + if err != nil { + return nil, err + } + fcgiClient, err := fcgiclient.DialTimeout("tcp", "127.0.0.1:"+runtime.Port, 10*time.Second) + if err != nil { + return nil, errors.New(" FastCGI : " + err.Error()) + } + defer fcgiClient.Close() + + reqEnv := map[string]string{ + "REQUEST_METHOD": "GET", + "REQUEST_URI": "/status", + "SCRIPT_FILENAME": "/status", + "SCRIPT_NAME": "/status", + "QUERY_STRING": "", + "CONTENT_TYPE": "", + "CONTENT_LENGTH": "0", + "SERVER_SOFTWARE": "go-fcgi-client", + "SERVER_NAME": "localhost", + "SERVER_PORT": runtime.Port, + "REMOTE_ADDR": "127.0.0.1", + "GATEWAY_INTERFACE": "CGI/1.1", + } + + resp, err := fcgiClient.Get(reqEnv) + if err != nil { + return nil, errors.New(" FastCGI : " + err.Error()) + } + defer resp.Body.Close() + + var status []response.FpmStatusItem + scanner := bufio.NewScanner(resp.Body) + + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + if line == "" { + continue + } + parts := strings.SplitN(line, ":", 2) + if len(parts) != 2 { + continue + } + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + status = append(status, response.FpmStatusItem{ + Key: key, + Value: value, + }) + } + + if err := scanner.Err(); err != nil { + return nil, errors.New(fmt.Sprintf(" FastCGI : %v", err)) + } + return status, nil +} diff --git a/agent/app/service/runtime_utils.go b/agent/app/service/runtime_utils.go new file mode 100644 index 0000000..db79237 --- /dev/null +++ b/agent/app/service/runtime_utils.go @@ -0,0 +1,1116 @@ +package service + +import ( + "bufio" + "bytes" + "context" + "encoding/json" + "fmt" + "io" + "maps" + "os" + "os/exec" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + cmd2 "github.com/1Panel-dev/1Panel/agent/utils/cmd" + + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/common" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "github.com/pkg/errors" + "github.com/subosito/gotenv" + "gopkg.in/yaml.v3" +) + +func handleRuntime(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) { + runtimeDir := path.Join(global.Dir.RuntimeDir, create.Type) + if err = fileOp.CopyDir(appVersionDir, runtimeDir); err != nil { + return + } + versionDir := path.Join(runtimeDir, filepath.Base(appVersionDir)) + projectDir := path.Join(runtimeDir, create.Name) + defer func() { + if err != nil { + _ = fileOp.DeleteDir(projectDir) + } + }() + if err = fileOp.Rename(versionDir, projectDir); err != nil { + return + } + composeContent, envContent, _, err := handleParams(create, projectDir) + if err != nil { + return + } + runtime.DockerCompose = string(composeContent) + runtime.Env = string(envContent) + runtime.Status = constant.StatusCreating + runtime.CodeDir = create.CodeDir + + nodeDetail, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID)) + if err != nil { + return err + } + + go func() { + RequestDownloadCallBack(nodeDetail.DownloadCallBackUrl) + }() + go startRuntime(runtime) + + return +} + +func handlePHP(create request.RuntimeCreate, runtime *model.Runtime, fileOp files.FileOp, appVersionDir string) (err error) { + runtimeDir := path.Join(global.Dir.RuntimeDir, create.Type) + if err = fileOp.CopyDirWithNewName(appVersionDir, runtimeDir, create.Name); err != nil { + return + } + projectDir := path.Join(runtimeDir, create.Name) + defer func() { + if err != nil { + _ = fileOp.DeleteDir(projectDir) + } + }() + + version, ok := create.Params["PHP_VERSION"] + if ok { + extensionsDir := path.Join(projectDir, "extensions", getExtensionDir(version.(string))) + _ = fileOp.CreateDir(extensionsDir, 0755) + } + + composeContent, envContent, forms, err := handleParams(create, projectDir) + if err != nil { + return + } + runtime.DockerCompose = string(composeContent) + runtime.Env = string(envContent) + runtime.Params = string(forms) + runtime.Status = constant.StatusBuilding + + go func() { + appDetail, err := appDetailRepo.GetFirst(repo.WithByID(runtime.AppDetailID)) + if err == nil { + RequestDownloadCallBack(appDetail.DownloadCallBackUrl) + } + }() + + go buildRuntime(runtime, "", "", false) + return +} + +func startRuntime(runtime *model.Runtime) { + if err := runComposeCmdWithLog("up", runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + runtime.Status = constant.StatusError + runtime.Message = err.Error() + _ = runtimeRepo.Save(runtime) + return + } + + if err := SyncRuntimeContainerStatus(runtime); err != nil { + runtime.Status = constant.StatusError + runtime.Message = err.Error() + _ = runtimeRepo.Save(runtime) + return + } +} + +func reCreateRuntime(runtime *model.Runtime) { + var err error + defer func() { + if err != nil { + runtime.Status = constant.StatusError + runtime.Message = err.Error() + _ = runtimeRepo.Save(runtime) + } + }() + if err = runComposeCmdWithLog("down", runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + return + } + if err = runComposeCmdWithLog("up", runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + return + } + if err := SyncRuntimeContainerStatus(runtime); err != nil { + return + } +} + +func getComposeCmd(composePath, operate string) *exec.Cmd { + dockerCommand := global.CONF.DockerConfig.Command + var cmd *exec.Cmd + if dockerCommand == "docker-compose" { + if operate == "up" { + cmd = exec.Command("docker-compose", "-f", composePath, operate, "-d") + } else { + cmd = exec.Command("docker-compose", "-f", composePath, operate) + } + } else { + if operate == "up" { + cmd = exec.Command("docker", "compose", "-f", composePath, operate, "-d") + } else { + cmd = exec.Command("docker", "compose", "-f", composePath, operate) + } + } + return cmd +} + +func runComposeCmdWithLog(operate string, composePath string, logPath string) error { + cmd := getComposeCmd(composePath, operate) + logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, constant.FilePerm) + if err != nil { + global.LOG.Errorf("Failed to open log file: %v", err) + return err + } + defer logFile.Close() + multiWriterStdout := io.MultiWriter(os.Stdout, logFile) + cmd.Stdout = multiWriterStdout + var stderrBuf bytes.Buffer + multiWriterStderr := io.MultiWriter(&stderrBuf, logFile, os.Stderr) + cmd.Stderr = multiWriterStderr + + err = cmd.Run() + if err != nil { + return errors.New(buserr.New("ErrRuntimeStart").Error() + ":" + stderrBuf.String()) + } + return nil +} + +func SyncRuntimesStatus(runtimes []model.Runtime) error { + cli, err := docker.NewClient() + if err != nil { + return err + } + defer cli.Close() + var containerNames []string + runtimeContainer := make(map[string]int) + for index, runtime := range runtimes { + containerNames = append(containerNames, runtime.ContainerName) + runtimeContainer["/"+runtime.ContainerName] = index + } + containers, err := cli.ListContainersByName(containerNames) + if err != nil { + return err + } + for _, contain := range containers { + if index, ok := runtimeContainer[contain.Names[0]]; ok { + if runtimes[index].Status == constant.StatusBuilding || runtimes[index].Status == constant.StatusCreating { + delete(runtimeContainer, contain.Names[0]) + continue + } + switch contain.State { + case "exited": + runtimes[index].Status = constant.StatusError + case "running": + runtimes[index].Status = constant.StatusRunning + case "paused": + runtimes[index].Status = constant.StatusStopped + case "restarting": + runtimes[index].Status = constant.StatusRestarting + } + _ = runtimeRepo.Save(&runtimes[index]) + delete(runtimeContainer, contain.Names[0]) + } + } + for _, index := range runtimeContainer { + if runtimes[index].Status != constant.StatusBuilding && runtimes[index].Status != constant.StatusCreating { + runtimes[index].Status = constant.StatusStopped + } + } + return nil +} + +func SyncRuntimeContainerStatus(runtime *model.Runtime) error { + env, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + return err + } + var containerNames []string + if containerName, ok := env["CONTAINER_NAME"]; !ok { + return buserr.New("ErrContainerNameNotFound") + } else { + containerNames = append(containerNames, containerName) + } + cli, err := docker.NewClient() + if err != nil { + return err + } + defer cli.Close() + containers, err := cli.ListContainersByName(containerNames) + if err != nil { + return err + } + if len(containers) == 0 { + return buserr.WithNameAndErr("ErrContainerNotFound", containerNames[0], nil) + } + container := containers[0] + + switch container.State { + case "exited": + runtime.Status = constant.StatusError + case "running": + runtime.Status = constant.StatusRunning + case "paused": + runtime.Status = constant.StatusStopped + default: + if runtime.Status != constant.StatusBuilding { + runtime.Status = constant.StatusStopped + } + } + + return runtimeRepo.Save(runtime) +} + +func getRuntimeEnv(envStr, key string) string { + env, err := gotenv.Unmarshal(envStr) + if err != nil { + return "" + } + if v, ok := env[key]; ok { + return v + } + return "" +} + +func deleteImageByID(oldImageID, imageName string, client docker.Client) { + newImageID, err := client.GetImageIDByName(imageName) + if err == nil && newImageID != oldImageID { + global.LOG.Infof("delete imageID [%s] ", oldImageID) + if err := client.DeleteImage(oldImageID); err != nil { + global.LOG.Errorf("delete imageID [%s] error %v", oldImageID, err) + } else { + global.LOG.Infof("delete old image success") + } + } +} + +func buildRuntime(runtime *model.Runtime, oldImageID string, oldEnv string, rebuild bool) { + runtimePath := runtime.GetPath() + composePath := runtime.GetComposePath() + logPath := path.Join(runtimePath, "build.log") + + logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + if err != nil { + global.LOG.Errorf("failed to open log file: %v", err) + return + } + defer func() { + _ = logFile.Close() + }() + + newPHPVersion := getRuntimeEnv(runtime.Env, "PHP_VERSION") + oldPHPVersion := getRuntimeEnv(oldEnv, "PHP_VERSION") + if newPHPVersion != oldPHPVersion { + _ = os.Rename(path.Join(runtimePath, "extensions", getExtensionDir(oldPHPVersion)), path.Join(runtimePath, "extensions", getExtensionDir(newPHPVersion))) + } + + ctx, cancel := context.WithTimeout(context.Background(), 1*time.Hour) + defer cancel() + + dockerCommand := global.CONF.DockerConfig.Command + var cmd *exec.Cmd + if dockerCommand == "docker-compose" { + cmd = exec.CommandContext(ctx, "docker-compose", "-f", composePath, "build") + } else { + cmd = exec.CommandContext(ctx, "docker", "compose", "-f", composePath, "build") + } + cmd.Stdout = logFile + var stderrBuf bytes.Buffer + multiWriterStderr := io.MultiWriter(&stderrBuf, logFile) + cmd.Stderr = multiWriterStderr + + err = cmd.Run() + if err != nil { + runtime.Status = constant.StatusError + runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + stderrBuf.String() + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + buserr.New("ErrCmdTimeout").Error() + } else { + runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + stderrBuf.String() + } + _ = runtimeRepo.Save(runtime) + return + } + if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + return + } + client, err := docker.NewClient() + if err != nil { + _, _ = logFile.WriteString(fmt.Sprintf("failed to connect to docker client: %v", err)) + return + } + runtime.Message = "" + if rebuild && runtime.ID > 0 { + extensionsStr := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS") + extensionsArray := strings.Split(extensionsStr, ",") + oldExtensionStr := getRuntimeEnv(oldEnv, "PHP_EXTENSIONS") + oldExtensionArray := strings.Split(oldExtensionStr, ",") + var delExtensions []string + for _, oldExt := range oldExtensionArray { + exist := false + for _, ext := range extensionsArray { + if oldExt == ext { + exist = true + break + } + } + if !exist { + delExtensions = append(delExtensions, oldExt) + } + } + if err = unInstallPHPExtension(runtime, delExtensions); err != nil { + _, _ = logFile.WriteString(fmt.Sprintf("unInstallPHPExtension error %v", err)) + } + } + + defer func() { + _ = runtimeRepo.Save(runtime) + }() + + if out, err := compose.Up(composePath); err != nil { + runtime.Status = constant.StatusStartErr + runtime.Message = out + return + } + deleteImageID := "" + extensions := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS") + if extensions != "" { + deleteImageID, _ = client.GetImageIDByName(runtime.Image) + cmdMgr := cmd2.NewCommandMgr(cmd2.WithTimeout(60*time.Minute), cmd2.WithOutputFile(logPath)) + if err = cmdMgr.Run("docker", "exec", "-i", runtime.ContainerName, "install-ext", extensions); err != nil { + runtime.Status = constant.StatusError + runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error() + return + } + commitMgr := cmd2.NewCommandMgr(cmd2.WithTimeout(10*time.Minute), cmd2.WithOutputFile(logPath)) + err = commitMgr.Run("docker", "commit", runtime.ContainerName, runtime.Image) + if err != nil { + runtime.Status = constant.StatusError + runtime.Message = buserr.New("ErrImageBuildErr").Error() + ":" + err.Error() + return + } + } + if oldImageID != "" { + deleteImageByID(oldImageID, runtime.Image, client) + } + if deleteImageID != "" { + deleteImageByID(deleteImageID, runtime.Image, client) + } + handlePHPDir(*runtime) + if out, err := compose.DownAndUp(composePath); err != nil { + runtime.Status = constant.StatusStartErr + runtime.Message = out + return + } + runtime.Status = constant.StatusRunning + _ = runtimeRepo.Save(runtime) +} + +func handleParams(create request.RuntimeCreate, projectDir string) (composeContent []byte, envContent []byte, forms []byte, err error) { + fileOp := files.NewFileOp() + composeContent, err = fileOp.GetContent(path.Join(projectDir, "docker-compose.yml")) + if err != nil { + return + } + envPath := path.Join(projectDir, ".env") + if !fileOp.Stat(envPath) { + _ = fileOp.CreateFile(envPath) + } + env, err := gotenv.Read(envPath) + if err != nil { + return + } + for k := range env { + if strings.HasPrefix(k, "CONTAINER_PORT_") || strings.HasPrefix(k, "HOST_PORT_") || strings.HasPrefix(k, "HOST_IP_") || strings.Contains(k, "APP_PORT") { + delete(env, k) + } + } + switch create.Type { + case constant.RuntimePHP: + create.Params["IMAGE_NAME"] = create.Image + var fromYml []byte + fromYml, err = fileOp.GetContent(path.Join(projectDir, "data.yml")) + if err != nil { + return + } + var data dto.PHPForm + err = yaml.Unmarshal(fromYml, &data) + if err != nil { + return + } + formFields := data.AdditionalProperties.FormFields + forms, err = json.MarshalIndent(map[string]interface{}{ + "formFields": formFields, + }, "", " ") + if err != nil { + return + } + if extends, ok := create.Params["PHP_EXTENSIONS"]; ok { + if extendsArray, ok := extends.([]interface{}); ok { + strArray := make([]string, len(extendsArray)) + for i, v := range extendsArray { + strArray[i] = strings.ToLower(fmt.Sprintf("%v", v)) + } + create.Params["PHP_EXTENSIONS"] = strings.Join(strArray, ",") + } + } + create.Params["CONTAINER_PACKAGE_URL"] = create.Source + siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR")) + if siteDir.Value == "" { + siteDir.Value = path.Join(global.Dir.BaseDir, "1panel", "www") + } + create.Params["PANEL_WEBSITE_DIR"] = siteDir.Value + composeContent, err = handleEnvironments(composeContent, create, projectDir) + if err != nil { + return + } + case constant.RuntimeNode: + create.Params["CODE_DIR"] = create.CodeDir + create.Params["NODE_VERSION"] = create.Version + if create.NodeConfig.Install { + create.Params["RUN_INSTALL"] = "1" + } else { + create.Params["RUN_INSTALL"] = "0" + } + create.Params["CONTAINER_PACKAGE_URL"] = create.Source + composeContent, err = handleCompose(env, composeContent, create, projectDir) + if err != nil { + return + } + case constant.RuntimeJava: + create.Params["CODE_DIR"] = create.CodeDir + create.Params["JAVA_VERSION"] = create.Version + composeContent, err = handleCompose(env, composeContent, create, projectDir) + if err != nil { + return + } + case constant.RuntimeGo: + create.Params["CODE_DIR"] = create.CodeDir + create.Params["GO_VERSION"] = create.Version + composeContent, err = handleCompose(env, composeContent, create, projectDir) + if err != nil { + return + } + case constant.RuntimePython: + create.Params["CODE_DIR"] = create.CodeDir + create.Params["PYTHON_VERSION"] = create.Version + composeContent, err = handleCompose(env, composeContent, create, projectDir) + if err != nil { + return + } + case constant.RuntimeDotNet: + create.Params["CODE_DIR"] = create.CodeDir + create.Params["DOTNET_VERSION"] = create.Version + composeContent, err = handleCompose(env, composeContent, create, projectDir) + if err != nil { + return + } + } + + newMap := make(map[string]string) + handleMap(create.Params, newMap) + maps.Copy(env, newMap) + + envStr, err := gotenv.Marshal(env) + if err != nil { + return + } + if err = gotenv.Write(env, envPath); err != nil { + return + } + envContent = []byte(envStr) + return +} + +func handleEnvironments(composeContent []byte, create request.RuntimeCreate, projectDir string) (composeByte []byte, err error) { + composeMap := make(map[string]interface{}) + if err = yaml.Unmarshal(composeContent, &composeMap); err != nil { + return + } + services, serviceValid := composeMap["services"].(map[string]interface{}) + if !serviceValid { + err = buserr.New("ErrFileParse") + return + } + serviceName := "" + serviceValue := make(map[string]interface{}) + for name, service := range services { + serviceName = name + serviceValue = service.(map[string]interface{}) + var environments []interface{} + for _, e := range create.Environments { + environments = append(environments, fmt.Sprintf("%s=%s", e.Key, e.Value)) + } + delete(serviceValue, "environment") + if len(environments) > 0 { + serviceValue["environment"] = environments + } + break + } + services[serviceName] = serviceValue + composeMap["services"] = services + composeByte, err = yaml.Marshal(composeMap) + if err != nil { + return + } + fileOp := files.NewFileOp() + _ = fileOp.SaveFile(path.Join(projectDir, "docker-compose.yml"), string(composeByte), constant.DirPerm) + return +} + +func handleCompose(env gotenv.Env, composeContent []byte, create request.RuntimeCreate, projectDir string) (composeByte []byte, err error) { + existMap := make(map[string]interface{}) + composeMap := make(map[string]interface{}) + if err = yaml.Unmarshal(composeContent, &composeMap); err != nil { + return + } + services, serviceValid := composeMap["services"].(map[string]interface{}) + if !serviceValid { + err = buserr.New("ErrFileParse") + return + } + serviceName := "" + serviceValue := make(map[string]interface{}) + for name, service := range services { + serviceName = name + serviceValue = service.(map[string]interface{}) + delete(serviceValue, "ports") + if len(create.ExposedPorts) > 0 { + var ports []interface{} + for i, port := range create.ExposedPorts { + containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i) + hostPortStr := fmt.Sprintf("HOST_PORT_%d", i) + existMap[containerPortStr] = struct{}{} + existMap[hostPortStr] = struct{}{} + hostIPStr := fmt.Sprintf("HOST_IP_%d", i) + ports = append(ports, fmt.Sprintf("${%s}:${%s}:${%s}", hostIPStr, hostPortStr, containerPortStr)) + create.Params[containerPortStr] = port.ContainerPort + create.Params[hostPortStr] = port.HostPort + create.Params[hostIPStr] = port.HostIP + } + if create.Type == constant.RuntimePHP { + ports = append(ports, "127.0.0.1:${PANEL_APP_PORT_HTTP}:9000") + } + serviceValue["ports"] = ports + } else { + if create.Type == constant.RuntimePHP { + serviceValue["ports"] = []interface{}{"127.0.0.1:${PANEL_APP_PORT_HTTP}:9000"} + } + } + var environments []interface{} + for _, e := range create.Environments { + environments = append(environments, fmt.Sprintf("%s=%s", e.Key, e.Value)) + } + delete(serviceValue, "environment") + if len(environments) > 0 { + serviceValue["environment"] = environments + } + var volumes []interface{} + defaultVolumes := make(map[string]string) + switch create.Type { + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimePython, constant.RuntimeDotNet: + defaultVolumes = constant.RuntimeDefaultVolumes + case constant.RuntimeGo: + defaultVolumes = constant.GoDefaultVolumes + case constant.RuntimePHP: + defaultVolumes = constant.PHPDefaultVolumes + } + for k, v := range defaultVolumes { + volumes = append(volumes, fmt.Sprintf("%s:%s", k, v)) + } + for _, volume := range create.Volumes { + volumes = append(volumes, fmt.Sprintf("%s:%s", volume.Source, volume.Target)) + } + + var extraHosts []interface{} + for _, host := range create.ExtraHosts { + extraHosts = append(extraHosts, fmt.Sprintf("%s:%s", host.Hostname, host.IP)) + } + delete(serviceValue, "extraHosts") + if len(extraHosts) > 0 { + serviceValue["extra_hosts"] = extraHosts + } + + serviceValue["volumes"] = volumes + break + } + for k := range env { + if strings.Contains(k, "CONTAINER_PORT_") || strings.Contains(k, "HOST_PORT_") { + if _, ok := existMap[k]; !ok { + delete(env, k) + } + } + } + + services[serviceName] = serviceValue + composeMap["services"] = services + composeByte, err = yaml.Marshal(composeMap) + if err != nil { + return + } + fileOp := files.NewFileOp() + _ = fileOp.SaveFile(path.Join(projectDir, "docker-compose.yml"), string(composeByte), constant.DirPerm) + return +} + +func checkContainerName(name string) error { + dockerCli, err := docker.NewClient() + if err != nil { + return err + } + defer dockerCli.Close() + names, err := dockerCli.ListContainersByName([]string{name}) + if err != nil { + return err + } + if len(names) > 0 { + return buserr.New("ErrContainerName") + } + return nil +} + +func checkContainerStatus(name string) (string, error) { + dockerCli, err := docker.NewClient() + if err != nil { + return "", err + } + defer dockerCli.Close() + names, err := dockerCli.ListContainersByName([]string{name}) + if err != nil { + return "", err + } + if len(names) > 0 { + return names[0].State, nil + } + return "", nil +} + +func delPHPExtensions(dir, phpExtensionDir, fileName, extName string) { + fileOp := files.NewFileOp() + _ = fileOp.DeleteFile(path.Join(phpExtensionDir, fileName)) + _ = fileOp.DeleteFile(path.Join(dir, "conf", "conf.d", "docker-php-ext-"+extName+".ini")) + _ = removePHPIniExt(path.Join(dir, "conf", "php.ini"), fileName) +} + +func unInstallPHPExtension(runtime *model.Runtime, delExtensions []string) error { + dir := runtime.GetPath() + var phpExtensions []response.SupportExtension + if err := json.Unmarshal(nginx_conf.GetWebsiteFile("php_extensions.json"), &phpExtensions); err != nil { + return err + } + phpVersion := getRuntimeEnv(runtime.Env, "PHP_VERSION") + phpExtensionDir := path.Join(dir, "extensions", getExtensionDir(phpVersion)) + + delMap := make(map[string]struct{}) + for _, del := range delExtensions { + exist := false + for _, ext := range phpExtensions { + if ext.Name == del { + exist = true + delMap[ext.Check] = struct{}{} + delPHPExtensions(dir, phpExtensionDir, ext.File, ext.Name) + break + } + } + if !exist { + delMap[del] = struct{}{} + delPHPExtensions(dir, phpExtensionDir, del+".so", del) + } + } + extensions := getRuntimeEnv(runtime.Env, "PHP_EXTENSIONS") + var ( + oldExts []string + newExts []string + ) + oldExts = strings.Split(extensions, ",") + for _, ext := range oldExts { + if _, ok := delMap[ext]; !ok { + newExts = append(newExts, ext) + } + } + newExtensions := strings.Join(newExts, ",") + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + return err + } + envs["PHP_EXTENSIONS"] = newExtensions + if err = gotenv.Write(envs, runtime.GetEnvPath()); err != nil { + return err + } + envContent, err := gotenv.Marshal(envs) + if err != nil { + return err + } + runtime.Env = envContent + return nil +} + +func removePHPIniExt(filePath, extensionName string) error { + file, err := os.Open(filePath) + if err != nil { + return err + } + defer file.Close() + targetLine1 := `extension="` + extensionName + `"` + targetLine2 := `zend_extension="` + extensionName + `"` + + tempFile, err := os.CreateTemp(path.Dir(filePath), "temp_*.txt") + if err != nil { + return err + } + defer tempFile.Close() + + scanner := bufio.NewScanner(file) + writer := bufio.NewWriter(tempFile) + + for scanner.Scan() { + line := scanner.Text() + if !strings.Contains(line, targetLine1) && !strings.Contains(line, targetLine2) { + _, err := writer.WriteString(line + "\n") + if err != nil { + return err + } + } + } + + if err := scanner.Err(); err != nil { + return err + } + + return os.Rename(tempFile.Name(), filePath) +} + +func restartRuntime(runtime *model.Runtime) (err error) { + if err = runComposeCmdWithLog(constant.RuntimeDown, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + return + } + if err = runComposeCmdWithLog(constant.RuntimeUp, runtime.GetComposePath(), runtime.GetLogPath()); err != nil { + return + } + return +} + +func getDockerComposeEnvironments(yml []byte) ([]request.Environment, error) { + var ( + composeProject docker.ComposeProject + err error + ) + err = yaml.Unmarshal(yml, &composeProject) + if err != nil { + return nil, err + } + var res []request.Environment + for _, service := range composeProject.Services { + for key, value := range service.Environment.Variables { + res = append(res, request.Environment{ + Key: key, + Value: value, + }) + } + } + return res, nil +} + +func getDockerComposeVolumes(yml []byte) ([]request.Volume, error) { + var ( + composeProject docker.ComposeProject + err error + ) + err = yaml.Unmarshal(yml, &composeProject) + if err != nil { + return nil, err + } + var res []request.Volume + for _, service := range composeProject.Services { + for _, volume := range service.Volumes { + envArray := strings.Split(volume, ":") + source := envArray[0] + target := "" + if len(envArray) > 1 { + target = envArray[1] + } + res = append(res, request.Volume{ + Source: source, + Target: target, + }) + } + } + return res, nil +} + +func getDockerComposeExtraHosts(yml []byte) ([]request.ExtraHost, error) { + var ( + composeProject docker.ComposeProject + err error + ) + err = yaml.Unmarshal(yml, &composeProject) + if err != nil { + return nil, err + } + var res []request.ExtraHost + for _, service := range composeProject.Services { + for _, extraHosts := range service.ExtraHosts { + envArray := strings.Split(extraHosts, ":") + source := envArray[0] + target := "" + if len(envArray) > 1 { + target = envArray[1] + } + res = append(res, request.ExtraHost{ + Hostname: source, + IP: target, + }) + } + } + return res, nil +} + +func checkRuntimePortExist(port int, scanPort bool, runtimeID uint) error { + errMap := make(map[string]interface{}) + errMap["port"] = port + appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port)) + if appInstall.ID > 0 { + errMap["type"] = i18n.GetMsgByKey("TYPE_APP") + errMap["name"] = appInstall.Name + return buserr.WithMap("ErrPortExist", errMap, nil) + } + opts := []repo.DBOption{runtimeRepo.WithPort(port)} + if runtimeID > 0 { + opts = append(opts, repo.WithByNOTID(runtimeID)) + } + runtime, _ := runtimeRepo.GetFirst(context.Background(), opts...) + if runtime != nil { + errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME") + errMap["name"] = runtime.Name + return buserr.WithMap("ErrPortExist", errMap, nil) + } + domain, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithPort(port)) + if domain.ID > 0 { + errMap["type"] = i18n.GetMsgByKey("TYPE_DOMAIN") + errMap["name"] = domain.Domain + return buserr.WithMap("ErrPortExist", errMap, nil) + } + if scanPort && common.ScanPort(port) { + return buserr.WithDetail("ErrPortInUsed", port, nil) + } + return nil +} + +func getExtensionDir(version string) string { + if strings.HasPrefix(version, "8.4") { + return "no-debug-non-zts-20240924" + } + if strings.HasPrefix(version, "8.3") { + return "no-debug-non-zts-20230831" + } + if strings.HasPrefix(version, "8.2") { + return "no-debug-non-zts-20220829" + } + if strings.HasPrefix(version, "8.1") { + return "no-debug-non-zts-20210902" + } + if strings.HasPrefix(version, "8.0") { + return "no-debug-non-zts-20200930" + } + if strings.HasPrefix(version, "7.4") { + return "no-debug-non-zts-20190902" + } + if strings.HasPrefix(version, "7.3") { + return "no-debug-non-zts-20180731" + } + if strings.HasPrefix(version, "7.2") { + return "no-debug-non-zts-20170718" + } + if strings.HasPrefix(version, "7.1") { + return "no-debug-non-zts-20160303" + } + if strings.HasPrefix(version, "7.0") { + return "no-debug-non-zts-20151012" + } + if strings.HasPrefix(version, "5.6") { + return "no-debug-non-zts-20131226" + } + return "" +} + +func RestartPHPRuntime() { + runtimes, err := runtimeRepo.List(repo.WithByType(constant.RuntimePHP)) + if err != nil { + return + } + websiteDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR") + for _, runtime := range runtimes { + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + global.LOG.Warningf("restart php runtime failed %v", err) + continue + } + envs["PANEL_WEBSITE_DIR"] = websiteDir + if err = gotenv.Write(envs, runtime.GetEnvPath()); err != nil { + global.LOG.Warningf("restart php runtime failed %v", err) + continue + } + go func() { + _ = restartRuntime(&runtime) + }() + } +} + +func handleRuntimeDTO(res *response.RuntimeDTO, runtime model.Runtime) error { + res.Params = make(map[string]interface{}) + envs, err := gotenv.Unmarshal(runtime.Env) + if err != nil { + return err + } + for k, v := range envs { + if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") { + if strings.Contains(k, "CONTAINER_PORT") { + matches := re.GetRegex(re.TrailingDigitsPattern).FindStringSubmatch(k) + if len(matches) < 2 { + return fmt.Errorf("invalid container port key: %s", k) + } + containerPort, err := strconv.Atoi(v) + if err != nil { + return err + } + hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])]) + if err != nil { + return err + } + hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])] + res.ExposedPorts = append(res.ExposedPorts, request.ExposedPort{ + ContainerPort: containerPort, + HostPort: hostPort, + HostIP: hostIP, + }) + } + } else { + res.Params[k] = v + } + } + if v, ok := envs["CONTAINER_PACKAGE_URL"]; ok { + res.Source = v + } + composeByte, err := files.NewFileOp().GetContent(runtime.GetComposePath()) + if err != nil { + return err + } + res.Environments, err = getDockerComposeEnvironments(composeByte) + if err != nil { + return err + } + volumes, err := getDockerComposeVolumes(composeByte) + if err != nil { + return err + } + + res.ExtraHosts, err = getDockerComposeExtraHosts(composeByte) + if err != nil { + return err + } + + defaultVolumes := make(map[string]string) + switch runtime.Type { + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimePython, constant.RuntimeDotNet: + defaultVolumes = constant.RuntimeDefaultVolumes + case constant.RuntimeGo: + defaultVolumes = constant.GoDefaultVolumes + case constant.RuntimePHP: + defaultVolumes = constant.PHPDefaultVolumes + } + for _, volume := range volumes { + exist := false + for key, value := range defaultVolumes { + if key == volume.Source && value == volume.Target { + exist = true + break + } + } + if !exist { + res.Volumes = append(res.Volumes, volume) + } + } + return nil +} + +func handlePHPDir(runtime model.Runtime) { + fileOp := files.NewFileOp() + dirs := []string{ + path.Join(runtime.GetPath(), "conf"), + path.Join(runtime.GetPath(), "extensions"), + path.Join(runtime.GetPath(), "composer"), + path.Join(runtime.GetPath(), "log"), + } + for _, dir := range dirs { + _ = fileOp.ChmodR(dir, 0755, true) + _ = fileOp.ChownR(dir, strconv.Itoa(1000), strconv.Itoa(1000), true) + } +} + +func HandleOldPHPRuntime() { + runtimes, _ := runtimeRepo.List(repo.WithByType(constant.RuntimePHP)) + if len(runtimes) == 0 { + return + } + fileOp := files.NewFileOp() + for _, runtime := range runtimes { + composePtah := runtime.GetComposePath() + composeBytes, _ := fileOp.GetContent(composePtah) + composeContent := strings.ReplaceAll(string(composeBytes), "./conf:/usr/local/etc/php", "./conf/php.ini:/usr/local/etc/php/php.ini") + composeContent = strings.ReplaceAll(composeContent, "./conf/php-fpm.conf:/usr/local/etc/php-fpm.d/www.conf", "./conf/php-fpm.conf:/usr/local/etc/php-fpm.conf") + composeContent = strings.ReplaceAll(composeContent, "./extensions:${EXTENSION_DIR}", "./extensions:/usr/local/lib/php/extensions") + _ = fileOp.WriteFile(composePtah, strings.NewReader(composeContent), constant.DirPerm) + _ = fileOp.WriteFile(runtime.GetFPMPath(), bytes.NewReader(nginx_conf.GetWebsiteFile("php-fpm.conf")), constant.DirPerm) + supervisorConfigPath := path.Join(runtime.GetPath(), "supervisor", "supervisor.d", "php-fpm.ini") + supervisorConfigBytes, _ := fileOp.GetContent(supervisorConfigPath) + if !strings.Contains(string(supervisorConfigBytes), "nodaemonize") { + newConfigContent := strings.ReplaceAll(string(supervisorConfigBytes), "command=php-fpm", "command=php-fpm --nodaemonize") + _ = fileOp.WriteFile(supervisorConfigPath, bytes.NewReader([]byte(newConfigContent)), constant.DirPerm) + } + go func() { + _ = restartRuntime(&runtime) + }() + } +} + +func getOperation(operate, pkgManager string) string { + operations := map[string][2]string{ + constant.RuntimeInstall: {"install", "add"}, + constant.RuntimeUninstall: {"uninstall", "remove"}, + constant.RuntimeUpdate: {"update", "upgrade"}, + } + + ops := operations[operate] + if pkgManager == constant.RuntimeNpm { + return ops[0] + } + return ops[1] +} + +func handleRuntimeDetailID(runtime model.Runtime) (uint, uint) { + app, _ := appRepo.GetFirst(appRepo.WithKey(runtime.Type)) + if app.ID == 0 { + return 0, 0 + } + appDetail, _ := appDetailRepo.GetFirst(appDetailRepo.WithVersion(runtime.Version), appDetailRepo.WithAppId(app.ID)) + if appDetail.ID == 0 { + return 0, 0 + } + runtime.AppDetailID = appDetail.ID + _ = runtimeRepo.Save(&runtime) + return app.ID, appDetail.ID +} diff --git a/agent/app/service/setting.go b/agent/app/service/setting.go new file mode 100644 index 0000000..cc45f67 --- /dev/null +++ b/agent/app/service/setting.go @@ -0,0 +1,208 @@ +package service + +import ( + "encoding/base64" + "encoding/json" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/ssh" + "github.com/jinzhu/copier" +) + +type SettingService struct{} + +type ISettingService interface { + GetSettingInfo() (*dto.SettingInfo, error) + Update(key, value string) error + + TestConnByInfo(req dto.SSHConnData) bool + SaveConnInfo(req dto.SSHConnData) error + SetDefaultIsConn(req dto.SSHDefaultConn) error + GetSystemProxy() (*dto.SystemProxy, error) + GetLocalConn() dto.SSHConnData + GetSettingByKey(key string) string + + SaveDescription(req dto.CommonDescription) error +} + +func NewISettingService() ISettingService { + return &SettingService{} +} + +func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) { + setting, err := settingRepo.GetList() + if err != nil { + return nil, buserr.New("ErrRecordNotFound") + } + settingMap := make(map[string]string) + for _, set := range setting { + settingMap[set.Key] = set.Value + } + var info dto.SettingInfo + arr, err := json.Marshal(settingMap) + if err != nil { + return nil, err + } + if err := json.Unmarshal(arr, &info); err != nil { + return nil, err + } + + info.LocalTime = time.Now().Format("2006-01-02 15:04:05 MST -0700") + return &info, err +} + +func (u *SettingService) Update(key, value string) error { + return settingRepo.UpdateOrCreate(key, value) +} + +func (u *SettingService) TestConnByInfo(req dto.SSHConnData) bool { + if req.AuthMode == "password" && len(req.Password) != 0 { + password, err := base64.StdEncoding.DecodeString(req.Password) + if err != nil { + return false + } + req.Password = string(password) + } + if req.AuthMode == "key" && len(req.PrivateKey) != 0 { + privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) + if err != nil { + return false + } + req.PrivateKey = string(privateKey) + } + + var connInfo ssh.ConnInfo + _ = copier.Copy(&connInfo, &req) + connInfo.PrivateKey = []byte(req.PrivateKey) + if len(req.PassPhrase) != 0 { + connInfo.PassPhrase = []byte(req.PassPhrase) + } + client, err := ssh.NewClient(connInfo) + if err != nil { + return false + } + defer client.Close() + return true +} + +func (u *SettingService) SaveConnInfo(req dto.SSHConnData) error { + if req.AuthMode == "password" && len(req.Password) != 0 { + password, err := base64.StdEncoding.DecodeString(req.Password) + if err != nil { + return err + } + req.Password = string(password) + } + if req.AuthMode == "key" && len(req.PrivateKey) != 0 { + privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) + if err != nil { + return err + } + req.PrivateKey = string(privateKey) + } + + var connInfo ssh.ConnInfo + _ = copier.Copy(&connInfo, &req) + connInfo.PrivateKey = []byte(req.PrivateKey) + if len(req.PassPhrase) != 0 { + connInfo.PassPhrase = []byte(req.PassPhrase) + } + client, err := ssh.NewClient(connInfo) + if err != nil { + return err + } + defer client.Close() + + var connItem model.LocalConnInfo + _ = copier.Copy(&connItem, &req) + localConn, _ := json.Marshal(&connItem) + connAfterEncrypt, _ := encrypt.StringEncrypt(string(localConn)) + _ = settingRepo.Update("LocalSSHConn", connAfterEncrypt) + return nil +} + +func (u *SettingService) SetDefaultIsConn(req dto.SSHDefaultConn) error { + if req.DefaultConn == constant.StatusDisable && req.WithReset { + if err := settingRepo.Update("LocalSSHConn", ""); err != nil { + return err + } + } + return settingRepo.Update("LocalSSHConnShow", req.DefaultConn) +} + +func (u *SettingService) GetSystemProxy() (*dto.SystemProxy, error) { + systemProxy := dto.SystemProxy{} + systemProxy.Type, _ = settingRepo.GetValueByKey("ProxyType") + systemProxy.URL, _ = settingRepo.GetValueByKey("ProxyUrl") + systemProxy.Port, _ = settingRepo.GetValueByKey("ProxyPort") + systemProxy.User, _ = settingRepo.GetValueByKey("ProxyUser") + passwd, _ := settingRepo.GetValueByKey("ProxyPasswd") + systemProxy.Password, _ = encrypt.StringDecrypt(passwd) + return &systemProxy, nil +} + +func (u *SettingService) GetLocalConn() dto.SSHConnData { + var data dto.SSHConnData + data.LocalSSHConnShow, _ = settingRepo.GetValueByKey("LocalSSHConnShow") + connItem, _ := settingRepo.GetValueByKey("LocalSSHConn") + if len(connItem) == 0 { + return data + } + connInfoInDB, _ := encrypt.StringDecrypt(connItem) + data.LocalSSHConnShow, _ = settingRepo.GetValueByKey("LocalSSHConnShow") + if err := json.Unmarshal([]byte(connInfoInDB), &data); err != nil { + return data + } + if len(data.Password) != 0 { + data.Password = base64.StdEncoding.EncodeToString([]byte(data.Password)) + } + if len(data.PrivateKey) != 0 { + data.PrivateKey = base64.StdEncoding.EncodeToString([]byte(data.PrivateKey)) + } + if len(data.PassPhrase) != 0 { + data.PassPhrase = base64.StdEncoding.EncodeToString([]byte(data.PassPhrase)) + } + return data +} + +func (u *SettingService) GetSettingByKey(key string) string { + switch key { + case "LocalSSHConn": + value, _ := settingRepo.GetValueByKey(key) + if len(value) == 0 { + return "" + } + itemStr, _ := encrypt.StringDecrypt(value) + return itemStr + default: + value, _ := settingRepo.GetValueByKey(key) + return value + } +} + +func (u *SettingService) SaveDescription(req dto.CommonDescription) error { + if len(req.Description) == 0 && !req.IsPinned { + _ = settingRepo.DelDescription(req.ID) + return nil + } + data, _ := settingRepo.GetDescription(settingRepo.WithByDescriptionID(req.ID), repo.WithByType(req.Type), repo.WithByDetailType(req.DetailType)) + if data.ID == "" { + if err := copier.Copy(&data, &req); err != nil { + return err + } + return settingRepo.CreateDescription(&data) + } + valMap := make(map[string]interface{}) + valMap["type"] = req.Type + valMap["detail_type"] = req.DetailType + valMap["is_pinned"] = req.IsPinned + valMap["description"] = req.Description + + return settingRepo.UpdateDescription(data.ID, valMap) +} diff --git a/agent/app/service/snapshot.go b/agent/app/service/snapshot.go new file mode 100644 index 0000000..6d3db1d --- /dev/null +++ b/agent/app/service/snapshot.go @@ -0,0 +1,442 @@ +package service + +import ( + "context" + "fmt" + "os" + "path" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + fileUtils "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/docker/docker/api/types/image" + "github.com/google/uuid" + "github.com/jinzhu/copier" + "github.com/shirou/gopsutil/v4/host" +) + +type SnapshotService struct { + OriginalPath string +} + +type ISnapshotService interface { + SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error) + LoadSnapshotData() (dto.SnapshotData, error) + SnapshotCreate(parentTask *task.Task, req dto.SnapshotCreate, jobID, retry uint) error + SnapshotReCreate(id uint) error + SnapshotRecover(req dto.SnapshotRecover) error + SnapshotRollback(req dto.SnapshotRecover) error + SnapshotImport(req dto.SnapshotImport) error + Delete(req dto.SnapshotBatchDelete) error + + UpdateDescription(req dto.UpdateDescription) error +} + +func NewISnapshotService() ISnapshotService { + return &SnapshotService{} +} + +func (u *SnapshotService) SearchWithPage(req dto.PageSnapshot) (int64, interface{}, error) { + total, records, err := snapshotRepo.Page(req.Page, req.PageSize, repo.WithByLikeName(req.Info), repo.WithOrderRuleBy(req.OrderBy, req.Order)) + if err != nil { + return 0, nil, err + } + var datas []dto.SnapshotInfo + for i := 0; i < len(records); i++ { + var item dto.SnapshotInfo + if err := copier.Copy(&item, &records[i]); err != nil { + return 0, nil, err + } + item.SourceAccounts, item.DownloadAccount, _ = loadBackupNamesByID(records[i].SourceAccountIDs, records[i].DownloadAccountID) + datas = append(datas, item) + } + return total, datas, err +} + +func (u *SnapshotService) SnapshotImport(req dto.SnapshotImport) error { + if len(req.Names) == 0 { + return fmt.Errorf("incorrect snapshot request body: %v", req.Names) + } + + for _, snapName := range req.Names { + if !checkSnapshotIsOk(snapName) { + return fmt.Errorf("incorrect snapshot name format of %s", snapName) + } + snap, _ := snapshotRepo.Get(repo.WithByName(strings.ReplaceAll(snapName, ".tar.gz", ""))) + if snap.ID != 0 { + return buserr.New("ErrRecordExist") + } + } + for _, snap := range req.Names { + shortName := strings.ReplaceAll(snap, "snapshot-", "") + shortName = strings.ReplaceAll(shortName, "1panel-", "") + shortName = strings.ReplaceAll(shortName, "core-", "") + shortName = strings.ReplaceAll(shortName, "agent-", "") + nameItems := strings.Split(shortName, "-linux") + if strings.HasSuffix(snap, ".tar.gz") { + snap = strings.ReplaceAll(snap, ".tar.gz", "") + } + itemSnap := model.Snapshot{ + Name: snap, + SourceAccountIDs: fmt.Sprintf("%v", req.BackupAccountID), + DownloadAccountID: req.BackupAccountID, + Version: nameItems[0], + Description: req.Description, + Status: constant.StatusSuccess, + } + if err := snapshotRepo.Create(&itemSnap); err != nil { + return err + } + } + return nil +} + +func (u *SnapshotService) LoadSnapshotData() (dto.SnapshotData, error) { + var ( + data dto.SnapshotData + err error + ) + fileOp := fileUtils.NewFileOp() + data.AppData, err = loadApps(fileOp) + if err != nil { + return data, err + } + data.WithDockerConf = true + data.PanelData, err = loadPanelFile(fileOp) + if err != nil { + return data, err + } + itemBackups, err := loadFile(global.Dir.LocalBackupDir, 8, fileOp) + if err != nil { + return data, err + } + i := 0 + for _, item := range itemBackups { + if item.Label != "app" && item.Label != "system_snapshot" && item.Label != "tmp" { + itemBackups[i] = item + i++ + } + } + data.BackupData = itemBackups[:i] + + return data, nil +} + +func (u *SnapshotService) UpdateDescription(req dto.UpdateDescription) error { + return snapshotRepo.Update(req.ID, map[string]interface{}{"description": req.Description}) +} + +type SnapshotJson struct { + BaseDir string `json:"baseDir"` + OperestyDir string `json:"operestyDir"` + BackupDataDir string `json:"backupDataDir"` + Size uint64 `json:"size"` +} + +func (u *SnapshotService) Delete(req dto.SnapshotBatchDelete) error { + snaps, _ := snapshotRepo.GetList(repo.WithByIDs(req.Ids)) + for _, snap := range snaps { + if req.DeleteWithFile { + accounts := NewBackupClientMap(strings.Split(snap.SourceAccountIDs, ",")) + for _, item := range accounts { + global.LOG.Debugf("remove snapshot file %s.tar.gz from %s", snap.Name, item.name) + if !item.isOk { + global.LOG.Errorf("remove snapshot file %s.tar.gz from %s failed, err: %s", snap.Name, item.name, item.message) + continue + } + _, _ = item.client.Delete(path.Join(item.backupPath, "system_snapshot", snap.Name+".tar.gz")) + } + _ = backupRepo.DeleteRecord(context.Background(), repo.WithByType("snapshot"), backupRepo.WithByFileName(snap.Name+".tar.gz")) + } + _ = os.Remove(path.Join(global.Dir.LocalBackupDir, "tmp/system", snap.Name+".tar.gz")) + + if err := snapshotRepo.Delete(repo.WithByID(snap.ID)); err != nil { + return err + } + } + return nil +} + +func hasOs(name string) bool { + return strings.Contains(name, "amd64") || + strings.Contains(name, "arm64") || + strings.Contains(name, "armv7") || + strings.Contains(name, "ppc64le") || + strings.Contains(name, "s390x") || + strings.Contains(name, "riscv64") +} + +func loadOs() string { + hostInfo, _ := host.Info() + switch hostInfo.KernelArch { + case "x86_64": + return "amd64" + case "armv7l": + return "armv7" + default: + return hostInfo.KernelArch + } +} + +func loadApps(fileOp fileUtils.FileOp) ([]dto.DataTree, error) { + var data []dto.DataTree + apps, err := appInstallRepo.ListBy(context.Background()) + if err != nil { + return data, err + } + openrestyID := 0 + for _, app := range apps { + if app.App.Key == constant.AppOpenresty { + openrestyID = int(app.ID) + } + } + websites, err := websiteRepo.List() + if err != nil { + return data, err + } + appRelationMap := make(map[uint]uint) + for _, website := range websites { + if website.Type == constant.Deployment && website.AppInstallID != 0 { + appRelationMap[uint(openrestyID)] = website.AppInstallID + } + } + appRelations, err := appInstallResourceRepo.GetBy() + if err != nil { + return data, err + } + for _, relation := range appRelations { + appRelationMap[uint(relation.AppInstallId)] = relation.LinkId + } + appMap := make(map[uint]string) + for _, app := range apps { + appMap[app.ID] = fmt.Sprintf("%s-%s", app.App.Key, app.Name) + } + + appTreeMap := make(map[string]dto.DataTree) + for _, app := range apps { + itemApp := dto.DataTree{ + ID: uuid.NewString(), + Label: fmt.Sprintf("%s - %s", app.App.Name, app.Name), + Key: app.App.Key, + Name: app.Name, + IsLocal: app.App.Resource == "local", + } + appPath := path.Join(global.Dir.DataDir, "apps", app.App.Key, app.Name) + if itemApp.IsLocal { + appPath = path.Join(global.Dir.AppDir, "local", strings.TrimPrefix(app.App.Key, "local"), app.Name) + } + itemAppData := dto.DataTree{ID: uuid.NewString(), Label: "appData", Key: app.App.Key, Name: app.Name, IsCheck: true, Path: appPath} + if app.App.Key == constant.AppOpenresty && len(websites) != 0 { + itemAppData.IsDisable = true + } + if val, ok := appRelationMap[app.ID]; ok { + itemAppData.RelationItemID = appMap[val] + } + sizeItem, err := fileOp.GetDirSize(appPath) + if err == nil { + itemAppData.Size = uint64(sizeItem) + } + itemApp.Size += itemAppData.Size + data = append(data, itemApp) + appTreeMap[fmt.Sprintf("%s-%s", itemApp.Key, itemApp.Name)] = itemAppData + } + + for key, val := range appTreeMap { + if valRelation, ok := appTreeMap[val.RelationItemID]; ok { + valRelation.IsDisable = true + appTreeMap[val.RelationItemID] = valRelation + + val.RelationItemID = valRelation.ID + appTreeMap[key] = val + } + } + for i := 0; i < len(data); i++ { + if val, ok := appTreeMap[fmt.Sprintf("%s-%s", data[i].Key, data[i].Name)]; ok { + data[i].Children = append(data[i].Children, val) + } + } + data = loadAppBackup(data, fileOp) + data = loadAppImage(data) + return data, nil +} +func loadAppBackup(list []dto.DataTree, fileOp fileUtils.FileOp) []dto.DataTree { + for i := 0; i < len(list); i++ { + appBackupPath := path.Join(global.Dir.LocalBackupDir, "app", list[i].Key, list[i].Name) + itemAppBackupTree, err := loadFile(appBackupPath, 8, fileOp) + itemAppBackup := dto.DataTree{ID: uuid.NewString(), Label: "appBackup", IsCheck: true, Children: itemAppBackupTree, Path: appBackupPath} + if err == nil { + backupSizeItem, err := fileOp.GetDirSize(appBackupPath) + if err == nil { + itemAppBackup.Size = uint64(backupSizeItem) + list[i].Size += itemAppBackup.Size + } + list[i].Children = append(list[i].Children, itemAppBackup) + } + } + return list +} +func loadAppImage(list []dto.DataTree) []dto.DataTree { + client, err := docker.NewDockerClient() + if err != nil { + global.LOG.Errorf("new docker client failed, err: %v", err) + return list + } + defer client.Close() + imageList, err := client.ImageList(context.Background(), image.ListOptions{}) + if err != nil { + global.LOG.Errorf("load image list failed, err: %v", err) + return list + } + + for i := 0; i < len(list); i++ { + itemAppImage := dto.DataTree{ID: uuid.NewString(), Label: "appImage"} + appPath := path.Join(global.Dir.DataDir, "apps", list[i].Key, list[i].Name) + if list[i].IsLocal { + appPath = path.Join(global.Dir.AppDir, "local", strings.TrimPrefix(list[i].Key, "local"), list[i].Name) + } + stdout, err := cmd.RunDefaultWithStdoutBashCf("cat %s | grep image: ", path.Join(appPath, "docker-compose.yml")) + if err != nil { + list[i].Children = append(list[i].Children, itemAppImage) + continue + } + itemAppImage.Name = strings.ReplaceAll(strings.ReplaceAll(strings.TrimSpace(stdout), "\n", ""), "image: ", "") + for _, imageItem := range imageList { + for _, tag := range imageItem.RepoTags { + if tag == itemAppImage.Name { + itemAppImage.Size = uint64(imageItem.Size) + break + } + } + } + list[i].Children = append(list[i].Children, itemAppImage) + } + return list +} + +func loadPanelFile(fileOp fileUtils.FileOp) ([]dto.DataTree, error) { + var data []dto.DataTree + snapFiles, err := os.ReadDir(global.Dir.DataDir) + if err != nil { + return data, err + } + for _, fileItem := range snapFiles { + itemData := dto.DataTree{ + ID: uuid.NewString(), + Label: fileItem.Name(), + IsCheck: true, + Path: path.Join(global.Dir.DataDir, fileItem.Name()), + } + switch itemData.Label { + case "runtime", "docker", "task", "geo", "secret", "uploads": + itemData.IsDisable = true + case "clamav", "download": + panelPath := path.Join(global.Dir.DataDir, itemData.Label) + itemData.Children, _ = loadFile(panelPath, 3, fileOp) + case "agent": + panelPath := path.Join(global.Dir.DataDir, itemData.Label) + itemData.Children, _ = loadFile(panelPath, 2, fileOp) + itemData.IsCheck = false + for i := 0; i < len(itemData.Children); i++ { + if itemData.Children[i].Label != "package" { + itemData.Children[i].IsDisable = true + } else { + itemData.Size = -itemData.Children[i].Size + itemData.Children[i].IsCheck = false + for j := 0; j < len(itemData.Children[i].Children); j++ { + itemData.Children[i].Children[j].IsCheck = false + } + } + } + case "apps", "backup", "log", "db", "tmp": + continue + } + if fileItem.IsDir() { + sizeItem, err := fileOp.GetDirSize(path.Join(global.Dir.DataDir, itemData.Label)) + if err != nil { + continue + } + itemData.Size = uint64(int64(sizeItem) + int64(itemData.Size)) + } else { + fileInfo, err := fileItem.Info() + if err != nil { + continue + } + itemData.Size = uint64(fileInfo.Size()) + } + if itemData.IsCheck && itemData.Size == 0 { + itemData.IsCheck = false + itemData.IsDisable = true + } + + data = append(data, itemData) + } + + openrestySite, _ := settingRepo.GetValueByKey("WEBSITE_DIR") + if len(openrestySite) != 0 && !strings.Contains(openrestySite, global.Dir.DataDir) { + sizeItem, _ := fileOp.GetDirSize(openrestySite) + data = append(data, dto.DataTree{ + ID: uuid.NewString(), + Label: "www", + IsCheck: true, + IsDisable: true, + Path: openrestySite, + Size: uint64(sizeItem), + }) + } + + return data, nil +} + +func loadFile(pathItem string, index int, fileOp fileUtils.FileOp) ([]dto.DataTree, error) { + var data []dto.DataTree + snapFiles, err := os.ReadDir(pathItem) + if err != nil { + return data, err + } + for _, fileItem := range snapFiles { + itemData := dto.DataTree{ + ID: uuid.NewString(), + Label: fileItem.Name(), + Name: fileItem.Name(), + Path: path.Join(pathItem, fileItem.Name()), + IsCheck: true, + } + if fileItem.IsDir() { + sizeItem, err := fileOp.GetDirSize(path.Join(pathItem, itemData.Label)) + if err != nil { + continue + } + itemData.Size = uint64(sizeItem) + if index > 1 { + itemData.Children, _ = loadFile(path.Join(pathItem, itemData.Label), index-1, fileOp) + } + } else { + fileInfo, err := fileItem.Info() + if err != nil { + continue + } + itemData.Size = uint64(fileInfo.Size()) + } + data = append(data, itemData) + } + return data, nil +} + +func checkSnapshotIsOk(name string) bool { + names := []string{"1panel-core-v2.", "1panel-agent-v2.", "1panel-v2.", "snapshot-1panel-core-v2.", "snapshot-1panel-agent-v2."} + for _, item := range names { + if strings.HasPrefix(name, item) { + return true + } + } + return false +} diff --git a/agent/app/service/snapshot_create.go b/agent/app/service/snapshot_create.go new file mode 100644 index 0000000..8e9073f --- /dev/null +++ b/agent/app/service/snapshot_create.go @@ -0,0 +1,569 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "os" + "path" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/docker/docker/api/types/image" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/copier" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "gorm.io/gorm" +) + +func (u *SnapshotService) SnapshotCreate(parentTask *task.Task, req dto.SnapshotCreate, jobID, retry uint) error { + versionItem, _ := settingRepo.Get(settingRepo.WithByKey("SystemVersion")) + + scope := "core" + if !global.IsMaster { + scope = "agent" + } + if jobID == 0 { + req.Name = fmt.Sprintf("1panel-%s-%s-linux-%s-%s", scope, versionItem.Value, loadOs(), time.Now().Format(constant.DateTimeSlimLayout)) + } + appItem, _ := json.Marshal(req.AppData) + panelItem, _ := json.Marshal(req.PanelData) + backupItem, _ := json.Marshal(req.BackupData) + snap := model.Snapshot{ + Name: req.Name, + TaskID: req.TaskID, + Secret: req.Secret, + Description: req.Description, + SourceAccountIDs: req.SourceAccountIDs, + DownloadAccountID: req.DownloadAccountID, + + AppData: string(appItem), + PanelData: string(panelItem), + BackupData: string(backupItem), + WithDockerConf: req.WithDockerConf, + WithMonitorData: req.WithMonitorData, + WithLoginLog: req.WithLoginLog, + WithOperationLog: req.WithOperationLog, + WithTaskLog: req.WithTaskLog, + WithSystemLog: req.WithSystemLog, + IgnoreFiles: strings.Join(req.IgnoreFiles, ","), + + Version: versionItem.Value, + Status: constant.StatusWaiting, + Timeout: req.Timeout, + } + if err := snapshotRepo.Create(&snap); err != nil { + global.LOG.Errorf("create snapshot record to db failed, err: %v", err) + return err + } + + req.ID = snap.ID + var err error + taskItem := parentTask + if parentTask == nil { + taskItem, err = task.NewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeSnapshot, req.TaskID, req.ID) + if err != nil { + global.LOG.Errorf("new task for create snapshot failed, err: %v", err) + return err + } + } + if jobID == 0 { + go func() { + _ = handleSnapshot(req, taskItem, jobID, 3, snap.Timeout) + }() + return nil + } + + return handleSnapshot(req, taskItem, jobID, retry, snap.Timeout) +} + +func (u *SnapshotService) SnapshotReCreate(id uint) error { + snap, err := snapshotRepo.Get(repo.WithByID(id)) + if err != nil { + return err + } + + var req dto.SnapshotCreate + _ = copier.Copy(&req, snap) + if err := json.Unmarshal([]byte(snap.PanelData), &req.PanelData); err != nil { + return err + } + if err := json.Unmarshal([]byte(snap.AppData), &req.AppData); err != nil { + return err + } + if err := json.Unmarshal([]byte(snap.BackupData), &req.BackupData); err != nil { + return err + } + req.TaskID = snap.TaskID + taskItem, err := task.ReNewTaskWithOps(req.Name, task.TaskCreate, task.TaskScopeSnapshot, req.TaskID, req.ID) + if err != nil { + global.LOG.Errorf("new task for create snapshot failed, err: %v", err) + return err + } + _ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusWaiting, "message": ""}) + go func() { + _ = handleSnapshot(req, taskItem, 0, 3, req.Timeout) + }() + + return nil +} + +func handleSnapshot(req dto.SnapshotCreate, taskItem *task.Task, jobID, retry, timeout uint) error { + rootDir := path.Join(global.Dir.LocalBackupDir, "tmp/system", req.Name) + openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR") + itemHelper := snapHelper{SnapID: req.ID, Task: *taskItem, FileOp: files.NewFileOp(), Ctx: context.Background(), OpenrestyDir: openrestyDir} + baseDir := path.Join(rootDir, "base") + _ = os.MkdirAll(baseDir, os.ModePerm) + taskItem.AddSubTaskWithAliasAndOps( + "SnapDBInfo", + func(t *task.Task) error { + if err := loadDbConn(&itemHelper, rootDir, req); err != nil { + return err + } + _ = itemHelper.snapAgentDB.Where("id = ?", req.ID).Delete(&model.Snapshot{}).Error + if jobID != 0 { + _ = itemHelper.snapAgentDB.Where("id = ?", jobID).Delete(&model.JobRecords{}).Error + } + return nil + }, nil, int(retry), time.Duration(timeout)*time.Second, + ) + + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapBaseInfo" { + taskItem.AddSubTaskWithAliasAndOps( + "SnapBaseInfo", + func(t *task.Task) error { return snapBaseData(itemHelper, baseDir, req.WithDockerConf) }, + nil, int(retry), time.Duration(timeout)*time.Second, + ) + req.InterruptStep = "" + } + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapInstallApp" { + taskItem.AddSubTaskWithAliasAndOps( + "SnapInstallApp", + func(t *task.Task) error { return snapAppImage(itemHelper, req, rootDir) }, + nil, int(retry), time.Duration(timeout)*time.Second, + ) + req.InterruptStep = "" + } + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapLocalBackup" { + taskItem.AddSubTaskWithAliasAndOps( + "SnapLocalBackup", + func(t *task.Task) error { return snapBackupData(itemHelper, req, rootDir) }, + nil, int(retry), time.Duration(timeout)*time.Second, + ) + req.InterruptStep = "" + } + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapPanelData" { + taskItem.AddSubTaskWithAliasAndOps( + "SnapPanelData", + func(t *task.Task) error { return snapPanelData(itemHelper, req, rootDir) }, + nil, int(retry), time.Duration(timeout)*time.Second, + ) + req.InterruptStep = "" + } + + taskItem.AddSubTaskWithAliasAndOps( + "SnapCloseDBConn", + func(t *task.Task) error { + taskItem.Log("---------------------- 6 / 8 ----------------------") + common.CloseDB(itemHelper.snapAgentDB) + common.CloseDB(itemHelper.snapCoreDB) + return nil + }, nil, int(retry), time.Duration(timeout)*time.Second, + ) + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapCompress" { + taskItem.AddSubTaskWithAliasAndOps( + "SnapCompress", + func(t *task.Task) error { return snapCompress(itemHelper, rootDir, req.Secret) }, + nil, int(retry), time.Duration(timeout)*time.Second, + ) + req.InterruptStep = "" + } + if len(req.InterruptStep) == 0 || req.InterruptStep == "SnapUpload" { + taskItem.AddSubTaskWithAliasAndOps( + "SnapUpload", + func(t *task.Task) error { + return snapUpload(itemHelper, req.SourceAccountIDs, req.DownloadAccountID, retry, fmt.Sprintf("%s.tar.gz", rootDir)) + }, nil, int(retry), time.Duration(timeout)*time.Second, + ) + req.InterruptStep = "" + } + if err := taskItem.Execute(); err != nil { + _ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusFailed, "message": err.Error(), "interrupt_step": taskItem.Task.CurrentStep}) + return err + } + _ = snapshotRepo.Update(req.ID, map[string]interface{}{"status": constant.StatusSuccess, "interrupt_step": ""}) + _ = os.RemoveAll(rootDir) + return nil +} + +type snapHelper struct { + SnapID uint + SnapName string + snapAgentDB *gorm.DB + snapCoreDB *gorm.DB + Ctx context.Context + FileOp files.FileOp + Wg *sync.WaitGroup + Task task.Task + + OpenrestyDir string +} + +func loadDbConn(snap *snapHelper, targetDir string, req dto.SnapshotCreate) error { + snap.Task.Log("---------------------- 1 / 8 ----------------------") + snap.Task.LogStart(i18n.GetMsgByKey("SnapDBInfo")) + pathDB := path.Join(global.Dir.DataDir, "db") + + err := snap.FileOp.CopyDir(pathDB, targetDir) + snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", pathDB), err) + if err != nil { + return err + } + _ = os.Remove(path.Join(targetDir, "db/session.db")) + + agentDb, err := common.LoadDBConnByPathWithErr(path.Join(targetDir, "db/agent.db"), "agent.db") + snap.Task.LogWithStatus(i18n.GetWithName("SnapNewDB", "agent"), err) + if err != nil { + return err + } + snap.snapAgentDB = agentDb + _ = snap.snapAgentDB.Model(&model.Setting{}).Where("key = ?", "SystemIP").Updates(map[string]interface{}{"value": ""}).Error + coreDb, err := common.LoadDBConnByPathWithErr(path.Join(targetDir, "db/core.db"), "core.db") + snap.Task.LogWithStatus(i18n.GetWithName("SnapNewDB", "core"), err) + if err != nil { + return err + } + snap.snapCoreDB = coreDb + + if !req.WithMonitorData { + err = os.Remove(path.Join(targetDir, "db/monitor.db")) + snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapDeleteMonitor"), err) + if err != nil { + return err + } + } + if !req.WithTaskLog { + err = os.Remove(path.Join(targetDir, "db/task.db")) + if err != nil { + return err + } + } else { + taskDB, err := common.LoadDBConnByPathWithErr(path.Join(targetDir, "db/task.db"), "task.db") + snap.Task.LogWithStatus(i18n.GetWithName("SnapNewDB", "task"), err) + if err != nil { + return err + } + defer common.CloseDB(taskDB) + _ = taskDB.Where("id = ?", req.TaskID).Delete(&model.Task{}).Error + } + if !req.WithOperationLog && global.IsMaster { + err = snap.snapCoreDB.Exec("DELETE FROM operation_logs").Error + snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapDeleteOperationLog"), err) + if err != nil { + return err + } + } + if !req.WithLoginLog && global.IsMaster { + err = snap.snapCoreDB.Exec("DELETE FROM login_logs").Error + snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapDeleteLoginLog"), err) + if err != nil { + return err + } + } + return nil +} + +func snapBaseData(snap snapHelper, targetDir string, withDockerConf bool) error { + snap.Task.Log("---------------------- 2 / 8 ----------------------") + snap.Task.LogStart(i18n.GetMsgByKey("SnapBaseInfo")) + + if global.IsMaster { + err := snap.FileOp.CopyFile("/usr/local/bin/1panel-core", targetDir) + snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"), err) + if err != nil { + return err + } + err = snap.FileOp.CopyFile("/usr/local/bin/1pctl", targetDir) + snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err) + if err != nil { + return err + } + } + + err := snap.FileOp.CopyFile("/usr/local/bin/1panel-agent", targetDir) + snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-agent"), err) + if err != nil { + return err + } + + if global.IsMaster { + svcCorePath, _ := controller.GetServicePath("1panel-core") + err = snap.FileOp.CopyFile(svcCorePath, targetDir) + snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", svcCorePath), err) + if err != nil { + return err + } + } + svcAgentName, _ := controller.GetServicePath("1panel-agent") + err = snap.FileOp.CopyFile(svcAgentName, targetDir) + snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", svcAgentName), err) + if err != nil { + return err + } + initScriptPath := path.Join(global.Dir.ResourceDir, "initscript") + if _, err := os.Stat(initScriptPath); err == nil { + err = snap.FileOp.CopyDirWithNewName(initScriptPath, targetDir, ".") + snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", initScriptPath), err) + if err != nil { + return err + } + } + if withDockerConf { + if snap.FileOp.Stat(constant.DaemonJsonPath) { + err = snap.FileOp.CopyFile(constant.DaemonJsonPath, targetDir) + snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", constant.DaemonJsonPath), err) + if err != nil { + return err + } + } + } + + remarkInfo, _ := json.MarshalIndent(SnapshotJson{ + BaseDir: global.Dir.BaseDir, + OperestyDir: snap.OpenrestyDir, + BackupDataDir: global.Dir.LocalBackupDir, + }, "", "\t") + err = os.WriteFile(path.Join(targetDir, "snapshot.json"), remarkInfo, 0640) + snap.Task.LogWithStatus(i18n.GetWithName("SnapCopy", path.Join(targetDir, "snapshot.json")), err) + if err != nil { + return err + } + + return nil +} + +func snapAppImage(snap snapHelper, req dto.SnapshotCreate, targetDir string) error { + snap.Task.Log("---------------------- 3 / 8 ----------------------") + snap.Task.LogStart(i18n.GetMsgByKey("SnapInstallApp")) + + var appInstalls []model.AppInstall + _ = snap.snapAgentDB.Where("1 = 1").Find(&appInstalls).Error + for _, item := range appInstalls { + if err := snap.snapAgentDB. + Model(&model.AppInstall{}). + Where("id = ?", item.ID). + Updates(map[string]interface{}{"status": constant.StatusWaitingRestart}). + Error; err != nil { + global.LOG.Errorf("update app %s status failed, err: %v", item.Name, err) + } + } + + client, err := docker.NewDockerClient() + if err != nil { + snap.Task.Log("load docker client failed, skip save app images") + return nil + } + images, err := client.ImageList(context.Background(), image.ListOptions{}) + if err != nil { + snap.Task.Log("list docker images failed, skip save app images") + return nil + } + var existImages []string + for _, img := range images { + existImages = append(existImages, img.RepoTags...) + } + var imageList []string + for _, app := range req.AppData { + for _, item := range app.Children { + if item.Label == "appImage" && item.IsCheck { + for _, existImage := range existImages { + if len(existImage) == 0 { + continue + } + if existImage == item.Name { + imageList = append(imageList, item.Name) + } + } + } + } + } + + if len(imageList) != 0 { + snap.Task.Log(strings.Join(imageList, " ")) + snap.Task.Logf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz")) + if err := cmd.RunDefaultBashCf("docker save %s | gzip -c > %s", strings.Join(imageList, " "), path.Join(targetDir, "images.tar.gz")); err != nil { + snap.Task.LogFailedWithErr(i18n.GetMsgByKey("SnapDockerSave"), err) + return err + } + snap.Task.LogSuccess(i18n.GetMsgByKey("SnapDockerSave")) + } else { + snap.Task.Log(i18n.GetMsgByKey("SnapInstallAppImageEmpty")) + } + return nil +} + +func snapBackupData(snap snapHelper, req dto.SnapshotCreate, targetDir string) error { + snap.Task.Log("---------------------- 4 / 8 ----------------------") + snap.Task.LogStart(i18n.GetMsgByKey("SnapLocalBackup")) + + excludes := loadBackupExcludes(snap, req.BackupData) + excludes = append(excludes, req.IgnoreFiles...) + excludes = append(excludes, "./system_snapshot") + excludes = append(excludes, "./tmp") + for _, item := range req.AppData { + for _, itemApp := range item.Children { + if itemApp.Label == "appBackup" { + excludes = append(excludes, loadAppBackupExcludes([]dto.DataTree{itemApp})...) + } + } + } + err := snap.FileOp.TarGzCompressPro(false, global.Dir.LocalBackupDir, path.Join(targetDir, "1panel_backup.tar.gz"), "", strings.Join(excludes, ",")) + snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressBackup"), err) + + return err +} +func loadBackupExcludes(snap snapHelper, req []dto.DataTree) []string { + var excludes []string + for _, item := range req { + if len(item.Children) == 0 { + if item.IsCheck { + continue + } + if err := snap.snapAgentDB.Where("file_dir = ? AND file_name = ?", strings.TrimPrefix(path.Dir(item.Path), global.Dir.LocalBackupDir+"/"), path.Base(item.Path)).Delete(&model.BackupRecord{}).Error; err != nil { + snap.Task.LogWithStatus("delete backup file from database", err) + } + itemDir, _ := filepath.Rel(global.Dir.LocalBackupDir, item.Path) + excludes = append(excludes, itemDir) + } else { + excludes = append(excludes, loadBackupExcludes(snap, item.Children)...) + } + } + return excludes +} +func loadAppBackupExcludes(req []dto.DataTree) []string { + var excludes []string + for _, item := range req { + if len(item.Children) == 0 { + if !item.IsCheck { + itemDir, _ := filepath.Rel(global.Dir.LocalBackupDir, item.Path) + excludes = append(excludes, itemDir) + } + } else { + excludes = append(excludes, loadAppBackupExcludes(item.Children)...) + } + } + return excludes +} + +func snapPanelData(snap snapHelper, req dto.SnapshotCreate, targetDir string) error { + snap.Task.Log("---------------------- 5 / 8 ----------------------") + snap.Task.LogStart(i18n.GetMsgByKey("SnapPanelData")) + + excludes := loadPanelExcludes(req.PanelData) + for _, item := range req.AppData { + for _, itemApp := range item.Children { + if itemApp.Label == "appData" { + excludes = append(excludes, loadPanelExcludes([]dto.DataTree{itemApp})...) + } + } + } + excludes = append(excludes, "./cache") + excludes = append(excludes, "./db") + excludes = append(excludes, "./tmp") + if !req.WithSystemLog { + excludes = append(excludes, "./log/1Panel*") + } + if !req.WithTaskLog { + excludes = append(excludes, "./log/task") + } + + rootDir := global.Dir.DataDir + if strings.Contains(global.Dir.LocalBackupDir, rootDir) { + itemDir, _ := filepath.Rel(rootDir, global.Dir.LocalBackupDir) + excludes = append(excludes, itemDir) + } + if len(snap.OpenrestyDir) != 0 && strings.Contains(snap.OpenrestyDir, rootDir) { + itemDir, _ := filepath.Rel(rootDir, snap.OpenrestyDir) + excludes = append(excludes, itemDir) + } + excludes = append(excludes, req.IgnoreFiles...) + err := snap.FileOp.TarGzCompressPro(false, rootDir, path.Join(targetDir, "1panel_data.tar.gz"), "", strings.Join(excludes, ",")) + snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressPanel"), err) + if err != nil { + return err + } + if len(snap.OpenrestyDir) != 0 { + err := snap.FileOp.TarGzCompressPro(false, snap.OpenrestyDir, path.Join(targetDir, "website.tar.gz"), "", strings.Join(req.IgnoreFiles, ",")) + snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapWebsite"), err) + if err != nil { + return err + } + } + + return err +} +func loadPanelExcludes(req []dto.DataTree) []string { + var excludes []string + for _, item := range req { + if len(item.Children) == 0 { + if !item.IsCheck { + itemDir, _ := filepath.Rel(path.Join(global.Dir.BaseDir, "1panel"), item.Path) + excludes = append(excludes, itemDir) + } + } else { + excludes = append(excludes, loadPanelExcludes(item.Children)...) + } + } + return excludes +} + +func snapCompress(snap snapHelper, rootDir string, secret string) error { + snap.Task.Log("---------------------- 7 / 8 ----------------------") + snap.Task.LogStart(i18n.GetMsgByKey("SnapCompress")) + + tmpDir := path.Join(global.Dir.LocalBackupDir, "tmp/system") + fileName := fmt.Sprintf("%s.tar.gz", path.Base(rootDir)) + err := snap.FileOp.TarGzCompressPro(true, rootDir, path.Join(tmpDir, fileName), secret, "") + snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCompressFile"), err) + if err != nil { + return err + } + + stat, err := os.Stat(path.Join(tmpDir, fileName)) + snap.Task.LogWithStatus(i18n.GetMsgByKey("SnapCheckCompress"), err) + if err != nil { + return err + } + + size := common.LoadSizeUnit2F(float64(stat.Size())) + snap.Task.Log(i18n.GetWithName("SnapCompressSize", size)) + _ = os.RemoveAll(rootDir) + return nil +} + +func snapUpload(snap snapHelper, accounts string, downloadID, retry uint, file string) error { + snap.Task.Log("---------------------- 8 / 8 ----------------------") + snap.Task.LogStart(i18n.GetMsgByKey("SnapUpload")) + + src := path.Join(global.Dir.LocalBackupDir, "tmp/system", path.Base(file)) + dst := path.Join("system_snapshot", path.Base(file)) + accountMap := NewBackupClientMap(strings.Split(accounts, ",")) + if !accountMap[fmt.Sprintf("%d", downloadID)].isOk { + return buserr.New(i18n.GetMsgWithDetail("LoadBackupFailed", accountMap[fmt.Sprintf("%d", downloadID)].message)) + } + return uploadWithMap(snap.Task, accountMap, src, dst, accounts, downloadID, retry) +} diff --git a/agent/app/service/snapshot_recover.go b/agent/app/service/snapshot_recover.go new file mode 100644 index 0000000..7eec242 --- /dev/null +++ b/agent/app/service/snapshot_recover.go @@ -0,0 +1,474 @@ +package service + +import ( + "encoding/json" + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/pkg/errors" +) + +var ( + svcBasePath, _ = controller.GetServicePath("") + svcCoreName, _ = controller.LoadServiceName("1panel-core") + selCoreName, _ = controller.SelectInitScript("1panel-core") + scriptCoreName, _ = controller.GetScriptName("1panel-core") + svcAgentName, _ = controller.LoadServiceName("1panel-agent") + selAgentName, _ = controller.SelectInitScript("1panel-agent") + scriptAgentName, _ = controller.GetScriptName("1panel-agent") +) + +type snapRecoverHelper struct { + FileOp files.FileOp + Task *task.Task +} + +func (u *SnapshotService) SnapshotRecover(req dto.SnapshotRecover) error { + global.LOG.Info("start to recover panel by snapshot now") + snap, err := snapshotRepo.Get(repo.WithByID(req.ID)) + if err != nil { + return err + } + if hasOs(snap.Name) && !strings.Contains(snap.Name, loadOs()) { + errInfo := fmt.Sprintf("restoring snapshots(%s) between different server architectures(%s) is not supported", snap.Name, loadOs()) + _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusFailed, "recover_message": errInfo}) + return errors.New(errInfo) + } + if !strings.Contains(snap.Name, "-v2.") { + return errors.New("snapshots are currently not supported for recovery across major versions") + } + if !strings.Contains(snap.Name, "-core") && !strings.Contains(snap.Name, "-agent") { + return errors.New("the name of the snapshot file does not conform to the format") + } + if strings.Contains(snap.Name, "-core") && !global.IsMaster { + return errors.New("the snapshot of the master node cannot be restored on the agent nodes") + } + if strings.Contains(snap.Name, "-agent") && global.IsMaster { + return errors.New("the snapshot of the agent node cannot be restored on the master node") + } + if len(snap.RollbackStatus) != 0 && snap.RollbackStatus != constant.StatusSuccess { + req.IsNew = true + } + if !req.IsNew && (snap.InterruptStep == "RecoverDownload" || snap.InterruptStep == "RecoverDecompress" || snap.InterruptStep == "BackupBeforeRecover") { + req.IsNew = true + } + _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"recover_status": constant.StatusWaiting}) + _ = settingRepo.Update("SystemStatus", "Recovering") + + if len(snap.InterruptStep) == 0 { + req.IsNew = true + } + if len(snap.TaskRecoverID) != 0 { + req.TaskID = snap.TaskRecoverID + } else { + _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"task_recover_id": req.TaskID}) + } + + var taskItem *task.Task + if req.IsNew { + taskItem, err = task.NewTaskWithOps(snap.Name, task.TaskRecover, task.TaskScopeSnapshot, req.TaskID, snap.ID) + } else { + taskItem, err = task.ReNewTaskWithOps(snap.Name, task.TaskRecover, task.TaskScopeSnapshot, req.TaskID, snap.ID) + } + if err != nil { + global.LOG.Errorf("new task for create snapshot failed, err: %v", err) + return err + } + rootDir := path.Join(global.Dir.LocalBackupDir, "tmp/system", snap.Name) + if _, err := os.Stat(rootDir); err != nil && os.IsNotExist(err) { + _ = os.MkdirAll(rootDir, os.ModePerm) + } + itemHelper := snapRecoverHelper{Task: taskItem, FileOp: files.NewFileOp()} + + go func() { + _ = global.Cron.Stop() + defer func() { + global.Cron.Start() + }() + + if req.IsNew || snap.InterruptStep == "RecoverDownload" || req.ReDownload { + taskItem.AddSubTaskWithAliasAndOps( + "RecoverDownload", + func(t *task.Task) error { return handleDownloadSnapshot(&itemHelper, snap, rootDir) }, + nil, 0, 90*time.Minute, + ) + req.IsNew = true + } + if req.IsNew || snap.InterruptStep == "RecoverDecompress" { + taskItem.AddSubTaskWithAliasAndOps( + "RecoverDecompress", + func(t *task.Task) error { + itemHelper.Task.Log("---------------------- 2 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetWithName("RecoverDecompress", snap.Name)) + err := itemHelper.FileOp.TarGzExtractPro(fmt.Sprintf("%s/%s.tar.gz", rootDir, snap.Name), rootDir, req.Secret) + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err) + return err + }, + nil, 0, 90*time.Minute, + ) + req.IsNew = true + } + if req.IsNew || snap.InterruptStep == "BackupBeforeRecover" { + taskItem.AddSubTaskWithAliasAndOps( + "BackupBeforeRecover", + func(t *task.Task) error { return backupBeforeRecover(snap.Name, &itemHelper) }, + nil, 0, 90*time.Minute, + ) + req.IsNew = true + } + + var snapJson SnapshotJson + taskItem.AddSubTaskWithAliasAndOps( + "Readjson", + func(t *task.Task) error { + snapJson, err = readFromJson(path.Join(rootDir, snap.Name), &itemHelper) + return err + }, + nil, 0, 90*time.Minute, + ) + if req.IsNew || snap.InterruptStep == "RecoverApp" { + taskItem.AddSubTaskWithAliasAndOps( + "RecoverApp", + func(t *task.Task) error { return recoverAppData(path.Join(rootDir, snap.Name), &itemHelper) }, + nil, 0, 90*time.Minute, + ) + req.IsNew = true + } + if req.IsNew || snap.InterruptStep == "RecoverBaseData" { + taskItem.AddSubTaskWithAliasAndOps( + "RecoverBaseData", + func(t *task.Task) error { return recoverBaseData(path.Join(rootDir, snap.Name, "base"), &itemHelper) }, + nil, 0, 90*time.Minute, + ) + req.IsNew = true + } + if req.IsNew || snap.InterruptStep == "RecoverDBData" { + taskItem.AddSubTaskWithAliasAndOps( + "RecoverDBData", + func(t *task.Task) error { return recoverDBData(path.Join(rootDir, snap.Name, "db"), &itemHelper) }, + nil, 0, 90*time.Minute, + ) + req.IsNew = true + } + if req.IsNew || snap.InterruptStep == "RecoverBackups" { + taskItem.AddSubTaskWithAliasAndOps( + "RecoverBackups", + func(t *task.Task) error { + itemHelper.Task.Log("---------------------- 8 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetWithName("RecoverBackups", snap.Name)) + err := itemHelper.FileOp.TarGzExtractPro(path.Join(rootDir, snap.Name, "/1panel_backup.tar.gz"), snapJson.BackupDataDir, "") + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err) + return err + }, + nil, 0, 90*time.Minute, + ) + req.IsNew = true + } + if req.IsNew || snap.InterruptStep == "RecoverWebsite" { + taskItem.AddSubTaskWithAliasAndOps( + "RecoverWebsite", + func(t *task.Task) error { + itemHelper.Task.Log("---------------------- 9 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetWithName("RecoverWebsite", snap.Name)) + webFile := path.Join(rootDir, snap.Name, "/website.tar.gz") + _ = itemHelper.FileOp.CreateDir(snapJson.OperestyDir, os.ModePerm) + var err error + if itemHelper.FileOp.Stat(webFile) { + err = itemHelper.FileOp.TarGzExtractPro(webFile, snapJson.OperestyDir, "") + } + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err) + return err + }, + nil, 0, 90*time.Minute, + ) + req.IsNew = true + } + if req.IsNew || snap.InterruptStep == "RecoverPanelData" { + taskItem.AddSubTaskWithAliasAndOps( + "RecoverPanelData", + func(t *task.Task) error { + itemHelper.Task.Log("---------------------- 10 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetWithName("RecoverPanelData", snap.Name)) + err := itemHelper.FileOp.TarGzExtractPro(path.Join(rootDir, snap.Name, "/1panel_data.tar.gz"), path.Join(snapJson.BaseDir, "1panel"), "") + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Decompress"), err) + if err != nil { + return err + } + + if len(snapJson.OperestyDir) != 0 { + err := itemHelper.FileOp.TarGzExtractPro(path.Join(rootDir, snap.Name, "/website.tar.gz"), snapJson.OperestyDir, "") + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverWebsite"), err) + if err != nil { + return err + } + } + return err + }, + nil, 0, 90*time.Minute, + ) + req.IsNew = true + } + taskItem.AddSubTaskWithAliasAndOps( + "RecoverDBData", + func(t *task.Task) error { + return restartCompose(path.Join(snapJson.BaseDir, "1panel/docker/compose"), &itemHelper) + }, + nil, 0, 90*time.Minute, + ) + + if err := taskItem.Execute(); err != nil { + _ = settingRepo.Update("SystemStatus", "Free") + _ = snapshotRepo.Update(req.ID, map[string]interface{}{"recover_status": constant.StatusFailed, "recover_message": err.Error(), "interrupt_step": taskItem.Task.CurrentStep}) + return + } + _ = os.RemoveAll(rootDir) + controller.RestartPanel(true, true, true) + }() + return nil +} + +func handleDownloadSnapshot(itemHelper *snapRecoverHelper, snap model.Snapshot, targetDir string) error { + itemHelper.Task.Log("---------------------- 1 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDownload")) + + account, client, err := NewBackupClientWithID(snap.DownloadAccountID) + itemHelper.Task.LogWithStatus(i18n.GetWithName("RecoverDownloadAccount", fmt.Sprintf("%s - %s", account.Type, account.Name)), err) + targetPath := "" + if len(account.BackupPath) != 0 { + targetPath = path.Join(account.BackupPath, fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name)) + } else { + targetPath = fmt.Sprintf("system_snapshot/%s.tar.gz", snap.Name) + } + filePath := fmt.Sprintf("%s/%s.tar.gz", targetDir, snap.Name) + _ = os.RemoveAll(filePath) + _, err = client.Download(targetPath, filePath) + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("Download"), err) + return err +} + +func backupBeforeRecover(name string, itemHelper *snapRecoverHelper) error { + itemHelper.Task.Log("---------------------- 3 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetMsgByKey("BackupBeforeRecover")) + + rootDir := fmt.Sprintf("%s/1panel_original/original_%s", global.Dir.BaseDir, name) + baseDir := path.Join(rootDir, "base") + if _, err := os.Stat(baseDir); err != nil { + _ = os.MkdirAll(baseDir, os.ModePerm) + } + initScriptPath := path.Join(global.Dir.ResourceDir, "initscript") + if _, err := os.Stat(initScriptPath); err == nil { + err = itemHelper.FileOp.CopyDirWithNewName(initScriptPath, baseDir, ".") + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", initScriptPath), err) + if err != nil { + return err + } + } + dataDir := global.Dir.DataDir + err := itemHelper.FileOp.CopyDirWithExclude(dataDir, rootDir, []string{"cache", "tmp", "backup"}) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", dataDir), err) + if err != nil { + return err + } + + openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR") + if len(openrestyDir) != 0 && !strings.Contains(openrestyDir, dataDir) { + err := itemHelper.FileOp.CopyDirWithExclude(openrestyDir, rootDir, nil) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", openrestyDir), err) + if err != nil { + return err + } + } + + backupDir := global.Dir.LocalBackupDir + if len(backupDir) != 0 && (backupDir == dataDir+"/backup" || !strings.HasPrefix(backupDir, dataDir)) { + err = itemHelper.FileOp.CopyDirWithExclude(backupDir, rootDir, []string{"system_snapshot", "tmp"}) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", backupDir), err) + if err != nil { + return err + } + } + + if global.IsMaster { + err = itemHelper.FileOp.CopyFile("/usr/local/bin/1pctl", baseDir) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err) + if err != nil { + return err + } + err = itemHelper.FileOp.CopyFile("/usr/local/bin/1panel-core", baseDir) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"), err) + if err != nil { + return err + } + svcCorePath, _ := controller.GetServicePath("1panel-core") + err = itemHelper.FileOp.CopyFile(svcCorePath, baseDir) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", svcCorePath), err) + if err != nil { + return err + } + } + err = itemHelper.FileOp.CopyFile("/usr/local/bin/1panel-agent", baseDir) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-agent"), err) + if err != nil { + return err + } + svcAgentPath, _ := controller.GetServicePath("1panel-agent") + err = itemHelper.FileOp.CopyFile(svcAgentPath, baseDir) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", svcAgentPath), err) + if err != nil { + return err + } + if itemHelper.FileOp.Stat(constant.DaemonJsonPath) { + err = itemHelper.FileOp.CopyFile(constant.DaemonJsonPath, baseDir) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", constant.DaemonJsonPath), err) + if err != nil { + return err + } + } + return nil +} + +func readFromJson(rootDir string, itemHelper *snapRecoverHelper) (SnapshotJson, error) { + itemHelper.Task.Log("---------------------- 4 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetMsgByKey("Readjson")) + + snapJsonPath := path.Join(rootDir, "base/snapshot.json") + var snap SnapshotJson + _, err := os.Stat(snapJsonPath) + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadjsonPath"), err) + if err != nil { + return snap, err + } + fileByte, err := os.ReadFile(snapJsonPath) + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadjsonContent"), err) + if err != nil { + return snap, err + } + err = json.Unmarshal(fileByte, &snap) + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("ReadjsonMarshal"), err) + if err != nil { + return snap, err + } + return snap, nil +} + +func recoverAppData(src string, itemHelper *snapRecoverHelper) error { + itemHelper.Task.Log("---------------------- 5 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverApp")) + + if _, err := os.Stat(path.Join(src, "images.tar.gz")); err != nil { + itemHelper.Task.Log(i18n.GetMsgByKey("RecoverAppEmpty")) + return nil + } + if err := cmd.NewCommandMgr(cmd.WithTimeout(10*time.Minute)).RunBashCf("docker load < %s", path.Join(src, "images.tar.gz")); err != nil { + itemHelper.Task.LogFailedWithErr(i18n.GetMsgByKey("RecoverAppImage"), err) + return fmt.Errorf("docker load images failed, %v", err) + } + itemHelper.Task.LogSuccess(i18n.GetMsgByKey("RecoverAppImage")) + return nil +} + +func recoverBaseData(src string, itemHelper *snapRecoverHelper) error { + itemHelper.Task.Log("---------------------- 6 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetMsgByKey("SnapBaseInfo")) + if global.IsMaster { + err := itemHelper.FileOp.CopyFile(path.Join(src, "1pctl"), "/usr/local/bin") + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), err) + if err != nil { + return err + } + err = itemHelper.FileOp.CopyFile(path.Join(src, "1panel-core"), "/usr/local/bin") + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"), err) + if err != nil { + return err + } + svcCoreName, _ := controller.LoadServiceName("1panel-core") + err = itemHelper.FileOp.CopyFile(path.Join(src, svcCoreName), svcBasePath) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", path.Join(svcBasePath, svcCoreName)), err) + if err != nil { + err = itemHelper.FileOp.CopyFile(path.Join(src, selCoreName), path.Join(svcBasePath, scriptCoreName)) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", path.Join(svcBasePath, scriptCoreName)), err) + if err != nil { + return err + } + } + } + err := itemHelper.FileOp.CopyFile(path.Join(src, "1panel-agent"), "/usr/local/bin") + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-agent"), err) + if err != nil { + return err + } + svcAgentName, _ := controller.LoadServiceName("1panel-agent") + err = itemHelper.FileOp.CopyFile(path.Join(src, svcAgentName), svcBasePath) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", path.Join(svcBasePath, svcAgentName)), err) + if err != nil { + err = itemHelper.FileOp.CopyFile(path.Join(src, selAgentName), path.Join(svcBasePath, scriptAgentName)) + itemHelper.Task.LogWithStatus(i18n.GetWithName("SnapCopy", path.Join(svcBasePath, scriptAgentName)), err) + if err != nil { + return err + } + } + + if !itemHelper.FileOp.Stat(path.Join(src, "daemon.json")) { + itemHelper.Task.Log(i18n.GetMsgByKey("RecoverDaemonJsonEmpty")) + return nil + } else { + err = itemHelper.FileOp.CopyFile(path.Join(src, "daemon.json"), path.Dir(constant.DaemonJsonPath)) + itemHelper.Task.Log(i18n.GetMsgByKey("RecoverDaemonJson")) + if err != nil { + return fmt.Errorf("recover docker daemon.json failed, err: %v", err) + } + } + + if err := controller.HandleRestart("docker"); err != nil { + return fmt.Errorf("failed to restart Docker: %v", err) + } + return nil +} + +func recoverDBData(src string, itemHelper *snapRecoverHelper) error { + itemHelper.Task.Log("---------------------- 7 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverDBData")) + err := itemHelper.FileOp.CopyDirWithExclude(src, global.Dir.DataDir, nil) + + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverDBData"), err) + return err +} + +func restartCompose(composePath string, itemHelper *snapRecoverHelper) error { + itemHelper.Task.Log("---------------------- 11 / 11 ----------------------") + itemHelper.Task.LogStart(i18n.GetMsgByKey("RecoverCompose")) + + composes, err := composeRepo.ListRecord() + itemHelper.Task.LogWithStatus(i18n.GetMsgByKey("RecoverComposeList"), err) + if err != nil { + return err + } + + for _, compose := range composes { + pathItem := path.Join(composePath, compose.Name, "docker-compose.yml") + if _, err := os.Stat(pathItem); err != nil { + continue + } + upCmd := fmt.Sprintf("docker compose -f %s up -d", pathItem) + if err := cmd.RunDefaultBashC(upCmd); err != nil { + itemHelper.Task.LogFailedWithErr(i18n.GetMsgByKey("RecoverCompose"), err) + continue + } + itemHelper.Task.LogSuccess(i18n.GetWithName("RecoverComposeItem", pathItem)) + } + return nil +} diff --git a/agent/app/service/snapshot_rollback.go b/agent/app/service/snapshot_rollback.go new file mode 100644 index 0000000..5c5d6ce --- /dev/null +++ b/agent/app/service/snapshot_rollback.go @@ -0,0 +1,127 @@ +package service + +import ( + "fmt" + "os" + "path" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +func (u *SnapshotService) SnapshotRollback(req dto.SnapshotRecover) error { + global.LOG.Info("start to rollback now") + snap, err := snapshotRepo.Get(repo.WithByID(req.ID)) + if err != nil { + return err + } + if len(snap.TaskRollbackID) != 0 { + req.TaskID = snap.TaskRollbackID + } else { + _ = snapshotRepo.Update(snap.ID, map[string]interface{}{"task_rollback_id": req.TaskID}) + } + taskItem, err := task.NewTaskWithOps(snap.Name, task.TaskRollback, task.TaskScopeSnapshot, req.TaskID, snap.ID) + if err != nil { + global.LOG.Errorf("new task for create snapshot failed, err: %v", err) + return err + } + go func() { + rootDir := fmt.Sprintf("%s/1panel_original/original_%s", global.Dir.BaseDir, snap.Name) + baseDir := path.Join(rootDir, "base") + + FileOp := files.NewFileOp() + taskItem.AddSubTask( + i18n.GetWithName("SnapCopy", "/usr/local/bin/1pctl"), + func(t *task.Task) error { + return FileOp.CopyFile(path.Join(baseDir, "1pctl"), "/usr/local/bin") + }, + nil, + ) + if global.IsMaster { + taskItem.AddSubTask( + i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-core"), + func(t *task.Task) error { + return FileOp.CopyFile(path.Join(baseDir, "1panel"), "/usr/local/bin") + }, + nil, + ) + } + taskItem.AddSubTask( + i18n.GetWithName("SnapCopy", "/usr/local/bin/1panel-agent"), + func(t *task.Task) error { + return FileOp.CopyFile(path.Join(baseDir, "1panel-agent"), "/usr/local/bin") + }, + nil, + ) + if global.IsMaster { + taskItem.AddSubTask( + i18n.GetWithName("SnapCopy", path.Join(svcBasePath, svcCoreName)), + func(t *task.Task) error { + return FileOp.CopyFile(path.Join(baseDir, svcCoreName), svcBasePath) + }, + nil, + ) + } + taskItem.AddSubTask( + i18n.GetWithName("SnapCopy", path.Join(svcBasePath, svcAgentName)), + func(t *task.Task) error { + return FileOp.CopyFile(path.Join(baseDir, svcAgentName), svcBasePath) + }, + nil, + ) + taskItem.AddSubTask( + i18n.GetWithName("SnapCopy", constant.DaemonJsonPath), + func(t *task.Task) error { + if FileOp.Stat(path.Join(baseDir, "daemon.json")) { + return FileOp.CopyFile(path.Join(baseDir, "daemon.json"), path.Dir(constant.DaemonJsonPath)) + } + return nil + }, + nil, + ) + taskItem.AddSubTask( + i18n.GetWithName("SnapCopy", global.Dir.LocalBackupDir), + func(t *task.Task) error { + return FileOp.CopyDir(path.Join(rootDir, "backup"), strings.TrimSuffix(global.Dir.LocalBackupDir, "/backup")) + }, + nil, + ) + openrestyDir, _ := settingRepo.GetValueByKey("WEBSITE_DIR") + if len(openrestyDir) != 0 { + taskItem.AddSubTask( + i18n.GetWithName("SnapCopy", openrestyDir), + func(t *task.Task) error { + return FileOp.CopyDir(path.Join(rootDir, "www"), strings.TrimSuffix(openrestyDir, "/www")) + }, + nil, + ) + } + taskItem.AddSubTask( + i18n.GetWithName("SnapCopy", global.Dir.BaseDir), + func(t *task.Task) error { + return FileOp.CopyDir(path.Join(rootDir, "1panel"), global.Dir.BaseDir) + }, + nil, + ) + if err := taskItem.Execute(); err != nil { + _ = snapshotRepo.Update(req.ID, map[string]interface{}{"rollback_status": constant.StatusFailed, "rollback_message": err.Error()}) + return + } + _ = snapshotRepo.Update(req.ID, map[string]interface{}{ + "recover_status": "", + "recover_message": "", + "rollback_status": "", + "rollback_message": "", + "interrupt_step": "", + }) + _ = os.RemoveAll(rootDir) + }() + return nil +} diff --git a/agent/app/service/ssh.go b/agent/app/service/ssh.go new file mode 100644 index 0000000..a4e7437 --- /dev/null +++ b/agent/app/service/ssh.go @@ -0,0 +1,908 @@ +package service + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "fmt" + "net" + "os" + "os/user" + "path" + "sort" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/copier" + csvexport "github.com/1Panel-dev/1Panel/agent/utils/csv_export" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/geo" + "github.com/gin-gonic/gin" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/pkg/errors" +) + +const sshPath = "/etc/ssh/sshd_config" + +type SSHService struct{} + +type ISSHService interface { + GetSSHInfo() (*dto.SSHInfo, error) + OperateSSH(operation string) error + Update(req dto.SSHUpdate) error + LoadSSHFile(name string) (string, error) + UpdateByFile(req dto.SettingUpdate) error + + LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error) + ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error) + + SyncRootCert() error + CreateRootCert(req dto.RootCertOperate) error + EditRootCert(req dto.RootCertOperate) error + SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error) + DeleteRootCerts(req dto.ForceDelete) error +} + +func NewISSHService() ISSHService { + return &SSHService{} +} + +func (u *SSHService) GetSSHInfo() (*dto.SSHInfo, error) { + data := dto.SSHInfo{ + AutoStart: true, + IsExist: true, + IsActive: true, + Message: "", + Port: "22", + ListenAddress: "", + PasswordAuthentication: "yes", + PubkeyAuthentication: "yes", + PermitRootLogin: "yes", + UseDNS: "yes", + } + serviceName, err := loadServiceName() + if err != nil { + data.IsExist = false + data.Message = err.Error() + } else { + active, err := controller.CheckActive(serviceName) + data.IsActive = active + if !active && err != nil { + data.Message = err.Error() + } + } + + enable, err := controller.CheckEnable(serviceName) + if err != nil { + data.AutoStart = false + } else { + data.AutoStart = enable + } + + sshConf, err := os.ReadFile(sshPath) + if err != nil { + data.Message = err.Error() + data.IsActive = false + } + lines := strings.Split(string(sshConf), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "Port ") { + data.Port = strings.ReplaceAll(line, "Port ", "") + } + if strings.HasPrefix(line, "ListenAddress ") { + itemAddr := strings.ReplaceAll(line, "ListenAddress ", "") + if len(data.ListenAddress) != 0 { + data.ListenAddress += ("," + itemAddr) + } else { + data.ListenAddress = itemAddr + } + } + if strings.HasPrefix(line, "PasswordAuthentication ") { + data.PasswordAuthentication = strings.ReplaceAll(line, "PasswordAuthentication ", "") + } + if strings.HasPrefix(line, "PubkeyAuthentication ") { + data.PubkeyAuthentication = strings.ReplaceAll(line, "PubkeyAuthentication ", "") + } + if strings.HasPrefix(line, "PermitRootLogin ") { + data.PermitRootLogin = strings.ReplaceAll(strings.ReplaceAll(line, "PermitRootLogin ", ""), "prohibit-password", "without-password") + } + if strings.HasPrefix(line, "UseDNS ") { + data.UseDNS = strings.ReplaceAll(line, "UseDNS ", "") + } + } + + currentUser, err := user.Current() + if err != nil || len(currentUser.Name) == 0 { + data.CurrentUser = "root" + } else { + data.CurrentUser = currentUser.Name + } + + return &data, nil +} + +func (u *SSHService) OperateSSH(operation string) error { + serviceName, err := loadServiceName() + if err != nil { + return err + } + if operation == "enable" || operation == "disable" { + serviceName += ".service" + } + if operation == "stop" { + isSocketActive, _ := controller.CheckActive(serviceName + ".socket") + if isSocketActive { + if err := controller.HandleStop(serviceName + ".socket"); err != nil { + global.LOG.Errorf("handle stop %s.socket failed, err: %v", serviceName, err) + } + } + } + + if err := controller.Handle(operation, serviceName); err != nil { + return fmt.Errorf("%s %s failed, err: %v", operation, serviceName, err) + } + return nil +} + +func (u *SSHService) Update(req dto.SSHUpdate) error { + serviceName, err := loadServiceName() + if err != nil { + return err + } + + sshConf, err := os.ReadFile(sshPath) + if err != nil { + return err + } + lines := strings.Split(string(sshConf), "\n") + newFiles := updateSSHConf(lines, req.Key, req.NewValue) + file, err := os.OpenFile(sshPath, os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + if _, err = file.WriteString(strings.Join(newFiles, "\n")); err != nil { + return err + } + sudo := cmd.SudoHandleCmd() + if req.Key == "Port" { + stdout, _ := cmd.RunDefaultWithStdoutBashCf("%s getenforce", sudo) + if stdout == "Enforcing\n" { + _ = cmd.RunDefaultBashCf("%s semanage port -a -t ssh_port_t -p tcp %s", sudo, req.NewValue) + } + + ruleItem := dto.PortRuleUpdate{ + OldRule: dto.PortRuleOperate{ + Operation: "remove", + Port: req.OldValue, + Protocol: "tcp", + Strategy: "accept", + }, + NewRule: dto.PortRuleOperate{ + Operation: "add", + Port: req.NewValue, + Protocol: "tcp", + Strategy: "accept", + }, + } + if err := NewIFirewallService().UpdatePortRule(ruleItem); err != nil { + global.LOG.Errorf("reset firewall rules %s -> %s failed, err: %v", req.OldValue, req.NewValue, err) + } + newPort, _ := strconv.Atoi(req.NewValue) + if err := updateLocalConn(uint(newPort)); err != nil { + global.LOG.Errorf("update local conn for terminal failed, err: %v", err) + } + + if err := updateSSHSocketFile(req.NewValue); err != nil { + global.LOG.Errorf("update port for ssh.socket failed, err: %v", err) + } + } + + _ = controller.HandleRestart(serviceName) + return nil +} + +func (u *SSHService) SyncRootCert() error { + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("load current user failed, err: %v", err) + } + sshDir := fmt.Sprintf("%s/.ssh", currentUser.HomeDir) + + fileList, err := os.ReadDir(sshDir) + if err != nil { + return err + } + var rootCerts []model.RootCert + fileMap := make(map[string]bool) + for _, item := range fileList { + if !item.IsDir() { + fileMap[item.Name()] = true + } + } + for item := range fileMap { + if !strings.HasSuffix(item, ".pub") { + continue + } + if !fileMap[strings.TrimSuffix(item, ".pub")] { + continue + } + cert := model.RootCert{Name: strings.TrimSuffix(item, ".pub"), PublicKeyPath: path.Join(sshDir, item), PrivateKeyPath: path.Join(sshDir, strings.TrimSuffix(item, ".pub"))} + pubItem, err := os.ReadFile(path.Join(sshDir, item)) + if err != nil { + global.LOG.Errorf("read pubic key of %s for sync failed, err: %v", item, err) + continue + } + cert.EncryptionMode = loadEncryptioMode(string(pubItem)) + rootCerts = append(rootCerts, cert) + } + return hostRepo.SyncCert(rootCerts) +} + +func (u *SSHService) CreateRootCert(req dto.RootCertOperate) error { + if cmd.CheckIllegal(req.EncryptionMode, req.PassPhrase) { + return buserr.New("ErrCmdIllegal") + } + certItem, _ := hostRepo.GetCert(repo.WithByName(req.Name)) + if certItem.ID != 0 { + return buserr.New("ErrRecordExist") + } + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("load current user failed, err: %v", err) + } + var cert model.RootCert + if err := copier.Copy(&cert, req); err != nil { + return err + } + privatePath := fmt.Sprintf("%s/.ssh/%s", currentUser.HomeDir, req.Name) + publicPath := fmt.Sprintf("%s/.ssh/%s.pub", currentUser.HomeDir, req.Name) + authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys" + + authFileItem, _ := os.ReadFile(authFilePath) + authFile := string(authFileItem) + if authFile != "" && !strings.HasSuffix(authFile, "\n") { + file, err := os.OpenFile(authFilePath, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + return err + } + defer file.Close() + if _, err := file.WriteString("\n"); err != nil { + return err + } + } + + if req.Mode == "input" || req.Mode == "import" { + if err := os.WriteFile(privatePath, []byte(req.PrivateKey), constant.FilePerm); err != nil { + return err + } + if err := os.WriteFile(publicPath, []byte(req.PublicKey), constant.FilePerm); err != nil { + return err + } + } else { + command := fmt.Sprintf("ssh-keygen -t %s -f %s/.ssh/%s -N ''", req.EncryptionMode, currentUser.HomeDir, req.Name) + if len(req.PassPhrase) != 0 { + command = fmt.Sprintf("ssh-keygen -t %s -P %s -f %s/.ssh/%s | echo y", req.EncryptionMode, req.PassPhrase, currentUser.HomeDir, req.Name) + } + if err := cmd.RunDefaultBashC(command); err != nil { + return fmt.Errorf("generate failed, %v", err) + } + } + + if err := cmd.RunDefaultBashCf("cat %s >> %s", publicPath, authFilePath); err != nil { + return fmt.Errorf("generate failed, %v", err) + } + + cert.PrivateKeyPath = privatePath + cert.PublicKeyPath = publicPath + if len(cert.PassPhrase) != 0 { + cert.PassPhrase, _ = encrypt.StringEncrypt(cert.PassPhrase) + } + return hostRepo.SaveCert(&cert) +} + +func (u *SSHService) EditRootCert(req dto.RootCertOperate) error { + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("load current user failed, err: %v", err) + } + certItem, _ := hostRepo.GetCert(repo.WithByID(req.ID)) + if certItem.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + oldPublicItem, err := os.ReadFile(certItem.PublicKeyPath) + if err != nil { + return err + } + + var cert model.RootCert + if err := copier.Copy(&cert, req); err != nil { + return err + } + cert.PrivateKeyPath = fmt.Sprintf("%s/.ssh/%s", currentUser.HomeDir, req.Name) + cert.PublicKeyPath = fmt.Sprintf("%s/.ssh/%s.pub", currentUser.HomeDir, req.Name) + if err := os.WriteFile(cert.PrivateKeyPath, []byte(req.PrivateKey), constant.FilePerm); err != nil { + return err + } + if err := os.WriteFile(cert.PublicKeyPath, []byte(req.PublicKey), constant.FilePerm); err != nil { + return err + } + + authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys" + authItem, err := os.ReadFile(authFilePath) + if err != nil { + return err + } + oldPublic := strings.ReplaceAll(string(oldPublicItem), "\n", "") + newPublic := strings.ReplaceAll(string(req.PublicKey), "\n", "") + lines := strings.Split(string(authItem), "\n") + var newFiles []string + for i := 0; i < len(lines); i++ { + if len(lines[i]) != 0 && lines[i] != oldPublic && lines[i] != newPublic { + newFiles = append(newFiles, lines[i]) + } + } + newFiles = append(newFiles, newPublic) + if err := os.WriteFile(authFilePath, []byte(strings.Join(newFiles, "\n")), constant.FilePerm); err != nil { + return fmt.Errorf("refresh authorized_keys failed, err: %v", err) + } + if len(cert.PassPhrase) != 0 { + cert.PassPhrase, _ = encrypt.StringEncrypt(cert.PassPhrase) + } + return hostRepo.SaveCert(&cert) +} + +func (u *SSHService) SearchRootCerts(req dto.SearchWithPage) (int64, interface{}, error) { + total, records, err := hostRepo.PageCert(req.Page, req.PageSize) + if err != nil { + return 0, nil, err + } + var datas []dto.RootCert + for i := 0; i < len(records); i++ { + publicItem, err := os.ReadFile(records[i].PublicKeyPath) + var publicBase64 string + if err == nil && len(publicItem) != 0 { + publicBase64 = base64.StdEncoding.EncodeToString(publicItem) + } + privateItem, _ := os.ReadFile(records[i].PrivateKeyPath) + var privateBase64 string + if err == nil && len(publicItem) != 0 { + privateBase64 = base64.StdEncoding.EncodeToString(privateItem) + } + passPhrase, _ := encrypt.StringDecryptWithBase64(records[i].PassPhrase) + datas = append(datas, dto.RootCert{ + ID: records[i].ID, + CreatedAt: records[i].CreatedAt, + Name: records[i].Name, + EncryptionMode: records[i].EncryptionMode, + PassPhrase: passPhrase, + PublicKey: publicBase64, + PrivateKey: privateBase64, + Description: records[i].Description, + }) + } + return total, datas, err +} + +func (u *SSHService) DeleteRootCerts(req dto.ForceDelete) error { + currentUser, err := user.Current() + if err != nil && !req.ForceDelete { + return fmt.Errorf("load current user failed, err: %v", err) + } + authFilePath := currentUser.HomeDir + "/.ssh/authorized_keys" + authItem, err := os.ReadFile(authFilePath) + if err != nil && !req.ForceDelete { + return err + } + for _, id := range req.IDs { + cert, _ := hostRepo.GetCert(repo.WithByID(id)) + if cert.ID == 0 { + if !req.ForceDelete { + return buserr.New("ErrRecordNotFound") + } else { + continue + } + } + publicItem, err := os.ReadFile(cert.PublicKeyPath) + if err != nil && !req.ForceDelete { + return err + } + newFile := bytes.ReplaceAll(authItem, publicItem, nil) + if err := os.WriteFile(authFilePath, newFile, constant.FilePerm); err != nil && !req.ForceDelete { + return fmt.Errorf("refresh authorized_keys failed, err: %v", err) + } + _ = os.Remove(cert.PublicKeyPath) + _ = os.Remove(cert.PrivateKeyPath) + if err := hostRepo.DeleteCert(repo.WithByID(id)); err != nil && !req.ForceDelete { + return err + } + } + + return nil +} + +type sshFileItem struct { + Name string + Year int +} + +func (u *SSHService) LoadLog(ctx *gin.Context, req dto.SearchSSHLog) (int64, []dto.SSHHistory, error) { + var fileList []sshFileItem + var data []dto.SSHHistory + baseDir := "/var/log" + fileItems, err := os.ReadDir(baseDir) + if err != nil { + return 0, data, err + } + for _, item := range fileItems { + if item.IsDir() || (!strings.HasPrefix(item.Name(), "secure") && !strings.HasPrefix(item.Name(), "auth")) { + continue + } + info, _ := item.Info() + itemPath := path.Join(baseDir, info.Name()) + if !strings.HasSuffix(item.Name(), ".gz") { + fileList = append(fileList, sshFileItem{Name: itemPath, Year: info.ModTime().Year()}) + continue + } + itemFileName := strings.TrimSuffix(itemPath, ".gz") + if _, err := os.Stat(itemFileName); err != nil && os.IsNotExist(err) { + if err := handleGunzip(itemPath); err == nil { + fileList = append(fileList, sshFileItem{Name: itemFileName, Year: info.ModTime().Year()}) + } + } + } + fileList = sortFileList(fileList) + + command := "" + if len(req.Info) != 0 { + command = fmt.Sprintf(" | grep '%s'", req.Info) + } + + showCountFrom := (req.Page - 1) * req.PageSize + showCountTo := req.Page * req.PageSize + nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + itemFailed, itemTotal := 0, 0 + for _, file := range fileList { + commandItem := "" + if strings.HasPrefix(path.Base(file.Name), "secure") { + switch req.Status { + case constant.StatusSuccess: + commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command) + case constant.StatusFailed: + commandItem = fmt.Sprintf("cat %s | grep -a 'Failed password for' %s", file.Name, command) + default: + commandItem = fmt.Sprintf("cat %s | grep -aE '(Failed password for|Accepted)' %s", file.Name, command) + } + } + if strings.HasPrefix(path.Base(file.Name), "auth.log") { + switch req.Status { + case constant.StatusSuccess: + commandItem = fmt.Sprintf("cat %s | grep -a Accepted %s", file.Name, command) + case constant.StatusFailed: + commandItem = fmt.Sprintf("cat %s | grep -aE 'Failed password for|Connection closed by authenticating user' %s", file.Name, command) + default: + commandItem = fmt.Sprintf("cat %s | grep -aE \"(Failed password for|Connection closed by authenticating user|Accepted)\" %s", file.Name, command) + } + } + dataItem, successCount, failedCount := loadSSHData(ctx, commandItem, showCountFrom, showCountTo, file.Year, nyc) + itemFailed += failedCount + itemTotal += successCount + failedCount + showCountFrom = showCountFrom - (successCount + failedCount) + if showCountTo != -1 { + showCountTo = showCountTo - (successCount + failedCount) + } + data = append(data, dataItem...) + } + + total := itemTotal + if req.Status == constant.StatusFailed { + total = itemFailed + } + if req.Status == constant.StatusSuccess { + total = itemTotal - itemFailed + } + return int64(total), data, nil +} + +func (u *SSHService) ExportLog(ctx *gin.Context, req dto.SearchSSHLog) (string, error) { + _, logs, err := u.LoadLog(ctx, req) + if err != nil { + return "", err + } + if len(logs) == 0 { + return "", buserr.New("ErrRecordNotFound") + } + tmpFileName := path.Join(global.Dir.TmpDir, "export/ssh-log", fmt.Sprintf("1panel-ssh-log-%s.csv", time.Now().Format(constant.DateTimeSlimLayout))) + if _, err := os.Stat(path.Dir(tmpFileName)); err != nil { + _ = os.MkdirAll(path.Dir(tmpFileName), constant.DirPerm) + } + if err := csvexport.ExportSSHLogs(tmpFileName, logs); err != nil { + return "", err + } + return tmpFileName, nil +} + +func (u *SSHService) LoadSSHFile(name string) (string, error) { + var fileName string + switch name { + case "authKeys": + currentUser, err := user.Current() + if err != nil { + return "", fmt.Errorf("load current user failed, err: %v", err) + } + fileName = currentUser.HomeDir + "/.ssh/authorized_keys" + case "sshdConf": + fileName = "/etc/ssh/sshd_config" + default: + return "", buserr.WithName("ErrNotSupportType", name) + } + if _, err := os.Stat(fileName); err != nil { + return "", buserr.WithErr("ErrHttpReqNotFound", err) + } + content, err := os.ReadFile(fileName) + if err != nil { + return "", err + } + return string(content), nil +} + +func (u *SSHService) UpdateByFile(req dto.SettingUpdate) error { + var fileName string + switch req.Key { + case "authKeys": + currentUser, err := user.Current() + if err != nil { + return fmt.Errorf("load current user failed, err: %v", err) + } + fileName = currentUser.HomeDir + "/.ssh/authorized_keys" + case "sshdConf": + fileName = "/etc/ssh/sshd_config" + default: + return buserr.WithName("ErrNotSupportType", req.Key) + } + file, err := os.OpenFile(fileName, os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer file.Close() + if _, err = file.WriteString(req.Value); err != nil { + return err + } + if req.Key == "authKeys" { + return nil + } + serviceName, err := loadServiceName() + if err != nil { + return err + } + _ = controller.HandleRestart(serviceName) + return nil +} + +func sortFileList(fileNames []sshFileItem) []sshFileItem { + if len(fileNames) < 2 { + return fileNames + } + if strings.HasPrefix(path.Base(fileNames[0].Name), "secure") { + var itemFile []sshFileItem + sort.Slice(fileNames, func(i, j int) bool { + return fileNames[i].Name > fileNames[j].Name + }) + itemFile = append(itemFile, fileNames[len(fileNames)-1]) + itemFile = append(itemFile, fileNames[:len(fileNames)-1]...) + return itemFile + } + sort.Slice(fileNames, func(i, j int) bool { + return fileNames[i].Name < fileNames[j].Name + }) + return fileNames +} + +func updateSSHConf(oldFiles []string, param string, value string) []string { + var valueItems []string + if param != "ListenAddress" { + valueItems = append(valueItems, value) + } else { + if value != "" { + valueItems = strings.Split(value, ",") + } + } + var newFiles []string + for _, line := range oldFiles { + lineItem := strings.TrimSpace(line) + if (strings.HasPrefix(lineItem, param) || strings.HasPrefix(lineItem, fmt.Sprintf("#%s", param))) && len(valueItems) != 0 { + newFiles = append(newFiles, fmt.Sprintf("%s %s", param, valueItems[0])) + valueItems = valueItems[1:] + continue + } + if strings.HasPrefix(lineItem, param) && len(valueItems) == 0 { + newFiles = append(newFiles, fmt.Sprintf("#%s", line)) + continue + } + newFiles = append(newFiles, line) + } + if len(valueItems) != 0 { + for _, item := range valueItems { + newFiles = append(newFiles, fmt.Sprintf("%s %s", param, item)) + } + } + return newFiles +} + +func loadSSHData(ctx *gin.Context, command string, showCountFrom, showCountTo, currentYear int, nyc *time.Location) ([]dto.SSHHistory, int, int) { + var ( + datas []dto.SSHHistory + successCount int + failedCount int + ) + getLoc, err := geo.NewGeo() + if err != nil { + return datas, 0, 0 + } + stdout, err := cmd.RunDefaultWithStdoutBashC(command) + if err != nil { + return datas, 0, 0 + } + lines := strings.Split(stdout, "\n") + for i := len(lines) - 1; i >= 0; i-- { + var itemData dto.SSHHistory + switch { + case strings.Contains(lines[i], "Failed password for"): + itemData = loadFailedSecureDatas(lines[i]) + if checkIsStandard(itemData) { + if successCount+failedCount >= showCountFrom && (showCountTo == -1 || successCount+failedCount < showCountTo) { + itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx)) + itemData.Date = loadDate(currentYear, itemData.DateStr, nyc) + datas = append(datas, itemData) + } + failedCount++ + } + case strings.Contains(lines[i], "Connection closed by authenticating user"): + itemData = loadFailedAuthDatas(lines[i]) + if checkIsStandard(itemData) { + if successCount+failedCount >= showCountFrom && (showCountTo == -1 || successCount+failedCount < showCountTo) { + itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx)) + itemData.Date = loadDate(currentYear, itemData.DateStr, nyc) + datas = append(datas, itemData) + } + failedCount++ + } + case strings.Contains(lines[i], "Accepted "): + itemData = loadSuccessDatas(lines[i]) + if checkIsStandard(itemData) { + if successCount+failedCount >= showCountFrom && (showCountTo == -1 || successCount+failedCount < showCountTo) { + itemData.Area, _ = geo.GetIPLocation(getLoc, itemData.Address, common.GetLang(ctx)) + itemData.Date = loadDate(currentYear, itemData.DateStr, nyc) + datas = append(datas, itemData) + } + successCount++ + } + } + } + return datas, successCount, failedCount +} + +func loadSuccessDatas(line string) dto.SSHHistory { + var data dto.SSHHistory + parts := strings.Fields(line) + index, dataStr := analyzeDateStr(parts) + if dataStr == "" { + return data + } + data.DateStr = dataStr + data.AuthMode = parts[4+index] + data.User = parts[6+index] + data.Address = parts[8+index] + data.Port = parts[10+index] + data.Status = constant.StatusSuccess + return data +} +func loadFailedAuthDatas(line string) dto.SSHHistory { + var data dto.SSHHistory + parts := strings.Fields(line) + index, dataStr := analyzeDateStr(parts) + if dataStr == "" { + return data + } + data.DateStr = dataStr + switch index { + case 1: + data.User = parts[9] + case 2: + data.User = parts[10] + default: + data.User = parts[7] + } + data.AuthMode = parts[6+index] + data.Address = parts[9+index] + data.Port = parts[11+index] + data.Status = constant.StatusFailed + if strings.Contains(line, ": ") { + data.Message = strings.Split(line, ": ")[1] + } + return data +} +func loadFailedSecureDatas(line string) dto.SSHHistory { + var data dto.SSHHistory + parts := strings.Fields(line) + index, dataStr := analyzeDateStr(parts) + if dataStr == "" { + return data + } + data.DateStr = dataStr + if strings.Contains(line, " invalid ") { + data.AuthMode = parts[4+index] + index += 2 + } else { + data.AuthMode = parts[4+index] + } + data.User = parts[6+index] + data.Address = parts[8+index] + data.Port = parts[10+index] + data.Status = constant.StatusFailed + if strings.Contains(line, ": ") { + data.Message = strings.Split(line, ": ")[1] + } + return data +} + +func checkIsStandard(item dto.SSHHistory) bool { + if len(item.Address) == 0 || net.ParseIP(item.Address) == nil { + return false + } + portItem, _ := strconv.Atoi(item.Port) + return portItem > 0 && portItem < 65536 +} + +func handleGunzip(path string) error { + if err := cmd.RunDefaultBashCf("gunzip %s", path); err != nil { + return err + } + return nil +} + +func loadServiceName() (string, error) { + if exist, _ := controller.CheckExist("sshd"); exist { + return "sshd", nil + } else if exist, _ := controller.CheckExist("ssh"); exist { + return "ssh", nil + } + return "", errors.New("The ssh or sshd service is unavailable") +} + +func loadDate(currentYear int, DateStr string, nyc *time.Location) time.Time { + itemDate, err := time.ParseInLocation("2006 Jan 2 15:04:05", fmt.Sprintf("%d %s", currentYear, DateStr), nyc) + if err != nil { + itemDate, _ = time.ParseInLocation("2006 Jan 2 15:04:05", DateStr, nyc) + } + return itemDate +} + +func analyzeDateStr(parts []string) (int, string) { + t, err := time.Parse(time.RFC3339Nano, parts[0]) + if err == nil { + if len(parts) < 12 { + return 0, "" + } + return 0, t.Format("2006 Jan 2 15:04:05") + } + t, err = time.Parse(constant.DateTimeLayout, fmt.Sprintf("%s %s", parts[0], parts[1])) + if err == nil { + if len(parts) < 14 { + return 0, "" + } + return 1, t.Format("2006 Jan 2 15:04:05") + } + + if len(parts) < 14 { + return 0, "" + } + return 2, fmt.Sprintf("%s %s %s", parts[0], parts[1], parts[2]) +} + +func loadEncryptioMode(content string) string { + if strings.HasPrefix(content, "ssh-rsa") { + return "rsa" + } + if strings.HasPrefix(content, "ssh-ed25519") { + return "ed25519" + } + if strings.HasPrefix(content, "ssh-ecdsa") { + return "ecdsa" + } + if strings.HasPrefix(content, "ssh-dsa") { + return "dsa" + } + return "" +} + +func updateLocalConn(newPort uint) error { + conn, _ := settingRepo.GetValueByKey("LocalSSHConn") + if len(conn) == 0 { + return nil + } + connItem, err := encrypt.StringDecrypt(conn) + if err != nil { + return err + } + var data dto.SSHConnData + if err := json.Unmarshal([]byte(connItem), &data); err != nil { + return err + } + data.Port = newPort + connNew, err := json.Marshal(data) + if err != nil { + return err + } + connNewItem, err := encrypt.StringEncrypt(string(connNew)) + if err != nil { + return err + } + return settingRepo.Update("LocalSSHConn", connNewItem) +} + +func updateSSHSocketFile(newPort string) error { + active, _ := controller.CheckActive("ssh.socket") + if !active { + return nil + } + filepath := "/usr/lib/systemd/system/ssh.socket" + file, err := os.ReadFile(filepath) + if err != nil { + return err + } + lines := strings.Split(string(file), "\n") + for i := 0; i < len(lines); i++ { + if strings.HasPrefix(lines[i], "ListenStream=") { + parts := strings.Split(lines[i], ":") + if len(parts) > 1 { + lines[i] = strings.ReplaceAll(lines[i], parts[len(parts)-1], newPort) + continue + } + parts = strings.Split(lines[i], "=") + if len(parts) > 1 { + lines[i] = strings.ReplaceAll(lines[i], parts[len(parts)-1], newPort) + } + } + } + fileItem, err := os.OpenFile(filepath, os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer fileItem.Close() + if _, err = fileItem.WriteString(strings.Join(lines, "\n")); err != nil { + return err + } + _ = controller.Reload() + _ = controller.HandleRestart("ssh.socket") + return nil +} + +func loadSSHPort() string { + port := "22" + sshConf, err := os.ReadFile(sshPath) + if err != nil { + return port + } + lines := strings.Split(string(sshConf), "\n") + for _, line := range lines { + if strings.HasPrefix(line, "Port ") { + portStr := strings.ReplaceAll(line, "Port ", "") + portItem, _ := strconv.Atoi(portStr) + if portItem > 0 && portItem < 65535 { + return portStr + } + } + } + return port +} diff --git a/agent/app/service/system.go b/agent/app/service/system.go new file mode 100644 index 0000000..9b0ca45 --- /dev/null +++ b/agent/app/service/system.go @@ -0,0 +1,29 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "os/exec" +) + +type SystemService struct{} + +type ISystemService interface { + IsComponentExist(name string) response.ComponentInfo +} + +func NewISystemService() ISystemService { + return &SystemService{} +} + +func (s *SystemService) IsComponentExist(name string) response.ComponentInfo { + info := response.ComponentInfo{} + path, err := exec.LookPath(name) + if err != nil { + info.Exists = false + info.Error = err.Error() + return info + } + info.Exists = true + info.Path = path + return info +} diff --git a/agent/app/service/task.go b/agent/app/service/task.go new file mode 100644 index 0000000..973a8f2 --- /dev/null +++ b/agent/app/service/task.go @@ -0,0 +1,52 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/repo" +) + +type TaskLogService struct{} + +type ITaskLogService interface { + Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) + SyncForRestart() error + CountExecutingTask() (int64, error) +} + +func NewITaskService() ITaskLogService { + return &TaskLogService{} +} + +func (u *TaskLogService) Page(req dto.SearchTaskLogReq) (int64, []dto.TaskDTO, error) { + opts := []repo.DBOption{ + repo.WithOrderBy("created_at desc"), + } + if req.Status != "" { + opts = append(opts, repo.WithByStatus(req.Status)) + } + if req.Type != "" { + opts = append(opts, repo.WithByType(req.Type)) + } + + total, tasks, err := taskRepo.Page( + req.Page, + req.PageSize, + opts..., + ) + var items []dto.TaskDTO + for _, t := range tasks { + item := dto.TaskDTO{ + Task: t, + } + items = append(items, item) + } + return total, items, err +} + +func (u *TaskLogService) SyncForRestart() error { + return taskRepo.UpdateRunningTaskToFailed() +} + +func (u *TaskLogService) CountExecutingTask() (int64, error) { + return taskRepo.CountExecutingTask() +} diff --git a/agent/app/service/tensorrt_llm.go b/agent/app/service/tensorrt_llm.go new file mode 100644 index 0000000..e944d83 --- /dev/null +++ b/agent/app/service/tensorrt_llm.go @@ -0,0 +1,441 @@ +package service + +import ( + "fmt" + "os" + "path" + "path/filepath" + "strconv" + "strings" + + "github.com/subosito/gotenv" + "gopkg.in/yaml.v3" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/cmd/server/ai" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/compose" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/env" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/re" +) + +type TensorRTLLMService struct{} + +type ITensorRTLLMService interface { + Page(req request.TensorRTLLMSearch) response.TensorRTLLMsRes + Create(create request.TensorRTLLMCreate) error + Update(req request.TensorRTLLMUpdate) error + Delete(id uint) error + Operate(req request.TensorRTLLMOperate) error +} + +func NewITensorRTLLMService() ITensorRTLLMService { + return &TensorRTLLMService{} +} + +func (t TensorRTLLMService) Page(req request.TensorRTLLMSearch) response.TensorRTLLMsRes { + var ( + res response.TensorRTLLMsRes + items []response.TensorRTLLMDTO + ) + + total, data, _ := tensorrtLLMRepo.Page(req.PageInfo.Page, req.PageInfo.PageSize) + for _, item := range data { + _ = syncTensorRTLLMContainerStatus(&item) + serverDTO := response.TensorRTLLMDTO{ + TensorRTLLM: item, + } + envs, _ := gotenv.Unmarshal(item.Env) + serverDTO.Version = envs["VERSION"] + serverDTO.ModelDir = envs["MODEL_PATH"] + serverDTO.Dir = path.Join(global.Dir.TensorRTLLMDir, item.Name) + serverDTO.Image = envs["IMAGE"] + serverDTO.Command = getCommand(item.Env) + + for k, v := range envs { + if strings.Contains(k, "CONTAINER_PORT") || strings.Contains(k, "HOST_PORT") { + if strings.Contains(k, "CONTAINER_PORT") { + matches := re.GetRegex(re.TrailingDigitsPattern).FindStringSubmatch(k) + if len(matches) < 2 { + continue + } + containerPort, err := strconv.Atoi(v) + if err != nil { + continue + } + hostPort, err := strconv.Atoi(envs[fmt.Sprintf("HOST_PORT_%s", matches[1])]) + if err != nil { + continue + } + hostIP := envs[fmt.Sprintf("HOST_IP_%s", matches[1])] + if hostIP == "" { + hostIP = "0.0.0.0" + } + serverDTO.ExposedPorts = append(serverDTO.ExposedPorts, request.ExposedPort{ + ContainerPort: containerPort, + HostPort: hostPort, + HostIP: hostIP, + }) + } + } + } + + composeByte, err := files.NewFileOp().GetContent(path.Join(global.Dir.TensorRTLLMDir, item.Name, "docker-compose.yml")) + if err == nil { + serverDTO.Environments, _ = getDockerComposeEnvironments(composeByte) + } + volumes, err := getDockerComposeVolumes(composeByte) + if err == nil { + var defaultVolumes = map[string]string{ + "${MODEL_PATH}": "${MODEL_PATH}", + } + for _, volume := range volumes { + exist := false + for key, value := range defaultVolumes { + if key == volume.Source && value == volume.Target { + exist = true + break + } + } + if !exist { + serverDTO.Volumes = append(serverDTO.Volumes, volume) + } + } + } + items = append(items, serverDTO) + } + res.Total = total + res.Items = items + return res +} + +func handleLLMParams(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) error { + var composeContent []byte + if llm.ID == 0 { + composeContent = ai.DefaultTensorrtLLMCompose + } else { + composeContent = []byte(llm.DockerCompose) + } + composeMap := make(map[string]interface{}) + if err := yaml.Unmarshal(composeContent, &composeMap); err != nil { + return err + } + services, serviceValid := composeMap["services"].(map[string]interface{}) + if !serviceValid { + return buserr.New("ErrFileParse") + } + serviceName := "" + serviceValue := make(map[string]interface{}) + + if llm.ID > 0 { + serviceName = llm.Name + serviceValue = services[serviceName].(map[string]interface{}) + } else { + for name, service := range services { + serviceName = name + serviceValue = service.(map[string]interface{}) + break + } + delete(services, serviceName) + } + + delete(serviceValue, "ports") + if len(create.ExposedPorts) > 0 { + var ports []interface{} + for i := range create.ExposedPorts { + containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i) + hostPortStr := fmt.Sprintf("HOST_PORT_%d", i) + hostIPStr := fmt.Sprintf("HOST_IP_%d", i) + ports = append(ports, fmt.Sprintf("${%s}:${%s}:${%s}", hostIPStr, hostPortStr, containerPortStr)) + } + serviceValue["ports"] = ports + } + + delete(serviceValue, "environment") + var environments []interface{} + environments = append(environments, fmt.Sprintf("MODEL_PATH=%s", create.ModelDir)) + for _, e := range create.Environments { + environments = append(environments, fmt.Sprintf("%s=%s", e.Key, e.Value)) + } + serviceValue["environment"] = environments + + var volumes []interface{} + var defaultVolumes = map[string]string{ + "${MODEL_PATH}": "${MODEL_PATH}", + } + for k, v := range defaultVolumes { + volumes = append(volumes, fmt.Sprintf("%s:%s", k, v)) + } + for _, volume := range create.Volumes { + volumes = append(volumes, fmt.Sprintf("%s:%s", volume.Source, volume.Target)) + } + serviceValue["volumes"] = volumes + + services[llm.Name] = serviceValue + composeByte, err := yaml.Marshal(composeMap) + if err != nil { + return err + } + llm.DockerCompose = string(composeByte) + return nil +} + +func handleLLMEnv(llm *model.TensorRTLLM, create request.TensorRTLLMCreate) gotenv.Env { + envMap := make(gotenv.Env) + envMap["CONTAINER_NAME"] = create.ContainerName + envMap["MODEL_PATH"] = create.ModelDir + envMap["VERSION"] = create.Version + envMap["IMAGE"] = create.Image + envMap["COMMAND"] = create.Command + for i, port := range create.ExposedPorts { + containerPortStr := fmt.Sprintf("CONTAINER_PORT_%d", i) + hostPortStr := fmt.Sprintf("HOST_PORT_%d", i) + hostIPStr := fmt.Sprintf("HOST_IP_%d", i) + envMap[containerPortStr] = strconv.Itoa(port.ContainerPort) + envMap[hostPortStr] = strconv.Itoa(port.HostPort) + envMap[hostIPStr] = port.HostIP + } + orders := []string{"MODEL_PATH", "COMMAND"} + envStr, _ := env.MarshalWithOrder(envMap, orders) + llm.Env = envStr + return envMap +} + +func (t TensorRTLLMService) Create(create request.TensorRTLLMCreate) error { + servers, _ := tensorrtLLMRepo.List() + for _, server := range servers { + if server.ContainerName == create.ContainerName { + return buserr.New("ErrContainerName") + } + if server.Name == create.Name { + return buserr.New("ErrNameIsExist") + } + } + for _, export := range create.ExposedPorts { + if err := checkPortExist(export.HostPort); err != nil { + return err + } + } + if err := checkContainerName(create.ContainerName); err != nil { + return err + } + + tensorrtLLMDir := path.Join(global.Dir.TensorRTLLMDir, create.Name) + filesOp := files.NewFileOp() + if !filesOp.Stat(tensorrtLLMDir) { + _ = filesOp.CreateDir(tensorrtLLMDir, 0644) + } + if create.ModelSpeedup { + if err := handleModelArchive(create.ModelType, create.ModelDir); err != nil { + return err + } + } + tensorrtLLM := &model.TensorRTLLM{ + Name: create.Name, + ContainerName: create.ContainerName, + Status: constant.StatusStarting, + ModelType: create.ModelType, + ModelSpeedup: create.ModelSpeedup, + } + + if err := handleLLMParams(tensorrtLLM, create); err != nil { + return err + } + envMap := handleLLMEnv(tensorrtLLM, create) + llmDir := path.Join(global.Dir.TensorRTLLMDir, create.Name) + envPath := path.Join(llmDir, ".env") + if err := env.WriteWithOrder(envMap, envPath, []string{"MODEL_PATH", "COMMAND"}); err != nil { + return err + } + dockerComposePath := path.Join(llmDir, "docker-compose.yml") + if err := filesOp.SaveFile(dockerComposePath, tensorrtLLM.DockerCompose, 0644); err != nil { + return err + } + tensorrtLLM.Status = constant.StatusStarting + + if err := tensorrtLLMRepo.Create(tensorrtLLM); err != nil { + return err + } + go startTensorRTLLM(tensorrtLLM) + return nil +} + +func (t TensorRTLLMService) Update(req request.TensorRTLLMUpdate) error { + tensorrtLLM, err := tensorrtLLMRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + if tensorrtLLM.ContainerName != req.ContainerName { + if err := checkContainerName(req.ContainerName); err != nil { + return err + } + } + tensorrtLLM.ModelType = req.ModelType + tensorrtLLM.ModelSpeedup = req.ModelSpeedup + tensorrtLLM.ContainerName = req.ContainerName + + if err := handleLLMParams(tensorrtLLM, req.TensorRTLLMCreate); err != nil { + return err + } + + envMap := handleLLMEnv(tensorrtLLM, req.TensorRTLLMCreate) + envStr, _ := gotenv.Marshal(envMap) + tensorrtLLM.Env = envStr + llmDir := path.Join(global.Dir.TensorRTLLMDir, tensorrtLLM.Name) + envPath := path.Join(llmDir, ".env") + if err := env.WriteWithOrder(envMap, envPath, []string{"MODEL_PATH", "COMMAND"}); err != nil { + return err + } + dockerComposePath := path.Join(llmDir, "docker-compose.yml") + if err := files.NewFileOp().SaveFile(dockerComposePath, tensorrtLLM.DockerCompose, 0644); err != nil { + return err + } + tensorrtLLM.Status = constant.StatusStarting + if err := tensorrtLLMRepo.Save(tensorrtLLM); err != nil { + return err + } + go startTensorRTLLM(tensorrtLLM) + return nil +} + +func (t TensorRTLLMService) Delete(id uint) error { + tensorrtLLM, err := tensorrtLLMRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return err + } + composePath := path.Join(global.Dir.TensorRTLLMDir, tensorrtLLM.Name, "docker-compose.yml") + _, _ = compose.Down(composePath) + _ = files.NewFileOp().DeleteDir(path.Join(global.Dir.TensorRTLLMDir, tensorrtLLM.Name)) + return tensorrtLLMRepo.DeleteBy(repo.WithByID(id)) +} + +func (t TensorRTLLMService) Operate(req request.TensorRTLLMOperate) error { + tensorrtLLM, err := tensorrtLLMRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + composePath := path.Join(global.Dir.TensorRTLLMDir, tensorrtLLM.Name, "docker-compose.yml") + var out string + switch req.Operate { + case "start": + out, err = compose.Up(composePath) + tensorrtLLM.Status = constant.StatusRunning + case "stop": + out, err = compose.Down(composePath) + tensorrtLLM.Status = constant.StatusStopped + case "restart": + out, err = compose.Restart(composePath) + tensorrtLLM.Status = constant.StatusRunning + } + if err != nil { + tensorrtLLM.Status = constant.StatusError + tensorrtLLM.Message = out + } + return tensorrtLLMRepo.Save(tensorrtLLM) +} + +func startTensorRTLLM(tensorrtLLM *model.TensorRTLLM) { + composePath := path.Join(global.Dir.TensorRTLLMDir, tensorrtLLM.Name, "docker-compose.yml") + if tensorrtLLM.Status != constant.StatusNormal { + _, _ = compose.Down(composePath) + } + if out, err := compose.Up(composePath); err != nil { + tensorrtLLM.Status = constant.StatusError + tensorrtLLM.Message = out + } else { + tensorrtLLM.Status = constant.StatusRunning + tensorrtLLM.Message = "" + } + _ = syncTensorRTLLMContainerStatus(tensorrtLLM) +} + +func syncTensorRTLLMContainerStatus(tensorrtLLM *model.TensorRTLLM) error { + containerNames := []string{tensorrtLLM.ContainerName} + cli, err := docker.NewClient() + if err != nil { + return err + } + defer cli.Close() + containers, err := cli.ListContainersByName(containerNames) + if err != nil { + return err + } + if len(containers) == 0 { + if tensorrtLLM.Status == constant.StatusStarting { + return nil + } + tensorrtLLM.Status = constant.StatusStopped + return tensorrtLLMRepo.Save(tensorrtLLM) + } + container := containers[0] + switch container.State { + case "exited": + tensorrtLLM.Status = constant.StatusError + case "running": + tensorrtLLM.Status = constant.StatusRunning + case "paused": + tensorrtLLM.Status = constant.StatusStopped + case "restarting": + tensorrtLLM.Status = constant.StatusRestarting + default: + if tensorrtLLM.Status != constant.StatusStarting { + tensorrtLLM.Status = constant.StatusStopped + } + } + return tensorrtLLMRepo.Save(tensorrtLLM) +} + +func findModelArchive(modelType string) (string, error) { + const baseDir = "/home/models" + prefix := fmt.Sprintf("FusionXplay_%s_Accelerator", modelType) + + entries, err := os.ReadDir(baseDir) + if err != nil { + return "", fmt.Errorf("failed to read %s: %w", baseDir, err) + } + + for _, entry := range entries { + if entry.IsDir() { + continue + } + name := entry.Name() + if strings.HasPrefix(name, prefix) && strings.HasSuffix(name, ".tar.gz") { + return filepath.Join(baseDir, name), nil + } + } + + return "", fmt.Errorf("no FusionXplay_%s_Accelerator*.tar.gz found in /home/models", modelType) +} + +func handleModelArchive(modelType string, modelDir string) error { + filePath, err := findModelArchive(modelType) + if err != nil { + return err + } + fileOp := files.NewFileOp() + if err = fileOp.TarGzExtractPro(filePath, modelDir, ""); err != nil { + return err + } + if err = fileOp.ChmodR(path.Join(modelDir, "fusionxpark_accelerator"), 0755, false); err != nil { + return err + } + return nil +} + +func getCommand(envStr string) string { + lines := strings.Split(envStr, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "COMMAND=") { + return strings.TrimPrefix(line, "COMMAND=") + } + } + return "" +} diff --git a/agent/app/service/website.go b/agent/app/service/website.go new file mode 100644 index 0000000..67d02c7 --- /dev/null +++ b/agent/app/service/website.go @@ -0,0 +1,2390 @@ +package service + +import ( + "bytes" + "context" + "encoding/base64" + "errors" + "fmt" + "net" + "os" + "path" + "reflect" + "strconv" + "strings" + "syscall" + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/docker" + + "github.com/1Panel-dev/1Panel/agent/app/task" + + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/jinzhu/copier" + + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/spf13/afero" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" + "github.com/1Panel-dev/1Panel/agent/utils/re" +) + +type WebsiteService struct { +} + +type IWebsiteService interface { + PageWebsite(req request.WebsiteSearch) (int64, []response.WebsiteRes, error) + GetWebsites() ([]response.WebsiteDTO, error) + CreateWebsite(create request.WebsiteCreate) error + OpWebsite(req request.WebsiteOp) error + GetWebsiteOptions(req request.WebsiteOptionReq) ([]response.WebsiteOption, error) + UpdateWebsite(req request.WebsiteUpdate) error + DeleteWebsite(req request.WebsiteDelete) error + GetWebsite(id uint) (response.WebsiteDTO, error) + + ChangePHPVersion(req request.WebsitePHPVersionReq) error + OperateCrossSiteAccess(req request.CrossSiteAccessOp) error + ExecComposer(req request.ExecComposerReq) error + ChangeGroup(group, newGroup uint) error + ChangeDefaultServer(id uint) error + PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) + OpWebsiteLog(req request.WebsiteLogReq) (*response.WebsiteLog, error) + UpdateStream(req request.StreamUpdate) error + + GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error) + UpdateNginxConfigByScope(req request.NginxConfigUpdate) error + GetWebsiteNginxConfig(websiteId uint, configType string) (*response.FileInfo, error) + UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error + + GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error) + OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error) + + LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) + UpdateSiteDir(req request.WebsiteUpdateDir) error + UpdateSitePermission(req request.WebsiteUpdateDirPermission) error + + UpdateCors(req request.CorsConfigReq) error + GetCors(websiteID uint) (*request.CorsConfig, error) + + GetAntiLeech(id uint) (*response.NginxAntiLeechRes, error) + UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error) + + OperateRedirect(req request.NginxRedirectReq) (err error) + GetRedirect(id uint) (res []response.NginxRedirectConfig, err error) + UpdateRedirectFile(req request.NginxRedirectUpdate) (err error) + + UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error + GetDefaultHtml(resourceType string) (*response.WebsiteHtmlRes, error) + + SetRealIPConfig(req request.WebsiteRealIP) error + GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error) + + GetWebsiteResource(websiteID uint) ([]response.Resource, error) + ListDatabases() ([]response.Database, error) + ChangeDatabase(req request.ChangeDatabase) error + + GetLoadBalances(id uint) ([]dto.NginxUpstream, error) + CreateLoadBalance(req request.WebsiteLBCreate) error + DeleteLoadBalance(req request.WebsiteLBDelete) error + UpdateLoadBalance(req request.WebsiteLBUpdate) error + UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error + + OperateProxy(req request.WebsiteProxyConfig) (err error) + GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) + UpdateProxyFile(req request.NginxProxyUpdate) (err error) + UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error) + GetProxyCache(id uint) (res response.NginxProxyCache, err error) + ClearProxyCache(req request.NginxCommonReq) error + DeleteProxy(req request.WebsiteProxyDel) (err error) + + CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) + GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) + DeleteWebsiteDomain(domainId uint) error + UpdateWebsiteDomain(req request.WebsiteDomainUpdate) error + + GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) + UpdateRewriteConfig(req request.NginxRewriteUpdate) error + OperateCustomRewrite(req request.CustomRewriteOperate) error + ListCustomRewrite() ([]string, error) + + GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) + UpdateAuthBasic(req request.NginxAuthUpdate) (err error) + GetPathAuthBasics(req request.NginxAuthReq) (res []response.NginxPathAuthRes, err error) + UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error + + BatchOpWebsite(req request.BatchWebsiteOp) error + BatchSetGroup(req request.BatchWebsiteGroup) error + BatchSetHttps(ctx context.Context, req request.BatchWebsiteHttps) error +} + +func NewIWebsiteService() IWebsiteService { + return &WebsiteService{} +} + +func (w WebsiteService) PageWebsite(req request.WebsiteSearch) (int64, []response.WebsiteRes, error) { + var ( + websiteDTOs []response.WebsiteRes + opts []repo.DBOption + ) + opts = append(opts, repo.WithOrderRuleBy(req.OrderBy, req.Order), repo.WithOrderRuleBy("updated_at", "descending")) + if req.Name != "" { + domains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithDomainLike(req.Name)) + if len(domains) > 0 { + var websiteIds []uint + for _, domain := range domains { + websiteIds = append(websiteIds, domain.WebsiteID) + } + opts = append(opts, repo.WithByIDs(websiteIds)) + } else { + opts = append(opts, websiteRepo.WithDomainLike(req.Name)) + } + } + if req.WebsiteGroupID != 0 { + opts = append(opts, websiteRepo.WithGroupID(req.WebsiteGroupID)) + } + if req.Type != "" { + opts = append(opts, websiteRepo.WithType(req.Type)) + } + total, websites, err := websiteRepo.Page(req.Page, req.PageSize, opts...) + if err != nil { + return 0, nil, err + } + for _, web := range websites { + var ( + appName string + runtimeName string + runtimeType string + appInstallID uint + ) + switch web.Type { + case constant.Deployment: + appInstall, err := appInstallRepo.GetFirst(repo.WithByID(web.AppInstallID)) + if err != nil { + return 0, nil, err + } + appName = appInstall.Name + appInstallID = appInstall.ID + case constant.Runtime: + runtime, _ := runtimeRepo.GetFirst(context.Background(), repo.WithByID(web.RuntimeID)) + if runtime != nil { + runtimeName = runtime.Name + runtimeType = runtime.Type + } + } + sitePath := GetSitePath(web, SiteDir) + + siteDTO := response.WebsiteRes{ + ID: web.ID, + CreatedAt: web.CreatedAt, + Protocol: web.Protocol, + PrimaryDomain: web.PrimaryDomain, + Type: web.Type, + Remark: web.Remark, + Status: web.Status, + Alias: web.Alias, + AppName: appName, + ExpireDate: web.ExpireDate, + SSLExpireDate: web.WebsiteSSL.ExpireDate, + SSLStatus: checkSSLStatus(web.WebsiteSSL.ExpireDate), + RuntimeName: runtimeName, + SitePath: sitePath, + AppInstallID: appInstallID, + RuntimeType: runtimeType, + Favorite: web.Favorite, + IPV6: web.IPV6, + } + + if siteDTO.Type == constant.Subsite { + parentWeb, _ := websiteRepo.GetFirst(repo.WithByID(web.ParentWebsiteID)) + if parentWeb.ID != 0 { + siteDTO.ParentSite = parentWeb.PrimaryDomain + } + } + + sites, _ := websiteRepo.List(websiteRepo.WithParentID(web.ID)) + if len(sites) > 0 { + for _, site := range sites { + siteDTO.ChildSites = append(siteDTO.ChildSites, site.PrimaryDomain) + } + } + websiteDTOs = append(websiteDTOs, siteDTO) + } + return total, websiteDTOs, nil +} + +func (w WebsiteService) GetWebsites() ([]response.WebsiteDTO, error) { + var websiteDTOs []response.WebsiteDTO + websites, _ := websiteRepo.List(repo.WithOrderRuleBy("primary_domain", "ascending")) + for _, web := range websites { + res := response.WebsiteDTO{ + Website: web, + } + websiteDTOs = append(websiteDTOs, res) + } + return websiteDTOs, nil +} + +func (w WebsiteService) CreateWebsite(create request.WebsiteCreate) (err error) { + alias := create.Alias + if alias == "default" { + return buserr.New("ErrDefaultAlias") + } + if common.ContainsChinese(alias) { + alias, err = common.PunycodeEncode(alias) + if err != nil { + return + } + } + if exist, _ := websiteRepo.GetBy(websiteRepo.WithAlias(alias)); len(exist) > 0 { + return buserr.New("ErrAliasIsExist") + } + if len(create.FtpPassword) != 0 { + pass, err := base64.StdEncoding.DecodeString(create.FtpPassword) + if err != nil { + return err + } + create.FtpPassword = string(pass) + } + + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + defaultHttpPort := nginxInstall.HttpPort + defaultDate, _ := time.Parse(constant.DateLayout, constant.WebsiteDefaultExpireDate) + + website := &model.Website{ + Type: create.Type, + Alias: alias, + Remark: create.Remark, + Status: constant.WebRunning, + ExpireDate: defaultDate, + WebsiteGroupID: create.WebsiteGroupID, + Proxy: create.Proxy, + SiteDir: "/", + AccessLog: true, + ErrorLog: true, + IPV6: create.IPV6, + } + + var ( + domains []model.WebsiteDomain + appInstall *model.AppInstall + runtime *model.Runtime + primaryDomain string + ) + if website.Type == constant.Stream { + if create.StreamConfig.StreamPorts == "" { + return buserr.New("ErrTypePortRange") + } + website.PrimaryDomain = create.Name + website.Protocol = constant.ProtocolStream + website.StreamPorts = create.StreamConfig.StreamPorts + ports := strings.Split(create.StreamConfig.StreamPorts, ",") + for _, port := range ports { + portNum, _ := strconv.Atoi(port) + if err = checkWebsitePort(nginxInstall.HttpsPort, portNum, website.Type); err != nil { + return err + } + } + } else { + domains, _, _, err = getWebsiteDomains(create.Domains, defaultHttpPort, nginxInstall.HttpsPort, 0) + if err != nil { + return err + } + primaryDomain = domains[0].Domain + if domains[0].Port != defaultHttpPort { + primaryDomain = fmt.Sprintf("%s:%v", domains[0].Domain, domains[0].Port) + } + website.PrimaryDomain = primaryDomain + website.Protocol = constant.ProtocolHTTP + } + + createTask, err := task.NewTaskWithOps(website.PrimaryDomain, task.TaskCreate, task.TaskScopeWebsite, create.TaskID, 0) + if err != nil { + return err + } + + if create.CreateDb { + createDataBase := func(t *task.Task) error { + database, _ := databaseRepo.Get(repo.WithByName(create.DbHost)) + if database.ID == 0 { + return nil + } + dbConfig := create.DataBaseConfig + switch database.Type { + case constant.AppPostgresql, constant.AppPostgres: + oldPostgresqlDb, _ := postgresqlRepo.Get(repo.WithByName(create.DbName), repo.WithByFrom(constant.ResourceLocal)) + if oldPostgresqlDb.ID > 0 { + return buserr.New("ErrDbUserNotValid") + } + var createPostgresql dto.PostgresqlDBCreate + createPostgresql.Name = dbConfig.DbName + createPostgresql.Username = dbConfig.DbUser + createPostgresql.Database = database.Name + createPostgresql.Format = dbConfig.DBFormat + createPostgresql.Password = dbConfig.DbPassword + createPostgresql.From = database.From + createPostgresql.SuperUser = true + pgDB, err := NewIPostgresqlService().Create(context.Background(), createPostgresql) + if err != nil { + return err + } + website.DbID = pgDB.ID + website.DbType = database.Type + case constant.AppMysql, constant.AppMariaDB: + oldMysqlDb, _ := mysqlRepo.Get(repo.WithByName(dbConfig.DbName), repo.WithByFrom(constant.ResourceLocal)) + if oldMysqlDb.ID > 0 { + return buserr.New("ErrDbUserNotValid") + } + var createMysql dto.MysqlDBCreate + createMysql.Name = dbConfig.DbName + createMysql.Username = dbConfig.DbUser + createMysql.Database = database.Name + createMysql.Format = dbConfig.DBFormat + createMysql.Permission = "%" + createMysql.Password = dbConfig.DbPassword + createMysql.From = database.From + mysqlDB, err := NewIMysqlService().Create(context.Background(), createMysql) + if err != nil { + return err + } + website.DbID = mysqlDB.ID + website.DbType = database.Type + } + return nil + } + createTask.AddSubTask(task.GetTaskName(create.DbName, task.TaskCreate, task.TaskScopeDatabase), createDataBase, nil) + } + + var proxy string + switch create.Type { + case constant.Deployment: + if create.AppType == constant.NewApp { + var ( + req request.AppInstallCreate + install *model.AppInstall + ) + req.Name = create.AppInstall.Name + req.AppDetailId = create.AppInstall.AppDetailId + req.Params = create.AppInstall.Params + req.AppContainerConfig = create.AppInstall.AppContainerConfig + install, err = NewIAppService().Install(req) + if err != nil { + return err + } + appInstall = install + website.AppInstallID = install.ID + website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) + } else { + var install model.AppInstall + install, err = appInstallRepo.GetFirst(repo.WithByID(create.AppInstallID)) + if err != nil { + return err + } + configApp := func(t *task.Task) error { + appInstall = &install + website.AppInstallID = appInstall.ID + website.Proxy = fmt.Sprintf("127.0.0.1:%d", appInstall.HttpPort) + return nil + } + createTask.AddSubTask(i18n.GetMsgByKey("ConfigApp"), configApp, nil) + } + case constant.Runtime: + runtime, err = runtimeRepo.GetFirst(context.Background(), repo.WithByID(create.RuntimeID)) + if err != nil { + return err + } + website.RuntimeID = runtime.ID + + switch runtime.Type { + case constant.RuntimePHP: + if runtime.Resource == constant.ResourceAppstore { + if !checkImageLike(nil, runtime.Image) { + return buserr.WithName("ErrImageNotExist", runtime.Name) + } + website.Proxy = fmt.Sprintf("127.0.0.1:%s", runtime.Port) + } else { + website.ProxyType = create.ProxyType + if website.ProxyType == constant.RuntimeProxyUnix { + proxy = fmt.Sprintf("unix:%s", path.Join("/www/sites", website.Alias, "php-pool", "php-fpm.sock")) + } + if website.ProxyType == constant.RuntimeProxyTcp { + proxy = fmt.Sprintf("127.0.0.1:%d", create.Port) + } + website.Proxy = proxy + } + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet: + proxyPort := runtime.Port + if proxyPort == "" { + return buserr.New("ErrRuntimeNoPort") + } + proxyPort = strconv.Itoa(create.Port) + website.Proxy = fmt.Sprintf("127.0.0.1:%s", proxyPort) + } + case constant.Subsite: + parentWebsite, err := websiteRepo.GetFirst(repo.WithByID(create.ParentWebsiteID)) + if err != nil { + return err + } + website.ParentWebsiteID = parentWebsite.ID + website.SiteDir = create.SiteDir + } + + configNginx := func(t *task.Task) error { + if err = configDefaultNginx(website, domains, appInstall, runtime, create.StreamConfig); err != nil { + return err + } + if website.Type != constant.Stream { + if err = createWafConfig(website, domains); err != nil { + return err + } + if create.Type == constant.Runtime { + runtime, err = runtimeRepo.GetFirst(context.Background(), repo.WithByID(create.RuntimeID)) + if err != nil { + return err + } + if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceAppstore { + createOpenBasedirConfig(website) + } + } + } + + tx, ctx := helper.GetTxAndContext() + defer tx.Rollback() + if err = websiteRepo.Create(ctx, website); err != nil { + return err + } + t.Task.ResourceID = website.ID + if len(domains) > 0 { + for i := range domains { + domains[i].WebsiteID = website.ID + } + if err = websiteDomainRepo.BatchCreate(ctx, domains); err != nil { + return err + } + } + + tx.Commit() + return nil + } + + deleteWebsite := func(t *task.Task) { + _ = deleteWebsiteFolder(website) + } + + createTask.AddSubTask(i18n.GetMsgByKey("ConfigOpenresty"), configNginx, deleteWebsite) + + if create.EnableSSL { + enableSSL := func(t *task.Task) error { + websiteModel, err := websiteSSLRepo.GetFirst(repo.WithByID(create.WebsiteSSLID)) + if err != nil { + return err + } + website.Protocol = constant.ProtocolHTTPS + website.WebsiteSSLID = create.WebsiteSSLID + appSSLReq := request.WebsiteHTTPSOp{ + WebsiteID: website.ID, + Enable: true, + WebsiteSSLID: websiteModel.ID, + Type: "existed", + HttpConfig: "HTTPToHTTPS", + SSLProtocol: []string{"TLSv1.3", "TLSv1.2"}, + Algorithm: "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED", + Hsts: true, + HstsIncludeSubDomains: true, + } + if err = applySSL(website, *websiteModel, appSSLReq); err != nil { + return err + } + if err = websiteRepo.Save(context.Background(), website); err != nil { + return err + } + return nil + } + createTask.AddSubTaskWithIgnoreErr(i18n.GetMsgByKey("EnableSSL"), enableSSL) + } + + if len(create.FtpUser) != 0 && len(create.FtpPassword) != 0 { + createFtpUser := func(t *task.Task) error { + indexDir := GetSitePath(*website, SiteIndexDir) + itemID, err := NewIFtpService().Create(dto.FtpCreate{User: create.FtpUser, Password: create.FtpPassword, Path: indexDir}) + if err != nil { + return err + } + website.FtpID = itemID + return nil + } + createTask.AddSubTaskWithIgnoreErr(i18n.GetWithName("ConfigFTP", create.FtpUser), createFtpUser) + } + + return createTask.Execute() +} + +func (w WebsiteService) OpWebsite(req request.WebsiteOp) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + if err := opWebsite(&website, req.Operate); err != nil { + return err + } + return websiteRepo.Save(context.Background(), &website) +} + +func (w WebsiteService) GetWebsiteOptions(req request.WebsiteOptionReq) ([]response.WebsiteOption, error) { + var options []repo.DBOption + if len(req.Types) > 0 { + options = append(options, repo.WithTypes(req.Types)) + } + webs, _ := websiteRepo.List(options...) + var datas []response.WebsiteOption + for _, web := range webs { + var item response.WebsiteOption + if err := copier.Copy(&item, &web); err != nil { + return nil, err + } + datas = append(datas, item) + } + return datas, nil +} + +func (w WebsiteService) UpdateWebsite(req request.WebsiteUpdate) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + if website.IPV6 != req.IPV6 { + if err := changeIPV6(website, req.IPV6); err != nil { + return err + } + } + website.PrimaryDomain = req.PrimaryDomain + if req.WebsiteGroupID != 0 { + website.WebsiteGroupID = req.WebsiteGroupID + } + website.Remark = req.Remark + website.IPV6 = req.IPV6 + website.Favorite = req.Favorite + + if req.ExpireDate != "" { + expireDate, err := time.Parse(constant.DateLayout, req.ExpireDate) + if err != nil { + return err + } + website.ExpireDate = expireDate + } + + return websiteRepo.Save(context.TODO(), &website) +} + +func (w WebsiteService) GetWebsite(id uint) (response.WebsiteDTO, error) { + var res response.WebsiteDTO + website, err := websiteRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return res, err + } + res.Website = website + res.ErrorLogPath = GetSitePath(website, SiteErrorLog) + res.AccessLogPath = GetSitePath(website, SiteAccessLog) + res.SitePath = GetSitePath(website, SiteDir) + res.SiteDir = website.SiteDir + fileOp := files.NewFileOp() + switch website.Type { + case constant.Runtime: + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) + if err != nil { + return res, err + } + res.RuntimeType = runtime.Type + res.RuntimeName = runtime.Name + if runtime.Type == constant.RuntimePHP { + res.OpenBaseDir = fileOp.Stat(path.Join(GetSitePath(website, SiteIndexDir), ".user.ini")) + } + case constant.Stream: + nginxParser, err := parser.NewParser(GetSitePath(website, StreamConf)) + if err != nil { + return res, err + } + config, err := nginxParser.Parse() + if err != nil { + return res, err + } + listens := config.FindDirectives("listen") + for _, listen := range listens { + params := listen.GetParameters() + if len(params) > 1 && params[1] == "udp" { + res.UDP = true + } + } + upstreams := config.FindUpstreams() + for _, up := range upstreams { + directives := up.GetDirectives() + for _, d := range directives { + dName := d.GetName() + if _, ok := dto.LBAlgorithms[dName]; ok { + res.Algorithm = dName + break + } + } + res.Servers = getNginxUpstreamServers(up.UpstreamServers) + } + } + return res, nil +} + +func (w WebsiteService) DeleteWebsite(req request.WebsiteDelete) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + if website.Type != constant.Subsite { + parentWebsites, _ := websiteRepo.List(websiteRepo.WithParentID(website.ID)) + if len(parentWebsites) > 0 { + var names []string + for _, site := range parentWebsites { + names = append(names, site.PrimaryDomain) + } + return buserr.WithName("ErrParentWebsite", strings.Join(names, ",")) + } + } + + if website.Type == constant.Runtime && req.DeleteDB && website.DbID != 0 { + switch website.DbType { + case constant.AppMysql, constant.AppMariaDB: + mysqlDB, _ := mysqlRepo.Get(repo.WithByID(website.DbID)) + if mysqlDB.ID > 0 { + deleteReq := dto.MysqlDBDelete{ + ID: mysqlDB.ID, + Database: mysqlDB.MysqlName, + ForceDelete: req.ForceDelete, + } + if err = NewIMysqlService().Delete(context.TODO(), deleteReq); err != nil && !req.ForceDelete { + return err + } + } + case constant.AppPostgresql, constant.AppPostgres: + pgDB, _ := postgresqlRepo.Get(repo.WithByID(website.DbID)) + if pgDB.ID > 0 { + deleteReq := dto.PostgresqlDBDelete{ + ID: pgDB.ID, + ForceDelete: req.ForceDelete, + Database: pgDB.PostgresqlName, + } + if err = NewIPostgresqlService().Delete(context.TODO(), deleteReq); err != nil && !req.ForceDelete { + return err + } + } + } + } + + if err = delNginxConfig(website, req.ForceDelete); err != nil { + return err + } + + if website.Type != constant.Stream { + if err = delWafConfig(website, req.ForceDelete); err != nil { + return err + } + } + + if checkIsLinkApp(website) && req.DeleteApp { + appInstall, _ := appInstallRepo.GetFirst(repo.WithByID(website.AppInstallID)) + if appInstall.ID > 0 { + deleteReq := request.AppInstallDelete{ + Install: appInstall, + ForceDelete: req.ForceDelete, + DeleteBackup: true, + DeleteDB: true, + } + if err = deleteAppInstall(deleteReq); err != nil && !req.ForceDelete { + return err + } + } + } + + tx, ctx := helper.GetTxAndContext() + defer tx.Rollback() + + go func() { + _ = NewIBackupRecordService().DeleteRecordByName("website", website.PrimaryDomain, website.Alias, req.DeleteBackup) + }() + + if err := websiteRepo.DeleteBy(ctx, repo.WithByID(req.ID)); err != nil { + return err + } + if err := websiteDomainRepo.DeleteBy(ctx, websiteDomainRepo.WithWebsiteId(req.ID)); err != nil { + return err + } + tx.Commit() + + uploadDir := path.Join(global.Dir.DataDir, "uploads/website", website.Alias) + if _, err := os.Stat(uploadDir); err == nil { + _ = os.RemoveAll(uploadDir) + } + return nil +} + +func (w WebsiteService) UpdateWebsiteDomain(req request.WebsiteDomainUpdate) error { + domain, err := websiteDomainRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + domain.SSL = req.SSL + website, err := websiteRepo.GetFirst(repo.WithByID(domain.WebsiteID)) + if err != nil { + return err + } + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + nginxConfig := nginxFull.SiteConfig + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + server.DeleteListen(strconv.Itoa(domain.Port)) + if website.IPV6 { + server.DeleteListen("[::]:" + strconv.Itoa(domain.Port)) + } + http3 := isHttp3(server) + setListen(server, strconv.Itoa(domain.Port), website.IPV6, http3, website.DefaultServer, domain.SSL && website.Protocol == constant.ProtocolHTTPS) + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + if err = nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName); err != nil { + return err + } + return websiteDomainRepo.Save(context.TODO(), &domain) +} + +func (w WebsiteService) GetNginxConfigByScope(req request.NginxScopeReq) (*response.WebsiteNginxConfig, error) { + keys, ok := dto.ScopeKeyMap[req.Scope] + if !ok || len(keys) == 0 { + return nil, nil + } + + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return nil, err + } + var config response.WebsiteNginxConfig + params, err := getNginxParamsByKeys(constant.NginxScopeServer, keys, &website) + if err != nil { + return nil, err + } + config.Params = params + config.Enable = len(params[0].Params) > 0 + + return &config, nil +} + +func (w WebsiteService) UpdateNginxConfigByScope(req request.NginxConfigUpdate) error { + keys, ok := dto.ScopeKeyMap[req.Scope] + if !ok || len(keys) == 0 { + return nil + } + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + if req.Operate == constant.ConfigDel { + var nginxParams []dto.NginxParam + for _, key := range keys { + nginxParams = append(nginxParams, dto.NginxParam{ + Name: key, + }) + } + return deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website) + } + params := getNginxParams(req.Params, keys) + if req.Operate == constant.ConfigNew { + if _, ok := dto.StaticFileKeyMap[req.Scope]; ok { + params = getNginxParamsFromStaticFile(req.Scope, params) + } + } + return updateNginxConfig(constant.NginxScopeServer, params, &website) +} + +func (w WebsiteService) GetWebsiteNginxConfig(websiteID uint, configType string) (*response.FileInfo, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(websiteID)) + if err != nil { + return nil, err + } + configPath := "" + switch configType { + case constant.AppOpenresty: + configPath = GetWebsiteConfigPath(website) + } + info, err := files.NewFileInfo(files.FileOption{ + Path: configPath, + Expand: true, + }) + if err != nil { + return nil, err + } + return &response.FileInfo{FileInfo: *info}, nil +} + +func (w WebsiteService) GetWebsiteHTTPS(websiteId uint) (response.WebsiteHTTPS, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(websiteId)) + if err != nil { + return response.WebsiteHTTPS{}, err + } + var ( + res response.WebsiteHTTPS + httpsPorts []string + ) + + httpsPortsMap := getHttpsPort(websiteId) + for port := range httpsPortsMap { + httpsPorts = append(httpsPorts, strconv.Itoa(port)) + } + res.HttpsPort = strings.Join(httpsPorts, ",") + if website.WebsiteSSLID == 0 { + res.Enable = false + return res, nil + } + websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(website.WebsiteSSLID)) + if err != nil { + return response.WebsiteHTTPS{}, err + } + res.SSL = *websiteSSL + res.Enable = true + if website.HttpConfig != "" { + res.HttpConfig = website.HttpConfig + } else { + res.HttpConfig = constant.HTTPToHTTPS + } + params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"ssl_protocols", "ssl_ciphers", "add_header", "listen"}, &website) + if err != nil { + return res, err + } + for _, p := range params { + if p.Name == "ssl_protocols" { + res.SSLProtocol = p.Params + } + if p.Name == "ssl_ciphers" { + res.Algorithm = p.Params[0] + } + if p.Name == "add_header" && len(p.Params) > 0 { + if p.Params[0] == "Strict-Transport-Security" { + res.Hsts = true + if len(p.Params) > 1 { + hstsValue := p.Params[1] + if strings.Contains(hstsValue, "includeSubDomains") { + res.HstsIncludeSubDomains = true + } + } + } + if p.Params[0] == "Alt-Svc" { + res.Http3 = true + } + } + } + return res, nil +} + +func (w WebsiteService) OpWebsiteHTTPS(ctx context.Context, req request.WebsiteHTTPSOp) (*response.WebsiteHTTPS, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return nil, err + } + var ( + res response.WebsiteHTTPS + websiteSSL model.WebsiteSSL + ) + if err = ChangeHSTSConfig(req.Hsts, req.HstsIncludeSubDomains, req.Http3, website); err != nil { + return nil, err + } + res.Enable = req.Enable + res.SSLProtocol = req.SSLProtocol + res.Algorithm = req.Algorithm + res.HstsIncludeSubDomains = req.HstsIncludeSubDomains + if !req.Enable { + website.Protocol = constant.ProtocolHTTP + website.WebsiteSSLID = 0 + + websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(website.ID)) + + ports := make(map[int]struct{}) + for _, domain := range websiteDomains { + ports[domain.Port] = struct{}{} + } + for port := range ports { + if err = removeSSLListen(website, []string{strconv.Itoa(port)}); err != nil { + return nil, err + } + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + if _, ok := ports[nginxInstall.HttpsPort]; !ok { + httpsPortStr := strconv.Itoa(nginxInstall.HttpsPort) + if err = deleteListenAndServerName(website, []string{httpsPortStr, "[::]:" + httpsPortStr}, []string{}); err != nil { + return nil, err + } + } + nginxParams := getNginxParamsFromStaticFile(dto.SSL, nil) + nginxParams = append(nginxParams, + dto.NginxParam{ + Name: "if", + Params: []string{"($scheme", "=", "http)"}, + }, + dto.NginxParam{ + Name: "ssl_certificate", + }, + dto.NginxParam{ + Name: "ssl_certificate_key", + }, + dto.NginxParam{ + Name: "ssl_protocols", + }, + dto.NginxParam{ + Name: "ssl_ciphers", + }, + dto.NginxParam{ + Name: "http2", + }, + dto.NginxParam{ + Name: "add_header", + Params: []string{"Strict-Transport-Security"}, + }, + ) + if err = deleteNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil { + return nil, err + } + if err = websiteRepo.Save(ctx, &website); err != nil { + return nil, err + } + return nil, nil + } + + if req.Type == constant.SSLExisted { + websiteModel, err := websiteSSLRepo.GetFirst(repo.WithByID(req.WebsiteSSLID)) + if err != nil { + return nil, err + } + if websiteModel.Pem == "" { + return nil, buserr.New("ErrSSLValid") + } + website.WebsiteSSLID = websiteModel.ID + res.SSL = *websiteModel + websiteSSL = *websiteModel + } + if req.Type == constant.SSLManual { + websiteSSL, err = getManualWebsiteSSL(req) + if err != nil { + return nil, err + } + res.SSL = websiteSSL + } + + website.Protocol = constant.ProtocolHTTPS + if err = applySSL(&website, websiteSSL, req); err != nil { + return nil, err + } + website.HttpConfig = req.HttpConfig + + if websiteSSL.ID == 0 { + if err = websiteSSLRepo.Create(ctx, &websiteSSL); err != nil { + return nil, err + } + website.WebsiteSSLID = websiteSSL.ID + } + if err = websiteRepo.Save(ctx, &website); err != nil { + return nil, err + } + return &res, nil +} + +func (w WebsiteService) PreInstallCheck(req request.WebsiteInstallCheckReq) ([]response.WebsitePreInstallCheck, error) { + var ( + res []response.WebsitePreInstallCheck + checkIds []uint + showErr = false + ) + + app, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenresty)) + if err != nil { + return nil, err + } + appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithAppId(app.ID)) + if reflect.DeepEqual(appInstall, model.AppInstall{}) { + res = append(res, response.WebsitePreInstallCheck{ + Name: appInstall.Name, + AppName: app.Name, + Status: buserr.WithDetail("ErrNotInstall", app.Name, nil).Error(), + Version: appInstall.Version, + }) + showErr = true + } else { + checkIds = append(req.InstallIds, appInstall.ID) + } + if len(checkIds) > 0 { + installList, _ := appInstallRepo.ListBy(context.Background(), repo.WithByIDs(checkIds)) + for _, install := range installList { + if err = syncAppInstallStatus(&install, false); err != nil { + return nil, err + } + res = append(res, response.WebsitePreInstallCheck{ + Name: install.Name, + Status: install.Status, + Version: install.Version, + AppName: install.App.Name, + }) + if install.Status != constant.StatusRunning { + showErr = true + } + } + } + if showErr { + return res, nil + } + return nil, nil +} + +func (w WebsiteService) UpdateNginxConfigFile(req request.WebsiteNginxUpdate) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + nginxFull, err := getNginxFull(&website) + if err != nil { + return err + } + + filePath := nginxFull.SiteConfig.FilePath + if err = files.NewFileOp().WriteFile(filePath, strings.NewReader(req.Content), constant.DirPerm); err != nil { + return err + } + return nginxCheckAndReload(nginxFull.SiteConfig.OldContent, filePath, nginxFull.Install.ContainerName) +} + +func (w WebsiteService) OpWebsiteLog(req request.WebsiteLogReq) (*response.WebsiteLog, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return nil, err + } + sitePath := GetSitePath(website, SiteDir) + res := &response.WebsiteLog{ + Content: "", + } + switch req.Operate { + case constant.GetLog: + switch req.LogType { + case constant.AccessLog: + res.Enable = website.AccessLog + if !website.AccessLog { + return res, nil + } + case constant.ErrorLog: + res.Enable = website.ErrorLog + if !website.ErrorLog { + return res, nil + } + } + filePath := path.Join(sitePath, "log", req.LogType) + logFileRes, err := files.ReadFileByLine(filePath, req.Page, req.PageSize, false) + if err != nil { + return nil, err + } + res.End = logFileRes.IsEndOfFile + res.Path = filePath + res.Content = strings.Join(logFileRes.Lines, "\n") + return res, nil + case constant.DisableLog: + params := dto.NginxParam{} + switch req.LogType { + case constant.AccessLog: + params.Name = "access_log" + params.Params = []string{"off"} + website.AccessLog = false + case constant.ErrorLog: + params.Name = "error_log" + params.Params = []string{"/dev/null", "crit"} + website.ErrorLog = false + } + var nginxParams []dto.NginxParam + nginxParams = append(nginxParams, params) + + if err := updateNginxConfig(constant.NginxScopeServer, nginxParams, &website); err != nil { + return nil, err + } + if err := websiteRepo.Save(context.Background(), &website); err != nil { + return nil, err + } + case constant.EnableLog: + key := "access_log" + logPath := path.Join("/www", "sites", website.Alias, "log", req.LogType) + params := []string{logPath} + switch req.LogType { + case constant.AccessLog: + if website.Type != constant.Stream { + params = append(params, "main") + } else { + params = append(params, "streamlog") + } + website.AccessLog = true + case constant.ErrorLog: + key = "error_log" + website.ErrorLog = true + } + if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: key, Params: params}}, &website); err != nil { + return nil, err + } + if err := websiteRepo.Save(context.Background(), &website); err != nil { + return nil, err + } + case constant.DeleteLog: + logPath := path.Join(sitePath, "log", req.LogType) + if err := files.NewFileOp().WriteFile(logPath, strings.NewReader(""), constant.DirPerm); err != nil { + return nil, err + } + } + return res, nil +} + +func (w WebsiteService) ChangeDefaultServer(id uint) error { + defaultWebsite, _ := websiteRepo.GetFirst(websiteRepo.WithDefaultServer()) + if defaultWebsite.ID > 0 { + params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"listen"}, &defaultWebsite) + if err != nil { + return err + } + var changeParams []dto.NginxParam + for _, param := range params { + paramLen := len(param.Params) + var newParam []string + if paramLen > 1 && param.Params[paramLen-1] == components.DefaultServer { + newParam = param.Params[:paramLen-1] + } + changeParams = append(changeParams, dto.NginxParam{ + Name: param.Name, + Params: newParam, + }) + } + if err := updateNginxConfig(constant.NginxScopeServer, changeParams, &defaultWebsite); err != nil { + return err + } + defaultWebsite.DefaultServer = false + if err := websiteRepo.Save(context.Background(), &defaultWebsite); err != nil { + return err + } + } + if err := updateDefaultServerConfig(!(id > 0)); err != nil { + return err + } + if id == 0 { + return nil + } + website, err := websiteRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return err + } + params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"listen"}, &website) + if err != nil { + return err + } + var changeParams []dto.NginxParam + for _, param := range params { + if hasHttp3(param.Params) || hasDefaultServer(param.Params) { + continue + } + newParam := append(param.Params, components.DefaultServer) + changeParams = append(changeParams, dto.NginxParam{ + Name: param.Name, + Params: newParam, + }) + } + if err := updateNginxConfig(constant.NginxScopeServer, changeParams, &website); err != nil { + return err + } + website.DefaultServer = true + return websiteRepo.Save(context.Background(), &website) +} + +func (w WebsiteService) ChangePHPVersion(req request.WebsitePHPVersionReq) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + if website.Type == constant.Runtime { + oldRuntime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + if oldRuntime.Resource == constant.ResourceLocal { + return buserr.New("ErrPHPResource") + } + client, err := docker.NewDockerClient() + if err != nil { + return err + } + defer client.Close() + if !checkImageLike(client, oldRuntime.Image) { + return buserr.WithName("ErrImageNotExist", oldRuntime.Name) + } + } + configPath := GetSitePath(website, SiteConf) + nginxContent, err := files.NewFileOp().GetContent(configPath) + if err != nil { + return err + } + config, err := parser.NewStringParser(string(nginxContent)).Parse() + if err != nil { + return err + } + servers := config.FindServers() + if len(servers) == 0 { + return errors.New("nginx config is not valid") + } + server := servers[0] + fileOp := files.NewFileOp() + indexPHPPath := path.Join(GetSitePath(website, SiteIndexDir), "index.php") + indexHtmlPath := path.Join(GetSitePath(website, SiteIndexDir), "index.html") + if req.RuntimeID > 0 { + server.UpdateDirective("index", []string{"index.php index.html index.htm default.php default.htm default.html"}) + server.RemoveDirective("location", []string{"~", "[^/]\\.php(/|$)"}) + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(req.RuntimeID)) + if err != nil { + return err + } + if runtime.Resource == constant.ResourceLocal { + return buserr.New("ErrPHPResource") + } + website.RuntimeID = req.RuntimeID + website.AppInstallID = 0 + phpProxy := fmt.Sprintf("127.0.0.1:%s", runtime.Port) + website.Proxy = phpProxy + server.UpdatePHPProxy([]string{website.Proxy}, "") + website.Type = constant.Runtime + if !fileOp.Stat(indexPHPPath) { + _ = fileOp.WriteFile(indexPHPPath, strings.NewReader(string(nginx_conf.IndexPHP)), constant.FilePerm) + } + } else { + server.UpdateDirective("index", []string{"index.html index.php index.htm default.php default.htm default.html"}) + website.RuntimeID = 0 + website.Type = constant.Static + website.Proxy = "" + server.RemoveDirective("location", []string{"~", "[^/]\\.php(/|$)"}) + if !fileOp.Stat(indexHtmlPath) { + _ = fileOp.WriteFile(indexHtmlPath, strings.NewReader(string(nginx_conf.Index)), constant.FilePerm) + } + } + + config.FilePath = configPath + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + if err = nginxCheckAndReload(string(nginxContent), configPath, nginxInstall.ContainerName); err != nil { + return err + } + return websiteRepo.Save(context.Background(), &website) +} + +func (w WebsiteService) UpdateSiteDir(req request.WebsiteUpdateDir) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + runDir := req.SiteDir + siteDir := path.Join("/www/sites", website.Alias, "index") + if req.SiteDir != "/" { + siteDir = fmt.Sprintf("%s%s", siteDir, req.SiteDir) + } + if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "root", Params: []string{siteDir}}}, &website); err != nil { + return err + } + website.SiteDir = runDir + return websiteRepo.Save(context.Background(), &website) +} + +func (w WebsiteService) UpdateSitePermission(req request.WebsiteUpdateDirPermission) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return err + } + absoluteIndexPath := GetSitePath(website, SiteIndexDir) + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second)) + if err := cmdMgr.RunBashCf("%s chown -R %s:%s %s", cmd.SudoHandleCmd(), req.User, req.Group, absoluteIndexPath); err != nil { + return err + } + website.User = req.User + website.Group = req.Group + return websiteRepo.Save(context.Background(), &website) +} + +func (w WebsiteService) UpdateCors(req request.CorsConfigReq) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + params := []dto.NginxParam{ + {Name: "add_header", Params: []string{"Access-Control-Allow-Origin"}}, + {Name: "add_header", Params: []string{"Access-Control-Allow-Methods"}}, + {Name: "add_header", Params: []string{"Access-Control-Allow-Headers"}}, + {Name: "add_header", Params: []string{"Access-Control-Allow-Credentials"}}, + {Name: "if", Params: []string{"(", "$request_method", "=", "'OPTIONS'", ")"}}, + } + if err := deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil { + return err + } + if req.Cors { + return updateWebsiteConfig(website, func(server *components.Server) error { + server.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"}) + if req.AllowMethods != "" { + server.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"}) + } + if req.AllowHeaders != "" { + server.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"}) + } + if req.AllowCredentials { + server.UpdateDirective("add_header", []string{"Access-Control-Allow-Credentials", "true", "always"}) + } + if req.Preflight { + server.AddCorsOption() + } + return nil + }) + } + return nil +} + +func (w WebsiteService) GetCors(websiteID uint) (*request.CorsConfig, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(websiteID)) + if err != nil { + return nil, err + } + server, err := getServer(website) + if err != nil { + return nil, err + } + if server == nil { + return nil, nil + } + cors := &request.CorsConfig{ + Cors: server.Cors, + AllowOrigins: server.AllowOrigins, + AllowMethods: server.AllowMethods, + AllowHeaders: server.AllowHeaders, + AllowCredentials: server.AllowCredentials, + Preflight: server.Preflight, + } + return cors, nil +} + +func (w WebsiteService) UpdateAntiLeech(req request.NginxAntiLeechUpdate) (err error) { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return + } + nginxFull, err := getNginxFull(&website) + if err != nil { + return + } + fileOp := files.NewFileOp() + backupContent, err := fileOp.GetContent(nginxFull.SiteConfig.Config.FilePath) + if err != nil { + return + } + block := nginxFull.SiteConfig.Config.FindServers()[0] + locations := block.FindDirectives("location") + for _, location := range locations { + loParams := location.GetParameters() + if len(loParams) > 1 || loParams[0] == "~" { + extendStr := loParams[1] + if strings.HasPrefix(extendStr, `.*\.(`) && strings.HasSuffix(extendStr, `)$`) { + block.RemoveDirective("location", loParams) + } + } + } + if req.Enable || req.Cache { + exts := strings.Split(req.Extends, ",") + newDirective := components.Directive{ + Name: "location", + Parameters: []string{"~", fmt.Sprintf(`.*\.(%s)$`, strings.Join(exts, "|"))}, + } + + newBlock := &components.Block{} + newBlock.Directives = make([]components.IDirective, 0) + if req.Cache { + newBlock.AppendDirectives(&components.Directive{ + Name: "expires", + Parameters: []string{strconv.Itoa(req.CacheTime) + req.CacheUint}, + }) + } + if !req.LogEnable { + newBlock.AppendDirectives(&components.Directive{ + Name: "access_log", + Parameters: []string{"off"}, + }) + } + newBlock.AppendDirectives(&components.Directive{ + Name: "log_not_found", + Parameters: []string{"off"}, + }) + if req.Enable { + validDir := &components.Directive{ + Name: "valid_referers", + Parameters: []string{}, + } + if req.NoneRef { + validDir.Parameters = append(validDir.Parameters, "none") + } + if req.Blocked { + validDir.Parameters = append(validDir.Parameters, "blocked") + } + if len(req.ServerNames) > 0 { + validDir.Parameters = append(validDir.Parameters, strings.Join(req.ServerNames, " ")) + } + newBlock.AppendDirectives(validDir) + + ifDir := &components.Directive{ + Name: "if", + Parameters: []string{"($invalid_referer)"}, + } + if !req.LogEnable { + ifDir.Block = &components.Block{ + Directives: []components.IDirective{ + &components.Directive{ + Name: "access_log", + Parameters: []string{"off"}, + }, + &components.Directive{ + Name: "return", + Parameters: []string{req.Return}, + }, + }, + } + } else { + ifDir.Block = &components.Block{ + Directives: []components.IDirective{ + &components.Directive{ + Name: "return", + Parameters: []string{req.Return}, + }, + }, + } + } + newBlock.AppendDirectives(ifDir) + } + if website.Type == constant.Deployment { + newBlock.AppendDirectives( + &components.Directive{ + Name: "proxy_set_header", + Parameters: []string{"Host", "$host"}, + }, + &components.Directive{ + Name: "proxy_set_header", + Parameters: []string{"X-Real-IP", "$remote_addr"}, + }, + &components.Directive{ + Name: "proxy_set_header", + Parameters: []string{"X-Forwarded-For", "$proxy_add_x_forwarded_for"}, + }, + &components.Directive{ + Name: "proxy_pass", + Parameters: []string{fmt.Sprintf("http://%s", website.Proxy)}, + }) + } + newDirective.Block = newBlock + index := -1 + for i, directive := range block.Directives { + if directive.GetName() == "include" { + index = i + break + } + } + if index != -1 { + block.Directives = append(block.Directives[:index], append([]components.IDirective{&newDirective}, block.Directives[index:]...)...) + } else { + block.Directives = append(block.Directives, &newDirective) + } + } + + if err = nginx.WriteConfig(nginxFull.SiteConfig.Config, nginx.IndentedStyle); err != nil { + return + } + if err = updateNginxConfig(constant.NginxScopeServer, nil, &website); err != nil { + _ = fileOp.WriteFile(nginxFull.SiteConfig.Config.FilePath, bytes.NewReader(backupContent), constant.DirPerm) + return + } + return +} + +func (w WebsiteService) GetAntiLeech(id uint) (*response.NginxAntiLeechRes, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return nil, err + } + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil, err + } + res := &response.NginxAntiLeechRes{ + LogEnable: true, + ServerNames: []string{}, + } + block := nginxFull.SiteConfig.Config.FindServers()[0] + locations := block.FindDirectives("location") + for _, location := range locations { + loParams := location.GetParameters() + if len(loParams) > 1 || loParams[0] == "~" { + extendStr := loParams[1] + if strings.HasPrefix(extendStr, `.*\.(`) && strings.HasSuffix(extendStr, `)$`) { + str1 := strings.TrimPrefix(extendStr, `.*\.(`) + str2 := strings.TrimSuffix(str1, ")$") + res.Extends = strings.Join(strings.Split(str2, "|"), ",") + } + } + lDirectives := location.GetBlock().GetDirectives() + for _, lDir := range lDirectives { + if lDir.GetName() == "access_log" { + if strings.Join(lDir.GetParameters(), "") == "off" { + res.LogEnable = false + } + } + if lDir.GetName() == "valid_referers" { + res.Enable = true + params := lDir.GetParameters() + for _, param := range params { + if param == "none" { + res.NoneRef = true + continue + } + if param == "blocked" { + res.Blocked = true + continue + } + if param == "server_names" { + continue + } + res.ServerNames = append(res.ServerNames, param) + } + } + if lDir.GetName() == "if" && lDir.GetParameters()[0] == "($invalid_referer)" { + directives := lDir.GetBlock().GetDirectives() + for _, dir := range directives { + if dir.GetName() == "return" { + res.Return = strings.Join(dir.GetParameters(), " ") + } + } + } + if lDir.GetName() == "expires" { + res.Cache = true + matches := re.GetRegex(re.NumberWordPattern).FindStringSubmatch(lDir.GetParameters()[0]) + if matches == nil { + continue + } + cacheTime, err := strconv.Atoi(matches[1]) + if err != nil { + continue + } + unit := matches[2] + res.CacheUint = unit + res.CacheTime = cacheTime + } + } + } + return res, nil +} + +func (w WebsiteService) OperateRedirect(req request.NginxRedirectReq) (err error) { + var ( + website model.Website + oldContent []byte + ) + + website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + includeDir := GetSitePath(website, SiteRedirectDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(includeDir) { + _ = fileOp.CreateDir(includeDir, constant.DirPerm) + } + fileName := fmt.Sprintf("%s.conf", req.Name) + includePath := path.Join(includeDir, fileName) + backName := fmt.Sprintf("%s.bak", req.Name) + backPath := path.Join(includeDir, backName) + + if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) { + err = buserr.New("ErrNameIsExist") + return + } + + defer func() { + if err != nil { + switch req.Operate { + case "create": + _ = fileOp.DeleteFile(includePath) + case "edit": + _ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), constant.DirPerm) + } + } + }() + + var ( + config *components.Config + oldPar *parser.Parser + ) + + switch req.Operate { + case "create": + config = &components.Config{} + case "edit": + oldPar, err = parser.NewParser(includePath) + if err != nil { + return + } + config, err = oldPar.Parse() + if err != nil { + return + } + oldContent, err = fileOp.GetContent(includePath) + if err != nil { + return + } + case "delete": + _ = fileOp.DeleteFile(includePath) + _ = fileOp.DeleteFile(backPath) + return updateNginxConfig(constant.NginxScopeServer, nil, &website) + case "disable": + _ = fileOp.Rename(includePath, backPath) + return updateNginxConfig(constant.NginxScopeServer, nil, &website) + case "enable": + _ = fileOp.Rename(backPath, includePath) + return updateNginxConfig(constant.NginxScopeServer, nil, &website) + } + + target := req.Target + block := &components.Block{} + + switch req.Type { + case "path": + if req.KeepPath { + target = req.Target + "$1" + } else { + target = req.Target + "?" + } + redirectKey := "permanent" + if req.Redirect == "302" { + redirectKey = "redirect" + } + block = &components.Block{ + Directives: []components.IDirective{ + &components.Directive{ + Name: "rewrite", + Parameters: []string{fmt.Sprintf("^%s(.*)", req.Path), target, redirectKey}, + }, + }, + } + case "domain": + if req.KeepPath { + target = req.Target + "$request_uri" + } + returnBlock := &components.Block{ + Directives: []components.IDirective{ + &components.Directive{ + Name: "return", + Parameters: []string{req.Redirect, target}, + }, + }, + } + for _, domain := range req.Domains { + block.Directives = append(block.Directives, &components.Directive{ + Name: "if", + Parameters: []string{"($host", "~", fmt.Sprintf("'^%s')", domain)}, + Block: returnBlock, + }) + } + case "404": + if req.RedirectRoot { + target = "/" + } + block = &components.Block{ + Directives: []components.IDirective{ + &components.Directive{ + Name: "error_page", + Parameters: []string{"404", "=", "@notfound"}, + }, + &components.Directive{ + Name: "location", + Parameters: []string{"@notfound"}, + Block: &components.Block{ + Directives: []components.IDirective{ + &components.Directive{ + Name: "return", + Parameters: []string{req.Redirect, target}, + }, + }, + }, + }, + }, + } + } + config.FilePath = includePath + config.Block = block + + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return buserr.WithErr("ErrUpdateBuWebsite", err) + } + + nginxInclude := fmt.Sprintf("/www/sites/%s/redirect/*.conf", website.Alias) + if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil { + return + } + return +} + +func (w WebsiteService) GetRedirect(id uint) (res []response.NginxRedirectConfig, err error) { + var ( + website model.Website + ) + website, err = websiteRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return + } + includeDir := GetSitePath(website, SiteRedirectDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(includeDir) { + return + } + entries, err := os.ReadDir(includeDir) + if err != nil { + return + } + if len(entries) == 0 { + return + } + + var ( + content []byte + config *components.Config + ) + for _, entry := range entries { + if entry.IsDir() { + continue + } + fileName := entry.Name() + if !strings.HasSuffix(fileName, ".conf") && !strings.HasSuffix(fileName, ".bak") { + continue + } + redirectConfig := response.NginxRedirectConfig{ + WebsiteID: website.ID, + } + parts := strings.Split(fileName, ".") + redirectConfig.Name = parts[0] + if parts[1] == "conf" { + redirectConfig.Enable = true + } else { + redirectConfig.Enable = false + } + filePath := path.Join(includeDir, fileName) + redirectConfig.FilePath = filePath + content, err = fileOp.GetContent(filePath) + if err != nil { + return + } + redirectConfig.Content = string(content) + config, err = parser.NewStringParser(string(content)).Parse() + if err != nil { + return + } + + dirs := config.GetDirectives() + if len(dirs) > 0 { + firstName := dirs[0].GetName() + switch firstName { + case "if": + for _, ifDir := range dirs { + params := ifDir.GetParameters() + if len(params) > 2 && params[0] == "($host" { + domain := strings.Trim(strings.Trim(params[2], "'"), "^") + redirectConfig.Domains = append(redirectConfig.Domains, domain) + if len(redirectConfig.Domains) > 1 { + continue + } + redirectConfig.Type = "domain" + } + childDirs := ifDir.GetBlock().GetDirectives() + for _, dir := range childDirs { + if dir.GetName() == "return" { + dirParams := dir.GetParameters() + if len(dirParams) > 1 { + redirectConfig.Redirect = dirParams[0] + if strings.HasSuffix(dirParams[1], "$request_uri") { + redirectConfig.KeepPath = true + redirectConfig.Target = strings.TrimSuffix(dirParams[1], "$request_uri") + } else { + redirectConfig.KeepPath = false + redirectConfig.Target = dirParams[1] + } + } + } + } + } + case "rewrite": + redirectConfig.Type = "path" + for _, pathDir := range dirs { + if pathDir.GetName() == "rewrite" { + params := pathDir.GetParameters() + if len(params) > 2 { + redirectConfig.Path = strings.Trim(strings.Trim(params[0], "^"), "(.*)") + if strings.HasSuffix(params[1], "$1") { + redirectConfig.KeepPath = true + redirectConfig.Target = strings.TrimSuffix(params[1], "$1") + } else { + redirectConfig.KeepPath = false + redirectConfig.Target = strings.TrimSuffix(params[1], "?") + } + if params[2] == "permanent" { + redirectConfig.Redirect = "301" + } else { + redirectConfig.Redirect = "302" + } + } + } + } + case "error_page": + redirectConfig.Type = "404" + for _, errDir := range dirs { + if errDir.GetName() == "location" { + childDirs := errDir.GetBlock().GetDirectives() + for _, dir := range childDirs { + if dir.GetName() == "return" { + dirParams := dir.GetParameters() + if len(dirParams) > 1 { + redirectConfig.Redirect = dirParams[0] + if strings.HasSuffix(dirParams[1], "$request_uri") { + redirectConfig.KeepPath = true + redirectConfig.Target = strings.TrimSuffix(dirParams[1], "$request_uri") + redirectConfig.RedirectRoot = false + } else { + redirectConfig.KeepPath = false + redirectConfig.Target = dirParams[1] + redirectConfig.RedirectRoot = redirectConfig.Target == "/" + } + } + } + } + } + } + } + } + res = append(res, redirectConfig) + } + return +} + +func (w WebsiteService) UpdateRedirectFile(req request.NginxRedirectUpdate) (err error) { + var ( + website model.Website + oldRewriteContent []byte + ) + website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + absolutePath := path.Join(GetSitePath(website, SiteRedirectDir), req.Name+".conf") + fileOp := files.NewFileOp() + oldRewriteContent, err = fileOp.GetContent(absolutePath) + if err != nil { + return err + } + if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil { + return err + } + defer func() { + if err != nil { + _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm) + } + }() + return updateNginxConfig(constant.NginxScopeServer, nil, &website) +} + +func (w WebsiteService) LoadWebsiteDirConfig(req request.WebsiteCommonReq) (*response.WebsiteDirConfig, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return nil, err + } + res := &response.WebsiteDirConfig{} + absoluteIndexPath := GetSitePath(website, SiteIndexDir) + var appFs = afero.NewOsFs() + info, err := appFs.Stat(absoluteIndexPath) + if err != nil { + return nil, err + } + res.User = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Uid), 10) + res.UserGroup = strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Gid), 10) + + indexFiles, err := os.ReadDir(absoluteIndexPath) + if err != nil { + return nil, err + } + res.Dirs = []string{"/"} + for _, file := range indexFiles { + if !file.IsDir() || file.Name() == "node_modules" || file.Name() == "vendor" { + continue + } + res.Dirs = append(res.Dirs, fmt.Sprintf("/%s", file.Name())) + fileInfo, _ := file.Info() + if fileInfo.Sys().(*syscall.Stat_t).Uid != 1000 || fileInfo.Sys().(*syscall.Stat_t).Gid != 1000 { + res.Msg = i18n.GetMsgByKey("ErrPathPermission") + } + childFiles, _ := os.ReadDir(absoluteIndexPath + "/" + file.Name()) + for _, childFile := range childFiles { + if !childFile.IsDir() { + continue + } + childInfo, _ := childFile.Info() + if childInfo.Sys().(*syscall.Stat_t).Uid != 1000 || childInfo.Sys().(*syscall.Stat_t).Gid != 1000 { + res.Msg = i18n.GetMsgByKey("ErrPathPermission") + } + res.Dirs = append(res.Dirs, fmt.Sprintf("/%s/%s", file.Name(), childFile.Name())) + } + } + + return res, nil +} + +func (w WebsiteService) GetDefaultHtml(resourceType string) (*response.WebsiteHtmlRes, error) { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + rootPath := path.Join(nginxInstall.GetPath(), "root") + fileOp := files.NewFileOp() + defaultPath := path.Join(rootPath, "default") + if !fileOp.Stat(defaultPath) { + _ = fileOp.CreateDir(defaultPath, constant.DirPerm) + } + + res := &response.WebsiteHtmlRes{} + + switch resourceType { + case "404": + resourcePath := path.Join(defaultPath, "404.html") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.NotFoundHTML) + return res, nil + case "php": + resourcePath := path.Join(defaultPath, "index.php") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.IndexPHP) + return res, nil + case "index": + resourcePath := path.Join(defaultPath, "index.html") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.Index) + return res, nil + case "domain404": + resourcePath := path.Join(rootPath, "404.html") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.DomainNotFoundHTML) + return res, nil + case "stop": + resourcePath := path.Join(rootPath, "stop", "index.html") + if content, _ := getResourceContent(fileOp, resourcePath); content != "" { + res.Content = content + return res, nil + } + res.Content = string(nginx_conf.StopHTML) + return res, nil + } + return res, nil +} + +func (w WebsiteService) UpdateDefaultHtml(req request.WebsiteHtmlUpdate) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + rootPath := path.Join(nginxInstall.GetPath(), "root") + fileOp := files.NewFileOp() + defaultPath := path.Join(rootPath, "default") + if !fileOp.Stat(defaultPath) { + _ = fileOp.CreateDir(defaultPath, constant.DirPerm) + } + var resourcePath string + switch req.Type { + case "404": + resourcePath = path.Join(defaultPath, "404.html") + if req.Sync { + websites, _ := websiteRepo.GetBy(repo.WithTypes([]string{constant.Static, constant.Runtime})) + for _, website := range websites { + filePath := path.Join(GetSitePath(website, SiteIndexDir), "404.html") + if fileOp.Stat(filePath) { + _ = fileOp.SaveFile(filePath, req.Content, constant.DirPerm) + } + } + } + case "php": + resourcePath = path.Join(defaultPath, "index.php") + case "index": + resourcePath = path.Join(defaultPath, "index.html") + case "domain404": + resourcePath = path.Join(rootPath, "404.html") + case "stop": + resourcePath = path.Join(rootPath, "stop", "index.html") + default: + return nil + } + return fileOp.SaveFile(resourcePath, req.Content, constant.DirPerm) +} + +func (w WebsiteService) ChangeGroup(group, newGroup uint) error { + return websiteRepo.UpdateGroup(group, newGroup) +} + +func (w WebsiteService) SetRealIPConfig(req request.WebsiteRealIP) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + params := []dto.NginxParam{ + {Name: "real_ip_recursive", Params: []string{"on"}}, + {Name: "set_real_ip_from", Params: []string{}}, + {Name: "real_ip_header", Params: []string{}}, + } + if req.Open { + if err := deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil { + return err + } + params = []dto.NginxParam{ + {Name: "real_ip_recursive", Params: []string{"on"}}, + } + var ips []string + ipArray := strings.Split(req.IPFrom, "\n") + for _, ip := range ipArray { + if ip == "" { + continue + } + if parsedIP := net.ParseIP(ip); parsedIP == nil { + if _, _, err := net.ParseCIDR(ip); err != nil { + return buserr.New("ErrParseIP") + } + } + ips = append(ips, strings.TrimSpace(ip)) + } + for _, ip := range ips { + params = append(params, dto.NginxParam{Name: "set_real_ip_from", Params: []string{ip}}) + } + if req.IPHeader == "other" { + params = append(params, dto.NginxParam{Name: "real_ip_header", Params: []string{req.IPOther}}) + } else { + params = append(params, dto.NginxParam{Name: "real_ip_header", Params: []string{req.IPHeader}}) + } + return updateNginxConfig(constant.NginxScopeServer, params, &website) + } + return deleteNginxConfig(constant.NginxScopeServer, params, &website) +} + +func (w WebsiteService) GetRealIPConfig(websiteID uint) (*response.WebsiteRealIP, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(websiteID)) + if err != nil { + return nil, err + } + params, err := getNginxParamsByKeys(constant.NginxScopeServer, []string{"real_ip_recursive"}, &website) + if err != nil { + return nil, err + } + if len(params) == 0 || len(params[0].Params) == 0 { + return &response.WebsiteRealIP{Open: false}, nil + } + params, err = getNginxParamsByKeys(constant.NginxScopeServer, []string{"set_real_ip_from", "real_ip_header"}, &website) + if err != nil { + return nil, err + } + res := &response.WebsiteRealIP{ + Open: true, + } + var ips []string + for _, param := range params { + if param.Name == "set_real_ip_from" { + ips = append(ips, param.Params...) + } + if param.Name == "real_ip_header" { + if _, ok := dto.RealIPKeys[param.Params[0]]; ok { + res.IPHeader = param.Params[0] + } else { + res.IPHeader = "other" + res.IPOther = param.Params[0] + } + } + } + res.IPFrom = strings.Join(ips, "\n") + return res, err +} + +func (w WebsiteService) GetWebsiteResource(websiteID uint) ([]response.Resource, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(websiteID)) + if err != nil { + return nil, err + } + var ( + res []response.Resource + databaseID uint + databaseType string + ) + if website.Type == constant.Runtime { + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) + if err != nil { + return nil, err + } + res = append(res, response.Resource{ + Name: runtime.Name, + Type: "runtime", + ResourceID: runtime.ID, + Detail: runtime, + }) + } + if website.Type == constant.Deployment { + install, err := appInstallRepo.GetFirst(repo.WithByID(website.AppInstallID)) + if err != nil { + return nil, err + } + res = append(res, response.Resource{ + Name: install.Name, + Type: "app", + ResourceID: install.ID, + Detail: install, + }) + installResources, _ := appInstallResourceRepo.GetBy(appInstallResourceRepo.WithAppInstallId(install.ID)) + for _, resource := range installResources { + if resource.Key == constant.AppMysql || resource.Key == constant.AppMariaDB || resource.Key == constant.AppPostgres || resource.Key == constant.AppPostgresql { + databaseType = resource.Key + databaseID = resource.ResourceId + } + } + } + if website.DbID > 0 { + databaseType = website.DbType + databaseID = website.DbID + } + if databaseID > 0 { + switch databaseType { + case constant.AppMysql, constant.AppMariaDB: + db, _ := mysqlRepo.Get(repo.WithByID(databaseID)) + if db.ID > 0 { + res = append(res, response.Resource{ + Name: db.Name, + Type: "database", + ResourceID: db.ID, + Detail: db, + }) + } + case constant.AppPostgresql, constant.AppPostgres: + db, _ := postgresqlRepo.Get(repo.WithByID(databaseID)) + if db.ID > 0 { + res = append(res, response.Resource{ + Name: db.Name, + Type: "database", + ResourceID: db.ID, + Detail: db, + }) + } + } + } + + return res, nil +} + +func (w WebsiteService) ListDatabases() ([]response.Database, error) { + var res []response.Database + mysqlDBs, _ := mysqlRepo.List() + for _, db := range mysqlDBs { + database, _ := databaseRepo.Get(repo.WithByName(db.MysqlName)) + if database.ID > 0 { + res = append(res, response.Database{ + ID: db.ID, + Name: db.Name, + Type: database.Type, + From: database.From, + DatabaseName: database.Name, + }) + } + } + pgSqls, _ := postgresqlRepo.List() + for _, db := range pgSqls { + database, _ := databaseRepo.Get(repo.WithByName(db.PostgresqlName)) + if database.ID > 0 { + res = append(res, response.Database{ + ID: db.ID, + Name: db.Name, + Type: database.Type, + From: database.From, + DatabaseName: database.Name, + }) + } + } + return res, nil +} + +func (w WebsiteService) ChangeDatabase(req request.ChangeDatabase) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + if website.DbID == req.DatabaseID { + return nil + } + website.DbID = req.DatabaseID + website.DbType = req.DatabaseType + return websiteRepo.Save(context.Background(), &website) +} + +func (w WebsiteService) OperateCrossSiteAccess(req request.CrossSiteAccessOp) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + if req.Operation == constant.StatusEnable { + createOpenBasedirConfig(&website) + } + if req.Operation == constant.StatusDisable { + fileOp := files.NewFileOp() + return fileOp.DeleteFile(path.Join(GetSitePath(website, SiteIndexDir), ".user.ini")) + } + return nil +} + +func (w WebsiteService) ExecComposer(req request.ExecComposerReq) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + sitePath := GetSitePath(website, SiteDir) + if !strings.Contains(req.Dir, sitePath) { + return buserr.New("ErrWebsiteDir") + } + if !files.NewFileOp().Stat(path.Join(req.Dir, "composer.json")) { + return buserr.New("ErrComposerFileNotFound") + } + if task.CheckResourceTaskIsExecuting(task.TaskExec, req.Command, website.ID) { + return buserr.New("ErrInstallExtension") + } + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + var command string + if req.Command != "custom" { + command = fmt.Sprintf("%s %s", req.Command, req.ExtCommand) + } else { + command = req.ExtCommand + } + resourceName := fmt.Sprintf("composer %s", command) + composerTask, err := task.NewTaskWithOps(resourceName, task.TaskExec, req.Command, req.TaskID, website.ID) + if err != nil { + return err + } + cmdMgr := cmd.NewCommandMgr(cmd.WithTask(*composerTask), cmd.WithTimeout(20*time.Minute)) + siteDir, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR")) + execDir := strings.ReplaceAll(req.Dir, siteDir.Value, "/www") + composerTask.AddSubTask("", func(t *task.Task) error { + cmdStr := fmt.Sprintf("docker exec -u %s %s sh -c 'composer config -g repo.packagist composer %s && composer %s --working-dir=%s'", req.User, runtime.ContainerName, req.Mirror, command, execDir) + err = cmdMgr.RunBashC(cmdStr) + if err != nil { + return err + } + return nil + }, nil) + go func() { + _ = composerTask.Execute() + }() + return nil +} + +func (w WebsiteService) UpdateStream(req request.StreamUpdate) error { + if req.StreamConfig.StreamPorts == "" { + return buserr.New("ErrTypePortRange") + } + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + website.StreamPorts = req.StreamConfig.StreamPorts + ports := strings.Split(req.StreamConfig.StreamPorts, ",") + for _, port := range ports { + portNum, _ := strconv.Atoi(port) + if err = checkWebsitePort(nginxFull.Install.HttpsPort, portNum, website.Type); err != nil { + return err + } + } + + config := nginxFull.SiteConfig.Config + servers := config.FindServers() + if len(servers) == 0 { + return errors.New("nginx config is not valid") + } + server := servers[0] + server.Listens = []*components.ServerListen{} + var params []string + if req.UDP { + params = []string{"udp"} + } + for _, port := range ports { + server.UpdateListen(port, false, params...) + if website.IPV6 { + server.UpdateListen("[::]:"+port, false, params...) + } + } + upstream := components.Upstream{ + UpstreamName: website.Alias, + } + if req.Algorithm != "default" { + upstream.UpdateDirective(req.Algorithm, []string{}) + } + upstream.UpstreamServers = parseUpstreamServers(req.Servers) + for i, dir := range config.Block.Directives { + if dir.GetName() == "upstream" && dir.GetParameters()[0] == website.Alias { + config.Block.Directives[i] = &upstream + } + } + + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + if err = nginxCheckAndReload(nginxFull.SiteConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName); err != nil { + return err + } + website.StreamPorts = req.StreamConfig.StreamPorts + return websiteRepo.Save(context.Background(), &website) +} diff --git a/agent/app/service/website_acme_account.go b/agent/app/service/website_acme_account.go new file mode 100644 index 0000000..f002326 --- /dev/null +++ b/agent/app/service/website_acme_account.go @@ -0,0 +1,96 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/ssl" +) + +type WebsiteAcmeAccountService struct { +} + +type IWebsiteAcmeAccountService interface { + Page(search dto.PageInfo) (int64, []response.WebsiteAcmeAccountDTO, error) + Create(create request.WebsiteAcmeAccountCreate) (*response.WebsiteAcmeAccountDTO, error) + Delete(id uint) error + Update(update request.WebsiteAcmeAccountUpdate) (*response.WebsiteAcmeAccountDTO, error) +} + +func NewIWebsiteAcmeAccountService() IWebsiteAcmeAccountService { + return &WebsiteAcmeAccountService{} +} + +func (w WebsiteAcmeAccountService) Page(search dto.PageInfo) (int64, []response.WebsiteAcmeAccountDTO, error) { + total, accounts, err := websiteAcmeRepo.Page(search.Page, search.PageSize, repo.WithOrderBy("created_at desc")) + var accountDTOs []response.WebsiteAcmeAccountDTO + for _, account := range accounts { + accountDTOs = append(accountDTOs, response.WebsiteAcmeAccountDTO{ + WebsiteAcmeAccount: account, + }) + } + return total, accountDTOs, err +} + +func (w WebsiteAcmeAccountService) Create(create request.WebsiteAcmeAccountCreate) (*response.WebsiteAcmeAccountDTO, error) { + exist, _ := websiteAcmeRepo.GetFirst(websiteAcmeRepo.WithEmail(create.Email), websiteAcmeRepo.WithType(create.Type)) + if exist != nil { + return nil, buserr.New("ErrEmailIsExist") + } + acmeAccount := &model.WebsiteAcmeAccount{ + Email: create.Email, + Type: create.Type, + KeyType: create.KeyType, + UseProxy: create.UseProxy, + UseEAB: create.UseEAB, + } + + if create.Type == "google" || create.Type == "freessl" || (create.Type == "custom" && create.UseEAB) { + if create.EabKid == "" || create.EabHmacKey == "" { + return nil, buserr.New("ErrEabKidOrEabHmacKeyCannotBlank") + } + acmeAccount.EabKid = create.EabKid + acmeAccount.EabHmacKey = create.EabHmacKey + } + if create.Type == "custom" && create.CaDirURL != "" { + acmeAccount.CaDirURL = create.CaDirURL + } + + client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy)) + if err != nil { + return nil, err + } + privateKey, err := ssl.GetPrivateKey(client.User.GetPrivateKey(), ssl.KeyType(create.KeyType)) + if err != nil { + return nil, err + } + acmeAccount.PrivateKey = string(privateKey) + acmeAccount.URL = client.User.Registration.URI + + if err := websiteAcmeRepo.Create(*acmeAccount); err != nil { + return nil, err + } + return &response.WebsiteAcmeAccountDTO{WebsiteAcmeAccount: *acmeAccount}, nil +} + +func (w WebsiteAcmeAccountService) Delete(id uint) error { + if ssls, _ := websiteSSLRepo.List(websiteSSLRepo.WithByAcmeAccountId(id)); len(ssls) > 0 { + return buserr.New("ErrAccountCannotDelete") + } + return websiteAcmeRepo.DeleteBy(repo.WithByID(id)) +} + +func (w WebsiteAcmeAccountService) Update(update request.WebsiteAcmeAccountUpdate) (*response.WebsiteAcmeAccountDTO, error) { + account, err := websiteAcmeRepo.GetFirst(repo.WithByID(update.ID)) + if err != nil { + return nil, err + } + account.UseProxy = update.UseProxy + if err := websiteAcmeRepo.Save(*account); err != nil { + return nil, err + } + return &response.WebsiteAcmeAccountDTO{WebsiteAcmeAccount: *account}, nil +} diff --git a/agent/app/service/website_auth_basic.go b/agent/app/service/website_auth_basic.go new file mode 100644 index 0000000..3d1c803 --- /dev/null +++ b/agent/app/service/website_auth_basic.go @@ -0,0 +1,312 @@ +package service + +import ( + "bufio" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" + "golang.org/x/crypto/bcrypt" + "os" + "path" + "strings" +) + +func (w WebsiteService) GetAuthBasics(req request.NginxAuthReq) (res response.NginxAuthRes, err error) { + var ( + website model.Website + authContent []byte + nginxParams []response.NginxParam + ) + website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return + } + absoluteAuthPath := GetSitePath(website, SiteRootAuthBasicPath) + fileOp := files.NewFileOp() + if !fileOp.Stat(absoluteAuthPath) { + return + } + nginxParams, err = getNginxParamsByKeys(constant.NginxScopeServer, []string{"auth_basic"}, &website) + if err != nil { + return + } + res.Enable = len(nginxParams[0].Params) > 0 + authContent, err = fileOp.GetContent(absoluteAuthPath) + authArray := strings.Split(string(authContent), "\n") + for _, line := range authArray { + if line == "" { + continue + } + params := strings.Split(line, ":") + auth := dto.NginxAuth{ + Username: params[0], + } + if len(params) == 3 { + auth.Remark = params[2] + } + res.Items = append(res.Items, auth) + } + return +} + +func (w WebsiteService) UpdateAuthBasic(req request.NginxAuthUpdate) (err error) { + var ( + website model.Website + params []dto.NginxParam + authContent []byte + authArray []string + ) + website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + authPath := fmt.Sprintf("/www/sites/%s/auth_basic/auth.pass", website.Alias) + absoluteAuthPath := GetSitePath(website, SiteRootAuthBasicPath) + fileOp := files.NewFileOp() + if !fileOp.Stat(path.Dir(absoluteAuthPath)) { + _ = fileOp.CreateDir(path.Dir(absoluteAuthPath), constant.DirPerm) + } + if !fileOp.Stat(absoluteAuthPath) { + _ = fileOp.CreateFile(absoluteAuthPath) + } + + params = append(params, dto.NginxParam{Name: "auth_basic", Params: []string{`"Authentication"`}}) + params = append(params, dto.NginxParam{Name: "auth_basic_user_file", Params: []string{authPath}}) + authContent, err = fileOp.GetContent(absoluteAuthPath) + if err != nil { + return + } + if len(authContent) > 0 { + authArray = strings.Split(string(authContent), "\n") + } + switch req.Operate { + case "disable": + return deleteNginxConfig(constant.NginxScopeServer, params, &website) + case "enable": + return updateNginxConfig(constant.NginxScopeServer, params, &website) + case "create": + for _, line := range authArray { + authParams := strings.Split(line, ":") + username := authParams[0] + if username == req.Username { + err = buserr.New("ErrUsernameIsExist") + return + } + } + var passwdHash []byte + passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + return + } + line := fmt.Sprintf("%s:%s\n", req.Username, passwdHash) + if req.Remark != "" { + line = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark) + } + authArray = append(authArray, line) + case "edit": + userExist := false + for index, line := range authArray { + authParams := strings.Split(line, ":") + username := authParams[0] + if username == req.Username { + userExist = true + var passwdHash []byte + passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + return + } + userPasswd := fmt.Sprintf("%s:%s\n", req.Username, passwdHash) + if req.Remark != "" { + userPasswd = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark) + } + authArray[index] = userPasswd + } + } + if !userExist { + err = buserr.New("ErrUsernameIsNotExist") + return + } + case "delete": + deleteIndex := -1 + for index, line := range authArray { + authParams := strings.Split(line, ":") + username := authParams[0] + if username == req.Username { + deleteIndex = index + } + } + if deleteIndex < 0 { + return + } + authArray = append(authArray[:deleteIndex], authArray[deleteIndex+1:]...) + } + + var passFile *os.File + passFile, err = os.Create(absoluteAuthPath) + if err != nil { + return + } + defer passFile.Close() + writer := bufio.NewWriter(passFile) + for _, line := range authArray { + if line == "" { + continue + } + _, err = writer.WriteString(line + "\n") + if err != nil { + return + } + } + err = writer.Flush() + if err != nil { + return + } + authContent, err = fileOp.GetContent(absoluteAuthPath) + if err != nil { + return + } + if len(authContent) == 0 { + if err = deleteNginxConfig(constant.NginxScopeServer, params, &website); err != nil { + return + } + } + return +} + +func (w WebsiteService) GetPathAuthBasics(req request.NginxAuthReq) (res []response.NginxPathAuthRes, err error) { + var ( + website model.Website + authContent []byte + ) + website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return + } + fileOp := files.NewFileOp() + absoluteAuthDir := GetSitePath(website, SitePathAuthBasicDir) + passDir := path.Join(absoluteAuthDir, "pass") + if !fileOp.Stat(absoluteAuthDir) || !fileOp.Stat(passDir) { + return + } + + entries, err := os.ReadDir(absoluteAuthDir) + if err != nil { + return nil, err + } + + for _, entry := range entries { + if !entry.IsDir() { + name := strings.TrimSuffix(entry.Name(), ".conf") + pathAuth := dto.NginxPathAuth{ + Name: name, + } + configPath := path.Join(absoluteAuthDir, entry.Name()) + content, err := fileOp.GetContent(configPath) + if err != nil { + return nil, err + } + config, err := parser.NewStringParser(string(content)).Parse() + if err != nil { + return nil, err + } + directives := config.Directives + location, _ := directives[0].(*components.Location) + pathAuth.Path = strings.TrimPrefix(location.Match, "^") + passPath := path.Join(passDir, fmt.Sprintf("%s.pass", name)) + authContent, err = fileOp.GetContent(passPath) + if err != nil { + return nil, err + } + authArray := strings.Split(string(authContent), "\n") + for _, line := range authArray { + if line == "" { + continue + } + params := strings.Split(line, ":") + pathAuth.Username = params[0] + if len(params) == 3 { + pathAuth.Remark = params[2] + } + } + res = append(res, response.NginxPathAuthRes{ + NginxPathAuth: pathAuth, + }) + } + } + return +} + +func (w WebsiteService) UpdatePathAuthBasic(req request.NginxPathAuthUpdate) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + fileOp := files.NewFileOp() + authDir := GetSitePath(website, SitePathAuthBasicDir) + if !fileOp.Stat(authDir) { + _ = fileOp.CreateDir(authDir, constant.DirPerm) + } + passDir := path.Join(authDir, "pass") + if !fileOp.Stat(passDir) { + _ = fileOp.CreateDir(passDir, constant.DirPerm) + } + confPath := path.Join(authDir, fmt.Sprintf("%s.conf", req.Name)) + passPath := path.Join(passDir, fmt.Sprintf("%s.pass", req.Name)) + var config *components.Config + switch req.Operate { + case "delete": + _ = fileOp.DeleteFile(confPath) + _ = fileOp.DeleteFile(passPath) + return updateNginxConfig(constant.NginxScopeServer, nil, &website) + case "create": + config, err = parser.NewStringParser(string(nginx_conf.PathAuth)).Parse() + if err != nil { + return err + } + if fileOp.Stat(confPath) || fileOp.Stat(passPath) { + return buserr.New("ErrNameIsExist") + } + case "edit": + par, err := parser.NewParser(confPath) + if err != nil { + return err + } + config, err = par.Parse() + if err != nil { + return err + } + } + config.FilePath = confPath + directives := config.Directives + location, _ := directives[0].(*components.Location) + location.UpdateDirective("auth_basic_user_file", []string{fmt.Sprintf("/www/sites/%s/path_auth/pass/%s", website.Alias, fmt.Sprintf("%s.pass", req.Name))}) + location.ChangePath("~*", fmt.Sprintf("^%s", req.Path)) + var passwdHash []byte + passwdHash, err = bcrypt.GenerateFromPassword([]byte(req.Password), bcrypt.DefaultCost) + if err != nil { + return err + } + line := fmt.Sprintf("%s:%s\n", req.Username, passwdHash) + if req.Remark != "" { + line = fmt.Sprintf("%s:%s:%s\n", req.Username, passwdHash, req.Remark) + } + _ = fileOp.SaveFile(passPath, line, constant.DirPerm) + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return buserr.WithErr("ErrUpdateBuWebsite", err) + } + nginxInclude := fmt.Sprintf("/www/sites/%s/path_auth/*.conf", website.Alias) + if err = updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil { + return nil + } + return nil +} diff --git a/agent/app/service/website_ca.go b/agent/app/service/website_ca.go new file mode 100644 index 0000000..e5e765c --- /dev/null +++ b/agent/app/service/website_ca.go @@ -0,0 +1,450 @@ +package service + +import ( + "bytes" + "context" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "log" + "math/big" + "net" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/utils/common" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/ssl" + "github.com/go-acme/lego/v4/certcrypto" +) + +type WebsiteCAService struct { +} + +type IWebsiteCAService interface { + Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error) + Create(create request.WebsiteCACreate) (*request.WebsiteCACreate, error) + GetCA(id uint) (*response.WebsiteCADTO, error) + Delete(id uint) error + ObtainSSL(req request.WebsiteCAObtain) (*model.WebsiteSSL, error) + DownloadFile(id uint) (*os.File, error) +} + +func NewIWebsiteCAService() IWebsiteCAService { + return &WebsiteCAService{} +} + +func (w WebsiteCAService) Page(search request.WebsiteCASearch) (int64, []response.WebsiteCADTO, error) { + total, cas, err := websiteCARepo.Page(search.Page, search.PageSize, repo.WithOrderBy("created_at desc")) + if err != nil { + return 0, nil, err + } + var caDTOs []response.WebsiteCADTO + for _, ca := range cas { + caDTOs = append(caDTOs, response.WebsiteCADTO{ + WebsiteCA: ca, + }) + } + return total, caDTOs, err +} + +func (w WebsiteCAService) Create(create request.WebsiteCACreate) (*request.WebsiteCACreate, error) { + if exist, _ := websiteCARepo.GetFirst(repo.WithByName(create.Name)); exist.ID > 0 { + return nil, buserr.New("ErrNameIsExist") + } + + ca := &model.WebsiteCA{ + Name: create.Name, + KeyType: create.KeyType, + } + + pkixName := pkix.Name{ + CommonName: create.CommonName, + Country: []string{create.Country}, + Organization: []string{create.Organization}, + OrganizationalUnit: []string{create.OrganizationUint}, + } + if create.Province != "" { + pkixName.Province = []string{create.Province} + } + if create.City != "" { + pkixName.Locality = []string{create.City} + } + + rootCA := &x509.Certificate{ + SerialNumber: big.NewInt(time.Now().Unix() + 1), + Subject: pkixName, + NotBefore: time.Now(), + NotAfter: time.Now().AddDate(10, 0, 0), + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 1, + MaxPathLenZero: false, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + } + + interPrivateKey, interPublicKey, privateBytes, err := createPrivateKey(create.KeyType) + if err != nil { + return nil, err + } + ca.PrivateKey = string(privateBytes) + + rootDer, err := x509.CreateCertificate(rand.Reader, rootCA, rootCA, interPublicKey, interPrivateKey) + if err != nil { + return nil, err + } + rootCert, err := x509.ParseCertificate(rootDer) + if err != nil { + return nil, err + } + certBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: rootCert.Raw, + } + pemData := pem.EncodeToMemory(certBlock) + ca.CSR = string(pemData) + + if err := websiteCARepo.Create(context.Background(), ca); err != nil { + return nil, err + } + return &create, nil +} + +func (w WebsiteCAService) GetCA(id uint) (*response.WebsiteCADTO, error) { + res := &response.WebsiteCADTO{} + ca, err := websiteCARepo.GetFirst(repo.WithByID(id)) + if err != nil { + return nil, err + } + res.WebsiteCA = ca + certBlock, _ := pem.Decode([]byte(ca.CSR)) + if certBlock == nil { + return nil, buserr.New("ErrSSLCertificateFormat") + } + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return nil, err + } + res.CommonName = cert.Issuer.CommonName + res.Organization = strings.Join(cert.Issuer.Organization, ",") + res.Country = strings.Join(cert.Issuer.Country, ",") + res.Province = strings.Join(cert.Issuer.Province, ",") + res.City = strings.Join(cert.Issuer.Locality, ",") + res.OrganizationUint = strings.Join(cert.Issuer.OrganizationalUnit, ",") + + return res, nil +} + +func (w WebsiteCAService) Delete(id uint) error { + ssls, _ := websiteSSLRepo.List(websiteSSLRepo.WithByCAID(id)) + if len(ssls) > 0 { + return buserr.New("ErrDeleteCAWithSSL") + } + exist, err := websiteCARepo.GetFirst(repo.WithByID(id)) + if err != nil { + return err + } + if exist.Name == "1Panel" { + return buserr.New("ErrDefaultCA") + } + return websiteCARepo.DeleteBy(repo.WithByID(id)) +} + +func (w WebsiteCAService) ObtainSSL(req request.WebsiteCAObtain) (*model.WebsiteSSL, error) { + var ( + domains []string + ips []net.IP + websiteSSL = &model.WebsiteSSL{} + err error + ca model.WebsiteCA + ) + if req.Renew { + websiteSSL, err = websiteSSLRepo.GetFirst(repo.WithByID(req.SSLID)) + if err != nil { + return nil, err + } + ca, err = websiteCARepo.GetFirst(repo.WithByID(websiteSSL.CaID)) + if err != nil { + return nil, err + } + existDomains := []string{websiteSSL.PrimaryDomain} + if websiteSSL.Domains != "" { + existDomains = append(existDomains, strings.Split(websiteSSL.Domains, ",")...) + } + for _, domain := range existDomains { + if ipAddress := net.ParseIP(domain); ipAddress == nil { + domains = append(domains, domain) + } else { + ips = append(ips, ipAddress) + } + } + } else { + ca, err = websiteCARepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return nil, err + } + websiteSSL = &model.WebsiteSSL{ + Provider: constant.SelfSigned, + KeyType: req.KeyType, + PushDir: req.PushDir, + CaID: ca.ID, + AutoRenew: req.AutoRenew, + Description: req.Description, + ExecShell: req.ExecShell, + } + if req.ExecShell { + websiteSSL.Shell = req.Shell + } + if req.PushDir { + if !files.NewFileOp().Stat(req.Dir) { + return nil, buserr.New("ErrLinkPathNotFound") + } + websiteSSL.Dir = req.Dir + } + if req.Domains != "" { + domainArray := strings.Split(req.Domains, "\n") + for _, domain := range domainArray { + if ipAddress := net.ParseIP(domain); ipAddress == nil { + if domain != "localhost" && !common.IsValidDomain(domain) { + err = buserr.WithName("ErrDomainFormat", domain) + return nil, err + } + domains = append(domains, domain) + } else { + ips = append(ips, ipAddress) + } + } + if len(domains) > 0 { + websiteSSL.PrimaryDomain = domains[0] + websiteSSL.Domains = strings.Join(domains[1:], ",") + } + ipStrings := make([]string, len(ips)) + for i, ip := range ips { + ipStrings[i] = ip.String() + } + if websiteSSL.PrimaryDomain == "" && len(ips) > 0 { + websiteSSL.PrimaryDomain = ipStrings[0] + ipStrings = ipStrings[1:] + } + if len(ipStrings) > 0 { + if websiteSSL.Domains != "" { + websiteSSL.Domains += "," + } + websiteSSL.Domains += strings.Join(ipStrings, ",") + } + + } + } + + rootCertBlock, _ := pem.Decode([]byte(ca.CSR)) + if rootCertBlock == nil { + return nil, buserr.New("ErrSSLCertificateFormat") + } + rootCsr, err := x509.ParseCertificate(rootCertBlock.Bytes) + if err != nil { + return nil, err + } + rootPrivateKeyBlock, _ := pem.Decode([]byte(ca.PrivateKey)) + if rootPrivateKeyBlock == nil { + return nil, buserr.New("ErrSSLCertificateFormat") + } + + var rootPrivateKey any + if ssl.KeyType(ca.KeyType) == certcrypto.EC256 || ssl.KeyType(ca.KeyType) == certcrypto.EC384 { + rootPrivateKey, err = x509.ParseECPrivateKey(rootPrivateKeyBlock.Bytes) + if err != nil { + return nil, err + } + } else { + rootPrivateKey, err = x509.ParsePKCS1PrivateKey(rootPrivateKeyBlock.Bytes) + if err != nil { + return nil, err + } + } + interPrivateKey, interPublicKey, _, err := createPrivateKey(websiteSSL.KeyType) + if err != nil { + return nil, err + } + notAfter := time.Now() + if req.Unit == "year" { + notAfter = notAfter.AddDate(req.Time, 0, 0) + } else { + notAfter = notAfter.AddDate(0, 0, req.Time) + } + interCsr := &x509.Certificate{ + SerialNumber: big.NewInt(time.Now().Unix() + 2), + Subject: rootCsr.Subject, + NotBefore: time.Now(), + NotAfter: notAfter, + BasicConstraintsValid: true, + IsCA: true, + MaxPathLen: 0, + MaxPathLenZero: true, + KeyUsage: x509.KeyUsageCertSign | x509.KeyUsageCRLSign, + } + interDer, err := x509.CreateCertificate(rand.Reader, interCsr, rootCsr, interPublicKey, rootPrivateKey) + if err != nil { + return nil, err + } + interCert, err := x509.ParseCertificate(interDer) + if err != nil { + return nil, err + } + interCertBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: interCert.Raw, + } + _, publicKey, privateKeyBytes, err := createPrivateKey(websiteSSL.KeyType) + if err != nil { + return nil, err + } + commonName := "" + if len(domains) > 0 { + commonName = domains[0] + } + if len(ips) > 0 { + commonName = ips[0].String() + } + subject := rootCsr.Subject + subject.CommonName = commonName + csr := &x509.Certificate{ + SerialNumber: big.NewInt(time.Now().Unix() + 3), + Subject: subject, + NotBefore: time.Now(), + NotAfter: notAfter, + BasicConstraintsValid: true, + IsCA: false, + KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, + DNSNames: domains, + IPAddresses: ips, + } + + der, err := x509.CreateCertificate(rand.Reader, csr, interCert, publicKey, interPrivateKey) + if err != nil { + return nil, err + } + cert, err := x509.ParseCertificate(der) + if err != nil { + return nil, err + } + + certBlock := &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert.Raw, + } + websiteSSL.Pem = string(pem.EncodeToMemory(certBlock)) + string(pem.EncodeToMemory(rootCertBlock)) + string(pem.EncodeToMemory(interCertBlock)) + websiteSSL.PrivateKey = string(privateKeyBytes) + websiteSSL.ExpireDate = cert.NotAfter + websiteSSL.StartDate = cert.NotBefore + websiteSSL.Type = cert.Issuer.CommonName + websiteSSL.Organization = rootCsr.Subject.Organization[0] + websiteSSL.Status = constant.SSLReady + + if req.Renew { + if err := websiteSSLRepo.Save(websiteSSL); err != nil { + return nil, err + } + } else { + if err := websiteSSLRepo.Create(context.Background(), websiteSSL); err != nil { + return nil, err + } + } + + logFile, _ := os.OpenFile(path.Join(global.Dir.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", websiteSSL.PrimaryDomain, websiteSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + defer logFile.Close() + logger := log.New(logFile, "", log.LstdFlags) + logger.Println(i18n.GetMsgWithMap("ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")})) + saveCertificateFile(websiteSSL, logger) + if websiteSSL.ExecShell { + workDir := global.Dir.DataDir + if websiteSSL.PushDir { + workDir = websiteSSL.Dir + } + logger.Println(i18n.GetMsgByKey("ExecShellStart")) + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30*time.Minute), cmd.WithLogger(logger), cmd.WithWorkDir(workDir)) + if err = cmdMgr.RunBashC(websiteSSL.Shell); err != nil { + logger.Println(i18n.GetMsgWithMap("ErrExecShell", map[string]interface{}{"err": err.Error()})) + } else { + logger.Println(i18n.GetMsgByKey("ExecShellSuccess")) + } + } + reloadSystemSSL(websiteSSL, logger) + return websiteSSL, nil +} + +func createPrivateKey(keyType string) (privateKey any, publicKey any, privateKeyBytes []byte, err error) { + privateKey, err = certcrypto.GeneratePrivateKey(ssl.KeyType(keyType)) + if err != nil { + return + } + var ( + caPrivateKeyPEM = new(bytes.Buffer) + ) + if ssl.KeyType(keyType) == certcrypto.EC256 || ssl.KeyType(keyType) == certcrypto.EC384 { + publicKey = &privateKey.(*ecdsa.PrivateKey).PublicKey + publicKey = publicKey.(*ecdsa.PublicKey) + block := &pem.Block{ + Type: "EC PRIVATE KEY", + } + privateBytes, sErr := x509.MarshalECPrivateKey(privateKey.(*ecdsa.PrivateKey)) + if sErr != nil { + err = sErr + return + } + block.Bytes = privateBytes + _ = pem.Encode(caPrivateKeyPEM, block) + } else { + publicKey = &privateKey.(*rsa.PrivateKey).PublicKey + _ = pem.Encode(caPrivateKeyPEM, &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey.(*rsa.PrivateKey)), + }) + } + privateKeyBytes = caPrivateKeyPEM.Bytes() + return +} + +func (w WebsiteCAService) DownloadFile(id uint) (*os.File, error) { + ca, err := websiteCARepo.GetFirst(repo.WithByID(id)) + if err != nil { + return nil, err + } + fileOp := files.NewFileOp() + dir := path.Join(global.Dir.DataDir, "tmp/ssl", ca.Name) + if fileOp.Stat(dir) { + if err = fileOp.DeleteDir(dir); err != nil { + return nil, err + } + } + if err = fileOp.CreateDir(dir, constant.DirPerm); err != nil { + return nil, err + } + if err = fileOp.WriteFile(path.Join(dir, "ca.crt"), strings.NewReader(ca.CSR), constant.DirPerm); err != nil { + return nil, err + } + if err = fileOp.WriteFile(path.Join(dir, "ca.key"), strings.NewReader(ca.PrivateKey), constant.DirPerm); err != nil { + return nil, err + } + fileName := ca.Name + ".zip" + if err = fileOp.Compress([]string{path.Join(dir, "ca.crt"), path.Join(dir, "ca.key")}, dir, fileName, files.SdkZip, ""); err != nil { + return nil, err + } + return os.Open(path.Join(dir, fileName)) +} diff --git a/agent/app/service/website_dns_account.go b/agent/app/service/website_dns_account.go new file mode 100644 index 0000000..61b84e0 --- /dev/null +++ b/agent/app/service/website_dns_account.go @@ -0,0 +1,95 @@ +package service + +import ( + "encoding/json" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/buserr" +) + +type WebsiteDnsAccountService struct { +} + +type IWebsiteDnsAccountService interface { + Page(search dto.PageInfo) (int64, []response.WebsiteDnsAccountDTO, error) + Create(create request.WebsiteDnsAccountCreate) (request.WebsiteDnsAccountCreate, error) + Update(update request.WebsiteDnsAccountUpdate) (request.WebsiteDnsAccountUpdate, error) + Delete(id uint) error +} + +func NewIWebsiteDnsAccountService() IWebsiteDnsAccountService { + return &WebsiteDnsAccountService{} +} + +func (w WebsiteDnsAccountService) Page(search dto.PageInfo) (int64, []response.WebsiteDnsAccountDTO, error) { + total, accounts, err := websiteDnsRepo.Page(search.Page, search.PageSize, repo.WithOrderBy("created_at desc")) + var accountDTOs []response.WebsiteDnsAccountDTO + for _, account := range accounts { + auth := make(map[string]string) + _ = json.Unmarshal([]byte(account.Authorization), &auth) + accountDTOs = append(accountDTOs, response.WebsiteDnsAccountDTO{ + WebsiteDnsAccount: account, + Authorization: auth, + }) + } + return total, accountDTOs, err +} + +func (w WebsiteDnsAccountService) Create(create request.WebsiteDnsAccountCreate) (request.WebsiteDnsAccountCreate, error) { + exist, _ := websiteDnsRepo.GetFirst(repo.WithByName(create.Name)) + if exist != nil { + return request.WebsiteDnsAccountCreate{}, buserr.New("ErrNameIsExist") + } + + authorization, err := json.Marshal(create.Authorization) + if err != nil { + return request.WebsiteDnsAccountCreate{}, err + } + + if err := websiteDnsRepo.Create(model.WebsiteDnsAccount{ + Name: create.Name, + Type: create.Type, + Authorization: string(authorization), + }); err != nil { + return request.WebsiteDnsAccountCreate{}, err + } + + return create, nil +} + +func (w WebsiteDnsAccountService) Update(update request.WebsiteDnsAccountUpdate) (request.WebsiteDnsAccountUpdate, error) { + authorization, err := json.Marshal(update.Authorization) + if err != nil { + return request.WebsiteDnsAccountUpdate{}, err + } + exists, _ := websiteDnsRepo.List(repo.WithByName(update.Name)) + for _, exist := range exists { + if exist.ID != update.ID { + return request.WebsiteDnsAccountUpdate{}, buserr.New("ErrNameIsExist") + } + } + if err := websiteDnsRepo.Save(model.WebsiteDnsAccount{ + BaseModel: model.BaseModel{ + ID: update.ID, + }, + Name: update.Name, + Type: update.Type, + Authorization: string(authorization), + }); err != nil { + return request.WebsiteDnsAccountUpdate{}, err + } + + return update, nil +} + +func (w WebsiteDnsAccountService) Delete(id uint) error { + if ssls, _ := websiteSSLRepo.List(websiteSSLRepo.WithByDnsAccountId(id)); len(ssls) > 0 { + return buserr.New("ErrAccountCannotDelete") + } + return websiteDnsRepo.DeleteBy(repo.WithByID(id)) +} diff --git a/agent/app/service/website_domain.go b/agent/app/service/website_domain.go new file mode 100644 index 0000000..1bf2e00 --- /dev/null +++ b/agent/app/service/website_domain.go @@ -0,0 +1,185 @@ +package service + +import ( + "context" + "encoding/json" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "path" + "strconv" +) + +func (w WebsiteService) CreateWebsiteDomain(create request.WebsiteDomainCreate) ([]model.WebsiteDomain, error) { + var ( + domainModels []model.WebsiteDomain + addPorts []int + ) + httpPort, httpsPort, err := getAppInstallPort(constant.AppOpenresty) + if err != nil { + return nil, err + } + website, err := websiteRepo.GetFirst(repo.WithByID(create.WebsiteID)) + if err != nil { + return nil, err + } + + domainModels, addPorts, _, err = getWebsiteDomains(create.Domains, httpPort, httpsPort, create.WebsiteID) + if err != nil { + return nil, err + } + go func() { + _ = OperateFirewallPort(nil, addPorts) + }() + + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return nil, err + } + wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data") + fileOp := files.NewFileOp() + if fileOp.Stat(wafDataPath) { + websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json") + content, err := fileOp.GetContent(websitesConfigPath) + if err != nil { + return nil, err + } + var websitesArray []request.WafWebsite + if content != nil { + if err := json.Unmarshal(content, &websitesArray); err != nil { + return nil, err + } + } + for index, wafWebsite := range websitesArray { + if wafWebsite.Key == website.Alias { + wafSite := request.WafWebsite{ + Key: website.Alias, + Domains: wafWebsite.Domains, + Host: wafWebsite.Host, + } + for _, domain := range domainModels { + wafSite.Domains = append(wafSite.Domains, domain.Domain) + wafSite.Host = append(wafSite.Host, domain.Domain+":"+strconv.Itoa(domain.Port)) + } + if len(wafSite.Host) == 0 { + wafSite.Host = []string{} + } + websitesArray[index] = wafSite + break + } + } + websitesContent, err := json.Marshal(websitesArray) + if err != nil { + return nil, err + } + if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil { + return nil, err + } + } + + if err = addListenAndServerName(website, domainModels); err != nil { + return nil, err + } + + return domainModels, websiteDomainRepo.BatchCreate(context.TODO(), domainModels) +} + +func (w WebsiteService) GetWebsiteDomain(websiteId uint) ([]model.WebsiteDomain, error) { + return websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteId)) +} + +func (w WebsiteService) DeleteWebsiteDomain(domainId uint) error { + webSiteDomain, err := websiteDomainRepo.GetFirst(repo.WithByID(domainId)) + if err != nil { + return err + } + + if websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID)); len(websiteDomains) == 1 { + return fmt.Errorf("can not delete last domain") + } + website, err := websiteRepo.GetFirst(repo.WithByID(webSiteDomain.WebsiteID)) + if err != nil { + return err + } + var ports []int + if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithPort(webSiteDomain.Port)); len(oldDomains) == 1 { + ports = append(ports, webSiteDomain.Port) + } + + var domains []string + if oldDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(webSiteDomain.WebsiteID), websiteDomainRepo.WithDomain(webSiteDomain.Domain)); len(oldDomains) == 1 { + domains = append(domains, webSiteDomain.Domain) + } + + if len(ports) > 0 || len(domains) > 0 { + stringBinds := make([]string, len(ports)) + for i := 0; i < len(ports); i++ { + stringBinds[i] = strconv.Itoa(ports[i]) + } + if err := deleteListenAndServerName(website, stringBinds, domains); err != nil { + return err + } + } + + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data") + fileOp := files.NewFileOp() + if fileOp.Stat(wafDataPath) { + websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json") + content, err := fileOp.GetContent(websitesConfigPath) + if err != nil { + return err + } + var websitesArray []request.WafWebsite + var newWebsitesArray []request.WafWebsite + if content != nil { + if err := json.Unmarshal(content, &websitesArray); err != nil { + return err + } + } + for _, wafWebsite := range websitesArray { + if wafWebsite.Key == website.Alias { + wafSite := wafWebsite + oldDomains := wafSite.Domains + var newDomains []string + for _, domain := range oldDomains { + if domain == webSiteDomain.Domain { + continue + } + newDomains = append(newDomains, domain) + } + wafSite.Domains = newDomains + oldHostArray := wafSite.Host + var newHostArray []string + for _, host := range oldHostArray { + if host == webSiteDomain.Domain+":"+strconv.Itoa(webSiteDomain.Port) { + continue + } + newHostArray = append(newHostArray, host) + } + wafSite.Host = newHostArray + if len(wafSite.Host) == 0 { + wafSite.Host = []string{} + } + newWebsitesArray = append(newWebsitesArray, wafSite) + } else { + newWebsitesArray = append(newWebsitesArray, wafWebsite) + } + } + websitesContent, err := json.Marshal(newWebsitesArray) + if err != nil { + return err + } + if err = fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil { + return err + } + } + + return websiteDomainRepo.DeleteBy(context.TODO(), repo.WithByID(domainId)) +} diff --git a/agent/app/service/website_lb.go b/agent/app/service/website_lb.go new file mode 100644 index 0000000..9094d59 --- /dev/null +++ b/agent/app/service/website_lb.go @@ -0,0 +1,229 @@ +package service + +import ( + "bytes" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" + "os" + "path" + "strings" +) + +func (w WebsiteService) GetLoadBalances(id uint) ([]dto.NginxUpstream, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return nil, err + } + includeDir := GetSitePath(website, SiteUpstreamDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(includeDir) { + return nil, nil + } + entries, err := os.ReadDir(includeDir) + if err != nil { + return nil, err + } + var res []dto.NginxUpstream + for _, entry := range entries { + if entry.IsDir() { + continue + } + name := entry.Name() + if !strings.HasSuffix(name, ".conf") { + continue + } + upstreamName := strings.TrimSuffix(name, ".conf") + upstream := dto.NginxUpstream{ + Name: upstreamName, + } + upstreamPath := path.Join(includeDir, name) + content, err := fileOp.GetContent(upstreamPath) + if err != nil { + return nil, err + } + upstream.Content = string(content) + nginxParser, err := parser.NewParser(upstreamPath) + if err != nil { + return nil, err + } + config, err := nginxParser.Parse() + if err != nil { + return nil, err + } + upstreams := config.FindUpstreams() + for _, up := range upstreams { + if up.UpstreamName == upstreamName { + directives := up.GetDirectives() + for _, d := range directives { + dName := d.GetName() + if _, ok := dto.LBAlgorithms[dName]; ok { + upstream.Algorithm = dName + } + } + upstream.Servers = getNginxUpstreamServers(up.UpstreamServers) + } + } + res = append(res, upstream) + } + return res, nil +} + +func (w WebsiteService) CreateLoadBalance(req request.WebsiteLBCreate) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + includeDir := GetSitePath(website, SiteUpstreamDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(includeDir) { + _ = fileOp.CreateDir(includeDir, constant.DirPerm) + } + filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) + if fileOp.Stat(filePath) { + return buserr.New("ErrNameIsExist") + } + config, err := parser.NewStringParser(string(nginx_conf.Upstream)).Parse() + if err != nil { + return err + } + config.Block = &components.Block{} + config.FilePath = filePath + upstream := components.Upstream{ + UpstreamName: req.Name, + } + if req.Algorithm != "default" { + upstream.UpdateDirective(req.Algorithm, []string{}) + } + upstream.UpstreamServers = parseUpstreamServers(req.Servers) + config.Block.Directives = append(config.Block.Directives, &upstream) + + defer func() { + if err != nil { + _ = fileOp.DeleteFile(filePath) + } + }() + + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return buserr.WithErr("ErrUpdateBuWebsite", err) + } + nginxInclude := fmt.Sprintf("/www/sites/%s/upstream/*.conf", website.Alias) + if err = updateNginxConfig("", []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website); err != nil { + return err + } + return nil +} + +func (w WebsiteService) UpdateLoadBalance(req request.WebsiteLBUpdate) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + includeDir := GetSitePath(website, SiteUpstreamDir) + fileOp := files.NewFileOp() + filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) + if !fileOp.Stat(filePath) { + return nil + } + oldContent, err := fileOp.GetContent(filePath) + if err != nil { + return err + } + parser, err := parser.NewParser(filePath) + if err != nil { + return err + } + config, err := parser.Parse() + if err != nil { + return err + } + upstreams := config.FindUpstreams() + for _, up := range upstreams { + if up.UpstreamName == req.Name { + directives := up.GetDirectives() + for _, d := range directives { + dName := d.GetName() + if _, ok := dto.LBAlgorithms[dName]; ok { + up.RemoveDirective(dName, nil) + } + } + if req.Algorithm != "default" { + up.UpdateDirective(req.Algorithm, []string{}) + } + up.UpstreamServers = parseUpstreamServers(req.Servers) + } + } + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return buserr.WithErr("ErrUpdateBuWebsite", err) + } + return nginxCheckAndReload(string(oldContent), filePath, nginxInstall.ContainerName) +} + +func (w WebsiteService) DeleteLoadBalance(req request.WebsiteLBDelete) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + proxies, _ := w.GetProxies(website.ID) + if len(proxies) > 0 { + for _, proxy := range proxies { + if strings.HasSuffix(proxy.ProxyPass, fmt.Sprintf("://%s", req.Name)) { + return buserr.New("ErrProxyIsUsed") + } + } + } + + includeDir := GetSitePath(website, SiteUpstreamDir) + fileOp := files.NewFileOp() + filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) + if !fileOp.Stat(filePath) { + return nil + } + if err = fileOp.DeleteFile(filePath); err != nil { + return err + } + return opNginx(nginxInstall.ContainerName, constant.NginxReload) +} + +func (w WebsiteService) UpdateLoadBalanceFile(req request.WebsiteLBUpdateFile) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + includeDir := GetSitePath(website, SiteUpstreamDir) + filePath := path.Join(includeDir, fmt.Sprintf("%s.conf", req.Name)) + fileOp := files.NewFileOp() + oldContent, err := fileOp.GetContent(filePath) + if err != nil { + return err + } + if err = fileOp.WriteFile(filePath, strings.NewReader(req.Content), constant.DirPerm); err != nil { + return err + } + defer func() { + if err != nil { + _ = fileOp.WriteFile(filePath, bytes.NewReader(oldContent), constant.DirPerm) + } + }() + return opNginx(nginxInstall.ContainerName, constant.NginxReload) +} diff --git a/agent/app/service/website_op.go b/agent/app/service/website_op.go new file mode 100644 index 0000000..aca7678 --- /dev/null +++ b/agent/app/service/website_op.go @@ -0,0 +1,126 @@ +package service + +import ( + "context" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/i18n" + "sort" +) + +func (w WebsiteService) BatchOpWebsite(req request.BatchWebsiteOp) error { + websites, _ := websiteRepo.List(repo.WithByIDs(req.IDs)) + opTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("Status"), task.TaskBatch, task.TaskScopeWebsite, req.TaskID, 0) + if err != nil { + return err + } + sort.SliceStable(websites, func(i, j int) bool { + if websites[i].Type == constant.Subsite && websites[j].Type != constant.Subsite { + return true + } + if websites[i].Type != constant.Subsite && websites[j].Type == constant.Subsite { + return false + } + return false + }) + opWebsiteTask := func(t *task.Task) error { + for _, web := range websites { + msg := fmt.Sprintf("%s %s", i18n.GetMsgByKey(req.Operate), web.PrimaryDomain) + switch req.Operate { + case constant.StopWeb, constant.StartWeb: + if err := opWebsite(&web, req.Operate); err != nil { + t.LogFailedWithErr(msg, err) + continue + } + _ = websiteRepo.Save(context.Background(), &web) + case "delete": + if err := w.DeleteWebsite(request.WebsiteDelete{ + ID: web.ID, + }); err != nil { + t.LogFailedWithErr(msg, err) + continue + } + } + + t.LogSuccess(msg) + } + return nil + } + opTask.AddSubTask("", opWebsiteTask, nil) + + go func() { + _ = opTask.Execute() + }() + return nil +} + +func (w WebsiteService) BatchSetGroup(req request.BatchWebsiteGroup) error { + websites, _ := websiteRepo.List(repo.WithByIDs(req.IDs)) + for _, web := range websites { + web.WebsiteGroupID = req.GroupID + if err := websiteRepo.Save(context.Background(), &web); err != nil { + return err + } + } + return nil +} + +func (w WebsiteService) BatchSetHttps(ctx context.Context, req request.BatchWebsiteHttps) error { + websites, _ := websiteRepo.List(repo.WithByIDs(req.IDs)) + opTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("SSL"), task.TaskBatch, task.TaskScopeWebsite, req.TaskID, 0) + if err != nil { + return err + } + websiteHttpsOp := request.WebsiteHTTPSOp{ + Enable: true, + WebsiteSSLID: req.WebsiteSSLID, + Type: req.Type, + PrivateKey: req.PrivateKey, + Certificate: req.Certificate, + PrivateKeyPath: req.PrivateKeyPath, + CertificatePath: req.CertificatePath, + ImportType: req.ImportType, + HttpConfig: req.HttpConfig, + SSLProtocol: req.SSLProtocol, + Algorithm: req.Algorithm, + Hsts: req.Hsts, + HstsIncludeSubDomains: req.HstsIncludeSubDomains, + HttpsPorts: req.HttpsPorts, + Http3: req.Http3, + } + if req.Type == constant.SSLManual { + websiteSSL, err := getManualWebsiteSSL(websiteHttpsOp) + if err != nil { + return err + } + if err = websiteSSLRepo.Create(ctx, &websiteSSL); err != nil { + return err + } + websiteHttpsOp.Type = constant.SSLExisted + websiteHttpsOp.WebsiteSSLID = websiteSSL.ID + } + opWebsiteTask := func(t *task.Task) error { + for _, web := range websites { + if web.Type == constant.Stream { + continue + } + websiteHttpsOp.WebsiteID = web.ID + msg := fmt.Sprintf("%s [%s] %s", i18n.GetMsgByKey("Set"), web.PrimaryDomain, i18n.GetMsgByKey("SSL")) + if _, err := w.OpWebsiteHTTPS(ctx, websiteHttpsOp); err != nil { + t.LogFailedWithErr(msg, err) + continue + } + t.LogSuccess(msg) + } + return nil + } + + opTask.AddSubTask("", opWebsiteTask, nil) + go func() { + _ = opTask.Execute() + }() + return nil +} diff --git a/agent/app/service/website_proxy.go b/agent/app/service/website_proxy.go new file mode 100644 index 0000000..7ee6f6e --- /dev/null +++ b/agent/app/service/website_proxy.go @@ -0,0 +1,408 @@ +package service + +import ( + "bytes" + "errors" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "path" + "strconv" + "strings" +) + +func (w WebsiteService) OperateProxy(req request.WebsiteProxyConfig) (err error) { + var ( + website model.Website + par *parser.Parser + oldContent []byte + ) + + website, err = websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return + } + fileOp := files.NewFileOp() + includeDir := GetSitePath(website, SiteProxyDir) + if !fileOp.Stat(includeDir) { + _ = fileOp.CreateDir(includeDir, constant.DirPerm) + } + fileName := fmt.Sprintf("%s.conf", req.Name) + includePath := path.Join(includeDir, fileName) + backName := fmt.Sprintf("%s.bak", req.Name) + backPath := path.Join(includeDir, backName) + + if req.Operate == "create" && (fileOp.Stat(includePath) || fileOp.Stat(backPath)) { + err = buserr.New("ErrNameIsExist") + return + } + + defer func() { + if err != nil { + switch req.Operate { + case "create": + _ = fileOp.DeleteFile(includePath) + case "edit": + _ = fileOp.WriteFile(includePath, bytes.NewReader(oldContent), constant.DirPerm) + } + } + }() + + var config *components.Config + + switch req.Operate { + case "create": + config, err = parser.NewStringParser(string(nginx_conf.GetWebsiteFile("proxy.conf"))).Parse() + if err != nil { + return + } + case "edit": + par, err = parser.NewParser(includePath) + if err != nil { + return + } + config, err = par.Parse() + if err != nil { + return + } + oldContent, err = fileOp.GetContent(includePath) + if err != nil { + return + } + case "delete": + _ = fileOp.DeleteFile(includePath) + _ = fileOp.DeleteFile(backPath) + return updateNginxConfig(constant.NginxScopeServer, nil, &website) + case "disable": + _ = fileOp.Rename(includePath, backPath) + return updateNginxConfig(constant.NginxScopeServer, nil, &website) + case "enable": + _ = fileOp.Rename(backPath, includePath) + return updateNginxConfig(constant.NginxScopeServer, nil, &website) + } + + config.FilePath = includePath + directives := config.Directives + + var location *components.Location + for _, directive := range directives { + if loc, ok := directive.(*components.Location); ok { + location = loc + break + } + } + if location == nil { + err = errors.New("invalid proxy config, no location found") + return + } + location.UpdateDirective("proxy_pass", []string{req.ProxyPass}) + location.UpdateDirective("proxy_set_header", []string{"Host", req.ProxyHost}) + location.ChangePath(req.Modifier, req.Match) + // Server Cache Settings + if req.Cache { + if err = openProxyCache(website); err != nil { + return + } + location.AddServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias), req.ServerCacheTime, req.ServerCacheUnit) + } else { + location.RemoveServerCache(fmt.Sprintf("proxy_cache_zone_of_%s", website.Alias)) + } + // Browser Cache Settings + if req.CacheTime != 0 { + location.AddBrowserCache(req.CacheTime, req.CacheUnit) + } else { + location.RemoveBrowserCache() + } + // Content Replace Settings + if len(req.Replaces) > 0 { + location.AddSubFilter(req.Replaces) + } else { + location.RemoveSubFilter() + } + // SSL Settings + if req.SNI { + location.UpdateDirective("proxy_ssl_server_name", []string{"on"}) + if req.ProxySSLName != "" { + location.UpdateDirective("proxy_ssl_name", []string{req.ProxySSLName}) + } + } else { + location.UpdateDirective("proxy_ssl_server_name", []string{"off"}) + } + // CORS Settings + if req.Cors { + location.UpdateDirective("add_header", []string{"Access-Control-Allow-Origin", req.AllowOrigins, "always"}) + if req.AllowMethods != "" { + location.UpdateDirective("add_header", []string{"Access-Control-Allow-Methods", req.AllowMethods, "always"}) + } else { + location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"}) + } + if req.AllowHeaders != "" { + location.UpdateDirective("add_header", []string{"Access-Control-Allow-Headers", req.AllowHeaders, "always"}) + } else { + location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"}) + } + if req.AllowCredentials { + location.UpdateDirective("add_header", []string{"Access-Control-Allow-Credentials", "true", "always"}) + } else { + location.RemoveDirective("add_header", []string{"Access-Control-Allow-Credentials"}) + } + if req.Preflight { + location.AddCorsOption() + } else { + location.RemoveCorsOption() + } + } else { + location.RemoveDirective("add_header", []string{"Access-Control-Allow-Origin"}) + location.RemoveDirective("add_header", []string{"Access-Control-Allow-Methods"}) + location.RemoveDirective("add_header", []string{"Access-Control-Allow-Headers"}) + location.RemoveDirective("add_header", []string{"Access-Control-Allow-Credentials"}) + location.RemoveDirectiveByFullParams("if", []string{"(", "$request_method", "=", "'OPTIONS'", ")"}) + } + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return buserr.WithErr("ErrUpdateBuWebsite", err) + } + nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias) + return updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{nginxInclude}}}, &website) +} + +func (w WebsiteService) UpdateProxyCache(req request.NginxProxyCacheUpdate) (err error) { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return + } + cacheDir := GetSitePath(website, SiteCacheDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(cacheDir) { + _ = fileOp.CreateDir(cacheDir, constant.DirPerm) + } + if req.Open { + proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:%d%s max_size=%d%s inactive=%d%s", website.Alias, website.Alias, req.ShareCache, req.ShareCacheUnit, req.CacheLimit, req.CacheLimitUnit, req.CacheExpire, req.CacheExpireUnit) + return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website) + } + return deleteNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path"}}, &website) +} + +func (w WebsiteService) GetProxyCache(id uint) (res response.NginxProxyCache, err error) { + var ( + website model.Website + ) + website, err = websiteRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return + } + + parser, err := parser.NewParser(GetSitePath(website, SiteConf)) + if err != nil { + return + } + config, err := parser.Parse() + if err != nil { + return + } + var params []string + for _, d := range config.GetDirectives() { + if d.GetName() == "proxy_cache_path" { + params = d.GetParameters() + } + } + if len(params) == 0 { + return + } + for _, param := range params { + if re.GetRegex(re.ProxyCacheZonePattern).MatchString(param) { + matches := re.GetRegex(re.ProxyCacheZonePattern).FindStringSubmatch(param) + if len(matches) > 0 { + res.ShareCache, _ = strconv.Atoi(matches[1]) + res.ShareCacheUnit = matches[2] + } + } + + if re.GetRegex(re.ProxyCacheMaxSizeValidationPattern).MatchString(param) { + matches := re.GetRegex(re.ProxyCacheMaxSizePattern).FindStringSubmatch(param) + if len(matches) > 0 { + res.CacheLimit, _ = strconv.ParseFloat(matches[1], 64) + res.CacheLimitUnit = matches[2] + } + } + if re.GetRegex(re.ProxyCacheInactivePattern).MatchString(param) { + matches := re.GetRegex(re.ProxyCacheInactivePattern).FindStringSubmatch(param) + if len(matches) > 0 { + res.CacheExpire, _ = strconv.Atoi(matches[1]) + res.CacheExpireUnit = matches[2] + } + } + } + res.Open = true + return +} + +func (w WebsiteService) GetProxies(id uint) (res []request.WebsiteProxyConfig, err error) { + var ( + website model.Website + fileList response.FileInfo + ) + website, err = websiteRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return + } + includeDir := GetSitePath(website, SiteProxyDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(includeDir) { + return + } + fileList, err = NewIFileService().GetFileList(request.FileOption{FileOption: files.FileOption{Path: includeDir, Expand: true, Page: 1, PageSize: 100}}) + if len(fileList.Items) == 0 { + return + } + var ( + content []byte + config *components.Config + ) + for _, configFile := range fileList.Items { + proxyConfig := request.WebsiteProxyConfig{ + ID: website.ID, + } + parts := strings.Split(configFile.Name, ".") + proxyConfig.Name = parts[0] + if parts[1] == "conf" { + proxyConfig.Enable = true + } else { + proxyConfig.Enable = false + } + proxyConfig.FilePath = configFile.Path + content, err = fileOp.GetContent(configFile.Path) + if err != nil { + return + } + proxyConfig.Content = string(content) + config, err = parser.NewStringParser(string(content)).Parse() + if err != nil { + return nil, err + } + directives := config.GetDirectives() + + var location *components.Location + for _, directive := range directives { + if loc, ok := directive.(*components.Location); ok { + location = loc + break + } + } + if location == nil { + err = errors.New("invalid proxy config, no location found") + return + } + proxyConfig.ProxyPass = location.ProxyPass + proxyConfig.Cache = location.Cache + if location.CacheTime > 0 { + proxyConfig.CacheTime = location.CacheTime + proxyConfig.CacheUnit = location.CacheUint + } + if location.ServerCacheTime > 0 { + proxyConfig.ServerCacheTime = location.ServerCacheTime + proxyConfig.ServerCacheUnit = location.ServerCacheUint + } + proxyConfig.Match = location.Match + proxyConfig.Modifier = location.Modifier + proxyConfig.ProxyHost = location.Host + proxyConfig.Replaces = location.Replaces + for _, directive := range location.Directives { + if directive.GetName() == "proxy_ssl_server_name" { + proxyConfig.SNI = directive.GetParameters()[0] == "on" + } + if directive.GetName() == "proxy_ssl_name" && len(directive.GetParameters()) > 0 { + proxyConfig.ProxySSLName = directive.GetParameters()[0] + } + } + proxyConfig.Cors = location.Cors + proxyConfig.AllowCredentials = location.AllowCredentials + proxyConfig.AllowHeaders = location.AllowHeaders + proxyConfig.AllowOrigins = location.AllowOrigins + proxyConfig.AllowMethods = location.AllowMethods + proxyConfig.Preflight = location.Preflight + res = append(res, proxyConfig) + } + return +} + +func (w WebsiteService) UpdateProxyFile(req request.NginxProxyUpdate) (err error) { + var ( + website model.Website + oldRewriteContent []byte + ) + website, err = websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + absolutePath := fmt.Sprintf("%s/%s.conf", GetSitePath(website, SiteProxyDir), req.Name) + fileOp := files.NewFileOp() + oldRewriteContent, err = fileOp.GetContent(absolutePath) + if err != nil { + return err + } + if err = fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil { + return err + } + defer func() { + if err != nil { + _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm) + } + }() + return updateNginxConfig(constant.NginxScopeServer, nil, &website) +} + +func (w WebsiteService) ClearProxyCache(req request.NginxCommonReq) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + cacheDir := GetSitePath(website, SiteCacheDir) + fileOp := files.NewFileOp() + if fileOp.Stat(cacheDir) { + if err = fileOp.CleanDir(cacheDir); err != nil { + return err + } + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { + return err + } + return nil +} + +func (w WebsiteService) DeleteProxy(req request.WebsiteProxyDel) (err error) { + fileOp := files.NewFileOp() + website, err := websiteRepo.GetFirst(repo.WithByID(req.ID)) + if err != nil { + return + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return + } + includeDir := path.Join(nginxInstall.GetPath(), "www", "sites", website.Alias, "proxy") + if !fileOp.Stat(includeDir) { + _ = fileOp.CreateDir(includeDir, 0755) + } + fileName := fmt.Sprintf("%s.conf", req.Name) + includePath := path.Join(includeDir, fileName) + backName := fmt.Sprintf("%s.bak", req.Name) + backPath := path.Join(includeDir, backName) + _ = fileOp.DeleteFile(includePath) + _ = fileOp.DeleteFile(backPath) + return updateNginxConfig(constant.NginxScopeServer, nil, &website) +} diff --git a/agent/app/service/website_rewrite.go b/agent/app/service/website_rewrite.go new file mode 100644 index 0000000..04b22e1 --- /dev/null +++ b/agent/app/service/website_rewrite.go @@ -0,0 +1,124 @@ +package service + +import ( + "bytes" + "context" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "os" + "path" + "strings" +) + +func (w WebsiteService) UpdateRewriteConfig(req request.NginxRewriteUpdate) error { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return err + } + includePath := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias) + absolutePath := GetSitePath(website, SiteReWritePath) + fileOp := files.NewFileOp() + var oldRewriteContent []byte + if !fileOp.Stat(path.Dir(absolutePath)) { + if err := fileOp.CreateDir(path.Dir(absolutePath), constant.DirPerm); err != nil { + return err + } + } + if !fileOp.Stat(absolutePath) { + if err := fileOp.CreateFile(absolutePath); err != nil { + return err + } + } else { + oldRewriteContent, err = fileOp.GetContent(absolutePath) + if err != nil { + return err + } + } + if err := fileOp.WriteFile(absolutePath, strings.NewReader(req.Content), constant.DirPerm); err != nil { + return err + } + + if err := updateNginxConfig(constant.NginxScopeServer, []dto.NginxParam{{Name: "include", Params: []string{includePath}}}, &website); err != nil { + _ = fileOp.WriteFile(absolutePath, bytes.NewReader(oldRewriteContent), constant.DirPerm) + return err + } + website.Rewrite = req.Name + return websiteRepo.Save(context.Background(), &website) +} + +func (w WebsiteService) GetRewriteConfig(req request.NginxRewriteReq) (*response.NginxRewriteRes, error) { + website, err := websiteRepo.GetFirst(repo.WithByID(req.WebsiteID)) + if err != nil { + return nil, err + } + var contentByte []byte + if req.Name == "current" { + rewriteConfPath := GetSitePath(website, SiteReWritePath) + fileOp := files.NewFileOp() + if fileOp.Stat(rewriteConfPath) { + contentByte, err = fileOp.GetContent(rewriteConfPath) + if err != nil { + return nil, err + } + } + } else { + rewriteFile := fmt.Sprintf("rewrite/%s.conf", strings.ToLower(req.Name)) + contentByte, _ = nginx_conf.Rewrites.ReadFile(rewriteFile) + if contentByte == nil { + customRewriteDir := GetOpenrestyDir(DefaultRewriteDir) + customRewriteFile := path.Join(customRewriteDir, fmt.Sprintf("%s.conf", strings.ToLower(req.Name))) + contentByte, err = files.NewFileOp().GetContent(customRewriteFile) + } + } + return &response.NginxRewriteRes{ + Content: string(contentByte), + }, err +} + +func (w WebsiteService) OperateCustomRewrite(req request.CustomRewriteOperate) error { + rewriteDir := GetOpenrestyDir(DefaultRewriteDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(rewriteDir) { + if err := fileOp.CreateDir(rewriteDir, constant.DirPerm); err != nil { + return err + } + } + rewriteFile := path.Join(rewriteDir, fmt.Sprintf("%s.conf", req.Name)) + switch req.Operate { + case "create": + if fileOp.Stat(rewriteFile) { + return buserr.New("ErrNameIsExist") + } + return fileOp.WriteFile(rewriteFile, strings.NewReader(req.Content), constant.DirPerm) + case "delete": + return fileOp.DeleteFile(rewriteFile) + } + return nil +} + +func (w WebsiteService) ListCustomRewrite() ([]string, error) { + rewriteDir := GetOpenrestyDir(DefaultRewriteDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(rewriteDir) { + return nil, nil + } + entries, err := os.ReadDir(rewriteDir) + if err != nil { + return nil, err + } + var res []string + for _, entry := range entries { + if entry.IsDir() { + continue + } + res = append(res, strings.TrimSuffix(entry.Name(), ".conf")) + } + return res, nil +} diff --git a/agent/app/service/website_ssl.go b/agent/app/service/website_ssl.go new file mode 100644 index 0000000..bc04a3d --- /dev/null +++ b/agent/app/service/website_ssl.go @@ -0,0 +1,784 @@ +package service + +import ( + "context" + "crypto/x509" + "encoding/pem" + "fmt" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "github.com/go-acme/lego/v4/certificate" + "log" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/req_helper" + "github.com/1Panel-dev/1Panel/agent/utils/ssl" + legoLogger "github.com/go-acme/lego/v4/log" + "github.com/jinzhu/gorm" +) + +type WebsiteSSLService struct { +} + +type IWebsiteSSLService interface { + Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error) + GetSSL(id uint) (*response.WebsiteSSLDTO, error) + Search(req request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error) + Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error) + GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error) + GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO, error) + Delete(ids []uint) error + Update(update request.WebsiteSSLUpdate) error + Upload(req request.WebsiteSSLUpload) error + ObtainSSL(apply request.WebsiteSSLApply) error + SyncForRestart() error + DownloadFile(id uint) (*os.File, error) + ImportMasterSSL(create model.WebsiteSSL) error +} + +func NewIWebsiteSSLService() IWebsiteSSLService { + return &WebsiteSSLService{} +} + +func (w WebsiteSSLService) Page(search request.WebsiteSSLSearch) (int64, []response.WebsiteSSLDTO, error) { + var ( + result []response.WebsiteSSLDTO + opts []repo.DBOption + ) + if search.OrderBy != "" && search.Order != "null" { + opts = append(opts, repo.WithOrderRuleBy(search.OrderBy, search.Order)) + } else { + opts = append(opts, repo.WithOrderBy("created_at desc")) + } + if search.Domain != "" { + opts = append(opts, websiteSSLRepo.WithByDomain(search.Domain)) + } + total, sslList, err := websiteSSLRepo.Page(search.Page, search.PageSize, opts...) + if err != nil { + return 0, nil, err + } + for _, model := range sslList { + result = append(result, response.WebsiteSSLDTO{ + WebsiteSSL: model, + LogPath: path.Join(global.Dir.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", model.PrimaryDomain, model.ID)), + }) + } + return total, result, err +} + +func (w WebsiteSSLService) GetSSL(id uint) (*response.WebsiteSSLDTO, error) { + var res response.WebsiteSSLDTO + websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return nil, err + } + res.WebsiteSSL = *websiteSSL + return &res, nil +} + +func (w WebsiteSSLService) Search(search request.WebsiteSSLSearch) ([]response.WebsiteSSLDTO, error) { + var ( + opts []repo.DBOption + result []response.WebsiteSSLDTO + ) + opts = append(opts, repo.WithOrderBy("created_at desc")) + if search.AcmeAccountID != "" { + acmeAccountID, err := strconv.ParseUint(search.AcmeAccountID, 10, 64) + if err != nil { + return nil, err + } + opts = append(opts, websiteSSLRepo.WithByAcmeAccountId(uint(acmeAccountID))) + } + sslList, err := websiteSSLRepo.List(opts...) + if err != nil { + return nil, err + } + for _, sslModel := range sslList { + result = append(result, response.WebsiteSSLDTO{ + WebsiteSSL: sslModel, + }) + } + return result, err +} + +func (w WebsiteSSLService) Create(create request.WebsiteSSLCreate) (request.WebsiteSSLCreate, error) { + if create.Nameserver1 != "" && !common.IsValidIP(create.Nameserver1) { + return create, buserr.New("ErrParseIP") + } + if create.Nameserver2 != "" && !common.IsValidIP(create.Nameserver2) { + return create, buserr.New("ErrParseIP") + } + var res request.WebsiteSSLCreate + acmeAccount, err := websiteAcmeRepo.GetFirst(repo.WithByID(create.AcmeAccountID)) + if err != nil { + return res, err + } + websiteSSL := model.WebsiteSSL{ + Status: constant.SSLInit, + Provider: create.Provider, + AcmeAccountID: acmeAccount.ID, + PrimaryDomain: create.PrimaryDomain, + ExpireDate: time.Now(), + KeyType: create.KeyType, + PushDir: create.PushDir, + Description: create.Description, + Nameserver1: create.Nameserver1, + Nameserver2: create.Nameserver2, + SkipDNS: create.SkipDNS, + DisableCNAME: create.DisableCNAME, + ExecShell: create.ExecShell, + IsIp: create.IsIp, + } + if create.ExecShell { + websiteSSL.Shell = create.Shell + } + if create.PushDir { + fileOP := files.NewFileOp() + if !fileOP.Stat(create.Dir) { + return res, buserr.New("ErrLinkPathNotFound") + } + websiteSSL.Dir = create.Dir + } + if create.PushNode && global.IsMaster && len(create.Nodes) > 0 { + websiteSSL.PushNode = true + websiteSSL.Nodes = create.Nodes + } + + var domains []string + if create.OtherDomains != "" { + otherDomainArray := strings.Split(create.OtherDomains, "\n") + for _, domain := range otherDomainArray { + if !common.IsValidDomain(domain) { + err = buserr.WithName("ErrDomainFormat", domain) + return res, err + } + domains = append(domains, domain) + } + } + if create.Provider == constant.Http { + if strings.Contains(create.PrimaryDomain, "*") { + return res, buserr.New("ErrWildcardDomain") + } + for _, domain := range domains { + if strings.Contains(domain, "*") { + return res, buserr.New("ErrWildcardDomain") + } + } + } + websiteSSL.Domains = strings.Join(domains, ",") + + if create.Provider == constant.DNSAccount || create.Provider == constant.Http { + websiteSSL.AutoRenew = create.AutoRenew + } + if create.Provider == constant.DNSAccount { + dnsAccount, err := websiteDnsRepo.GetFirst(repo.WithByID(create.DnsAccountID)) + if err != nil { + return res, err + } + websiteSSL.DnsAccountID = dnsAccount.ID + } + + if err := websiteSSLRepo.Create(context.TODO(), &websiteSSL); err != nil { + return res, err + } + create.ID = websiteSSL.ID + logFile, _ := os.OpenFile(path.Join(global.Dir.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", websiteSSL.PrimaryDomain, websiteSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + logFile.Close() + go func() { + if create.Provider != constant.DnsManual { + if err = w.ObtainSSL(request.WebsiteSSLApply{ + ID: websiteSSL.ID, + }); err != nil { + global.LOG.Errorf("obtain ssl failed, err: %v", err) + } + } + }() + return create, nil +} + +func printSSLLog(logger *log.Logger, msgKey string, params map[string]interface{}, disableLog bool) { + if disableLog { + return + } + logger.Println(i18n.GetMsgWithMap(msgKey, params)) +} + +func reloadSystemSSL(websiteSSL *model.WebsiteSSL, logger *log.Logger) { + if !global.IsMaster { + return + } + systemSSLEnable, sslID := GetSystemSSL() + if systemSSLEnable && sslID == websiteSSL.ID { + fileOp := files.NewFileOp() + certPath := path.Join(global.Dir.DataDir, "secret/server.crt") + keyPath := path.Join(global.Dir.DataDir, "secret/server.key") + printSSLLog(logger, "StartUpdateSystemSSL", nil, logger == nil) + if err := fileOp.WriteFile(certPath, strings.NewReader(websiteSSL.Pem), 0600); err != nil { + logger.Printf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error()) + return + } + if err := fileOp.WriteFile(keyPath, strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil { + logger.Printf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error()) + return + } + if err := req_helper.PostLocalCore("/core/settings/ssl/reload"); err != nil { + logger.Printf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error()) + return + } + printSSLLog(logger, "UpdateSystemSSLSuccess", nil, logger == nil) + } +} + +func (w WebsiteSSLService) ObtainSSL(apply request.WebsiteSSLApply) error { + var ( + err error + websiteSSL *model.WebsiteSSL + acmeAccount *model.WebsiteAcmeAccount + dnsAccount *model.WebsiteDnsAccount + client *ssl.AcmeClient + manualClient *ssl.ManualClient + resource certificate.Resource + ) + + websiteSSL, err = websiteSSLRepo.GetFirst(repo.WithByID(apply.ID)) + if err != nil { + return err + } + acmeAccount, err = websiteAcmeRepo.GetFirst(repo.WithByID(websiteSSL.AcmeAccountID)) + if err != nil { + return err + } + domains := []string{websiteSSL.PrimaryDomain} + if websiteSSL.Domains != "" { + domains = append(domains, strings.Split(websiteSSL.Domains, ",")...) + } + if websiteSSL.Provider != constant.DnsManual { + client, err = ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy)) + if err != nil { + return err + } + + switch websiteSSL.Provider { + case constant.DNSAccount: + dnsAccount, err = websiteDnsRepo.GetFirst(repo.WithByID(websiteSSL.DnsAccountID)) + if err != nil { + return err + } + if err = client.UseDns(ssl.DnsType(dnsAccount.Type), dnsAccount.Authorization, *websiteSSL); err != nil { + return err + } + case constant.Http: + appInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + if gorm.IsRecordNotFoundError(err) { + return buserr.New("ErrOpenrestyNotFound") + } + return err + } + for _, domain := range domains { + if strings.Contains(domain, "*") { + return buserr.New("ErrWildcardDomain") + } + } + if err := client.UseHTTP(path.Join(appInstall.GetPath(), "root")); err != nil { + return err + } + } + } + websiteSSL.Status = constant.SSLApply + err = websiteSSLRepo.Save(websiteSSL) + if err != nil { + return err + } + + go func() { + logFile, _ := os.OpenFile(path.Join(global.Dir.SSLLogDir, fmt.Sprintf("%s-ssl-%d.log", websiteSSL.PrimaryDomain, websiteSSL.ID)), os.O_CREATE|os.O_WRONLY|os.O_TRUNC, constant.FilePerm) + defer logFile.Close() + logger := log.New(logFile, "", log.LstdFlags) + legoLogger.Logger = logger + if !apply.DisableLog { + startMsg := i18n.GetMsgWithMap("ApplySSLStart", map[string]interface{}{"domain": strings.Join(domains, ","), "type": i18n.GetMsgByKey(websiteSSL.Provider)}) + if websiteSSL.Provider == constant.DNSAccount { + startMsg = startMsg + i18n.GetMsgWithMap("DNSAccountName", map[string]interface{}{"name": dnsAccount.Name, "type": dnsAccount.Type}) + } + logger.Println(startMsg) + } + if websiteSSL.Provider != constant.DnsManual { + privateKey, err := ssl.GetPrivateKeyByType(websiteSSL.KeyType, websiteSSL.PrivateKey) + if err != nil { + handleError(websiteSSL, err) + return + } + if websiteSSL.IsIp { + resource, err = client.ObtainIPSSL(domains[0], privateKey) + } else { + resource, err = client.ObtainSSL(domains, privateKey) + } + if err != nil { + handleError(websiteSSL, err) + return + } + } else { + manualClient, err = ssl.NewCustomAcmeClient(acmeAccount, logger) + if err != nil { + handleError(websiteSSL, err) + return + } + resource, err = manualClient.RequestCertificate(context.Background(), websiteSSL) + if err != nil { + handleError(websiteSSL, err) + return + } + } + + websiteSSL.PrivateKey = string(resource.PrivateKey) + websiteSSL.Pem = string(resource.Certificate) + websiteSSL.CertURL = resource.CertURL + certBlock, _ := pem.Decode(resource.Certificate) + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + handleError(websiteSSL, err) + return + } + websiteSSL.ExpireDate = cert.NotAfter + websiteSSL.StartDate = cert.NotBefore + websiteSSL.Type = cert.Issuer.CommonName + if len(cert.Issuer.Organization) > 0 { + websiteSSL.Organization = cert.Issuer.Organization[0] + } + websiteSSL.Status = constant.SSLReady + printSSLLog(logger, "ApplySSLSuccess", map[string]interface{}{"domain": strings.Join(domains, ",")}, apply.DisableLog) + saveCertificateFile(websiteSSL, logger) + + if websiteSSL.ExecShell { + workDir := global.Dir.DataDir + if websiteSSL.PushDir { + workDir = websiteSSL.Dir + } + printSSLLog(logger, "ExecShellStart", nil, apply.DisableLog) + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(30*time.Minute), cmd.WithLogger(logger), cmd.WithWorkDir(workDir)) + if err = cmdMgr.RunBashC(websiteSSL.Shell); err != nil { + printSSLLog(logger, "ErrExecShell", map[string]interface{}{"err": err.Error()}, apply.DisableLog) + } else { + printSSLLog(logger, "ExecShellSuccess", nil, apply.DisableLog) + } + } + + err = websiteSSLRepo.Save(websiteSSL) + if err != nil { + return + } + + websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID)) + if len(websites) > 0 { + for _, website := range websites { + printSSLLog(logger, "ApplyWebSiteSSLLog", map[string]interface{}{"name": website.PrimaryDomain}, apply.DisableLog) + if err := createPemFile(website, *websiteSSL); err != nil { + printSSLLog(logger, "ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}, apply.DisableLog) + } + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return + } + if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { + printSSLLog(logger, "ErrSSLApply", nil, apply.DisableLog) + return + } + printSSLLog(logger, "ApplyWebSiteSSLSuccess", nil, apply.DisableLog) + } + reloadSystemSSL(websiteSSL, logger) + if websiteSSL.PushNode { + printSSLLog(logger, "StartPushSSLToNode", nil, apply.DisableLog) + if err = xpack.PushSSLToNode(websiteSSL); err != nil { + printSSLLog(logger, "PushSSLToNodeFailed", map[string]interface{}{"err": err.Error()}, apply.DisableLog) + return + } + printSSLLog(logger, "PushSSLToNodeSuccess", nil, apply.DisableLog) + } + }() + + return nil +} + +func handleError(websiteSSL *model.WebsiteSSL, err error) { + if websiteSSL.Status == constant.SSLInit || websiteSSL.Status == constant.SSLError { + websiteSSL.Status = constant.StatusError + } else { + websiteSSL.Status = constant.SSLApplyError + } + websiteSSL.Message = err.Error() + legoLogger.Logger.Println(i18n.GetErrMsg("ApplySSLFailed", map[string]interface{}{"domain": websiteSSL.PrimaryDomain, "detail": err.Error()})) + _ = websiteSSLRepo.Save(websiteSSL) +} + +func (w WebsiteSSLService) GetDNSResolve(req request.WebsiteDNSReq) ([]response.WebsiteDNSRes, error) { + acmeAccount, err := websiteAcmeRepo.GetFirst(repo.WithByID(req.AcmeAccountID)) + if err != nil { + return nil, err + } + client, err := ssl.NewCustomAcmeClient(acmeAccount, nil) + if err != nil { + return nil, err + } + websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(req.WebsiteSSLID)) + if err != nil { + return nil, err + } + resolves, err := client.GetDNSResolve(context.TODO(), websiteSSL) + if err != nil { + return nil, err + } + var res []response.WebsiteDNSRes + for k, v := range resolves { + res = append(res, response.WebsiteDNSRes{ + Domain: k, + Key: v.Key, + Value: v.Value, + Err: v.Err, + }) + } + return res, nil +} + +func (w WebsiteSSLService) GetWebsiteSSL(websiteId uint) (response.WebsiteSSLDTO, error) { + var res response.WebsiteSSLDTO + website, err := websiteRepo.GetFirst(repo.WithByID(websiteId)) + if err != nil { + return res, err + } + websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(website.WebsiteSSLID)) + if err != nil { + return res, err + } + res.WebsiteSSL = *websiteSSL + return res, nil +} + +func (w WebsiteSSLService) Delete(ids []uint) error { + var ( + websiteSSLS []string + applySSLS []string + ) + for _, id := range ids { + if websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(id)); len(websites) > 0 { + oldSSL, _ := websiteSSLRepo.GetFirst(repo.WithByID(id)) + if oldSSL.ID > 0 { + websiteSSLS = append(websiteSSLS, oldSSL.PrimaryDomain) + } + continue + } + sslSetting, _ := settingRepo.Get(settingRepo.WithByKey("SSL")) + if sslSetting.Value == "enable" { + sslID, _ := settingRepo.Get(settingRepo.WithByKey("SSLID")) + idValue, _ := strconv.Atoi(sslID.Value) + if idValue > 0 && uint(idValue) == id { + return buserr.New("ErrDeleteWithPanelSSL") + } + } + websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return err + } + if websiteSSL.Status == constant.SSLApply { + applySSLS = append(applySSLS, websiteSSL.PrimaryDomain) + continue + } + if websiteSSL.Provider != constant.Manual && websiteSSL.Provider != constant.SelfSigned { + go func() { + acmeAccount, err := websiteAcmeRepo.GetFirst(repo.WithByID(websiteSSL.AcmeAccountID)) + if err != nil { + global.LOG.Errorf("Failed to get acme account for SSL revoke, err: %v", err) + return + } + client, err := ssl.NewAcmeClient(acmeAccount, getSystemProxy(acmeAccount.UseProxy)) + if err != nil { + global.LOG.Errorf("Failed to create ACME client for SSL revoke, err: %v", err) + return + } + err = client.RevokeSSL([]byte(websiteSSL.Pem)) + if err != nil { + global.LOG.Errorf("Failed to revoke SSL for domain %s, err: %v", websiteSSL.PrimaryDomain, err) + return + } + }() + } + _ = websiteSSLRepo.DeleteBy(repo.WithByID(id)) + } + if len(websiteSSLS) > 0 { + return buserr.WithName("ErrSSLCannotDelete", strings.Join(websiteSSLS, ",")) + } + if len(applySSLS) > 0 { + return buserr.WithName("ErrApplySSLCanNotDelete", strings.Join(applySSLS, ",")) + } + return nil +} + +func (w WebsiteSSLService) Update(update request.WebsiteSSLUpdate) error { + websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(update.ID)) + if err != nil { + return err + } + updateParams := make(map[string]interface{}) + updateParams["primary_domain"] = update.PrimaryDomain + updateParams["description"] = update.Description + updateParams["provider"] = update.Provider + updateParams["push_dir"] = update.PushDir + updateParams["disable_cname"] = update.DisableCNAME + updateParams["skip_dns"] = update.SkipDNS + updateParams["nameserver1"] = update.Nameserver1 + updateParams["nameserver2"] = update.Nameserver2 + updateParams["exec_shell"] = update.ExecShell + if update.ExecShell { + updateParams["shell"] = update.Shell + } else { + updateParams["shell"] = "" + } + if update.PushNode { + updateParams["push_node"] = true + updateParams["nodes"] = update.Nodes + } else { + updateParams["push_node"] = false + updateParams["nodes"] = "" + } + + if websiteSSL.Provider != constant.SelfSigned && websiteSSL.Provider != constant.Manual { + acmeAccount, err := websiteAcmeRepo.GetFirst(repo.WithByID(update.AcmeAccountID)) + if err != nil { + return err + } + updateParams["acme_account_id"] = acmeAccount.ID + } + + if update.PushDir { + fileOP := files.NewFileOp() + if !fileOP.Stat(update.Dir) { + _ = fileOP.CreateDir(update.Dir, constant.DirPerm) + } + updateParams["dir"] = update.Dir + } + var domains []string + if update.OtherDomains != "" { + otherDomainArray := strings.Split(update.OtherDomains, "\n") + for _, domain := range otherDomainArray { + if websiteSSL.Provider != constant.SelfSigned && !common.IsValidDomain(domain) { + return buserr.WithName("ErrDomainFormat", domain) + } + domains = append(domains, domain) + } + } + updateParams["domains"] = strings.Join(domains, ",") + if update.Provider == constant.DNSAccount || update.Provider == constant.Http || update.Provider == constant.SelfSigned { + updateParams["auto_renew"] = update.AutoRenew + } else { + updateParams["auto_renew"] = false + } + if update.Provider == constant.DNSAccount { + dnsAccount, err := websiteDnsRepo.GetFirst(repo.WithByID(update.DnsAccountID)) + if err != nil { + return err + } + updateParams["dns_account_id"] = dnsAccount.ID + } else { + updateParams["dns_account_id"] = 0 + } + return websiteSSLRepo.SaveByMap(websiteSSL, updateParams) +} + +func (w WebsiteSSLService) Upload(req request.WebsiteSSLUpload) error { + websiteSSL := &model.WebsiteSSL{ + Provider: constant.Manual, + Description: req.Description, + Status: constant.SSLReady, + } + var err error + if req.SSLID > 0 { + websiteSSL, err = websiteSSLRepo.GetFirst(repo.WithByID(req.SSLID)) + if err != nil { + return err + } + websiteSSL.Description = req.Description + } + if req.Type == "local" { + fileOp := files.NewFileOp() + if !fileOp.Stat(req.PrivateKeyPath) { + return buserr.New("ErrSSLKeyNotFound") + } + if !fileOp.Stat(req.CertificatePath) { + return buserr.New("ErrSSLCertificateNotFound") + } + if content, err := fileOp.GetContent(req.PrivateKeyPath); err != nil { + return err + } else { + websiteSSL.PrivateKey = string(content) + } + if content, err := fileOp.GetContent(req.CertificatePath); err != nil { + return err + } else { + websiteSSL.Pem = string(content) + } + websiteSSL.CertPath = req.CertificatePath + websiteSSL.PrivateKeyPath = req.PrivateKeyPath + } else { + websiteSSL.PrivateKey = req.PrivateKey + websiteSSL.Pem = req.Certificate + websiteSSL.CertPath = "" + websiteSSL.PrivateKeyPath = "" + } + + privateKeyCertBlock, _ := pem.Decode([]byte(websiteSSL.PrivateKey)) + if privateKeyCertBlock == nil { + return buserr.New("ErrSSLKeyFormat") + } + + var ( + cert *x509.Certificate + pemData = []byte(websiteSSL.Pem) + ) + for { + certBlock, reset := pem.Decode(pemData) + if certBlock == nil { + break + } + cert, err = x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return err + } + if len(cert.DNSNames) > 0 || len(cert.IPAddresses) > 0 { + break + } + pemData = reset + } + if pemData == nil || cert == nil { + return buserr.New("ErrSSLCertificateFormat") + } + + websiteSSL.ExpireDate = cert.NotAfter + websiteSSL.StartDate = cert.NotBefore + websiteSSL.Type = cert.Issuer.CommonName + if len(cert.Issuer.Organization) > 0 { + websiteSSL.Organization = cert.Issuer.Organization[0] + } else { + websiteSSL.Organization = cert.Issuer.CommonName + } + + var domains []string + if len(cert.DNSNames) > 0 { + websiteSSL.PrimaryDomain = cert.DNSNames[0] + domains = cert.DNSNames[1:] + } + if len(cert.IPAddresses) > 0 { + if websiteSSL.PrimaryDomain == "" { + websiteSSL.PrimaryDomain = cert.IPAddresses[0].String() + for _, ip := range cert.IPAddresses[1:] { + domains = append(domains, ip.String()) + } + } else { + for _, ip := range cert.IPAddresses { + domains = append(domains, ip.String()) + } + } + } + websiteSSL.Domains = strings.Join(domains, ",") + + if websiteSSL.ID > 0 { + if err := UpdateSSLConfig(*websiteSSL); err != nil { + return err + } + return websiteSSLRepo.Save(websiteSSL) + } + return websiteSSLRepo.Create(context.Background(), websiteSSL) +} + +func (w WebsiteSSLService) DownloadFile(id uint) (*os.File, error) { + websiteSSL, err := websiteSSLRepo.GetFirst(repo.WithByID(id)) + if err != nil { + return nil, err + } + fileOp := files.NewFileOp() + dir := path.Join(global.Dir.DataDir, "tmp/ssl", websiteSSL.PrimaryDomain) + if fileOp.Stat(dir) { + if err = fileOp.DeleteDir(dir); err != nil { + return nil, err + } + } + if err = fileOp.CreateDir(dir, constant.DirPerm); err != nil { + return nil, err + } + if err = fileOp.WriteFile(path.Join(dir, "fullchain.pem"), strings.NewReader(websiteSSL.Pem), constant.DirPerm); err != nil { + return nil, err + } + if err = fileOp.WriteFile(path.Join(dir, "privkey.pem"), strings.NewReader(websiteSSL.PrivateKey), constant.DirPerm); err != nil { + return nil, err + } + fileName := websiteSSL.PrimaryDomain + ".zip" + if err = fileOp.Compress([]string{path.Join(dir, "fullchain.pem"), path.Join(dir, "privkey.pem")}, dir, fileName, files.SdkZip, ""); err != nil { + return nil, err + } + return os.Open(path.Join(dir, fileName)) +} + +func (w WebsiteSSLService) SyncForRestart() error { + sslList, err := websiteSSLRepo.List() + if err != nil { + return err + } + for _, ssl := range sslList { + if ssl.Status == constant.SSLApply { + ssl.Status = constant.SystemRestart + ssl.Message = "System restart causing interrupt" + _ = websiteSSLRepo.Save(&ssl) + } + } + return nil +} + +func (w WebsiteSSLService) ImportMasterSSL(create model.WebsiteSSL) error { + websiteSSL, _ := websiteSSLRepo.GetFirst(websiteSSLRepo.WithByMasterSSLID(create.ID)) + websiteSSL.Status = constant.SSLReady + websiteSSL.Provider = constant.FromMaster + websiteSSL.PrimaryDomain = create.PrimaryDomain + websiteSSL.StartDate = create.StartDate + websiteSSL.ExpireDate = create.ExpireDate + websiteSSL.KeyType = create.KeyType + websiteSSL.Description = create.Description + websiteSSL.PrivateKey = create.PrivateKey + websiteSSL.Pem = create.Pem + websiteSSL.Type = create.Type + websiteSSL.Organization = create.Organization + websiteSSL.MasterSSLID = create.ID + websiteSSL.Domains = create.Domains + if err := websiteSSLRepo.Save(websiteSSL); err != nil { + return err + } + websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID)) + if len(websites) == 0 { + return nil + } + for _, website := range websites { + if err := createPemFile(website, *websiteSSL); err != nil { + continue + } + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err == nil { + if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { + return err + } + } + return nil +} diff --git a/agent/app/service/website_utils.go b/agent/app/service/website_utils.go new file mode 100644 index 0000000..85ade74 --- /dev/null +++ b/agent/app/service/website_utils.go @@ -0,0 +1,1791 @@ +package service + +import ( + "context" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "log" + "net" + "os" + "path" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/cmd/server/nginx_conf" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/nginx" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "github.com/pkg/errors" + "gorm.io/gorm" +) + +func handleChineseDomain(domain string) (string, error) { + if common.ContainsChinese(domain) { + return common.PunycodeEncode(domain) + } + return domain, nil +} + +func createIndexFile(website *model.Website, runtime *model.Runtime) error { + var ( + indexPath string + indexContent string + websiteService = NewIWebsiteService() + indexFolder = GetSitePath(*website, SiteIndexDir) + ) + + switch website.Type { + case constant.Static: + indexPath = path.Join(indexFolder, "index.html") + indexHtml, _ := websiteService.GetDefaultHtml("index") + indexContent = indexHtml.Content + case constant.Runtime: + if runtime.Type == constant.RuntimePHP { + indexPath = path.Join(indexFolder, "index.php") + indexPhp, _ := websiteService.GetDefaultHtml("php") + indexContent = indexPhp.Content + } + } + + fileOp := files.NewFileOp() + if !fileOp.Stat(indexFolder) { + if err := fileOp.CreateDir(indexFolder, constant.DirPerm); err != nil { + return err + } + } + if !fileOp.Stat(indexPath) { + if err := fileOp.CreateFile(indexPath); err != nil { + return err + } + } + if website.Type == constant.Runtime && runtime.Resource == constant.ResourceAppstore { + if err := chownRootDir(indexFolder); err != nil { + return err + } + } + if err := fileOp.WriteFile(indexPath, strings.NewReader(indexContent), constant.DirPerm); err != nil { + return err + } + + html404, _ := websiteService.GetDefaultHtml("404") + path404 := path.Join(indexFolder, "404.html") + if err := fileOp.WriteFile(path404, strings.NewReader(html404.Content), constant.DirPerm); err != nil { + return err + } + + return nil +} + +func createProxyFile(website *model.Website) error { + proxyFolder := GetSitePath(*website, SiteProxyDir) + filePath := path.Join(proxyFolder, "root.conf") + fileOp := files.NewFileOp() + if !fileOp.Stat(proxyFolder) { + if err := fileOp.CreateDir(proxyFolder, constant.DirPerm); err != nil { + return err + } + } + if !fileOp.Stat(filePath) { + if err := fileOp.CreateFile(filePath); err != nil { + return err + } + } + config, err := parser.NewStringParser(string(nginx_conf.Proxy)).Parse() + if err != nil { + return err + } + config.FilePath = filePath + directives := config.Directives + location, ok := directives[0].(*components.Location) + if !ok { + return errors.New("error") + } + location.ChangePath("^~", "/") + location.UpdateDirective("proxy_pass", []string{website.Proxy}) + location.UpdateDirective("proxy_set_header", []string{"Host", "$host"}) + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return buserr.WithErr("ErrUpdateBuWebsite", err) + } + return nil +} + +func createWebsiteFolder(website *model.Website, runtime *model.Runtime) error { + siteFolder := GetSiteDir(website.Alias) + fileOp := files.NewFileOp() + if !fileOp.Stat(siteFolder) { + if err := fileOp.CreateDir(siteFolder, constant.DirPerm); err != nil { + return err + } + if err := fileOp.CreateDir(path.Join(siteFolder, "log"), constant.DirPerm); err != nil { + return err + } + if err := fileOp.CreateFile(path.Join(siteFolder, "log", "access.log")); err != nil { + return err + } + if err := fileOp.CreateFile(path.Join(siteFolder, "log", "error.log")); err != nil { + return err + } + if website.Type != constant.Stream { + if err := fileOp.CreateDir(path.Join(siteFolder, "index"), constant.DirPerm); err != nil { + return err + } + if err := fileOp.CreateDir(path.Join(siteFolder, "ssl"), constant.DirPerm); err != nil { + return err + } + } + if website.Type == constant.Runtime { + if runtime.Type == constant.RuntimePHP && runtime.Resource == constant.ResourceLocal { + phpPoolDir := path.Join(siteFolder, "php-pool") + if err := fileOp.CreateDir(phpPoolDir, constant.DirPerm); err != nil { + return err + } + if err := fileOp.CreateFile(path.Join(phpPoolDir, "php-fpm.sock")); err != nil { + return err + } + } + } + if website.Type == constant.Static || (website.Type == constant.Runtime && runtime.Type == constant.RuntimePHP) { + if err := createIndexFile(website, runtime); err != nil { + return err + } + } + if website.Type == constant.Proxy { + if err := createProxyFile(website); err != nil { + return err + } + } + } + return nil +} + +func configDefaultNginx(website *model.Website, domains []model.WebsiteDomain, appInstall *model.AppInstall, runtime *model.Runtime, streamConfig request.StreamConfig) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + if err = createWebsiteFolder(website, runtime); err != nil { + return err + } + var ( + configPath string + config *components.Config + ) + if website.Type == constant.Stream { + if streamConfig.StreamPorts == "" { + return buserr.New("ErrTypePortRange") + } + nginxContent := nginx_conf.GetWebsiteFile("stream_default.conf") + config, err = parser.NewStringParser(string(nginxContent)).Parse() + if err != nil { + return err + } + servers := config.FindServers() + if len(servers) == 0 { + return errors.New("nginx config is not valid") + } + server := servers[0] + ports := strings.Split(streamConfig.StreamPorts, ",") + var params []string + if streamConfig.UDP { + params = []string{"udp"} + } + for _, port := range ports { + server.UpdateListen(port, false, params...) + if website.IPV6 { + server.UpdateListen("[::]:"+port, false, params...) + } + } + siteFolder := path.Join("/www", "sites", website.Alias) + server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log"), "streamlog"}) + server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")}) + server.UpdateDirective("proxy_pass", []string{website.Alias}) + + upstream := components.Upstream{ + UpstreamName: website.Alias, + } + if streamConfig.Algorithm != "default" { + upstream.UpdateDirective(streamConfig.Algorithm, []string{}) + } + upstream.UpstreamServers = parseUpstreamServers(streamConfig.Servers) + config.Block.Directives = append(config.Block.Directives, &upstream) + configPath = GetSitePath(*website, StreamConf) + } else { + configPath = GetSitePath(*website, SiteConf) + nginxContent := nginx_conf.GetWebsiteFile("website_default.conf") + config, err = parser.NewStringParser(string(nginxContent)).Parse() + if err != nil { + return err + } + servers := config.FindServers() + if len(servers) == 0 { + return errors.New("nginx config is not valid") + } + server := servers[0] + server.DeleteListen("80") + var serverNames []string + for _, domain := range domains { + serverNames = append(serverNames, domain.Domain) + setListen(server, strconv.Itoa(domain.Port), website.IPV6, false, website.DefaultServer, false) + } + server.UpdateServerName(serverNames) + + siteFolder := path.Join("/www", "sites", website.Alias) + server.UpdateDirective("access_log", []string{path.Join(siteFolder, "log", "access.log"), "main"}) + server.UpdateDirective("error_log", []string{path.Join(siteFolder, "log", "error.log")}) + + rootIndex := path.Join("/www/sites", website.Alias, "index") + switch website.Type { + case constant.Deployment: + proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort) + server.UpdateRootProxy([]string{proxy}) + case constant.Static: + server.UpdateRoot(rootIndex) + server.UpdateDirective("error_page", []string{"404", "/404.html"}) + case constant.Proxy: + nginxInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias) + server.UpdateDirective("include", []string{nginxInclude}) + server.UpdateRoot(rootIndex) + case constant.Runtime: + switch runtime.Type { + case constant.RuntimePHP: + server.UpdateDirective("error_page", []string{"404", "/404.html"}) + if runtime.Resource == constant.ResourceLocal { + server.UpdateRoot(rootIndex) + localPath := path.Join(rootIndex, "index.php") + server.UpdatePHPProxy([]string{website.Proxy}, localPath) + } else { + server.UpdateRoot(rootIndex) + server.UpdatePHPProxy([]string{website.Proxy}, "") + } + case constant.RuntimeNode, constant.RuntimeJava, constant.RuntimeGo, constant.RuntimePython, constant.RuntimeDotNet: + server.UpdateRootProxy([]string{fmt.Sprintf("http://%s", website.Proxy)}) + } + case constant.Subsite: + parentWebsite, err := websiteRepo.GetFirst(repo.WithByID(website.ParentWebsiteID)) + if err != nil { + return err + } + website.Proxy = parentWebsite.Proxy + rootIndex = path.Join("/www/sites", parentWebsite.Alias, "index", website.SiteDir) + server.UpdateDirective("error_page", []string{"404", "/404.html"}) + if parentWebsite.Type == constant.Runtime { + parentRuntime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(parentWebsite.RuntimeID)) + if err != nil { + return err + } + website.RuntimeID = parentRuntime.ID + if parentRuntime.Type == constant.RuntimePHP { + if parentRuntime.Resource == constant.ResourceLocal { + server.UpdateRoot(rootIndex) + localPath := path.Join(rootIndex, "index.php") + server.UpdatePHPProxy([]string{website.Proxy}, localPath) + } else { + server.UpdateRoot(rootIndex) + server.UpdatePHPProxy([]string{website.Proxy}, "") + } + } + } + if parentWebsite.Type == constant.Static { + server.UpdateRoot(rootIndex) + } + } + } + config.FilePath = configPath + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + if err = opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil { + return err + } + if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { + return err + } + return nil +} + +func moveDefaultWafConfig(websiteDir string, defaultConfigContent []byte, defaultRuleDir string, fileOp files.FileOp) error { + if !fileOp.Stat(websiteDir) { + if err := fileOp.CreateDir(websiteDir, constant.DirPerm); err != nil { + return err + } + } + if err := fileOp.SaveFileWithByte(path.Join(websiteDir, "config.json"), defaultConfigContent, constant.DirPerm); err != nil { + return err + } + websiteRuleDir := path.Join(websiteDir, "rules") + if !fileOp.Stat(websiteRuleDir) { + if err := fileOp.CreateDir(websiteRuleDir, constant.DirPerm); err != nil { + return err + } + } + defaultRulesName := []string{"acl", "args", "cookie", "defaultUaBlack", "defaultUrlBlack", "fileExt", "header", "methodWhite", "cdn"} + for _, ruleName := range defaultRulesName { + srcPath := path.Join(defaultRuleDir, ruleName+".json") + if fileOp.Stat(srcPath) { + _ = fileOp.Copy(srcPath, websiteRuleDir) + } + } + return nil +} + +func createAllWebsitesWAFConfig(websites []model.Website) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data") + fileOp := files.NewFileOp() + if !fileOp.Stat(wafDataPath) { + return nil + } + websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json") + var websitesArray []request.WafWebsite + for _, website := range websites { + wafWebsite := request.WafWebsite{ + Key: website.Alias, + Domains: make([]string, 0), + Host: make([]string, 0), + } + websiteDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(website.ID)) + for _, domain := range websiteDomains { + wafWebsite.Domains = append(wafWebsite.Domains, domain.Domain) + wafWebsite.Host = append(wafWebsite.Host, domain.Domain+":"+strconv.Itoa(domain.Port)) + } + websitesArray = append(websitesArray, wafWebsite) + } + websitesContent, err := json.Marshal(websitesArray) + if err != nil { + return err + } + if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil { + return err + } + var ( + defaultConfigPath = path.Join(wafDataPath, "conf", "siteConfig.json") + defaultRuleDir = path.Join(wafDataPath, "rules") + sitesDir = path.Join(wafDataPath, "sites") + ) + defaultConfigContent, err := fileOp.GetContent(defaultConfigPath) + if err != nil { + return err + } + + for _, website := range websites { + websiteDir := path.Join(sitesDir, website.Alias) + if err := moveDefaultWafConfig(websiteDir, defaultConfigContent, defaultRuleDir, fileOp); err != nil { + return err + } + } + return nil +} + +func createOpenBasedirConfig(website *model.Website) { + fileOp := files.NewFileOp() + userIniPath := path.Join(GetSitePath(*website, SiteIndexDir), ".user.ini") + _ = fileOp.CreateFile(userIniPath) + _ = fileOp.SaveFile(userIniPath, fmt.Sprintf("open_basedir=/www/sites/%s/index:/tmp/", website.Alias), 0644) +} + +func createWafConfig(website *model.Website, domains []model.WebsiteDomain) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data") + fileOp := files.NewFileOp() + if !fileOp.Stat(wafDataPath) { + return nil + } + websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json") + content, err := fileOp.GetContent(websitesConfigPath) + if err != nil { + return err + } + var websitesArray []request.WafWebsite + if len(content) != 0 { + if err := json.Unmarshal(content, &websitesArray); err != nil { + return err + } + } + wafWebsite := request.WafWebsite{ + Key: website.Alias, + Domains: make([]string, 0), + Host: make([]string, 0), + } + + for _, domain := range domains { + wafWebsite.Domains = append(wafWebsite.Domains, domain.Domain) + wafWebsite.Host = append(wafWebsite.Host, domain.Domain+":"+strconv.Itoa(domain.Port)) + } + websitesArray = append(websitesArray, wafWebsite) + websitesContent, err := json.Marshal(websitesArray) + if err != nil { + return err + } + if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil { + return err + } + + var ( + sitesDir = path.Join(wafDataPath, "sites") + defaultConfigPath = path.Join(wafDataPath, "conf", "siteConfig.json") + defaultRuleDir = path.Join(wafDataPath, "rules") + websiteDir = path.Join(sitesDir, website.Alias) + ) + + defaultConfigContent, err := fileOp.GetContent(defaultConfigPath) + if err != nil { + return err + } + + defer func() { + if err != nil { + _ = fileOp.DeleteDir(websiteDir) + } + }() + + if err := moveDefaultWafConfig(websiteDir, defaultConfigContent, defaultRuleDir, fileOp); err != nil { + return err + } + + if err = opNginx(nginxInstall.ContainerName, constant.NginxCheck); err != nil { + return err + } + if err = opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { + return err + } + + return nil +} + +func delNginxConfig(website model.Website, force bool) error { + fileOp := files.NewFileOp() + var ( + configPath string + ) + if website.Type == constant.Stream { + configPath = GetSitePath(website, StreamConf) + + } else { + configPath = GetSitePath(website, SiteConf) + } + if fileOp.Stat(configPath) { + if err := fileOp.DeleteFile(configPath); err != nil { + return err + } + } + sitePath := GetSiteDir(website.Alias) + if fileOp.Stat(sitePath) { + xpack.RemoveTamper(website.Alias) + _ = fileOp.DeleteDir(sitePath) + } + + nginxApp, err := appRepo.GetFirst(appRepo.WithKey(constant.AppOpenresty)) + if err != nil { + return err + } + nginxInstall, err := appInstallRepo.GetFirst(appInstallRepo.WithAppId(nginxApp.ID)) + if err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return nil + } + return err + } + if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { + if force { + return nil + } + return err + } + return nil +} + +func delWafConfig(website model.Website, force bool) error { + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + if !common.CompareVersion(nginxInstall.Version, "1.21.4.3-2-0") { + return nil + } + wafDataPath := path.Join(nginxInstall.GetPath(), "1pwaf", "data") + fileOp := files.NewFileOp() + if !fileOp.Stat(wafDataPath) { + return nil + } + monitorDir := path.Join(wafDataPath, "db", "sites", website.Alias) + if fileOp.Stat(monitorDir) { + _ = fileOp.DeleteDir(monitorDir) + } + websitesConfigPath := path.Join(wafDataPath, "conf", "sites.json") + content, err := fileOp.GetContent(websitesConfigPath) + if err != nil { + return err + } + var websitesArray []request.WafWebsite + var newWebsiteArray []request.WafWebsite + if len(content) > 0 { + if err = json.Unmarshal(content, &websitesArray); err != nil { + return err + } + } + for _, wafWebsite := range websitesArray { + if wafWebsite.Key != website.Alias { + newWebsiteArray = append(newWebsiteArray, wafWebsite) + } + } + websitesContent, err := json.Marshal(newWebsiteArray) + if err != nil { + return err + } + if err := fileOp.SaveFileWithByte(websitesConfigPath, websitesContent, constant.DirPerm); err != nil { + return err + } + + _ = fileOp.DeleteDir(path.Join(wafDataPath, "sites", website.Alias)) + + if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { + if force { + return nil + } + return err + } + return nil +} + +func isHttp3(server *components.Server) bool { + for _, listen := range server.Listens { + for _, param := range listen.Parameters { + if param == "quic" { + return true + } + } + } + return false +} + +func addListenAndServerName(website model.Website, domains []model.WebsiteDomain) error { + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + nginxConfig := nginxFull.SiteConfig + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + http3 := isHttp3(server) + + var allDomains []string + existDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(website.ID)) + for _, domain := range existDomains { + allDomains = append(allDomains, domain.Domain) + } + + for _, domain := range domains { + setListen(server, strconv.Itoa(domain.Port), website.IPV6, http3, website.DefaultServer, website.Protocol == constant.ProtocolHTTPS && domain.SSL) + allDomains = append(allDomains, domain.Domain) + } + server.UpdateServerName(allDomains) + + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName) +} + +func deleteListenAndServerName(website model.Website, binds []string, domains []string) error { + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + nginxConfig := nginxFull.SiteConfig + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + for _, bind := range binds { + server.DeleteListen(bind) + server.DeleteListen("[::]:" + bind) + } + for _, domain := range domains { + server.DeleteServerName(domain) + } + + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName) +} + +func setListen(server *components.Server, port string, ipv6, http3, defaultServer, ssl bool) { + var params []string + if ssl { + params = []string{"ssl"} + } + server.UpdateListen(port, defaultServer, params...) + if ssl && http3 { + server.UpdateListen(port, defaultServer, "quic") + } + if !ipv6 { + return + } + server.UpdateListen("[::]:"+port, defaultServer, params...) + if ssl && http3 { + server.UpdateListen("[::]:"+port, defaultServer, "quic") + } +} + +func removeSSLListen(website model.Website, binds []string) error { + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + nginxConfig := nginxFull.SiteConfig + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + http3 := isHttp3(server) + for _, bind := range binds { + server.DeleteListen(bind) + if website.IPV6 { + server.DeleteListen("[::]:" + bind) + } + setListen(server, bind, website.IPV6, http3, website.DefaultServer, website.Protocol == constant.ProtocolHTTPS) + } + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(nginxConfig.OldContent, nginxConfig.FilePath, nginxFull.Install.ContainerName) +} + +func createPemFile(website model.Website, websiteSSL model.WebsiteSSL) error { + configDir := GetSitePath(website, SiteSSLDir) + fileOp := files.NewFileOp() + + if !fileOp.Stat(configDir) { + if err := fileOp.CreateDir(configDir, constant.DirPerm); err != nil { + return err + } + } + + fullChainFile := path.Join(configDir, "fullchain.pem") + privatePemFile := path.Join(configDir, "privkey.pem") + + if !fileOp.Stat(fullChainFile) { + if err := fileOp.CreateFile(fullChainFile); err != nil { + return err + } + } + if !fileOp.Stat(privatePemFile) { + if err := fileOp.CreateFile(privatePemFile); err != nil { + return err + } + } + + if err := fileOp.WriteFile(fullChainFile, strings.NewReader(websiteSSL.Pem), constant.DirPerm); err != nil { + return err + } + if err := fileOp.WriteFile(privatePemFile, strings.NewReader(websiteSSL.PrivateKey), constant.DirPerm); err != nil { + return err + } + return nil +} + +func getHttpsPort(websiteID uint) map[int]struct{} { + domains, err := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteID)) + if err != nil { + return nil + } + httpsPorts := make(map[int]struct{}) + nginxInstall, _ := getAppInstallByKey(constant.AppOpenresty) + hasDefaultPort := false + for _, domain := range domains { + if domain.Port == nginxInstall.HttpPort { + hasDefaultPort = true + } + if domain.SSL { + httpsPorts[domain.Port] = struct{}{} + } + } + if hasDefaultPort { + httpsPorts[nginxInstall.HttpsPort] = struct{}{} + } + if len(httpsPorts) == 0 { + for _, domain := range domains { + if !domain.SSL { + httpsPorts[domain.Port] = struct{}{} + } + } + } + return httpsPorts +} + +func applySSL(website *model.Website, websiteSSL model.WebsiteSSL, req request.WebsiteHTTPSOp) error { + nginxFull, err := getNginxFull(website) + if err != nil { + return nil + } + domains, err := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(website.ID)) + if err != nil { + return nil + } + httpPorts := make(map[int]struct{}) + httpsPorts := make(map[int]struct{}) + sslPort := 0 + + hasDefaultPort := false + for _, domain := range domains { + if domain.Port == nginxFull.Install.HttpPort { + hasDefaultPort = true + } + if domain.SSL { + httpsPorts[domain.Port] = struct{}{} + } else { + httpPorts[domain.Port] = struct{}{} + } + } + if hasDefaultPort { + httpsPorts[nginxFull.Install.HttpsPort] = struct{}{} + } + if len(httpsPorts) == 0 { + for port := range httpPorts { + httpsPorts[port] = struct{}{} + } + } + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + + defaultHttpPort := strconv.Itoa(nginxFull.Install.HttpPort) + defaultHttpPortIPV6 := "[::]:" + defaultHttpPort + + for port := range httpsPorts { + sslPort = port + portStr := strconv.Itoa(port) + server.RemoveListenByBind(portStr) + server.RemoveListenByBind("[::]:" + portStr) + setListen(server, portStr, website.IPV6, req.Http3, website.DefaultServer, true) + } + + server.UpdateDirective("http2", []string{"on"}) + + switch req.HttpConfig { + case constant.HTTPSOnly: + server.RemoveListenByBind(defaultHttpPort) + server.RemoveListenByBind(defaultHttpPortIPV6) + server.RemoveDirective("if", []string{"($scheme"}) + case constant.HTTPToHTTPS: + if hasDefaultPort { + server.UpdateListen(defaultHttpPort, website.DefaultServer) + if website.IPV6 { + server.UpdateListen(defaultHttpPortIPV6, website.DefaultServer) + } + } + server.AddHTTP2HTTPS(sslPort) + case constant.HTTPAlso: + if hasDefaultPort { + server.UpdateListen(defaultHttpPort, website.DefaultServer) + if website.IPV6 { + server.UpdateListen(defaultHttpPortIPV6, website.DefaultServer) + } + } + server.RemoveDirective("if", []string{"($scheme"}) + } + + if !req.Hsts { + server.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""}) + server.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000; includeSubDomains\""}) + } + if !req.Http3 { + for port := range httpsPorts { + server.RemoveListen(strconv.Itoa(port), "quic") + if website.IPV6 { + httpsPortIPV6 := "[::]:" + strconv.Itoa(port) + server.RemoveListen(httpsPortIPV6, "quic") + } + } + server.RemoveDirective("add_header", []string{"Alt-Svc"}) + } + + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + if err = createPemFile(*website, websiteSSL); err != nil { + return err + } + nginxParams := getNginxParamsFromStaticFile(dto.SSL, []dto.NginxParam{}) + for i, param := range nginxParams { + if param.Name == "ssl_certificate" { + nginxParams[i].Params = []string{path.Join("/www", "sites", website.Alias, "ssl", "fullchain.pem")} + } + if param.Name == "ssl_certificate_key" { + nginxParams[i].Params = []string{path.Join("/www", "sites", website.Alias, "ssl", "privkey.pem")} + } + if param.Name == "ssl_protocols" { + nginxParams[i].Params = req.SSLProtocol + if len(req.SSLProtocol) == 0 { + nginxParams[i].Params = []string{"TLSv1.3", "TLSv1.2"} + } + } + if param.Name == "ssl_ciphers" { + nginxParams[i].Params = []string{req.Algorithm} + if len(req.Algorithm) == 0 { + nginxParams[i].Params = []string{"ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED"} + } + } + } + if req.Hsts { + var hstsValue string + if req.HstsIncludeSubDomains { + hstsValue = "\"max-age=31536000; includeSubDomains\"" + } else { + hstsValue = "\"max-age=31536000\"" + } + nginxParams = append(nginxParams, dto.NginxParam{ + Name: "add_header", + Params: []string{"Strict-Transport-Security", hstsValue}, + }) + } + if req.Http3 { + nginxParams = append(nginxParams, dto.NginxParam{ + Name: "add_header", + Params: []string{"Alt-Svc", "'h3=\":443\"; ma=2592000'"}, + }) + } + + if err := updateNginxConfig(constant.NginxScopeServer, nginxParams, website); err != nil { + return err + } + return nil +} + +func getParamArray(key string, param interface{}) []string { + var res []string + switch p := param.(type) { + case string: + if key == "index" { + res = strings.Split(p, "\n") + return res + } + + res = strings.Split(p, " ") + return res + } + return res +} + +func handleParamMap(paramMap map[string]string, keys []string) []dto.NginxParam { + var nginxParams []dto.NginxParam + for k, v := range paramMap { + for _, name := range keys { + if name == k { + param := dto.NginxParam{ + Name: k, + Params: getParamArray(k, v), + } + nginxParams = append(nginxParams, param) + } + } + } + return nginxParams +} + +func getNginxParams(params interface{}, keys []string) []dto.NginxParam { + var nginxParams []dto.NginxParam + + switch p := params.(type) { + case map[string]interface{}: + return handleParamMap(toMapStr(p), keys) + case []interface{}: + for _, mA := range p { + if m, ok := mA.(map[string]interface{}); ok { + nginxParams = append(nginxParams, handleParamMap(toMapStr(m), keys)...) + } + } + } + return nginxParams +} + +func toMapStr(m map[string]interface{}) map[string]string { + ret := make(map[string]string, len(m)) + for k, v := range m { + ret[k] = fmt.Sprint(v) + } + return ret +} + +func deleteWebsiteFolder(website *model.Website) error { + siteFolder := GetSitePath(*website, SiteDir) + fileOp := files.NewFileOp() + if fileOp.Stat(siteFolder) { + _ = fileOp.DeleteDir(siteFolder) + } + if website.Type == constant.Stream { + steamFilePath := GetSitePath(*website, StreamConf) + if fileOp.Stat(steamFilePath) { + _ = fileOp.DeleteFile(steamFilePath) + } + } else { + nginxFilePath := GetSitePath(*website, SiteConf) + if fileOp.Stat(nginxFilePath) { + _ = fileOp.DeleteFile(nginxFilePath) + } + } + return nil +} + +func opWebsite(website *model.Website, operate string) error { + nginxInstall, err := getNginxFull(website) + if err != nil { + return err + } + config := nginxInstall.SiteConfig.Config + servers := config.FindServers() + if len(servers) == 0 { + return errors.New("nginx config is not valid") + } + server := servers[0] + if operate == constant.StopWeb { + proxyInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias) + server.RemoveDirective("include", []string{proxyInclude}) + rewriteInclude := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias) + server.RemoveDirective("include", []string{rewriteInclude}) + redirectInclude := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, website.Alias) + server.RemoveDirective("include", []string{redirectInclude}) + + switch website.Type { + case constant.Deployment: + server.RemoveDirective("location", []string{"/"}) + case constant.Runtime: + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + if runtime.Type == constant.RuntimePHP { + server.RemoveDirective("location", []string{"~", "[^/]\\.php(/|$)"}) + } else { + server.RemoveDirective("location", []string{"/"}) + } + } + server.UpdateRoot("/usr/share/nginx/html/stop") + website.Status = constant.WebStopped + } + if operate == constant.StartWeb { + absoluteIncludeDir := GetSitePath(*website, SiteProxyDir) + fileOp := files.NewFileOp() + if fileOp.Stat(absoluteIncludeDir) && !files.IsEmptyDir(absoluteIncludeDir) { + proxyInclude := fmt.Sprintf("/www/sites/%s/proxy/*.conf", website.Alias) + server.UpdateDirective("include", []string{proxyInclude}) + } + rewriteInclude := fmt.Sprintf("/www/sites/%s/rewrite/%s.conf", website.Alias, website.Alias) + absoluteRewritePath := GetSitePath(*website, SiteReWritePath) + if fileOp.Stat(absoluteRewritePath) { + server.UpdateDirective("include", []string{rewriteInclude}) + } + redirectInclude := fmt.Sprintf("/www/sites/%s/redirect/%s.conf", website.Alias, website.Alias) + absoluteRedirectPath := GetSitePath(*website, SiteRedirectDir) + if fileOp.Stat(absoluteRedirectPath) { + server.UpdateDirective("include", []string{redirectInclude}) + } + rootIndex := path.Join("/www/sites", website.Alias, "index") + if website.SiteDir != "/" { + rootIndex = path.Join(rootIndex, website.SiteDir) + } + switch website.Type { + case constant.Deployment: + server.RemoveDirective("root", nil) + appInstall, err := appInstallRepo.GetFirst(repo.WithByID(website.AppInstallID)) + if err != nil { + return err + } + proxy := fmt.Sprintf("http://127.0.0.1:%d", appInstall.HttpPort) + server.UpdateRootProxy([]string{proxy}) + case constant.Static: + server.UpdateRoot(rootIndex) + server.UpdateRootLocation() + case constant.Proxy: + server.RemoveDirective("root", nil) + case constant.Runtime: + server.UpdateRoot(rootIndex) + localPath := "" + runtime, err := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) + if err != nil { + return err + } + if runtime.Type == constant.RuntimePHP { + if website.ProxyType == constant.RuntimeProxyUnix || website.ProxyType == constant.RuntimeProxyTcp { + localPath = path.Join(rootIndex, "index.php") + } + server.UpdatePHPProxy([]string{website.Proxy}, localPath) + } else { + proxy := fmt.Sprintf("http://%s", website.Proxy) + server.UpdateRootProxy([]string{proxy}) + } + } + website.Status = constant.WebRunning + now := time.Now() + if website.ExpireDate.Before(now) { + defaultDate, _ := time.Parse(constant.DateLayout, constant.WebsiteDefaultExpireDate) + website.ExpireDate = defaultDate + } + } + + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(nginxInstall.SiteConfig.OldContent, config.FilePath, nginxInstall.Install.ContainerName) +} + +func changeIPV6(website model.Website, enable bool) error { + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + listens := server.Listens + if enable { + for _, listen := range listens { + if strings.HasPrefix(listen.Bind, "[::]:") { + continue + } + exist := false + ipv6Bind := fmt.Sprintf("[::]:%s", listen.Bind) + for _, li := range listens { + if li.Bind == ipv6Bind { + exist = true + break + } + } + if !exist { + server.UpdateListen(ipv6Bind, false, listen.GetParameters()[1:]...) + } + } + } else { + for _, listen := range listens { + if strings.HasPrefix(listen.Bind, "[::]:") { + server.RemoveListenByBind(listen.Bind) + } + } + } + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(nginxFull.SiteConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName) +} + +func checkIsLinkApp(website model.Website) bool { + if website.Type == constant.Deployment { + return true + } + if website.Type == constant.Runtime { + runtime, _ := runtimeRepo.GetFirst(context.Background(), repo.WithByID(website.RuntimeID)) + return runtime.Resource == constant.ResourceAppstore + } + return false +} + +func chownRootDir(path string) error { + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(1 * time.Second)) + if err := cmdMgr.RunBashCf(`chown -R 1000:1000 "%s"`, path); err != nil { + return err + } + return nil +} + +func getWebsiteDomains(domains []request.WebsiteDomain, defaultHTTPPort, defaultHTTPsPort int, websiteID uint) (domainModels []model.WebsiteDomain, addPorts []int, addDomains []string, err error) { + var ( + ports = make(map[int]struct{}) + existPort = make(map[int]struct{}) + ) + existDomains, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteID)) + for _, domain := range existDomains { + existPort[domain.Port] = struct{}{} + } + for _, domain := range domains { + if domain.Domain == "" { + continue + } + if !(common.IsValidNginxServerName(domain.Domain) || common.IsValidIP(domain.Domain)) { + err = buserr.WithName("ErrDomainFormat", domain.Domain) + return + } + var domainModel model.WebsiteDomain + domainModel.Domain, err = handleChineseDomain(domain.Domain) + if err != nil { + return + } + domainModel.Domain = strings.ToLower(domainModel.Domain) + domainModel.Port = domain.Port + if domain.Port == 0 { + domain.Port = defaultHTTPPort + } + domainModel.SSL = domain.SSL + domainModel.WebsiteID = websiteID + domainModels = append(domainModels, domainModel) + if _, ok := existPort[domainModel.Port]; !ok { + ports[domainModel.Port] = struct{}{} + } + if exist, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithDomain(domainModel.Domain), websiteDomainRepo.WithWebsiteId(websiteID)); exist.ID == 0 { + addDomains = append(addDomains, domainModel.Domain) + } + } + for _, domain := range domainModels { + if exist, _ := websiteDomainRepo.GetFirst(websiteDomainRepo.WithDomain(domain.Domain), websiteDomainRepo.WithPort(domain.Port)); exist.ID > 0 { + website, _ := websiteRepo.GetFirst(repo.WithByID(exist.WebsiteID)) + err = buserr.WithName("ErrDomainIsUsed", website.PrimaryDomain) + return + } + } + + for port := range ports { + if port == defaultHTTPPort || port == defaultHTTPsPort { + addPorts = append(addPorts, port) + continue + } + if err = checkWebsitePort(defaultHTTPsPort, port, ""); err != nil { + return + } + if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithWebsiteId(websiteID), websiteDomainRepo.WithPort(port)); len(existPorts) == 0 { + addPorts = append(addPorts, port) + } + } + + return +} + +func checkWebsitePort(defaultHTTPsPort, port int, websiteType string) error { + if existPorts, _ := websiteDomainRepo.GetBy(websiteDomainRepo.WithPort(port)); len(existPorts) > 0 { + return nil + } + if websiteType == constant.Stream { + websites, _ := websiteRepo.List(websiteRepo.WithType(constant.Stream)) + for _, website := range websites { + ports := strings.Split(website.StreamPorts, ",") + for _, p := range ports { + pInt, _ := strconv.Atoi(p) + if pInt == port { + return nil + } + } + } + } + + errMap := make(map[string]interface{}) + errMap["port"] = port + appInstall, _ := appInstallRepo.GetFirst(appInstallRepo.WithPort(port)) + if appInstall.ID > 0 { + errMap["type"] = i18n.GetMsgByKey("TYPE_APP") + errMap["name"] = appInstall.Name + return buserr.WithMap("ErrPortExist", errMap, nil) + } + runtime, _ := runtimeRepo.GetFirst(context.Background(), runtimeRepo.WithPort(port)) + if runtime != nil { + errMap["type"] = i18n.GetMsgByKey("TYPE_RUNTIME") + errMap["name"] = runtime.Name + return buserr.WithMap("ErrPortExist", errMap, nil) + } + if port != defaultHTTPsPort && common.ScanPort(port) { + return buserr.WithDetail("ErrPortInUsed", port, nil) + } + return nil +} + +func saveCertificateFile(websiteSSL *model.WebsiteSSL, logger *log.Logger) { + if websiteSSL.PushDir { + fileOp := files.NewFileOp() + var ( + pushErr error + MsgMap = map[string]interface{}{"path": websiteSSL.Dir, "status": i18n.GetMsgByKey("Success")} + ) + if pushErr = fileOp.SaveFile(path.Join(websiteSSL.Dir, "privkey.pem"), websiteSSL.PrivateKey, constant.FilePerm); pushErr != nil { + MsgMap["status"] = i18n.GetMsgByKey("Failed") + logger.Println(i18n.GetMsgWithMap("PushDirLog", MsgMap)) + logger.Println("Push dir failed:" + pushErr.Error()) + } + if pushErr = fileOp.SaveFile(path.Join(websiteSSL.Dir, "fullchain.pem"), websiteSSL.Pem, constant.FilePerm); pushErr != nil { + MsgMap["status"] = i18n.GetMsgByKey("Failed") + logger.Println(i18n.GetMsgWithMap("PushDirLog", MsgMap)) + logger.Println("Push dir failed:" + pushErr.Error()) + } + if pushErr == nil { + logger.Println(i18n.GetMsgWithMap("PushDirLog", MsgMap)) + } + } +} + +func GetSystemSSL() (bool, uint) { + var sslSetting model.Setting + _ = global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SSL").First(&sslSetting).Error + if sslSetting.Value == "Enable" { + var sslIDSetting model.Setting + _ = global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SSLID").First(&sslIDSetting).Error + idValue, _ := strconv.Atoi(sslIDSetting.Value) + if idValue > 0 { + return true, uint(idValue) + } + } + return false, 0 +} + +func UpdateSSLConfig(websiteSSL model.WebsiteSSL) error { + websites, _ := websiteRepo.GetBy(websiteRepo.WithWebsiteSSLID(websiteSSL.ID)) + if len(websites) > 0 { + for _, website := range websites { + if err := createPemFile(website, websiteSSL); err != nil { + return buserr.WithMap("ErrUpdateWebsiteSSL", map[string]interface{}{"name": website.PrimaryDomain, "err": err.Error()}, err) + } + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + if err := opNginx(nginxInstall.ContainerName, constant.NginxReload); err != nil { + return buserr.WithErr("ErrSSLApply", err) + } + } + if !global.IsMaster { + return nil + } + enable, sslID := GetSystemSSL() + if enable && sslID == websiteSSL.ID { + fileOp := files.NewFileOp() + secretDir := path.Join(global.Dir.DataDir, "secret") + if err := fileOp.WriteFile(path.Join(secretDir, "server.crt"), strings.NewReader(websiteSSL.Pem), 0600); err != nil { + global.LOG.Errorf("Failed to update the SSL certificate File for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error()) + return err + } + if err := fileOp.WriteFile(path.Join(secretDir, "server.key"), strings.NewReader(websiteSSL.PrivateKey), 0600); err != nil { + global.LOG.Errorf("Failed to update the SSL certificate for 1Panel System domain [%s] , err:%s", websiteSSL.PrimaryDomain, err.Error()) + return err + } + } + return nil +} + +func ChangeHSTSConfig(enable bool, includeSubDomains bool, http3Enable bool, website model.Website) error { + includeDir := GetSitePath(website, SiteProxyDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(includeDir) { + return nil + } + err := filepath.Walk(includeDir, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + if filepath.Ext(path) == ".conf" { + par, err := parser.NewParser(path) + if err != nil { + return err + } + config, err := par.Parse() + if err != nil { + return err + } + config.FilePath = path + directives := config.Directives + location, ok := directives[0].(*components.Location) + if !ok { + return nil + } + //前置移除HSTS配置 + location.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000\""}) + location.RemoveDirective("add_header", []string{"Strict-Transport-Security", "\"max-age=31536000; includeSubDomains\""}) + if enable { + var hstsValue string + if includeSubDomains { + hstsValue = "\"max-age=31536000; includeSubDomains\"" + } else { + hstsValue = "\"max-age=31536000\"" + } + location.UpdateDirective("add_header", []string{"Strict-Transport-Security", hstsValue}) + } + + if http3Enable { + location.UpdateDirective("add_header", []string{"Alt-Svc", "'h3=\":443\"; ma=2592000'"}) + } else { + location.RemoveDirective("add_header", []string{"Alt-Svc", "'h3=\":443\"; ma=2592000'"}) + } + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return buserr.WithErr("ErrUpdateBuWebsite", err) + } + } + } + return nil + }) + if err != nil { + return err + } + return nil +} + +func checkSSLStatus(expireDate time.Time) string { + now := time.Now() + daysUntilExpiry := int(expireDate.Sub(now).Hours() / 24) + + if daysUntilExpiry < 0 { + return "danger" + } else if daysUntilExpiry <= 10 { + return "warning" + } + return "success" +} + +func getResourceContent(fileOp files.FileOp, resourcePath string) (string, error) { + if fileOp.Stat(resourcePath) { + content, err := fileOp.GetContent(resourcePath) + if err != nil { + return "", err + } + return string(content), nil + } + return "", nil +} + +func GetWebSiteRootDir() string { + siteSetting, _ := settingRepo.Get(settingRepo.WithByKey("WEBSITE_DIR")) + dir := siteSetting.Value + if dir == "" { + dir = path.Join(global.Dir.DataDir, "www") + } + return dir +} + +func GetSiteDir(alias string) string { + return path.Join(GetWebSiteRootDir(), "sites", alias) +} + +const ( + SiteConf = "SiteConf" + SiteAccessLog = "access.log" + SiteErrorLog = "error.log" + WebsiteRootDir = "WebsiteRootDir" + SiteDir = "SiteDir" + SiteIndexDir = "SiteIndexDir" + SiteProxyDir = "SiteProxyDir" + SiteSSLDir = "SiteSSLDir" + SiteReWritePath = "SiteReWritePath" + SiteRedirectDir = "SiteRedirectDir" + SiteCacheDir = "SiteCacheDir" + SiteConfDir = "SiteConfDir" + SitesRootDir = "SitesRootDir" + DefaultDir = "DefaultDir" + DefaultRewriteDir = "DefaultRewriteDir" + SiteRootAuthBasicPath = "SiteRootAuthBasicPath" + SitePathAuthBasicDir = "SitePathAuthBasicDir" + SiteUpstreamDir = "SiteUpstreamDir" + SiteCorsPath = "SiteCorsPath" + StreamDir = "StreamDir" + StreamConf = "StreamConf" +) + +func GetWebsiteConfigPath(website model.Website) string { + if website.Type != constant.Stream { + return GetSitePath(website, SiteConf) + } + return GetSitePath(website, StreamConf) +} + +func GetSitePath(website model.Website, confType string) string { + switch confType { + case SiteConf: + return path.Join(GetWebSiteRootDir(), "conf.d", website.Alias+".conf") + case StreamConf: + return path.Join(GetWebSiteRootDir(), "stream.d", website.Alias+".conf") + case SiteAccessLog: + return path.Join(GetSiteDir(website.Alias), "log", "access.log") + case SiteErrorLog: + return path.Join(GetSiteDir(website.Alias), "log", "error.log") + case SiteDir: + return GetSiteDir(website.Alias) + case SiteIndexDir: + return path.Join(GetSiteDir(website.Alias), "index") + case SiteCacheDir: + return path.Join(GetSiteDir(website.Alias), "cache") + case SiteProxyDir: + return path.Join(GetSiteDir(website.Alias), "proxy") + case SiteSSLDir: + return path.Join(GetSiteDir(website.Alias), "ssl") + case SiteReWritePath: + return path.Join(GetSiteDir(website.Alias), "rewrite", website.Alias+".conf") + case SiteRedirectDir: + return path.Join(GetSiteDir(website.Alias), "redirect") + case SiteRootAuthBasicPath: + return path.Join(GetSiteDir(website.Alias), "auth_basic", "auth.pass") + case SitePathAuthBasicDir: + return path.Join(GetSiteDir(website.Alias), "path_auth") + case SiteUpstreamDir: + return path.Join(GetSiteDir(website.Alias), "upstream") + case SiteCorsPath: + return path.Join(GetSiteDir(website.Alias), "cors", "cors.conf") + } + return "" +} + +func GetConfDir(website model.Website) string { + if website.Type != constant.Stream { + return GetOpenrestyDir(SiteConf) + } + return GetOpenrestyDir(StreamDir) +} + +func GetOpenrestyDir(confType string) string { + switch confType { + case WebsiteRootDir: + return GetWebSiteRootDir() + case SiteConfDir: + return path.Join(GetWebSiteRootDir(), "conf.d") + case StreamDir: + return path.Join(GetWebSiteRootDir(), "stream.d") + case SitesRootDir: + return path.Join(GetWebSiteRootDir(), "sites") + case DefaultDir: + return path.Join(GetWebSiteRootDir(), "default") + case DefaultRewriteDir: + return path.Join(GetWebSiteRootDir(), "default", "rewrite") + } + return "" +} + +func openProxyCache(website model.Website) error { + cacheDir := GetSitePath(website, SiteCacheDir) + fileOp := files.NewFileOp() + if !fileOp.Stat(cacheDir) { + _ = fileOp.CreateDir(cacheDir, constant.DirPerm) + } + content, err := fileOp.GetContent(GetSitePath(website, SiteConf)) + if err != nil { + return err + } + if strings.Contains(string(content), "proxy_cache_path") { + return nil + } + proxyCachePath := fmt.Sprintf("/www/sites/%s/cache levels=1:2 keys_zone=proxy_cache_zone_of_%s:5m max_size=1g inactive=24h", website.Alias, website.Alias) + return updateNginxConfig("", []dto.NginxParam{{Name: "proxy_cache_path", Params: []string{proxyCachePath}}}, &website) +} + +func ConfigAllowIPs(ips []string, website model.Website) error { + nginxFull, err := getNginxFull(&website) + if err != nil { + return err + } + nginxConfig := nginxFull.SiteConfig + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + server.RemoveDirective("allow", nil) + server.RemoveDirective("deny", nil) + if len(ips) > 0 { + server.UpdateAllowIPs(ips) + } + if err := nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + return nginxCheckAndReload(nginxConfig.OldContent, config.FilePath, nginxFull.Install.ContainerName) +} + +func GetAllowIps(website model.Website) []string { + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + dirs := server.GetDirectives() + var ips []string + for _, dir := range dirs { + if dir.GetName() == "allow" { + ips = append(ips, dir.GetParameters()...) + } + } + return ips +} + +func ConfigAIProxy(website model.Website) error { + nginxFull, err := getNginxFull(&website) + if err != nil { + return nil + } + config := nginxFull.SiteConfig.Config + server := config.FindServers()[0] + dirs := server.GetDirectives() + for _, dir := range dirs { + if dir.GetName() == "location" && dir.GetParameters()[0] == "/" { + server.UpdateRootProxyForAi([]string{fmt.Sprintf("http://%s", website.Proxy)}) + } + } + return nil +} + +func handleDefaultOwn(dir string) { + parentDir := path.Dir(dir) + info, err := os.Stat(parentDir) + if err != nil { + return + } + stat, ok := info.Sys().(*syscall.Stat_t) + uid, gid := -1, -1 + if ok { + uid, gid = int(stat.Uid), int(stat.Gid) + } + _ = os.Chown(dir, uid, gid) +} + +func getSystemProxy(useProxy bool) *dto.SystemProxy { + if !useProxy { + return nil + } + settingService := NewISettingService() + systemProxy, _ := settingService.GetSystemProxy() + return systemProxy +} + +func hasHttp3(params []string) bool { + for _, param := range params { + if param == "quic" { + return true + } + } + return false +} + +func hasDefaultServer(params []string) bool { + for _, param := range params { + if param == "default_server" { + return true + } + } + return false +} + +func ParseDomain(domain string) (*model.WebsiteDomain, error) { + host, portStr, err := net.SplitHostPort(domain) + if err != nil { + return &model.WebsiteDomain{ + Domain: domain, + Port: 0, + }, nil + } + + port, err := strconv.Atoi(portStr) + if err != nil { + return nil, fmt.Errorf("invalid port in domain %s: %v", domain, err) + } + return &model.WebsiteDomain{ + Domain: host, + Port: port, + }, nil +} + +func updateWebsiteConfig(website model.Website, updateFunc func(server *components.Server) error) error { + configPath := GetSitePath(website, SiteConf) + nginxContent, err := files.NewFileOp().GetContent(configPath) + if err != nil { + return err + } + + config, err := parser.NewStringParser(string(nginxContent)).Parse() + if err != nil { + return err + } + config.FilePath = configPath + servers := config.FindServers() + if len(servers) == 0 { + return errors.New("nginx config is not valid") + } + server := servers[0] + if err := updateFunc(server); err != nil { + return err + } + if err = nginx.WriteConfig(config, nginx.IndentedStyle); err != nil { + return err + } + nginxInstall, err := getAppInstallByKey(constant.AppOpenresty) + if err != nil { + return err + } + return nginxCheckAndReload(string(nginxContent), configPath, nginxInstall.ContainerName) +} + +func getServer(website model.Website) (*components.Server, error) { + configPath := GetSitePath(website, SiteConf) + nginxContent, err := files.NewFileOp().GetContent(configPath) + if err != nil { + return nil, err + } + + config, err := parser.NewStringParser(string(nginxContent)).Parse() + if err != nil { + return nil, err + } + + servers := config.FindServers() + if len(servers) == 0 { + return nil, errors.New("nginx config is not valid") + } + server := servers[0] + return server, nil +} + +func parseTimeString(input string) (int, string, error) { + input = strings.TrimSpace(input) + + matches := re.GetRegex(re.DurationWithOptionalUnitPattern).FindStringSubmatch(input) + + if len(matches) < 2 { + return 0, "", fmt.Errorf("invalid time format: %s", input) + } + + value, err := strconv.Atoi(matches[1]) + if err != nil { + return 0, "", fmt.Errorf("invalid number: %s", matches[1]) + } + + unit := matches[2] + if unit == "" { + unit = "s" + } + return value, unit, nil +} + +func parseUpstreamServers(reqServers []dto.NginxUpstreamServer) []*components.UpstreamServer { + var servers []*components.UpstreamServer + for _, server := range reqServers { + upstreamServer := &components.UpstreamServer{ + Address: server.Server, + } + parameters := make(map[string]string) + if server.Weight > 0 { + parameters["weight"] = strconv.Itoa(server.Weight) + } + if server.MaxFails > 0 { + parameters["max_fails"] = strconv.Itoa(server.MaxFails) + } + if server.FailTimeout > 0 { + parameters["fail_timeout"] = fmt.Sprintf("%d%s", server.FailTimeout, server.FailTimeoutUnit) + } + if server.MaxConns > 0 { + parameters["max_conns"] = strconv.Itoa(server.MaxConns) + } + if server.Flag != "" { + upstreamServer.Flags = []string{server.Flag} + } + upstreamServer.Parameters = parameters + servers = append(servers, upstreamServer) + } + return servers +} + +func getNginxUpstreamServers(upstreamServers []*components.UpstreamServer) []dto.NginxUpstreamServer { + var servers []dto.NginxUpstreamServer + for _, ups := range upstreamServers { + server := dto.NginxUpstreamServer{ + Server: ups.Address, + } + parameters := ups.Parameters + if weight, ok := parameters["weight"]; ok { + num, err := strconv.Atoi(weight) + if err == nil { + server.Weight = num + } + } + if maxFails, ok := parameters["max_fails"]; ok { + num, err := strconv.Atoi(maxFails) + if err == nil { + server.MaxFails = num + } + } + if failTimeout, ok := parameters["fail_timeout"]; ok { + server.FailTimeout, server.FailTimeoutUnit, _ = parseTimeString(failTimeout) + } + if maxConns, ok := parameters["max_conns"]; ok { + num, err := strconv.Atoi(maxConns) + if err == nil { + server.MaxConns = num + } + } + for _, flag := range ups.Flags { + server.Flag = flag + } + servers = append(servers, server) + } + return servers +} + +func getManualWebsiteSSL(req request.WebsiteHTTPSOp) (model.WebsiteSSL, error) { + var websiteSSL model.WebsiteSSL + var ( + certificate string + privateKey string + ) + switch req.ImportType { + case "paste": + certificate = req.Certificate + privateKey = req.PrivateKey + case "local": + fileOp := files.NewFileOp() + if !fileOp.Stat(req.PrivateKeyPath) { + return websiteSSL, buserr.New("ErrSSLKeyNotFound") + } + if !fileOp.Stat(req.CertificatePath) { + return websiteSSL, buserr.New("ErrSSLCertificateNotFound") + } + if content, err := fileOp.GetContent(req.PrivateKeyPath); err != nil { + return websiteSSL, err + } else { + privateKey = string(content) + } + if content, err := fileOp.GetContent(req.CertificatePath); err != nil { + return websiteSSL, err + } else { + certificate = string(content) + } + } + + privateKeyCertBlock, _ := pem.Decode([]byte(privateKey)) + if privateKeyCertBlock == nil { + return websiteSSL, buserr.New("ErrSSLKeyFormat") + } + + certBlock, _ := pem.Decode([]byte(certificate)) + if certBlock == nil { + return websiteSSL, buserr.New("ErrSSLCertificateFormat") + } + cert, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return websiteSSL, err + } + websiteSSL.ExpireDate = cert.NotAfter + websiteSSL.StartDate = cert.NotBefore + websiteSSL.Type = cert.Issuer.CommonName + if len(cert.Issuer.Organization) > 0 { + websiteSSL.Organization = cert.Issuer.Organization[0] + } else { + websiteSSL.Organization = cert.Issuer.CommonName + } + if len(cert.DNSNames) > 0 { + websiteSSL.PrimaryDomain = cert.DNSNames[0] + websiteSSL.Domains = strings.Join(cert.DNSNames, ",") + } + websiteSSL.Provider = constant.Manual + websiteSSL.PrivateKey = privateKey + websiteSSL.Pem = certificate + websiteSSL.Status = constant.SSLReady + return websiteSSL, nil +} diff --git a/agent/app/task/task.go b/agent/app/task/task.go new file mode 100644 index 0000000..4222acd --- /dev/null +++ b/agent/app/task/task.go @@ -0,0 +1,379 @@ +package task + +import ( + "context" + "errors" + "fmt" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/sirupsen/logrus" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/google/uuid" +) + +type ActionFunc func(*Task) error +type RollbackFunc func(*Task) + +type Task struct { + TaskCtx context.Context + + Name string + TaskID string + Logger *logrus.Logger + SubTasks []*SubTask + Rollbacks []RollbackFunc + logFile *os.File + taskRepo repo.ITaskRepo + Task *model.Task + ParentID string +} + +type SubTask struct { + RootTask *Task + Name string + StepAlias string + Retry int + Timeout time.Duration + Action ActionFunc + Rollback RollbackFunc + Error error + IgnoreErr bool +} + +const ( + TaskInstall = "TaskInstall" + TaskUninstall = "TaskUninstall" + TaskCreate = "TaskCreate" + TaskDelete = "TaskDelete" + TaskUpgrade = "TaskUpgrade" + TaskUpdate = "TaskUpdate" + TaskRestart = "TaskRestart" + TaskBackup = "TaskBackup" + TaskRecover = "TaskRecover" + TaskRollback = "TaskRollback" + TaskSync = "TaskSync" + TaskBuild = "TaskBuild" + TaskPull = "TaskPull" + TaskImport = "TaskImport" + TaskExport = "TaskExport" + TaskCommit = "TaskCommit" + TaskPush = "TaskPush" + TaskClean = "TaskClean" + TaskHandle = "TaskHandle" + TaskScan = "TaskScan" + TaskExec = "TaskExec" + TaskBatch = "TaskBatch" + TaskProtect = "TaskProtect" + TaskConvert = "TaskConvert" + TaskSwapSet = "TaskSwapSet" +) + +const ( + TaskScopeWebsite = "Website" + TaskScopeAI = "AI" + TaskScopeApp = "App" + TaskScopeRuntime = "Runtime" + TaskScopeDatabase = "Database" + TaskScopeCronjob = "Cronjob" + TaskScopeClam = "Clam" + TaskScopeSystem = "System" + TaskScopeAppStore = "AppStore" + TaskScopeSnapshot = "Snapshot" + TaskScopeContainer = "Container" + TaskScopeCompose = "Compose" + TaskScopeImage = "Image" + TaskScopeBackup = "Backup" + TaskScopeRuntimeExtension = "RuntimeExtension" + TaskScopeCustomAppstore = "CustomAppstore" + TaskScopeTamper = "Tamper" + TaskScopeFileConvert = "Convert" + TaskScopeTask = "Task" +) + +func GetTaskName(resourceName, operate, scope string) string { + return fmt.Sprintf("%s%s [%s]", i18n.GetMsgByKey(operate), i18n.GetMsgByKey(scope), resourceName) +} + +func NewTaskWithOps(resourceName, operate, scope, taskID string, resourceID uint) (*Task, error) { + return NewTask(GetTaskName(resourceName, operate, scope), operate, scope, taskID, resourceID) +} + +func CheckTaskIsExecuting(name string) error { + taskRepo := repo.NewITaskRepo() + task, _ := taskRepo.GetFirst(taskRepo.WithByStatus(constant.StatusExecuting), repo.WithByName(name)) + if task.ID != "" { + return buserr.New("TaskIsExecuting") + } + return nil +} + +func CheckResourceTaskIsExecuting(operate, scope string, resourceID uint) bool { + taskRepo := repo.NewITaskRepo() + task, _ := taskRepo.GetFirst( + taskRepo.WithByStatus(constant.StatusExecuting), + taskRepo.WithResourceID(resourceID), + taskRepo.WithOperate(operate), + repo.WithByType(scope)) + return task.ID != "" +} + +func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, error) { + if taskID == "" { + taskID = uuid.New().String() + } + logDir := path.Join(global.Dir.TaskDir, taskScope) + if _, err := os.Stat(logDir); os.IsNotExist(err) { + if err = os.MkdirAll(logDir, constant.DirPerm); err != nil { + return nil, fmt.Errorf("failed to create log directory: %w", err) + } + } + logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log") + logger := logrus.New() + logger.SetFormatter(&SimpleFormatter{}) + logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, constant.FilePerm) + if err != nil { + return nil, fmt.Errorf("failed to open log file: %w", err) + } + logger.SetOutput(logFile) + taskModel := &model.Task{ + ID: taskID, + Name: name, + Type: taskScope, + LogFile: logPath, + Status: constant.StatusExecuting, + ResourceID: resourceID, + Operate: operate, + } + taskRepo := repo.NewITaskRepo() + ctx, cancel := context.WithCancel(context.Background()) + global.TaskCtxMap[taskID] = cancel + task := &Task{TaskCtx: ctx, Name: name, logFile: logFile, Logger: logger, taskRepo: taskRepo, Task: taskModel} + return task, nil +} + +func ReNewTaskWithOps(resourceName, operate, scope, taskID string, resourceID uint) (*Task, error) { + return ReNewTask(GetTaskName(resourceName, operate, scope), operate, scope, taskID, resourceID) +} +func ReNewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, error) { + taskRepo := repo.NewITaskRepo() + taskItem, _ := taskRepo.GetFirst(taskRepo.WithByID(taskID)) + if taskItem.ID == "" { + return NewTask(name, operate, taskScope, taskID, resourceID) + } + logDir := path.Join(global.Dir.TaskDir, taskScope) + if _, err := os.Stat(logDir); err != nil { + if err = os.MkdirAll(logDir, constant.DirPerm); err != nil { + return nil, fmt.Errorf("failed to create log directory: %w", err) + } + } + + logPath := path.Join(global.Dir.TaskDir, taskScope, taskID+".log") + logger := logrus.New() + logger.SetFormatter(&SimpleFormatter{}) + logFile, err := os.OpenFile(logPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, constant.FilePerm) + if err != nil { + return nil, fmt.Errorf("failed to open log file: %w", err) + } + logger.SetOutput(logFile) + logger.Print("\n --------------------------------------------------- \n") + taskItem.Status = constant.StatusExecuting + task := &Task{Name: name, logFile: logFile, Logger: logger, taskRepo: taskRepo, Task: &taskItem} + task.updateTask(&taskItem) + return task, nil +} + +func (t *Task) AddSubTask(name string, action ActionFunc, rollback RollbackFunc) { + subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 30 * time.Minute, Action: action, Rollback: rollback} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (t *Task) AddSubTaskWithAlias(key string, action ActionFunc, rollback RollbackFunc) { + subTask := &SubTask{RootTask: t, Name: i18n.GetMsgByKey(key), StepAlias: key, Retry: 0, Timeout: 30 * time.Minute, Action: action, Rollback: rollback} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (t *Task) AddSubTaskWithOps(name string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) { + subTask := &SubTask{RootTask: t, Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (t *Task) AddSubTaskWithAliasAndOps(key string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) { + subTask := &SubTask{RootTask: t, Name: i18n.GetMsgByKey(key), StepAlias: key, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (t *Task) AddSubTaskWithIgnoreErr(name string, action ActionFunc) { + subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 30 * time.Minute, Action: action, Rollback: nil, IgnoreErr: true} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (s *SubTask) Execute() error { + defer delete(global.TaskCtxMap, s.RootTask.TaskID) + subTaskName := s.Name + if s.Name == "" { + subTaskName = i18n.GetMsgByKey("SubTask") + } + var err error + for i := 0; i < s.Retry+1; i++ { + if i > 0 { + s.RootTask.Log(i18n.GetWithName("TaskRetry", strconv.Itoa(i))) + } + ctx, cancel := context.WithTimeout(context.Background(), s.Timeout) + if s.Timeout == 0 { + ctx, cancel = context.WithCancel(context.Background()) + } + defer cancel() + + done := make(chan error) + go func() { + done <- s.Action(s.RootTask) + }() + + select { + case <-ctx.Done(): + s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName)) + err = errors.New("timeout!") + case err = <-done: + if err != nil { + s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err)) + if err.Error() == i18n.GetMsgByKey("ErrShutDown") { + return err + } + } else { + s.RootTask.Log(i18n.GetWithName("SubTaskSuccess", subTaskName)) + return nil + } + } + + if i == s.Retry { + if s.Rollback != nil { + s.Rollback(s.RootTask) + } + } + time.Sleep(1 * time.Second) + } + return err +} + +func (t *Task) updateTask(task *model.Task) { + _ = t.taskRepo.Update(context.Background(), task) +} + +func (t *Task) Execute() error { + if err := t.taskRepo.Save(context.Background(), t.Task); err != nil { + return err + } + var err error + t.Log(i18n.GetWithName("TaskStart", t.Name)) + for _, subTask := range t.SubTasks { + t.Task.CurrentStep = subTask.StepAlias + t.updateTask(t.Task) + if err = subTask.Execute(); err == nil { + if subTask.Rollback != nil { + t.Rollbacks = append(t.Rollbacks, subTask.Rollback) + } + } else { + if subTask.IgnoreErr { + err = nil + continue + } + t.Task.ErrorMsg = err.Error() + t.Task.Status = constant.StatusFailed + for _, rollback := range t.Rollbacks { + rollback(t) + } + t.updateTask(t.Task) + break + } + } + if t.Task.Status == constant.StatusExecuting { + t.Task.Status = constant.StatusSuccess + } + t.Log("[TASK-END]") + t.Task.EndAt = time.Now() + t.updateTask(t.Task) + _ = t.logFile.Close() + return err +} + +func (t *Task) DeleteLogFile() { + _ = os.Remove(t.Task.LogFile) +} + +func (t *Task) LogWithStatus(msg string, err error) { + if err != nil { + t.Logger.Print(i18n.GetWithNameAndErr("FailedStatus", msg, err)) + } else { + t.Logger.Print(i18n.GetWithName("SuccessStatus", msg)) + } +} + +func (t *Task) Log(msg string) { + t.Logger.Print(msg) +} + +func (t *Task) Logf(format string, v ...any) { + t.Logger.Printf(format, v...) +} + +func (t *Task) LogFailed(msg string) { + t.Logger.Println(msg + i18n.GetMsgByKey("Failed")) +} + +func (t *Task) LogFailedWithErr(msg string, err error) { + t.Logger.Printf("%s %s : %s", msg, i18n.GetMsgByKey("Failed"), err.Error()) +} + +func (t *Task) LogSuccess(msg string) { + t.Logger.Println(msg + " " + i18n.GetMsgByKey("Success")) +} +func (t *Task) LogSuccessF(format string, v ...any) { + t.Logger.Println(fmt.Sprintf(format, v...) + i18n.GetMsgByKey("Success")) +} + +func (t *Task) LogStart(msg string) { + t.Logger.Printf("%s%s", i18n.GetMsgByKey("Start"), msg) +} + +func (t *Task) LogWithOps(operate, msg string) { + t.Logger.Printf("%s%s", i18n.GetMsgByKey(operate), msg) +} + +func (t *Task) LogSuccessWithOps(operate, msg string) { + t.Logger.Printf("%s%s%s", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Success")) +} + +func (t *Task) LogFailedWithOps(operate, msg string, err error) { + t.Logger.Printf("%s%s : %s ", msg, i18n.GetMsgByKey("Failed"), err.Error()) +} + +func (t *Task) LogWithProgress(msg string, current int, total int) { + const barWidth = 10 + filled := int(float64(current) / float64(total) * 100 / 10) + if filled > barWidth { + filled = barWidth + } + if filled < 0 { + filled = 0 + } + bar := strings.Repeat("=", filled) + strings.Repeat("-", barWidth-filled) + t.Logger.Printf("%s [%s] %.2f%%", msg, bar, float64(current)/float64(total)*100) +} + +type SimpleFormatter struct{} + +func (f *SimpleFormatter) Format(entry *logrus.Entry) ([]byte, error) { + timestamp := entry.Time.Format("2006/01/02 15:04:05") + message := fmt.Sprintf("%s %s\n", timestamp, entry.Message) + return []byte(message), nil +} diff --git a/agent/buserr/errors.go b/agent/buserr/errors.go new file mode 100644 index 0000000..ad58d1f --- /dev/null +++ b/agent/buserr/errors.go @@ -0,0 +1,93 @@ +package buserr + +import ( + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/pkg/errors" +) + +type BusinessError struct { + Msg string + Detail interface{} + Map map[string]interface{} + Err error +} + +func (e BusinessError) Error() string { + content := "" + if e.Detail != nil { + content = i18n.GetErrMsg(e.Msg, map[string]interface{}{"detail": e.Detail}) + } else if e.Map != nil { + content = i18n.GetErrMsg(e.Msg, e.Map) + } else { + content = i18n.GetErrMsg(e.Msg, nil) + } + if content == "" { + if e.Err != nil { + return e.Err.Error() + } + return errors.New(e.Msg).Error() + } + return content +} + +func New(Key string) BusinessError { + return BusinessError{ + Msg: Key, + Detail: nil, + Err: nil, + } +} + +func WithDetail(Key string, detail interface{}, err error) BusinessError { + return BusinessError{ + Msg: Key, + Detail: detail, + Err: err, + } +} + +func WithErr(Key string, err error) BusinessError { + paramMap := map[string]interface{}{} + if err != nil { + paramMap["err"] = err + } + return BusinessError{ + Msg: Key, + Map: paramMap, + Err: err, + } +} + +func WithMap(Key string, maps map[string]interface{}, err error) BusinessError { + return BusinessError{ + Msg: Key, + Map: maps, + Err: err, + } +} + +func WithNameAndErr(Key string, name string, err error) BusinessError { + paramMap := map[string]interface{}{} + if name != "" { + paramMap["name"] = name + } + if err != nil { + paramMap["err"] = err.Error() + } + return BusinessError{ + Msg: Key, + Map: paramMap, + Err: err, + } +} + +func WithName(Key string, name string) BusinessError { + paramMap := map[string]interface{}{} + if name != "" { + paramMap["name"] = name + } + return BusinessError{ + Msg: Key, + Map: paramMap, + } +} diff --git a/agent/buserr/multi_err.go b/agent/buserr/multi_err.go new file mode 100644 index 0000000..bf0c875 --- /dev/null +++ b/agent/buserr/multi_err.go @@ -0,0 +1,23 @@ +package buserr + +import ( + "bytes" + "fmt" + "sort" +) + +type MultiErr map[string]error + +func (e MultiErr) Error() string { + var keys []string + for key := range e { + keys = append(keys, key) + } + sort.Strings(keys) + + buffer := bytes.NewBufferString("") + for _, key := range keys { + buffer.WriteString(fmt.Sprintf("[%s] %s\n", key, e[key])) + } + return buffer.String() +} diff --git a/agent/cmd/server/ai/ai.go b/agent/cmd/server/ai/ai.go new file mode 100644 index 0000000..8888eee --- /dev/null +++ b/agent/cmd/server/ai/ai.go @@ -0,0 +1,11 @@ +package ai + +import ( + _ "embed" +) + +//go:embed compose.yml +var DefaultMcpCompose []byte + +//go:embed llm-compose.yml +var DefaultTensorrtLLMCompose []byte diff --git a/agent/cmd/server/ai/compose.yml b/agent/cmd/server/ai/compose.yml new file mode 100644 index 0000000..433ee22 --- /dev/null +++ b/agent/cmd/server/ai/compose.yml @@ -0,0 +1,21 @@ +services: + mcp-server: + image: supercorp/supergateway:latest + container_name: ${CONTAINER_NAME} + restart: unless-stopped + ports: + - "${HOST_IP}:${PANEL_APP_PORT_HTTP}:${PANEL_APP_PORT_HTTP}" + command: [ + "--stdio", "${COMMAND}", + "--outputTransport","${OUTPUT_TRANSPORT}", + "--port", "${PANEL_APP_PORT_HTTP}", + "--baseUrl", "${BASE_URL}", + "--ssePath", "${SSE_PATH}", + "--streamableHttpPath", "${STREAMABLE_HTTP_PATH}", + "--messagePath", "${SSE_PATH}/messages" + ] + networks: + - 1panel-network +networks: + 1panel-network: + external: true \ No newline at end of file diff --git a/agent/cmd/server/ai/llm-compose.yml b/agent/cmd/server/ai/llm-compose.yml new file mode 100644 index 0000000..62e4b87 --- /dev/null +++ b/agent/cmd/server/ai/llm-compose.yml @@ -0,0 +1,30 @@ +services: + tensorrt-llm: + image: ${IMAGE}:${VERSION} + container_name: ${CONTAINER_NAME} + restart: always + runtime: nvidia + deploy: + resources: + reservations: + devices: + - driver: nvidia + count: all + capabilities: [gpu] + networks: + - 1panel-network + volumes: + - ${MODEL_PATH}:${MODEL_PATH} + ipc: host + ulimits: + memlock: + soft: -1 + hard: -1 + nofile: + soft: 65535 + hard: 65535 + stack: 67108864 + command: ${COMMAND} +networks: + 1panel-network: + external: true \ No newline at end of file diff --git a/agent/cmd/server/cmd/root.go b/agent/cmd/server/cmd/root.go new file mode 100644 index 0000000..38adf9a --- /dev/null +++ b/agent/cmd/server/cmd/root.go @@ -0,0 +1,14 @@ +package cmd + +import ( + "github.com/1Panel-dev/1Panel/agent/server" + "github.com/spf13/cobra" +) + +var RootCmd = &cobra.Command{ + Use: "1panel-agent", + RunE: func(cmd *cobra.Command, args []string) error { + server.Start() + return nil + }, +} diff --git a/agent/cmd/server/conf/app.yaml b/agent/cmd/server/conf/app.yaml new file mode 100644 index 0000000..74af8be --- /dev/null +++ b/agent/cmd/server/conf/app.yaml @@ -0,0 +1,17 @@ +base: + install_dir: /opt + mode: dev + is_demo: false + is_offline: false + +remote_url: + app_repo: https://apps-assets.fit2cloud.com + repo_url: https://resource.fit2cloud.com/1panel/package/v2 + resource_url: https://resource.fit2cloud.com/1panel/resource/v2 + +log: + level: debug + time_zone: Asia/Shanghai + log_name: 1Panel + log_suffix: .log + max_backup: 10 diff --git a/agent/cmd/server/conf/conf.go b/agent/cmd/server/conf/conf.go new file mode 100644 index 0000000..6654d8b --- /dev/null +++ b/agent/cmd/server/conf/conf.go @@ -0,0 +1,6 @@ +package conf + +import _ "embed" + +//go:embed app.yaml +var AppYaml []byte diff --git a/agent/cmd/server/main.go b/agent/cmd/server/main.go new file mode 100644 index 0000000..e06fc21 --- /dev/null +++ b/agent/cmd/server/main.go @@ -0,0 +1,41 @@ +package main + +import ( + "fmt" + "github.com/1Panel-dev/1Panel/agent/cmd/server/cmd" + "os" +) + +// @title 1Panel +// @version 2.0 +// @description Top-Rated Web-based Linux Server Management Tool +// @termsOfService http://swagger.io/terms/ +// @license.name GPL-3.0 +// @license.url https://www.gnu.org/licenses/gpl-3.0.html +// @BasePath /api/v2 +// @schemes http https + +// @securityDefinitions.apikey ApiKeyAuth +// @description Custom Token Format, Format: md5('1panel' + API-Key + UnixTimestamp). +// @description ``` +// @description eg: +// @description curl -X GET "http://{host}:{port}/api/v2/toolbox/device/base" \ +// @description -H "1Panel-Token: <1panel_token>" \ +// @description -H "1Panel-Timestamp: " +// @description ``` +// @description - `1Panel-Token` is the key for the panel API Key. +// @type apiKey +// @in Header +// @name 1Panel-Token +// @securityDefinitions.apikey Timestamp +// @type apiKey +// @in header +// @name 1Panel-Timestamp +// @description - `1Panel-Timestamp` is the Unix timestamp of the current time in seconds. + +func main() { + if err := cmd.RootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/agent/cmd/server/nginx_conf/404.html b/agent/cmd/server/nginx_conf/404.html new file mode 100644 index 0000000..d75ed78 --- /dev/null +++ b/agent/cmd/server/nginx_conf/404.html @@ -0,0 +1,6 @@ + +404 Not Found + +

404 Not Found

+
nginx
+ \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/cache.conf b/agent/cmd/server/nginx_conf/cache.conf new file mode 100644 index 0000000..ef1285b --- /dev/null +++ b/agent/cmd/server/nginx_conf/cache.conf @@ -0,0 +1,12 @@ +proxy_temp_path /www/common/proxy/proxy_temp_dir; +proxy_cache_path /www/common/proxy/proxy_cache_dir levels=1:2 keys_zone=proxy_cache_panel:20m inactive=1d max_size=5g; +client_body_buffer_size 512k; +proxy_connect_timeout 60; +proxy_read_timeout 60; +proxy_send_timeout 60; +proxy_buffer_size 32k; +proxy_buffers 4 64k; +proxy_busy_buffers_size 128k; +proxy_temp_file_write_size 128k; +proxy_next_upstream error timeout invalid_header http_500 http_503 http_404; +proxy_cache proxy_cache_panel; \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/domain404.html b/agent/cmd/server/nginx_conf/domain404.html new file mode 100644 index 0000000..d75ed78 --- /dev/null +++ b/agent/cmd/server/nginx_conf/domain404.html @@ -0,0 +1,6 @@ + +404 Not Found + +

404 Not Found

+
nginx
+ \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/gzip.conf b/agent/cmd/server/nginx_conf/gzip.conf new file mode 100644 index 0000000..b277f1f --- /dev/null +++ b/agent/cmd/server/nginx_conf/gzip.conf @@ -0,0 +1,4 @@ +gzip on; +gzip_comp_level 6; +gzip_min_length 1k; +gzip_types text/plain text/css text/xml text/javascript text/x-component application/json application/javascript application/x-javascript application/xml application/xhtml+xml application/rss+xml application/atom+xml application/x-font-ttf application/vnd.ms-fontobject image/svg+xml image/x-icon font/opentype; \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/index.html b/agent/cmd/server/nginx_conf/index.html new file mode 100644 index 0000000..6c9cb57 --- /dev/null +++ b/agent/cmd/server/nginx_conf/index.html @@ -0,0 +1,35 @@ + + + + + + 恭喜,站点创建成功! + + + +
+

恭喜, 站点创建成功!

+

这是默认index.html,本页面由系统自动生成

+
+ + \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/index.php b/agent/cmd/server/nginx_conf/index.php new file mode 100644 index 0000000..5cd4e8c --- /dev/null +++ b/agent/cmd/server/nginx_conf/index.php @@ -0,0 +1,25 @@ +欢迎使用 PHP!'; +echo '

版本信息

'; + +echo '
    '; +echo '
  • PHP版本:', PHP_VERSION, '
  • '; +echo '
'; + +echo '

已安装扩展

'; +printExtensions(); + +/** + * 获取已安装扩展列表 + */ +function printExtensions() +{ + echo '
    '; + foreach (get_loaded_extensions() as $i => $name) { + echo "
  1. ", $name, '=', phpversion($name), '
  2. '; + } + echo '
'; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/nginx_conf.go b/agent/cmd/server/nginx_conf/nginx_conf.go new file mode 100644 index 0000000..6b93c7b --- /dev/null +++ b/agent/cmd/server/nginx_conf/nginx_conf.go @@ -0,0 +1,59 @@ +package nginx_conf + +import ( + "embed" + _ "embed" + "io" +) + +//go:embed ssl.conf +var SSL []byte + +//go:embed index.html +var Index []byte + +//go:embed index.php +var IndexPHP []byte + +//go:embed rewrite/* +var Rewrites embed.FS + +//go:embed cache.conf +var Cache []byte + +//go:embed proxy.conf +var Proxy []byte + +//go:embed proxy_cache.conf +var ProxyCache []byte + +//go:embed 404.html +var NotFoundHTML []byte + +//go:embed domain404.html +var DomainNotFoundHTML []byte + +//go:embed stop.html +var StopHTML []byte + +//go:embed path_auth.conf +var PathAuth []byte + +//go:embed upstream.conf +var Upstream []byte + +//go:embed sse.conf +var SSE []byte + +//go:embed *.json *.conf +var websitesFiles embed.FS + +func GetWebsiteFile(filename string) []byte { + file, err := websitesFiles.Open(filename) + if err != nil { + return nil + } + defer file.Close() + res, _ := io.ReadAll(file) + return res +} diff --git a/agent/cmd/server/nginx_conf/path_auth.conf b/agent/cmd/server/nginx_conf/path_auth.conf new file mode 100644 index 0000000..38e7556 --- /dev/null +++ b/agent/cmd/server/nginx_conf/path_auth.conf @@ -0,0 +1,4 @@ +location ~* ^/test* { + auth_basic "Authorization"; + auth_basic_user_file /www/site/pass/ceshi.pass ; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/php-fpm.conf b/agent/cmd/server/nginx_conf/php-fpm.conf new file mode 100644 index 0000000..a8019f8 --- /dev/null +++ b/agent/cmd/server/nginx_conf/php-fpm.conf @@ -0,0 +1,16 @@ +[global] +error_log = /var/log/php/fpm.error.log +log_level = notice +[www] +user = www-data +group = www-data +listen = 0.0.0.0:9000 +pm = dynamic +pm.max_children = 10 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +pm.status_path = /status +slowlog = /var/log/php/fpm.slow.log +request_slowlog_timeout = 3 +catch_workers_output = yes \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/php_extensions.json b/agent/cmd/server/nginx_conf/php_extensions.json new file mode 100644 index 0000000..cf16c3d --- /dev/null +++ b/agent/cmd/server/nginx_conf/php_extensions.json @@ -0,0 +1,438 @@ +[ + { + "name": "amqp", + "check": "amqp", + "file": "amqp.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "apcu", + "check": "apcu", + "file": "apcu.so", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "bcmath", + "check": "bcmath", + "file": "bcmath.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "ionCube", + "check": "ionCube Loader", + "file": "ioncube_loader.so", + "versions": ["56", "70", "71", "72", "73", "74", "81", "82"], + "installed": false + }, + { + "name": "opcache", + "check": "Zend OPcache", + "file": "opcache.so", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "memcache", + "check": "memcache", + "file": "memcache.so", + "versions": ["56", "70", "71", "72", "73", "74", "80"], + "installed": false + }, + { + "name": "memcached", + "check": "memcached", + "file": "memcached.so", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "redis", + "check": "redis", + "file": "redis.so", + "versions": [ "56", "70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "mcrypt", + "check": "mcrypt", + "file": "mcrypt.so", + "versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + + { + "name": "imagick", + "check": "imagick", + "file": "imagick.so", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82","83","84","85"], + "installed": false + }, + { + "name": "xdebug", + "check": "xdebug", + "file": "xdebug.so", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "imap", + "check": "imap", + "file": "imap.so", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "exif", + "check": "exif", + "file": "exif.so", + "versions": [ "56", "70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "intl", + "check": "intl", + "file": "intl.so", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "xsl", + "check": "xsl", + "file": "xsl.so", + "versions": ["56", "70", "71", "72", "73", "74", "80", "81", "82"], + "installed": false + }, + { + "name": "swoole", + "check": "swoole", + "file": "swoole.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "zstd", + "check": "zstd", + "file": "zstd.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "xlswriter", + "check": "xlswriter", + "file": "xlswriter.so", + "versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "oci8", + "check": "oci8", + "file": "oci8.so", + "versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "pdo_oci", + "check": "pdo_oci", + "file": "pdo_oci.so", + "versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "pdo_sqlsrv", + "check": "pdo_sqlsrv", + "file": "pdo_sqlsrv.so", + "versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "sqlsrv", + "check": "sqlsrv", + "file": "sqlsrv.so", + "versions": ["81", "82", "83","84"], + "installed": false + }, + { + "name": "yaf", + "check": "yaf", + "file": "yaf.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "mongodb", + "check": "mongodb", + "file": "mongodb.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "yac", + "check": "yac", + "file": "yac.so", + "versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "pgsql", + "check": "pgsql", + "file": "pgsql.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "ssh2", + "check": "ssh2", + "file": "ssh2.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "grpc", + "check": "grpc", + "file": "grpc.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "xhprof", + "check": "xhprof", + "file": "xhprof.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "protobuf", + "check": "protobuf", + "file": "protobuf.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "pdo_pgsql", + "check": "pdo_pgsql", + "file": "pdo_pgsql.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "snmp", + "check": "snmp", + "file": "snmp.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "ldap", + "check": "ldap", + "file": "ldap.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "recode", + "check": "recode", + "file": "recode.so", + "versions": ["56","70", "71", "72", "73"], + "installed": false + }, + { + "name": "enchant", + "check": "enchant", + "file": "enchant.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "pspell", + "check": "pspell", + "file": "pspell.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "bz2", + "check": "bz2", + "file": "bz2.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "sysvshm", + "check": "sysvshm", + "file": "sysvshm.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "calendar", + "check": "calendar", + "file": "calendar.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "gmp", + "check": "gmp", + "file": "gmp.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "wddx", + "check": "wddx", + "file": "wddx.so", + "versions": ["56","70", "71", "72", "73", "74"], + "installed": false + }, + { + "name": "sysvmsg", + "check": "sysvmsg", + "file": "sysvmsg.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "igbinary", + "check": "igbinary", + "file": "igbinary.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "zmq", + "check": "zmq", + "file": "zmq.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "smbclient", + "check": "smbclient", + "file": "smbclient.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "event", + "check": "event", + "file": "event.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "mailparse", + "check": "mailparse", + "file": "mailparse.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "yaml", + "check": "yaml", + "file": "yaml.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "sg16", + "check": "SourceGuardian", + "file": "sourceguardian.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "mysqli", + "check": "mysqli", + "file": "mysqli.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "pdo_mysql", + "check": "pdo_mysql", + "file": "pdo_mysql.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "zip", + "check": "zip", + "file": "zip.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "shmop", + "check": "shmop", + "file": "shmop.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "gd", + "check": "gd", + "file": "gd.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + + { + "name": "pcntl", + "check": "pcntl", + "file": "pcntl.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "sodium", + "check": "sodium", + "file": "sodium.so", + "versions": ["70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "gettext", + "check": "gettext", + "file": "gettext.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "soap", + "check": "soap", + "file": "soap.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "sysvsem", + "check": "sysvsem", + "file": "sysvsem.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "sockets", + "check": "sockets", + "file": "sockets.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "xmlrpc", + "check": "xmlrpc", + "file": "xmlrpc.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + }, + { + "name": "lz4", + "check": "lz4", + "file": "lz4.so", + "versions": ["56","70", "71", "72", "73", "74", "80", "81", "82", "83","84","85"], + "installed": false + } +] diff --git a/agent/cmd/server/nginx_conf/proxy.conf b/agent/cmd/server/nginx_conf/proxy.conf new file mode 100644 index 0000000..270e928 --- /dev/null +++ b/agent/cmd/server/nginx_conf/proxy.conf @@ -0,0 +1,16 @@ +location ^~ /test { + proxy_pass http://1panel.cloud/; + 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 REMOTE-HOST $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $http_connection; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_set_header X-Forwarded-Port $server_port; + proxy_http_version 1.1; + + add_header X-Cache $upstream_cache_status; + proxy_ssl_server_name off; + proxy_ssl_name $proxy_host; +} diff --git a/agent/cmd/server/nginx_conf/proxy_cache.conf b/agent/cmd/server/nginx_conf/proxy_cache.conf new file mode 100644 index 0000000..45172d6 --- /dev/null +++ b/agent/cmd/server/nginx_conf/proxy_cache.conf @@ -0,0 +1,4 @@ +proxy_ignore_headers Set-Cookie Cache-Control expires; +proxy_cache cache_one; +proxy_cache_key $host$uri$is_args$args; +proxy_cache_valid 200 304 301 302 10m; \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/proxy_no_cache.conf b/agent/cmd/server/nginx_conf/proxy_no_cache.conf new file mode 100644 index 0000000..597f25a --- /dev/null +++ b/agent/cmd/server/nginx_conf/proxy_no_cache.conf @@ -0,0 +1,10 @@ +set $static_fileg 0; +if ( $uri ~* "\.(gif|png|jpg|css|js|woff|woff2)$" ) +{ + set $static_fileg 1; + expires 1m; +} +if ( $static_fileg = 0 ) +{ + add_header Cache-Control no-cache; +} diff --git a/agent/cmd/server/nginx_conf/rewrite/crmeb.conf b/agent/cmd/server/nginx_conf/rewrite/crmeb.conf new file mode 100644 index 0000000..4260774 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/crmeb.conf @@ -0,0 +1,6 @@ +location / { + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php?s=/$1 last; + break; + } +} diff --git a/agent/cmd/server/nginx_conf/rewrite/dabr.conf b/agent/cmd/server/nginx_conf/rewrite/dabr.conf new file mode 100644 index 0000000..37c1313 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/dabr.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/agent/cmd/server/nginx_conf/rewrite/dbshop.conf b/agent/cmd/server/nginx_conf/rewrite/dbshop.conf new file mode 100644 index 0000000..61d23f7 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/dbshop.conf @@ -0,0 +1,7 @@ +location /{ + try_files $uri $uri/ /index.php$is_args$args; +} + +location ~ \.htaccess{ + deny all; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/dedecms.conf b/agent/cmd/server/nginx_conf/rewrite/dedecms.conf new file mode 100644 index 0000000..6e110cb --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/dedecms.conf @@ -0,0 +1,10 @@ +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/agent/cmd/server/nginx_conf/rewrite/default.conf b/agent/cmd/server/nginx_conf/rewrite/default.conf new file mode 100644 index 0000000..e69de29 diff --git a/agent/cmd/server/nginx_conf/rewrite/discuz.conf b/agent/cmd/server/nginx_conf/rewrite/discuz.conf new file mode 100644 index 0000000..578da76 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/discuz.conf @@ -0,0 +1,7 @@ +location / { + rewrite ^/archiver/((fid|tid)-[\w\-]+\.html)$ /archiver/index.php?$1 last; + rewrite ^/forum-([0-9]+)-([0-9]+)\.html$ /forumdisplay.php?fid=$1&page=$2 last; + rewrite ^/thread-([0-9]+)-([0-9]+)-([0-9]+)\.html$ /viewthread.php?tid=$1&extra=page%3D$3&page=$2 last; + rewrite ^/space-(username|uid)-(.+)\.html$ /space.php?$1=$2 last; + rewrite ^/tag-(.+)\.html$ /tag.php?name=$1 last; + } \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/discuzx.conf b/agent/cmd/server/nginx_conf/rewrite/discuzx.conf new file mode 100644 index 0000000..8058495 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/discuzx.conf @@ -0,0 +1,12 @@ +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) { + return 404; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/discuzx2.conf b/agent/cmd/server/nginx_conf/rewrite/discuzx2.conf new file mode 100644 index 0000000..61059e2 --- /dev/null +++ b/agent/cmd/server/nginx_conf/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) { + return 404; + } +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/discuzx3.conf b/agent/cmd/server/nginx_conf/rewrite/discuzx3.conf new file mode 100644 index 0000000..6618897 --- /dev/null +++ b/agent/cmd/server/nginx_conf/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) { + return 404; + } +} + diff --git a/agent/cmd/server/nginx_conf/rewrite/drupal.conf b/agent/cmd/server/nginx_conf/rewrite/drupal.conf new file mode 100644 index 0000000..460b779 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/drupal.conf @@ -0,0 +1,3 @@ +if (!-e $request_filename) { + rewrite ^/(.*)$ /index.php?q=$1 last; + } \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/ecshop.conf b/agent/cmd/server/nginx_conf/rewrite/ecshop.conf new file mode 100644 index 0000000..3574daa --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/ecshop.conf @@ -0,0 +1,32 @@ +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/agent/cmd/server/nginx_conf/rewrite/edusoho.conf b/agent/cmd/server/nginx_conf/rewrite/edusoho.conf new file mode 100644 index 0000000..b0099dc --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/edusoho.conf @@ -0,0 +1,8 @@ +location / { + index app.php; + try_files $uri @rewriteapp; +} + +location @rewriteapp { + rewrite ^(.*)$ /app.php/$1 last; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/emlog.conf b/agent/cmd/server/nginx_conf/rewrite/emlog.conf new file mode 100644 index 0000000..e122a85 --- /dev/null +++ b/agent/cmd/server/nginx_conf/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/agent/cmd/server/nginx_conf/rewrite/empirecms.conf b/agent/cmd/server/nginx_conf/rewrite/empirecms.conf new file mode 100644 index 0000000..c68b92d --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/empirecms.conf @@ -0,0 +1,8 @@ +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/agent/cmd/server/nginx_conf/rewrite/laravel5.conf b/agent/cmd/server/nginx_conf/rewrite/laravel5.conf new file mode 100644 index 0000000..da4c674 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/laravel5.conf @@ -0,0 +1,3 @@ +location / { + try_files $uri $uri/ /index.php?$query_string; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/maccms.conf b/agent/cmd/server/nginx_conf/rewrite/maccms.conf new file mode 100644 index 0000000..b083251 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/maccms.conf @@ -0,0 +1,13 @@ +if (!-e $request_filename) { + rewrite ^/index.php(.*)$ /index.php?s=$1 break; + # MacCMS要求强制修改后台文件名称 所以需要手动修改下方这条重写规则 将admin修改为你修改后的文件名即可 + rewrite ^/admin.php(.*)$ /admin.php?s=$1 break; + rewrite ^/api.php(.*)$ /api.php?s=$1 break; + rewrite ^/(.*)$ /index.php?s=$1 break; + rewrite ^/vod-(.*)$ /index.php?m=vod-$1 break; + rewrite ^/art-(.*)$ /index.php?m=art-$1 break; + rewrite ^/gbook-(.*)$ /index.php?m=gbook-$1 break; + rewrite ^/label-(.*)$ /index.php?m=label-$1 break; + rewrite ^/map-(.*)$ /index.php?m=map-$1 break; + } +try_files $uri $uri/ /index.php?$query_string; \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/mvc.conf b/agent/cmd/server/nginx_conf/rewrite/mvc.conf new file mode 100644 index 0000000..bf90625 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/mvc.conf @@ -0,0 +1,6 @@ +location /{ + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php/$1 last; + break; + } +} diff --git a/agent/cmd/server/nginx_conf/rewrite/niushop.conf b/agent/cmd/server/nginx_conf/rewrite/niushop.conf new file mode 100644 index 0000000..c32c40c --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/niushop.conf @@ -0,0 +1,6 @@ +location / { + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php?s=$1 last; + break; + } +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/phpcms.conf b/agent/cmd/server/nginx_conf/rewrite/phpcms.conf new file mode 100644 index 0000000..a6e0df3 --- /dev/null +++ b/agent/cmd/server/nginx_conf/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/agent/cmd/server/nginx_conf/rewrite/phpwind.conf b/agent/cmd/server/nginx_conf/rewrite/phpwind.conf new file mode 100644 index 0000000..388af90 --- /dev/null +++ b/agent/cmd/server/nginx_conf/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/agent/cmd/server/nginx_conf/rewrite/sablog.conf b/agent/cmd/server/nginx_conf/rewrite/sablog.conf new file mode 100644 index 0000000..fa4f00c --- /dev/null +++ b/agent/cmd/server/nginx_conf/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/agent/cmd/server/nginx_conf/rewrite/seacms.conf b/agent/cmd/server/nginx_conf/rewrite/seacms.conf new file mode 100644 index 0000000..0dc6f36 --- /dev/null +++ b/agent/cmd/server/nginx_conf/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/agent/cmd/server/nginx_conf/rewrite/shopex.conf b/agent/cmd/server/nginx_conf/rewrite/shopex.conf new file mode 100644 index 0000000..f57463c --- /dev/null +++ b/agent/cmd/server/nginx_conf/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/agent/cmd/server/nginx_conf/rewrite/shopwind.conf b/agent/cmd/server/nginx_conf/rewrite/shopwind.conf new file mode 100644 index 0000000..0edf086 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/shopwind.conf @@ -0,0 +1,14 @@ +location / { + #Redirect everything that isn't a real file to index.php + try_files $uri $uri/ /index.php$is_args$args; +} +#If you want a single domain name at the front and back ends +location /admin { + try_files $uri $uri/ /admin/index.php$is_args$args; +} +location /mobile { + try_files $uri $uri/ /mobile/index.php$is_args$args; +} +location /api { + try_files $uri $uri/ /api/index.php$is_args$args; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/thinkphp.conf b/agent/cmd/server/nginx_conf/rewrite/thinkphp.conf new file mode 100644 index 0000000..216b1dc --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/thinkphp.conf @@ -0,0 +1,8 @@ +location ~* (runtime|application)/ { + return 403; +} +location / { + if (!-e $request_filename){ + rewrite ^(.*)$ /index.php?s=$1 last; break; + } +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/typecho.conf b/agent/cmd/server/nginx_conf/rewrite/typecho.conf new file mode 100644 index 0000000..dae6ba9 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/typecho.conf @@ -0,0 +1,3 @@ + if (!-e $request_filename) { + rewrite ^(.*)$ /index.php$1 last; + } diff --git a/agent/cmd/server/nginx_conf/rewrite/typecho2.conf b/agent/cmd/server/nginx_conf/rewrite/typecho2.conf new file mode 100644 index 0000000..22397d8 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/typecho2.conf @@ -0,0 +1,5 @@ +location /typecho/ { + if (!-e $request_filename) { + rewrite ^(.*)$ /typecho/index.php$1 last; + } +} diff --git a/agent/cmd/server/nginx_conf/rewrite/wordpress.conf b/agent/cmd/server/nginx_conf/rewrite/wordpress.conf new file mode 100644 index 0000000..9fe73c1 --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/wordpress.conf @@ -0,0 +1,6 @@ +location / +{ + try_files $uri $uri/ /index.php?$args; +} + +rewrite /wp-admin$ $scheme://$host$uri/ permanent; \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/wp2.conf b/agent/cmd/server/nginx_conf/rewrite/wp2.conf new file mode 100644 index 0000000..0e5fbae --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/wp2.conf @@ -0,0 +1,6 @@ +rewrite ^.*/files/(.*)$ /wp-includes/ms-files.php?file=$1 last; +if (!-e $request_filename){ + rewrite ^.+?(/wp-.*) $1 last; + rewrite ^.+?(/.*\.php)$ $1 last; + rewrite ^ /index.php last; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/yii2.conf b/agent/cmd/server/nginx_conf/rewrite/yii2.conf new file mode 100644 index 0000000..103ebdc --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/yii2.conf @@ -0,0 +1,3 @@ +location / { + try_files $uri $uri/ /index.php$is_args$args; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/rewrite/zblog.conf b/agent/cmd/server/nginx_conf/rewrite/zblog.conf new file mode 100644 index 0000000..5d2de2b --- /dev/null +++ b/agent/cmd/server/nginx_conf/rewrite/zblog.conf @@ -0,0 +1,9 @@ +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/agent/cmd/server/nginx_conf/root_ssl.conf b/agent/cmd/server/nginx_conf/root_ssl.conf new file mode 100644 index 0000000..9d9b0a2 --- /dev/null +++ b/agent/cmd/server/nginx_conf/root_ssl.conf @@ -0,0 +1,8 @@ +ssl_certificate /usr/local/openresty/nginx/conf/ssl/fullchain.pem; +ssl_certificate_key /usr/local/openresty/nginx/conf/ssl/privkey.pem; +ssl_protocols TLSv1.2 TLSv1.3; +ssl_ciphers EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:!MD5; +ssl_prefer_server_ciphers off; +ssl_session_cache shared:SSL:10m; +ssl_session_timeout 10m; +add_header Strict-Transport-Security "max-age=31536000"; \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/sse.conf b/agent/cmd/server/nginx_conf/sse.conf new file mode 100644 index 0000000..1a03ba9 --- /dev/null +++ b/agent/cmd/server/nginx_conf/sse.conf @@ -0,0 +1,7 @@ +location ^~ /github { + proxy_pass http://127.0.0.1:8001/github; + proxy_buffering off; + proxy_http_version 1.1; + proxy_set_header Connection ''; + chunked_transfer_encoding off; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/ssl.conf b/agent/cmd/server/nginx_conf/ssl.conf new file mode 100644 index 0000000..b29527b --- /dev/null +++ b/agent/cmd/server/nginx_conf/ssl.conf @@ -0,0 +1,9 @@ +ssl_certificate /www/sites/1panel.pro/ssl/fullchain.pem; +ssl_certificate_key /www/sites/1panel.pro/ssl/privkey.pem; +ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3; +ssl_ciphers ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA256:!aNULL:!eNULL:!EXPORT:!DSS:!DES:!RC4:!3DES:!MD5:!PSK:!KRB5:!SRP:!CAMELLIA:!SEED; +ssl_prefer_server_ciphers off; +ssl_session_cache shared:SSL:10m; +ssl_session_timeout 10m; +error_page 497 https://$host$request_uri; +proxy_set_header X-Forwarded-Proto https; \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/stop.html b/agent/cmd/server/nginx_conf/stop.html new file mode 100644 index 0000000..a38fa64 --- /dev/null +++ b/agent/cmd/server/nginx_conf/stop.html @@ -0,0 +1,33 @@ + + + + + 抱歉,站点已暂停 + + + + +
+

抱歉!该站点已经被管理员停止运行,请联系管理员了解详情!

+
+ + \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/stream_default.conf b/agent/cmd/server/nginx_conf/stream_default.conf new file mode 100644 index 0000000..f242951 --- /dev/null +++ b/agent/cmd/server/nginx_conf/stream_default.conf @@ -0,0 +1,5 @@ +server { + proxy_pass mysql_cluster; + access_log /www/sites/domain/log/access.log streamlog; + error_log /www/sites/domain/log/error.log; +} diff --git a/agent/cmd/server/nginx_conf/upstream.conf b/agent/cmd/server/nginx_conf/upstream.conf new file mode 100644 index 0000000..4d66caf --- /dev/null +++ b/agent/cmd/server/nginx_conf/upstream.conf @@ -0,0 +1,3 @@ +upstream backend { + server backend1.example.com; +} \ No newline at end of file diff --git a/agent/cmd/server/nginx_conf/website_default.conf b/agent/cmd/server/nginx_conf/website_default.conf new file mode 100644 index 0000000..6a88d98 --- /dev/null +++ b/agent/cmd/server/nginx_conf/website_default.conf @@ -0,0 +1,21 @@ +server { + listen 80; + server_name ko.wp-1.com; + + index index.php index.html index.htm default.php default.htm default.html; + + access_log /www/sites/domain/log/access.log main; + error_log /www/sites/domain/log/error.log; + + location ~ ^/(\.user.ini|\.htaccess|\.git|\.env|\.svn|\.project|LICENSE|README.md){ + return 404; + } + location ^~ /.well-known/acme-challenge { + allow all; + root /usr/share/nginx/html; + } + if ( $uri ~ "^/\.well-known/.*\.(php|jsp|py|js|css|lua|ts|go|zip|tar\.gz|rar|7z|sql|bak)$" ) { + return 403; + } + +} diff --git a/agent/constant/alert.go b/agent/constant/alert.go new file mode 100644 index 0000000..3fc50c0 --- /dev/null +++ b/agent/constant/alert.go @@ -0,0 +1,27 @@ +package constant + +const ( + AlertEnable = "Enable" + AlertDisable = "Disable" + AlertSuccess = "Success" + AlertPushing = "Pushing" + AlertError = "Error" + AlertSyncError = "SyncError" + AlertPushError = "PushError" + AlertPushSuccess = "PushSuccess" +) + +const ( + CommonConfig = "common" + SMSConfig = "sms" + EmailConfig = "email" +) + +const ( + WeChat = "wechat" + SMS = "sms" + Email = "mail" + WeCom = "weCom" + DingTalk = "dingTalk" + FeiShu = "feiShu" +) diff --git a/agent/constant/app.go b/agent/constant/app.go new file mode 100644 index 0000000..ee1efa6 --- /dev/null +++ b/agent/constant/app.go @@ -0,0 +1,45 @@ +package constant + +const ( + ContainerPrefix = "1Panel-" + + AppNormal = "Normal" + AppTakeDown = "TakeDown" + + AppOpenresty = "openresty" + AppMysql = "mysql" + AppMariaDB = "mariadb" + AppPostgresql = "postgresql" + AppRedis = "redis" + AppPostgres = "postgres" + AppMongodb = "mongodb" + AppMemcached = "memcached" + AppMysqlCluster = "mysql-cluster" + AppPostgresqlCluster = "postgresql-cluster" + AppRedisCluster = "redis-cluster" + + AppResourceLocal = "local" + AppResourceRemote = "remote" + AppResourceCustom = "custom" + + CPUS = "CPUS" + MemoryLimit = "MEMORY_LIMIT" + HostIP = "HOST_IP" + ContainerName = "CONTAINER_NAME" +) + +type AppOperate string + +var ( + Start AppOperate = "start" + Stop AppOperate = "stop" + Restart AppOperate = "restart" + Delete AppOperate = "delete" + Sync AppOperate = "sync" + Backup AppOperate = "backup" + Update AppOperate = "update" + Rebuild AppOperate = "rebuild" + Upgrade AppOperate = "upgrade" + Reload AppOperate = "reload" + Favorite AppOperate = "favorite" +) diff --git a/agent/constant/backup.go b/agent/constant/backup.go new file mode 100644 index 0000000..b6c6d9d --- /dev/null +++ b/agent/constant/backup.go @@ -0,0 +1,19 @@ +package constant + +const ( + Valid = "VALID" + DisConnect = "DISCONNECT" + VerifyFailed = "VERIFYFAILED" + S3 = "S3" + OSS = "OSS" + Sftp = "SFTP" + OneDrive = "OneDrive" + MinIo = "MINIO" + Cos = "COS" + Kodo = "KODO" + WebDAV = "WebDAV" + Local = "LOCAL" + UPYUN = "UPYUN" + ALIYUN = "ALIYUN" + GoogleDrive = "GoogleDrive" +) diff --git a/agent/constant/common.go b/agent/constant/common.go new file mode 100644 index 0000000..7a9c38f --- /dev/null +++ b/agent/constant/common.go @@ -0,0 +1,38 @@ +package constant + +type DBContext string + +const ( + DB DBContext = "db" + + SystemRestart = "systemRestart" + + TypeWebsite = "website" + TypePhp = "php" + TypeSSL = "ssl" + TypeSystem = "system" + TypeTask = "task" + TypeApp = "app" + TypeImagePull = "image-pull" + TypeImagePush = "image-push" + TypeImageBuild = "image-build" + TypeComposeCreate = "compose-create" + + InterruptedMsg = "the task was interrupted due to the restart of the 1panel service" +) + +const ( + TimeOut5s = 5 + TimeOut20s = 20 + TimeOut5m = 300 + + DateLayout = "2006-01-02" // or use time.DateOnly while go version >= 1.20 + DateTimeLayout = "2006-01-02 15:04:05" // or use time.DateTime while go version >= 1.20 + DateTimeSlimLayout = "20060102150405" + WebsiteDefaultExpireDate = "9999-12-31" +) + +const ( + DirPerm = 0755 + FilePerm = 0644 +) diff --git a/agent/constant/container.go b/agent/constant/container.go new file mode 100644 index 0000000..0bb7be6 --- /dev/null +++ b/agent/constant/container.go @@ -0,0 +1,18 @@ +package constant + +const ( + ContainerOpStart = "start" + ContainerOpStop = "stop" + ContainerOpRestart = "restart" + ContainerOpKill = "kill" + ContainerOpPause = "pause" + ContainerOpUnpause = "unpause" + ContainerOpRename = "rename" + ContainerOpRemove = "remove" + + ComposeOpStop = "stop" + ComposeOpRestart = "restart" + ComposeOpRemove = "remove" +) + +var DaemonJsonPath = "/etc/docker/daemon.json" diff --git a/agent/constant/host_tool.go b/agent/constant/host_tool.go new file mode 100644 index 0000000..60f483b --- /dev/null +++ b/agent/constant/host_tool.go @@ -0,0 +1,8 @@ +package constant + +const ( + Supervisord = "supervisord" + Supervisor = "supervisor" + SupervisorConfigPath = "SupervisorConfigPath" + SupervisorServiceName = "SupervisorServiceName" +) diff --git a/agent/constant/nginx.go b/agent/constant/nginx.go new file mode 100644 index 0000000..93b46b8 --- /dev/null +++ b/agent/constant/nginx.go @@ -0,0 +1,15 @@ +package constant + +const ( + NginxScopeServer = "server" + NginxScopeHttp = "http" + NginxScopeOut = "out" + + NginxReload = "reload" + NginxCheck = "check" + NginxRestart = "restart" + + ConfigNew = "add" + ConfigUpdate = "update" + ConfigDel = "delete" +) diff --git a/agent/constant/runtime.go b/agent/constant/runtime.go new file mode 100644 index 0000000..b96e235 --- /dev/null +++ b/agent/constant/runtime.go @@ -0,0 +1,54 @@ +package constant + +const ( + ResourceLocal = "local" + ResourceAppstore = "appstore" + + RuntimePHP = "php" + RuntimeNode = "node" + RuntimeJava = "java" + RuntimeGo = "go" + RuntimePython = "python" + RuntimeDotNet = "dotnet" + + RuntimeProxyUnix = "unix" + RuntimeProxyTcp = "tcp" + + RuntimeUp = "up" + RuntimeDown = "down" + RuntimeRestart = "restart" + + RuntimeInstall = "install" + RuntimeUninstall = "uninstall" + RuntimeUpdate = "update" + + RuntimeNpm = "npm" + RuntimeYarn = "yarn" +) + +var GoDefaultVolumes = map[string]string{ + "${CODE_DIR}": "/app", + "./run.sh": "/run.sh", + "./.env": "/.env", + "./mod": "/go/pkg/mod", +} + +var RuntimeDefaultVolumes = map[string]string{ + "${CODE_DIR}": "/app", + "./run.sh": "/run.sh", + "./.env": "/.env", +} + +var PHPDefaultVolumes = map[string]string{ + "${PANEL_WEBSITE_DIR}": "/www/", + "./conf/php.ini": "/usr/local/etc/php/php.ini", + "./conf/conf.d": "/usr/local/etc/php/conf.d", + "./conf/php-fpm.conf": "/usr/local/etc/php-fpm.conf", + "./log": "/var/log/php", + "./extensions": "/usr/local/lib/php/extensions", + "./supervisor/supervisord.conf": "/etc/supervisord.conf", + "./supervisor/supervisor.d/php-fpm.ini": "/etc/supervisor.d/php-fpm.ini", + "./supervisor/supervisor.d": "/etc/supervisor.d", + "./supervisor/log": "/var/log/supervisor", + "./composer": "/tmp/composer", +} diff --git a/agent/constant/status.go b/agent/constant/status.go new file mode 100644 index 0000000..3c7aca5 --- /dev/null +++ b/agent/constant/status.go @@ -0,0 +1,43 @@ +package constant + +const ( + StatusRunning = "Running" + StatusCanceled = "Canceled" + StatusDone = "Done" + StatusWaiting = "Waiting" + StatusPending = "Pending" + StatusSuccess = "Success" + StatusFailed = "Failed" + StatusUploading = "Uploading" + StatusEnable = "Enable" + StatusDisable = "Disable" + StatusNone = "None" + StatusDeleted = "Deleted" + StatusExecuting = "Executing" + StatusBuilding = "Building" + StatusReCreating = "Recreating" + StatusStopped = "Stopped" + StatusCreating = "Creating" + StatusStartErr = "StartErr" + StatusNormal = "Normal" + StatusError = "Error" + StatusStarting = "Starting" + StatusRestarting = "ReStarting" + StatusUnHealthy = "UnHealthy" + StatusInstalling = "Installing" + StatusDownloadErr = "DownloadErr" + StatusUpgrading = "Upgrading" + StatusUpgradeErr = "UpgradeErr" + StatusRebuilding = "Rebuilding" + StatusSyncing = "Syncing" + StatusSyncSuccess = "SyncSuccess" + StatusPaused = "Paused" + StatusUpErr = "UpErr" + StatusInstallErr = "InstallErr" + StatusUninstalling = "Uninstalling" + StatusWaitingRestart = "WaitingRestart" + StatusUnexecuted = "Unexecuted" + + OrderDesc = "descending" + OrderAsc = "ascending" +) diff --git a/agent/constant/task.go b/agent/constant/task.go new file mode 100644 index 0000000..67e8433 --- /dev/null +++ b/agent/constant/task.go @@ -0,0 +1,6 @@ +package constant + +const ( + TaskInstall = "installApp" + TaskCreateWebsite = "createWebsite" +) diff --git a/agent/constant/website.go b/agent/constant/website.go new file mode 100644 index 0000000..d13f187 --- /dev/null +++ b/agent/constant/website.go @@ -0,0 +1,56 @@ +package constant + +const ( + WebRunning = "Running" + WebStopped = "Stopped" + + ProtocolHTTP = "HTTP" + ProtocolHTTPS = "HTTPS" + ProtocolStream = "TCP/UDP" + + NewApp = "new" + InstalledApp = "installed" + + Deployment = "deployment" + Static = "static" + Proxy = "proxy" + Runtime = "runtime" + Subsite = "subsite" + Stream = "stream" + + SSLExisted = "existed" + SSLAuto = "auto" + SSLManual = "manual" + + DNSAccount = "dnsAccount" + DnsManual = "dnsManual" + Http = "http" + Manual = "manual" + SelfSigned = "selfSigned" + FromMaster = "fromMaster" + + StartWeb = "start" + StopWeb = "stop" + SetHttps = "setHttps" + + HTTPSOnly = "HTTPSOnly" + HTTPAlso = "HTTPAlso" + HTTPToHTTPS = "HTTPToHTTPS" + + GetLog = "get" + DisableLog = "disable" + EnableLog = "enable" + DeleteLog = "delete" + + AccessLog = "access.log" + ErrorLog = "error.log" + + ConfigPHP = "php" + ConfigFPM = "fpm" + + SSLInit = "init" + SSLError = "error" + SSLReady = "ready" + SSLApply = "applying" + SSLApplyError = "applyError" +) diff --git a/agent/cron/cron.go b/agent/cron/cron.go new file mode 100644 index 0000000..70ed24d --- /dev/null +++ b/agent/cron/cron.go @@ -0,0 +1,96 @@ +package cron + +import ( + "fmt" + mathRand "math/rand" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/service" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/cron/job" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/ntp" + "github.com/robfig/cron/v3" +) + +func Run() { + nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger))) + + var ( + interval model.Setting + status model.Setting + ) + go syncBeforeStart() + if err := global.DB.Where("key = ?", "MonitorStatus").Find(&status).Error; err != nil { + global.LOG.Errorf("load monitor status from db failed, err: %v", err) + } + if status.Value == "Enable" { + if err := global.DB.Where("key = ?", "MonitorInterval").Find(&interval).Error; err != nil { + global.LOG.Errorf("load monitor interval from db failed, err: %v", err) + } + if err := service.StartMonitor(false, interval.Value); err != nil { + global.LOG.Errorf("can not add monitor corn job: %s", err.Error()) + } + } + + if _, err := global.Cron.AddJob("@daily", job.NewWebsiteJob()); err != nil { + global.LOG.Errorf("can not add website corn job: %s", err.Error()) + } + if _, err := global.Cron.AddJob("@daily", job.NewSSLJob()); err != nil { + global.LOG.Errorf("can not add ssl corn job: %s", err.Error()) + } + if _, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", mathRand.Intn(60), mathRand.Intn(3)), job.NewAppStoreJob()); err != nil { + global.LOG.Errorf("can not add appstore corn job: %s", err.Error()) + } + if _, err := global.Cron.AddJob("0 3 */31 * *", job.NewBackupJob()); err != nil { + global.LOG.Errorf("can not add backup token refresh corn job: %s", err.Error()) + } + + var cronJobs []model.Cronjob + if err := global.DB.Where("status = ?", constant.StatusEnable).Find(&cronJobs).Error; err != nil { + global.LOG.Errorf("start my cronjob failed, err: %v", err) + } + if err := global.DB.Model(&model.JobRecords{}). + Where("status = ?", constant.StatusRunning). + Updates(map[string]interface{}{ + "status": constant.StatusFailed, + "message": "Task Cancel", + "records": "errHandle", + }).Error; err != nil { + global.LOG.Errorf("start my cronjob failed, err: %v", err) + } + for i := 0; i < len(cronJobs); i++ { + entryIDs, err := service.NewICronjobService().StartJob(&cronJobs[i], false) + if err != nil { + global.LOG.Errorf("start %s job %s failed, err: %v", cronJobs[i].Type, cronJobs[i].Name, err) + } + if err := repo.NewICronjobRepo().Update(cronJobs[i].ID, map[string]interface{}{"entry_ids": entryIDs}); err != nil { + global.LOG.Errorf("update cronjob %s %s failed, err: %v", cronJobs[i].Type, cronJobs[i].Name, err) + } + } + global.Cron.Start() +} + +func syncBeforeStart() { + var ntpSite model.Setting + if err := global.DB.Where("key = ?", "NtpSite").Find(&ntpSite).Error; err != nil { + global.LOG.Errorf("load ntp serve from db failed, err: %v", err) + } + if len(ntpSite.Value) == 0 { + ntpSite.Value = "pool.ntp.org" + } + ntime, err := ntp.GetRemoteTime(ntpSite.Value) + if err != nil { + global.LOG.Errorf("load remote time with [%s] failed, err: %v", ntpSite.Value, err) + return + } + ts := ntime.Format(constant.DateTimeLayout) + if err := ntp.UpdateSystemTime(ts); err != nil { + global.LOG.Errorf("failed to synchronize system time with [%s], err: %v", ntpSite.Value, err) + } + global.LOG.Debugf("synchronize system time with [%s] successful!", ntpSite.Value) +} diff --git a/agent/cron/job/app.go b/agent/cron/job/app.go new file mode 100644 index 0000000..11cc39b --- /dev/null +++ b/agent/cron/job/app.go @@ -0,0 +1,20 @@ +package job + +import ( + "github.com/1Panel-dev/1Panel/agent/app/service" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type app struct{} + +func NewAppStoreJob() *app { + return &app{} +} + +func (a *app) Run() { + global.LOG.Info("AppStore scheduled task in progress ...") + if err := service.NewIAppService().SyncAppListFromRemote(""); err != nil { + global.LOG.Errorf("AppStore sync failed %s", err.Error()) + } + global.LOG.Info("AppStore scheduled task has completed") +} diff --git a/agent/cron/job/backup.go b/agent/cron/job/backup.go new file mode 100644 index 0000000..caad335 --- /dev/null +++ b/agent/cron/job/backup.go @@ -0,0 +1,59 @@ +package job + +import ( + "encoding/json" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cloud_storage/client" +) + +type backup struct{} + +func NewBackupJob() *backup { + return &backup{} +} + +func (b *backup) Run() { + var backups []model.BackupAccount + _ = global.DB.Where("`type` in (?) AND is_public = 0", []string{constant.OneDrive, constant.ALIYUN}).Find(&backups) + if len(backups) == 0 { + return + } + for _, backupItem := range backups { + if backupItem.ID == 0 { + continue + } + global.LOG.Infof("Start to refresh %s-%s access_token ...", backupItem.Type, backupItem.Name) + varMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil { + global.LOG.Errorf("failed to refresh %s - %s token, please retry, err: %v", backupItem.Type, backupItem.Name, err) + continue + } + var ( + refreshToken string + err error + ) + switch backupItem.Type { + case constant.OneDrive: + refreshToken, err = client.RefreshToken("refresh_token", "refreshToken", varMap) + case constant.ALIYUN: + refreshToken, err = client.RefreshALIToken(varMap) + } + if err != nil { + varMap["refresh_status"] = constant.StatusFailed + varMap["refresh_msg"] = err.Error() + global.LOG.Errorf("failed to refresh OneDrive token, please retry, err: %v", err) + continue + } + varMap["refresh_status"] = constant.StatusSuccess + varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout) + varMap["refresh_token"] = refreshToken + + varsItem, _ := json.Marshal(varMap) + _ = global.DB.Model(&model.BackupAccount{}).Where("id = ?", backupItem.ID).Updates(map[string]interface{}{"vars": string(varsItem)}).Error + global.LOG.Infof("Refresh %s-%s access_token successful!", backupItem.Type, backupItem.Name) + } +} diff --git a/agent/cron/job/ssl.go b/agent/cron/job/ssl.go new file mode 100644 index 0000000..38ed631 --- /dev/null +++ b/agent/cron/job/ssl.go @@ -0,0 +1,61 @@ +package job + +import ( + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/service" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +type ssl struct { +} + +func NewSSLJob() *ssl { + return &ssl{} +} + +func (ssl *ssl) Run() { + sslRepo := repo.NewISSLRepo() + sslService := service.NewIWebsiteSSLService() + sslList, _ := sslRepo.List() + nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + global.LOG.Info("The scheduled certificate update task is currently in progress ...") + now := time.Now().Add(10 * time.Second) + for _, s := range sslList { + if !s.AutoRenew || s.Provider == "manual" || s.Provider == "dnsManual" || s.Status == "applying" { + continue + } + expireDate := s.ExpireDate.In(nyc) + sub := expireDate.Sub(now) + if s.IsIp && sub.Hours() < 72 || !s.IsIp && sub.Hours() < 720 { + global.LOG.Infof("Update the SSL certificate for the [%s] domain", s.PrimaryDomain) + if s.Provider == constant.SelfSigned { + caService := service.NewIWebsiteCAService() + if _, err := caService.ObtainSSL(request.WebsiteCAObtain{ + ID: s.CaID, + SSLID: s.ID, + Renew: true, + Unit: "year", + Time: 10, + }); err != nil { + global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error()) + continue + } + } else { + if err := sslService.ObtainSSL(request.WebsiteSSLApply{ + ID: s.ID, + DisableLog: true, + }); err != nil { + global.LOG.Errorf("Failed to update the SSL certificate for the [%s] domain , err:%s", s.PrimaryDomain, err.Error()) + continue + } + } + global.LOG.Infof("The SSL certificate for the [%s] domain has been successfully updated", s.PrimaryDomain) + } + } + global.LOG.Info("The scheduled certificate update task has completed") +} diff --git a/agent/cron/job/website.go b/agent/cron/job/website.go new file mode 100644 index 0000000..dffbff6 --- /dev/null +++ b/agent/cron/job/website.go @@ -0,0 +1,63 @@ +package job + +import ( + "sync" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/service" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +type website struct{} + +func NewWebsiteJob() *website { + return &website{} +} + +func (w *website) Run() { + nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + websites, _ := repo.NewIWebsiteRepo().List() + global.LOG.Info("Website scheduled task in progress ...") + now := time.Now().Add(10 * time.Minute) + if len(websites) > 0 { + neverExpireDate, _ := time.Parse(constant.DateLayout, constant.WebsiteDefaultExpireDate) + var wg sync.WaitGroup + for _, site := range websites { + if site.Status != constant.WebRunning || neverExpireDate.Equal(site.ExpireDate) { + continue + } + expireDate, err := time.ParseInLocation(constant.DateLayout, site.ExpireDate.Format(constant.DateLayout), nyc) + if err != nil { + global.LOG.Errorf("time parse err %v", err) + continue + } + if expireDate.Before(now) { + wg.Add(1) + go func(ws model.Website) { + stopWebsite(ws.ID, ws.PrimaryDomain, &wg) + }(site) + } + } + wg.Wait() + } + global.LOG.Info("Website scheduled task has completed") +} + +func stopWebsite(websiteId uint, websiteName string, wg *sync.WaitGroup) { + websiteService := service.NewIWebsiteService() + req := request.WebsiteOp{ + ID: websiteId, + Operate: constant.StopWeb, + } + if err := websiteService.OpWebsite(req); err != nil { + global.LOG.Errorf("Website [%s] seop failed err %v", websiteName, err) + } else { + global.LOG.Infof("Website [%s] stopped successfully", websiteName) + } + wg.Done() +} diff --git a/agent/global/config.go b/agent/global/config.go new file mode 100644 index 0000000..27fbe4c --- /dev/null +++ b/agent/global/config.go @@ -0,0 +1,63 @@ +package global + +type ServerConfig struct { + Base Base `mapstructure:"base"` + RemoteURL RemoteURL `mapstructure:"remote_url"` + Log LogConfig `mapstructure:"log"` + DockerConfig DockerConfig +} + +type Base struct { + Port string `mapstructure:"port"` + Version string `mapstructure:"version"` + EncryptKey string `mapstructure:"encrypt_key"` + Mode string `mapstructure:"mode"` // xpack [ Enable / Disable ] + IsDemo bool `mapstructure:"is_demo"` + InstallDir string `mapstructure:"install_dir"` + IsOffLine bool `mapstructure:"is_offline"` +} + +type RemoteURL struct { + AppRepo string `mapstructure:"app_repo"` + RepoUrl string `mapstructure:"repo_url"` + ResourceUrl string `mapstructure:"resource_url"` +} + +type SystemDir struct { + BaseDir string + DbDir string + LogDir string + TaskDir string + DataDir string + TmpDir string + LocalBackupDir string + + AppDir string + ResourceDir string + AppResourceDir string + AppInstallDir string + LocalAppResourceDir string + LocalAppInstallDir string + RemoteAppResourceDir string + CustomAppResourceDir string + OfflineAppResourceDir string + RuntimeDir string + RecycleBinDir string + SSLLogDir string + McpDir string + ConvertLogDir string + TensorRTLLMDir string + FirewallDir string +} + +type LogConfig struct { + Level string `mapstructure:"level"` + TimeZone string `mapstructure:"timeZone"` + LogName string `mapstructure:"log_name"` + LogSuffix string `mapstructure:"log_suffix"` + MaxBackup int `mapstructure:"max_backup"` +} + +type DockerConfig struct { + Command string +} diff --git a/agent/global/global.go b/agent/global/global.go new file mode 100644 index 0000000..efff796 --- /dev/null +++ b/agent/global/global.go @@ -0,0 +1,42 @@ +package global + +import ( + "context" + + badger_db "github.com/1Panel-dev/1Panel/agent/init/cache/db" + "github.com/go-playground/validator/v10" + "github.com/nicksnyder/go-i18n/v2/i18n" + "github.com/robfig/cron/v3" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "gorm.io/gorm" +) + +var ( + DB *gorm.DB + MonitorDB *gorm.DB + GPUMonitorDB *gorm.DB + TaskDB *gorm.DB + CoreDB *gorm.DB + AlertDB *gorm.DB + + LOG *logrus.Logger + CONF ServerConfig + VALID *validator.Validate + CACHE *badger_db.Cache + Viper *viper.Viper + + Dir SystemDir + + Cron *cron.Cron + MonitorCronID cron.EntryID + + IsMaster bool + + I18n *i18n.Localizer + + AlertBaseJobID cron.EntryID + AlertResourceJobID cron.EntryID + + TaskCtxMap = make(map[string]context.CancelFunc) +) diff --git a/agent/go.mod b/agent/go.mod new file mode 100644 index 0000000..fe8373f --- /dev/null +++ b/agent/go.mod @@ -0,0 +1,266 @@ +module github.com/1Panel-dev/1Panel/agent + +go 1.24.9 + +require ( + github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible + github.com/aws/aws-sdk-go v1.55.0 + github.com/compose-spec/compose-go/v2 v2.9.0 + github.com/creack/pty v1.1.24 + github.com/docker/compose/v2 v2.40.2 + github.com/docker/docker v28.5.1+incompatible + github.com/docker/go-connections v0.6.0 + github.com/fsnotify/fsnotify v1.9.0 + github.com/gin-gonic/gin v1.10.0 + github.com/glebarez/sqlite v1.11.0 + github.com/go-acme/lego/v4 v4.30.1 + github.com/go-gormigrate/gormigrate/v2 v2.1.2 + github.com/go-playground/validator/v10 v10.26.0 + github.com/go-redis/redis v6.15.9+incompatible + github.com/go-resty/resty/v2 v2.17.0 + github.com/go-sql-driver/mysql v1.8.1 + github.com/goh-chunlin/go-onedrive v1.1.1 + github.com/google/uuid v1.6.0 + github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 + github.com/jackc/pgx/v5 v5.6.0 + github.com/jinzhu/copier v0.4.0 + github.com/jinzhu/gorm v1.9.16 + github.com/joho/godotenv v1.5.1 + github.com/klauspost/compress v1.18.1 + github.com/mholt/archiver/v4 v4.0.0-alpha.8 + github.com/miekg/dns v1.1.69 + github.com/minio/minio-go/v7 v7.0.74 + github.com/nicksnyder/go-i18n/v2 v2.4.0 + github.com/opencontainers/image-spec v1.1.1 + github.com/oschwald/maxminddb-golang v1.13.1 + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/pkg/errors v0.9.1 + github.com/pkg/sftp v1.13.6 + github.com/qiniu/go-sdk/v7 v7.21.1 + github.com/robfig/cron/v3 v3.0.1 + github.com/shirou/gopsutil/v4 v4.25.11 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/afero v1.11.0 + github.com/spf13/cobra v1.10.1 + github.com/spf13/viper v1.19.0 + github.com/subosito/gotenv v1.6.0 + github.com/tencentyun/cos-go-sdk-v5 v0.7.54 + github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19 + github.com/upyun/go-sdk v2.1.0+incompatible + golang.org/x/crypto v0.46.0 + golang.org/x/net v0.48.0 + golang.org/x/oauth2 v0.34.0 + golang.org/x/sys v0.39.0 + golang.org/x/text v0.32.0 + google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 + gopkg.in/ini.v1 v1.67.0 + gopkg.in/yaml.v3 v3.0.1 + gorm.io/gorm v1.30.0 +) + +require ( + filippo.io/edwards25519 v1.1.0 // indirect + github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect + github.com/BurntSushi/toml v1.5.0 // indirect + github.com/Microsoft/go-winio v0.6.2 // indirect + github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 // indirect + github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect + github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 // indirect + github.com/alibabacloud-go/debug v1.0.1 // indirect + github.com/alibabacloud-go/tea v1.3.14 // indirect + github.com/alibabacloud-go/tea-utils/v2 v2.0.7 // indirect + github.com/aliyun/credentials-go v1.4.7 // indirect + github.com/andybalholm/brotli v1.0.4 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.0 // indirect + github.com/aws/aws-sdk-go-v2/config v1.32.5 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.5 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 // indirect + github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 // indirect + github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 // indirect + github.com/aws/smithy-go v1.24.0 // indirect + github.com/baidubce/bce-sdk-go v0.9.254 // indirect + github.com/bodgit/plumbing v1.2.0 // indirect + github.com/bodgit/sevenzip v1.3.0 // indirect + github.com/bodgit/windows v1.0.0 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cenkalti/backoff/v5 v5.0.3 // indirect + github.com/clbanning/mxj v1.8.4 // indirect + github.com/clbanning/mxj/v2 v2.7.0 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/connesc/cipherio v0.2.1 // indirect + github.com/containerd/console v1.0.5 // indirect + github.com/containerd/containerd/api v1.10.0 // indirect + github.com/containerd/containerd/v2 v2.2.0 // indirect + github.com/containerd/continuity v0.4.5 // indirect + github.com/containerd/errdefs v1.0.0 // indirect + github.com/containerd/errdefs/pkg v0.3.0 // indirect + github.com/containerd/log v0.1.0 // indirect + github.com/containerd/platforms v1.0.0-rc.2 // indirect + github.com/containerd/ttrpc v1.2.7 // indirect + github.com/containerd/typeurl/v2 v2.2.3 // indirect + github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 // indirect + github.com/distribution/reference v0.6.0 // indirect + github.com/docker/buildx v0.29.1 // indirect + github.com/docker/cli v28.5.1+incompatible // indirect + github.com/docker/docker-credential-helpers v0.9.3 // indirect + github.com/docker/go-units v0.5.0 // indirect + github.com/dsnet/compress v0.0.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/ebitengine/purego v0.9.1 // indirect + github.com/fatih/color v1.16.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fvbommel/sortorder v1.1.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.8 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/glebarez/go-sqlite v1.22.0 // indirect + github.com/go-acme/alidns-20150109/v4 v4.7.0 // indirect + github.com/go-acme/esa-20240910/v2 v2.40.3 // indirect + github.com/go-acme/tencentclouddnspod v1.1.25 // indirect + github.com/go-ini/ini v1.67.0 // indirect + github.com/go-jose/go-jose/v4 v4.1.3 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-ole/go-ole v1.2.6 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/goccy/go-json v0.10.5 // indirect + github.com/goccy/go-yaml v1.9.8 // indirect + github.com/gofrs/flock v0.13.0 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/go-querystring v1.1.0 // indirect + github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 // indirect + github.com/h2non/filetype v1.1.1 // indirect + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180 // indirect + github.com/in-toto/in-toto-golang v0.9.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/jmespath/go-jmespath v0.4.0 // indirect + github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/klauspost/pgzip v1.2.5 // indirect + github.com/kr/fs v0.1.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect + github.com/magiconair/properties v1.8.9 // indirect + github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mattn/go-runewidth v0.0.16 // indirect + github.com/mattn/go-shellwords v1.0.12 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/moby/buildkit v0.25.1 // indirect + github.com/moby/docker-image-spec v1.3.1 // indirect + github.com/moby/go-archive v0.1.0 // indirect + github.com/moby/locker v1.0.1 // indirect + github.com/moby/patternmatcher v0.6.0 // indirect + github.com/moby/sys/atomicwriter v0.1.0 // indirect + github.com/moby/sys/sequential v0.6.0 // indirect + github.com/moby/sys/signal v0.7.1 // indirect + github.com/moby/sys/user v0.4.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect + github.com/moby/term v0.5.2 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/morikuni/aec v1.0.0 // indirect + github.com/mozillazg/go-httpheader v0.2.1 // indirect + github.com/namedotcom/go/v4 v4.0.2 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/nrdcg/dnspod-go v0.4.0 // indirect + github.com/nrdcg/freemyip v0.3.0 // indirect + github.com/nrdcg/goacmedns v0.2.0 // indirect + github.com/nrdcg/namesilo v0.5.0 // indirect + github.com/nrdcg/porkbun v0.4.0 // indirect + github.com/nwaples/rardecode/v2 v2.2.0 // indirect + github.com/opencontainers/go-digest v1.0.0 // indirect + github.com/ovh/go-ovh v1.9.0 // indirect + github.com/pelletier/go-toml v1.9.5 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/pierrec/lz4/v4 v4.1.15 // indirect + github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect + github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/rivo/uniseg v0.2.0 // indirect + github.com/rs/xid v1.5.0 // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 // indirect + github.com/secure-systems-lab/go-securesystemslib v0.6.0 // indirect + github.com/shibumi/go-pathspec v1.3.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/cast v1.7.0 // indirect + github.com/spf13/pflag v1.0.10 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 // indirect + github.com/therootcompany/xz v1.0.1 // indirect + github.com/tjfoc/gmsm v1.4.1 // indirect + github.com/tklauser/go-sysconf v0.3.16 // indirect + github.com/tklauser/numcpus v0.11.0 // indirect + github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 // indirect + github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f // indirect + github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 // indirect + github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea // indirect + github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/ulikunitz/xz v0.5.15 // indirect + github.com/volcengine/volc-sdk-golang v1.0.230 // indirect + github.com/xhit/go-str2duration/v2 v2.1.0 // indirect + github.com/yusufpapurcu/wmi v1.2.4 // indirect + go.mongodb.org/mongo-driver v1.13.1 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel v1.38.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 // indirect + go.opentelemetry.io/otel/metric v1.38.0 // indirect + go.opentelemetry.io/otel/sdk v1.38.0 // indirect + go.opentelemetry.io/otel/sdk/metric v1.38.0 // indirect + go.opentelemetry.io/otel/trace v1.38.0 // indirect + go.opentelemetry.io/proto/otlp v1.5.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + go4.org v0.0.0-20200411211856-f5505b9728dd // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/mod v0.30.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/time v0.14.0 // indirect + golang.org/x/tools v0.39.0 // indirect + golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 // indirect + google.golang.org/grpc v1.77.0 // indirect + google.golang.org/protobuf v1.36.10 // indirect + modernc.org/libc v1.66.1 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.38.0 // indirect +) diff --git a/agent/go.sum b/agent/go.sum new file mode 100644 index 0000000..2310f02 --- /dev/null +++ b/agent/go.sum @@ -0,0 +1,1590 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6 h1:He8afgbRMd7mFxO99hRNu+6tazq8nFF9lIwo9JFroBk= +github.com/AdaLogics/go-fuzz-headers v0.0.0-20240806141605-e8a1dd7889d6/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEKWjV8V+WSxDXJ4NFATAsZjh8iIbsQIg= +github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= +github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/DataDog/datadog-go v3.2.0+incompatible/go.mod h1:LButxg5PwREeZtORoXG3tL4fMGNddJ+vMq1mwgfaqoQ= +github.com/HdrHistogram/hdrhistogram-go v1.1.0/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/HdrHistogram/hdrhistogram-go v1.1.2/go.mod h1:yDgFjdqOqDEKOvasDdhWNXYg9BVp4O+o5f6V/ehm6Oo= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Microsoft/go-winio v0.6.2 h1:F2VQgta7ecxGYO8k3ZZz3RS8fVIXVxONVUPlNERoyfY= +github.com/Microsoft/go-winio v0.6.2/go.mod h1:yd8OoFMLzJbo9gZq8j5qaps8bJ9aShtEA8Ipt1oGCvU= +github.com/Microsoft/hcsshim v0.14.0-rc.1 h1:qAPXKwGOkVn8LlqgBN8GS0bxZ83hOJpcjxzmlQKxKsQ= +github.com/Microsoft/hcsshim v0.14.0-rc.1/go.mod h1:hTKFGbnDtQb1wHiOWv4v0eN+7boSWAHyK/tNAaYZL0c= +github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU= +github.com/PuerkitoBio/goquery v1.5.1/go.mod h1:GsLWisAFVj4WgDibEWF4pvYnkVQBpKBKeU+7zCJoLcc= +github.com/QcloudApi/qcloud_sign_golang v0.0.0-20141224014652-e4130a326409/go.mod h1:1pk82RBxDY/JZnPQrtqHlUFfCctgdorsd9M06fMynOM= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/sarama v1.30.1/go.mod h1:hGgx05L/DiW8XYBXeJdKIN6V2QUy2H6JqME5VT1NLRw= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/Shopify/toxiproxy/v2 v2.1.6-0.20210914104332-15ea381dcdae/go.mod h1:/cvHQkZ1fst0EmZnA5dFtiQdWCNCFYzb+uE2vqVgvx0= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= +github.com/ajstarks/svgo v0.0.0-20180226025133-644b8db467af/go.mod h1:K08gAheRH3/J6wwsYMMT4xOr94bZjxIelGM0+d/wbFw= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho= +github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82 h1:7dONQ3WNZ1zy960TmkxJPuwoolZwL7xKtpcM04MBnt4= +github.com/alex-ant/gomath v0.0.0-20160516115720-89013a210a82/go.mod h1:nLnM0KdK1CmygvjpDUO6m1TjSsiQtL61juhNsvV/JVI= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6 h1:eIf+iGJxdU4U9ypaUfbtOWCsZSbTb8AUHvyPrxu6mAA= +github.com/alibabacloud-go/alibabacloud-gateway-pop v0.0.6/go.mod h1:4EUIoxs/do24zMOGGqYVWgw0s9NtiylnJglOeEB5UJo= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.4/go.mod h1:sCavSAvdzOjul4cEqeVtvlSaSScfNsTQ+46HwlTL1hc= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 h1:zE8vH9C7JiZLNJJQ5OwjU9mSi4T9ef9u3BURT6LCLC8= +github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5/go.mod h1:tWnyE9AjF8J8qqLk645oUmVUnFybApTQWklQmi5tY6g= +github.com/alibabacloud-go/darabonba-array v0.1.0 h1:vR8s7b1fWAQIjEjWnuF0JiKsCvclSRTfDzZHTYqfufY= +github.com/alibabacloud-go/darabonba-array v0.1.0/go.mod h1:BLKxr0brnggqOJPqT09DFJ8g3fsDshapUD3C3aOEFaI= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2 h1:1uJGrbsGEVqWcWxrS9MyC2NG0Ax+GpOM5gtupki31XE= +github.com/alibabacloud-go/darabonba-encode-util v0.0.2/go.mod h1:JiW9higWHYXm7F4PKuMgEUETNZasrDM6vqVr/Can7H8= +github.com/alibabacloud-go/darabonba-map v0.0.2 h1:qvPnGB4+dJbJIxOOfawxzF3hzMnIpjmafa0qOTp6udc= +github.com/alibabacloud-go/darabonba-map v0.0.2/go.mod h1:28AJaX8FOE/ym8OUFWga+MtEzBunJwQGceGQlvaPGPc= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13 h1:Q00FU3H94Ts0ZIHDmY+fYGgB7dV9D/YX6FGsgorQPgw= +github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.13/go.mod h1:lxFGfobinVsQ49ntjpgWghXmIF0/Sm4+wvBJ1h5RtaE= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7 h1:UzCnKvsjPFzApvODDNEYqBHMFt1w98wC7FOo0InLyxg= +github.com/alibabacloud-go/darabonba-signature-util v0.0.7/go.mod h1:oUzCYV2fcCH797xKdL6BDH8ADIHlzrtKVjeRtunBNTQ= +github.com/alibabacloud-go/darabonba-string v1.0.2 h1:E714wms5ibdzCqGeYJ9JCFywE5nDyvIXIIQbZVFkkqo= +github.com/alibabacloud-go/darabonba-string v1.0.2/go.mod h1:93cTfV3vuPhhEwGGpKKqhVW4jLe7tDpo3LUM0i0g6mA= +github.com/alibabacloud-go/debug v0.0.0-20190504072949-9472017b5c68/go.mod h1:6pb/Qy8c+lqua8cFpEy7g39NRRqOWc3rOwAy8m5Y2BY= +github.com/alibabacloud-go/debug v1.0.0/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/debug v1.0.1 h1:MsW9SmUtbb1Fnt3ieC6NNZi6aEwrXfDksD4QA6GSbPg= +github.com/alibabacloud-go/debug v1.0.1/go.mod h1:8gfgZCCAC3+SCzjWtY053FrOcd4/qlH6IHTI4QyICOc= +github.com/alibabacloud-go/endpoint-util v1.1.0 h1:r/4D3VSw888XGaeNpP994zDUaxdgTSHBbVfZlzf6b5Q= +github.com/alibabacloud-go/endpoint-util v1.1.0/go.mod h1:O5FuCALmCKs2Ff7JFJMudHs0I5EBgecXXxZRyswlEjE= +github.com/alibabacloud-go/openapi-util v0.1.0/go.mod h1:sQuElr4ywwFRlCCberQwKRFhRzIyG4QTP/P4y1CJ6Ws= +github.com/alibabacloud-go/openapi-util v0.1.1 h1:ujGErJjG8ncRW6XtBBMphzHTvCxn4DjrVw4m04HsS28= +github.com/alibabacloud-go/openapi-util v0.1.1/go.mod h1:/UehBSE2cf1gYT43GV4E+RxTdLRzURImCYY0aRmlXpw= +github.com/alibabacloud-go/tea v1.1.0/go.mod h1:IkGyUSX4Ba1V+k4pCtJUc6jDpZLFph9QMy2VUPTwukg= +github.com/alibabacloud-go/tea v1.1.7/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.8/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.11/go.mod h1:/tmnEaQMyb4Ky1/5D+SE1BAsa5zj/KeGOFfwYm3N/p4= +github.com/alibabacloud-go/tea v1.1.17/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.1.20/go.mod h1:nXxjm6CIFkBhwW4FQkNrolwbfon8Svy6cujmKFUq98A= +github.com/alibabacloud-go/tea v1.2.2/go.mod h1:CF3vOzEMAG+bR4WOql8gc2G9H3EkH3ZLAQdpmpXMgwk= +github.com/alibabacloud-go/tea v1.3.13/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea v1.3.14 h1:/Uzj5ZCFPpbPR+Bs7jfzsyXkYIVsi5TOIuQNOWwc/9c= +github.com/alibabacloud-go/tea v1.3.14/go.mod h1:A560v/JTQ1n5zklt2BEpurJzZTI8TUT+Psg2drWlxRg= +github.com/alibabacloud-go/tea-utils v1.3.1/go.mod h1:EI/o33aBfj3hETm4RLiAxF/ThQdSngxrpF8rKUDJjPE= +github.com/alibabacloud-go/tea-utils/v2 v2.0.5/go.mod h1:dL6vbUT35E4F4bFTHL845eUloqaerYBYPsdWR2/jhe4= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7 h1:WDx5qW3Xa5ZgJ1c8NfqJkF6w+AU5wB8835UdhPr6Ax0= +github.com/alibabacloud-go/tea-utils/v2 v2.0.7/go.mod h1:qxn986l+q33J5VkialKMqT/TTs3E+U9MJpd001iWQ9I= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible h1:8psS8a+wKfiLt1iVDX79F7Y6wUM49Lcha2FMXt4UM8g= +github.com/aliyun/aliyun-oss-go-sdk v3.0.2+incompatible/go.mod h1:T/Aws4fEfogEE9v+HPhhw+CntffsBHJ8nXQCwKr0/g8= +github.com/aliyun/credentials-go v1.1.2/go.mod h1:ozcZaMR5kLM7pwtCMEpVmQ242suV6qTJya2bDq4X1Tw= +github.com/aliyun/credentials-go v1.3.1/go.mod h1:8jKYhQuDawt8x2+fusqa1Y6mPxemTsBEN04dgcAcYz0= +github.com/aliyun/credentials-go v1.3.6/go.mod h1:1LxUuX7L5YrZUWzBrRyk0SwSdH4OmPrib8NVePL3fxM= +github.com/aliyun/credentials-go v1.4.5/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/aliyun/credentials-go v1.4.7 h1:T17dLqEtPUFvjDRRb5giVvLh6dFT8IcNFJJb7MeyCxw= +github.com/aliyun/credentials-go v1.4.7/go.mod h1:Jm6d+xIgwJVLVWT561vy67ZRP4lPTQxMbEYRuT2Ti1U= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092 h1:aM1rlcoLz8y5B2r4tTLMiVTrMtpfY0O8EScKJxaSaEc= +github.com/anchore/go-struct-converter v0.0.0-20221118182256-c68fdcfa2092/go.mod h1:rYqSE9HbjzpHTI74vwPvae4ZVYZd1lue2ta6xHPdblA= +github.com/andybalholm/brotli v1.0.4 h1:V7DdXeJtZscaqfNuAdSRuRFzuiKlHSC/Zh3zl9qY3JY= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= +github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y= +github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-metrics v0.3.9/go.mod h1:4O98XIr/9W0sxpJ8UaYkvjk10Iff7SnFrb4QAOwNTFc= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= +github.com/aws/aws-sdk-go v1.40.45/go.mod h1:585smgzpB/KqRA+K3y/NL/oYRqQvpNJYvLm+LY1U59Q= +github.com/aws/aws-sdk-go v1.55.0 h1:hVALKPjXz33kP1R9nTyJpUK7qF59dO2mleQxUW9mCVE= +github.com/aws/aws-sdk-go v1.55.0/go.mod h1:eRwEWoyTWFMVYVQzKMNHWP5/RV4xIUGMQfXQHfHkpNU= +github.com/aws/aws-sdk-go-v2 v1.9.1/go.mod h1:cK/D0BBs0b/oWPIcX/Z/obahJK1TT7IPVjy53i/mX/4= +github.com/aws/aws-sdk-go-v2 v1.41.0 h1:tNvqh1s+v0vFYdA1xq0aOJH+Y5cRyZ5upu6roPgPKd4= +github.com/aws/aws-sdk-go-v2 v1.41.0/go.mod h1:MayyLB8y+buD9hZqkCW3kX1AKq07Y5pXxtgB+rRFhz0= +github.com/aws/aws-sdk-go-v2/config v1.32.5 h1:pz3duhAfUgnxbtVhIK39PGF/AHYyrzGEyRD9Og0QrE8= +github.com/aws/aws-sdk-go-v2/config v1.32.5/go.mod h1:xmDjzSUs/d0BB7ClzYPAZMmgQdrodNjPPhd6bGASwoE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5 h1:xMo63RlqP3ZZydpJDMBsH9uJ10hgHYfQFIk1cHDXrR4= +github.com/aws/aws-sdk-go-v2/credentials v1.19.5/go.mod h1:hhbH6oRcou+LpXfA/0vPElh/e0M3aFeOblE1sssAAEk= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16 h1:80+uETIWS1BqjnN9uJ0dBUaETh+P1XwFy5vwHwK5r9k= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.16/go.mod h1:wOOsYuxYuB/7FlnVtzeBYRcjSRtQpAW0hCP7tIULMwo= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16 h1:rgGwPzb82iBYSvHMHXc8h9mRoOUBZIGFgKb9qniaZZc= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.16/go.mod h1:L/UxsGeKpGoIj6DxfhOWHWQ/kGKcd4I1VncE4++IyKA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16 h1:1jtGzuV7c82xnqOVfx2F0xmJcOw5374L7N6juGW6x6U= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.16/go.mod h1:M2E5OQf+XLe+SZGmmpaI2yy+J326aFf6/+54PoxSANc= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4 h1:WKuaxf++XKWlHWu9ECbMlha8WOEGm0OUEZqm4K/Gcfk= +github.com/aws/aws-sdk-go-v2/internal/ini v1.8.4/go.mod h1:ZWy7j6v1vWGmPReu0iSGvRiise4YI5SkR3OHKTZ6Wuc= +github.com/aws/aws-sdk-go-v2/service/cloudwatch v1.8.1/go.mod h1:CM+19rL1+4dFWnOQKwDc7H1KwXTz+h61oUSHyhV0b3o= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4 h1:0ryTNEdJbzUCEWkVXEXoqlXV72J5keC1GvILMOuD00E= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.4/go.mod h1:HQ4qwNZh32C3CBeO6iJLQlgtMzqeG17ziAA/3KDJFow= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16 h1:oHjJHeUy0ImIV0bsrX0X91GkV5nJAyv1l1CC9lnO0TI= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.16/go.mod h1:iRSNGgOYmiYwSCXxXaKb9HfOEj40+oTKn8pTxMlYkRM= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0 h1:80pDB3Tpmb2RCSZORrK9/3iQxsd+w6vSzVqpT1FGiwE= +github.com/aws/aws-sdk-go-v2/service/route53 v1.62.0/go.mod h1:6EZUGGNLPLh5Unt30uEoA+KQcByERfXIkax9qrc80nA= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4 h1:HpI7aMmJ+mm1wkSHIA2t5EaFFv5EFYXePW30p1EIrbQ= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.4/go.mod h1:C5RdGMYGlfM0gYq/tifqgn4EbyX99V15P2V3R+VHbQU= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7 h1:eYnlt6QxnFINKzwxP5/Ucs1vkG7VT3Iezmvfgc2waUw= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.7/go.mod h1:+fWt2UHSb4kS7Pu8y+BMBvJF0EWx+4H0hzNwtDNRTrg= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12 h1:AHDr0DaHIAo8c9t1emrzAlVDFp+iMMKnPdYy6XO4MCE= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.12/go.mod h1:GQ73XawFFiWxyWXMHWfhiomvP3tXtdNar/fi8z18sx0= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5 h1:SciGFVNZ4mHdm7gpD1dgZYnCuVdX1s+lFTg4+4DOy70= +github.com/aws/aws-sdk-go-v2/service/sts v1.41.5/go.mod h1:iW40X4QBmUxdP+fZNOpfmkdMZqsovezbAeO+Ubiv2pk= +github.com/aws/smithy-go v1.8.0/go.mod h1:SObp3lf9smib00L/v3U2eAKG8FyQ7iLrJnQiAmR5n+E= +github.com/aws/smithy-go v1.24.0 h1:LpilSUItNPFr1eY85RYgTIg5eIEPtvFbskaFcmmIUnk= +github.com/aws/smithy-go v1.24.0/go.mod h1:LEj2LM3rBRQJxPZTB4KuzZkaZYnZPnvgIhb4pu07mx0= +github.com/baidubce/bce-sdk-go v0.9.254 h1:A7GtBOt7z2lnV7fqlZPZefhcBFg7z6iliUAhEOiIhoE= +github.com/baidubce/bce-sdk-go v0.9.254/go.mod h1:zbYJMQwE4IZuyrJiFO8tO8NbtYiKTFTbwh4eIsqjVdg= +github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/bodgit/plumbing v1.2.0 h1:gg4haxoKphLjml+tgnecR4yLBV5zo4HAZGCtAh3xCzM= +github.com/bodgit/plumbing v1.2.0/go.mod h1:b9TeRi7Hvc6Y05rjm8VML3+47n4XTZPtQ/5ghqic2n8= +github.com/bodgit/sevenzip v1.3.0 h1:1ljgELgtHqvgIp8W8kgeEGHIWP4ch3xGI8uOBZgLVKY= +github.com/bodgit/sevenzip v1.3.0/go.mod h1:omwNcgZTEooWM8gA/IJ2Nk/+ZQ94+GsytRzOJJ8FBlM= +github.com/bodgit/windows v1.0.0 h1:rLQ/XjsleZvx4fR1tB/UxQrK+SJ2OFHzfPjLWWOhDIA= +github.com/bodgit/windows v1.0.0/go.mod h1:a6JLwrB4KrTR5hBpp8FI9/9W9jJfeQ2h4XDXU74ZCdM= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/casbin/casbin/v2 v2.37.0/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/cenkalti/backoff/v4 v4.1.1/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.1.2/go.mod h1:scbssz8iZGpm3xbr14ovlUdkxfGXNInqkPWOWmG2CLw= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= +github.com/cenkalti/backoff/v5 v5.0.3/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash v1.1.0 h1:a6HrQnmkObjyL+Gs60czilIUGqrzKutQD6XZog3p+ko= +github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible/go.mod h1:nmEj6Dob7S7YxXgwXpfOuvO54S+tGdZdw9fuRZt25Ag= +github.com/circonus-labs/circonusllhist v0.1.3/go.mod h1:kMXHVDlOchFAehlya5ePtbp5jckzBHf4XRpQvBOLI+I= +github.com/clbanning/mxj v1.8.4 h1:HuhwZtbyvyOw+3Z1AowPkU87JkJUSv751ELWaiTpj8I= +github.com/clbanning/mxj v1.8.4/go.mod h1:BVjHeAH+rl9rs6f+QIpeRl0tfu10SXn1pUSa5PVGJng= +github.com/clbanning/mxj/v2 v2.7.0 h1:WA/La7UGCanFe5NpHF0Q3DNtnCsVoxbPKuyBNHWRyME= +github.com/clbanning/mxj/v2 v2.7.0/go.mod h1:hNiWqW14h+kc+MdF9C6/YoRfjEJoR3ou6tn/Qo+ve2s= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb h1:EDmT6Q9Zs+SbUoc7Ik9EfrFqcylYqgPZ9ANSbTAntnE= +github.com/codahale/rfc6979 v0.0.0-20141003034818-6a90f24967eb/go.mod h1:ZjrT6AXHbDs86ZSdt/osfBi5qfexBrKUdONk989Wnk4= +github.com/compose-spec/compose-go/v2 v2.9.0 h1:UHSv/QHlo6QJtrT4igF1rdORgIUhDo1gWuyJUoiNNIM= +github.com/compose-spec/compose-go/v2 v2.9.0/go.mod h1:Oky9AZGTRB4E+0VbTPZTUu4Kp+oEMMuwZXZtPPVT1iE= +github.com/connesc/cipherio v0.2.1 h1:FGtpTPMbKNNWByNrr9aEBtaJtXjqOzkIXNYJp6OEycw= +github.com/connesc/cipherio v0.2.1/go.mod h1:ukY0MWJDFnJEbXMQtOcn2VmTpRfzcTz4OoVrWGGJZcA= +github.com/containerd/cgroups/v3 v3.1.0 h1:azxYVj+91ZgSnIBp2eI3k9y2iYQSR/ZQIgh9vKO+HSY= +github.com/containerd/cgroups/v3 v3.1.0/go.mod h1:SA5DLYnXO8pTGYiAHXz94qvLQTKfVM5GEVisn4jpins= +github.com/containerd/console v1.0.5 h1:R0ymNeydRqH2DmakFNdmjR2k0t7UPuiOV/N/27/qqsc= +github.com/containerd/console v1.0.5/go.mod h1:YynlIjWYF8myEu6sdkwKIvGQq+cOckRm6So2avqoYAk= +github.com/containerd/containerd/api v1.10.0 h1:5n0oHYVBwN4VhoX9fFykCV9dF1/BvAXeg2F8W6UYq1o= +github.com/containerd/containerd/api v1.10.0/go.mod h1:NBm1OAk8ZL+LG8R0ceObGxT5hbUYj7CzTmR3xh0DlMM= +github.com/containerd/containerd/v2 v2.2.0 h1:K7TqcXy+LnFmZaui2DgHsnp2gAHhVNWYaHlx7HXfys8= +github.com/containerd/containerd/v2 v2.2.0/go.mod h1:YCMjKjA4ZA7egdHNi3/93bJR1+2oniYlnS+c0N62HdE= +github.com/containerd/continuity v0.4.5 h1:ZRoN1sXq9u7V6QoHMcVWGhOwDFqZ4B9i5H6un1Wh0x4= +github.com/containerd/continuity v0.4.5/go.mod h1:/lNJvtJKUQStBzpVQ1+rasXO1LAWtUQssk28EZvJ3nE= +github.com/containerd/errdefs v1.0.0 h1:tg5yIfIlQIrxYtu9ajqY42W3lpS19XqdxRQeEwYG8PI= +github.com/containerd/errdefs v1.0.0/go.mod h1:+YBYIdtsnF4Iw6nWZhJcqGSg/dwvV7tyJ/kCkyJ2k+M= +github.com/containerd/errdefs/pkg v0.3.0 h1:9IKJ06FvyNlexW690DXuQNx2KA2cUJXx151Xdx3ZPPE= +github.com/containerd/errdefs/pkg v0.3.0/go.mod h1:NJw6s9HwNuRhnjJhM7pylWwMyAkmCQvQ4GpJHEqRLVk= +github.com/containerd/fifo v1.1.0 h1:4I2mbh5stb1u6ycIABlBw9zgtlK8viPI9QkQNRQEEmY= +github.com/containerd/fifo v1.1.0/go.mod h1:bmC4NWMbXlt2EZ0Hc7Fx7QzTFxgPID13eH0Qu+MAb2o= +github.com/containerd/log v0.1.0 h1:TCJt7ioM2cr/tfR8GPbGf9/VRAX8D2B4PjzCpfX540I= +github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3EhrzVo= +github.com/containerd/nydus-snapshotter v0.15.2 h1:qsHI4M+Wwrf6Jr4eBqhNx8qh+YU0dSiJ+WPmcLFWNcg= +github.com/containerd/nydus-snapshotter v0.15.2/go.mod h1:FfwH2KBkNYoisK/e+KsmNr7xTU53DmnavQHMFOcXwfM= +github.com/containerd/platforms v1.0.0-rc.2 h1:0SPgaNZPVWGEi4grZdV8VRYQn78y+nm6acgLGv/QzE4= +github.com/containerd/platforms v1.0.0-rc.2/go.mod h1:J71L7B+aiM5SdIEqmd9wp6THLVRzJGXfNuWCZCllLA4= +github.com/containerd/plugin v1.0.0 h1:c8Kf1TNl6+e2TtMHZt+39yAPDbouRH9WAToRjex483Y= +github.com/containerd/plugin v1.0.0/go.mod h1:hQfJe5nmWfImiqT1q8Si3jLv3ynMUIBB47bQ+KexvO8= +github.com/containerd/stargz-snapshotter v0.16.3 h1:zbQMm8dRuPHEOD4OqAYGajJJUwCeUzt4j7w9Iaw58u4= +github.com/containerd/stargz-snapshotter/estargz v0.16.3 h1:7evrXtoh1mSbGj/pfRccTampEyKpjpOnS3CyiV1Ebr8= +github.com/containerd/stargz-snapshotter/estargz v0.16.3/go.mod h1:uyr4BfYfOj3G9WBVE8cOlQmXAbPN9VEQpBBeJIuOipU= +github.com/containerd/ttrpc v1.2.7 h1:qIrroQvuOL9HQ1X6KHe2ohc7p+HP/0VE6XPU7elJRqQ= +github.com/containerd/ttrpc v1.2.7/go.mod h1:YCXHsb32f+Sq5/72xHubdiJRQY9inL4a4ZQrAbN1q9o= +github.com/containerd/typeurl/v2 v2.2.3 h1:yNA/94zxWdvYACdYO8zofhrTVuQY73fFU1y++dYSw40= +github.com/containerd/typeurl/v2 v2.2.3/go.mod h1:95ljDnPfD3bAbDJRugOiShd/DlAAsxGtUBhJxIn7SCk= +github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= +github.com/dave/jennifer v1.6.1/go.mod h1:nXbxhEmQfOZhWml3D1cDK5M1FLnMSozpbFN/m3RmGZc= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73 h1:OGNva6WhsKst5OZf7eZOklDztV3hwtTHovdrLHV+MsA= +github.com/denisenkom/go-mssqldb v0.0.0-20191128021309-1d7a30a10f73/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= +github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= +github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= +github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/docker/buildx v0.29.1 h1:58hxM5Z4mnNje3G5NKfULT9xCr8ooM8XFtlfUK9bKaA= +github.com/docker/buildx v0.29.1/go.mod h1:J4EFv6oxlPiV1MjO0VyJx2u5tLM7ImDEl9zyB8d4wPI= +github.com/docker/cli v28.5.1+incompatible h1:ESutzBALAD6qyCLqbQSEf1a/U8Ybms5agw59yGVc+yY= +github.com/docker/cli v28.5.1+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= +github.com/docker/compose/v2 v2.40.2 h1:h2bDBJkOuqmj93XvT2oI0ArPQonE0lGtWiILXdiXvbA= +github.com/docker/compose/v2 v2.40.2/go.mod h1:CbSJpKGw20LInVsPjglZ8z7Squ3OBQOD7Ux5nkjGfIU= +github.com/docker/distribution v2.8.3+incompatible h1:AtKxIZ36LoNK51+Z6RpzLpddBirtxJnzDrHLEKxTAYk= +github.com/docker/distribution v2.8.3+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= +github.com/docker/docker v28.5.1+incompatible h1:Bm8DchhSD2J6PsFzxC35TZo4TLGR2PdW/E69rU45NhM= +github.com/docker/docker v28.5.1+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= +github.com/docker/docker-credential-helpers v0.9.3 h1:gAm/VtF9wgqJMoxzT3Gj5p4AqIjCBS4wrsOh9yRqcz8= +github.com/docker/docker-credential-helpers v0.9.3/go.mod h1:x+4Gbw9aGmChi3qTLZj8Dfn0TD20M/fuWy0E5+WDeCo= +github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c h1:lzqkGL9b3znc+ZUgi7FlLnqjQhcXxkNM/quxIjBVMD0= +github.com/docker/go v1.5.1-1.0.20160303222718-d30aec9fd63c/go.mod h1:CADgU4DSXK5QUlFslkQu2yW2TKzFZcXq/leZfM0UH5Q= +github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= +github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= +github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= +github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= +github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= +github.com/docker/go-units v0.5.0/go.mod h1:fgPhTUdO+D/Jk86RDLlptpiXQzgHJF7gydDDbaIK4Dk= +github.com/dsnet/compress v0.0.1 h1:PlZu0n3Tuv04TzpfPbrnI0HW/YwodEXDS+oPKahKF0Q= +github.com/dsnet/compress v0.0.1/go.mod h1:Aw8dCMJ7RioblQeTqt88akK31OvO8Dhf5JflhBbQEHo= +github.com/dsnet/golib v0.0.0-20171103203638-1ea166775780/go.mod h1:Lj+Z9rebOhdfkVLjJ8T6VcRQv3SXugXy999NBtR9aFY= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-resiliency v1.2.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/ebitengine/purego v0.9.1 h1:a/k2f2HQU3Pi399RPW1MOaZyhKJL9w/xFpKAg4q1s0A= +github.com/ebitengine/purego v0.9.1/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5 h1:Yzb9+7DPaBjB8zlTR87/ElzFsnQfuHnVUVqpZZIcV5Y= +github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= +github.com/fatih/color v1.10.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.12.0/go.mod h1:ELkj/draVOlAH/xkhN6mQ50Qd0MPOk5AAr3maGEBuJM= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fogleman/gg v1.2.1-0.20190220221249-0403632d5b90/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k= +github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHquHwclZch5g= +github.com/franela/goblin v0.0.0-20210519012713-85d372ac71e2/go.mod h1:VzmDKDJVZI3aJmnRI9VjAn9nJ8qPPsN1fqzr9dqInIo= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fvbommel/sortorder v1.1.0 h1:fUmoe+HLsBTctBDoaBwpQo5N+nrCp8g/BjKb/6ZQmYw= +github.com/fvbommel/sortorder v1.1.0/go.mod h1:uk88iVf1ovNn1iLfgUVU2F9o5eO30ui720w+kxuqRs0= +github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3GqacKw1NM= +github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-acme/alidns-20150109/v4 v4.7.0 h1:PqJ/wR0JTpL4v0Owu1uM7bPQ1Yww0eQLAuuSdLjjQaQ= +github.com/go-acme/alidns-20150109/v4 v4.7.0/go.mod h1:btQvB6xZoN6ykKB74cPhiR+uvhrEE2AFVXm6RDmCHm0= +github.com/go-acme/esa-20240910/v2 v2.40.3 h1:xXOMRex148wKEHbv7Izn73/HdAxSmz5GOaF4HdnqN+M= +github.com/go-acme/esa-20240910/v2 v2.40.3/go.mod h1:ZYdN9EN9ikn26SNapxCVjZ65pHT/1qm4fzuJ7QGVX6g= +github.com/go-acme/lego/v4 v4.30.1 h1:tmb6U0lvy8Mc3lQbqKwTat7oAhE8FUYNJ3D0gSg6pJU= +github.com/go-acme/lego/v4 v4.30.1/go.mod h1:V7m/Ip+EeFkjOe028+zeH+SwWtESxw1LHelwMIfAjm4= +github.com/go-acme/tencentclouddnspod v1.1.25 h1:7H3ZKshkaHzCXfRpAHVB5nvxeDDl2XLeNZfrNHiZj/s= +github.com/go-acme/tencentclouddnspod v1.1.25/go.mod h1:XXfzp0AYV7UAUsHKT6R0KAUJFhqAUXmWGF07Elpa5cE= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY= +github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo= +github.com/go-ini/ini v1.67.0 h1:z6ZrTEZqSWOTyH2FlglNbNgARyHG8oLW9gMELqKr06A= +github.com/go-ini/ini v1.67.0/go.mod h1:ByCAeIL28uOIIG0E3PJtZPDL8WnHpFKFOtgjp+3Ies8= +github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= +github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= +github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.12.0/go.mod h1:lHd+EkCZPIwYItmGDDRdhinkzX2A1sj+M9biaEaizzs= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-kit/log v0.2.0/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logfmt/logfmt v0.5.1/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.4.1/go.mod h1:nlOn6nFhuKACm19sB/8EGNn9GlaMV7XkbRSipzJ0Ii4= +github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk= +github.com/go-playground/validator/v10 v10.26.0 h1:SP05Nqhjcvz81uJaRfEV0YBSSSGMc/iMaVtFbr3Sw2k= +github.com/go-playground/validator/v10 v10.26.0/go.mod h1:I5QpIEbmr8On7W0TktmJAumgzX4CA1XNl4ZmDuVHKKo= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-resty/resty/v2 v2.17.0 h1:pW9DeXcaL4Rrym4EZ8v7L19zZiIlWPg5YXAcVmt+gN0= +github.com/go-resty/resty/v2 v2.17.0/go.mod h1:kCKZ3wWmwJaNc7S29BRtUhJwy7iqmn+2mLtQrOyQlVA= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-zookeeper/zk v1.0.2/go.mod h1:nOB03cncLtlp4t+UAkGSV+9beXP/akpekBwL+UX1Qcw= +github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= +github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/goccy/go-yaml v1.9.8 h1:5gMyLUeU1/6zl+WFfR1hN7D2kf+1/eRGa7DFtToiBvQ= +github.com/goccy/go-yaml v1.9.8/go.mod h1:JubOolP3gh0HpiBc4BLRD4YmjEjHAmIIB2aaXKkTfoE= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14jxHU= +github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= +github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/goh-chunlin/go-onedrive v1.1.1 h1:HGtHk5iG0MZ92zYUtaY04czfZPBIJUr12UuFc+PW8m4= +github.com/goh-chunlin/go-onedrive v1.1.1/go.mod h1:N8qIGHD7tryO734epiBKk5oXcpGwxKET/u3LuBHciTs= +github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe h1:lXe2qZdvpiX5WZkZR4hgp4KJVfY3nMkvmwbVkpv1rVY= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8 h1:f+oWsMOmNPc8JmEHVZIycC7hBoQxHH9pNKQORJNozsQ= +github.com/golang/groupcache v0.0.0-20241129210726-2c02b8208cf8/go.mod h1:wcDNUvekVysuuOpQKo3191zZyTpiI6se1N1ULghS0sw= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4 h1:l75CXGRSwbaYNpl/Z2X1XIIAMSCquvXgpVZDhwEIJsc= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck= +github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= +github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= +github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gopherjs/gopherjs v0.0.0-20200217142428-fce0ec30dd00/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So= +github.com/gorilla/mux v1.8.1 h1:TuBL49tXwgrFYWhqrNgrUNEY92u81SPhu7sTdzQEiWY= +github.com/gorilla/mux v1.8.1/go.mod h1:AKf9I4AEqPTmMytcMc0KkNouC66V3BtZ4qD5fmWSiMQ= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/grafov/m3u8 v0.12.0/go.mod h1:nqzOkfBiZJENr52zTVd/Dcl03yzphIMbJqkXGu+u080= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFbcEtjT1g+wF4CSlocrBnw= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1 h1:e9Rjr40Z98/clHv5Yg79Is0NtosR5LXRvdr7o/6NwbA= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.1/go.mod h1:tIxuGz/9mpox++sgp9fJjHO0+q1X9/UOWd798aAm22M= +github.com/h2non/filetype v1.1.1 h1:xvOwnXKAckvtLWsN398qS9QhlxlnVXBjXBydK2/UFB4= +github.com/h2non/filetype v1.1.1/go.mod h1:319b3zT68BvV+WRj7cwy856M2ehB3HqNOt6sy1HndBY= +github.com/hashicorp/consul/api v1.10.1/go.mod h1:XjsvQN+RJGWI2TWy1/kqaE16HrR2J/FWgkYjdZQsX9M= +github.com/hashicorp/consul/sdk v0.8.0/go.mod h1:GBvyrGALthsZObzUGsfgHZQDXjg4lOjagTIwIR1vPms= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.0/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-cleanhttp v0.5.2/go.mod h1:kO/YDlP8L1346E6Sodw+PrpBSV4/SoxCXGY6BqNFT48= +github.com/hashicorp/go-hclog v0.12.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-hclog v0.16.2/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/hashicorp/go-retryablehttp v0.5.3/go.mod h1:9B5zBasrRhHXnJnui7y6sL7es7NDiJgTc6Er0maI1Xs= +github.com/hashicorp/go-rootcerts v1.0.2/go.mod h1:pqUvnprVnM5bf7AOirdbb01K4ccR319Vf4pU3K5EGc8= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.2/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.1/go.mod h1:4gW7WsVCke5TE7EPeYliwHlRUyBtfCwuFwuMg2DmyNY= +github.com/hashicorp/memberlist v0.2.2/go.mod h1:MS2lj3INKhZjWNqd3N0m3J+Jxf3DAOnAH9VT3Sh9MUE= +github.com/hashicorp/serf v0.9.5/go.mod h1:UWDWwZeL5cuWDJdl0C6wrvrUwEqtQ4ZKBKKENpqIUyk= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180 h1:uia+R3K1izQRGpxTV+bS4q3/ueMW7ProAMWqM6OlqOU= +github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.180/go.mod h1:M+yna96Fx9o5GbIUnF3OvVvQGjgfVSyeJbV9Yb1z/wI= +github.com/hudl/fargo v1.4.0/go.mod h1:9Ai6uvFy5fQNq6VPKtg+Ceq1+eTY4nKUlR2JElEOcDo= +github.com/iancoleman/strcase v0.3.0/go.mod h1:iwCmte+B7n89clKwxIoIXy/HfoL7AsD47ZCWhYzw7ho= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/in-toto/in-toto-golang v0.9.0 h1:tHny7ac4KgtsfrG6ybU8gVOZux2H8jN05AXJ9EBM1XU= +github.com/in-toto/in-toto-golang v0.9.0/go.mod h1:xsBVrVsHNsB61++S6Dy2vWosKhuA3lUTQd+eF9HdeMo= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/influxdata/influxdb1-client v0.0.0-20200827194710-b269163b24ab/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= +github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= +github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= +github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= +github.com/jcmturner/gofork v1.0.0/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= +github.com/jcmturner/goidentity/v6 v6.0.1/go.mod h1:X1YW3bgtvwAXju7V3LCIMpY0Gbxyjn/mY9zx4tFonSg= +github.com/jcmturner/gokrb5/v8 v8.4.2/go.mod h1:sb+Xq/fTY5yktf/VxLsE3wlfPqQjp0aWNYyvBVK62bc= +github.com/jcmturner/rpc/v2 v2.0.3/go.mod h1:VUJYCIDm3PVOEHw8sgt091/20OJjskO/YJki3ELg/Hc= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jinzhu/gorm v1.9.16 h1:+IyIjPEABKRpsu/F8OvDPy9fyQlgsg2luMV2ZIH5i5o= +github.com/jinzhu/gorm v1.9.16/go.mod h1:G3LB3wezTOWM2ITLzPxEXgSkOXAntiLHS7UdBefADcs= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.0.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= +github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= +github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8= +github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= +github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 h1:9Nu54bhS/H/Kgo2/7xNSUuC5G28VR8ljfrLKU2G4IjU= +github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12/go.mod h1:TBzl5BIHNXfS9+C35ZyJaklL7mLDbgUkcgXzSLa8Tk0= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= +github.com/julienschmidt/httprouter v1.3.0/go.mod h1:JR6WtHb+2LUe8TCKY3cZOxFyyO8IZAc4RVcycCCAKdM= +github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.4.1/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= +github.com/klauspost/compress v1.13.4/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= +github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= +github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= +github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/klauspost/pgzip v1.2.5 h1:qnWYvvKqedOF2ulHpMG72XQol4ILEJ8k2wwRl/Km8oE= +github.com/klauspost/pgzip v1.2.5/go.mod h1:Ch1tH69qFZu15pkjo5kYi6mth2Zzwzt50oCQKQE9RUs= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.1.1 h1:sJZmqHoEaY7f+NPP8pgLB/WxulyR3fewgCM2qaSlBb4= +github.com/lib/pq v1.1.1/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4= +github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I= +github.com/magiconair/properties v1.8.9 h1:nWcCbLq1N2v/cpNsy5WvQ37Fb+YElfq20WJ/a8RkpQM= +github.com/magiconair/properties v1.8.9/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f h1:B0OD7nYl2FPQEVrw8g2uyc1lGEzNbvrKh7fspGZcbvY= +github.com/matishsiao/goInfo v0.0.0-20210923090445-da2e3fa8d45f/go.mod h1:aEt7p9Rvh67BYApmZwNDPpgircTO2kgdmDUoF/1QmwA= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.8/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc= +github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-shellwords v1.0.12 h1:M2zGm7EW6UQJvDeQxo4T51eKPurbeFbe8WtebGE2xrk= +github.com/mattn/go-shellwords v1.0.12/go.mod h1:EZzvwXDESEeg03EKmM+RmDnNOPKG4lLtQsUlTZDWQ8Y= +github.com/mattn/go-sqlite3 v1.14.0 h1:mLyGNKR8+Vv9CAU7PphKa2hkEqxxhn8i32J6FPj1/QA= +github.com/mattn/go-sqlite3 v1.14.0/go.mod h1:JIl7NbARA7phWnGvh0LKTyg7S9BA+6gx71ShQilpsus= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= +github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= +github.com/mholt/archiver/v4 v4.0.0-alpha.8 h1:tRGQuDVPh66WCOelqe6LIGh0gwmfwxUrSSDunscGsRM= +github.com/mholt/archiver/v4 v4.0.0-alpha.8/go.mod h1:5f7FUYGXdJWUjESffJaYR4R60VhnHxb2X3T1teMyv5A= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/miekg/dns v1.1.26/go.mod h1:bPDLeHnStXmXAq1m/Ch/hvfNHr14JKNPMBo3VZKjuso= +github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4= +github.com/miekg/dns v1.1.69 h1:Kb7Y/1Jo+SG+a2GtfoFUfDkG//csdRPwRLkCsxDG9Sc= +github.com/miekg/dns v1.1.69/go.mod h1:7OyjD9nEba5OkqQ/hB4fy3PIoxafSZJtducccIelz3g= +github.com/miekg/pkcs11 v1.1.1 h1:Ugu9pdy6vAYku5DEpVWVFPYnzV+bxB+iRdbuFSu7TvU= +github.com/miekg/pkcs11 v1.1.1/go.mod h1:XsNlhZGX73bx86s2hdc/FuaLm2CPZJemRLMA+WTFxgs= +github.com/minio/highwayhash v1.0.1/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/highwayhash v1.0.2/go.mod h1:BQskDq+xkJ12lmlUUi7U0M5Swg3EWR+dLTk+kldvVxY= +github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= +github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= +github.com/minio/minio-go/v7 v7.0.74 h1:fTo/XlPBTSpo3BAMshlwKL5RspXRv9us5UeHEGYCFe0= +github.com/minio/minio-go/v7 v7.0.74/go.mod h1:qydcVzV8Hqtj1VtEocfxbmVFa2siu6HGa+LDEPogjD8= +github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI= +github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= +github.com/mitchellh/hashstructure/v2 v2.0.2 h1:vGKWl0YJqUNxE8d+h8f6NJLcCJrgbhC4NcD46KavDd4= +github.com/mitchellh/hashstructure/v2 v2.0.2/go.mod h1:MG3aRVU/N29oo/V/IhBX8GR/zz4kQkprJgF2EVszyDE= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.4.2/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.4.3/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/moby/buildkit v0.25.1 h1:j7IlVkeNbEo+ZLoxdudYCHpmTsbwKvhgc/6UJ/mY/o8= +github.com/moby/buildkit v0.25.1/go.mod h1:phM8sdqnvgK2y1dPDnbwI6veUCXHOZ6KFSl6E164tkc= +github.com/moby/docker-image-spec v1.3.1 h1:jMKff3w6PgbfSa69GfNg+zN/XLhfXJGnEx3Nl2EsFP0= +github.com/moby/docker-image-spec v1.3.1/go.mod h1:eKmb5VW8vQEh/BAr2yvVNvuiJuY6UIocYsFu/DxxRpo= +github.com/moby/go-archive v0.1.0 h1:Kk/5rdW/g+H8NHdJW2gsXyZ7UnzvJNOy6VKJqueWdcQ= +github.com/moby/go-archive v0.1.0/go.mod h1:G9B+YoujNohJmrIYFBpSd54GTUB4lt9S+xVQvsJyFuo= +github.com/moby/locker v1.0.1 h1:fOXqR41zeveg4fFODix+1Ch4mj/gT0NE1XJbp/epuBg= +github.com/moby/locker v1.0.1/go.mod h1:S7SDdo5zpBK84bzzVlKr2V0hz+7x9hWbYC/kq7oQppc= +github.com/moby/patternmatcher v0.6.0 h1:GmP9lR19aU5GqSSFko+5pRqHi+Ohk1O69aFiKkVGiPk= +github.com/moby/patternmatcher v0.6.0/go.mod h1:hDPoyOpDY7OrrMDLaYoY3hf52gNCR/YOUYxkhApJIxc= +github.com/moby/sys/atomicwriter v0.1.0 h1:kw5D/EqkBwsBFi0ss9v1VG3wIkVhzGvLklJ+w3A14Sw= +github.com/moby/sys/atomicwriter v0.1.0/go.mod h1:Ul8oqv2ZMNHOceF643P6FKPXeCmYtlQMvpizfsSoaWs= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= +github.com/moby/sys/sequential v0.6.0 h1:qrx7XFUd/5DxtqcoH1h438hF5TmOvzC/lspjy7zgvCU= +github.com/moby/sys/sequential v0.6.0/go.mod h1:uyv8EUTrca5PnDsdMGXhZe6CCe8U/UiTWd+lL+7b/Ko= +github.com/moby/sys/signal v0.7.1 h1:PrQxdvxcGijdo6UXXo/lU/TvHUWyPhj7UOpSo8tuvk0= +github.com/moby/sys/signal v0.7.1/go.mod h1:Se1VGehYokAkrSQwL4tDzHvETwUZlnY7S5XtQ50mQp8= +github.com/moby/sys/user v0.4.0 h1:jhcMKit7SA80hivmFJcbB1vqmw//wU61Zdui2eQXuMs= +github.com/moby/sys/user v0.4.0/go.mod h1:bG+tYYYJgaMtRKgEmuueC0hJEAZWwtIbZTB+85uoHjs= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= +github.com/moby/term v0.5.2 h1:6qk3FJAFDs6i/q3W/pQ97SX192qKfZgGjCQqfCJkgzQ= +github.com/moby/term v0.5.2/go.mod h1:d3djjFCrjnB+fl8NJux+EJzu0msscUP+f8it8hPkFLc= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/morikuni/aec v1.0.0 h1:nP9CBfwrvYnBRgY6qfDQkygYDmYwOilePFkwzv4dU8A= +github.com/morikuni/aec v1.0.0/go.mod h1:BbKIizmSmc5MMPqRYbxO4ZU0S0+P200+tUnFx7PXmsc= +github.com/mozillazg/go-httpheader v0.2.1 h1:geV7TrjbL8KXSyvghnFm+NyTux/hxwueTSrwhe88TQQ= +github.com/mozillazg/go-httpheader v0.2.1/go.mod h1:jJ8xECTlalr6ValeXYdOF8fFUISeBAdw6E61aqQma60= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/namedotcom/go/v4 v4.0.2 h1:4gNkPaPRG/2tqFNUUof7jAVsA6vDutFutEOd7ivnDwA= +github.com/namedotcom/go/v4 v4.0.2/go.mod h1:J6sVueHMb0qbarPgdhrzEVhEaYp+R1SCaTGl2s6/J1Q= +github.com/nats-io/jwt v1.2.2/go.mod h1:/xX356yQA6LuXI9xWW7mZNpxgF2mBmGecH+Fj34sP5Q= +github.com/nats-io/jwt/v2 v2.0.3/go.mod h1:VRP+deawSXyhNjXmxPCHskrR6Mq50BqpEI5SEcNiGlY= +github.com/nats-io/nats-server/v2 v2.5.0/go.mod h1:Kj86UtrXAL6LwYRA6H4RqzkHhK0Vcv2ZnKD5WbQ1t3g= +github.com/nats-io/nats.go v1.12.1/go.mod h1:BPko4oXsySz4aSWeFgOHLZs3G4Jq4ZAyE6/zMCxRT6w= +github.com/nats-io/nkeys v0.2.0/go.mod h1:XdZpAbhgyyODYqjTawOnIOI7VlbKSarI9Gfy1tqEu/s= +github.com/nats-io/nkeys v0.3.0/go.mod h1:gvUNGjVcM2IPr5rCsRsC6Wb3Hr2CQAm08dsxtV6A5y4= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= +github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nrdcg/dnspod-go v0.4.0 h1:c/jn1mLZNKF3/osJ6mz3QPxTudvPArXTjpkmYj0uK6U= +github.com/nrdcg/dnspod-go v0.4.0/go.mod h1:vZSoFSFeQVm2gWLMkyX61LZ8HI3BaqtHZWgPTGKr6KQ= +github.com/nrdcg/freemyip v0.3.0 h1:0D2rXgvLwe2RRaVIjyUcQ4S26+cIS2iFwnhzDsEuuwc= +github.com/nrdcg/freemyip v0.3.0/go.mod h1:c1PscDvA0ukBF0dwelU/IwOakNKnVxetpAQ863RMJoM= +github.com/nrdcg/goacmedns v0.2.0 h1:ADMbThobzEMnr6kg2ohs4KGa3LFqmgiBA22/6jUWJR0= +github.com/nrdcg/goacmedns v0.2.0/go.mod h1:T5o6+xvSLrQpugmwHvrSNkzWht0UGAwj2ACBMhh73Cg= +github.com/nrdcg/namesilo v0.5.0 h1:6QNxT/XxE+f5B+7QlfWorthNzOzcGlBLRQxqi6YeBrE= +github.com/nrdcg/namesilo v0.5.0/go.mod h1:4UkwlwQfDt74kSGmhLaDylnBrD94IfflnpoEaj6T2qw= +github.com/nrdcg/porkbun v0.4.0 h1:rWweKlwo1PToQ3H+tEO9gPRW0wzzgmI/Ob3n2Guticw= +github.com/nrdcg/porkbun v0.4.0/go.mod h1:/QMskrHEIM0IhC/wY7iTCUgINsxdT2WcOphktJ9+Q54= +github.com/nwaples/rardecode/v2 v2.2.0 h1:4ufPGHiNe1rYJxYfehALLjup4Ls3ck42CWwjKiOqu0A= +github.com/nwaples/rardecode/v2 v2.2.0/go.mod h1:7uz379lSxPe6j9nvzxUZ+n7mnJNgjsRNb6IbvGVHRmw= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.2 h1:HFB2fbVIlhIfCfOW81bZFbiC/RvnpXSdhbF2/DJr134= +github.com/onsi/ginkgo v1.16.2/go.mod h1:CObGmKUOKaSC0RjmoAK7tKyn4Azo5P2IWuoMnvwxz1E= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.13.0 h1:7lLHu94wT9Ij0o6EWWclhu0aOh32VxhkwEJvzuWPeak= +github.com/onsi/gomega v1.13.0/go.mod h1:lRk9szgn8TxENtWd0Tp4c3wjlRfMTMH27I+3Je41yGY= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= +github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= +github.com/opencontainers/image-spec v1.1.1 h1:y0fUlFfIZhPF1W537XOLg0/fcx6zcHCJwooC2xJA040= +github.com/opencontainers/image-spec v1.1.1/go.mod h1:qpqAh3Dmcf36wStyyWU+kCeDgrGnAve2nCC8+7h8Q0M= +github.com/opencontainers/runtime-spec v1.2.1 h1:S4k4ryNgEpxW1dzyqffOmhI1BHYcjzU8lpJfSlR0xww= +github.com/opencontainers/runtime-spec v1.2.1/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/selinux v1.12.0 h1:6n5JV4Cf+4y0KNXW48TLj5DwfXpvWlxXplUkdTrmPb8= +github.com/opencontainers/selinux v1.12.0/go.mod h1:BTPX+bjVbWGXw7ZZWUbdENt8w0htPSrlgOOysQaU62U= +github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= +github.com/openzipkin/zipkin-go v0.2.5/go.mod h1:KpXfKdgRDnnhsxw4pNIH9Md5lyFqKUa4YDFlwRYAMyE= +github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= +github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= +github.com/ovh/go-ovh v1.9.0 h1:6K8VoL3BYjVV3In9tPJUdT7qMx9h0GExN9EXx1r2kKE= +github.com/ovh/go-ovh v1.9.0/go.mod h1:cTVDnl94z4tl8pP1uZ/8jlVxntjSIf09bNcQ5TJSC7c= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pascaldekloe/goe v0.1.0/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml v1.9.5 h1:4yBQzkHv+7BHq2PQUZF3Mx0IYxG7LsP222s7Agd3ve8= +github.com/pelletier/go-toml v1.9.5/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/performancecopilot/speed/v4 v4.0.0/go.mod h1:qxrSyuDGrTOWfV+uKRFhfxw6h/4HXRGUiZiufxo49BM= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= +github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0= +github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 h1:GFCKgmp0tecUJ0sJuv4pzYCqS9+RGSn52M3FUwPs+uo= +github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/posener/complete v1.2.3/go.mod h1:WZIdtGGp+qx0sLrYKtIRAruyNpv6hFCicSgv7Sy7s/s= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= +github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.4.0/go.mod h1:e9GMxYsXl05ICDXkRhurwBS4Q3OK1iX/F2sw+iXX5zU= +github.com/prometheus/client_golang v1.7.1/go.mod h1:PY5Wy2awLA44sXw4AOSfFBetzPP4j5+D6mVACh+pe2M= +github.com/prometheus/client_golang v1.11.0/go.mod h1:Z6t4BnS23TR94PD6BsDNk8yVqroYurpAkEiz0P2BEV0= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.2.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.9.1/go.mod h1:yhUN8i9wzaXS3w1O07YhxHEBxD+W35wd8bs7vj7HSQ4= +github.com/prometheus/common v0.10.0/go.mod h1:Tlit/dnDKsSWFlCLTWaA1cyBgKHSMdTB80sz/V91rCo= +github.com/prometheus/common v0.26.0/go.mod h1:M7rCNAaPfAosfx8veZJCuw84e35h3Cfd9VFqTh1DIvc= +github.com/prometheus/common v0.30.0/go.mod h1:vu+V0TpY+O6vW9J44gczi3Ap/oXXR10b+M/gUGO4Hls= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/prometheus/procfs v0.1.3/go.mod h1:lV6e/gmhEcM9IjHGsFOCxxuZ+z1YqCvr4OA4YeYWdaU= +github.com/prometheus/procfs v0.6.0/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.7.3/go.mod h1:cz+aTbrPOrUb4q7XlbU9ygM+/jj0fzG6c1xBZuNvfVA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/qiniu/dyn v1.3.0/go.mod h1:E8oERcm8TtwJiZvkQPbcAh0RL8jO1G0VXJMW3FAWdkk= +github.com/qiniu/go-sdk/v7 v7.21.1 h1:D/IjVOlg5pTw0jeDjqTo6H5QM73Obb1AYfPOHmIFN+Q= +github.com/qiniu/go-sdk/v7 v7.21.1/go.mod h1:8EM2awITynlem2VML2dXGHkMYP2UyECsGLOdp6yMpco= +github.com/qiniu/x v1.10.5/go.mod h1:03Ni9tj+N2h2aKnAz+6N0Xfl8FwMEDRC2PAlxekASDs= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/rcrowley/go-metrics v0.0.0-20201227073835-cf1acfcdf475/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= +github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/rs/xid v1.5.0 h1:mKX4bl4iPYJtEIxp6CYiUuLQ/8DYMoz0PUdtGgMFRVc= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/rwcarlsen/goexif v0.0.0-20190401172101-9e8deecbddbd/go.mod h1:hPqNNc0+uJM6H+SuU8sEs5K5IQeKccPqeSjfgcKGgPk= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1 h1:PKK9DyHxif4LZo+uQSgXNqs0jj5+xZwwfKHgph2lxBw= +github.com/santhosh-tekuri/jsonschema/v6 v6.0.1/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= +github.com/secure-systems-lab/go-securesystemslib v0.6.0 h1:T65atpAVCJQK14UA57LMdZGpHi4QYSH/9FZyNGqMYIA= +github.com/secure-systems-lab/go-securesystemslib v0.6.0/go.mod h1:8Mtpo9JKks/qhPG4HGZ2LGMvrPbzuxwfz/f/zLfEWkk= +github.com/shibumi/go-pathspec v1.3.0 h1:QUyMZhFo0Md5B8zV8x2tesohbb5kfbpTi9rBnKh5dkI= +github.com/shibumi/go-pathspec v1.3.0/go.mod h1:Xutfslp817l2I1cZvgcfeMQJG5QnU2lh5tVaaMCl3jE= +github.com/shirou/gopsutil/v4 v4.25.11 h1:X53gB7muL9Gnwwo2evPSE+SfOrltMoR6V3xJAXZILTY= +github.com/shirou/gopsutil/v4 v4.25.11/go.mod h1:EivAfP5x2EhLp2ovdpKSozecVXn1TmuG7SMzs/Wh4PU= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88= +github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/assertions v1.1.0/go.mod h1:tcbTF8ujkAEcZ8TElKY+i30BzYlVhC/LOxJk7iOWnoo= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= +github.com/spdx/tools-golang v0.5.5 h1:61c0KLfAcNqAjlg6UNMdkwpMernhw3zVRwDZ2x9XOmk= +github.com/spdx/tools-golang v0.5.5/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w= +github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.10.1 h1:lJeBwCfmrnXthfAupyUTzJ/J4Nc1RsHC/mSRU2dll/s= +github.com/spf13/cobra v1.10.1/go.mod h1:7SmJGaTHFVBY0jW4NXGluQoLvhqFQM+6XSKD+P4XaB0= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v1.0.0/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20200128134331-0f66f006fb2e/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.0.563/go.mod h1:7sCQWVkxcsR38nffDW057DRGk8mUjK1Ing/EFOK8s8Y= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.1.25/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12 h1:/ABtv4x4FSGxGW0d6Sc88iQn6Up2LalWKwt/Tj7Dtz8= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.12/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/kms v1.0.563/go.mod h1:uom4Nvi9W+Qkom0exYiJ9VWJjXwyxtPYTkKkaLMlfE0= +github.com/tencentyun/cos-go-sdk-v5 v0.7.54 h1:FRamEhNBbSeggyYfWfzFejTLftgbICocSYFk4PKTSV4= +github.com/tencentyun/cos-go-sdk-v5 v0.7.54/go.mod h1:UN+VdbCl1hg+kKi5RXqZgaP+Boqfmk+D04GRc4XFk70= +github.com/therootcompany/xz v1.0.1 h1:CmOtsn1CbtmyYiusbfmhmkpAAETj0wBIH6kCYaX+xzw= +github.com/therootcompany/xz v1.0.1/go.mod h1:3K3UH1yCKgBneZYhuQUvJ9HPD19UEXEI0BWbMn8qNMY= +github.com/theupdateframework/notary v0.7.0 h1:QyagRZ7wlSpjT5N2qQAh/pN+DVqgekv4DzbAiAiEL3c= +github.com/theupdateframework/notary v0.7.0/go.mod h1:c9DRxcmhHmVLDay4/2fUYdISnHqbFDGRSlXPO0AhYWw= +github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= +github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= +github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= +github.com/tklauser/go-sysconf v0.3.16 h1:frioLaCQSsF5Cy1jgRBrzr6t502KIIwQ0MArYICU0nA= +github.com/tklauser/go-sysconf v0.3.16/go.mod h1:/qNL9xxDhc7tx3HSRsLWNnuzbVfh3e7gh/BmM179nYI= +github.com/tklauser/numcpus v0.11.0 h1:nSTwhKH5e1dMNsCdVBukSZrURJRoHbSEQjdEbY+9RXw= +github.com/tklauser/numcpus v0.11.0/go.mod h1:z+LwcLq54uWZTX0u/bGobaV34u6V7KNlTZejzM6/3MQ= +github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19 h1:ZCmSnT6CLGhfoQ2lPEhL4nsJstKDCw1F1RfN8/smTCU= +github.com/tomasen/fcgi_client v0.0.0-20180423082037-2bb3d819fd19/go.mod h1:SXTY+QvI+KTTKXQdg0zZ7nx0u94QWh8ZAwBQYsW9cqk= +github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323 h1:r0p7fK56l8WPequOaR3i9LBqfPtEdXIQbUTzT55iqT4= +github.com/tonistiigi/dchapes-mode v0.0.0-20250318174251-73d941a28323/go.mod h1:3Iuxbr0P7D3zUzBMAZB+ois3h/et0shEz0qApgHYGpY= +github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f h1:MoxeMfHAe5Qj/ySSBfL8A7l1V+hxuluj8owsIEEZipI= +github.com/tonistiigi/fsutil v0.0.0-20250605211040-586307ad452f/go.mod h1:BKdcez7BiVtBvIcef90ZPc6ebqIWr4JWD7+EvLm6J98= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0 h1:2f304B10LaZdB8kkVEaoXvAMVan2tl9AiK4G0odjQtE= +github.com/tonistiigi/go-csvvalue v0.0.0-20240814133006-030d3b2625d0/go.mod h1:278M4p8WsNh3n4a1eqiFcV2FGk7wE5fwUpUom9mK9lE= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea h1:SXhTLE6pb6eld/v/cCndK0AMpt1wiVFb/YYmqB3/QG0= +github.com/tonistiigi/units v0.0.0-20180711220420-6950e57a87ea/go.mod h1:WPnis/6cRcDZSUvVmezrxJPkiO87ThFYsoUiMwWNDJk= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab h1:H6aJ0yKQ0gF49Qb2z5hI1UHxSQt4JMyxebFR15KnApw= +github.com/tonistiigi/vt100 v0.0.0-20240514184818-90bafcd6abab/go.mod h1:ulncasL3N9uLrVann0m+CDlJKWsIAP34MPcOJF6VRvc= +github.com/tv42/httpunix v0.0.0-20150427012821-b75d8614f926/go.mod h1:9ESjWnEqriFuLhtthL60Sar/7RFoluCcXsuvEwTV5KM= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/ulikunitz/xz v0.5.6/go.mod h1:2bypXElzHzzJZwzH67Y6wb67pO62Rzfn7BSiF4ABRW8= +github.com/ulikunitz/xz v0.5.15 h1:9DNdB5s+SgV3bQ2ApL10xRc35ck0DuIX/isZvIk+ubY= +github.com/ulikunitz/xz v0.5.15/go.mod h1:nbz6k7qbPmH4IRqmfOplQw/tblSgqTqBwxkY0oWt/14= +github.com/upyun/go-sdk v2.1.0+incompatible h1:OdjXghQ/TVetWV16Pz3C1/SUpjhGBVPr+cLiqZLLyq0= +github.com/upyun/go-sdk v2.1.0+incompatible/go.mod h1:eu3F5Uz4b9ZE5bE5QsCL6mgSNWRwfj0zpJ9J626HEqs= +github.com/urfave/cli/v2 v2.3.0/go.mod h1:LJmUH05zAU44vOAcrfzZQKsZbVcdbOG8rtL3/XcUArI= +github.com/vbatts/tar-split v0.12.1 h1:CqKoORW7BUWBe7UL/iqTVvkTBOF8UvOMKOIZykxnnbo= +github.com/vbatts/tar-split v0.12.1/go.mod h1:eF6B6i6ftWQcDqEn3/iGFRFRo8cBIMSJVOpnNdfTMFA= +github.com/volcengine/volc-sdk-golang v1.0.230 h1:84/MOF0zUPtAHt3e1+MsFq5qrnQRC+e3XzTUwIOzZxw= +github.com/volcengine/volc-sdk-golang v1.0.230/go.mod h1:zHJlaqiMbIB+0mcrsZPTwOb3FB7S/0MCfqlnO8R7hlM= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.0.2/go.mod h1:1WAq6h33pAW+iRreB34OORO2Nf7qel3VV3fjBj+hCSs= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.2/go.mod h1:8F9zXuvzgwmyT5DUm4GUfZGDdT3W+LCvS6+da4O5kxM= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc= +github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.30/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0= +github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= +go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs= +go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g= +go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ= +go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E= +go.opencensus.io v0.24.0 h1:y73uSU6J157QMP2kn2r30vwW1A2W2WFwSCGnAVxeaD0= +go.opencensus.io v0.24.0/go.mod h1:vNK8G9p7aAivkbmorf4v+7Hgx+Zs0yY+0fOtgBfjQKo= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0 h1:0tY123n7CdWMem7MOVdKOt0YfshufLCwfE5Bob+hQuM= +go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace v0.60.0/go.mod h1:CosX/aS4eHnG9D7nESYpV753l4j9q5j3SL/PUYd2lR8= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.38.0 h1:RkfdswUDRimDg0m2Az18RKOsnI8UDzppJAtj01/Ymk8= +go.opentelemetry.io/otel v1.38.0/go.mod h1:zcmtmQ1+YmQM9wrNsTGV/q/uyusom3P8RxwExxkZhjM= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0 h1:QcFwRrZLc82r8wODjvyCbP7Ifp3UANaBSmhDSFjnqSc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.35.0/go.mod h1:CXIWhUomyWBG/oY2/r/kLp6K/cmx9e/7DLpBuuGdLCA= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0 h1:1fTNlAIJZGWLP5FVu0fikVry1IsiUnXjf7QFvoNN3Xw= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.35.0/go.mod h1:zjPK58DtkqQFn+YUMbx0M2XV3QgKU0gS9LeGohREyK4= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0 h1:m639+BofXTvcY1q8CGs4ItwQarYtJPOWmVobfM1HpVI= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.35.0/go.mod h1:LjReUci/F4BUyv+y4dwnq3h/26iNOeC3wAIqgvTIZVo= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0 h1:xJ2qHD0C1BeYVTLLR9sX12+Qb95kfeD/byKj6Ky1pXg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.35.0/go.mod h1:u5BF1xyjstDowA1R5QAO9JHzqK+ublenEW/dyqTjBVk= +go.opentelemetry.io/otel/metric v1.38.0 h1:Kl6lzIYGAh5M159u9NgiRkmoMKjvbsKtYRwgfrA6WpA= +go.opentelemetry.io/otel/metric v1.38.0/go.mod h1:kB5n/QoRM8YwmUahxvI3bO34eVtQf2i4utNVLr9gEmI= +go.opentelemetry.io/otel/sdk v1.38.0 h1:l48sr5YbNf2hpCUj/FoGhW9yDkl+Ma+LrVl8qaM5b+E= +go.opentelemetry.io/otel/sdk v1.38.0/go.mod h1:ghmNdGlVemJI3+ZB5iDEuk4bWA3GkTpW+DOoZMYBVVg= +go.opentelemetry.io/otel/sdk/metric v1.38.0 h1:aSH66iL0aZqo//xXzQLYozmWrXxyFkBJ6qT5wthqPoM= +go.opentelemetry.io/otel/sdk/metric v1.38.0/go.mod h1:dg9PBnW9XdQ1Hd6ZnRz689CbtrUp0wMMs9iPcgT9EZA= +go.opentelemetry.io/otel/trace v1.38.0 h1:Fxk5bKrDZJUH+AMyyIXGcFAPah0oRcT+LuNtJrmcNLE= +go.opentelemetry.io/otel/trace v1.38.0/go.mod h1:j1P9ivuFsTceSWe1oY+EeW3sc+Pp42sO++GHkg4wwhs= +go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI= +go.opentelemetry.io/proto/otlp v1.5.0 h1:xJvq7gMzB31/d406fB8U5CBdyQGw4P399D1aQWU/3i4= +go.opentelemetry.io/proto/otlp v1.5.0/go.mod h1:keN8WnHxOy8PG0rQZjJJ5A2ebUoafqWp0eVQ4yIXvJ4= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/goleak v1.1.11-0.20210813005559-691160354723/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU= +go.uber.org/multierr v1.7.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo= +go.uber.org/zap v1.19.1/go.mod h1:j3DNczoxDZroyBnOT1L/Q79cfUMGZxlv/9dzN7SM1rI= +go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI= +go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +go4.org v0.0.0-20200411211856-f5505b9728dd h1:BNJlw5kRTzdmyfh5U8F93HA2OwkP7ZGwA51eJ/0wKOU= +go4.org v0.0.0-20200411211856-f5505b9728dd/go.mod h1:CIiUVy99QCPfoE13bO4EZaz5GZMZXMSBGhxRdsvzbkg= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3H3cr1v9wB50oz8l4C4h62xy7jSTY= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191205180655-e7c4368fe9dd/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20191219195013-becbf705a915/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200323165209-0ec3e9974c59/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200510223506-06a226fb4e37/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201012173705-84dcc777aaee/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201112155050-0c6587e931a9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210314154223-e6e6c4f2bb5b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210915214749-c084706c2272/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210920023735-84f357641f63/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= +golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= +golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= +golang.org/x/crypto v0.24.0/go.mod h1:Z1PMYSOR5nyMcyAVAIQSKCDwalqy85Aqn1x3Ws4L5DM= +golang.org/x/crypto v0.46.0 h1:cKRW/pmt1pKAfetfu+RCEvjvZkA9RimPbh7bhFjGVBU= +golang.org/x/crypto v0.46.0/go.mod h1:Evb/oLKmMraqjZ2iQTwDwvCtJkczlDuTmdJXoZVzqU0= +golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= +golang.org/x/mod v0.30.0 h1:fDEXFVZ/fmCKProc/yAXXUijritrDzahmwwefnjoPFk= +golang.org/x/mod v0.30.0/go.mod h1:lAsf5O2EvJeSFMiBxXDki7sCgAxEUcZHXoXMKT4GJKc= +golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201010224723-4f7140c49acb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= +golang.org/x/net v0.0.0-20210525063256-abc453219eb5/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= +golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= +golang.org/x/net v0.26.0/go.mod h1:5YKkiSynbBIh3p6iOc/vibscux0x38BZDkn8sCUPxHE= +golang.org/x/net v0.48.0 h1:zyQRTTrjc33Lhh0fBgT/H3oZq9WuvRR5gPC70xpDiQU= +golang.org/x/net v0.48.0/go.mod h1:+ndRgGjkh8FGtu1w1FGbEC31if4VrNVMuKTgcAAnQRY= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20210313182246-cd4f82c27b84/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190130150945-aca44879d564/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190922100055-0a153f010e69/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200106162015-b016eb3dc98e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200124204421-9fbb57f87de9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200509044756-6aff5f38e54f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200615200032-f1bc736245b1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200625212154-ddb9806d33ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210403161142-5e06dd20ab57/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210917161153-d61c044b1678/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220406163625-3f8b81556e12/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.21.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.39.0 h1:CvCKL8MeisomCi6qNZ+wbb0DN9E5AATixKsvNtMoMFk= +golang.org/x/sys v0.39.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o= +golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.16.0/go.mod h1:yn7UURbUtPyrVJPGPq404EukNFxcm/foM+bV/bfcDsY= +golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= +golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= +golang.org/x/term v0.21.0/go.mod h1:ooXLefLobQVslOqselCNF4SxFAaoS6KujMbsGzSDmX0= +golang.org/x/term v0.38.0 h1:PQ5pkm/rLO6HnxFR7N2lJHOZX6Kez5Y1gDSJla6jo7Q= +golang.org/x/term v0.38.0/go.mod h1:bSEAKrOT1W+VSu9TSCMtoGEOUcKxOKgl3LE5QEF/xVg= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.32.0 h1:ZD01bjUt1FQ9WJ0ClOL5vxgxOI/sVCNgX1YtKwcY0mU= +golang.org/x/text v0.32.0/go.mod h1:o/rUWzghvpD5TXrTIBuJU77MTaN0ljMWE47kxGJQ7jY= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20200416051211-89c76fbcd5d1/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20210723032227-1f47c861a9ac/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.14.0 h1:MRx4UaLrDotUKUdCIqzPC48t1Y9hANFKIRpNx+Te8PI= +golang.org/x/time v0.14.0/go.mod h1:eL/Oa2bBBK0TkX57Fyni+NgnyQQN4LitPmob2Hjnqw4= +golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190206041539-40960b6deb8e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190907020128-2ca718005c18/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200509030707-2212a7e161a5/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= +golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= +golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 h1:+cNy6SZtPcJQH3LJVLOSmiC7MMxXNOb3PU/VUEz+EhU= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +gonum.org/v1/gonum v0.0.0-20180816165407-929014505bf4/go.mod h1:Y+Yx5eoAFn32cQvJDxZx5Dpnq+c3wtXuadVZAcxbbBo= +gonum.org/v1/gonum v0.8.2/go.mod h1:oe/vMfY3deqTw+1EZJhuvEW2iwGF1bW9wwu7XCu0+v0= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +gonum.org/v1/netlib v0.0.0-20190313105609-8cb42192e0e0/go.mod h1:wa6Ws7BG/ESfp6dHfk7C6KdzKA7wR7u/rKwOGE66zvw= +gonum.org/v1/plot v0.0.0-20190515093506-e2840ee46a6b/go.mod h1:Wt8AAjI+ypCyYX3nZBvf6cAIx93T+c/OS2HFAYskSZc= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0= +google.golang.org/genproto v0.0.0-20210917145530-b395a37504d4/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY= +google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38 h1:Q3nlH8iSQSRUwOskjbcSMcF2jiYMNiQYZ0c2KEJLKKU= +google.golang.org/genproto v0.0.0-20241021214115-324edc3d5d38/go.mod h1:xBI+tzfqGGN2JBeSebfKXFSdBpWVQ7sLW40PTupVRm4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8 h1:mepRgnBZa07I4TRuomDE4sTIYieg/osKmzIf4USdWS4= +google.golang.org/genproto/googleapis/api v0.0.0-20251022142026-3a174f9686a8/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846 h1:Wgl1rcDNThT+Zn47YyCXOXyX/COgMTIdhJ717F0l4xk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20251124214823-79d6a2a48846/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM= +google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34= +google.golang.org/grpc v1.77.0 h1:wVVY6/8cGA6vvffn+wWK5ToddbgdU3d8MNENr4evgXM= +google.golang.org/grpc v1.77.0/go.mod h1:z0BY1iVj0q8E1uSQCjL9cppRj+gnZjzDnzV0dHhrNig= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= +google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= +gopkg.in/ini.v1 v1.56.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.0.0/go.mod h1:l0ndWWf7gzL7RNwBG7wST/UCcT4T24xpD6X8LsfU/+k= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.5/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +gotest.tools/v3 v3.5.2 h1:7koQfIKdy+I8UTetycgUqXWSDwpgv193Ka+qRsmBY8Q= +gotest.tools/v3 v3.5.2/go.mod h1:LtdLGcnqToBH83WByAAi/wiwSFCArdFIUV/xxN4pcjA= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= +modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA= +modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/goabi0 v0.0.3 h1:y81b9r3asCh6Xtse6Nz85aYGB0cG3M3U6222yap1KWI= +modernc.org/goabi0 v0.0.3/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.66.1 h1:4uQsntXbVyAgrV+j6NhKvDiUypoJL48BWQx6sy9y8ok= +modernc.org/libc v1.66.1/go.mod h1:AiZxInURfEJx516LqEaFcrC+X38rt9G7+8ojIXQKHbo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI= +modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= +sigs.k8s.io/yaml v1.2.0/go.mod h1:yfXDCHCao9+ENCvLSE62v9VSji2MKu5jeNfTrofGhJc= diff --git a/agent/i18n/i18n.go b/agent/i18n/i18n.go new file mode 100644 index 0000000..d6b2a2d --- /dev/null +++ b/agent/i18n/i18n.go @@ -0,0 +1,163 @@ +package i18n + +import ( + "embed" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "strings" + + "github.com/1Panel-dev/1Panel/agent/global" + + "github.com/gin-gonic/gin" + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/text/language" + "gopkg.in/yaml.v3" +) + +var langFiles = map[string]string{ + "zh": "lang/zh.yaml", + "en": "lang/en.yaml", + "zh-Hant": "lang/zh-Hant.yaml", + "pt-BR": "lang/pt-BR.yaml", + "ja": "lang/ja.yaml", + "ru": "lang/ru.yaml", + "ms": "lang/ms.yaml", + "ko": "lang/ko.yaml", + "tr": "lang/tr.yaml", + "es-ES": "lang/es-ES.yaml", +} + +func GetMsgWithMap(key string, maps map[string]interface{}) string { + var content string + if maps == nil { + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + } else { + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: maps, + }) + } + content = strings.ReplaceAll(content, ": ", "") + if content == "" { + return key + } else { + return content + } +} + +func GetMsgWithDetail(key string, detail string) string { + var ( + content string + dataMap = make(map[string]interface{}) + ) + dataMap["detail"] = detail + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: dataMap, + }) + if content != "" { + return content + } + return key +} + +func GetErrMsg(key string, maps map[string]interface{}) string { + var content string + if maps == nil { + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + } else { + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: maps, + }) + } + return content +} + +func GetMsgByKey(key string) string { + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + return content +} + +func Get(key string) string { + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + if content != "" { + return content + } + return key +} + +func GetWithName(key string, name string) string { + var ( + dataMap = make(map[string]interface{}) + ) + dataMap["name"] = name + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: dataMap, + }) + return content +} + +func GetWithNameAndErr(key string, name string, err error) string { + var ( + dataMap = make(map[string]interface{}) + ) + dataMap["name"] = name + dataMap["err"] = err.Error() + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: dataMap, + }) + return content +} + +//go:embed lang/* +var fs embed.FS +var bundle *i18n.Bundle + +func UseI18n() gin.HandlerFunc { + return func(context *gin.Context) { + lang := context.GetHeader("Accept-Language") + if lang == "" { + lang = GetLanguageFromDB() + } + global.I18n = i18n.NewLocalizer(bundle, lang) + } +} + +func Init() { + bundle = i18n.NewBundle(language.Chinese) + bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) + isSuccess := true + for _, file := range langFiles { + if _, err := bundle.LoadMessageFileFS(fs, file); err != nil { + global.LOG.Errorf("[i18n] load language file %s failed: %v\n", file, err) + isSuccess = false + } + } + + if !isSuccess { + panic("[i18n] failed to init language files, See log above for details") + } + lang := GetLanguageFromDB() + global.I18n = i18n.NewLocalizer(bundle, lang) +} + +func GetLanguageFromDB() string { + if global.DB == nil { + return "en" + } + lang, _ := repo.NewISettingRepo().GetValueByKey("Language") + if lang == "" { + return "en" + } + return lang +} diff --git a/agent/i18n/lang/en.yaml b/agent/i18n/lang/en.yaml new file mode 100644 index 0000000..ebe445f --- /dev/null +++ b/agent/i18n/lang/en.yaml @@ -0,0 +1,530 @@ +ErrInvalidParams: 'Request parameter error: {{ .detail }}' +ErrTokenParse: 'Token generation error: {{ .detail }}' +ErrInitialPassword: 'Original password is incorrect' +ErrInternalServer: 'Internal server error: {{ .detail }}' +ErrRecordExist: 'Record already exists' +ErrRecordNotFound: 'Record not found' +ErrStructTransform: 'Type conversion failed: {{ .err }}' +ErrNotLogin: 'User not logged in: {{ .detail }}' +ErrPasswordExpired: 'The current password has expired: {{ .detail }}' +ErrNotSupportType: 'The system does not support the current type: {{ .name }}' +ErrProxy: 'Request error, please check the node status: {{ .detail }}' +ErrApiConfigStatusInvalid: 'Access to the API interface is prohibited: {{ .detail }}' +ErrApiConfigKeyInvalid: 'API interface key error: {{ .detail }}' +ErrApiConfigIPInvalid: 'The IP used to call the API interface is not in the whitelist: {{ .detail }}' +ErrApiConfigDisable: 'This interface prohibits the use of API interface calls: {{ .detail }}' +ErrApiConfigKeyTimeInvalid: 'API interface timestamp error: {{ .detail }}' +ErrShutDown: "Command manually terminated!" + +ErrMinQuickJump: "Please set at least one quick jump entry!" +ErrMaxQuickJump: "You can set up to four quick jump entries!" + +#common +ErrUsernameIsExist: 'Username already exists' +ErrNameIsExist: 'Name already exists' +ErrDemoEnvironment: 'Demo server, this operation is forbidden!' +ErrCmdTimeout: 'Command execution timed out!' +ErrCmdIllegal: 'There are illegal characters in the execution command, please modify it and try again!' +ErrPortExist: '{{ .port }} port is already occupied by {{ .type }} [{{ .name }}]' +TYPE_APP: 'Application' +TYPE_RUNTIME: 'Runtime environment' +TYPE_DOMAIN: 'Domain Name' +ErrTypePort: 'Port {{ .name }} format is incorrect' +ErrTypePortRange: 'The port range needs to be between 1-65535' +Success: 'Success' +Failed: 'Failed' +SystemRestart: 'Task interrupted due to system restart' +ErrGroupIsDefault: 'Default group, cannot be deleted' +ErrGroupIsInWebsiteUse: 'The group is being used by another website and cannot be deleted.' +Decrypt: "Decrypt" + +#backup +Localhost: 'Local Machine' +ErrBackupInUsed: 'The backup account has been used in the scheduled task and cannot be deleted.' +ErrBackupCheck: 'Backup account test connection failed {{ .err }}' +ErrBackupLocalDelete: 'Deleting the local server backup account is not supported yet' +ErrBackupLocalCreate: 'Creating local server backup accounts is not supported yet' + +#app +ErrPortInUsed: '{{ .detail }} port is already occupied!' +ErrAppLimit: 'The number of applications installed has exceeded the limit' +ErrNotInstall: 'Application not installed' +ErrPortInOtherApp: '{{ .port }} port is already occupied by application {{ .apps }}!' +ErrDbUserNotValid: 'Existing database, username and password do not match!' +ErrUpdateBuWebsite: 'The application was updated successfully, but the website configuration file modification failed. Please check the configuration! ' +Err1PanelNetworkFailed: 'Default container network creation failed! {{ .detail }}' +ErrFileParse: 'Application docker-compose file parsing failed!' +ErrInstallDirNotFound: 'The installation directory does not exist. If you need to uninstall, please select Force Uninstall' +AppStoreIsUpToDate: 'The app store is already the latest version' +LocalAppVersionNull: '{{ .name }} application is not synchronized to the version! Cannot be added to the application list' +LocalAppVersionErr: '{{ .name }} sync version {{ .version }} failed! {{ .err }}' +ErrFileNotFound: '{{ .name }} file does not exist' +ErrFileParseApp: '{{ .name }} file parsing failed {{ .err }}' +ErrAppDirNull: 'The version folder does not exist' +LocalAppErr: 'Application {{ .name }} sync failed! {{ .err }}' +ErrContainerName: 'Container name already exists' +ErrCreateHttpClient: 'Failed to create request {{ .err }}' +ErrHttpReqTimeOut: 'Request timed out {{ .err }}' +ErrHttpReqFailed: 'Request failed {{ .err }}' +ErrNoSuchHost: 'Unable to find the requested server {{ .err }}' +ErrHttpReqNotFound: 'The requested resource {{ .err }} could not be found' +ErrContainerNotFound: '{{ .name }} container does not exist' +ErrContainerMsg: '{{ .name }} container is abnormal. Please check the log on the container page for details' +ErrAppBackup: '{{ .name }} application backup failed {{ .err }}' +ErrVersionTooLow: 'The current 1Panel version is too low to update the App Store. Please upgrade the version before operating.' +ErrAppNameExist: 'The application name already exists' +AppStoreIsSyncing: 'The App Store is syncing, please try again later' +ErrGetCompose: 'Failed to obtain the docker-compose.yml file! {{ .detail }}' +ErrAppWarn: 'Abnormal status, please check the log' +ErrAppParamKey: 'Parameter {{ .name }} field is abnormal' +ErrAppUpgrade: 'Application {{ .name }} upgrade failed {{ .err }}' +AppRecover: 'Rollback application {{ .name }}' +PullImageStart: 'Start pulling image [{{ .name }}]' +PullImageSuccess: 'Image pull successful' +AppStoreIsLastVersion: 'The App Store is already the latest version' +AppStoreSyncSuccess: 'App Store synchronization successful' +SyncAppDetail: 'Synchronize application configuration' +AppVersionNotMatch: '{{ .name }} application requires a higher 1Panel version, skipping synchronization' +MoveSiteDir: "Detected website directory change. Need to migrate OpenResty website directory to {{ .name }}" +MoveSiteDirSuccess: 'Successful migration of website directory' +DeleteRuntimePHP: 'Delete PHP runtime' +CustomAppStoreFileValid: 'App store packages need to be in .tar.gz format' +PullImageTimeout: 'Pull image timeout, please try to increase the image acceleration or change to another image acceleration' +ErrAppIsDown: '{{ .name }} application status is abnormal, please check' +ErrCustomApps: 'There is an installed application, please uninstall it first' +ErrCustomRuntimes: 'There is an installed runtime environment, please delete it first' +ErrAppVersionDeprecated: "The {{ .name }} application is not compatible with the current 1Panel version, skipped" +ErrDockerFailed: "The state of Docker is abnormal, please check the service status" +ErrDockerComposeCmdNotFound: "The Docker Compose command does not exist, please install this command on the host machine first" +UseExistImage: 'Failed to pull image, using existing image' + +#ssh +ExportIP: "Login IP" +ExportArea: "Location" +ExportPort: "Port" +ExportAuthMode: "Login Method" +ExportUser: "User" +ExportStatus: "Login Status" +ExportDate: "Time" + +#file +ErrFileCanNotRead: 'This file does not support preview' +ErrFileToLarge: 'The file is larger than 10M and cannot be opened' +ErrPathNotFound: 'Directory does not exist' +ErrMovePathFailed: 'The target path cannot contain the original path!' +ErrLinkPathNotFound: 'The target path does not exist!' +ErrFileIsExist: 'The file or folder already exists!' +ErrFileUpload: '{{ .name }} failed to upload file {{ .detail }}' +ErrFileDownloadDir: 'Download folder is not supported' +ErrCmdNotFound: '{{ .name}} command does not exist, please install this command on the host first' +ErrSourcePathNotFound: 'Source directory does not exist' +ErrFavoriteExist: 'This path has already been favorited' +ErrInvalidChar: 'Illegal characters are not allowed' +ErrPathNotDelete: 'The selected directory cannot be deleted' +ErrLogFileToLarge: "The log file exceeds 500MB and cannot be opened" +FileDropFailed: "Failed to clean up file {{ .name }}: {{ .err }}" +FileDropSuccess: "Successfully cleaned up file {{ .name }}, cleaned {{ .count }} files, freed {{ .size }} disk space" +FileDropSum: "File cleanup completed successfully, total {{ .count }} files cleaned, total {{ .size }} disk space freed" +ErrBadDecrypt: "Compressed package decryption password error, decompression failed, please check and try again!" + +#website +ErrAliasIsExist: 'Alias already exists' +ErrBackupMatch: 'The backup file does not match some of the current website data {{ .detail }}' +ErrBackupExist: 'The corresponding part of the source data in the backup file does not exist {{ .detail }}' +ErrPHPResource: 'The local operating environment does not support switching! ' +ErrPathPermission: 'A folder with non-1000:1000 permissions was detected in the index directory, which may cause an Access denied error on the website. Please click the Save button above' +ErrDomainIsUsed: 'The domain name is already used by the website [{{ .name }}]' +ErrDomainFormat: '{{ .name }} domain name format is incorrect' +ErrDefaultAlias: 'default is a reserved code, please use another code' +ErrParentWebsite: 'You need to delete the subsite {{ .name }} first' +ErrBuildDirNotFound: 'The build directory does not exist' +ErrImageNotExist: 'The operating environment {{ .name }} image does not exist, please re-edit the operating environment' +ErrProxyIsUsed: "Load balancing has been used by reverse proxy, cannot be deleted" +ErrSSLValid: 'Certificate file is abnormal, please check the certificate status!' +ErrWebsiteDir: "Please select a directory within the website directory." +ErrComposerFileNotFound: "composer.json file does not exist" +ErrRuntimeNoPort: "The runtime environment is not set with a port, please edit the runtime environment first." +Status: 'Status' +start: 'Start' +stop: 'Stop' +restart: 'Restart' +kill: 'Force Stop' +pause: 'Pause' +unpause: 'Resume' +remove: 'Delete' +delete: 'Delete' +ErrDefaultWebsite: 'Default website has been set, please cancel it before setting!' +SSL: 'Certificate' +Set: 'Settings' + +#ssl +ErrSSLCannotDelete: 'The {{ .name }} certificate is being used by a website and cannot be deleted' +ErrAccountCannotDelete: 'The account is associated with a certificate and cannot be deleted' +ErrSSLApply: 'Certificate renewal successful, openresty reload failed, please check the configuration!' +ErrEmailIsExist: 'The mailbox already exists' +ErrSSLKeyNotFound: 'The private key file does not exist' +ErrSSLCertificateNotFound: 'The certificate file does not exist' +ErrSSLKeyFormat: 'Private key file verification failed' +ErrSSLCertificateFormat: 'The certificate file format is incorrect, please use pem format' +ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid or EabHmacKey cannot be blank' +ErrOpenrestyNotFound: 'Http mode requires Openresty to be installed first' +ApplySSLStart: 'Start applying for a certificate, domain name [{{ .domain }}] application method [{{ .type }}] ' +dnsAccount: 'DNS Auto' +dnsManual: 'DNS Manual' +http: 'HTTP' +ApplySSLFailed: 'Application for [{{ .domain }}] certificate failed, {{ .detail }} ' +ApplySSLSuccess: 'Apply for [{{ .domain }}] certificate successfully! !' +DNSAccountName: 'DNS account [{{ .name }}] vendor [{{ .type }}]' +PushDirLog: 'Certificate pushed to directory [{{ .path }}] {{ .status }}' +ErrDeleteCAWithSSL: 'The current organization has a certificate that has been issued and cannot be deleted.' +ErrDeleteWithPanelSSL: 'Panel SSL configuration uses this certificate and cannot be deleted' +ErrDefaultCA: 'The default authority cannot be deleted' +ApplyWebSiteSSLLog: 'Starting to renew the {{ .name }} website certificate' +ErrUpdateWebsiteSSL: '{{ .name }} website certificate update failed: {{ .err }}' +ApplyWebSiteSSLSuccess: 'Update website certificate successfully' +ErrExecShell: 'Failed to execute script {{ .err }}' +ExecShellStart: 'Start executing the script' +ExecShellSuccess: 'Script execution successful' +StartUpdateSystemSSL: 'Start updating system certificate' +UpdateSystemSSLSuccess: 'Update system certificate successfully' +ErrWildcardDomain: 'Unable to apply for wildcard domain name certificate in HTTP mode' +ErrApplySSLCanNotDelete: "The certificate {{.name}} being applied for cannot be deleted, please try again later." +StartPushSSLToNode: "Starting to push certificate to node" +PushSSLToNodeFailed: "Failed to push certificate to node: {{ .err }}" +PushSSLToNodeSuccess: "Successfully pushed certificate to node" + +#mysql +ErrUserIsExist: 'The current user already exists, please re-enter' +ErrDatabaseIsExist: 'The current database already exists, please re-enter' +ErrExecTimeOut: 'SQL execution timed out, please check the database' +ErrRemoteExist: 'The remote database already exists with this name, please modify it and try again' +ErrLocalExist: 'The name already exists in the local database, please modify it and try again' +RemoteBackup: "To back up the remote database, the local container database service needs to be started first using the image {{ .name }}, please wait..." +RemoteRecover: "To restore the remote database, the local container database service needs to be started first using the image {{ .name }}, please wait..." +Arg: "Argument" + +#redis +ErrTypeOfRedis: 'The recovery file type does not match the current persistence method, please modify it and try again' + +#container +ErrInUsed: "{{ .detail }} is in use and cannot be deleted!" +ErrObjectInUsed: "This object is in use and cannot be deleted!" +ErrObjectBeDependent: "This image is depended on by other images and cannot be deleted!" +ErrPortRules: 'Port number does not match, please re-enter!' +ErrPgImagePull: 'Image pull timed out, please configure image acceleration or manually pull the {{ .name }} image and try again' +PruneHelper: "This cleanup {{ .name }} freed disk space {{ .size }}" +ImageRemoveHelper: "Deleted image {{ .name }}, freeing {{ .size }} disk space" +BuildCache: "Build cache" +Volume: "Storage volume" +Network: "Network" +PruneStart: "Cleaning in progress, please wait..." + +#runtime +ErrFileNotExist: '{{ .detail }} file does not exist! Please check the integrity of the source file!' +ErrImageBuildErr: 'Image build failed' +ErrImageExist: "Image already exists! Please modify the image name." +ErrDelWithWebsite: 'The operating environment is already associated with a website and cannot be deleted' +ErrRuntimeStart: 'Startup failed' +ErrPackageJsonNotFound: 'package.json file does not exist' +ErrScriptsNotFound: 'The scripts configuration item was not found in package.json' +ErrContainerNameNotFound: 'Unable to get container name, please check .env file' +ErrNodeModulesNotFound: 'The node_modules folder does not exist! Please edit the runtime environment or wait for the runtime environment to start successfully' +ErrContainerNameIsNull: 'Container name does not exist' +ErrPHPPortIsDefault: "Port 9000 is the default port, please modify and try again" +ErrPHPRuntimePortFailed: "The port {{ .name }} is already used by the current runtime environment, please modify and try again" + +#tool +ErrConfigNotFound: 'Configuration file does not exist' +ErrConfigParse: 'The configuration file format is incorrect' +ErrConfigIsNull: 'Configuration file cannot be empty' +ErrConfigDirNotFound: 'The running directory does not exist' +ErrConfigAlreadyExist: 'A configuration file with the same name already exists' +ErrUserFindErr: 'User {{ .name }} search failed {{ .err }}' + +#cronjob +CutWebsiteLogSuccess: '{{ .name }} website log cut successfully, backup path {{ .path }}' +HandleShell: 'Execute script {{ .name }}' +HandleCurl: "Access URL {{ .name }}" +HandleNtpSync: 'System time synchronization' +HandleSystemClean: 'System cache cleanup' +SystemLog: 'System Log' +CutWebsiteLog: 'Rotate Website Log' +FileOrDir: 'Directory / File' +UploadFile: 'Uploading backup file {{ .file }} to {{ .backup }}' +Upload: "Upload" +IgnoreBackupErr: "Backup failed, error: {{ .detail }}, ignoring this error..." +IgnoreUploadErr: "Upload failed, error: {{ .detail }}, ignoring this error..." +LoadBackupFailed: "Failed to get backup account connection, error: {{ .detail }}" +InExecuting: "The current task is being executed, please do not repeat the execution!" +NoSuchResource: "No backup content found in the database, skipping..." +CleanLog: "Clean Log" +CleanLogByName: "Clean {{.name}} Log" +WafIpGroupNotFound: "WAF IP group not found. Please go to [Advanced Features - WAF - Black/White List - IP Group] to add an IP group via remote download method." + +#toolbox +ErrNotExistUser: 'The current user does not exist, please modify and try again!' +ErrBanAction: 'Setting failed. The current {{ .name }} service is unavailable. Please check and try again!' +ErrClamdscanNotFound: 'The clamdscan command was not detected, please refer to the documentation to install it!' +TaskSwapSet: "Set Swap" +SetSwap: "Set Swap {{ .path }} - {{ .size }}" +CreateSwap: "Create Swap File" +FormatSwap: "Format Swap File" +EnableSwap: "Enable Swap" + +#tamper +CleanTamperSetting: "Clean up historical protection settings" +SetTamperWithRules: "Start directory protection according to rules" +ProtectDir: "Protect directory {{ .name }}" +IgnoreFile: "Protect file {{ .name }}" +TamperSetSuccessful: "Protection settings completed, start monitoring the following directories:" + +#waf +ErrScope: 'Modifying this configuration is not supported' +ErrStateChange: 'State change failed' +ErrRuleExist: 'Rule already exists' +ErrRuleNotExist: 'Rule does not exist' +ErrParseIP: 'Wrong IP format' +ErrDefaultIP: 'default is a reserved name, please change it to another name' +ErrGroupInUse: 'IP group is used by blacklist/whitelist and cannot be deleted' +ErrIPGroupAclUse: "IP group is used by custom rules of website {{ .name }}, cannot be deleted" +ErrGroupExist: 'IP group name already exists' +ErrIPRange: 'Wrong IP range' +ErrIPExist: 'IP already exists' +urlDefense: 'URL rules' +urlHelper: 'Prohibited URL' +dirFilter: 'Directory filter' +xss: 'XSS' +phpExec: 'PHP script execution' +oneWordTrojan: 'One Word Trojan' +appFilter: 'Apply dangerous directory filtering' +webshell: 'Webshell' +args: 'Parameter rules' +protocolFilter: 'Protocol filtering' +javaFileter: 'Java dangerous file filter' +scannerFilter: 'Scanner filter' +escapeFilter: 'escape filter' +customRule: 'Custom rule' +httpMethod: 'HTTP method filtering' +fileExt: 'File upload restrictions' +defaultIpBlack: 'Malicious IP group' +cookie: 'Cookie Rules' +urlBlack: 'URL blacklist' +uaBlack: 'User-Agent blacklist' +attackCount: 'Attack frequency limit' +fileExtCheck: 'File upload restrictions' +geoRestrict: 'Regional access restrictions' +unknownWebsite: 'Unauthorized domain name access' +notFoundCount: '404 Rate Limit' +headerDefense: 'Header rules' +defaultUaBlack: 'User-Agent rules' +methodWhite: 'HTTP rules' +captcha: 'human-machine verification' +fiveSeconds: '5 seconds verification' +vulnCheck: 'Supplementary rules' +acl: 'Custom rules' +sql: 'SQL injection' +cc: 'Access frequency limit' +defaultUrlBlack: 'URL rules' +sqlInject: 'SQL injection' +ErrDBNotExist: 'Database does not exist' +allow: 'allow' +deny: 'deny' +OpenrestyNotFound: 'Openresty is not installed' +remoteIpIsNull: "IP list is empty" +OpenrestyVersionErr: "Openresty version is too low, please upgrade Openresty to 1.27.1.2-2-2-focal" + +#task +TaskStart: '{{ .name }} task starts [START]' +TaskEnd: '{{ .name }} Task completed [COMPLETED]' +TaskFailed: '{{ .name }} task failed' +TaskTimeout: '{{ .name }} timed out' +TaskSuccess: '{{ .name }} Task succeeded' +TaskRetry: 'Start {{ .name }}th retry' +SubTaskSuccess: '{{ .name }} succeeded' +SubTaskFailed: '{{ .name }} failed: {{ .err }}' +TaskInstall: 'Install' +TaskUninstall: 'Uninstall' +TaskCreate: 'Create' +TaskDelete: 'Delete' +TaskUpgrade: 'Upgrade' +TaskUpdate: 'Update' +TaskRestart: 'Restart' +TaskProtect: "Protect" +TaskBackup: 'Backup' +TaskRecover: 'Recover' +TaskRollback: 'Rollback' +TaskPull: 'Pull' +TaskCommit: 'Commit' +TaskBuild: 'Build' +TaskPush: 'Push' +TaskClean: "Cleanup" +TaskHandle: 'Execute' +TaskImport: "Import" +TaskExport: "Export" +Website: 'Website' +App: 'Application' +Runtime: 'Runtime environment' +Database: 'Database' +ConfigFTP: 'Create FTP user {{ .name }}' +ConfigOpenresty: 'Create Openresty configuration file' +InstallAppSuccess: 'Application {{ .name }} installed successfully' +ConfigRuntime: 'Configure the runtime environment' +ConfigApp: 'Configuration Application' +SuccessStatus: '{{ .name }} succeeded' +FailedStatus: '{{ .name }} failed {{ .err }}' +HandleLink: 'Handle application association' +HandleDatabaseApp: 'Handling application parameters' +ExecShell: 'Execute {{ .name }} script' +PullImage: 'Pull image' +Start: 'Start' +Run: 'Start' +Stop: 'Stop' +Image: 'Mirror' +Compose: 'Orchestration' +Container: 'Container' +AppLink: 'Linked Application' +EnableSSL: 'Enable HTTPS' +AppStore: 'App Store' +TaskSync: 'Synchronize' +LocalApp: 'Local Application' +SubTask: 'Subtask' +RuntimeExtension: 'Runtime Environment Extension' +TaskIsExecuting: 'Task is running' +CustomAppstore: 'Custom application warehouse' +TaskExec: 'Execute' +TaskBatch: "Batch Operation" +FileConvert: 'File Conversion' + +# task - clam +Clamscan: "Scan {{ .name }}" +TaskScan: "Scan" + +# task - ai +OllamaModelPull: 'Pull Ollama model {{ .name }}' +OllamaModelSize: 'Get the size of Ollama model {{ .name }}' + +# task-snapshot +Snapshot: 'Snapshot' +SnapDBInfo: 'Write 1Panel database information' +SnapCopy: 'Copy files & directories {{ .name }}' +SnapNewDB: 'Initialize database {{ .name }} connection' +SnapDeleteOperationLog: 'Delete operation log' +SnapDeleteLoginLog: 'Delete access log' +SnapDeleteMonitor: 'Delete monitoring data' +SnapRemoveSystemIP: 'Remove system IP' +SnapBaseInfo: 'Write 1Panel basic information' +SnapInstallAppImageEmpty: 'No application images selected, skipping...' +SnapInstallApp: 'Backup 1Panel installed applications' +SnapDockerSave: 'Compress installed applications' +SnapLocalBackup: 'Backup 1Panel local backup directory' +SnapCompressBackup: 'Compress local backup directory' +SnapPanelData: 'Backup 1Panel data directory' +SnapCompressPanel: 'Compressed Data Directory' +SnapWebsite: 'Backup 1Panel website directory' +SnapCloseDBConn: 'Close the database connection' +SnapCompress: 'Make snapshot files' +SnapCompressFile: 'Compress snapshot file' +SnapCheckCompress: 'Check snapshot compression file' +SnapCompressSize: 'Snapshot file size {{ .name }}' +SnapUpload: 'Upload snapshot file' +SnapUploadTo: 'Upload snapshot file to {{ .name }}' +SnapUploadRes: 'Upload snapshot file to {{ .name }}' + +SnapshotRecover: 'Snapshot Restore' +RecoverDownload: 'Download snapshot file' +Download: 'Download' +RecoverDownloadAccount: 'Get snapshot download backup account {{ .name }}' +RecoverDecompress: 'Decompress snapshot compressed files' +Decompress: 'Decompression' +BackupBeforeRecover: 'Backup system-related data before snapshot' +Readjson: 'Read the Json file in the snapshot' +ReadjsonPath: 'Get the Json file path in the snapshot' +ReadjsonContent: 'Read Json file' +ReadjsonMarshal: 'Json escape processing' +RecoverApp: 'Restore installed apps' +RecoverWebsite: 'Recover website directory' +RecoverAppImage: 'Restore snapshot image backup' +RecoverCompose: 'Restore other composer content' +RecoverComposeList: 'Get all composers to be restored' +RecoverComposeItem: 'Recover compose {{ .name }}' +RecoverAppEmpty: 'No application image backup was found in the snapshot file' +RecoverBaseData: 'Recover base data and files' +RecoverDaemonJsonEmpty: 'Both the snapshot file and the current machine do not have the container configuration daemon.json file' +RecoverDaemonJson: 'Restore container configuration daemon.json file' +RecoverDBData: 'Recover database data' +RecoverBackups: 'Restore local backup directory' +RecoverPanelData: 'Recovery data directory' + +# task - container +ContainerNewCliet: 'Initialize Docker Client' +ContainerImagePull: 'Pull container image {{ .name }}' +ContainerRemoveOld: 'Remove the original container {{ .name }}' +ContainerImageCheck: 'Check if the image is pulled normally' +ContainerLoadInfo: 'Get basic container information' +ContainerRecreate: 'Container update failed, now starting to restore the original container' +ContainerCreate: 'Create a new container {{ .name }}' +ContainerCreateFailed: 'Container creation failed, delete the failed container' +ContainerStartCheck: 'Check if the container has been started' + +# task - image +ImageBuild: 'Image Build' +ImageBuildStdoutCheck: 'Parse image output content' +ImageBuildRes: 'Image build output: {{ .name }}' +ImagePull: 'Pull image' +ImageRepoAuthFromDB: 'Get repository authentication information from the database' +ImaegPullRes: 'Image pull output: {{ .name }}' +ImagePush: 'Push image' +ImageRenameTag: 'Modify image tag' +ImageNewTag: 'New image tag {{ .name }}' +ImaegPushRes: 'Image push output: {{ .name }}' +ComposeCreate: 'Create a composition' +ComposeCreateRes: 'Compose create output: {{ .name }}' + +# task - website +BackupNginxConfig: 'Backup website OpenResty configuration file' +CompressFileSuccess: 'Compress directory successfully, compressed to {{ .name }}' +CompressDir: 'Compression directory' +DeCompressFile: 'Decompress file {{ .name }}' +ErrCheckValid: 'Backup file verification failed, {{ .name }}' +Rollback: 'Rollback' +websiteDir: 'Website Directory' +RecoverFailedStartRollBack: 'Recovery failed, start rollback' +AppBackupFileIncomplete: 'The backup file is incomplete and lacks app.json or app.tar.gz files' +AppAttributesNotMatch: 'Application type or name does not match' + +#alert +ErrAlert: 'The format of the warning message is incorrect, please check and try again!' +ErrAlertPush: 'Error in pushing alert information, please check and try again!' +ErrAlertSave: 'Error saving the alarm information, please check and try again!' +ErrAlertSync: 'Alarm information synchronization error, please check and try again!' +ErrAlertRemote: 'Alarm message remote error, please check and try again!' + +#task - runtime +ErrInstallExtension: "An installation task is already in progress, please wait for the task to finish" + +# alert mail template +PanelAlertTitle: "Panel Alert Notification" +TestAlertTitle: "Test Email - Verify Email Connectivity" +TestAlert: "This is a test email to verify that your email sending configuration is correct." +LicenseExpirationAlert: "Your {{ .node }}{{ .ip }} Panel, the license will expire in {{ .day }} days. Please log in to the panel for details." +CronJobFailedAlert: "Your {{ .node }}{{ .ip }} Panel, scheduled task - {{ .name }} execution failed. Please log in to the panel for details." +ClamAlert: "Your {{ .node }}{{ .ip }} Panel, the virus scan task detected {{ .num }} infected files. Please log in to the panel for details." +WebSiteAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} websites will expire in {{ .day }} days. Please log in to the panel for details." +SSLAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} SSL certificates will expire in {{ .day }} days. Please log in to the panel for details." +DiskUsedAlert: "Your {{ .node }}{{ .ip }} Panel, disk {{ .name }} usage has reached {{ .used }}. Please log in to the panel for details." +ResourceAlert: "Your {{ .node }}{{ .ip }} Panel, the average {{ .name }} usage rate in {{ .time }} minutes is {{ .used }}. Please log in to the panel for details." +PanelVersionAlert: "Your {{ .node }}{{ .ip }} Panel, the latest panel version is available for upgrade. Please log in to the panel for details." +PanelPwdExpirationAlert: "Your {{ .node }}{{ .ip }} Panel, the panel password will expire in {{ .day }} days. Please log in to the panel for details." +CommonAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .msg }}. Please log in to the panel for details." +NodeExceptionAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} nodes are abnormal. Please log in to the panel for details." +LicenseExceptionAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .num }} licenses are abnormal. Please log in to the panel for details." +SSHAndPanelLoginAlert: "Your {{ .node }}{{ .ip }} Panel, {{ .name }} login from {{ .loginIp }} is abnormal. Please log in to the panel for details." + +#disk +DeviceNotFound: "Device {{ .name }} not found" +DeviceIsMounted: "Device {{ .name }} is mounted, please unmount first" +PartitionDiskErr: "Failed to partition, {{ .err }}" +FormatDiskErr: "Failed to format disk, {{ .err }}" +MountDiskErr: "Failed to mount disk, {{ .err }}" +UnMountDiskErr: "Failed to unmount disk, {{ .err }}" +XfsNotFound: "xfs filesystem not detected, please install xfsprogs first" \ No newline at end of file diff --git a/agent/i18n/lang/es-ES.yaml b/agent/i18n/lang/es-ES.yaml new file mode 100644 index 0000000..73125fb --- /dev/null +++ b/agent/i18n/lang/es-ES.yaml @@ -0,0 +1,528 @@ +ErrInvalidParams: 'Error en los parámetros de la solicitud: {{ .detail }}' +ErrTokenParse: 'Error al generar el token: {{ .detail }}' +ErrInitialPassword: 'La contraseña original es incorrecta' +ErrInternalServer: 'Error interno del servidor: {{ .detail }}' +ErrRecordExist: 'El registro ya existe' +ErrRecordNotFound: 'Registro no encontrado' +ErrStructTransform: 'Error de conversión de tipo: {{ .err }}' +ErrNotLogin: 'Usuario no ha iniciado sesión: {{ .detail }}' +ErrPasswordExpired: 'La contraseña actual ha expirado: {{ .detail }}' +ErrNotSupportType: 'El sistema no admite el tipo actual: {{ .name }}' +ErrProxy: 'Error en la solicitud, por favor revise el estado del nodo: {{ .detail }}' +ErrApiConfigStatusInvalid: 'Se prohíbe el acceso a la interfaz API: {{ .detail }}' +ErrApiConfigKeyInvalid: 'Error en la clave de la interfaz API: {{ .detail }}' +ErrApiConfigIPInvalid: 'La IP usada para llamar a la API no está en la lista blanca: {{ .detail }}' +ErrApiConfigDisable: 'Esta interfaz prohíbe el uso de llamadas a la API: {{ .detail }}' +ErrApiConfigKeyTimeInvalid: 'Error en la marca de tiempo de la interfaz API: {{ .detail }}' +ErrShutDown: "¡Comando terminado manualmente!" + +ErrMinQuickJump: "¡Por favor configure al menos una entrada de acceso rápido!" +ErrMaxQuickJump: "¡Puede configurar hasta cuatro entradas de acceso rápido!" + +#común +ErrUsernameIsExist: 'El nombre de usuario ya existe' +ErrNameIsExist: 'El nombre ya existe' +ErrDemoEnvironment: '¡Servidor de demostración, esta operación está prohibida!' +ErrCmdTimeout: '¡Tiempo de espera excedido en la ejecución del comando!' +ErrCmdIllegal: 'Hay caracteres ilegales en el comando, ¡modifíquelo e intente de nuevo!' +ErrPortExist: 'El puerto {{ .port }} ya está ocupado por {{ .type }} [{{ .name }}]' +TYPE_APP: 'Aplicación' +TYPE_RUNTIME: 'Entorno de ejecución' +TYPE_DOMAIN: 'Nombre de dominio' +ErrTypePort: 'El formato del puerto {{ .name }} es incorrecto' +ErrTypePortRange: 'El rango de puertos debe estar entre 1-65535' +Success: 'Éxito' +Failed: 'Error' +SystemRestart: 'La tarea fue interrumpida debido a un reinicio del sistema' +ErrGroupIsDefault: 'Grupo predeterminado, no se puede eliminar' +ErrGroupIsInWebsiteUse: 'El grupo está siendo usado por otro sitio web y no se puede eliminar.' +Decrypt: "Descifrar" + +#backup +ErrBackupInUsed: 'La cuenta de respaldo está siendo utilizada en una tarea programada y no se puede eliminar.' +ErrBackupCheck: 'Error al probar la conexión de la cuenta de respaldo {{ .err }}' +ErrBackupLocalDelete: 'No se admite aún la eliminación de la cuenta de respaldo del servidor local' +ErrBackupLocalCreate: 'No se admite aún la creación de cuentas de respaldo del servidor local' +Localhost: "Máquina local" + +#app +ErrPortInUsed: '¡El puerto {{ .detail }} ya está ocupado!' +ErrAppLimit: 'El número de aplicaciones instaladas ha superado el límite' +ErrNotInstall: 'Aplicación no instalada' +ErrPortInOtherApp: '¡El puerto {{ .port }} ya está ocupado por la aplicación {{ .apps }}!' +ErrDbUserNotValid: '¡Base de datos existente, usuario y contraseña no coinciden!' +ErrUpdateBuWebsite: 'La aplicación se actualizó correctamente, pero falló la modificación del archivo de configuración del sitio web. ¡Por favor revise la configuración!' +Err1PanelNetworkFailed: '¡Fallo la creación de la red por defecto del contenedor! {{ .detail }}' +ErrFileParse: '¡Fallo al analizar el archivo docker-compose de la aplicación!' +ErrInstallDirNotFound: 'El directorio de instalación no existe. Si desea desinstalar, seleccione Desinstalación forzada' +AppStoreIsUpToDate: 'La tienda de aplicaciones ya está en la última versión' +LocalAppVersionNull: '¡La aplicación {{ .name }} no está sincronizada a la versión! No se puede agregar a la lista de aplicaciones' +LocalAppVersionErr: '¡La sincronización de la versión {{ .version }} de {{ .name }} falló! {{ .err }}' +ErrFileNotFound: 'El archivo {{ .name }} no existe' +ErrFileParseApp: '¡El archivo {{ .name }} falló al analizarse! {{ .err }}' +ErrAppDirNull: 'La carpeta de la versión no existe' +LocalAppErr: '¡La sincronización de la aplicación {{ .name }} falló! {{ .err }}' +ErrContainerName: 'El nombre del contenedor ya existe' +ErrCreateHttpClient: 'Fallo al crear la solicitud {{ .err }}' +ErrHttpReqTimeOut: 'La solicitud superó el tiempo de espera {{ .err }}' +ErrHttpReqFailed: 'La solicitud falló {{ .err }}' +ErrNoSuchHost: 'No se pudo encontrar el servidor solicitado {{ .err }}' +ErrHttpReqNotFound: 'No se pudo encontrar el recurso solicitado {{ .err }}' +ErrContainerNotFound: 'El contenedor {{ .name }} no existe' +ErrContainerMsg: 'El contenedor {{ .name }} presenta anomalías. Revise el log en la página de contenedores para más detalles' +ErrAppBackup: 'Falló el respaldo de la aplicación {{ .name }} {{ .err }}' +ErrVersionTooLow: 'La versión actual de 1Panel es demasiado baja para actualizar la tienda de aplicaciones. Actualice primero.' +ErrAppNameExist: 'El nombre de la aplicación ya existe' +AppStoreIsSyncing: 'La tienda de aplicaciones está sincronizando, inténtelo más tarde' +ErrGetCompose: '¡Fallo al obtener el archivo docker-compose.yml! {{ .detail }}' +ErrAppWarn: 'Estado anómalo, revise el log' +ErrAppParamKey: 'El campo de parámetro {{ .name }} es anómalo' +ErrAppUpgrade: 'La actualización de la aplicación {{ .name }} falló {{ .err }}' +AppRecover: 'Revertir la aplicación {{ .name }}' +PullImageStart: 'Iniciar descarga de la imagen [{{ .name }}]' +PullImageSuccess: 'Descarga de imagen exitosa' +AppStoreIsLastVersion: 'La tienda de aplicaciones ya está en la última versión' +AppStoreSyncSuccess: 'Sincronización de la tienda de aplicaciones exitosa' +SyncAppDetail: 'Sincronizar configuración de la aplicación' +AppVersionNotMatch: 'La aplicación {{ .name }} requiere una versión superior de 1Panel, se omite la sincronización' +MoveSiteDir: 'La actualización actual requiere migrar el directorio del sitio web OpenResty' +MoveSiteDirSuccess: 'Migración del directorio del sitio exitosa' +DeleteRuntimePHP: 'Eliminar entorno de ejecución PHP' +CustomAppStoreFileValid: 'Los paquetes de la tienda de aplicaciones deben estar en formato .tar.gz' +PullImageTimeout: 'Tiempo de espera al descargar la imagen, intente aumentar la aceleración o use otra' +ErrAppIsDown: 'El estado de la aplicación {{ .name }} es anómalo, revise' +ErrCustomApps: 'Hay una aplicación instalada, desinstálela primero' +ErrCustomRuntimes: 'Hay un entorno de ejecución instalado, elimínelo primero' +ErrAppVersionDeprecated: "La aplicación {{ .name }} no es compatible con la versión actual de 1Panel, omitida" +ErrDockerFailed: "El estado de Docker es anómalo, revise el servicio" +ErrDockerComposeCmdNotFound: "El comando Docker Compose no existe, por favor instálelo primero en el host" +UseExistImage: 'Error al extraer la imagen, usando imagen existente' + +#ssh +ExportIP: "IP de inicio de sesión" +ExportArea: "Ubicación" +ExportPort: "Puerto" +ExportAuthMode: "Método de inicio de sesión" +ExportUser: "Usuario" +ExportStatus: "Estado de inicio de sesión" +ExportDate: "Fecha y hora" + +#file +ErrFileCanNotRead: 'Este archivo no soporta vista previa' +ErrFileToLarge: 'El archivo es mayor a 10M y no puede abrirse' +ErrPathNotFound: 'El directorio no existe' +ErrMovePathFailed: '¡La ruta de destino no puede contener la ruta original!' +ErrLinkPathNotFound: '¡La ruta de destino no existe!' +ErrFileIsExist: '¡El archivo o carpeta ya existe!' +ErrFileUpload: '{{ .name }} falló al subir archivo {{ .detail }}' +ErrFileDownloadDir: 'No se admite la descarga de carpetas' +ErrCmdNotFound: 'El comando {{ .name }} no existe, instálelo primero en el host' +ErrSourcePathNotFound: 'El directorio fuente no existe' +ErrFavoriteExist: 'Esta ruta ya ha sido marcada como favorita' +ErrInvalidChar: 'No se permiten caracteres ilegales' +ErrPathNotDelete: 'No se puede eliminar el directorio seleccionado' +FileDropFailed: "Error al limpiar el archivo {{ .name }}: {{ .err }}" +FileDropSuccess: "Archivo {{ .name }} limpiado correctamente, {{ .count }} archivos eliminados, {{ .size }} de espacio liberado" +FileDropSum: "Limpieza de archivos completada, total {{ .count }} archivos eliminados, {{ .size }} de espacio liberado" +ErrBadDecrypt: "Error de contraseña de descifrado del paquete comprimido, descompresión fallida, ¡compruebe e inténtelo de nuevo!" + +#website +ErrAliasIsExist: 'El alias ya existe' +ErrBackupMatch: 'El archivo de respaldo no coincide con parte de los datos actuales del sitio web {{ .detail }}' +ErrBackupExist: 'Parte de los datos fuente del respaldo no existe {{ .detail }}' +ErrPHPResource: '¡El entorno local no soporta cambio!' +ErrPathPermission: 'Se detectó una carpeta con permisos distintos a 1000:1000 en el directorio index, lo cual puede causar errores de acceso denegado. Haga clic en Guardar arriba' +ErrDomainIsUsed: 'El dominio ya está en uso por el sitio web [{{ .name }}]' +ErrDomainFormat: 'El formato del dominio {{ .name }} es incorrecto' +ErrDefaultAlias: 'default es un código reservado, use otro' +ErrParentWebsite: 'Primero debe eliminar el sub-sitio {{ .name }}' +ErrBuildDirNotFound: 'El directorio de compilación no existe' +ErrImageNotExist: 'La imagen del entorno {{ .name }} no existe, edítela de nuevo' +ErrProxyIsUsed: "El balanceo de carga ya está usado por un proxy reverso, no se puede eliminar" +ErrSSLValid: 'Archivo de certificado anómalo, ¡revise el estado del certificado!' +ErrWebsiteDir: "Por favor seleccione un directorio dentro del directorio del sitio web" +ErrComposerFileNotFound: "El archivo composer.json no existe" +ErrRuntimeNoPort: "El entorno de ejecución no tiene configurado un puerto, edítelo primero" +Status: 'Estado' +start: 'Iniciar' +stop: 'Detener' +restart: 'Reiniciar' +kill: 'Detener Forzosamente' +pause: 'Pausar' +unpause: 'Reanudar' +remove: 'Eliminar' +delete: 'Eliminar' +ErrDefaultWebsite: 'El sitio web predeterminado ya está configurado, ¡cancélelo antes de configurar!' +SSL: 'Certificado' +Set: 'Configuración' + +#ssl +ErrSSLCannotDelete: 'El certificado {{ .name }} está siendo utilizado por un sitio web y no puede eliminarse' +ErrAccountCannotDelete: 'La cuenta está asociada a un certificado y no puede eliminarse' +ErrSSLApply: 'Renovación del certificado exitosa, recarga de openresty fallida, ¡revise la configuración!' +ErrEmailIsExist: 'El correo ya existe' +ErrSSLKeyNotFound: 'El archivo de clave privada no existe' +ErrSSLCertificateNotFound: 'El archivo de certificado no existe' +ErrSSLKeyFormat: 'Error en la verificación del archivo de clave privada' +ErrSSLCertificateFormat: 'Formato de certificado incorrecto, use formato pem' +ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid o EabHmacKey no pueden estar en blanco' +ErrOpenrestyNotFound: 'El modo Http requiere tener Openresty instalado primero' +ApplySSLStart: 'Iniciando solicitud de certificado, dominio [{{ .domain }}] método de solicitud [{{ .type }}]' +dnsAccount: 'DNS Automático' +dnsManual: 'DNS Manual' +http: 'HTTP' +ApplySSLFailed: 'Solicitud de certificado para [{{ .domain }}] fallida, {{ .detail }}' +ApplySSLSuccess: '¡Solicitud de certificado para [{{ .domain }}] exitosa!' +DNSAccountName: 'Cuenta DNS [{{ .name }}] proveedor [{{ .type }}]' +PushDirLog: 'Certificado empujado al directorio [{{ .path }}] {{ .status }}' +ErrDeleteWithPanelSSL: 'La configuración SSL del panel utiliza este certificado y no puede eliminarse' +ErrDefaultCA: 'La autoridad por defecto no puede eliminarse' +ApplyWebSiteSSLLog: 'Iniciando renovación del certificado del sitio web {{ .name }}' +ErrUpdateWebsiteSSL: 'Actualización de certificado del sitio web {{ .name }} falló: {{ .err }}' +ApplyWebSiteSSLSuccess: 'Certificado del sitio web actualizado exitosamente' +ErrExecShell: 'Ejecución del script fallida {{ .err }}' +ExecShellStart: 'Iniciando ejecución de script' +ExecShellSuccess: 'Ejecución de script exitosa' +StartUpdateSystemSSL: 'Iniciando actualización del certificado del sistema' +UpdateSystemSSLSuccess: 'Certificado del sistema actualizado correctamente' +ErrWildcardDomain: 'No se puede solicitar certificado de dominio wildcard en modo HTTP' +ErrDeleteCAWithSSL: "La organización actual tiene un certificado emitido y no puede eliminarse." +ErrApplySSLCanNotDelete: "El certificado {{ .name }} en proceso de solicitud no puede eliminarse, inténtelo más tarde" +StartPushSSLToNode: "Iniciando envío de certificado al nodo" +PushSSLToNodeFailed: "Error al enviar el certificado al nodo: {{ .err }}" +PushSSLToNodeSuccess: "Certificado enviado correctamente al nodo" + +#mysql +ErrUserIsExist: 'El usuario actual ya existe, intente con otro' +ErrDatabaseIsExist: 'La base de datos actual ya existe, intente con otro nombre' +ErrExecTimeOut: 'Tiempo de espera en la ejecución SQL, revise la base de datos' +ErrRemoteExist: 'La base de datos remota ya existe con ese nombre, modifíquelo e intente de nuevo' +ErrLocalExist: 'El nombre ya existe en la base de datos local, modifíquelo e intente de nuevo' +RemoteBackup: "Para hacer una copia de seguridad de la base de datos remota, primero debe iniciarse el servicio de base de datos del contenedor local utilizando la imagen {{ .name }}, espere por favor..." +RemoteRecover: "Para restaurar la base de datos remota, primero debe iniciarse el servicio de base de datos del contenedor local utilizando la imagen {{ .name }}, espere por favor..." +Arg: "Argumento" + +#redis +ErrTypeOfRedis: 'El tipo de archivo de recuperación no coincide con el método de persistencia actual, modifíquelo e intente' + +#container +ErrInUsed: "{{ .detail }} está en uso y no se puede eliminar!" +ErrObjectInUsed: "Este objeto está en uso y no se puede eliminar!" +ErrObjectBeDependent: "Esta imagen es dependida por otras imágenes y no se puede eliminar!" +ErrPortRules: 'El número de puerto no coincide, ingrese de nuevo' +ErrPgImagePull: 'Tiempo de espera al descargar la imagen, configure la aceleración o descárguela manualmente ({{ .name }}) e intente de nuevo' +PruneHelper: "Esta limpieza {{ .name }} liberó espacio en disco {{ .size }}" +ImageRemoveHelper: "Imagen {{ .name }} eliminada, liberando {{ .size }} de espacio en disco" +BuildCache: "Caché de compilación" +Volume: "Volumen de almacenamiento" +Network: "Red" +PruneStart: "Limpiando en progreso, por favor espere..." + +#runtime +ErrFileNotExist: 'El archivo {{ .detail }} no existe, ¡verifique la integridad!' +ErrImageBuildErr: 'Fallo al construir la imagen' +ErrImageExist: "¡La imagen ya existe! Modifique el nombre de la imagen." +ErrDelWithWebsite: 'El entorno de ejecución ya está asociado a un sitio web y no puede eliminarse' +ErrRuntimeStart: 'Fallo al iniciar' +ErrPackageJsonNotFound: 'El archivo package.json no existe' +ErrScriptsNotFound: 'No se encontró la configuración de scripts en package.json' +ErrContainerNameNotFound: 'No se puede obtener el nombre del contenedor, revise el archivo .env' +ErrNodeModulesNotFound: 'La carpeta node_modules no existe, edite o espere a que inicie' +ErrContainerNameIsNull: 'El nombre del contenedor no existe' +ErrPHPPortIsDefault: "El puerto 9000 es el predeterminado, modifique e intente de nuevo" +ErrPHPRuntimePortFailed: "El puerto {{ .name }} ya está en uso por el entorno actual, modifique e intente" + +#tool +ErrConfigNotFound: 'El archivo de configuración no existe' +ErrConfigParse: 'El formato del archivo de configuración es incorrecto' +ErrConfigIsNull: 'El archivo de configuración no puede estar vacío' +ErrConfigDirNotFound: 'El directorio de ejecución no existe' +ErrConfigAlreadyExist: 'Ya existe un archivo de configuración con ese nombre' +ErrUserFindErr: 'Búsqueda del usuario {{ .name }} fallida {{ .err }}' + +#cronjob +CutWebsiteLogSuccess: 'Log del sitio web {{ .name }} cortado con éxito, ruta de respaldo {{ .path }}' +HandleShell: 'Ejecutar script {{ .name }}' +HandleCurl: "URL {{ .name }} にアクセス" +HandleNtpSync: 'Sincronizar hora del sistema' +HandleSystemClean: 'Limpiar caché del sistema' +SystemLog: 'Log del sistema' +CutWebsiteLog: 'Rotar log del sitio web' +FileOrDir: 'Directorio / Archivo' +UploadFile: 'Subiendo archivo de respaldo {{ .file }} a {{ .backup }}' +IgnoreBackupErr: 'Fallo en el respaldo, error: {{ .detail }}, se ignora este error...' +IgnoreUploadErr: 'Fallo en la subida, error: {{ .detail }}, se ignora este error...' +Upload: "Subir" +LoadBackupFailed: "Error al obtener la conexión de la cuenta de respaldo, error: {{ .detail }}" +InExecuting: "La tarea actual se está ejecutando, por favor no la repita" +NoSuchResource: "No se encontraron contenidos de respaldo en la base de datos, omitiendo..." +CleanLog: "Limpiar registro" +CleanLogByName: "Limpiar registro de {{.name}}" +WafIpGroupNotFound: "Grupo de IP de WAF no encontrado. Por favor, vaya a [Funciones Avanzadas - WAF - Lista Blanca/Negra - Grupo de IP] para agregar un grupo de IP mediante el método de descarga remota." + +#toolbox +ErrNotExistUser: 'El usuario actual no existe, modifíquelo e intente de nuevo' +ErrBanAction: 'Fallo al configurar. El servicio {{ .name }} no está disponible, revise e intente de nuevo' +ErrClamdscanNotFound: 'El comando clamdscan no fue detectado, siga la documentación para instalarlo' +TaskSwapSet: "Configurar Swap" +SetSwap: "Configurar Swap {{ .path }} - {{ .size }}" +CreateSwap: "Crear Archivo Swap" +FormatSwap: "Formatear Archivo Swap" +EnableSwap: "Habilitar Swap" + +#tamper +CleanTamperSetting: "Limpiar configuraciones de protección históricas" +SetTamperWithRules: "Iniciar protección de directorio según reglas" +ProtectDir: "Proteger directorio {{ .name }}" +IgnoreFile: "Proteger archivo {{ .name }}" +TamperSetSuccessful: "Configuración de protección completada, comenzando monitoreo de los siguientes directorios:" + +#waf +ErrScope: 'No se admite la modificación de esta configuración' +ErrStateChange: 'Fallo al cambiar de estado' +ErrRuleExist: 'La regla ya existe' +ErrRuleNotExist: 'La regla no existe' +ErrParseIP: 'Formato de IP incorrecto' +ErrDefaultIP: 'default es un nombre reservado, cámbielo' +ErrGroupInUse: 'El grupo IP está en uso por listas negras/blancas y no se puede eliminar' +ErrIPGroupAclUse: "El grupo IP está en uso por reglas personalizadas del sitio web {{ .name }}, no puede eliminarse" +ErrGroupExist: 'El nombre del grupo IP ya existe' +ErrIPRange: 'Rango de IP incorrecto' +ErrIPExist: 'La IP ya existe' +urlDefense: 'Reglas de URL' +urlHelper: 'URL prohibida' +dirFilter: 'Filtro de directorio' +xss: 'XSS' +phpExec: 'Ejecución de scripts PHP' +oneWordTrojan: 'Troyano de una sola palabra' +appFilter: 'Filtrar directorios peligrosos' +webshell: 'Webshell' +args: 'Reglas de parámetros' +protocolFilter: 'Filtrado de protocolos' +javaFileter: 'Filtrado de archivos Java peligrosos' +scannerFilter: 'Filtrado de escáneres' +escapeFilter: 'Filtro de escape' +customRule: 'Regla personalizada' +httpMethod: 'Filtrado de métodos HTTP' +fileExt: 'Restricciones de carga de archivos' +defaultIpBlack: 'Grupo de IPs maliciosas' +cookie: 'Reglas de Cookie' +urlBlack: 'Lista negra de URLs' +uaBlack: 'Lista negra de User-Agent' +attackCount: 'Límite de frecuencia de ataques' +fileExtCheck: 'Restricción de carga de archivos' +geoRestrict: 'Restricciones de acceso regional' +unknownWebsite: 'Acceso no autorizado al dominio' +notFoundCount: 'Límite de tasa 404' +headerDefense: 'Reglas de encabezado' +defaultUaBlack: 'Reglas de User-Agent' +methodWhite: 'Reglas HTTP' +captcha: 'Verificación humano-máquina' +fiveSeconds: 'Verificación de 5 segundos' +vulnCheck: 'Reglas suplementarias' +acl: 'Reglas personalizadas' +sql: 'Inyección SQL' +cc: 'Límite de frecuencia de acceso' +defaultUrlBlack: 'Reglas de URL' +sqlInject: 'Inyección SQL' +ErrDBNotExist: 'La base de datos no existe' +allow: 'permitir' +deny: 'denegar' +OpenrestyNotFound: 'Openresty no está instalado' +remoteIpIsNull: "La lista de IP está vacía" +OpenrestyVersionErr: "La versión de Openresty es demasiado baja, actualice a 1.27.1.2-2-2-focal" + +#task +TaskStart: 'La tarea {{ .name }} inicia [INICIO]' +TaskEnd: 'La tarea {{ .name }} completada [COMPLETADA]' +TaskFailed: 'La tarea {{ .name }} falló' +TaskTimeout: 'La tarea {{ .name }} excedió el tiempo' +TaskSuccess: 'La tarea {{ .name }} se realizó con éxito' +TaskRetry: 'Iniciar el {{ .name }} reintento' +SubTaskSuccess: '{{ .name }} exitoso' +SubTaskFailed: '{{ .name }} falló: {{ .err }}' +TaskInstall: 'Instalar' +TaskUninstall: 'Desinstalar' +TaskCreate: 'Crear' +TaskProtect: "Proteger" +TaskDelete: 'Eliminar' +TaskUpgrade: 'Actualizar' +TaskUpdate: 'Actualizar' +TaskRestart: 'Reiniciar' +TaskBackup: 'Respaldar' +TaskRecover: 'Recuperar' +TaskRollback: 'Revertir' +TaskPull: 'Descargar' +TaskCommit: 'Commit' +TaskBuild: 'Construir' +TaskPush: 'Subir' +TaskHandle: 'Ejecutar' +TaskImport: "Importar" +TaskExport: "Exportar" +Website: 'Sitio web' +App: 'Aplicación' +Runtime: 'Entorno de ejecución' +Database: 'Base de datos' +ConfigFTP: 'Crear usuario FTP {{ .name }}' +ConfigOpenresty: 'Crear archivo de configuración Openresty' +InstallAppSuccess: 'Aplicación {{ .name }} instalada correctamente' +ConfigRuntime: 'Configurar entorno de ejecución' +ConfigApp: 'Configurar aplicación' +SuccessStatus: '{{ .name }} exitoso' +FailedStatus: '{{ .name }} falló {{ .err }}' +HandleLink: 'Gestionar asociación de aplicación' +HandleDatabaseApp: 'Gestionar parámetros de aplicación' +ExecShell: 'Ejecutar script {{ .name }}' +PullImage: 'Descargar imagen' +Start: 'Iniciar' +Run: 'Ejecutar' +Stop: 'Detener' +Image: 'Imagen' +Compose: 'Orquestación' +Container: 'Contenedor' +AppLink: 'Aplicación enlazada' +EnableSSL: 'Habilitar HTTPS' +AppStore: 'Tienda de aplicaciones' +TaskSync: 'Sincronizar' +LocalApp: 'Aplicación local' +SubTask: 'Subtarea' +RuntimeExtension: 'Extensión del entorno de ejecución' +TaskIsExecuting: 'Tarea en ejecución' +CustomAppstore: 'Almacén de aplicaciones personalizado' +TaskClean: "Limpieza" +TaskExec: "Ejecutar" +TaskBatch: "Operación por Lotes" +FileConvert: 'Conversión de Formato de Archivo' + +# task - ai +OllamaModelPull: 'Descargar modelo Ollama {{ .name }}' +OllamaModelSize: 'Obtener tamaño del modelo Ollama {{ .name }}' + +# task - clam +Clamscan: "Escanear {{ .name }}" +TaskScan: "Escaneo" + +# task-snapshot +Snapshot: 'Instantánea' +SnapDBInfo: 'Escribir información de base de datos de 1Panel' +SnapCopy: 'Copiar archivos y directorios {{ .name }}' +SnapNewDB: 'Inicializar conexión a base de datos {{ .name }}' +SnapDeleteOperationLog: 'Eliminar log de operaciones' +SnapDeleteLoginLog: 'Eliminar log de accesos' +SnapDeleteMonitor: 'Eliminar datos de monitoreo' +SnapRemoveSystemIP: 'Eliminar IP del sistema' +SnapBaseInfo: 'Escribir información básica de 1Panel' +SnapInstallAppImageEmpty: 'No se seleccionaron imágenes de aplicaciones, omitiendo...' +SnapInstallApp: 'Respaldar aplicaciones instaladas de 1Panel' +SnapDockerSave: 'Comprimir aplicaciones instaladas' +SnapLocalBackup: 'Respaldar directorio local de 1Panel' +SnapCompressBackup: 'Comprimir directorio local de respaldo' +SnapPanelData: 'Respaldar directorio de datos de 1Panel' +SnapCompressPanel: 'Directorio de datos comprimido' +SnapWebsite: 'Respaldar directorio de sitios web de 1Panel' +SnapCloseDBConn: 'Cerrar conexión de base de datos' +SnapCompress: 'Crear archivos de instantánea' +SnapCompressFile: 'Comprimir archivo de instantánea' +SnapCheckCompress: 'Verificar archivo de compresión de instantánea' +SnapCompressSize: 'Tamaño del archivo de instantánea {{ .name }}' +SnapUpload: 'Subir archivo de instantánea' +SnapUploadTo: 'Subir archivo de instantánea a {{ .name }}' + +SnapshotRecover: 'Restaurar instantánea' +RecoverDownload: 'Descargar archivo de instantánea' +Download: 'Descargar' +RecoverDownloadAccount: 'Obtener cuenta de respaldo para descargar instantánea {{ .name }}' +RecoverDecompress: 'Descomprimir archivos de instantánea' +Decompress: 'Descompresión' +BackupBeforeRecover: 'Respaldar datos del sistema antes de restaurar instantánea' +Readjson: 'Leer archivo Json de la instantánea' +ReadjsonPath: 'Obtener ruta del archivo Json en la instantánea' +ReadjsonContent: 'Leer archivo Json' +ReadjsonMarshal: 'Procesar escape de Json' +RecoverApp: 'Restaurar aplicaciones instaladas' +RecoverWebsite: 'Recuperar directorio del sitio web' +RecoverAppImage: 'Restaurar imagen de la instantánea' +RecoverCompose: 'Restaurar otros contenidos de compose' +RecoverComposeList: 'Obtener todos los compose a restaurar' +RecoverComposeItem: 'Restaurar compose {{ .name }}' +RecoverAppEmpty: 'No se encontraron imágenes de respaldo de aplicaciones en la instantánea' +RecoverBaseData: 'Recuperar datos y archivos básicos' +RecoverDaemonJsonEmpty: 'El archivo daemon.json no existe ni en la instantánea ni en el equipo actual' +RecoverDaemonJson: 'Restaurar archivo daemon.json de configuración de contenedor' +RecoverDBData: 'Restaurar datos de base de datos' +RecoverBackups: 'Restaurar directorio local de respaldo' +RecoverPanelData: 'Recuperar directorio de datos' + +# task - container +ContainerNewCliet: 'Inicializar cliente Docker' +ContainerImagePull: 'Descargar imagen de contenedor {{ .name }}' +ContainerRemoveOld: 'Eliminar el contenedor original {{ .name }}' +ContainerImageCheck: 'Comprobar si la imagen se descargó correctamente' +ContainerLoadInfo: 'Obtener información básica del contenedor' +ContainerRecreate: 'Actualización del contenedor fallida, restaurando el contenedor original' +ContainerCreate: 'Crear nuevo contenedor {{ .name }}' +ContainerCreateFailed: 'Falló la creación del contenedor, eliminando el contenedor fallido' +ContainerStartCheck: 'Verificar si el contenedor ha iniciado' + +# task - image +ImageBuild: 'Construcción de imagen' +ImageBuildStdoutCheck: 'Analizar salida de la imagen' +ImageBuildRes: 'Salida de construcción de imagen: {{ .name }}' +ImagePull: 'Descargar imagen' +ImagePush: 'Subir imagen' +ImageRenameTag: 'Modificar etiqueta de imagen' +ImageNewTag: 'Nueva etiqueta de imagen {{ .name }}' +ComposeCreate: 'Crear orquestación' +ComposeCreateRes: 'Salida de creación de orquestación: {{ .name }}' +ImageRepoAuthFromDB: "Obtener información de autenticación del repositorio desde la base de datos" +ImaegPullRes: "Salida de descarga de imagen: {{ .name }}" +ImaegPushRes: "Salida de subida de imagen: {{ .name }}" + +# task - website +BackupNginxConfig: 'Respaldar archivo de configuración OpenResty' +CompressFileSuccess: 'Directorio comprimido con éxito, guardado en {{ .name }}' +CompressDir: 'Directorio comprimido' +DeCompressFile: 'Descomprimir archivo {{ .name }}' +ErrCheckValid: 'Verificación de archivo de respaldo fallida, {{ .name }}' +Rollback: 'Revertir' +websiteDir: 'Directorio del sitio web' +RecoverFailedStartRollBack: 'Restauración fallida, iniciando reversión' +AppBackupFileIncomplete: 'El archivo de respaldo está incompleto, falta app.json o app.tar.gz' +AppAttributesNotMatch: 'El tipo o nombre de la aplicación no coincide' + +#alert +ErrAlert: 'El formato del mensaje de alerta es incorrecto, ¡revise e intente de nuevo!' +ErrAlertPush: 'Error al enviar la alerta, ¡revise e intente de nuevo!' +ErrAlertSave: 'Error al guardar la alerta, ¡revise e intente de nuevo!' +ErrAlertSync: 'Error en la sincronización de la alerta, ¡revise e intente de nuevo!' +ErrAlertRemote: 'Error remoto de alerta, ¡revise e intente de nuevo!' + +#task - runtime +ErrInstallExtension: "Ya hay una tarea de instalación en curso, espere a que termine" + +# alert mail template +PanelAlertTitle: "Notificación de alerta del panel" +TestAlertTitle: "Correo de prueba - Verificar conectividad de correo" +TestAlert: "Este es un correo de prueba para verificar que la configuración de envío de correos es correcta." +LicenseExpirationAlert: "Su Panel {{ .node }}{{ .ip }}, la licencia expirará en {{ .day }} días. Inicie sesión en el panel para ver los detalles." +CronJobFailedAlert: "Su Panel {{ .node }}{{ .ip }}, la ejecución de la tarea programada {{ .name }} falló. Inicie sesión en el panel para ver los detalles." +ClamAlert: "Su Panel {{ .node }}{{ .ip }}, la tarea de escaneo antivirus detectó {{ .num }} archivos infectados. Inicie sesión en el panel para ver los detalles." +WebSiteAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} sitios web expirarán en {{ .day }} días. Inicie sesión en el panel para ver los detalles." +SSLAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} certificados SSL expirarán en {{ .day }} días. Inicie sesión en el panel para ver los detalles." +DiskUsedAlert: "Su Panel {{ .node }}{{ .ip }}, el uso del disco {{ .name }} ha alcanzado {{ .used }}. Inicie sesión en el panel para ver los detalles." +ResourceAlert: "Su Panel {{ .node }}{{ .ip }}, la tasa de uso promedio de {{ .name }} en {{ .time }} minutos es {{ .used }}. Inicie sesión en el panel para ver los detalles." +PanelVersionAlert: "Su Panel {{ .node }}{{ .ip }}, está disponible la última versión del panel para actualizar. Inicie sesión en el panel para ver los detalles." +PanelPwdExpirationAlert: "Su Panel {{ .node }}{{ .ip }}, la contraseña del panel expirará en {{ .day }} días. Inicie sesión en el panel para ver los detalles." +CommonAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .msg }}. Inicie sesión en el panel para ver los detalles." +NodeExceptionAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} nodos son anómalos. Inicie sesión en el panel para ver los detalles." +LicenseExceptionAlert: "Su Panel {{ .node }}{{ .ip }}, {{ .num }} licencias son anómalas. Inicie sesión en el panel para ver los detalles." +SSHAndPanelLoginAlert: "Su Panel {{ .node }}{{ .ip }}, el inicio de sesión {{ .name }} desde {{ .loginIp }} es anómalo. Inicie sesión en el panel para ver los detalles." + +#disk +DeviceNotFound: "Dispositivo {{ .name }} no encontrado" +DeviceIsMounted: "El dispositivo {{ .name }} está montado, desmóntelo primero" +PartitionDiskErr: "Error al particionar, {{ .err }}" +FormatDiskErr: "Error al formatear disco, {{ .err }}" +MountDiskErr: "Error al montar disco, {{ .err }}" +UnMountDiskErr: "Error al desmontar disco, {{ .err }}" +XfsNotFound: "No se detectó el sistema de archivos xfs, por favor instale xfsprogs primero" diff --git a/agent/i18n/lang/ja.yaml b/agent/i18n/lang/ja.yaml new file mode 100644 index 0000000..66cfba0 --- /dev/null +++ b/agent/i18n/lang/ja.yaml @@ -0,0 +1,529 @@ +ErrInvalidParams: 'リクエストパラメータエラー: {{ .detail }}' +ErrTokenParse: 'トークン生成エラー: {{ .detail }}' +ErrInitialPassword: '元のパスワードが正しくありません' +ErrInternalServer: '内部サーバーエラー: {{ .detail }}' +ErrRecordExist: 'レコードがすでに存在します' +ErrRecordNotFound: 'レコードが見つかりません' +ErrStructTransform: '型変換に失敗しました: {{ .err }}' +ErrNotLogin: 'ユーザーがログインしていません: {{ .detail }}' +ErrPasswordExpired: '現在のパスワードの有効期限が切れています: {{ .detail }}' +ErrNotSupportType: 'システムは現在のタイプ: {{ .name }} をサポートしていません' +ErrProxy: 'リクエスト エラーです。ノードのステータスを確認してください: {{ .detail }}' +ErrApiConfigStatusInvalid: 'API インターフェースへのアクセスは禁止されています: {{ .detail }}' +ErrApiConfigKeyInvalid: 'API インターフェース キー エラー: {{ .detail }}' +ErrApiConfigIPInvalid: 'API インターフェースの呼び出しに使用された IP がホワイトリストにありません: {{ .detail }}' +ErrApiConfigDisable: 'このインターフェースは、API インターフェース呼び出しの使用を禁止しています: {{ .detail }}' +ErrApiConfigKeyTimeInvalid: 'API インターフェースのタイムスタンプ エラー: {{ .detail }}' + +ErrMinQuickJump: "少なくとも1つのクイックジャンプエントリを設定してください!" +ErrMaxQuickJump: "最大4つのクイックジャンプエントリを設定できます!" + +#common +ErrUsernameIsExist: 'ユーザー名は既に存在します' +ErrNameIsExist: '名前は既に存在します' +ErrDemoEnvironment: 'デモ サーバー、この操作は禁止されています!' +ErrCmdTimeout: 'コマンドの実行がタイムアウトしました!' +ErrCmdIllegal: '実行コマンドに不正な文字が含まれています。修正してもう一度お試しください。' +ErrPortExist: '{{ .port }} ポートは、{{ .type }} [{{ .name }}] によって既に使用されています' +TYPE_APP: 'アプリケーション' +TYPE_RUNTIME: 'ランタイム環境' +TYPE_DOMAIN: 'ドメイン名' +ErrTypePort: 'ポート {{ .name }} の形式が正しくありません' +ErrTypePortRange: 'ポート範囲は 1 ~ 65535 である必要があります' +Success: '成功' +Failed: '失敗' +SystemRestart: 'システムの再起動によりタスクが中断されました' +ErrGroupIsDefault: 'デフォルト グループ、削除できません' +ErrGroupIsInWebsiteUse: 'グループは別の Web サイトで使用されているため、削除できません。' +Decrypt: "復号化" + +#backup +Localhost: 'ローカルマシン' +ErrBackupInUsed: 'バックアップ アカウントはスケジュールされたタスクで使用されているため、削除できません。' +ErrBackupCheck: 'バックアップ アカウントのテスト接続に失敗しました {{ .err }}' +ErrBackupLocalDelete: 'ローカル サーバーのバックアップ アカウントの削除はまだサポートされていません' +ErrBackupLocalCreate: 'ローカル サーバーのバックアップ アカウントの作成はまだサポートされていません' + +#app +ErrPortInUsed: '{{ .detail }} ポートはすでに使用されています!' +ErrAppLimit: 'インストールされているアプリケーションの数が制限を超えました' +ErrNotInstall: 'アプリケーションがインストールされていません' +ErrPortInOtherApp: '{{ .port }} ポートは既にアプリケーション {{ .apps }} によって使用されています!' +ErrDbUserNotValid: '既存のデータベース、ユーザー名、およびパスワードが一致しません!' +ErrUpdateBuWebsite: 'アプリケーションは正常に更新されましたが、Web サイト構成ファイルの変更に失敗しました。設定を確認してください! ' +Err1PanelNetworkFailed: 'デフォルトのコンテナ ネットワークの作成に失敗しました。 {{ .detail }}' +ErrFileParse: 'アプリケーションの docker-compose ファイルの解析に失敗しました!' +ErrInstallDirNotFound: 'インストール ディレクトリが存在しません。アンインストールする必要がある場合は、強制アンインストールを選択してください。' +AppStoreIsUpToDate: 'アプリストアはすでに最新バージョンです' +LocalAppVersionNull: '{{ .name }} アプリケーションはバージョンに同期されていません。アプリリストに追加できません' +LocalAppVersionErr: '{{ .name }} バージョン {{ .version }} の同期に失敗しました! {{ .err }}' +ErrFileNotFound: '{{ .name }} ファイルが存在しません' +ErrFileParseApp: '{{ .name }} ファイルの解析に失敗しました {{ .err }}' +ErrAppDirNull: 'バージョン フォルダーが存在しません' +LocalAppErr: 'アプリケーション {{ .name }} の同期に失敗しました! {{ .err }}' +ErrContainerName: 'コンテナ名が既に存在します' +ErrCreateHttpClient: 'リクエスト {{ .err }} の作成に失敗しました' +ErrHttpReqTimeOut: 'リクエストがタイムアウトしました {{ .err }}' +ErrHttpReqFailed: 'リクエストが失敗しました {{ .err }}' +ErrNoSuchHost: '要求されたサーバー {{ .err }} が見つかりません' +ErrHttpReqNotFound: '要求されたリソース {{ .err }} が見つかりませんでした' +ErrContainerNotFound: '{{ .name }} コンテナが存在しません' +ErrContainerMsg: '{{ .name }} コンテナが異常です。詳細についてはコンテナページのログを確認してください。' +ErrAppBackup: '{{ .name }} アプリケーションのバックアップに失敗しました。エラー {{ .err }}' +ErrVersionTooLow: '現在の 1Panel のバージョンが低すぎるため、App Store を更新できません。操作する前にバージョンをアップグレードしてください。' +ErrAppNameExist: 'アプリケーション名がすでに存在します' +AppStoreIsSyncing: 'App Store が同期中です。しばらくしてからもう一度お試しください' +ErrGetCompose: 'docker-compose.yml ファイルの取得に失敗しました! {{ .detail }}' +ErrAppWarn: '異常な状態です。ログを確認してください' +ErrAppParamKey: 'パラメータ {{ .name }} フィールドが異常です' +ErrAppUpgrade: 'アプリケーション {{ .name }} のアップグレードに失敗しました {{ .err }}' +AppRecover: 'アプリケーション {{ .name }} をロールバックします' +PullImageStart: "イメージ [{{ .name }}] のプルを開始します" +PullImageSuccess: 'イメージのプルが成功しました' +AppStoreIsLastVersion: 'App Store はすでに最新バージョンです' +AppStoreSyncSuccess: 'App Store の同期が成功しました' +SyncAppDetail: 'アプリケーション構成を同期する' +AppVersionNotMatch: '{{ .name }} アプリケーションには、より高い 1Panel バージョンが必要なため、同期をスキップします' +MoveSiteDir: "ウェブサイトディレクトリの変更を検出しました。OpenResty ウェブサイトディレクトリを {{ .name }} に移行する必要があります" +MoveSiteDirSuccess: 'Web サイト ディレクトリの移行に成功しました' +DeleteRuntimePHP: 'PHP ランタイムを削除する' +CustomAppStoreFileValid: 'App Store パッケージは .tar.gz 形式である必要があります' +PullImageTimeout: 'プル イメージのタイムアウトです。イメージのアクセラレーションを増やすか、別のイメージのアクセラレーションに変更してください' +ErrAppIsDown: '{{ .name }} アプリケーションの状態が異常です。確認してください' +ErrCustomApps: 'インストールされているアプリケーションがあります。まずアンインストールしてください' +ErrCustomRuntimes: 'ランタイム環境がインストールされています。まずそれを削除してください' +ErrAppVersionDeprecated: "{{ .name }} アプリケーションは現在の 1Panel バージョンと互換性がありません、スキップしました" +ErrDockerFailed: "Docker の状態が異常です。サービス状態を確認してください" +ErrDockerComposeCmdNotFound: "Docker Compose コマンドは存在しません。ホストマシンにこのコマンドを先にインストールしてください" +UseExistImage: 'イメージのプルに失敗しました、既存のイメージを使用します' + +#ssh +ExportIP: "ログインIP" +ExportArea: "地域" +ExportPort: "ポート" +ExportAuthMode: "ログイン方式" +ExportUser: "ユーザー" +ExportStatus: "ログイン状態" +ExportDate: "時間" + +#file +ErrFileCanNotRead: 'このファイルはプレビューをサポートしていません' +ErrFileToLarge: 'ファイルは 10M より大きいため開けません' +ErrPathNotFound: 'ディレクトリが存在しません' +ErrMovePathFailed: 'ターゲット パスに元のパスを含めることはできません!' +ErrLinkPathNotFound: 'ターゲット パスが存在しません!' +ErrFileIsExist: 'ファイルまたはフォルダーは既に存在します!' +ErrFileUpload: '{{ .name }} はファイル {{ .detail }} のアップロードに失敗しました' +ErrFileDownloadDir: 'ダウンロード フォルダーはサポートされていません' +ErrCmdNotFound: '{{ .name}} コマンドが存在しません。まずこのコマンドをホストにインストールしてください' +ErrSourcePathNotFound: 'ソース ディレクトリが存在しません' +ErrFavoriteExist: 'このパスはすでにお気に入りに登録されています' +ErrInvalidChar: '不正な文字は許可されません' +ErrPathNotDelete: '選択されたディレクトリは削除できません' +ErrLogFileToLarge: "ログファイルが500MBを超えており、開くことができません" +FileDropFailed: "ファイル {{ .name }} のクリーンアップに失敗しました: {{ .err }}" +FileDropSuccess: "ファイル {{ .name }} のクリーンアップに成功しました、{{ .count }} ファイルを削除、{{ .size }} のディスク領域を解放" +FileDropSum: "ファイルのクリーンアップが完了しました、合計 {{ .count }} ファイルを削除、合計 {{ .size }} のディスク領域を解放" +ErrBadDecrypt: "圧縮パッケージの解凍パスワードが間違っています。解凍に失敗しました。確認して再試行してください!" + +#website +ErrAliasIsExist: 'エイリアスがすでに存在します' +ErrBackupMatch: 'バックアップ ファイルは、現在の Web サイト データ {{ .detail }} の一部と一致しません' +ErrBackupExist: 'バックアップ ファイル内のソース データの対応する部分が存在しません {{ .detail }}' +ErrPHPResource: 'ローカルオペレーティング環境は切り替えをサポートしていません! ' +ErrPathPermission: 'インデックス ディレクトリに 1000:1000 以外の権限を持つフォルダーが検出されました。これにより、Web サイトでアクセス拒否エラーが発生する可能性があります。上記の保存ボタンをクリックしてください。' +ErrDomainIsUsed: 'ドメイン名はウェブサイト [{{ .name }}] で既に使用されています' +ErrDomainFormat: '{{ .name }} ドメイン名の形式が正しくありません' +ErrDefaultAlias: 'デフォルトは予約済みのコードです。別のコードを使用してください' +ErrParentWebsite: 'まずサブサイト {{ .name }} を削除する必要があります' +ErrBuildDirNotFound: 'ビルド ディレクトリが存在しません' +ErrImageNotExist: 'オペレーティング環境 {{ .name }} イメージが存在しません。オペレーティング環境を再編集してください' +ErrProxyIsUsed: "ロードバランシングはリバースプロキシによって使用されているため、削除できません" +ErrSSLValid: '証明書ファイルが異常です、証明書の状態を確認してください!' +ErrWebsiteDir: "ウェブサイトディレクトリ内のディレクトリを選択してください。" +ErrComposerFileNotFound: "composer.json ファイルが存在しません" +ErrRuntimeNoPort: "ランタイム環境にポートが設定されていません。先にランタイム環境を編集してください。" +Status: 'ステータス' +start: '開始' +stop: '停止' +restart: '再起動' +kill: '強制停止' +pause: '一時停止' +unpause: '再開' +remove: '削除' +delete: '削除' +ErrDefaultWebsite: 'デフォルト Web サイトが既に設定されています。設定する前にキャンセルしてください!' +SSL: '証明書' +Set: '設定' + +#ssl +ErrSSLCannotDelete: '{{ .name }} 証明書は Web サイトで使用されているため、削除できません' +ErrAccountCannotDelete: 'アカウントは証明書に関連付けられているため、削除できません' +ErrSSLApply: '証明書の更新は成功しましたが、openresty の再ロードに失敗しました。設定を確認してください。' +ErrEmailIsExist: 'メールボックスは既に存在します' +ErrSSLKeyNotFound: '秘密鍵ファイルが存在しません' +ErrSSLCertificateNotFound: '証明書ファイルが存在しません' +ErrSSLKeyFormat: '秘密鍵ファイルの検証に失敗しました' +ErrSSLCertificateFormat: '証明書ファイルの形式が正しくありません。pem 形式を使用してください' +ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid または EabHmacKey は空白にできません' +ErrOpenrestyNotFound: 'Http モードでは、まず Openresty をインストールする必要があります' +ApplySSLStart: '証明書の申請を開始します。ドメイン名 [{{ .domain }}] 申請方法 [{{ .type }}] ' +dnsAccount: 'DNS 自動' +dnsManual: 'DNS マニュアル' +http: "HTTP" +ApplySSLFailed: '[{{ .domain }}] 証明書の申請に失敗しました、{{ .detail }}' +ApplySSLSuccess: '[{{ .domain }}] 証明書の申請に成功しました! ' +DNSAccountName: 'DNS アカウント [{{ .name }}] ベンダー [{{ .type }}]' +PushDirLog: '証明書がディレクトリ [{{ .path }}] {{ .status }} にプッシュされました' +ErrDeleteCAWithSSL: '現在の組織には発行済みの証明書があり、削除できません。' +ErrDeleteWithPanelSSL: 'パネル SSL 構成はこの証明書を使用しているため、削除できません' +ErrDefaultCA: 'デフォルトの権限を削除できません' +ApplyWebSiteSSLLog: '{{ .name }} ウェブサイト証明書の更新を開始しています' +ErrUpdateWebsiteSSL: '{{ .name }} Web サイト証明書の更新に失敗しました: {{ .err }}' +ApplyWebSiteSSLSuccess: 'Web サイトの証明書を正常に更新しました' +ErrExecShell: 'スクリプト {{ .err }} の実行に失敗しました' +ExecShellStart: 'スクリプトの実行を開始します' +ExecShellSuccess: 'スクリプトの実行が成功しました' +StartUpdateSystemSSL: 'システム証明書の更新を開始します' +UpdateSystemSSLSuccess: 'システム証明書を正常に更新しました' +ErrWildcardDomain: 'HTTP モードでワイルドカード ドメイン名証明書を申請できません' +ErrApplySSLCanNotDelete: "申請中の証明書 {{.name}} は削除できません。しばらくしてからもう一度お試しください。" +StartPushSSLToNode: "証明書をノードにプッシュ開始" +PushSSLToNodeFailed: "ノードに証明書をプッシュ失敗: {{ .err }}" +PushSSLToNodeSuccess: "ノードに証明書をプッシュ成功" + +#mysql +ErrUserIsExist: '現在のユーザーは既に存在します。再入力してください' +ErrDatabaseIsExist: '現在のデータベースは既に存在します。再入力してください' +ErrExecTimeOut: 'SQL 実行がタイムアウトしました。データベースを確認してください' +ErrRemoteExist: 'この名前のリモート データベースは既に存在します。変更してもう一度お試しください' +ErrLocalExist: '名前はローカル データベースに既に存在します。変更してもう一度お試しください' +RemoteBackup: "リモートデータベースをバックアップするには、まずイメージ {{ .name }} を使用してローカルコンテナデータベースサービスを起動する必要があります。しばらくお待ちください..." +RemoteRecover: "リモートデータベースを復元するには、まずイメージ {{ .name }} を使用してローカルコンテナデータベースサービスを起動する必要があります。しばらくお待ちください..." +Arg: "引数" + +#redis +ErrTypeOfRedis: 'リカバリ ファイルの種類が現在の永続化方法と一致しません。変更して再試行してください' + +#container +ErrInUsed: "{{ .detail }} は使用中であり、削除できません!" +ErrObjectInUsed: "このオブジェクトは使用中であり、削除できません!" +ErrObjectBeDependent: "このイメージは他のイメージに依存されているため、削除できません!" +ErrPortRules: 'ポート番号が一致しません。再入力してください。' +ErrPgImagePull: 'イメージのプルがタイムアウトしました。イメージのアクセラレーションを設定するか、{{ .name }} イメージを手動でプルして再試行してください' +PruneHelper: "今回のクリーンアップ {{ .name }} でディスク領域 {{ .size }} を解放しました" +ImageRemoveHelper: "イメージ{{ .name }}を削除し、{{ .size }}のディスク領域を解放しました" +BuildCache: "ビルドキャッシュ" +Volume: "ストレージボリューム" +Network: "ネットワーク" +PruneStart: "クリーンアップ中、しばらくお待ちください..." + +#runtime +ErrFileNotExist: '{{ .detail }} ファイルが存在しません。ソース ファイルの整合性を確認してください。' +ErrImageBuildErr: 'イメージのビルドに失敗しました' +ErrImageExist: "イメージはすでに存在します!イメージ名を変更してください。" +ErrDelWithWebsite: 'オペレーティング環境は既に Web サイトに関連付けられているため、削除できません' +ErrRuntimeStart: '起動に失敗しました' +ErrPackageJsonNotFound: 'package.json ファイルが存在しません' +ErrScriptsNotFound: 'スクリプト構成項目が package.json に見つかりませんでした' +ErrContainerNameNotFound: 'コンテナ名を取得できません。.env ファイルを確認してください' +ErrNodeModulesNotFound: 'node_modules フォルダが存在しません。ランタイム環境を編集するか、ランタイム環境が正常に起動するまでお待ちください。' +ErrContainerNameIsNull: 'コンテナ名が存在しません' +ErrPHPPortIsDefault: "ポート9000はデフォルトポートです。修正してから再試行してください" +ErrPHPRuntimePortFailed: "ポート {{ .name }} は現在のランタイム環境で使用されています。修正してから再試行してください" + +#tool +ErrConfigNotFound: '構成ファイルが存在しません' +ErrConfigParse: '構成ファイルの形式が正しくありません' +ErrConfigIsNull: '構成ファイルは空にできません' +ErrConfigDirNotFound: '実行ディレクトリが存在しません' +ErrConfigAlreadyExist: '同じ名前の設定ファイルがすでに存在します' +ErrUserFindErr: 'ユーザー {{ .name }} の検索に失敗しました {{ .err }}' + +#cronjob +CutWebsiteLogSuccess: '{{ .name }} ウェブサイトのログが正常にカットされました。バックアップ パス {{ .path }}' +HandleShell: 'スクリプト {{ .name }} を実行します' +HandleCurl: "URL {{ .name }} にアクセス" +HandleNtpSync: 'システム時刻の同期' +HandleSystemClean: 'システム キャッシュのクリーンアップ' +SystemLog: 'システムログ' +CutWebsiteLog: 'ウェブサイトログのローテーション' +FileOrDir: 'ディレクトリ / ファイル' +UploadFile: 'バックアップファイル {{ .file }} を {{ .backup }} にアップロード中' +Upload: "アップロード" +IgnoreBackupErr: "バックアップ失敗、エラー: {{ .detail }}、このエラーを無視します..." +IgnoreUploadErr: "アップロード失敗、エラー: {{ .detail }}、このエラーを無視します..." +LoadBackupFailed: "バックアップアカウントの接続取得に失敗、エラー: {{ .detail }}" +InExecuting: "現在のタスクは実行中です。重複して実行しないでください!" +NoSuchResource: "データベースにバックアップ内容が見つかりませんでした。スキップします..." +CleanLog: "ログをクリーン" +CleanLogByName: "{{.name}} のログをクリーン" +WafIpGroupNotFound: "WAF IPグループが見つかりません。[高度な機能 - WAF - ブラックリスト/ホワイトリスト - IPグループ]に移動し、リモートダウンロード方式でIPグループを追加してください。" + +#toolbox +ErrNotExistUser: '現在のユーザーは存在しません。変更してもう一度お試しください。' +ErrBanAction: '設定に失敗しました。現在の {{ .name }} サービスは利用できません。確認してもう一度お試しください。' +ErrClamdscanNotFound: 'clamdscan コマンドが検出されませんでした。インストールするにはドキュメントを参照してください。' +TaskSwapSet: "Swap設定" +SetSwap: "Swap設定 {{ .path }} - {{ .size }}" +CreateSwap: "Swapファイル作成" +FormatSwap: "Swapファイルフォーマット" +EnableSwap: "Swapファイル有効化" + +#tamper +CleanTamperSetting: "履歴保護設定をクリーンアップ" +SetTamperWithRules: "ルールに従ってディレクトリ保護を開始" +ProtectDir: "ディレクトリを保護 {{ .name }}" +IgnoreFile: "ファイルを保護 {{ .name }}" +TamperSetSuccessful: "保護設定が完了し、以下のディレクトリの監視を開始します:" + +#waf +ErrScope: 'この構成の変更はサポートされていません' +ErrStateChange: '状態の変更に失敗しました' +ErrRuleExist: 'ルールは既に存在します' +ErrRuleNotExist: 'ルールが存在しません' +ErrParseIP: 'IP 形式が間違っています' +ErrDefaultIP: 'デフォルトは予約名です。別の名前に変更してください' +ErrGroupInUse: 'IP グループはブラックリスト/ホワイトリストで使用されており、削除できません' +ErrIPGroupAclUse: "IPグループはウェブサイト {{ .name }} のカスタムルールで使用されているため、削除できません" +ErrGroupExist: 'IP グループ名がすでに存在します' +ErrIPRange: 'IP 範囲が間違っています' +ErrIPExist: 'IP がすでに存在します' +urlDefense: 'URL ルール' +urlHelper: '禁止された URL' +dirFilter: 'ディレクトリ フィルター' +xss: 'XSS' +phpExec: 'PHP スクリプトの実行' +oneWordTrojan: 'ワンワードトロイの木馬' +appFilter: '危険なディレクトリフィルタリングを適用する' +webshell: 'ウェブシェル' +args: 'パラメータルール' +protocolFilter: 'プロトコルフィルタリング' +javaFileter: 'Java 危険ファイル フィルター' +scannerFilter: 'スキャナ フィルタ' +escapeFilter: 'エスケープフィルター' +customRule: 'カスタムルール' +httpMethod: 'HTTP メソッド フィルタリング' +fileExt: 'ファイルアップロード制限' +defaultIpBlack: '悪意のある IP グループ' +cookie: 'クッキールール' +urlBlack: 'URL ブラックリスト' +uaBlack: 'ユーザーエージェント ブラックリスト' +attackCount: '攻撃頻度の制限' +fileExtCheck: 'ファイルアップロード制限' +geoRestrict: '地域アクセス制限' +unknownWebsite: '不正なドメイン名アクセス' +notFoundCount: '404 レート制限' +headerDefense: 'ヘッダールール' +defaultUaBlack: 'ユーザーエージェントルール' +methodWhite: 'HTTP ルール' +captcha: '人間と機械による認証' +fiveSeconds: '5秒検証' +vulnCheck: '補足ルール' +acl: 'カスタムルール' +sql: 'SQL インジェクション' +cc: 'アクセス頻度制限' +defaultUrlBlack: 'URL ルール' +sqlInject: 'SQL インジェクション' +ErrDBNotExist: 'データベースが存在しません' +allow: '許可' +deny: '拒否' +OpenrestyNotFound: 'Openresty がインストールされていません' +remoteIpIsNull: "IPリストが空です" +OpenrestyVersionErr: "Openrestyのバージョンが低すぎます。Openrestyを1.27.1.2-2-2-focalにアップグレードしてください" + +#task +TaskStart: '{{ .name }} タスクが開始されました [START]' +TaskEnd: '{{ .name }} タスクが完了しました [COMPLETED]' +TaskFailed: '{{ .name }} タスクが失敗しました' +TaskTimeout: '{{ .name }} がタイムアウトしました' +TaskSuccess: '{{ .name }} タスクが成功しました' +TaskRetry: '{{ .name }} 回目の再試行を開始します' +SubTaskSuccess: '{{ .name }} が成功しました' +SubTaskFailed: '{{ .name }} が失敗しました: {{ .err }}' +TaskInstall: 'インストール' +TaskUninstall: 'アンインストール' +TaskCreate: '作成' +TaskDelete: '削除' +TaskUpgrade: 'アップグレード' +TaskUpdate: '更新' +TaskRestart: '再起動' +TaskProtect: "保護" +TaskBackup: 'バックアップ' +TaskRecover: '回復' +TaskRollback: 'ロールバック' +TaskPull: 'プル' +TaskCommit: 'コミット' +TaskBuild: 'ビルド' +TaskPush: 'プッシュ' +TaskClean: "クリーンアップ" +TaskHandle: '実行' +TaskImport: "インポート" +TaskExport: "エクスポート" +Website: 'ウェブサイト' +App: 'アプリケーション' +Runtime: 'ランタイム環境' +Database: 'データベース' +ConfigFTP: 'FTP ユーザー {{ .name }} を作成します' +ConfigOpenresty: 'Openresty 構成ファイルを作成する' +InstallAppSuccess: 'アプリケーション {{ .name }} が正常にインストールされました' +ConfigRuntime: 'ランタイム環境を構成する' +ConfigApp: '構成アプリケーション' +SuccessStatus: '{{ .name }} は成功しました' +FailedStatus: '{{ .name }} が {{ .err }} に失敗しました' +HandleLink: 'ハンドルアプリケーションの関連付け' +HandleDatabaseApp: 'アプリケーション パラメータの処理' +ExecShell: '{{ .name }} スクリプトを実行する' +PullImage: 'イメージをプル' +Start: '開始' +Run: '開始' +Stop: '停止' +Image: '鏡' +Compose: 'オーケストレーション' +Container: 'コンテナ' +AppLink: 'リンクされたアプリケーション' +EnableSSL: 'HTTPS を有効にする' +AppStore: 'Appストア' +TaskSync: '同期' +LocalApp: 'ローカルアプリケーション' +SubTask: 'サブタスク' +RuntimeExtension: 'ランタイム環境拡張' +TaskIsExecuting: 'タスクは実行中です' +CustomAppstore: 'カスタム アプリケーション ウェアハウス' +TaskExec: '実行' +TaskBatch: "一括操作" +FileConvert: 'ファイル形式の変換' + +# task - clam +Clamscan: "{{ .name }} をスキャン" +TaskScan: "スキャン" + +#task - ai +OllamaModelPull: 'Ollama モデル {{ .name }} をプルします' +OllamaModelSize: "Ollama モデル {{ .name }} のサイズを取得します" + +#task - snapshot +Snapshot: 'スナップショット' +SnapDBInfo: '1Panel データベース情報を書き込む' +SnapCopy: 'ファイルとディレクトリ {{ .name }} をコピーします' +SnapNewDB: 'データベース {{ .name }} 接続を初期化します' +SnapDeleteOperationLog: '操作ログの削除' +SnapDeleteLoginLog: 'アクセスログを削除する' +SnapDeleteMonitor: '監視データを削除する' +SnapRemoveSystemIP: 'システム IP を削除します' +SnapBaseInfo: '1Panel の基本情報を書き込む' +SnapInstallAppImageEmpty: 'アプリケーションイメージが選択されていないため、スキップします...' +SnapInstallApp: '1Panelインストール済みアプリケーションのバックアップ' +SnapDockerSave: 'インストールされたアプリケーションを圧縮する' +SnapLocalBackup: '1Panel ローカル バックアップ ディレクトリをバックアップします' +SnapCompressBackup: 'ローカル バックアップ ディレクトリを圧縮する' +SnapPanelData: '1Panel データ ディレクトリをバックアップ' +SnapCompressPanel: '圧縮データ ディレクトリ' +SnapWebsite: '1Panel ウェブサイト ディレクトリのバックアップ' +SnapCloseDBConn: 'データベース接続を閉じる' +SnapCompress: 'スナップショット ファイルを作成する' +SnapCompressFile: 'スナップショット ファイルを圧縮する' +SnapCheckCompress: 'スナップショット圧縮ファイルをチェックする' +SnapCompressSize: 'スナップショット ファイル サイズ {{ .name }}' +SnapUpload: 'スナップショット ファイルをアップロード' +SnapUploadTo: 'スナップショット ファイルを {{ .name }} にアップロードします' +SnapUploadRes: 'スナップショット ファイルを {{ .name }} にアップロードします' + +SnapshotRecover: 'スナップショットの復元' +RecoverDownload: 'スナップショット ファイルをダウンロード' +Download: 'ダウンロード' +RecoverDownloadAccount: 'スナップショット ダウンロード バックアップ アカウント {{ .name }} を取得します' +RecoverDecompress: 'スナップショット圧縮ファイルを解凍する' +Decompress: '減圧' +BackupBeforeRecover: 'スナップショットの前にシステム関連データをバックアップする' +Readjson: 'スナップショット内の Json ファイルを読み取ります' +ReadjsonPath: 'スナップショット内の Json ファイル パスを取得します' +ReadjsonContent: 'Json ファイルを読み取ります' +ReadjsonMarshal: 'Json エスケープ処理' +RecoverApp: 'インストールされたアプリを復元する' +RecoverWebsite: 'ウェブサイトディレクトリを回復する' +RecoverAppImage: 'スナップショットイメージのバックアップを復元する' +RecoverCompose: '他のコンポーザーのコンテンツを復元する' +RecoverComposeList: 'すべてのコンポーザーを復元する' +RecoverComposeItem: '{{ .name }} の作成を回復する' +RecoverAppEmpty: 'スナップショット ファイルにアプリケーション イメージのバックアップが見つかりませんでした' +RecoverBaseData: '基本データとファイルを回復する' +RecoverDaemonJsonEmpty: 'スナップショット ファイルと現在のマシンの両方に、コンテナー構成 daemon.json ファイルがありません' +RecoverDaemonJson: 'コンテナ構成 daemon.json ファイルを復元する' +RecoverDBData: 'データベース データを回復する' +RecoverBackups: 'ローカル バックアップ ディレクトリを復元する' +RecoverPanelData: 'リカバリデータディレクトリ' + +#task - container +ContainerNewCliet: 'Docker クライアントを初期化する' +ContainerImagePull: 'コンテナ イメージ {{ .name }} をプルします' +ContainerRemoveOld: '元のコンテナ {{ .name }} を削除します' +ContainerImageCheck: 'イメージが正常にプルされているかどうかを確認します' +ContainerLoadInfo: 'コンテナの基本情報を取得する' +ContainerRecreate: 'コンテナの更新に失敗しました。元のコンテナの復元を開始しています' +ContainerCreate: '新しいコンテナ {{ .name }} を作成します' +ContainerCreateFailed: 'コンテナの作成に失敗しました。失敗したコンテナを削除してください' +ContainerStartCheck: 'コンテナが起動されているかどうかを確認します' + +#task - image +ImageBuild: 'イメージビルド' +ImageBuildStdoutCheck: '画像出力コンテンツを解析する' +ImageBuildRes: 'イメージビルド出力: {{ .name }}' +ImagePull: '画像をプル' +ImageRepoAuthFromDB: 'データベースからリポジトリ認証情報を取得する' +ImaegPullRes: "画像プル出力: {{ .name }}" +ImagePush: '画像をプッシュ' +ImageRenameTag: '画像タグを変更する' +ImageNewTag: '新しい画像タグ {{ .name }}' +ImaegPushRes: '画像プッシュ出力: {{ .name }}' +ComposeCreate: 'コンポジションを作成する' +ComposeCreateRes: "Compose 作成出力: {{ .name }}" + +#task - website +BackupNginxConfig: 'ウェブサイトの OpenResty 構成ファイルをバックアップする' +CompressFileSuccess: 'ディレクトリを正常に圧縮しました。{{ .name }} に圧縮されました' +CompressDir: '圧縮ディレクトリ' +DeCompressFile: 'ファイル {{ .name }} を解凍します' +ErrCheckValid: 'バックアップ ファイルの検証に失敗しました、{{ .name }}' +Rollback: 'ロールバック' +websiteDir: 'ウェブサイトディレクトリ' +RecoverFailedStartRollBack: 'リカバリに失敗しました。ロールバックを開始します' +AppBackupFileIncomplete: 'バックアップ ファイルが不完全で、app.json または app.tar.gz ファイルが不足しています' +AppAttributesNotMatch: 'アプリケーションの種類または名前が一致しません' + +#alert +ErrAlert: '警告メッセージの形式が正しくありません。確認してもう一度お試しください。' +ErrAlertPush: 'アラート情報のプッシュ中にエラーが発生しました。確認してもう一度お試しください。' +ErrAlertSave: 'アラーム情報の保存中にエラーが発生しました。確認してもう一度お試しください。' +ErrAlertSync: 'アラーム情報の同期エラーです。確認してもう一度お試しください。' +ErrAlertRemote: 'アラーム メッセージのリモート エラーです。確認してもう一度お試しください。' + +#task - runtime +ErrInstallExtension: "インストールタスクが進行中です、タスクが終了するのを待ってください" + +# alert mail template +PanelAlertTitle: "パネルアラート通知" +TestAlertTitle: "テストメール - メール接続の確認" +TestAlert: "これはテストメールです。メール送信設定が正しく構成されているかを確認します。" +LicenseExpirationAlert: "あなたの {{ .node }}{{ .ip }} パネル、ライセンスは {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。" +CronJobFailedAlert: "あなたの {{ .node }}{{ .ip }} パネル、スケジュールタスク {{ .name }} の実行に失敗しました。詳細はパネルにログインしてご確認ください。" +ClamAlert: "あなたの {{ .node }}{{ .ip }} パネル、ウイルススキャンタスクで {{ .num }} 個の感染ファイルを検出しました。詳細はパネルにログインしてご確認ください。" +WebSiteAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のウェブサイトが {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。" +SSLAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 枚のSSL証明書が {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。" +DiskUsedAlert: "あなたの {{ .node }}{{ .ip }} パネル、ディスク {{ .name }} の使用率が {{ .used }} に達しました。詳細はパネルにログインしてご確認ください。" +ResourceAlert: "あなたの {{ .node }}{{ .ip }} パネル、過去 {{ .time }} 分間の {{ .name }} 平均使用率は {{ .used }} です。詳細はパネルにログインしてご確認ください。" +PanelVersionAlert: "あなたの {{ .node }}{{ .ip }} パネル、最新バージョンがアップグレード可能です。詳細はパネルにログインしてご確認ください。" +PanelPwdExpirationAlert: "あなたの {{ .node }}{{ .ip }} パネル、パネルパスワードは {{ .day }} 日後に期限切れになります。詳細はパネルにログインしてご確認ください。" +CommonAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .msg }}。詳細はパネルにログインしてご確認ください。" +NodeExceptionAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のノードに異常が発生しています。詳細はパネルにログインしてご確認ください。" +LicenseExceptionAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .num }} 個のライセンスに異常が発生しています。詳細はパネルにログインしてご確認ください。" +SSHAndPanelLoginAlert: "あなたの {{ .node }}{{ .ip }} パネル、{{ .loginIp }} からの {{ .name }} ログインに異常があります。詳細はパネルにログインしてご確認ください。" + +#disk +DeviceNotFound: "デバイス {{ .name }} が見つかりません" +DeviceIsMounted: "デバイス {{ .name }} はマウントされています、まずアンマウントしてください" +PartitionDiskErr: "パーティションに失敗しました、{{ .err }}" +FormatDiskErr: "ディスクのフォーマットに失敗しました、{{ .err }}" +MountDiskErr: "ディスクのマウントに失敗しました、{{ .err }}" +UnMountDiskErr: "ディスクのアンマウントに失敗しました、{{ .err }}" +XfsNotFound: "xfs ファイルシステムが検出されませんでした、最初に xfsprogs をインストールしてください" \ No newline at end of file diff --git a/agent/i18n/lang/ko.yaml b/agent/i18n/lang/ko.yaml new file mode 100644 index 0000000..664d048 --- /dev/null +++ b/agent/i18n/lang/ko.yaml @@ -0,0 +1,530 @@ +ErrInvalidParams: '요청 매개변수 오류: {{ .detail }}' +ErrTokenParse: '토큰 생성 오류: {{ .detail }}' +ErrInitialPassword: '원래 비밀번호가 올바르지 않습니다' +ErrInternalServer: '내부 서버 오류: {{ .detail }}' +ErrRecordExist: '레코드가 이미 존재합니다' +ErrRecordNotFound: '레코드를 찾을 수 없습니다' +ErrStructTransform: '유형 변환에 실패했습니다: {{ .err }}' +ErrNotLogin: '사용자가 로그인하지 않았습니다: {{ .detail }}' +ErrPasswordExpired: '현재 비밀번호가 만료되었습니다: {{ .detail }}' +ErrNotSupportType: '시스템은 현재 유형 {{ .name }}을 지원하지 않습니다.' +ErrProxy: '요청 오류, 노드 상태를 확인하세요: {{ .detail }}' +ErrApiConfigStatusInvalid: 'API 인터페이스에 대한 액세스가 금지되었습니다: {{ .detail }}' +ErrApiConfigKeyInvalid: 'API 인터페이스 키 오류: {{ .detail }}' +ErrApiConfigIPInvalid: 'API 인터페이스를 호출하는 데 사용된 IP가 허용 목록에 없습니다: {{ .detail }}' +ErrApiConfigDisable: '이 인터페이스는 API 인터페이스 호출 사용을 금지합니다: {{ .detail }}' +ErrApiConfigKeyTimeInvalid: 'API 인터페이스 타임스탬프 오류: {{ .detail }}' +ErrShutDown: "명령이 수동으로 종료되었습니다!" + +ErrMinQuickJump: "최소 하나의 빠른 점프 항목을 설정해 주세요!" +ErrMaxQuickJump: "최대 네 개의 빠른 점프 항목을 설정할 수 있습니다!" + +#흔한 +ErrUsernameIsExist: '사용자 이름이 이미 존재합니다' +ErrNameIsExist: '이름이 이미 존재합니다' +ErrDemoEnvironment: '데모 서버, 이 작업은 금지되어 있습니다!' +ErrCmdTimeout: '명령 실행 시간이 초과되었습니다!' +ErrCmdIllegal: '실행 명령에 잘못된 문자가 있습니다. 수정하여 다시 시도하세요!' +ErrPortExist: '{{ .port }} 포트는 이미 {{ .type }} [{{ .name }}]에 의해 사용되고 있습니다.' +TYPE_APP: '응용 프로그램' +TYPE_RUNTIME: '런타임 환경' +TYPE_DOMAIN: '도메인 이름' +ErrTypePort: '포트 {{ .name }} 형식이 올바르지 않습니다' +ErrTypePortRange: '포트 범위는 1-65535 사이여야 합니다.' +Success: '성공' +Failed: '실패했습니다' +SystemRestart: '시스템 재시작으로 인해 작업이 중단되었습니다' +ErrGroupIsDefault: '기본 그룹, 삭제할 수 없습니다' +ErrGroupIsInWebsiteUse: '그룹이 다른 웹사이트에서 사용 중이므로 삭제할 수 없습니다.' +Decrypt: "복호화" + +#지원 +Localhost: '로컬 머신' +ErrBackupInUsed: '백업 계정이 예약된 작업에 사용되었으므로 삭제할 수 없습니다.' +ErrBackupCheck: '백업 계정 테스트 연결에 실패했습니다 {{ .err }}' +ErrBackupLocalDelete: '로컬 서버 백업 계정 삭제는 아직 지원되지 않습니다.' +ErrBackupLocalCreate: '로컬 서버 백업 계정 생성은 아직 지원되지 않습니다.' + +#앱 +ErrPortInUsed: '{{ .detail }} 포트가 이미 사용 중입니다!' +ErrAppLimit: '설치된 애플리케이션 수가 한도를 초과했습니다' +ErrNotInstall: '응용 프로그램이 설치되지 않았습니다' +ErrPortInOtherApp: '{{ .port }} 포트는 이미 {{ .apps }} 애플리케이션에 의해 사용되고 있습니다!' +ErrDbUserNotValid: '기존 데이터베이스, 사용자 이름 및 비밀번호가 일치하지 않습니다!' +ErrUpdateBuWebsite: '응용 프로그램이 성공적으로 업데이트되었지만, 웹사이트 구성 파일 수정에 실패했습니다. 구성을 확인하세요! ' +Err1PanelNetworkFailed: '기본 컨테이너 네트워크 생성에 실패했습니다! {{ .세부 사항 }}' +ErrFileParse: '응용 프로그램 docker-compose 파일 구문 분석에 실패했습니다!' +ErrInstallDirNotFound: '설치 디렉토리가 존재하지 않습니다. 제거를 원하실 경우 강제 제거 를 선택해주세요.' +AppStoreIsUpToDate: '앱 스토어가 이미 최신 버전입니다' +LocalAppVersionNull: '{{ .name }} 애플리케이션이 버전과 동기화되지 않았습니다! 앱 목록에 추가할 수 없습니다.' +LocalAppVersionErr: '{{ .name }} 동기화 버전 {{ .version }}이 실패했습니다! {{ .err }}' +ErrFileNotFound: '{{ .name }} 파일이 존재하지 않습니다' +ErrFileParseApp: '{{ .name }} 파일 구문 분석에 실패했습니다 {{ .err }}' +ErrAppDirNull: '버전 폴더가 존재하지 않습니다' +LocalAppErr: '애플리케이션 {{ .name }} 동기화에 실패했습니다! {{ .err }}' +ErrContainerName: '컨테이너 이름이 이미 존재합니다' +ErrCreateHttpClient: '요청 {{ .err }}을(를) 생성하지 못했습니다.' +ErrHttpReqTimeOut: '요청 시간이 초과되었습니다 {{ .err }}' +ErrHttpReqFailed: '요청이 실패했습니다 {{ .err }}' +ErrNoSuchHost: '요청한 서버 {{ .err }}을 찾을 수 없습니다' +ErrHttpReqNotFound: '요청한 리소스 {{ .err }}을 찾을 수 없습니다.' +ErrContainerNotFound: '{{ .name }} 컨테이너가 존재하지 않습니다' +ErrContainerMsg: '{{ .name }} 컨테이너가 비정상입니다. 자세한 내용은 컨테이너 페이지의 로그를 확인하세요.' +ErrAppBackup: '{{ .name }} 애플리케이션 백업에 실패했습니다 {{ .err }}' +ErrVersionTooLow: '현재 1Panel 버전이 너무 낮아 App Store를 업데이트할 수 없습니다. 작동하시기 전에 버전을 업그레이드하세요.' +ErrAppNameExist: '응용 프로그램 이름이 이미 존재합니다' +AppStoreIsSyncing: 'App Store가 동기화 중입니다. 나중에 다시 시도하세요.' +ErrGetCompose: 'docker-compose.yml 파일을 가져오지 못했습니다! {{ .detail }}' +ErrAppWarn: '비정상적인 상태입니다. 로그를 확인해 주세요.' +ErrAppParamKey: '매개변수 {{ .name }} 필드가 비정상입니다.' +ErrAppUpgrade: '애플리케이션 {{ .name }} 업그레이드에 실패했습니다 {{ .err }}' +AppRecover: '롤백 애플리케이션 {{ .name }}' +PullImageStart: '이미지 [{{ .name }}] 가져오기 시작' +PullImageSuccess: '이미지 가져오기 성공' +AppStoreIsLastVersion: '앱스토어가 이미 최신 버전입니다' +AppStoreSyncSuccess: '앱스토어 동기화 성공' +SyncAppDetail: '애플리케이션 구성 동기화' +AppVersionNotMatch: '{{ .name }} 애플리케이션에 더 높은 1Panel 버전이 필요하므로 동기화를 건너뜁니다.' +MoveSiteDir: "웹사이트 디렉토리 변경이 감지되었습니다. OpenResty 웹사이트 디렉토리를 {{ .name }} 로 마이그레이션해야 합니다" +MoveSiteDirSuccess: '웹사이트 디렉토리 마이그레이션 성공' +DeleteRuntimePHP: 'PHP 런타임 삭제' +CustomAppStoreFileValid: '앱 스토어 패키지는 .tar.gz 형식이어야 합니다.' +PullImageTimeout: '이미지 가져오기 시간 초과, 이미지 가속을 높이거나 다른 이미지 가속으로 변경해 보세요.' +ErrAppIsDown: '{{ .name }} 애플리케이션 상태가 비정상적입니다. 확인해 주세요' +ErrCustomApps: '설치된 애플리케이션이 있습니다. 먼저 제거해 주세요' +ErrCustomRuntimes: '설치된 런타임 환경이 있습니다. 먼저 삭제해 주세요' +ErrAppVersionDeprecated: "{{ .name }} 응용 프로그램은 현재 1Panel 버전과 호환되지 않아 건너뛰었습니다" +ErrDockerFailed: "Docker의 상태가 비정상입니다. 서비스 상태를 확인하세요" +ErrDockerComposeCmdNotFound: "Docker Compose 명령이 없습니다. 호스트 머신에 먼저 이 명령을 설치하세요" +UseExistImage: '이미지 가져오기 실패, 기존 이미지 사용' + +#ssh +ExportIP: "로그인 IP" +ExportArea: "위치" +ExportPort: "포트" +ExportAuthMode: "로그인 방식" +ExportUser: "사용자" +ExportStatus: "로그인 상태" +ExportDate: "시간" + +#파일 +ErrFileCanNotRead: '이 파일은 미리보기를 지원하지 않습니다' +ErrFileToLarge: '파일이 10M보다 커서 열 수 없습니다' +ErrPathNotFound: '디렉토리가 존재하지 않습니다' +ErrMovePathFailed: '대상 경로에는 원래 경로가 포함될 수 없습니다!' +ErrLinkPathNotFound: '대상 경로가 존재하지 않습니다!' +ErrFileIsExist: '파일이나 폴더가 이미 존재합니다!' +ErrFileUpload: '{{ .name }}이 파일 {{ .detail }}을 업로드하지 못했습니다.' +ErrFileDownloadDir: '다운로드 폴더가 지원되지 않습니다' +ErrCmdNotFound: '{{ .name}} 명령이 존재하지 않습니다. 먼저 호스트에 이 명령을 설치하세요' +ErrSourcePathNotFound: '소스 디렉토리가 존재하지 않습니다' +ErrFavoriteExist: '이 경로는 이미 즐겨찾기되었습니다' +ErrInvalidChar: '불법 문자는 허용되지 않습니다' +ErrPathNotDelete: '선택한 디렉토리를 삭제할 수 없습니다' +ErrLogFileToLarge: "로그 파일이 500MB를 초과하여 열 수 없습니다" +FileDropFailed: "파일 {{ .name }} 정리 실패: {{ .err }}" +FileDropSuccess: "파일 {{ .name }} 정리 성공, {{ .count }}개 파일 삭제, {{ .size }} 디스크 공간 확보" +FileDropSum: "파일 정리 완료, 총 {{ .count }}개 파일 삭제, 총 {{ .size }} 디스크 공간 확보" +ErrBadDecrypt: "압축 패키지 암호 해제 비밀번호 오류, 압축 해제 실패, 확인 후 다시 시도하세요!" + +#웹사이트 +ErrAliasIsExist: '별칭이 이미 존재합니다' +ErrBackupMatch: '백업 파일이 현재 웹사이트 데이터 중 일부 {{ .detail }}와 일치하지 않습니다.' +ErrBackupExist: '백업 파일에 있는 소스 데이터의 해당 부분이 존재하지 않습니다 {{ .detail }}' +ErrPHPResource: '로컬 운영 환경이 전환을 지원하지 않습니다! ' +ErrPathPermission: '인덱스 디렉토리에서 1000:1000이 아닌 권한을 가진 폴더가 감지되었습니다. 이로 인해 웹사이트에서 액세스 거부 오류가 발생할 수 있습니다. 위의 저장 버튼을 클릭해주세요.' +ErrDomainIsUsed: '[{{ .name }}] 웹사이트에서 이미 도메인 이름을 사용하고 있습니다.' +ErrDomainFormat: '{{ .name }} 도메인 이름 형식이 올바르지 않습니다' +ErrDefaultAlias: '기본값은 예약된 코드입니다. 다른 코드를 사용하세요' +ErrParentWebsite: '먼저 하위 사이트 {{ .name }}을 삭제해야 합니다.' +ErrBuildDirNotFound: '빌드 디렉토리가 존재하지 않습니다' +ErrImageNotExist: '운영 환경 {{ .name }} 이미지가 존재하지 않습니다. 운영 환경을 다시 편집하세요.' +ErrProxyIsUsed: "로드 밸런싱이 역방향 프록시에 의해 사용되었으므로 삭제할 수 없습니다" +ErrSSLValid: '인증서 파일에 문제가 있습니다. 인증서 상태를 확인하세요!' +ErrWebsiteDir: "웹사이트 디렉토리 내의 디렉토리를 선택하세요." +ErrComposerFileNotFound: "composer.json 파일이 존재하지 않습니다" +ErrRuntimeNoPort: "런타임 환경에 포트가 설정되지 않았습니다. 먼저 런타임 환경을 편집하세요." +Status: '상태' +start: '시작' +stop: '중지' +restart: '재시작' +kill: '강제 중지' +pause: '일시 정지' +unpause: '재개' +remove: '삭제' +delete: '삭제' +ErrDefaultWebsite: '기본 웹사이트가 이미 설정되었습니다. 설정하기 전에 취소하세요!' +SSL: '인증서' +Set: '설정' + +#SSL인증 +ErrSSLCannotDelete: '{{ .name }} 인증서는 웹사이트에서 사용 중이므로 삭제할 수 없습니다.' +ErrAccountCannotDelete: '계정이 인증서와 연결되어 있어 삭제할 수 없습니다.' +ErrSSLApply: '인증서 갱신이 성공했으나, openresty 재로드에 실패했습니다. 구성을 확인해 주세요!' +ErrEmailIsExist: '사서함이 이미 존재합니다' +ErrSSLKeyNotFound: '개인 키 파일이 존재하지 않습니다' +ErrSSLCertificateNotFound: '인증서 파일이 존재하지 않습니다' +ErrSSLKeyFormat: '개인 키 파일 검증에 실패했습니다' +ErrSSLCertificateFormat: '인증서 파일 형식이 올바르지 않습니다. pem 형식을 사용하세요.' +ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 또는 EabHmacKey는 비워둘 수 없습니다' +ErrOpenrestyNotFound: 'Http 모드를 사용하려면 먼저 Openresty를 설치해야 합니다.' +ApplySSLStart: '인증서 신청 시작, 도메인 이름 [{{ .domain }}] 신청 방법 [{{ .type }}] ' +dnsAccount: 'DNS 자동' +dnsManual: 'DNS 매뉴얼' +http: 'HTTP' +ApplySSLFailed: '[{{ .domain }}] 인증서 신청이 실패했습니다. {{ .detail }} ' +ApplySSLSuccess: '[{{ .domain }}] 인증서 신청이 성공적으로 완료되었습니다! ! ' +DNSAccountName: 'DNS 계정 [{{ .name }}] 공급업체 [{{ .type }}]' +PushDirLog: '인증서가 [{{ .path }}] {{ .status }} 디렉토리로 푸시되었습니다.' +ErrDeleteCAWithSSL: '현재 조직에는 발급된 인증서가 있어 삭제할 수 없습니다.' +ErrDeleteWithPanelSSL: '패널 SSL 구성은 이 인증서를 사용하므로 삭제할 수 없습니다.' +ErrDefaultCA: '기본 권한을 삭제할 수 없습니다' +ApplyWebSiteSSLLog: '{{ .name }} 웹사이트 인증서 갱신 시작' +ErrUpdateWebsiteSSL: '{{ .name }} 웹사이트 인증서 업데이트에 실패했습니다: {{ .err }}' +ApplyWebSiteSSLSuccess: '웹사이트 인증서 업데이트 성공' +ErrExecShell: '스크립트 {{ .err }}을 실행하지 못했습니다.' +ExecShellStart: '스크립트 실행 시작' +ExecShellSuccess: '스크립트 실행 성공' +StartUpdateSystemSSL: '시스템 인증서 업데이트 시작' +UpdateSystemSSLSuccess: '시스템 인증서 업데이트가 성공적으로 완료되었습니다.' +ErrWildcardDomain: 'HTTP 모드에서 와일드카드 도메인 이름 인증서를 신청할 수 없습니다' +ErrApplySSLCanNotDelete: "신청 중인 인증서 {{.name}}는 삭제할 수 없습니다. 나중에 다시 시도해 주세요." +StartPushSSLToNode: "인증서를 노드로 푸시 시작" +PushSSLToNodeFailed: "노드로 인증서 푸시 실패: {{ .err }}" +PushSSLToNodeSuccess: "노드로 인증서 푸시 성공" + +#마이SQL +ErrUserIsExist: '현재 사용자가 이미 존재합니다. 다시 입력하세요' +ErrDatabaseIsExist: '현재 데이터베이스가 이미 존재합니다. 다시 입력하세요' +ErrExecTimeOut: 'SQL 실행 시간이 초과되었습니다. 데이터베이스를 확인하십시오.' +ErrRemoteExist: '이 이름을 가진 원격 데이터베이스가 이미 존재합니다. 수정하고 다시 시도하세요' +ErrLocalExist: '이름이 로컬 데이터베이스에 이미 존재합니다. 이름을 수정하고 다시 시도하세요' +RemoteBackup: "원격 데이터베이스를 백업하려면 먼저 이미지 {{ .name }}을(를) 사용하여 로컬 컨테이너 데이터베이스 서비스를 시작해야 합니다. 잠시만 기다려 주세요..." +RemoteRecover: "원격 데이터베이스를 복원하려면 먼저 이미지 {{ .name }}을(를) 사용하여 로컬 컨테이너 데이터베이스 서비스를 시작해야 합니다. 잠시만 기다려 주세요..." +Arg: "인수" + +#레디스 +ErrTypeOfRedis: '복구 파일 유형이 현재 지속성 방법과 일치하지 않습니다. 수정하고 다시 시도하세요' + +#컨테이너 +ErrInUsed: "{{ .detail }}(이)가 사용 중이어서 삭제할 수 없습니다!" +ErrObjectInUsed: "이 객체는 사용 중이어서 삭제할 수 없습니다!" +ErrObjectBeDependent: "이 이미지는 다른 이미지에 의존되어 있어 삭제할 수 없습니다!" +ErrPortRules: '포트 번호가 일치하지 않습니다. 다시 입력하세요!' +ErrPgImagePull: '이미지 풀링 시간이 초과되었습니다. 이미지 가속을 구성하거나 {{ .name }} 이미지를 수동으로 풀링한 다음 다시 시도하세요.' +PruneHelper: "이번 정리 {{ .name }}가 디스크 공간 {{ .size }}을(를) 확보했습니다" +ImageRemoveHelper: "이미지 {{ .name }} 삭제, {{ .size }} 디스크 공간 확보" +BuildCache: "빌드 캐시" +Volume: "스토리지 볼륨" +Network: "네트워크" +PruneStart: "정리 중입니다. 잠시만 기다려주세요..." + +#실행 시간 +ErrFileNotExist: '{{ .detail }} 파일이 존재하지 않습니다! 소스 파일의 무결성을 확인하세요!' +ErrImageBuildErr: '이미지 빌드 실패' +ErrImageExist: "이미지가 이미 존재합니다! 이미지 이름을 수정하세요." +ErrDelWithWebsite: '운영 환경이 이미 웹사이트와 연결되어 있어 삭제할 수 없습니다' +ErrRuntimeStart: '시작 실패' +ErrPackageJsonNotFound: 'package.json 파일이 존재하지 않습니다' +ErrScriptsNotFound: 'package.json에서 스크립트 구성 항목을 찾을 수 없습니다.' +ErrContainerNameNotFound: '컨테이너 이름을 가져올 수 없습니다. .env 파일을 확인하세요.' +ErrNodeModulesNotFound: 'node_modules 폴더가 없습니다! 런타임 환경을 편집하거나 런타임 환경이 성공적으로 시작될 때까지 기다리십시오' +ErrContainerNameIsNull: '컨테이너 이름이 존재하지 않습니다' +ErrPHPPortIsDefault: "9000 포트는 기본 포트입니다. 수정 후 다시 시도하세요" +ErrPHPRuntimePortFailed: "포트 {{ .name }} 는 현재 런타임 환경에서 이미 사용 중입니다. 수정 후 다시 시도하세요" + +#도구 +ErrConfigNotFound: '구성 파일이 존재하지 않습니다' +ErrConfigParse: '구성 파일 형식이 올바르지 않습니다' +ErrConfigIsNull: '구성 파일은 비어 있을 수 없습니다' +ErrConfigDirNotFound: '실행 디렉토리가 존재하지 않습니다' +ErrConfigAlreadyExist: '같은 이름의 구성 파일이 이미 존재합니다' +ErrUserFindErr: '사용자 {{ .name }} 검색에 실패했습니다 {{ .err }}' + +#크론잡 +CutWebsiteLogSuccess: '{{ .name }} 웹사이트 로그가 성공적으로 잘렸습니다. 백업 경로 {{ .path }}' +HandleShell: '스크립트 {{ .name }} 실행' +HandleCurl: "URL {{ .name }} 접근" +HandleNtpSync: '시스템 시간 동기화' +HandleSystemClean: '시스템 캐시 정리' +SystemLog: '시스템 로그' +CutWebsiteLog: '웹사이트 로그 회전' +FileOrDir: '디렉터리 / 파일' +UploadFile: '백업 파일 {{ .file }} 을(를) {{ .backup }}(으)로 업로드 중' +Upload: "업로드" +IgnoreBackupErr: "백업 실패, 오류: {{ .detail }}, 이 오류를 무시합니다..." +IgnoreUploadErr: "업로드 실패, 오류: {{ .detail }}, 이 오류를 무시합니다..." +LoadBackupFailed: "백업 계정 연결 획득 실패, 오류: {{ .detail }}" +InExecuting: "현재 작업이 실행 중입니다. 중복 실행하지 마세요!" +NoSuchResource: "데이터베이스에서 백업 내용을 찾을 수 없어 건너뜁니다..." +CleanLog: "로그 정리" +CleanLogByName: "{{.name}} 로그 정리" +WafIpGroupNotFound: "WAF IP 그룹을 찾을 수 없습니다. [고급 기능 - WAF - 블랙/화이트 목록 - IP 그룹]으로 이동하여 원격 다운로드 방식으로 IP 그룹을 추가하십시오." + +#도구상자 +ErrNotExistUser: '현재 사용자가 존재하지 않습니다. 수정한 후 다시 시도하세요!' +ErrBanAction: '설정에 실패했습니다. 현재 {{ .name }} 서비스를 사용할 수 없습니다. 확인하고 다시 시도하세요!' +ErrClamdscanNotFound: 'clamdscan 명령이 감지되지 않았습니다. 설명서를 참조하여 설치하세요!' +TaskSwapSet: "Swap 설정" +SetSwap: "Swap 설정 {{ .path }} - {{ .size }}" +CreateSwap: "Swap 파일 생성" +FormatSwap: "Swap 파일 포맷" +EnableSwap: "Swap 활성화" + +#tamper +CleanTamperSetting: "히스토리 보호 설정 정리" +SetTamperWithRules: "규칙에 따라 디렉토리 보호 시작" +ProtectDir: "디렉토리 보호 {{ .name }}" +IgnoreFile: "파일 보호 {{ .name }}" +TamperSetSuccessful: "보호 설정 완료, 다음 디렉토리 모니터링 시작:" + +#와프 +ErrScope: '이 구성을 수정하는 것은 지원되지 않습니다' +ErrStateChange: '상태 변경에 실패했습니다' +ErrRuleExist: '규칙이 이미 존재합니다' +ErrRuleNotExist: '규칙이 존재하지 않습니다' +ErrParseIP: '잘못된 IP 형식' +ErrDefaultIP: '기본값은 예약된 이름입니다. 다른 이름으로 변경해 주세요' +ErrGroupInUse: 'IP 그룹이 블랙리스트/화이트리스트에 사용 중이므로 삭제할 수 없습니다.' +ErrIPGroupAclUse: "IP 그룹은 웹사이트 {{ .name }} 의 사용자 정의 규칙에 사용되므로 삭제할 수 없습니다" +ErrGroupExist: 'IP 그룹 이름이 이미 존재합니다' +ErrIPRange: '잘못된 IP 범위' +ErrIPExist: 'IP가 이미 존재합니다' +urlDefense: 'URL 규칙' +urlHelper: '금지된 URL' +dirFilter: '디렉토리 필터' +xss: 'XSS' +phpExec: 'PHP 스크립트 실행' +oneWordTrojan: '한 단어 트로이' +appFilter: '위험한 디렉토리 필터링 적용' +webshell: '웹쉘' +args: '매개변수 규칙' +protocolFilter: '프로토콜 필터링' +javaFileter: 'Java 위험 파일 필터' +scannerFilter: '스캐너 필터' +escapeFilter: '탈출 필터' +customRule: '사용자 정의 규칙' +httpMethod: 'HTTP 메서드 필터링' +fileExt: '파일 업로드 제한' +defaultIpBlack: '악성 IP 그룹' +cookie: '쿠키 규칙' +urlBlack: 'URL 블랙리스트' +uaBlack: '사용자 에이전트 블랙리스트' +attackCount: '공격 빈도 제한' +fileExtCheck: '파일 업로드 제한' +geoRestrict: '지역 접근 제한' +unknownWebsite: '허가받지 않은 도메인 이름 접근' +notFoundCount: '404 속도 제한' +headerDefense: '헤더 규칙' +defaultUaBlack: '사용자 에이전트 규칙' +methodWhite: 'HTTP 규칙' +captcha: '인간-기계 검증' +fiveSeconds: '5초 검증' +vulnCheck: '보충 규칙' +acl: '사용자 정의 규칙' +sql: 'SQL 주입' +cc: '접근 주파수 제한' +defaultUrlBlack: 'URL 규칙' +sqlInject: 'SQL 주입' +ErrDBNotExist: '데이터베이스가 존재하지 않습니다' +allow: '허용하다' +deny: '거부하다' +OpenrestyNotFound: 'Openresty가 설치되지 않았습니다' +remoteIpIsNull: "IP 목록이 비어 있습니다" +OpenrestyVersionErr: "Openresty 버전이 너무 낮습니다. Openresty를 1.27.1.2-2-2-focal로 업그레이드하세요" + +#일 +TaskStart: '[START] {{ .name }} 작업이 시작됩니다.' +TaskEnd: '{{ .name }} 작업이 완료되었습니다 [완료]' +TaskFailed: '{{ .name }} 작업이 실패했습니다' +TaskTimeout: '{{ .name }}의 시간이 초과되었습니다' +TaskSuccess: '{{ .name }} 작업이 성공했습니다' +TaskRetry: '{{ .name }}번째 재시도 시작' +SubTaskSuccess: '{{ .name }}이 성공했습니다' +SubTaskFailed: '{{ .name }}이 실패했습니다: {{ .err }}' +TaskInstall: '설치' +TaskUninstall: '제거' +TaskCreate: '생성' +TaskDelete: '삭제' +TaskUpgrade: '업그레이드' +TaskUpdate: '업데이트' +TaskRestart: '다시 시작' +TaskProtect: "보호" +TaskBackup: '백업' +TaskRecover: '복구' +TaskRollback: '롤백' +TaskPull: '당기기' +TaskCommit: '커밋' +TaskBuild: '빌드' +TaskPush: '푸시' +TaskClean: "정리" +TaskHandle: '실행' +TaskImport: "가져오기" +TaskExport: "내보내기" +Website: '웹사이트' +App: '애플리케이션' +Runtime: '런타임 환경' +Database: '데이터베이스' +ConfigFTP: 'FTP 사용자 {{ .name }} 생성' +ConfigOpenresty: 'Openresty 구성 파일 생성' +InstallAppSuccess: '애플리케이션 {{ .name }}이 성공적으로 설치되었습니다.' +ConfigRuntime: '런타임 환경 구성' +ConfigApp: '구성 애플리케이션' +SuccessStatus: '{{ .name }}이 성공했습니다' +FailedStatus: '{{ .name }}이(가) {{ .err }}에 실패했습니다' +HandleLink: '핸들 애플리케이션 연결' +HandleDatabaseApp: '애플리케이션 매개변수 처리' +ExecShell: '{{ .name }} 스크립트 실행' +PullImage: '이미지 가져오기' +Start: '시작' +Run: '시작' +Stop: '정지' +Image: '거울' +Compose : '오케스트레이션' +Container: '컨테이너' +AppLink: '연결된 애플리케이션' +EnableSSL: 'HTTPS 활성화' +AppStore: '앱스토어' +TaskSync: '동기화' +LocalApp: '로컬 애플리케이션' +SubTask: '하위 작업' +RuntimeExtension: '런타임 환경 확장' +TaskIsExecuting: '작업이 실행 중입니다' +CustomAppstore: '사용자 정의 애플리케이션 웨어하우스' +TaskExec: '실행' +TaskBatch: "일괄 작업" +FileConvert: '파일 형식 변환' + +# task - clam +Clamscan: "{{ .name }} 스캔" +TaskScan: "스캔" + +# 작업 - ai +OllamaModelPull: 'Ollama 모델 {{ .name }}을(를) 끌어오세요' +OllamaModelSize: 'Ollama 모델 {{ .name }}의 크기를 가져옵니다' + +# 작업-스냅샷 +Snapshot: '스냅샷' +SnapDBInfo: '1Panel 데이터베이스 정보 쓰기' +SnapCopy: '파일 및 디렉토리 {{ .name }} 복사' +SnapNewDB: '데이터베이스 {{ .name }} 연결 초기화' +SnapDeleteOperationLog: '작업 로그 삭제' +SnapDeleteLoginLog: '액세스 로그 삭제' +SnapDeleteMonitor: '모니터링 데이터 삭제' +SnapRemoveSystemIP: '시스템 IP 제거' +SnapBaseInfo: '1패널 기본 정보 쓰기' +SnapInstallAppImageEmpty: '애플리케이션 이미지가 선택되지 않아 건너뜁니다...' +SnapInstallApp: '1Panel 설치된 애플리케이션 백업' +SnapDockerSave: '설치된 애플리케이션 압축' +SnapLocalBackup: '1Panel 로컬 백업 디렉토리 백업' +SnapCompressBackup: '로컬 백업 디렉토리 압축' +SnapPanelData: '1Panel 데이터 디렉토리 백업' +SnapCompressPanel: '압축 데이터 디렉토리' +SnapWebsite: '1Panel 웹사이트 디렉토리 백업' +SnapCloseDBConn: '데이터베이스 연결 닫기' +SnapCompress: '스냅샷 파일 만들기' +SnapCompressFile: '스냅샷 파일 압축' +SnapCheckCompress: '스냅샷 압축 파일 확인' +SnapCompressSize: '스냅샷 파일 크기 {{ .name }}' +SnapUpload: '스냅샷 파일 업로드' +SnapUploadTo: '스냅샷 파일을 {{ .name }}에 업로드' +SnapUploadRes: '스냅샷 파일을 {{ .name }}에 업로드' + +SnapshotRecover: '스냅샷 복원' +RecoverDownload: '스냅샷 파일 다운로드' +Download: '다운로드' +RecoverDownloadAccount: '스냅샷 다운로드 백업 계정 {{ .name }} 가져오기' +RecoverDecompress: '스냅샷 압축 파일 압축 해제' +Decompress: '감압' +BackupBeforeRecover: '스냅샷 전에 시스템 관련 데이터 백업' +Readjson: '스냅샷에서 Json 파일을 읽습니다' +ReadjsonPath: '스냅샷의 Json 파일 경로 가져오기' +ReadjsonContent: 'Json 파일 읽기' +ReadjsonMarshal: 'Json 이스케이프 처리' +RecoverApp: '설치된 앱 복원' +RecoverWebsite: '웹사이트 디렉토리 복구' +RecoverAppImage: '스냅샷 이미지 백업 복원' +RecoverCompose: '다른 Composer 콘텐츠 복원' +RecoverComposeList: '모든 작곡가를 복원합니다' +RecoverComposeItem: '작성 {{ .name }} 복구' +RecoverAppEmpty: '스냅샷 파일에서 애플리케이션 이미지 백업을 찾을 수 없습니다' +RecoverBaseData: '기본 데이터 및 파일 복구' +RecoverDaemonJsonEmpty: '스냅샷 파일과 현재 머신 모두 컨테이너 구성 daemon.json 파일이 없습니다.' +RecoverDaemonJson: '컨테이너 구성 daemon.json 파일 복원' +RecoverDBData: '데이터베이스 데이터 복구' +RecoverBackups: '로컬 백업 디렉토리 복원' +RecoverPanelData: '복구 데이터 디렉토리' + +# 작업 - 컨테이너 +ContainerNewCliet: 'Docker 클라이언트 초기화' +ContainerImagePull: '컨테이너 이미지 {{ .name }} 가져오기' +ContainerRemoveOld: '원래 컨테이너 {{ .name }} 제거' +ContainerImageCheck: '이미지가 정상적으로 당겨졌는지 확인' +ContainerLoadInfo: '기본 컨테이너 정보 가져오기' +ContainerRecreate: '컨테이너 업데이트에 실패했습니다. 이제 원래 컨테이너를 복원하기 시작합니다.' +ContainerCreate: '새로운 컨테이너 {{ .name }}를 만듭니다' +ContainerCreateFailed: '컨테이너 생성에 실패했습니다. 실패한 컨테이너를 삭제하세요' +ContainerStartCheck: '컨테이너가 시작되었는지 확인' + +# 작업 - 이미지 +ImageBuild: '이미지 빌드' +ImageBuildStdoutCheck: '이미지 출력 콘텐츠 구문 분석' +ImageBuildRes: '이미지 빌드 출력: {{ .name }}' +ImagePull: '이미지 가져오기' +ImageRepoAuthFromDB: '데이터베이스에서 저장소 인증 정보 가져오기' +ImaegPullRes: '이미지 풀 출력: {{ .name }}' +ImagePush: '이미지 푸시' +ImageRenameTag: '이미지 태그 수정' +ImageNewTag: '새로운 이미지 태그 {{ .name }}' +ImaegPushRes: '이미지 푸시 출력: {{ .name }}' +ComposeCreate: '작곡 만들기' +ComposeCreateRes: 'Compose 생성 출력: {{ .name }}' + +# 작업 - 웹사이트 +BackupNginxConfig: '웹사이트 OpenResty 구성 파일 백업' +CompressFileSuccess: '디렉토리 압축이 성공했습니다. {{ .name }}으로 압축되었습니다.' +CompressDir: '압축 디렉토리' +DeCompressFile: '파일 {{ .name }} 압축 해제' +ErrCheckValid: '백업 파일 검증에 실패했습니다, {{ .name }}' +Rollback: '롤백' +websiteDir: '웹사이트 디렉토리' +RecoverFailedStartRollBack: '복구 실패, 롤백 시작' +AppBackupFileIncomplete: '백업 파일이 불완전하며 app.json 또는 app.tar.gz 파일이 없습니다.' +AppAttributesNotMatch: '애플리케이션 유형 또는 이름이 일치하지 않습니다' + +#알리다 +ErrAlert: '경고 메시지의 형식이 올바르지 않습니다. 확인하고 다시 시도하세요!' +ErrAlertPush: '알림 정보를 푸시하는 중 오류가 발생했습니다. 확인하고 다시 시도하세요!' +ErrAlertSave: '알람 정보를 저장하는 중 오류가 발생했습니다. 확인하고 다시 시도하세요!' +ErrAlertSync: '알람 정보 동기화 오류입니다. 확인하고 다시 시도하세요!' +ErrAlertRemote: '알람 메시지 원격 오류, 확인하고 다시 시도하세요!' + +#task - runtime +ErrInstallExtension: "이미 설치 작업이 진행 중입니다. 작업이 완료될 때까지 기다려 주세요." + +# alert mail template +PanelAlertTitle: "패널 알림 통지" +TestAlertTitle: "테스트 이메일 - 이메일 연결 확인" +TestAlert: "이것은 테스트 이메일이며 이메일 발송 설정이 올바른지 확인합니다." +LicenseExpirationAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 라이센스가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오." +CronJobFailedAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 예약 작업 {{ .name }} 실행에 실패했습니다. 자세한 내용은 패널에 로그인하십시오." +ClamAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 바이러스 스캔 작업에서 {{ .num }}개의 감염 파일이 감지되었습니다. 자세한 내용은 패널에 로그인하십시오." +WebSiteAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 웹사이트가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오." +SSLAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 SSL 인증서가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오." +DiskUsedAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 디스크 {{ .name }} 사용률이 {{ .used }}에 도달했습니다. 자세한 내용은 패널에 로그인하십시오." +ResourceAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 최근 {{ .time }}분간 {{ .name }} 평균 사용률은 {{ .used }}입니다. 자세한 내용은 패널에 로그인하십시오." +PanelVersionAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 최신 버전으로 업그레이드할 수 있습니다. 자세한 내용은 패널에 로그인하십시오." +PanelPwdExpirationAlert: "귀하의 {{ .node }}{{ .ip }} 패널, 패널 비밀번호가 {{ .day }}일 후에 만료됩니다. 자세한 내용은 패널에 로그인하십시오." +CommonAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .msg }}。자세한 내용은 패널에 로그인하십시오." +NodeExceptionAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 노드에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오." +LicenseExceptionAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .num }}개의 라이센스에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오." +SSHAndPanelLoginAlert: "귀하의 {{ .node }}{{ .ip }} 패널, {{ .loginIp }}에서의 {{ .name }} 로그인에 이상이 있습니다. 자세한 내용은 패널에 로그인하십시오." + +#disk +DeviceNotFound: "장치 {{ .name }} 을(를) 찾을 수 없습니다" +DeviceIsMounted: "장치 {{ .name }} 이(가) 마운트되었습니다, 먼저 마운트 해제하세요" +PartitionDiskErr: "파티션 분할에 실패했습니다, {{ .err }}" +FormatDiskErr: "디스크 포맷에 실패했습니다, {{ .err }}" +MountDiskErr: "디스크 마운트에 실패했습니다, {{ .err }}" +UnMountDiskErr: "디스크 마운트 해제에 실패했습니다, {{ .err }}" +XfsNotFound: "xfs 파일 시스템이 감지되지 않았습니다, 먼저 xfsprogs 를 설치하세요" \ No newline at end of file diff --git a/agent/i18n/lang/ms.yaml b/agent/i18n/lang/ms.yaml new file mode 100644 index 0000000..1a71cd5 --- /dev/null +++ b/agent/i18n/lang/ms.yaml @@ -0,0 +1,530 @@ +ErrInvalidParams: 'Permintaan ralat parameter: {{ .detail }}' +ErrTokenParse: 'Ralat penjanaan token: {{ .detail }}' +ErrInitialPassword: 'Kata laluan asal tidak betul' +ErrInternalServer: 'Ralat pelayan dalaman: {{ .detail }}' +ErrRecordExist: 'Rekod sudah wujud' +ErrRecordNotFound: 'Rekod tidak ditemui' +ErrStructTransform: 'Penukaran jenis gagal: {{ .err }}' +ErrNotLogin: 'Pengguna tidak log masuk: {{ .detail }}' +ErrPasswordExpired: 'Kata laluan semasa telah tamat tempoh: {{ .detail }}' +ErrNotSupportType: 'Sistem tidak menyokong jenis semasa: {{ .name }}' +ErrProxy: 'Ralat permintaan, sila semak status nod: {{ .detail }}' +ErrApiConfigStatusInvalid: 'Akses kepada antara muka API adalah dilarang: {{ .detail }}' +ErrApiConfigKeyInvalid: 'Ralat kunci antara muka API: {{ .detail }}' +ErrApiConfigIPInvalid: 'IP yang digunakan untuk memanggil antara muka API tiada dalam senarai putih: {{ .detail }}' +ErrApiConfigDisable: 'Antara muka ini melarang penggunaan panggilan antara muka API: {{ .detail }}' +ErrApiConfigKeyTimeInvalid: 'Ralat cap masa antara muka API: {{ .detail }}' +StartPushSSLToNode: "Mula menolak sijil ke nod" +PushSSLToNodeFailed: "Gagal menolak sijil ke nod: {{ .err }}" +PushSSLToNodeSuccess: "Berjaya menolak sijil ke nod" +ErrShutDown: "Arahan dihentikan secara manual!" + +ErrMinQuickJump: "Sila tetapkan sekurang-kurangnya satu entri lompat pantas!" +ErrMaxQuickJump: "Anda boleh menetapkan sehingga empat entri lompat pantas!" + +#biasa +ErrUsernameIsExist: 'Nama pengguna sudah wujud' +ErrNameIsExist: 'Nama sudah wujud' +ErrDemoEnvironment: 'Pelayan demo, operasi ini dilarang!' +ErrCmdTimeout: 'Pelaksanaan arahan tamat masa!' +ErrCmdIllegal: 'Terdapat aksara yang menyalahi undang-undang dalam perintah pelaksanaan, sila ubah suainya dan cuba lagi!' +ErrPortExist: '{{ .port }} port sudah diduduki oleh {{ .type }} [{{ .name }}]' +TYPE_APP: 'Aplikasi' +TYPE_RUNTIME: 'Persekitaran masa jalan' +TYPE_DOMAIN: 'Nama Domain' +ErrTypePort: Format 'Port {{ .name }} tidak betul' +ErrTypePortRange: 'Julat port perlu antara 1-65535' +Success: 'Kejayaan' +Failed: 'Gagal' +SystemRestart: 'Tugas terganggu kerana sistem mula semula' +ErrGroupIsDefault: 'Kumpulan lalai, tidak boleh dipadamkan' +ErrGroupIsInWebsiteUse: 'Kumpulan sedang digunakan oleh tapak web lain dan tidak boleh dipadamkan.' +Decrypt: "Dekripsi" + +#sandaran +Localhost: 'Mesin Tempatan' +ErrBackupInUsed: 'Akaun sandaran telah digunakan dalam tugas yang dijadualkan dan tidak boleh dipadamkan.' +ErrBackupCheck: 'Sambungan ujian akaun sandaran gagal {{ .err }}' +ErrBackupLocalDelete: 'Memadam akaun sandaran pelayan tempatan belum disokong lagi' +ErrBackupLocalCreate: 'Membuat akaun sandaran pelayan tempatan belum disokong lagi' + +#app +ErrPortInUsed: 'Port {{ .detail }} sudah diduduki!' +ErrAppLimit: 'Bilangan aplikasi yang dipasang telah melebihi had' +ErrNotInstall: 'Aplikasi tidak dipasang' +ErrPortInOtherApp: 'Port {{ .port }} sudah diduduki oleh aplikasi {{ .apps }}!' +ErrDbUserNotValid: 'Pangkalan data sedia ada, nama pengguna dan kata laluan tidak sepadan!' +ErrUpdateBuWebsite: 'Aplikasi telah berjaya dikemas kini, tetapi pengubahsuaian fail konfigurasi tapak web gagal. Sila semak konfigurasi! ' +Err1PanelNetworkFailed: 'Pembuatan rangkaian kontena lalai gagal! {{ .detail }}' +ErrFileParse: 'Penghuraian fail karang docker aplikasi gagal!' +ErrInstallDirNotFound: 'Direktori pemasangan tidak wujud. Jika anda perlu menyahpasang, sila pilih Paksa Nyahpasang' +AppStoreIsUpToDate: 'Gedung aplikasi sudah pun versi terkini' +LocalAppVersionNull: Aplikasi '{{ .name }} tidak disegerakkan ke versi! Tidak dapat menambah pada senarai apl' +LocalAppVersionErr: '{{ .name }} versi penyegerakan {{ .version }} gagal! {{ .err }}' +ErrFileNotFound: 'Fail {{ .name }} tidak wujud' +ErrFileParseApp: '{{ .name }} penghuraian fail gagal {{ .err }}' +ErrAppDirNull: 'Folder versi tidak wujud' +LocalAppErr: 'Penyegerakan {{ .name }} aplikasi gagal! {{ .err }}' +ErrContainerName: 'Nama kontena sudah wujud' +ErrCreateHttpClient: 'Gagal membuat permintaan {{ .err }}' +ErrHttpReqTimeOut: 'Permintaan tamat masa {{ .err }}' +ErrHttpReqFailed: 'Permintaan gagal {{ .err }}' +ErrNoSuchHost: 'Tidak dapat mencari pelayan yang diminta {{ .err }}' +ErrHttpReqNotFound: 'Sumber yang diminta {{ .err }} tidak dapat ditemui' +ErrContainerNotFound: 'Bekas {{ .name }} tidak wujud' +ErrContainerMsg: Bekas '{{ .name }} adalah tidak normal. Sila semak log pada halaman kontena untuk butiran' +ErrAppBackup: '{{ .name }} sandaran aplikasi gagal {{ .err }}' +ErrVersionTooLow: 'Versi 1Panel semasa terlalu rendah untuk mengemas kini App Store. Sila tingkatkan versi sebelum beroperasi.' +ErrAppNameExist: 'Nama aplikasi sudah wujud' +AppStoreIsSyncing: 'App Store sedang menyegerak, sila cuba sebentar lagi' +ErrGetCompose: 'Gagal mendapatkan fail docker-compose.yml! {{ .detail }}' +ErrAppWarn: 'Status tidak normal, sila semak log' +ErrAppParamKey: 'Parameter {{ .name }} medan tidak normal' +ErrAppUpgrade: 'Peningkatan {{ .name }} aplikasi gagal {{ .err }}' +AppRecover: 'Aplikasi tarik balik {{ .name }}' +PullImageStart: 'Mula tarik imej [{{ .name }}]' +PullImageSuccess: 'Tarik imej berjaya' +AppStoreIsLastVersion: 'App Store sudah pun versi terkini' +AppStoreSyncSuccess: 'Penyegerakan App Store berjaya' +SyncAppDetail: 'Segerakkan konfigurasi aplikasi' +AppVersionNotMatch: 'Aplikasi {{ .name }} memerlukan versi 1Panel yang lebih tinggi, melangkau penyegerakan' +MoveSiteDir: "Perubahan direktori laman web didapati. Perlu memindahkan direktori laman web OpenResty ke {{ .name }}" +MoveSiteDirSuccess: 'Penghijrahan direktori tapak web yang berjaya' +DeleteRuntimePHP: 'Padam masa jalan PHP' +CustomAppStoreFileValid: 'Pakej gedung apl perlu dalam format .tar.gz' +PullImageTimeout: 'Tarik tamat masa imej, sila cuba tingkatkan pecutan imej atau tukar kepada pecutan imej lain' +ErrAppIsDown: 'Status permohonan {{ .name }} tidak normal, sila semak' +ErrCustomApps: 'Ada aplikasi yang dipasang, sila nyahpasangnya dahulu' +ErrCustomRuntimes: 'Terdapat persekitaran masa jalan yang dipasang, sila padamkannya dahulu' +ErrAppVersionDeprecated: "Aplikasi {{ .name }} tidak sesuai dengan versi 1Panel saat ini, dilewati" +ErrDockerFailed: "Keadaan Docker tidak normal, sila periksa status perkhidmatan" +ErrDockerComposeCmdNotFound: "Perintah Docker Compose tidak wujud, sila pasang perintah ini di mesin tuan terlebih dahulu" +UseExistImage: 'Gagal menarik imej, menggunakan imej sedia ada' + +#ssh +ExportIP: "IP Log Masuk" +ExportArea: "Lokasi" +ExportPort: "Port" +ExportAuthMode: "Kaedah Log Masuk" +ExportUser: "Pengguna" +ExportStatus: "Status Log Masuk" +ExportDate: "Masa" + +#fail +ErrFileCanNotRead: 'Fail ini tidak menyokong pratonton' +ErrFileToLarge: 'Fail lebih besar daripada 10M dan tidak boleh dibuka' +ErrPathNotFound: 'Direktori tidak wujud' +ErrMovePathFailed: 'Laluan sasaran tidak boleh mengandungi laluan asal!' +ErrLinkPathNotFound: 'Laluan sasaran tidak wujud!' +ErrFileIsExist: 'Fail atau folder sudah wujud!' +ErrFileUpload: '{{ .name }} gagal memuat naik fail {{ .detail }}' +ErrFileDownloadDir: 'Folder muat turun tidak disokong' +ErrCmdNotFound: 'Arahan {{ .name}} tidak wujud, sila pasang arahan ini pada hos dahulu' +ErrSourcePathNotFound: 'Direktori sumber tidak wujud' +ErrFavoriteExist: 'Laluan ini telah digemari' +ErrInvalidChar: 'Aksara haram tidak dibenarkan' +ErrPathNotDelete: 'Direktori yang dipilih tidak boleh dipadamkan' +ErrLogFileTooLarge: "Fail log melebihi 500MB dan tidak boleh dibuka" +FileDropFailed: "Gagal membersihkan fail {{ .name }}: {{ .err }}" +FileDropSuccess: "Berjaya membersihkan fail {{ .name }}, {{ .count }} fail dibersihkan, {{ .size }} ruang cakera dibebaskan" +FileDropSum: "Pembersihan fail selesai, sejumlah {{ .count }} fail dibersihkan, sejumlah {{ .size }} ruang cakera dibebaskan" +ErrBadDecrypt: "Kata laluan penyahsulitan pakej termampat salah, penyahmampatan gagal, sila periksa dan cuba lagi!" + +#laman web +ErrAliasIsExist: 'Alias sudah wujud' +ErrBackupMatch: 'Fail sandaran tidak sepadan dengan beberapa data tapak web semasa {{ .detail }}' +ErrBackupExist: 'Bahagian sepadan data sumber dalam fail sandaran tidak wujud {{ .detail }}' +ErrPHPResource: 'Persekitaran operasi tempatan tidak menyokong penukaran! ' +ErrPathPermission: 'Folder dengan keizinan bukan 1000:1000 telah dikesan dalam direktori indeks, yang mungkin menyebabkan ralat dinafikan Akses pada tapak web. Sila klik butang Simpan di atas' +ErrDomainIsUsed: 'Nama domain telah digunakan oleh tapak web [{{ .name }}]' +ErrDomainFormat: 'Format nama domain {{ .name }} tidak betul' +ErrDefaultAlias: 'lalai ialah kod simpanan, sila gunakan kod lain' +ErrParentWebsite: 'Anda perlu memadamkan subtapak {{ .name }} dahulu' +ErrBuildDirNotFound: 'Direktori binaan tidak wujud' +ErrImageNotExist: 'Imej persekitaran operasi {{ .name }} tidak wujud, sila edit semula persekitaran pengendalian' +ErrProxyIsUsed: "Pengimbang beban telah digunakan oleh pengganti terbalik, tidak boleh dipadamkan" +ErrSSLValid: 'Fail sijil bermasalah, sila periksa status sijil!' +ErrWebsiteDir: "Sila pilih direktori dalam direktori laman web." +ErrComposerFileNotFound: "Fail composer.json tidak wujud" +ErrRuntimeNoPort: "Persekitaran runtime tidak diset dengan port, sila edit persekitaran runtime terlebih dahulu." +Status: 'Status' +start: 'Mulakan' +stop: 'Berhenti' +restart: 'Mulakan Semula' +kill: 'Hentikan Paksa' +pause: 'Jeda' +unpause: 'Sambung Semula' +remove: 'Padam' +delete: 'Padam' +ErrDefaultWebsite: 'Laman web lalai telah ditetapkan, sila batalkan sebelum menetapkan!' +SSL: 'Sijil' +Set: 'Tetapan' + +#ssl +ErrSSLCannotDelete: 'Sijil {{ .name }} sedang digunakan oleh tapak web dan tidak boleh dipadamkan' +ErrAccountCannotDelete: 'Akaun dikaitkan dengan sijil dan tidak boleh dipadamkan' +ErrSSSLApply: 'Pembaharuan sijil berjaya, muat semula openresty gagal, sila semak konfigurasi!' +ErrEmailIsExist: 'Peti mel sudah wujud' +ErrSSLKeyNotFound: 'Fail kunci peribadi tidak wujud' +ErrSSLCertificateNotFound: 'Fail sijil tidak wujud' +ErrSSLKeyFormat: 'Pengesahan fail kunci peribadi gagal' +ErrSSLCertificateFormat: 'Format fail sijil tidak betul, sila gunakan format pem' +ErrEabKidOrEabHmacKeyCannot Blank: 'EabKid atau EabHmacKey tidak boleh kosong' +ErrOpenrestyNotFound: 'Mod Http memerlukan Openresty dipasang dahulu' +ApplySSLStart: 'Mula memohon sijil, nama domain [{{ .domain }}] kaedah permohonan [{{ .type }}] ' +dnsAccount: 'DNS Auto' +dnsManual: 'Manual DNS' +http: 'HTTP' +ApplySSLFailed: 'Permohonan untuk sijil [{{ .domain }}] gagal, {{ .detail }} ' +ApplySSLSuccess: 'Berjaya memohon sijil [{{ .domain }}]! ! ' +DNSAccountName: 'Akaun DNS [{{ .name }}] vendor [{{ .type }}]' +PushDirLog: 'Sijil ditolak ke direktori [{{ .path }}] {{ .status }}' +ErrDeleteCAWithSSL: 'Organisasi semasa mempunyai sijil yang telah dikeluarkan dan tidak boleh dipadamkan.' +ErrDeleteWithPanelSSL: 'Konfigurasi SSL Panel menggunakan sijil ini dan tidak boleh dipadamkan' +ErrDefaultCA: 'Pihak berkuasa lalai tidak boleh dipadamkan' +ApplyWebSiteSSLLog: 'Mula memperbaharui sijil tapak web {{ .name }}' +ErrUpdateWebsiteSSL: '{{ .name }} kemas kini sijil tapak web gagal: {{ .err }}' +ApplyWebSiteSSLSuccess: 'Berjaya kemas kini sijil tapak web' +ErrExecShell: 'Gagal melaksanakan skrip {{ .err }}' +ExecShellStart: 'Mula melaksanakan skrip' +ExecShellSuccess: 'Pelaksanaan skrip berjaya' +StartUpdateSystemSSL: 'Mula mengemas kini sijil sistem' +UpdateSystemSSLSuccess: 'Berjaya kemas kini sijil sistem' +ErrWildcardDomain: 'Tidak dapat memohon sijil nama domain kad bebas dalam mod HTTP' +ErrApplySSLCanNotDelete: "Sertifikat {{.name}} yang sedang diajukan tidak dapat dihapus, sila cuba lagi kemudian." + +#mysql +ErrUserIsExist: 'Pengguna semasa sudah wujud, sila masukkan semula' +ErrDatabaseIsExist: 'Pangkalan data semasa sudah wujud, sila masukkan semula' +ErrExecTimeOut: 'Pelaksanaan SQL tamat masa, sila semak pangkalan data' +ErrRemoteExist: 'Pangkalan data jauh sudah wujud dengan nama ini, sila ubah suainya dan cuba lagi' +ErrLocalExist: 'Nama sudah wujud dalam pangkalan data tempatan, sila ubah suai dan cuba lagi' +RemoteBackup: "Untuk menyandarkan pangkalan data jauh, perkhidmatan pangkalan data bekas tempatan perlu dimulakan terlebih dahulu menggunakan imej {{ .name }}, sila tunggu..." +RemoteRecover: "Untuk memulihkan pangkalan data jauh, perkhidmatan pangkalan data bekas tempatan perlu dimulakan terlebih dahulu menggunakan imej {{ .name }}, sila tunggu..." +Arg: "Hujah" + +#redis +ErrTypeOfRedis: 'Jenis fail pemulihan tidak sepadan dengan kaedah kegigihan semasa, sila ubah suai dan cuba lagi' + +#bekas +ErrInUsed: "{{ .detail }} sedang digunakan dan tidak boleh dipadam!" +ErrObjectInUsed: "Objek ini sedang digunakan dan tidak boleh dipadam!" +ErrObjectBeDependent: "Imej ini digantungkan oleh imej lain dan tidak boleh dipadam!" +ErrPortRules: 'Nombor port tidak sepadan, sila masukkan semula!' +ErrPgImagePull: 'Tarikh imej tamat masa, sila konfigurasikan pecutan imej atau tarik imej {{ .name }} secara manual dan cuba lagi' +PruneHelper: "Pembersihan ini {{ .name }} membebaskan ruang cakera {{ .size }}" +ImageRemoveHelper: "Padam imej {{ .name }}, membebaskan {{ .size }} ruang cakera" +BuildCache: "Cache binaan" +Volume: "Jilid storan" +Network: "Rangkaian" +PruneStart: "Pembersihan sedang dijalankan, sila tunggu..." + +#masa berjalan +ErrFileNotExist: 'Fail {{ .detail }} tidak wujud! Sila semak integriti fail sumber!' +ErrImageBuildErr: 'Pembinaan imej gagal' +ErrImageExist: "Imej sudah wujud! Sila ubah nama imej." +ErrDelWithWebsite: 'Persekitaran pengendalian sudah dikaitkan dengan tapak web dan tidak boleh dipadamkan' +ErrRuntimeStart: 'Permulaan gagal' +ErrPackageJsonNotFound: 'fail package.json tidak wujud' +ErrScriptsNotFound: 'Item konfigurasi skrip tidak ditemui dalam package.json' +ErrContainerNameNotFound: 'Tidak dapat mendapatkan nama kontena, sila semak fail .env' +ErrNodeModulesNotFound: 'Folder node_modules tidak wujud! Sila edit persekitaran masa jalan atau tunggu persekitaran masa jalan berjaya dimulakan' +ErrContainerNameIsNull: 'Nama kontena tidak wujud' +ErrPHPPortIsDefault: "Порт 9000 является портом по умолчанию, пожалуйста, измените и попробуйте снова" +ErrPHPRuntimePortFailed: "Порт {{ .name }} уже используется текущей средой выполнения, пожалуйста, измените и попробуйте снова" + +#alat +ErrConfigNotFound: 'Fail konfigurasi tidak wujud' +ErrConfigParse: 'Format fail konfigurasi tidak betul' +ErrConfigIsNull: 'Fail konfigurasi tidak boleh kosong' +ErrConfigDirNotFound: 'Direktori berjalan tidak wujud' +ErrConfigAlreadyExist: 'Fail konfigurasi dengan nama yang sama sudah wujud' +ErrUserFindErr: 'Pengguna {{ .name }} carian gagal {{ .err }}' + +#cronjob +CutWebsiteLogSuccess: '{{ .name }} log tapak web berjaya dipotong, laluan sandaran {{ .path }}' +HandleShell: 'Laksanakan skrip {{ .name }}' +HandleCurl: "Akses URL {{ .name }}" +HandleNtpSync: 'Penyegerakan masa sistem' +HandleSystemClean: 'Pembersihan cache sistem' +SystemLog: 'Log Sistem' +CutWebsiteLog: 'Putar Log Laman Web' +FileOrDir: 'Direktori / Fail' +UploadFile: 'Muat naik fail sandaran {{ .file }} ke {{ .backup }}' +Upload: "Muat Naik" +IgnoreBackupErr: "Sandaran gagal, ralat: {{ .detail }}, abaikan ralat ini..." +IgnoreUploadErr: "Muat naik gagal, ralat: {{ .detail }}, abaikan ralat ini..." +LoadBackupFailed: "Gagal mendapatkan sambungan akaun sandaran, ralat: {{ .detail }}" +InExecuting: "Tugas semasa sedang dilaksanakan, jangan ulangi pelaksanaan!" +NoSuchResource: "Tiada kandungan sandaran ditemui dalam pangkalan data, dilangkau..." +CleanLog: "Bersihkan log" +CleanLogByName: "Bersihkan log {{.name}}" +WafIpGroupNotFound: "Kumpulan IP WAF tidak ditemui. Sila pergi ke [Ciri Lanjutan - WAF - Senarai Hitam/Putih - Kumpulan IP] untuk menambah kumpulan IP melalui kaedah muat turun jauh." + +#kotak alat +ErrNotExistUser: 'Pengguna semasa tidak wujud, sila ubah suai dan cuba lagi!' +ErrBanAction: 'Tetapan gagal. Perkhidmatan {{ .name }} semasa tidak tersedia. Sila semak dan cuba lagi!' +ErrClamdscanNotFound: 'Arahan clamdscan tidak dikesan, sila rujuk dokumentasi untuk memasangnya!' +TaskSwapSet: "Tetapkan Swap" +SetSwap: "Tetapkan Swap {{ .path }} - {{ .size }}" +CreateSwap: "Buat Fail Swap" +FormatSwap: "Format Fail Swap" +EnableSwap: "Dayakan Swap" + +#tamper +CleanTamperSetting: "Bersihkan tetapan perlindungan sejarah" +SetTamperWithRules: "Mulakan perlindungan direktori mengikut peraturan" +ProtectDir: "Lindungi direktori {{ .name }}" +IgnoreFile: "Lindungi fail {{ .name }}" +TamperSetSuccessful: "Tetapan perlindungan selesai, mula memantau direktori berikut:" + +#waf +ErrScope: 'Mengubah suai konfigurasi ini tidak disokong' +ErrStateChange: 'Perubahan keadaan gagal' +ErrRuleExist: 'Peraturan sudah wujud' +ErrRuleNotExist: 'Peraturan tidak wujud' +ErrParseIP: 'Format IP salah' +ErrDefaultIP: 'default ialah nama simpanan, sila tukar kepada nama lain' +ErrGroupInUse: 'Kumpulan IP digunakan oleh senarai hitam/senarai putih dan tidak boleh dipadamkan' +ErrIPGroupAclUse: "Kumpulan IP digunakan oleh peraturan tersuai tapak web {{ .name }}, tidak boleh dipadamkan" +ErrGroupExist: 'Nama kumpulan IP sudah wujud' +ErrIPRange: 'Julat IP salah' +ErrIPExist: 'IP sudah wujud' +urlDefense: 'Peraturan URL' +urlHelper: 'URL Terlarang' +dirFilter: 'Penapis direktori' +xss: 'XSS' +phpExec: 'Pelaksanaan skrip PHP' +oneWordTrojan: 'One Word Trojan' +appFilter: 'Gunakan penapisan direktori berbahaya' +webshell: 'Webshell' +args: 'Peraturan parameter' +protocolFilter: 'Penapisan protokol' +javaFileter: 'Penapis fail berbahaya Java' +Penapis pengimbas: 'Penapis pengimbas' +escapeFilter: 'penapis melarikan diri' +customRule: 'Peraturan tersuai' +httpMethod: 'Penapisan kaedah HTTP' +fileExt: 'Sekatan muat naik fail' +defaultIpBlack: 'Kumpulan IP berniat jahat' +cookie: 'Peraturan Kuki' +urlBlack: 'senarai hitam URL' +uaBlack: 'Senarai hitam Ejen Pengguna' +attackCount: 'Had kekerapan serangan' +fileExtCheck: 'Sekatan muat naik fail' +geoRestrict: 'Sekatan akses serantau' +unknownWebsite: 'Akses nama domain yang tidak dibenarkan' +notFoundCount: '404 Had Kadar' +headerDefense: 'Peraturan header' +defaultUaBlack: 'Peraturan Ejen Pengguna' +methodWhite: 'Peraturan HTTP' +captcha: 'pengesahan mesin manusia' +fiveSeconds: 'pengesahan 5 saat' +vulnCheck: 'Peraturan tambahan' +acl: 'Peraturan tersuai' +sql: 'suntikan SQL' +cc: 'Had kekerapan akses' +defaultUrlBlack: 'Peraturan URL' +sqlInject: 'Suntikan SQL' +ErrDBNotExist: 'Pangkalan data tidak wujud' +allow: 'membenarkan' +deny: 'menafikan' +OpenrestyNotFound: 'Openresty tidak dipasang' +remoteIpIsNull: "Senarai IP kosong" +OpenrestyVersionErr: "Versi Openresty terlalu rendah, sila naik taraf Openresty ke 1.27.1.2-2-2-focal" + +#tugas +TaskStart: '{{ .name }} Task mula [MULA]' +TaskEnd: '{{ .name }} Task selesai [SELESAI]' +TaskFailed: 'tugas {{ .name }} gagal' +TaskTimeout: '{{ .name }} tamat masa' +TaskSuccess: '{{ .name }} Task berjaya' +TaskRetry: 'Mulakan {{ .name }} cuba semula' +SubTaskSuccess: '{{ .name }} berjaya' +SubTaskFailed: '{{ .name }} gagal: {{ .err }}' +TaskInstall: 'Pasang' +TaskUninstall: 'Nyahpasang' +TaskCreate: 'Buat' +TaskDelete: 'Padam' +TaskUpgrade: 'Naik taraf' +TaskUpdate: 'Kemas kini' +TaskRestart: 'Mulakan semula' +TaskProtect: "Lindungi" +TaskBackup: 'Sandaran' +TaskRecover: 'Pulihkan' +TaskRollback: 'Rollback' +TaskPull: 'Tarik' +TaskCommit: 'Komit' +TaskBuild: 'Bina' +TaskPush: 'Tolak' +TaskClean: "Pembersihan" +TaskHandle: 'Laksanakan' +TaskImport: "Import" +TaskExport: "Eksport" +Website: 'Laman web' +App: 'Aplikasi' +Runtime: 'Persekitaran masa jalan' +Database: 'Pangkalan Data' +ConfigFTP: 'Buat pengguna FTP {{ .name }}' +ConfigOpenresty: 'Buat fail konfigurasi Openresty' +InstallAppSuccess: 'Aplikasi {{ .name }} berjaya dipasang' +ConfigRuntime: 'Konfigurasikan persekitaran masa jalan' +ConfigApp: 'Aplikasi Konfigurasi' +SuccessStatus: '{{ .name }} berjaya' +FailedStatus: '{{ .name }} gagal {{ .err }}' +HandleLink: 'Kendalikan persatuan aplikasi' +HandleDatabaseApp: 'Mengendalikan parameter aplikasi' +ExecShell: 'Laksanakan skrip {{ .name }}' +PullImage: 'Tarik imej' +Start: 'Mula' +Run: 'Mula' +Stop: 'Berhenti' +Image: 'Cermin' +Compose: 'Orkestrasi' +Container: 'Bekas' +AppLink: 'Aplikasi Terpaut' +EnableSSL: 'Dayakan HTTPS' +AppStore: 'App Store' +TaskSync: 'Segerakkan' +LocalApp: 'Aplikasi Tempatan' +SubTask: 'Subtugas' +RuntimeExtension: 'Sambungan Persekitaran Runtime' +TaskIsExecuting: 'Tugas sedang berjalan' +CustomAppstore: 'Gudang aplikasi tersuai' +TaskExec: 'Laksanakan' +TaskBatch: "Operasi Batch" +FileConvert: 'Penukaran Format Fail' + +# task - clam +Clamscan: "Imbas {{ .name }}" +TaskScan: "Imbas" + +# tugasan - ai +OllamaModelPull: 'Tarik model Ollama {{ .name }}' +OllamaModelSize: 'Dapatkan saiz model Ollama {{ .name }}' + +# gambar tugasan +Snapshot: 'Snapshot' +SnapDBInfo: 'Tulis maklumat pangkalan data 1Panel' +SnapCopy: 'Salin fail & direktori {{ .name }}' +SnapNewDB: 'Mulakan sambungan {{ .name }} pangkalan data' +SnapDeleteOperationLog: 'Padam log operasi' +SnapDeleteLoginLog: 'Padam log akses' +SnapDeleteMonitor: 'Padamkan data pemantauan' +SnapRemoveSystemIP: 'Alih keluar IP sistem' +SnapBaseInfo: 'Tulis maklumat asas 1Panel' +SnapInstallAppImageEmpty: 'Tiada imej aplikasi dipilih, dilangkau...' +SnapInstallApp: 'Sandaran aplikasi terpasang 1Panel' +SnapDockerSave: 'Mampatkan aplikasi yang dipasang' +SnapLocalBackup: 'Sandaran 1Panel direktori sandaran tempatan' +SnapCompressBackup: 'Mampatkan direktori sandaran tempatan' +SnapPanelData: 'Sandaran direktori data 1Panel' +SnapCompressPanel: 'Direktori Data Mampat' +SnapWebsite: 'Sandaran direktori tapak web 1Panel' +SnapCloseDBConn: 'Tutup sambungan pangkalan data' +SnapCompress: 'Buat fail syot kilat' +SnapCompressFile: 'Mampatkan fail syot kilat' +SnapCheckCompress: 'Semak fail mampatan syot kilat' +SnapCompressSize: 'Saiz fail syot kilat {{ .name }}' +SnapUpload: 'Muat naik fail syot kilat' +SnapUploadTo: 'Muat naik fail syot kilat ke {{ .name }}' +SnapUploadRes: 'Muat naik fail syot kilat ke {{ .name }}' + +SnapshotRecover: 'Snapshot Restore' +RecoverDownload: 'Muat turun fail syot kilat' +Download: 'Muat turun' +RecoverDownloadAccount: 'Dapatkan syot kilat muat turun akaun sandaran {{ .name }}' +RecoverDecompress: 'Nyahmampatkan fail termampat syot kilat' +Decompress: 'Penyahmampatan' +BackupBeforeRecover: 'Sandarkan data berkaitan sistem sebelum syot kilat' +Readjson: 'Baca fail Json dalam petikan' +ReadjsonPath: 'Dapatkan laluan fail Json dalam petikan' +ReadjsonContent: 'Baca fail Json' +ReadjsonMarshal: 'Pemprosesan melarikan diri Json' +RecoverApp: 'Pulihkan apl yang dipasang' +RecoverWebsite: 'Pulihkan direktori tapak web' +RecoverAppImage: 'Pulihkan sandaran imej syot kilat' +RecoverCompose: 'Pulihkan kandungan komposer lain' +RecoverComposeList: 'Dapatkan semua komposer untuk dipulihkan' +RecoverComposeItem: 'Recover compose {{ .name }}' +RecoverAppEmpty: 'Tiada sandaran imej aplikasi ditemui dalam fail syot kilat' +RecoverBaseData: 'Pulihkan data asas dan fail' +RecoverDaemonJsonEmpty: 'Kedua-dua fail syot kilat dan mesin semasa tidak mempunyai fail daemon.json konfigurasi bekas' +RecoverDaemonJson: 'Pulihkan fail daemon.json konfigurasi bekas' +RecoverDBData: 'Pulihkan data pangkalan data' +RecoverBackups: 'Pulihkan direktori sandaran tempatan' +RecoverPanelData: 'Direktori data pemulihan' + +# tugas - bekas +ContainerNewCliet: 'Memulakan Klien Docker' +ContainerImagePull: 'Tarik imej bekas {{ .name }}' +ContainerRemoveOld: 'Alih keluar bekas asal {{ .name }}' +ContainerImageCheck: 'Periksa sama ada imej ditarik seperti biasa' +ContainerLoadInfo: 'Dapatkan maklumat bekas asas' +ContainerRecreate: 'Kemas kini kontena gagal, kini mula memulihkan bekas asal' +ContainerCreate: 'Buat bekas baharu {{ .name }}' +ContainerCreateFailed: 'Penciptaan bekas gagal, padamkan bekas yang gagal' +ContainerStartCheck: 'Periksa sama ada bekas telah dimulakan' + +# tugas - imej +ImageBuild: 'Membina Imej' +ImageBuildStdoutCheck: 'Menghuraikan kandungan output imej' +ImageBuildRes: 'Output binaan imej: {{ .name }}' +ImagePull: 'Tarik imej' +ImageRepoAuthFromDB: 'Dapatkan maklumat pengesahan repositori daripada pangkalan data' +ImaegPullRes: 'Output tarik imej: {{ .name }}' +ImagePush: 'Tolak imej' +ImageRenameTag: 'Ubah suai teg imej' +ImageNewTag: 'Teg imej baharu {{ .name }}' +ImaegPushRes: 'Output tolak imej: {{ .name }}' +ComposeCreate: 'Buat gubahan' +ComposeCreateRes: 'Karang cipta output: {{ .name }}' + +# tugas - laman web +BackupNginxConfig: 'Fail konfigurasi OpenResty tapak web sandaran' +CompressFileSuccess: 'Mampatkan direktori berjaya, dimampatkan ke {{ .name }}' +CompressDir: 'Direktori mampatan' +DeCompressFile: 'Nyahmampatkan fail {{ .name }}' +ErrCheckValid: 'Pengesahan fail sandaran gagal, {{ .name }}' +Rollback: 'Undur' +websiteDir: 'Direktori Laman Web' +RecoverFailedStartRollBack: 'Pemulihan gagal, mulakan rollback' +AppBackupFileIncomplete: 'Fail sandaran tidak lengkap dan tidak mempunyai fail app.json atau app.tar.gz' +AppAttributesNotMatch: 'Jenis atau nama aplikasi tidak sepadan' + +#alert +ErrAlert: 'Format mesej amaran tidak betul, sila semak dan cuba lagi!' +ErrAlertPush: 'Ralat dalam menolak maklumat amaran, sila semak dan cuba lagi!' +ErrAlertSave: 'Ralat menyimpan maklumat penggera, sila semak dan cuba lagi!' +ErrAlertSync: 'Ralat penyegerakan maklumat penggera, sila semak dan cuba lagi!' +ErrAlertRemote: 'Ralat jauh mesej penggera, sila semak dan cuba lagi!' + +#task - runtime +ErrInstallExtension: "Tugas pemasangan sudah sedang berjalan, silakan tunggu tugas selesai" + +# alert mail template +PanelAlertTitle: "Notifikasi Amaran Panel" +TestAlertTitle: "E-mel Ujian - Sahkan Sambungan E-mel" +TestAlert: "Ini adalah e-mel ujian untuk mengesahkan konfigurasi penghantaran e-mel anda betul." +LicenseExpirationAlert: "Panel {{ .node }}{{ .ip }} Anda, lesen akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut." +CronJobFailedAlert: "Panel {{ .node }}{{ .ip }} Anda, tugas jadual {{ .name }} gagal dilaksanakan. Sila log masuk ke panel untuk butiran lanjut." +ClamAlert: "Panel {{ .node }}{{ .ip }} Anda, tugas imbasan virus mengesan {{ .num }} fail yang dijangkiti. Sila log masuk ke panel untuk butiran lanjut." +WebSiteAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} laman web akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut." +SSLAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} sijil SSL akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut." +DiskUsedAlert: "Panel {{ .node }}{{ .ip }} Anda, penggunaan cakera {{ .name }} telah mencapai {{ .used }}. Sila log masuk ke panel untuk butiran lanjut." +ResourceAlert: "Panel {{ .node }}{{ .ip }} Anda, kadar penggunaan purata {{ .name }} dalam tempoh {{ .time }} minit ialah {{ .used }}. Sila log masuk ke panel untuk butiran lanjut." +PanelVersionAlert: "Panel {{ .node }}{{ .ip }} Anda, versi panel terkini tersedia untuk dinaik taraf. Sila log masuk ke panel untuk butiran lanjut." +PanelPwdExpirationAlert: "Panel {{ .node }}{{ .ip }} Anda, kata laluan panel akan tamat tempoh dalam {{ .day }} hari. Sila log masuk ke panel untuk butiran lanjut." +CommonAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .msg }}. Sila log masuk ke panel untuk butiran lanjut." +NodeExceptionAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} nod tidak normal. Sila log masuk ke panel untuk butiran lanjut." +LicenseExceptionAlert: "Panel {{ .node }}{{ .ip }} Anda, {{ .num }} lesen tidak normal. Sila log masuk ke panel untuk butiran lanjut." +SSHAndPanelLoginAlert: "Panel {{ .node }}{{ .ip }} Anda, log masuk {{ .name }} dari {{ .loginIp }} tidak normal. Sila log masuk ke panel untuk butiran lanjut." + +#disk +DeviceNotFound: "Peranti {{ .name }} tidak ditemui" +DeviceIsMounted: "Peranti {{ .name }} telah dikaitkan, sila nyahkaitkan terlebih dahulu" +PartitionDiskErr: "Gagal membahagikan, {{ .err }}" +FormatDiskErr: "Gagal memformat cakera, {{ .err }}" +MountDiskErr: "Gagal mengkaitkan cakera, {{ .err }}" +UnMountDiskErr: "Gagal nyahkaitkan cakera, {{ .err }}" +XfsNotFound: "Sistem fail xfs tidak dikesan, sila pasang xfsprogs terlebih dahulu" \ No newline at end of file diff --git a/agent/i18n/lang/pt-BR.yaml b/agent/i18n/lang/pt-BR.yaml new file mode 100644 index 0000000..0fd9b57 --- /dev/null +++ b/agent/i18n/lang/pt-BR.yaml @@ -0,0 +1,530 @@ +ErrInvalidParams: 'Erro de parâmetro de solicitação: {{ .detail }}' +ErrTokenParse: 'Erro de geração de token: {{ .detail }}' +ErrInitialPassword: 'A senha original está incorreta' +ErrInternalServer: 'Erro interno do servidor: {{ .detail }}' +ErrRecordExist: 'O registro já existe' +ErrRecordNotFound: 'Registro não encontrado' +ErrStructTransform: 'Falha na conversão de tipo: {{ .err }}' +ErrNotLogin: 'Usuário não logado: {{ .detail }}' +ErrPasswordExpired: 'A senha atual expirou: {{ .detail }}' +ErrNotSupportType: 'O sistema não suporta o tipo atual: {{ .name }}' +ErrProxy: 'Erro de solicitação, verifique o status do nó: {{ .detail }}' +ErrApiConfigStatusInvalid: 'O acesso à interface da API é proibido: {{ .detail }}' +ErrApiConfigKeyInvalid: 'Erro de chave da interface da API: {{ .detail }}' +ErrApiConfigIPInvalid: 'O IP usado para chamar a interface da API não está na lista de permissões: {{ .detail }}' +ErrApiConfigDisable: 'Esta interface proíbe o uso de chamadas de interface de API: {{ .detail }}' +ErrApiConfigKeyTimeInvalid: 'Erro de registro de data e hora da interface da API: {{ .detail }}' +StartPushSSLToNode: "Iniciando o envio do certificado para o nó" +PushSSLToNodeFailed: "Falha ao enviar o certificado para o nó: {{ .err }}" +PushSSLToNodeSuccess: "Certificado enviado com sucesso para o nó" +ErrShutDown: "Comando terminado manualmente!" + +ErrMinQuickJump: "Defina pelo menos uma entrada de salto rápido!" +ErrMaxQuickJump: "Você pode definir até quatro entradas de salto rápido!" + +#comum +ErrUsernameIsExist: 'Nome de usuário já existe' +ErrNameIsExist: 'Nome já existe' +ErrDemoEnvironment: 'Servidor de demonstração, esta operação é proibida!' +ErrCmdTimeout: 'Tempo limite de execução do comando expirou!' +ErrCmdIllegal: 'Há caracteres ilegais no comando de execução, modifique-o e tente novamente!' +ErrPortExist: 'A porta {{ .port }} já está ocupada por {{ .type }} [{{ .name }}]' +TYPE_APP: 'Aplicativo' +TYPE_RUNTIME: 'Ambiente de tempo de execução' +TYPE_DOMAIN: 'Nome de domínio' +ErrTypePort: 'O formato da porta {{ .name }} está incorreto' +ErrTypePortRange: 'O intervalo de portas precisa estar entre 1-65535' +Success: 'Sucesso' +Failed: 'Falhou' +SystemRestart: 'Tarefa interrompida devido à reinicialização do sistema' +ErrGroupIsDefault: 'Grupo padrão, não pode ser excluído' +ErrGroupIsInWebsiteUse: 'O grupo está sendo usado por outro site e não pode ser excluído.' +Decrypt: "Descriptografar" + +#backup +Localhost: 'Máquina Local' +ErrBackupInUsed: 'A conta de backup foi usada na tarefa agendada e não pode ser excluída.' +ErrBackupCheck: 'Falha na conexão de teste da conta de backup {{ .err }}' +ErrBackupLocalDelete: 'A exclusão da conta de backup do servidor local ainda não é suportada' +ErrBackupLocalCreate: 'A criação de contas de backup do servidor local ainda não é suportada' + +#aplicativo +ErrPortInUsed: 'A porta {{ .detail }} já está ocupada!' +ErrAppLimit: 'O número de aplicativos instalados excedeu o limite' +ErrNotInstall: 'Aplicativo não instalado' +ErrPortInOtherApp: 'A porta {{ .port }} já está ocupada pelo aplicativo {{ .apps }}!' +ErrDbUserNotValid: 'Banco de dados existente, nome de usuário e senha não correspondem!' +ErrUpdateBuWebsite: 'O aplicativo foi atualizado com sucesso, mas a modificação do arquivo de configuração do site falhou. Por favor, verifique a configuração! ' +Err1PanelNetworkFailed: 'Falha na criação da rede de contêiner padrão! {{ .detalhe }}' +ErrFileParse: 'Falha na análise do arquivo docker-compose do aplicativo!' +ErrInstallDirNotFound: 'O diretório de instalação não existe. Se precisar desinstalar, selecione Forçar desinstalação' +AppStoreIsUpToDate: 'A loja de aplicativos já é a versão mais recente' +LocalAppVersionNull: 'O aplicativo {{ .name }} não está sincronizado com a versão! Não é possível adicionar à lista de aplicativos' +LocalAppVersionErr: '{{ .name }} sincronização da versão {{ .version }} falhou! {{ .err }}' +ErrFileNotFound: 'arquivo {{ .name }} não existe' +ErrFileParseApp: 'Falha na análise do arquivo {{ .name }} {{ .err }}' +ErrAppDirNull: 'A pasta da versão não existe' +LocalAppErr: 'Falha na sincronização do aplicativo {{ .name }}! {{ .err }}' +ErrContainerName: 'O nome do contêiner já existe' +ErrCreateHttpClient: 'Falha ao criar solicitação {{ .err }}' +ErrHttpReqTimeOut: 'Tempo limite da solicitação esgotado {{ .err }}' +ErrHttpReqFailed: 'Falha na solicitação {{ .err }}' +ErrNoSuchHost: 'Não foi possível encontrar o servidor solicitado {{ .err }}' +ErrHttpReqNotFound: 'O recurso solicitado {{ .err }} não pôde ser encontrado' +ErrContainerNotFound: '{{ .name }} container não existe' +ErrContainerMsg: 'O contêiner {{ .name }} está anormal. Por favor, verifique o log na página do contêiner para obter detalhes.' +ErrAppBackup: '{{ .name }} falha no backup do aplicativo {{ .err }}' +ErrVersionTooLow: 'A versão atual do 1Panel é muito antiga para atualizar a App Store. Por favor, atualize a versão antes de operar.' +ErrAppNameExist: 'O nome do aplicativo já existe' +AppStoreIsSyncing: 'A App Store está sincronizando, tente novamente mais tarde' +ErrGetCompose: 'Falha ao obter o arquivo docker-compose.yml! {{ .detail }}' +ErrAppWarn: 'Status anormal, verifique o log' +ErrAppParamKey: 'O campo de parâmetro {{ .name }} está anormal' +ErrAppUpgrade: 'Falha na atualização do aplicativo {{ .name }} {{ .err }}' +AppRecover: 'Reverter aplicativo {{ .name }}' +PullImageStart: 'Comece a extrair a imagem [{{ .name }}]' +PullImageSuccess: 'Imagem retirada com sucesso' +AppStoreIsLastVersion: 'A App Store já é a versão mais recente' +AppStoreSyncSuccess: 'Sincronização da App Store bem-sucedida' +SyncAppDetail: 'Sincronizar configuração do aplicativo' +AppVersionNotMatch: 'o aplicativo {{ .name }} requer uma versão superior do 1Panel, ignorando a sincronização' +MoveSiteDir: "Detecção de alteração no diretório do site. É necessário migrar o diretório do site OpenResty para {{ .name }}" +MoveSiteDirSuccess: 'Migração bem-sucedida do diretório do site' +DeleteRuntimePHP: 'Excluir tempo de execução do PHP' +CustomAppStoreFileValid: 'Os pacotes da App Store precisam estar no formato .tar.gz' +PullImageTimeout: 'Tempo limite para puxar imagem, tente aumentar a aceleração da imagem ou altere para outra aceleração de imagem' +ErrAppIsDown: 'O status do aplicativo {{ .name }} é anormal, verifique' +ErrCustomApps: 'Há um aplicativo instalado, desinstale-o primeiro' +ErrCustomRuntimes: 'Há um ambiente de execução instalado, exclua-o primeiro' +ErrAppVersionDeprecated: "O aplicativo {{ .name }} não é compatível com a versão atual do 1Panel, ignorado" +ErrDockerFailed: "O estado do Docker está anormal, verifique o status do serviço" +ErrDockerComposeCmdNotFound: "O comando Docker Compose não existe, por favor, instale este comando na máquina host primeiro" +UseExistImage: 'Falha ao puxar a imagem, usando imagem existente' + +#ssh +ExportIP: "IP de Login" +ExportArea: "Localização" +ExportPort: "Porta" +ExportAuthMode: "Método de Login" +ExportUser: "Usuário" +ExportStatus: "Status de Login" +ExportDate: "Hora" + +#arquivo +ErrFileCanNotRead: 'Este arquivo não suporta visualização' +ErrFileToLarge: 'O arquivo é maior que 10M e não pode ser aberto' +ErrPathNotFound: 'O diretório não existe' +ErrMovePathFailed: 'O caminho de destino não pode conter o caminho original!' +ErrLinkPathNotFound: 'O caminho de destino não existe!' +ErrFileIsExist: 'O arquivo ou pasta já existe!' +ErrFileUpload: '{{ .name }} falhou ao carregar o arquivo {{ .detail }}' +ErrFileDownloadDir: 'A pasta de download não é suportada' +ErrCmdNotFound: 'O comando {{ .name}} não existe, instale este comando no host primeiro' +ErrSourcePathNotFound: 'O diretório de origem não existe' +ErrFavoriteExist: 'Este caminho já foi favorito' +ErrInvalidChar: 'Caracteres ilegais não são permitidos' +ErrPathNotDelete: 'O diretório selecionado não pode ser excluído' +ErrLogFileToLarge: "Fail log melebihi 500MB, tidak boleh dibuka" +FileDropFailed: "Falha ao limpar arquivo {{ .name }}: {{ .err }}" +FileDropSuccess: "Arquivo {{ .name }} limpo com sucesso, {{ .count }} arquivos removidos, {{ .size }} de espaço em disco liberado" +FileDropSum: "Limpeza de arquivos concluída com sucesso, total de {{ .count }} arquivos removidos, total de {{ .size }} de espaço em disco liberado" +ErrBadDecrypt: "Erro de senha de descriptografia do pacote compactado, descompactação falhou, verifique e tente novamente!" + +#site +ErrAliasIsExist: 'Alias já existe' +ErrBackupMatch: 'O arquivo de backup não corresponde a alguns dados atuais do site {{ .detail }}' +ErrBackupExist: 'A parte correspondente dos dados de origem no arquivo de backup não existe {{ .detail }}' +ErrPHPResource: 'O ambiente operacional local não suporta comutação! ' +ErrPathPermission: 'Uma pasta com permissões diferentes de 1000:1000 foi detectada no diretório de índice, o que pode causar um erro de acesso negado no site. Clique no botão Salvar acima' +ErrDomainIsUsed: 'O nome de domínio já está sendo usado pelo site [{{ .name }}]' +ErrDomainFormat: 'o formato do nome de domínio {{ .name }} está incorreto' +ErrDefaultAlias: 'padrão é um código reservado, use outro código' +ErrParentWebsite: 'Você precisa excluir o subsite {{ .name }} primeiro' +ErrBuildDirNotFound: 'O diretório de compilação não existe' +ErrImageNotExist: 'A imagem do ambiente operacional {{ .name }} não existe, edite novamente o ambiente operacional' +ErrProxyIsUsed: "Balanceamento de carga foi usado por proxy reverso, não pode ser excluído" +ErrSSLValid: 'O arquivo do certificado está anormal, verifique o status do certificado!' +ErrWebsiteDir: "Por favor, selecione um diretório dentro do diretório do site." +ErrComposerFileNotFound: "O arquivo composer.json não existe" +ErrRuntimeNoPort: "O ambiente de tempo de execução não está configurado com uma porta, edite o ambiente de tempo de execução primeiro." +Status: 'Status' +start: 'Iniciar' +stop: 'Parar' +restart: 'Reiniciar' +kill: 'Parar Forçadamente' +pause: 'Pausar' +unpause: 'Retomar' +remove: 'Excluir' +delete: 'Excluir' +ErrDefaultWebsite: 'O site padrão já foi definido, cancele-o antes de definir!' +SSL: 'Certificado' +Set: 'Configurações' + +#ssl +ErrSSLCannotDelete: 'O certificado {{ .name }} está sendo usado por um site e não pode ser excluído' +ErrAccountCannotDelete: 'A conta está associada a um certificado e não pode ser excluída' +ErrSSLApply: 'Renovação do certificado bem-sucedida, falha na recarga do Openresty, verifique a configuração!' +ErrEmailIsExist: 'A caixa de correio já existe' +ErrSSLKeyNotFound: 'O arquivo de chave privada não existe' +ErrSSLCertificateNotFound: 'O arquivo de certificado não existe' +ErrSSLKeyFormat: 'Falha na verificação do arquivo de chave privada' +ErrSSLCertificateFormat: 'O formato do arquivo de certificado está incorreto, use o formato pem' +ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid ou EabHmacKey não podem estar em branco' +ErrOpenrestyNotFound: 'O modo HTTP requer que o Openresty seja instalado primeiro' +ApplySSLStart: 'Comece a solicitar um certificado, nome de domínio [{{ .domain }}] método de solicitação [{{ .type }}]' +dnsAccount: 'DNS Automático' +dnsManual: 'Manual DNS' +http: 'HTTP' +ApplySSLFailed: 'Falha na solicitação de certificado [{{ .domain }}], {{ .detail }} ' +ApplySSLSuccess: 'Solicitação de certificado [{{ .domain }}] realizada com sucesso! ! ' +DNSAccountName: 'Conta DNS [{{ .name }}] fornecedor [{{ .type }}]' +PushDirLog: 'Certificado enviado para o diretório [{{ .path }}] {{ .status }}' +ErrDeleteCAWithSSL: 'A organização atual tem um certificado que foi emitido e não pode ser excluído.' +ErrDeleteWithPanelSSL: 'A configuração SSL do painel usa este certificado e não pode ser excluída' +ErrDefaultCA: 'A autoridade padrão não pode ser excluída' +ApplyWebSiteSSLLog: 'Iniciando a renovação do certificado do site {{ .name }}' +ErrUpdateWebsiteSSL: 'Falha na atualização do certificado do site {{ .name }}: {{ .err }}' +ApplyWebSiteSSLSuccess: 'Atualizar certificado do site com sucesso' +ErrExecShell: 'Falha ao executar o script {{ .err }}' +ExecShellStart: 'Iniciar execução do script' +ExecShellSuccess: 'Execução do script bem-sucedida' +StartUpdateSystemSSL: 'Iniciar atualização do certificado do sistema' +UpdateSystemSSLSuccess: 'Atualizar certificado do sistema com sucesso' +ErrWildcardDomain: 'Não é possível solicitar certificado de nome de domínio curinga no modo HTTP' +ErrApplySSLCanNotDelete: "O certificado {{.name}} que está sendo solicitado não pode ser excluído, tente novamente mais tarde." + +#mysql +ErrUserIsExist: 'O usuário atual já existe, digite novamente' +ErrDatabaseIsExist: 'O banco de dados atual já existe, digite novamente' +ErrExecTimeOut: 'Tempo limite de execução do SQL expirou, verifique o banco de dados' +ErrRemoteExist: 'O banco de dados remoto já existe com este nome, modifique-o e tente novamente' +ErrLocalExist: 'O nome já existe no banco de dados local, modifique-o e tente novamente' +RemoteBackup: "Para fazer backup do banco de dados remoto, o serviço de banco de dados do contêiner local precisa ser iniciado primeiro usando a imagem {{ .name }}, por favor aguarde..." +RemoteRecover: "Para restaurar o banco de dados remoto, o serviço de banco de dados do contêiner local precisa ser iniciado primeiro usando a imagem {{ .name }}, por favor aguarde..." +Arg: "Argumento" + +#redis +ErrTypeOfRedis: 'O tipo de arquivo de recuperação não corresponde ao método de persistência atual, modifique-o e tente novamente' + +#recipiente +ErrInUsed: "{{ .detail }} está em uso e não pode ser excluído!" +ErrObjectInUsed: "Este objeto está em uso e não pode ser excluído!" +ErrObjectBeDependent: "Esta imagem é dependida por outras imagens e não pode ser excluída!" +ErrPortRules: 'O número da porta não corresponde, digite novamente!' +ErrPgImagePull: 'Tempo limite para extração de imagem. Configure a aceleração de imagem ou extraia manualmente a imagem {{ .name }} e tente novamente' +PruneHelper: "Esta limpeza {{ .name }} liberou espaço em disco {{ .size }}" +ImageRemoveHelper: "Excluída a imagem {{ .name }}, liberando {{ .size }} de espaço em disco" +BuildCache: "Cache de construção" +Volume: "Volume de armazenamento" +Network: "Rede" +PruneStart: "Limpando em andamento, por favor aguarde..." + +#tempo de execução +ErrFileNotExist: 'O arquivo {{ .detail }} não existe! Verifique a integridade do arquivo de origem!' +ErrImageBuildErr: 'Falha na criação da imagem' +ErrImageExist: "A imagem já existe! Por favor, modifique o nome da imagem." +ErrDelWithWebsite: 'O ambiente operacional já está associado a um site e não pode ser excluído' +ErrRuntimeStart: 'Falha na inicialização' +ErrPackageJsonNotFound: 'o arquivo package.json não existe' +ErrScriptsNotFound: 'O item de configuração de scripts não foi encontrado em package.json' +ErrContainerNameNotFound: 'Não foi possível obter o nome do contêiner, verifique o arquivo .env' +ErrNodeModulesNotFound: 'A pasta node_modules não existe! Edite o ambiente de execução ou aguarde até que o ambiente de execução inicie com sucesso' +ErrContainerNameIsNull: 'O nome do contêiner não existe' +ErrPHPPortIsDefault: "Port 9000 adalah port lalai, sila ubah dan cuba lagi" +ErrPHPRuntimePortFailed: "Port {{ .name }} telah digunakan oleh persekitaran runtime semasa, sila ubah dan cuba lagi" + +#ferramenta +ErrConfigNotFound: 'O arquivo de configuração não existe' +ErrConfigParse: 'O formato do arquivo de configuração está incorreto' +ErrConfigIsNull: 'O arquivo de configuração não pode estar vazio' +ErrConfigDirNotFound: 'O diretório de execução não existe' +ErrConfigAlreadyExist: 'Um arquivo de configuração com o mesmo nome já existe' +ErrUserFindErr: 'Falha na pesquisa do usuário {{ .name }} {{ .err }}' + +#cronjob +CutWebsiteLogSuccess: '{{ .name }} registro do site cortado com sucesso, caminho de backup {{ .path }}' +HandleShell: 'Executar script {{ .name }}' +HandleCurl: "Acessar URL {{ .name }}" +HandleNtpSync: 'Sincronização de hora do sistema' +HandleSystemClean: 'Limpeza de cache do sistema' +SystemLog: 'Log do Sistema' +CutWebsiteLog: 'Rotacionar Log do Website' +FileOrDir: 'Diretório / Arquivo' +UploadFile: 'Enviando arquivo de backup {{ .file }} para {{ .backup }}' +Upload: "Enviar" +IgnoreBackupErr: "Backup falhou, erro: {{ .detail }}, ignorando este erro..." +IgnoreUploadErr: "Upload falhou, erro: {{ .detail }}, ignorando este erro..." +LoadBackupFailed: "Falha ao obter conexão da conta de backup, erro: {{ .detail }}" +InExecuting: "A tarefa atual está sendo executada, não repita a execução!" +NoSuchResource: "Nenhum conteúdo de backup encontrado no banco de dados, ignorando..." +CleanLog: "Limpar log" +CleanLogByName: "Limpar log de {{.name}}" +WafIpGroupNotFound: "Grupo de IP do WAF não encontrado. Por favor, vá para [Recursos Avançados - WAF - Lista Branca/Preta - Grupo de IP] para adicionar um grupo de IP via método de download remoto." + +#caixa de ferramentas +ErrNotExistUser: 'O usuário atual não existe, modifique e tente novamente!' +ErrBanAction: 'Falha na configuração. O serviço atual {{ .name }} não está disponível. Verifique e tente novamente!' +ErrClamdscanNotFound: 'O comando clamdscan não foi detectado, consulte a documentação para instalá-lo!' +TaskSwapSet: "Configurar Swap" +SetSwap: "Configurar Swap {{ .path }} - {{ .size }}" +CreateSwap: "Criar Arquivo Swap" +FormatSwap: "Formatar Arquivo Swap" +EnableSwap: "Ativar Swap" + +#tamper +CleanTamperSetting: "Limpar configurações de proteção históricas" +SetTamperWithRules: "Iniciar proteção de diretório de acordo com as regras" +ProtectDir: "Proteger diretório {{ .name }}" +IgnoreFile: "Proteger arquivo {{ .name }}" +TamperSetSuccessful: "Configurações de proteção concluídas, iniciando monitoramento dos seguintes diretórios:" + +#waf +ErrScope: 'A modificação desta configuração não é suportada' +ErrStateChange: 'Falha na alteração de estado' +ErrRuleExist: 'A regra já existe' +ErrRuleNotExist: 'A regra não existe' +ErrParseIP: 'Formato de IP errado' +ErrDefaultIP: 'padrão é um nome reservado, altere-o para outro nome' +ErrGroupInUse: 'O grupo de IP é usado pela lista negra/lista branca e não pode ser excluído' +ErrIPGroupAclUse: "O grupo de IP está sendo usado por regras personalizadas do site {{ .name }}, não pode ser excluído" +ErrGroupExist: 'O nome do grupo IP já existe' +ErrIPRange: 'Intervalo de IP errado' +ErrIPExist: 'IP já existe' +urlDefense: 'regras de URL' +urlHelper: 'URL proibido' +dirFilter: 'Filtro de diretório' +xss: 'XSS' +phpExec: 'Execução de script PHP' +oneWordTrojan: 'Cavalo de Tróia de Uma Palavra' +appFilter: 'Aplicar filtragem de diretório perigosa' +webshell: 'Webshell' +args: 'Regras de parâmetros' +protocolFilter: 'Filtragem de protocolo' +javaFileter: 'Filtro de arquivo perigoso Java' +scannerFilter: 'Filtro do scanner' +escapeFilter: 'filtro de escape' +customRule: 'Regra personalizada' +httpMethod: 'Filtragem de método HTTP' +fileExt: 'Restrições de upload de arquivo' +defaultIpBlack: 'Grupo de IP malicioso' +cookie: 'Regras de cookies' +urlBlack: 'lista negra de URLs' +uaBlack: 'Lista negra de agentes do usuário' +attackCount: 'Limite de frequência de ataque' +fileExtCheck: 'Restrições de upload de arquivo' +geoRestrict: 'Restrições de acesso regionais' +unknownWebsite: 'Acesso não autorizado ao nome de domínio' +notFoundCount: 'Limite de taxa 404' +headerDefense: 'Regras de cabeçalho' +defaultUaBlack: 'Regras do agente do usuário' +methodWhite: 'regras HTTP' +captcha: 'verificação homem-máquina' +fiveSeconds: 'verificação de 5 segundos' +vulnCheck: 'Regras suplementares' +acl: 'Regras personalizadas' +sql: 'injeção de SQL' +cc: 'Limite de frequência de acesso' +defaultUrlBlack: 'regras de URL' +sqlInject: 'injeção de SQL' +ErrDBNotExist: 'O banco de dados não existe' +allow: 'permitir' +deny: 'negar' +OpenrestyNotFound: 'Openresty não está instalado' +remoteIpIsNull: "A lista de IP está vazia" +OpenrestyVersionErr: "A versão do Openresty é muito baixa, por favor atualize o Openresty para 1.27.1.2-2-2-focal" + +#tarefa +TaskStart: '{{ .name }} A tarefa inicia [START]' +TaskEnd: '{{ .name }} Tarefa concluída [CONCLUÍDA]' +TaskFailed: '{{ .name }} tarefa falhou' +TaskTimeout: '{{ .name }} expirou' +TaskSuccess: '{{ .name }} Tarefa bem-sucedida' +TaskRetry: 'Iniciar {{ .name }}ª nova tentativa' +SubTaskSuccess: '{{ .name }} bem-sucedido' +SubTaskFailed: '{{ .name }} falhou: {{ .err }}' +TaskInstall: 'Instalar' +TaskUninstall: 'Desinstalar' +TaskCreate: 'Criar' +TaskDelete: 'Excluir' +TaskUpgrade: 'Atualizar' +TaskUpdate: 'Atualizar' +TaskRestart: 'Reiniciar' +TaskProtect: "Proteger" +Backup de Tarefa: 'Backup' +TaskRecover: 'Recuperar' +TaskRollback: 'Reverter' +TaskPull: 'Puxar' +TaskCommit: 'Commit' +TaskBuild: 'Construir' +TaskPush: 'Empurrar' +TaskClean: "Limpeza" +TaskHandle: 'Executar' +TaskImport: "Importar" +TaskExport: "Exportar" +Website: 'Site' +App: 'Aplicativo' +Runtime: 'Ambiente de tempo de execução' +Database: 'Banco de dados' +ConfigFTP: 'Criar usuário FTP {{ .name }}' +ConfigOpenresty: 'Criar arquivo de configuração Openresty' +InstallAppSuccess: 'Aplicativo {{ .name }} instalado com sucesso' +ConfigRuntime: 'Configurar o ambiente de tempo de execução' +ConfigApp: 'Aplicativo de configuração' +SuccessStatus: '{{ .name }} bem-sucedido' +FailedStatus: '{{ .name }} falhou {{ .err }}' +HandleLink: 'Manipular associação de aplicativos' +HandleDatabaseApp: 'Manipulando parâmetros do aplicativo' +ExecShell: 'Executar script {{ .name }}' +PullImage: 'Puxar imagem' +Start: 'Iniciar' +Run: 'Iniciar' +Stop: 'Pare' +Image: 'Espelho' +Compose: 'Orquestração' +Container: 'Recipiente' +AppLink: 'Aplicativo vinculado' +EnableSSL: 'Habilitar HTTPS' +AppStore: 'Loja de aplicativos' +TaskSync: 'Sincronizar' +LocalApp: 'Aplicativo local' +SubTask: 'Subtarefa' +RuntimeExtension: 'Extensão do ambiente de tempo de execução' +TaskIsExecuting: 'A tarefa está em execução' +CustomAppstore: 'Armazém de aplicativos personalizados' +TaskExec: 'Executar' +TaskBatch: "Operação em Lote" +FileConvert: 'Conversão de Formato de Arquivo' + +# task - clam +Clamscan: "Escanear {{ .name }}" +TaskScan: "Escanear" + +# tarefa - ai +OllamaModelPull: 'Puxar modelo Ollama {{ .name }}' +OllamaModelSize: 'Obtenha o tamanho do modelo Ollama {{ .name }}' + +# tarefa-instantânea +Snapshot: 'Instantâneo' +SnapDBInfo: 'Escrever informações do banco de dados 1Panel' +SnapCopy: 'Copiar arquivos e diretórios {{ .name }}' +SnapNewDB: 'Inicializar conexão com o banco de dados {{ .name }}' +SnapDeleteOperationLog: 'Excluir log de operação' +SnapDeleteLoginLog: 'Excluir log de acesso' +SnapDeleteMonitor: 'Excluir dados de monitoramento' +SnapRemoveSystemIP: 'Remover IP do sistema' +SnapBaseInfo: 'Escreva informações básicas do 1Panel' +SnapInstallAppImageEmpty: 'Nenhuma imagem de aplicativo selecionada, pulando...' +SnapInstallApp: 'Backup dos aplicativos instalados do 1Panel' +SnapDockerSave: 'Compactar aplicativos instalados' +SnapLocalBackup: 'Backup do diretório de backup local do 1Panel' +SnapCompressBackup: 'Compactar diretório de backup local' +SnapPanelData: 'Backup do diretório de dados do 1Panel' +SnapCompressPanel: 'Diretório de dados compactados' +SnapWebsite: 'Backup do diretório do site 1Panel' +SnapCloseDBConn: 'Fechar a conexão com o banco de dados' +SnapCompress: 'Criar arquivos de instantâneos' +SnapCompressFile: 'Compactar arquivo de instantâneo' +SnapCheckCompress: 'Verificar arquivo de compactação de instantâneo' +SnapCompressSize: 'Tamanho do arquivo de instantâneo {{ .name }}' +SnapUpload: 'Carregar arquivo de instantâneo' +SnapUploadTo: 'Carregar arquivo de instantâneo para {{ .name }}' +SnapUploadRes: 'Carregar arquivo de instantâneo para {{ .name }}' + +SnapshotRecover: 'Restauração de instantâneo' +RecoverDownload: 'Baixar arquivo de instantâneo' +Download: 'Baixar' +RecoverDownloadAccount: 'Obter conta de backup de download de instantâneo {{ .name }}' +RecoverDecompress: 'Descompacte arquivos compactados de instantâneos' +Decompress: 'Descompressão' +BackupBeforeRecover: 'Fazer backup de dados relacionados ao sistema antes do snapshot' +Readjson: 'Ler o arquivo Json no snapshot' +ReadjsonPath: 'Obtenha o caminho do arquivo Json no snapshot' +ReadjsonContent: 'Ler arquivo Json' +ReadjsonMarshal: 'Processamento de escape JSON' +RecoverApp: 'Restaurar aplicativos instalados' +RecoverWebsite: 'Recuperar diretório de sites' +RecoverAppImage: 'Restaurar backup de imagem instantânea' +RecoverCompose: 'Restaurar outro conteúdo do compositor' +RecoverComposeList: 'Obter todos os compositores para serem restaurados' +RecoverComposeItem: 'Recuperar composição {{ .name }}' +RecoverAppEmpty: 'Nenhum backup de imagem de aplicativo foi encontrado no arquivo de instantâneo' +RecoverBaseData: 'Recuperar dados e arquivos básicos' +RecoverDaemonJsonEmpty: 'O arquivo de instantâneo e a máquina atual não têm o arquivo daemon.json de configuração do contêiner' +RecoverDaemonJson: 'Restaurar arquivo daemon.json de configuração do contêiner' +RecoverDBData: 'Recuperar dados do banco de dados' +RecoverBackups: 'Restaurar diretório de backup local' +RecoverPanelData: 'Diretório de dados de recuperação' + +# tarefa - contêiner +ContainerNewCliet: 'Inicializar cliente Docker' +ContainerImagePull: 'Puxar imagem do contêiner {{ .name }}' +ContainerRemoveOld: 'Remover o contêiner original {{ .name }}' +ContainerImageCheck: 'Verifique se a imagem é extraída normalmente' +ContainerLoadInfo: 'Obter informações básicas do contêiner' +ContainerRecreate: 'A atualização do contêiner falhou, agora iniciando a restauração do contêiner original' +ContainerCreate: 'Criar um novo contêiner {{ .name }}' +ContainerCreateFailed: 'Falha na criação do contêiner, exclua o contêiner com falha' +ContainerStartCheck: 'Verifique se o contêiner foi iniciado' + +# tarefa - imagem +ImageBuild: 'Construção de imagem' +ImageBuildStdoutCheck: 'Analisar conteúdo de saída da imagem' +ImageBuildRes: 'Saída da criação da imagem: {{ .name }}' +ImagePull: 'Puxar imagem' +ImageRepoAuthFromDB: 'Obter informações de autenticação do repositório do banco de dados' +ImaegPullRes: 'Saída de extração de imagem: {{ .name }}' +ImagePush: 'Enviar imagem' +ImageRenameTag: 'Modificar tag de imagem' +ImageNewTag: 'Nova tag de imagem {{ .name }}' +ImaegPushRes: 'Saída de envio de imagem: {{ .name }}' +ComposeCreate: 'Criar uma composição' +ComposeCreateRes: 'Compose cria saída: {{ .name }}' + +# tarefa - site +BackupNginxConfig: 'Fazer backup do arquivo de configuração do OpenResty do site' +CompressFileSuccess: 'Diretório compactado com sucesso, compactado para {{ .name }}' +CompressDir: 'Diretório de compressão' +DeCompressFile: 'Descompacte o arquivo {{ .name }}' +ErrCheckValid: 'Falha na verificação do arquivo de backup, {{ .name }}' +Rollback: 'Reversão' +websiteDir: 'Diretório de sites' +RecoverFailedStartRollBack: 'Falha na recuperação, iniciar reversão' +AppBackupFileIncomplete: 'O arquivo de backup está incompleto e não contém os arquivos app.json ou app.tar.gz' +AppAttributesNotMatch: 'O tipo ou nome do aplicativo não corresponde' + +#alerta +ErrAlert: 'O formato da mensagem de aviso está incorreto, verifique e tente novamente!' +ErrAlertPush: 'Erro ao enviar informações de alerta, verifique e tente novamente!' +ErrAlertSave: 'Erro ao salvar as informações do alarme. Verifique e tente novamente!' +ErrAlertSync: 'Erro de sincronização de informações de alarme, verifique e tente novamente!' +ErrAlertRemote: 'Erro remoto na mensagem de alarme, verifique e tente novamente!' + +#task - runtime +ErrInstallExtension: "Já existe uma tarefa de instalação em andamento, aguarde a conclusão da tarefa" + +# alert mail template +PanelAlertTitle: "Notificação de Alerta do Painel" +TestAlertTitle: "E-mail de Teste - Verificar Conectividade de E-mail" +TestAlert: "Este é um e-mail de teste para verificar se sua configuração de envio de e-mails está correta." +LicenseExpirationAlert: "Seu Painel {{ .node }}{{ .ip }}, a licença expirará em {{ .day }} dias. Faça login no painel para obter detalhes." +CronJobFailedAlert: "Seu Painel {{ .node }}{{ .ip }}, a execução da tarefa agendada {{ .name }} falhou. Faça login no painel para obter detalhes." +ClamAlert: "Seu Painel {{ .node }}{{ .ip }}, a tarefa de verificação de vírus detectou {{ .num }} arquivos infectados. Faça login no painel para obter detalhes." +WebSiteAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} sites expirarão em {{ .day }} dias. Faça login no painel para obter detalhes." +SSLAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} certificados SSL expirarão em {{ .day }} dias. Faça login no painel para obter detalhes." +DiskUsedAlert: "Seu Painel {{ .node }}{{ .ip }}, o uso do disco {{ .name }} atingiu {{ .used }}. Faça login no painel para obter detalhes." +ResourceAlert: "Seu Painel {{ .node }}{{ .ip }}, a taxa de uso médio do {{ .name }} nos últimos {{ .time }} minutos é {{ .used }}. Faça login no painel para obter detalhes." +PanelVersionAlert: "Seu Painel {{ .node }}{{ .ip }}, a versão mais recente do painel está disponível para atualização. Faça login no painel para obter detalhes." +PanelPwdExpirationAlert: "Seu Painel {{ .node }}{{ .ip }}, a senha do painel expirará em {{ .day }} dias. Faça login no painel para obter detalhes." +CommonAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .msg }}. Faça login no painel para obter detalhes." +NodeExceptionAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} nós estão anormais. Faça login no painel para obter detalhes." +LicenseExceptionAlert: "Seu Painel {{ .node }}{{ .ip }}, {{ .num }} licenças estão anormais. Faça login no painel para obter detalhes." +SSHAndPanelLoginAlert: "Seu Painel {{ .node }}{{ .ip }}, o login {{ .name }} a partir de {{ .loginIp }} é anormal. Faça login no painel para obter detalhes." + +#disk +DeviceNotFound: "Dispositivo {{ .name }} não encontrado" +DeviceIsMounted: "Dispositivo {{ .name }} está montado, por favor desmonte primeiro" +PartitionDiskErr: "Falha ao particionar, {{ .err }}" +FormatDiskErr: "Falha ao formatar disco, {{ .err }}" +MountDiskErr: "Falha ao montar disco, {{ .err }}" +UnMountDiskErr: "Falha ao desmontar disco, {{ .err }}" +XfsNotFound: "Sistema de arquivos xfs não detectado, por favor instale xfsprogs primeiro" \ No newline at end of file diff --git a/agent/i18n/lang/ru.yaml b/agent/i18n/lang/ru.yaml new file mode 100644 index 0000000..98cf38a --- /dev/null +++ b/agent/i18n/lang/ru.yaml @@ -0,0 +1,530 @@ +ErrInvalidParams: 'Ошибка параметра запроса: {{ .detail }}' +ErrTokenParse: 'Ошибка генерации токена: {{ .detail }}' +ErrInitialPassword: 'Исходный пароль неверный' +ErrInternalServer: 'Внутренняя ошибка сервера: {{ .detail }}' +ErrRecordExist: 'Запись уже существует' +ErrRecordNotFound: 'Запись не найдена' +ErrStructTransform: 'Не удалось преобразовать тип: {{ .err }}' +ErrNotLogin: 'Пользователь не вошел в систему: {{ .detail }}' +ErrPasswordExpired: 'Срок действия текущего пароля истек: {{ .detail }}' +ErrNotSupportType: 'Система не поддерживает текущий тип: {{ .name }}' +ErrProxy: 'Ошибка запроса, проверьте статус узла: {{ .detail }}' +ErrApiConfigStatusInvalid: 'Доступ к интерфейсу API запрещен: {{ .detail }}' +ErrApiConfigKeyInvalid: 'Ошибка ключа интерфейса API: {{ .detail }}' +ErrApiConfigIPInvalid: 'IP-адрес, используемый для вызова интерфейса API, отсутствует в белом списке: {{ .detail }}' +ErrApiConfigDisable: 'Этот интерфейс запрещает использование вызовов интерфейса API: {{ .detail }}' +ErrApiConfigKeyTimeInvalid: 'Ошибка временной метки интерфейса API: {{ .detail }}' +StartPushSSLToNode: "Начало отправки сертификата на узел" +PushSSLToNodeFailed: "Не удалось отправить сертификат на узел: {{ .err }}" +PushSSLToNodeSuccess: "Сертификат успешно отправлен на узел" +ErrShutDown: "Команда завершена вручную!" + +ErrMinQuickJump: "Пожалуйста, установите хотя бы одну запись быстрого перехода!" +ErrMaxQuickJump: "Можно установить до четырех записей быстрого перехода!" + +#общий +ErrUsernameIsExist: 'Имя пользователя уже существует' +ErrNameIsExist: 'Имя уже существует' +ErrDemoEnvironment: 'Демо-сервер, эта операция запрещена!' +ErrCmdTimeout: 'Время выполнения команды истекло!' +ErrCmdIllegal: 'В команде выполнения есть недопустимые символы. Измените ее и повторите попытку!' +ErrPortExist: 'Порт {{ .port }} уже занят {{ .type }} [{{ .name }}]' +TYPE_APP: 'Приложение' +TYPE_RUNTIME: 'Среда выполнения' +TYPE_DOMAIN: 'Доменное имя' +ErrTypePort: 'Формат порта {{ .name }} неверен' +ErrTypePortRange: 'Диапазон портов должен быть в пределах 1-65535' +Success: 'Успех' +Failed: 'Не удалось' +SystemRestart: 'Задача прервана из-за перезапуска системы' +ErrGroupIsDefault: 'Группа по умолчанию, не может быть удалена' +ErrGroupIsInWebsiteUse: 'Группа используется другим веб-сайтом и не может быть удалена.' +Decrypt: "Расшифровать" + +#резервное копирование +Localhost: 'Локальная машина' +ErrBackupInUsed: 'Учетная запись резервного копирования использовалась в запланированной задаче и не может быть удалена.' +ErrBackupCheck: 'Проверка подключения к резервной копии учетной записи не удалась {{ .err }}' +ErrBackupLocalDelete: 'Удаление учетной записи резервного копирования локального сервера пока не поддерживается' +ErrBackupLocalCreate: 'Создание учетных записей резервного копирования локального сервера пока не поддерживается' + +#приложение +ErrPortInUsed: '{{ .detail }} порт уже занят!' +ErrAppLimit: 'Количество установленных приложений превысило лимит' +ErrNotInstall: 'Приложение не установлено' +ErrPortInOtherApp: 'Порт {{ .port }} уже занят приложением {{ .apps }}!' +ErrDbUserNotValid: 'Существующая база данных, имя пользователя и пароль не совпадают!' +ErrUpdateBuWebsite: 'Приложение успешно обновлено, но изменение файла конфигурации веб-сайта не удалось. Пожалуйста, проверьте конфигурацию! ' +Err1PanelNetworkFailed: 'Создание сети контейнеров по умолчанию не удалось! {{ .detail }}' +ErrFileParse: 'Ошибка анализа файла docker-compose приложения!' +ErrInstallDirNotFound: 'Каталог установки не существует. Если вам необходимо удалить, выберите Принудительное удаление.' +AppStoreIsUpToDate: 'В магазине приложений уже установлена последняя версия' +LocalAppVersionNull: 'Приложение {{ .name }} не синхронизировано с версией! Невозможно добавить в список приложений.' +LocalAppVersionErr: 'Синхронизация версии {{ .version }} {{ .name }} не удалась! {{ .err }}' +ErrFileNotFound: 'Файл {{ .name }} не существует' +ErrFileParseApp: '{{ .name }} анализ файла не удался {{ .err }}' +ErrAppDirNull: 'Папка версии не существует' +LocalAppErr: 'Синхронизация приложения {{ .name }} не удалась! {{ .err }}' +ErrContainerName: 'Имя контейнера уже существует' +ErrCreateHttpClient: 'Не удалось создать запрос {{ .err }}' +ErrHttpReqTimeOut: 'Истекло время ожидания запроса {{ .err }}' +ErrHttpReqFailed: 'Запрос не выполнен {{ .err }}' +ErrNoSuchHost: 'Не удалось найти запрошенный сервер {{ .err }}' +ErrHttpReqNotFound: 'Запрошенный ресурс {{ .err }} не найден' +ErrContainerNotFound: 'Контейнер {{ .name }} не существует' +ErrContainerMsg: 'Контейнер {{ .name }} ненормален. Подробности смотрите в журнале на странице контейнера.' +ErrAppBackup: '{{ .name }} резервное копирование приложения не удалось {{ .err }}' +ErrVersionTooLow: 'Текущая версия 1Panel слишком низкая для обновления App Store. Пожалуйста, обновите версию перед началом работы.' +ErrAppNameExist: 'Имя приложения уже существует' +AppStoreIsSyncing: 'App Store синхронизируется, повторите попытку позже' +ErrGetCompose: 'Не удалось получить файл docker-compose.yml! {{ .detail }}' +ErrAppWarn: 'Ненормальное состояние, проверьте журнал' +ErrAppParamKey: 'Поле параметра {{ .name }} ненормально' +ErrAppUpgrade: 'Обновление приложения {{ .name }} не удалось {{ .err }}' +AppRecover: 'Откатить приложение {{ .name }}' +PullImageStart: 'Начать извлечение изображения [{{ .name }}]' +PullImageSuccess: 'Изображение извлечено успешно' +AppStoreIsLastVersion: 'В App Store уже установлена последняя версия' +AppStoreSyncSuccess: 'Синхронизация с App Store прошла успешно' +SyncAppDetail: 'Синхронизировать конфигурацию приложения' +AppVersionNotMatch: 'Приложению {{ .name }} требуется более высокая версия 1Panel, синхронизация пропускается' +MoveSiteDir: "Обнаружено изменение каталога сайта. Необходимо перенести каталог сайта OpenResty в {{ .name }}" +MoveSiteDirSuccess: 'Успешная миграция каталога веб-сайта' +DeleteRuntimePHP: 'Удалить среду выполнения PHP' +CustomAppStoreFileValid: 'Пакеты магазина приложений должны быть в формате .tar.gz' +PullImageTimeout: 'Истекло время ожидания извлечения изображения. Попробуйте увеличить ускорение изображения или выбрать другое ускорение изображения' +ErrAppIsDown: '{{ .name }} статус приложения ненормальный, проверьте' +ErrCustomApps: 'Установлено приложение. Сначала удалите его' +ErrCustomRuntimes: 'Установлена среда выполнения. Сначала удалите ее' +ErrAppVersionDeprecated: "Приложение {{ .name }} несовместимо с текущей версией 1Panel, пропущено" +ErrDockerFailed: "Состояние Docker аномально, проверьте состояние сервиса" +ErrDockerComposeCmdNotFound: "Команда Docker Compose отсутствует, пожалуйста, установите эту команду на хост-машине сначала" +UseExistImage: 'Не удалось загрузить образ, используется существующий образ' + +#ssh +ExportIP: "IP входа" +ExportArea: "Местоположение" +ExportPort: "Порт" +ExportAuthMode: "Способ входа" +ExportUser: "Пользователь" +ExportStatus: "Статус входа" +ExportDate: "Время" + +#файл +ErrFileCanNotRead: 'Этот файл не поддерживает предварительный просмотр' +ErrFileToLarge: 'Файл больше 10 МБ и не может быть открыт' +ErrPathNotFound: 'Каталог не существует' +ErrMovePathFailed: 'Целевой путь не может содержать исходный путь!' +ErrLinkPathNotFound: 'Целевой путь не существует!' +ErrFileIsExist: 'Файл или папка уже существует!' +ErrFileUpload: '{{ .name }} не удалось загрузить файл {{ .detail }}' +ErrFileDownloadDir: 'Папка загрузки не поддерживается' +ErrCmdNotFound: 'Команда {{ .name }} не существует, сначала установите эту команду на хосте' +ErrSourcePathNotFound: 'Исходный каталог не существует' +ErrFavoriteExist: 'Этот путь уже добавлен в избранное' +ErrInvalidChar: 'Недопустимые символы не допускаются' +ErrPathNotDelete: 'Выбранный каталог не может быть удален' +ErrLogFileToLarge: "Файл журнала превышает 500MB и не может быть открыт" +FileDropFailed: "Не удалось очистить файл {{ .name }}: {{ .err }}" +FileDropSuccess: "Файл {{ .name }} успешно очищен, удалено {{ .count }} файлов, освобождено {{ .size }} дискового пространства" +FileDropSum: "Очистка файлов завершена успешно, всего удалено {{ .count }} файлов, освобождено {{ .size }} дискового пространства" +ErrBadDecrypt: "Ошибка пароля расшифровки сжатого пакета, распаковка не удалась, проверьте и повторите попытку!" + +#веб-сайт +ErrAliasIsExist: 'Псевдоним уже существует' +ErrBackupMatch: 'Файл резервной копии не соответствует некоторым текущим данным веб-сайта {{ .detail }}' +ErrBackupExist: 'Соответствующая часть исходных данных в файле резервной копии не существует {{ .detail }}' +ErrPHPResource: 'Локальная операционная среда не поддерживает переключение!' +ErrPathPermission: 'В каталоге index обнаружена папка с разрешениями, отличными от 1000:1000, что может привести к ошибке Отказано в доступе на веб-сайте. Пожалуйста, нажмите кнопку Сохранить выше.' +ErrDomainIsUsed: 'Доменное имя уже используется веб-сайтом [{{ .name }}]' +ErrDomainFormat: 'Неверный формат доменного имени {{ .name }}' +ErrDefaultAlias: 'по умолчанию зарезервирован код, используйте другой код' +ErrParentWebsite: 'Сначала вам необходимо удалить дочерний сайт {{ .name }}' +ErrBuildDirNotFound: 'Каталог сборки не существует' +ErrImageNotExist: 'Образ операционной среды {{ .name }} не существует, пожалуйста, отредактируйте операционную среду заново' +ErrProxyIsUsed: "Балансировка нагрузки используется обратным прокси, невозможно удалить" +ErrSSLValid: 'Файл сертификата аномален, проверьте статус сертификата!' +ErrWebsiteDir: "Пожалуйста, выберите директорию внутри директории сайта." +ErrComposerFileNotFound: "Файл composer.json не существует" +ErrRuntimeNoPort: "Среда выполнения не настроена с портом, сначала отредактируйте среду выполнения." +Status: 'Статус' +start: 'Запустить' +stop: 'Остановить' +restart: 'Перезапустить' +kill: 'Принудительно Остановить' +pause: 'Приостановить' +unpause: 'Возобновить' +remove: 'Удалить' +delete: 'Удалить' +ErrDefaultWebsite: 'Веб-сайт по умолчанию уже установлен, отмените его перед настройкой!' +SSL: 'Сертификат' +Set: 'Настройки' + +#ssl +ErrSSLCannotDelete: 'Сертификат {{ .name }} используется веб-сайтом и не может быть удален' +ErrAccountCannotDelete: 'Учетная запись связана с сертификатом и не может быть удалена' +ErrSSLApply: 'Обновление сертификата прошло успешно, перезагрузка OpenResty не удалась, проверьте конфигурацию!' +ErrEmailIsExist: 'Почтовый ящик уже существует' +ErrSSLKeyNotFound: 'Файл закрытого ключа не существует' +ErrSSLCertificateNotFound: 'Файл сертификата не существует' +ErrSSLKeyFormat: 'Проверка файла закрытого ключа не удалась' +ErrSSLCertificateFormat: 'Неверный формат файла сертификата, используйте формат pem' +ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid или EabHmacKey не могут быть пустыми' +ErrOpenrestyNotFound: 'Для режима HTTP необходимо сначала установить Openresty' +ApplySSLStart: 'Начать подачу заявки на сертификат, доменное имя [{{ .domain }}], метод подачи заявки [{{ .type }}]' +dnsAccount: 'DNS Авто' +dnsManual: 'Руководство по DNS' +http: 'HTTP' +ApplySSLFailed: 'Заявка на сертификат [{{ .domain }}] не выполнена, {{ .detail }}' +ApplySSLSuccess: 'Заявка на сертификат [{{ .domain }}] успешно подана! ' +DNSAccountName: 'DNS-аккаунт [{{ .name }}] поставщик [{{ .type }}]' +PushDirLog: 'Сертификат перемещен в каталог [{{ .path }}] {{ .status }}' +ErrDeleteCAWithSSL: 'Текущая организация имеет сертификат, который был выдан и не может быть удален.' +ErrDeleteWithPanelSSL: 'Конфигурация SSL панели использует этот сертификат и не может быть удалена' +ErrDefaultCA: 'Невозможно удалить полномочия по умолчанию' +ApplyWebSiteSSLLog: 'Начинается обновление сертификата веб-сайта {{ .name }}' +ErrUpdateWebsiteSSL: 'Обновление сертификата веб-сайта {{ .name }} не удалось: {{ .err }}' +ApplyWebSiteSSLSuccess: 'Сертификат веб-сайта успешно обновлен' +ErrExecShell: 'Не удалось выполнить скрипт {{ .err }}' +ExecShellStart: 'Начать выполнение скрипта' +ExecShellSuccess: 'Скрипт выполнен успешно' +StartUpdateSystemSSL: 'Начать обновление системного сертификата' +UpdateSystemSSLSuccess: 'Сертификат системы обновлен успешно' +ErrWildcardDomain: 'Невозможно подать заявку на получение подстановочного сертификата доменного имени в режиме HTTP' +ErrApplySSLCanNotDelete: "Нельзя удалить сертификат {{.name}}, который находится в процессе выпуска. Пожалуйста, попробуйте позже." + +#mysql +ErrUserIsExist: 'Текущий пользователь уже существует, пожалуйста, введите еще раз' +ErrDatabaseIsExist: 'Текущая база данных уже существует, пожалуйста, введите ее повторно' +ErrExecTimeOut: 'Время выполнения SQL истекло, проверьте базу данных' +ErrRemoteExist: 'Удаленная база данных с таким именем уже существует. Измените его и повторите попытку' +ErrLocalExist: 'Имя уже существует в локальной базе данных, измените его и повторите попытку' +RemoteBackup: "Для резервного копирования удаленной базы данных необходимо сначала запустить службу базы данных локального контейнера с помощью образа {{ .name }}, пожалуйста, подождите..." +RemoteRecover: "Для восстановления удаленной базы данных необходимо сначала запустить службу базы данных локального контейнера с помощью образа {{ .name }}, пожалуйста, подождите..." +Arg: "Аргумент" + +#редис +ErrTypeOfRedis: 'Тип файла восстановления не соответствует текущему методу сохранения. Измените его и повторите попытку' + +#контейнер +ErrInUsed: "{{ .detail }} используется и не может быть удалено!" +ErrObjectInUsed: "Этот объект используется и не может быть удален!" +ErrObjectBeDependent: "Этот образ зависит от других образов и не может быть удален!" +ErrPortRules: 'Номер порта не совпадает, введите заново!' +ErrPgImagePull: 'Время извлечения изображения истекло. Настройте ускорение изображения или вручную извлеките изображение {{ .name }} и повторите попытку' +PruneHelper: "Эта очистка {{ .name }} освободила дисковое пространство {{ .size }}" +ImageRemoveHelper: "Удалён образ {{ .name }}, освобождено {{ .size }} дискового пространства" +BuildCache: "Кэш сборки" +Volume: "Том хранилища" +Network: "Сеть" +PruneStart: "Очистка выполняется, пожалуйста, подождите..." + +#время выполнения +ErrFileNotExist: 'Файл {{ .detail }} не существует! Проверьте целостность исходного файла!' +ErrImageBuildErr: 'Сборка образа не удалась' +ErrImageExist: "Изображение уже существует! Пожалуйста, измените имя изображения." +ErrDelWithWebsite: 'Операционная среда уже связана с веб-сайтом и не может быть удалена' +ErrRuntimeStart: 'Запуск не удался' +ErrPackageJsonNotFound: 'файл package.json не существует' +ErrScriptsNotFound: 'Элемент конфигурации скриптов не найден в package.json' +ErrContainerNameNotFound: 'Не удалось получить имя контейнера, проверьте файл .env' +ErrNodeModulesNotFound: 'Папка node_modules не существует! Измените среду выполнения или дождитесь ее успешного запуска' +ErrContainerNameIsNull: 'Имя контейнера не существует' +ErrPHPPortIsDefault: "A porta 9000 é a porta padrão, por favor, modifique e tente novamente" +ErrPHPRuntimePortFailed: "A porta {{ .name }} já está sendo usada pelo ambiente de tempo de execução atual, por favor, modifique e tente novamente" + +#инструмент +ErrConfigNotFound: 'Файл конфигурации не существует' +ErrConfigParse: 'Неверный формат файла конфигурации' +ErrConfigIsNull: 'Файл конфигурации не может быть пустым' +ErrConfigDirNotFound: 'Рабочий каталог не существует' +ErrConfigAlreadyExist: 'Файл конфигурации с таким именем уже существует' +ErrUserFindErr: 'Поиск пользователя {{ .name }} не удался {{ .err }}' + +#cronjob +CutWebsiteLogSuccess: 'Журнал веб-сайта {{ .name }} успешно вырезан, путь к резервной копии {{ .path }}' +HandleShell: 'Выполнить скрипт {{ .name }}' +HandleCurl: "Доступ к URL {{ .name }}" +HandleNtpSync: 'Синхронизация системного времени' +HandleSystemClean: 'Очистка системного кэша' +SystemLog: 'Системный лог' +CutWebsiteLog: 'Ротация логов сайта' +FileOrDir: 'Каталог / Файл' +UploadFile: 'Загрузка файла резервной копии {{ .file }} в {{ .backup }}' +Upload: "Загрузить" +IgnoreBackupErr: "Ошибка резервного копирования: {{ .detail }}, игнорируем эту ошибку..." +IgnoreUploadErr: "Ошибка загрузки: {{ .detail }}, игнорируем эту ошибку..." +LoadBackupFailed: "Не удалось получить подключение к учетной записи резервной копии, ошибка: {{ .detail }}" +InExecuting: "Текущая задача выполняется, не повторяйте выполнение!" +NoSuchResource: "В базе данных не найдено содержимое резервной копии, пропускаем..." +CleanLog: "Очистить журнал" +CleanLogByName: "Очистить журнал {{.name}}" +WafIpGroupNotFound: "Группа IP WAF не найдена. Пожалуйста, перейдите в [Расширенные функции - WAF - Черный/Белый список - Группа IP], чтобы добавить группу IP с помощью метода удаленной загрузки." + +#ящик для инструментов +ErrNotExistUser: 'Текущий пользователь не существует, измените его и повторите попытку!' +ErrBanAction: 'Настройка не удалась. Текущая служба {{ .name }} недоступна. Проверьте и повторите попытку!' +ErrClamdscanNotFound: 'Команда clamdscan не обнаружена, обратитесь к документации, чтобы установить ее!' +TaskSwapSet: "Настройка Swap" +SetSwap: "Настроить Swap {{ .path }} - {{ .size }}" +CreateSwap: "Создать Файл Swap" +FormatSwap: "Форматировать Файл Swap" +EnableSwap: "Включить Swap" + +#tamper +CleanTamperSetting: "Очистить исторические настройки защиты" +SetTamperWithRules: "Запустить защиту каталога согласно правилам" +ProtectDir: "Защитить каталог {{ .name }}" +IgnoreFile: "Защитить файл {{ .name }}" +TamperSetSuccessful: "Настройки защиты завершены, начинаю мониторинг следующих каталогов:" + +#ваф +ErrScope: 'Изменение этой конфигурации не поддерживается' +ErrStateChange: 'He удалось изменить состояние' +ErrRuleExist: 'Правило уже существует' +ErrRuleNotExist: 'Правило не существует' +ErrParseIP: 'Неправильный формат IP' +ErrDefaultIP: 'по умолчанию это зарезервированное имя, пожалуйста, измените его на другое имя' +ErrGroupInUse: 'IP-группа используется черным/белым списком и не может быть удалена' +ErrIPGroupAclUse: "Группа IP используется пользовательскими правилами сайта {{ .name }}, невозможно удалить" +ErrGroupExist: 'Имя группы IP уже существует' +ErrIPRange: 'Неверный диапазон IP-адресов' +ErrIPExist: 'IP-адрес уже существует' +urlDefense: 'Правила URL' +urlHelper: 'Запрещенный URL' +dirFilter: 'Фильтр каталогов' +xss: 'XSS' +phpExec: 'Выполнение PHP-скрипта' +oneWordTrojan: 'Троян из одного слова' +appFilter: 'Применить фильтрацию опасных каталогов' +webshell: 'Be6-оболочка' +args: 'Правила параметров' +protocolFilter: 'Фильтрация протоколов' +javaFileter: 'Фильтр опасных файлов Java' +scannerFilter: 'Фильтр сканера' +escapeFilter: 'фильтр побега' +customRule: 'Пользовательское правило' +httpMethod: 'Фильтрация метода HTTP' +fileExt: 'Ограничения на загрузку файлов' +defaultIpBlack: 'Вредоносная группа IP' +cookie: 'Правила использования файлов cookie' +urlBlack: 'Черный список URL' +uaBlack: 'Черный список User-Agent' +attackCount: 'Ограничение частоты атак' +fileExtCheck: 'Ограничения на загрузку файлов' +geoRestrict: 'Региональные ограничения доступа' +unknownWebsite: 'Несанкционированный доступ к доменному имени' +notFoundCount: 'Ограничение скорости 404' +headerDefense: 'Правила заголовка' +defaultUaBlack: 'Правила User-Agent' +methodWhite: 'Правила HTTP' +captcha: 'человеко-машинная верификация' +fiveSeconds: '5 секунд проверки' +vulnCheck: 'Дополнительные правила' +acl: 'Пользовательские правила' +sql: 'SQL-инъекция' +cc: 'Ограничение частоты доступа' +defaultUrlBlack: 'Правила URL' +sqlInject: 'SQL-инъекция' +ErrDBNotExist: 'База данных не существует' +allow: 'разрешить' +deny: 'отрицать' +OpenrestyNotFound: 'Openresty не установлен' +remoteIpIsNull: "Список IP пуст" +OpenrestyVersionErr: "Версия Openresty слишком низкая, пожалуйста, обновите Openresty до 1.27.1.2-2-2-focal" + +#задача +TaskStart: '{{ .name }} Задача начинается [START]' +TaskEnd: '{{ .name }} Задача выполнена [ЗАВЕРШЕНО]' +TaskFailed: 'Задача {{ .name }} не выполнена' +TaskTimeout: '{{ .name }} истекло время ожидания' +TaskSuccess: '{{ .name }} Задача выполнена успешно' +TaskRetry: 'Начать {{ .name }}-ю повторную попытку' +SubTaskSuccess: '{{ .name }} выполнено успешно' +SubTaskFailed: '{{ .name }} не удалось: {{ .err }}' +TaskInstall: 'Установить' +TaskUninstall: 'Удалить' +TaskCreate: 'Создать' +TaskDelete: 'Удалить' +TaskUpgrade: 'Обновить' +TaskUpdate: 'Обновить' +TaskRestart: 'Перезапуск' +TaskProtect: "Защита" +TaskBackup: 'Резервное копирование' +TaskRecover: 'Восстановить' +TaskRollback: 'Откат' +TaskPull: 'Вытянуть' +TaskCommit: 'Kоммит' +ЗадачаСборка: 'Сборка' +TaskPush: 'Push' +TaskClean: "Очистка" +TaskHandle: 'Выполнить' +TaskImport: "Импорт" +TaskExport: "Экспорт" +Website: 'Be6-сайт' +App: 'Приложение' +Runtime: 'Среда выполнения' +Database: 'База данных' +ConfigFTP: 'Создать пользователя FTP {{ .name }}' +ConfigOpenresty: 'Создать файл конфигурации Openresty' +InstallAppSuccess: 'Приложение {{ .name }} успешно установлено' +ConfigRuntime: 'Настройка среды выполнения' +ConfigApp: 'Приложение конфигурации' +SuccessStatus: '{{ .name }} успешно' +FailedStatus: '{{ .name }} не удалось {{ .err }}' +HandleLink: 'Обработка ассоциации приложений' +HandleDatabaseApp: 'Обработка параметров приложения' +ExecShell: 'Выполнить скрипт {{ .name }}' +PullImage: 'Вытянуть изображение' +Start: 'Старт' +Run: 'Старт' +Stop: 'Стоп' +Image: 'Зеркало' +Compose: 'Оркестровка' +Container: 'Контейнер' +AppLink: 'Связанное приложение' +EnableSSL: 'Включить HTTPS' +AppStore: 'Магазин приложений' +TaskSync: 'Синхронизировать' +LocalApp: 'Локальное приложение' +SubTask: 'Подзадача' +RuntimeExtension: 'Расширение среды выполнения' +TaskIsExecuting: 'Задача выполняется' +CustomAppstore: 'Хранилище пользовательских приложений' +TaskExec: 'Выполнить' +TaskBatch: "Пакетная операция" +FileConvert: 'Преобразование формата файла' + +# task - clam +Clamscan: "Сканировать {{ .name }}" +TaskScan: "Сканировать" + +# задача - ай +OllamaModelPull: 'Вытянуть модель Ollama {{ .name }}' +OllamaModelSize: 'Получить размер модели Ollama {{ .name }}' + +# снимок задачи +Snapshot: 'Снимок' +SnapDBInfo: 'Запись информации о базе данных 1Panel' +SnapCopy: 'Копировать файлы и каталоги {{ .name }}' +SnapNewDB: 'Инициализировать соединение с базой данных {{ .name }}' +SnapDeleteOperationLog: 'Удалить журнал операций' +SnapDeleteLoginLog: 'Удалить журнал доступа' +SnapDeleteMonitor: 'Удалить данные мониторинга' +SnapRemoveSystemIP: 'Удалить системный IP' +SnapBaseInfo: 'Записать основную информацию 1Panel' +SnapInstallAppImageEmpty: 'Образы приложений не выбраны, пропускаем...' +SnapInstallApp: 'Резервное копирование приложений, установленных в 1Panel' +SnapDockerSave: 'Сжать установленные приложения' +SnapLocalBackup: 'Резервное копирование локального каталога резервных копий 1Panel' +SnapCompressBackup: 'Сжать локальный каталог резервных копий' +SnapPanelData: 'Резервное копирование каталога данных 1Panel' +SnapCompressPanel: 'Каталог сжатых данных' +SnapWebsite: 'Резервное копирование каталога веб-сайта 1Panel' +SnapCloseDBConn: 'Закрыть соединение с базой данных' +SnapCompress: 'Создание файлов моментальных снимков' +SnapCompressFile: 'Сжать файл моментального снимка' +SnapCheckCompress: 'Проверить файл сжатия снимка' +SnapCompressSize: 'Размер файла снимка {{ .name }}' +SnapUpload: 'Загрузить файл снимка' +SnapUploadTo: 'Загрузить файл снимка в {{ .name }}' +SnapUploadRes: 'Загрузить файл снимка в {{ .name }}' + +SnapshotRecover: 'Восстановление моментального снимка' +RecoverDownload: 'Загрузить файл снимка' +Download: 'Скачать' +RecoverDownloadAccount: 'Получить резервную копию учетной записи для загрузки моментального снимка {{ .name }}' +RecoverDecompress: 'Распаковка сжатых файлов моментальных снимков' +Decompress: 'Декомпрессия' +BackupBeforeRecover: 'Резервное копирование системных данных перед созданием снимка' +Readjson: 'Прочитать файл JSON в снимке' +ReadjsonPath: 'Получить путь к файлу JSON в снимке' +ReadjsonContent: 'Прочитать файл Json' +ReadjsonMarshal: 'Обработка Json escape' +RecoverApp: 'Восстановить установленные приложения' +RecoverWebsite: 'Восстановить каталог веб-сайта' +RecoverAppImage: 'Восстановить резервную копию образа снимка' +RecoverCompose: 'Восстановить содержимое другого композитора' +RecoverComposeList: 'Восстановить всех композиторов' +RecoverComposeItem: 'Восстановить сообщение {{ .name }}' +RecoverAppEmpty: 'В файле снимка не найдено резервной копии образа приложения' +RecoverBaseData: 'Восстановить базовые данные и файлы' +RecoverDaemonJsonEmpty: 'Ни файл снимка, ни текущая машина не имеют файла конфигурации контейнера daemon.json' +RecoverDaemonJson: 'Восстановить файл конфигурации контейнера daemon.json' +RecoverDBData: 'Восстановить данные базы данных' +RecoverBackups: 'Восстановить локальный каталог резервных копий' +RecoverPanelData: 'Каталог данных восстановления' + +# задача - контейнер +ContainerNewCliet: 'Инициализировать Docker-клиент' +ContainerImagePull: 'Извлечь изображение контейнера {{ .name }}' +ContainerRemoveOld: 'Удалить исходный контейнер {{ .name }}' +ContainerImageCheck: 'Проверьте, нормально ли извлекается изображение' +ContainerLoadInfo: 'Получить основную информацию о контейнере' +ContainerRecreate: 'Обновление контейнера не удалось, сейчас начну восстанавливать исходный контейнер' +ContainerCreate: 'Создать новый контейнер {{ .name }}' +ContainerCreateFailed: 'Создание контейнера не удалось, удалите неудавшийся контейнер' +ContainerStartCheck: 'Проверить, запущен ли контейнер' + +# задача - изображение +ImageBuild: 'Создание изображения' +ImageBuildStdoutCheck: 'Анализ содержимого выходного изображения' +ImageBuildRes: 'Выходные данные сборки образа: {{ .name }}' +ImagePull: 'Вытащить изображение' +ImageRepoAuthFromDB: 'Получить информацию об аутентификации репозитория из базы данных' +ImaegPullRes: 'Выход извлечения изображения: {{ .name }}' +ImagePush: 'Отправить изображение' +ImageRenameTag: 'Изменить тег изображения' +ImageNewTag: 'Новый тег изображения {{ .name }}' +ImaegPushRes: 'Вывод push-изображения: {{ .name }}' +ComposeCreate: 'Создать композицию' +ComposeCreateRes: 'Составить вывод создания: {{ .name }}' + +# задача - веб-сайт +BackupNginxConfig: 'Резервное копирование файла конфигурации OpenResty веб-сайта' +CompressFileSuccess: 'Каталог успешно сжат, сжат в {{ .name }}' +CompressDir: 'Каталог сжатия' +DeCompressFile: 'Распаковать файл {{ .name }}' +ErrCheckValid: 'Проверка файла резервной копии не удалась, {{ .name }}' +Rollback: 'Откат' +websiteDir: 'Каталог веб-сайтов' +RecoverFailedStartRollBack: 'Восстановление не удалось, начать откат' +AppBackupFileIncomplete: 'Файл резервной копии неполный и в нем отсутствуют файлы app.json или app.tar.gz' +AppAttributesNotMatch: 'Тип или имя приложения не совпадают' + +#тревога +ErrAlert: 'Формат предупреждающего сообщения неверен, проверьте и повторите попытку!' +ErrAlertPush: 'Ошибка при отправке оповещения. Проверьте и повторите попытку!' +ErrAlertSave: 'Ошибка сохранения информации о тревоге, проверьте и повторите попытку!' +ErrAlertSync: 'Ошибка синхронизации информации о тревоге, проверьте и повторите попытку!' +ErrAlertRemote: 'Ошибка удаленного сообщения об ошибке, проверьте и повторите попытку!' + +#task - runtime +ErrInstallExtension: "Уже выполняется задача установки, подождите, пока задача завершится" + +# alert mail template +PanelAlertTitle: "Уведомление панели" +TestAlertTitle: "Тестовое письмо - проверка подключения" +TestAlert: "Это тестовое письмо для проверки правильности настройки отправки писем." +LicenseExpirationAlert: "Ваш панель {{ .node }}{{ .ip }}, лицензия истечет через {{ .day }} дней. Войдите в панель для получения деталей." +CronJobFailedAlert: "Ваш панель {{ .node }}{{ .ip }}, выполнение запланированной задачи {{ .name }} не удалось. Войдите в панель для получения деталей." +ClamAlert: "Ваш панель {{ .node }}{{ .ip }}, задача сканирования на вирусы обнаружила {{ .num }} зараженных файлов. Войдите в панель для получения деталей." +WebSiteAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} веб-сайтов истечут через {{ .day }} дней. Войдите в панель для получения деталей." +SSLAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} сертификатов SSL истечут через {{ .day }} дней. Войдите в панель для получения деталей." +DiskUsedAlert: "Ваш панель {{ .node }}{{ .ip }}, использование диска {{ .name }} достигло {{ .used }}. Войдите в панель для получения деталей." +ResourceAlert: "Ваш панель {{ .node }}{{ .ip }}, средний уровень использования {{ .name }} за {{ .time }} минут составляет {{ .used }}. Войдите в панель для получения деталей." +PanelVersionAlert: "Ваш панель {{ .node }}{{ .ip }}, доступна последняя версия панели для обновления. Войдите в панель для получения деталей." +PanelPwdExpirationAlert: "Ваш панель {{ .node }}{{ .ip }}, пароль панели истечет через {{ .day }} дней. Войдите в панель для получения деталей." +CommonAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .msg }}. Войдите в панель для получения деталей." +NodeExceptionAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} узлов работают с ошибками. Войдите в панель для получения деталей." +LicenseExceptionAlert: "Ваш панель {{ .node }}{{ .ip }}, {{ .num }} лицензий имеют ошибки. Войдите в панель для получения деталей." +SSHAndPanelLoginAlert: "Ваш панель {{ .node }}{{ .ip }}, вход {{ .name }} с адреса {{ .loginIp }} является аномальным. Войдите в панель для получения деталей." + +#disk +DeviceNotFound: "Устройство {{ .name }} не найдено" +DeviceIsMounted: "Устройство {{ .name }} подключено, сначала отключите" +PartitionDiskErr: "Не удалось разделить, {{ .err }}" +FormatDiskErr: "Не удалось отформатировать диск, {{ .err }}" +MountDiskErr: "Не удалось подключить диск, {{ .err }}" +UnMountDiskErr: "Не удалось отключить диск, {{ .err }}" +XfsNotFound: "Файловая система xfs не обнаружена, сначала установите xfsprogs" \ No newline at end of file diff --git a/agent/i18n/lang/tr.yaml b/agent/i18n/lang/tr.yaml new file mode 100644 index 0000000..d385af7 --- /dev/null +++ b/agent/i18n/lang/tr.yaml @@ -0,0 +1,531 @@ +ErrInvalidParams: 'İstek parametre hatası: {{ .detail }}' +ErrTokenParse: 'Token oluşturma hatası: {{ .detail }}' +ErrInitialPassword: 'Orijinal şifre yanlış' +ErrInternalServer: 'Dahili sunucu hatası: {{ .detail }}' +ErrRecordExist: 'Kayıt zaten mevcut' +ErrRecordNotFound: 'Kayıt bulunamadı' +ErrStructTransform: 'Tür dönüştürme başarısız: {{ .err }}' +ErrNotLogin: 'Kullanıcı giriş yapmamış: {{ .detail }}' +ErrPasswordExpired: 'Mevcut şifrenin süresi dolmuş: {{ .detail }}' +ErrNotSupportType: 'Sistem mevcut türü desteklemiyor: {{ .name }}' +ErrProxy: 'İstek hatası, lütfen düğüm durumunu kontrol edin: {{ .detail }}' +ErrApiConfigStatusInvalid: 'API arayüzüne erişim yasak: {{ .detail }}' +ErrApiConfigKeyInvalid: 'API arayüz anahtarı hatası: {{ .detail }}' +ErrApiConfigIPInvalid: 'API arayüzünü çağırmak için kullanılan IP beyaz listede değil: {{ .detail }}' +ErrApiConfigDisable: 'Bu arayüz API arayüz çağrılarının kullanımını yasaklıyor: {{ .detail }}' +ErrApiConfigKeyTimeInvalid: 'API arayüz zaman damgası hatası: {{ .detail }}' +StartPushSSLToNode: "Sertifika düğüme gönderilmeye başlandı" +PushSSLToNodeFailed: "Sertifika düğüme gönderilemedi: {{ .err }}" +PushSSLToNodeSuccess: "Sertifika düğüme başarıyla gönderildi" +ErrShutDown: "Komut manuel olarak sonlandırıldı!" + +ErrMinQuickJump: "Lütfen en az bir hızlı atlama girişi ayarlayın!" +ErrMaxQuickJump: "En fazla dört hızlı atlama girişi ayarlayabilirsiniz!" + +#common +ErrUsernameIsExist: 'Kullanıcı adı zaten mevcut' +ErrNameIsExist: 'İsim zaten mevcut' +ErrDemoEnvironment: 'Demo sunucu, bu işlem yasak!' +ErrCmdTimeout: 'Komut yürütme zaman aşımına uğradı!' +ErrCmdIllegal: 'Yürütme komutunda yasak karakterler var, lütfen değiştirin ve tekrar deneyin!' +ErrPortExist: '{{ .port }} portu zaten {{ .type }} [{{ .name }}] tarafından kullanılıyor' +TYPE_APP: 'Uygulama' +TYPE_RUNTIME: 'Çalışma ortamı' +TYPE_DOMAIN: 'Alan Adı' +ErrTypePort: 'Port {{ .name }} formatı yanlış' +ErrTypePortRange: 'Port aralığının 1-65535 arasında olması gerekiyor' +Success: 'Başarılı' +Failed: 'Başarısız' +SystemRestart: 'Sistem yeniden başlatması nedeniyle görev kesildi' +ErrGroupIsDefault: 'Varsayılan grup, silinemez' +ErrGroupIsInWebsiteUse: 'Grup başka bir web sitesi tarafından kullanılıyor ve silinemez.' +Decrypt: "Şifre Çöz" + +#backup +Localhost: 'Yerel Makine' +ErrBackupInUsed: 'Yedekleme hesabı zamanlanmış görevde kullanıldı ve silinemez.' +ErrBackupCheck: 'Yedekleme hesabı test bağlantısı başarısız {{ .err }}' +ErrBackupLocalDelete: 'Yerel sunucu yedekleme hesabını silme henüz desteklenmiyor' +ErrBackupLocalCreate: 'Yerel sunucu yedekleme hesapları oluşturma henüz desteklenmiyor' + +#app +ErrPortInUsed: '{{ .detail }} portu zaten kullanılıyor!' +ErrAppLimit: 'Yüklenen uygulama sayısı sınırı aştı' +ErrNotInstall: 'Uygulama yüklenmedi' +ErrPortInOtherApp: '{{ .port }} portu zaten {{ .apps }} uygulaması tarafından kullanılıyor!' +ErrDbUserNotValid: 'Mevcut veritabanı, kullanıcı adı ve şifre eşleşmiyor!' +ErrUpdateBuWebsite: 'Uygulama başarıyla güncellendi, ancak web sitesi yapılandırma dosyası değiştirme başarısız. Lütfen yapılandırmayı kontrol edin! ' +Err1PanelNetworkFailed: 'Varsayılan konteyner ağ oluşturma başarısız! {{ .detail }}' +ErrFileParse: 'Uygulama docker-compose dosya ayrıştırma başarısız!' +ErrInstallDirNotFound: 'Kurulum dizini mevcut değil. Kaldırmanız gerekiyorsa, lütfen Zorla Kaldır seçeneğini seçin' +AppStoreIsUpToDate: 'Uygulama mağazası zaten en son sürüm' +LocalAppVersionNull: '{{ .name }} uygulaması sürüme senkronize edilmedi! Uygulama listesine eklenemez' +LocalAppVersionErr: '{{ .name }} senkronizasyon sürümü {{ .version }} başarısız! {{ .err }}' +ErrFileNotFound: '{{ .name }} dosyası mevcut değil' +ErrFileParseApp: '{{ .name }} dosya ayrıştırma başarısız {{ .err }}' +ErrAppDirNull: 'Sürüm klasörü mevcut değil' +LocalAppErr: 'Uygulama {{ .name }} senkronizasyonu başarısız! {{ .err }}' +ErrContainerName: 'Konteyner adı zaten mevcut' +ErrCreateHttpClient: 'İstek oluşturma başarısız {{ .err }}' +ErrHttpReqTimeOut: 'İstek zaman aşımı {{ .err }}' +ErrHttpReqFailed: 'İstek başarısız {{ .err }}' +ErrNoSuchHost: 'İstenen sunucu bulunamıyor {{ .err }}' +ErrHttpReqNotFound: 'İstenen kaynak {{ .err }} bulunamadı' +ErrContainerNotFound: '{{ .name }} konteyneri mevcut değil' +ErrContainerMsg: '{{ .name }} konteyneri anormal. Lütfen ayrıntılar için konteyner sayfasındaki günlüğü kontrol edin' +ErrAppBackup: '{{ .name }} uygulama yedeklemesi başarısız {{ .err }}' +ErrVersionTooLow: 'Mevcut 1Panel sürümü Uygulama Mağazasını güncellemek için çok düşük. Lütfen işlemden önce sürümü yükseltin.' +ErrAppNameExist: 'Uygulama adı zaten mevcut' +AppStoreIsSyncing: 'Uygulama Mağazası senkronize ediliyor, lütfen daha sonra tekrar deneyin' +ErrGetCompose: 'docker-compose.yml dosyası alınamadı! {{ .detail }}' +ErrAppWarn: 'Anormal durum, lütfen günlüğü kontrol edin' +ErrAppParamKey: 'Parametre {{ .name }} alanı anormal' +ErrAppUpgrade: 'Uygulama {{ .name }} yükseltmesi başarısız {{ .err }}' +AppRecover: 'Uygulama {{ .name }} geri alma' +PullImageStart: 'Image [{{ .name }}] çekmeye başla' +PullImageSuccess: 'Image çekme başarılı' +AppStoreIsLastVersion: 'Uygulama Mağazası zaten en son sürüm' +AppStoreSyncSuccess: 'Uygulama Mağazası senkronizasyonu başarılı' +SyncAppDetail: 'Uygulama yapılandırmasını senkronize et' +AppVersionNotMatch: '{{ .name }} uygulaması daha yüksek 1Panel sürümü gerektiriyor, senkronizasyon atlanıyor' +MoveSiteDir: "Web sitesi dizini değişikliği tespit edildi. OpenResty web sitesi dizinini {{ .name }} adresine taşımak gerekiyor" +MoveSiteDirSuccess: 'Web sitesi dizini taşıma başarılı' +DeleteRuntimePHP: 'PHP çalışma zamanını sil' +CustomAppStoreFileValid: 'Uygulama mağazası paketlerinin .tar.gz formatında olması gerekiyor' +PullImageTimeout: 'Image çekme zaman aşımı, lütfen image hızlandırmayı artırmayı deneyin veya başka bir image hızlandırmaya geçin' +ErrAppIsDown: '{{ .name }} uygulama durumu anormal, lütfen kontrol edin' +ErrCustomApps: 'Yüklü bir uygulama var, lütfen önce kaldırın' +ErrCustomRuntimes: 'Yüklü bir çalışma ortamı var, lütfen önce silin' +ErrAppVersionDeprecated: "{{ .name }} uygulaması mevcut 1Panel sürümü ile uyumlu değil, atlandı" +ErrDockerFailed: "Docker durumu anormal, lütfen servis durumunu kontrol edin" +ErrDockerComposeCmdNotFound: "Docker Compose komutu mevcut değil, lütfen önce bu komutu host makinesine yükleyin" +UseExistImage: 'Görüntü çekme başarısız, mevcut görüntü kullanılıyor' + +#ssh +ExportIP: "Giriş IP" +ExportArea: "Konum" +ExportPort: "Port" +ExportAuthMode: "Giriş Yöntemi" +ExportUser: "Kullanıcı" +ExportStatus: "Giriş Durumu" +ExportDate: "Zaman" + +#file +ErrFileCanNotRead: 'Bu dosya önizlemeyi desteklemiyor' +ErrFileToLarge: 'Dosya 10M dan büyük ve açılamıyor' +ErrPathNotFound: 'Dizin mevcut değil' +ErrMovePathFailed: 'Hedef yol orijinal yolu içeremez!' +ErrLinkPathNotFound: 'Hedef yol mevcut değil!' +ErrFileIsExist: 'Dosya veya klasör zaten mevcut!' +ErrFileUpload: '{{ .name }} dosya yükleme başarısız {{ .detail }}' +ErrFileDownloadDir: 'Klasör indirme desteklenmiyor' +ErrCmdNotFound: '{{ .name}} komutu mevcut değil, lütfen önce bu komutu host a yükleyin' +ErrSourcePathNotFound: 'Kaynak dizin mevcut değil' +ErrFavoriteExist: 'Bu yol zaten favorilere eklendi' +ErrInvalidChar: 'Yasak karakterlere izin verilmiyor' +ErrPathNotDelete: 'Seçilen dizin silinemez' +ErrLogFileToLarge: "Günlük dosyası 500MB'ı aşıyor ve açılamıyor" +FileDropFailed: "{{ .name }} dosyası temizlenemedi: {{ .err }}" +FileDropSuccess: "{{ .name }} dosyası başarıyla temizlendi, {{ .count }} dosya silindi, {{ .size }} disk alanı boşaltıldı" +FileDropSum: "Dosya temizleme tamamlandı, toplam {{ .count }} dosya silindi, toplam {{ .size }} disk alanı boşaltıldı" +ErrBadDecrypt: "Sıkıştırılmış paket şifre çözme parolası hatalı, açma başarısız, lütfen kontrol edip tekrar deneyin!" + + +#website +ErrAliasIsExist: 'Takma ad zaten mevcut' +ErrBackupMatch: 'Yedekleme dosyası mevcut web sitesi verilerinin bir kısmıyla eşleşmiyor {{ .detail }}' +ErrBackupExist: 'Yedekleme dosyasındaki kaynak verilerin karşılık gelen kısmı mevcut değil {{ .detail }}' +ErrPHPResource: 'Yerel işletim ortamı değiştirmeyi desteklemiyor! ' +ErrPathPermission: 'Dizin dizininde 1000:1000 olmayan izinlere sahip bir klasör tespit edildi, bu web sitesinde Erişim reddedildi hatasına neden olabilir. Lütfen yukarıdaki Kaydet düğmesine tıklayın' +ErrDomainIsUsed: 'Alan adı zaten [{{ .name }}] web sitesi tarafından kullanılıyor' +ErrDomainFormat: '{{ .name }} alan adı formatı yanlış' +ErrDefaultAlias: 'default ayrılmış bir kod, lütfen başka bir kod kullanın' +ErrParentWebsite: 'Önce {{ .name }} alt sitesini silmeniz gerekiyor' +ErrBuildDirNotFound: 'Yapı dizini mevcut değil' +ErrImageNotExist: 'İşletim ortamı {{ .name }} image mevcut değil, lütfen işletim ortamını yeniden düzenleyin' +ErrProxyIsUsed: "Yük dengeleme ters proxy tarafından kullanıldı, silinemez" +ErrSSLValid: 'Sertifika dosyası anormal, lütfen sertifika durumunu kontrol edin!' +ErrWebsiteDir: "Lütfen web sitesi dizini içindeki bir dizin seçin." +ErrComposerFileNotFound: "composer.json dosyası mevcut değil" +ErrRuntimeNoPort: "Çalışma zamanı ortamı bir porta sahip değil, lütfen önce çalışma zamanı ortamını düzenleyin." +Status: 'Durum' +start: 'Başlat' +stop: 'Durdur' +restart: 'Yeniden Başlat' +kill: 'Zorla Durdur' +pause: 'Duraklat' +unpause: 'Devam Et' +remove: 'Sil' +delete: 'Sil' +ErrDefaultWebsite: "Varsayılan bir web sitesi zaten ayarlanmış. Yeni bir tane ayarlamadan önce lütfen iptal edin!" +SSL: 'Sertifika' +Set: 'Ayarlar' + +#ssl +ErrSSLCannotDelete: '{{ .name }} sertifikası bir web sitesi tarafından kullanılıyor ve silinemez' +ErrAccountCannotDelete: 'Hesap bir sertifikayla ilişkili ve silinemez' +ErrSSLApply: 'Sertifika yenileme başarılı, openresty yeniden yükleme başarısız, lütfen yapılandırmayı kontrol edin!' +ErrEmailIsExist: 'Posta kutusu zaten mevcut' +ErrSSLKeyNotFound: 'Özel anahtar dosyası mevcut değil' +ErrSSLCertificateNotFound: 'Sertifika dosyası mevcut değil' +ErrSSLKeyFormat: 'Özel anahtar dosyası doğrulama başarısız' +ErrSSLCertificateFormat: 'Sertifika dosyası formatı yanlış, lütfen pem formatını kullanın' +ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid veya EabHmacKey boş olamaz' +ErrOpenrestyNotFound: 'Http modu önce Openresty yüklenmesini gerektiriyor' +ApplySSLStart: 'Sertifika başvurusu başlatılıyor, alan adı [{{ .domain }}] başvuru yöntemi [{{ .type }}] ' +dnsAccount: 'DNS Otomatik' +dnsManual: 'DNS Manuel' +http: 'HTTP' +ApplySSLFailed: '[{{ .domain }}] sertifika başvurusu başarısız, {{ .detail }} ' +ApplySSLSuccess: '[{{ .domain }}] sertifika başvurusu başarılı! !' +DNSAccountName: 'DNS hesabı [{{ .name }}] sağlayıcı [{{ .type }}]' +PushDirLog: 'Sertifika [{{ .path }}] dizinine gönderildi {{ .status }}' +ErrDeleteCAWithSSL: 'Mevcut kuruluşun verilmiş bir sertifikası var ve silinemez.' +ErrDeleteWithPanelSSL: 'Panel SSL yapılandırması bu sertifikayı kullanıyor ve silinemez' +ErrDefaultCA: 'Varsayılan otorite silinemez' +ApplyWebSiteSSLLog: '{{ .name }} web sitesi sertifikası yenilemeyi başlatıyor' +ErrUpdateWebsiteSSL: '{{ .name }} web sitesi sertifika güncelleme başarısız: {{ .err }}' +ApplyWebSiteSSLSuccess: 'Web sitesi sertifikası güncelleme başarılı' +ErrExecShell: 'Betik yürütme başarısız {{ .err }}' +ExecShellStart: 'Betik yürütmeyi başlat' +ExecShellSuccess: 'Betik yürütme başarılı' +StartUpdateSystemSSL: 'Sistem sertifikası güncellemeyi başlat' +UpdateSystemSSLSuccess: 'Sistem sertifikası güncelleme başarılı' +ErrWildcardDomain: 'HTTP modunda joker alan adı sertifikası başvurusu yapılamıyor' +ErrApplySSLCanNotDelete: "İşlemde olan sertifika {{.name}} silinemez, lütfen daha sonra tekrar deneyin." + +#mysql +ErrUserIsExist: 'Mevcut kullanıcı zaten mevcut, lütfen yeniden girin' +ErrDatabaseIsExist: 'Mevcut veritabanı zaten mevcut, lütfen yeniden girin' +ErrExecTimeOut: 'SQL yürütme zaman aşımı, lütfen veritabanını kontrol edin' +ErrRemoteExist: 'Uzak veritabanında bu adla zaten mevcut, lütfen değiştirin ve tekrar deneyin' +ErrLocalExist: 'Yerel veritabanında bu ad zaten mevcut, lütfen değiştirin ve tekrar deneyin' +RemoteBackup: "Uzak veritabanını yedeklemek için önce {{ .name }} görüntüsü kullanılarak yerel konteyner veritabanı hizmetinin başlatılması gerekiyor, lütfen bekleyin..." +RemoteRecover: "Uzak veritabanını geri yüklemek için önce {{ .name }} görüntüsü kullanılarak yerel konteyner veritabanı hizmetinin başlatılması gerekiyor, lütfen bekleyin..." +Arg: "Argüman" + +#redis +ErrTypeOfRedis: 'Kurtarma dosyası türü mevcut kalıcılık yöntemiyle eşleşmiyor, lütfen değiştirin ve tekrar deneyin' + +#container +ErrInUsed: "{{ .detail }} kullanımda ve silinemez!" +ErrObjectInUsed: "Bu nesne kullanımda ve silinemez!" +ErrObjectBeDependent: "Bu görüntü diğer görüntüler tarafından bağımlıdır ve silinemez!" +ErrPortRules: 'Port numarası eşleşmiyor, lütfen yeniden girin!' +ErrPgImagePull: 'Image çekme zaman aşımı, lütfen image hızlandırma yapılandırın veya manuel olarak {{ .name }} imageını çekin ve tekrar deneyin' +PruneHelper: "Bu temizlik {{ .name }} disk alanı {{ .size }} boşalttı" +ImageRemoveHelper: "{{ .name }} imajı silindi, {{ .size }} disk alanı boşalttı" +BuildCache: "Derleme önbelleği" +Volume: "Depolama hacmi" +Network: "Ağ" +PruneStart: "Temizlik devam ediyor, lütfen bekleyin..." + +#runtime +ErrFileNotExist: '{{ .detail }} dosyası mevcut değil! Lütfen kaynak dosyanın bütünlüğünü kontrol edin!' +ErrImageBuildErr: 'Image yapı başarısız' +ErrImageExist: "Görüntü zaten var! Lütfen görüntü adını değiştirin." +ErrDelWithWebsite: 'İşletim ortamı zaten bir web sitesiyle ilişkili ve silinemez' +ErrRuntimeStart: 'Başlatma başarısız' +ErrPackageJsonNotFound: 'package.json dosyası mevcut değil' +ErrScriptsNotFound: 'package.json içinde scripts yapılandırma öğesi bulunamadı' +ErrContainerNameNotFound: 'Konteyner adı alınamıyor, lütfen .env dosyasını kontrol edin' +ErrNodeModulesNotFound: 'node_modules klasörü mevcut değil! Lütfen çalışma ortamını düzenleyin veya çalışma ortamının başarıyla başlatılmasını bekleyin' +ErrContainerNameIsNull: 'Konteyner adı mevcut değil' +ErrPHPPortIsDefault: "9000 portu varsayılan port, lütfen değiştirin ve tekrar deneyin" +ErrPHPRuntimePortFailed: "{{ .name }} portu mevcut çalışma ortamı tarafından zaten kullanılıyor, lütfen değiştirin ve tekrar deneyin" + +#tool +ErrConfigNotFound: 'Yapılandırma dosyası mevcut değil' +ErrConfigParse: 'Yapılandırma dosyası formatı yanlış' +ErrConfigIsNull: 'Yapılandırma dosyası boş olamaz' +ErrConfigDirNotFound: 'Çalışma dizini mevcut değil' +ErrConfigAlreadyExist: 'Aynı ada sahip bir yapılandırma dosyası zaten mevcut' +ErrUserFindErr: 'Kullanıcı {{ .name }} arama başarısız {{ .err }}' + +#cronjob +CutWebsiteLogSuccess: '{{ .name }} web sitesi günlüğü başarıyla kesildi, yedekleme yolu {{ .path }}' +HandleShell: 'Betik {{ .name }} yürüt' +HandleCurl: "URL'ye Eriş {{ .name }}" +HandleNtpSync: 'Sistem zaman senkronizasyonu' +HandleSystemClean: 'Sistem önbellek temizliği' +SystemLog: 'Sistem Günlüğü' +CutWebsiteLog: 'Web Sitesi Günlüğünü Döndür' +FileOrDir: 'Dizin / Dosya' +UploadFile: 'Yedekleme dosyası {{ .file }} {{ .backup }} konumuna yükleniyor' +Upload: "Yükle" +IgnoreBackupErr: "Yedekleme başarısız, hata: {{ .detail }}, bu hata yoksayılıyor..." +IgnoreUploadErr: "Yükleme başarısız, hata: {{ .detail }}, bu hata yoksayılıyor..." +LoadBackupFailed: "Yedek hesap bağlantısı alınamadı, hata: {{ .detail }}" +InExecuting: "Mevcut görev yürütülüyor, lütfen tekrar yürütmeyin!" +NoSuchResource: "Veritabanında yedek içeriği bulunamadı, atlanıyor..." +CleanLog: "Günlüğü temizle" +CleanLogByName: "{{.name}} günlüğünü temizle" +WafIpGroupNotFound: "WAF IP grubu bulunamadı. Lütfen [Gelişmiş Özellikler - WAF - Kara/Beyaz Liste - IP Grubu] bölümüne giderek uzaktan indirme yöntemiyle bir IP grubu ekleyin." + +#toolbox +ErrNotExistUser: 'Mevcut kullanıcı mevcut değil, lütfen değiştirin ve tekrar deneyin!' +ErrBanAction: 'Ayarlama başarısız. Mevcut {{ .name }} servisi kullanılamıyor. Lütfen kontrol edin ve tekrar deneyin!' +ErrClamdscanNotFound: 'clamdscan komutu tespit edilmedi, lütfen yüklemek için belgeleri inceleyin!' +TaskSwapSet: "Swap Ayarla" +SetSwap: "Swap Ayarla {{ .path }} - {{ .size }}" +CreateSwap: "Swap Dosyası Oluştur" +FormatSwap: "Swap Dosyasını Biçimlendir" +EnableSwap: "Swap Etkinleştir" + +#tamper +CleanTamperSetting: "Geçmiş koruma ayarlarını temizle" +SetTamperWithRules: "Kurallara göre dizin korumasını başlat" +ProtectDir: "Dizini koru {{ .name }}" +IgnoreFile: "Dosyayı koru {{ .name }}" +TamperSetSuccessful: "Koruma ayarları tamamlandı, aşağıdaki dizinler izlenmeye başlanıyor:" + +#waf +ErrScope: 'Bu yapılandırmayı değiştirme desteklenmiyor' +ErrStateChange: 'Durum değiştirme başarısız' +ErrRuleExist: 'Kural zaten mevcut' +ErrRuleNotExist: 'Kural mevcut değil' +ErrParseIP: 'Yanlış IP formatı' +ErrDefaultIP: 'default ayrılmış bir isim, lütfen başka bir isme değiştirin' +ErrGroupInUse: 'IP grubu kara liste/beyaz liste tarafından kullanılıyor ve silinemez' +ErrIPGroupAclUse: "IP grubu {{ .name }} web sitesinin özel kuralları tarafından kullanılıyor, silinemez" +ErrGroupExist: 'IP grup adı zaten mevcut' +ErrIPRange: 'Yanlış IP aralığı' +ErrIPExist: 'IP zaten mevcut' +urlDefense: 'URL kuralları' +urlHelper: 'Yasak URL' +dirFilter: 'Dizin filtresi' +xss: 'XSS' +phpExec: 'PHP betik yürütme' +oneWordTrojan: 'Tek Kelime Truva Atı' +appFilter: 'Tehlikeli dizin filtreleme uygula' +webshell: 'Webshell' +args: 'Parametre kuralları' +protocolFilter: 'Protokol filtreleme' +javaFileter: 'Java tehlikeli dosya filtresi' +scannerFilter: 'Tarayıcı filtresi' +escapeFilter: 'kaçış filtresi' +customRule: 'Özel kural' +httpMethod: 'HTTP yöntem filtreleme' +fileExt: 'Dosya yükleme kısıtlamaları' +defaultIpBlack: 'Kötü niyetli IP grubu' +cookie: 'Cookie Kuralları' +urlBlack: 'URL kara listesi' +uaBlack: 'User-Agent kara listesi' +attackCount: 'Saldırı sıklığı sınırı' +fileExtCheck: 'Dosya yükleme kısıtlamaları' +geoRestrict: 'Bölgesel erişim kısıtlamaları' +unknownWebsite: 'Yetkisiz alan adı erişimi' +notFoundCount: '404 Hız Sınırı' +headerDefense: 'Başlık kuralları' +defaultUaBlack: 'User-Agent kuralları' +methodWhite: 'HTTP kuralları' +captcha: 'insan-makine doğrulaması' +fiveSeconds: '5 saniye doğrulaması' +vulnCheck: 'Tamamlayıcı kurallar' +acl: 'Özel kurallar' +sql: 'SQL enjeksiyonu' +cc: 'Erişim sıklığı sınırı' +defaultUrlBlack: 'URL kuralları' +sqlInject: 'SQL enjeksiyonu' +ErrDBNotExist: 'Veritabanı mevcut değil' +allow: 'izin ver' +deny: 'reddet' +OpenrestyNotFound: 'Openresty yüklü değil' +remoteIpIsNull: "IP listesi boş" +OpenrestyVersionErr: "Openresty sürümü çok düşük, lütfen Openresty'i 1.27.1.2-2-2-focal olarak güncelleyin" + +#task +TaskStart: '{{ .name }} görevi başlıyor [BAŞLAT]' +TaskEnd: '{{ .name }} Görev tamamlandı [TAMAMLANDI]' +TaskFailed: '{{ .name }} görev başarısız' +TaskTimeout: '{{ .name }} zaman aşımına uğradı' +TaskSuccess: '{{ .name }} Görev başarılı' +TaskRetry: '{{ .name }}. yeniden denemeyi başlat' +SubTaskSuccess: '{{ .name }} başarılı' +SubTaskFailed: '{{ .name }} başarısız: {{ .err }}' +TaskInstall: 'Yükle' +TaskUninstall: 'Kaldır' +TaskCreate: 'Oluştur' +TaskDelete: 'Sil' +TaskUpgrade: 'Yükselt' +TaskUpdate: 'Güncelle' +TaskRestart: 'Yeniden Başlat' +TaskProtect: "Koru" +TaskBackup: 'Yedekle' +TaskRecover: 'Kurtar' +TaskRollback: 'Geri Al' +TaskPull: 'Çek' +TaskCommit: 'işleme' +TaskBuild: 'Yapı' +TaskPush: 'Gönder' +TaskClean: "Temizleme" +TaskHandle: 'Yürüt' +TaskImport: "İçe Aktar" +TaskExport: "Dışa Aktar" +Website: 'Web Sitesi' +App: 'Uygulama' +Runtime: 'Çalışma ortamı' +Database: 'Veritabanı' +ConfigFTP: 'FTP kullanıcısı {{ .name }} oluştur' +ConfigOpenresty: 'Openresty yapılandırma dosyası oluştur' +InstallAppSuccess: 'Uygulama {{ .name }} başarıyla yüklendi' +ConfigRuntime: 'Çalışma ortamını yapılandır' +ConfigApp: 'Uygulama Yapılandırması' +SuccessStatus: '{{ .name }} başarılı' +FailedStatus: '{{ .name }} başarısız {{ .err }}' +HandleLink: 'Uygulama ilişkisini işle' +HandleDatabaseApp: 'Uygulama parametrelerini işle' +ExecShell: '{{ .name }} betiğini yürüt' +PullImage: 'Image çek' +Start: 'Başlat' +Run: 'Başlat' +Stop: 'Durdur' +Image: 'Ayna' +Compose: 'Düzenleme' +Container: 'Konteyner' +AppLink: 'Bağlantılı Uygulama' +EnableSSL: 'HTTPS Etkinleştir' +AppStore: 'Uygulama Mağazası' +TaskSync: 'Senkronize Et' +LocalApp: 'Yerel Uygulama' +SubTask: 'Alt görev' +RuntimeExtension: 'Çalışma Ortamı Uzantısı' +TaskIsExecuting: 'Görev çalışıyor' +CustomAppstore: 'Özel uygulama deposu' +TaskExec: 'Çalıştır' +TaskBatch: "Toplu İşlem" +FileConvert: 'Dosya Formatı Dönüştürme' + +# task - clam +Clamscan: "{{ .name }} Tara" +TaskScan: "Tara" + +# task - ai +OllamaModelPull: 'Ollama modeli {{ .name }} çek' +OllamaModelSize: 'Ollama modeli {{ .name }} boyutunu al' + +# task-snapshot +Snapshot: 'Anlık Görüntü' +SnapDBInfo: '1Panel veritabanı bilgilerini yaz' +SnapCopy: 'Dosya ve dizinleri {{ .name }} kopyala' +SnapNewDB: 'Veritabanı {{ .name }} bağlantısını başlat' +SnapDeleteOperationLog: 'İşlem günlüğünü sil' +SnapDeleteLoginLog: 'Erişim günlüğünü sil' +SnapDeleteMonitor: 'İzleme verilerini sil' +SnapRemoveSystemIP: 'Sistem IP sini kaldır' +SnapBaseInfo: '1Panel temel bilgilerini yaz' +SnapInstallAppImageEmpty: 'Uygulama imageı seçilmedi, atlanıyor...' +SnapInstallApp: '1Panel yüklü uygulamaları yedekle' +SnapDockerSave: 'Yüklü uygulamaları sıkıştır' +SnapLocalBackup: '1Panel yerel yedekleme dizinini yedekle' +SnapCompressBackup: 'Yerel yedekleme dizinini sıkıştır' +SnapPanelData: '1Panel veri dizinini yedekle' +SnapCompressPanel: 'Sıkıştırılmış Veri Dizini' +SnapWebsite: '1Panel web sitesi dizinini yedekle' +SnapCloseDBConn: 'Veritabanı bağlantısını kapat' +SnapCompress: 'Anlık görüntü dosyaları oluştur' +SnapCompressFile: 'Anlık görüntü dosyasını sıkıştır' +SnapCheckCompress: 'Anlık görüntü sıkıştırma dosyasını kontrol et' +SnapCompressSize: 'Anlık görüntü dosya boyutu {{ .name }}' +SnapUpload: 'Anlık görüntü dosyasını yükle' +SnapUploadTo: 'Anlık görüntü dosyasını {{ .name }} konumuna yükle' +SnapUploadRes: 'Anlık görüntü dosyasını {{ .name }} konumuna yükle' + +SnapshotRecover: 'Anlık Görüntü Geri Yükleme' +RecoverDownload: 'Anlık görüntü dosyasını indir' +Download: 'İndir' +RecoverDownloadAccount: 'Anlık görüntü indirme yedek hesabı al {{ .name }}' +RecoverDecompress: 'Anlık görüntü sıkıştırılmış dosyalarını açma' +Decompress: 'Açma' +BackupBeforeRecover: 'Anlık görüntü öncesi sistem ilgili verileri yedekleme' +Readjson: 'Anlık görüntüdeki Json dosyasını oku' +ReadjsonPath: 'Anlık görüntüdeki Json dosya yolunu al' +ReadjsonContent: 'Json dosyasını oku' +ReadjsonMarshal: 'Json kaçış işlemesi' +RecoverApp: 'Yüklü uygulamaları geri yükle' +RecoverWebsite: 'Web sitesi dizinini kurtar' +RecoverAppImage: 'Anlık görüntü imaj yedeklemesini geri yükle' +RecoverCompose: 'Diğer besteci içeriğini geri yükle' +RecoverComposeList: 'Geri yüklenecek tüm bestecileri al' +RecoverComposeItem: 'Besteci geri yükle {{ .name }}' +RecoverAppEmpty: 'Anlık görüntü dosyasında uygulama imaj yedeklemesi bulunamadı' +RecoverBaseData: 'Temel veri ve dosyaları kurtar' +RecoverDaemonJsonEmpty: 'Hem anlık görüntü dosyası hem de mevcut makine konteyner yapılandırma daemon.json dosyasına sahip değil' +RecoverDaemonJson: 'Konteyner yapılandırma daemon.json dosyasını geri yükle' +RecoverDBData: 'Veritabanı verilerini kurtar' +RecoverBackups: 'Yerel yedekleme dizinini geri yükle' +RecoverPanelData: 'Veri dizinini kurtar' + +# task - container +ContainerNewCliet: 'Docker İstemcisini Başlat' +ContainerImagePull: 'Konteyner imajını çek {{ .name }}' +ContainerRemoveOld: 'Orijinal konteyneri kaldır {{ .name }}' +ContainerImageCheck: 'İmajın normal çekilip çekilmediğini kontrol et' +ContainerLoadInfo: 'Temel konteyner bilgilerini al' +ContainerRecreate: 'Konteyner güncellemesi başarısız, şimdi orijinal konteyneri geri yüklemeye başlıyor' +ContainerCreate: 'Yeni konteyner oluştur {{ .name }}' +ContainerCreateFailed: 'Konteyner oluşturma başarısız, başarısız konteyneri sil' +ContainerStartCheck: 'Konteynerin başlatılıp başlatılmadığını kontrol et' + +# task - image +ImageBuild: 'İmaj Oluşturma' +ImageBuildStdoutCheck: 'İmaj çıktı içeriğini ayrıştır' +ImageBuildRes: 'İmaj oluşturma çıktısı: {{ .name }}' +ImagePull: 'İmaj çek' +ImageRepoAuthFromDB: 'Veritabanından depo kimlik doğrulama bilgilerini al' +ImaegPullRes: 'İmaj çekme çıktısı: {{ .name }}' +ImagePush: 'İmaj gönder' +ImageRenameTag: 'İmaj etiketini değiştir' +ImageNewTag: 'Yeni imaj etiketi {{ .name }}' +ImaegPushRes: 'İmaj gönderme çıktısı: {{ .name }}' +ComposeCreate: 'Kompozisyon oluştur' +ComposeCreateRes: 'Compose oluşturma çıktısı: {{ .name }}' + +# task - website +BackupNginxConfig: 'Web sitesi OpenResty yapılandırma dosyasını yedekle' +CompressFileSuccess: 'Dizin başarıyla sıkıştırıldı, şuraya sıkıştırıldı {{ .name }}' +CompressDir: 'Sıkıştırma dizini' +DeCompressFile: 'Dosyayı aç {{ .name }}' +ErrCheckValid: 'Yedekleme dosyası doğrulaması başarısız, {{ .name }}' +Rollback: 'Geri alma' +websiteDir: 'Web Sitesi Dizini' +RecoverFailedStartRollBack: 'Kurtarma başarısız, geri almayı başlat' +AppBackupFileIncomplete: 'Yedekleme dosyası eksik ve app.json veya app.tar.gz dosyalarından yoksun' +AppAttributesNotMatch: 'Uygulama türü veya adı eşleşmiyor' + +#alert +ErrAlert: 'Uyarı mesajının formatı yanlış, lütfen kontrol edin ve tekrar deneyin!' +ErrAlertPush: 'Uyarı bilgisi gönderiminde hata, lütfen kontrol edin ve tekrar deneyin!' +ErrAlertSave: 'Alarm bilgisini kaydetmede hata, lütfen kontrol edin ve tekrar deneyin!' +ErrAlertSync: 'Alarm bilgisi senkronizasyon hatası, lütfen kontrol edin ve tekrar deneyin!' +ErrAlertRemote: 'Alarm mesajı uzak hata, lütfen kontrol edin ve tekrar deneyin!' + +#task - runtime +ErrInstallExtension: "Zaten devam eden bir kurulum görevi var, görevin bitmesini bekleyin" + +# alert mail template +PanelAlertTitle: "Panel Uyarı Bildirimi" +TestAlertTitle: "Test E-postası - E-posta Bağlantısını Doğrula" +TestAlert: "Bu, e-posta gönderim ayarlarınızın doğru yapılandırıldığını doğrulamak için gönderilen bir test e-postasıdır." +LicenseExpirationAlert: "Paneliniz {{ .node }}{{ .ip }}, lisans {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın." +CronJobFailedAlert: "Paneliniz {{ .node }}{{ .ip }}, zamanlanmış görev {{ .name }} çalıştırma başarısız oldu. Detaylar için paneline giriş yapın." +ClamAlert: "Paneliniz {{ .node }}{{ .ip }}, virüs tarama görevi {{ .num }} enfekte dosya tespit etti. Detaylar için paneline giriş yapın." +WebSiteAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} web sitesi {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın." +SSLAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} SSL sertifikası {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın." +DiskUsedAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .name }} diski kullanımı {{ .used }}'a ulaştı. Detaylar için paneline giriş yapın." +ResourceAlert: "Paneliniz {{ .node }}{{ .ip }}, son {{ .time }} dakika içindeki ortalama {{ .name }} kullanım oranı {{ .used }}'dır. Detaylar için paneline giriş yapın." +PanelVersionAlert: "Paneliniz {{ .node }}{{ .ip }}, en son panel sürümü yükseltme için mevcut. Detaylar için paneline giriş yapın." +PanelPwdExpirationAlert: "Paneliniz {{ .node }}{{ .ip }}, panel şifresi {{ .day }} gün içinde süresi dolacak. Detaylar için paneline giriş yapın." +CommonAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .msg }}. Detaylar için paneline giriş yapın." +NodeExceptionAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} düğüm anormal durumda. Detaylar için paneline giriş yapın." +LicenseExceptionAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .num }} lisans anormal durumda. Detaylar için paneline giriş yapın." +SSHAndPanelLoginAlert: "Paneliniz {{ .node }}{{ .ip }}, {{ .loginIp }} adresinden {{ .name }} girişi anormal. Detaylar için paneline giriş yapın." + +#disk +DeviceNotFound: "Cihaz {{ .name }} bulunamadı" +DeviceIsMounted: "Cihaz {{ .name }} bağlandı, önce çıkarın" +PartitionDiskErr: "Bölümleme başarısız, {{ .err }}" +FormatDiskErr: "Disk biçimlendirme başarısız, {{ .err }}" +MountDiskErr: "Disk bağlama başarısız, {{ .err }}" +UnMountDiskErr: "Disk bağının kaldırılması başarısız, {{ .err }}" +XfsNotFound: "XFS dosya sistemi algılanmadı, lütfen önce xfsprogs'i yükleyin" diff --git a/agent/i18n/lang/zh-Hant.yaml b/agent/i18n/lang/zh-Hant.yaml new file mode 100644 index 0000000..48372a4 --- /dev/null +++ b/agent/i18n/lang/zh-Hant.yaml @@ -0,0 +1,529 @@ +ErrInvalidParams: '請求參數錯誤: {{ .detail }}' +ErrTokenParse: 'Token 產生錯誤: {{ .detail }}' +ErrInitialPassword: '原密碼錯誤' +ErrInternalServer: '服務內部錯誤: {{ .detail }}' +ErrRecordExist: '記錄已存在' +ErrRecordNotFound: '記錄未能找到' +ErrStructTransform: '型別轉換失敗: {{ .err }}' +ErrNotLogin: '使用者未登入: {{ .detail }}' +ErrPasswordExpired: '目前密碼已過期: {{ .detail }}' +ErrNotSupportType: '系統暫不支援目前類型: {{ .name }}' +ErrProxy: '請求錯誤,請檢查該節點狀態: {{ .detail }}' +ErrApiConfigStatusInvalid: 'API 介面禁止存取: {{ .detail }}' +ErrApiConfigKeyInvalid: 'API 介面金鑰錯誤: {{ .detail }}' +ErrApiConfigIPInvalid: '呼叫 API 介面 IP 不在白名單: {{ .detail }}' +ErrApiConfigDisable: '此介面禁止使用 API 介面呼叫: {{ .detail }}' +ErrApiConfigKeyTimeInvalid: 'API 介面時間戳記錯誤: {{ .detail }}' +ErrShutDown: "指令被手動結束!" + +ErrMinQuickJump: "請至少設定一個快速跳轉入口!" +ErrMaxQuickJump: "最多可設定四個快速跳轉入口!" + +#common +ErrUsernameIsExist: '使用者名稱已存在' +ErrNameIsExist: '名稱已存在' +ErrDemoEnvironment: '示範伺服器,禁止此操作!' +ErrCmdTimeout: '指令執行逾時!' +ErrCmdIllegal: '執行指令中存在不合法字元,請修改後重試!' +ErrPortExist: '{{ .port }} 埠已被{{ .type }} [{{ .name }}] 佔用' +TYPE_APP: '應用' +TYPE_RUNTIME: '運作環境' +TYPE_DOMAIN: '網域名稱' +ErrTypePort: '埠{{ .name }} 格式錯誤' +ErrTypePortRange: '連接埠範圍需要在 1-65535 之間' +Success: '成功' +Failed: '失敗' +SystemRestart: '系統重新啟動導致任務中斷' +ErrGroupIsDefault: '預設分組,無法刪除' +ErrGroupIsInWebsiteUse: '分組正在被其他網站使用,無法刪除' +Decrypt: "解密" + +#backup +Localhost: '本機' +ErrBackupInUsed: '此備份帳號已在排程任務中使用,無法刪除' +ErrBackupCheck: '備份帳號測試連線失敗{{ .err }}' +ErrBackupLocalDelete: '暫時不支援刪除本機伺服器備份帳號' +ErrBackupLocalCreate: '暫時不支援建立本機伺服器備份帳號' + +#app +ErrPortInUsed: '{{ .detail }} 連接埠已被佔用!' +ErrAppLimit: '應用程式超出安裝數量限制' +ErrNotInstall: '應用程式未安裝' +ErrPortInOtherApp: '{{ .port }} 連接埠已被應用程式{{ .apps }} 佔用!' +ErrDbUserNotValid: '存量資料庫,使用者名稱密碼不符!' +ErrUpdateBuWebsite: '應用程式更新成功,但網站設定檔修改失敗,請檢查設定! ' +Err1PanelNetworkFailed: '預設容器網路建立失敗! {{ .detail }}' +ErrFileParse: '應用docker-compose 檔案解析失敗!' +ErrInstallDirNotFound: '安裝目錄不存在,如需移除,請選擇強制移除' +AppStoreIsUpToDate: '應用程式商店已經是最新版本' +LocalAppVersionNull: '{{ .name }} 應用程式未同步到版本!無法加入到應用程式列表' +LocalAppVersionErr: '{{ .name }} 同步版本{{ .version }} 失敗! {{ .err }}' +ErrFileNotFound: '{{ .name }} 檔案不存在' +ErrFileParseApp: '{{ .name }} 檔案解析失敗{{ .err }}' +ErrAppDirNull: '版本資料夾不存在' +LocalAppErr: '應用程式{{ .name }} 同步失敗!{{ .err }}' +ErrContainerName: '容器名稱已存在' +ErrCreateHttpClient: '建立請求失敗{{ .err }}' +ErrHttpReqTimeOut: '請求逾時{{ .err }}' +ErrHttpReqFailed: '請求失敗{{ .err }}' +ErrNoSuchHost: '無法找到請求的伺服器{{ .err }}' +ErrHttpReqNotFound: '無法找到要求的資源{{ .err }}' +ErrContainerNotFound: '{{ .name }} 容器不存在' +ErrContainerMsg: '{{ .name }} 容器異常,請在容器頁面上查看日誌' +ErrAppBackup: '{{ .name }} 應用備份失敗 {{ .err }}' +ErrVersionTooLow: '目前1Panel 版本過低,無法更新應用程式商店,請升級版本之後操作' +ErrAppNameExist: '應用程式名稱已存在' +AppStoreIsSyncing: '應用程式商店正在同步中,請稍後再試' +ErrGetCompose: 'docker-compose.yml 檔案取得失敗!{{ .detail }}' +ErrAppWarn: '狀態異常,請檢視日誌' +ErrAppParamKey: '參數{{ .name }} 欄位異常' +ErrAppUpgrade: '應用程式{{ .name }} 升級失敗{{ .err }}' +AppRecover: '應用程式{{ .name }} 回滾' +PullImageStart: '開始拉取鏡像 [{{ .name }}]' +PullImageSuccess: '鏡像拉取成功' +AppStoreSyncSuccess: '應用程式商店同步成功' +SyncAppDetail: '同步應用程式設定' +AppVersionNotMatch: '{{ .name }} 應用程式需要更高的 1Panel 版本,跳過同步' +MoveSiteDir: "檢測到網站目錄變更 需要遷移 OpenResty 網站目錄到 {{ .name }}" +MoveSiteDirSuccess: '遷移網站目錄成功' +DeleteRuntimePHP: '刪除PHP 執行環境' +CustomAppStoreFileValid: '應用程式商店包需要 .tar.gz 格式' +PullImageTimeout: '拉取鏡像逾時,請嘗試增加鏡像加速或更換其他鏡像加速' +ErrAppIsDown: '{{ .name }} 應用程式狀態異常,請檢查' +ErrCustomApps: '存在已安裝的應用程式,請先解除安裝' +ErrCustomRuntimes: '存在已安裝的運作環境,請先刪除' +ErrAppVersionDeprecated: "{{ .name }} 應用不適配目前 1Panel 版本,跳過" +ErrDockerFailed: "Docker 狀態異常,請檢查服務狀態" +ErrDockerComposeCmdNotFound: "Docker Compose 指令不存在,請先在宿主機安裝此指令" +UseExistImage: '拉取映像失敗,使用存量映像' + +#ssh +ExportIP: "登入IP" +ExportArea: "歸屬地" +ExportPort: "埠" +ExportAuthMode: "登入方式" +ExportUser: "使用者" +ExportStatus: "登入狀態" +ExportDate: "時間" + +#file +ErrFileCanNotRead: '此檔案不支援預覽' +ErrFileToLarge: '檔案超過10M,無法開啟' +ErrPathNotFound: '目錄不存在' +ErrMovePathFailed: '目標路徑不能包含原路徑!' +ErrLinkPathNotFound: '目標路徑不存在!' +ErrFileIsExist: '檔案或資料夾已存在!' +ErrFileUpload: '{{ .name }} 上傳檔案失敗{{ .detail }}' +ErrFileDownloadDir: '不支援下載資料夾' +ErrCmdNotFound: '{{ .name}} 指令不存在,請先在宿主機安裝此指令' +ErrSourcePathNotFound: '來源目錄不存在' +ErrFavoriteExist: '已收藏此路徑' +ErrInvalidChar: '禁止使用無效字元' +ErrPathNotDelete: '所選目錄不可刪除' +ErrLogFileToLarge: "日誌檔案超過 500M,無法打開" +FileDropFailed: "清理文件 {{ .name }} 失敗: {{ .err }}" +FileDropSuccess: "清理文件 {{ .name }} 成功,清理 {{ .count }} 個文件,釋放 {{ .size }} 磁碟空間" +FileDropSum: "文件清理完成,共清理 {{ .count }} 個文件,共釋放 {{ .size }} 磁碟空間" +ErrBadDecrypt: "壓縮檔解壓密碼錯誤,解壓失敗,請檢查後重試!" + +#website +ErrAliasIsExist: '代號已存在' +ErrBackupMatch: '該備份檔案與目前網站部分資料不符{{ .detail }}' +ErrBackupExist: '該備份檔案對應部分來源資料不存在{{ .detail }}' +ErrPHPResource: '本機執行環境不支援切換! ' +ErrPathPermission: 'index 目錄下偵測到非 1000:1000 權限資料夾,可能導致網站存取 Access denied 錯誤,請點擊上方儲存按鈕' +ErrDomainIsUsed: '網域名稱已被網站【{{ .name }}】使用' +ErrDomainFormat: '{{ .name }} 網域格式不正確' +ErrDefaultAlias: 'default 為保留代號,請使用其他代號' +ErrParentWebsite: '需要先移除子網站{{ .name }}' +ErrBuildDirNotFound: '建置目錄不存在' +ErrImageNotExist: '執行環境{{ .name }} 映像不存在,請重新編輯執行環境' +ErrProxyIsUsed: "負載均衡已被反向代理使用,無法刪除" +ErrSSLValid: '證書文件異常,請檢查證書狀態!' +ErrWebsiteDir: "請選擇網站目錄下的目錄" +ErrComposerFileNotFound: "composer.json 文件不存在" +ErrRuntimeNoPort: "執行環境未設定埠,請先編輯執行環境" +Status: '狀態' +start: '開啟' +stop: '關閉' +restart: '重啟' +kill: '強制停止' +pause: '暫停' +unpause: '恢復' +remove: '刪除' +delete: '刪除' +ErrDefaultWebsite: '已經設置默認網站,請取消後再設置!' +SSL: '憑證' +Set: '設定' + +#ssl +ErrSSLCannotDelete: '{{ .name }} 憑證正在被網站使用,無法刪除' +ErrAccountCannotDelete: '帳號關聯證書,無法刪除' +ErrSSLApply: '憑證續簽成功,openresty reload 失敗,請檢查設定!' +ErrEmailIsExist: '信箱已存在' +ErrSSLKeyNotFound: '私鑰檔案不存在' +ErrSSLCertificateNotFound: '憑證檔案不存在' +ErrSSLKeyFormat: '私鑰檔案校驗失敗' +ErrSSLCertificateFormat: '憑證檔案格式錯誤,請使用 pem 格式' +ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能為空' +ErrOpenrestyNotFound: 'Http 模式需要先安裝Openresty' +ApplySSLStart: '開始申請證書,網域名稱[{{ .domain }}] 申請方式[{{ .type }}] ' +dnsAccount: 'DNS 自動' +dnsManual: 'DNS 手排' +http: 'HTTP' +ApplySSLFailed: '申請[{{ .domain }}] 憑證失敗, {{ .detail }} ' +ApplySSLSuccess: '申請[{{ .domain }}] 憑證成功! ! ' +DNSAccountName: 'DNS 帳號[{{ .name }}] 廠商[{{ .type }}]' +PushDirLog: '憑證推送到目錄[{{ .path }}] {{ .status }}' +ErrDeleteCAWithSSL: '目前機構下存在已簽發證書,無法刪除' +ErrDeleteWithPanelSSL: '面板SSL 配置使用此證書,無法刪除' +ErrDefaultCA: '預設機構不能刪除' +ApplyWebSiteSSLLog: '開始更新{{ .name }} 網站憑證' +ErrUpdateWebsiteSSL: '{{ .name }} 網站更新憑證失敗: {{ .err }}' +ApplyWebSiteSSLSuccess: '更新網站憑證成功' +ErrExecShell: '執行腳本失敗{{ .err }}' +ExecShellStart: '開始執行腳本' +ExecShellSuccess: '腳本執行成功' +StartUpdateSystemSSL: '開始更新系統憑證' +UpdateSystemSSLSuccess: '更新系統憑證成功' +ErrWildcardDomain: 'HTTP 模式無法申請泛網域憑證' +ErrApplySSLCanNotDelete: "正在申請的證書 {{.name}} 無法刪除,請稍後再試" +StartPushSSLToNode: "開始推送證書到節點" +PushSSLToNodeFailed: "推送證書到節點失敗: {{ .err }}" +PushSSLToNodeSuccess: "推送證書到節點成功" + +#mysql +ErrUserIsExist: '目前使用者已存在,請重新輸入' +ErrDatabaseIsExist: '目前資料庫已存在,請重新輸入' +ErrExecTimeOut: 'SQL 執行逾時,請檢查資料庫' +ErrRemoteExist: '遠端資料庫已存在該名稱,請修改後重試' +ErrLocalExist: '本機資料庫已存在該名稱,請修改後重試' +RemoteBackup: "備份遠端資料庫需要先使用映像 {{ .name }} 啟動本機容器資料庫服務,請稍候..." +RemoteRecover: "恢復遠端資料庫需要先使用映像 {{ .name }} 啟動本機容器資料庫服務,請稍候..." +Arg: "參數" + +#redis +ErrTypeOfRedis: '復原檔案類型與目前持久化方式不符,請修改後重試' + +#container +ErrInUsed: "{{ .detail }} 正被使用,無法刪除!" +ErrObjectInUsed: "該對象正被使用,無法刪除!" +ErrObjectBeDependent: "該映像正被其他映像所依賴,無法刪除!" +ErrPortRules: '連接埠數目不匹配,請重新輸入!' +ErrPgImagePull: '鏡像拉取逾時,請配置鏡像加速或手動拉取{{ .name }} 鏡像後重試' +PruneHelper: "本次清理 {{ .name }} 釋放磁碟空間 {{ .size }}" +ImageRemoveHelper: "刪除鏡像 {{ .name }} ,釋放磁碟空間 {{ .size }}" +BuildCache: "構建快取" +Volume: "磁碟區" +Network: "網路" +PruneStart: "清理中,請稍候..." + +#runtime +ErrFileNotExist: '{{ .detail }} 檔案不存在!請檢查來源檔案完整性!' +ErrImageBuildErr: '鏡像build 失敗' +ErrImageExist: "鏡像已存在!請修改鏡像名稱。" +ErrDelWithWebsite: '執行環境已經關聯網站,無法刪除' +ErrRuntimeStart: '啟動失敗' +ErrPackageJsonNotFound: 'package.json 檔案不存在' +ErrScriptsNotFound: '沒有在 package.json 中找到 scripts 設定項' +ErrContainerNameNotFound: '無法取得容器名稱,請檢查 .env 檔案' +ErrNodeModulesNotFound: 'node_modules 資料夾不存在!請編輯執行環境或等待執行環境啟動成功' +ErrContainerNameIsNull: '容器名稱不存在' +ErrPHPPortIsDefault: "9000 埠為預設埠,請修改後重試" +ErrPHPRuntimePortFailed: "{{ .name }} 埠已被目前執行環境使用,請修改後重試" + +#tool +ErrConfigNotFound: '設定檔不存在' +ErrConfigParse: '設定檔格式有誤' +ErrConfigIsNull: '設定檔不允許為空' +ErrConfigDirNotFound: '執行目錄不存在' +ErrConfigAlreadyExist: '已存在同名設定檔' +ErrUserFindErr: '使用者{{ .name }} 尋找失敗{{ .err }}' + +#cronjob +CutWebsiteLogSuccess: '{{ .name }} 網站日誌切割成功,備份路徑{{ .path }}' +HandleShell: '執行腳本{{ .name }}' +HandleCurl: "存取 URL {{ .name }}" +HandleNtpSync: '系統時間同步' +HandleSystemClean: '系統快取清理' +SystemLog: '系統日誌' +CutWebsiteLog: '切割網站日誌' +FileOrDir: '目錄 / 檔案' +UploadFile: '上傳備份文件 {{ .file }} 到 {{ .backup }}' +Upload: "上傳" +IgnoreBackupErr: "備份失敗,錯誤:{{ .detail }},忽略本次錯誤..." +IgnoreUploadErr: "上傳失敗,錯誤:{{ .detail }},忽略本次錯誤..." +LoadBackupFailed: "取得備份帳號連線失敗,錯誤:{{ .detail }}" +InExecuting: "目前任務正在執行中,請勿重複執行!" +NoSuchResource: "資料庫中未能查詢到備份內容,跳過..." +CleanLog: "清理日誌" +CleanLogByName: "清理 {{.name}} 日誌" +WafIpGroupNotFound: "WAF IP 群組未找到,請先到【進階功能-WAF-黑白名單-IP群組】添加遠端下載方式的IP群組" + +#toolbox +ErrNotExistUser: '目前使用者不存在,請修改後重試!' +ErrBanAction: '設定失敗,目前{{ .name }} 服務不可用,請檢查後再試一次!' +ErrClamdscanNotFound: '未偵測到clamdscan 指令,請參考文件安裝!' +TaskSwapSet: "設定 Swap" +SetSwap: "設定 Swap {{ .path }} - {{ .size }}" +CreateSwap: "建立 Swap 檔案" +FormatSwap: "格式化 Swap 檔案" +EnableSwap: "啟用 Swap" + +#tamper +CleanTamperSetting: "清理歷史防護設定" +SetTamperWithRules: "根據規則啟動目錄防護" +ProtectDir: "防護目錄 {{ .name }}" +IgnoreFile: "保護文件 {{ .name }}" +TamperSetSuccessful: "防護設定完成,開始監聽以下目錄:" + +#waf +ErrScope: '不支援修改此配置' +ErrStateChange: '狀態修改失敗' +ErrRuleExist: '規則已存在' +ErrRuleNotExist: '規則不存在' +ErrParseIP: 'IP 格式錯誤' +ErrDefaultIP: 'default 為保留名稱,請更換其他名稱' +ErrGroupInUse: 'IP 群組被黑/白名單使用,無法刪除' +ErrIPGroupAclUse: "IP 群組被網站 {{ .name }} 自訂規則使用,無法刪除" +ErrGroupExist: 'IP 群組名稱已存在' +ErrIPRange: 'IP 範圍錯誤' +ErrIPExist: 'IP 已存在' +urlDefense: 'URL 規則' +urlHelper: '禁止訪問的URL' +dirFilter: '目錄過濾' +xss: 'XSS' +phpExec: 'PHP 腳本執行' +oneWordTrojan: '一句話木馬' +appFilter: '套用危險目錄過濾' +webshell: 'Webshell' +args: '參數規則' +protocolFilter: '協定過濾' +javaFileter: 'Java 危險檔案過濾' +scannerFilter: '掃描器過濾' +escapeFilter: '轉義過濾' +customRule: '自訂規則' +httpMethod: 'HTTP 方法過濾' +fileExt: '檔案上傳限制' +defaultIpBlack: '惡意IP 群組' +cookie: 'Cookie 規則' +urlBlack: 'URL 黑名單' +uaBlack: 'User-Agent 黑名單' +attackCount: '攻擊頻率限制' +fileExtCheck: '檔案上傳限制' +geoRestrict: '地區訪問限制' +unknownWebsite: '未授權網域存取' +notFoundCount: '404 頻率限制' +headerDefense: 'Header 規則' +defaultUaBlack: 'User-Agent 規則' +methodWhite: 'HTTP 規則' +captcha: '人機驗證' +fiveSeconds: '5 秒驗證' +vulnCheck: '補充規則' +acl: '自訂規則' +sql: 'SQL 注入' +cc: '訪問頻率限制' +defaultUrlBlack: 'URL 規則' +sqlInject: 'SQL 注入' +ErrDBNotExist: '資料庫不存在' +allow: '允許' +deny: '禁止' +OpenrestyNotFound: 'Openresty 未安裝' +remoteIpIsNull: "IP 列表為空" +OpenrestyVersionErr: "Openresty 版本過低,請升級 Openresty 至 1.27.1.2-2-2-focal" + +#task +TaskStart: '{{ .name }} 任務開始[START]' +TaskEnd: '{{ .name }} 任務結束[COMPLETED]' +TaskFailed: '{{ .name }} 任務失敗' +TaskTimeout: '{{ .name }} 逾時' +TaskSuccess: '{{ .name }} 任務成功' +TaskRetry: '開始第{{ .name }} 次重試' +SubTaskSuccess: '{{ .name }} 成功' +SubTaskFailed: '{{ .name }} 失敗: {{ .err }}' +TaskInstall: '安裝' +TaskUninstall: '移除' +TaskCreate: '建立' +TaskDelete: '刪除' +TaskUpgrade: '升級' +TaskUpdate: '更新' +TaskRestart: '重啟' +TaskProtect: "防護" +TaskBackup: '備份' +TaskRecover: '復原' +TaskRollback: '回滾' +TaskPull: '拉取' +TaskCommit: '製作' +TaskBuild: '建置' +TaskPush: '推送' +TaskClean: "清理" +TaskHandle: '執行' +TaskImport: "導入" +TaskExport: "導出" +Website: '網站' +App: '應用程式' +Runtime: '運作環境' +Database: '資料庫' +ConfigFTP: '建立FTP 使用者{{ .name }}' +ConfigOpenresty: '建立Openresty 設定檔' +InstallAppSuccess: '應用程式{{ .name }} 安裝成功' +ConfigRuntime: '設定執行環境' +ConfigApp: '設定應用程式' +SuccessStatus: '{{ .name }} 成功' +FailedStatus: '{{ .name }} 失敗{{ .err }}' +HandleLink: '處理應用程式關聯' +HandleDatabaseApp: '處理應用程式參數' +ExecShell: '執行{{ .name }} 腳本' +PullImage: '拉取鏡像' +Start: '開始' +Run: '啟動' +Stop: '停止' +Image: '鏡像' +Compose: '編排' +Container: '容器' +AppLink: '關聯應用程式' +EnableSSL: '開啟HTTPS' +AppStore: '應用程式商店' +TaskSync: '同步' +LocalApp: '本機應用' +SubTask: '子任務' +RuntimeExtension: '執行環境擴充' +TaskIsExecuting: '任務正在運作' +CustomAppstore: '自訂應用程式倉庫' +TaskExec: '執行' +TaskBatch: "批次操作" +FileConvert: '檔案格式轉換' + +# task - clam +Clamscan: "掃描 {{ .name }}" +TaskScan: "掃描" + +# task - ai +OllamaModelPull: '拉取 Ollama 模型{{ .name }} ' +OllamaModelSize: '取得 Ollama 模型{{ .name }} 大小' + +# task - snapshot +Snapshot: '快照' +SnapDBInfo: '寫入1Panel 資料庫資訊' +SnapCopy: '複製檔案&目錄{{ .name }} ' +SnapNewDB: '初始化資料庫{{ .name }} 連線' +SnapDeleteOperationLog: '刪除操作日誌' +SnapDeleteLoginLog: '刪除存取日誌' +SnapDeleteMonitor: '刪除監控資料' +SnapRemoveSystemIP: '移除系統IP' +SnapBaseInfo: '寫入1Panel 基本資料' +SnapInstallAppImageEmpty: '目前未勾選應用鏡像,跳過...' +SnapInstallApp: '備份 1Panel 已安裝應用' +SnapDockerSave: '壓縮已安裝應用' +SnapLocalBackup: '備份1Panel 本機備份目錄' +SnapCompressBackup: '壓縮本機備份目錄' +SnapPanelData: '備份1Panel 資料目錄' +SnapCompressPanel: '壓縮資料目錄' +SnapWebsite: '備份1Panel 網站目錄' +SnapCloseDBConn: '關閉資料庫連線' +SnapCompress: '製作快照檔案' +SnapCompressFile: '壓縮快照檔案' +SnapCheckCompress: '檢查快照壓縮檔' +SnapCompressSize: '快照檔案大小{{ .name }}' +SnapUpload: '上傳快照檔案' +SnapUploadTo: '上傳快照檔案到{{ .name }}' +SnapUploadRes: '上傳快照檔案到{{ .name }}' + +SnapshotRecover: '快照復原' +RecoverDownload: '下載快照檔案' +Download: '下載' +RecoverDownloadAccount: '取得快照下載備份帳號{{ .name }}' +RecoverDecompress: '解壓縮快照壓縮檔' +Decompress: '解壓縮' +BackupBeforeRecover: '快照前備份系統相關資料' +Readjson: '讀取快照內Json 檔案' +ReadjsonPath: '取得快照內Json 檔案路徑' +ReadjsonContent: '讀取Json 檔案' +ReadjsonMarshal: 'Json 轉義處理' +RecoverApp: '復原已安裝應用程式' +RecoverWebsite: '復原網站目錄' +RecoverAppImage: '復原快照鏡像備份' +RecoverCompose: '復原其他編排內容' +RecoverComposeList: '取得所有待復原編排' +RecoverComposeItem: '復原編排{{ .name }}' +RecoverAppEmpty: '快照檔案中未發現應用程式鏡像備份' +RecoverBaseData: '復原基礎資料及檔案' +RecoverDaemonJsonEmpty: '快照檔案及目前機器都不存在容器配置daemon.json 檔案' +RecoverDaemonJson: '復原容器配置daemon.json 檔案' +RecoverDBData: '復原資料庫資料' +RecoverBackups: '還原本機備份目錄' +RecoverPanelData: '復原資料目錄' + +# task - container +ContainerNewCliet: '初始化Docker Client' +ContainerImagePull: '拉取容器鏡像{{ .name }}' +ContainerRemoveOld: '刪除原容器{{ .name }}' +ContainerImageCheck: '檢查鏡像是否正常拉取' +ContainerLoadInfo: '取得容器基本資訊' +ContainerRecreate: '容器更新失敗,現在開始復原原容器' +ContainerCreate: '建立新容器{{ .name }}' +ContainerCreateFailed: '容器建立失敗,刪除失敗容器' +ContainerStartCheck: '檢查容器是否已啟動' + +# task - image +ImageBuild: '鏡像建置' +ImageBuildStdoutCheck: '解析鏡像輸出內容' +ImageBuildRes: '鏡像建置輸出:{{ .name }}' +ImagePull: '拉取鏡像' +ImageRepoAuthFromDB: '從資料庫取得倉庫認證資訊' +ImaegPullRes: '鏡像拉取輸出:{{ .name }}' +ImagePush: '推播鏡像' +ImageRenameTag: '修改鏡像Tag' +ImageNewTag: '新鏡像Tag {{ .name }}' +ImaegPushRes: '鏡像推播輸出:{{ .name }}' +ComposeCreate: '建立編排' +ComposeCreateRes: '編排建立輸出:{{ .name }}' + +# task - website +BackupNginxConfig: '備份網站OpenResty 設定檔' +CompressFileSuccess: '壓縮目錄成功,壓縮為{{ .name }}' +CompressDir: '壓縮目錄' +DeCompressFile: '解壓縮檔案{{ .name }}' +ErrCheckValid: '校驗備份檔失敗,{{ .name }}' +Rollback: '回滾' +websiteDir: '網站目錄' +RecoverFailedStartRollBack: '復原失敗,開始回溯' +AppBackupFileIncomplete: '備份檔案不完整缺少 app.json 或 app.tar.gz 檔案' +AppAttributesNotMatch: '應用程式類型或名稱不一致' + +#alert +ErrAlert: '警告訊息格式錯誤,請檢查後重試!' +ErrAlertPush: '警告訊息推送錯誤,請檢查後重試!' +ErrAlertSave: '警告訊息儲存錯誤,請檢查後重試!' +ErrAlertSync: '警告訊息同步錯誤,請檢查後重試!' +ErrAlertRemote: '警告訊息遠端錯誤,請檢查後重試!' + +#task - runtime +ErrInstallExtension: "已有安裝任務正在進行,請等待任務結束" + +# alert mail template +PanelAlertTitle: "面板警示通知" +TestAlertTitle: "測試郵件 - 驗證信箱連通性" +TestAlert: "這是一封測試郵件,用於確認您的郵件發送設定是否正確。" +LicenseExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,授權將於 {{ .day }} 天後到期,詳情請登入面板查看。" +CronJobFailedAlert: "您的 {{ .node }}{{ .ip }} 面板,排程任務{{ .name }}執行失敗,詳情請登入面板查看。" +ClamAlert: "您的 {{ .node }}{{ .ip }} 面板,病毒掃描任務偵測到 {{ .num }} 個感染檔案,詳情請登入面板查看。" +WebSiteAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個網站將於 {{ .day }} 天後到期,詳情請登入面板查看。" +SSLAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 張SSL憑證將於 {{ .day }} 天後到期,詳情請登入面板查看。" +DiskUsedAlert: "您的 {{ .node }}{{ .ip }} 面板,磁碟 {{ .name }} 使用率已達 {{ .used }},詳情請登入面板查看。" +ResourceAlert: "您的 {{ .node }}{{ .ip }} 面板,過去 {{ .time }} 分鐘內 {{ .name }} 平均使用率為 {{ .used }},詳情請登入面板查看。" +PanelVersionAlert: "您的 {{ .node }}{{ .ip }} 面板,已有最新版本可供升級,詳情請登入面板查看。" +PanelPwdExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,面板密碼將於 {{ .day }} 天後到期,詳情請登入面板查看。" +CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},詳情請登入面板查看。" +NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個節點出現異常,詳情請登入面板查看。" +LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 個授權出現異常,詳情請登入面板查看。" +SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,來自 {{ .loginIp }} 的 {{ .name }} 登入出現異常,詳情請登入面板查看。" + +#disk +DeviceNotFound: "裝置 {{ .name }} 未找到" +DeviceIsMounted: "裝置 {{ .name }} 已掛載,請先移除" +PartitionDiskErr: "分區失敗,{{ .err }}" +FormatDiskErr: "格式化磁碟失敗,{{ .err }}" +MountDiskErr: "掛載磁碟失敗,{{ .err }}" +UnMountDiskErr: "移除磁碟失敗,{{ .err }}" +XfsNotFound: "未檢測到 xfs 文件系統,請先安裝 xfsprogs" diff --git a/agent/i18n/lang/zh.yaml b/agent/i18n/lang/zh.yaml new file mode 100644 index 0000000..f3b0761 --- /dev/null +++ b/agent/i18n/lang/zh.yaml @@ -0,0 +1,531 @@ +ErrInvalidParams: "请求参数错误: {{ .detail }}" +ErrTokenParse: "Token 生成错误: {{ .detail }}" +ErrInitialPassword: "原密码错误" +ErrInternalServer: "服务内部错误: {{ .detail }}" +ErrRecordExist: "记录已存在" +ErrRecordNotFound: "记录未能找到" +ErrStructTransform: "类型转换失败: {{ .err }}" +ErrNotLogin: "用户未登录: {{ .detail }}" +ErrPasswordExpired: "当前密码已过期: {{ .detail }}" +ErrNotSupportType: "系统暂不支持当前类型: {{ .name }}" +ErrProxy: "请求错误,请检查该节点状态: {{ .detail }}" +ErrApiConfigStatusInvalid: "API 接口禁止访问: {{ .detail }}" +ErrApiConfigKeyInvalid: "API 接口密钥错误: {{ .detail }}" +ErrApiConfigIPInvalid: "调用 API 接口 IP 不在白名单: {{ .detail }}" +ErrApiConfigDisable: "此接口禁止使用 API 接口调用: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "API 接口时间戳错误: {{ .detail }}" +ErrShutDown: "命令被手动结束!" + +ErrMinQuickJump: "请至少设置一个快速跳转入口!" +ErrMaxQuickJump: "最多可设置四个快速跳转入口!" + +#common +ErrUsernameIsExist: "用户名已存在" +ErrNameIsExist: "名称已存在" +ErrDemoEnvironment: "演示服务器,禁止此操作!" +ErrCmdTimeout: "命令执行超时!" +ErrCmdIllegal: "执行命令中存在不合法字符,请修改后重试!" +ErrPortExist: '{{ .port }} 端口已被 {{ .type }} [{{ .name }}] 占用' +TYPE_APP: "应用" +TYPE_RUNTIME: "运行环境" +TYPE_DOMAIN: "域名" +ErrTypePort: '端口 {{ .name }} 格式错误' +ErrTypePortRange: '端口范围需要在 1-65535 之间' +Success: "成功" +Failed: "失败" +SystemRestart: "系统重启导致任务中断" +ErrGroupIsDefault: "默认分组,无法删除" +ErrGroupIsInWebsiteUse: "分组正在被其他网站使用,无法删除" +Decrypt: "解密" + +#backup +Localhost: '本机' +ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除" +ErrBackupCheck: "备份账号测试连接失败 {{ .err }}" +ErrBackupLocalDelete: "暂不支持删除本地服务器备份账号" +ErrBackupLocalCreate: "暂不支持创建本地服务器备份账号" + +#app +ErrPortInUsed: "{{ .detail }} 端口已被占用!" +ErrAppLimit: "应用超出安装数量限制" +ErrNotInstall: "应用未安装" +ErrPortInOtherApp: "{{ .port }} 端口已被应用 {{ .apps }} 占用!" +ErrDbUserNotValid: "存量数据库,用户名密码不匹配!" +ErrUpdateBuWebsite: '应用更新成功,但是网站配置文件修改失败,请检查配置!' +Err1PanelNetworkFailed: '默认容器网络创建失败!{{ .detail }}' +ErrFileParse: '应用 docker-compose 文件解析失败!' +ErrInstallDirNotFound: '安装目录不存在,如需卸载,请选择强制卸载' +AppStoreIsUpToDate: '应用商店已经是最新版本' +LocalAppVersionNull: '{{ .name }} 应用未同步到版本!无法添加到应用列表' +LocalAppVersionErr: '{{ .name }} 同步版本 {{ .version }} 失败!{{ .err }}' +ErrFileNotFound: '{{ .name }} 文件不存在' +ErrFileParseApp: '{{ .name }} 文件解析失败 {{ .err }}' +ErrAppDirNull: '版本文件夹不存在' +LocalAppErr: "应用 {{ .name }} 同步失败!{{ .err }}" +ErrContainerName: "容器名称已存在" +ErrCreateHttpClient: "创建请求失败 {{ .err }}" +ErrHttpReqTimeOut: "请求超时 {{ .err }}" +ErrHttpReqFailed: "请求失败 {{ .err }}" +ErrNoSuchHost: "无法找到请求的服务器 {{ .err }}" +ErrHttpReqNotFound: "无法找到请求的资源 {{ .err }}" +ErrContainerNotFound: '{{ .name }} 容器不存在' +ErrContainerMsg: '{{ .name }} 容器异常,具体请在容器页面查看日志' +ErrAppBackup: '{{ .name }} 应用备份失败 {{ .err }}' +ErrVersionTooLow: '当前 1Panel 版本过低,无法更新应用商店,请升级版本之后操作' +ErrAppNameExist: '应用名称已存在' +AppStoreIsSyncing: '应用商店正在同步中,请稍后再试' +ErrGetCompose: "docker-compose.yml 文件获取失败!{{ .detail }}" +ErrAppWarn: "状态异常,请查看日志" +ErrAppParamKey: "参数 {{ .name }} 字段异常" +ErrAppUpgrade: "应用 {{ .name }} 升级失败 {{ .err }}" +AppRecover: "应用 {{ .name }} 回滚 " +PullImageStart: "开始拉取镜像 [{{ .name }}]" +PullImageSuccess: "镜像拉取成功" +AppStoreSyncSuccess: "应用商店同步成功" +SyncAppDetail: "同步应用配置" +AppVersionNotMatch: "{{ .name }} 应用需要更高的 1Panel 版本,跳过" +MoveSiteDir: "检测到网站目录变更 需要迁移 OpenResty 网站目录到 {{ .name }}" +MoveSiteDirSuccess: "迁移网站目录成功" +DeleteRuntimePHP: "删除 PHP 运行环境" +CustomAppStoreFileValid: "应用商店包需要 .tar.gz 格式" +PullImageTimeout: "拉取镜像超时,请尝试增加镜像加速或者更换其他镜像加速" +ErrAppIsDown: "{{ .name }} 应用状态异常,请检查" +ErrCustomApps: "存在已经安装的应用,请先卸载" +ErrCustomRuntimes: "存在已经安装的运行环境,请先删除" +ErrAppVersionDeprecated: " {{ .name }} 应用不适配当前 1Panel 版本,跳过" +ErrDockerFailed: "Docker 状态异常,请检查服务状态" +ErrDockerComposeCmdNotFound: "Docker Compose 命令不存在,请先在宿主机安装此命令" +UseExistImage: "拉取镜像失败,使用存量镜像" +ErrDatabaseNotFound: "数据库 {{ .name }} 不存在" + +#ssh +ExportIP: "登录 IP" +ExportArea: "归属地" +ExportPort: "端口" +ExportAuthMode: "登录方式" +ExportUser: "用户" +ExportStatus: "登录状态" +ExportDate: "时间" + +#file +ErrFileCanNotRead: "此文件不支持预览" +ErrFileToLarge: "文件超过10M,无法打开" +ErrPathNotFound: "目录不存在" +ErrMovePathFailed: "目标路径不能包含原路径!" +ErrLinkPathNotFound: "目标路径不存在!" +ErrFileIsExist: "文件或文件夹已存在!" +ErrFileUpload: "{{ .name }} 上传文件失败 {{ .detail }}" +ErrFileDownloadDir: "不支持下载文件夹" +ErrCmdNotFound: "{{ .name}} 命令不存在,请先在宿主机安装此命令" +ErrSourcePathNotFound: "源目录不存在" +ErrFavoriteExist: "已收藏此路径" +ErrInvalidChar: "禁止使用非法字符" +ErrPathNotDelete: "所选目录不可删除" +ErrLogFileToLarge: "日志文件超过 500M,无法打开" +FileDropFailed: "清理文件 {{ .name }} 失败 {{ .err }}" +FileDropSuccess: "清理文件 {{ .name }} 成功,清理文件 {{ .count }},释放磁盘空间 {{ .size }}" +FileDropSum: "文件清理成功,清理文件共计 {{ .count }},释放磁盘空间共计 {{ .size }}" +ErrBadDecrypt: "压缩包解压密码错误,解压失败,请检查后重试!" + +#website +ErrAliasIsExist: "代号已存在" +ErrBackupMatch: '该备份文件与当前网站部分数据不匹配 {{ .detail }}' +ErrBackupExist: '该备份文件对应部分源数据不存在 {{ .detail }}' +ErrPHPResource: '本地运行环境不支持切换!' +ErrPathPermission: 'index 目录下检测到非 1000:1000 权限文件夹,可能导致网站访问 Access denied 错误,请点击上方保存按钮' +ErrDomainIsUsed: "域名已被网站【{{ .name }}】使用" +ErrDomainFormat: "{{ .name }} 域名格式不正确" +ErrDefaultAlias: "default 为保留代号,请使用其他代号" +ErrParentWebsite: "需要先删除子网站 {{ .name }}" +ErrBuildDirNotFound: "构建目录不存在" +ErrImageNotExist: "运行环境 {{ .name }} 镜像不存在,请重新编辑运行环境" +ErrProxyIsUsed: "负载均衡已被反向代理使用,无法删除" +ErrSSLValid: '证书文件异常,请检查证书状态!' +ErrWebsiteDir: '请选择网站目录下的目录' +ErrComposerFileNotFound: 'composer.json 文件不存在' +ErrRuntimeNoPort: '运行环境未设置端口,请先编辑运行环境' +Status: '状态' +start: '开启' +stop: '关闭' +restart: '重启' +kill: '强制停止' +pause: '暂停' +unpause: '恢复' +remove: '删除' +delete: '删除' +ErrDefaultWebsite: "已经设置默认网站,请取消后再设置!" +SSL: '证书' +Set: '设置' + +#ssl +ErrSSLCannotDelete: "{{ .name }} 证书正在被网站使用,无法删除" +ErrAccountCannotDelete: "账号关联证书,无法删除" +ErrSSLApply: "证书续签成功,openresty reload失败,请检查配置!" +ErrEmailIsExist: '邮箱已存在' +ErrSSLKeyNotFound: '私钥文件不存在' +ErrSSLCertificateNotFound: '证书文件不存在' +ErrSSLKeyFormat: '私钥文件校验失败' +ErrSSLCertificateFormat: '证书文件格式错误,请使用 pem 格式' +ErrEabKidOrEabHmacKeyCannotBlank: 'EabKid 或 EabHmacKey 不能为空' +ErrOpenrestyNotFound: 'Http 模式需要首先安装 Openresty' +ApplySSLStart: '开始申请证书,域名 [{{ .domain }}] 申请方式 [{{ .type }}] ' +dnsAccount: "DNS 自动" +dnsManual: "DNS 手动" +http: "HTTP" +ApplySSLFailed: '申请 [{{ .domain }}] 证书失败, {{ .detail }} ' +ApplySSLSuccess: '申请 [{{ .domain }}] 证书成功!!' +DNSAccountName: 'DNS 账号 [{{ .name }}] 厂商 [{{ .type }}]' +PushDirLog: '证书推送到目录 [{{ .path }}] {{ .status }}' +ErrDeleteCAWithSSL: "当前机构下存在已签发证书,无法删除" +ErrDeleteWithPanelSSL: "面板 SSL 配置使用此证书,无法删除" +ErrDefaultCA: "默认机构不能删除" +ApplyWebSiteSSLLog: "开始更新 {{ .name }} 网站证书" +ErrUpdateWebsiteSSL: "{{ .name }} 网站更新证书失败: {{ .err }}" +ApplyWebSiteSSLSuccess: "更新网站证书成功" +ErrExecShell: "执行脚本失败 {{ .err }}" +ExecShellStart: "开始执行脚本" +ExecShellSuccess: "脚本执行成功" +StartUpdateSystemSSL: "开始更新系统证书" +UpdateSystemSSLSuccess: "更新系统证书成功" +ErrWildcardDomain: "HTTP 模式无法申请泛域名证书" +ErrApplySSLCanNotDelete: "正在申请的证书{{.name}}无法删除,请稍后再试" +StartPushSSLToNode: "开始推送证书到节点" +PushSSLToNodeFailed: "推送证书到节点失败: {{ .err }}" +PushSSLToNodeSuccess: "推送证书到节点成功" + +#mysql +ErrUserIsExist: "当前用户已存在,请重新输入" +ErrDatabaseIsExist: "当前数据库已存在,请重新输入" +ErrExecTimeOut: "SQL 执行超时,请检查数据库" +ErrRemoteExist: "远程数据库已存在该名称,请修改后重试" +ErrLocalExist: "本地数据库已存在该名称,请修改后重试" +RemoteBackup: "备份远程数据库需要先使用镜像 {{ .name }} 启动本地容器数据库服务,请稍候..." +RemoteRecover: "恢复远程数据库需要先使用镜像 {{ .name }} 启动本地容器数据库服务,请稍候..." +Arg: "参数" + +#redis +ErrTypeOfRedis: "恢复文件类型与当前持久化方式不符,请修改后重试" + +#container +ErrInUsed: "{{ .detail }} 正被使用,无法删除!" +ErrObjectInUsed: "该对象正被使用,无法删除!" +ErrObjectBeDependent: "该镜像正被其他镜像所依赖,无法删除!" +ErrPortRules: "端口数目不匹配,请重新输入!" +ErrPgImagePull: "镜像拉取超时,请配置镜像加速或手动拉取 {{ .name }} 镜像后重试" +PruneHelper: "本次清理 {{ .name }} 释放磁盘空间 {{ .size }}" +ImageRemoveHelper: "删除镜像 {{ .name }} ,释放磁盘空间 {{ .size }}" +BuildCache: "构建缓存" +Volume: "存储卷" +Network: "网络" +PruneStart: "清理中,请稍候..." + +#runtime +ErrFileNotExist: "{{ .detail }} 文件不存在!请检查源文件完整性!" +ErrImageBuildErr: "镜像 build 失败" +ErrImageExist: "镜像已存在!请修改镜像名称" +ErrDelWithWebsite: "运行环境已经关联网站,无法删除" +ErrRuntimeStart: "启动失败" +ErrPackageJsonNotFound: "package.json 文件不存在" +ErrScriptsNotFound: "没有在 package.json 中找到 scripts 配置项" +ErrContainerNameNotFound: "无法获取容器名称,请检查 .env 文件" +ErrNodeModulesNotFound: "node_modules 文件夹不存在!请编辑运行环境或者等待运行环境启动成功" +ErrContainerNameIsNull: "容器名称不存在" +ErrPHPPortIsDefault: "9000 端口为默认端口,请修改后重试" +ErrPHPRuntimePortFailed: "{{ .name }} 端口已被当前运行环境使用,请修改后重试" + +#tool +ErrConfigNotFound: "配置文件不存在" +ErrConfigParse: "配置文件格式有误" +ErrConfigIsNull: "配置文件不允许为空" +ErrConfigDirNotFound: "运行目录不存在" +ErrConfigAlreadyExist: "已存在同名配置文件" +ErrUserFindErr: "用户 {{ .name }} 查找失败 {{ .err }}" + +#cronjob +CutWebsiteLogSuccess: "{{ .name }} 网站日志切割成功,备份路径 {{ .path }}" +HandleShell: "执行脚本 {{ .name }}" +HandleCurl: "访问 URL {{ .name }}" +HandleNtpSync: "系统时间同步" +HandleSystemClean: "系统缓存清理" +SystemLog: "系统日志" +CutWebsiteLog: "切割网站日志" +FileOrDir: "目录 / 文件" +UploadFile: "上传备份文件 {{ .file }} 到 {{ .backup }}" +Upload: "上传" +IgnoreBackupErr: "备份失败,错误:{{ .detail }},忽略本次错误..." +IgnoreUploadErr: "上传失败,错误:{{ .detail }},忽略本次错误..." +LoadBackupFailed: "获取备份账号连接失败,错误:{{ .detail }}" +InExecuting: "当前任务正在执行中,请勿重复执行!" +NoSuchResource: "数据库中未能查询到备份内容,跳过..." +CleanLog: "清理日志" +CleanLogByName: "清理 {{.name}} 日志" +WafIpGroupNotFound: "WAF IP组未找到,请先到【高级功能-WAF-黑白名单-IP组】添加远程下载方式的IP组" + +#toolbox +ErrNotExistUser: "当前用户不存在,请修改后重试!" +ErrBanAction: "设置失败,当前 {{ .name }} 服务不可用,请检查后重试!" +ErrClamdscanNotFound: "未检测到 clamdscan 命令,请参考文档安装!" +TaskSwapSet: "设置 Swap" +SetSwap: "设置 Swap {{ .path }} - {{ .size }}" +CreateSwap: "创建 Swap 文件" +FormatSwap: "格式化 Swap 文件" +EnableSwap: "启用 Swap" + +#tamper +CleanTamperSetting: "清理历史防护设置" +SetTamperWithRules: "根据规则启动目录防护" +ProtectDir: "防护目录 {{ .name }}" +IgnoreFile: "保护文件 {{ .name }}" +TamperSetSuccessful: "防护设置完成,开始监听以下目录:" + +#waf +ErrScope: "不支持修改此配置" +ErrStateChange: "状态修改失败" +ErrRuleExist: "规则已存在" +ErrRuleNotExist: "规则不存在" +ErrParseIP: "IP 格式错误" +ErrDefaultIP: "default 为保留名称,请更换其他名称" +ErrGroupInUse: "IP 组被黑/白名单使用,无法删除" +ErrIPGroupAclUse: "IP 组被网站 {{ .name }} 自定义规则使用,无法删除" +ErrGroupExist: "IP 组名称已存在" +ErrIPRange: "IP 范围错误" +ErrIPExist: "IP 已存在" +urlDefense: 'URL 规则' +urlHelper: '禁止访问的 URL' +dirFilter: '目录过滤' +xss: 'XSS' +phpExec: 'PHP 脚本执行' +oneWordTrojan: '一句话木马' +appFilter: '应用危险目录过滤' +webshell: 'Webshell' +args: '参数规则' +protocolFilter: '协议过滤' +javaFileter: 'Java 危险文件过滤' +scannerFilter: '扫描器过滤' +escapeFilter: '转义过滤' +customRule: '自定义规则' +httpMethod: 'HTTP 方法过滤' +fileExt: '文件上传限制' +defaultIpBlack: '恶意 IP 组' +cookie: 'Cookie 规则' +urlBlack: 'URL 黑名单' +uaBlack: 'User-Agent 黑名单' +attackCount: '攻击频率限制' +fileExtCheck: '文件上传限制' +geoRestrict: '地区访问限制' +unknownWebsite: '未授权域名访问' +notFoundCount: '404 频率限制' +headerDefense: 'Header 规则' +defaultUaBlack: 'User-Agent 规则' +methodWhite: 'HTTP 规则' +captcha: '人机验证' +fiveSeconds: '5 秒验证' +vulnCheck: '补充规则' +acl: '自定义规则' +sql: 'SQL 注入' +cc: '访问频率限制' +defaultUrlBlack: 'URL 规则' +sqlInject: 'SQL 注入' +ErrDBNotExist: "数据库不存在" +allow: "允许" +deny: "禁止" +OpenrestyNotFound: "Openresty 未安装" +remoteIpIsNull: "IP 列表为空" +OpenrestyVersionErr: "Openresty 版本过低,请升级 Openresty 至 1.27.1.2-2-2-focal" +ErrFileTooLarge: "文件超过 1MB 无法上传" + +#task +TaskStart: "{{ .name }} 任务开始 [START]" +TaskEnd: "{{ .name }} 任务结束 [COMPLETED]" +TaskFailed: "{{ .name }} 任务失败" +TaskTimeout: "{{ .name }} 超时" +TaskSuccess: "{{ .name }} 任务成功" +TaskRetry: "开始第 {{ .name }} 次重试" +SubTaskSuccess: "{{ .name }} 成功" +SubTaskFailed: "{{ .name }} 失败: {{ .err }}" +TaskInstall: "安装" +TaskUninstall: "卸载" +TaskCreate: "创建" +TaskDelete: "删除" +TaskUpgrade: "升级" +TaskUpdate: "更新" +TaskRestart: "重启" +TaskProtect: "防护" +TaskBackup: "备份" +TaskRecover: "恢复" +TaskRollback: "回滚" +TaskPull: "拉取" +TaskCommit: "制作" +TaskBuild: "构建" +TaskPush: "推送" +TaskClean: "清理" +TaskHandle: "执行" +TaskImport: "导入" +TaskExport: "导出" +Website: "网站" +App: "应用" +Runtime: "运行环境" +Database: "数据库" +ConfigFTP: "创建 FTP 用户 {{ .name }}" +ConfigOpenresty: "创建 Openresty 配置文件" +InstallAppSuccess: "应用 {{ .name }} 安装成功" +ConfigRuntime: "配置运行环境" +ConfigApp: "配置应用" +SuccessStatus: "{{ .name }} 成功" +FailedStatus: "{{ .name }} 失败 {{ .err }}" +HandleLink: "处理应用关联" +HandleDatabaseApp: "处理应用参数" +ExecShell: "执行 {{ .name }} 脚本" +PullImage: "拉取镜像" +Start: "开始" +Run: "启动" +Stop: "停止" +Image: "镜像" +Compose: "编排" +Container: "容器" +AppLink: "关联应用" +EnableSSL: "开启 HTTPS" +AppStore: "应用商店" +TaskSync: "同步" +LocalApp: "本地应用" +SubTask: "子任务" +RuntimeExtension: "运行环境扩展" +TaskIsExecuting: "任务正在运行" +CustomAppstore: "自定义应用仓库" +TaskExec: "执行" +TaskBatch: "批量操作" +FileConvert: "文件格式转换" + +# task - clam +Clamscan: "扫描 {{ .name }}" +TaskScan: "扫描" + +# task - ai +OllamaModelPull: "拉取 Ollama 模型 {{ .name }} " +OllamaModelSize: "获取 Ollama 模型 {{ .name }} 大小 " + +# task - snapshot +Snapshot: "快照" +SnapDBInfo: "写入 1Panel 数据库信息" +SnapCopy: "复制文件&目录 {{ .name }} " +SnapNewDB: "初始化数据库 {{ .name }} 连接 " +SnapDeleteOperationLog: "删除操作日志" +SnapDeleteLoginLog: "删除访问日志" +SnapDeleteMonitor: "删除监控数据" +SnapRemoveSystemIP: "移除系统 IP" +SnapBaseInfo: "写入 1Panel 基本信息" +SnapInstallAppImageEmpty: "当前未勾选应用镜像,跳过..." +SnapInstallApp: "备份 1Panel 已安装应用" +SnapDockerSave: "压缩已安装应用" +SnapLocalBackup: "备份 1Panel 本地备份目录" +SnapCompressBackup: "压缩本地备份目录" +SnapPanelData: "备份 1Panel 数据目录" +SnapCompressPanel: "压缩数据目录" +SnapWebsite: "备份 1Panel 网站目录" +SnapCloseDBConn: "关闭数据库连接" +SnapCompress: "制作快照文件" +SnapCompressFile: "压缩快照文件" +SnapCheckCompress: "检查快照压缩文件" +SnapCompressSize: "快照文件大小 {{ .name }}" +SnapUpload: "上传快照文件" +SnapUploadTo: "上传快照文件到 {{ .name }}" +SnapUploadRes: "上传快照文件到 {{ .name }}" + +SnapshotRecover: "快照恢复" +RecoverDownload: "下载快照文件" +Download: "下载" +RecoverDownloadAccount: "获取快照下载备份账号 {{ .name }}" +RecoverDecompress: "解压快照压缩文件" +Decompress: "解压" +BackupBeforeRecover: "快照前备份系统相关数据" +Readjson: "读取快照内 Json 文件" +ReadjsonPath: "获取快照内 Json 文件路径" +ReadjsonContent: "读取 Json 文件" +ReadjsonMarshal: "Json 转义处理" +RecoverApp: "恢复已安装应用" +RecoverWebsite: "恢复网站目录" +RecoverAppImage: "恢复快照镜像备份" +RecoverCompose: "恢复其他编排内容" +RecoverComposeList: "获取所有待恢复编排" +RecoverComposeItem: "恢复编排 {{ .name }}" +RecoverAppEmpty: "快照文件中未发现应用镜像备份" +RecoverBaseData: "恢复基础数据及文件" +RecoverDaemonJsonEmpty: "快照文件及当前机器都不存在容器配置 daemon.json 文件" +RecoverDaemonJson: "恢复容器配置 daemon.json 文件" +RecoverDBData: "恢复数据库数据" +RecoverBackups: "恢复本地备份目录" +RecoverPanelData: "恢复数据目录" + +# task - container +ContainerNewCliet: "初始化 Docker Client" +ContainerImagePull: "拉取容器镜像 {{ .name }}" +ContainerRemoveOld: "删除原容器 {{ .name }}" +ContainerImageCheck: "检查镜像是否正常拉取" +ContainerLoadInfo: "获取容器基本信息" +ContainerRecreate: "容器更新失败,现在开始恢复原容器" +ContainerCreate: "创建新容器 {{ .name }}" +ContainerCreateFailed: "容器创建失败,删除失败容器" +ContainerStartCheck: "检查容器是否已启动" + +# task - image +ImageBuild: "镜像构建" +ImageBuildStdoutCheck: "解析镜像输出内容" +ImageBuildRes: "镜像构建输出:{{ .name }}" +ImagePull: "拉取镜像" +ImageRepoAuthFromDB: "从数据库获取仓库认证信息" +ImaegPullRes: "镜像拉取输出:{{ .name }}" +ImagePush: "推送镜像" +ImageRenameTag: "修改镜像 Tag" +ImageNewTag: "新镜像 Tag {{ .name }}" +ImaegPushRes: "镜像推送输出:{{ .name }}" +ComposeCreate: "创建编排" +ComposeCreateRes: "编排创建输出:{{ .name }}" + +# task - website +BackupNginxConfig: "备份网站 OpenResty 配置文件" +CompressFileSuccess: "压缩目录成功,压缩为 {{ .name }}" +CompressDir: "压缩目录" +DeCompressFile: "解压文件 {{ .name }}" +ErrCheckValid: "校验备份文件失败,{{ .name }}" +Rollback: "回滚" +websiteDir: "网站目录" +RecoverFailedStartRollBack: "恢复失败,开始回滚" +AppBackupFileIncomplete: "备份文件不完整 缺少 app.json 或者 app.tar.gz 文件" +AppAttributesNotMatch: "应用类型或者名称不一致" + +#alert +ErrAlert: "告警信息格式错误,请检查后重试!" +ErrAlertPush: "告警信息推送错误,请检查后重试!" +ErrAlertSave: "告警信息保存错误,请检查后重试!" +ErrAlertSync: "告警信息同步错误,请检查后重试!" +ErrAlertRemote: "告警信息远端错误,请检查后重试!" + +#task - runtime +ErrInstallExtension: "已有安装任务正在进行,请等待任务结束" + +# alert mail template +PanelAlertTitle: "面板告警通知" +TestAlertTitle: "测试邮件 - 验证邮箱连通性" +TestAlert: "这是一封测试邮件,旨在验证您的邮箱发件配置是否正确。" +LicenseExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,许可证将在 {{ .day }} 天后到期,详情请登录面板查看。" +CronJobFailedAlert: "您的 {{ .node }}{{ .ip }} 面板,计划任务-{{ .name }}执行失败,详情请登录面板查看。" +ClamAlert: "您的 {{ .node }}{{ .ip }} 面板,病毒扫描任务发现 {{ .num }} 个感染文件,详情请登录面板查看。" +WebSiteAlert: "您的 {{ .node }}{{ .ip }} 面板,有 {{ .num }} 个网站将在 {{ .day }} 天后到期,详情请登录面板查看。" +SSLAlert: "您的 {{ .node }}{{ .ip }} 面板,有 {{ .num }} 张SSL证书将在 {{ .day }} 天后到期,详情请登录面板查看。" +DiskUsedAlert: "您的 {{ .node }}{{ .ip }} 面板,磁盘 {{ .name }} 已使用 {{ .used }},详情请登录面板查看。" +ResourceAlert: "您的 {{ .node }}{{ .ip }} 面板,平均 {{ .time }} 分钟内的 {{ .name }} 使用率为 {{ .used }},详情请登录面板查看。" +PanelVersionAlert: "您的 {{ .node }}{{ .ip }} 面板,有最新面板版本可供升级,详情请登录面板查看。" +PanelPwdExpirationAlert: "您的 {{ .node }}{{ .ip }} 面板,面板密码将在 {{ .day }} 天后到期,详情请登录面板查看。" +CommonAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .msg }},详情请登录面板查看。" +NodeExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个节点存在异常,详情请登录面板查看。" +LicenseExceptionAlert: "您的 {{ .node }}{{ .ip }} 面板,{{ .num }} 个许可证存在异常,详情请登录面板查看。" +SSHAndPanelLoginAlert: "您的 {{ .node }}{{ .ip }} 面板,面板{{ .name }}登录{{ .loginIp }}异常,详情请登录面板查看。" + +#disk +DeviceNotFound: "设备 {{ .name }} 未找到" +DeviceIsMounted: "设备 {{ .name }} 已挂载,请先卸载" +PartitionDiskErr: "分区失败,{{ .err }}" +FormatDiskErr: "格式化磁盘失败,{{ .err }}" +MountDiskErr: "挂载磁盘失败,{{ .err }}" +UnMountDiskErr: "卸载磁盘失败,{{ .err }}" +XfsNotFound: "未检测到 xfs 文件系统,请先安装 xfsprogs" \ No newline at end of file diff --git a/agent/init/app/app.go b/agent/init/app/app.go new file mode 100644 index 0000000..800eabe --- /dev/null +++ b/agent/init/app/app.go @@ -0,0 +1,11 @@ +package app + +import ( + "github.com/1Panel-dev/1Panel/agent/utils/docker" +) + +func Init() { + go func() { + _ = docker.CreateDefaultDockerNetwork() + }() +} diff --git a/agent/init/business/business.go b/agent/init/business/business.go new file mode 100644 index 0000000..b3b9e44 --- /dev/null +++ b/agent/init/business/business.go @@ -0,0 +1,91 @@ +package business + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/service" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +func Init() { + go syncApp() + go syncInstalledApp() + go syncRuntime() + go syncSSL() + go syncTask() + go initAcmeAccount() + go checkDockerCompose() +} + +func syncApp() { + if global.CONF.Base.IsOffLine { + return + } + _ = service.NewISettingService().Update("AppStoreSyncStatus", constant.StatusSyncSuccess) + if err := service.NewIAppService().SyncAppListFromRemote(""); err != nil { + global.LOG.Errorf("App Store synchronization failed") + return + } +} + +func syncInstalledApp() { + if err := service.NewIAppInstalledService().SyncAll(true); err != nil { + global.LOG.Errorf("sync installed app error: %s", err.Error()) + } +} + +func syncRuntime() { + if err := service.NewRuntimeService().SyncForRestart(); err != nil { + global.LOG.Errorf("sync runtime status error : %s", err.Error()) + } +} + +func syncSSL() { + if err := service.NewIWebsiteSSLService().SyncForRestart(); err != nil { + global.LOG.Errorf("sync ssl status error : %s", err.Error()) + } +} + +func syncTask() { + if err := service.NewITaskService().SyncForRestart(); err != nil { + global.LOG.Errorf("sync task status error : %s", err.Error()) + } +} + +func initAcmeAccount() { + acmeAccountService := service.NewIWebsiteAcmeAccountService() + search := dto.PageInfo{ + Page: 1, + PageSize: 10, + } + count, _, _ := acmeAccountService.Page(search) + if count == 0 { + createAcmeAccount := request.WebsiteAcmeAccountCreate{ + Email: "acme@1paneldev.com", + Type: "letsencrypt", + KeyType: "2048", + } + systemProxy, _ := service.NewISettingService().GetSystemProxy() + if systemProxy.URL != "" { + createAcmeAccount.UseProxy = true + } + if _, err := acmeAccountService.Create(createAcmeAccount); err != nil { + global.LOG.Warningf("create acme account error: %s", err.Error()) + } + } +} + +func checkDockerCompose() { + dockerComposeCmd := common.GetDockerComposeCommand() + if dockerComposeCmd == "" { + global.LOG.Errorf("Docker Compose command not found, please install Docker Compose Plugin") + return + } + global.CONF.DockerConfig.Command = dockerComposeCmd + if err := service.NewISettingService().Update("DockerComposeCommand", dockerComposeCmd); err != nil { + global.LOG.Errorf("update docker compose command error: %s", err.Error()) + return + } +} diff --git a/agent/init/cache/cache.go b/agent/init/cache/cache.go new file mode 100644 index 0000000..7ec3b4f --- /dev/null +++ b/agent/init/cache/cache.go @@ -0,0 +1,10 @@ +package cache + +import ( + "github.com/1Panel-dev/1Panel/agent/global" + cachedb "github.com/1Panel-dev/1Panel/agent/init/cache/db" +) + +func Init() { + global.CACHE = cachedb.NewCacheDB() +} diff --git a/agent/init/cache/db/db.go b/agent/init/cache/db/db.go new file mode 100644 index 0000000..506b378 --- /dev/null +++ b/agent/init/cache/db/db.go @@ -0,0 +1,52 @@ +package badger_db + +import ( + "github.com/patrickmn/go-cache" + "strings" + "time" +) + +type Cache struct { + db *cache.Cache +} + +func NewCacheDB() *Cache { + db := cache.New(5*time.Minute, 10*time.Minute) + return &Cache{ + db: db, + } +} + +func (c *Cache) Set(key string, value interface{}) { + c.db.Set(key, value, cache.DefaultExpiration) +} + +func (c *Cache) SetWithTTL(key string, value interface{}, d time.Duration) { + c.db.Set(key, value, d) +} +func (c *Cache) Del(key string) { + c.db.Delete(key) +} + +func (c *Cache) Clean() error { + return nil +} + +func (c *Cache) Get(key string) string { + obj, exist := c.db.Get(key) + if !exist { + return "" + } + return obj.(string) +} + +func (c *Cache) PrefixScanKey(prefixStr string) []string { + var res []string + values := c.db.Items() + for key := range values { + if strings.HasPrefix(key, prefixStr) { + res = append(res, key) + } + } + return res +} diff --git a/agent/init/db/db.go b/agent/init/db/db.go new file mode 100644 index 0000000..7832223 --- /dev/null +++ b/agent/init/db/db.go @@ -0,0 +1,21 @@ +package db + +import ( + "os" + "path" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +func Init() { + global.DB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "agent.db"), "agent") + global.TaskDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "task.db"), "task") + global.MonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "monitor.db"), "monitor") + global.GPUMonitorDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "gpu_monitor.db"), "gpu_monitor") + global.AlertDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "alert.db"), "alert") + + if _, err := os.Stat(path.Join(global.Dir.DbDir, "core.db")); err == nil { + global.CoreDB = common.LoadDBConnByPath(path.Join(global.Dir.DbDir, "core.db"), "core") + } +} diff --git a/agent/init/dir/dir.go b/agent/init/dir/dir.go new file mode 100644 index 0000000..651f786 --- /dev/null +++ b/agent/init/dir/dir.go @@ -0,0 +1,38 @@ +package dir + +import ( + "path" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +func Init() { + fileOp := files.NewFileOp() + baseDir := global.CONF.Base.InstallDir + _, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/docker/compose/")) + + global.Dir.BaseDir, _ = fileOp.CreateDirWithPath(true, baseDir) + global.Dir.DataDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel")) + global.Dir.DbDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/db")) + global.Dir.LogDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/log")) + global.Dir.TaskDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/log/task")) + global.Dir.TmpDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/tmp")) + + global.Dir.AppDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/apps")) + global.Dir.ResourceDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource")) + global.Dir.AppResourceDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource/apps")) + global.Dir.AppInstallDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/apps")) + global.Dir.LocalAppResourceDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource/apps/local")) + global.Dir.LocalAppInstallDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/apps/local")) + global.Dir.RemoteAppResourceDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource/apps/remote")) + global.Dir.CustomAppResourceDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource/apps/custom")) + global.Dir.OfflineAppResourceDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/resource/offline")) + global.Dir.RuntimeDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/runtime")) + global.Dir.RecycleBinDir, _ = fileOp.CreateDirWithPath(true, "/.1panel_clash") + global.Dir.SSLLogDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/log/ssl")) + global.Dir.McpDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/mcp")) + global.Dir.ConvertLogDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/log/convert")) + global.Dir.TensorRTLLMDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/ai/tensorrt_llm")) + global.Dir.FirewallDir, _ = fileOp.CreateDirWithPath(true, path.Join(baseDir, "1panel/firewall")) +} diff --git a/agent/init/firewall/firewall.go b/agent/init/firewall/firewall.go new file mode 100644 index 0000000..089d997 --- /dev/null +++ b/agent/init/firewall/firewall.go @@ -0,0 +1,142 @@ +package firewall + +import ( + "fmt" + "os" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/service" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/firewall" + firewallClient "github.com/1Panel-dev/1Panel/agent/utils/firewall/client" + "github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables" +) + +func Init() { + if !needInit() { + return + } + InitPingStatus() + global.LOG.Info("initializing firewall settings...") + client, err := firewall.NewFirewallClient() + if err != nil { + return + } + clientName := client.Name() + + settingRepo := repo.NewISettingRepo() + if clientName == "ufw" || clientName == "iptables" { + if err := iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelForward, iptables.ForwardFileName); err != nil { + global.LOG.Errorf("load forward rules from file failed, err: %v", err) + return + } + if err := iptables.LoadRulesFromFile(iptables.NatTab, iptables.Chain1PanelPreRouting, iptables.ForwardFileName1); err != nil { + global.LOG.Errorf("load prerouting rules from file failed, err: %v", err) + return + } + if err := iptables.LoadRulesFromFile(iptables.NatTab, iptables.Chain1PanelPostRouting, iptables.ForwardFileName2); err != nil { + global.LOG.Errorf("load postrouting rules from file failed, err: %v", err) + return + } + global.LOG.Infof("loaded iptables rules for forward from file successfully") + + iptablesForwardStatus, _ := settingRepo.GetValueByKey("IptablesForwardStatus") + if iptablesForwardStatus == constant.StatusEnable { + if err := firewallClient.EnableIptablesForward(); err != nil { + global.LOG.Errorf("enable iptables forward failed, err: %v", err) + return + } + } + } + + if clientName != "iptables" { + return + } + if err := iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelBasicBefore, iptables.BasicBeforeFileName); err != nil { + global.LOG.Errorf("load basic before rules from file failed, err: %v", err) + return + } + if err := iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelBasic, iptables.BasicFileName); err != nil { + global.LOG.Errorf("load basic rules from file failed, err: %v", err) + return + } + if err := iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelBasicAfter, iptables.BasicAfterFileName); err != nil { + global.LOG.Errorf("load basic after rules from file failed, err: %v", err) + return + } + panelPort := service.LoadPanelPort() + if len(panelPort) == 0 { + global.LOG.Errorf("find 1panel service port failed") + return + } + if err := iptables.AddRule(iptables.FilterTab, iptables.Chain1PanelBasicBefore, fmt.Sprintf("-p tcp -m tcp --dport %v -j ACCEPT", panelPort)); err != nil { + global.LOG.Errorf("add port accept rule %v failed, err: %v", panelPort, err) + return + } + global.LOG.Infof("loaded iptables rules for basic from file successfully") + iptablesService := service.IptablesService{} + iptablesStatus, _ := settingRepo.GetValueByKey("IptablesStatus") + if iptablesStatus == constant.StatusEnable { + if err := iptablesService.Operate(dto.IptablesOp{Operate: "bind-base-without-init"}); err != nil { + global.LOG.Errorf("bind base chains failed, err: %v", err) + return + } + } + + if err := iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelInput, iptables.InputFileName); err != nil { + global.LOG.Errorf("load input rules from file failed, err: %v", err) + return + } + if err := iptables.LoadRulesFromFile(iptables.FilterTab, iptables.Chain1PanelOutput, iptables.OutputFileName); err != nil { + global.LOG.Errorf("load output rules from file failed, err: %v", err) + return + } + global.LOG.Infof("loaded iptables rules for input and output from file successfully") + iptablesInputStatus, _ := settingRepo.GetValueByKey("IptablesInputStatus") + if iptablesInputStatus == constant.StatusEnable { + if err := iptablesService.Operate(dto.IptablesOp{Name: iptables.Chain1PanelInput, Operate: "bind"}); err != nil { + global.LOG.Errorf("bind input chains failed, err: %v", err) + return + } + } + iptablesOutputStatus, _ := settingRepo.GetValueByKey("IptablesOutputStatus") + if iptablesOutputStatus == constant.StatusEnable { + if err := iptablesService.Operate(dto.IptablesOp{Name: iptables.Chain1PanelOutput, Operate: "bind"}); err != nil { + global.LOG.Errorf("bind output chains failed, err: %v", err) + return + } + } +} + +func needInit() bool { + file, err := os.OpenFile("/run/1panel_boot_mark", os.O_RDWR|os.O_CREATE|os.O_EXCL, 0644) + if err != nil { + if os.IsExist(err) { + return false + } + global.LOG.Errorf("check boot mark file failed: %v", err) + return true + } + defer file.Close() + fmt.Fprintf(file, "Boot Mark for 1panel\n") + return true +} + +func InitPingStatus() { + global.LOG.Info("initializing ban ping status from settings...") + status := firewall.LoadPingStatus() + statusInDB, _ := repo.NewISettingRepo().GetValueByKey("BanPing") + if statusInDB == status { + return + } + + enable := "1" + if statusInDB == constant.StatusDisable { + enable = "0" + } + if err := firewall.UpdatePingStatus(enable); err != nil { + global.LOG.Errorf("initialize ping status failed: %v", err) + } +} diff --git a/agent/init/hook/hook.go b/agent/init/hook/hook.go new file mode 100644 index 0000000..ec1ce3b --- /dev/null +++ b/agent/init/hook/hook.go @@ -0,0 +1,153 @@ +package hook + +import ( + "os" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/service" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/alert_push" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" +) + +func Init() { + initGlobalData() + handleCronjobStatus() + handleClamStatus() + handleRecordStatus() + handleSnapStatus() + handleOllamaModelStatus() + + loadLocalDir() + + initDockerConf() + initAlertTask() +} + +func initGlobalData() { + settingRepo := repo.NewISettingRepo() + if _, err := settingRepo.Get(settingRepo.WithByKey("SystemStatus")); err != nil { + _ = settingRepo.Create("SystemStatus", "Free") + } + if err := settingRepo.Update("SystemStatus", "Free"); err != nil { + global.LOG.Fatalf("init service before start failed, err: %v", err) + } + node, _ := xpack.LoadNodeInfo(false) + if len(node.Version) != 0 { + _ = settingRepo.Update("SystemVersion", node.Version) + } + global.CONF.Base.Version = node.Version + global.CONF.Base.EncryptKey, _ = settingRepo.GetValueByKey("EncryptKey") +} + +func handleSnapStatus() { + _ = global.DB.Model(&model.Snapshot{}).Where("status = ?", "OnSaveData"). + Updates(map[string]interface{}{"status": constant.StatusSuccess}).Error + + _ = global.DB.Model(&model.Snapshot{}).Where("status = ?", constant.StatusWaiting). + Updates(map[string]interface{}{ + "status": constant.StatusFailed, + "message": constant.InterruptedMsg, + }).Error + + _ = global.DB.Model(&model.Snapshot{}).Where("recover_status = ?", constant.StatusWaiting). + Updates(map[string]interface{}{ + "recover_status": constant.StatusFailed, + "recover_message": constant.InterruptedMsg, + }).Error + + _ = global.DB.Model(&model.Snapshot{}).Where("rollback_status = ?", constant.StatusWaiting). + Updates(map[string]interface{}{ + "rollback_status": constant.StatusFailed, + "rollback_message": constant.InterruptedMsg, + }).Error +} + +func handleCronjobStatus() { + var jobRecords []model.JobRecords + _ = global.DB.Model(&model.Cronjob{}).Where("is_executing = ?", true).Updates(map[string]interface{}{"is_executing": false}).Error + _ = global.DB.Where("status = ?", constant.StatusWaiting).Find(&jobRecords).Error + for _, record := range jobRecords { + err := global.DB.Model(&model.JobRecords{}).Where("status = ?", constant.StatusWaiting). + Updates(map[string]interface{}{ + "status": constant.StatusFailed, + "message": constant.InterruptedMsg, + }).Error + + if err != nil { + global.LOG.Errorf("Failed to update job ID: %v, Error:%v", record.ID, err) + continue + } + + var cronjob *model.Cronjob + _ = global.DB.Where("id = ?", record.CronjobID).First(&cronjob).Error + handleCronJobAlert(cronjob) + } +} + +func handleClamStatus() { + _ = global.DB.Model(&model.Clam{}).Where("is_executing = ?", true).Updates(map[string]interface{}{"is_executing": false}).Error + _ = global.DB.Model(&model.ClamRecord{}).Where("status = ?", constant.StatusWaiting).Updates(map[string]interface{}{ + "status": constant.StatusFailed, + "message": constant.InterruptedMsg, + }).Error +} + +func handleRecordStatus() { + _ = global.DB.Model(&model.BackupRecord{}).Where("status = ?", constant.StatusWaiting).Updates(map[string]interface{}{ + "status": constant.StatusFailed, + "message": constant.InterruptedMsg, + }).Error +} + +func handleOllamaModelStatus() { + _ = global.DB.Model(&model.OllamaModel{}).Where("status = ?", constant.StatusWaiting).Updates(map[string]interface{}{ + "status": constant.StatusCanceled, + "message": constant.InterruptedMsg, + }).Error +} + +func handleCronJobAlert(cronjob *model.Cronjob) { + pushAlert := dto.PushAlert{ + TaskName: cronjob.Name, + AlertType: cronjob.Type, + EntryID: cronjob.ID, + Param: cronjob.Type, + } + _ = alert_push.PushAlert(pushAlert) +} + +func loadLocalDir() { + var account model.BackupAccount + if err := global.DB.Where("`type` = ?", constant.Local).First(&account).Error; err != nil { + global.LOG.Errorf("load local backup account info failed, err: %v", err) + return + } + global.Dir.LocalBackupDir = account.BackupPath + + if _, err := os.Stat(account.BackupPath); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(account.BackupPath, os.ModePerm); err != nil { + global.LOG.Errorf("mkdir %s failed, err: %v", account.BackupPath, err) + } + } +} + +func initDockerConf() { + stdout, err := cmd.RunDefaultWithStdoutBashC("which docker") + if err != nil { + return + } + dockerPath := stdout + if strings.Contains(dockerPath, "snap") { + constant.DaemonJsonPath = "/var/snap/docker/current/config/daemon.json" + } +} + +func initAlertTask() { + service.NewIAlertTaskHelper().ResetTask() +} diff --git a/agent/init/lang/lang.go b/agent/init/lang/lang.go new file mode 100644 index 0000000..8091e54 --- /dev/null +++ b/agent/init/lang/lang.go @@ -0,0 +1,132 @@ +package lang + +import ( + "fmt" + "os" + "path" + "sort" + "strings" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +func Init() { + go initLang() +} + +func initLang() { + fileOp := files.NewFileOp() + geoPath := path.Join(global.CONF.Base.InstallDir, "1panel/geo/GeoIP.mmdb") + isLangExist := fileOp.Stat("/usr/local/bin/lang/zh.sh") + isGeoExist := fileOp.Stat(geoPath) + if isLangExist && isGeoExist { + return + } + upgradePath := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/upgrade") + tmpPath, err := loadRestorePath(upgradePath) + upgradeDir := path.Join(upgradePath, tmpPath, "downloads") + if err != nil || len(tmpPath) == 0 || !fileOp.Stat(upgradeDir) { + if !isLangExist { + downloadLangFromRemote(fileOp) + } + if !isGeoExist { + downloadGeoFromRemote(fileOp, geoPath) + } + return + } + + files, _ := os.ReadDir(upgradeDir) + if len(files) == 0 { + tmpPath = "no such file" + } else { + for _, item := range files { + if item.IsDir() && strings.HasPrefix(item.Name(), "1panel-") { + tmpPath = path.Join(upgradePath, tmpPath, "downloads", item.Name()) + break + } + } + } + if tmpPath == "no such file" || !fileOp.Stat(tmpPath) { + if !isLangExist { + downloadLangFromRemote(fileOp) + } + if !isGeoExist { + downloadGeoFromRemote(fileOp, geoPath) + } + return + } + if !isLangExist { + if !fileOp.Stat(path.Join(tmpPath, "lang")) { + downloadLangFromRemote(fileOp) + return + } + if err := cmd.RunDefaultBashCf("cp -r %s %s", path.Join(tmpPath, "lang"), "/usr/local/bin/"); err != nil { + global.LOG.Errorf("load lang from package failed, %v", err) + return + } + global.LOG.Info("init lang successful") + } + if !isGeoExist { + if !fileOp.Stat(path.Join(tmpPath, "GeoIP.mmdb")) { + downloadGeoFromRemote(fileOp, geoPath) + return + } + if err := cmd.RunDefaultBashCf("mkdir %s && cp %s %s/", path.Dir(geoPath), path.Join(tmpPath, "GeoIP.mmdb"), path.Dir(geoPath)); err != nil { + global.LOG.Errorf("load geo ip from package failed, %v", err) + return + } + global.LOG.Info("init geo ip successful") + } +} + +func loadRestorePath(upgradeDir string) (string, error) { + if _, err := os.Stat(upgradeDir); err != nil && os.IsNotExist(err) { + return "no such file", nil + } + files, err := os.ReadDir(upgradeDir) + if err != nil { + return "", err + } + var folders []string + for _, file := range files { + if file.IsDir() { + folders = append(folders, file.Name()) + } + } + if len(folders) == 0 { + return "no such file", nil + } + sort.Slice(folders, func(i, j int) bool { + return folders[i] > folders[j] + }) + return folders[0], nil +} + +func downloadLangFromRemote(fileOp files.FileOp) { + path := fmt.Sprintf("%s/language/lang.tar.gz", global.CONF.RemoteURL.RepoUrl) + if err := fileOp.DownloadFile(path, "/usr/local/bin/lang.tar.gz"); err != nil { + global.LOG.Errorf("download lang.tar.gz failed, err: %v", err) + return + } + if !fileOp.Stat("/usr/local/bin/lang.tar.gz") { + global.LOG.Error("download lang.tar.gz failed, no such file") + return + } + if err := cmd.RunDefaultBashCf("tar zxvfC %s %s", "/usr/local/bin/lang.tar.gz", "/usr/local/bin/"); err != nil { + global.LOG.Errorf("decompress lang.tar.gz failed, %v", err) + return + } + _ = os.Remove("/usr/local/bin/lang.tar.gz") + global.LOG.Info("download lang successful") +} +func downloadGeoFromRemote(fileOp files.FileOp, targetPath string) { + _ = os.MkdirAll(path.Dir(targetPath), os.ModePerm) + pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.CONF.RemoteURL.RepoUrl) + if err := fileOp.DownloadFile(pathItem, targetPath); err != nil { + global.LOG.Errorf("download geo ip failed, err: %v", err) + return + } + global.LOG.Info("download geo ip successful") +} diff --git a/agent/init/log/log.go b/agent/init/log/log.go new file mode 100644 index 0000000..372117b --- /dev/null +++ b/agent/init/log/log.go @@ -0,0 +1,67 @@ +package log + +import ( + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/log" + + "github.com/1Panel-dev/1Panel/agent/global" + + "github.com/sirupsen/logrus" +) + +const ( + TimeFormat = "2006-01-02 15:04:05" + FileTImeFormat = "2006-01-02" + RollingTimePattern = "0 0 * * *" +) + +func Init() { + l := logrus.New() + setOutput(l, (global.CONF.Log)) + global.LOG = l + global.LOG.Info("init logger successfully") +} + +func setOutput(logger *logrus.Logger, config global.LogConfig) { + writer, err := log.NewWriterFromConfig(&log.Config{ + LogPath: global.Dir.LogDir, + FileName: config.LogName, + TimeTagFormat: FileTImeFormat, + MaxRemain: config.MaxBackup, + RollingTimePattern: RollingTimePattern, + LogSuffix: config.LogSuffix, + }) + if err != nil { + panic(err) + } + level, err := logrus.ParseLevel(config.Level) + if err != nil { + panic(err) + } + fileAndStdoutWriter := io.MultiWriter(writer, os.Stdout) + + logger.SetOutput(fileAndStdoutWriter) + logger.SetLevel(level) + logger.SetFormatter(new(MineFormatter)) +} + +type MineFormatter struct{} + +func (s *MineFormatter) Format(entry *logrus.Entry) ([]byte, error) { + detailInfo := "" + if entry.Caller != nil { + function := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/agent/", "") + detailInfo = fmt.Sprintf("(%s: %d)", function, entry.Caller.Line) + } + if len(entry.Data) == 0 { + msg := fmt.Sprintf("[%s] [%s] %s %s \n", time.Now().Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo) + return []byte(msg), nil + } + msg := fmt.Sprintf("[%s] [%s] %s %s {%v} \n", time.Now().Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo, entry.Data) + return []byte(msg), nil +} diff --git a/agent/init/migration/migrate.go b/agent/init/migration/migrate.go new file mode 100644 index 0000000..c940244 --- /dev/null +++ b/agent/init/migration/migrate.go @@ -0,0 +1,81 @@ +package migration + +import ( + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/init/migration/migrations" + + "github.com/go-gormigrate/gormigrate/v2" +) + +func Init() { + InitAgentDB() + InitTaskDB() + global.LOG.Info("Migration run successfully") +} + +func InitAgentDB() { + m := gormigrate.New(global.DB, gormigrate.DefaultOptions, []*gormigrate.Migration{ + migrations.AddTable, + migrations.AddMonitorTable, + migrations.InitSetting, + migrations.InitImageRepo, + migrations.InitDefaultCA, + migrations.InitPHPExtensions, + migrations.InitBackup, + migrations.InitDefault, + migrations.UpdateWebsiteExpireDate, + migrations.UpdateRuntime, + migrations.AddSnapshotRule, + migrations.UpdatePHPRuntime, + migrations.AddSnapshotIgnore, + migrations.InitAppLauncher, + migrations.AddTableAlert, + migrations.InitAlertConfig, + migrations.AddMethodToAlertLog, + migrations.AddMethodToAlertTask, + migrations.UpdateMcpServer, + migrations.InitCronjobGroup, + migrations.AddColumnToAlert, + migrations.UpdateWebsiteSSL, + migrations.AddQuickJump, + migrations.UpdateMcpServerAddType, + migrations.InitLocalSSHConn, + migrations.InitLocalSSHShow, + migrations.InitRecordStatus, + migrations.AddShowNameForQuickJump, + migrations.AddTimeoutForClam, + migrations.UpdateCronjobSpec, + migrations.UpdateWebsiteSSLAddColumn, + migrations.AddTensorRTLLMModel, + migrations.UpdateMonitorInterval, + migrations.AddMonitorProcess, + migrations.UpdateCronJob, + migrations.UpdateTensorrtLLM, + migrations.AddIptablesFilterRuleTable, + migrations.AddCommonDescription, + migrations.UpdateDatabase, + migrations.AddGPUMonitor, + migrations.UpdateDatabaseMysql, + migrations.InitIptablesStatus, + migrations.UpdateWebsite, + migrations.AddisIPtoWebsiteSSL, + migrations.InitPingStatus, + migrations.UpdateApp, + migrations.AddCronjobArgs, + migrations.AddWebsiteAcmeAccountColumn, + }) + if err := m.Migrate(); err != nil { + global.LOG.Error(err) + panic(err) + } +} + +func InitTaskDB() { + m := gormigrate.New(global.TaskDB, gormigrate.DefaultOptions, []*gormigrate.Migration{ + migrations.AddTaskTable, + }) + if err := m.Migrate(); err != nil { + global.LOG.Error(err) + panic(err) + } +} diff --git a/agent/init/migration/migrations/init.go b/agent/init/migration/migrations/init.go new file mode 100644 index 0000000..de5a566 --- /dev/null +++ b/agent/init/migration/migrations/init.go @@ -0,0 +1,823 @@ +package migrations + +import ( + "encoding/json" + "fmt" + "os" + "os/user" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/dto/request" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/service" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/copier" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/firewall" + "github.com/1Panel-dev/1Panel/agent/utils/ssh" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +var AddTable = &gormigrate.Migration{ + ID: "20250930-add-table", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate( + &model.AppDetail{}, + &model.AppInstallResource{}, + &model.AppInstall{}, + &model.AppTag{}, + &model.Tag{}, + &model.App{}, + &model.AppLauncher{}, + &model.OllamaModel{}, + &model.BackupAccount{}, + &model.BackupRecord{}, + &model.Clam{}, + &model.ComposeTemplate{}, + &model.Compose{}, + &model.Cronjob{}, + &model.Database{}, + &model.DatabaseMysql{}, + &model.DatabasePostgresql{}, + &model.Favorite{}, + &model.Firewall{}, + &model.Ftp{}, + &model.ImageRepo{}, + &model.ScriptLibrary{}, + &model.JobRecords{}, + &model.MonitorBase{}, + &model.MonitorIO{}, + &model.MonitorNetwork{}, + &model.PHPExtensions{}, + &model.Runtime{}, + &model.Setting{}, + &model.Snapshot{}, + &model.Tag{}, + &model.Website{}, + &model.WebsiteAcmeAccount{}, + &model.WebsiteCA{}, + &model.WebsiteDnsAccount{}, + &model.WebsiteDomain{}, + &model.WebsiteSSL{}, + &model.Group{}, + &model.AppIgnoreUpgrade{}, + &model.McpServer{}, + &model.RootCert{}, + &model.ClamRecord{}, + ) + }, +} + +var AddMonitorTable = &gormigrate.Migration{ + ID: "20240813-add-monitor-table", + Migrate: func(tx *gorm.DB) error { + return global.MonitorDB.AutoMigrate( + &model.MonitorBase{}, + &model.MonitorIO{}, + &model.MonitorNetwork{}, + ) + }, +} + +var InitSetting = &gormigrate.Migration{ + ID: "20240722-init-setting", + Migrate: func(tx *gorm.DB) error { + global.CONF.Base.EncryptKey = common.RandStr(16) + nodeInfo, err := xpack.LoadNodeInfo(true) + if err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "BaseDir", Value: nodeInfo.BaseDir}).Error; err != nil { + return err + } + itemKey, _ := encrypt.StringEncrypt(nodeInfo.ServerKey) + if err := tx.Create(&model.Setting{Key: "ServerKey", Value: itemKey}).Error; err != nil { + return err + } + itemCrt, _ := encrypt.StringEncrypt(nodeInfo.ServerCrt) + if err := tx.Create(&model.Setting{Key: "ServerCrt", Value: itemCrt}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "NodeScope", Value: nodeInfo.Scope}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "NodePort", Value: fmt.Sprintf("%v", nodeInfo.NodePort)}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "SystemVersion", Value: nodeInfo.Version}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "EncryptKey", Value: global.CONF.Base.EncryptKey}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "DockerSockPath", Value: "unix:///var/run/docker.sock"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "SystemStatus", Value: "Free"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "Language", Value: "zh"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "SystemIP", Value: ""}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "LocalTime", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "TimeZone", Value: common.LoadTimeZoneByCmd()}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "NtpSite", Value: "pool.ntp.org"}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "LastCleanTime", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "LastCleanSize", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "LastCleanData", Value: ""}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "DefaultNetwork", Value: "all"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "MonitorStatus", Value: constant.StatusEnable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "MonitorStoreDays", Value: "7"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "MonitorInterval", Value: "5"}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "ProxyType", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyUrl", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyPort", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyUser", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyPasswd", Value: ""}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "AppStoreVersion", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "AppStoreSyncStatus", Value: "SyncSuccess"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "AppStoreLastModified", Value: "0"}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "FileRecycleBin", Value: constant.StatusEnable}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "LocalSSHConn", Value: ""}).Error; err != nil { + return err + } + + return nil + }, +} + +var InitImageRepo = &gormigrate.Migration{ + ID: "20240722-init-imagerepo", + Migrate: func(tx *gorm.DB) error { + item := &model.ImageRepo{ + Name: "Docker Hub", + Protocol: "https", + DownloadUrl: "docker.io", + Status: constant.StatusSuccess, + } + if err := tx.Create(item).Error; err != nil { + return err + } + return nil + }, +} + +var InitDefaultCA = &gormigrate.Migration{ + ID: "20240722-init-default-ca", + Migrate: func(tx *gorm.DB) error { + caService := service.NewIWebsiteCAService() + if _, err := caService.Create(request.WebsiteCACreate{ + CommonName: "1Panel-CA", + Country: "CN", + KeyType: "P256", + Name: "1Panel", + Organization: "FIT2CLOUD", + OrganizationUint: "1Panel", + Province: "Beijing", + City: "Beijing", + }); err != nil { + return err + } + return nil + }, +} + +var InitPHPExtensions = &gormigrate.Migration{ + ID: "20240722-add-php-extensions", + Migrate: func(tx *gorm.DB) error { + phpExtensions := []model.PHPExtensions{ + { + Name: "Default", + Extensions: "bcmath,ftp,gd,gettext,intl,mysqli,pcntl,pdo_mysql,shmop,soap,sockets,sysvsem,xmlrpc,zip", + }, + { + Name: "WordPress", + Extensions: "exif,igbinary,imagick,intl,zip,apcu,memcached,opcache,redis,shmop,mysqli,pdo_mysql,gd", + }, + { + Name: "Flarum", + Extensions: "curl,gd,pdo_mysql,mysqli,bz2,exif,yaf,imap", + }, + { + Name: "SeaCMS", + Extensions: "mysqli,pdo_mysql,gd,curl", + }, + { + Name: "Dev", + Extensions: "bcmath,ftp,gd,gettext,intl,mysqli,pcntl,pdo_mysql,shmop,soap,sockets,sysvsem,xmlrpc,zip,exif,igbinary,imagick,apcu,memcached,opcache,redis,bc,image,dom,iconv,mbstring,mysqlnd,openssl,pdo,tokenizer,xml,curl,bz2,yaf,imap,xdebug,swoole,pdo_pgsql,fileinfo,pgsql,calendar,gmp", + }, + } + + for _, ext := range phpExtensions { + if err := tx.Create(&ext).Error; err != nil { + return err + } + } + return nil + }, +} + +var AddTaskTable = &gormigrate.Migration{ + ID: "20241226-add-task", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate( + &model.Task{}, + ) + }, +} + +var InitBackup = &gormigrate.Migration{ + ID: "20241226-init-backup", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.BackupAccount{ + Name: "localhost", + Type: "LOCAL", + BackupPath: path.Join(global.Dir.DataDir, "backup"), + }).Error; err != nil { + return err + } + return nil + }, +} + +var InitDefault = &gormigrate.Migration{ + ID: "20250301-init-default", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Group{Name: "Default", Type: "website", IsDefault: true}).Error; err != nil { + return err + } + return nil + }, +} + +var UpdateWebsiteExpireDate = &gormigrate.Migration{ + ID: "20250304-update-website", + Migrate: func(tx *gorm.DB) error { + targetDate := time.Date(9999, 12, 31, 0, 0, 0, 0, time.UTC) + + if err := tx.Model(&model.Website{}). + Where("expire_date = ?", time.Date(1970, 1, 1, 0, 0, 0, 0, time.UTC)). + Update("expire_date", targetDate).Error; err != nil { + return err + } + return nil + }, +} + +var UpdateRuntime = &gormigrate.Migration{ + ID: "20250624-update-runtime", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate( + &model.Runtime{}, + ) + }, +} + +var AddSnapshotRule = &gormigrate.Migration{ + ID: "20250703-add-snapshot-rule", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate( + &model.Cronjob{}, + ) + }, +} +var UpdatePHPRuntime = &gormigrate.Migration{ + ID: "20250702-update-php-runtime", + Migrate: func(tx *gorm.DB) error { + service.HandleOldPHPRuntime() + return nil + }, +} +var AddSnapshotIgnore = &gormigrate.Migration{ + ID: "20250716-add-snapshot-ignore", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate( + &model.Snapshot{}, + ) + }, +} + +var InitAppLauncher = &gormigrate.Migration{ + ID: "20250702-init-app-launcher", + Migrate: func(tx *gorm.DB) error { + launchers := []string{"openresty", "mysql", "halo", "redis", "maxkb", "wordpress"} + for _, val := range launchers { + var item model.AppLauncher + _ = tx.Model(&model.AppLauncher{}).Where("key = ?", val).First(&item).Error + if item.ID == 0 { + item.Key = val + _ = tx.Create(&item).Error + } + } + return nil + }, +} + +var AddTableAlert = &gormigrate.Migration{ + ID: "20250122-add-table-alert", + Migrate: func(tx *gorm.DB) error { + return global.AlertDB.AutoMigrate(&model.Alert{}, &model.AlertTask{}, model.AlertLog{}, model.AlertConfig{}) + }, +} + +var InitAlertConfig = &gormigrate.Migration{ + ID: "20250705-init-alert-config", + Migrate: func(tx *gorm.DB) error { + records := []model.AlertConfig{ + { + Type: "sms", + Title: "xpack.alert.smsConfig", + Status: "Enable", + Config: `{"alertDailyNum":50}`, + }, + { + Type: "common", + Title: "xpack.alert.commonConfig", + Status: "Enable", + Config: `{"isOffline":"Disable","alertSendTimeRange":{"noticeAlert":{"sendTimeRange":"08:00:00 - 23:59:59","type":["ssl","siteEndTime","panelPwdEndTime","panelUpdate"]},"resourceAlert":{"sendTimeRange":"00:00:00 - 23:59:59","type":["clams","cronJob","cpu","memory","load","disk"]}}}`, + }, + } + for _, r := range records { + if err := global.AlertDB.Model(&model.AlertConfig{}).Create(&r).Error; err != nil { + return err + } + } + return nil + }, +} + +var AddMethodToAlertLog = &gormigrate.Migration{ + ID: "20250713-add-method-to-alert_log", + Migrate: func(tx *gorm.DB) error { + if err := global.AlertDB.AutoMigrate(&model.AlertLog{}); err != nil { + return err + } + if err := global.AlertDB.Model(&model.AlertLog{}).Where("method IS NULL OR method = ''").Update("method", "sms").Error; err != nil { + return err + } + return nil + }, +} + +var AddMethodToAlertTask = &gormigrate.Migration{ + ID: "20250723-add-method-to-alert_task", + Migrate: func(tx *gorm.DB) error { + if err := global.AlertDB.AutoMigrate(&model.AlertTask{}); err != nil { + return err + } + if err := global.AlertDB.Model(&model.AlertTask{}).Where("method IS NULL OR method = ''").Update("method", "sms").Error; err != nil { + return err + } + return nil + }, +} + +var UpdateMcpServer = &gormigrate.Migration{ + ID: "20250729-update-mcp-server", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.McpServer{}); err != nil { + return err + } + if err := tx.Model(&model.McpServer{}).Where("1=1").Update("output_transport", "sse").Error; err != nil { + return err + } + return nil + }, +} + +var InitCronjobGroup = &gormigrate.Migration{ + ID: "20250805-init-cronjob-group", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.Cronjob{}); err != nil { + return err + } + if err := tx.Model(&model.Cronjob{}).Where("1=1").Updates(map[string]interface{}{"group_id": 0}).Error; err != nil { + return err + } + return nil + }, +} + +var AddColumnToAlert = &gormigrate.Migration{ + ID: "20250729-add-column-to-alert", + Migrate: func(tx *gorm.DB) error { + if err := global.AlertDB.AutoMigrate(&model.Alert{}); err != nil { + return err + } + if err := global.AlertDB.Model(&model.Alert{}). + Where("advanced_params IS NULL"). + Update("advanced_params", "").Error; err != nil { + return err + } + return nil + }, +} + +var UpdateWebsiteSSL = &gormigrate.Migration{ + ID: "20250819-update-website-ssl", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.WebsiteSSL{}); err != nil { + return err + } + return nil + }, +} + +var AddQuickJump = &gormigrate.Migration{ + ID: "20250901-add-quick-jump", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.QuickJump{}); err != nil { + return err + } + if err := tx.Create(&model.QuickJump{Name: "Website", Title: "menu.website", Recommend: 10, IsShow: true, Router: "/websites"}).Error; err != nil { + return err + } + if err := tx.Create(&model.QuickJump{Name: "Database", Title: "home.database", Recommend: 30, IsShow: true, Router: "/databases"}).Error; err != nil { + return err + } + if err := tx.Create(&model.QuickJump{Name: "Cronjob", Title: "menu.cronjob", Recommend: 50, IsShow: true, Router: "/cronjobs"}).Error; err != nil { + return err + } + if err := tx.Create(&model.QuickJump{Name: "AppInstalled", Title: "home.appInstalled", Recommend: 70, IsShow: true, Router: "/apps/installed"}).Error; err != nil { + return err + } + if err := tx.Create(&model.QuickJump{Name: "File", Detail: "/", Title: "home.quickDir", Recommend: 90, IsShow: false, Router: "/hosts/files"}).Error; err != nil { + return err + } + return nil + }, +} + +var UpdateMcpServerAddType = &gormigrate.Migration{ + ID: "20250904-update-mcp-server", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.McpServer{}); err != nil { + return err + } + if err := tx.Model(&model.McpServer{}).Where("1=1").Update("type", "npx").Error; err != nil { + return err + } + return nil + }, +} + +var InitLocalSSHConn = &gormigrate.Migration{ + ID: "20250905-init-local-ssh", + Migrate: func(tx *gorm.DB) error { + itemPath := "" + currentInfo, _ := user.Current() + if len(currentInfo.HomeDir) == 0 { + itemPath = "/root/.ssh/id_ed25519_1panel" + } else { + itemPath = path.Join(currentInfo.HomeDir, ".ssh/id_ed25519_1panel") + } + if _, err := os.Stat(itemPath); err != nil { + _ = service.NewISSHService().CreateRootCert(dto.RootCertOperate{EncryptionMode: "ed25519", Name: "id_ed25519_1panel", Description: "1Panel Terminal"}) + } + privateKey, _ := os.ReadFile(itemPath) + connWithKey := ssh.ConnInfo{ + Addr: "127.0.0.1", + User: "root", + Port: 22, + AuthMode: "key", + PrivateKey: privateKey, + } + if _, err := ssh.NewClient(connWithKey); err != nil { + return nil + } + var conn model.LocalConnInfo + _ = copier.Copy(&conn, &connWithKey) + conn.PrivateKey = string(privateKey) + conn.PassPhrase = "" + localConn, _ := json.Marshal(&conn) + connAfterEncrypt, _ := encrypt.StringEncrypt(string(localConn)) + if err := tx.Model(&model.Setting{}).Where("key = ?", "LocalSSHConn").Updates(map[string]interface{}{"value": connAfterEncrypt}).Error; err != nil { + return err + } + return nil + }, +} + +var InitLocalSSHShow = &gormigrate.Migration{ + ID: "20250908-init-local-ssh-show", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "LocalSSHConnShow", Value: constant.StatusEnable}).Error; err != nil { + return err + } + return nil + }, +} + +var InitRecordStatus = &gormigrate.Migration{ + ID: "20250910-init-record-status", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.BackupRecord{}); err != nil { + return err + } + if err := tx.Model(&model.BackupRecord{}).Where("1 == 1").Updates(map[string]interface{}{"status": constant.StatusSuccess}).Error; err != nil { + return err + } + return nil + }, +} + +var AddShowNameForQuickJump = &gormigrate.Migration{ + ID: "20250918-add-show-name-for-quick-jump", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.QuickJump{}) + }, +} + +var AddTimeoutForClam = &gormigrate.Migration{ + ID: "20250922-add-timeout-for-clam", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.Clam{}); err != nil { + return err + } + if err := tx.Model(&model.Clam{}).Where("1 == 1").Updates(map[string]interface{}{"timeout": 18000}).Error; err != nil { + return err + } + return nil + }, +} + +var UpdateCronjobSpec = &gormigrate.Migration{ + ID: "20250925-update-cronjob-spec", + Migrate: func(tx *gorm.DB) error { + var cronjobs []model.Cronjob + if err := tx.Where("1 == 1").Find(&cronjobs).Error; err != nil { + return err + } + for _, item := range cronjobs { + if !strings.Contains(item.Spec, ",") { + continue + } + if err := tx.Model(&model.Cronjob{}).Where("id = ?", item.ID).Updates( + map[string]interface{}{"spec": strings.ReplaceAll(item.Spec, ",", "&&")}).Error; err != nil { + return err + } + } + return nil + }, +} + +var UpdateWebsiteSSLAddColumn = &gormigrate.Migration{ + ID: "20250928-update-website-ssl", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.WebsiteSSL{}); err != nil { + return err + } + return nil + }, +} + +var AddTensorRTLLMModel = &gormigrate.Migration{ + ID: "20251018-add-tensorrt-llm-model", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.TensorRTLLM{}) + }, +} + +var UpdateMonitorInterval = &gormigrate.Migration{ + ID: "20251026-update-monitor-interval", + Migrate: func(tx *gorm.DB) error { + var monitorInterval model.Setting + if err := tx.Where("key = ?", "MonitorInterval").First(&monitorInterval).Error; err != nil { + return err + } + interval, _ := strconv.Atoi(monitorInterval.Value) + if interval == 0 { + interval = 300 + } + if err := tx.Model(&model.Setting{}). + Where("key = ?", "MonitorInterval"). + Updates(map[string]interface{}{"value": fmt.Sprintf("%v", interval*60)}). + Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "DefaultIO", Value: "all"}).Error; err != nil { + return err + } + return nil + }, +} + +var AddIptablesFilterRuleTable = &gormigrate.Migration{ + ID: "20251106-add-iptables-filter-rule-table", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.Firewall{}); err != nil { + return err + } + var firewalls []model.Firewall + _ = tx.Where("1 = 1").Find(&firewalls).Error + + firewallType := "" + client, err := firewall.NewFirewallClient() + if err == nil { + firewallType = client.Name() + } + for _, item := range firewalls { + if err := tx.Model(&model.Firewall{}). + Where("id = ?", item.ID). + Updates(map[string]interface{}{"dst_port": item.Port, "src_ip": item.Address, "firewall_type": firewallType}); err != nil { + global.LOG.Errorf("update firewall failed, err: %v", err) + } + } + return nil + }, +} + +var AddMonitorProcess = &gormigrate.Migration{ + ID: "20251030-add-monitor-process", + Migrate: func(tx *gorm.DB) error { + return global.MonitorDB.AutoMigrate(&model.MonitorBase{}) + }, +} + +var UpdateCronJob = &gormigrate.Migration{ + ID: "20251105-update-cronjob", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.Cronjob{}) + }, +} + +var UpdateTensorrtLLM = &gormigrate.Migration{ + ID: "20251110-update-tensorrt-llm", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.TensorRTLLM{}) + }, +} + +var AddCommonDescription = &gormigrate.Migration{ + ID: "20251117-add-common-description", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.CommonDescription{}) + }, +} + +var UpdateDatabase = &gormigrate.Migration{ + ID: "20251117-update-database", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.Database{}) + }, +} + +var AddGPUMonitor = &gormigrate.Migration{ + ID: "20251122-add-gpu-monitor", + Migrate: func(tx *gorm.DB) error { + return global.GPUMonitorDB.AutoMigrate(&model.MonitorGPU{}) + }, +} + +var UpdateDatabaseMysql = &gormigrate.Migration{ + ID: "20251126-update-database-mysql", + Migrate: func(tx *gorm.DB) error { + if err := tx.AutoMigrate(&model.DatabaseMysql{}); err != nil { + return err + } + var data []model.DatabaseMysql + _ = tx.Where("1 = 1").Find(&data).Error + for _, item := range data { + if len(item.Collation) == 0 { + collation := "" + switch item.Format { + case "utf8": + collation = "utf8_general_ci" + case "utf8mb4": + collation = "utf8mb4_general_ci" + case "gbk": + collation = "gbk_chinese_ci" + case "big5": + collation = "big5_chinese_ci" + default: + collation = "utf8mb4_general_ci" + } + _ = tx.Model(&model.DatabaseMysql{}).Where("id = ?", item.ID).Updates(map[string]interface{}{"collation": collation}).Error + } + } + return nil + }, +} + +var InitIptablesStatus = &gormigrate.Migration{ + ID: "20251201-init-iptables-status", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "IptablesStatus", Value: constant.StatusDisable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "IptablesForwardStatus", Value: constant.StatusDisable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "IptablesInputStatus", Value: constant.StatusDisable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "IptablesOutputStatus", Value: constant.StatusDisable}).Error; err != nil { + return err + } + return nil + }, +} + +var UpdateWebsite = &gormigrate.Migration{ + ID: "20251203-update-website", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.Website{}) + }, +} + +var AddisIPtoWebsiteSSL = &gormigrate.Migration{ + ID: "20251223-update-website-ssl", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.WebsiteSSL{}) + }, +} + +var InitPingStatus = &gormigrate.Migration{ + ID: "20251201-init-ping-status", + Migrate: func(tx *gorm.DB) error { + status := firewall.LoadPingStatus() + if err := tx.Create(&model.Setting{Key: "BanPing", Value: status}).Error; err != nil { + return err + } + return nil + }, +} + +var UpdateApp = &gormigrate.Migration{ + ID: "20251228-update-app", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.App{}) + }, +} + +var AddCronjobArgs = &gormigrate.Migration{ + ID: "20260106-add-cronjob-args", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.Cronjob{}) + }, +} + +var AddWebsiteAcmeAccountColumn = &gormigrate.Migration{ + ID: "20260110-add-website-acme-account", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate(&model.WebsiteAcmeAccount{}) + }, +} diff --git a/agent/init/router/router.go b/agent/init/router/router.go new file mode 100644 index 0000000..18f9dac --- /dev/null +++ b/agent/init/router/router.go @@ -0,0 +1,30 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/middleware" + rou "github.com/1Panel-dev/1Panel/agent/router" + "github.com/gin-gonic/gin" +) + +var ( + Router *gin.Engine +) + +func Routers() *gin.Engine { + Router = gin.Default() + Router.Use(i18n.UseI18n()) + + PrivateGroup := Router.Group("/api/v2") + if !global.IsMaster { + PrivateGroup.Use(middleware.Certificate()) + } + for _, router := range rou.RouterGroupApp { + router.InitRouter(PrivateGroup) + } + PrivateGroup.GET("/health/check", v2.ApiGroupApp.BaseApi.CheckHealth) + + return Router +} diff --git a/agent/init/validator/validator.go b/agent/init/validator/validator.go new file mode 100644 index 0000000..6a58af5 --- /dev/null +++ b/agent/init/validator/validator.go @@ -0,0 +1,57 @@ +package validator + +import ( + "unicode" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/re" + + "github.com/go-playground/validator/v10" +) + +func Init() { + validator := validator.New() + if err := validator.RegisterValidation("name", checkNamePattern); err != nil { + panic(err) + } + if err := validator.RegisterValidation("ip", checkIpPattern); err != nil { + panic(err) + } + if err := validator.RegisterValidation("password", checkPasswordPattern); err != nil { + panic(err) + } + global.VALID = validator +} + +func checkNamePattern(fl validator.FieldLevel) bool { + value := fl.Field().String() + return re.GetRegex(re.ValidatorNamePattern).MatchString(value) +} + +func checkIpPattern(fl validator.FieldLevel) bool { + value := fl.Field().String() + return re.GetRegex(re.ValidatorIPPattern).MatchString(value) +} + +func checkPasswordPattern(fl validator.FieldLevel) bool { + value := fl.Field().String() + if len(value) < 8 || len(value) > 30 { + return false + } + + hasNum := false + hasLetter := false + for _, r := range value { + if unicode.IsLetter(r) && !hasLetter { + hasLetter = true + } + if unicode.IsNumber(r) && !hasNum { + hasNum = true + } + if hasLetter && hasNum { + return true + } + } + + return false +} diff --git a/agent/init/viper/viper.go b/agent/init/viper/viper.go new file mode 100644 index 0000000..f1f77cd --- /dev/null +++ b/agent/init/viper/viper.go @@ -0,0 +1,64 @@ +package viper + +import ( + "bytes" + "fmt" + "path" + + "github.com/1Panel-dev/1Panel/agent/cmd/server/conf" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" +) + +func Init() { + mode := "" + fileOp := files.NewFileOp() + v := viper.NewWithOptions() + v.SetConfigType("yaml") + + config := global.ServerConfig{} + if err := yaml.Unmarshal(conf.AppYaml, &config); err != nil { + panic(err) + } + if config.Base.Mode != "" { + mode = config.Base.Mode + } + if mode == "dev" && fileOp.Stat("/opt/1panel/conf/app.yaml") { + v.SetConfigName("app") + v.AddConfigPath(path.Join("/opt/1panel/conf")) + if err := v.ReadInConfig(); err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + } else { + reader := bytes.NewReader(conf.AppYaml) + if err := v.ReadConfig(reader); err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + } + v.OnConfigChange(func(e fsnotify.Event) { + if err := v.Unmarshal(&global.CONF); err != nil { + panic(err) + } + }) + serverConfig := global.ServerConfig{} + if err := v.Unmarshal(&serverConfig); err != nil { + panic(err) + } + + global.CONF = serverConfig + + initBaseInfo() + global.Viper = v +} + +func initBaseInfo() { + nodeInfo, err := xpack.LoadNodeInfo(true) + if err != nil { + panic(err) + } + global.CONF.Base.InstallDir = nodeInfo.BaseDir +} diff --git a/agent/log/config.go b/agent/log/config.go new file mode 100644 index 0000000..22c1c27 --- /dev/null +++ b/agent/log/config.go @@ -0,0 +1,42 @@ +package log + +import ( + "errors" + "github.com/1Panel-dev/1Panel/agent/constant" + "io" + "os" + "path" +) + +var ( + BufferSize = 0x100000 + DefaultFileMode = os.FileMode(constant.DirPerm) + DefaultFileFlag = os.O_RDWR | os.O_CREATE | os.O_APPEND + ErrInvalidArgument = errors.New("error argument invalid") + QueueSize = 1024 + ErrClosed = errors.New("error write on close") +) + +type Config struct { + TimeTagFormat string + LogPath string + FileName string + LogSuffix string + MaxRemain int + RollingTimePattern string +} + +type Manager interface { + Fire() chan string + Close() +} + +type RollingWriter interface { + io.Writer + Close() error +} + +func FilePath(c *Config) (filepath string) { + filepath = path.Join(c.LogPath, c.FileName) + c.LogSuffix + return +} diff --git a/agent/log/dup_write_darwin.go b/agent/log/dup_write_darwin.go new file mode 100644 index 0000000..822d24e --- /dev/null +++ b/agent/log/dup_write_darwin.go @@ -0,0 +1,20 @@ +package log + +import ( + "golang.org/x/sys/unix" + "os" + "runtime" +) + +var stdErrFileHandler *os.File + +func dupWrite(file *os.File) error { + stdErrFileHandler = file + if err := unix.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil { + return err + } + runtime.SetFinalizer(stdErrFileHandler, func(fd *os.File) { + fd.Close() + }) + return nil +} diff --git a/agent/log/dup_write_linux.go b/agent/log/dup_write_linux.go new file mode 100644 index 0000000..822d24e --- /dev/null +++ b/agent/log/dup_write_linux.go @@ -0,0 +1,20 @@ +package log + +import ( + "golang.org/x/sys/unix" + "os" + "runtime" +) + +var stdErrFileHandler *os.File + +func dupWrite(file *os.File) error { + stdErrFileHandler = file + if err := unix.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil { + return err + } + runtime.SetFinalizer(stdErrFileHandler, func(fd *os.File) { + fd.Close() + }) + return nil +} diff --git a/agent/log/dup_write_windows.go b/agent/log/dup_write_windows.go new file mode 100644 index 0000000..4996cc1 --- /dev/null +++ b/agent/log/dup_write_windows.go @@ -0,0 +1,9 @@ +package log + +import ( + "os" +) + +func dupWrite(file *os.File) error { + return nil +} diff --git a/agent/log/manager.go b/agent/log/manager.go new file mode 100644 index 0000000..1f4f945 --- /dev/null +++ b/agent/log/manager.go @@ -0,0 +1,53 @@ +package log + +import ( + "github.com/robfig/cron/v3" + "path" + "sync" + "time" +) + +type manager struct { + startAt time.Time + fire chan string + cr *cron.Cron + context chan int + wg sync.WaitGroup + lock sync.Mutex +} + +func (m *manager) Fire() chan string { + return m.fire +} + +func (m *manager) Close() { + close(m.context) + m.cr.Stop() +} + +func NewManager(c *Config) (Manager, error) { + m := &manager{ + startAt: time.Now(), + cr: cron.New(), + fire: make(chan string), + context: make(chan int), + wg: sync.WaitGroup{}, + } + + if _, err := m.cr.AddFunc(c.RollingTimePattern, func() { + m.fire <- m.GenLogFileName(c) + }); err != nil { + return nil, err + } + m.cr.Start() + + return m, nil +} + +func (m *manager) GenLogFileName(c *Config) (filename string) { + m.lock.Lock() + filename = path.Join(c.LogPath, c.FileName+"-"+m.startAt.Format(c.TimeTagFormat)) + c.LogSuffix + m.startAt = time.Now() + m.lock.Unlock() + return +} diff --git a/agent/log/writer.go b/agent/log/writer.go new file mode 100644 index 0000000..bf4f4bb --- /dev/null +++ b/agent/log/writer.go @@ -0,0 +1,251 @@ +package log + +import ( + "log" + "os" + "path" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + "unsafe" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" +) + +type Writer struct { + m Manager + file *os.File + absPath string + fire chan string + cf *Config + rollingfilech chan string +} + +type AsynchronousWriter struct { + Writer + ctx chan int + queue chan []byte + errChan chan error + closed int32 + wg sync.WaitGroup +} + +func (w *AsynchronousWriter) Close() error { + if atomic.CompareAndSwapInt32(&w.closed, 0, 1) { + close(w.ctx) + w.onClose() + + func() { + defer func() { + if r := recover(); r != nil { + global.LOG.Error(r) + } + }() + w.m.Close() + }() + return w.file.Close() + } + return ErrClosed +} + +func (w *AsynchronousWriter) onClose() { + var err error + for { + select { + case b := <-w.queue: + if _, err = w.file.Write(b); err != nil { + select { + case w.errChan <- err: + default: + _asyncBufferPool.Put(&b) + return + } + } + _asyncBufferPool.Put(&b) + default: + return + } + } +} + +var _asyncBufferPool = sync.Pool{ + New: func() interface{} { + return make([]byte, BufferSize) + }, +} + +func NewWriterFromConfig(c *Config) (RollingWriter, error) { + if c.LogPath == "" || c.FileName == "" { + return nil, ErrInvalidArgument + } + if err := os.MkdirAll(c.LogPath, 0700); err != nil { + return nil, err + } + filepath := FilePath(c) + file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_APPEND, constant.FilePerm) + if err != nil { + return nil, err + } + if err := dupWrite(file); err != nil { + return nil, err + } + mng, err := NewManager(c) + if err != nil { + return nil, err + } + + var rollingWriter RollingWriter + writer := Writer{ + m: mng, + file: file, + absPath: filepath, + fire: mng.Fire(), + cf: c, + } + if c.MaxRemain > 0 { + writer.rollingfilech = make(chan string, c.MaxRemain) + dir, err := os.ReadDir(c.LogPath) + if err != nil { + mng.Close() + return nil, err + } + + files := make([]string, 0, 10) + for _, fi := range dir { + if fi.IsDir() { + continue + } + + fileName := c.FileName + if strings.Contains(fi.Name(), fileName) && strings.Contains(fi.Name(), c.LogSuffix) { + start := strings.Index(fi.Name(), "-") + end := strings.Index(fi.Name(), c.LogSuffix) + name := fi.Name() + if start > 0 && end > 0 { + _, err := time.Parse(c.TimeTagFormat, name[start+1:end]) + if err == nil { + files = append(files, fi.Name()) + } + } + } + } + sort.Slice(files, func(i, j int) bool { + t1Start := strings.Index(files[i], "-") + t1End := strings.Index(files[i], c.LogSuffix) + t2Start := strings.Index(files[i], "-") + t2End := strings.Index(files[i], c.LogSuffix) + t1, _ := time.Parse(c.TimeTagFormat, files[i][t1Start+1:t1End]) + t2, _ := time.Parse(c.TimeTagFormat, files[j][t2Start+1:t2End]) + return t1.Before(t2) + }) + + for _, file := range files { + retry: + select { + case writer.rollingfilech <- path.Join(c.LogPath, file): + default: + writer.DoRemove() + goto retry + } + } + } + + wr := &AsynchronousWriter{ + ctx: make(chan int), + queue: make(chan []byte, QueueSize), + errChan: make(chan error, QueueSize), + wg: sync.WaitGroup{}, + closed: 0, + Writer: writer, + } + + wr.wg.Add(1) + go wr.writer() + wr.wg.Wait() + rollingWriter = wr + + return rollingWriter, nil +} + +func (w *AsynchronousWriter) writer() { + var err error + w.wg.Done() + for { + select { + case filename := <-w.fire: + if err = w.Reopen(filename); err != nil && len(w.errChan) < cap(w.errChan) { + w.errChan <- err + } + case b := <-w.queue: + if _, err = w.file.Write(b); err != nil && len(w.errChan) < cap(w.errChan) { + w.errChan <- err + } + _asyncBufferPool.Put(&b) + case <-w.ctx: + return + } + } +} + +func (w *Writer) DoRemove() { + file := <-w.rollingfilech + if err := os.Remove(file); err != nil { + log.Println("error in remove log file", file, err) + } +} + +func (w *Writer) Write(b []byte) (int, error) { + var ok = false + for !ok { + select { + case filename := <-w.fire: + if err := w.Reopen(filename); err != nil { + return 0, err + } + default: + ok = true + } + } + + fp := atomic.LoadPointer((*unsafe.Pointer)(unsafe.Pointer(&w.file))) + file := (*os.File)(fp) + return file.Write(b) +} + +func (w *Writer) Reopen(file string) error { + fileInfo, err := w.file.Stat() + if err != nil { + return err + } + + if fileInfo.Size() == 0 { + return nil + } + + w.file.Close() + if err := os.Rename(w.absPath, file); err != nil { + return err + } + newFile, err := os.OpenFile(w.absPath, DefaultFileFlag, DefaultFileMode) + if err != nil { + return err + } + + w.file = newFile + + go func() { + if w.cf.MaxRemain > 0 { + retry: + select { + case w.rollingfilech <- file: + default: + w.DoRemove() + goto retry + } + } + }() + return nil +} diff --git a/agent/middleware/certificate.go b/agent/middleware/certificate.go new file mode 100644 index 0000000..1a96426 --- /dev/null +++ b/agent/middleware/certificate.go @@ -0,0 +1,54 @@ +package middleware + +import ( + "fmt" + "net" + "net/http" + "strings" + + "github.com/1Panel-dev/1Panel/agent/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "github.com/gin-gonic/gin" +) + +func Certificate() gin.HandlerFunc { + return func(c *gin.Context) { + if global.IsMaster { + c.Next() + return + } + if !xpack.ValidateCertificate(c) { + CloseDirectly(c) + return + } + conn := c.Request.Header.Get("Connection") + if conn == "Upgrade" { + c.Next() + return + } + masterProxyID := c.Request.Header.Get("Proxy-Id") + proxyID, err := cmd.RunDefaultWithStdoutBashC("cat /etc/1panel/.nodeProxyID") + if err == nil && len(proxyID) != 0 && strings.TrimSpace(proxyID) != strings.TrimSpace(masterProxyID) { + helper.InternalServer(c, fmt.Errorf("err proxy id")) + return + } + c.Next() + } +} + +func CloseDirectly(c *gin.Context) { + hijacker, ok := c.Writer.(http.Hijacker) + if !ok { + c.AbortWithStatus(http.StatusForbidden) + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + c.AbortWithStatus(http.StatusForbidden) + return + } + _ = conn.(*net.TCPConn).SetLinger(0) + conn.Close() +} diff --git a/agent/router/backup.go b/agent/router/backup.go new file mode 100644 index 0000000..ca8c16b --- /dev/null +++ b/agent/router/backup.go @@ -0,0 +1,38 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type BackupRouter struct{} + +func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) { + backupRouter := Router.Group("backups") + baseApi := v2.ApiGroupApp.BaseApi + { + backupRouter.GET("/check/:name", baseApi.CheckBackupUsed) + backupRouter.GET("/options", baseApi.LoadBackupOptions) + backupRouter.POST("/search", baseApi.SearchBackup) + + backupRouter.GET("/local", baseApi.GetLocalDir) + backupRouter.POST("/refresh/token", baseApi.RefreshToken) + backupRouter.POST("/buckets", baseApi.ListBuckets) + backupRouter.POST("", baseApi.CreateBackup) + backupRouter.POST("/conn/check", baseApi.CheckBackup) + backupRouter.POST("/del", baseApi.DeleteBackup) + backupRouter.POST("/update", baseApi.UpdateBackup) + + backupRouter.POST("/backup", baseApi.Backup) + backupRouter.POST("/recover", baseApi.Recover) + backupRouter.POST("/upload", baseApi.UploadForRecover) + backupRouter.POST("/recover/byupload", baseApi.RecoverByUpload) + backupRouter.POST("/search/files", baseApi.LoadFilesFromBackup) + backupRouter.POST("/record/search", baseApi.SearchBackupRecords) + backupRouter.POST("/record/size", baseApi.LoadBackupRecordSize) + backupRouter.POST("/record/search/bycronjob", baseApi.SearchBackupRecordsByCronjob) + backupRouter.POST("/record/download", baseApi.DownloadRecord) + backupRouter.POST("/record/del", baseApi.DeleteBackupRecord) + backupRouter.POST("/record/description/update", baseApi.UpdateRecordDescription) + } +} diff --git a/agent/router/common.go b/agent/router/common.go new file mode 100644 index 0000000..5ebcd5e --- /dev/null +++ b/agent/router/common.go @@ -0,0 +1,28 @@ +package router + +func commonGroups() []CommonRouter { + return []CommonRouter{ + &DashboardRouter{}, + &HostRouter{}, + &ContainerRouter{}, + &LogRouter{}, + &FileRouter{}, + &ToolboxRouter{}, + &CronjobRouter{}, + &BackupRouter{}, + &SettingRouter{}, + &AppRouter{}, + &WebsiteRouter{}, + &WebsiteDnsAccountRouter{}, + &WebsiteAcmeAccountRouter{}, + &WebsiteSSLRouter{}, + &DatabaseRouter{}, + &NginxRouter{}, + &RuntimeRouter{}, + &ProcessRouter{}, + &WebsiteCARouter{}, + &AIToolsRouter{}, + &GroupRouter{}, + &AlertRouter{}, + } +} diff --git a/agent/router/entry.go b/agent/router/entry.go new file mode 100644 index 0000000..49ed25f --- /dev/null +++ b/agent/router/entry.go @@ -0,0 +1,9 @@ +//go:build !xpack + +package router + +func RouterGroups() []CommonRouter { + return commonGroups() +} + +var RouterGroupApp = RouterGroups() diff --git a/agent/router/ro_ai.go b/agent/router/ro_ai.go new file mode 100644 index 0000000..47246c6 --- /dev/null +++ b/agent/router/ro_ai.go @@ -0,0 +1,43 @@ +package router + +import ( + v1 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type AIToolsRouter struct { +} + +func (a *AIToolsRouter) InitRouter(Router *gin.RouterGroup) { + aiToolsRouter := Router.Group("ai") + + baseApi := v1.ApiGroupApp.BaseApi + { + aiToolsRouter.POST("/ollama/close", baseApi.CloseOllamaModel) + aiToolsRouter.POST("/ollama/model", baseApi.CreateOllamaModel) + aiToolsRouter.POST("/ollama/model/recreate", baseApi.RecreateOllamaModel) + aiToolsRouter.POST("/ollama/model/search", baseApi.SearchOllamaModel) + aiToolsRouter.POST("/ollama/model/sync", baseApi.SyncOllamaModel) + aiToolsRouter.POST("/ollama/model/load", baseApi.LoadOllamaModelDetail) + aiToolsRouter.POST("/ollama/model/del", baseApi.DeleteOllamaModel) + aiToolsRouter.GET("/gpu/load", baseApi.LoadGpuInfo) + aiToolsRouter.POST("/domain/bind", baseApi.BindDomain) + aiToolsRouter.POST("/domain/get", baseApi.GetBindDomain) + aiToolsRouter.POST("/domain/update", baseApi.UpdateBindDomain) + + aiToolsRouter.POST("/mcp/search", baseApi.PageMcpServers) + aiToolsRouter.POST("/mcp/server", baseApi.CreateMcpServer) + aiToolsRouter.POST("/mcp/server/update", baseApi.UpdateMcpServer) + aiToolsRouter.POST("/mcp/server/del", baseApi.DeleteMcpServer) + aiToolsRouter.POST("/mcp/server/op", baseApi.OperateMcpServer) + aiToolsRouter.POST("/mcp/domain/bind", baseApi.BindMcpDomain) + aiToolsRouter.GET("/mcp/domain/get", baseApi.GetMcpBindDomain) + aiToolsRouter.POST("/mcp/domain/update", baseApi.UpdateMcpBindDomain) + + aiToolsRouter.POST("/tensorrt/search", baseApi.PageTensorRTLLMs) + aiToolsRouter.POST("/tensorrt/create", baseApi.CreateTensorRTLLM) + aiToolsRouter.POST("/tensorrt/update", baseApi.UpdateTensorRTLLM) + aiToolsRouter.POST("/tensorrt/delete", baseApi.DeleteTensorRTLLM) + aiToolsRouter.POST("/tensorrt/operate", baseApi.OperateTensorRTLLM) + } +} diff --git a/agent/router/ro_alert.go b/agent/router/ro_alert.go new file mode 100644 index 0000000..d4e2055 --- /dev/null +++ b/agent/router/ro_alert.go @@ -0,0 +1,31 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type AlertRouter struct { +} + +func (a *AlertRouter) InitRouter(Router *gin.RouterGroup) { + alertRouter := Router.Group("alert") + baseApi := v2.ApiGroupApp.BaseApi + { + alertRouter.POST("", baseApi.CreateAlert) + alertRouter.POST("/update", baseApi.UpdateAlert) + alertRouter.POST("/search", baseApi.PageAlert) + alertRouter.POST("/status", baseApi.UpdateAlertStatus) + alertRouter.POST("/del", baseApi.DeleteAlert) + alertRouter.GET("/disks/list", baseApi.GetDisks) + alertRouter.POST("/logs/search", baseApi.PageAlertLogs) + alertRouter.POST("/logs/clean", baseApi.CleanAlertLogs) + alertRouter.GET("/clams/list", baseApi.GetClams) + alertRouter.POST("/cronjob/list", baseApi.GetCronJobs) + + alertRouter.POST("/config/update", baseApi.UpdateAlertConfig) + alertRouter.POST("/config/info", baseApi.GetAlertConfig) + alertRouter.POST("/config/del", baseApi.DeleteAlertConfig) + alertRouter.POST("/config/test", baseApi.TestAlertConfig) + } +} diff --git a/agent/router/ro_app.go b/agent/router/ro_app.go new file mode 100644 index 0000000..3d9cc12 --- /dev/null +++ b/agent/router/ro_app.go @@ -0,0 +1,49 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type AppRouter struct { +} + +func (a *AppRouter) InitRouter(Router *gin.RouterGroup) { + appRouter := Router.Group("apps") + + baseApi := v2.ApiGroupApp.BaseApi + { + appRouter.POST("/sync/remote", baseApi.SyncApp) + appRouter.POST("/sync/local", baseApi.SyncLocalApp) + appRouter.GET("/checkupdate", baseApi.GetAppListUpdate) + appRouter.POST("/search", baseApi.SearchApp) + appRouter.GET("/:key", baseApi.GetApp) + appRouter.GET("/detail/:appId/:version/:type", baseApi.GetAppDetail) + appRouter.GET("/detail/node/:appKey/:version", baseApi.GetAppDetailForNode) + appRouter.GET("/details/:id", baseApi.GetAppDetailByID) + appRouter.POST("/install", baseApi.InstallApp) + appRouter.GET("/tags", baseApi.GetAppTags) + appRouter.GET("/icon/:key", baseApi.GetAppIcon) + + appRouter.POST("/installed/check", baseApi.CheckAppInstalled) + appRouter.POST("/installed/loadport", baseApi.LoadPort) + appRouter.POST("/installed/conninfo", baseApi.LoadConnInfo) + appRouter.GET("/installed/delete/check/:appInstallId", baseApi.DeleteCheck) + appRouter.POST("/installed/search", baseApi.SearchAppInstalled) + appRouter.GET("/installed/list", baseApi.ListAppInstalled) + appRouter.POST("/installed/op", baseApi.OperateInstalled) + appRouter.POST("/installed/sync", baseApi.SyncInstalled) + appRouter.POST("/installed/port/change", baseApi.ChangeAppPort) + appRouter.GET("/services/:key", baseApi.GetServices) + appRouter.POST("/installed/conf", baseApi.GetDefaultConfig) + appRouter.GET("/installed/params/:appInstallId", baseApi.GetParams) + appRouter.POST("/installed/params/update", baseApi.UpdateInstalled) + appRouter.POST("/installed/update/versions", baseApi.GetUpdateVersions) + appRouter.POST("/installed/config/update", baseApi.UpdateAppConfig) + appRouter.GET("/installed/info/:appInstallId", baseApi.GetAppInstallInfo) + + appRouter.POST("/installed/ignore", baseApi.IgnoreAppUpgrade) + appRouter.GET("/ignored/detail", baseApi.ListAppIgnored) + appRouter.POST("/ignored/cancel", baseApi.CancelIgnoreAppUpgrade) + } +} diff --git a/agent/router/ro_container.go b/agent/router/ro_container.go new file mode 100644 index 0000000..b64a8ea --- /dev/null +++ b/agent/router/ro_container.go @@ -0,0 +1,90 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type ContainerRouter struct{} + +func (s *ContainerRouter) InitRouter(Router *gin.RouterGroup) { + baRouter := Router.Group("containers") + baseApi := v2.ApiGroupApp.BaseApi + { + baRouter.GET("/exec", baseApi.ContainerWsSSH) + baRouter.GET("/stats/:id", baseApi.ContainerStats) + + baRouter.POST("", baseApi.ContainerCreate) + baRouter.POST("command", baseApi.ContainerCreateByCommand) + baRouter.POST("/update", baseApi.ContainerUpdate) + baRouter.POST("/upgrade", baseApi.ContainerUpgrade) + baRouter.POST("/info", baseApi.ContainerInfo) + baRouter.POST("/search", baseApi.SearchContainer) + baRouter.POST("/list", baseApi.ListContainer) + baRouter.POST("/list/byimage", baseApi.ListContainerByImage) + baRouter.GET("/status", baseApi.LoadContainerStatus) + baRouter.GET("/list/stats", baseApi.ContainerListStats) + baRouter.POST("/item/stats", baseApi.ContainerItemStats) + baRouter.GET("/search/log", baseApi.ContainerStreamLogs) + baRouter.POST("/download/log", baseApi.DownloadContainerLogs) + baRouter.GET("/limit", baseApi.LoadResourceLimit) + baRouter.POST("/clean/log", baseApi.CleanContainerLog) + baRouter.POST("/inspect", baseApi.Inspect) + baRouter.POST("/rename", baseApi.ContainerRename) + baRouter.POST("/commit", baseApi.ContainerCommit) + baRouter.POST("/operate", baseApi.ContainerOperation) + baRouter.POST("/prune", baseApi.ContainerPrune) + + baRouter.POST("/users", baseApi.LoadContainerUsers) + + baRouter.GET("/repo", baseApi.ListRepo) + baRouter.POST("/repo/status", baseApi.CheckRepoStatus) + baRouter.POST("/repo/search", baseApi.SearchRepo) + baRouter.POST("/repo/update", baseApi.UpdateRepo) + baRouter.POST("/repo", baseApi.CreateRepo) + baRouter.POST("/repo/del", baseApi.DeleteRepo) + + baRouter.POST("/compose/search", baseApi.SearchCompose) + baRouter.POST("/compose", baseApi.CreateCompose) + baRouter.POST("/compose/test", baseApi.TestCompose) + baRouter.POST("/compose/operate", baseApi.OperatorCompose) + baRouter.POST("/compose/clean/log", baseApi.CleanComposeLog) + baRouter.POST("/compose/update", baseApi.ComposeUpdate) + + baRouter.GET("/template", baseApi.ListComposeTemplate) + baRouter.POST("/template/search", baseApi.SearchComposeTemplate) + baRouter.POST("/template/update", baseApi.UpdateComposeTemplate) + baRouter.POST("/template/batch", baseApi.BatchComposeTemplate) + baRouter.POST("/template", baseApi.CreateComposeTemplate) + baRouter.POST("/template/del", baseApi.DeleteComposeTemplate) + + baRouter.GET("/image", baseApi.ListImage) + baRouter.GET("/image/all", baseApi.ListAllImage) + baRouter.POST("/image/search", baseApi.SearchImage) + baRouter.POST("/image/pull", baseApi.ImagePull) + baRouter.POST("/image/push", baseApi.ImagePush) + baRouter.POST("/image/save", baseApi.ImageSave) + baRouter.POST("/image/load", baseApi.ImageLoad) + baRouter.POST("/image/remove", baseApi.ImageRemove) + baRouter.POST("/image/tag", baseApi.ImageTag) + baRouter.POST("/image/build", baseApi.ImageBuild) + + baRouter.GET("/network", baseApi.ListNetwork) + baRouter.POST("/network/del", baseApi.DeleteNetwork) + baRouter.POST("/network/search", baseApi.SearchNetwork) + baRouter.POST("/network", baseApi.CreateNetwork) + baRouter.GET("/volume", baseApi.ListVolume) + baRouter.POST("/volume/del", baseApi.DeleteVolume) + baRouter.POST("/volume/search", baseApi.SearchVolume) + baRouter.POST("/volume", baseApi.CreateVolume) + + baRouter.GET("/daemonjson", baseApi.LoadDaemonJson) + baRouter.GET("/daemonjson/file", baseApi.LoadDaemonJsonFile) + baRouter.GET("/docker/status", baseApi.LoadDockerStatus) + baRouter.POST("/docker/operate", baseApi.OperateDocker) + baRouter.POST("/daemonjson/update", baseApi.UpdateDaemonJson) + baRouter.POST("/logoption/update", baseApi.UpdateLogOption) + baRouter.POST("/ipv6option/update", baseApi.UpdateIpv6Option) + baRouter.POST("/daemonjson/update/byfile", baseApi.UpdateDaemonJsonByFile) + } +} diff --git a/agent/router/ro_cronjob.go b/agent/router/ro_cronjob.go new file mode 100644 index 0000000..576baa8 --- /dev/null +++ b/agent/router/ro_cronjob.go @@ -0,0 +1,31 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type CronjobRouter struct{} + +func (s *CronjobRouter) InitRouter(Router *gin.RouterGroup) { + cmdRouter := Router.Group("cronjobs") + baseApi := v2.ApiGroupApp.BaseApi + { + cmdRouter.POST("", baseApi.CreateCronjob) + cmdRouter.POST("/next", baseApi.LoadNextHandle) + cmdRouter.POST("/import", baseApi.ImportCronjob) + cmdRouter.POST("/export", baseApi.ExportCronjob) + cmdRouter.POST("/load/info", baseApi.LoadCronjobInfo) + cmdRouter.GET("/script/options", baseApi.LoadScriptOptions) + cmdRouter.POST("/del", baseApi.DeleteCronjob) + cmdRouter.POST("/stop", baseApi.StopCronJob) + cmdRouter.POST("/update", baseApi.UpdateCronjob) + cmdRouter.POST("/group/update", baseApi.UpdateCronjobGroup) + cmdRouter.POST("/status", baseApi.UpdateCronjobStatus) + cmdRouter.POST("/handle", baseApi.HandleOnce) + cmdRouter.POST("/search", baseApi.SearchCronjob) + cmdRouter.POST("/search/records", baseApi.SearchJobRecords) + cmdRouter.POST("/records/log", baseApi.LoadRecordLog) + cmdRouter.POST("/records/clean", baseApi.CleanRecord) + } +} diff --git a/agent/router/ro_dashboard.go b/agent/router/ro_dashboard.go new file mode 100644 index 0000000..959bd53 --- /dev/null +++ b/agent/router/ro_dashboard.go @@ -0,0 +1,27 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type DashboardRouter struct{} + +func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) { + cmdRouter := Router.Group("dashboard") + baseApi := v2.ApiGroupApp.BaseApi + { + cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo) + cmdRouter.GET("/quick/option", baseApi.LoadQuickOption) + cmdRouter.POST("/quick/change", baseApi.UpdateQuickJump) + cmdRouter.GET("/app/launcher", baseApi.LoadAppLauncher) + cmdRouter.POST("/app/launcher/show", baseApi.UpdateAppLauncher) + cmdRouter.POST("/app/launcher/option", baseApi.LoadAppLauncherOption) + cmdRouter.GET("/base/:ioOption/:netOption", baseApi.LoadDashboardBaseInfo) + cmdRouter.GET("/current/node", baseApi.LoadCurrentInfoForNode) + cmdRouter.GET("/current/:ioOption/:netOption", baseApi.LoadDashboardCurrentInfo) + cmdRouter.GET("/current/top/cpu", baseApi.LoadDashboardTopCPU) + cmdRouter.GET("/current/top/mem", baseApi.LoadDashboardTopMem) + cmdRouter.POST("/system/restart/:operation", baseApi.SystemRestart) + } +} diff --git a/agent/router/ro_database.go b/agent/router/ro_database.go new file mode 100644 index 0000000..8db3bd3 --- /dev/null +++ b/agent/router/ro_database.go @@ -0,0 +1,62 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type DatabaseRouter struct{} + +func (s *DatabaseRouter) InitRouter(Router *gin.RouterGroup) { + cmdRouter := Router.Group("databases") + baseApi := v2.ApiGroupApp.BaseApi + { + cmdRouter.POST("/common/info", baseApi.LoadDBBaseInfo) + cmdRouter.POST("/common/load/file", baseApi.LoadDBFile) + cmdRouter.POST("/common/update/conf", baseApi.UpdateDBConfByFile) + + cmdRouter.POST("", baseApi.CreateMysql) + cmdRouter.POST("/bind", baseApi.BindUser) + cmdRouter.POST("load", baseApi.LoadDBFromRemote) + cmdRouter.POST("/change/access", baseApi.ChangeMysqlAccess) + cmdRouter.POST("/change/password", baseApi.ChangeMysqlPassword) + cmdRouter.POST("/del/check", baseApi.DeleteCheckMysql) + cmdRouter.POST("/del", baseApi.DeleteMysql) + cmdRouter.POST("/description/update", baseApi.UpdateMysqlDescription) + cmdRouter.POST("/variables/update", baseApi.UpdateMysqlVariables) + cmdRouter.POST("/search", baseApi.SearchMysql) + cmdRouter.POST("/variables", baseApi.LoadVariables) + cmdRouter.POST("/status", baseApi.LoadStatus) + cmdRouter.POST("/remote", baseApi.LoadRemoteAccess) + cmdRouter.POST("/format/options", baseApi.ListDBFormatCollationOptions) + + cmdRouter.POST("/redis/persistence/conf", baseApi.LoadPersistenceConf) + cmdRouter.POST("/redis/status", baseApi.LoadRedisStatus) + cmdRouter.POST("/redis/conf", baseApi.LoadRedisConf) + cmdRouter.GET("/redis/check", baseApi.CheckHasCli) + cmdRouter.POST("/redis/install/cli", baseApi.InstallCli) + cmdRouter.POST("/redis/password", baseApi.ChangeRedisPassword) + cmdRouter.POST("/redis/conf/update", baseApi.UpdateRedisConf) + cmdRouter.POST("/redis/persistence/update", baseApi.UpdateRedisPersistenceConf) + + cmdRouter.POST("/db/check", baseApi.CheckDatabase) + cmdRouter.POST("/db", baseApi.CreateDatabase) + cmdRouter.GET("/db/:name", baseApi.GetDatabase) + cmdRouter.GET("/db/list/:type", baseApi.ListDatabase) + cmdRouter.GET("/db/item/:type", baseApi.LoadDatabaseItems) + cmdRouter.POST("/db/update", baseApi.UpdateDatabase) + cmdRouter.POST("/db/search", baseApi.SearchDatabase) + cmdRouter.POST("/db/del/check", baseApi.DeleteCheckDatabase) + cmdRouter.POST("/db/del", baseApi.DeleteDatabase) + + cmdRouter.POST("/pg", baseApi.CreatePostgresql) + cmdRouter.POST("/pg/search", baseApi.SearchPostgresql) + cmdRouter.POST("/pg/:database/load", baseApi.LoadPostgresqlDBFromRemote) + cmdRouter.POST("/pg/bind", baseApi.BindPostgresqlUser) + cmdRouter.POST("/pg/del/check", baseApi.DeleteCheckPostgresql) + cmdRouter.POST("/pg/del", baseApi.DeletePostgresql) + cmdRouter.POST("/pg/privileges", baseApi.ChangePostgresqlPrivileges) + cmdRouter.POST("/pg/password", baseApi.ChangePostgresqlPassword) + cmdRouter.POST("/pg/description", baseApi.UpdatePostgresqlDescription) + } +} diff --git a/agent/router/ro_file.go b/agent/router/ro_file.go new file mode 100644 index 0000000..6c68243 --- /dev/null +++ b/agent/router/ro_file.go @@ -0,0 +1,61 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type FileRouter struct { +} + +func (f *FileRouter) InitRouter(Router *gin.RouterGroup) { + fileRouter := Router.Group("files") + baseApi := v2.ApiGroupApp.BaseApi + { + fileRouter.POST("/search", baseApi.ListFiles) + fileRouter.POST("/upload/search", baseApi.SearchUploadWithPage) + fileRouter.POST("/tree", baseApi.GetFileTree) + fileRouter.POST("", baseApi.CreateFile) + fileRouter.POST("/del", baseApi.DeleteFile) + fileRouter.POST("/batch/del", baseApi.BatchDeleteFile) + fileRouter.POST("/mode", baseApi.ChangeFileMode) + fileRouter.POST("/owner", baseApi.ChangeFileOwner) + fileRouter.POST("/compress", baseApi.CompressFile) + fileRouter.POST("/decompress", baseApi.DeCompressFile) + fileRouter.POST("/content", baseApi.GetContent) + fileRouter.POST("/preview", baseApi.PreviewContent) + fileRouter.POST("/save", baseApi.SaveContent) + fileRouter.POST("/remarks", baseApi.BatchGetFileRemarks) + fileRouter.POST("/remark", baseApi.SetFileRemark) + fileRouter.POST("/check", baseApi.CheckFile) + fileRouter.POST("/batch/check", baseApi.BatchCheckFiles) + fileRouter.POST("/upload", baseApi.UploadFiles) + fileRouter.POST("/chunkupload", baseApi.UploadChunkFiles) + fileRouter.POST("/rename", baseApi.ChangeFileName) + fileRouter.POST("/wget", baseApi.WgetFile) + fileRouter.POST("/move", baseApi.MoveFile) + fileRouter.GET("/download", baseApi.Download) + fileRouter.POST("/chunkdownload", baseApi.DownloadChunkFiles) + fileRouter.POST("/size", baseApi.Size) + fileRouter.POST("/depth/size", baseApi.DepthDirSize) + fileRouter.GET("/wget/process", baseApi.WgetProcess) + fileRouter.GET("/wget/process/keys", baseApi.ProcessKeys) + fileRouter.POST("/read", baseApi.ReadFileByLine) + fileRouter.POST("/batch/role", baseApi.BatchChangeModeAndOwner) + + fileRouter.POST("/recycle/search", baseApi.SearchRecycleBinFile) + fileRouter.POST("/recycle/reduce", baseApi.ReduceRecycleBinFile) + fileRouter.POST("/recycle/clear", baseApi.ClearRecycleBinFile) + fileRouter.GET("/recycle/status", baseApi.GetRecycleStatus) + + fileRouter.POST("/favorite/search", baseApi.SearchFavorite) + fileRouter.POST("/favorite", baseApi.CreateFavorite) + fileRouter.POST("/favorite/del", baseApi.DeleteFavorite) + + fileRouter.GET("/path/:type", baseApi.GetPathByType) + fileRouter.POST("/mount", baseApi.GetHostMount) + fileRouter.POST("/user/group", baseApi.GetUsersAndGroups) + fileRouter.POST("/convert", baseApi.ConvertFile) + fileRouter.POST("/convert/log", baseApi.ConvertLog) + } +} diff --git a/agent/router/ro_group.go b/agent/router/ro_group.go new file mode 100644 index 0000000..257ed7c --- /dev/null +++ b/agent/router/ro_group.go @@ -0,0 +1,20 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type GroupRouter struct { +} + +func (a *GroupRouter) InitRouter(Router *gin.RouterGroup) { + groupRouter := Router.Group("groups") + baseApi := v2.ApiGroupApp.BaseApi + { + groupRouter.POST("", baseApi.CreateGroup) + groupRouter.POST("/del", baseApi.DeleteGroup) + groupRouter.POST("/update", baseApi.UpdateGroup) + groupRouter.POST("/search", baseApi.ListGroup) + } +} diff --git a/agent/router/ro_host.go b/agent/router/ro_host.go new file mode 100644 index 0000000..2b70bf6 --- /dev/null +++ b/agent/router/ro_host.go @@ -0,0 +1,71 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type HostRouter struct{} + +func (s *HostRouter) InitRouter(Router *gin.RouterGroup) { + hostRouter := Router.Group("hosts") + baseApi := v2.ApiGroupApp.BaseApi + { + hostRouter.POST("/firewall/base", baseApi.LoadFirewallBaseInfo) + hostRouter.POST("/firewall/search", baseApi.SearchFirewallRule) + hostRouter.POST("/firewall/operate", baseApi.OperateFirewall) + hostRouter.POST("/firewall/port", baseApi.OperatePortRule) + hostRouter.POST("/firewall/forward", baseApi.OperateForwardRule) + hostRouter.POST("/firewall/ip", baseApi.OperateIPRule) + hostRouter.POST("/firewall/batch", baseApi.BatchOperateRule) + hostRouter.POST("/firewall/update/port", baseApi.UpdatePortRule) + hostRouter.POST("/firewall/update/addr", baseApi.UpdateAddrRule) + hostRouter.POST("/firewall/update/description", baseApi.UpdateFirewallDescription) + + hostRouter.POST("/firewall/filter/rule/search", baseApi.SearchFilterRules) + hostRouter.POST("/firewall/filter/rule/operate", baseApi.OperateFilterRule) + hostRouter.POST("/firewall/filter/rule/batch", baseApi.BatchOperateFilterRule) + hostRouter.POST("/firewall/filter/operate", baseApi.OperateFilterChain) + hostRouter.POST("/firewall/filter/chain/status", baseApi.LoadChainStatus) + + hostRouter.POST("/monitor/search", baseApi.LoadMonitor) + hostRouter.POST("/monitor/gpu/search", baseApi.LoadGPUMonitor) + hostRouter.POST("/monitor/clean", baseApi.CleanMonitor) + hostRouter.GET("/monitor/gpuoptions", baseApi.GetCPUOptions) + hostRouter.GET("/monitor/netoptions", baseApi.GetNetworkOptions) + hostRouter.GET("/monitor/iooptions", baseApi.GetIOOptions) + hostRouter.GET("/monitor/setting", baseApi.LoadMonitorSetting) + hostRouter.POST("/monitor/setting/update", baseApi.UpdateMonitorSetting) + + hostRouter.POST("/ssh/search", baseApi.GetSSHInfo) + hostRouter.POST("/ssh/update", baseApi.UpdateSSH) + hostRouter.POST("/ssh/log", baseApi.LoadSSHLogs) + hostRouter.POST("/ssh/log/export", baseApi.ExportSSHLogs) + hostRouter.POST("/ssh/operate", baseApi.OperateSSH) + hostRouter.POST("/ssh/file", baseApi.LoadSSHFile) + hostRouter.POST("/ssh/file/update", baseApi.UpdateSSHByFile) + + hostRouter.POST("/ssh/cert", baseApi.CreateRootCert) + hostRouter.POST("/ssh/cert/update", baseApi.EditRootCert) + hostRouter.POST("/ssh/cert/sync", baseApi.SyncRootCert) + hostRouter.POST("/ssh/cert/search", baseApi.SearchRootCert) + hostRouter.POST("/ssh/cert/delete", baseApi.DeleteRootCert) + + hostRouter.POST("/tool", baseApi.GetToolStatus) + hostRouter.POST("/tool/init", baseApi.InitToolConfig) + hostRouter.POST("/tool/operate", baseApi.OperateTool) + hostRouter.POST("/tool/config", baseApi.OperateToolConfig) + hostRouter.POST("/tool/supervisor/process", baseApi.OperateProcess) + hostRouter.GET("/tool/supervisor/process", baseApi.GetProcess) + hostRouter.POST("/tool/supervisor/process/file", baseApi.GetProcessFile) + + hostRouter.GET("/terminal", baseApi.WsSSH) + + hostRouter.GET("/disks", baseApi.GetCompleteDiskInfo) + hostRouter.POST("/disks/partition", baseApi.PartitionDisk) + hostRouter.POST("/disks/mount", baseApi.MountDisk) + hostRouter.POST("/disks/unmount", baseApi.UnmountDisk) + + hostRouter.GET("/components/:name", baseApi.CheckComponentExistence) + } +} diff --git a/agent/router/ro_log.go b/agent/router/ro_log.go new file mode 100644 index 0000000..e6be0ff --- /dev/null +++ b/agent/router/ro_log.go @@ -0,0 +1,18 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type LogRouter struct{} + +func (s *LogRouter) InitRouter(Router *gin.RouterGroup) { + operationRouter := Router.Group("logs") + baseApi := v2.ApiGroupApp.BaseApi + { + operationRouter.GET("/system/files", baseApi.GetSystemFiles) + operationRouter.POST("/tasks/search", baseApi.PageTasks) + operationRouter.GET("/tasks/executing/count", baseApi.CountExecutingTasks) + } +} diff --git a/agent/router/ro_nginx.go b/agent/router/ro_nginx.go new file mode 100644 index 0000000..c0599e6 --- /dev/null +++ b/agent/router/ro_nginx.go @@ -0,0 +1,27 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type NginxRouter struct { +} + +func (a *NginxRouter) InitRouter(Router *gin.RouterGroup) { + groupRouter := Router.Group("openresty") + + baseApi := v2.ApiGroupApp.BaseApi + { + groupRouter.GET("", baseApi.GetNginx) + groupRouter.POST("/scope", baseApi.GetNginxConfigByScope) + groupRouter.POST("/update", baseApi.UpdateNginxConfigByScope) + groupRouter.GET("/status", baseApi.GetNginxStatus) + groupRouter.POST("/file", baseApi.UpdateNginxFile) + groupRouter.POST("/build", baseApi.BuildNginx) + groupRouter.POST("/modules/update", baseApi.UpdateNginxModule) + groupRouter.GET("/modules", baseApi.GetNginxModules) + groupRouter.POST("/https", baseApi.OperateDefaultHTTPs) + groupRouter.GET("/https", baseApi.GetDefaultHTTPsStatus) + } +} diff --git a/agent/router/ro_process.go b/agent/router/ro_process.go new file mode 100644 index 0000000..685de15 --- /dev/null +++ b/agent/router/ro_process.go @@ -0,0 +1,20 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type ProcessRouter struct { +} + +func (f *ProcessRouter) InitRouter(Router *gin.RouterGroup) { + processRouter := Router.Group("process") + baseApi := v2.ApiGroupApp.BaseApi + { + processRouter.GET("/ws", baseApi.ProcessWs) + processRouter.POST("/stop", baseApi.StopProcess) + processRouter.POST("/listening", baseApi.GetListeningProcess) + processRouter.GET("/:pid", baseApi.GetProcessInfoByPID) + } +} diff --git a/agent/router/ro_router.go b/agent/router/ro_router.go new file mode 100644 index 0000000..58a52dc --- /dev/null +++ b/agent/router/ro_router.go @@ -0,0 +1,7 @@ +package router + +import "github.com/gin-gonic/gin" + +type CommonRouter interface { + InitRouter(Router *gin.RouterGroup) +} diff --git a/agent/router/ro_runtime.go b/agent/router/ro_runtime.go new file mode 100644 index 0000000..7f03a8a --- /dev/null +++ b/agent/router/ro_runtime.go @@ -0,0 +1,55 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type RuntimeRouter struct { +} + +func (r *RuntimeRouter) InitRouter(Router *gin.RouterGroup) { + groupRouter := Router.Group("runtimes") + + baseApi := v2.ApiGroupApp.BaseApi + { + groupRouter.GET("/installed/delete/check/:id", baseApi.DeleteRuntimeCheck) + groupRouter.POST("/search", baseApi.SearchRuntimes) + groupRouter.POST("", baseApi.CreateRuntime) + groupRouter.POST("/del", baseApi.DeleteRuntime) + groupRouter.POST("/update", baseApi.UpdateRuntime) + groupRouter.GET("/:id", baseApi.GetRuntime) + groupRouter.POST("/sync", baseApi.SyncStatus) + + groupRouter.POST("/node/package", baseApi.GetNodePackageRunScript) + groupRouter.POST("/operate", baseApi.OperateRuntime) + groupRouter.POST("/node/modules", baseApi.GetNodeModules) + groupRouter.POST("/node/modules/operate", baseApi.OperateNodeModules) + + groupRouter.POST("/php/extensions/search", baseApi.PagePHPExtensions) + groupRouter.POST("/php/extensions", baseApi.CreatePHPExtensions) + groupRouter.POST("/php/extensions/update", baseApi.UpdatePHPExtensions) + groupRouter.POST("/php/extensions/del", baseApi.DeletePHPExtensions) + + groupRouter.GET("/php/:id/extensions", baseApi.GetRuntimeExtension) + groupRouter.POST("/php/extensions/install", baseApi.InstallPHPExtension) + groupRouter.POST("/php/extensions/uninstall", baseApi.UnInstallPHPExtension) + + groupRouter.GET("/php/config/:id", baseApi.GetPHPConfig) + groupRouter.POST("/php/config", baseApi.UpdatePHPConfig) + groupRouter.POST("/php/update", baseApi.UpdatePHPFile) + groupRouter.POST("/php/file", baseApi.GetPHPConfigFile) + groupRouter.POST("/php/fpm/config", baseApi.UpdateFPMConfig) + groupRouter.GET("/php/fpm/config/:id", baseApi.GetFPMConfig) + groupRouter.GET("/php/fpm/status/:id", baseApi.GetFPMStatus) + + groupRouter.POST("/php/container/update", baseApi.UpdatePHPContainer) + groupRouter.GET("/php/container/:id", baseApi.GetPHPContainerConfig) + + groupRouter.GET("/supervisor/process/:id", baseApi.GetSupervisorProcess) + groupRouter.POST("/supervisor/process", baseApi.OperateSupervisorProcess) + groupRouter.POST("/supervisor/process/file", baseApi.OperateSupervisorProcessFile) + + groupRouter.POST("/remark", baseApi.UpdateRuntimeRemark) + } +} diff --git a/agent/router/ro_setting.go b/agent/router/ro_setting.go new file mode 100644 index 0000000..fea8ede --- /dev/null +++ b/agent/router/ro_setting.go @@ -0,0 +1,39 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type SettingRouter struct{} + +func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { + settingRouter := Router.Group("settings") + baseApi := v2.ApiGroupApp.BaseApi + { + settingRouter.POST("/search", baseApi.GetSettingInfo) + settingRouter.GET("/search/available", baseApi.GetSystemAvailable) + settingRouter.POST("/update", baseApi.UpdateSetting) + settingRouter.GET("/get/:key", baseApi.GetSettingByKey) + + settingRouter.POST("/description/save", baseApi.SaveDescription) + + settingRouter.GET("/snapshot/load", baseApi.LoadSnapshotData) + settingRouter.POST("/snapshot", baseApi.CreateSnapshot) + settingRouter.POST("/snapshot/recreate", baseApi.RecreateSnapshot) + settingRouter.POST("/snapshot/search", baseApi.SearchSnapshot) + settingRouter.POST("/snapshot/import", baseApi.ImportSnapshot) + settingRouter.POST("/snapshot/del", baseApi.DeleteSnapshot) + settingRouter.POST("/snapshot/recover", baseApi.RecoverSnapshot) + settingRouter.POST("/snapshot/rollback", baseApi.RollbackSnapshot) + settingRouter.POST("/snapshot/description/update", baseApi.UpdateSnapDescription) + + settingRouter.GET("/basedir", baseApi.LoadBaseDir) + + settingRouter.POST("/ssh/check", baseApi.CheckLocalConn) + settingRouter.GET("/ssh/conn", baseApi.LoadLocalConn) + settingRouter.POST("/ssh/default", baseApi.SetDefaultIsConn) + settingRouter.POST("/ssh", baseApi.SaveLocalConn) + settingRouter.POST("/ssh/check/info", baseApi.CheckLocalConnByInfo) + } +} diff --git a/agent/router/ro_toolbox.go b/agent/router/ro_toolbox.go new file mode 100644 index 0000000..06693e8 --- /dev/null +++ b/agent/router/ro_toolbox.go @@ -0,0 +1,58 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type ToolboxRouter struct{} + +func (s *ToolboxRouter) InitRouter(Router *gin.RouterGroup) { + toolboxRouter := Router.Group("toolbox") + baseApi := v2.ApiGroupApp.BaseApi + { + toolboxRouter.POST("/device/base", baseApi.LoadDeviceBaseInfo) + toolboxRouter.GET("/device/users", baseApi.LoadUsers) + toolboxRouter.GET("/device/zone/options", baseApi.LoadTimeOption) + toolboxRouter.POST("/device/update/conf", baseApi.UpdateDeviceConf) + toolboxRouter.POST("/device/update/host", baseApi.UpdateDeviceHost) + toolboxRouter.POST("/device/update/passwd", baseApi.UpdateDevicePasswd) + toolboxRouter.POST("/device/update/swap", baseApi.UpdateDeviceSwap) + toolboxRouter.POST("/device/update/byconf", baseApi.UpdateDeviceByFile) + toolboxRouter.POST("/device/check/dns", baseApi.CheckDNS) + toolboxRouter.POST("/device/conf", baseApi.LoadDeviceConf) + + toolboxRouter.POST("/scan", baseApi.ScanSystem) + toolboxRouter.POST("/clean", baseApi.SystemClean) + + toolboxRouter.GET("/fail2ban/base", baseApi.LoadFail2BanBaseInfo) + toolboxRouter.GET("/fail2ban/load/conf", baseApi.LoadFail2BanConf) + toolboxRouter.POST("/fail2ban/search", baseApi.SearchFail2Ban) + toolboxRouter.POST("/fail2ban/operate", baseApi.OperateFail2Ban) + toolboxRouter.POST("/fail2ban/operate/sshd", baseApi.OperateSSHD) + toolboxRouter.POST("/fail2ban/update", baseApi.UpdateFail2BanConf) + toolboxRouter.POST("/fail2ban/update/byconf", baseApi.UpdateFail2BanConfByFile) + + toolboxRouter.GET("/ftp/base", baseApi.LoadFtpBaseInfo) + toolboxRouter.POST("/ftp/log/search", baseApi.LoadFtpLogInfo) + toolboxRouter.POST("/ftp/operate", baseApi.OperateFtp) + toolboxRouter.POST("/ftp/search", baseApi.SearchFtp) + toolboxRouter.POST("/ftp", baseApi.CreateFtp) + toolboxRouter.POST("/ftp/update", baseApi.UpdateFtp) + toolboxRouter.POST("/ftp/del", baseApi.DeleteFtp) + toolboxRouter.POST("/ftp/sync", baseApi.SyncFtp) + + toolboxRouter.POST("/clam/search", baseApi.SearchClam) + toolboxRouter.POST("/clam/record/search", baseApi.SearchClamRecord) + toolboxRouter.POST("/clam/record/clean", baseApi.CleanClamRecord) + toolboxRouter.POST("/clam/file/search", baseApi.SearchClamFile) + toolboxRouter.POST("/clam/file/update", baseApi.UpdateFile) + toolboxRouter.POST("/clam", baseApi.CreateClam) + toolboxRouter.POST("/clam/base", baseApi.LoadClamBaseInfo) + toolboxRouter.POST("/clam/operate", baseApi.OperateClam) + toolboxRouter.POST("/clam/update", baseApi.UpdateClam) + toolboxRouter.POST("/clam/status/update", baseApi.UpdateClamStatus) + toolboxRouter.POST("/clam/del", baseApi.DeleteClam) + toolboxRouter.POST("/clam/handle", baseApi.HandleClamScan) + } +} diff --git a/agent/router/ro_website.go b/agent/router/ro_website.go new file mode 100644 index 0000000..6e2c177 --- /dev/null +++ b/agent/router/ro_website.go @@ -0,0 +1,100 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type WebsiteRouter struct { +} + +func (a *WebsiteRouter) InitRouter(Router *gin.RouterGroup) { + websiteRouter := Router.Group("websites") + + baseApi := v2.ApiGroupApp.BaseApi + { + websiteRouter.POST("/search", baseApi.PageWebsite) + websiteRouter.GET("/list", baseApi.GetWebsites) + websiteRouter.POST("", baseApi.CreateWebsite) + websiteRouter.POST("/operate", baseApi.OpWebsite) + websiteRouter.POST("/log", baseApi.OpWebsiteLog) + websiteRouter.POST("/check", baseApi.CreateWebsiteCheck) + websiteRouter.POST("/options", baseApi.GetWebsiteOptions) + websiteRouter.POST("/update", baseApi.UpdateWebsite) + websiteRouter.GET("/:id", baseApi.GetWebsite) + websiteRouter.POST("/del", baseApi.DeleteWebsite) + websiteRouter.POST("/default/server", baseApi.ChangeDefaultServer) + websiteRouter.POST("/group/change", baseApi.ChangeWebsiteGroup) + + websiteRouter.POST("/batch/operate", baseApi.BatchOpWebsites) + websiteRouter.POST("/batch/group", baseApi.BatchSetWebsiteGroup) + websiteRouter.POST("/batch/ssl", baseApi.BatchSetHttps) + + websiteRouter.GET("/domains/:websiteId", baseApi.GetWebDomains) + websiteRouter.POST("/domains/del", baseApi.DeleteWebDomain) + websiteRouter.POST("/domains", baseApi.CreateWebDomain) + websiteRouter.POST("/domains/update", baseApi.UpdateWebDomain) + + websiteRouter.GET("/:id/config/:type", baseApi.GetWebsiteNginx) + websiteRouter.POST("/config", baseApi.GetNginxConfig) + websiteRouter.POST("/config/update", baseApi.UpdateNginxConfig) + websiteRouter.POST("/nginx/update", baseApi.UpdateWebsiteNginxConfig) + + websiteRouter.GET("/:id/https", baseApi.GetHTTPSConfig) + websiteRouter.POST("/:id/https", baseApi.UpdateHTTPSConfig) + + websiteRouter.POST("/rewrite", baseApi.GetRewriteConfig) + websiteRouter.POST("/rewrite/update", baseApi.UpdateRewriteConfig) + websiteRouter.POST("/rewrite/custom", baseApi.OperateCustomRewrite) + websiteRouter.GET("/rewrite/custom", baseApi.ListCustomRewrite) + + websiteRouter.POST("/dir/update", baseApi.UpdateSiteDir) + websiteRouter.POST("/dir/permission", baseApi.UpdateSiteDirPermission) + websiteRouter.POST("/dir", baseApi.GetDirConfig) + + websiteRouter.POST("/proxies", baseApi.GetProxyConfig) + websiteRouter.POST("/proxies/update", baseApi.UpdateProxyConfig) + websiteRouter.POST("/proxies/file", baseApi.UpdateProxyConfigFile) + websiteRouter.POST("/proxy/config", baseApi.UpdateProxyCache) + websiteRouter.GET("/proxy/config/:id", baseApi.GetProxyCache) + websiteRouter.POST("/proxy/clear", baseApi.ClearProxyCache) + + websiteRouter.POST("/auths", baseApi.GetAuthConfig) + websiteRouter.POST("/auths/update", baseApi.UpdateAuthConfig) + websiteRouter.POST("/auths/path", baseApi.GetPathAuthConfig) + websiteRouter.POST("/auths/path/update", baseApi.UpdatePathAuthConfig) + + websiteRouter.GET("/cors/:id", baseApi.GetCORSConfig) + websiteRouter.POST("/cors/update", baseApi.UpdateCORSConfig) + + websiteRouter.POST("/leech", baseApi.GetAntiLeech) + websiteRouter.POST("/leech/update", baseApi.UpdateAntiLeech) + + websiteRouter.POST("/redirect/update", baseApi.UpdateRedirectConfig) + websiteRouter.POST("/redirect", baseApi.GetRedirectConfig) + websiteRouter.POST("/redirect/file", baseApi.UpdateRedirectConfigFile) + + websiteRouter.GET("/default/html/:type", baseApi.GetDefaultHtml) + websiteRouter.POST("/default/html/update", baseApi.UpdateDefaultHtml) + + websiteRouter.GET("/:id/lbs", baseApi.GetLoadBalances) + websiteRouter.POST("/lbs/create", baseApi.CreateLoadBalance) + websiteRouter.POST("/lbs/del", baseApi.DeleteLoadBalance) + websiteRouter.POST("/lbs/update", baseApi.UpdateLoadBalance) + websiteRouter.POST("/lbs/file", baseApi.UpdateLoadBalanceFile) + + websiteRouter.POST("/php/version", baseApi.ChangePHPVersion) + + websiteRouter.POST("/realip/config", baseApi.SetRealIPConfig) + websiteRouter.GET("/realip/config/:id", baseApi.GetRealIPConfig) + + websiteRouter.GET("/resource/:id", baseApi.GetWebsiteResource) + websiteRouter.GET("/databases", baseApi.GetWebsiteDatabase) + websiteRouter.POST("/databases", baseApi.ChangeWebsiteDatabase) + + websiteRouter.POST("/crosssite", baseApi.OperateCrossSiteAccess) + + websiteRouter.POST("/exec/composer", baseApi.ExecComposer) + websiteRouter.POST("/stream/update", baseApi.UpdateStreamConfig) + } +} diff --git a/agent/router/ro_website_acme_account.go b/agent/router/ro_website_acme_account.go new file mode 100644 index 0000000..d764c4e --- /dev/null +++ b/agent/router/ro_website_acme_account.go @@ -0,0 +1,21 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type WebsiteAcmeAccountRouter struct { +} + +func (a *WebsiteAcmeAccountRouter) InitRouter(Router *gin.RouterGroup) { + groupRouter := Router.Group("websites/acme") + + baseApi := v2.ApiGroupApp.BaseApi + { + groupRouter.POST("/search", baseApi.PageWebsiteAcmeAccount) + groupRouter.POST("", baseApi.CreateWebsiteAcmeAccount) + groupRouter.POST("/del", baseApi.DeleteWebsiteAcmeAccount) + groupRouter.POST("/update", baseApi.UpdateWebsiteAcmeAccount) + } +} diff --git a/agent/router/ro_website_ca.go b/agent/router/ro_website_ca.go new file mode 100644 index 0000000..e86585a --- /dev/null +++ b/agent/router/ro_website_ca.go @@ -0,0 +1,24 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type WebsiteCARouter struct { +} + +func (a *WebsiteCARouter) InitRouter(Router *gin.RouterGroup) { + groupRouter := Router.Group("websites/ca") + + baseApi := v2.ApiGroupApp.BaseApi + { + groupRouter.POST("/search", baseApi.PageWebsiteCA) + groupRouter.POST("", baseApi.CreateWebsiteCA) + groupRouter.POST("/del", baseApi.DeleteWebsiteCA) + groupRouter.POST("/obtain", baseApi.ObtainWebsiteCA) + groupRouter.POST("/renew", baseApi.RenewWebsiteCA) + groupRouter.GET("/:id", baseApi.GetWebsiteCA) + groupRouter.POST("/download", baseApi.DownloadCAFile) + } +} diff --git a/agent/router/ro_website_dns_account.go b/agent/router/ro_website_dns_account.go new file mode 100644 index 0000000..80c008c --- /dev/null +++ b/agent/router/ro_website_dns_account.go @@ -0,0 +1,21 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type WebsiteDnsAccountRouter struct { +} + +func (a *WebsiteDnsAccountRouter) InitRouter(Router *gin.RouterGroup) { + groupRouter := Router.Group("websites/dns") + + baseApi := v2.ApiGroupApp.BaseApi + { + groupRouter.POST("/search", baseApi.PageWebsiteDnsAccount) + groupRouter.POST("", baseApi.CreateWebsiteDnsAccount) + groupRouter.POST("/update", baseApi.UpdateWebsiteDnsAccount) + groupRouter.POST("/del", baseApi.DeleteWebsiteDnsAccount) + } +} diff --git a/agent/router/ro_website_ssl.go b/agent/router/ro_website_ssl.go new file mode 100644 index 0000000..420b704 --- /dev/null +++ b/agent/router/ro_website_ssl.go @@ -0,0 +1,29 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/agent/app/api/v2" + "github.com/gin-gonic/gin" +) + +type WebsiteSSLRouter struct { +} + +func (a *WebsiteSSLRouter) InitRouter(Router *gin.RouterGroup) { + groupRouter := Router.Group("websites/ssl") + + baseApi := v2.ApiGroupApp.BaseApi + { + groupRouter.POST("/search", baseApi.PageWebsiteSSL) + groupRouter.POST("", baseApi.CreateWebsiteSSL) + groupRouter.POST("/resolve", baseApi.GetDNSResolve) + groupRouter.POST("/del", baseApi.DeleteWebsiteSSL) + groupRouter.GET("/website/:websiteId", baseApi.GetWebsiteSSLByWebsiteId) + groupRouter.GET("/:id", baseApi.GetWebsiteSSLById) + groupRouter.POST("/update", baseApi.UpdateWebsiteSSL) + groupRouter.POST("/upload", baseApi.UploadWebsiteSSL) + groupRouter.POST("/obtain", baseApi.ApplyWebsiteSSL) + groupRouter.POST("/download", baseApi.DownloadWebsiteSSL) + groupRouter.POST("/import", baseApi.ImportMasterSSL) + groupRouter.POST("/upload/file", baseApi.UploadSSLFile) + } +} diff --git a/agent/server/init.go b/agent/server/init.go new file mode 100644 index 0000000..b8ed30b --- /dev/null +++ b/agent/server/init.go @@ -0,0 +1,6 @@ +//go:build !xpack + +package server + +func InitOthers() { +} diff --git a/agent/server/server.go b/agent/server/server.go new file mode 100644 index 0000000..66d395f --- /dev/null +++ b/agent/server/server.go @@ -0,0 +1,110 @@ +package server + +import ( + "crypto/tls" + "crypto/x509" + "fmt" + "net" + "net/http" + "os" + + "github.com/gin-gonic/gin" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/cron" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/init/app" + "github.com/1Panel-dev/1Panel/agent/init/business" + "github.com/1Panel-dev/1Panel/agent/init/cache" + "github.com/1Panel-dev/1Panel/agent/init/db" + "github.com/1Panel-dev/1Panel/agent/init/dir" + "github.com/1Panel-dev/1Panel/agent/init/firewall" + "github.com/1Panel-dev/1Panel/agent/init/hook" + "github.com/1Panel-dev/1Panel/agent/init/lang" + "github.com/1Panel-dev/1Panel/agent/init/log" + "github.com/1Panel-dev/1Panel/agent/init/migration" + "github.com/1Panel-dev/1Panel/agent/init/router" + "github.com/1Panel-dev/1Panel/agent/init/validator" + "github.com/1Panel-dev/1Panel/agent/init/viper" + "github.com/1Panel-dev/1Panel/agent/utils/encrypt" + "github.com/1Panel-dev/1Panel/agent/utils/re" +) + +func Start() { + re.Init() + viper.Init() + dir.Init() + log.Init() + db.Init() + migration.Init() + i18n.Init() + cache.Init() + app.Init() + lang.Init() + validator.Init() + cron.Run() + hook.Init() + go firewall.Init() + InitOthers() + + rootRouter := router.Routers() + + server := &http.Server{ + Handler: rootRouter, + } + + if global.CONF.Base.Mode != "stable" { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + + if global.IsMaster { + _ = os.Remove("/etc/1panel/agent.sock") + _ = os.Mkdir("/etc/1panel", constant.DirPerm) + listener, err := net.Listen("unix", "/etc/1panel/agent.sock") + if err != nil { + panic(err) + } + business.Init() + _ = server.Serve(listener) + return + } else { + server.Addr = fmt.Sprintf("0.0.0.0:%s", global.CONF.Base.Port) + settingRepo := repo.NewISettingRepo() + certItem, err := settingRepo.Get(settingRepo.WithByKey("ServerCrt")) + if err != nil { + panic(err) + } + cert, _ := encrypt.StringDecrypt(certItem.Value) + keyItem, err := settingRepo.Get(settingRepo.WithByKey("ServerKey")) + if err != nil { + panic(err) + } + key, _ := encrypt.StringDecrypt(keyItem.Value) + tlsCert, err := tls.X509KeyPair([]byte(cert), []byte(key)) + if err != nil { + fmt.Printf("failed to load X.509 key pair: %s\n", err) + return + } + + server.TLSConfig = &tls.Config{ + Certificates: []tls.Certificate{tlsCert}, + ClientAuth: tls.RequireAndVerifyClientCert, + } + caItem, _ := settingRepo.GetValueByKey("RootCrt") + if len(caItem) != 0 { + caCertPool := x509.NewCertPool() + rootCrt, _ := encrypt.StringDecrypt(caItem) + caCertPool.AppendCertsFromPEM([]byte(rootCrt)) + server.TLSConfig.ClientCAs = caCertPool + } + business.Init() + global.LOG.Infof("listen at https://0.0.0.0:%s", global.CONF.Base.Port) + if err := server.ListenAndServeTLS("", ""); err != nil { + panic(err) + } + } +} diff --git a/agent/utils/ai_tools/gpu/common/gpu_info.go b/agent/utils/ai_tools/gpu/common/gpu_info.go new file mode 100644 index 0000000..3d827eb --- /dev/null +++ b/agent/utils/ai_tools/gpu/common/gpu_info.go @@ -0,0 +1,37 @@ +package common + +type GpuInfo struct { + CudaVersion string `json:"cudaVersion"` + DriverVersion string `json:"driverVersion"` + Type string `json:"type"` + + GPUs []GPU `json:"gpu"` +} + +type GPU struct { + Index uint `json:"index"` + ProductName string `json:"productName"` + PersistenceMode string `json:"persistenceMode"` + BusID string `json:"busID"` + DisplayActive string `json:"displayActive"` + ECC string `json:"ecc"` + FanSpeed string `json:"fanSpeed"` + + Temperature string `json:"temperature"` + PerformanceState string `json:"performanceState"` + PowerDraw string `json:"powerDraw"` + MaxPowerLimit string `json:"maxPowerLimit"` + MemUsed string `json:"memUsed"` + MemTotal string `json:"memTotal"` + GPUUtil string `json:"gpuUtil"` + ComputeMode string `json:"computeMode"` + MigMode string `json:"migMode"` + Processes []Process `json:"processes"` +} + +type Process struct { + Pid string `json:"pid"` + Type string `json:"type"` + ProcessName string `json:"processName"` + UsedMemory string `json:"usedMemory"` +} diff --git a/agent/utils/ai_tools/gpu/gpu.go b/agent/utils/ai_tools/gpu/gpu.go new file mode 100644 index 0000000..7bed744 --- /dev/null +++ b/agent/utils/ai_tools/gpu/gpu.go @@ -0,0 +1,63 @@ +package gpu + +import ( + "bytes" + _ "embed" + "encoding/xml" + "errors" + "fmt" + "io" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common" + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/schema" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type NvidiaSMI struct{} + +func New() (bool, NvidiaSMI) { + return cmd.Which("nvidia-smi"), NvidiaSMI{} +} + +func (n NvidiaSMI) LoadGpuInfo() (*common.GpuInfo, error) { + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) + itemData, err := cmdMgr.RunWithStdoutBashC("nvidia-smi -q -x") + if err != nil { + return nil, fmt.Errorf("calling nvidia-smi failed, %v", err) + } + data := []byte(itemData) + version := "v11" + + buf := bytes.NewBuffer(data) + decoder := xml.NewDecoder(buf) + for { + token, err := decoder.Token() + if err != nil { + if errors.Is(err, io.EOF) { + break + } + return nil, fmt.Errorf("reading token failed: %w", err) + } + d, ok := token.(xml.Directive) + if !ok { + continue + } + directive := string(d) + if !strings.HasPrefix(directive, "DOCTYPE") { + continue + } + parts := strings.Split(directive, " ") + s := strings.Trim(parts[len(parts)-1], "\" ") + if strings.HasPrefix(s, "nvsmi_device_") && strings.HasSuffix(s, ".dtd") { + version = strings.TrimSuffix(strings.TrimPrefix(s, "nvsmi_device_"), ".dtd") + } else { + global.LOG.Debugf("Cannot find schema version in %q", directive) + } + break + } + + return schema.Parse(data, version) +} diff --git a/agent/utils/ai_tools/gpu/schema/parser.go b/agent/utils/ai_tools/gpu/schema/parser.go new file mode 100644 index 0000000..10efe1c --- /dev/null +++ b/agent/utils/ai_tools/gpu/schema/parser.go @@ -0,0 +1,63 @@ +package schema + +import ( + "encoding/xml" + + "github.com/1Panel-dev/1Panel/agent/utils/ai_tools/gpu/common" +) + +func Parse(buf []byte, version string) (*common.GpuInfo, error) { + var ( + s smi + info common.GpuInfo + ) + if err := xml.Unmarshal(buf, &s); err != nil { + return nil, err + } + + info.Type = "nvidia" + info.CudaVersion = s.CudaVersion + info.DriverVersion = s.DriverVersion + if len(s.Gpu) == 0 { + return &info, nil + } + for i := 0; i < len(s.Gpu); i++ { + var gpuItem common.GPU + gpuItem.Index = uint(i) + gpuItem.ProductName = s.Gpu[i].ProductName + gpuItem.PersistenceMode = s.Gpu[i].PersistenceMode + gpuItem.BusID = s.Gpu[i].ID + gpuItem.DisplayActive = s.Gpu[i].DisplayActive + gpuItem.ECC = s.Gpu[i].EccErrors.Volatile.DramUncorrectable + gpuItem.FanSpeed = s.Gpu[i].FanSpeed + + gpuItem.Temperature = s.Gpu[i].Temperature.GpuTemp + gpuItem.PerformanceState = s.Gpu[i].PerformanceState + if version == "v12" || version == "v13" { + gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.PowerDraw + if len(gpuItem.PowerDraw) == 0 { + gpuItem.PowerDraw = s.Gpu[i].GpuPowerReadings.InstantPowerDraw + } + gpuItem.MaxPowerLimit = s.Gpu[i].GpuPowerReadings.CurrentPowerLimit + } else { + gpuItem.PowerDraw = s.Gpu[i].PowerReadings.PowerDraw + gpuItem.MaxPowerLimit = s.Gpu[i].PowerReadings.MaxPowerLimit + } + gpuItem.MemUsed = s.Gpu[i].FbMemoryUsage.Used + gpuItem.MemTotal = s.Gpu[i].FbMemoryUsage.Total + gpuItem.GPUUtil = s.Gpu[i].Utilization.GpuUtil + gpuItem.ComputeMode = s.Gpu[i].ComputeMode + gpuItem.MigMode = s.Gpu[i].MigMode.CurrentMig + + for _, process := range s.Gpu[i].Processes.ProcessInfo { + gpuItem.Processes = append(gpuItem.Processes, common.Process{ + Pid: process.Pid, + Type: process.Type, + ProcessName: process.ProcessName, + UsedMemory: process.UsedMemory, + }) + } + info.GPUs = append(info.GPUs, gpuItem) + } + return &info, nil +} diff --git a/agent/utils/ai_tools/gpu/schema/types.go b/agent/utils/ai_tools/gpu/schema/types.go new file mode 100644 index 0000000..7d50324 --- /dev/null +++ b/agent/utils/ai_tools/gpu/schema/types.go @@ -0,0 +1,295 @@ +package schema + +type smi struct { + AttachedGpus string `xml:"attached_gpus"` + CudaVersion string `xml:"cuda_version"` + DriverVersion string `xml:"driver_version"` + Gpu []struct { + ID string `xml:"id,attr"` + AccountedProcesses struct{} `xml:"accounted_processes"` + AccountingMode string `xml:"accounting_mode"` + AccountingModeBufferSize string `xml:"accounting_mode_buffer_size"` + AddressingMode string `xml:"addressing_mode"` + ApplicationsClocks struct { + GraphicsClock string `xml:"graphics_clock"` + MemClock string `xml:"mem_clock"` + } `xml:"applications_clocks"` + Bar1MemoryUsage struct { + Free string `xml:"free"` + Total string `xml:"total"` + Used string `xml:"used"` + } `xml:"bar1_memory_usage"` + BoardID string `xml:"board_id"` + BoardPartNumber string `xml:"board_part_number"` + CcProtectedMemoryUsage struct { + Free string `xml:"free"` + Total string `xml:"total"` + Used string `xml:"used"` + } `xml:"cc_protected_memory_usage"` + ClockPolicy struct { + AutoBoost string `xml:"auto_boost"` + AutoBoostDefault string `xml:"auto_boost_default"` + } `xml:"clock_policy"` + Clocks struct { + GraphicsClock string `xml:"graphics_clock"` + MemClock string `xml:"mem_clock"` + SmClock string `xml:"sm_clock"` + VideoClock string `xml:"video_clock"` + } `xml:"clocks"` + ClocksEventReasons struct { + ClocksEventReasonApplicationsClocksSetting string `xml:"clocks_event_reason_applications_clocks_setting"` + ClocksEventReasonDisplayClocksSetting string `xml:"clocks_event_reason_display_clocks_setting"` + ClocksEventReasonGpuIdle string `xml:"clocks_event_reason_gpu_idle"` + ClocksEventReasonHwPowerBrakeSlowdown string `xml:"clocks_event_reason_hw_power_brake_slowdown"` + ClocksEventReasonHwSlowdown string `xml:"clocks_event_reason_hw_slowdown"` + ClocksEventReasonHwThermalSlowdown string `xml:"clocks_event_reason_hw_thermal_slowdown"` + ClocksEventReasonSwPowerCap string `xml:"clocks_event_reason_sw_power_cap"` + ClocksEventReasonSwThermalSlowdown string `xml:"clocks_event_reason_sw_thermal_slowdown"` + ClocksEventReasonSyncBoost string `xml:"clocks_event_reason_sync_boost"` + } `xml:"clocks_event_reasons"` + ComputeMode string `xml:"compute_mode"` + DefaultApplicationsClocks struct { + GraphicsClock string `xml:"graphics_clock"` + MemClock string `xml:"mem_clock"` + } `xml:"default_applications_clocks"` + DeferredClocks struct { + MemClock string `xml:"mem_clock"` + } `xml:"deferred_clocks"` + DisplayActive string `xml:"display_active"` + DisplayMode string `xml:"display_mode"` + DriverModel struct { + CurrentDm string `xml:"current_dm"` + PendingDm string `xml:"pending_dm"` + } `xml:"driver_model"` + EccErrors struct { + Aggregate struct { + DramCorrectable string `xml:"dram_correctable"` + DramUncorrectable string `xml:"dram_uncorrectable"` + SramCorrectable string `xml:"sram_correctable"` + SramUncorrectable string `xml:"sram_uncorrectable"` + } `xml:"aggregate"` + Volatile struct { + DramCorrectable string `xml:"dram_correctable"` + DramUncorrectable string `xml:"dram_uncorrectable"` + SramCorrectable string `xml:"sram_correctable"` + SramUncorrectable string `xml:"sram_uncorrectable"` + } `xml:"volatile"` + } `xml:"ecc_errors"` + EccMode struct { + CurrentEcc string `xml:"current_ecc"` + PendingEcc string `xml:"pending_ecc"` + } `xml:"ecc_mode"` + EncoderStats struct { + AverageFps string `xml:"average_fps"` + AverageLatency string `xml:"average_latency"` + SessionCount string `xml:"session_count"` + } `xml:"encoder_stats"` + Fabric struct { + State string `xml:"state"` + Status string `xml:"status"` + } `xml:"fabric"` + FanSpeed string `xml:"fan_speed"` + FbMemoryUsage struct { + Free string `xml:"free"` + Reserved string `xml:"reserved"` + Total string `xml:"total"` + Used string `xml:"used"` + } `xml:"fb_memory_usage"` + FbcStats struct { + AverageFps string `xml:"average_fps"` + AverageLatency string `xml:"average_latency"` + SessionCount string `xml:"session_count"` + } `xml:"fbc_stats"` + GpuFruPartNumber string `xml:"gpu_fru_part_number"` + GpuModuleID string `xml:"gpu_module_id"` + GpuOperationMode struct { + CurrentGom string `xml:"current_gom"` + PendingGom string `xml:"pending_gom"` + } `xml:"gpu_operation_mode"` + GpuPartNumber string `xml:"gpu_part_number"` + GpuPowerReadings struct { + InstantPowerDraw string `xml:"instant_power_draw"` + CurrentPowerLimit string `xml:"current_power_limit"` + DefaultPowerLimit string `xml:"default_power_limit"` + MaxPowerLimit string `xml:"max_power_limit"` + MinPowerLimit string `xml:"min_power_limit"` + PowerDraw string `xml:"power_draw"` + PowerState string `xml:"power_state"` + RequestedPowerLimit string `xml:"requested_power_limit"` + } `xml:"gpu_power_readings"` + GpuResetStatus struct { + DrainAndResetRecommended string `xml:"drain_and_reset_recommended"` + ResetRequired string `xml:"reset_required"` + } `xml:"gpu_reset_status"` + GpuVirtualizationMode struct { + HostVgpuMode string `xml:"host_vgpu_mode"` + VirtualizationMode string `xml:"virtualization_mode"` + } `xml:"gpu_virtualization_mode"` + GspFirmwareVersion string `xml:"gsp_firmware_version"` + Ibmnpu struct { + RelaxedOrderingMode string `xml:"relaxed_ordering_mode"` + } `xml:"ibmnpu"` + InforomVersion struct { + EccObject string `xml:"ecc_object"` + ImgVersion string `xml:"img_version"` + OemObject string `xml:"oem_object"` + PwrObject string `xml:"pwr_object"` + } `xml:"inforom_version"` + MaxClocks struct { + GraphicsClock string `xml:"graphics_clock"` + MemClock string `xml:"mem_clock"` + SmClock string `xml:"sm_clock"` + VideoClock string `xml:"video_clock"` + } `xml:"max_clocks"` + MaxCustomerBoostClocks struct { + GraphicsClock string `xml:"graphics_clock"` + } `xml:"max_customer_boost_clocks"` + MigDevices struct { + MigDevice []struct { + Index string `xml:"index"` + GpuInstanceID string `xml:"gpu_instance_id"` + ComputeInstanceID string `xml:"compute_instance_id"` + EccErrorCount struct { + Text string `xml:",chardata" json:"text"` + VolatileCount struct { + SramUncorrectable string `xml:"sram_uncorrectable"` + } `xml:"volatile_count" json:"volatile_count"` + } `xml:"ecc_error_count" json:"ecc_error_count"` + FbMemoryUsage struct { + Total string `xml:"total"` + Reserved string `xml:"reserved"` + Used string `xml:"used"` + Free string `xml:"free"` + } `xml:"fb_memory_usage" json:"fb_memory_usage"` + Bar1MemoryUsage struct { + Total string `xml:"total"` + Used string `xml:"used"` + Free string `xml:"free"` + } `xml:"bar1_memory_usage" json:"bar1_memory_usage"` + } `xml:"mig_device" json:"mig_device"` + } `xml:"mig_devices" json:"mig_devices"` + MigMode struct { + CurrentMig string `xml:"current_mig"` + PendingMig string `xml:"pending_mig"` + } `xml:"mig_mode"` + MinorNumber string `xml:"minor_number"` + ModulePowerReadings struct { + CurrentPowerLimit string `xml:"current_power_limit"` + DefaultPowerLimit string `xml:"default_power_limit"` + MaxPowerLimit string `xml:"max_power_limit"` + MinPowerLimit string `xml:"min_power_limit"` + PowerDraw string `xml:"power_draw"` + PowerState string `xml:"power_state"` + RequestedPowerLimit string `xml:"requested_power_limit"` + } `xml:"module_power_readings"` + MultigpuBoard string `xml:"multigpu_board"` + Pci struct { + AtomicCapsInbound string `xml:"atomic_caps_inbound"` + AtomicCapsOutbound string `xml:"atomic_caps_outbound"` + PciBridgeChip struct { + BridgeChipFw string `xml:"bridge_chip_fw"` + BridgeChipType string `xml:"bridge_chip_type"` + } `xml:"pci_bridge_chip"` + PciBus string `xml:"pci_bus"` + PciBusID string `xml:"pci_bus_id"` + PciDevice string `xml:"pci_device"` + PciDeviceID string `xml:"pci_device_id"` + PciDomain string `xml:"pci_domain"` + PciGpuLinkInfo struct { + LinkWidths struct { + CurrentLinkWidth string `xml:"current_link_width"` + MaxLinkWidth string `xml:"max_link_width"` + } `xml:"link_widths"` + PcieGen struct { + CurrentLinkGen string `xml:"current_link_gen"` + DeviceCurrentLinkGen string `xml:"device_current_link_gen"` + MaxDeviceLinkGen string `xml:"max_device_link_gen"` + MaxHostLinkGen string `xml:"max_host_link_gen"` + MaxLinkGen string `xml:"max_link_gen"` + } `xml:"pcie_gen"` + } `xml:"pci_gpu_link_info"` + PciSubSystemID string `xml:"pci_sub_system_id"` + ReplayCounter string `xml:"replay_counter"` + ReplayRolloverCounter string `xml:"replay_rollover_counter"` + RxUtil string `xml:"rx_util"` + TxUtil string `xml:"tx_util"` + } `xml:"pci"` + PerformanceState string `xml:"performance_state"` + PersistenceMode string `xml:"persistence_mode"` + PowerReadings struct { + PowerState string `xml:"power_state"` + PowerManagement string `xml:"power_management"` + PowerDraw string `xml:"power_draw"` + PowerLimit string `xml:"power_limit"` + DefaultPowerLimit string `xml:"default_power_limit"` + EnforcedPowerLimit string `xml:"enforced_power_limit"` + MinPowerLimit string `xml:"min_power_limit"` + MaxPowerLimit string `xml:"max_power_limit"` + } `xml:"power_readings"` + Processes struct { + ProcessInfo []struct { + Pid string `xml:"pid"` + Type string `xml:"type"` + ProcessName string `xml:"process_name"` + UsedMemory string `xml:"used_memory"` + } `xml:"process_info"` + } `xml:"processes"` + ProductArchitecture string `xml:"product_architecture"` + ProductBrand string `xml:"product_brand"` + ProductName string `xml:"product_name"` + RemappedRows struct { + // Manually added + Correctable string `xml:"remapped_row_corr"` + Uncorrectable string `xml:"remapped_row_unc"` + Pending string `xml:"remapped_row_pending"` + Failure string `xml:"remapped_row_failure"` + } `xml:"remapped_rows"` + RetiredPages struct { + DoubleBitRetirement struct { + RetiredCount string `xml:"retired_count"` + RetiredPagelist string `xml:"retired_pagelist"` + } `xml:"double_bit_retirement"` + MultipleSingleBitRetirement struct { + RetiredCount string `xml:"retired_count"` + RetiredPagelist string `xml:"retired_pagelist"` + } `xml:"multiple_single_bit_retirement"` + PendingBlacklist string `xml:"pending_blacklist"` + PendingRetirement string `xml:"pending_retirement"` + } `xml:"retired_pages"` + Serial string `xml:"serial"` + SupportedClocks struct { + SupportedMemClock []struct { + SupportedGraphicsClock []string `xml:"supported_graphics_clock"` + Value string `xml:"value"` + } `xml:"supported_mem_clock"` + } `xml:"supported_clocks"` + SupportedGpuTargetTemp struct { + GpuTargetTempMax string `xml:"gpu_target_temp_max"` + GpuTargetTempMin string `xml:"gpu_target_temp_min"` + } `xml:"supported_gpu_target_temp"` + Temperature struct { + GpuTargetTemperature string `xml:"gpu_target_temperature"` + GpuTemp string `xml:"gpu_temp"` + GpuTempMaxGpuThreshold string `xml:"gpu_temp_max_gpu_threshold"` + GpuTempMaxMemThreshold string `xml:"gpu_temp_max_mem_threshold"` + GpuTempMaxThreshold string `xml:"gpu_temp_max_threshold"` + GpuTempSlowThreshold string `xml:"gpu_temp_slow_threshold"` + GpuTempTlimit string `xml:"gpu_temp_tlimit"` + MemoryTemp string `xml:"memory_temp"` + } `xml:"temperature"` + Utilization struct { + DecoderUtil string `xml:"decoder_util"` + EncoderUtil string `xml:"encoder_util"` + GpuUtil string `xml:"gpu_util"` + JpegUtil string `xml:"jpeg_util"` + MemoryUtil string `xml:"memory_util"` + OfaUtil string `xml:"ofa_util"` + } `xml:"utilization"` + UUID string `xml:"uuid"` + VbiosVersion string `xml:"vbios_version"` + Voltage struct { + GraphicsVolt string `xml:"graphics_volt"` + } `xml:"voltage"` + } `xml:"gpu"` + Timestamp string `xml:"timestamp"` +} diff --git a/agent/utils/ai_tools/xpu/types.go b/agent/utils/ai_tools/xpu/types.go new file mode 100644 index 0000000..e78f6a7 --- /dev/null +++ b/agent/utils/ai_tools/xpu/types.go @@ -0,0 +1,43 @@ +package xpu + +type DeviceUtilByProc struct { + DeviceID int `json:"device_id"` + MemSize float64 `json:"mem_size"` + ProcessID int `json:"process_id"` + ProcessName string `json:"process_name"` + SharedMemSize float64 `json:"shared_mem_size"` +} + +type DeviceUtilByProcList struct { + DeviceUtilByProcList []DeviceUtilByProc `json:"device_util_by_proc_list"` +} + +type Device struct { + DeviceFunctionType string `json:"device_function_type"` + DeviceID int `json:"device_id"` + DeviceName string `json:"device_name"` + DeviceType string `json:"device_type"` + DrmDevice string `json:"drm_device"` + PciBdfAddress string `json:"pci_bdf_address"` + PciDeviceID string `json:"pci_device_id"` + UUID string `json:"uuid"` + VendorName string `json:"vendor_name"` + + MemoryPhysicalSizeByte string `json:"memory_physical_size_byte"` + MemoryFreeSizeByte string `json:"memory_free_size_byte"` + DriverVersion string `json:"driver_version"` +} + +type DeviceInfo struct { + DeviceList []Device `json:"device_list"` +} + +type DeviceLevelMetric struct { + MetricsType string `json:"metrics_type"` + Value float64 `json:"value"` +} + +type DeviceStats struct { + DeviceID int `json:"device_id"` + DeviceLevel []DeviceLevelMetric `json:"device_level"` +} diff --git a/agent/utils/ai_tools/xpu/xpu.go b/agent/utils/ai_tools/xpu/xpu.go new file mode 100644 index 0000000..66702ab --- /dev/null +++ b/agent/utils/ai_tools/xpu/xpu.go @@ -0,0 +1,258 @@ +package xpu + +import ( + "encoding/json" + "fmt" + "sort" + "strconv" + "sync" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type XpuSMI struct{} + +func New() (bool, XpuSMI) { + return cmd.Which("xpu-smi"), XpuSMI{} +} + +func (x XpuSMI) loadDeviceData(device Device, wg *sync.WaitGroup, res *[]XPUSimpleInfo, mu *sync.Mutex) { + defer wg.Done() + + var xpu XPUSimpleInfo + xpu.DeviceID = device.DeviceID + xpu.DeviceName = device.DeviceName + + var xpuData, statsData string + var xpuErr, statsErr error + + var wgCmd sync.WaitGroup + wgCmd.Add(2) + + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) + go func() { + defer wgCmd.Done() + xpuData, xpuErr = cmdMgr.RunWithStdoutBashCf("xpu-smi discovery -d %d -j", device.DeviceID) + }() + + go func() { + defer wgCmd.Done() + statsData, statsErr = cmdMgr.RunWithStdoutBashCf("xpu-smi stats -d %d -j", device.DeviceID) + }() + + wgCmd.Wait() + + if xpuErr != nil { + global.LOG.Errorf("calling xpu-smi discovery failed for device %d, %v", device.DeviceID, xpuErr) + return + } + + var info Device + if err := json.Unmarshal([]byte(xpuData), &info); err != nil { + global.LOG.Errorf("xpuData json unmarshal failed for device %d, err: %v", device.DeviceID, err) + return + } + + bytes, err := strconv.ParseInt(info.MemoryPhysicalSizeByte, 10, 64) + if err != nil { + global.LOG.Errorf("Error parsing memory size for device %d, err: %v", device.DeviceID, err) + return + } + xpu.Memory = fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024)) + + if statsErr != nil { + global.LOG.Errorf("calling xpu-smi stats failed for device %d, err: %v", device.DeviceID, statsErr) + return + } + + var stats DeviceStats + if err := json.Unmarshal([]byte(statsData), &stats); err != nil { + global.LOG.Errorf("statsData json unmarshal failed for device %d, err: %v", device.DeviceID, err) + return + } + + for _, stat := range stats.DeviceLevel { + switch stat.MetricsType { + case "XPUM_STATS_POWER": + xpu.Power = fmt.Sprintf("%.1fW", stat.Value) + case "XPUM_STATS_GPU_CORE_TEMPERATURE": + xpu.Temperature = fmt.Sprintf("%.1f°C", stat.Value) + case "XPUM_STATS_MEMORY_USED": + xpu.MemoryUsed = fmt.Sprintf("%.1fMB", stat.Value) + case "XPUM_STATS_MEMORY_UTILIZATION": + xpu.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value) + } + } + + mu.Lock() + *res = append(*res, xpu) + mu.Unlock() +} + +func (x XpuSMI) LoadDashData() ([]XPUSimpleInfo, error) { + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) + data, err := cmdMgr.RunWithStdoutBashC("xpu-smi discovery -j") + if err != nil { + return nil, fmt.Errorf("calling xpu-smi failed, %v", err) + } + + var deviceInfo DeviceInfo + if err := json.Unmarshal([]byte(data), &deviceInfo); err != nil { + return nil, fmt.Errorf("deviceInfo json unmarshal failed, err: %w", err) + } + + var res []XPUSimpleInfo + var wg sync.WaitGroup + var mu sync.Mutex + + for _, device := range deviceInfo.DeviceList { + wg.Add(1) + go x.loadDeviceData(device, &wg, &res, &mu) + } + + wg.Wait() + + sort.Slice(res, func(i, j int) bool { + return res[i].DeviceID < res[j].DeviceID + }) + return res, nil +} + +func (x XpuSMI) LoadGpuInfo() (*XpuInfo, error) { + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) + data, err := cmdMgr.RunWithStdoutBashC("xpu-smi discovery -j") + if err != nil { + return nil, fmt.Errorf("calling xpu-smi failed, %v", err) + } + var deviceInfo DeviceInfo + if err := json.Unmarshal([]byte(data), &deviceInfo); err != nil { + return nil, fmt.Errorf("deviceInfo json unmarshal failed, err: %w", err) + } + res := &XpuInfo{ + Type: "xpu", + } + + var wg sync.WaitGroup + var mu sync.Mutex + + for _, device := range deviceInfo.DeviceList { + wg.Add(1) + go x.loadDeviceInfo(device, &wg, res, &mu) + } + + wg.Wait() + + processData, err := cmdMgr.RunWithStdoutBashC("xpu-smi ps -j") + if err != nil { + return nil, fmt.Errorf("calling xpu-smi ps failed, %s", err) + } + var psList DeviceUtilByProcList + if err := json.Unmarshal([]byte(processData), &psList); err != nil { + return nil, fmt.Errorf("processData json unmarshal failed, err: %w", err) + } + for _, ps := range psList.DeviceUtilByProcList { + process := Process{ + PID: ps.ProcessID, + Command: ps.ProcessName, + } + if ps.SharedMemSize > 0 { + process.SHR = fmt.Sprintf("%.1f MB", ps.SharedMemSize/1024) + } + if ps.MemSize > 0 { + process.Memory = fmt.Sprintf("%.1f MB", ps.MemSize/1024) + } + for index, xpu := range res.Xpu { + if xpu.Basic.DeviceID == ps.DeviceID { + res.Xpu[index].Processes = append(res.Xpu[index].Processes, process) + } + } + } + + return res, nil +} + +func (x XpuSMI) loadDeviceInfo(device Device, wg *sync.WaitGroup, res *XpuInfo, mu *sync.Mutex) { + defer wg.Done() + + xpu := Xpu{ + Basic: Basic{ + DeviceID: device.DeviceID, + DeviceName: device.DeviceName, + VendorName: device.VendorName, + PciBdfAddress: device.PciBdfAddress, + }, + } + + var xpuData, statsData string + var xpuErr, statsErr error + + var wgCmd sync.WaitGroup + wgCmd.Add(2) + + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(5 * time.Second)) + go func() { + defer wgCmd.Done() + xpuData, xpuErr = cmdMgr.RunWithStdoutBashCf("xpu-smi discovery -d %d -j", device.DeviceID) + }() + + go func() { + defer wgCmd.Done() + statsData, statsErr = cmdMgr.RunWithStdoutBashCf("xpu-smi stats -d %d -j", device.DeviceID) + }() + + wgCmd.Wait() + + if xpuErr != nil { + global.LOG.Errorf("calling xpu-smi discovery failed for device %d, %v", device.DeviceID, xpuErr) + return + } + + var info Device + if err := json.Unmarshal([]byte(xpuData), &info); err != nil { + global.LOG.Errorf("xpuData json unmarshal failed for device %d, err: %v", device.DeviceID, err) + return + } + + res.DriverVersion = info.DriverVersion + xpu.Basic.DriverVersion = info.DriverVersion + + bytes, err := strconv.ParseInt(info.MemoryPhysicalSizeByte, 10, 64) + if err != nil { + global.LOG.Errorf("Error parsing memory size for device %d, err: %v", device.DeviceID, err) + return + } + xpu.Basic.Memory = fmt.Sprintf("%.1f MB", float64(bytes)/(1024*1024)) + xpu.Basic.FreeMemory = info.MemoryFreeSizeByte + + if statsErr != nil { + global.LOG.Errorf("calling xpu-smi stats failed for device %d, err: %v", device.DeviceID, statsErr) + return + } + + var stats DeviceStats + if err := json.Unmarshal([]byte(statsData), &stats); err != nil { + global.LOG.Errorf("statsData json unmarshal failed for device %d, err: %v", device.DeviceID, err) + return + } + + for _, stat := range stats.DeviceLevel { + switch stat.MetricsType { + case "XPUM_STATS_POWER": + xpu.Stats.Power = fmt.Sprintf("%.1fW", stat.Value) + case "XPUM_STATS_GPU_FREQUENCY": + xpu.Stats.Frequency = fmt.Sprintf("%.1fMHz", stat.Value) + case "XPUM_STATS_GPU_CORE_TEMPERATURE": + xpu.Stats.Temperature = fmt.Sprintf("%.1f°C", stat.Value) + case "XPUM_STATS_MEMORY_USED": + xpu.Stats.MemoryUsed = fmt.Sprintf("%.1fMB", stat.Value) + case "XPUM_STATS_MEMORY_UTILIZATION": + xpu.Stats.MemoryUtil = fmt.Sprintf("%.1f%%", stat.Value) + } + } + + mu.Lock() + res.Xpu = append(res.Xpu, xpu) + mu.Unlock() +} diff --git a/agent/utils/ai_tools/xpu/xpu_info.go b/agent/utils/ai_tools/xpu/xpu_info.go new file mode 100644 index 0000000..9c7d456 --- /dev/null +++ b/agent/utils/ai_tools/xpu/xpu_info.go @@ -0,0 +1,49 @@ +package xpu + +type XpuInfo struct { + Type string `json:"type"` + DriverVersion string `json:"driverVersion"` + + Xpu []Xpu `json:"xpu"` +} + +type Xpu struct { + Basic Basic `json:"basic"` + Stats Stats `json:"stats"` + Processes []Process `json:"processes"` +} + +type Basic struct { + DeviceID int `json:"deviceID"` + DeviceName string `json:"deviceName"` + VendorName string `json:"vendorName"` + DriverVersion string `json:"driverVersion"` + Memory string `json:"memory"` + FreeMemory string `json:"freeMemory"` + PciBdfAddress string `json:"pciBdfAddress"` +} + +type Stats struct { + Power string `json:"power"` + Frequency string `json:"frequency"` + Temperature string `json:"temperature"` + MemoryUsed string `json:"memoryUsed"` + MemoryUtil string `json:"memoryUtil"` +} + +type Process struct { + PID int `json:"pid"` + Command string `json:"command"` + SHR string `json:"shr"` + Memory string `json:"memory"` +} + +type XPUSimpleInfo struct { + DeviceID int `json:"deviceID"` + DeviceName string `json:"deviceName"` + Memory string `json:"memory"` + Temperature string `json:"temperature"` + MemoryUsed string `json:"memoryUsed"` + Power string `json:"power"` + MemoryUtil string `json:"memoryUtil"` +} diff --git a/agent/utils/alert/alert.go b/agent/utils/alert/alert.go new file mode 100644 index 0000000..08e2ccd --- /dev/null +++ b/agent/utils/alert/alert.go @@ -0,0 +1,598 @@ +package alert + +import ( + "encoding/json" + "errors" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/email" + "github.com/1Panel-dev/1Panel/agent/utils/psutil" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "github.com/jinzhu/copier" + network "net" + "net/http" + "os" + "os/exec" + "strconv" + "strings" + "sync" + "time" +) + +var cronJobAlertTypes = []string{"shell", "app", "website", "database", "directory", "log", "snapshot", "curl", "cutWebsiteLog", "clean", "ntp"} + +func CreateTaskScanEmailAlertLog(alert dto.AlertDTO, create dto.AlertLogCreate, pushAlert dto.PushAlert, method string, transport *http.Transport, agentInfo *dto.AgentInfo) error { + params := CreateAlertParams(GetCronJobTypeName(pushAlert.Param)) + alertDetail := ProcessAlertDetail(alert, pushAlert.TaskName, params, method) + alertRule := ProcessAlertRule(alert) + create.AlertRule = alertRule + create.AlertDetail = alertDetail + return CreateEmailAlertLog(create, alert, params, transport, agentInfo) +} + +func CreateEmailAlertLog(create dto.AlertLogCreate, alert dto.AlertDTO, params []dto.Param, transport *http.Transport, agentInfo *dto.AgentInfo) error { + var alertLog model.AlertLog + alertRepo := repo.NewIAlertRepo() + config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig)) + if err != nil { + return err + } + var cfg dto.AlertCommonConfig + err = json.Unmarshal([]byte(config.Config), &cfg) + if err != nil { + return err + } + create.Method = constant.Email + if !global.IsMaster && cfg.IsOffline == constant.StatusEnable { + create.Status = constant.AlertPushing + return SaveAlertLog(create, &alertLog) + } else { + emailConfig, err := alertRepo.GetConfig(alertRepo.WithByType(constant.EmailConfig)) + if err != nil { + return err + } + var emailInfo dto.AlertEmailConfig + err = json.Unmarshal([]byte(emailConfig.Config), &emailInfo) + if err != nil { + return err + } + username := emailInfo.UserName + if username == "" { + username = emailInfo.Sender + } + smtpConfig := email.SMTPConfig{ + Host: emailInfo.Host, + Port: emailInfo.Port, + Sender: emailInfo.Sender, + Username: username, + Password: emailInfo.Password, + From: fmt.Sprintf(`"%s" <%s>`, emailInfo.DisplayName, emailInfo.Sender), + Encryption: emailInfo.Encryption, + Recipient: emailInfo.Recipient, + } + content := GetEmailContent(alert.Type, params, agentInfo) + if content == "" { + content = i18n.GetMsgWithMap("CommonAlert", map[string]interface{}{"msg": alert.Title}) + } + msg := email.EmailMessage{ + Subject: i18n.GetMsgByKey("PanelAlertTitle"), + Body: content, + IsHTML: true, + } + + if err = email.SendMail(smtpConfig, msg, transport); err != nil { + create.Message = err.Error() + create.Status = constant.AlertError + return SaveAlertLog(create, &alertLog) + } + create.Status = constant.AlertSuccess + return SaveAlertLog(create, &alertLog) + } +} + +func SaveAlertLog(create dto.AlertLogCreate, alertLog *model.AlertLog) error { + alertRepo := repo.NewIAlertRepo() + if err := copier.Copy(&alertLog, &create); err != nil { + return buserr.WithErr("ErrStructTransform", err) + } + + if err := alertRepo.CreateLog(alertLog); err != nil { + global.LOG.Errorf("Error creating alert logs, err: %v", err) + return err + } + + return nil +} + +func CreateNewAlertTask(quota, alertType, quotaType, method string) { + alertRepo := repo.NewIAlertRepo() + taskBase := model.AlertTask{ + Type: alertType, + Quota: quota, + QuotaType: quotaType, + Method: method, + } + err := alertRepo.CreateAlertTask(&taskBase) + if err != nil { + global.LOG.Errorf("error creating alert tasks, err: %v", err) + } +} + +func ProcessAlertDetail(alert dto.AlertDTO, project string, params []dto.Param, method string) string { + alertDetail := dto.AlertDetail{ + Type: GetCronJobType(alert.Type), + SubType: alert.Type, + Title: alert.Title, + Method: method, + Project: project, + Params: params, + } + marshal, err := json.Marshal(alertDetail) + if err != nil { + global.LOG.Errorf("error processing alert detail, err: %v", err) + return "" + } + return string(marshal) +} + +func ProcessAlertRule(alert dto.AlertDTO) string { + marshal, err := json.Marshal(alert) + if err != nil { + global.LOG.Errorf("error processing alert rule, err: %v", err) + return "" + } + return string(marshal) +} + +func GetCronJobType(alertType string) string { + for _, at := range cronJobAlertTypes { + if at == alertType { + return "cronJob" + } + } + return alertType +} + +func GetCronJobTypeName(cronJobType string) string { + module := cronJobType + switch cronJobType { + case "shell": + module = "Shell 脚本" + case "app": + module = "备份应用" + case "website": + module = "备份网站" + case "database": + module = "备份数据库" + case "log": + module = "备份日志" + case "directory": + module = "备份目录" + case "curl": + module = "访问 URL" + case "cutWebsiteLog": + module = "切割网站日志" + case "clean": + module = "缓存清理" + case "snapshot": + module = "系统快照" + case "ntp": + module = "同步服务器时间" + default: + } + return module +} + +func CreateAlertParams(param string) []dto.Param { + return []dto.Param{ + { + Index: "1", + Key: "param", + Value: param, + }, + } +} + +var checkTaskMutex sync.Mutex + +func CheckSMSSendLimit(method string) bool { + alertRepo := repo.NewIAlertRepo() + config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.SMSConfig)) + if err != nil { + return false + } + var cfg dto.AlertSmsConfig + cfg, err = ParseAlertSmsConfig(config.Config) + if err != nil { + return false + } + limitCount, err := strconv.ParseUint(cfg.AlertDailyNum, 10, 64) + if err != nil { + return false + } + checkTaskMutex.Lock() + defer checkTaskMutex.Unlock() + todayCount, err := alertRepo.GetLicensePushCount(method) + if err != nil { + global.LOG.Errorf("error getting license push count info, err: %v", err) + return false + } + if todayCount >= uint(limitCount) { + return false + } + + return true +} + +type Settings struct { + NoticeAlert Category `json:"noticeAlert"` + ResourceAlert Category `json:"resourceAlert"` +} + +type Category struct { + SendTimeRange string `json:"sendTimeRange"` + Type []string `json:"type"` +} + +func CheckSendTimeRange(alertType string) bool { + alertRepo := repo.NewIAlertRepo() + config, err := alertRepo.GetConfig(alertRepo.WithByType(constant.CommonConfig)) + if err != nil { + return false + } + var cfg dto.AlertCommonConfig + err = json.Unmarshal([]byte(config.Config), &cfg) + if err != nil { + return false + } + + var timeRange string + if contains(cfg.AlertSendTimeRange.NoticeAlert.Type, alertType) { + timeRange = cfg.AlertSendTimeRange.NoticeAlert.SendTimeRange + } else if contains(cfg.AlertSendTimeRange.ResourceAlert.Type, alertType) { + timeRange = cfg.AlertSendTimeRange.ResourceAlert.SendTimeRange + } else { + global.LOG.Warnf("Alert type not found in sendTimeRange: %s", alertType) + return false + } + + if !isWithinTimeRange(timeRange) { + return false + } + return true +} + +func contains(arr []string, target string) bool { + for _, item := range arr { + if item == target { + return true + } + } + return false +} + +func isWithinTimeRange(savedTimeString string) bool { + now := time.Now() + timeParts := strings.Split(savedTimeString, " - ") + if len(timeParts) != 2 { + global.LOG.Info("Time range string format error, should be: 'HH:MM:SS - HH:MM:SS'") + return false + } + startTime, err1 := time.Parse("15:04:05", strings.TrimSpace(timeParts[0])) + endTime, err2 := time.Parse("15:04:05", strings.TrimSpace(timeParts[1])) + if err1 != nil || err2 != nil { + global.LOG.Infof("Invalid time format in range: %s, errors: %v, %v", savedTimeString, err1, err2) + return false + } + + skipTime := time.Date(now.Year(), now.Month(), now.Day(), startTime.Hour(), startTime.Minute(), startTime.Second(), 0, now.Location()) + endSkipTime := time.Date(now.Year(), now.Month(), now.Day(), endTime.Hour(), endTime.Minute(), endTime.Second(), 0, now.Location()) + + if endSkipTime.Before(skipTime) { + return now.After(skipTime) || now.Before(endSkipTime) + } + return now.After(skipTime) && now.Before(endSkipTime) +} + +func GetEmailContent(alertType string, params []dto.Param, agentInfo *dto.AgentInfo) string { + switch GetCronJobType(alertType) { + case "ssl": + return i18n.GetMsgWithMap("SSLAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "siteEndTime": + return i18n.GetMsgWithMap("WebSiteAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "day": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "panelPwdEndTime": + return i18n.GetMsgWithMap("PanelPwdExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "licenseTime": + return i18n.GetMsgWithMap("LicenseExpirationAlert", map[string]interface{}{"day": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "panelUpdate": + return i18n.GetMsgWithMap("PanelVersionAlert", map[string]interface{}{"node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "cpu": + return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "memory": + return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "load": + return i18n.GetMsgWithMap("ResourceAlert", map[string]interface{}{"time": getValueByIndex(params, "1"), "name": getValueByIndex(params, "2"), "used": getValueByIndex(params, "3"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "disk": + return i18n.GetMsgWithMap("DiskUsedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "used": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "cronJob": + return i18n.GetMsgWithMap("CronJobFailedAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "clams": + return i18n.GetMsgWithMap("ClamAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "panelLogin": + return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "loginIp": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "sshLogin": + return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "loginIp": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "panelIpLogin": + return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "loginIp": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "sshIpLogin": + return i18n.GetMsgWithMap("SSHAndPanelLoginAlert", map[string]interface{}{"name": getValueByIndex(params, "1"), "loginIp": getValueByIndex(params, "2"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "nodeException": + return i18n.GetMsgWithMap("NodeExceptionAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + case "licenseException": + return i18n.GetMsgWithMap("LicenseExceptionAlert", map[string]interface{}{"num": getValueByIndex(params, "1"), "node": getNodeName(agentInfo), "ip": getNodeIp(agentInfo)}) + default: + return "" + } +} + +func getValueByIndex(params []dto.Param, index string) string { + for _, p := range params { + if p.Index == index { + return p.Value + } + } + return "" +} + +func CountRecentFailedLoginLogs(minutes uint, failCount uint) (int, bool, error) { + now := time.Now() + startTime := now.Add(-time.Duration(minutes) * time.Minute) + db := global.CoreDB.Model(&model.LoginLog{}) + var count int64 + err := db.Where("created_at >= ? AND status = ?", startTime, constant.StatusFailed). + Count(&count).Error + if err != nil { + return 0, false, err + } + return int(count), int(count) >= int(failCount), nil +} + +func FindRecentSuccessLoginsNotInWhitelist(minutes int, whitelist []string) ([]model.LoginLog, error) { + now := time.Now() + startTime := now.Add(-time.Duration(minutes) * time.Minute) + + whitelistMap := make(map[string]struct{}) + for _, ip := range whitelist { + whitelistMap[ip] = struct{}{} + } + + var logs []model.LoginLog + err := global.CoreDB.Model(&model.LoginLog{}). + Where("created_at >= ? AND status = ?", startTime, constant.StatusSuccess). + Find(&logs).Error + if err != nil { + return nil, err + } + + var abnormalLogs []model.LoginLog + for _, log := range logs { + if _, ok := whitelistMap[log.IP]; !ok { + abnormalLogs = append(abnormalLogs, log) + } + } + return abnormalLogs, nil +} + +func CountRecentFailedSSHLog(minutes uint, maxAllowed uint) (int, bool, error) { + lines, err := grepSSHLog([]string{"Failed password", "Invalid user", "authentication failure"}) + if err != nil { + return 0, false, err + } + + thresholdTime := time.Now().Add(-time.Duration(minutes) * time.Minute) + count := 0 + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + t, err := parseLogTime(line) + if err != nil { + continue + } + if t.After(thresholdTime) { + count++ + } + } + return count, count >= int(maxAllowed), nil +} + +func FindRecentSuccessLoginNotInWhitelist(minutes int, whitelist []string) ([]string, error) { + lines, err := grepSSHLog([]string{"Accepted password", "Accepted publickey"}) + if err != nil { + return nil, err + } + + thresholdTime := time.Now().Add(-time.Duration(minutes) * time.Minute) + var abnormalLogins []string + + whitelistMap := make(map[string]struct{}, len(whitelist)) + for _, ip := range whitelist { + whitelistMap[ip] = struct{}{} + } + + ipRegex := re.GetRegex(re.AlertIPPattern) + + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + + t, err := parseLogTime(line) + if err != nil || t.Before(thresholdTime) { + continue + } + + match := ipRegex.FindStringSubmatch(line) + if len(match) >= 2 { + ip := match[1] + if _, ok := whitelistMap[ip]; !ok { + abnormalLogins = append(abnormalLogins, fmt.Sprintf("%s-%s", ip, t.Format("2006-01-02 15:04:05"))) + } + } + } + + return abnormalLogins, nil +} + +func findGrepPath() (string, error) { + path, err := exec.LookPath("grep") + if err != nil { + return "", fmt.Errorf("grep not found in PATH: %w", err) + } + return path, nil +} + +func grepSSHLog(keywords []string) ([]string, error) { + logFiles := []string{"/var/log/secure", "/var/log/auth.log"} + var results []string + seen := make(map[string]struct{}) + + grepPath, err := findGrepPath() + if err != nil { + return nil, fmt.Errorf("find grep failed: %w", err) + } + + for _, logFile := range logFiles { + if _, err := os.Stat(logFile); err != nil { + continue + } + for _, keyword := range keywords { + cmd := exec.Command(grepPath, "-a", keyword, logFile) + output, err := cmd.Output() + if err != nil { + var exitErr *exec.ExitError + if errors.As(err, &exitErr) { + if exitErr.ExitCode() == 1 { + continue + } + } + return nil, fmt.Errorf("read log file fail [%s]: %w", logFile, err) + } + + lines := strings.Split(string(output), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line != "" { + if _, exists := seen[line]; !exists { + results = append(results, line) + seen[line] = struct{}{} + } + } + } + } + } + + return results, nil +} + +func parseLogTime(line string) (time.Time, error) { + if len(line) < 15 { + return time.Time{}, nil + } + timeStr := line[:15] + parsedTime, err := time.ParseInLocation("Jan 2 15:04:05", timeStr, time.Local) + if err != nil { + return time.Time{}, nil + } + return parsedTime.AddDate(time.Now().Year(), 0, 0), nil +} + +func getNodeName(agentInfo *dto.AgentInfo) string { + var nodeName string + if agentInfo != nil && agentInfo.NodeName != "" { + nodeName = agentInfo.NodeName + } + + return formatWithFallback(nodeName, getFallbackHostname) +} + +func getNodeIp(agentInfo *dto.AgentInfo) string { + var nodeIP string + if agentInfo != nil && agentInfo.NodeAddr != "" && agentInfo.NodeAddr != "127.0.0.1" { + nodeIP = agentInfo.NodeAddr + } + + return formatWithFallback(nodeIP, getFallbackIP) +} + +func formatWithFallback(value string, fallback func() string) string { + value = strings.TrimSpace(value) + if value == "" { + value = strings.TrimSpace(fallback()) + } + + if value == "" { + return "" + } + + return fmt.Sprintf("「%s」", value) +} + +func getFallbackHostname() string { + hostInfo, err := psutil.HOST.GetHostInfo(false) + if err != nil { + return "" + } + return hostInfo.Hostname +} + +func getFallbackIP() string { + if systemIP, err := repo.NewISettingRepo().GetValueByKey("SystemIP"); err == nil && systemIP != "" { + return systemIP + } + return loadOutboundIP() +} + +func loadOutboundIP() string { + conn, err := network.Dial("udp", "8.8.8.8:80") + + if err != nil { + return "" + } + defer conn.Close() + localAddr := conn.LocalAddr().(*network.UDPAddr) + return localAddr.IP.String() +} + +func ParseAlertSmsConfig(configJSON string) (dto.AlertSmsConfig, error) { + var tempMap map[string]interface{} + err := json.Unmarshal([]byte(configJSON), &tempMap) + if err != nil { + return dto.AlertSmsConfig{}, err + } + + var cfg dto.AlertSmsConfig + if phone, ok := tempMap["phone"].(string); ok { + cfg.Phone = phone + } + + switch v := tempMap["alertDailyNum"].(type) { + case float64: + cfg.AlertDailyNum = strconv.FormatFloat(v, 'f', 0, 64) + case string: + cfg.AlertDailyNum = v + default: + cfg.AlertDailyNum = "50" + } + + return cfg, nil +} diff --git a/agent/utils/alert_push/alert_push.go b/agent/utils/alert_push/alert_push.go new file mode 100644 index 0000000..8ea04a9 --- /dev/null +++ b/agent/utils/alert_push/alert_push.go @@ -0,0 +1,70 @@ +package alert_push + +import ( + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + alertUtil "github.com/1Panel-dev/1Panel/agent/utils/alert" + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + "github.com/jinzhu/copier" + "strconv" + "strings" +) + +func PushAlert(pushAlert dto.PushAlert) error { + if !alertUtil.CheckSendTimeRange(alertUtil.GetCronJobType(pushAlert.AlertType)) { + return nil + } + + alertRepo := repo.NewIAlertRepo() + alertInfo, err := alertRepo.Get(alertRepo.WithByType(pushAlert.AlertType), alertRepo.WithByProject(strconv.Itoa(int(pushAlert.EntryID))), repo.WithByStatus(constant.AlertEnable)) + if err != nil { + return err + } + var alert dto.AlertDTO + _ = copier.Copy(&alert, &alertInfo) + + methods := strings.Split(alert.Method, ",") + for _, m := range methods { + m = strings.TrimSpace(m) + switch m { + case constant.SMS: + if !alertUtil.CheckSMSSendLimit(constant.SMS) { + continue + } + todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.SMS) + if err != nil || alert.SendCount <= todayCount { + continue + } + var create = dto.AlertLogCreate{ + Type: alertUtil.GetCronJobType(alert.Type), + AlertId: alert.ID, + Count: todayCount + 1, + } + _ = xpack.CreateTaskScanSMSAlertLog(alert, alert.Type, create, pushAlert, constant.SMS) + alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.SMS) + global.LOG.Infof("%s %s alert push successful", alert.Type, constant.SMS) + case constant.Email: + todayCount, _, err := alertRepo.LoadTaskCount(alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Email) + if err != nil || alert.SendCount <= todayCount { + continue + } + var create = dto.AlertLogCreate{ + Type: alertUtil.GetCronJobType(alert.Type), + AlertId: alert.ID, + Count: todayCount + 1, + } + transport := xpack.LoadRequestTransport() + agentInfo, _ := xpack.GetAgentInfo() + err = alertUtil.CreateTaskScanEmailAlertLog(alert, create, pushAlert, constant.Email, transport, agentInfo) + if err != nil { + return err + } + alertUtil.CreateNewAlertTask(strconv.Itoa(int(pushAlert.EntryID)), alertUtil.GetCronJobType(alert.Type), strconv.Itoa(int(pushAlert.EntryID)), constant.Email) + global.LOG.Infof("%s %s alert push successful", alert.Type, constant.Email) + default: + } + } + return nil +} diff --git a/agent/utils/clam/clam.go b/agent/utils/clam/clam.go new file mode 100644 index 0000000..b10e16f --- /dev/null +++ b/agent/utils/clam/clam.go @@ -0,0 +1,83 @@ +package clam + +import ( + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/robfig/cron/v3" +) + +func AddScanTask(taskItem *task.Task, clam model.Clam, timeNow string) { + taskItem.AddSubTask(i18n.GetWithName("Clamscan", clam.Path), func(t *task.Task) error { + strategy := "" + switch clam.InfectedStrategy { + case "remove": + strategy = "--remove" + case "move", "copy": + dir := path.Join(clam.InfectedDir, "1panel-infected", clam.Name, timeNow) + taskItem.Log("infected dir: " + dir) + if _, err := os.Stat(dir); err != nil { + _ = os.MkdirAll(dir, os.ModePerm) + } + strategy = fmt.Sprintf("--%s=%s", clam.InfectedStrategy, dir) + } + taskItem.Logf("clamdscan --fdpass %s %s", strategy, clam.Path) + mgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(time.Duration(clam.Timeout)*time.Second), cmd.WithTask(*taskItem)) + if err := mgr.RunBashCf("clamdscan --fdpass %s %s", strategy, clam.Path); err != nil { + return fmt.Errorf("clamdscan failed, %v", err) + } + return nil + }, nil) +} + +func AnalysisFromLog(pathItem string, record *model.ClamRecord) { + file, err := os.ReadFile(pathItem) + if err != nil { + return + } + lines := strings.Split(string(file), "\n") + for _, line := range lines { + if len(line) < 20 { + continue + } + line = line[20:] + switch { + case strings.HasPrefix(line, "Infected files: "): + record.InfectedFiles = strings.TrimPrefix(line, "Infected files: ") + case strings.HasPrefix(line, "Total errors: "): + record.TotalError = strings.TrimPrefix(line, "Total errors: ") + case strings.HasPrefix(line, "Time: "): + record.ScanTime = strings.TrimPrefix(line, "Time: ") + } + } +} + +func StopAllClamJob(withCheck bool, clamRepo repo.IClamRepo) bool { + if withCheck { + isExist, _ := controller.CheckExist("clam") + if !isExist { + return false + } + isActive, _ := controller.CheckActive("clam") + if !isActive { + return false + } + } + clams, _ := clamRepo.List(repo.WithByStatus(constant.StatusEnable)) + for i := 0; i < len(clams); i++ { + global.Cron.Remove(cron.EntryID(clams[i].EntryID)) + _ = clamRepo.Update(clams[i].ID, map[string]interface{}{"status": constant.StatusDisable, "entry_id": 0}) + } + return true +} diff --git a/agent/utils/cloud_storage/client/ali.go b/agent/utils/cloud_storage/client/ali.go new file mode 100644 index 0000000..cf52eda --- /dev/null +++ b/agent/utils/cloud_storage/client/ali.go @@ -0,0 +1,561 @@ +package client + +import ( + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "strings" + + "github.com/go-resty/resty/v2" +) + +type aliClient struct { + token string + driveID string +} + +func NewALIClient(vars map[string]interface{}) (*aliClient, error) { + refresh_token := loadParamFromVars("refresh_token", vars) + drive_id := loadParamFromVars("drive_id", vars) + + token, err := loadToken(refresh_token) + if err != nil { + return nil, err + } + return &aliClient{token: token, driveID: drive_id}, nil +} + +func (a aliClient) ListBuckets() ([]interface{}, error) { + return nil, nil +} + +func (a aliClient) Exist(pathItem string) (bool, error) { + pathItem = path.Join("root", pathItem) + if _, err := a.loadFileWithName(pathItem); err != nil { + return false, err + } + return true, nil +} + +func (a aliClient) Size(pathItem string) (int64, error) { + pathItem = path.Join("root", pathItem) + fileInfo, err := a.loadFileWithName(pathItem) + if err != nil { + return 0, err + } + return int64(fileInfo.Size), nil +} + +func (a aliClient) Delete(pathItem string) (bool, error) { + pathItem = path.Join("root", pathItem) + fileInfo, err := a.loadFileWithName(pathItem) + if err != nil { + return false, err + } + client := resty.New() + client.SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }) + data := map[string]interface{}{ + "drive_id": a.driveID, + "file_id": fileInfo.FileID, + } + url := "https://api.alipan.com/v2/file/delete" + resp, err := client.R(). + SetHeader("Authorization", a.token). + SetBody(data). + Post(url) + if err != nil { + return false, err + } + if resp.StatusCode() != 204 { + return false, fmt.Errorf("delete file %s failed, err: %v", pathItem, string(resp.Body())) + } + return true, nil +} + +func (a aliClient) Upload(src, target string) (bool, error) { + target = path.Join("/root", target) + parentID := "root" + var err error + if path.Dir(target) != "/root" { + parentID, err = a.mkdirWithPath(path.Dir(target)) + if err != nil { + return false, err + } + } + file, err := os.Open(src) + if err != nil { + return false, err + } + defer file.Close() + fileInfo, err := file.Stat() + if err != nil { + return false, err + } + data := map[string]interface{}{ + "drive_id": a.driveID, + "part_info_list": makePartInfoList(fileInfo.Size()), + "parent_file_id": parentID, + "name": path.Base(src), + "type": "file", + "size": fileInfo.Size(), + "check_name_mode": "auto_rename", + } + client := resty.New() + client.SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }) + url := "https://api.alipan.com/v2/file/create" + + resp, err := client.R(). + SetHeader("Authorization", a.token). + SetBody(data). + Post(url) + if err != nil { + return false, err + } + + var createResp createFileResp + if err := json.Unmarshal(resp.Body(), &createResp); err != nil { + return false, err + } + for _, part := range createResp.PartInfoList { + err = a.uploadPart(part.UploadURL, io.LimitReader(file, 1024*1024*1024)) + if err != nil { + return false, err + } + } + + if err := a.completeUpload(createResp.UploadID, createResp.FileID); err != nil { + return false, err + } + return true, nil +} + +func (a aliClient) Download(src, target string) (bool, error) { + src = path.Join("/root", src) + fileInfo, err := a.loadFileWithName(src) + if err != nil { + return false, err + } + client := resty.New() + client.SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }) + if fileInfo.Size > 100*1024*1024 { + return false, fmt.Errorf("The translation file %s exceeds 100MB, please download it through the client.", src) + } + data := map[string]interface{}{ + "drive_id": a.driveID, + "file_id": fileInfo.FileID, + } + url := "https://api.aliyundrive.com/v2/file/get_download_url" + resp, err := client.R(). + SetHeader("Authorization", a.token). + SetBody(data). + Post(url) + if err != nil { + return false, err + } + if resp.StatusCode() != 200 { + return false, fmt.Errorf("download file %s failed, err: %v", src, string(resp.Body())) + } + var respItem downloadResp + if err := json.Unmarshal(resp.Body(), &respItem); err != nil { + return false, err + } + if err := a.handleDownload(respItem.URL, target); err != nil { + return false, err + } + return true, nil +} + +func (a *aliClient) ListObjects(src string) ([]string, error) { + if len(src) == 0 || src == "root" || src == "/root" { + src = "root" + } else { + src = path.Join("/root", src) + } + fileInfos, err := a.loadDirWithPath(src) + if err != nil { + return nil, err + } + var names []string + for _, item := range fileInfos { + names = append(names, item.Name) + } + return names, nil +} + +func (a aliClient) loadFileWithName(pathItem string) (fileInfo, error) { + pathItems := strings.Split(pathItem, "/") + var ( + fileInfos []fileInfo + err error + ) + parentID := "root" + for i := 0; i < len(pathItems); i++ { + if len(pathItems[i]) == 0 { + continue + } + fileInfos, err = a.loadFileWithParentID(parentID) + if err != nil { + return fileInfo{}, err + } + isEnd := false + if i == len(pathItems)-2 { + isEnd = true + } + exist := false + for _, item := range fileInfos { + if item.Name == pathItems[i+1] { + if isEnd { + return item, nil + } else { + parentID = item.FileID + exist = true + } + } + } + if !exist { + return fileInfo{}, errors.New("no such file or dir") + } + + } + return fileInfo{}, errors.New("no such file or dir") +} + +func (a aliClient) loadDirWithPath(path string) ([]fileInfo, error) { + pathItems := strings.Split(path, "/") + var ( + fileInfos []fileInfo + err error + ) + parentID := "root" + for i := 0; i < len(pathItems); i++ { + if len(pathItems[i]) == 0 { + continue + } + fileInfos, err = a.loadFileWithParentID(parentID) + if err != nil { + return fileInfos, err + } + if i == len(pathItems)-1 { + return fileInfos, nil + } + exist := false + for _, item := range fileInfos { + if item.Name == pathItems[i+1] { + parentID = item.FileID + exist = true + } + } + if !exist { + return nil, errors.New("no such file or dir") + } + } + return fileInfos, errors.New("no such file or dir") +} + +func (a aliClient) loadFileWithParentID(parentID string) ([]fileInfo, error) { + client := resty.New() + client.SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }) + data := map[string]interface{}{ + "drive_id": a.driveID, + "fields": "*", + "limit": 100, + "parent_file_id": parentID, + } + url := "https://api.aliyundrive.com/adrive/v3/file/list" + resp, err := client.R(). + SetHeader("Authorization", a.token). + SetBody(data). + Post(url) + if err != nil { + return nil, err + } + if resp.StatusCode() != 200 { + return nil, fmt.Errorf("load file list failed, code: %v, err: %v", resp.StatusCode(), string(resp.Body())) + } + var fileResp fileResp + if err := json.Unmarshal(resp.Body(), &fileResp); err != nil { + return nil, err + } + return fileResp.Items, nil +} + +func (a aliClient) mkdirWithPath(target string) (string, error) { + pathItems := strings.Split(target, "/") + var ( + fileInfos []fileInfo + err error + ) + parentID := "root" + for i := 0; i < len(pathItems); i++ { + if len(pathItems[i]) == 0 { + continue + } + fileInfos, err = a.loadFileWithParentID(parentID) + if err != nil { + return "", err + } + isEnd := false + if i == len(pathItems)-2 { + isEnd = true + } + exist := false + for _, item := range fileInfos { + if item.Name == pathItems[i+1] { + parentID = item.FileID + if isEnd { + return item.FileID, nil + } else { + exist = true + } + } + } + if !exist { + parentID, err = a.mkdir(parentID, pathItems[i+1]) + if err != nil { + return parentID, err + } + if isEnd { + return parentID, nil + } + } + } + return "", errors.New("mkdir failed.") +} + +func (a aliClient) mkdir(parentID, name string) (string, error) { + client := resty.New() + client.SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }) + data := map[string]interface{}{ + "drive_id": a.driveID, + "name": name, + "type": "folder", + "limit": 100, + "parent_file_id": parentID, + } + url := "https://api.aliyundrive.com/adrive/v2/file/createWithFolders" + resp, err := client.R(). + SetHeader("Authorization", a.token). + SetBody(data). + Post(url) + if err != nil { + return "", err + } + if resp.StatusCode() != 201 { + return "", fmt.Errorf("mkdir %s failed, code: %v, err: %v", name, resp.StatusCode(), string(resp.Body())) + } + var mkdirResp mkdirResp + if err := json.Unmarshal(resp.Body(), &mkdirResp); err != nil { + return "", err + } + return mkdirResp.FileID, nil +} + +type fileResp struct { + Items []fileInfo `json:"items"` +} +type fileInfo struct { + FileID string `json:"file_id"` + Name string `json:"name"` + Size int `json:"size"` +} + +type mkdirResp struct { + FileID string `json:"file_id"` +} + +type partInfo struct { + PartNumber int `json:"part_number"` + UploadURL string `json:"upload_url"` + InternalUploadURL string `json:"internal_upload_url"` + ContentType string `json:"content_type"` +} + +func makePartInfoList(size int64) []*partInfo { + var res []*partInfo + maxPartSize := int64(1024 * 1024 * 1024) + partInfoNum := int(size / maxPartSize) + if size%maxPartSize > 0 { + partInfoNum += 1 + } + + for i := 0; i < partInfoNum; i++ { + res = append(res, &partInfo{PartNumber: i + 1}) + } + + return res +} + +type createFileResp struct { + Type string `json:"type"` + RapidUpload bool `json:"rapid_upload"` + DomainId string `json:"domain_id"` + DriveId string `json:"drive_id"` + FileName string `json:"file_name"` + EncryptMode string `json:"encrypt_mode"` + Location string `json:"location"` + UploadID string `json:"upload_id"` + FileID string `json:"file_id"` + PartInfoList []*partInfo `json:"part_info_list,omitempty"` +} + +func (a aliClient) uploadPart(uri string, reader io.Reader) error { + req, err := http.NewRequest(http.MethodPut, uri, reader) + if err != nil { + return err + } + client := &http.Client{} + defer client.CloseIdleConnections() + response, err := client.Do(req) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return fmt.Errorf("handle upload park file with url failed, code: %v", response.StatusCode) + } + + return nil +} + +type downloadResp struct { + URL string `json:"url"` +} + +func (a aliClient) handleDownload(uri string, target string) error { + req, err := http.NewRequest(http.MethodGet, uri, nil) + if err != nil { + return err + } + req.Header.Add("Authorization", a.token) + req.Header.Add("origin", "https://www.aliyundrive.com") + req.Header.Add("referer", "https://www.aliyundrive.com/") + client := &http.Client{} + defer client.CloseIdleConnections() + response, err := client.Do(req) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return fmt.Errorf("handle download with url failed, code: %v", response.StatusCode) + } + if _, err := os.Stat(path.Dir(target)); err != nil { + _ = os.MkdirAll(path.Dir(target), os.ModePerm) + } + out, err := os.Create(target) + if err != nil { + return err + } + defer out.Close() + if _, err = io.Copy(out, response.Body); err != nil { + return err + } + + return nil +} + +func (a *aliClient) completeUpload(uploadID, fileID string) error { + client := resty.New() + client.SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }) + data := map[string]interface{}{ + "drive_id": a.driveID, + "upload_id": uploadID, + "file_id": fileID, + } + + url := "https://api.aliyundrive.com/v2/file/complete" + resp, err := client.R(). + SetHeader("Authorization", a.token). + SetBody(data). + Post(url) + if err != nil { + return err + } + if resp.StatusCode() != 200 { + return fmt.Errorf("complete upload failed, err: %v", string(resp.Body())) + } + + return nil +} + +type tokenResp struct { + RefreshToken string `json:"refresh_token"` + AccessToken string `json:"access_token"` +} + +func loadToken(refresh_token string) (string, error) { + client := resty.New() + client.SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }) + data := map[string]interface{}{ + "grant_type": "refresh_token", + "refresh_token": refresh_token, + } + + url := "https://api.aliyundrive.com/token/refresh" + resp, err := client.R(). + SetBody(data). + Post(url) + + if err != nil { + return "", fmt.Errorf("load account token failed, err: %v", err) + } + if resp.StatusCode() != 200 { + return "", fmt.Errorf("load account token failed, code: %v", resp.StatusCode()) + } + var respItem tokenResp + if err := json.Unmarshal(resp.Body(), &respItem); err != nil { + return "", err + } + return respItem.AccessToken, nil +} + +func RefreshALIToken(varMap map[string]interface{}) (string, error) { + refresh_token := loadParamFromVars("refresh_token", varMap) + if len(refresh_token) == 0 { + return "", errors.New("no such refresh token find in db") + } + client := resty.New() + client.SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }) + data := map[string]interface{}{ + "grant_type": "refresh_token", + "refresh_token": refresh_token, + } + + url := "https://api.aliyundrive.com/token/refresh" + resp, err := client.R(). + SetBody(data). + Post(url) + + if err != nil { + return "", fmt.Errorf("load account token failed, err: %v", err) + } + if resp.StatusCode() != 200 { + return "", fmt.Errorf("load account token failed, code: %v", resp.StatusCode()) + } + var respItem tokenResp + if err := json.Unmarshal(resp.Body(), &respItem); err != nil { + return "", err + } + return respItem.RefreshToken, nil +} diff --git a/agent/utils/cloud_storage/client/cos.go b/agent/utils/cloud_storage/client/cos.go new file mode 100644 index 0000000..9ee46f8 --- /dev/null +++ b/agent/utils/cloud_storage/client/cos.go @@ -0,0 +1,152 @@ +package client + +import ( + "context" + "fmt" + "net/http" + "net/url" + "os" + + cosSDK "github.com/tencentyun/cos-go-sdk-v5" + + "github.com/1Panel-dev/1Panel/agent/utils/re" +) + +type cosClient struct { + scType string + client *cosSDK.Client + clientWithBucket *cosSDK.Client +} + +func NewCosClient(vars map[string]interface{}) (*cosClient, error) { + region := loadParamFromVars("region", vars) + endpoint := loadParamFromVars("endpoint", vars) + accessKey := loadParamFromVars("accessKey", vars) + secretKey := loadParamFromVars("secretKey", vars) + bucket := loadParamFromVars("bucket", vars) + scType := loadParamFromVars("scType", vars) + if len(scType) == 0 { + scType = "Standard" + } + + endpointType := "cos" + if len(endpoint) != 0 { + if re.GetRegex(re.CosDualStackPattern).MatchString(endpoint) { + endpointType = "cos-dualstack" + } + } + + u, _ := url.Parse(fmt.Sprintf("https://%s.%s.myqcloud.com", endpointType, region)) + b := &cosSDK.BaseURL{BucketURL: u} + client := cosSDK.NewClient(b, &http.Client{ + Transport: &cosSDK.AuthorizationTransport{ + SecretID: accessKey, + SecretKey: secretKey, + }, + }) + + if len(bucket) != 0 { + u2, _ := url.Parse(fmt.Sprintf("https://%s.%s.%s.myqcloud.com", bucket, endpointType, region)) + b2 := &cosSDK.BaseURL{BucketURL: u2} + clientWithBucket := cosSDK.NewClient(b2, &http.Client{ + Transport: &cosSDK.AuthorizationTransport{ + SecretID: accessKey, + SecretKey: secretKey, + }, + }) + return &cosClient{client: client, clientWithBucket: clientWithBucket, scType: scType}, nil + } + + return &cosClient{client: client, clientWithBucket: nil, scType: scType}, nil +} + +func (c cosClient) ListBuckets() ([]interface{}, error) { + buckets, _, err := c.client.Service.Get(context.Background()) + if err != nil { + return nil, err + } + var datas []interface{} + for _, bucket := range buckets.Buckets { + datas = append(datas, bucket.Name) + } + return datas, nil +} + +func (c cosClient) Exist(path string) (bool, error) { + exist, err := c.clientWithBucket.Object.IsExist(context.Background(), path) + if err != nil { + return false, err + } + return exist, nil +} + +func (c cosClient) Size(path string) (int64, error) { + data, _, err := c.clientWithBucket.Bucket.Get(context.Background(), &cosSDK.BucketGetOptions{Prefix: path}) + if err != nil { + return 0, err + } + if len(data.Contents) == 0 { + return 0, fmt.Errorf("no such file %s", path) + } + return data.Contents[0].Size, nil +} + +func (c cosClient) Delete(path string) (bool, error) { + if _, err := c.clientWithBucket.Object.Delete(context.Background(), path); err != nil { + return false, err + } + return true, nil +} + +func (c cosClient) Upload(src, target string) (bool, error) { + fileInfo, err := os.Stat(src) + if err != nil { + return false, err + } + if fileInfo.Size() > 5368709120 { + opt := &cosSDK.MultiUploadOptions{ + OptIni: &cosSDK.InitiateMultipartUploadOptions{ + ACLHeaderOptions: nil, + ObjectPutHeaderOptions: &cosSDK.ObjectPutHeaderOptions{ + XCosStorageClass: c.scType, + }, + }, + PartSize: 200, + } + if _, _, err := c.clientWithBucket.Object.MultiUpload( + context.Background(), target, src, opt, + ); err != nil { + return false, err + } + return true, nil + } + if _, err := c.clientWithBucket.Object.PutFromFile(context.Background(), target, src, &cosSDK.ObjectPutOptions{ + ACLHeaderOptions: nil, + ObjectPutHeaderOptions: &cosSDK.ObjectPutHeaderOptions{ + XCosStorageClass: c.scType, + }, + }); err != nil { + return false, err + } + return true, nil +} + +func (c cosClient) Download(src, target string) (bool, error) { + if _, err := c.clientWithBucket.Object.Download(context.Background(), src, target, &cosSDK.MultiDownloadOptions{}); err != nil { + return false, err + } + return true, nil +} + +func (c cosClient) ListObjects(prefix string) ([]string, error) { + datas, _, err := c.clientWithBucket.Bucket.Get(context.Background(), &cosSDK.BucketGetOptions{Prefix: prefix}) + if err != nil { + return nil, err + } + + var result []string + for _, item := range datas.Contents { + result = append(result, item.Key) + } + return result, nil +} diff --git a/agent/utils/cloud_storage/client/google_drive.go b/agent/utils/cloud_storage/client/google_drive.go new file mode 100644 index 0000000..65d8c06 --- /dev/null +++ b/agent/utils/cloud_storage/client/google_drive.go @@ -0,0 +1,435 @@ +package client + +import ( + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "strconv" + "strings" + + "github.com/go-resty/resty/v2" +) + +type googleDriveClient struct { + accessToken string +} + +func NewGoogleDriveClient(vars map[string]interface{}) (*googleDriveClient, error) { + accessToken, err := RefreshGoogleToken("refresh_token", "accessToken", vars) + if err != nil { + return nil, err + } + return &googleDriveClient{accessToken: accessToken}, nil +} + +func (g *googleDriveClient) ListBuckets() ([]interface{}, error) { + return nil, nil +} + +func (g *googleDriveClient) Exist(pathItem string) (bool, error) { + pathItem = path.Join("root", pathItem) + if _, err := g.loadFileWithName(pathItem); err != nil { + return false, err + } + return true, nil +} + +func (g *googleDriveClient) Size(pathItem string) (int64, error) { + pathItem = path.Join("root", pathItem) + fileInfo, err := g.loadFileWithName(pathItem) + if err != nil { + return 0, err + } + size, _ := strconv.ParseInt(fileInfo.Size, 10, 64) + return size, nil +} + +func (g *googleDriveClient) Delete(pathItem string) (bool, error) { + pathItem = path.Join("root", pathItem) + fileInfo, err := g.loadFileWithName(pathItem) + if err != nil { + return false, err + } + if len(fileInfo.ID) == 0 { + return false, fmt.Errorf("no such file %s", pathItem) + } + if _, err = g.googleRequest("https://www.googleapis.com/drive/v3/files/"+fileInfo.ID, http.MethodDelete, nil, nil); err != nil { + return false, err + } + return true, nil +} + +func (g *googleDriveClient) Upload(src, target string) (bool, error) { + target = path.Join("/root", target) + parentID := "root" + var err error + if path.Dir(target) != "/root" { + parentID, err = g.mkdirWithPath(path.Dir(target)) + if err != nil { + return false, err + } + } + file, err := os.Open(src) + if err != nil { + return false, err + } + defer file.Close() + fileInfo, err := file.Stat() + if err != nil { + return false, err + } + + data := map[string]interface{}{ + "name": fileInfo.Name(), + "parents": []string{parentID}, + } + urlItem := "https://www.googleapis.com/upload/drive/v3/files?uploadType=resumable&supportsAllDrives=true" + client := resty.New() + resp, err := client.R(). + SetHeader("Authorization", "Bearer "+g.accessToken). + SetBody(data). + Post(urlItem) + if err != nil { + return false, err + } + uploadUrl := resp.Header().Get("location") + + buf := make([]byte, 5*1024*1024) + var offset int64 = 0 + for { + n, err := file.Read(buf) + if err != nil && err != io.EOF { + return false, fmt.Errorf("read file failed, err: %v", err) + } + if n == 0 { + break + } + chunk := buf[:n] + start := offset + end := offset + int64(n) - 1 + if err = uploadChunk(client, uploadUrl, chunk, start, end, fileInfo.Size()); err != nil { + return false, err + } + offset += int64(n) + if err == io.EOF { + break + } + } + return true, nil +} + +func uploadChunk(client *resty.Client, uploadURL string, chunk []byte, start, end, total int64) error { + contentRange := fmt.Sprintf("bytes %d-%d/%d", start, end, total) + + resp, err := client.R(). + SetHeader("Content-Length", strconv.Itoa(len(chunk))). + SetHeader("Content-Range", contentRange). + SetBody(chunk). + Put(uploadURL) + + if err != nil { + return fmt.Errorf("new request for upload chunk failed: %v", err) + } + + if resp.StatusCode() != 200 && resp.StatusCode() != 308 { + return fmt.Errorf("upload chunk failed: %s, %s", resp.Status(), string(resp.Body())) + } + + return nil +} + +func (g *googleDriveClient) Download(src, target string) (bool, error) { + src = path.Join("/root", src) + fileInfo, err := g.loadFileWithName(src) + if err != nil { + return false, err + } + url := fmt.Sprintf("https://www.googleapis.com/drive/v3/files/%s?alt=media&acknowledgeAbuse=true", fileInfo.ID) + if err := g.handleDownload(url, target); err != nil { + return false, err + } + return true, nil +} + +func (g *googleDriveClient) ListObjects(src string) ([]string, error) { + if len(src) == 0 || src == "root" || src == "/root" { + src = "root" + } else { + src = path.Join("/root", src) + } + fileInfos, err := g.loadDirWithPath(src) + if err != nil { + return nil, err + } + var names []string + for _, item := range fileInfos { + names = append(names, item.Name) + } + return names, nil +} + +type googleFileResp struct { + Files []googleFile `json:"files"` +} +type googleFile struct { + ID string `json:"id"` + Name string `json:"name"` + Size string `json:"size"` +} + +func (g *googleDriveClient) mkdirWithPath(target string) (string, error) { + pathItems := strings.Split(target, "/") + var ( + fileInfos []googleFile + err error + ) + parentID := "root" + for i := 0; i < len(pathItems); i++ { + if len(pathItems[i]) == 0 { + continue + } + fileInfos, err = g.loadFileWithParentID(parentID) + if err != nil { + return "", err + } + isEnd := false + if i == len(pathItems)-2 { + isEnd = true + } + exist := false + for _, item := range fileInfos { + if item.Name == pathItems[i+1] { + parentID = item.ID + if isEnd { + return item.ID, nil + } else { + exist = true + } + } + } + if !exist { + parentID, err = g.mkdir(parentID, pathItems[i+1]) + if err != nil { + return parentID, err + } + if isEnd { + return parentID, nil + } + } + } + return "", errors.New("mkdir failed.") +} + +type googleMkdirRes struct { + ID string `json:"id"` +} + +func (g *googleDriveClient) mkdir(parentID, name string) (string, error) { + data := map[string]interface{}{ + "name": name, + "parents": []string{parentID}, + "mimeType": "application/vnd.google-apps.folder", + } + res, err := g.googleRequest("https://www.googleapis.com/drive/v3/files", http.MethodPost, func(req *resty.Request) { + req.SetBody(data) + }, nil) + if err != nil { + return "", err + } + var mkdirResp googleMkdirRes + if err := json.Unmarshal(res, &mkdirResp); err != nil { + return "", err + } + return mkdirResp.ID, nil +} + +func (g *googleDriveClient) handleDownload(urlItem string, target string) error { + req, err := http.NewRequest(http.MethodGet, urlItem, nil) + if err != nil { + return err + } + req.Header.Add("Authorization", "Bearer "+g.accessToken) + client := &http.Client{} + defer client.CloseIdleConnections() + response, err := client.Do(req) + if err != nil { + return err + } + defer response.Body.Close() + if response.StatusCode != http.StatusOK { + return fmt.Errorf("handle download with url failed, code: %v", response.StatusCode) + } + if _, err := os.Stat(path.Dir(target)); err != nil { + _ = os.MkdirAll(path.Dir(target), os.ModePerm) + } + out, err := os.Create(target) + if err != nil { + return err + } + defer out.Close() + if _, err = io.Copy(out, response.Body); err != nil { + return err + } + + return nil +} + +func (g *googleDriveClient) loadFileWithName(pathItem string) (googleFile, error) { + pathItems := strings.Split(pathItem, "/") + var ( + fileInfos []googleFile + err error + ) + parentID := "root" + for i := 0; i < len(pathItems); i++ { + if len(pathItems[i]) == 0 { + continue + } + fileInfos, err = g.loadFileWithParentID(parentID) + if err != nil { + return googleFile{}, err + } + isEnd := false + if i == len(pathItems)-2 { + isEnd = true + } + exist := false + for _, item := range fileInfos { + if item.Name == pathItems[i+1] { + if isEnd { + return item, nil + } else { + parentID = item.ID + exist = true + } + } + } + if !exist { + return googleFile{}, errors.New("no such file or dir") + } + + } + return googleFile{}, errors.New("no such file or dir") +} + +func (g *googleDriveClient) loadFileWithParentID(parentID string) ([]googleFile, error) { + query := map[string]string{ + "fields": "files(id,name,mimeType,size)", + "q": fmt.Sprintf("'%s' in parents and trashed = false", parentID), + } + + res, err := g.googleRequest("https://www.googleapis.com/drive/v3/files", http.MethodGet, func(req *resty.Request) { + req.SetQueryParams(query) + }, nil) + if err != nil { + return nil, err + } + var fileResp googleFileResp + if err := json.Unmarshal(res, &fileResp); err != nil { + return nil, err + } + return fileResp.Files, nil +} + +func (g googleDriveClient) loadDirWithPath(path string) ([]googleFile, error) { + pathItems := strings.Split(path, "/") + var ( + fileInfos []googleFile + err error + ) + parentID := "root" + for i := 0; i < len(pathItems); i++ { + if len(pathItems[i]) == 0 { + continue + } + fileInfos, err = g.loadFileWithParentID(parentID) + if err != nil { + return fileInfos, err + } + if i == len(pathItems)-1 { + return fileInfos, nil + } + exist := false + for _, item := range fileInfos { + if item.Name == pathItems[i+1] { + parentID = item.ID + exist = true + } + } + if !exist { + return nil, errors.New("no such file or dir") + } + } + return fileInfos, errors.New("no such file or dir") +} + +type reqCallback func(req *resty.Request) + +func (g *googleDriveClient) googleRequest(urlItem, method string, callback reqCallback, resp interface{}) ([]byte, error) { + client := resty.New() + req := client.R() + req.SetHeader("Authorization", "Bearer "+g.accessToken) + if callback != nil { + callback(req) + } + if resp != nil { + req.SetResult(req) + } + res, err := req.Execute(method, urlItem) + if err != nil { + return nil, err + } + + if res.StatusCode() == 401 { + // refresh and retry + return nil, fmt.Errorf("request for %s failed, code: %v", urlItem, res.StatusCode()) + } + if res.StatusCode() > 300 { + return nil, fmt.Errorf("request for %s failed, err: %v", urlItem, res.StatusCode()) + } + return res.Body(), nil +} + +type googleTokenRes struct { + AccessToken string `json:"access_token"` + RefreshToken string `json:"refresh_token"` +} + +func RefreshGoogleToken(grantType string, tokenType string, varMap map[string]interface{}) (string, error) { + client := resty.New() + data := map[string]interface{}{ + "client_id": loadParamFromVars("client_id", varMap), + "client_secret": loadParamFromVars("client_secret", varMap), + "redirect_uri": loadParamFromVars("redirect_uri", varMap), + } + if grantType == "refresh_token" { + data["grant_type"] = "refresh_token" + data["refresh_token"] = loadParamFromVars("refresh_token", varMap) + } else { + data["grant_type"] = "authorization_code" + data["code"] = loadParamFromVars("code", varMap) + } + urlItem := "https://www.googleapis.com/oauth2/v4/token" + resp, err := client.R(). + SetBody(data). + Post(urlItem) + if err != nil { + return "", fmt.Errorf("load account token failed, err: %v", err) + } + + if resp.StatusCode() != 200 { + return "", fmt.Errorf("load account token failed, code: %v", resp.StatusCode()) + } + var respItem googleTokenRes + if err := json.Unmarshal(resp.Body(), &respItem); err != nil { + return "", err + } + if tokenType == "accessToken" { + return respItem.AccessToken, nil + } + return respItem.RefreshToken, nil +} diff --git a/agent/utils/cloud_storage/client/helper.go b/agent/utils/cloud_storage/client/helper.go new file mode 100644 index 0000000..94523db --- /dev/null +++ b/agent/utils/cloud_storage/client/helper.go @@ -0,0 +1,18 @@ +package client + +import ( + "fmt" + + "github.com/1Panel-dev/1Panel/agent/global" +) + +func loadParamFromVars(key string, vars map[string]interface{}) string { + if _, ok := vars[key]; !ok { + if key != "bucket" && key != "port" && key != "authMode" && key != "passPhrase" { + global.LOG.Errorf("load param %s from vars failed, err: not exist!", key) + } + return "" + } + + return fmt.Sprintf("%v", vars[key]) +} diff --git a/agent/utils/cloud_storage/client/helper/webdav/auth.go b/agent/utils/cloud_storage/client/helper/webdav/auth.go new file mode 100644 index 0000000..d20218d --- /dev/null +++ b/agent/utils/cloud_storage/client/helper/webdav/auth.go @@ -0,0 +1,288 @@ +package webdav + +import ( + "bytes" + "errors" + "io" + "net/http" + "strings" + "sync" +) + +type AuthFactory func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) + +type Authorizer interface { + NewAuthenticator(body io.Reader) (Authenticator, io.Reader) + AddAuthenticator(key string, fn AuthFactory) +} + +type Authenticator interface { + Authorize(c *http.Client, rq *http.Request, path string) error + Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) + Clone() Authenticator + io.Closer +} + +type authfactory struct { + key string + create AuthFactory +} + +type authorizer struct { + factories []authfactory + defAuthMux sync.Mutex + defAuth Authenticator +} + +type preemptiveAuthorizer struct { + auth Authenticator +} + +type authShim struct { + factory AuthFactory + body io.Reader + auth Authenticator +} + +type negoAuth struct { + auths []Authenticator + setDefaultAuthenticator func(auth Authenticator) +} + +type nullAuth struct{} + +type noAuth struct{} + +func NewAutoAuth(login string, secret string) Authorizer { + fmap := make([]authfactory, 0) + az := &authorizer{factories: fmap, defAuthMux: sync.Mutex{}, defAuth: &nullAuth{}} + + az.AddAuthenticator("basic", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) { + return &BasicAuth{user: login, pw: secret}, nil + }) + + az.AddAuthenticator("digest", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) { + return NewDigestAuth(login, secret, rs) + }) + + az.AddAuthenticator("passport1.4", func(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) { + return NewPassportAuth(c, login, secret, rs.Request.URL.String(), &rs.Header) + }) + + return az +} + +func (a *authorizer) NewAuthenticator(body io.Reader) (Authenticator, io.Reader) { + var retryBuf io.Reader = body + if body != nil { + if _, ok := retryBuf.(io.Seeker); ok { + body = io.NopCloser(body) + } else { + buff := &bytes.Buffer{} + retryBuf = buff + body = io.TeeReader(body, buff) + } + } + a.defAuthMux.Lock() + defAuth := a.defAuth.Clone() + a.defAuthMux.Unlock() + + return &authShim{factory: a.factory, body: retryBuf, auth: defAuth}, body +} + +func (a *authorizer) AddAuthenticator(key string, fn AuthFactory) { + key = strings.ToLower(key) + for _, f := range a.factories { + if f.key == key { + panic("Authenticator exists: " + key) + } + } + a.factories = append(a.factories, authfactory{key, fn}) +} + +func (a *authorizer) factory(c *http.Client, rs *http.Response, path string) (auth Authenticator, err error) { + headers := rs.Header.Values("Www-Authenticate") + if len(headers) > 0 { + auths := make([]Authenticator, 0) + for _, f := range a.factories { + for _, header := range headers { + headerLower := strings.ToLower(header) + if strings.Contains(headerLower, f.key) { + rs.Header.Set("Www-Authenticate", header) + if auth, err = f.create(c, rs, path); err == nil { + auths = append(auths, auth) + break + } + } + } + } + + switch len(auths) { + case 0: + return nil, NewPathError("NoAuthenticator", path, rs.StatusCode) + case 1: + auth = auths[0] + default: + auth = &negoAuth{auths: auths, setDefaultAuthenticator: a.setDefaultAuthenticator} + } + } else { + auth = &noAuth{} + } + + a.setDefaultAuthenticator(auth) + + return auth, nil +} + +func (a *authorizer) setDefaultAuthenticator(auth Authenticator) { + a.defAuthMux.Lock() + a.defAuth.Close() + a.defAuth = auth + a.defAuthMux.Unlock() +} + +func (s *authShim) Authorize(c *http.Client, rq *http.Request, path string) error { + if err := s.auth.Authorize(c, rq, path); err != nil { + return err + } + body := s.body + rq.GetBody = func() (io.ReadCloser, error) { + if body != nil { + if sk, ok := body.(io.Seeker); ok { + if _, err := sk.Seek(0, io.SeekStart); err != nil { + return nil, err + } + } + return io.NopCloser(body), nil + } + return nil, nil + } + return nil +} + +func (s *authShim) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { + redo, err = s.auth.Verify(c, rs, path) + if err != nil && errors.Is(err, ErrAuthChanged) { + if auth, aerr := s.factory(c, rs, path); aerr == nil { + s.auth.Close() + s.auth = auth + return true, nil + } else { + return false, aerr + } + } + return +} + +func (s *authShim) Close() error { + s.auth.Close() + s.auth, s.factory = nil, nil + if s.body != nil { + if closer, ok := s.body.(io.Closer); ok { + return closer.Close() + } + } + return nil +} + +func (s *authShim) Clone() Authenticator { + return &noAuth{} +} + +func (s *authShim) String() string { + return "AuthShim" +} + +func (n *negoAuth) Authorize(c *http.Client, rq *http.Request, path string) error { + if len(n.auths) == 0 { + return NewPathError("NoAuthenticator", path, 400) + } + return n.auths[0].Authorize(c, rq, path) +} + +func (n *negoAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { + if len(n.auths) == 0 { + return false, NewPathError("NoAuthenticator", path, 400) + } + redo, err = n.auths[0].Verify(c, rs, path) + if err != nil { + if len(n.auths) > 1 { + n.auths[0].Close() + n.auths = n.auths[1:] + return true, nil + } + } else if redo { + return + } else { + auth := n.auths[0] + n.auths = n.auths[1:] + n.setDefaultAuthenticator(auth) + return + } + + return false, NewPathError("NoAuthenticator", path, rs.StatusCode) +} + +func (n *negoAuth) Close() error { + for _, a := range n.auths { + a.Close() + } + n.setDefaultAuthenticator = nil + return nil +} + +func (n *negoAuth) Clone() Authenticator { + auths := make([]Authenticator, len(n.auths)) + for i, e := range n.auths { + auths[i] = e.Clone() + } + return &negoAuth{auths: auths, setDefaultAuthenticator: n.setDefaultAuthenticator} +} + +func (n *negoAuth) String() string { + return "NegoAuth" +} + +func (n *noAuth) Authorize(c *http.Client, rq *http.Request, path string) error { + return nil +} + +func (n *noAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { + if "" != rs.Header.Get("Www-Authenticate") { + err = ErrAuthChanged + } + return +} + +func (n *noAuth) Close() error { + return nil +} + +func (n *noAuth) Clone() Authenticator { + return n +} + +func (n *noAuth) String() string { + return "NoAuth" +} + +func (n *nullAuth) Authorize(c *http.Client, rq *http.Request, path string) error { + rq.Header.Set(XInhibitRedirect, "1") + return nil +} + +func (n *nullAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { + return true, ErrAuthChanged +} + +func (n *nullAuth) Close() error { + return nil +} + +func (n *nullAuth) Clone() Authenticator { + return n +} + +func (n *nullAuth) String() string { + return "NullAuth" +} diff --git a/agent/utils/cloud_storage/client/helper/webdav/auth_basic.go b/agent/utils/cloud_storage/client/helper/webdav/auth_basic.go new file mode 100644 index 0000000..a737385 --- /dev/null +++ b/agent/utils/cloud_storage/client/helper/webdav/auth_basic.go @@ -0,0 +1,36 @@ +package webdav + +import ( + "fmt" + "net/http" +) + +type BasicAuth struct { + user string + pw string +} + +func (b *BasicAuth) Authorize(c *http.Client, rq *http.Request, path string) error { + rq.SetBasicAuth(b.user, b.pw) + return nil +} + +func (b *BasicAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { + if rs.StatusCode == 401 { + err = NewPathError("Authorize", path, rs.StatusCode) + } + return +} + +func (b *BasicAuth) Close() error { + return nil +} + +func (b *BasicAuth) Clone() Authenticator { + // no copy due to read only access + return b +} + +func (b *BasicAuth) String() string { + return fmt.Sprintf("BasicAuth login: %s", b.user) +} diff --git a/agent/utils/cloud_storage/client/helper/webdav/auth_digest.go b/agent/utils/cloud_storage/client/helper/webdav/auth_digest.go new file mode 100644 index 0000000..0e7026e --- /dev/null +++ b/agent/utils/cloud_storage/client/helper/webdav/auth_digest.go @@ -0,0 +1,173 @@ +package webdav + +import ( + "crypto/md5" + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "maps" + "net/http" + "strings" +) + +type DigestAuth struct { + user string + pw string + digestParts map[string]string +} + +func NewDigestAuth(login, secret string, rs *http.Response) (Authenticator, error) { + return &DigestAuth{user: login, pw: secret, digestParts: digestParts(rs)}, nil +} + +func (d *DigestAuth) Authorize(c *http.Client, rq *http.Request, path string) error { + d.digestParts["uri"] = path + d.digestParts["method"] = rq.Method + d.digestParts["username"] = d.user + d.digestParts["password"] = d.pw + rq.Header.Set("Authorization", getDigestAuthorization(d.digestParts)) + return nil +} + +func (d *DigestAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { + if rs.StatusCode == 401 { + if isStaled(rs) { + redo = true + err = ErrAuthChanged + } else { + err = NewPathError("Authorize", path, rs.StatusCode) + } + } + return +} + +func (d *DigestAuth) Close() error { + return nil +} + +func (d *DigestAuth) Clone() Authenticator { + var parts map[string]string + if parts = maps.Clone(parts); parts == nil { + parts = make(map[string]string) + } + return &DigestAuth{user: d.user, pw: d.pw, digestParts: parts} +} + +func (d *DigestAuth) String() string { + return fmt.Sprintf("DigestAuth login: %s", d.user) +} + +func digestParts(resp *http.Response) map[string]string { + result := map[string]string{} + if len(resp.Header["Www-Authenticate"]) > 0 { + wantedHeaders := []string{"nonce", "realm", "qop", "opaque", "algorithm", "entityBody"} + responseHeaders := strings.Split(resp.Header["Www-Authenticate"][0], ",") + for _, r := range responseHeaders { + for _, w := range wantedHeaders { + if strings.Contains(r, w) { + result[w] = strings.Trim( + strings.SplitN(r, `=`, 2)[1], + `"`, + ) + } + } + } + } + return result +} + +func getMD5(text string) string { + hasher := md5.New() + hasher.Write([]byte(text)) + return hex.EncodeToString(hasher.Sum(nil)) +} + +func getCnonce() string { + b := make([]byte, 8) + io.ReadFull(rand.Reader, b) + return fmt.Sprintf("%x", b)[:16] +} + +func getDigestAuthorization(digestParts map[string]string) string { + d := digestParts + + var ( + ha1 string + ha2 string + nonceCount = 00000001 + cnonce = getCnonce() + response string + ) + + switch d["algorithm"] { + case "MD5", "": + ha1 = getMD5(d["username"] + ":" + d["realm"] + ":" + d["password"]) + case "MD5-sess": + ha1 = getMD5( + fmt.Sprintf("%s:%v:%s", + getMD5(d["username"]+":"+d["realm"]+":"+d["password"]), + nonceCount, + cnonce, + ), + ) + } + + switch d["qop"] { + case "auth", "": + ha2 = getMD5(d["method"] + ":" + d["uri"]) + case "auth-int": + if d["entityBody"] != "" { + ha2 = getMD5(d["method"] + ":" + d["uri"] + ":" + getMD5(d["entityBody"])) + } + } + + switch d["qop"] { + case "": + response = getMD5( + fmt.Sprintf("%s:%s:%s", + ha1, + d["nonce"], + ha2, + ), + ) + case "auth", "auth-int": + response = getMD5( + fmt.Sprintf("%s:%s:%v:%s:%s:%s", + ha1, + d["nonce"], + nonceCount, + cnonce, + d["qop"], + ha2, + ), + ) + } + + authorization := fmt.Sprintf(`Digest username="%s", realm="%s", nonce="%s", uri="%s", nc=%v, cnonce="%s", response="%s"`, + d["username"], d["realm"], d["nonce"], d["uri"], nonceCount, cnonce, response) + + if d["qop"] != "" { + authorization += fmt.Sprintf(`, qop=%s`, d["qop"]) + } + + if d["opaque"] != "" { + authorization += fmt.Sprintf(`, opaque="%s"`, d["opaque"]) + } + + return authorization +} + +func isStaled(rs *http.Response) bool { + header := rs.Header.Get("Www-Authenticate") + if len(header) > 0 { + directives := strings.Split(header, ",") + for i := range directives { + name, value, _ := strings.Cut(strings.Trim(directives[i], " "), "=") + if strings.EqualFold(name, "stale") { + return strings.EqualFold(value, "true") + } + } + } + return false +} diff --git a/agent/utils/cloud_storage/client/helper/webdav/auth_passport.go b/agent/utils/cloud_storage/client/helper/webdav/auth_passport.go new file mode 100644 index 0000000..fbbe9cc --- /dev/null +++ b/agent/utils/cloud_storage/client/helper/webdav/auth_passport.go @@ -0,0 +1,160 @@ +package webdav + +import ( + "fmt" + "io" + "net/http" + "net/url" + "strings" +) + +type PassportAuth struct { + user string + pw string + cookies []http.Cookie + inhibitRedirect bool +} + +func NewPassportAuth(c *http.Client, user, pw, partnerURL string, header *http.Header) (Authenticator, error) { + p := &PassportAuth{ + user: user, + pw: pw, + inhibitRedirect: true, + } + err := p.genCookies(c, partnerURL, header) + return p, err +} + +func (p *PassportAuth) Authorize(c *http.Client, rq *http.Request, path string) error { + if p.inhibitRedirect { + rq.Header.Set(XInhibitRedirect, "1") + } else { + p.inhibitRedirect = true + } + for _, cookie := range p.cookies { + rq.AddCookie(&cookie) + } + return nil +} + +func (p *PassportAuth) Verify(c *http.Client, rs *http.Response, path string) (redo bool, err error) { + switch rs.StatusCode { + case 301, 302, 307, 308: + redo = true + if rs.Header.Get("Www-Authenticate") != "" { + err = p.genCookies(c, rs.Request.URL.String(), &rs.Header) + } else { + p.inhibitRedirect = false + } + case 401: + err = NewPathError("Authorize", path, rs.StatusCode) + } + return +} + +func (p *PassportAuth) Close() error { + return nil +} + +func (p *PassportAuth) Clone() Authenticator { + clonedCookies := make([]http.Cookie, len(p.cookies)) + copy(clonedCookies, p.cookies) + + return &PassportAuth{ + user: p.user, + pw: p.pw, + cookies: clonedCookies, + inhibitRedirect: true, + } +} + +func (p *PassportAuth) String() string { + return fmt.Sprintf("PassportAuth login: %s", p.user) +} + +func (p *PassportAuth) genCookies(c *http.Client, partnerUrl string, header *http.Header) error { + baseAuthenticationServer := header.Get("Location") + baseAuthenticationServerURL, err := url.Parse(baseAuthenticationServer) + if err != nil { + return err + } + + authenticationServerUrl := url.URL{ + Scheme: baseAuthenticationServerURL.Scheme, + Host: baseAuthenticationServerURL.Host, + Path: "/login2.srf", + } + + partnerServerChallenge := strings.Split(header.Get("Www-Authenticate"), " ")[1] + + req := http.Request{ + Method: "GET", + URL: &authenticationServerUrl, + Header: http.Header{ + "Authorization": []string{"Passport1.4 sign-in=" + url.QueryEscape(p.user) + ",pwd=" + url.QueryEscape(p.pw) + ",OrgVerb=GET,OrgUrl=" + partnerUrl + "," + partnerServerChallenge}, + }, + } + + rs, err := c.Do(&req) + if err != nil { + return err + } + io.Copy(io.Discard, rs.Body) + rs.Body.Close() + if rs.StatusCode != 200 { + return NewPathError("Authorize", "/", rs.StatusCode) + } + + tokenResponseHeader := rs.Header.Get("Authentication-Info") + if tokenResponseHeader == "" { + return NewPathError("Authorize", "/", 401) + } + tokenResponseHeaderList := strings.Split(tokenResponseHeader, ",") + token := "" + for _, tokenResponseHeader := range tokenResponseHeaderList { + if strings.HasPrefix(tokenResponseHeader, "from-PP='") { + token = tokenResponseHeader + break + } + } + if token == "" { + return NewPathError("Authorize", "/", 401) + } + + origUrl, err := url.Parse(partnerUrl) + if err != nil { + return err + } + req = http.Request{ + Method: "GET", + URL: origUrl, + Header: http.Header{ + "Authorization": []string{"Passport1.4 " + token}, + }, + } + + rs, err = c.Do(&req) + if err != nil { + return err + } + io.Copy(io.Discard, rs.Body) + rs.Body.Close() + if rs.StatusCode != 200 && rs.StatusCode != 302 { + return NewPathError("Authorize", "/", rs.StatusCode) + } + + cookies := rs.Header.Values("Set-Cookie") + p.cookies = make([]http.Cookie, len(cookies)) + for i, cookie := range cookies { + cookieParts := strings.Split(cookie, ";") + cookieName := strings.Split(cookieParts[0], "=")[0] + cookieValue := strings.Split(cookieParts[0], "=")[1] + + p.cookies[i] = http.Cookie{ + Name: cookieName, + Value: cookieValue, + } + } + + return nil +} diff --git a/agent/utils/cloud_storage/client/helper/webdav/errors.go b/agent/utils/cloud_storage/client/helper/webdav/errors.go new file mode 100644 index 0000000..5448d31 --- /dev/null +++ b/agent/utils/cloud_storage/client/helper/webdav/errors.go @@ -0,0 +1,35 @@ +package webdav + +import ( + "errors" + "fmt" + "os" +) + +var ErrAuthChanged = errors.New("authentication failed, change algorithm") + +var ErrTooManyRedirects = errors.New("stopped after 10 redirects") + +type StatusError struct { + Status int +} + +func (se StatusError) Error() string { + return fmt.Sprintf("%d", se.Status) +} + +func NewPathError(op string, path string, statusCode int) error { + return &os.PathError{ + Op: op, + Path: path, + Err: StatusError{statusCode}, + } +} + +func NewPathErrorErr(op string, path string, err error) error { + return &os.PathError{ + Op: op, + Path: path, + Err: err, + } +} diff --git a/agent/utils/cloud_storage/client/helper/webdav/file.go b/agent/utils/cloud_storage/client/helper/webdav/file.go new file mode 100644 index 0000000..dc0157a --- /dev/null +++ b/agent/utils/cloud_storage/client/helper/webdav/file.go @@ -0,0 +1,61 @@ +package webdav + +import ( + "fmt" + "os" + "time" +) + +type File struct { + path string + name string + contentType string + size int64 + modified time.Time + etag string + isdir bool +} + +func (f File) Name() string { + return f.name +} + +func (f File) ContentType() string { + return f.contentType +} + +func (f File) Size() int64 { + return f.size +} + +func (f File) Mode() os.FileMode { + if f.isdir { + return 0775 | os.ModeDir + } + + return 0664 +} + +func (f File) ModTime() time.Time { + return f.modified +} + +func (f File) ETag() string { + return f.etag +} + +func (f File) IsDir() bool { + return f.isdir +} + +func (f File) Sys() interface{} { + return nil +} + +func (f File) String() string { + if f.isdir { + return fmt.Sprintf("Dir : '%s' - '%s'", f.path, f.name) + } + + return fmt.Sprintf("File: '%s' SIZE: %d MODIFIED: %s ETAG: %s CTYPE: %s", f.path, f.size, f.modified.String(), f.etag, f.contentType) +} diff --git a/agent/utils/cloud_storage/client/helper/webdav/reques.go b/agent/utils/cloud_storage/client/helper/webdav/reques.go new file mode 100644 index 0000000..e8e2715 --- /dev/null +++ b/agent/utils/cloud_storage/client/helper/webdav/reques.go @@ -0,0 +1,95 @@ +package webdav + +import ( + "fmt" + "io" + "net/http" + "strings" +) + +func (c *Client) req(method, path string, body io.Reader, intercept func(*http.Request)) (rs *http.Response, err error) { + var redo bool + var r *http.Request + var uri = PathEscape(Join(c.root, path)) + auth, body := c.auth.NewAuthenticator(body) + defer auth.Close() + + for { + if r, err = http.NewRequest(method, uri, body); err != nil { + err = fmt.Errorf("handle request with uri: %s, method: %s failed, err: %v", uri, method, err) + return + } + + for k, vals := range c.headers { + for _, v := range vals { + r.Header.Add(k, v) + } + } + + if err = auth.Authorize(c.c, r, path); err != nil { + return + } + + if intercept != nil { + intercept(r) + } + + if rs, err = c.c.Do(r); err != nil { + err = fmt.Errorf("do request for resp with uri: %s, method: %s failed, err: %v", uri, method, err) + return + } + + if redo, err = auth.Verify(c.c, rs, path); err != nil { + rs.Body.Close() + return nil, err + } + if redo { + rs.Body.Close() + if body, err = r.GetBody(); err != nil { + return nil, err + } + continue + } + break + } + + return rs, err +} + +func (c *Client) propfind(path string, self bool, body string, resp interface{}, parse func(resp interface{}) error) error { + rs, err := c.req("PROPFIND", path, strings.NewReader(body), func(rq *http.Request) { + if self { + rq.Header.Add("Depth", "0") + } else { + rq.Header.Add("Depth", "1") + } + rq.Header.Add("Content-Type", "application/xml;charset=UTF-8") + rq.Header.Add("Accept", "application/xml,text/xml") + rq.Header.Add("Accept-Charset", "utf-8") + // TODO add support for 'gzip,deflate;q=0.8,q=0.7' + rq.Header.Add("Accept-Encoding", "") + }) + if err != nil { + return err + } + defer rs.Body.Close() + + if rs.StatusCode != 207 { + return NewPathError("PROPFIND", path, rs.StatusCode) + } + + return parseXML(rs.Body, resp, parse) +} + +func (c *Client) put(path string, stream io.Reader, contentLength int64) (status int, err error) { + rs, err := c.req("PUT", path, stream, func(r *http.Request) { + r.ContentLength = contentLength + }) + if err != nil { + return + } + defer rs.Body.Close() + + status = rs.StatusCode + return +} diff --git a/agent/utils/cloud_storage/client/helper/webdav/utils.go b/agent/utils/cloud_storage/client/helper/webdav/utils.go new file mode 100644 index 0000000..a6b7d47 --- /dev/null +++ b/agent/utils/cloud_storage/client/helper/webdav/utils.go @@ -0,0 +1,87 @@ +package webdav + +import ( + "encoding/xml" + "io" + "net/url" + "path" + "strconv" + "strings" +) + +func PathEscape(path string) string { + s := strings.Split(path, "/") + for i, e := range s { + s[i] = url.PathEscape(e) + } + return strings.Join(s, "/") +} + +func FixSlash(s string) string { + if !strings.HasSuffix(s, "/") { + s += "/" + } + return s +} + +func SplitPathToHierarchy(fullPath string) []string { + cleanPath := path.Clean(fullPath) + parts := strings.Split(cleanPath, "/") + + var result []string + currentPath := "" + + for _, part := range parts { + if part == "" { + currentPath = "/" + result = append(result, currentPath) + continue + } + + if currentPath == "/" { + currentPath = path.Join(currentPath, part) + } else { + currentPath = path.Join(currentPath, part) + } + + result = append(result, currentPath) + } + + return result +} + +func FixSlashes(s string) string { + if !strings.HasPrefix(s, "/") { + s = "/" + s + } + + return FixSlash(s) +} + +func Join(path0 string, path1 string) string { + return strings.TrimSuffix(path0, "/") + "/" + strings.TrimPrefix(path1, "/") +} + +func parseInt64(s *string) int64 { + if n, e := strconv.ParseInt(*s, 10, 64); e == nil { + return n + } + return 0 +} + +func parseXML(data io.Reader, resp interface{}, parse func(resp interface{}) error) error { + decoder := xml.NewDecoder(data) + for t, _ := decoder.Token(); t != nil; t, _ = decoder.Token() { + switch se := t.(type) { + case xml.StartElement: + if se.Name.Local == "response" { + if e := decoder.DecodeElement(resp, &se); e == nil { + if err := parse(resp); err != nil { + return err + } + } + } + } + } + return nil +} diff --git a/agent/utils/cloud_storage/client/helper/webdav/webdav.go b/agent/utils/cloud_storage/client/helper/webdav/webdav.go new file mode 100644 index 0000000..b26b308 --- /dev/null +++ b/agent/utils/cloud_storage/client/helper/webdav/webdav.go @@ -0,0 +1,261 @@ +package webdav + +import ( + "bytes" + "encoding/xml" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path/filepath" + "strings" +) + +const XInhibitRedirect = "X-Gowebdav-Inhibit-Redirect" +const template = ` + + + + + +` + +type Client struct { + root string + headers http.Header + c *http.Client + auth Authorizer +} + +func NewClient(uri, user, pw string) *Client { + return NewAuthClient(uri, NewAutoAuth(user, pw)) +} + +func NewAuthClient(uri string, auth Authorizer) *Client { + c := &http.Client{ + CheckRedirect: func(rq *http.Request, via []*http.Request) error { + if len(via) >= 10 { + return ErrTooManyRedirects + } + if via[0].Header.Get(XInhibitRedirect) != "" { + return http.ErrUseLastResponse + } + return nil + }, + } + return &Client{root: FixSlash(uri), headers: make(http.Header), c: c, auth: auth} +} + +func (c *Client) SetTransport(transport http.RoundTripper) { + c.c.Transport = transport +} + +func (c *Client) Connect() error { + rs, err := c.req("OPTIONS", "/", nil, func(rq *http.Request) { rq.Header.Add("Depth", "0") }) + if err != nil { + return err + } + defer rs.Body.Close() + + if rs.StatusCode != 200 && rs.StatusCode != 204 { + return fmt.Errorf("check conn failed, code: %d, err: %v", rs.StatusCode, rs.Status) + } + + return nil +} + +type props struct { + Status string `xml:"DAV: status"` + Name string `xml:"DAV: prop>displayname,omitempty"` + Type xml.Name `xml:"DAV: prop>resourcetype>collection,omitempty"` + Size string `xml:"DAV: prop>getcontentlength,omitempty"` +} + +type response struct { + Href string `xml:"DAV: href"` + Props []props `xml:"DAV: propstat"` +} + +func getProps(r *response, status string) *props { + for _, prop := range r.Props { + if strings.Contains(prop.Status, status) { + return &prop + } + } + return nil +} + +func (c *Client) ReadDir(path string) ([]os.FileInfo, error) { + path = FixSlashes(path) + files := make([]os.FileInfo, 0) + skipSelf := true + parse := func(resp interface{}) error { + r := resp.(*response) + + if skipSelf { + skipSelf = false + if p := getProps(r, "200"); p != nil && p.Type.Local == "collection" { + r.Props = nil + return nil + } + return NewPathError("ReadDir", path, 405) + } + + if p := getProps(r, "200"); p != nil { + f := new(File) + if ps, err := url.PathUnescape(r.Href); err == nil { + f.name = filepath.Base(ps) + } else { + f.name = p.Name + } + f.path = path + f.name + if p.Type.Local == "collection" { + f.path += "/" + f.size = 0 + f.isdir = true + } else { + f.size = parseInt64(&p.Size) + f.isdir = false + } + + files = append(files, *f) + } + + r.Props = nil + return nil + } + + if err := c.propfind(path, false, template, &response{}, parse); err != nil { + if _, ok := err.(*os.PathError); !ok { + return files, fmt.Errorf("load files from %s failed, err: %v", path, err) + } + } + return files, nil +} + +func (c *Client) Stat(path string) (os.FileInfo, error) { + var f *File + parse := func(resp interface{}) error { + r := resp.(*response) + if p := getProps(r, "200"); p != nil && f == nil { + f = new(File) + f.name = p.Name + f.path = path + + if p.Type.Local == "collection" { + if !strings.HasSuffix(f.path, "/") { + f.path += "/" + } + f.size = 0 + f.isdir = true + } else { + f.size = parseInt64(&p.Size) + f.isdir = false + } + } + + r.Props = nil + return nil + } + + if err := c.propfind(path, true, template, &response{}, parse); err != nil { + if _, ok := err.(*os.PathError); !ok { + return f, fmt.Errorf("load file %s failed, path err: %v", path, err) + } + return f, fmt.Errorf("load file %s failed, err: %v", path, err) + } + return f, nil +} + +func (c *Client) RemoveAll(path string) error { + rs, err := c.req("DELETE", path, nil, nil) + if err != nil { + return fmt.Errorf("handle remove file %s failed, err: %s", path, err) + } + defer rs.Body.Close() + if rs.StatusCode == 200 || rs.StatusCode == 204 || rs.StatusCode == 404 { + return nil + } + return fmt.Errorf("handle remove file %s failed, code: %d, err: %s", path, rs.StatusCode, rs.Status) +} + +func (c *Client) MkdirAll(path string, _ os.FileMode) (err error) { + parentPath := filepath.Dir(path) + if parentPath == "." || parentPath == "/" { + return nil + } + + paths := SplitPathToHierarchy(parentPath) + for _, item := range paths { + itemFile, err := c.Stat(item) + if err == nil && itemFile.IsDir() { + continue + } + rs, err := c.req("MKCOL", item, nil, nil) + if err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", item, err) + } + defer rs.Body.Close() + if rs.StatusCode != 201 && rs.StatusCode != 200 { + return fmt.Errorf("mkdir %s failed, code: %d, err: %v", item, rs.StatusCode, rs.Status) + } + } + return nil +} + +func (c *Client) ReadStream(path string) (io.ReadCloser, error) { + rs, err := c.req("GET", path, nil, nil) + if err != nil { + return nil, NewPathErrorErr("ReadStream", path, err) + } + + if rs.StatusCode == 200 { + return rs.Body, nil + } + + rs.Body.Close() + return nil, NewPathError("ReadStream", path, rs.StatusCode) +} + +func (c *Client) WriteStream(path string, stream io.Reader, _ os.FileMode) (err error) { + err = c.MkdirAll(path, 0755) + if err != nil { + return err + } + + contentLength := int64(0) + if seeker, ok := stream.(io.Seeker); ok { + contentLength, err = seeker.Seek(0, io.SeekEnd) + if err != nil { + return err + } + + _, err = seeker.Seek(0, io.SeekStart) + if err != nil { + return err + } + } else { + buffer := bytes.NewBuffer(make([]byte, 0, 1024*1024 /* 1MB */)) + + contentLength, err = io.Copy(buffer, stream) + if err != nil { + return err + } + + stream = buffer + } + + s, err := c.put(path, stream, contentLength) + if err != nil { + return err + } + + switch s { + case 200, 201, 204: + return nil + + default: + return NewPathError("WriteStream", path, s) + } +} diff --git a/agent/utils/cloud_storage/client/kodo.go b/agent/utils/cloud_storage/client/kodo.go new file mode 100644 index 0000000..2038cc4 --- /dev/null +++ b/agent/utils/cloud_storage/client/kodo.go @@ -0,0 +1,122 @@ +package client + +import ( + "context" + "strconv" + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/qiniu/go-sdk/v7/auth" + "github.com/qiniu/go-sdk/v7/storage" +) + +type kodoClient struct { + bucket string + domain string + timeout string + auth *auth.Credentials + client *storage.BucketManager +} + +func NewKodoClient(vars map[string]interface{}) (*kodoClient, error) { + accessKey := loadParamFromVars("accessKey", vars) + secretKey := loadParamFromVars("secretKey", vars) + bucket := loadParamFromVars("bucket", vars) + domain := loadParamFromVars("domain", vars) + timeout := loadParamFromVars("timeout", vars) + if timeout == "" { + timeout = "1" + } + conn := auth.New(accessKey, secretKey) + cfg := storage.Config{ + UseHTTPS: false, + } + bucketManager := storage.NewBucketManager(conn, &cfg) + + return &kodoClient{client: bucketManager, auth: conn, bucket: bucket, domain: domain, timeout: timeout}, nil +} + +func (k kodoClient) ListBuckets() ([]interface{}, error) { + buckets, err := k.client.Buckets(true) + if err != nil { + return nil, err + } + var datas []interface{} + for _, bucket := range buckets { + datas = append(datas, bucket) + } + return datas, nil +} + +func (k kodoClient) Exist(path string) (bool, error) { + if _, err := k.client.Stat(k.bucket, path); err != nil { + return false, err + } + return true, nil +} + +func (k kodoClient) Size(path string) (int64, error) { + file, err := k.client.Stat(k.bucket, path) + if err != nil { + return 0, err + } + return file.Fsize, nil +} + +func (k kodoClient) Delete(path string) (bool, error) { + if err := k.client.Delete(k.bucket, path); err != nil { + return false, err + } + return true, nil +} + +func (k kodoClient) Upload(src, target string) (bool, error) { + + int64Value, _ := strconv.ParseInt(k.timeout, 10, 64) + unixTimestamp := int64Value * 3600 + + putPolicy := storage.PutPolicy{ + Scope: k.bucket, + Expires: uint64(unixTimestamp), + } + upToken := putPolicy.UploadToken(k.auth) + cfg := storage.Config{UseHTTPS: true, UseCdnDomains: false} + resumeUploader := storage.NewResumeUploaderV2(&cfg) + ret := storage.PutRet{} + putExtra := storage.RputV2Extra{} + if err := resumeUploader.PutFile(context.Background(), &ret, upToken, target, src, &putExtra); err != nil { + return false, err + } + return true, nil +} + +func (k kodoClient) Download(src, target string) (bool, error) { + deadline := time.Now().Add(time.Second * 3600).Unix() + privateAccessURL := storage.MakePrivateURL(k.auth, k.domain, src, deadline) + + fo := files.NewFileOp() + if err := fo.DownloadFile(privateAccessURL, target); err != nil { + return false, err + } + return true, nil +} + +func (k kodoClient) ListObjects(prefix string) ([]string, error) { + var result []string + marker := "" + for { + entries, _, nextMarker, hashNext, err := k.client.ListFiles(k.bucket, prefix, "", marker, 1000) + if err != nil { + return nil, err + } + for _, entry := range entries { + result = append(result, entry.Key) + } + if hashNext { + marker = nextMarker + } else { + break + } + } + return result, nil +} diff --git a/agent/utils/cloud_storage/client/local.go b/agent/utils/cloud_storage/client/local.go new file mode 100644 index 0000000..852c5ce --- /dev/null +++ b/agent/utils/cloud_storage/client/local.go @@ -0,0 +1,93 @@ +package client + +import ( + "fmt" + "os" + "path" + + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +type localClient struct{} + +func NewLocalClient(vars map[string]interface{}) (*localClient, error) { + return &localClient{}, nil +} + +func (c localClient) ListBuckets() ([]interface{}, error) { + return nil, nil +} + +func (c localClient) Exist(file string) (bool, error) { + _, err := os.Stat(file) + return err == nil, err +} + +func (c localClient) Size(file string) (int64, error) { + fileInfo, err := os.Stat(file) + if err != nil { + return 0, err + } + return fileInfo.Size(), nil +} + +func (c localClient) Delete(file string) (bool, error) { + if err := os.RemoveAll(file); err != nil { + return false, err + } + return true, nil +} + +func (c localClient) Upload(src, target string) (bool, error) { + fileOp := files.NewFileOp() + if _, err := os.Stat(path.Dir(target)); err != nil { + if os.IsNotExist(err) { + if err = os.MkdirAll(path.Dir(target), os.ModePerm); err != nil { + return false, err + } + } else { + return false, err + } + } + + if err := fileOp.CopyAndReName(src, target, "", true); err != nil { + return false, fmt.Errorf("cp file failed, err: %v", err) + } + return true, nil +} + +func (c localClient) Download(src, target string) (bool, error) { + fileOp := files.NewFileOp() + if _, err := os.Stat(path.Dir(target)); err != nil { + if os.IsNotExist(err) { + if err = os.MkdirAll(path.Dir(target), os.ModePerm); err != nil { + return false, err + } + } else { + return false, err + } + } + + if err := fileOp.CopyAndReName(src, target, "", true); err != nil { + return false, fmt.Errorf("cp file failed, err: %v", err) + } + return true, nil +} + +func (c localClient) ListObjects(prefix string) ([]string, error) { + var files []string + if _, err := os.Stat(prefix); err != nil { + return files, nil + } + list, err := os.ReadDir(prefix) + if err != nil { + return files, err + } + for i := 0; i < len(list); i++ { + if !list[i].IsDir() { + files = append(files, list[i].Name()) + } + } + + return files, nil +} diff --git a/agent/utils/cloud_storage/client/minio.go b/agent/utils/cloud_storage/client/minio.go new file mode 100644 index 0000000..36fef7d --- /dev/null +++ b/agent/utils/cloud_storage/client/minio.go @@ -0,0 +1,150 @@ +package client + +import ( + "context" + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + "strings" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/minio/minio-go/v7" + "github.com/minio/minio-go/v7/pkg/credentials" +) + +type minIoClient struct { + bucket string + client *minio.Client +} + +func NewMinIoClient(vars map[string]interface{}) (*minIoClient, error) { + endpoint := loadParamFromVars("endpoint", vars) + accessKeyID := loadParamFromVars("accessKey", vars) + secretAccessKey := loadParamFromVars("secretKey", vars) + bucket := loadParamFromVars("bucket", vars) + ssl := strings.Split(endpoint, ":")[0] + if len(ssl) == 0 || (ssl != "https" && ssl != "http") { + return nil, buserr.WithDetail("ErrInvalidParams", "ssl", fmt.Errorf("no such proto in ssl: %s", ssl)) + } + + secure := false + tlsConfig := &tls.Config{} + if ssl == "https" { + secure = true + tlsConfig.InsecureSkipVerify = true + } + var transport http.RoundTripper = &http.Transport{ + TLSClientConfig: tlsConfig, + } + client, err := minio.New(strings.ReplaceAll(endpoint, ssl+"://", ""), &minio.Options{ + Creds: credentials.NewStaticV4(accessKeyID, secretAccessKey, ""), + Secure: secure, + Transport: transport, + }) + if err != nil { + return nil, err + } + return &minIoClient{bucket: bucket, client: client}, nil +} + +func (m minIoClient) ListBuckets() ([]interface{}, error) { + buckets, err := m.client.ListBuckets(context.Background()) + if err != nil { + return nil, err + } + var result []interface{} + for _, bucket := range buckets { + result = append(result, bucket.Name) + } + return result, err +} + +func (m minIoClient) Exist(path string) (bool, error) { + if _, err := m.client.GetObject(context.Background(), m.bucket, path, minio.GetObjectOptions{}); err != nil { + return false, err + } + return true, nil +} + +func (m minIoClient) Size(path string) (int64, error) { + obj, err := m.client.GetObject(context.Background(), m.bucket, path, minio.GetObjectOptions{}) + if err != nil { + return 0, err + } + file, err := obj.Stat() + if err != nil { + return 0, err + } + return file.Size, nil +} + +func (m minIoClient) Delete(path string) (bool, error) { + object, err := m.client.GetObject(context.Background(), m.bucket, path, minio.GetObjectOptions{}) + if err != nil { + return false, err + } + info, err := object.Stat() + if err != nil { + return false, err + } + if err = m.client.RemoveObject(context.Background(), m.bucket, path, minio.RemoveObjectOptions{ + GovernanceBypass: true, + VersionID: info.VersionID, + }); err != nil { + return false, err + } + return true, nil +} + +func (m minIoClient) Upload(src, target string) (bool, error) { + file, err := os.Open(src) + if err != nil { + return false, err + } + defer file.Close() + + fileStat, err := file.Stat() + if err != nil { + return false, err + } + _, err = m.client.PutObject(context.Background(), m.bucket, target, file, fileStat.Size(), minio.PutObjectOptions{ContentType: "application/octet-stream"}) + if err != nil { + return false, err + } + return true, nil +} + +func (m minIoClient) Download(src, target string) (bool, error) { + object, err := m.client.GetObject(context.Background(), m.bucket, src, minio.GetObjectOptions{}) + if err != nil { + return false, err + } + defer object.Close() + localFile, err := os.Create(target) + if err != nil { + return false, err + } + defer localFile.Close() + if _, err = io.Copy(localFile, object); err != nil { + return false, err + } + return true, nil +} + +func (m minIoClient) ListObjects(prefix string) ([]string, error) { + opts := minio.ListObjectsOptions{ + Recursive: true, + Prefix: prefix, + } + + var result []string + for object := range m.client.ListObjects(context.Background(), m.bucket, opts) { + if object.Err != nil { + continue + } + result = append(result, object.Key) + } + return result, nil +} diff --git a/agent/utils/cloud_storage/client/onedrive.go b/agent/utils/cloud_storage/client/onedrive.go new file mode 100644 index 0000000..1e0abd7 --- /dev/null +++ b/agent/utils/cloud_storage/client/onedrive.go @@ -0,0 +1,406 @@ +package client + +import ( + "bufio" + "bytes" + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/files" + odsdk "github.com/goh-chunlin/go-onedrive/onedrive" + "golang.org/x/oauth2" +) + +type oneDriveClient struct { + client odsdk.Client +} + +func NewOneDriveClient(vars map[string]interface{}) (*oneDriveClient, error) { + token, err := RefreshToken("refresh_token", "accessToken", vars) + if err != nil { + return nil, err + } + isCN := loadParamFromVars("isCN", vars) + ctx := context.Background() + ts := oauth2.StaticTokenSource( + &oauth2.Token{AccessToken: token}, + ) + tc := oauth2.NewClient(ctx, ts) + + client := odsdk.NewClient(tc) + if isCN == "true" { + client.BaseURL, _ = url.Parse("https://microsoftgraph.chinacloudapi.cn/v1.0/") + } + return &oneDriveClient{client: *client}, nil +} + +func (o oneDriveClient) ListBuckets() ([]interface{}, error) { + return nil, nil +} + +func (o oneDriveClient) Exist(path string) (bool, error) { + path = "/" + strings.TrimPrefix(path, "/") + fileID, err := o.loadIDByPath(path) + if err != nil { + return false, err + } + + return len(fileID) != 0, nil +} + +func (o oneDriveClient) Size(path string) (int64, error) { + path = "/" + strings.TrimPrefix(path, "/") + pathItem := "root:" + path + if path == "/" { + pathItem = "root" + } + req, err := o.client.NewRequest("GET", fmt.Sprintf("me/drive/%s", pathItem), nil) + if err != nil { + return 0, fmt.Errorf("new request for file id failed, err: %v", err) + } + var driveItem myDriverItem + if err := o.client.Do(context.Background(), req, false, &driveItem); err != nil { + return 0, fmt.Errorf("do request for file id failed, err: %v", err) + } + + return driveItem.Size, nil +} + +type myDriverItem struct { + Name string `json:"name"` + Id string `json:"id"` + Size int64 `json:"size"` +} + +func (o oneDriveClient) Delete(path string) (bool, error) { + path = "/" + strings.TrimPrefix(path, "/") + req, err := o.client.NewRequest("DELETE", fmt.Sprintf("me/drive/root:%s", path), nil) + if err != nil { + return false, fmt.Errorf("new request for delete file failed, err: %v \n", err) + } + if err := o.client.Do(context.Background(), req, false, nil); err != nil { + return false, fmt.Errorf("do request for delete file failed, err: %v \n", err) + } + + return true, nil +} + +func (o oneDriveClient) Upload(src, target string) (bool, error) { + target = "/" + strings.TrimPrefix(target, "/") + if _, err := o.loadIDByPath(path.Dir(target)); err != nil { + if !strings.Contains(err.Error(), "itemNotFound") { + return false, err + } + if err := o.createFolder(path.Dir(target)); err != nil { + return false, fmt.Errorf("create dir before upload failed, err: %v", err) + } + } + + ctx := context.Background() + folderID, err := o.loadIDByPath(path.Dir(target)) + if err != nil { + return false, err + } + fileInfo, err := os.Stat(src) + if err != nil { + return false, err + } + if fileInfo.IsDir() { + return false, errors.New("only file is allowed to be uploaded here") + } + var isOk bool + if fileInfo.Size() < 4*1024*1024 { + isOk, err = o.upSmall(src, folderID, fileInfo.Size()) + } else { + isOk, err = o.upBig(ctx, src, folderID, fileInfo.Size()) + } + return isOk, err +} + +func (o oneDriveClient) Download(src, target string) (bool, error) { + src = "/" + strings.TrimPrefix(src, "/") + req, err := o.client.NewRequest("GET", fmt.Sprintf("me/drive/root:%s", src), nil) + if err != nil { + return false, fmt.Errorf("new request for file id failed, err: %v", err) + } + var driveItem *odsdk.DriveItem + if err := o.client.Do(context.Background(), req, false, &driveItem); err != nil { + return false, fmt.Errorf("do request for file id failed, err: %v", err) + } + + resp, err := http.Get(driveItem.DownloadURL) + if err != nil { + return false, err + } + defer resp.Body.Close() + + out, err := os.Create(target) + if err != nil { + return false, err + } + defer out.Close() + buffer := make([]byte, 2*1024*1024) + + _, err = io.CopyBuffer(out, resp.Body, buffer) + if err != nil { + return false, err + } + + return true, nil +} + +func (o *oneDriveClient) ListObjects(prefix string) ([]string, error) { + prefix = "/" + strings.TrimPrefix(prefix, "/") + folderID, err := o.loadIDByPath(prefix) + if err != nil { + return nil, err + } + + req, err := o.client.NewRequest("GET", fmt.Sprintf("me/drive/items/%s/children", folderID), nil) + if err != nil { + return nil, fmt.Errorf("new request for list failed, err: %v", err) + } + var driveItems *odsdk.OneDriveDriveItemsResponse + if err := o.client.Do(context.Background(), req, false, &driveItems); err != nil { + return nil, fmt.Errorf("do request for list failed, err: %v", err) + } + + var itemList []string + for _, item := range driveItems.DriveItems { + itemList = append(itemList, item.Name) + } + return itemList, nil +} + +func (o *oneDriveClient) loadIDByPath(path string) (string, error) { + pathItem := "root:" + path + if path == "/" { + pathItem = "root" + } + req, err := o.client.NewRequest("GET", fmt.Sprintf("me/drive/%s", pathItem), nil) + if err != nil { + return "", fmt.Errorf("new request for file id failed, err: %v", err) + } + var driveItem *odsdk.DriveItem + if err := o.client.Do(context.Background(), req, false, &driveItem); err != nil { + return "", fmt.Errorf("do request for file id failed, err: %v", err) + } + return driveItem.Id, nil +} + +func RefreshToken(grantType string, tokenType string, varMap map[string]interface{}) (string, error) { + data := url.Values{} + isCN := loadParamFromVars("isCN", varMap) + data.Set("client_id", loadParamFromVars("client_id", varMap)) + data.Set("client_secret", loadParamFromVars("client_secret", varMap)) + if grantType == "refresh_token" { + data.Set("grant_type", "refresh_token") + data.Set("refresh_token", loadParamFromVars("refresh_token", varMap)) + } else { + data.Set("grant_type", "authorization_code") + data.Set("code", loadParamFromVars("code", varMap)) + } + data.Set("redirect_uri", loadParamFromVars("redirect_uri", varMap)) + client := &http.Client{} + defer client.CloseIdleConnections() + url := "https://login.microsoftonline.com/common/oauth2/v2.0/token" + if isCN == "true" { + url = "https://login.chinacloudapi.cn/common/oauth2/v2.0/token" + } + req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode())) + if err != nil { + return "", fmt.Errorf("new http post client for access token failed, err: %v", err) + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("request for access token failed, err: %v", err) + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("read data from response body failed, err: %v", err) + } + + tokenMap := map[string]interface{}{} + if err := json.Unmarshal(respBody, &tokenMap); err != nil { + return "", fmt.Errorf("unmarshal data from response body failed, err: %v", err) + } + if tokenType == "accessToken" { + accessToken, ok := tokenMap["access_token"].(string) + if !ok { + return "", errors.New("no such access token in response") + } + tokenMap = nil + return accessToken, nil + } + refreshToken, ok := tokenMap["refresh_token"].(string) + if !ok { + return "", errors.New("no such access token in response") + } + tokenMap = nil + return refreshToken, nil +} + +func (o *oneDriveClient) createFolder(parent string) error { + if _, err := o.loadIDByPath(path.Dir(parent)); err != nil { + if !strings.Contains(err.Error(), "itemNotFound") { + return err + } + _ = o.createFolder(path.Dir(parent)) + } + item2, err := o.loadIDByPath(path.Dir(parent)) + if err != nil { + return err + } + if _, err := o.client.DriveItems.CreateNewFolder(context.Background(), "", item2, path.Base(parent)); err != nil { + return err + } + return nil +} + +type NewUploadSessionCreationRequest struct { + ConflictBehavior string `json:"@microsoft.graph.conflictBehavior,omitempty"` +} +type NewUploadSessionCreationResponse struct { + UploadURL string `json:"uploadUrl"` + ExpirationDateTime string `json:"expirationDateTime"` +} +type UploadSessionUploadResponse struct { + ExpirationDateTime string `json:"expirationDateTime"` + NextExpectedRanges []string `json:"nextExpectedRanges"` + DriveItem +} +type DriveItem struct { + Name string `json:"name"` + Id string `json:"id"` + DownloadURL string `json:"@microsoft.graph.downloadUrl"` + Description string `json:"description"` + Size int64 `json:"size"` + WebURL string `json:"webUrl"` +} + +func (o *oneDriveClient) NewSessionFileUploadRequest(absoluteUrl string, grandOffset, grandTotalSize int64, byteReader *bytes.Reader) (*http.Request, error) { + apiUrl, err := o.client.BaseURL.Parse(absoluteUrl) + if err != nil { + return nil, err + } + absoluteUrl = apiUrl.String() + contentLength := byteReader.Size() + req, err := http.NewRequest("PUT", absoluteUrl, byteReader) + req.Header.Set("Content-Length", strconv.FormatInt(contentLength, 10)) + preliminaryLength := grandOffset + preliminaryRange := grandOffset + contentLength - 1 + if preliminaryRange >= grandTotalSize { + preliminaryRange = grandTotalSize - 1 + preliminaryLength = preliminaryRange - grandOffset + 1 + } + req.Header.Set("Content-Range", fmt.Sprintf("bytes %d-%d/%d", preliminaryLength, preliminaryRange, grandTotalSize)) + + return req, err +} + +func (o *oneDriveClient) upSmall(srcPath, folderID string, fileSize int64) (bool, error) { + file, err := os.Open(srcPath) + if err != nil { + return false, err + } + defer file.Close() + + buffer := make([]byte, fileSize) + _, _ = file.Read(buffer) + fileReader := bytes.NewReader(buffer) + apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/content?@microsoft.graph.conflictBehavior=rename", url.PathEscape(folderID), path.Base(srcPath)) + + mimeType := files.GetMimeType(srcPath) + req, err := o.client.NewFileUploadRequest(apiURL, mimeType, fileReader) + if err != nil { + return false, err + } + var response *DriveItem + if err := o.client.Do(context.Background(), req, false, &response); err != nil { + return false, fmt.Errorf("do request for list failed, err: %v", err) + } + return true, nil +} + +func (o *oneDriveClient) upBig(ctx context.Context, srcPath, folderID string, fileSize int64) (bool, error) { + file, err := os.Open(srcPath) + if err != nil { + return false, err + } + defer file.Close() + + apiURL := fmt.Sprintf("me/drive/items/%s:/%s:/createUploadSession", url.PathEscape(folderID), path.Base(srcPath)) + sessionCreationRequestInside := NewUploadSessionCreationRequest{ + ConflictBehavior: "rename", + } + + sessionCreationRequest := struct { + Item NewUploadSessionCreationRequest `json:"item"` + DeferCommit bool `json:"deferCommit"` + }{sessionCreationRequestInside, false} + + sessionCreationReq, err := o.client.NewRequest("POST", apiURL, sessionCreationRequest) + if err != nil { + return false, err + } + + var sessionCreationResp *NewUploadSessionCreationResponse + err = o.client.Do(ctx, sessionCreationReq, false, &sessionCreationResp) + if err != nil { + return false, fmt.Errorf("session creation failed %w", err) + } + + fileSessionUploadUrl := sessionCreationResp.UploadURL + + sizePerSplit := int64(5 * 1024 * 1024) + buffer := make([]byte, 5*1024*1024) + splitCount := fileSize / sizePerSplit + if fileSize%sizePerSplit != 0 { + splitCount += 1 + } + bfReader := bufio.NewReader(file) + httpClient := http.Client{ + Timeout: time.Minute * 10, + Transport: &http.Transport{ + Proxy: http.ProxyFromEnvironment, + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + }, + } + for splitNow := int64(0); splitNow < splitCount; splitNow++ { + length, err := bfReader.Read(buffer) + if err != nil { + return false, err + } + if int64(length) < sizePerSplit { + bufferLast := buffer[:length] + buffer = bufferLast + } + sessionFileUploadReq, err := o.NewSessionFileUploadRequest(fileSessionUploadUrl, splitNow*sizePerSplit, fileSize, bytes.NewReader(buffer)) + if err != nil { + return false, err + } + res, err := httpClient.Do(sessionFileUploadReq) + if err != nil { + return false, err + } + res.Body.Close() + if res.StatusCode != 201 && res.StatusCode != 202 && res.StatusCode != 200 { + data, _ := io.ReadAll(res.Body) + return false, errors.New(string(data)) + } + } + return true, nil +} diff --git a/agent/utils/cloud_storage/client/oss.go b/agent/utils/cloud_storage/client/oss.go new file mode 100644 index 0000000..e4fed15 --- /dev/null +++ b/agent/utils/cloud_storage/client/oss.go @@ -0,0 +1,118 @@ +package client + +import ( + "fmt" + + osssdk "github.com/aliyun/aliyun-oss-go-sdk/oss" +) + +type ossClient struct { + scType string + bucketStr string + client osssdk.Client +} + +func NewOssClient(vars map[string]interface{}) (*ossClient, error) { + endpoint := loadParamFromVars("endpoint", vars) + accessKey := loadParamFromVars("accessKey", vars) + secretKey := loadParamFromVars("secretKey", vars) + bucketStr := loadParamFromVars("bucket", vars) + scType := loadParamFromVars("scType", vars) + if len(scType) == 0 { + scType = "Standard" + } + client, err := osssdk.New(endpoint, accessKey, secretKey) + if err != nil { + return nil, err + } + + return &ossClient{scType: scType, bucketStr: bucketStr, client: *client}, nil +} + +func (o ossClient) ListBuckets() ([]interface{}, error) { + response, err := o.client.ListBuckets() + if err != nil { + return nil, err + } + var result []interface{} + for _, bucket := range response.Buckets { + result = append(result, bucket.Name) + } + return result, err +} + +func (o ossClient) Exist(path string) (bool, error) { + bucket, err := o.client.Bucket(o.bucketStr) + if err != nil { + return false, err + } + return bucket.IsObjectExist(path) +} + +func (o ossClient) Size(path string) (int64, error) { + bucket, err := o.client.Bucket(o.bucketStr) + if err != nil { + return 0, err + } + lor, err := bucket.ListObjectsV2(osssdk.Prefix(path)) + if err != nil { + return 0, err + } + if len(lor.Objects) == 0 { + return 0, fmt.Errorf("no such file %s", path) + } + return lor.Objects[0].Size, nil +} + +func (o ossClient) Delete(path string) (bool, error) { + bucket, err := o.client.Bucket(o.bucketStr) + if err != nil { + return false, err + } + if err := bucket.DeleteObject(path); err != nil { + return false, err + } + return true, nil +} + +func (o ossClient) Upload(src, target string) (bool, error) { + bucket, err := o.client.Bucket(o.bucketStr) + if err != nil { + return false, err + } + if err := bucket.UploadFile(target, src, + 200*1024*1024, + osssdk.Routines(5), + osssdk.Checkpoint(true, ""), + osssdk.ObjectStorageClass(osssdk.StorageClassType(o.scType))); err != nil { + return false, err + } + return true, nil +} + +func (o ossClient) Download(src, target string) (bool, error) { + bucket, err := o.client.Bucket(o.bucketStr) + if err != nil { + return false, err + } + if err := bucket.DownloadFile(src, target, 200*1024*1024, osssdk.Routines(5), osssdk.Checkpoint(true, "")); err != nil { + return false, err + } + return true, nil +} + +func (o *ossClient) ListObjects(prefix string) ([]string, error) { + bucket, err := o.client.Bucket(o.bucketStr) + if err != nil { + return nil, err + } + lor, err := bucket.ListObjectsV2(osssdk.Prefix(prefix)) + if err != nil { + return nil, err + } + var result []string + for _, obj := range lor.Objects { + result = append(result, obj.Key) + } + return result, nil +} diff --git a/agent/utils/cloud_storage/client/s3.go b/agent/utils/cloud_storage/client/s3.go new file mode 100644 index 0000000..3a00647 --- /dev/null +++ b/agent/utils/cloud_storage/client/s3.go @@ -0,0 +1,162 @@ +package client + +import ( + "os" + + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/awserr" + "github.com/aws/aws-sdk-go/aws/credentials" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/s3" + "github.com/aws/aws-sdk-go/service/s3/s3manager" +) + +type s3Client struct { + scType string + bucket string + Sess session.Session +} + +func NewS3Client(vars map[string]interface{}) (*s3Client, error) { + accessKey := loadParamFromVars("accessKey", vars) + secretKey := loadParamFromVars("secretKey", vars) + endpoint := loadParamFromVars("endpoint", vars) + region := loadParamFromVars("region", vars) + bucket := loadParamFromVars("bucket", vars) + scType := loadParamFromVars("scType", vars) + if len(scType) == 0 { + scType = "Standard" + } + mode := loadParamFromVars("mode", vars) + if len(mode) == 0 { + mode = "virtual hosted" + } + sess, err := session.NewSession(&aws.Config{ + Credentials: credentials.NewStaticCredentials(accessKey, secretKey, ""), + Endpoint: aws.String(endpoint), + Region: aws.String(region), + DisableSSL: aws.Bool(true), S3ForcePathStyle: aws.Bool(mode == "path"), + }) + if err != nil { + return nil, err + } + return &s3Client{scType: scType, bucket: bucket, Sess: *sess}, nil +} + +func (s s3Client) ListBuckets() ([]interface{}, error) { + var result []interface{} + svc := s3.New(&s.Sess) + res, err := svc.ListBuckets(nil) + if err != nil { + return nil, err + } + for _, b := range res.Buckets { + result = append(result, b.Name) + } + return result, nil +} + +func (s s3Client) Exist(path string) (bool, error) { + svc := s3.New(&s.Sess) + if _, err := svc.HeadObject(&s3.HeadObjectInput{ + Bucket: &s.bucket, + Key: &path, + }); err != nil { + if aerr, ok := err.(awserr.RequestFailure); ok { + if aerr.StatusCode() == 404 { + return false, nil + } + } else { + return false, aerr + } + } + return true, nil +} + +func (s *s3Client) Size(path string) (int64, error) { + svc := s3.New(&s.Sess) + file, err := svc.GetObject(&s3.GetObjectInput{ + Bucket: &s.bucket, + Key: &path, + }) + if err != nil { + return 0, err + } + return *file.ContentLength, nil +} + +func (s s3Client) Delete(path string) (bool, error) { + svc := s3.New(&s.Sess) + if _, err := svc.DeleteObject(&s3.DeleteObjectInput{Bucket: aws.String(s.bucket), Key: aws.String(path)}); err != nil { + return false, err + } + if err := svc.WaitUntilObjectNotExists(&s3.HeadObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(path), + }); err != nil { + return false, err + } + return true, nil +} + +func (s s3Client) Upload(src, target string) (bool, error) { + fileInfo, err := os.Stat(src) + if err != nil { + return false, err + } + file, err := os.Open(src) + if err != nil { + return false, err + } + defer file.Close() + + uploader := s3manager.NewUploader(&s.Sess) + if fileInfo.Size() > s3manager.MaxUploadParts*s3manager.DefaultUploadPartSize { + uploader.PartSize = fileInfo.Size() / (s3manager.MaxUploadParts - 1) + } + if _, err := uploader.Upload(&s3manager.UploadInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(target), + Body: file, + StorageClass: &s.scType, + }); err != nil { + return false, err + } + return true, nil +} + +func (s s3Client) Download(src, target string) (bool, error) { + if _, err := os.Stat(target); err == nil { + _ = os.Remove(target) + } + file, err := os.Create(target) + if err != nil { + return false, err + } + defer file.Close() + downloader := s3manager.NewDownloader(&s.Sess) + if _, err = downloader.Download(file, &s3.GetObjectInput{ + Bucket: aws.String(s.bucket), + Key: aws.String(src), + }); err != nil { + os.Remove(target) + return false, err + } + return true, nil +} + +func (s *s3Client) ListObjects(prefix string) ([]string, error) { + svc := s3.New(&s.Sess) + var result []string + outputs, err := svc.ListObjects(&s3.ListObjectsInput{ + Bucket: &s.bucket, + Prefix: &prefix, + }) + if err != nil { + return result, err + } + for _, item := range outputs.Contents { + result = append(result, *item.Key) + } + return result, nil +} diff --git a/agent/utils/cloud_storage/client/sftp.go b/agent/utils/cloud_storage/client/sftp.go new file mode 100644 index 0000000..edc899b --- /dev/null +++ b/agent/utils/cloud_storage/client/sftp.go @@ -0,0 +1,222 @@ +package client + +import ( + "io" + "net" + "os" + "path" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/pkg/sftp" + "golang.org/x/crypto/ssh" +) + +type sftpClient struct { + connInfo string + config *ssh.ClientConfig +} + +func NewSftpClient(vars map[string]interface{}) (*sftpClient, error) { + address := loadParamFromVars("address", vars) + port := loadParamFromVars("port", vars) + if len(port) == 0 { + global.LOG.Errorf("load param port from vars failed, err: not exist!") + } + authMode := loadParamFromVars("authMode", vars) + passPhrase := loadParamFromVars("passPhrase", vars) + username := loadParamFromVars("username", vars) + password := loadParamFromVars("password", vars) + + var auth []ssh.AuthMethod + if authMode == "key" { + var signer ssh.Signer + var err error + if len(passPhrase) != 0 { + signer, err = ssh.ParsePrivateKeyWithPassphrase([]byte(password), []byte(passPhrase)) + } else { + signer, err = ssh.ParsePrivateKey([]byte(password)) + } + if err != nil { + return nil, err + } + auth = []ssh.AuthMethod{ssh.PublicKeys(signer)} + } else { + auth = []ssh.AuthMethod{ssh.Password(password)} + } + clientConfig := &ssh.ClientConfig{ + User: username, + Auth: auth, + Timeout: 30 * time.Second, + HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { + return nil + }, + } + addr := net.JoinHostPort(address, port) + if _, err := ssh.Dial("tcp", addr, clientConfig); err != nil { + return nil, err + } + + return &sftpClient{connInfo: addr, config: clientConfig}, nil +} + +func (s sftpClient) Upload(src, target string) (bool, error) { + sshClient, err := ssh.Dial("tcp", s.connInfo, s.config) + if err != nil { + return false, err + } + defer sshClient.Close() + client, err := sftp.NewClient(sshClient) + if err != nil { + return false, err + } + defer client.Close() + + srcFile, err := os.Open(src) + if err != nil { + return false, err + } + defer srcFile.Close() + + targetDir, _ := path.Split(target) + if len(targetDir) != 0 { + if _, err = client.Stat(targetDir); err != nil { + if os.IsNotExist(err) { + if err = client.MkdirAll(targetDir); err != nil { + return false, err + } + } else { + return false, err + } + } + } + dstFile, err := client.Create(target) + if err != nil { + return false, err + } + defer dstFile.Close() + + if _, err := io.Copy(dstFile, srcFile); err != nil { + return false, err + } + return true, nil +} + +func (s sftpClient) ListBuckets() ([]interface{}, error) { + var result []interface{} + return result, nil +} + +func (s sftpClient) Download(src, target string) (bool, error) { + sshClient, err := ssh.Dial("tcp", s.connInfo, s.config) + if err != nil { + return false, err + } + client, err := sftp.NewClient(sshClient) + if err != nil { + return false, err + } + defer client.Close() + defer sshClient.Close() + + srcFile, err := client.Open(src) + if err != nil { + return false, err + } + defer srcFile.Close() + + dstFile, err := os.Create(target) + if err != nil { + return false, err + } + defer dstFile.Close() + + if _, err = io.Copy(dstFile, srcFile); err != nil { + return false, err + } + return true, err +} + +func (s sftpClient) Exist(filePath string) (bool, error) { + sshClient, err := ssh.Dial("tcp", s.connInfo, s.config) + if err != nil { + return false, err + } + client, err := sftp.NewClient(sshClient) + if err != nil { + return false, err + } + defer client.Close() + defer sshClient.Close() + + srcFile, err := client.Open(filePath) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } else { + return false, err + } + } + defer srcFile.Close() + return true, err +} + +func (s sftpClient) Size(filePath string) (int64, error) { + sshClient, err := ssh.Dial("tcp", s.connInfo, s.config) + if err != nil { + return 0, err + } + client, err := sftp.NewClient(sshClient) + if err != nil { + return 0, err + } + defer client.Close() + defer sshClient.Close() + + files, err := client.Stat(filePath) + if err != nil { + return 0, err + } + return files.Size(), nil +} + +func (s sftpClient) Delete(filePath string) (bool, error) { + sshClient, err := ssh.Dial("tcp", s.connInfo, s.config) + if err != nil { + return false, err + } + client, err := sftp.NewClient(sshClient) + if err != nil { + return false, err + } + defer client.Close() + defer sshClient.Close() + + if err := client.Remove(filePath); err != nil { + return false, err + } + return true, nil +} + +func (s sftpClient) ListObjects(prefix string) ([]string, error) { + sshClient, err := ssh.Dial("tcp", s.connInfo, s.config) + if err != nil { + return nil, err + } + client, err := sftp.NewClient(sshClient) + if err != nil { + return nil, err + } + defer client.Close() + defer sshClient.Close() + + files, err := client.ReadDir(prefix) + if err != nil { + return nil, err + } + var result []string + for _, file := range files { + result = append(result, file.Name()) + } + return result, nil +} diff --git a/agent/utils/cloud_storage/client/up.go b/agent/utils/cloud_storage/client/up.go new file mode 100644 index 0000000..4b8f096 --- /dev/null +++ b/agent/utils/cloud_storage/client/up.go @@ -0,0 +1,94 @@ +package client + +import ( + "path" + + "github.com/upyun/go-sdk/upyun" +) + +type upClient struct { + bucket string + client *upyun.UpYun +} + +func NewUpClient(vars map[string]interface{}) (*upClient, error) { + operator := loadParamFromVars("operator", vars) + password := loadParamFromVars("password", vars) + bucket := loadParamFromVars("bucket", vars) + client := upyun.NewUpYun(&upyun.UpYunConfig{ + Bucket: bucket, + Operator: operator, + Password: password, + }) + + return &upClient{bucket: bucket, client: client}, nil +} + +func (o upClient) ListBuckets() ([]interface{}, error) { + var result []interface{} + return result, nil +} + +func (s upClient) Upload(src, target string) (bool, error) { + if _, err := s.client.GetInfo(path.Dir(src)); err != nil { + _ = s.client.Mkdir(path.Dir(target)) + } + if err := s.client.Put(&upyun.PutObjectConfig{ + Path: target, + LocalPath: src, + UseResumeUpload: true, + }); err != nil { + return false, err + } + return true, nil +} + +func (s upClient) Size(path string) (int64, error) { + fileInfo, err := s.client.GetInfo(path) + if err != nil { + return 0, err + } + return fileInfo.Size, nil +} + +func (s upClient) Delete(path string) (bool, error) { + if err := s.client.Delete(&upyun.DeleteObjectConfig{ + Path: path, + }); err != nil { + return false, err + } + return true, nil +} + +func (s upClient) Exist(filePath string) (bool, error) { + if _, err := s.client.GetInfo(filePath); err != nil { + return false, err + } + return true, nil +} + +func (s upClient) Download(src, target string) (bool, error) { + if _, err := s.client.Get(&upyun.GetObjectConfig{ + Path: src, + LocalPath: target, + }); err != nil { + return false, err + } + return true, nil +} + +func (s *upClient) ListObjects(prefix string) ([]string, error) { + objsChan := make(chan *upyun.FileInfo, 10) + if err := s.client.List(&upyun.GetObjectsConfig{ + Path: prefix, + ObjectsChan: objsChan, + MaxListTries: 1, + }); err != nil { + return nil, err + } + var files []string + for obj := range objsChan { + files = append(files, obj.Name) + } + return files, nil +} diff --git a/agent/utils/cloud_storage/client/webdav.go b/agent/utils/cloud_storage/client/webdav.go new file mode 100644 index 0000000..a7f5816 --- /dev/null +++ b/agent/utils/cloud_storage/client/webdav.go @@ -0,0 +1,122 @@ +package client + +import ( + "crypto/tls" + "fmt" + "io" + "net/http" + "os" + "path" + "strings" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/cloud_storage/client/helper/webdav" +) + +type webDAVClient struct { + Bucket string + client *webdav.Client +} + +func NewWebDAVClient(vars map[string]interface{}) (*webDAVClient, error) { + address := loadParamFromVars("address", vars) + port := loadParamFromVars("port", vars) + password := loadParamFromVars("password", vars) + username := loadParamFromVars("username", vars) + bucket := loadParamFromVars("bucket", vars) + + url := fmt.Sprintf("%s:%s", address, port) + if len(port) == 0 { + url = address + } + client := webdav.NewClient(url, username, password) + tlsConfig := &tls.Config{} + if strings.HasPrefix(address, "https") { + tlsConfig.InsecureSkipVerify = true + } + var transport http.RoundTripper = &http.Transport{ + TLSClientConfig: tlsConfig, + } + client.SetTransport(transport) + if err := client.Connect(); err != nil { + return nil, err + } + return &webDAVClient{Bucket: bucket, client: client}, nil +} + +func (s webDAVClient) Upload(src, target string) (bool, error) { + targetFilePath := path.Join(s.Bucket, target) + srcFile, err := os.Open(src) + if err != nil { + return false, err + } + defer srcFile.Close() + + if err := s.client.WriteStream(targetFilePath, srcFile, constant.DirPerm); err != nil { + return false, err + } + return true, nil +} + +func (s webDAVClient) ListBuckets() ([]interface{}, error) { + var result []interface{} + return result, nil +} + +func (s webDAVClient) Download(src, target string) (bool, error) { + srcPath := path.Join(s.Bucket, src) + info, err := s.client.Stat(srcPath) + if err != nil { + return false, err + } + targetStat, err := os.Stat(target) + if err == nil { + if info.Size() == targetStat.Size() { + return true, nil + } + } + file, err := os.Create(target) + if err != nil { + return false, err + } + defer file.Close() + reader, _ := s.client.ReadStream(srcPath) + if _, err := io.Copy(file, reader); err != nil { + return false, err + } + return true, err +} + +func (s webDAVClient) Exist(pathItem string) (bool, error) { + if _, err := s.client.Stat(path.Join(s.Bucket, pathItem)); err != nil { + return false, err + } + return true, nil +} + +func (s webDAVClient) Size(pathItem string) (int64, error) { + file, err := s.client.Stat(path.Join(s.Bucket, pathItem)) + if err != nil { + return 0, err + } + return file.Size(), nil +} + +func (s webDAVClient) Delete(pathItem string) (bool, error) { + if err := s.client.RemoveAll(path.Join(s.Bucket, pathItem)); err != nil { + return false, err + } + return true, nil +} + +func (s webDAVClient) ListObjects(prefix string) ([]string, error) { + files, err := s.client.ReadDir(path.Join(s.Bucket, prefix)) + if err != nil { + return nil, err + } + var result []string + for _, file := range files { + result = append(result, file.Name()) + } + return result, nil +} diff --git a/agent/utils/cloud_storage/cloud_storage_client.go b/agent/utils/cloud_storage/cloud_storage_client.go new file mode 100644 index 0000000..af444f8 --- /dev/null +++ b/agent/utils/cloud_storage/cloud_storage_client.go @@ -0,0 +1,49 @@ +package cloud_storage + +import ( + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/cloud_storage/client" +) + +type CloudStorageClient interface { + ListBuckets() ([]interface{}, error) + ListObjects(prefix string) ([]string, error) + Exist(path string) (bool, error) + Delete(path string) (bool, error) + Upload(src, target string) (bool, error) + Download(src, target string) (bool, error) + + Size(path string) (int64, error) +} + +func NewCloudStorageClient(backupType string, vars map[string]interface{}) (CloudStorageClient, error) { + switch backupType { + case constant.Local: + return client.NewLocalClient(vars) + case constant.S3: + return client.NewS3Client(vars) + case constant.OSS: + return client.NewOssClient(vars) + case constant.Sftp: + return client.NewSftpClient(vars) + case constant.WebDAV: + return client.NewWebDAVClient(vars) + case constant.MinIo: + return client.NewMinIoClient(vars) + case constant.Cos: + return client.NewCosClient(vars) + case constant.Kodo: + return client.NewKodoClient(vars) + case constant.OneDrive: + return client.NewOneDriveClient(vars) + case constant.UPYUN: + return client.NewUpClient(vars) + case constant.ALIYUN: + return client.NewALIClient(vars) + case constant.GoogleDrive: + return client.NewGoogleDriveClient(vars) + default: + return nil, buserr.WithName("ErrNotSupportType", backupType) + } +} diff --git a/agent/utils/cmd/cmd.go b/agent/utils/cmd/cmd.go new file mode 100644 index 0000000..fd1d7f4 --- /dev/null +++ b/agent/utils/cmd/cmd.go @@ -0,0 +1,37 @@ +package cmd + +import ( + "os/exec" + "strings" +) + +func CheckIllegal(args ...string) bool { + if args == nil { + return false + } + for _, arg := range args { + if strings.Contains(arg, "&") || strings.Contains(arg, "|") || strings.Contains(arg, ";") || + strings.Contains(arg, "$") || strings.Contains(arg, "'") || strings.Contains(arg, "`") || + strings.Contains(arg, "(") || strings.Contains(arg, ")") || strings.Contains(arg, "\"") || + strings.Contains(arg, "\n") || strings.Contains(arg, "\r") || strings.Contains(arg, ">") || strings.Contains(arg, "<") { + return true + } + } + return false +} + +func SudoHandleCmd() string { + cmd := exec.Command("sudo", "-n", "ls") + if err := cmd.Run(); err == nil { + return "sudo " + } + return "" +} + +func Which(name string) bool { + stdout, err := RunDefaultWithStdoutBashCf("which %s", name) + if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) { + return false + } + return true +} diff --git a/agent/utils/cmd/cmdx.go b/agent/utils/cmd/cmdx.go new file mode 100644 index 0000000..27689dc --- /dev/null +++ b/agent/utils/cmd/cmdx.go @@ -0,0 +1,273 @@ +package cmd + +import ( + "bytes" + "context" + "errors" + "fmt" + "log" + "os" + "os/exec" + "strings" + "syscall" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" +) + +type CommandHelper struct { + context context.Context + workDir string + outputFile string + scriptPath string + timeout time.Duration + taskItem *task.Task + logger *log.Logger + IgnoreExist1 bool +} + +type Option func(*CommandHelper) + +func NewCommandMgr(opts ...Option) *CommandHelper { + s := &CommandHelper{} + for _, opt := range opts { + opt(s) + } + return s +} + +func RunDefaultBashC(command string) error { + mgr := NewCommandMgr() + return mgr.RunBashC(command) +} +func RunDefaultBashCf(command string, arg ...interface{}) error { + mgr := NewCommandMgr() + return mgr.RunBashCf(command, arg...) +} +func RunDefaultWithStdoutBashC(command string) (string, error) { + mgr := NewCommandMgr(WithTimeout(20 * time.Second)) + return mgr.RunWithStdoutBashC(command) +} +func RunDefaultWithStdoutBashCf(command string, arg ...interface{}) (string, error) { + mgr := NewCommandMgr(WithTimeout(20 * time.Second)) + return mgr.RunWithStdoutBashCf(command, arg...) +} +func RunDefaultWithStdoutBashCfAndTimeOut(command string, timeout time.Duration, arg ...interface{}) (string, error) { + mgr := NewCommandMgr(WithTimeout(timeout)) + return mgr.RunWithStdoutBashCf(command, arg...) +} + +func (c *CommandHelper) Run(name string, arg ...string) error { + _, err := c.run(name, arg...) + return err +} +func (c *CommandHelper) RunBashCWithArgs(arg ...string) error { + arg = append([]string{"-c"}, arg...) + _, err := c.run("bash", arg...) + return err +} + +func (c *CommandHelper) RunBashC(command string) error { + if _, err := c.run("bash", "-c", command); err != nil { + return err + } + return nil +} +func (c *CommandHelper) RunBashCf(command string, arg ...interface{}) error { + if _, err := c.run("bash", "-c", fmt.Sprintf(command, arg...)); err != nil { + return err + } + return nil +} + +func (c *CommandHelper) RunWithStdout(name string, arg ...string) (string, error) { + return c.run(name, arg...) +} +func (c *CommandHelper) RunWithStdoutBashC(command string) (string, error) { + return c.run("bash", "-c", command) +} +func (c *CommandHelper) RunWithStdoutBashCf(command string, arg ...interface{}) (string, error) { + return c.run("bash", "-c", fmt.Sprintf(command, arg...)) +} + +func (c *CommandHelper) run(name string, arg ...string) (string, error) { + var cmd *exec.Cmd + var newContext context.Context + var cancel context.CancelFunc + + if c.timeout != 0 { + if c.context == nil { + newContext, cancel = context.WithTimeout(context.Background(), c.timeout) + defer cancel() + } else { + newContext, cancel = context.WithTimeout(c.context, c.timeout) + defer cancel() + } + cmd = exec.CommandContext(newContext, name, arg...) + } else { + if c.context == nil { + newContext = context.Background() + cmd = exec.Command(name, arg...) + } else { + newContext = c.context + cmd = exec.CommandContext(c.context, name, arg...) + } + } + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + + customWriter := &CustomWriter{taskItem: c.taskItem} + var stdout, stderr bytes.Buffer + if c.taskItem != nil { + cmd.Stdout = customWriter + cmd.Stderr = customWriter + } else if c.logger != nil { + cmd.Stdout = c.logger.Writer() + cmd.Stderr = c.logger.Writer() + } else if len(c.outputFile) != 0 { + file, err := os.OpenFile(c.outputFile, os.O_WRONLY|os.O_CREATE, constant.FilePerm) + if err != nil { + return "", err + } + defer file.Close() + cmd.Stdout = file + cmd.Stderr = file + } else if len(c.scriptPath) != 0 { + cmd = exec.Command("bash", c.scriptPath) + cmd.Stdout = &stdout + cmd.Stderr = &stderr + } else { + cmd.Stdout = &stdout + cmd.Stderr = &stderr + } + env := os.Environ() + cmd.Env = env + if len(c.workDir) != 0 { + cmd.Dir = c.workDir + } + + if err := cmd.Start(); err != nil { + return "", fmt.Errorf("cmd.Start() failed with '%s'\n", err) + } + if c.taskItem != nil { + defer customWriter.Flush() + } + + done := make(chan error, 1) + go func() { + done <- cmd.Wait() + }() + select { + case err := <-done: + if err != nil { + return handleErr(stdout, stderr, c.IgnoreExist1, err) + } + return stdout.String(), nil + case <-newContext.Done(): + if cmd.Process != nil && cmd.Process.Pid > 0 { + syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL) + } + var err error + switch newContext.Err() { + case context.DeadlineExceeded: + err = buserr.New("ErrCmdTimeout") + case context.Canceled: + err = buserr.New("ErrShutDown") + default: + err = newContext.Err() + } + <-done + return "", err + } +} + +func WithContext(ctx context.Context) Option { + return func(s *CommandHelper) { + s.context = ctx + } +} +func WithOutputFile(outputFile string) Option { + return func(s *CommandHelper) { + s.outputFile = outputFile + } +} +func WithTimeout(timeout time.Duration) Option { + return func(s *CommandHelper) { + s.timeout = timeout + } +} +func WithLogger(logger *log.Logger) Option { + return func(s *CommandHelper) { + s.logger = logger + } +} +func WithTask(taskItem task.Task) Option { + return func(s *CommandHelper) { + s.taskItem = &taskItem + } +} +func WithWorkDir(workDir string) Option { + return func(s *CommandHelper) { + s.workDir = workDir + } +} +func WithScriptPath(scriptPath string) Option { + return func(s *CommandHelper) { + s.scriptPath = scriptPath + } +} +func WithIgnoreExist1() Option { + return func(s *CommandHelper) { + s.IgnoreExist1 = true + } +} + +type CustomWriter struct { + taskItem *task.Task + buffer bytes.Buffer +} + +func (cw *CustomWriter) Write(p []byte) (n int, err error) { + cw.buffer.Write(p) + lines := strings.Split(cw.buffer.String(), "\n") + + for i := 0; i < len(lines)-1; i++ { + cw.taskItem.Log(lines[i]) + } + cw.buffer.Reset() + cw.buffer.WriteString(lines[len(lines)-1]) + + return len(p), nil +} +func (cw *CustomWriter) Flush() { + if cw.buffer.Len() > 0 { + cw.taskItem.Log(cw.buffer.String()) + cw.buffer.Reset() + } +} + +func handleErr(stdout, stderr bytes.Buffer, ignoreExist1 bool, err error) (string, error) { + var exitError *exec.ExitError + if ignoreExist1 && errors.As(err, &exitError) { + if status, ok := exitError.Sys().(syscall.WaitStatus); ok { + if status.ExitStatus() == 1 { + return "", nil + } + } + } + outItem := stdout.String() + errItem := stderr.String() + if len(errItem) != 0 && len(outItem) != 0 { + return outItem, fmt.Errorf("stdout: %s; stderr: %s, err: %v", outItem, errItem, err) + } + if len(errItem) != 0 { + return outItem, fmt.Errorf("stderr: %s, err: %v", errItem, err) + } + if len(outItem) != 0 { + return outItem, fmt.Errorf("stdout: %s, err: %v", outItem, err) + } + return "", err +} diff --git a/agent/utils/common/common.go b/agent/utils/common/common.go new file mode 100644 index 0000000..749d550 --- /dev/null +++ b/agent/utils/common/common.go @@ -0,0 +1,421 @@ +package common + +import ( + "crypto/rand" + "fmt" + "io" + mathRand "math/rand" + "net" + "os/exec" + "reflect" + "sort" + "strconv" + "strings" + "time" + "unicode" + + "github.com/gin-gonic/gin" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/re" + "golang.org/x/net/idna" +) + +func CompareVersion(version1, version2 string) bool { + v1s := extractNumbers(version1) + v2s := extractNumbers(version2) + + maxLen := max(len(v1s), len(v2s)) + v1s = append(v1s, make([]string, maxLen-len(v1s))...) + v2s = append(v2s, make([]string, maxLen-len(v2s))...) + + for i := 0; i < maxLen; i++ { + v1, err1 := strconv.Atoi(v1s[i]) + v2, err2 := strconv.Atoi(v2s[i]) + if err1 != nil { + v1 = 0 + } + if err2 != nil { + v2 = 0 + } + if v1 != v2 { + return v1 > v2 + } + } + return false +} + +func CompareAppVersion(version1, version2 string) bool { + v1s := extractNumbers(version1) + v2s := extractNumbers(version2) + + maxLen := max(len(v1s), len(v2s)) + v1s = append(v1s, make([]string, maxLen-len(v1s))...) + v2s = append(v2s, make([]string, maxLen-len(v2s))...) + + for i := 0; i < maxLen; i++ { + v1, err1 := strconv.Atoi(v1s[i]) + v2, err2 := strconv.Atoi(v2s[i]) + if err1 != nil { + v1 = 0 + } + if err2 != nil { + v2 = 0 + } + if v1 > v2 { + return true + } + if v1 < v2 { + return false + } + } + return true +} + +func ComparePanelVersion(version1, version2 string) bool { + if version1 == version2 { + return false + } + version1s := SplitStr(version1, ".", "-") + version2s := SplitStr(version2, ".", "-") + + if len(version2s) > len(version1s) { + for i := 0; i < len(version2s)-len(version1s); i++ { + version1s = append(version1s, "0") + } + } + if len(version1s) > len(version2s) { + for i := 0; i < len(version1s)-len(version2s); i++ { + version2s = append(version2s, "0") + } + } + + n := min(len(version1s), len(version2s)) + for i := 0; i < n; i++ { + if version1s[i] == version2s[i] { + continue + } else { + v1, err1 := strconv.Atoi(version1s[i]) + if err1 != nil { + return version1s[i] > version2s[i] + } + v2, err2 := strconv.Atoi(version2s[i]) + if err2 != nil { + return version1s[i] > version2s[i] + } + return v1 > v2 + } + } + return true +} + +func extractNumbers(version string) []string { + var numbers []string + start := -1 + for i, r := range version { + if isDigit(r) { + if start == -1 { + start = i + } + } else { + if start != -1 { + numbers = append(numbers, version[start:i]) + start = -1 + } + } + } + if start != -1 { + numbers = append(numbers, version[start:]) + } + return numbers +} + +func isDigit(r rune) bool { + return r >= '0' && r <= '9' +} + +func max(x, y int) int { + if x > y { + return x + } + return y +} + +func GetSortedVersions(versions []string) []string { + sort.Slice(versions, func(i, j int) bool { + return CompareVersion(versions[i], versions[j]) + }) + return versions +} + +func IsCrossVersion(version1, version2 string) bool { + version1s := strings.Split(version1, ".") + version2s := strings.Split(version2, ".") + v1num, _ := strconv.Atoi(version1s[0]) + v2num, _ := strconv.Atoi(version2s[0]) + return v2num > v1num +} + +func GetUuid() string { + b := make([]byte, 16) + _, _ = io.ReadFull(rand.Reader, b) + b[6] = (b[6] & 0x0f) | 0x40 + b[8] = (b[8] & 0x3f) | 0x80 + return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]) +} + +var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") + +func RandStr(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letters[mathRand.Intn(len(letters))] + } + return string(b) +} + +func RandStrAndNum(n int) string { + source := mathRand.NewSource(time.Now().UnixNano()) + randGen := mathRand.New(source) + const charset = "abcdefghijklmnopqrstuvwxyz0123456789" + b := make([]byte, n) + for i := range b { + b[i] = charset[randGen.Intn(len(charset)-1)] + } + return (string(b)) +} + +func ScanPort(port int) bool { + ln, err := net.Listen("tcp", ":"+strconv.Itoa(port)) + if err != nil { + return true + } + defer ln.Close() + return false +} + +func ScanUDPPort(port int) bool { + ln, err := net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4(127, 0, 0, 1), Port: port}) + if err != nil { + return true + } + defer ln.Close() + return false +} + +func ScanPortWithProto(port int, proto string) bool { + if proto == "udp" { + return ScanUDPPort(port) + } + return ScanPort(port) +} + +func ScanPortWithIP(ip string, port int) bool { + if len(ip) == 0 || ip == "0.0.0.0" || ip == "::" { + return ScanPort(port) + } + address := net.JoinHostPort(ip, fmt.Sprintf("%d", port)) + timeout := time.Second * 2 + conn, err := net.DialTimeout("tcp", address, timeout) + + if err != nil { + if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() { + return true + } + return false + } + defer conn.Close() + return true +} + +func IsNum(s string) bool { + _, err := strconv.ParseFloat(s, 64) + return err == nil +} + +func RemoveRepeatElement(a interface{}) (ret []interface{}) { + va := reflect.ValueOf(a) + for i := 0; i < va.Len(); i++ { + if i > 0 && reflect.DeepEqual(va.Index(i-1).Interface(), va.Index(i).Interface()) { + continue + } + ret = append(ret, va.Index(i).Interface()) + } + return ret +} + +func RemoveRepeatStr(list []string) (ret []string) { + mapItem := make(map[string]struct{}) + for _, item := range list { + mapItem[item] = struct{}{} + } + for key := range mapItem { + ret = append(ret, key) + } + return ret +} + +func LoadSizeUnit(value float64) string { + val := int64(value) + if val%1024 != 0 { + return fmt.Sprintf("%v", val) + } + if val > 1048576 { + return fmt.Sprintf("%vM", val/1048576) + } + if val > 1024 { + return fmt.Sprintf("%vK", val/1024) + } + return fmt.Sprintf("%v", val) +} + +func LoadSizeUnit2F(value float64) string { + if value > 1073741824 { + return fmt.Sprintf("%.2fG", value/1073741824) + } + if value > 1048576 { + return fmt.Sprintf("%.2fM", value/1048576) + } + if value > 1024 { + return fmt.Sprintf("%.2fK", value/1024) + } + return fmt.Sprintf("%.2f", value) +} + +func LoadTimeZoneByCmd() string { + loc := time.Now().Location().String() + if _, err := time.LoadLocation(loc); err != nil { + loc = "Asia/Shanghai" + } + std, err := cmd.RunDefaultWithStdoutBashC("timedatectl | grep 'Time zone'") + if err != nil { + return loc + } + fields := strings.Fields(string(std)) + if len(fields) != 5 { + return loc + } + if _, err := time.LoadLocation(fields[2]); err != nil { + return loc + } + return fields[2] +} + +func IsValidDomain(domain string) bool { + return re.GetRegex(re.DomainPattern).MatchString(domain) +} + +func IsValidNginxServerName(serverName string) bool { + return re.GetRegex(re.NginxServerNamePattern).MatchString(serverName) +} + +func ContainsChinese(text string) bool { + for _, char := range text { + if unicode.Is(unicode.Han, char) { + return true + } + } + return false +} + +func PunycodeEncode(text string) (string, error) { + encoder := idna.New() + ascii, err := encoder.ToASCII(text) + if err != nil { + return "", err + } + return ascii, nil +} + +func SplitStr(str string, spi ...string) []string { + lists := []string{str} + var results []string + for _, s := range spi { + results = []string{} + for _, list := range lists { + results = append(results, strings.Split(list, s)...) + } + lists = results + } + return results +} + +func IsValidIP(ip string) bool { + return net.ParseIP(ip) != nil +} + +const ( + b = uint64(1) + kb = 1024 * b + mb = 1024 * kb + gb = 1024 * mb +) + +func FormatBytes(bytes uint64) string { + switch { + case bytes < kb: + return fmt.Sprintf("%dB", bytes) + case bytes < mb: + return fmt.Sprintf("%.2fKB", float64(bytes)/float64(kb)) + case bytes < gb: + return fmt.Sprintf("%.2fMB", float64(bytes)/float64(mb)) + default: + return fmt.Sprintf("%.2fGB", float64(bytes)/float64(gb)) + } +} + +func FormatPercent(percent float64) string { + return fmt.Sprintf("%.2f%%", percent) +} + +func GetLang(c *gin.Context) string { + lang := c.GetHeader("Accept-Language") + if lang == "" { + lang = "en" + } + return lang +} + +func HandleIPList(content string) ([]string, error) { + ipList := strings.Split(content, "\n") + var res []string + for _, ip := range ipList { + if ip == "" { + continue + } + if net.ParseIP(ip) != nil { + res = append(res, ip) + continue + } + if _, _, err := net.ParseCIDR(ip); err != nil { + return nil, buserr.New("ErrParseIP") + } + res = append(res, ip) + } + return res, nil +} + +func GetSystemVersion(versionString string) string { + match := re.GetRegex(re.VersionPattern).FindStringSubmatch(versionString) + if len(match) > 1 { + return match[1] + } + return "" +} + +func isCommandAvailable(name string, args ...string) bool { + execCmd := exec.Command(name, args...) + err := execCmd.Run() + return err == nil +} + +func GetDockerComposeCommand() string { + if isCommandAvailable("docker", "compose", "version") { + return "docker compose" + } + if isCommandAvailable("docker-compose", "version") { + return "docker-compose" + } + return "" +} diff --git a/agent/utils/common/sqlite.go b/agent/utils/common/sqlite.go new file mode 100644 index 0000000..18a8bf7 --- /dev/null +++ b/agent/utils/common/sqlite.go @@ -0,0 +1,109 @@ +package common + +import ( + "fmt" + "log" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/glebarez/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +func LoadDBConnByPath(fullPath, dbName string) *gorm.DB { + if _, err := createDirWhenNotExist(true, path.Dir(fullPath)); err != nil { + panic(fmt.Errorf("init db dir failed, err: %v", err)) + } + if _, err := os.Stat(fullPath); err != nil { + f, err := os.Create(fullPath) + if err != nil { + panic(fmt.Errorf("init %s db file failed, err: %v", dbName, err)) + } + _ = f.Close() + } + + db, err := GetDBWithPath(fullPath) + if err != nil { + panic(err) + } + return db +} + +func LoadDBConnByPathWithErr(fullPath, dbName string) (*gorm.DB, error) { + if _, err := createDirWhenNotExist(true, path.Dir(fullPath)); err != nil { + return nil, fmt.Errorf("init db dir failed, err: %v", err) + } + if _, err := os.Stat(fullPath); err != nil { + f, err := os.Create(fullPath) + if err != nil { + return nil, fmt.Errorf("init %s db file failed, err: %v", dbName, err) + } + _ = f.Close() + } + + db, err := GetDBWithPath(fullPath) + if err != nil { + return nil, fmt.Errorf("init %s db failed, err: %v", dbName, err) + } + return db, nil +} + +func CloseDB(db *gorm.DB) { + sqlDB, err := db.DB() + if err != nil { + return + } + _ = sqlDB.Close() +} + +func GetDBWithPath(dbPath string) (*gorm.DB, error) { + db, err := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + Logger: newLogger(), + }) + if err != nil { + return nil, err + } + if strings.HasSuffix(dbPath, "core.db") || strings.HasSuffix(dbPath, "agent.db") { + initializeTxWatch(db) + } + sqlDB, dbError := db.DB() + if dbError != nil { + return nil, dbError + } + sqlDB.SetMaxOpenConns(4) + sqlDB.SetMaxIdleConns(1) + sqlDB.SetConnMaxIdleTime(15 * time.Minute) + sqlDB.SetConnMaxLifetime(time.Hour) + return db, nil +} + +func newLogger() logger.Interface { + return logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), + logger.Config{ + SlowThreshold: time.Second, + LogLevel: logger.Silent, + IgnoreRecordNotFoundError: true, + Colorful: false, + }, + ) +} + +func createDirWhenNotExist(isDir bool, pathItem string) (string, error) { + checkPath := pathItem + if !isDir { + checkPath = path.Dir(pathItem) + } + if _, err := os.Stat(checkPath); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(checkPath, os.ModePerm); err != nil { + global.LOG.Errorf("mkdir %s failed, err: %v", checkPath, err) + return pathItem, err + } + } + return pathItem, nil +} diff --git a/agent/utils/common/sqlite_tx_logs.go b/agent/utils/common/sqlite_tx_logs.go new file mode 100644 index 0000000..6482a17 --- /dev/null +++ b/agent/utils/common/sqlite_tx_logs.go @@ -0,0 +1,67 @@ +package common + +import ( + "context" + "fmt" + "runtime" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "gorm.io/gorm" +) + +type contextKey string + +func initializeTxWatch(db *gorm.DB) { + _ = db.Callback().Create().Before("gorm:begin_transaction").Register( + "my_plugin:before_begin", + func(db *gorm.DB) { + var caller []string + for i := 0; ; i++ { + pc, file, line, ok := runtime.Caller(i) + if !ok { + break + } + funcName := runtime.FuncForPC(pc).Name() + if !strings.HasPrefix(funcName, "github.com/1Panel-dev/1Panel") { + continue + } + fileParts := strings.Split(file, "/") + fileName := fileParts[len(fileParts)-1] + caller = append(caller, fmt.Sprintf("%s/%s:%d", funcName, fileName, line)) + } + txID := generateTransactionID() + db.Statement.Context = context.WithValue( + db.Statement.Context, + contextKey("tx_id"), txID, + ) + db.Statement.Context = context.WithValue( + db.Statement.Context, + contextKey("tx_start"), time.Now(), + ) + global.LOG.Debugf("[%s] tx start \n%s", txID, strings.Join(caller, "\n")) + }, + ) + + _ = db.Callback().Create().After("gorm:commit_or_rollback_transaction").Register( + "my_plugin:after_commit_or_rollback", + func(db *gorm.DB) { + ctx := db.Statement.Context + txID, _ := ctx.Value(contextKey("tx_id")).(string) + startTime, _ := ctx.Value(contextKey("tx_start")).(time.Time) + if txID != "" { + duration := time.Since(startTime) + if db.Error != nil { + global.LOG.Debugf("[%s] tx rollback! time: %v, err: %v", txID, duration, db.Error) + } else { + global.LOG.Debugf("[%s] tx commit! time: %v", txID, duration) + } + } + }, + ) +} + +func generateTransactionID() string { + return fmt.Sprintf("tx_%d", time.Now().UnixNano()) +} diff --git a/agent/utils/compose/compose.go b/agent/utils/compose/compose.go new file mode 100644 index 0000000..6d13e6b --- /dev/null +++ b/agent/utils/compose/compose.go @@ -0,0 +1,138 @@ +package compose + +import ( + "errors" + "fmt" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "gopkg.in/yaml.v3" +) + +func checkCmd() error { + if global.CONF.DockerConfig.Command == "" { + dockerComposeCmd := common.GetDockerComposeCommand() + if dockerComposeCmd == "" { + return buserr.New("ErrDockerComposeCmdNotFound") + } + global.CONF.DockerConfig.Command = dockerComposeCmd + } + return nil +} + +func Up(filePath string) (string, error) { + if err := checkCmd(); err != nil { + return "", err + } + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath)) +} + +func UpWithTask(filePath string, task *task.Task, pullImages bool) error { + if !pullImages { + return cmd.NewCommandMgr(cmd.WithTask(*task)).RunBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath)) + } + content, err := os.ReadFile(filePath) + if err != nil { + return err + } + env, _ := os.ReadFile(path.Join(path.Dir(filePath), ".env")) + var compose docker.ComposeProject + if err := yaml.Unmarshal(content, &compose); err != nil { + return fmt.Errorf("parse docker-compose file failed: %v", err) + } + images, err := docker.GetImagesFromDockerCompose(env, content) + if err != nil { + return err + } + dockerCLi, err := docker.NewClient() + if err != nil { + return err + } + errMsg := "" + for _, image := range images { + task.Log(i18n.GetWithName("PullImageStart", image)) + if err = dockerCLi.PullImageWithProcess(task, image); err != nil { + errOur := err.Error() + if errOur != "" { + if strings.Contains(errOur, "no such host") { + errMsg = i18n.GetMsgByKey("ErrNoSuchHost") + ":" + } + if strings.Contains(errOur, "Error response from daemon") { + errMsg = i18n.GetMsgByKey("PullImageTimeout") + ":" + } + } + message := errMsg + errOur + installErr := errors.New(message) + task.LogFailedWithErr(i18n.GetMsgByKey("PullImage"), installErr) + if exist, _ := dockerCLi.ImageExists(image); !exist { + return installErr + } else { + task.Log(i18n.GetMsgByKey("UseExistImage")) + } + } else { + task.Log(i18n.GetMsgByKey("PullImageSuccess")) + } + } + + return cmd.NewCommandMgr(cmd.WithTask(*task)).RunBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath)) +} + +func Down(filePath string) (string, error) { + if err := checkCmd(); err != nil { + return "", err + } + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s down --remove-orphans", global.CONF.DockerConfig.Command, loadFiles(filePath)) +} + +func Stop(filePath string) (string, error) { + if err := checkCmd(); err != nil { + return "", err + } + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s stop", global.CONF.DockerConfig.Command, loadFiles(filePath)) +} + +func Restart(filePath string) (string, error) { + if err := checkCmd(); err != nil { + return "", err + } + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s restart", global.CONF.DockerConfig.Command, loadFiles(filePath)) +} + +func Operate(filePath, operation string) (string, error) { + if err := checkCmd(); err != nil { + return "", err + } + return cmd.NewCommandMgr(cmd.WithTimeout(20*time.Minute)).RunWithStdoutBashCf("%s %s %s", global.CONF.DockerConfig.Command, loadFiles(filePath), operation) +} + +func DownAndUp(filePath string) (string, error) { + if err := checkCmd(); err != nil { + return "", err + } + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(20 * time.Minute)) + stdout, err := cmdMgr.RunWithStdoutBashCf("%s %s down", global.CONF.DockerConfig.Command, loadFiles(filePath)) + if err != nil { + return stdout, err + } + stdout, err = cmdMgr.RunWithStdoutBashCf("%s %s up -d", global.CONF.DockerConfig.Command, loadFiles(filePath)) + return stdout, err +} + +func loadFiles(filePath string) string { + var fileItem []string + for _, item := range strings.Split(filePath, ",") { + if len(item) != 0 { + fileItem = append(fileItem, fmt.Sprintf("-f %s", item)) + } + } + return strings.Join(fileItem, " ") +} diff --git a/agent/utils/controller/controller.go b/agent/utils/controller/controller.go new file mode 100644 index 0000000..3232138 --- /dev/null +++ b/agent/utils/controller/controller.go @@ -0,0 +1,290 @@ +package controller + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/controller/manager" +) + +type Controller interface { + Name() string + IsActive(serviceName string) (bool, error) + IsEnable(serviceName string) (bool, error) + IsExist(serviceName string) (bool, error) + Status(serviceName string) (string, error) + + Operate(operate, serviceName string) error + + Reload() error +} + +func New() (Controller, error) { + managerOptions := []string{"systemctl", "rc-service", "service"} + for _, item := range managerOptions { + if _, err := exec.LookPath(item); err != nil { + continue + } + switch item { + case "systemctl": + return manager.NewSystemd(), nil + case "rc-service": + return manager.NewOpenrc(), nil + case "service": + return manager.NewSysvinit(), nil + } + } + return nil, errors.New("not support such manager initializatio") +} + +func Handle(operate, serviceName string) error { + service, err := LoadServiceName(serviceName) + if err != nil { + return err + } + client, err := New() + if err != nil { + return err + } + return client.Operate(operate, service) +} +func HandleStart(serviceName string) error { + service, err := LoadServiceName(serviceName) + if err != nil { + return err + } + return Handle("start", service) +} +func HandleStop(serviceName string) error { + service, err := LoadServiceName(serviceName) + if err != nil { + return err + } + return Handle("stop", service) +} +func HandleRestart(serviceName string) error { + service, err := LoadServiceName(serviceName) + if err != nil { + return err + } + return Handle("restart", service) +} + +func CheckExist(serviceName string) (bool, error) { + service, err := LoadServiceName(serviceName) + if err != nil { + return false, err + } + client, err := New() + if err != nil { + return false, err + } + b, er := client.IsExist(service) + return b, er +} +func CheckActive(serviceName string) (bool, error) { + service, err := LoadServiceName(serviceName) + if err != nil { + return false, err + } + client, err := New() + if err != nil { + return false, err + } + return client.IsActive(service) +} +func CheckEnable(serviceName string) (bool, error) { + service, err := LoadServiceName(serviceName) + if err != nil { + return false, err + } + client, err := New() + if err != nil { + return false, err + } + return client.IsEnable(service) +} + +func Reload() error { + client, err := New() + if err != nil { + return err + } + return client.Reload() +} + +func RestartPanel(core, agent, reload bool) { + client, err := New() + if err != nil { + global.LOG.Errorf("load client for controller failed, err: %v", err) + return + } + if reload { + if err := client.Reload(); err != nil { + global.LOG.Errorf("restart 1panel service failed, err: %v", err) + return + } + } + if core { + if err := client.Operate("restart", "1panel-core"); err != nil { + global.LOG.Errorf("restart 1panel core service failed, err: %v", err) + return + } + } + if agent { + if err := client.Operate("restart", "1panel-agent"); err != nil { + global.LOG.Errorf("restart 1panel agent service failed, err: %v", err) + return + } + } +} + +func LoadServiceName(keyword string) (string, error) { + client, err := New() + if err != nil { + return "", err + } + + processedName := loadProcessedName(client.Name(), keyword) + exist, err := client.IsExist(processedName) + if exist { + return processedName, nil + } + alistName := loadFromPredefined(client, keyword) + if len(alistName) != 0 { + return alistName, nil + } + return "", fmt.Errorf("find such service for %s failed", keyword) +} + +func loadProcessedName(mgr, keyword string) string { + keyword = strings.ToLower(keyword) + if strings.HasSuffix(keyword, ".service.socket") { + keyword = strings.TrimSuffix(keyword, ".service.socket") + ".socket" + } + if mgr != "systemd" { + keyword = strings.TrimSuffix(keyword, ".service") + return keyword + } + if !strings.HasSuffix(keyword, ".service") && !strings.HasSuffix(keyword, ".socket") { + keyword += ".service" + } + return keyword +} + +func loadFromPredefined(mgr Controller, keyword string) string { + predefinedMap := map[string][]string{ + "clam": {"clamav-daemon.service", "clamd@scan.service", "clamd"}, + "freshclam": {"clamav-freshclam.service", "freshclam.service"}, + "fail2ban": {"fail2ban.service", "fail2ban"}, + "supervisor": {"supervisord.service", "supervisor.service", "supervisord", "supervisor"}, + "ssh": {"sshd.service", "ssh.service", "sshd", "ssh"}, + "1panel-core": {"1panel-core.service"}, + "1panel-agent": {"1panel-agent.service"}, + "docker": {"docker.service", "dockerd"}, + "iptables": {"iptables", "iptables-services"}, + } + if val, ok := predefinedMap[keyword]; ok { + for _, item := range val { + if exist, _ := mgr.IsExist(item); exist { + return item + } + } + } + return "" +} + +// GetServicePath returns the configuration file path for the specified service. +// If serviceName is empty, it returns the default directory path based on the system's service manager. +// For non-empty serviceName, it retrieves the exact service file path. +// Parameters: +// - serviceName: Name of the service. If empty, returns the default directory. +// +// Returns: +// - string: The service configuration file path. +// - error: Error if the service manager is unsupported or command execution fails. +func GetServicePath(serviceName string) (string, error) { + if serviceName == "" { + client, err := New() + if err != nil { + return "", err + } + switch client.Name() { + case "systemd": + return "/etc/systemd/system/", nil + case "openrc", "sysvinit": + return "/etc/init.d/", nil + default: + return "", fmt.Errorf("unsupported manager: %s", client.Name()) + } + } + service, err := LoadServiceName(serviceName) + if err != nil { + return "", err + } + client, err := New() + if err != nil { + return "", err + } + switch client.Name() { + case "systemd": + stdout, err := exec.Command("systemctl", "show", "-p", "FragmentPath", service).Output() + if err != nil { + return "", err + } + parts := strings.SplitN(string(stdout), "=", 2) + if len(parts) != 2 { + return "", fmt.Errorf("unexpected output: %s", string(stdout)) + } + return strings.TrimSpace(parts[1]), nil + case "openrc", "sysvinit": + return fmt.Sprintf("/etc/init.d/%s", service), nil + default: + return "", fmt.Errorf("unsupported manager: %s", client.Name()) + } +} + +func SelectInitScript(keyword string) (string, error) { + client, err := New() + if err != nil { + return "", err + } + switch client.Name() { + case "systemd": + keyword = strings.TrimSuffix(keyword, ".service") + ".service" + case "openrc": + keyword = strings.TrimSuffix(keyword, ".service") + ".openrc" + case "sysvinit": + if _, err := os.Stat("/etc/rc.common"); err == nil { + keyword = strings.TrimSuffix(keyword, ".service") + ".prod" + } else { + keyword = strings.TrimSuffix(keyword, ".service") + ".init" + } + default: + return "", fmt.Errorf("unsupported manager: %s", client.Name()) + } + return keyword, nil +} + +func GetScriptName(keyword string) (string, error) { + client, err := New() + if err != nil { + return "", err + } + switch client.Name() { + case "systemd": + keyword = strings.TrimSuffix(keyword, ".service") + ".service" + case "openrc", "sysvinit": + lastDotIdx := strings.LastIndex(keyword, ".") + if lastDotIdx != -1 { + keyword = keyword[:lastDotIdx] + } + default: + return "", fmt.Errorf("unsupported manager: %s", client.Name()) + } + return keyword, nil +} diff --git a/agent/utils/controller/manager/common.go b/agent/utils/controller/manager/common.go new file mode 100644 index 0000000..46a310d --- /dev/null +++ b/agent/utils/controller/manager/common.go @@ -0,0 +1,23 @@ +package manager + +import ( + "errors" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +func handlerErr(out string, err error) error { + if err != nil { + if out != "" { + return errors.New(out) + } + return err + } + return nil +} + +func run(name string, args ...string) (string, error) { + return cmd.NewCommandMgr(cmd.WithTimeout(10*time.Second)).RunWithStdoutBashCf("LANGUAGE=en_US:en %s %s", name, strings.Join(args, " ")) +} diff --git a/agent/utils/controller/manager/openrc.go b/agent/utils/controller/manager/openrc.go new file mode 100644 index 0000000..68cb5ba --- /dev/null +++ b/agent/utils/controller/manager/openrc.go @@ -0,0 +1,61 @@ +package manager + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type Openrc struct{ toolCmd string } + +func NewOpenrc() *Openrc { + return &Openrc{toolCmd: "rc-service"} +} + +func (s *Openrc) Name() string { + return "openrc" +} +func (s *Openrc) IsActive(serviceName string) (bool, error) { + out, err := cmd.RunDefaultWithStdoutBashCf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", serviceName) + if err != nil { + return false, err + } + return out == "active\n", nil +} +func (s *Openrc) IsEnable(serviceName string) (bool, error) { + out, err := cmd.RunDefaultWithStdoutBashCf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", serviceName) + if err != nil { + return false, err + } + return out == "enabled\n", nil +} +func (s *Openrc) IsExist(serviceName string) (bool, error) { + _, err := os.Stat(filepath.Join("/etc/init.d", serviceName)) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", serviceName, err) + } + return true, nil +} +func (s *Openrc) Status(serviceName string) (string, error) { + return run(s.toolCmd, serviceName, "status") +} + +func (s *Openrc) Operate(operate, serviceName string) error { + switch operate { + case "enable": + return handlerErr(run("rc-update", "add", serviceName, "default")) + case "disable": + return handlerErr(run("rc-update", "del", serviceName, "default")) + default: + return handlerErr(run(s.toolCmd, serviceName, operate)) + } +} + +func (s *Openrc) Reload() error { + return nil +} diff --git a/agent/utils/controller/manager/snap.go b/agent/utils/controller/manager/snap.go new file mode 100644 index 0000000..a4410ee --- /dev/null +++ b/agent/utils/controller/manager/snap.go @@ -0,0 +1,54 @@ +package manager + +import ( + "strings" +) + +type Snap struct{ toolCmd string } + +func NewSnap() *Snap { + return &Snap{toolCmd: "snap"} +} + +func (s *Snap) IsExist(serviceName string) bool { + out, err := run(s.toolCmd, "services") + if err != nil { + return false + } + return strings.Contains(out, serviceName) +} + +func (s *Snap) IsActive(serviceName string) bool { + out, err := run(s.toolCmd, "services") + if err != nil { + return false + } + lines := strings.Split(out, "\n") + for _, line := range lines { + if strings.Contains(line, serviceName) && strings.Contains(line, "active") { + return true + } + } + return false +} + +func (s *Snap) IsEnable(serviceName string) bool { + out, err := run(s.toolCmd, "services") + if err != nil { + return false + } + lines := strings.Split(out, "\n") + for _, line := range lines { + if strings.Contains(line, serviceName) && strings.Contains(line, "enabled") { + return true + } + } + return false +} + +func (s *Snap) Operate(operate, serviceName string) error { + if s.IsExist(serviceName) { + return handlerErr(run(s.toolCmd, operate, serviceName)) + } + return nil +} diff --git a/agent/utils/controller/manager/systemd.go b/agent/utils/controller/manager/systemd.go new file mode 100644 index 0000000..fef757a --- /dev/null +++ b/agent/utils/controller/manager/systemd.go @@ -0,0 +1,75 @@ +package manager + +import ( + "strings" +) + +type Systemd struct{ toolCmd string } + +func NewSystemd() *Systemd { + return &Systemd{toolCmd: "systemctl"} +} + +func (s *Systemd) Name() string { + return "systemd" +} +func (s *Systemd) IsActive(serviceName string) (bool, error) { + out, err := run(s.toolCmd, "is-active", serviceName) + if err != nil && out != "inactive\n" { + if NewSnap().IsActive(serviceName) { + return true, nil + } + return false, err + } + return out == "active\n", nil +} + +func (s *Systemd) IsEnable(serviceName string) (bool, error) { + out, err := run(s.toolCmd, "is-enabled", serviceName) + if out == "alias\n" && serviceName == "sshd.service" { + return s.IsEnable("ssh") + } + if err != nil && out != "disabled\n" { + if NewSnap().IsEnable(serviceName) { + return true, nil + } + return false, err + } + return out == "enabled\n", nil +} + +func (s *Systemd) IsExist(serviceName string) (bool, error) { + out, err := run(s.toolCmd, "is-enabled", serviceName) + if err != nil && out != "enabled\n" { + if strings.Contains(out, "disabled") { + return true, err + } + if NewSnap().IsExist(serviceName) { + return true, nil + } + return false, err + } + return true, err +} + +func (s *Systemd) Status(serviceName string) (string, error) { + return run(s.toolCmd, "status", serviceName) +} +func (s *Systemd) Operate(operate, serviceName string) error { + out, err := run(s.toolCmd, operate, serviceName) + if err != nil { + if serviceName == "sshd" && strings.Contains(out, "alias name or linked unit file") { + return s.Operate(operate, "ssh") + } + if err := NewSnap().Operate(operate, serviceName); err == nil { + return nil + } + return handlerErr(run(s.toolCmd, operate, serviceName)) + } + return nil +} + +func (s *Systemd) Reload() error { + out, err := run(s.toolCmd, "daemon-reload") + return handlerErr(out, err) +} diff --git a/agent/utils/controller/manager/sysvinit.go b/agent/utils/controller/manager/sysvinit.go new file mode 100644 index 0000000..a1a8b1a --- /dev/null +++ b/agent/utils/controller/manager/sysvinit.go @@ -0,0 +1,54 @@ +package manager + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type Sysvinit struct{ toolCmd string } + +func NewSysvinit() *Sysvinit { + return &Sysvinit{toolCmd: "service"} +} + +func (s *Sysvinit) Name() string { + return "sysvinit" +} +func (s *Sysvinit) IsActive(serviceName string) (bool, error) { + out, err := cmd.RunDefaultWithStdoutBashCf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", serviceName) + if err != nil { + return false, err + } + return out == "active\n", nil +} +func (s *Sysvinit) IsEnable(serviceName string) (bool, error) { + out, err := cmd.RunDefaultWithStdoutBashCf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", serviceName) + if err != nil { + return false, err + } + return out == "enabled\n", nil +} +func (s *Sysvinit) IsExist(serviceName string) (bool, error) { + _, err := os.Stat(filepath.Join("/etc/init.d", serviceName)) + if err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", serviceName, err) + } + return true, nil +} +func (s *Sysvinit) Status(serviceName string) (string, error) { + return run(s.toolCmd, serviceName, "status") +} + +func (s *Sysvinit) Operate(operate, serviceName string) error { + return handlerErr(run(s.toolCmd, serviceName, operate)) +} + +func (s *Sysvinit) Reload() error { + return nil +} diff --git a/agent/utils/convert/convert.go b/agent/utils/convert/convert.go new file mode 100644 index 0000000..61dc753 --- /dev/null +++ b/agent/utils/convert/convert.go @@ -0,0 +1,181 @@ +package convert + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto/response" + "github.com/1Panel-dev/1Panel/agent/global" + "os" + "os/exec" + "path/filepath" + "strings" + "time" +) + +type FormatOption struct { + Type string + Codec string +} + +var FormatMap = map[string]FormatOption{ + // images + "png": {Type: "image", Codec: "png"}, + "jpg": {Type: "image", Codec: "mjpeg"}, + "jpeg": {Type: "image", Codec: "mjpeg"}, + "webp": {Type: "image", Codec: "libwebp"}, + "gif": {Type: "image", Codec: "gif"}, + "bmp": {Type: "image", Codec: "bmp"}, + "tiff": {Type: "image", Codec: "tiff"}, + + // videos + "mp4": {Type: "video", Codec: "libx264"}, + "avi": {Type: "video", Codec: "libx264"}, + "mov": {Type: "video", Codec: "libx264"}, + "mkv": {Type: "video", Codec: "libx264"}, + + // audios + "mp3": {Type: "audio", Codec: "libmp3lame"}, + "wav": {Type: "audio", Codec: "pcm_s16le"}, + "flac": {Type: "audio", Codec: "flac"}, + "aac": {Type: "audio", Codec: "aac"}, +} + +func hasFfmpeg() (string, bool) { + ffmpegPath, err := exec.LookPath("ffmpeg") + return ffmpegPath, err == nil +} + +func MediaFile(inputFile, outputFile, outputFormat string, deleteSource bool) (state string, err error) { + status := "FAILED" + msg := "" + ffmpegPath, flag := hasFfmpeg() + if !flag { + return status, fmt.Errorf("ffmpeg not found, cannot convert file") + } + logFile, logErr := os.OpenFile(filepath.Join(global.Dir.ConvertLogDir, "convert.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + allLogFile, allErr := os.OpenFile(filepath.Join(global.Dir.ConvertLogDir, "convert-all.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + + if logErr != nil || allErr != nil { + return status, fmt.Errorf("cannot open log file: %w", err) + } + defer logFile.Close() + args, fileType, err := buildFFmpegArgs(inputFile, outputFile, outputFormat) + if err != nil { + return status, fmt.Errorf("FFmpeg args failed: %w", err) + } + ctx := context.Background() + var cancel context.CancelFunc + ctx, cancel = context.WithTimeout(ctx, 5*time.Minute) + defer cancel() + cmdr := exec.CommandContext(ctx, ffmpegPath, args...) + cmdr.Env = append(os.Environ(), + "PATH="+filepath.Dir(ffmpegPath)+":"+os.Getenv("PATH"), + "LD_LIBRARY_PATH=/usr/local/lib:"+os.Getenv("LD_LIBRARY_PATH"), + ) + var buf bytes.Buffer + cmdr.Stdout = &buf + cmdr.Stderr = &buf + err = cmdr.Run() + logStr := buf.String() + + stat, statErr := os.Stat(outputFile) + if err != nil || statErr != nil || stat.Size() == 0 { + status = "FAILED" + msg = extractFFmpegError(logStr) + _ = os.Remove(outputFile) + } else { + status = "SUCCESS" + msg = "SUCCESS" + } + + entry := response.FileConvertLog{ + Date: time.Now().Format("2006-01-02 15:04:05"), + Type: fileType, + Log: fmt.Sprintf("%s -> %s", inputFile, outputFile), + Status: status, + Message: msg, + } + _ = appendJSONLog(logFile, entry) + + allLogEntry := fmt.Sprintf("[%s] %s %s -> %s [%s]: %s\n", + time.Now().Format("2006-01-02 15:04:05"), + fileType, inputFile, outputFile, status, logStr) + _ = appendLog(allLogFile, allLogEntry) + if err == nil && deleteSource { + _ = os.Remove(inputFile) + } + return status, nil +} + +func buildFFmpegArgs(inputFile, outputFile, outputFormat string) ([]string, string, error) { + args := []string{"-y", "-i", inputFile} + opt, ok := FormatMap[outputFormat] + if !ok { + return nil, "", fmt.Errorf("unsupported format: %s", outputFormat) + } + + switch opt.Type { + case "image": + switch outputFormat { + case "webp": + args = append(args, "-c:v", "libwebp", "-lossless", "0", "-q:v", "75") + case "png", "gif", "jpg", "jpeg", "bmp", "tiff": + args = append(args, "-c:v", opt.Codec) + } + + case "video": + args = append(args, "-c:v", opt.Codec, "-preset", "fast", "-crf", "23", "-c:a", "aac", "-b:a", "192k") + + case "audio": + args = append(args, "-c:a", opt.Codec, "-b:a", "192k") + + default: + return nil, opt.Type, fmt.Errorf("unsupported media type: %s", opt.Type) + } + + args = append(args, outputFile) + return args, opt.Type, nil +} + +func appendLog(f *os.File, content string) error { + _, err := f.WriteString(content) + return err +} + +func appendJSONLog(f *os.File, entry response.FileConvertLog) error { + data, err := json.Marshal(entry) + if err != nil { + return err + } + if _, err := f.WriteString(string(data) + "\n"); err != nil { + return err + } + return nil +} + +func extractFFmpegError(logStr string) string { + priority := []string{"Error", "Invalid", "failed", "No "} + matches := make(map[string]string) + lines := strings.Split(strings.TrimSpace(logStr), "\n") + for i := 0; i < len(lines); i++ { + line := strings.TrimSpace(lines[i]) + for _, kw := range priority { + if _, ok := matches[kw]; !ok && strings.Contains(line, kw) { + matches[kw] = line + } + } + } + + for _, kw := range priority { + if line, ok := matches[kw]; ok { + return line + } + } + + if len(lines) > 0 { + return lines[len(lines)-1] + } + return "" +} diff --git a/agent/utils/copier/copier.go b/agent/utils/copier/copier.go new file mode 100644 index 0000000..bfc684c --- /dev/null +++ b/agent/utils/copier/copier.go @@ -0,0 +1,18 @@ +package copier + +import ( + "encoding/json" + + "github.com/pkg/errors" +) + +func Copy(to, from interface{}) error { + b, err := json.Marshal(from) + if err != nil { + return errors.Wrap(err, "marshal from data err") + } + if err = json.Unmarshal(b, to); err != nil { + return errors.Wrap(err, "unmarshal to data err") + } + return nil +} diff --git a/agent/utils/csv_export/ssh_log.go b/agent/utils/csv_export/ssh_log.go new file mode 100644 index 0000000..618481d --- /dev/null +++ b/agent/utils/csv_export/ssh_log.go @@ -0,0 +1,50 @@ +package csvexport + +import ( + "encoding/csv" + "os" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/i18n" +) + +func ExportSSHLogs(filename string, logs []dto.SSHHistory) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + writer := csv.NewWriter(file) + defer writer.Flush() + + if err := writer.Write([]string{ + i18n.GetMsgByKey("ExportIP"), + i18n.GetMsgByKey("ExportArea"), + i18n.GetMsgByKey("ExportPort"), + i18n.GetMsgByKey("ExportAuthMode"), + i18n.GetMsgByKey("ExportUser"), + i18n.GetMsgByKey("ExportStatus"), + i18n.GetMsgByKey("ExportDate"), + }); err != nil { + return err + } + + for _, log := range logs { + record := []string{ + log.Address, + log.Area, + log.Port, + log.AuthMode, + log.User, + log.Status, + log.Date.Format(constant.DateTimeLayout), + } + if err := writer.Write(record); err != nil { + return err + } + } + + return nil +} diff --git a/agent/utils/docker/compose.go b/agent/utils/docker/compose.go new file mode 100644 index 0000000..6cab5e5 --- /dev/null +++ b/agent/utils/docker/compose.go @@ -0,0 +1,151 @@ +package docker + +import ( + "bufio" + "bytes" + "context" + "fmt" + "path" + "strings" + + "github.com/compose-spec/compose-go/v2/loader" + "github.com/compose-spec/compose-go/v2/types" + "github.com/docker/compose/v2/pkg/api" + "github.com/joho/godotenv" + "gopkg.in/yaml.v3" + + "github.com/1Panel-dev/1Panel/agent/utils/re" +) + +type ComposeService struct { + api.Compose +} + +func GetComposeProject(projectName, workDir string, yml []byte, env []byte, skipNormalization bool) (*types.Project, error) { + var configFiles []types.ConfigFile + configFiles = append(configFiles, types.ConfigFile{ + Filename: "docker-compose.yml", + Content: yml}, + ) + envMap, err := godotenv.UnmarshalBytes(env) + if err != nil { + return nil, err + } + details := types.ConfigDetails{ + WorkingDir: workDir, + ConfigFiles: configFiles, + Environment: envMap, + } + projectName = strings.ToLower(projectName) + projectName = re.GetRegex(re.ComposeDisallowedCharsPattern).ReplaceAllString(projectName, "") + project, err := loader.LoadWithContext(context.Background(), details, func(options *loader.Options) { + options.SetProjectName(projectName, true) + options.ResolvePaths = true + options.SkipNormalization = skipNormalization + }) + if err != nil { + return nil, err + } + project.ComposeFiles = []string{path.Join(workDir, "docker-compose.yml")} + return project, nil +} + +type ComposeProject struct { + Version string + Services map[string]Service `yaml:"services"` +} + +type Service struct { + Image string `yaml:"image"` + Environment Environment `yaml:"environment"` + Volumes []string `yaml:"volumes"` + ExtraHosts []string `yaml:"extra_hosts"` + Restart string `yaml:"restart"` +} + +type Environment struct { + Variables map[string]string +} + +func (e *Environment) UnmarshalYAML(value *yaml.Node) error { + e.Variables = make(map[string]string) + switch value.Kind { + case yaml.MappingNode: + for i := 0; i < len(value.Content); i += 2 { + key := value.Content[i].Value + val := value.Content[i+1].Value + e.Variables[key] = val + } + case yaml.SequenceNode: + for _, item := range value.Content { + var kv string + if err := item.Decode(&kv); err != nil { + return err + } + parts := strings.SplitN(kv, "=", 2) + if len(parts) == 2 { + e.Variables[parts[0]] = parts[1] + } else { + e.Variables[parts[0]] = "" + } + } + default: + return fmt.Errorf("unsupported environment format") + } + return nil +} + +func GetImagesFromDockerCompose(env, yml []byte) ([]string, error) { + envVars, err := loadEnvFile(env) + if err != nil { + return nil, fmt.Errorf("load env failed: %v", err) + } + + var compose ComposeProject + if err := yaml.Unmarshal(yml, &compose); err != nil { + return nil, fmt.Errorf("parse docker-compose file failed: %v", err) + } + + var images []string + for _, service := range compose.Services { + if service.Image != "" { + resolvedImage := replaceEnvVars(service.Image, envVars) + images = append(images, resolvedImage) + } + } + + return images, nil +} + +func loadEnvFile(env []byte) (map[string]string, error) { + envVars := make(map[string]string) + + scanner := bufio.NewScanner(bytes.NewReader(env)) + for scanner.Scan() { + line := strings.TrimSpace(scanner.Text()) + + if line == "" || strings.HasPrefix(line, "#") { + continue + } + + parts := strings.SplitN(line, "=", 2) + if len(parts) == 2 { + key := strings.TrimSpace(parts[0]) + value := strings.TrimSpace(parts[1]) + value = strings.Trim(value, `"'`) + envVars[key] = value + } + } + + return envVars, scanner.Err() +} + +func replaceEnvVars(input string, envVars map[string]string) string { + return re.GetRegex(re.ComposeEnvVarPattern).ReplaceAllStringFunc(input, func(match string) string { + varName := match[2 : len(match)-1] + if value, exists := envVars[varName]; exists { + return value + } + return match + }) +} diff --git a/agent/utils/docker/docker.go b/agent/utils/docker/docker.go new file mode 100644 index 0000000..98f2065 --- /dev/null +++ b/agent/utils/docker/docker.go @@ -0,0 +1,326 @@ +package docker + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/app/task" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/client" +) + +func NewDockerClient() (*client.Client, error) { + var settingItem model.Setting + _ = global.DB.Where("key = ?", "DockerSockPath").First(&settingItem).Error + if len(settingItem.Value) == 0 { + settingItem.Value = "unix:///var/run/docker.sock" + } + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithHost(settingItem.Value), client.WithAPIVersionNegotiation()) + if err != nil { + return nil, err + } + return cli, nil +} + +func NewClient() (Client, error) { + var settingItem model.Setting + _ = global.DB.Where("key = ?", "DockerSockPath").First(&settingItem).Error + if len(settingItem.Value) == 0 { + settingItem.Value = "unix:///var/run/docker.sock" + } + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithHost(settingItem.Value), client.WithAPIVersionNegotiation()) + if err != nil { + return Client{}, err + } + + return Client{ + cli: cli, + }, nil +} + +func NewClientWithExist(cli *client.Client) Client { + return Client{ + cli: cli, + } +} + +type Client struct { + cli *client.Client +} + +func (c Client) Close() { + _ = c.cli.Close() +} + +func (c Client) ListContainersByName(names []string) ([]container.Summary, error) { + var ( + options container.ListOptions + namesMap = make(map[string]bool) + res []container.Summary + ) + options.All = true + if len(names) > 0 { + var array []filters.KeyValuePair + for _, n := range names { + namesMap["/"+n] = true + array = append(array, filters.Arg("name", n)) + } + options.Filters = filters.NewArgs(array...) + } + containers, err := c.cli.ContainerList(context.Background(), options) + if err != nil { + return nil, err + } + for _, con := range containers { + if _, ok := namesMap[con.Names[0]]; ok { + res = append(res, con) + } + } + return res, nil +} +func (c Client) ListAllContainers() ([]container.Summary, error) { + var ( + options container.ListOptions + ) + options.All = true + containers, err := c.cli.ContainerList(context.Background(), options) + if err != nil { + return nil, err + } + return containers, nil +} + +func (c Client) CreateNetwork(name string) error { + _, err := c.cli.NetworkCreate(context.Background(), name, network.CreateOptions{ + Driver: "bridge", + EnableIPv6: new(bool), + }) + return err +} + +func (c Client) DeleteImage(imageID string) error { + if _, err := c.cli.ImageRemove(context.Background(), imageID, image.RemoveOptions{Force: true}); err != nil { + return err + } + return nil +} +func (c Client) ImageExists(imageID string) (bool, error) { + _, err := c.cli.ImageInspect(context.Background(), imageID) + if err != nil { + return false, err + } + return true, nil +} + +func (c Client) GetImageIDByName(imageName string) (string, error) { + filter := filters.NewArgs() + filter.Add("reference", imageName) + list, err := c.cli.ImageList(context.Background(), image.ListOptions{ + Filters: filter, + }) + if err != nil { + return "", err + } + if len(list) > 0 { + return list[0].ID, nil + } + return "", nil +} + +func (c Client) NetworkExist(name string) bool { + var options network.ListOptions + options.Filters = filters.NewArgs(filters.Arg("name", name)) + networks, err := c.cli.NetworkList(context.Background(), options) + if err != nil { + return false + } + return len(networks) > 0 +} + +func CreateDefaultDockerNetwork() error { + cli, err := NewClient() + if err != nil { + global.LOG.Warnf("init docker client error %s", err.Error()) + return err + } + + defer cli.Close() + if !cli.NetworkExist("1panel-network") { + if err := cli.CreateNetwork("1panel-network"); err != nil { + global.LOG.Warnf("create default docker network error %s", err.Error()) + return err + } + } + return nil +} + +func setLog(id, newLastLine string, task *task.Task) error { + data, err := os.ReadFile(task.Task.LogFile) + if err != nil { + return fmt.Errorf("failed to read file: %v", err) + } + lines := strings.Split(string(data), "\n") + exist := false + for index, line := range lines { + if strings.Contains(line, id) { + timeStr := time.Now().Format("2006/01/02 15:04:05") + lines[index] = timeStr + " " + newLastLine + exist = true + break + } else { + lines[index] = strings.TrimSpace(lines[index]) + } + } + if !exist { + task.Log(newLastLine) + return nil + } + output := strings.Join(lines, "\n") + _ = os.WriteFile(task.Task.LogFile, []byte(output), os.ModePerm) + return nil +} + +func (c Client) PullImageWithProcessAndOptions(task *task.Task, imageName string, options image.PullOptions) error { + out, err := c.cli.ImagePull(context.Background(), imageName, options) + if err != nil { + return err + } + defer out.Close() + decoder := json.NewDecoder(out) + for { + var progress map[string]interface{} + if err = decoder.Decode(&progress); err != nil { + if err == io.EOF { + break + } + return err + } + status, _ := progress["status"].(string) + if status == "Downloading" || status == "Extracting" { + logProcess(progress, task) + } + if status == "Pull complete" || status == "Download complete" { + id, _ := progress["id"].(string) + progressStr := fmt.Sprintf("%s %s", status, id) + _ = setLog(id, progressStr, task) + } + } + return nil +} + +func (c Client) PushImageWithProcessAndOptions(task *task.Task, imageName string, options image.PushOptions) error { + out, err := c.cli.ImagePush(context.Background(), imageName, options) + if err != nil { + return err + } + defer out.Close() + decoder := json.NewDecoder(out) + for { + var progress map[string]interface{} + if err = decoder.Decode(&progress); err != nil { + if err == io.EOF { + break + } + return err + } + if msg, ok := progress["errorDetail"]; ok { + return fmt.Errorf("image push failed, err: %v", msg) + } + if msg, ok := progress["error"]; ok { + return fmt.Errorf("image push failed, err: %v", msg) + } + status, _ := progress["status"].(string) + switch status { + case "Pushing": + logProcess(progress, task) + case "Pushed": + id, _ := progress["id"].(string) + progressStr := fmt.Sprintf("%s %s", status, id) + _ = setLog(id, progressStr, task) + default: + progressStr, _ := json.Marshal(progress) + task.Log(string(progressStr)) + } + } + return nil +} + +func (c Client) BuildImageWithProcessAndOptions(task *task.Task, tar io.ReadCloser, options types.ImageBuildOptions) error { + out, err := c.cli.ImageBuild(context.Background(), tar, options) + if err != nil { + return err + } + defer out.Body.Close() + decoder := json.NewDecoder(out.Body) + for { + var progress map[string]interface{} + if err = decoder.Decode(&progress); err != nil { + if err == io.EOF { + break + } + return err + } + if msg, ok := progress["errorDetail"]; ok { + return fmt.Errorf("image build failed, err: %v", msg) + } + if msg, ok := progress["error"]; ok { + return fmt.Errorf("image build failed, err: %v", msg) + } + status, _ := progress["status"].(string) + stream, _ := progress["stream"].(string) + if len(status) == 0 && len(stream) != 0 { + if stream != "\n" { + task.Log(stream) + } + continue + } + switch status { + case "Downloading", "Extracting": + logProcess(progress, task) + case "Pull complete", "Download complete", "Verifying Checksum": + id, _ := progress["id"].(string) + progressStr := fmt.Sprintf("%s %s", status, id) + _ = setLog(id, progressStr, task) + default: + progressStr, _ := json.Marshal(progress) + task.Log(string(progressStr)) + } + } + return nil +} + +func (c Client) PullImageWithProcess(task *task.Task, imageName string) error { + return c.PullImageWithProcessAndOptions(task, imageName, image.PullOptions{}) +} + +func logProcess(progress map[string]interface{}, task *task.Task) { + status, _ := progress["status"].(string) + id, _ := progress["id"].(string) + progressItem, _ := progress["progress"].(string) + progressStr := "" + progressStr = fmt.Sprintf("%s %s %s", status, id, progressItem) + _ = setLog(id, progressStr, task) +} + +func PullImage(imageName string) error { + cli, err := NewDockerClient() + if err != nil { + return err + } + defer cli.Close() + if _, err := cli.ImagePull(context.Background(), imageName, image.PullOptions{}); err != nil { + return err + } + return nil +} diff --git a/agent/utils/email/smtp_sender.go b/agent/utils/email/smtp_sender.go new file mode 100644 index 0000000..3c23b00 --- /dev/null +++ b/agent/utils/email/smtp_sender.go @@ -0,0 +1,276 @@ +package email + +import ( + "context" + "crypto/tls" + "fmt" + "mime" + "net" + "net/http" + "net/smtp" + "strings" + "time" +) + +type SMTPConfig struct { + Host string + Port int + Sender string + Username string + Password string + From string + Encryption string + Recipient string +} + +type EmailMessage struct { + Subject string + Body string + IsHTML bool +} + +type loginAuth struct { + username, password string +} + +func LoginAuth(username, password string) smtp.Auth { + return &loginAuth{username, password} +} + +func (a *loginAuth) Start(server *smtp.ServerInfo) (string, []byte, error) { + return "LOGIN", []byte{}, nil +} + +func (a *loginAuth) Next(fromServer []byte, more bool) ([]byte, error) { + if more { + switch string(fromServer) { + case "Username:": + return []byte(a.username), nil + case "Password:": + return []byte(a.password), nil + default: + return nil, fmt.Errorf("unknown server challenge: %s", fromServer) + } + } + return nil, nil +} + +func SendMail(config SMTPConfig, message EmailMessage, transport *http.Transport) error { + if err := validateConfig(config); err != nil { + return err + } + + addr := fmt.Sprintf("%s:%d", config.Host, config.Port) + toList := parseRecipients(config.Recipient) + + msg, err := buildMessage(config, message, toList) + if err != nil { + return err + } + + switch strings.ToLower(config.Encryption) { + case "ssl": + return sendWithSSL(config, addr, toList, msg, transport) + case "starttls", "tls": + return sendWithStartTLS(config, addr, toList, msg, transport) + case "none": + return sendPlaintext(config, addr, toList, msg, transport) + default: + return fmt.Errorf("unsupported encryption type: %s", config.Encryption) + } +} + +func validateConfig(config SMTPConfig) error { + if config.Host == "" { + return fmt.Errorf("SMTP host is required") + } + if config.Port <= 0 { + return fmt.Errorf("invalid SMTP port: %d", config.Port) + } + if config.Username == "" { + return fmt.Errorf("SMTP username is required") + } + if config.Password == "" { + return fmt.Errorf("SMTP password is required") + } + if config.From == "" { + return fmt.Errorf("SMTP from address is required") + } + if config.Recipient == "" { + return fmt.Errorf("SMTP recipient is required") + } + if !isValidEncryption(config.Encryption) { + return fmt.Errorf("invalid encryption type: %s. Allowed: ssl, starttls, none", config.Encryption) + } + return nil +} + +func isValidEncryption(enc string) bool { + enc = strings.ToLower(enc) + return enc == "ssl" || enc == "starttls" || enc == "none" || enc == "tls" +} + +func parseRecipients(recipient string) []string { + toList := strings.Split(recipient, ",") + for i := range toList { + toList[i] = strings.TrimSpace(toList[i]) + } + return toList +} + +func buildMessage(config SMTPConfig, message EmailMessage, toList []string) (string, error) { + headers := make(map[string]string) + encodedFrom := mime.BEncoding.Encode("UTF-8", config.From) + headers["From"] = encodedFrom + encodedSubject := mime.BEncoding.Encode("UTF-8", message.Subject) + headers["Subject"] = encodedSubject + headers["To"] = strings.Join(toList, ",") + headers["Date"] = time.Now().UTC().Format(time.RFC1123Z) + + if message.IsHTML { + headers["MIME-version"] = "1.0" + headers["Content-Type"] = "text/html; charset=\"UTF-8\"" + } else { + headers["Content-Type"] = "text/plain; charset=\"UTF-8\"" + } + + var msg strings.Builder + for k, v := range headers { + if !isValidHeader(k, v) { + return "", fmt.Errorf("invalid header: %s: %s", k, v) + } + msg.WriteString(fmt.Sprintf("%s: %s\r\n", k, v)) + } + msg.WriteString("\r\n" + message.Body) + + return msg.String(), nil +} + +func isValidHeader(key, value string) bool { + return !strings.ContainsAny(key, "\r\n") && !strings.ContainsAny(value, "\r\n") +} + +func sendWithSSL(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error { + var err error + var conn net.Conn + if transport != nil && transport.DialContext != nil { + conn, err = transport.DialContext(context.Background(), "tcp", addr) + } else { + conn, err = net.Dial("tcp", addr) + } + if err != nil { + return fmt.Errorf("failed to connect to SMTP server: %w", err) + } + defer conn.Close() + tlsConfig := &tls.Config{ + ServerName: config.Host, + } + tlsConn := tls.Client(conn, tlsConfig) + if err := tlsConn.Handshake(); err != nil { + return fmt.Errorf("TLS handshake failed: %w", err) + } + + client, err := smtp.NewClient(tlsConn, config.Host) + if err != nil { + return fmt.Errorf("failed to create SMTP client: %w", err) + } + defer client.Quit() + if err := tryAuth(client, config.Username, config.Password, config.Host); err != nil { + return fmt.Errorf("authentication failed: %w", err) + } + return sendEmailWithClient(client, config, toList, msg) +} + +func sendWithStartTLS(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error { + var err error + var conn net.Conn + if transport != nil && transport.DialContext != nil { + conn, err = transport.DialContext(context.Background(), "tcp", addr) + } else { + conn, err = net.Dial("tcp", addr) + } + if err != nil { + return fmt.Errorf("failed to connect to SMTP server: %w", err) + } + defer conn.Close() + + client, err := smtp.NewClient(conn, config.Host) + if err != nil { + return fmt.Errorf("failed to create SMTP client: %w", err) + } + defer client.Quit() + + if err = client.StartTLS(&tls.Config{ServerName: config.Host}); err != nil { + return fmt.Errorf("failed to start TLS: %w", err) + } + if err := tryAuth(client, config.Username, config.Password, config.Host); err != nil { + return fmt.Errorf("authentication failed: %w", err) + } + return sendEmailWithClient(client, config, toList, msg) +} + +func sendPlaintext(config SMTPConfig, addr string, toList []string, msg string, transport *http.Transport) error { + var err error + var conn net.Conn + if transport != nil && transport.DialContext != nil { + conn, err = transport.DialContext(context.Background(), "tcp", addr) + } else { + conn, err = net.Dial("tcp", addr) + } + if err != nil { + return fmt.Errorf("failed to connect to SMTP server: %w", err) + } + defer conn.Close() + + client, err := smtp.NewClient(conn, config.Host) + if err != nil { + return fmt.Errorf("failed to create SMTP client: %w", err) + } + + return sendEmailWithClient(client, config, toList, msg) +} + +func sendEmailWithClient(client *smtp.Client, config SMTPConfig, toList []string, msg string) error { + if err := client.Mail(config.Sender); err != nil { + return fmt.Errorf("setting sender failed: %w", err) + } + for _, addr := range toList { + if err := client.Rcpt(addr); err != nil { + return fmt.Errorf("adding recipient %s failed: %w", addr, err) + } + } + w, err := client.Data() + if err != nil { + return fmt.Errorf("preparing data failed: %w", err) + } + defer w.Close() + + if _, err := w.Write([]byte(msg)); err != nil { + return fmt.Errorf("writing message failed: %w", err) + } + + return nil +} + +func tryAuth(client *smtp.Client, username, password, host string) error { + ok, authCap := client.Extension("AUTH") + if !ok { + return fmt.Errorf("server does not support AUTH") + } + authCap = strings.ToUpper(authCap) + if strings.Contains(authCap, "PLAIN") { + auth := smtp.PlainAuth("", username, password, host) + if err := client.Auth(auth); err != nil { + return fmt.Errorf("plain auth failed: %w", err) + } + return nil + } + if strings.Contains(authCap, "LOGIN") { + if err := client.Auth(LoginAuth(username, password)); err != nil { + return fmt.Errorf("login auth failed: %w", err) + } + return nil + } + + return fmt.Errorf("no supported auth mechanism, server supports: %s", authCap) +} diff --git a/agent/utils/encrypt/encrypt.go b/agent/utils/encrypt/encrypt.go new file mode 100644 index 0000000..9a48e49 --- /dev/null +++ b/agent/utils/encrypt/encrypt.go @@ -0,0 +1,157 @@ +package encrypt + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "encoding/base64" + "fmt" + "io" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" +) + +func StringEncryptWithBase64(text string) (string, error) { + accessKeyItem, err := base64.StdEncoding.DecodeString(text) + if err != nil { + return "", err + } + encryptKeyItem, err := StringEncrypt(string(accessKeyItem)) + if err != nil { + return "", err + } + return encryptKeyItem, nil +} + +func StringEncryptWithKey(text, key string) (string, error) { + if len(text) == 0 { + return "", nil + } + if len(key) < 16 { + for len(key) < 16 { + key += "u" + } + } else { + key = key[:16] + } + pass := []byte(text) + xpass, err := aesEncryptWithSalt([]byte(key), pass) + if err == nil { + pass64 := base64.StdEncoding.EncodeToString(xpass) + return pass64, err + } + return "", err +} + +func StringEncrypt(text string) (string, error) { + if len(text) == 0 { + return "", nil + } + if len(global.CONF.Base.EncryptKey) == 0 { + var encryptSetting model.Setting + if err := global.DB.Where("key = ?", "EncryptKey").First(&encryptSetting).Error; err != nil { + return "", err + } + global.CONF.Base.EncryptKey = encryptSetting.Value + } + key := global.CONF.Base.EncryptKey + return StringEncryptWithKey(text, key) +} + +func StringDecryptWithBase64(text string) (string, error) { + decryptItem, err := StringDecrypt(text) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString([]byte(decryptItem)), nil +} + +func StringDecryptWithKey(text, key string) (string, error) { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("A panic occurred during string decrypt with key, error message: %v", r) + } + }() + if len(text) == 0 { + return "", nil + } + if len(key) < 16 { + for len(key) < 16 { + key += "u" + } + } else { + key = key[:16] + } + bytesPass, err := base64.StdEncoding.DecodeString(text) + if err != nil { + return "", err + } + var tpass []byte + tpass, err = aesDecryptWithSalt([]byte(key), bytesPass) + if err == nil { + result := string(tpass[:]) + return result, err + } + return "", err +} + +func StringDecrypt(text string) (string, error) { + if len(text) == 0 { + return "", nil + } + if len(global.CONF.Base.EncryptKey) == 0 { + var encryptSetting model.Setting + if err := global.DB.Where("key = ?", "EncryptKey").First(&encryptSetting).Error; err != nil { + return "", err + } + global.CONF.Base.EncryptKey = encryptSetting.Value + } + key := global.CONF.Base.EncryptKey + return StringDecryptWithKey(text, key) +} + +func padding(plaintext []byte, blockSize int) []byte { + padding := blockSize - len(plaintext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(plaintext, padtext...) +} + +func unPadding(origData []byte) []byte { + length := len(origData) + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} + +func aesEncryptWithSalt(key, plaintext []byte) ([]byte, error) { + plaintext = padding(plaintext, aes.BlockSize) + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + ciphertext := make([]byte, aes.BlockSize+len(plaintext)) + iv := ciphertext[0:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + cbc := cipher.NewCBCEncrypter(block, iv) + cbc.CryptBlocks(ciphertext[aes.BlockSize:], plaintext) + return ciphertext, nil +} +func aesDecryptWithSalt(key, ciphertext []byte) ([]byte, error) { + var block cipher.Block + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + if len(ciphertext) < aes.BlockSize { + return nil, fmt.Errorf("iciphertext too short") + } + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + cbc := cipher.NewCBCDecrypter(block, iv) + cbc.CryptBlocks(ciphertext, ciphertext) + ciphertext = unPadding(ciphertext) + return ciphertext, nil +} diff --git a/agent/utils/env/env.go b/agent/utils/env/env.go new file mode 100644 index 0000000..43ca0a5 --- /dev/null +++ b/agent/utils/env/env.go @@ -0,0 +1,125 @@ +package env + +import ( + "fmt" + "github.com/joho/godotenv" + "os" + "sort" + "strconv" + "strings" +) + +func Write(envMap map[string]string, filename string) error { + content, err := Marshal(envMap) + if err != nil { + return err + } + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(content + "\n") + if err != nil { + return err + } + return file.Sync() +} + +func Marshal(envMap map[string]string) (string, error) { + lines := make([]string, 0, len(envMap)) + for k, v := range envMap { + if d, err := strconv.Atoi(v); err == nil && !isStartWithZero(v) { + lines = append(lines, fmt.Sprintf(`%s=%d`, k, d)) + } else if hasEvenDoubleQuotes(v) { + lines = append(lines, fmt.Sprintf(`%s='%s'`, k, v)) + } else { + lines = append(lines, fmt.Sprintf(`%s="%s"`, k, v)) + } + } + return strings.Join(lines, "\n"), nil +} + +func WriteWithOrder(envMap map[string]string, filename string, orders []string) error { + content, err := MarshalWithOrder(envMap, orders) + if err != nil { + return err + } + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + _, err = file.WriteString(content + "\n") + if err != nil { + return err + } + return file.Sync() +} + +func MarshalWithOrder(envMap map[string]string, orders []string) (string, error) { + lines := make([]string, 0, len(envMap)) + for _, k := range orders { + if v, ok := envMap[k]; ok { + lines = append(lines, formatEnvLine(k, v)) + } + } + + extraKeys := make([]string, 0) + for k := range envMap { + found := false + for _, okk := range orders { + if k == okk { + found = true + break + } + } + if !found { + extraKeys = append(extraKeys, k) + } + } + sort.Strings(extraKeys) + for _, k := range extraKeys { + lines = append(lines, formatEnvLine(k, envMap[k])) + } + return strings.Join(lines, "\n"), nil +} + +func formatEnvLine(k, v string) string { + if d, err := strconv.Atoi(v); err == nil && !isStartWithZero(v) { + return fmt.Sprintf(`%s=%d`, k, d) + } else if hasEvenDoubleQuotes(v) { + return fmt.Sprintf(`%s='%s'`, k, v) + } else { + return fmt.Sprintf(`%s="%s"`, k, v) + } +} + +func GetEnvValueByKey(envPath, key string) (string, error) { + envMap, err := godotenv.Read(envPath) + if err != nil { + return "", err + } + value, ok := envMap[key] + if !ok { + return "", fmt.Errorf("key %s not found in %s", key, envPath) + } + return value, nil +} + +func isStartWithZero(value string) bool { + if strings.HasPrefix(value, "0") && len(value) > 1 { + return true + } + return false +} + +func hasEvenDoubleQuotes(s string) bool { + count := 0 + for _, ch := range s { + if ch == '"' { + count++ + } + } + return count%2 == 0 +} diff --git a/agent/utils/files/archiver.go b/agent/utils/files/archiver.go new file mode 100644 index 0000000..8e95079 --- /dev/null +++ b/agent/utils/files/archiver.go @@ -0,0 +1,47 @@ +package files + +import ( + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type ShellArchiver interface { + Extract(filePath, dstDir string, secret string) error + Compress(sourcePaths []string, dstFile string, secret string) error +} + +func NewShellArchiver(compressType CompressType) (ShellArchiver, error) { + switch compressType { + case Tar: + if err := checkCmdAvailability("tar"); err != nil { + return nil, err + } + return NewTarArchiver(compressType), nil + case TarGz: + return NewTarGzArchiver(), nil + case Zip: + if err := checkCmdAvailability("zip"); err != nil { + return nil, err + } + return NewZipArchiver(), nil + case Rar: + if err := checkCmdAvailability("unrar"); err != nil { + return nil, err + } + return NewRarArchiver(), nil + case X7z: + if err := checkCmdAvailability("7z"); err != nil { + return nil, err + } + return NewX7zArchiver(), nil + default: + return nil, buserr.New("unsupported compress type") + } +} + +func checkCmdAvailability(cmdStr string) error { + if cmd.Which(cmdStr) { + return nil + } + return buserr.WithName("ErrCmdNotFound", cmdStr) +} diff --git a/agent/utils/files/file_op.go b/agent/utils/files/file_op.go new file mode 100644 index 0000000..558ce9a --- /dev/null +++ b/agent/utils/files/file_op.go @@ -0,0 +1,1025 @@ +package files + +import ( + "archive/zip" + "bufio" + "compress/gzip" + "context" + "crypto/tls" + "encoding/json" + "fmt" + "io" + "io/fs" + "net/http" + "os" + "os/exec" + "path" + "path/filepath" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/buserr" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/req_helper" + cZip "github.com/klauspost/compress/zip" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/mholt/archiver/v4" + "github.com/spf13/afero" +) + +var protectedPaths = []string{ + "/", + "/bin", + "/sbin", + "/etc", + "/boot", + "/usr", + "/lib", + "/lib64", + "/dev", + "/proc", + "/sys", + "/root", +} + +func IsProtected(path string) bool { + real, err := filepath.EvalSymlinks(path) + if err == nil { + path = real + } + + abs, err := filepath.Abs(path) + if err == nil { + path = abs + } + + for _, p := range protectedPaths { + if path == p { + return true + } + } + return false +} + +type FileOp struct { + Fs afero.Fs +} + +func NewFileOp() FileOp { + return FileOp{ + Fs: afero.NewOsFs(), + } +} + +func (f FileOp) OpenFile(dst string) (fs.File, error) { + return f.Fs.Open(dst) +} + +func (f FileOp) GetContent(dst string) ([]byte, error) { + afs := &afero.Afero{Fs: f.Fs} + cByte, err := afs.ReadFile(dst) + if err != nil { + return nil, err + } + return cByte, nil +} + +func (f FileOp) CreateDir(dst string, mode fs.FileMode) error { + return f.Fs.MkdirAll(dst, mode) +} + +func (f FileOp) CreateDirWithMode(dst string, mode fs.FileMode) error { + if err := f.Fs.MkdirAll(dst, mode); err != nil { + return err + } + return f.ChmodRWithMode(dst, mode, true) +} +func (f FileOp) CreateDirWithPath(isDir bool, pathItem string) (string, error) { + checkPath := pathItem + if !isDir { + checkPath = path.Dir(pathItem) + } + if !f.Stat(checkPath) { + if err := f.CreateDir(checkPath, os.ModePerm); err != nil { + return pathItem, err + } + } + return pathItem, nil +} + +func (f FileOp) CreateFile(dst string) error { + if _, err := f.Fs.Create(dst); err != nil { + return err + } + return nil +} + +func (f FileOp) CreateFileWithMode(dst string, mode fs.FileMode) error { + file, err := f.Fs.OpenFile(dst, os.O_CREATE, mode) + if err != nil { + return err + } + return file.Close() +} + +func (f FileOp) LinkFile(source string, dst string, isSymlink bool) error { + if isSymlink { + osFs := afero.OsFs{} + return osFs.SymlinkIfPossible(source, dst) + } else { + return os.Link(source, dst) + } +} + +func (f FileOp) DeleteDir(dst string) error { + if IsProtected(dst) { + return buserr.New("ErrPathNotDelete") + } + return f.Fs.RemoveAll(dst) +} + +func (f FileOp) Stat(dst string) bool { + info, _ := f.Fs.Stat(dst) + return info != nil +} + +func (f FileOp) DeleteFile(dst string) error { + if IsProtected(dst) { + return buserr.New("ErrPathNotDelete") + } + return f.Fs.Remove(dst) +} + +func (f FileOp) CleanDir(dst string) error { + if IsProtected(dst) { + return buserr.New("ErrPathNotDelete") + } + return cmd.RunDefaultBashCf("rm -rf %s/*", dst) +} + +func (f FileOp) RmRf(dst string) error { + if IsProtected(dst) { + return buserr.New("ErrPathNotDelete") + } + return cmd.RunDefaultBashCf("rm -rf %s", dst) +} + +func (f FileOp) WriteFile(dst string, in io.Reader, mode fs.FileMode) error { + file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) + if err != nil { + return err + } + defer file.Close() + + if _, err = io.Copy(file, in); err != nil { + return err + } + + if _, err = file.Stat(); err != nil { + return err + } + return nil +} + +func (f FileOp) SaveFile(dst string, content string, mode fs.FileMode) error { + if !f.Stat(path.Dir(dst)) { + _ = f.CreateDir(path.Dir(dst), mode.Perm()) + } + file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.WriteString(content) + write.Flush() + return nil +} + +func (f FileOp) SaveFileWithByte(dst string, content []byte, mode fs.FileMode) error { + if !f.Stat(path.Dir(dst)) { + _ = f.CreateDir(path.Dir(dst), mode.Perm()) + } + file, err := f.Fs.OpenFile(dst, os.O_RDWR|os.O_CREATE|os.O_TRUNC, mode) + if err != nil { + return err + } + defer file.Close() + write := bufio.NewWriter(file) + _, _ = write.Write(content) + write.Flush() + return nil +} + +func (f FileOp) ChownR(dst string, uid string, gid string, sub bool) error { + cmdStr := fmt.Sprintf(`%s chown %s:%s "%s"`, cmd.SudoHandleCmd(), uid, gid, dst) + if sub { + cmdStr = fmt.Sprintf(`chown -R %s:%s "%s"`, uid, gid, dst) + } + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second)) + if err := cmdMgr.RunBashC(cmdStr); err != nil { + return err + } + return nil +} + +func (f FileOp) ChmodR(dst string, mode int64, sub bool) error { + cmdStr := fmt.Sprintf(`%s chmod %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%04o", mode), dst) + if sub { + cmdStr = fmt.Sprintf(`%s chmod -R %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%04o", mode), dst) + } + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second)) + if err := cmdMgr.RunBashC(cmdStr); err != nil { + return err + } + return nil +} + +func (f FileOp) ChmodRWithMode(dst string, mode fs.FileMode, sub bool) error { + cmdStr := fmt.Sprintf(`%s chmod %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%o", mode.Perm()), dst) + if sub { + cmdStr = fmt.Sprintf(`%s chmod -R %v "%s"`, cmd.SudoHandleCmd(), fmt.Sprintf("%o", mode.Perm()), dst) + } + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(10 * time.Second)) + if err := cmdMgr.RunBashC(cmdStr); err != nil { + return err + } + return nil +} + +func (f FileOp) Rename(oldName string, newName string) error { + return f.Fs.Rename(oldName, newName) +} + +type WriteCounter struct { + Total uint64 + Written uint64 + Key string + Name string +} + +type Process struct { + Total uint64 `json:"total"` + Written uint64 `json:"written"` + Percent float64 `json:"percent"` + Name string `json:"name"` +} + +func (w *WriteCounter) Write(p []byte) (n int, err error) { + n = len(p) + w.Written += uint64(n) + w.SaveProcess() + return n, nil +} + +func (w *WriteCounter) SaveProcess() { + percentValue := 0.0 + if w.Total > 0 { + percent := float64(w.Written) / float64(w.Total) * 100 + percentValue, _ = strconv.ParseFloat(fmt.Sprintf("%.2f", percent), 64) + } + process := Process{ + Total: w.Total, + Written: w.Written, + Percent: percentValue, + Name: w.Name, + } + by, _ := json.Marshal(process) + if percentValue < 100 { + global.CACHE.Set(w.Key, string(by)) + } else { + global.CACHE.SetWithTTL(w.Key, string(by), time.Second*time.Duration(10)) + } +} + +func (f FileOp) DownloadFileWithProcess(url, dst, key string, ignoreCertificate bool) error { + client := &http.Client{} + if ignoreCertificate { + client.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + } + defer client.CloseIdleConnections() + request, err := http.NewRequest("GET", url, nil) + if err != nil { + return nil + } + request.Header.Set("Accept-Encoding", "identity") + resp, err := client.Do(request) + if err != nil { + global.LOG.Errorf("get download file [%s] error, err %s", dst, err.Error()) + return err + } + out, err := os.Create(dst) + if err != nil { + global.LOG.Errorf("create download file [%s] error, err %s", dst, err.Error()) + return err + } + go func() { + counter := &WriteCounter{} + counter.Key = key + if resp.ContentLength > 0 { + counter.Total = uint64(resp.ContentLength) + } + counter.Name = filepath.Base(dst) + if _, err = io.Copy(out, io.TeeReader(resp.Body, counter)); err != nil { + global.LOG.Errorf("save download file [%s] error, err %s", dst, err.Error()) + } + out.Close() + resp.Body.Close() + + value := global.CACHE.Get(counter.Key) + process := &Process{} + _ = json.Unmarshal([]byte(value), process) + process.Percent = 100 + process.Name = counter.Name + process.Total = process.Written + by, _ := json.Marshal(process) + global.CACHE.Set(counter.Key, string(by)) + }() + return nil +} + +func (f FileOp) DownloadFile(url, dst string) error { + resp, err := req_helper.HandleGet(url) + if err != nil { + return err + } + defer resp.Body.Close() + + out, err := os.Create(dst) + if err != nil { + return fmt.Errorf("create download file [%s] error, err %s", dst, err.Error()) + } + defer out.Close() + + if _, err = io.Copy(out, resp.Body); err != nil { + return fmt.Errorf("save download file [%s] error, err %s", dst, err.Error()) + } + return nil +} + +func (f FileOp) Cut(oldPaths []string, dst, name string, cover bool) error { + if len(oldPaths) == 0 { + return nil + } + var dstPath string + coverFlag := "" + if name != "" { + dstPath = filepath.Join(dst, name) + if f.Stat(dstPath) { + dstPath = dst + } + if cover { + coverFlag = "-f" + } + } else { + dstPath = dst + coverFlag = "-f" + } + var quotedPaths []string + for _, p := range oldPaths { + quotedPaths = append(quotedPaths, fmt.Sprintf("'%s'", p)) + } + mvCommand := fmt.Sprintf("mv %s %s '%s'", coverFlag, strings.Join(quotedPaths, " "), dstPath) + if err := cmd.RunDefaultBashCf(mvCommand); err != nil { + return err + } + return nil +} + +func (f FileOp) Mv(oldPath, dstPath string) error { + if err := cmd.RunDefaultBashCf(`mv '%s' '%s'`, oldPath, dstPath); err != nil { + return err + } + return nil +} + +func (f FileOp) Copy(src, dst string) error { + if src = path.Clean("/" + src); src == "" { + return os.ErrNotExist + } + if dst = path.Clean("/" + dst); dst == "" { + return os.ErrNotExist + } + if src == "/" || dst == "/" { + return os.ErrInvalid + } + if dst == src { + return os.ErrInvalid + } + info, err := f.Fs.Stat(src) + if err != nil { + return err + } + if info.IsDir() { + return f.CopyDir(src, dst) + } + return f.CopyFile(src, dst) +} + +func (f FileOp) CopyAndReName(src, dst, name string, cover bool) error { + if src = path.Clean("/" + src); src == "" { + return os.ErrNotExist + } + if dst = path.Clean("/" + dst); dst == "" { + return os.ErrNotExist + } + if src == "/" || dst == src { + return os.ErrInvalid + } + + srcInfo, err := f.Fs.Stat(src) + if err != nil { + return err + } + + if name != "" && !cover { + if f.Stat(filepath.Join(dst, name)) { + return buserr.New("ErrFileIsExist") + } + } + + if srcInfo.IsDir() { + dstPath := dst + if name != "" && !cover { + dstPath = filepath.Join(dst, name) + } + return cmd.RunDefaultBashCf(`cp -rf '%s' '%s'`, src, dstPath) + } else { + dstPath := filepath.Join(dst, name) + if cover { + dstPath = dst + } + return cmd.RunDefaultBashCf(`cp -f '%s' '%s'`, src, dstPath) + } +} + +func (f FileOp) CopyDirWithNewName(src, dst, newName string) error { + if newName == "." || newName == "" { + return cmd.RunDefaultBashCf(`cp -rf '%s'/. '%s'`, src, dst) + } + dstDir := filepath.Join(dst, newName) + return cmd.RunDefaultBashCf(`cp -rf '%s' '%s'`, src, dstDir) +} + +func (f FileOp) CopyDir(src, dst string) error { + srcInfo, err := f.Fs.Stat(src) + if err != nil { + return err + } + dstDir := filepath.Join(dst, srcInfo.Name()) + if err = f.Fs.MkdirAll(dstDir, srcInfo.Mode()); err != nil { + return err + } + return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -rf '%s' '%s'`, src, dst+"/") +} + +func (f FileOp) CopyDirWithExclude(src, dst string, excludeNames []string) error { + srcInfo, err := f.Fs.Stat(src) + if err != nil { + return err + } + dstDir := filepath.Join(dst, srcInfo.Name()) + if err = f.Fs.MkdirAll(dstDir, srcInfo.Mode()); err != nil { + return err + } + if len(excludeNames) == 0 { + return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -rf '%s' '%s'`, src, dst+"/") + } + tmpFiles, err := os.ReadDir(src) + if err != nil { + return err + } + for _, item := range tmpFiles { + isExclude := false + for _, name := range excludeNames { + if item.Name() == name { + isExclude = true + break + } + } + if isExclude { + continue + } + if item.IsDir() { + if err := f.CopyDir(path.Join(src, item.Name()), dstDir); err != nil { + return err + } + continue + } + if err := f.CopyFile(path.Join(src, item.Name()), dstDir); err != nil { + return err + } + } + + return nil +} + +func (f FileOp) CopyFile(src, dst string) error { + dst = filepath.Clean(dst) + string(filepath.Separator) + return cmd.NewCommandMgr(cmd.WithIgnoreExist1()).RunBashCf(`cp -f '%s' '%s'`, src, dst+"/") +} + +func (f FileOp) GetDirSize(path string) (int64, error) { + duCmd := exec.Command("du", "-s", path) + output, err := duCmd.Output() + if err == nil { + fields := strings.Fields(string(output)) + if len(fields) == 2 { + var cmdSize int64 + _, err = fmt.Sscanf(fields[0], "%d", &cmdSize) + if err == nil { + return cmdSize * 1024, nil + } + } + } + + var size int64 + err = filepath.Walk(path, func(path string, info os.FileInfo, err error) error { + if err != nil { + return err + } + if !info.IsDir() { + size += info.Size() + } + return nil + }) + if err != nil { + return 0, err + } + return size, nil +} + +type DirSize struct { + Path string `json:"path"` + Size int64 `json:"size"` +} + +func (f FileOp) GetDepthDirSize(path string) ([]DirSize, error) { + var result []DirSize + sizeMap := make(map[string]int64) + duCmd := exec.Command("du", "-k", "--max-depth=1", "--exclude=proc", path) + output, err := duCmd.Output() + if err == nil { + parseDUOutput(output, sizeMap) + } else { + calculateDirSizeFallback(path, sizeMap) + } + + for dir, size := range sizeMap { + result = append(result, DirSize{ + Path: dir, + Size: size, + }) + } + + return result, nil +} + +func parseDUOutput(output []byte, sizeMap map[string]int64) { + lines := strings.Split(string(output), "\n") + for _, line := range lines { + if strings.TrimSpace(line) == "" { + continue + } + fields := strings.Fields(line) + if len(fields) == 2 { + if sizeKB, err := strconv.ParseInt(fields[0], 10, 64); err == nil { + dir := fields[1] + sizeMap[dir] = sizeKB * 1024 + } + } + } +} + +func calculateDirSizeFallback(path string, sizeMap map[string]int64) { + _ = filepath.Walk(path, func(p string, info os.FileInfo, err error) error { + if err != nil { + return nil + } + if !info.IsDir() { + rel, err := filepath.Rel(path, p) + if err != nil { + return nil + } + parts := strings.Split(rel, string(os.PathSeparator)) + var topLevel string + if len(parts) == 0 || parts[0] == "." { + topLevel = path + } else { + topLevel = filepath.Join(path, parts[0]) + } + sizeMap[topLevel] += info.Size() + } + return nil + }) +} + +func getFormat(cType CompressType) archiver.CompressedArchive { + format := archiver.CompressedArchive{} + switch cType { + case Tar: + format.Archival = archiver.Tar{} + case TarGz, Gz, Tgz: + format.Compression = archiver.Gz{} + format.Archival = archiver.Tar{} + case SdkTarGz: + format.Compression = archiver.Gz{} + format.Archival = archiver.Tar{} + case SdkZip, Zip: + format.Archival = archiver.Zip{ + Compression: zip.Deflate, + } + case Bz2, TarBz2: + format.Compression = archiver.Bz2{} + format.Archival = archiver.Tar{} + case Xz, TarXz: + format.Compression = archiver.Xz{} + format.Archival = archiver.Tar{} + } + return format +} + +func (f FileOp) Compress(srcRiles []string, dst string, name string, cType CompressType, secret string) error { + format := getFormat(cType) + + fileMaps := make(map[string]string, len(srcRiles)) + for _, s := range srcRiles { + base := filepath.Base(s) + fileMaps[s] = base + } + + if !f.Stat(dst) { + _ = f.CreateDir(dst, constant.DirPerm) + } + + files, err := archiver.FilesFromDisk(nil, fileMaps) + if err != nil { + return err + } + dstFile := filepath.Join(dst, name) + out, err := f.Fs.Create(dstFile) + if err != nil { + return err + } + + switch cType { + case Zip: + if err := ZipFile(files, out); err == nil { + return nil + } + _ = f.DeleteFile(dstFile) + return NewZipArchiver().Compress(srcRiles, dstFile, "") + case TarGz: + err = NewTarGzArchiver().Compress(srcRiles, dstFile, secret) + if err != nil { + _ = f.DeleteFile(dstFile) + return err + } + case Rar: + err = NewRarArchiver().Compress(srcRiles, dstFile, secret) + if err != nil { + _ = f.DeleteFile(dstFile) + return err + } + case X7z: + err = NewX7zArchiver().Compress(srcRiles, dstFile, secret) + if err != nil { + _ = f.DeleteFile(dstFile) + return err + } + default: + err = format.Archive(context.Background(), out, files) + if err != nil { + _ = f.DeleteFile(dstFile) + return err + } + } + return nil +} + +func isIgnoreFile(name string) bool { + return strings.HasPrefix(name, "__MACOSX") || strings.HasSuffix(name, ".DS_Store") || strings.HasPrefix(name, "._") +} + +func decodeGBK(input string) (string, error) { + decoder := simplifiedchinese.GBK.NewDecoder() + decoded, _, err := transform.String(decoder, input) + if err != nil { + return "", err + } + return decoded, nil +} + +func (f FileOp) decompressWithSDK(srcFile string, dst string, cType CompressType) error { + format := getFormat(cType) + if cType == Gz { + err := f.DecompressGzFile(srcFile, dst) + if err != nil { + return err + } + return nil + } + + handler := func(ctx context.Context, archFile archiver.File) error { + info := archFile.FileInfo + if isIgnoreFile(archFile.Name()) { + return nil + } + fileName := archFile.NameInArchive + var err error + if header, ok := archFile.Header.(cZip.FileHeader); ok { + if header.NonUTF8 && header.Flags == 0 { + fileName, err = decodeGBK(fileName) + if err != nil { + return err + } + } + } + filePath := filepath.Join(dst, fileName) + if archFile.FileInfo.IsDir() { + if err := f.Fs.MkdirAll(filePath, info.Mode()); err != nil { + return err + } + return nil + } else { + parentDir := path.Dir(filePath) + if !f.Stat(parentDir) { + if err := f.Fs.MkdirAll(parentDir, info.Mode()); err != nil { + return err + } + } + } + fr, err := archFile.Open() + if err != nil { + return err + } + defer fr.Close() + fw, err := f.Fs.OpenFile(filePath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, info.Mode()) + if err != nil { + return err + } + defer fw.Close() + if _, err := io.Copy(fw, fr); err != nil { + return err + } + + return nil + } + input, err := f.Fs.Open(srcFile) + if err != nil { + return err + } + return format.Extract(context.Background(), input, nil, handler) +} + +func (f FileOp) Decompress(srcFile string, dst string, cType CompressType, secret string) error { + if cType == Tar || cType == Zip || cType == TarGz || cType == Rar || cType == X7z { + shellArchiver, err := NewShellArchiver(cType) + if !f.Stat(dst) { + _ = f.CreateDir(dst, 0755) + } + if err == nil { + if err = shellArchiver.Extract(srcFile, dst, secret); err == nil { + return nil + } + if cType == TarGz { + if strings.Contains(err.Error(), "bad decrypt") { + return buserr.New("ErrBadDecrypt") + } + if err := shellArchiver.Extract(srcFile, dst, "-"); strings.Contains(err.Error(), "bad decrypt") { + return buserr.New("ErrBadDecrypt") + } + } + } else { + if cType == Rar || cType == X7z { + return err + } + } + } + return f.decompressWithSDK(srcFile, dst, cType) +} + +func ZipFile(files []archiver.File, dst afero.File) error { + zw := zip.NewWriter(dst) + defer zw.Close() + + for _, file := range files { + hdr, err := zip.FileInfoHeader(file) + if err != nil { + return err + } + hdr.Method = zip.Deflate + hdr.Name = file.NameInArchive + if file.IsDir() { + if !strings.HasSuffix(hdr.Name, "/") { + hdr.Name += "/" + } + } + w, err := zw.CreateHeader(hdr) + if err != nil { + return err + } + if file.IsDir() { + continue + } + + if file.LinkTarget != "" { + _, err = w.Write([]byte(filepath.ToSlash(file.LinkTarget))) + if err != nil { + return err + } + } else { + fileReader, err := file.Open() + if err != nil { + return err + } + _, err = io.Copy(w, fileReader) + if err != nil { + return err + } + } + } + return nil +} + +func (f FileOp) DecompressGzFile(srcFile, dst string) error { + in, err := f.Fs.Open(srcFile) + if err != nil { + return fmt.Errorf("open source file failed: %w", err) + } + defer in.Close() + + gr, err := gzip.NewReader(in) + if err != nil { + return fmt.Errorf("gzip reader creation failed: %w", err) + } + defer gr.Close() + + outName := strings.TrimSuffix(filepath.Base(srcFile), ".gz") + outPath := filepath.Join(dst, outName) + parentDir := filepath.Dir(outPath) + if !f.Stat(parentDir) { + if err := f.Fs.MkdirAll(parentDir, 0755); err != nil { + return err + } + } + + fw, err := f.Fs.OpenFile(outPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, 0644) + if err != nil { + return fmt.Errorf("create output file failed: %w", err) + } + defer fw.Close() + + if _, err := io.Copy(fw, gr); err != nil { + return fmt.Errorf("copy content failed: %w", err) + } + + return nil +} + +func (f FileOp) TarGzCompressPro(withDir bool, src, dst, secret, exclusionRules string) error { + if !f.Stat(path.Dir(dst)) { + if err := f.Fs.MkdirAll(path.Dir(dst), constant.FilePerm); err != nil { + return err + } + } + workdir := src + srcItem := "." + if withDir { + workdir = path.Dir(src) + srcItem = path.Base(src) + } + commands := "" + + exMap := make(map[string]struct{}) + exStr := "" + excludes := strings.Split(exclusionRules, ",") + for _, exclude := range excludes { + if len(exclude) == 0 { + continue + } + if strings.HasPrefix(exclude, "/") { + exclude, _ = filepath.Rel(src, exclude) + } + if _, ok := exMap[exclude]; ok { + continue + } + exStr += fmt.Sprintf(" --exclude '%s'", exclude) + exMap[exclude] = struct{}{} + } + + if len(secret) != 0 { + commands = fmt.Sprintf("tar %s -zcf - %s | openssl enc -aes-256-cbc -salt -k '%s' -out %s", exStr, srcItem, secret, dst) + global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + } else { + commands = fmt.Sprintf("tar -zcf %s %s %s", dst, exStr, srcItem) + global.LOG.Debug(commands) + } + cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(workdir), cmd.WithIgnoreExist1()) + return cmdMgr.RunBashC(commands) +} + +func (f FileOp) TarGzFilesWithCompressPro(list []string, dst, secret string) error { + if !f.Stat(path.Dir(dst)) { + if err := f.Fs.MkdirAll(path.Dir(dst), constant.FilePerm); err != nil { + return err + } + } + + var filelist []string + for _, item := range list { + filelist = append(filelist, "-C '"+path.Dir(item)+"' '"+path.Base(item)+"' ") + } + commands := "" + if len(secret) != 0 { + commands = fmt.Sprintf("tar -zcf - %s | openssl enc -aes-256-cbc -salt -k '%s' -out %s", strings.Join(filelist, " "), secret, dst) + global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + } else { + commands = fmt.Sprintf("tar -zcf %s %s", dst, strings.Join(filelist, " ")) + global.LOG.Debug(commands) + } + cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1()) + return cmdMgr.RunBashC(commands) +} + +func (f FileOp) TarGzExtractPro(src, dst string, secret string) error { + if _, err := os.Stat(path.Dir(dst)); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(path.Dir(dst), os.ModePerm); err != nil { + return err + } + } + + commands := "" + if len(secret) != 0 { + commands = fmt.Sprintf("openssl enc -d -aes-256-cbc -salt -k '%s' -in %s | tar -zxf - > /root/log", secret, src) + global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + } else { + commands = fmt.Sprintf("tar zxvf %s", src) + global.LOG.Debug(commands) + } + cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(dst), cmd.WithIgnoreExist1()) + return cmdMgr.RunBashC(commands) +} +func CopyCustomAppFile(srcPath, dstPath string) error { + if _, err := os.Stat(srcPath); os.IsNotExist(err) { + return fmt.Errorf("source file does not exist: %s", srcPath) + } + + destDir := path.Dir(dstPath) + if err := os.MkdirAll(destDir, 0755); err != nil { + return fmt.Errorf("failed to create destination directory %s: %v", destDir, err) + } + + source, err := os.Open(srcPath) + if err != nil { + return fmt.Errorf("failed to open source file %s: %v", srcPath, err) + } + defer source.Close() + + tempFile, err := os.CreateTemp(destDir, "temp_*.tar.gz") + if err != nil { + return fmt.Errorf("failed to create temporary file in %s: %v", destDir, err) + } + defer os.Remove(tempFile.Name()) + defer tempFile.Close() + + if _, err = io.Copy(tempFile, source); err != nil { + return fmt.Errorf("failed to copy file contents: %v", err) + } + + tempFile.Close() + source.Close() + + if err = os.Rename(tempFile.Name(), dstPath); err != nil { + return fmt.Errorf("failed to rename temporary file to %s: %v", dstPath, err) + } + return nil +} + +func OpensslEncrypt(filePath, secret string) error { + tmpName := path.Join(path.Dir(filePath), "tmp_"+path.Base(filePath)) + if err := cmd.RunDefaultBashCf("MY_PASS='%s' openssl enc -aes-256-cbc -salt -pass env:MY_PASS -in %s -out %s", secret, filePath, tmpName); err != nil { + _ = os.Remove(tmpName) + return err + } + return os.Rename(tmpName, filePath) +} + +func OpensslDecrypt(filePath, secret string) error { + tmpName := path.Join(path.Dir(filePath), "tmp_"+path.Base(filePath)) + if err := cmd.RunDefaultBashCf("MY_PASS='%s' openssl enc -aes-256-cbc -d -salt -pass env:MY_PASS -in %s -out %s", secret, filePath, tmpName); err != nil { + if strings.Contains(err.Error(), "bad decrypt") || strings.Contains(err.Error(), "bad magic number") { + return buserr.New("ErrBadDecrypt") + } + return err + } + return nil +} diff --git a/agent/utils/files/fileinfo.go b/agent/utils/files/fileinfo.go new file mode 100644 index 0000000..1c4a278 --- /dev/null +++ b/agent/utils/files/fileinfo.go @@ -0,0 +1,473 @@ +package files + +import ( + "bufio" + "fmt" + "io/fs" + "net/http" + "os" + "os/exec" + "path" + "path/filepath" + "sort" + "strconv" + "strings" + "syscall" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/repo" + "github.com/1Panel-dev/1Panel/agent/buserr" + + "github.com/spf13/afero" +) + +type FileInfo struct { + Fs afero.Fs `json:"-"` + Path string `json:"path"` + Name string `json:"name"` + User string `json:"user"` + Group string `json:"group"` + Uid string `json:"uid"` + Gid string `json:"gid"` + Extension string `json:"extension"` + Content string `json:"content"` + Size int64 `json:"size"` + IsDir bool `json:"isDir"` + IsSymlink bool `json:"isSymlink"` + IsHidden bool `json:"isHidden"` + LinkPath string `json:"linkPath"` + Type string `json:"type"` + Mode string `json:"mode"` + MimeType string `json:"mimeType"` + UpdateTime time.Time `json:"updateTime"` + ModTime time.Time `json:"modTime"` + FileMode os.FileMode `json:"-"` + Items []*FileInfo `json:"items"` + ItemTotal int `json:"itemTotal"` + FavoriteID uint `json:"favoriteID"` + IsDetail bool `json:"isDetail"` +} + +type FileOption struct { + Path string `json:"path"` + Search string `json:"search"` + ContainSub bool `json:"containSub"` + Expand bool `json:"expand"` + Dir bool `json:"dir"` + ShowHidden bool `json:"showHidden"` + Page int `json:"page"` + PageSize int `json:"pageSize"` + SortBy string `json:"sortBy"` + SortOrder string `json:"sortOrder"` + IsDetail bool `json:"isDetail"` +} + +type FileSearchInfo struct { + Path string `json:"path"` + fs.FileInfo +} + +func NewFileInfo(op FileOption) (*FileInfo, error) { + var appFs = afero.NewOsFs() + + info, err := appFs.Stat(op.Path) + if err != nil { + if os.IsNotExist(err) { + return nil, buserr.New("ErrLinkPathNotFound") + } + return nil, err + } + + file := &FileInfo{ + Fs: appFs, + Path: op.Path, + Name: info.Name(), + IsDir: info.IsDir(), + FileMode: info.Mode(), + ModTime: info.ModTime(), + Size: info.Size(), + IsSymlink: IsSymlink(info.Mode()), + Extension: filepath.Ext(info.Name()), + IsHidden: IsHidden(op.Path), + Mode: fmt.Sprintf("%04o", info.Mode().Perm()), + User: GetUsername(info.Sys().(*syscall.Stat_t).Uid), + Uid: strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Uid), 10), + Gid: strconv.FormatUint(uint64(info.Sys().(*syscall.Stat_t).Gid), 10), + Group: GetGroup(info.Sys().(*syscall.Stat_t).Gid), + MimeType: GetMimeType(op.Path), + IsDetail: op.IsDetail, + } + favoriteRepo := repo.NewIFavoriteRepo() + favorite, _ := favoriteRepo.GetFirst(favoriteRepo.WithByPath(op.Path)) + if favorite.ID > 0 { + file.FavoriteID = favorite.ID + } + + if file.IsSymlink { + linkPath := GetSymlink(op.Path) + if !filepath.IsAbs(linkPath) { + dir := filepath.Dir(op.Path) + var err error + linkPath, err = filepath.Abs(filepath.Join(dir, linkPath)) + if err != nil { + return nil, err + } + } + file.LinkPath = linkPath + targetInfo, err := appFs.Stat(linkPath) + if err != nil { + file.IsDir = false + file.Mode = "-" + file.User = "-" + file.Group = "-" + } else { + file.IsDir = targetInfo.IsDir() + } + file.Extension = filepath.Ext(file.LinkPath) + } + if op.Expand { + if err := handleExpansion(file, op); err != nil { + return nil, err + } + } + return file, nil +} + +func handleExpansion(file *FileInfo, op FileOption) error { + if file.IsDir { + return file.listChildren(op) + } + + if !file.IsDetail { + return file.getContent() + } + + return nil +} + +func (f *FileInfo) search(search string, count int) (files []FileSearchInfo, total int, err error) { + cmd := exec.Command("find", f.Path, "-name", fmt.Sprintf("*%s*", search)) + output, err := cmd.StdoutPipe() + if err != nil { + return + } + if err = cmd.Start(); err != nil { + return + } + defer func() { + _ = cmd.Wait() + _ = cmd.Process.Kill() + }() + + scanner := bufio.NewScanner(output) + for scanner.Scan() { + line := scanner.Text() + info, err := os.Stat(line) + if err != nil { + continue + } + total++ + if total > count { + continue + } + files = append(files, FileSearchInfo{ + Path: line, + FileInfo: info, + }) + } + if err = scanner.Err(); err != nil { + return + } + return +} + +func sortFileList(list []FileSearchInfo, sortBy, sortOrder string) { + switch sortBy { + case "name": + if sortOrder == "ascending" { + sort.Slice(list, func(i, j int) bool { + return list[i].Name() < list[j].Name() + }) + } else { + sort.Slice(list, func(i, j int) bool { + return list[i].Name() > list[j].Name() + }) + } + case "size": + if sortOrder == "ascending" { + sort.Slice(list, func(i, j int) bool { + return list[i].Size() < list[j].Size() + }) + } else { + sort.Slice(list, func(i, j int) bool { + return list[i].Size() > list[j].Size() + }) + } + case "modTime": + if sortOrder == "ascending" { + sort.Slice(list, func(i, j int) bool { + return list[i].ModTime().Before(list[j].ModTime()) + }) + } else { + sort.Slice(list, func(i, j int) bool { + return list[i].ModTime().After(list[j].ModTime()) + }) + } + } +} + +func (f *FileInfo) listChildren(option FileOption) error { + var ( + files []FileSearchInfo + err error + total int + ) + + if option.Search != "" && option.ContainSub { + files, total, err = f.search(option.Search, option.Page*option.PageSize) + if err != nil { + return err + } + } else { + files, err = f.getFiles(option) + if err != nil { + return err + } + } + + items, err := f.processFiles(files, option) + if err != nil { + return err + } + + if option.ContainSub { + f.ItemTotal = total + } + start := (option.Page - 1) * option.PageSize + end := option.PageSize + start + var result []*FileInfo + if start < 0 || start > f.ItemTotal || end < 0 || start > end { + result = items + } else { + if end > f.ItemTotal { + result = items[start:] + } else { + result = items[start:end] + } + } + + f.Items = result + return nil +} + +func (f *FileInfo) getFiles(option FileOption) ([]FileSearchInfo, error) { + infos, err := os.ReadDir(f.Path) + if err != nil { + return nil, err + } + var ( + dirs []FileSearchInfo + fileList []FileSearchInfo + ) + + for _, file := range infos { + fileInfo, err := file.Info() + if err != nil { + continue + } + info := FileSearchInfo{ + Path: f.Path, + FileInfo: fileInfo, + } + if file.IsDir() { + dirs = append(dirs, info) + } else { + fileList = append(fileList, info) + } + } + + sortFileList(dirs, option.SortBy, option.SortOrder) + sortFileList(fileList, option.SortBy, option.SortOrder) + + return append(dirs, fileList...), nil +} + +func (f *FileInfo) processFiles(files []FileSearchInfo, option FileOption) ([]*FileInfo, error) { + var items []*FileInfo + + for _, df := range files { + if shouldSkipFile(df, option) { + continue + } + + name, fPath := f.getFilePathAndName(option, df) + + if !option.ShowHidden && IsHidden(name) { + continue + } + f.ItemTotal++ + + isSymlink, isInvalidLink := f.checkSymlink(df) + + file := &FileInfo{ + Fs: f.Fs, + Name: name, + Size: df.Size(), + ModTime: df.ModTime(), + FileMode: df.Mode(), + IsDir: df.IsDir(), + IsSymlink: isSymlink, + IsHidden: IsHidden(fPath), + Extension: filepath.Ext(name), + Path: fPath, + Mode: fmt.Sprintf("%04o", df.Mode().Perm()), + User: GetUsername(df.Sys().(*syscall.Stat_t).Uid), + Group: GetGroup(df.Sys().(*syscall.Stat_t).Gid), + Uid: strconv.FormatUint(uint64(df.Sys().(*syscall.Stat_t).Uid), 10), + Gid: strconv.FormatUint(uint64(df.Sys().(*syscall.Stat_t).Gid), 10), + } + favoriteRepo := repo.NewIFavoriteRepo() + favorite, _ := favoriteRepo.GetFirst(favoriteRepo.WithByPath(fPath)) + if favorite.ID > 0 { + file.FavoriteID = favorite.ID + } + if isSymlink { + linkPath := GetSymlink(fPath) + if !filepath.IsAbs(linkPath) { + dir := filepath.Dir(fPath) + var err error + linkPath, err = filepath.Abs(filepath.Join(dir, linkPath)) + if err != nil { + return nil, err + } + } + file.LinkPath = linkPath + targetInfo, err := file.Fs.Stat(linkPath) + if err != nil { + file.IsDir = false + file.Mode = "-" + file.User = "-" + file.Group = "-" + } else { + file.IsDir = targetInfo.IsDir() + } + file.Extension = filepath.Ext(file.LinkPath) + } + if df.Size() > 0 { + file.MimeType = GetMimeType(fPath) + } + if isInvalidLink { + file.Type = "invalid_link" + } + items = append(items, file) + } + + return items, nil +} + +func shouldSkipFile(df FileSearchInfo, option FileOption) bool { + if option.Dir && !df.IsDir() { + return true + } + + if option.Search != "" && !option.ContainSub { + lowerName := strings.ToLower(df.Name()) + lowerSearch := strings.ToLower(option.Search) + if !strings.Contains(lowerName, lowerSearch) { + return true + } + } + + return false +} + +func (f *FileInfo) getFilePathAndName(option FileOption, df FileSearchInfo) (string, string) { + name := df.Name() + fPath := path.Join(df.Path, df.Name()) + + if option.Search != "" && option.ContainSub { + fPath = df.Path + name = strings.TrimPrefix(strings.TrimPrefix(fPath, f.Path), "/") + } + + return name, fPath +} + +func (f *FileInfo) checkSymlink(df FileSearchInfo) (bool, bool) { + isSymlink := false + isInvalidLink := false + + if IsSymlink(df.Mode()) { + isSymlink = true + info, err := f.Fs.Stat(path.Join(df.Path, df.Name())) + if err == nil { + df.FileInfo = info + } else { + isInvalidLink = true + } + } + + return isSymlink, isInvalidLink +} + +func (f *FileInfo) getContent() error { + if IsBlockDevice(f.FileMode) { + return buserr.New("ErrFileCanNotRead") + } + if f.Size > 10*1024*1024 { + return buserr.New("ErrFileToLarge") + } + afs := &afero.Afero{Fs: f.Fs} + cByte, err := afs.ReadFile(f.Path) + if err != nil { + return nil + } + if len(cByte) > 0 && DetectBinary(cByte) { + return buserr.New("ErrFileCanNotRead") + } + f.Content = string(cByte) + return nil +} + +func DetectBinary(buf []byte) bool { + mimeType := http.DetectContentType(buf) + if !strings.HasPrefix(mimeType, "text/") { + whiteByte := 0 + n := min(1024, len(buf)) + for i := 0; i < n; i++ { + if (buf[i] >= 0x20) || buf[i] == 9 || buf[i] == 10 || buf[i] == 13 { + whiteByte++ + } else if buf[i] <= 6 || (buf[i] >= 14 && buf[i] <= 31) { + return true + } + } + return whiteByte < 1 + } + return false + +} + +func min(x, y int) int { + if x < y { + return x + } + return y +} + +type CompressType string + +const ( + Zip CompressType = "zip" + Gz CompressType = "gz" + Bz2 CompressType = "bz2" + TarBz2 CompressType = "tar.bz2" + Tar CompressType = "tar" + TarGz CompressType = "tar.gz" + Tgz CompressType = "tgz" + Xz CompressType = "xz" + TarXz CompressType = "tar.xz" + SdkZip CompressType = "sdkZip" + SdkTarGz CompressType = "sdkTarGz" + Rar CompressType = "rar" + X7z CompressType = "7z" +) diff --git a/agent/utils/files/rar.go b/agent/utils/files/rar.go new file mode 100644 index 0000000..9621e00 --- /dev/null +++ b/agent/utils/files/rar.go @@ -0,0 +1,57 @@ +package files + +import ( + "fmt" + "path" + "time" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +type RarArchiver struct { +} + +func NewRarArchiver() ShellArchiver { + return &RarArchiver{} +} + +func (z RarArchiver) Extract(filePath, dstDir string, _ string) error { + if err := checkCmdAvailability("unrar"); err != nil { + return err + } + return cmd.RunDefaultBashCf("unrar x -y -o+ %q %q", filePath, dstDir) +} + +func (z RarArchiver) Compress(sourcePaths []string, dstFile string, _ string) (err error) { + if err = checkCmdAvailability("rar"); err != nil { + return err + } + tmpFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("%s%s.rar", common.RandStr(50), time.Now().Format(constant.DateTimeSlimLayout))) + op := NewFileOp() + defer func() { + _ = op.DeleteFile(tmpFile) + if err != nil { + _ = op.DeleteFile(dstFile) + } + }() + + baseDir := path.Dir(sourcePaths[0]) + relativePaths := make([]string, len(sourcePaths)) + for i, sp := range sourcePaths { + relativePaths[i] = path.Base(sp) + } + + cmdArgs := append([]string{"a", "-r", tmpFile}, relativePaths...) + cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(baseDir)) + if err = cmdMgr.Run("rar", cmdArgs...); err != nil { + return err + } + + if err = op.Mv(tmpFile, dstFile); err != nil { + return err + } + return nil +} diff --git a/agent/utils/files/tar.go b/agent/utils/files/tar.go new file mode 100644 index 0000000..0d67e68 --- /dev/null +++ b/agent/utils/files/tar.go @@ -0,0 +1,37 @@ +package files + +import ( + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type TarArchiver struct { + Cmd string + CompressType CompressType +} + +func NewTarArchiver(compressType CompressType) ShellArchiver { + return &TarArchiver{ + Cmd: "tar", + CompressType: compressType, + } +} + +func (t TarArchiver) Extract(FilePath string, dstDir string, secret string) error { + return cmd.RunDefaultBashCf("%s %s \"%s\" -C \"%s\"", t.Cmd, t.getOptionStr("extract"), FilePath, dstDir) +} + +func (t TarArchiver) Compress(sourcePaths []string, dstFile string, secret string) error { + return nil +} + +func (t TarArchiver) getOptionStr(Option string) string { + switch t.CompressType { + case Tar: + if Option == "compress" { + return "cvf" + } else { + return "xf" + } + } + return "" +} diff --git a/agent/utils/files/tar_gz.go b/agent/utils/files/tar_gz.go new file mode 100644 index 0000000..0122712 --- /dev/null +++ b/agent/utils/files/tar_gz.go @@ -0,0 +1,63 @@ +package files + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type TarGzArchiver struct { +} + +func NewTarGzArchiver() ShellArchiver { + return &TarGzArchiver{} +} + +func (t TarGzArchiver) Extract(filePath, dstDir string, secret string) error { + if err := os.MkdirAll(dstDir, 0755); err != nil { + return fmt.Errorf("failed to create destination dir: %w", err) + } + var err error + commands := "" + if len(secret) != 0 { + extraCmd := fmt.Sprintf("openssl enc -d -aes-256-cbc -k '%s' -in '%s' | ", secret, filePath) + commands = fmt.Sprintf("%s tar -zxvf - -C '%s' > /dev/null 2>&1", extraCmd, dstDir) + global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" %s ", secret), "******")) + } else { + commands = fmt.Sprintf("tar -zxvf '%s' -C '%s' > /dev/null 2>&1", filePath, dstDir) + global.LOG.Debug(commands) + } + if err = cmd.RunDefaultBashC(commands); err != nil { + return err + } + return nil +} + +func (t TarGzArchiver) Compress(sourcePaths []string, dstFile string, secret string) error { + var itemDirs []string + for _, item := range sourcePaths { + itemDirs = append(itemDirs, fmt.Sprintf("\"%s\"", filepath.Base(item))) + } + itemDir := strings.Join(itemDirs, " ") + aheadDir := filepath.Dir(sourcePaths[0]) + if len(aheadDir) == 0 { + aheadDir = "/" + } + commands := "" + if len(secret) != 0 { + extraCmd := fmt.Sprintf("| openssl enc -aes-256-cbc -salt -k '%s' -out '%s'", secret, dstFile) + commands = fmt.Sprintf("tar -zcf - -C \"%s\" %s %s", aheadDir, itemDir, extraCmd) + global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + } else { + commands = fmt.Sprintf("tar -zcf \"%s\" -C \"%s\" %s", dstFile, aheadDir, itemDir) + global.LOG.Debug(commands) + } + if err := cmd.RunDefaultBashC(commands); err != nil { + return err + } + return nil +} diff --git a/agent/utils/files/utils.go b/agent/utils/files/utils.go new file mode 100644 index 0000000..b7f6641 --- /dev/null +++ b/agent/utils/files/utils.go @@ -0,0 +1,415 @@ +package files + +import ( + "bufio" + "fmt" + "io" + "net/http" + "os" + "os/user" + "path/filepath" + "strconv" + "strings" + "sync" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/utils/req_helper" + "golang.org/x/text/encoding" + "golang.org/x/text/encoding/charmap" + "golang.org/x/text/encoding/japanese" + "golang.org/x/text/encoding/korean" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/encoding/traditionalchinese" + "golang.org/x/text/encoding/unicode" +) + +const ( + MaxReadFileSize = 512 * 1024 * 1024 + tailBufSize = int64(32768) +) + +func IsSymlink(mode os.FileMode) bool { + return mode&os.ModeSymlink != 0 +} + +func IsBlockDevice(mode os.FileMode) bool { + return mode&os.ModeDevice != 0 && mode&os.ModeCharDevice == 0 +} + +func GetMimeType(path string) string { + file, err := os.Open(path) + if err != nil { + return "" + } + defer file.Close() + + buffer := make([]byte, 512) + _, err = file.Read(buffer) + if err != nil { + return "" + } + mimeType := http.DetectContentType(buffer) + return mimeType +} + +func GetSymlink(path string) string { + linkPath, err := os.Readlink(path) + if err != nil { + return "" + } + return linkPath +} + +func GetUsername(uid uint32) string { + usr, err := user.LookupId(strconv.Itoa(int(uid))) + if err != nil { + return "" + } + return usr.Username +} + +func GetGroup(gid uint32) string { + usr, err := user.LookupGroupId(strconv.Itoa(int(gid))) + if err != nil { + return "" + } + return usr.Name +} + +const dotCharacter = 46 + +func IsHidden(path string) bool { + base := filepath.Base(path) + return len(base) > 1 && base[0] == dotCharacter +} + +var readerPool = sync.Pool{ + New: func() interface{} { + return bufio.NewReaderSize(nil, 8192) + }, +} + +var tailBufPool = sync.Pool{ + New: func() interface{} { + buf := make([]byte, tailBufSize) + return &buf + }, +} + +func readLineTrimmed(reader *bufio.Reader) (string, error) { + line, err := reader.ReadString('\n') + if err == io.EOF { + if len(line) == 0 { + return "", io.EOF + } + err = nil + } + if err != nil { + return "", err + } + line = strings.TrimSuffix(line, "\n") + line = strings.TrimSuffix(line, "\r") + return line, nil +} + +func TailFromEnd(filename string, lines int) ([]string, error) { + file, err := os.Open(filename) + if err != nil { + return nil, err + } + defer file.Close() + + stat, err := file.Stat() + if err != nil { + return nil, err + } + fileSize := stat.Size() + + bufPtr := tailBufPool.Get().(*[]byte) + buf := *bufPtr + defer tailBufPool.Put(bufPtr) + + var result []string + var leftover string + + for offset := fileSize; offset > 0 && len(result) < lines; { + readSize := tailBufSize + if offset < tailBufSize { + readSize = offset + } + offset -= readSize + + _, err := file.ReadAt(buf[:readSize], offset) + if err != nil && err != io.EOF { + return nil, err + } + + data := string(buf[:readSize]) + leftover + linesInChunk := strings.Split(data, "\n") + + if offset > 0 { + leftover = linesInChunk[0] + linesInChunk = linesInChunk[1:] + } else { + leftover = "" + } + + for i := len(linesInChunk) - 1; i >= 0; i-- { + if len(result) >= lines { + break + } + if i == len(linesInChunk)-1 && linesInChunk[i] == "" && len(result) == 0 { + continue + } + // 反插数据 + result = append(result, linesInChunk[i]) + } + } + + if leftover != "" && len(result) < lines { + result = append(result, leftover) + } + + if len(result) > lines { + result = result[:lines] + } + + // 反转数据 + for i, j := 0, len(result)-1; i < j; i, j = i+1, j-1 { + result[i], result[j] = result[j], result[i] + } + + return result, nil +} + +func ReadFileByLine(filename string, page, pageSize int, latest bool) (res *dto.LogFileRes, err error) { + if !NewFileOp().Stat(filename) { + return + } + if pageSize <= 0 { + err = fmt.Errorf("pageSize must be positive") + return + } + file, err := os.Open(filename) + if err != nil { + return + } + defer file.Close() + + fi, err := file.Stat() + if err != nil { + return + } + + if fi.Size() > MaxReadFileSize { + err = buserr.New("ErrLogFileToLarge") + return + } + + res = &dto.LogFileRes{} + reader := readerPool.Get().(*bufio.Reader) + reader.Reset(file) + defer readerPool.Put(reader) + + if latest { + ringBuf := make([]string, pageSize) + writeIdx := 0 + totalLines := 0 + + for { + line, readErr := readLineTrimmed(reader) + if readErr == io.EOF { + break + } + if readErr != nil { + err = readErr + return + } + ringBuf[writeIdx%pageSize] = line + writeIdx++ + totalLines++ + } + + if totalLines == 0 { + res.Lines = []string{} + res.TotalLines = 0 + res.TotalPages = 0 + res.IsEndOfFile = true + return + } + + total := (totalLines + pageSize - 1) / pageSize + res.TotalPages = total + res.TotalLines = totalLines + + lastPageSize := totalLines % pageSize + if lastPageSize == 0 { + lastPageSize = pageSize + } + if lastPageSize > totalLines { + lastPageSize = totalLines + } + + result := make([]string, 0, lastPageSize) + startIdx := writeIdx - lastPageSize + for i := 0; i < lastPageSize; i++ { + idx := (startIdx + i) % pageSize + result = append(result, ringBuf[idx]) + } + res.Lines = result + res.IsEndOfFile = true + } else { + startLine := (page - 1) * pageSize + endLine := startLine + pageSize + currentLine := 0 + lines := make([]string, 0, pageSize) + + for { + line, readErr := readLineTrimmed(reader) + if readErr == io.EOF { + break + } + if readErr != nil { + err = readErr + return + } + + if currentLine >= startLine && currentLine < endLine { + lines = append(lines, line) + } + currentLine++ + } + + res.Lines = lines + res.TotalLines = currentLine + total := (currentLine + pageSize - 1) / pageSize + res.TotalPages = total + res.IsEndOfFile = page >= total + } + + return +} + +func GetParentMode(path string) (os.FileMode, error) { + absPath, err := filepath.Abs(path) + if err != nil { + return 0, err + } + + for { + fileInfo, err := os.Stat(absPath) + if err == nil { + return fileInfo.Mode() & os.ModePerm, nil + } + if !os.IsNotExist(err) { + return 0, err + } + + parentDir := filepath.Dir(absPath) + if parentDir == absPath { + return 0, fmt.Errorf("no existing directory found in the path: %s", path) + } + absPath = parentDir + } +} + +func IsInvalidChar(name string) bool { + return strings.Contains(name, "&") +} + +func IsEmptyDir(dir string) bool { + f, err := os.Open(dir) + if err != nil { + return false + } + defer f.Close() + _, err = f.Readdirnames(1) + return err == io.EOF +} + +func DownloadFileWithProxy(url, dst string) error { + resp, cancel, err := req_helper.RequestFile(url, http.MethodGet, constant.TimeOut5m) + if err != nil { + return err + } + defer cancel() + defer resp.Close() + + out, err := os.Create(dst) + if err != nil { + return fmt.Errorf("create download file [%s] error, err %s", dst, err.Error()) + } + defer out.Close() + + if _, err = io.Copy(out, resp); err != nil { + return fmt.Errorf("save download file [%s] error, err %s", dst, err.Error()) + } + return nil +} + +func GetDecoderByName(name string) encoding.Encoding { + switch strings.ToLower(name) { + case "gbk": + return simplifiedchinese.GBK + case "gb18030": + return simplifiedchinese.GB18030 + case "big5": + return traditionalchinese.Big5 + case "euc-jp": + return japanese.EUCJP + case "iso-2022-jp": + return japanese.ISO2022JP + case "shift_jis": + return japanese.ShiftJIS + case "euc-kr": + return korean.EUCKR + case "utf-16be": + return unicode.UTF16(unicode.BigEndian, unicode.ExpectBOM) + case "utf-16le": + return unicode.UTF16(unicode.LittleEndian, unicode.ExpectBOM) + case "windows-1250": + return charmap.Windows1250 + case "windows-1251": + return charmap.Windows1251 + case "windows-1252": + return charmap.Windows1252 + case "windows-1253": + return charmap.Windows1253 + case "windows-1254": + return charmap.Windows1254 + case "windows-1255": + return charmap.Windows1255 + case "windows-1256": + return charmap.Windows1256 + case "windows-1257": + return charmap.Windows1257 + case "windows-1258": + return charmap.Windows1258 + case "iso-8859-1": + return charmap.ISO8859_1 + case "iso-8859-2": + return charmap.ISO8859_2 + case "iso-8859-3": + return charmap.ISO8859_3 + case "iso-8859-4": + return charmap.ISO8859_4 + case "iso-8859-5": + return charmap.ISO8859_5 + case "iso-8859-6": + return charmap.ISO8859_6 + case "iso-8859-7": + return charmap.ISO8859_7 + case "iso-8859-8": + return charmap.ISO8859_8 + case "iso-8859-9": + return charmap.ISO8859_9 + case "iso-8859-13": + return charmap.ISO8859_13 + case "iso-8859-15": + return charmap.ISO8859_15 + default: + return encoding.Nop + } +} diff --git a/agent/utils/files/x7z.go b/agent/utils/files/x7z.go new file mode 100644 index 0000000..8f29e11 --- /dev/null +++ b/agent/utils/files/x7z.go @@ -0,0 +1,57 @@ +package files + +import ( + "fmt" + "path" + "time" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +type X7zArchiver struct { +} + +func NewX7zArchiver() ShellArchiver { + return &X7zArchiver{} +} + +func (z X7zArchiver) Extract(filePath, dstDir string, _ string) error { + if err := checkCmdAvailability("7z"); err != nil { + return err + } + return cmd.RunDefaultBashCf("7z x -y -o%q %q", dstDir, filePath) +} + +func (z X7zArchiver) Compress(sourcePaths []string, dstFile string, _ string) (err error) { + if err = checkCmdAvailability("7z"); err != nil { + return err + } + tmpFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("%s%s.7z", common.RandStr(50), time.Now().Format(constant.DateTimeSlimLayout))) + op := NewFileOp() + defer func() { + _ = op.DeleteFile(tmpFile) + if err != nil { + _ = op.DeleteFile(dstFile) + } + }() + + baseDir := path.Dir(sourcePaths[0]) + relativePaths := make([]string, len(sourcePaths)) + for i, sp := range sourcePaths { + relativePaths[i] = path.Base(sp) + } + + cmdArgs := append([]string{"a", "-r", tmpFile}, relativePaths...) + cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(baseDir)) + if err = cmdMgr.Run("7z", cmdArgs...); err != nil { + return err + } + + if err = op.Mv(tmpFile, dstFile); err != nil { + return err + } + return nil +} diff --git a/agent/utils/files/zip.go b/agent/utils/files/zip.go new file mode 100644 index 0000000..80704a4 --- /dev/null +++ b/agent/utils/files/zip.go @@ -0,0 +1,52 @@ +package files + +import ( + "fmt" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +type ZipArchiver struct { +} + +func NewZipArchiver() ShellArchiver { + return &ZipArchiver{} +} + +func (z ZipArchiver) Extract(filePath, dstDir string, secret string) error { + if err := checkCmdAvailability("unzip"); err != nil { + return err + } + return cmd.RunDefaultBashCf("unzip -qo %s -d %s", filePath, dstDir) +} + +func (z ZipArchiver) Compress(sourcePaths []string, dstFile string, _ string) error { + var err error + tmpFile := path.Join(global.Dir.TmpDir, fmt.Sprintf("%s%s.zip", common.RandStr(50), time.Now().Format(constant.DateTimeSlimLayout))) + op := NewFileOp() + defer func() { + _ = op.DeleteFile(tmpFile) + if err != nil { + _ = op.DeleteFile(dstFile) + } + }() + baseDir := path.Dir(sourcePaths[0]) + relativePaths := make([]string, len(sourcePaths)) + for i, sp := range sourcePaths { + relativePaths[i] = path.Base(sp) + } + cmdMgr := cmd.NewCommandMgr(cmd.WithWorkDir(baseDir)) + if err = cmdMgr.Run("zip", "-qr", tmpFile, strings.Join(relativePaths, " ")); err != nil { + return err + } + if err = op.Mv(tmpFile, dstFile); err != nil { + return err + } + return nil +} diff --git a/agent/utils/firewall/client.go b/agent/utils/firewall/client.go new file mode 100644 index 0000000..fef4310 --- /dev/null +++ b/agent/utils/firewall/client.go @@ -0,0 +1,154 @@ +package firewall + +import ( + "errors" + "fmt" + "os" + "strings" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/firewall/client" +) + +type FirewallClient interface { + Name() string // ufw firewalld + Start() error + Stop() error + Restart() error + Reload() error + Status() (bool, error) + Version() (string, error) + + ListPort() ([]client.FireInfo, error) + ListForward() ([]client.FireInfo, error) + ListAddress() ([]client.FireInfo, error) + + Port(port client.FireInfo, operation string) error + RichRules(rule client.FireInfo, operation string) error + PortForward(info client.Forward, operation string) error + + EnableForward() error +} + +func NewFirewallClient() (FirewallClient, error) { + firewalld := cmd.Which("firewalld") + ufw := cmd.Which("ufw") + + if firewalld && ufw { + return nil, errors.New("It is detected that the system has both firewalld and ufw services. To avoid conflicts, please uninstall and try again!") + } + if firewalld { + return client.NewFirewalld() + } + if ufw { + return client.NewUfw() + } + + iptables := cmd.Which("iptables") + if iptables { + return client.NewIptables() + } + return nil, errors.New("No system firewall service detected (firewalld/ufw/iptables), please check and try again!") +} + +func LoadPingStatus() string { + data, err := os.ReadFile("/proc/sys/net/ipv4/icmp_echo_ignore_all") + if err != nil { + return constant.StatusNone + } + v6Data, v6err := os.ReadFile("/proc/sys/net/ipv6/icmp/echo_ignore_all") + if v6err != nil { + if strings.TrimSpace(string(data)) == "1" { + return constant.StatusEnable + } + return constant.StatusDisable + } else { + if strings.TrimSpace(string(data)) == "1" && strings.TrimSpace(string(v6Data)) == "1" { + return constant.StatusEnable + } + return constant.StatusDisable + } +} + +func UpdatePingStatus(enable string) error { + const confPath = "/etc/sysctl.conf" + const panelSysctlPath = "/etc/sysctl.d/98-onepanel.conf" + + var targetPath string + var applyCmd string + + if _, err := os.Stat(confPath); os.IsNotExist(err) { + targetPath = panelSysctlPath + applyCmd = fmt.Sprintf("%s sysctl --system", cmd.SudoHandleCmd()) + if err := cmd.RunDefaultBashCf("%s mkdir -p /etc/sysctl.d", cmd.SudoHandleCmd()); err != nil { + return fmt.Errorf("failed to create directory /etc/sysctl.d: %v", err) + } + } else { + targetPath = confPath + applyCmd = fmt.Sprintf("%s sysctl -p", cmd.SudoHandleCmd()) + } + + lineBytes, err := os.ReadFile(targetPath) + if err != nil && !os.IsNotExist(err) { + return fmt.Errorf("failed to read %s: %v", targetPath, err) + } + + if err := cmd.RunDefaultBashCf("echo %s | %s tee /proc/sys/net/ipv4/icmp_echo_ignore_all > /dev/null", enable, cmd.SudoHandleCmd()); err != nil { + return fmt.Errorf("failed to apply ipv4 ping status temporarily: %v", err) + } + + var hasIpv6 bool + if _, err := os.Stat("/proc/sys/net/ipv6/icmp/echo_ignore_all"); err == nil { + hasIpv6 = true + if err := cmd.RunDefaultBashCf("echo %s | %s tee /proc/sys/net/ipv6/icmp/echo_ignore_all > /dev/null", enable, cmd.SudoHandleCmd()); err != nil { + global.LOG.Warnf("failed to apply ipv6 ping status temporarily: %v", err) + } + } + + var files []string + if err == nil { + files = strings.Split(string(lineBytes), "\n") + } + + var newFiles []string + hasIPv4Line, hasIPv6Line := false, false + + for _, line := range files { + if strings.HasPrefix(strings.TrimSpace(line), "net.ipv4.icmp_echo_ignore_all") { + newFiles = append(newFiles, "net.ipv4.icmp_echo_ignore_all="+enable) + hasIPv4Line = true + continue + } + if strings.HasPrefix(strings.TrimSpace(line), "net.ipv6.icmp.echo_ignore_all") { + newFiles = append(newFiles, "net.ipv6.icmp.echo_ignore_all="+enable) + hasIPv6Line = true + continue + } + newFiles = append(newFiles, line) + } + + if !hasIPv4Line { + newFiles = append(newFiles, "net.ipv4.icmp_echo_ignore_all="+enable) + } + if hasIpv6 && !hasIPv6Line { + newFiles = append(newFiles, "net.ipv6.icmp.echo_ignore_all="+enable) + } + + file, err := os.OpenFile(targetPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, constant.FilePerm) + if err != nil { + return fmt.Errorf("failed to open %s: %v", targetPath, err) + } + defer file.Close() + + if _, err = file.WriteString(strings.Join(newFiles, "\n")); err != nil { + return fmt.Errorf("failed to write to %s: %v", targetPath, err) + } + + if err := cmd.RunDefaultBashC(applyCmd); err != nil { + global.LOG.Warnf("failed to apply persistent config with '%s': %v", applyCmd, err) + } + + return nil +} diff --git a/agent/utils/firewall/client/firewalld.go b/agent/utils/firewall/client/firewalld.go new file mode 100644 index 0000000..6c0c9bc --- /dev/null +++ b/agent/utils/firewall/client/firewalld.go @@ -0,0 +1,248 @@ +package client + +import ( + "fmt" + "strings" + "sync" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/controller" +) + +type Firewall struct{} + +func NewFirewalld() (*Firewall, error) { + return &Firewall{}, nil +} + +func (f *Firewall) Name() string { + return "firewalld" +} + +func (f *Firewall) Status() (bool, error) { + stdout, _ := cmd.RunDefaultWithStdoutBashC("LANGUAGE=en_US:en firewall-cmd --state") + return stdout == "running\n", nil +} + +func (f *Firewall) Version() (string, error) { + stdout, err := cmd.RunDefaultWithStdoutBashC("LANGUAGE=en_US:en firewall-cmd --version") + if err != nil { + return "", fmt.Errorf("load the firewall version failed, %v", err) + } + return strings.ReplaceAll(stdout, "\n ", ""), nil +} + +func (f *Firewall) Start() error { + if err := controller.HandleStart("firewalld"); err != nil { + return fmt.Errorf("enable the firewall failed, err: %v", err) + } + return nil +} + +func (f *Firewall) Stop() error { + if err := controller.HandleStop("firewalld"); err != nil { + return fmt.Errorf("stop the firewall failed, err: %v", err) + } + return nil +} + +func (f *Firewall) Restart() error { + if err := controller.HandleRestart("firewalld"); err != nil { + return fmt.Errorf("restart the firewall failed, err: %v", err) + } + return nil +} + +func (f *Firewall) Reload() error { + if err := cmd.RunDefaultBashC("firewall-cmd --reload"); err != nil { + return fmt.Errorf("reload firewall failed, err: %v", err) + } + return nil +} + +func (f *Firewall) ListPort() ([]FireInfo, error) { + var wg sync.WaitGroup + var datas []FireInfo + wg.Add(2) + go func() { + defer wg.Done() + stdout, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --list-ports") + if err != nil { + return + } + ports := strings.Split(strings.ReplaceAll(stdout, "\n", ""), " ") + for _, port := range ports { + if len(port) == 0 { + continue + } + var itemPort FireInfo + if strings.Contains(port, "/") { + itemPort.Port = strings.Split(port, "/")[0] + itemPort.Protocol = strings.Split(port, "/")[1] + } + itemPort.Strategy = "accept" + datas = append(datas, itemPort) + } + }() + + go func() { + defer wg.Done() + stdout1, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --list-rich-rules") + if err != nil { + return + } + rules := strings.Split(stdout1, "\n") + for _, rule := range rules { + if len(rule) == 0 { + continue + } + itemRule := f.loadInfo(rule) + if len(itemRule.Port) != 0 && (itemRule.Family == "ipv4" || (itemRule.Family == "ipv6" && len(itemRule.Address) != 0)) { + datas = append(datas, itemRule) + } + } + }() + wg.Wait() + return datas, nil +} + +func (f *Firewall) ListForward() ([]FireInfo, error) { + if err := f.EnableForward(); err != nil { + global.LOG.Errorf("init port forward failed, err: %v", err) + } + stdout, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --list-forward-ports") + if err != nil { + return nil, err + } + var datas []FireInfo + for _, line := range strings.Split(stdout, "\n") { + line = strings.TrimSpace(line) + parts := strings.Split(line, ":") + if len(parts) < 4 { + continue + } + if parts[3] == "toaddr=" { + parts[3] = "127.0.0.1" + } + datas = append(datas, FireInfo{ + Port: strings.TrimPrefix(parts[0], "port="), + Protocol: strings.TrimPrefix(parts[1], "proto="), + TargetIP: strings.TrimPrefix(parts[3], "toaddr="), + TargetPort: strings.TrimPrefix(parts[2], "toport="), + }) + } + return datas, nil +} + +func (f *Firewall) ListAddress() ([]FireInfo, error) { + stdout, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --list-rich-rules") + if err != nil { + return nil, err + } + var datas []FireInfo + rules := strings.Split(stdout, "\n") + for _, rule := range rules { + if len(rule) == 0 { + continue + } + itemRule := f.loadInfo(rule) + if len(itemRule.Port) == 0 && len(itemRule.Address) != 0 { + datas = append(datas, itemRule) + } + } + return datas, nil +} + +func (f *Firewall) Port(port FireInfo, operation string) error { + if cmd.CheckIllegal(operation, port.Protocol, port.Port) { + return buserr.New("ErrCmdIllegal") + } + + if err := cmd.RunDefaultBashCf("firewall-cmd --zone=public --%s-port=%s/%s --permanent", operation, port.Port, port.Protocol); err != nil { + return fmt.Errorf("%s (port: %s/%s strategy: %s) failed, %v", operation, port.Port, port.Protocol, port.Strategy, err) + } + return nil +} + +func (f *Firewall) RichRules(rule FireInfo, operation string) error { + if cmd.CheckIllegal(operation, rule.Address, rule.Protocol, rule.Port, rule.Strategy) { + return buserr.New("ErrCmdIllegal") + } + ruleStr := "rule family=ipv4 " + if strings.Contains(rule.Address, ":") { + ruleStr = "rule family=ipv6 " + } + if len(rule.Address) != 0 { + ruleStr += fmt.Sprintf("source address=%s ", rule.Address) + } + if len(rule.Port) != 0 { + ruleStr += fmt.Sprintf("port port=%s ", rule.Port) + } + if len(rule.Protocol) != 0 { + ruleStr += fmt.Sprintf("protocol=%s ", rule.Protocol) + } + ruleStr += rule.Strategy + if err := cmd.RunDefaultBashCf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, ruleStr); err != nil { + return fmt.Errorf("%s rich rules (%s) failed, %v", operation, ruleStr, err) + } + if len(rule.Address) == 0 { + if err := cmd.RunDefaultBashCf("firewall-cmd --zone=public --%s-rich-rule '%s' --permanent", operation, strings.ReplaceAll(ruleStr, "family=ipv4 ", "family=ipv6 ")); err != nil { + return fmt.Errorf("%s rich rules (%s) failed, %v", operation, strings.ReplaceAll(ruleStr, "family=ipv4 ", "family=ipv6 "), err) + } + } + return nil +} + +func (f *Firewall) PortForward(info Forward, operation string) error { + ruleStr := fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetPort) + if info.TargetIP != "" && info.TargetIP != "127.0.0.1" && info.TargetIP != "localhost" { + ruleStr = fmt.Sprintf("firewall-cmd --zone=public --%s-forward-port=port=%s:proto=%s:toaddr=%s:toport=%s --permanent", operation, info.Port, info.Protocol, info.TargetIP, info.TargetPort) + } + + if err := cmd.RunDefaultBashC(ruleStr); err != nil { + return fmt.Errorf("%s port forward failed, %s", operation, err) + } + if err := f.Reload(); err != nil { + return err + } + return nil +} + +func (f *Firewall) loadInfo(line string) FireInfo { + var itemRule FireInfo + ruleInfo := strings.Split(strings.ReplaceAll(line, "\"", ""), " ") + for _, item := range ruleInfo { + switch { + case strings.Contains(item, "family="): + itemRule.Family = strings.ReplaceAll(item, "family=", "") + case strings.Contains(item, "ipset="): + itemRule.Address = strings.ReplaceAll(item, "ipset=", "") + case strings.Contains(item, "address="): + itemRule.Address = strings.ReplaceAll(item, "address=", "") + case strings.Contains(item, "port="): + itemRule.Port = strings.ReplaceAll(item, "port=", "") + case strings.Contains(item, "protocol="): + itemRule.Protocol = strings.ReplaceAll(item, "protocol=", "") + case item == "accept" || item == "drop" || item == "reject": + itemRule.Strategy = item + } + } + return itemRule +} + +func (f *Firewall) EnableForward() error { + stdout, err := cmd.RunDefaultWithStdoutBashC("firewall-cmd --zone=public --query-masquerade") + if err != nil { + if strings.HasSuffix(strings.TrimSpace(stdout), "no") { + if err := cmd.RunDefaultBashC("firewall-cmd --zone=public --add-masquerade --permanent"); err != nil { + return err + } + return f.Reload() + } + return err + } + + return nil +} diff --git a/agent/utils/firewall/client/info.go b/agent/utils/firewall/client/info.go new file mode 100644 index 0000000..dff1ac1 --- /dev/null +++ b/agent/utils/firewall/client/info.go @@ -0,0 +1,28 @@ +package client + +type FireInfo struct { + ID uint `json:"id"` + Chain string `json:"chain"` + Family string `json:"family"` // ipv4 ipv6 + Address string `json:"address"` // Anywhere + Port string `json:"port"` + Protocol string `json:"protocol"` // tcp udp tcp/udp + Strategy string `json:"strategy"` // accept drop + + Num string `json:"num"` + TargetIP string `json:"targetIP"` + TargetPort string `json:"targetPort"` + Interface string `json:"interface"` + + UsedStatus string `json:"usedStatus"` + Description string `json:"description"` +} + +type Forward struct { + Num string `json:"num"` + Protocol string `json:"protocol"` + Port string `json:"port"` + TargetIP string `json:"targetIP"` + TargetPort string `json:"targetPort"` + Interface string `json:"interface"` +} diff --git a/agent/utils/firewall/client/iptables.go b/agent/utils/firewall/client/iptables.go new file mode 100644 index 0000000..5659541 --- /dev/null +++ b/agent/utils/firewall/client/iptables.go @@ -0,0 +1,355 @@ +package client + +import ( + "fmt" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/firewall/client/iptables" +) + +type Iptables struct{} + +func NewIptables() (*Iptables, error) { + return &Iptables{}, nil +} + +func (i *Iptables) Name() string { + return "iptables" +} + +func (i *Iptables) Status() (bool, error) { + stdout, err := cmd.RunDefaultWithStdoutBashC("iptables -L -n | head -1") + if err != nil { + return false, err + } + return strings.Contains(stdout, "Chain"), nil +} + +func (i *Iptables) Start() error { + return nil +} + +func (i *Iptables) Stop() error { + return nil +} + +func (i *Iptables) Restart() error { + return nil +} + +func (i *Iptables) Reload() error { + return nil +} + +func (i *Iptables) Version() (string, error) { + stdout, err := cmd.RunDefaultWithStdoutBashC("iptables --version") + if err != nil { + return "", fmt.Errorf("failed to get iptables version: %w", err) + } + parts := strings.Fields(stdout) + if len(parts) >= 2 { + return strings.TrimPrefix(parts[1], "v"), nil + } + return strings.TrimSpace(stdout), nil +} + +func (i *Iptables) ListPort() ([]FireInfo, error) { + var datas []FireInfo + basicRules, err := iptables.ReadFilterRulesByChain(iptables.Chain1PanelBasic) + if err != nil { + return nil, err + } + beforeRules, _ := iptables.ReadFilterRulesByChain(iptables.Chain1PanelBasicBefore) + basicRules = append(basicRules, beforeRules...) + for _, item := range basicRules { + if len(item.DstPort) == 0 { + continue + } + if item.Strategy == "drop" || item.Strategy == "reject" { + item.Strategy = "drop" + } + + datas = append(datas, FireInfo{ + Chain: item.Chain, + Address: item.SrcIP, + Protocol: item.Protocol, + Port: item.DstPort, + Strategy: item.Strategy, + Family: "ipv4", + }) + } + + return datas, nil +} + +func (i *Iptables) ListAddress() ([]FireInfo, error) { + var datas []FireInfo + basicRules, err := iptables.ReadFilterRulesByChain(iptables.Chain1PanelBasic) + if err != nil { + return nil, err + } + for _, item := range basicRules { + if len(item.DstPort) != 0 || len(item.SrcPort) != 0 { + continue + } + if item.Strategy == "drop" || item.Strategy == "reject" { + item.Strategy = "drop" + } + datas = append(datas, FireInfo{ + Address: item.SrcIP, + Strategy: item.Strategy, + Family: "ipv4", + }) + } + return datas, nil +} + +func (i *Iptables) Port(port FireInfo, operation string) error { + if operation != "add" && operation != "remove" { + return buserr.New("ErrCmdIllegal") + } + if len(port.Chain) == 0 { + port.Chain = iptables.Chain1PanelBasic + } + + portSpec, err := normalizePortSpec(port.Port) + if err != nil { + return err + } + + protocol := port.Protocol + if protocol == "" { + protocol = "tcp" + } + + action := "ACCEPT" + if port.Strategy == "drop" { + action = "DROP" + } + + ruleArgs := []string{fmt.Sprintf("-p %s", protocol)} + ruleArgs = append(ruleArgs, fmt.Sprintf("--dport %s", portSpec), fmt.Sprintf("-j %s", action)) + ruleSpec := strings.Join(ruleArgs, " ") + if operation == "add" { + if err := iptables.AddRule(iptables.FilterTab, port.Chain, ruleSpec); err != nil { + return err + } + } else { + if err := iptables.DeleteRule(iptables.FilterTab, port.Chain, ruleSpec); err != nil { + return err + } + } + + name := iptables.BasicFileName + if port.Chain == iptables.Chain1PanelBasicBefore { + name = iptables.BasicBeforeFileName + } + if err := iptables.SaveRulesToFile(iptables.FilterTab, port.Chain, name); err != nil { + global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err) + } + return nil +} + +func (i *Iptables) RichRules(rule FireInfo, operation string) error { + if operation != "add" && operation != "remove" { + return buserr.New("ErrCmdIllegal") + } + if len(rule.Chain) == 0 { + rule.Chain = iptables.Chain1PanelBasic + } + + address := strings.TrimSpace(rule.Address) + if strings.EqualFold(address, "Anywhere") { + address = "" + } + + action := "ACCEPT" + if rule.Strategy == "drop" { + action = "DROP" + } + + var ruleArgs []string + if address != "" { + ruleArgs = append(ruleArgs, fmt.Sprintf("-s %s", address)) + } + + protocol := strings.TrimSpace(rule.Protocol) + if rule.Port != "" && protocol == "" { + protocol = "tcp" + } + + if protocol != "" { + ruleArgs = append(ruleArgs, fmt.Sprintf("-p %s", protocol)) + } + + if rule.Port != "" { + portSegment, err := normalizePortSpec(rule.Port) + if err != nil { + return err + } + if protocol == "" { + return fmt.Errorf("protocol is required when specifying a port") + } + ruleArgs = append(ruleArgs, fmt.Sprintf("--dport %s", portSegment)) + } + + ruleArgs = append(ruleArgs, fmt.Sprintf("-j %s", action)) + ruleSpec := strings.Join(ruleArgs, " ") + if operation == "add" { + if err := iptables.AddRule(iptables.FilterTab, rule.Chain, ruleSpec); err != nil { + return err + } + } else { + if err := iptables.DeleteRule(iptables.FilterTab, rule.Chain, ruleSpec); err != nil { + return err + } + } + + name := iptables.BasicFileName + if rule.Chain == iptables.Chain1PanelBasicBefore { + name = iptables.BasicBeforeFileName + } + if err := iptables.SaveRulesToFile(iptables.FilterTab, rule.Chain, name); err != nil { + global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelBasic, err) + } + return nil +} + +func (i *Iptables) PortForward(info Forward, operation string) error { + return iptablesPortForward(info, operation) +} + +func (i *Iptables) EnableForward() error { + return EnableIptablesForward() +} + +func (i *Iptables) ListForward() ([]FireInfo, error) { + return iptablesListForward() +} + +func EnableIptablesForward() error { + if err := cmd.RunDefaultBashC("echo 1 > /proc/sys/net/ipv4/ip_forward"); err != nil { + return fmt.Errorf("failed to enable IP forwarding: %w", err) + } + _ = cmd.RunDefaultBashC("grep -q '^net.ipv4.ip_forward' /etc/sysctl.conf || echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf") + _ = cmd.RunDefaultBashC("sysctl -p") + + if err := iptables.AddChainWithAppend(iptables.NatTab, "PREROUTING", iptables.Chain1PanelPreRouting); err != nil { + return err + } + if err := iptables.AddChainWithAppend(iptables.NatTab, "POSTROUTING", iptables.Chain1PanelPostRouting); err != nil { + return err + } + if err := iptables.AddChainWithAppend(iptables.FilterTab, "FORWARD", iptables.Chain1PanelForward); err != nil { + return err + } + + return nil +} + +func iptablesPortForward(info Forward, operation string) error { + if operation != "add" && operation != "remove" { + return buserr.New("ErrCmdIllegal") + } + if info.Protocol == "" || info.Port == "" || info.TargetPort == "" { + return fmt.Errorf("protocol, port, and target port are required") + } + if operation == "add" { + if err := iptables.AddForward(info.Protocol, info.Port, info.TargetIP, info.TargetPort, info.Interface, true); err != nil { + return err + } + } else { + if err := iptables.DeleteForward(info.Num, info.Protocol, info.Port, info.TargetIP, info.TargetPort, info.Interface); err != nil { + return err + } + } + forwardPersistence() + return nil +} + +func forwardPersistence() { + if err := iptables.SaveRulesToFile(iptables.FilterTab, iptables.Chain1PanelForward, iptables.ForwardFileName); err != nil { + global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelForward, err) + } + if err := iptables.SaveRulesToFile(iptables.NatTab, iptables.Chain1PanelPreRouting, iptables.ForwardFileName1); err != nil { + global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelPreRouting, err) + } + if err := iptables.SaveRulesToFile(iptables.NatTab, iptables.Chain1PanelPostRouting, iptables.ForwardFileName2); err != nil { + global.LOG.Errorf("persistence for %s failed, err: %v", iptables.Chain1PanelPostRouting, err) + } +} + +func iptablesListForward() ([]FireInfo, error) { + natList, err := iptables.ListForward(iptables.Chain1PanelPreRouting) + if err != nil { + return nil, fmt.Errorf("failed to list NAT rules: %w", err) + } + + var datas []FireInfo + for _, nat := range natList { + datas = append(datas, FireInfo{ + Num: nat.Num, + Protocol: nat.Protocol, + Port: strings.TrimPrefix(nat.SrcPort, ":"), + TargetIP: nat.Destination, + TargetPort: strings.TrimPrefix(nat.DestPort, ":"), + Interface: nat.InIface, + }) + } + + return datas, nil +} + +func parsePort(portStr string) (int, error) { + port, err := strconv.Atoi(portStr) + if err != nil { + return 0, fmt.Errorf("invalid port number: %s", portStr) + } + if port < 1 || port > 65535 { + return 0, fmt.Errorf("port out of range: %d", port) + } + return port, nil +} + +func normalizePortSpec(port string) (string, error) { + value := strings.TrimSpace(port) + if value == "" { + return "", fmt.Errorf("port is required") + } + + separator := "" + if strings.Contains(value, "-") { + separator = "-" + } else if strings.Contains(value, ":") { + separator = ":" + } + + if separator != "" { + parts := strings.Split(value, separator) + if len(parts) != 2 { + return "", fmt.Errorf("invalid port range: %s", port) + } + start, err := parsePort(strings.TrimSpace(parts[0])) + if err != nil { + return "", err + } + end, err := parsePort(strings.TrimSpace(parts[1])) + if err != nil { + return "", err + } + if start > end { + return "", fmt.Errorf("invalid port range: %d-%d", start, end) + } + return fmt.Sprintf("%d:%d", start, end), nil + } + + single, err := parsePort(value) + if err != nil { + return "", err + } + return fmt.Sprintf("%d", single), nil +} diff --git a/agent/utils/firewall/client/iptables/common.go b/agent/utils/firewall/client/iptables/common.go new file mode 100644 index 0000000..6ad9952 --- /dev/null +++ b/agent/utils/firewall/client/iptables/common.go @@ -0,0 +1,194 @@ +package iptables + +import ( + "fmt" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +const ( + Chain1PanelPreRouting = "1PANEL_PREROUTING" + Chain1PanelPostRouting = "1PANEL_POSTROUTING" + Chain1PanelForward = "1PANEL_FORWARD" + ChainInput = "INPUT" + ChainOutput = "OUTPUT" + Chain1PanelInput = "1PANEL_INPUT" + Chain1PanelOutput = "1PANEL_OUTPUT" + Chain1PanelBasicBefore = "1PANEL_BASIC_BEFORE" + Chain1PanelBasic = "1PANEL_BASIC" + Chain1PanelBasicAfter = "1PANEL_BASIC_AFTER" +) + +const ( + EstablishedRule = "-m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT -m comment --comment 'ESTABLISHED Whitelist'" + IoRuleIn = "-i lo -j ACCEPT -m comment --comment 'Loopback Whitelist'" + DropAllTcp = "-p tcp -j DROP" + DropAllUdp = "-p udp -j DROP" + AllowSSH = "-p tcp --dport ssh -j ACCEPT" +) + +const ( + ACCEPT = "ACCEPT" + DROP = "DROP" + REJECT = "REJECT" + ANYWHERE = "anywhere" +) + +const ( + FilterTab = "filter" + NatTab = "nat" +) + +func RunWithStd(tab, rule string) (string, error) { + cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(60*time.Second)) + stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -w -t %s %s", cmd.SudoHandleCmd(), tab, rule) + if err != nil { + global.LOG.Errorf("iptables command failed [table=%s, rule=%s]: %v", tab, rule, err) + return stdout, err + } + return stdout, nil +} +func RunWithoutIgnore(tab, rule string) (string, error) { + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(60 * time.Second)) + stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -t %s %s", cmd.SudoHandleCmd(), tab, rule) + if err != nil { + return stdout, err + } + return stdout, nil +} +func Run(tab, rule string) error { + if _, err := RunWithStd(tab, rule); err != nil { + return err + } + return nil +} + +func NewChain(tab, chain string) error { + return Run(tab, "-N "+chain) +} + +func ClearChain(tab, chain string) error { + return Run(tab, "-F "+chain) +} + +func AddRule(tab, chain, rule string) error { + if CheckRuleExist(tab, chain, rule) { + return nil + } + return Run(tab, fmt.Sprintf("-A %s %s", chain, rule)) +} +func DeleteRule(tab, chain, rule string) error { + return Run(tab, fmt.Sprintf("-D %s %s", chain, rule)) +} + +func CheckChainExist(tab, chain string) (bool, error) { + stdout, err := RunWithStd(tab, fmt.Sprintf("-S | grep -w 'N %s'", chain)) + if err != nil { + global.LOG.Errorf("check chain %s from tab %s exist failed, err: %v", chain, tab, err) + return false, fmt.Errorf("check chain %s from tab %s exist failed, err: %v", chain, tab, err) + } + if strings.TrimSpace(stdout) == "" { + return false, nil + } + return true, nil +} +func CheckChainBind(tab, parentChain, chain string) (bool, error) { + stdout, err := RunWithStd(tab, fmt.Sprintf("-S %s | grep -- '-j %s'", parentChain, chain)) + if err != nil { + global.LOG.Errorf("check chain %s from tab %s is bind to %s failed, err: %v", chain, tab, parentChain, err) + return false, fmt.Errorf("check chain %s from tab %s is bind to %s failed, err: %v", chain, tab, parentChain, err) + } + if strings.TrimSpace(stdout) == "" { + return false, nil + } + return true, nil +} +func CheckRuleExist(tab, chain, rule string) bool { + _, err := RunWithoutIgnore(tab, fmt.Sprintf("-C %s %s", chain, rule)) + return err == nil +} + +func AddChain(tab, chain string) error { + exists, err := CheckChainExist(tab, chain) + if err != nil { + return fmt.Errorf("check chain %s exist from tab %s failed, err: %w", chain, tab, err) + } + if !exists { + if err := NewChain(tab, chain); err != nil { + return fmt.Errorf("add chain %s for tab %s failed, err: %w", tab, chain, err) + } + } + return nil +} +func BindChain(tab, targetChain, chain string, position int) error { + line, err := FindChainNum(tab, targetChain, chain) + if err != nil { + return fmt.Errorf("find chain %s number from %s failed, err: %w", chain, targetChain, err) + } + if line == 0 { + if err := Run(tab, fmt.Sprintf("-I %s %d -j %s", targetChain, position, chain)); err != nil { + return fmt.Errorf("bind chain %s to %s failed, err: %w", chain, targetChain, err) + } + } + return nil +} +func UnbindChain(tab, targetChain, chain string) error { + line, err := FindChainNum(tab, targetChain, chain) + if err != nil { + return fmt.Errorf("find chain %s number from %s failed, err: %w", chain, targetChain, err) + } + if line != 0 { + return Run(tab, fmt.Sprintf("-D %s %v", targetChain, line)) + } + return nil +} + +func FindChainNum(tab, targetChain, chain string) (int, error) { + stdout, err := RunWithStd(tab, fmt.Sprintf("-L %s --line-numbers -n | grep -w %s", targetChain, chain)) + if err != nil { + return 0, fmt.Errorf("failed to list rules in chain %s: %w", targetChain, err) + } + + lineItem := strings.TrimSpace(stdout) + lines := strings.Split(lineItem, "\n") + for _, line := range lines { + fields := strings.Fields(line) + if len(fields) < 2 { + continue + } + if fields[1] == chain { + itemNum, err := strconv.Atoi(fields[0]) + return itemNum, err + } + } + return 0, nil +} + +func AddChainWithAppend(tab, parentChain, chain string) error { + exists, err := CheckChainExist(tab, chain) + if err != nil { + return fmt.Errorf("failed to check chain %s: %w", chain, err) + } + if !exists { + if err := NewChain(tab, chain); err != nil { + return fmt.Errorf("failed to create chain %s: %w", chain, err) + } + } + isBind, err := CheckChainBind(tab, parentChain, chain) + if err != nil { + return fmt.Errorf("check chain %s bind to %s failed, err: %w", parentChain, chain, err) + } + if !isBind { + if err := AppendChain(tab, parentChain, chain); err != nil { + return fmt.Errorf("failed to append %s to %s: %w", chain, parentChain, err) + } + } + return nil +} +func AppendChain(tab string, parentChain, chain string) error { + return Run(tab, fmt.Sprintf("-A %s -j %s", parentChain, chain)) +} diff --git a/agent/utils/firewall/client/iptables/filter.go b/agent/utils/firewall/client/iptables/filter.go new file mode 100644 index 0000000..4a40bc7 --- /dev/null +++ b/agent/utils/firewall/client/iptables/filter.go @@ -0,0 +1,297 @@ +package iptables + +import ( + "fmt" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type FilterRules struct { + ID uint `json:"id"` + Chain string `json:"chain"` + Protocol string `json:"protocol"` + SrcPort string `json:"srcPort"` + DstPort string `json:"dstPort"` + SrcIP string `json:"srcIP"` + DstIP string `json:"dstIP"` + Strategy string `json:"strategy"` + Description string `json:"description"` +} + +func AddFilterRule(chain string, policy FilterRules) error { + if err := validateRuleSafety(policy, chain); err != nil { + return err + } + iptablesArg := fmt.Sprintf("-A %s", chain) + if policy.Protocol != "" { + iptablesArg += fmt.Sprintf(" -p %s", policy.Protocol) + } + if len(policy.SrcPort) != 0 { + iptablesArg += fmt.Sprintf(" --sport %s", policy.SrcPort) + } + if len(policy.DstPort) != 0 { + iptablesArg += fmt.Sprintf(" --dport %s", policy.DstPort) + } + if policy.SrcIP != "" { + iptablesArg += fmt.Sprintf(" -s %s", policy.SrcIP) + } + if policy.DstIP != "" { + iptablesArg += fmt.Sprintf(" -d %s", policy.DstIP) + } + iptablesArg += fmt.Sprintf(" -j %s", policy.Strategy) + + return Run(FilterTab, iptablesArg) +} + +func DeleteFilterRule(chain string, policy FilterRules) error { + iptablesArg := fmt.Sprintf("-D %s", chain) + if policy.Protocol != "" { + iptablesArg += fmt.Sprintf(" -p %s", policy.Protocol) + } + if len(policy.SrcPort) != 0 { + iptablesArg += fmt.Sprintf(" --sport %s", policy.SrcPort) + } + if len(policy.DstPort) != 0 { + iptablesArg += fmt.Sprintf(" --dport %s", policy.DstPort) + } + if policy.SrcIP != "" { + iptablesArg += fmt.Sprintf(" -s %s", policy.SrcIP) + } + if policy.DstIP != "" { + iptablesArg += fmt.Sprintf(" -d %s", policy.DstIP) + } + iptablesArg += fmt.Sprintf(" -j %s", policy.Strategy) + + return Run(FilterTab, iptablesArg) +} + +func ReadFilterRulesByChain(chain string) ([]FilterRules, error) { + var rules []FilterRules + cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second)) + stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -w -t %s -nL %s", cmd.SudoHandleCmd(), FilterTab, chain) + if err != nil { + return rules, fmt.Errorf("load filter fules by chain %s failed, %v", chain, err) + } + lines := strings.Split(stdout, "\n") + for i := 0; i < len(lines); i++ { + fields := strings.Fields(lines[i]) + if len(fields) < 5 { + continue + } + strategy := strings.ToLower(fields[0]) + if strategy != "accept" && strategy != "drop" && strategy != "reject" { + continue + } + itemRule := FilterRules{ + Chain: chain, + Protocol: loadProtocol(fields[1]), + SrcPort: loadPort("src", fields), + DstPort: loadPort("dst", fields), + SrcIP: loadIP(fields[3]), + DstIP: loadIP(fields[4]), + Strategy: strategy, + } + rules = append(rules, itemRule) + } + return rules, nil +} + +func LoadDefaultStrategy(chain string) (string, error) { + cmdMgr := cmd.NewCommandMgr(cmd.WithIgnoreExist1(), cmd.WithTimeout(20*time.Second)) + stdout, err := cmdMgr.RunWithStdoutBashCf("%s iptables -w -t %s -L %s", cmd.SudoHandleCmd(), FilterTab, chain) + if err != nil { + return "", fmt.Errorf("load filter fules by chain %s failed, %v", chain, err) + } + lines := strings.Split(stdout, "\n") + for i := len(lines) - 1; i > 0; i-- { + fields := strings.Fields(lines[i]) + if len(fields) < 5 { + continue + } + if fields[0] == "DROP" && fields[1] == "all" && fields[3] == ANYWHERE && fields[4] == ANYWHERE { + return DROP, nil + } + } + return ACCEPT, nil +} + +func LoadInitStatus(clientName, tab string) (bool, bool) { + if clientName == "firewalld" { + return true, true + } + if clientName == "ufw" && tab != "forward" { + return true, true + } + switch tab { + case "base": + filterRules, err := RunWithStd(FilterTab, "-S") + if err != nil { + return false, false + } + lines := strings.Split(filterRules, "\n") + initRules := []string{ + "-N " + Chain1PanelBasicBefore, + "-N " + Chain1PanelBasic, + "-N " + Chain1PanelBasicAfter, + fmt.Sprintf("-A %s %s -j ACCEPT", Chain1PanelBasicBefore, strings.ReplaceAll(strings.ReplaceAll(IoRuleIn, "'", "\""), " -j ACCEPT", "")), + fmt.Sprintf("-A %s %s -j ACCEPT", Chain1PanelBasicBefore, strings.ReplaceAll(strings.ReplaceAll(EstablishedRule, "'", "\""), " -j ACCEPT", "")), + fmt.Sprintf("-A %s %s", Chain1PanelBasicAfter, DropAllTcp), + fmt.Sprintf("-A %s %s", Chain1PanelBasicAfter, DropAllUdp), + } + bindRules := []string{ + fmt.Sprintf("-A %s -j %s", ChainInput, Chain1PanelBasicBefore), + fmt.Sprintf("-A %s -j %s", ChainInput, Chain1PanelBasic), + fmt.Sprintf("-A %s -j %s", ChainInput, Chain1PanelBasicAfter), + } + return checkWithInitAndBind(initRules, bindRules, lines) + case "advance": + filterRules, err := RunWithStd(FilterTab, "-S") + if err != nil { + return false, false + } + lines := strings.Split(filterRules, "\n") + initRules := []string{ + "-N " + Chain1PanelInput, + "-N " + Chain1PanelOutput, + } + bindRules := []string{ + fmt.Sprintf("-A %s -j %s", ChainInput, Chain1PanelInput), + fmt.Sprintf("-A %s -j %s", ChainOutput, Chain1PanelOutput), + } + return checkWithInitAndBind(initRules, bindRules, lines) + case "forward": + stdout, err := cmd.RunDefaultWithStdoutBashC("cat /proc/sys/net/ipv4/ip_forward") + if err != nil { + global.LOG.Errorf("check /proc/sys/net/ipv4/ip_forward failed, err: %v", err) + return false, false + } + if strings.TrimSpace(stdout) == "0" { + return false, false + } + natRules, err := RunWithStd(NatTab, "-S") + if err != nil { + return false, false + } + lines := strings.Split(natRules, "\n") + initRules := []string{ + "-N " + Chain1PanelPreRouting, + "-N " + Chain1PanelPostRouting, + } + bindRules := []string{ + fmt.Sprintf("-A PREROUTING -j %s", Chain1PanelPreRouting), + fmt.Sprintf("-A POSTROUTING -j %s", Chain1PanelPostRouting), + } + isNatInit, isNatBind := checkWithInitAndBind(initRules, bindRules, lines) + if !isNatInit { + return false, false + } + filterRules, err := RunWithStd(FilterTab, "-S") + if err != nil { + return false, false + } + filterLines := strings.Split(filterRules, "\n") + filterInitRules := []string{"-N " + Chain1PanelForward} + filterBindRules := []string{fmt.Sprintf("-A FORWARD -j %s", Chain1PanelForward)} + isFilterInit, isFilterBind := checkWithInitAndBind(filterInitRules, filterBindRules, filterLines) + return isNatInit && isFilterInit, isNatBind && isFilterBind + default: + return false, false + } +} + +func checkWithInitAndBind(initRules, bindRules []string, lines []string) (bool, bool) { + for _, rule := range initRules { + found := false + for _, line := range lines { + if strings.TrimSpace(line) == strings.TrimSpace(rule) { + found = true + break + } + } + if !found { + global.LOG.Debugf("not found init rule: %s", rule) + return false, false + } + } + for _, rule := range bindRules { + found := false + for _, line := range lines { + if strings.TrimSpace(line) == strings.TrimSpace(rule) { + found = true + break + } + } + if !found { + global.LOG.Debugf("not found bind rule: %s", rule) + return true, false + } + } + return true, true +} + +func loadPort(position string, portStr []string) string { + if len(portStr) < 7 { + return "" + } + + var portItem string + if strings.Contains(portStr[6], "spt:") && position == "src" { + portItem = strings.ReplaceAll(portStr[6], "spt:", "") + } + if strings.Contains(portStr[6], "dpt:") && position == "dst" { + portItem = strings.ReplaceAll(portStr[6], "dpt:", "") + } + if strings.Contains(portStr[6], "spts:") && position == "src" { + portItem = strings.ReplaceAll(portStr[6], "spts:", "") + } + if strings.Contains(portStr[6], "dpts:") && position == "dst" { + portItem = strings.ReplaceAll(portStr[6], "dpts:", "") + } + portItem = strings.ReplaceAll(portItem, ":", "-") + return portItem +} + +func loadIP(ipStr string) string { + if ipStr == ANYWHERE || ipStr == "0.0.0.0/0" { + return "" + } + return ipStr +} + +func loadProtocol(protocol string) string { + switch protocol { + case "0": + return "all" + case "1": + return "icmp" + case "6": + return "tcp" + case "17": + return "udp" + default: + return protocol + } +} + +func validateRuleSafety(rule FilterRules, chain string) error { + if strings.ToUpper(rule.Strategy) != "DROP" { + return nil + } + + if chain == ChainInput || chain == Chain1PanelInput || chain == Chain1PanelBasic { + if rule.SrcIP == "0.0.0.0/0" && len(rule.SrcPort) == 0 && len(rule.DstPort) == 0 { + return fmt.Errorf("unsafe DROP is not allowed") + } + } + + if chain == ChainOutput || chain == Chain1PanelOutput || chain == Chain1PanelBasicAfter { + if rule.DstIP == "0.0.0.0/0" && len(rule.DstPort) == 0 && len(rule.SrcPort) == 0 { + return fmt.Errorf("unsafe DROP is not allowed") + } + } + + return nil +} diff --git a/agent/utils/firewall/client/iptables/forward.go b/agent/utils/firewall/client/iptables/forward.go new file mode 100644 index 0000000..3492676 --- /dev/null +++ b/agent/utils/firewall/client/iptables/forward.go @@ -0,0 +1,130 @@ +package iptables + +import ( + "fmt" + "strings" +) + +func AddForward(protocol, srcPort, dest, destPort, iface string, save bool) error { + srcPort = strings.ReplaceAll(srcPort, "-", ":") + itemDstPort := strings.ReplaceAll(destPort, "-", ":") + if dest != "" && dest != "127.0.0.1" && dest != "localhost" { + iptablesArg := fmt.Sprintf("-A %s", Chain1PanelPreRouting) + if iface != "" { + iptablesArg += fmt.Sprintf(" -i %s", iface) + } + iptablesArg += fmt.Sprintf(" -p %s --dport %s -j DNAT --to-destination %s:%s", protocol, srcPort, dest, destPort) + if err := Run(NatTab, iptablesArg); err != nil { + return err + } + + if err := Run(NatTab, fmt.Sprintf("-A %s -d %s -p %s --dport %s -j MASQUERADE", Chain1PanelPostRouting, dest, protocol, itemDstPort)); err != nil { + return err + } + + if err := Run(FilterTab, fmt.Sprintf("-A %s -d %s -p %s --dport %s -j ACCEPT", Chain1PanelForward, dest, protocol, itemDstPort)); err != nil { + return err + } + + if err := Run(FilterTab, fmt.Sprintf("-A %s -s %s -p %s --sport %s -j ACCEPT", Chain1PanelForward, dest, protocol, itemDstPort)); err != nil { + return err + } + } else { + iptablesArg := fmt.Sprintf("-A %s", Chain1PanelPreRouting) + if iface != "" { + iptablesArg += fmt.Sprintf(" -i %s", iface) + } + iptablesArg += fmt.Sprintf(" -p %s --dport %s -j REDIRECT --to-port %s", protocol, srcPort, destPort) + if err := Run(NatTab, iptablesArg); err != nil { + return err + } + } + return nil +} + +func DeleteForward(num string, protocol, srcPort, dest, destPort, iface string) error { + itemDstPort := strings.ReplaceAll(destPort, "-", ":") + if err := Run(NatTab, fmt.Sprintf("-D %s %s", Chain1PanelPreRouting, num)); err != nil { + return err + } + + if dest != "" && dest != "127.0.0.1" && dest != "localhost" { + if err := Run(NatTab, fmt.Sprintf("-D %s -d %s -p %s --dport %s -j MASQUERADE", Chain1PanelPostRouting, dest, protocol, itemDstPort)); err != nil { + return err + } + + if err := Run(FilterTab, fmt.Sprintf("-D %s -d %s -p %s --dport %s -j ACCEPT", Chain1PanelForward, dest, protocol, itemDstPort)); err != nil { + return err + } + + if err := Run(FilterTab, fmt.Sprintf("-D %s -s %s -p %s --sport %s -j ACCEPT", Chain1PanelForward, dest, protocol, itemDstPort)); err != nil { + return err + } + } + return nil +} + +func ListForward(chain ...string) ([]IptablesNatInfo, error) { + if len(chain) == 0 { + chain = append(chain, Chain1PanelPreRouting) + } + stdout, err := RunWithStd(NatTab, fmt.Sprintf("-nvL %s --line-numbers", chain[0])) + if err != nil { + return nil, err + } + var forwardList []IptablesNatInfo + lines := strings.Split(stdout, "\n") + for i := 0; i < len(lines); i++ { + fields := strings.Fields(lines[i]) + if len(fields) < 13 { + continue + } + item := IptablesNatInfo{ + Num: fields[0], + Protocol: loadProtocol(fields[4]), + InIface: fields[6], + OutIface: fields[7], + Source: fields[8], + SrcPort: loadNatSrcPort(fields[11]), + } + if len(fields) == 15 && fields[13] == "ports" { + item.DestPort = fields[14] + } + if len(fields) == 13 && strings.HasPrefix(fields[12], "to:") { + parts := strings.Split(fields[12], ":") + if len(parts) > 2 { + item.DestPort = parts[2] + item.Destination = parts[1] + } + } + if len(item.Destination) == 0 { + item.Destination = "127.0.0.1" + } + forwardList = append(forwardList, item) + } + + return forwardList, nil +} + +func loadNatSrcPort(portStr string) string { + var portItem string + if strings.Contains(portStr, "dpt:") { + portItem = strings.ReplaceAll(portStr, "dpt:", "") + } + if strings.Contains(portStr, "dpts:") { + portItem = strings.ReplaceAll(portStr, "dpts:", "") + } + portItem = strings.ReplaceAll(portItem, ":", "-") + return portItem +} + +type IptablesNatInfo struct { + Num string `json:"num"` + Protocol string `json:"protocol"` + InIface string `json:"inIface"` + OutIface string `json:"outIface"` + Source string `json:"source"` + Destination string `json:"destination"` + SrcPort string `json:"srcPort"` + DestPort string `json:"destPort"` +} diff --git a/agent/utils/firewall/client/iptables/persistence.go b/agent/utils/firewall/client/iptables/persistence.go new file mode 100644 index 0000000..41f11ba --- /dev/null +++ b/agent/utils/firewall/client/iptables/persistence.go @@ -0,0 +1,90 @@ +package iptables + +import ( + "bufio" + "fmt" + "os" + "path" + "strings" + + "github.com/1Panel-dev/1Panel/agent/global" +) + +const ( + BasicBeforeFileName = "1panel_basic_before.rules" + BasicFileName = "1panel_basic.rules" + BasicAfterFileName = "1panel_basic_after.rules" + InputFileName = "1panel_input.rules" + OutputFileName = "1panel_out.rules" + ForwardFileName = "1panel_forward.rules" + ForwardFileName1 = "1panel_forward_pre.rules" + ForwardFileName2 = "1panel_forward_post.rules" +) + +func SaveRulesToFile(tab, chain, fileName string) error { + rulesFile := path.Join(global.Dir.FirewallDir, fileName) + + stdout, err := RunWithStd(tab, fmt.Sprintf("-S %s", chain)) + if err != nil { + return fmt.Errorf("failed to list %s rules: %w", chain, err) + } + var rules []string + lines := strings.Split(stdout, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, fmt.Sprintf("-A %s", chain)) { + rules = append(rules, line) + } + } + + file, err := os.Create(rulesFile) + if err != nil { + return fmt.Errorf("failed to create rules file: %w", err) + } + defer file.Close() + + writer := bufio.NewWriter(file) + for _, rule := range rules { + _, err := writer.WriteString(rule + "\n") + if err != nil { + return fmt.Errorf("failed to write rule to file: %w", err) + } + } + + if err := writer.Flush(); err != nil { + return fmt.Errorf("failed to flush rules to file: %w", err) + } + + global.LOG.Infof("persistence rules to %s successful", rulesFile) + return nil +} + +func LoadRulesFromFile(tab, chain, fileName string) error { + if err := AddChain(tab, chain); err != nil { + global.LOG.Errorf("create chain %s failed: %v", chain, err) + return err + } + rulesFile := path.Join(global.Dir.FirewallDir, fileName) + if _, err := os.Stat(rulesFile); os.IsNotExist(err) { + return nil + } + data, err := os.ReadFile(rulesFile) + if err != nil { + global.LOG.Errorf("read rules from file %s failed, err: %v", rulesFile, err) + return err + } + rules := strings.Split(string(data), "\n") + if err := ClearChain(tab, chain); err != nil { + global.LOG.Warnf("clear existing rules from %s failed, err: %v", chain, err) + } + + for _, rule := range rules { + if strings.HasPrefix(rule, fmt.Sprintf("-A %s", chain)) { + if err := Run(tab, rule); err != nil { + global.LOG.Errorf("apply rule '%s' failed, err: %v", rule, err) + } + } + } + + return nil +} diff --git a/agent/utils/firewall/client/ufw.go b/agent/utils/firewall/client/ufw.go new file mode 100644 index 0000000..e7244b7 --- /dev/null +++ b/agent/utils/firewall/client/ufw.go @@ -0,0 +1,279 @@ +package client + +import ( + "fmt" + "strings" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +type Ufw struct { + CmdStr string +} + +func NewUfw() (*Ufw, error) { + var ufw Ufw + ufw.CmdStr = fmt.Sprintf("LANGUAGE=en_US:en %s ufw", cmd.SudoHandleCmd()) + return &ufw, nil +} + +func (f *Ufw) Name() string { + return "ufw" +} + +func (f *Ufw) Status() (bool, error) { + stdout, _ := cmd.RunDefaultWithStdoutBashCf("%s status | grep Status", f.CmdStr) + if stdout == "Status: active\n" { + return true, nil + } + stdout1, _ := cmd.RunDefaultWithStdoutBashCf("%s status | grep 状态", f.CmdStr) + if stdout1 == "状态: 激活\n" { + return true, nil + } + return false, nil +} + +func (f *Ufw) Version() (string, error) { + stdout, err := cmd.RunDefaultWithStdoutBashCf("%s version | grep ufw", f.CmdStr) + if err != nil { + return "", fmt.Errorf("load the firewall status failed, %v", err) + } + info := strings.ReplaceAll(stdout, "\n", "") + return strings.ReplaceAll(info, "ufw ", ""), nil +} + +func (f *Ufw) Start() error { + if err := cmd.RunDefaultBashCf("echo y | %s enable", f.CmdStr); err != nil { + return fmt.Errorf("enable the firewall failed, %v", err) + } + return nil +} + +func (f *Ufw) Stop() error { + if err := cmd.RunDefaultBashCf("%s disable", f.CmdStr); err != nil { + return fmt.Errorf("stop the firewall failed, %v", err) + } + return nil +} + +func (f *Ufw) Restart() error { + if err := f.Stop(); err != nil { + return err + } + if err := f.Start(); err != nil { + return err + } + return nil +} + +func (f *Ufw) Reload() error { + return nil +} + +func (f *Ufw) ListPort() ([]FireInfo, error) { + stdout, err := cmd.RunDefaultWithStdoutBashCf("%s status verbose", f.CmdStr) + if err != nil { + return nil, err + } + portInfos := strings.Split(stdout, "\n") + var datas []FireInfo + isStart := false + for _, line := range portInfos { + if strings.HasPrefix(line, "-") { + isStart = true + continue + } + if !isStart { + continue + } + itemFire := f.loadInfo(line, "port") + if len(itemFire.Port) != 0 && itemFire.Port != "Anywhere" && !strings.Contains(itemFire.Port, ".") { + itemFire.Port = strings.ReplaceAll(itemFire.Port, ":", "-") + datas = append(datas, itemFire) + } + } + return datas, nil +} + +func (f *Ufw) ListAddress() ([]FireInfo, error) { + stdout, err := cmd.RunDefaultWithStdoutBashCf("%s status verbose", f.CmdStr) + if err != nil { + return nil, err + } + portInfos := strings.Split(stdout, "\n") + var datas []FireInfo + isStart := false + for _, line := range portInfos { + if strings.HasPrefix(line, "-") { + isStart = true + continue + } + if !isStart { + continue + } + if !strings.Contains(line, " IN") { + continue + } + itemFire := f.loadInfo(line, "address") + if strings.Contains(itemFire.Port, ".") { + itemFire.Address += ("-" + itemFire.Port) + itemFire.Port = "" + } + if len(itemFire.Port) == 0 && len(itemFire.Address) != 0 { + datas = append(datas, itemFire) + } + } + return datas, nil +} + +func (f *Ufw) Port(port FireInfo, operation string) error { + switch port.Strategy { + case "accept": + port.Strategy = "allow" + case "drop": + port.Strategy = "deny" + default: + return fmt.Errorf("unsupported strategy %s", port.Strategy) + } + if cmd.CheckIllegal(port.Protocol, port.Port) { + return buserr.New("ErrCmdIllegal") + } + + command := fmt.Sprintf("%s %s %s", f.CmdStr, port.Strategy, port.Port) + if operation == "remove" { + command = fmt.Sprintf("%s delete %s %s", f.CmdStr, port.Strategy, port.Port) + } + if len(port.Protocol) != 0 { + command += fmt.Sprintf("/%s", port.Protocol) + } + if err := cmd.RunDefaultBashC(command); err != nil { + return fmt.Errorf("%s (%s) failed, %v", operation, command, err) + } + return nil +} + +func (f *Ufw) RichRules(rule FireInfo, operation string) error { + switch rule.Strategy { + case "accept": + rule.Strategy = "allow" + case "drop": + rule.Strategy = "deny" + default: + return fmt.Errorf("unsupported strategy %s", rule.Strategy) + } + + if cmd.CheckIllegal(operation, rule.Protocol, rule.Address, rule.Port) { + return buserr.New("ErrCmdIllegal") + } + + insertNum := f.loadInsertNum(rule, operation) + ruleStr := fmt.Sprintf("%s insert %d %s ", f.CmdStr, insertNum, rule.Strategy) + if operation == "remove" { + ruleStr = fmt.Sprintf("%s delete %s ", f.CmdStr, rule.Strategy) + } + if len(rule.Protocol) != 0 { + ruleStr += fmt.Sprintf("proto %s ", rule.Protocol) + } + if strings.Contains(rule.Address, "-") { + ruleStr += fmt.Sprintf("from %s to %s ", strings.Split(rule.Address, "-")[0], strings.Split(rule.Address, "-")[1]) + } else { + ruleStr += fmt.Sprintf("from %s ", rule.Address) + } + if len(rule.Port) != 0 { + ruleStr += fmt.Sprintf("to any port %s ", rule.Port) + } + + stdout, err := cmd.RunDefaultWithStdoutBashC(ruleStr) + if err != nil { + if strings.Contains(stdout, "ERROR: Invalid position") || strings.Contains(stdout, "ERROR: 无效位置") { + if err := cmd.RunDefaultBashC(strings.ReplaceAll(ruleStr, "insert 1 ", "")); err != nil { + return fmt.Errorf("%s rich rules (%s), failed, %v", operation, ruleStr, err) + } + return nil + } + return fmt.Errorf("%s rich rules (%s), failed, %v", operation, ruleStr, err) + } + return nil +} + +func (f *Ufw) PortForward(info Forward, operation string) error { + return iptablesPortForward(info, operation) +} + +func (f *Ufw) EnableForward() error { + return EnableIptablesForward() +} + +func (f *Ufw) ListForward() ([]FireInfo, error) { + return iptablesListForward() +} + +func (f *Ufw) loadInfo(line string, fireType string) FireInfo { + fields := strings.Fields(line) + var itemInfo FireInfo + if strings.Contains(line, "LIMIT") || strings.Contains(line, "ALLOW FWD") { + return itemInfo + } + if len(fields) < 4 { + return itemInfo + } + if fields[1] == "(v6)" && fireType == "port" { + return itemInfo + } + if fields[0] == "Anywhere" && fireType != "port" { + itemInfo.Strategy = "drop" + if fields[1] == "ALLOW" { + itemInfo.Strategy = "accept" + } + if fields[1] == "(v6)" { + if fields[2] == "ALLOW" { + itemInfo.Strategy = "accept" + } + itemInfo.Address = fields[4] + } else { + itemInfo.Address = fields[3] + } + return itemInfo + } + if strings.Contains(fields[0], "/") { + itemInfo.Port = strings.Split(fields[0], "/")[0] + itemInfo.Protocol = strings.Split(fields[0], "/")[1] + } else { + itemInfo.Port = fields[0] + itemInfo.Protocol = "tcp/udp" + } + itemInfo.Family = "ipv4" + if fields[1] == "ALLOW" { + itemInfo.Strategy = "accept" + } else { + itemInfo.Strategy = "drop" + } + itemInfo.Address = fields[3] + + return itemInfo +} + +func (f *Ufw) loadInsertNum(rule FireInfo, operation string) int { + if !strings.Contains(rule.Address, ":") || operation == "remove" { + return 1 + } + rules, err := cmd.RunDefaultWithStdoutBashCf("%s status numbered", f.CmdStr) + if err != nil { + global.LOG.Errorf("load ufw rules failed, err: %v", err) + return 1 + } + lines := strings.Split(rules, "\n") + i := 1 + for _, item := range lines { + fields := strings.Fields(item) + if len(fields) < 4 { + continue + } + if !strings.Contains(item, "(v6)") { + i++ + } + } + return i +} diff --git a/agent/utils/geo/geo.go b/agent/utils/geo/geo.go new file mode 100644 index 0000000..7af4edf --- /dev/null +++ b/agent/utils/geo/geo.go @@ -0,0 +1,48 @@ +package geo + +import ( + "net" + "path" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/oschwald/maxminddb-golang" +) + +type Location struct { + En string `maxminddb:"en"` + Zh string `maxminddb:"zh"` +} + +type LocationRes struct { + Iso string `maxminddb:"iso"` + Country Location `maxminddb:"country"` + Latitude float64 `maxminddb:"latitude"` + Longitude float64 `maxminddb:"longitude"` + Province Location `maxminddb:"province"` +} + +func NewGeo() (*maxminddb.Reader, error) { + geoPath := path.Join(global.Dir.DataDir, "geo", "GeoIP.mmdb") + return maxminddb.Open(geoPath) +} + +func GetIPLocation(reader *maxminddb.Reader, ip, lang string) (string, error) { + var err error + var geoLocation LocationRes + if reader == nil { + geoPath := path.Join(global.Dir.DataDir, "geo", "GeoIP.mmdb") + reader, err = maxminddb.Open(geoPath) + if err != nil { + return "", err + } + } + ipNet := net.ParseIP(ip) + err = reader.Lookup(ipNet, &geoLocation) + if err != nil { + return "", err + } + if lang == "zh" { + return geoLocation.Country.Zh + " " + geoLocation.Province.Zh, nil + } + return geoLocation.Country.En + " " + geoLocation.Province.En, nil +} diff --git a/agent/utils/ini_conf/ini.go b/agent/utils/ini_conf/ini.go new file mode 100644 index 0000000..4ea80a2 --- /dev/null +++ b/agent/utils/ini_conf/ini.go @@ -0,0 +1,19 @@ +package ini_conf + +import "gopkg.in/ini.v1" + +func GetIniValue(filePath, Group, Key string) (string, error) { + cfg, err := ini.Load(filePath) + if err != nil { + return "", err + } + service, err := cfg.GetSection(Group) + if err != nil { + return "", err + } + startKey, err := service.GetKey(Key) + if err != nil { + return "", err + } + return startKey.Value(), nil +} diff --git a/agent/utils/mysql/client.go b/agent/utils/mysql/client.go new file mode 100644 index 0000000..e2eb26d --- /dev/null +++ b/agent/utils/mysql/client.go @@ -0,0 +1,82 @@ +package mysql + +import ( + "context" + "database/sql" + "errors" + "fmt" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/mysql/client" +) + +type MysqlClient interface { + Create(info client.CreateInfo) error + CreateUser(info client.CreateInfo, withDeleteDB bool) error + Delete(info client.DeleteInfo) error + + ChangePassword(info client.PasswordChangeInfo) error + ChangeAccess(info client.AccessChangeInfo) error + + Backup(info client.BackupInfo) error + Recover(info client.RecoverInfo) error + + LoadFormatCollation(timeout uint) ([]dto.MysqlFormatCollationOption, error) + SyncDB(version string) ([]client.SyncDBInfo, error) + Close() +} + +func NewMysqlClient(conn client.DBInfo) (MysqlClient, error) { + if conn.From == "local" { + mysqlCli := conn.Type + if mysqlCli == "mysql-cluster" { + mysqlCli = "mysql" + } + connArgs := []string{"exec", conn.Address, mysqlCli, "-u" + conn.Username, "-p" + conn.Password, "-e"} + return client.NewLocal(connArgs, conn.Type, conn.Address, conn.Password, conn.Database), nil + } + + if strings.Contains(conn.Address, ":") { + conn.Address = fmt.Sprintf("[%s]", conn.Address) + } + + tlsItem, err := client.ConnWithSSL(conn.SSL, conn.SkipVerify, conn.ClientKey, conn.ClientCert, conn.RootCert) + if err != nil { + return nil, err + } + connArgs := fmt.Sprintf("%s:%s@tcp(%s:%d)/?charset=utf8%s", conn.Username, conn.Password, conn.Address, conn.Port, tlsItem) + db, err := sql.Open("mysql", connArgs) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second) + defer cancel() + if err := db.PingContext(ctx); err != nil { + global.LOG.Errorf("test mysql conn failed, err: %v", err) + return nil, err + } + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return nil, buserr.New("ErrExecTimeOut") + } + + return client.NewRemote(client.Remote{ + Type: conn.Type, + Client: db, + Database: conn.Database, + User: conn.Username, + Password: conn.Password, + Address: conn.Address, + Port: conn.Port, + + SSL: conn.SSL, + RootCert: conn.RootCert, + ClientKey: conn.ClientKey, + ClientCert: conn.ClientCert, + SkipVerify: conn.SkipVerify, + }), nil +} diff --git a/agent/utils/mysql/client/info.go b/agent/utils/mysql/client/info.go new file mode 100644 index 0000000..bc05007 --- /dev/null +++ b/agent/utils/mysql/client/info.go @@ -0,0 +1,139 @@ +package client + +import ( + "crypto/tls" + "crypto/x509" + "database/sql" + "errors" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/go-sql-driver/mysql" +) + +type DBInfo struct { + Type string `json:"type"` + From string `json:"from"` + Database string `json:"database"` + Address string `json:"address"` + Port uint `json:"port"` + Username string `json:"userName"` + Password string `json:"password"` + + SSL bool `json:"ssl"` + RootCert string `json:"rootCert"` + ClientKey string `json:"clientKey"` + ClientCert string `json:"clientCert"` + SkipVerify bool `json:"skipVerify"` + + Timeout uint `json:"timeout"` // second +} + +type CreateInfo struct { + Name string `json:"name"` + Format string `json:"format"` + Collation string `json:"collation"` + Version string `json:"version"` + Username string `json:"userName"` + Password string `json:"password"` + Permission string `json:"permission"` + + Timeout uint `json:"timeout"` // second +} + +type DeleteInfo struct { + Name string `json:"name"` + Version string `json:"version"` + Username string `json:"userName"` + Permission string `json:"permission"` + + ForceDelete bool `json:"forceDelete"` + Timeout uint `json:"timeout"` // second +} + +type PasswordChangeInfo struct { + Name string `json:"name"` + Version string `json:"version"` + Username string `json:"userName"` + Password string `json:"password"` + Permission string `json:"permission"` + + Timeout uint `json:"timeout"` // second +} + +type AccessChangeInfo struct { + Name string `json:"name"` + Version string `json:"version"` + Username string `json:"userName"` + Password string `json:"password"` + OldPermission string `json:"oldPermission"` + Permission string `json:"permission"` + + Timeout uint `json:"timeout"` // second +} + +type BackupInfo struct { + Name string `json:"name"` + Type string `json:"type"` + Version string `json:"version"` + Format string `json:"format"` + TargetDir string `json:"targetDir"` + FileName string `json:"fileName"` + Args []string `json:"args"` + + Timeout uint `json:"timeout"` // second +} + +type FormatCollation struct { + Format sql.NullString `json:"format" gorm:"column:CHARACTER_SET_NAME"` + Collation sql.NullString `json:"collation" gorm:"column:COLLATION_NAME"` +} + +type RecoverInfo struct { + Name string `json:"name"` + Type string `json:"type"` + Version string `json:"version"` + Format string `json:"format"` + SourceFile string `json:"sourceFile"` + + Timeout uint `json:"timeout"` // second +} + +type SyncDBInfo struct { + Name string `json:"name"` + From string `json:"from"` + MysqlName string `json:"mysqlName"` + Format string `json:"format"` + Collation string `json:"collation"` + Username string `json:"username"` + Password string `json:"password"` + Permission string `json:"permission"` +} + +func ConnWithSSL(ssl, skipVerify bool, clientKey, clientCert, rootCert string) (string, error) { + if !ssl { + return "", nil + } + tlsConfig := &tls.Config{ + InsecureSkipVerify: skipVerify, + } + if len(rootCert) != 0 { + pool := x509.NewCertPool() + if ok := pool.AppendCertsFromPEM([]byte(rootCert)); !ok { + global.LOG.Error("append certs from pem failed") + return "", errors.New("unable to append root cert to pool") + } + tlsConfig.RootCAs = pool + } + if len(clientCert) != 0 && len(clientKey) != 0 { + cert, err := tls.X509KeyPair([]byte(clientCert), []byte(clientKey)) + if err != nil { + return "", err + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + if err := mysql.RegisterTLSConfig("cloudsql", tlsConfig); err != nil { + global.LOG.Errorf("register tls config failed, err: %v", err) + return "", err + } + return "&tls=cloudsql", nil +} diff --git a/agent/utils/mysql/client/local.go b/agent/utils/mysql/client/local.go new file mode 100644 index 0000000..b3d4cff --- /dev/null +++ b/agent/utils/mysql/client/local.go @@ -0,0 +1,436 @@ +package client + +import ( + "bytes" + "compress/gzip" + "context" + "errors" + "fmt" + "os" + "os/exec" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +type Local struct { + Type string + PrefixCommand []string + Database string + Password string + ContainerName string +} + +func NewLocal(command []string, dbType, containerName, password, database string) *Local { + return &Local{Type: dbType, PrefixCommand: command, ContainerName: containerName, Password: password, Database: database} +} + +func (r *Local) Create(info CreateInfo) error { + createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", info.Name, info.Format, info.Collation) + if len(info.Collation) == 0 { + createSql = fmt.Sprintf("create database `%s` default character set %s", info.Name, info.Format) + } + if err := r.ExecSQL(createSql, info.Timeout); err != nil { + if strings.Contains(strings.ToLower(err.Error()), "error 1007") { + return buserr.New("ErrDatabaseIsExist") + } + return err + } + + if err := r.CreateUser(info, true); err != nil { + _ = r.ExecSQL(fmt.Sprintf("drop database if exists `%s`", info.Name), info.Timeout) + return err + } + + return nil +} + +func (r *Local) CreateUser(info CreateInfo, withDeleteDB bool) error { + var userlist []string + if strings.Contains(info.Permission, ",") { + ips := strings.Split(info.Permission, ",") + for _, ip := range ips { + if len(ip) != 0 { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) + } + } + } else { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) + } + + for _, user := range userlist { + if err := r.ExecSQL(fmt.Sprintf("create user %s identified by '%s';", user, info.Password), info.Timeout); err != nil { + if strings.Contains(strings.ToLower(err.Error()), "error 1396") { + return buserr.New("ErrUserIsExist") + } + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Version: info.Version, + Username: info.Username, + Permission: info.Permission, + ForceDelete: true, + Timeout: 300}) + } + return err + } + grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", info.Name, user) + if info.Name == "*" { + grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user) + } + if strings.HasPrefix(info.Version, "5.7") || strings.HasPrefix(info.Version, "5.6") { + grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, info.Password) + } else { + grantStr = grantStr + " with grant option;" + } + if err := r.ExecSQL(grantStr, info.Timeout); err != nil { + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Version: info.Version, + Username: info.Username, + Permission: info.Permission, + ForceDelete: true, + Timeout: 300}) + } + return err + } + } + return nil +} + +func (r *Local) Delete(info DeleteInfo) error { + var userlist []string + if strings.Contains(info.Permission, ",") { + ips := strings.Split(info.Permission, ",") + for _, ip := range ips { + if len(ip) != 0 { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) + } + } + } else { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) + } + + for _, user := range userlist { + if strings.HasPrefix(info.Version, "5.6") { + if err := r.ExecSQL(fmt.Sprintf("drop user %s", user), info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop user failed, err: %v", err) + } + } else { + if err := r.ExecSQL(fmt.Sprintf("drop user if exists %s", user), info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop user failed, err: %v", err) + } + } + } + if len(info.Name) != 0 { + if err := r.ExecSQL(fmt.Sprintf("drop database if exists `%s`", info.Name), info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop database failed, err: %v", err) + } + } + if !info.ForceDelete { + global.LOG.Info("execute delete database sql successful, now start to drop uploads and records") + } + + return nil +} + +func (r *Local) ChangePassword(info PasswordChangeInfo) error { + if info.Username != "root" { + var userlist []string + if strings.Contains(info.Permission, ",") { + ips := strings.Split(info.Permission, ",") + for _, ip := range ips { + if len(ip) != 0 { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) + } + } + } else { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) + } + + for _, user := range userlist { + passwordChangeSql := fmt.Sprintf("set password for %s = password('%s')", user, info.Password) + if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") { + passwordChangeSql = fmt.Sprintf("ALTER USER %s IDENTIFIED BY '%s';", user, info.Password) + } + if err := r.ExecSQL(passwordChangeSql, info.Timeout); err != nil { + return err + } + } + return nil + } + + hosts, err := r.ExecSQLForRows("select host from mysql.user where user='root';", info.Timeout) + if err != nil { + return err + } + for _, host := range hosts { + if host == "%" || host == "localhost" { + passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Password) + if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") { + passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified by '%s';", host, info.Password) + } + if err := r.ExecSQL(passwordRootChangeCMD, info.Timeout); err != nil { + return err + } + } + } + + return nil +} + +func (r *Local) ChangeAccess(info AccessChangeInfo) error { + if info.Username == "root" { + info.OldPermission = "%" + info.Name = "*" + info.Password = r.Password + } + if info.Permission != info.OldPermission { + if err := r.Delete(DeleteInfo{ + Version: info.Version, + Username: info.Username, + Permission: info.OldPermission, + ForceDelete: true, + Timeout: 300}); err != nil { + return err + } + if info.Username == "root" { + return nil + } + } + if err := r.CreateUser(CreateInfo{ + Name: info.Name, + Version: info.Version, + Username: info.Username, + Password: info.Password, + Permission: info.Permission, + Timeout: info.Timeout, + }, false); err != nil { + return err + } + if err := r.ExecSQL("flush privileges", 300); err != nil { + return err + } + return nil +} + +func (r *Local) Backup(info BackupInfo) error { + fileOp := files.NewFileOp() + if !fileOp.Stat(info.TargetDir) { + if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err) + } + } + outfile, err := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, constant.DirPerm) + if err != nil { + return fmt.Errorf("open file %s failed, err: %v", path.Join(info.TargetDir, info.FileName), err) + } + defer outfile.Close() + dumpCmd := "mysqldump" + if r.Type == constant.AppMariaDB { + dumpCmd = "mariadb-dump" + } + global.LOG.Infof("start to %s | gzip > %s.gzip, args: %v", dumpCmd, info.TargetDir+"/"+info.FileName, info.Args) + + info.Args = append(info.Args, "--routines") + itemArgs := common.RemoveRepeatStr(info.Args) + args := []string{"exec", r.ContainerName, dumpCmd, "-uroot", "-p" + r.Password, "--default-character-set=" + info.Format} + for _, arg := range itemArgs { + if len(arg) == 0 { + continue + } + args = append(args, arg) + } + args = append(args, info.Name) + cmd := exec.Command("docker", args...) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + gzipCmd := exec.Command("gzip", "-cf") + gzipCmd.Stdin, _ = cmd.StdoutPipe() + gzipCmd.Stdout = outfile + _ = gzipCmd.Start() + + if err := cmd.Run(); err != nil { + return fmt.Errorf("handle backup database failed, err: %v", stderr.String()) + } + _ = gzipCmd.Wait() + return nil +} + +func (r *Local) Recover(info RecoverInfo) error { + fi, _ := os.Open(info.SourceFile) + defer fi.Close() + mysqlCli := r.Type + if mysqlCli == "mysql-cluster" { + mysqlCli = "mysql" + } + + cmd := exec.Command("docker", "exec", "-i", r.ContainerName, mysqlCli, "-uroot", "-p"+r.Password, "--default-character-set="+info.Format, info.Name) + if strings.HasSuffix(info.SourceFile, ".gz") { + gzipFile, err := os.Open(info.SourceFile) + if err != nil { + return err + } + defer gzipFile.Close() + gzipReader, err := gzip.NewReader(gzipFile) + if err != nil { + return err + } + defer gzipReader.Close() + cmd.Stdin = gzipReader + } else { + cmd.Stdin = fi + } + stdout, err := cmd.CombinedOutput() + stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "") + if err != nil || strings.HasPrefix(stdStr, "ERROR ") { + return errors.New(stdStr) + } + + return nil +} + +func (r *Local) SyncDB(version string) ([]SyncDBInfo, error) { + var datas []SyncDBInfo + lines, err := r.ExecSQLForRows("SELECT SCHEMA_NAME, DEFAULT_CHARACTER_SET_NAME, DEFAULT_COLLATION_NAME FROM information_schema.SCHEMATA", 300) + if err != nil { + return datas, err + } + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) != 3 { + continue + } + if parts[0] == "SCHEMA_NAME" || parts[0] == "information_schema" || parts[0] == "mysql" || parts[0] == "performance_schema" || parts[0] == "sys" || parts[0] == "__recycle_bin__" || parts[0] == "recycle_bin" { + continue + } + dataItem := SyncDBInfo{ + Name: parts[0], + From: "local", + MysqlName: r.Database, + Format: parts[1], + Collation: parts[2], + } + userLines, err := r.ExecSQLForRows(fmt.Sprintf("select user,host from mysql.db where db = '%s'", parts[0]), 300) + if err != nil { + global.LOG.Debugf("sync user of db %s failed, err: %v", parts[0], err) + dataItem.Permission = "%" + datas = append(datas, dataItem) + continue + } + + var permissionItem []string + isLocal := true + i := 0 + for _, userline := range userLines { + userparts := strings.Fields(userline) + if len(userparts) != 2 { + continue + } + if userparts[0] == "root" { + continue + } + if i == 0 { + dataItem.Username = userparts[0] + } + dataItem.Username = userparts[0] + if dataItem.Username == userparts[0] && userparts[1] == "%" { + isLocal = false + dataItem.Permission = "%" + } else if dataItem.Username == userparts[0] && userparts[1] != "localhost" { + isLocal = false + permissionItem = append(permissionItem, userparts[1]) + } + } + if len(dataItem.Username) == 0 { + dataItem.Permission = "%" + } else { + if isLocal { + dataItem.Permission = "localhost" + } + if len(dataItem.Permission) == 0 { + dataItem.Permission = strings.Join(permissionItem, ",") + } + } + datas = append(datas, dataItem) + } + return datas, nil +} + +func (r *Local) Close() {} + +func (r *Local) ExecSQL(command string, timeout uint) error { + itemCommand := r.PrefixCommand[:] + itemCommand = append(itemCommand, command) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, "docker", itemCommand...) + stdout, err := cmd.CombinedOutput() + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return buserr.New("ErrExecTimeOut") + } + stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "") + if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") { + return errors.New(stdStr) + } + return nil +} + +func (r *Local) ExecSQLForRows(command string, timeout uint) ([]string, error) { + itemCommand := r.PrefixCommand[:] + itemCommand = append(itemCommand, command) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, "docker", itemCommand...) + stdout, err := cmd.CombinedOutput() + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return nil, buserr.New("ErrExecTimeOut") + } + stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "") + if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") { + return nil, errors.New(stdStr) + } + return strings.Split(stdStr, "\n"), nil +} + +func (r *Local) LoadFormatCollation(timeout uint) ([]dto.MysqlFormatCollationOption, error) { + std, err := r.ExecSQLForRows("SELECT CHARACTER_SET_NAME, COLLATION_NAME FROM INFORMATION_SCHEMA.COLLATIONS ORDER BY CHARACTER_SET_NAME, COLLATION_NAME;", timeout) + if err != nil { + return nil, err + } + formatMap := make(map[string][]string) + for _, item := range std { + if strings.ToLower(item) == "character_set_name\tcollation_name" { + continue + } + parts := strings.Split(item, "\t") + if len(parts) != 2 { + continue + } + if parts[0] == "NULL" { + continue + } + if _, ok := formatMap[parts[0]]; !ok { + formatMap[parts[0]] = []string{parts[1]} + } else { + formatMap[parts[0]] = append(formatMap[parts[0]], parts[1]) + } + } + options := []dto.MysqlFormatCollationOption{} + for key, val := range formatMap { + options = append(options, dto.MysqlFormatCollationOption{ + Format: key, + Collations: val, + }) + } + return options, nil +} diff --git a/agent/utils/mysql/client/remote.go b/agent/utils/mysql/client/remote.go new file mode 100644 index 0000000..113de45 --- /dev/null +++ b/agent/utils/mysql/client/remote.go @@ -0,0 +1,527 @@ +package client + +import ( + "bytes" + "compress/gzip" + "context" + "database/sql" + "errors" + "fmt" + "os" + "os/exec" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/docker/docker/api/types/image" + "github.com/docker/docker/client" +) + +type Remote struct { + Type string + Client *sql.DB + Database string + User string + Password string + Address string + Port uint + + SSL bool + RootCert string + ClientKey string + ClientCert string + SkipVerify bool +} + +func NewRemote(db Remote) *Remote { + return &db +} + +func (r *Remote) Create(info CreateInfo) error { + createSql := fmt.Sprintf("create database `%s` default character set %s collate %s", info.Name, info.Format, info.Collation) + if len(info.Collation) == 0 { + createSql = fmt.Sprintf("create database `%s` default character set %s", info.Name, info.Format) + } + if err := r.ExecSQL(createSql, info.Timeout); err != nil { + if strings.Contains(strings.ToLower(err.Error()), "error 1007") { + return buserr.New("ErrDatabaseIsExist") + } + return err + } + + if err := r.CreateUser(info, true); err != nil { + _ = r.ExecSQL(fmt.Sprintf("drop database if exists `%s`", info.Name), info.Timeout) + return err + } + + return nil +} + +func (r *Remote) CreateUser(info CreateInfo, withDeleteDB bool) error { + var userlist []string + if strings.Contains(info.Permission, ",") { + ips := strings.Split(info.Permission, ",") + for _, ip := range ips { + if len(ip) != 0 { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) + } + } + } else { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) + } + + for _, user := range userlist { + if err := r.ExecSQL(fmt.Sprintf("create user %s identified by '%s';", user, info.Password), info.Timeout); err != nil { + if strings.Contains(strings.ToLower(err.Error()), "error 1396") { + return buserr.New("ErrUserIsExist") + } + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Version: info.Version, + Username: info.Username, + Permission: info.Permission, + ForceDelete: true, + Timeout: 300}) + } + return err + } + grantStr := fmt.Sprintf("grant all privileges on `%s`.* to %s", info.Name, user) + if info.Name == "*" { + grantStr = fmt.Sprintf("grant all privileges on *.* to %s", user) + } + if strings.HasPrefix(info.Version, "5.7") || strings.HasPrefix(info.Version, "5.6") { + grantStr = fmt.Sprintf("%s identified by '%s' with grant option;", grantStr, info.Password) + } else { + grantStr = grantStr + " with grant option;" + } + if err := r.ExecSQL(grantStr, info.Timeout); err != nil { + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Version: info.Version, + Username: info.Username, + Permission: info.Permission, + ForceDelete: true, + Timeout: 300}) + } + return err + } + } + return nil +} + +func (r *Remote) Delete(info DeleteInfo) error { + var userlist []string + if strings.Contains(info.Permission, ",") { + ips := strings.Split(info.Permission, ",") + for _, ip := range ips { + if len(ip) != 0 { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) + } + } + } else { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) + } + + for _, user := range userlist { + if strings.HasPrefix(info.Version, "5.6") { + if err := r.ExecSQL(fmt.Sprintf("drop user %s", user), info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop user failed, err: %v", err) + } + } else { + if err := r.ExecSQL(fmt.Sprintf("drop user if exists %s", user), info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop user failed, err: %v", err) + } + } + } + if len(info.Name) != 0 { + if err := r.ExecSQL(fmt.Sprintf("drop database if exists `%s`", info.Name), info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop database failed, err: %v", err) + } + } + if !info.ForceDelete { + global.LOG.Info("execute delete database sql successful, now start to drop uploads and records") + } + + return nil +} + +func (r *Remote) ChangePassword(info PasswordChangeInfo) error { + if info.Username != "root" { + var userlist []string + if strings.Contains(info.Permission, ",") { + ips := strings.Split(info.Permission, ",") + for _, ip := range ips { + if len(ip) != 0 { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, ip)) + } + } + } else { + userlist = append(userlist, fmt.Sprintf("'%s'@'%s'", info.Username, info.Permission)) + } + + for _, user := range userlist { + passwordChangeSql := fmt.Sprintf("set password for %s = password('%s')", user, info.Password) + if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") { + passwordChangeSql = fmt.Sprintf("ALTER USER %s IDENTIFIED BY '%s';", user, info.Password) + } + if err := r.ExecSQL(passwordChangeSql, info.Timeout); err != nil { + return err + } + } + return nil + } + + hosts, err := r.ExecSQLForHosts(info.Timeout) + if err != nil { + return err + } + for _, host := range hosts { + if host == "%" || host == "localhost" { + passwordRootChangeCMD := fmt.Sprintf("set password for 'root'@'%s' = password('%s')", host, info.Password) + if !strings.HasPrefix(info.Version, "5.7") && !strings.HasPrefix(info.Version, "5.6") { + passwordRootChangeCMD = fmt.Sprintf("alter user 'root'@'%s' identified by '%s';", host, info.Password) + } + if err := r.ExecSQL(passwordRootChangeCMD, info.Timeout); err != nil { + return err + } + } + } + + return nil +} + +func (r *Remote) ChangeAccess(info AccessChangeInfo) error { + if info.Username == "root" { + info.OldPermission = "%" + info.Name = "*" + info.Password = r.Password + } + if info.Permission != info.OldPermission { + if err := r.Delete(DeleteInfo{ + Version: info.Version, + Username: info.Username, + Permission: info.OldPermission, + ForceDelete: true, + Timeout: 300}); err != nil { + return err + } + if info.Username == "root" { + return nil + } + } + if err := r.CreateUser(CreateInfo{ + Name: info.Name, + Version: info.Version, + Username: info.Username, + Password: info.Password, + Permission: info.Permission, + Timeout: info.Timeout, + }, false); err != nil { + return err + } + if err := r.ExecSQL("flush privileges", 300); err != nil { + return err + } + return nil +} + +func (r *Remote) Backup(info BackupInfo) error { + fileOp := files.NewFileOp() + if !fileOp.Stat(info.TargetDir) { + if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err) + } + } + outfile, err := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, constant.DirPerm) + if err != nil { + return fmt.Errorf("open file %s failed, err: %v", path.Join(info.TargetDir, info.FileName), err) + } + defer outfile.Close() + dumpCmd := "mysqldump" + if r.Type == constant.AppMariaDB { + dumpCmd = "mariadb-dump" + } + global.LOG.Infof("start to %s | gzip > %s.gzip", dumpCmd, info.TargetDir+"/"+info.FileName) + image, err := loadImage(info.Type, info.Version) + if err != nil { + return err + } + info.Args = append(info.Args, "--routines") + itemArgs := common.RemoveRepeatStr(info.Args) + var args []string + for _, arg := range itemArgs { + if len(arg) == 0 { + continue + } + args = append(args, arg) + } + + backupCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c '%s %s -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'", + image, dumpCmd, strings.Join(args, " "), r.Address, r.Port, r.User, r.Password, sslSkip(info.Version, r.Type), info.Format, info.Name) + + global.LOG.Debug(strings.ReplaceAll(backupCmd, r.Password, "******")) + cmd := exec.Command("bash", "-c", backupCmd) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + gzipCmd := exec.Command("gzip", "-cf") + gzipCmd.Stdin, _ = cmd.StdoutPipe() + gzipCmd.Stdout = outfile + + _ = gzipCmd.Start() + if err := cmd.Run(); err != nil { + return fmt.Errorf("handle backup database failed, err: %v", stderr.String()) + } + _ = gzipCmd.Wait() + return nil +} + +func (r *Remote) Recover(info RecoverInfo) error { + fi, _ := os.Open(info.SourceFile) + defer fi.Close() + + image, err := loadImage(info.Type, info.Version) + if err != nil { + return err + } + + recoverCmd := fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c '%s -h %s -P %d -u%s -p%s %s --default-character-set=%s %s'", + image, r.Type, r.Address, r.Port, r.User, r.Password, sslSkip(info.Version, r.Type), info.Format, info.Name) + + global.LOG.Debug(strings.ReplaceAll(recoverCmd, r.Password, "******")) + cmd := exec.Command("bash", "-c", recoverCmd) + + if strings.HasSuffix(info.SourceFile, ".gz") { + gzipFile, err := os.Open(info.SourceFile) + if err != nil { + return err + } + defer gzipFile.Close() + gzipReader, err := gzip.NewReader(gzipFile) + if err != nil { + return err + } + defer gzipReader.Close() + cmd.Stdin = gzipReader + } else { + cmd.Stdin = fi + } + stdout, err := cmd.CombinedOutput() + stdStr := strings.ReplaceAll(string(stdout), "mysql: [Warning] Using a password on the command line interface can be insecure.\n", "") + if err != nil || strings.HasPrefix(string(stdStr), "ERROR ") { + return errors.New(stdStr) + } + + return nil +} + +func (r *Remote) SyncDB(version string) ([]SyncDBInfo, error) { + var datas []SyncDBInfo + rows, err := r.Client.Query("select schema_name, default_character_set_name, default_collation_name from information_schema.SCHEMATA") + if err != nil { + return datas, err + } + defer rows.Close() + + for rows.Next() { + var dbName, charsetName, collation string + if err = rows.Scan(&dbName, &charsetName, &collation); err != nil { + return datas, err + } + if dbName == "information_schema" || dbName == "mysql" || dbName == "performance_schema" || dbName == "sys" || dbName == "__recycle_bin__" || dbName == "recycle_bin" { + continue + } + dataItem := SyncDBInfo{ + Name: dbName, + From: "remote", + MysqlName: r.Database, + Format: charsetName, + Collation: collation, + } + userRows, err := r.Client.Query("select user,host from mysql.db where db = ?", dbName) + if err != nil { + global.LOG.Debugf("sync user of db %s failed, err: %v", dbName, err) + dataItem.Permission = "%" + datas = append(datas, dataItem) + continue + } + + var permissionItem []string + isLocal := true + i := 0 + for userRows.Next() { + var user, host string + if err = userRows.Scan(&user, &host); err != nil { + return datas, err + } + if user == "root" { + continue + } + if i == 0 { + dataItem.Username = user + } + if dataItem.Username == user && host == "%" { + isLocal = false + dataItem.Permission = "%" + } else if dataItem.Username == user && host != "localhost" { + isLocal = false + permissionItem = append(permissionItem, host) + } + i++ + } + if len(dataItem.Username) == 0 { + dataItem.Permission = "%" + } else { + if isLocal { + dataItem.Permission = "localhost" + } + if len(dataItem.Permission) == 0 { + dataItem.Permission = strings.Join(permissionItem, ",") + } + } + datas = append(datas, dataItem) + } + if err = rows.Err(); err != nil { + return datas, err + } + return datas, nil +} + +func (r *Remote) Close() { + _ = r.Client.Close() +} + +func (r *Remote) ExecSQL(command string, timeout uint) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + if _, err := r.Client.ExecContext(ctx, command); err != nil { + return err + } + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return buserr.New("ErrExecTimeOut") + } + + return nil +} + +func (r *Remote) LoadFormatCollation(timeout uint) ([]dto.MysqlFormatCollationOption, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + rows, err := r.Client.QueryContext(ctx, "SELECT CHARACTER_SET_NAME, COLLATION_NAME FROM INFORMATION_SCHEMA.COLLATIONS ORDER BY CHARACTER_SET_NAME, COLLATION_NAME;") + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return nil, buserr.New("ErrExecTimeOut") + } + if err != nil { + return nil, err + } + defer rows.Close() + + formatMap := make(map[string][]string) + for rows.Next() { + var item FormatCollation + if err := rows.Scan(&item.Format, &item.Collation); err != nil { + return nil, err + } + if !item.Format.Valid { + continue + } + if _, ok := formatMap[item.Format.String]; !ok { + formatMap[item.Format.String] = []string{item.Collation.String} + } else { + formatMap[item.Format.String] = append(formatMap[item.Format.String], item.Collation.String) + } + } + options := []dto.MysqlFormatCollationOption{} + for key, val := range formatMap { + options = append(options, dto.MysqlFormatCollationOption{ + Format: key, + Collations: val, + }) + } + + return options, nil +} + +func (r *Remote) ExecSQLForHosts(timeout uint) ([]string, error) { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + results, err := r.Client.QueryContext(ctx, "select host from mysql.user where user='root';") + if err != nil { + return nil, err + } + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return nil, buserr.New("ErrExecTimeOut") + } + var rows []string + for results.Next() { + var host string + if err := results.Scan(&host); err != nil { + continue + } + rows = append(rows, host) + } + return rows, nil +} + +func loadImage(dbType, version string) (string, error) { + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) + if err != nil { + return "", err + } + images, err := cli.ImageList(context.Background(), image.ListOptions{}) + if err != nil { + return "", err + } + + for _, image := range images { + for _, tag := range image.RepoTags { + if !strings.HasPrefix(tag, dbType+":") { + continue + } + if dbType == "mariadb" && strings.HasPrefix(tag, "mariadb:") { + return tag, nil + } + if strings.HasPrefix(version, "5.6") && strings.HasPrefix(tag, "mysql:5.6") { + return tag, nil + } + if strings.HasPrefix(version, "5.7") && strings.HasPrefix(tag, "mysql:5.7") { + return tag, nil + } + if strings.HasPrefix(version, "8.") && strings.HasPrefix(tag, "mysql:8.") { + return tag, nil + } + } + } + return loadVersion(dbType, version), nil +} + +func loadVersion(dbType string, version string) string { + if dbType == "mariadb" { + return "mariadb:11.3.2 " + } + if strings.HasPrefix(version, "5.6") { + return "mysql:5.6.51" + } + if strings.HasPrefix(version, "5.7") { + return "mysql:5.7.44" + } + return "mysql:8.2.0" +} + +func sslSkip(version, dbType string) string { + if dbType == constant.AppMariaDB || strings.HasPrefix(version, "5.6") || strings.HasPrefix(version, "5.7") { + return "--skip-ssl" + } + return "--ssl-mode=DISABLED" +} diff --git a/agent/utils/nginx/components/block.go b/agent/utils/nginx/components/block.go new file mode 100644 index 0000000..d0f40b6 --- /dev/null +++ b/agent/utils/nginx/components/block.go @@ -0,0 +1,92 @@ +package components + +type Block struct { + Line int + Comment string + Directives []IDirective + IsLuaBlock bool + LiteralCode string +} + +func (b *Block) GetDirectives() []IDirective { + return b.Directives +} + +func (b *Block) GetComment() string { + return b.Comment +} + +func (b *Block) GetLine() int { + return b.Line +} + +func (b *Block) GetCodeBlock() string { + return b.LiteralCode +} + +func (b *Block) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range b.GetDirectives() { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + + return directives +} + +func (b *Block) AppendDirectives(directives ...IDirective) { + b.Directives = append(b.Directives, directives...) +} + +func (b *Block) UpdateDirective(key string, params []string) { + if key == "" || len(params) == 0 { + return + } + directives := b.GetDirectives() + index := -1 + for i, dir := range directives { + if dir.GetName() == key { + if IsRepeatKey(key) { + oldParams := dir.GetParameters() + if !(len(oldParams) > 0 && oldParams[0] == params[0]) { + continue + } + } + index = i + break + } + } + newDirective := &Directive{ + Name: key, + Parameters: params, + } + if index > -1 { + directives[index] = newDirective + } else { + directives = append(directives, newDirective) + } + b.Directives = directives +} + +func (b *Block) RemoveDirective(key string, params []string) { + directives := b.GetDirectives() + var newDirectives []IDirective + for _, dir := range directives { + if dir.GetName() == key { + if IsRepeatKey(key) && len(params) > 0 { + oldParams := dir.GetParameters() + if oldParams[0] == params[0] { + continue + } + } else { + continue + } + } + newDirectives = append(newDirectives, dir) + } + b.Directives = newDirectives +} diff --git a/agent/utils/nginx/components/comment.go b/agent/utils/nginx/components/comment.go new file mode 100644 index 0000000..31459be --- /dev/null +++ b/agent/utils/nginx/components/comment.go @@ -0,0 +1,26 @@ +package components + +type Comment struct { + Detail string + Line int +} + +func (c *Comment) GetName() string { + return "" +} + +func (c *Comment) GetParameters() []string { + return []string{} +} + +func (c *Comment) GetBlock() IBlock { + return nil +} + +func (c *Comment) GetComment() string { + return c.Detail +} + +func (c *Comment) GetLine() int { + return c.Line +} diff --git a/agent/utils/nginx/components/config.go b/agent/utils/nginx/components/config.go new file mode 100644 index 0000000..a2da80b --- /dev/null +++ b/agent/utils/nginx/components/config.go @@ -0,0 +1,56 @@ +package components + +type Config struct { + *Block + FilePath string +} + +func (c *Config) FindServers() []*Server { + var servers []*Server + directives := c.Block.FindDirectives("server") + for _, directive := range directives { + servers = append(servers, directive.(*Server)) + } + return servers +} + +func (c *Config) FindHttp() *Http { + var http *Http + directives := c.Block.FindDirectives("http") + if len(directives) > 0 { + http = directives[0].(*Http) + } + + return http +} + +func (c *Config) FindUpstreams() []*Upstream { + var upstreams []*Upstream + directives := c.Block.FindDirectives("upstream") + for _, directive := range directives { + upstreams = append(upstreams, directive.(*Upstream)) + } + return upstreams +} + +var repeatKeys = map[string]struct { +}{ + "limit_conn": {}, + "limit_conn_zone": {}, + "set": {}, + "if": {}, + "proxy_set_header": {}, + "location": {}, + "include": {}, + "sub_filter": {}, + "add_header": {}, + "set_real_ip_from": {}, + "error_page": {}, +} + +func IsRepeatKey(key string) bool { + if _, ok := repeatKeys[key]; ok { + return true + } + return false +} diff --git a/agent/utils/nginx/components/directive.go b/agent/utils/nginx/components/directive.go new file mode 100644 index 0000000..dfdef75 --- /dev/null +++ b/agent/utils/nginx/components/directive.go @@ -0,0 +1,29 @@ +package components + +type Directive struct { + Line int + Block IBlock + Name string + Comment string + Parameters []string +} + +func (d *Directive) GetComment() string { + return d.Comment +} + +func (d *Directive) GetName() string { + return d.Name +} + +func (d *Directive) GetParameters() []string { + return d.Parameters +} + +func (d *Directive) GetBlock() IBlock { + return d.Block +} + +func (d *Directive) GetLine() int { + return d.Line +} diff --git a/agent/utils/nginx/components/http.go b/agent/utils/nginx/components/http.go new file mode 100644 index 0000000..ff9d302 --- /dev/null +++ b/agent/utils/nginx/components/http.go @@ -0,0 +1,129 @@ +package components + +import ( + "errors" +) + +type Http struct { + Comment string + Servers []*Server + Directives []IDirective + Line int +} + +func (h *Http) GetCodeBlock() string { + return "" +} + +func (h *Http) GetComment() string { + return h.Comment +} + +func NewHttp(directive IDirective) (*Http, error) { + if block := directive.GetBlock(); block != nil { + http := &Http{ + Line: directive.GetBlock().GetLine(), + Servers: []*Server{}, + Directives: []IDirective{}, + Comment: block.GetComment(), + } + for _, directive := range block.GetDirectives() { + if server, ok := directive.(*Server); ok { + http.Servers = append(http.Servers, server) + continue + } + http.Directives = append(http.Directives, directive) + } + + return http, nil + } + return nil, errors.New("http directive must have a block") +} + +func (h *Http) GetName() string { + return "http" +} + +func (h *Http) GetParameters() []string { + return []string{} +} + +func (h *Http) GetDirectives() []IDirective { + directives := make([]IDirective, 0) + directives = append(directives, h.Directives...) + for _, directive := range h.Servers { + directives = append(directives, directive) + } + return directives +} + +func (h *Http) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range h.GetDirectives() { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + + return directives +} + +func (h *Http) UpdateDirective(key string, params []string) { + if key == "" || len(params) == 0 { + return + } + directives := h.GetDirectives() + index := -1 + for i, dir := range directives { + if dir.GetName() == key { + if IsRepeatKey(key) { + oldParams := dir.GetParameters() + if !(len(oldParams) > 0 && oldParams[0] == params[0]) { + continue + } + } + index = i + break + } + } + newDirective := &Directive{ + Name: key, + Parameters: params, + } + if index > -1 { + directives[index] = newDirective + } else { + directives = append(directives, newDirective) + } + h.Directives = directives +} + +func (h *Http) RemoveDirective(key string, params []string) { + directives := h.GetDirectives() + var newDirectives []IDirective + for _, dir := range directives { + if dir.GetName() == key { + if IsRepeatKey(key) && len(params) > 0 { + oldParams := dir.GetParameters() + if oldParams[0] == params[0] { + continue + } + } else { + continue + } + } + newDirectives = append(newDirectives, dir) + } + h.Directives = newDirectives +} + +func (h *Http) GetBlock() IBlock { + return h +} + +func (h *Http) GetLine() int { + return h.Line +} diff --git a/agent/utils/nginx/components/location.go b/agent/utils/nginx/components/location.go new file mode 100644 index 0000000..eb2740d --- /dev/null +++ b/agent/utils/nginx/components/location.go @@ -0,0 +1,384 @@ +package components + +import ( + "fmt" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/agent/utils/re" +) + +type Location struct { + Modifier string + Match string + Cache bool + ProxyPass string + Host string + CacheTime int + CacheUint string + Comment string + Directives []IDirective + Line int + Parameters []string + Replaces map[string]string + ServerCacheTime int + ServerCacheUint string + Cors bool + AllowMethods string + AllowHeaders string + AllowOrigins string + AllowCredentials bool + Preflight bool +} + +func (l *Location) GetCodeBlock() string { + return "" +} + +func NewLocation(directive IDirective) *Location { + location := &Location{ + Modifier: "", + Match: "", + } + directives := make([]IDirective, 0) + if len(directive.GetParameters()) == 0 { + panic("no enough parameter for location") + } + for _, dir := range directive.GetBlock().GetDirectives() { + directives = append(directives, dir) + params := dir.GetParameters() + switch dir.GetName() { + case "proxy_pass": + location.ProxyPass = params[0] + case "proxy_set_header": + if params[0] == "Host" { + location.Host = params[1] + } + case "proxy_cache": + location.Cache = true + case "if": + if params[0] == "(" && params[1] == "$uri" && params[2] == "~*" { + dirs := dir.GetBlock().GetDirectives() + for _, di := range dirs { + if di.GetName() == "expires" { + matches := re.GetRegex(re.NumberWordPattern).FindStringSubmatch(di.GetParameters()[0]) + if matches == nil { + continue + } + cacheTime, err := strconv.Atoi(matches[1]) + if err != nil { + continue + } + unit := matches[2] + location.CacheUint = unit + location.CacheTime = cacheTime + } + if di.GetName() == "add_header" && len(di.GetParameters()) >= 2 { + if di.GetParameters()[0] == "Cache-Control" && di.GetParameters()[1] == "no-cache" { + location.CacheTime = -1 + location.CacheUint = "" + } + } + } + } + if params[0] == "(" && params[1] == "$request_method" && params[2] == `=` && params[3] == `'OPTIONS'` && params[4] == ")" { + location.Preflight = true + } + case "proxy_cache_valid": + timeParam := params[len(params)-1] + matches := re.GetRegex(re.NumberWordPattern).FindStringSubmatch(timeParam) + if matches == nil { + continue + } + + cacheTime, err := strconv.Atoi(matches[1]) + if err != nil { + continue + } + unit := matches[2] + + location.ServerCacheTime = cacheTime + location.ServerCacheUint = unit + case "sub_filter": + if location.Replaces == nil { + location.Replaces = make(map[string]string, 0) + } + location.Replaces[strings.Trim(params[0], "\"")] = strings.Trim(params[1], "\"") + case "add_header": + if params[0] == "Access-Control-Allow-Origin" { + location.Cors = true + location.AllowOrigins = params[1] + } + if params[0] == "Access-Control-Allow-Methods" { + location.AllowMethods = params[1] + } + if params[0] == "Access-Control-Allow-Headers" { + location.AllowHeaders = params[1] + } + if params[0] == "Access-Control-Allow-Credentials" && params[1] == "true" { + location.AllowCredentials = true + } + } + } + + params := directive.GetParameters() + if len(params) == 1 { + location.Match = params[0] + } else if len(params) == 2 { + location.Match = params[1] + location.Modifier = params[0] + } + location.Parameters = directive.GetParameters() + location.Line = directive.GetLine() + location.Comment = directive.GetComment() + location.Directives = directives + return location +} + +func (l *Location) GetName() string { + return "location" +} + +func (l *Location) GetParameters() []string { + return l.Parameters +} + +func (l *Location) GetBlock() IBlock { + return l +} + +func (l *Location) GetComment() string { + return l.Comment +} + +func (l *Location) GetLine() int { + return l.Line +} + +func (l *Location) GetDirectives() []IDirective { + return l.Directives +} + +func (l *Location) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range l.Directives { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + return directives +} + +func (l *Location) UpdateDirective(key string, params []string) { + if key == "" || len(params) == 0 { + return + } + directives := l.Directives + index := -1 + for i, dir := range directives { + if dir.GetName() == key { + if IsRepeatKey(key) { + oldParams := dir.GetParameters() + if !(len(oldParams) > 0 && oldParams[0] == params[0]) { + continue + } + } + index = i + break + } + } + newDirective := &Directive{ + Name: key, + Parameters: params, + } + if index > -1 { + directives[index] = newDirective + } else { + directives = append(directives, newDirective) + } + l.Directives = directives +} + +// RemoveDirective removes a directive by its name and optional FIRST parameter match +func (l *Location) RemoveDirective(key string, params []string) { + directives := l.Directives + var newDirectives []IDirective + for _, dir := range directives { + if dir.GetName() == key { + if len(params) > 0 { + oldParams := dir.GetParameters() + if oldParams[0] == params[0] { + continue + } + } else { + continue + } + } + newDirectives = append(newDirectives, dir) + } + l.Directives = newDirectives +} + +// RemoveDirectiveByFullParams removes a directive by its name and full parameter match +func (l *Location) RemoveDirectiveByFullParams(key string, params []string) { + directives := l.Directives + var newDirectives []IDirective + for _, dir := range directives { + if dir.GetName() == key { + oldParams := dir.GetParameters() + if len(oldParams) == len(params) { + allMatch := true + for i, param := range params { + if oldParams[i] != param { + allMatch = false + break + } + } + if allMatch { + continue + } + } + } + newDirectives = append(newDirectives, dir) + } + l.Directives = newDirectives +} + +func (l *Location) ChangePath(Modifier string, Match string) { + if Match != "" && Modifier != "" { + l.Parameters = []string{Modifier, Match} + } + if Match != "" && Modifier == "" { + l.Parameters = []string{Match} + } + l.Modifier = Modifier + l.Match = Match +} + +func (l *Location) AddBrowserCache(cacheTime int, cacheUint string) { + l.RemoveDirective("add_header", []string{"Cache-Control", "no-cache"}) + l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"}) + l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2|jpeg|svg|webp|avif)$"`, ")"}) + directives := l.GetDirectives() + newDir := &Directive{ + Name: "if", + Parameters: []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2|jpeg|svg|webp|avif)$"`, ")"}, + Block: &Block{}, + } + block := &Block{} + block.Directives = append(block.Directives, &Directive{ + Name: "expires", + Parameters: []string{strconv.Itoa(cacheTime) + cacheUint}, + }) + newDir.Block = block + directives = append(directives, newDir) + l.Directives = directives + l.CacheTime = cacheTime + l.CacheUint = cacheUint +} + +func (l *Location) AddBroswerNoCache() { + l.RemoveDirective("add_header", []string{"Cache-Control", "no-cache"}) + l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"}) + l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2|jpeg|svg|webp|avif)$"`, ")"}) + directives := l.GetDirectives() + newDir := &Directive{ + Name: "if", + Parameters: []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2|jpeg|svg|webp|avif)$"`, ")"}, + Block: &Block{}, + } + block := &Block{} + block.Directives = append(block.Directives, &Directive{ + Name: "add_header", + Parameters: []string{"Cache-Control", "no-cache"}, + }) + newDir.Block = block + directives = append(directives, newDir) + l.Directives = directives + l.CacheTime = -1 + l.CacheUint = "s" +} + +func (l *Location) AddServerCache(cacheKey string, serverCacheTime int, serverCacheUint string) { + l.UpdateDirective("proxy_ignore_headers", []string{"Set-Cookie", "Cache-Control", "expires"}) + l.UpdateDirective("proxy_cache", []string{cacheKey}) + l.UpdateDirective("proxy_cache_key", []string{"$host$uri$is_args$args"}) + l.UpdateDirective("proxy_cache_valid", []string{"200", "304", "301", "302", strconv.Itoa(serverCacheTime) + serverCacheUint}) + l.Cache = true + l.ServerCacheTime = serverCacheTime + l.ServerCacheUint = serverCacheUint +} + +func (l *Location) RemoveBrowserCache() { + l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2)$"`, ")"}) + l.RemoveDirectiveByFullParams("if", []string{"(", "$uri", "~*", `"\.(gif|png|jpg|css|js|woff|woff2|jpeg|svg|webp|avif)$"`, ")"}) + l.RemoveDirective("add_header", []string{"Cache-Control", "no-cache"}) + l.CacheTime = 0 + l.CacheUint = "" +} + +func (l *Location) RemoveServerCache(cacheKey string) { + l.RemoveDirective("proxy_ignore_headers", []string{"Set-Cookie", "Cache-Control", "expires"}) + l.RemoveDirective("proxy_cache", []string{cacheKey}) + l.RemoveDirective("proxy_cache_key", []string{"$host$uri$is_args$args"}) + l.RemoveDirective("proxy_cache_valid", []string{"200"}) + l.Cache = false + l.ServerCacheTime = 0 + l.ServerCacheUint = "" +} + +func (l *Location) AddSubFilter(subFilters map[string]string) { + l.RemoveDirective("sub_filter", []string{}) + l.Replaces = subFilters + for k, v := range subFilters { + l.UpdateDirective("sub_filter", []string{fmt.Sprintf(`"%s"`, k), fmt.Sprintf(`"%s"`, v)}) + } + l.UpdateDirective("proxy_set_header", []string{"Accept-Encoding", `""`}) + l.UpdateDirective("sub_filter_once", []string{"off"}) + l.UpdateDirective("sub_filter_types", []string{"*"}) +} + +func (l *Location) RemoveSubFilter() { + l.RemoveDirective("sub_filter", []string{}) + l.RemoveDirective("proxy_set_header", []string{"Accept-Encoding", `""`}) + l.RemoveDirective("sub_filter_once", []string{"off"}) + l.RemoveDirective("sub_filter_types", []string{"*"}) + l.Replaces = nil +} + +func (l *Location) AddCorsOption() { + l.RemoveCorsOption() + newDir := &Directive{ + Name: "if", + Parameters: []string{"(", "$request_method", "=", "'OPTIONS'", ")"}, + Block: &Block{}, + } + block := &Block{} + block.AppendDirectives(&Directive{ + Name: "add_header", + Parameters: []string{"Access-Control-Max-Age", "1728000"}, + }) + block.AppendDirectives(&Directive{ + Name: "add_header", + Parameters: []string{"Content-Type", "'text/plain;charset=UTF-8'"}, + }) + block.AppendDirectives(&Directive{ + Name: "add_header", + Parameters: []string{"Content-Length", "0"}, + }) + block.AppendDirectives(&Directive{ + Name: "return", + Parameters: []string{"204"}, + }) + newDir.Block = block + directives := l.GetDirectives() + directives = append(directives, newDir) + l.Directives = directives +} + +func (l *Location) RemoveCorsOption() { + l.RemoveDirectiveByFullParams("if", []string{"(", "$request_method", "=", "'OPTIONS'", ")"}) +} diff --git a/agent/utils/nginx/components/lua_block.go b/agent/utils/nginx/components/lua_block.go new file mode 100644 index 0000000..d9b4b36 --- /dev/null +++ b/agent/utils/nginx/components/lua_block.go @@ -0,0 +1,120 @@ +package components + +import ( + "fmt" +) + +type LuaBlock struct { + Directives []IDirective + Name string + Comment string + LuaCode string + Line int +} + +func NewLuaBlock(directive IDirective) (*LuaBlock, error) { + if block := directive.GetBlock(); block != nil { + lb := &LuaBlock{ + Directives: []IDirective{}, + Name: directive.GetName(), + LuaCode: block.GetCodeBlock(), + } + + lb.Directives = append(lb.Directives, block.GetDirectives()...) + return lb, nil + } + return nil, fmt.Errorf("%s must have a block", directive.GetName()) +} + +func (lb *LuaBlock) GetName() string { + return lb.Name +} + +func (lb *LuaBlock) GetParameters() []string { + return []string{} +} + +func (lb *LuaBlock) GetDirectives() []IDirective { + directives := make([]IDirective, 0) + directives = append(directives, lb.Directives...) + return directives +} + +func (lb *LuaBlock) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range lb.GetDirectives() { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + + return directives +} + +func (lb *LuaBlock) GetCodeBlock() string { + return lb.LuaCode +} + +func (lb *LuaBlock) GetBlock() IBlock { + return lb +} + +func (lb *LuaBlock) GetComment() string { + return lb.Comment +} + +func (lb *LuaBlock) RemoveDirective(key string, params []string) { + directives := lb.Directives + var newDirectives []IDirective + for _, dir := range directives { + if dir.GetName() == key { + if len(params) > 0 { + oldParams := dir.GetParameters() + if oldParams[0] == params[0] { + continue + } + } else { + continue + } + } + newDirectives = append(newDirectives, dir) + } + lb.Directives = newDirectives +} + +func (lb *LuaBlock) UpdateDirective(key string, params []string) { + if key == "" || len(params) == 0 { + return + } + directives := lb.Directives + index := -1 + for i, dir := range directives { + if dir.GetName() == key { + if IsRepeatKey(key) { + oldParams := dir.GetParameters() + if !(len(oldParams) > 0 && oldParams[0] == params[0]) { + continue + } + } + index = i + break + } + } + newDirective := &Directive{ + Name: key, + Parameters: params, + } + if index > -1 { + directives[index] = newDirective + } else { + directives = append(directives, newDirective) + } + lb.Directives = directives +} + +func (lb *LuaBlock) GetLine() int { + return lb.Line +} diff --git a/agent/utils/nginx/components/server.go b/agent/utils/nginx/components/server.go new file mode 100644 index 0000000..028a8ee --- /dev/null +++ b/agent/utils/nginx/components/server.go @@ -0,0 +1,570 @@ +package components + +import ( + "errors" + "fmt" +) + +type Server struct { + Comment string + Listens []*ServerListen + Directives []IDirective + Line int + Cors bool + AllowMethods string + AllowHeaders string + AllowOrigins string + AllowCredentials bool + Preflight bool +} + +func (s *Server) GetCodeBlock() string { + return "" +} + +func NewServer(directive IDirective) (*Server, error) { + server := &Server{} + if block := directive.GetBlock(); block != nil { + server.Line = directive.GetBlock().GetLine() + server.Comment = block.GetComment() + directives := block.GetDirectives() + for _, dir := range directives { + switch dir.GetName() { + case "listen": + server.Listens = append(server.Listens, NewServerListen(dir.GetParameters(), dir.GetLine())) + case "add_header": + params := dir.GetParameters() + if params[0] == "Access-Control-Allow-Origin" { + server.Cors = true + server.AllowOrigins = params[1] + } + if params[0] == "Access-Control-Allow-Methods" { + server.AllowMethods = params[1] + } + if params[0] == "Access-Control-Allow-Headers" { + server.AllowHeaders = params[1] + } + if params[0] == "Access-Control-Allow-Credentials" && params[1] == "true" { + server.AllowCredentials = true + } + server.Directives = append(server.Directives, dir) + case "if": + params := dir.GetParameters() + if params[0] == "(" && params[1] == "$request_method" && params[2] == `=` && params[3] == `'OPTIONS'` && params[4] == ")" { + server.Preflight = true + } + server.Directives = append(server.Directives, dir) + default: + server.Directives = append(server.Directives, dir) + } + } + return server, nil + } + return nil, errors.New("server directive must have a block") +} + +func (s *Server) GetName() string { + return "server" +} + +func (s *Server) GetParameters() []string { + return []string{} +} + +func (s *Server) GetBlock() IBlock { + return s +} + +func (s *Server) GetComment() string { + return s.Comment +} + +func (s *Server) GetDirectives() []IDirective { + directives := make([]IDirective, 0) + for _, ls := range s.Listens { + directives = append(directives, ls) + } + directives = append(directives, s.Directives...) + return directives +} + +func (s *Server) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range s.Directives { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + if directiveName == "listen" { + for _, listen := range s.Listens { + params := []string{listen.Bind} + params = append(params, listen.Parameters...) + if listen.DefaultServer != "" { + params = append(params, DefaultServer) + } + directives = append(directives, &Directive{ + Name: "listen", + Parameters: params, + }) + } + } + return directives +} + +func (s *Server) UpdateDirective(key string, params []string) { + if key == "" || len(params) == 0 { + return + } + if key == "listen" { + defaultServer := false + paramLen := len(params) + if paramLen > 0 && params[paramLen-1] == "default_server" { + params = params[:paramLen-1] + defaultServer = true + } + s.UpdateListen(params[0], defaultServer, params[1:]...) + return + } + + directives := s.Directives + index := -1 + for i, dir := range directives { + if dir.GetName() == key { + if IsRepeatKey(key) { + oldParams := dir.GetParameters() + if !(len(oldParams) > 0 && oldParams[0] == params[0]) { + continue + } + } + index = i + break + } + } + newDirective := &Directive{ + Name: key, + Parameters: params, + } + if index > -1 { + directives[index] = newDirective + } else { + directives = append(directives, newDirective) + } + s.Directives = directives +} + +func (s *Server) RemoveDirective(key string, params []string) { + directives := s.Directives + var newDirectives []IDirective + for _, dir := range directives { + if dir.GetName() == key { + if len(params) == 0 { + continue + } + oldParams := dir.GetParameters() + if key == "location" { + if len(params) == len(oldParams) { + exist := true + for i := range params { + if params[i] != oldParams[i] { + exist = false + break + } + } + if exist { + continue + } + } + } else { + if oldParams[0] == params[0] { + continue + } + } + } + newDirectives = append(newDirectives, dir) + } + s.Directives = newDirectives +} + +func (s *Server) GetLine() int { + return s.Line +} + +func (s *Server) AddListen(bind string, defaultServer bool, params ...string) { + listen := &ServerListen{ + Bind: bind, + Parameters: params, + } + if defaultServer { + listen.DefaultServer = DefaultServer + } + s.Listens = append(s.Listens, listen) +} + +func isSameArray(arr1, arr2 []string) bool { + set1 := make(map[string]struct{}) + for _, v := range arr1 { + set1[v] = struct{}{} + } + for _, v := range arr2 { + if _, exists := set1[v]; !exists { + return false + } + } + return true +} + +func (s *Server) UpdateListen(bind string, defaultServer bool, params ...string) { + listen := &ServerListen{ + Bind: bind, + Parameters: params, + } + if defaultServer { + listen.DefaultServer = DefaultServer + } + var newListens []*ServerListen + exist := false + for _, li := range s.Listens { + if li.Bind == bind && isSameArray(li.Parameters, params) { + exist = true + newListens = append(newListens, listen) + } else { + newListens = append(newListens, li) + } + } + if !exist { + newListens = append(newListens, listen) + } + + s.Listens = newListens +} + +func (s *Server) DeleteListen(bind string) { + var newListens []*ServerListen + for _, li := range s.Listens { + if li.Bind != bind { + newListens = append(newListens, li) + } + } + s.Listens = newListens +} + +func (s *Server) RemoveListen(bind string, params ...string) { + var newListens []*ServerListen + for _, li := range s.Listens { + if li.Bind == bind && isSameArray(li.Parameters, params) { + continue + } + newListens = append(newListens, li) + } + s.Listens = newListens +} + +func (s *Server) DeleteServerName(name string) { + var names []string + dirs := s.FindDirectives("server_name") + params := dirs[0].GetParameters() + for _, param := range params { + if param != name { + names = append(names, param) + } + } + s.UpdateServerName(names) +} + +func (s *Server) AddServerName(name string) { + dirs := s.FindDirectives("server_name") + params := dirs[0].GetParameters() + params = append(params, name) + s.UpdateServerName(params) +} + +func (s *Server) UpdateServerName(names []string) { + s.UpdateDirective("server_name", names) +} + +func (s *Server) UpdateRoot(path string) { + s.UpdateDirective("root", []string{path}) +} + +func (s *Server) UpdateRootProxyForAi(proxy []string) { + newDir := Directive{ + Name: "location", + Parameters: []string{"/"}, + Block: &Block{}, + } + block := &Block{} + block.AppendDirectives( + &Directive{ + Name: "proxy_buffering", + Parameters: []string{ + "off", + }, + }, + &Directive{ + Name: "proxy_cache", + Parameters: []string{ + "off", + }, + }, + &Directive{ + Name: "proxy_http_version", + Parameters: []string{ + "1.1", + }, + }, + &Directive{ + Name: "proxy_set_header", + Parameters: []string{ + "Connection", "''", + }, + }, + &Directive{ + Name: "chunked_transfer_encoding", + Parameters: []string{ + "off", + }, + }, + &Directive{ + Name: "proxy_pass", + Parameters: proxy, + }, + ) + + newDir.Block = block + s.UpdateDirectiveBySecondKey("location", "/", newDir) +} + +func (s *Server) UpdateRootLocation() { + newDir := Directive{ + Name: "location", + Parameters: []string{"/"}, + Block: &Block{}, + } + block := &Block{} + block.AppendDirectives(&Directive{ + Name: "root", + Parameters: []string{"index.html"}, + }) + newDir.Block = block +} + +func (s *Server) UpdateRootProxy(proxy []string) { + newDir := Directive{ + Name: "location", + Parameters: []string{"/"}, + Block: &Block{}, + } + block := &Block{} + + block.AppendDirectives( + &Directive{ + Name: "proxy_set_header", + Parameters: []string{"Host", "$host"}, + }, + &Directive{ + Name: "proxy_set_header", + Parameters: []string{"X-Forwarded-For", "$proxy_add_x_forwarded_for"}, + }, + &Directive{ + Name: "proxy_set_header", + Parameters: []string{"X-Forwarded-Host", "$server_name"}, + }, + &Directive{ + Name: "proxy_set_header", + Parameters: []string{"X-Real-IP", "$remote_addr"}, + }, + &Directive{ + Name: "proxy_set_header", + Parameters: []string{"X-Forwarded-Proto", "$scheme"}, + }, + &Directive{ + Name: "proxy_set_header", + Parameters: []string{"Connection", "upgrade"}, + }, + &Directive{ + Name: "proxy_set_header", + Parameters: []string{"Upgrade", "$http_upgrade"}, + }, + &Directive{ + Name: "proxy_http_version", + Parameters: []string{"1.1"}, + }, + &Directive{ + Name: "proxy_pass", + Parameters: proxy, + }, + ) + + newDir.Block = block + s.UpdateDirectiveBySecondKey("location", "/", newDir) +} + +func (s *Server) UpdatePHPProxy(proxy []string, localPath string) { + newDir := Directive{ + Name: "location", + Parameters: []string{"~ [^/]\\.php(/|$)"}, + Block: &Block{}, + } + block := &Block{} + block.AppendDirectives( + &Directive{ + Name: "fastcgi_pass", + Parameters: proxy, + }, + &Directive{ + Name: "include", + Parameters: []string{"fastcgi-php.conf"}, + }, + &Directive{ + Name: "include", + Parameters: []string{"fastcgi_params"}, + }, + ) + if localPath == "" { + block.AppendDirectives(&Directive{ + Name: "set", + Parameters: []string{"$real_script_name", "$fastcgi_script_name"}, + }) + ifDir := &Directive{ + Name: "if", + Parameters: []string{"($fastcgi_script_name ~ \"^(.+?\\.php)(/.+)$\")"}, + } + ifDir.Block = &Block{ + Directives: []IDirective{ + &Directive{ + Name: "set", + Parameters: []string{"$real_script_name", "$1"}, + }, + &Directive{ + Name: "set", + Parameters: []string{"$path_info", "$2"}, + }, + }, + } + block.AppendDirectives( + ifDir, + &Directive{ + Name: "fastcgi_param", + Parameters: []string{"SCRIPT_FILENAME", "$document_root$real_script_name"}, + }, + &Directive{ + Name: "fastcgi_param", + Parameters: []string{"SCRIPT_NAME", "$real_script_name"}, + }, + &Directive{ + Name: "fastcgi_param", + Parameters: []string{"PATH_INFO", "$path_info"}, + }) + + } else { + block.AppendDirectives(&Directive{ + Name: "fastcgi_param", + Parameters: []string{"SCRIPT_FILENAME", localPath}, + }) + } + newDir.Block = block + s.UpdateDirectiveBySecondKey("location", "~ [^/]\\.php(/|$)", newDir) +} + +func (s *Server) UpdateDirectiveBySecondKey(name string, key string, directive Directive) { + directives := s.Directives + index := -1 + for i, dir := range directives { + if dir.GetName() == name && dir.GetParameters()[0] == key { + index = i + break + } + } + if index > -1 { + directives[index] = &directive + } else { + directives = append(directives, &directive) + } + s.Directives = directives +} + +func (s *Server) RemoveListenByBind(bind string) { + var listens []*ServerListen + for _, listen := range s.Listens { + if listen.Bind != bind { + listens = append(listens, listen) + } + } + s.Listens = listens +} + +func (s *Server) AddHTTP2HTTPS(httpsPort int) { + newDir := Directive{ + Name: "if", + Parameters: []string{"($scheme = http)"}, + Block: &Block{}, + } + block := &Block{} + if httpsPort == 443 { + block.AppendDirectives(&Directive{ + Name: "return", + Parameters: []string{"301", "https://$host$request_uri"}, + }) + } else { + block.AppendDirectives(&Directive{ + Name: "return", + Parameters: []string{"301", fmt.Sprintf("https://$host:%d$request_uri", httpsPort)}, + }) + } + + newDir.Block = block + s.UpdateDirectiveBySecondKey("if", "($scheme", newDir) +} + +func (s *Server) UpdateAllowIPs(ips []string) { + index := -1 + for i, directive := range s.Directives { + if directive.GetName() == "location" && directive.GetParameters()[0] == "/" { + index = i + break + } + } + ipDirectives := make([]IDirective, 0) + for _, ip := range ips { + ipDirectives = append(ipDirectives, &Directive{ + Name: "allow", + Parameters: []string{ip}, + }) + } + ipDirectives = append(ipDirectives, &Directive{ + Name: "deny", + Parameters: []string{"all"}, + }) + if index != -1 { + newDirectives := append(ipDirectives, s.Directives[index:]...) + s.Directives = append(s.Directives[:index], newDirectives...) + } else { + s.Directives = append(s.Directives, ipDirectives...) + } +} + +func (s *Server) AddCorsOption() { + newDir := &Directive{ + Name: "if", + Parameters: []string{"(", "$request_method", "=", "'OPTIONS'", ")"}, + Block: &Block{}, + } + block := &Block{} + block.AppendDirectives(&Directive{ + Name: "return", + Parameters: []string{"204"}, + }) + newDir.Block = block + directives := s.GetDirectives() + newDirectives := make([]IDirective, 0) + for _, dir := range directives { + if dir.GetName() != "listen" { + newDirectives = append(newDirectives, dir) + } + } + newDirectives = append(newDirectives, newDir) + s.Directives = newDirectives +} diff --git a/agent/utils/nginx/components/server_listen.go b/agent/utils/nginx/components/server_listen.go new file mode 100644 index 0000000..d45bb7e --- /dev/null +++ b/agent/utils/nginx/components/server_listen.go @@ -0,0 +1,75 @@ +package components + +import ( + "strings" + + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +const DefaultServer = "default_server" + +type ServerListen struct { + Bind string + DefaultServer string + Parameters []string + Comment string + Line int +} + +func NewServerListen(params []string, line int) *ServerListen { + server := &ServerListen{ + Parameters: []string{}, + Line: line, + } + for _, param := range params { + if isBind(param) { + server.Bind = param + } else if param == DefaultServer { + server.DefaultServer = DefaultServer + } else { + server.Parameters = append(server.Parameters, param) + } + } + return server +} + +func isBind(param string) bool { + if common.IsNum(param) { + return true + } + if strings.Contains(param, "*") || strings.Contains(param, ":") || strings.Contains(param, ".") { + return true + } + return false +} + +func (sl *ServerListen) GetName() string { + return "listen" +} + +func (sl *ServerListen) GetBlock() IBlock { + return nil +} + +func (sl *ServerListen) GetParameters() []string { + params := []string{sl.Bind} + params = append(params, sl.Parameters...) + params = append(params, sl.DefaultServer) + return params +} + +func (sl *ServerListen) GetComment() string { + return sl.Comment +} + +func (sl *ServerListen) AddDefaultServer() { + sl.DefaultServer = DefaultServer +} + +func (sl *ServerListen) RemoveDefaultServe() { + sl.DefaultServer = "" +} + +func (sl *ServerListen) GetLine() int { + return sl.Line +} diff --git a/agent/utils/nginx/components/statement.go b/agent/utils/nginx/components/statement.go new file mode 100644 index 0000000..432a4c1 --- /dev/null +++ b/agent/utils/nginx/components/statement.go @@ -0,0 +1,19 @@ +package components + +type IBlock interface { + GetDirectives() []IDirective + FindDirectives(directiveName string) []IDirective + RemoveDirective(name string, params []string) + UpdateDirective(name string, params []string) + GetComment() string + GetLine() int + GetCodeBlock() string +} + +type IDirective interface { + GetName() string + GetParameters() []string + GetBlock() IBlock + GetComment() string + GetLine() int +} diff --git a/agent/utils/nginx/components/upstream.go b/agent/utils/nginx/components/upstream.go new file mode 100644 index 0000000..1b0c0d5 --- /dev/null +++ b/agent/utils/nginx/components/upstream.go @@ -0,0 +1,135 @@ +package components + +import ( + "errors" +) + +type Upstream struct { + UpstreamName string + UpstreamServers []*UpstreamServer + Directives []IDirective + Comment string + Line int +} + +func (us *Upstream) GetCodeBlock() string { + return "" +} + +func (us *Upstream) GetName() string { + return "upstream" +} + +func (us *Upstream) GetParameters() []string { + return []string{us.UpstreamName} +} + +func (us *Upstream) GetBlock() IBlock { + return us +} + +func (us *Upstream) GetComment() string { + return us.Comment +} + +func (us *Upstream) GetDirectives() []IDirective { + directives := make([]IDirective, 0) + directives = append(directives, us.Directives...) + for _, uss := range us.UpstreamServers { + directives = append(directives, uss) + } + return directives +} + +func NewUpstream(directive IDirective) (*Upstream, error) { + parameters := directive.GetParameters() + us := &Upstream{ + UpstreamName: parameters[0], + Line: directive.GetLine(), + } + + if block := directive.GetBlock(); block != nil { + us.Comment = block.GetComment() + for _, d := range block.GetDirectives() { + if d.GetName() == "server" { + us.UpstreamServers = append(us.UpstreamServers, NewUpstreamServer(d)) + } else { + us.Directives = append(us.Directives, d) + } + } + return us, nil + } + + return nil, errors.New("missing upstream block") +} + +func (us *Upstream) AddServer(server *UpstreamServer) { + us.UpstreamServers = append(us.UpstreamServers, server) +} + +func (us *Upstream) FindDirectives(directiveName string) []IDirective { + directives := make([]IDirective, 0) + for _, directive := range us.Directives { + if directive.GetName() == directiveName { + directives = append(directives, directive) + } + if directive.GetBlock() != nil { + directives = append(directives, directive.GetBlock().FindDirectives(directiveName)...) + } + } + + return directives +} + +func (us *Upstream) UpdateDirective(key string, params []string) { + if key == "" { + return + } + directives := us.Directives + index := -1 + for i, dir := range directives { + if dir.GetName() == key { + if IsRepeatKey(key) { + oldParams := dir.GetParameters() + if !(len(oldParams) > 0 && oldParams[0] == params[0]) { + continue + } + } + index = i + break + } + } + newDirective := &Directive{ + Name: key, + Parameters: params, + } + if index > -1 { + directives[index] = newDirective + } else { + directives = append(directives, newDirective) + } + us.Directives = directives +} + +func (us *Upstream) RemoveDirective(key string, params []string) { + directives := us.Directives + var newDirectives []IDirective + for _, dir := range directives { + if dir.GetName() == key { + if IsRepeatKey(key) && len(params) > 0 { + oldParams := dir.GetParameters() + if oldParams[0] == params[0] { + continue + } + } else { + continue + } + } + newDirectives = append(newDirectives, dir) + } + us.Directives = newDirectives +} + +func (us *Upstream) GetLine() int { + return us.Line +} diff --git a/agent/utils/nginx/components/upstream_server.go b/agent/utils/nginx/components/upstream_server.go new file mode 100644 index 0000000..f5af464 --- /dev/null +++ b/agent/utils/nginx/components/upstream_server.go @@ -0,0 +1,83 @@ +package components + +import ( + "fmt" + "sort" + "strings" +) + +type UpstreamServer struct { + Comment string + Address string + Flags []string + Parameters map[string]string + Line int +} + +func (uss *UpstreamServer) GetName() string { + return "server" +} + +func (uss *UpstreamServer) GetBlock() IBlock { + return nil +} + +func (uss *UpstreamServer) GetParameters() []string { + return uss.GetDirective().Parameters +} + +func (uss *UpstreamServer) GetComment() string { + return uss.Comment +} + +func (uss *UpstreamServer) GetDirective() *Directive { + directive := &Directive{ + Name: "server", + Parameters: make([]string, 0), + Block: nil, + } + + directive.Parameters = append(directive.Parameters, uss.Address) + + paramNames := make([]string, 0) + for k := range uss.Parameters { + paramNames = append(paramNames, k) + } + sort.Strings(paramNames) + + for _, k := range paramNames { + directive.Parameters = append(directive.Parameters, fmt.Sprintf("%s=%s", k, uss.Parameters[k])) + } + + directive.Parameters = append(directive.Parameters, uss.Flags...) + + return directive +} + +func NewUpstreamServer(directive IDirective) *UpstreamServer { + uss := &UpstreamServer{ + Comment: directive.GetComment(), + Flags: make([]string, 0), + Parameters: make(map[string]string, 0), + Line: directive.GetLine(), + } + + for i, parameter := range directive.GetParameters() { + if i == 0 { + uss.Address = parameter + continue + } + if strings.Contains(parameter, "=") { + s := strings.SplitN(parameter, "=", 2) + uss.Parameters[s[0]] = s[1] + } else { + uss.Flags = append(uss.Flags, parameter) + } + } + + return uss +} + +func (uss *UpstreamServer) GetLine() int { + return uss.Line +} diff --git a/agent/utils/nginx/dumper.go b/agent/utils/nginx/dumper.go new file mode 100644 index 0000000..90d240b --- /dev/null +++ b/agent/utils/nginx/dumper.go @@ -0,0 +1,128 @@ +package nginx + +import ( + "bytes" + "fmt" + "github.com/1Panel-dev/1Panel/agent/constant" + "os" + "strings" + + "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" +) + +var ( + IndentedStyle = &Style{ + SpaceBeforeBlocks: false, + StartIndent: 0, + Indent: 4, + } +) + +type Style struct { + SpaceBeforeBlocks bool + StartIndent int + Indent int +} + +func (s *Style) Iterate() *Style { + newStyle := &Style{ + SpaceBeforeBlocks: s.SpaceBeforeBlocks, + StartIndent: s.StartIndent + s.Indent, + Indent: s.Indent, + } + return newStyle +} + +func DumpDirective(d components.IDirective, style *Style) string { + var buf bytes.Buffer + + if style.SpaceBeforeBlocks && d.GetBlock() != nil { + buf.WriteString("\n") + } + buf.WriteString(fmt.Sprintf("%s%s", strings.Repeat(" ", style.StartIndent), d.GetName())) + if len(d.GetParameters()) > 0 { + buf.WriteString(fmt.Sprintf(" %s", strings.Join(d.GetParameters(), " "))) + } + if d.GetBlock() == nil { + if d.GetName() != "" { + buf.WriteRune(';') + buf.WriteString(" ") + } + if d.GetComment() != "" { + buf.WriteString(d.GetComment()) + } + } else { + buf.WriteString(" {") + if d.GetComment() != "" { + buf.WriteString(" ") + buf.WriteString(d.GetComment()) + } + buf.WriteString("\n") + buf.WriteString(DumpBlock(d.GetBlock(), style.Iterate(), d.GetBlock().GetLine())) + buf.WriteString(fmt.Sprintf("\n%s}", strings.Repeat(" ", style.StartIndent))) + } + return buf.String() +} + +func DumpBlock(b components.IBlock, style *Style, startLine int) string { + var buf bytes.Buffer + + if b.GetCodeBlock() != "" { + luaLines := strings.Split(b.GetCodeBlock(), "\n") + for i, line := range luaLines { + if strings.Replace(line, " ", "", -1) == "" { + continue + } + buf.WriteString(line) + if i != len(luaLines)-1 { + buf.WriteString("\n") + } + } + return buf.String() + } + + line := startLine + if b.GetLine() > startLine { + for i := 0; i < b.GetLine()-startLine; i++ { + buf.WriteString("\n") + } + line = b.GetLine() + } + + directives := b.GetDirectives() + + var sortDirectives []components.IDirective + var proxyIncludes []components.IDirective + for _, directive := range directives { + if directive.GetName() == "include" && strings.Contains(strings.Join(directive.GetParameters(), " "), "/proxy/") { + proxyIncludes = append(proxyIncludes, directive) + } else { + sortDirectives = append(sortDirectives, directive) + } + } + directives = append(sortDirectives, proxyIncludes...) + + for i, directive := range directives { + + if directive.GetLine() > line { + for i := 0; i < b.GetLine()-line; i++ { + buf.WriteString("\n") + } + line = b.GetLine() + } + + buf.WriteString(DumpDirective(directive, style)) + if i != len(directives)-1 { + buf.WriteString("\n") + } + } + return buf.String() +} + +func DumpConfig(c *components.Config, style *Style) string { + return DumpBlock(c.Block, style, 1) +} + +func WriteConfig(c *components.Config, style *Style) error { + return os.WriteFile(c.FilePath, []byte(DumpConfig(c, style)), constant.DirPerm) +} diff --git a/agent/utils/nginx/parser/flag/flag.go b/agent/utils/nginx/parser/flag/flag.go new file mode 100644 index 0000000..b39d9e7 --- /dev/null +++ b/agent/utils/nginx/parser/flag/flag.go @@ -0,0 +1,59 @@ +package flag + +type Type int + +const ( + EOF Type = iota + Eol + Keyword + QuotedString + Variable + BlockStart + BlockEnd + Semicolon + Comment + Illegal + Regex + LuaCode +) + +var ( + FlagName = map[Type]string{ + QuotedString: "QuotedString", + EOF: "Eof", + Keyword: "Keyword", + Variable: "Variable", + BlockStart: "BlockStart", + BlockEnd: "BlockEnd", + Semicolon: "Semicolon", + Comment: "Comment", + Illegal: "Illegal", + Regex: "Regex", + } +) + +func (tt Type) String() string { + return FlagName[tt] +} + +type Flag struct { + Type Type + Literal string + Line int + Column int +} + +func (t Flag) Lit(literal string) Flag { + t.Literal = literal + return t +} + +type Flags []Flag + +func (t Flag) Is(typ Type) bool { + return t.Type == typ +} + +func (t Flag) IsParameterEligible() bool { + return t.Is(Keyword) || t.Is(QuotedString) || t.Is(Variable) || t.Is(Regex) +} diff --git a/agent/utils/nginx/parser/lexer.go b/agent/utils/nginx/parser/lexer.go new file mode 100644 index 0000000..c0648f8 --- /dev/null +++ b/agent/utils/nginx/parser/lexer.go @@ -0,0 +1,262 @@ +package parser + +import ( + "bufio" + "bytes" + "io" + "strings" + + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser/flag" +) + +type lexer struct { + reader *bufio.Reader + file string + line int + column int + inLuaBlock bool + Latest flag.Flag +} + +func lex(content string) *lexer { + return newLexer(bytes.NewBuffer([]byte(content))) +} + +func newLexer(r io.Reader) *lexer { + return &lexer{ + line: 1, + reader: bufio.NewReader(r), + } +} + +func (s *lexer) scan() flag.Flag { + s.Latest = s.getNextFlag() + return s.Latest +} + +//func (s *lexer) all() flag.Flags { +// tokens := make([]flag.Flag, 0) +// for { +// v := s.scan() +// if v.Type == flag.EOF || v.Type == -1 { +// break +// } +// tokens = append(tokens, v) +// } +// return tokens +//} + +func (s *lexer) getNextFlag() flag.Flag { + if s.inLuaBlock { + s.inLuaBlock = false + flag := s.scanLuaCode() + return flag + } +retoFlag: + ch := s.peek() + switch { + case isSpace(ch): + s.skipWhitespace() + goto retoFlag + case isEOF(ch): + return s.NewToken(flag.EOF).Lit(string(s.read())) + case ch == ';': + return s.NewToken(flag.Semicolon).Lit(string(s.read())) + case ch == '{': + if isLuaBlock(s.Latest) { + s.inLuaBlock = true + } + return s.NewToken(flag.BlockStart).Lit(string(s.read())) + case ch == '}': + return s.NewToken(flag.BlockEnd).Lit(string(s.read())) + case ch == '#': + return s.scanComment() + case ch == '$': + return s.scanVariable() + case isQuote(ch): + return s.scanQuotedString(ch) + default: + return s.scanKeyword() + } +} + +func (s *lexer) scanLuaCode() flag.Flag { + ret := s.NewToken(flag.LuaCode) + stack := make([]rune, 0, 50) + code := strings.Builder{} + + for { + ch := s.read() + if ch == rune(flag.EOF) { + panic("unexpected end of file while scanning a string, maybe an unclosed lua code?") + } + if ch == '#' { + code.WriteRune(ch) + code.WriteString(s.readUntil(isEndOfLine)) + continue + } else if ch == '}' { + if len(stack) == 0 { + _ = s.reader.UnreadRune() + return ret.Lit(strings.TrimRight(strings.Trim(code.String(), "\n"), "\n ")) + } + if stack[len(stack)-1] == '{' { + stack = stack[0 : len(stack)-1] + } + } else if ch == '{' { + stack = append(stack, ch) + } + code.WriteRune(ch) + } +} + +func (s *lexer) peek() rune { + r, _, _ := s.reader.ReadRune() + _ = s.reader.UnreadRune() + return r +} + +type runeCheck func(rune) bool + +func (s *lexer) readUntil(until runeCheck) string { + var buf bytes.Buffer + buf.WriteRune(s.read()) + + for { + if ch := s.peek(); isEOF(ch) { + break + } else if until(ch) { + break + } else { + buf.WriteRune(s.read()) + } + } + + return buf.String() +} + +func (s *lexer) NewToken(tokenType flag.Type) flag.Flag { + return flag.Flag{ + Type: tokenType, + Line: s.line, + Column: s.column, + } +} + +func (s *lexer) readWhile(while runeCheck) string { + var buf bytes.Buffer + buf.WriteRune(s.read()) + + for { + if ch := s.peek(); while(ch) { + buf.WriteRune(s.read()) + } else { + break + } + } + return buf.String() +} + +func (s *lexer) skipWhitespace() { + s.readWhile(isSpace) +} + +func (s *lexer) scanComment() flag.Flag { + return s.NewToken(flag.Comment).Lit(s.readUntil(isEndOfLine)) +} + +func (s *lexer) scanQuotedString(delimiter rune) flag.Flag { + var buf bytes.Buffer + tok := s.NewToken(flag.QuotedString) + _, _ = buf.WriteRune(s.read()) + for { + ch := s.read() + + if ch == rune(flag.EOF) { + panic("unexpected end of file while scanning a string, maybe an unclosed quote?") + } + + if ch == '\\' && (s.peek() == delimiter) { + buf.WriteRune(ch) + buf.WriteRune(s.read()) + continue + } + + _, _ = buf.WriteRune(ch) + if ch == delimiter { + break + } + } + + return tok.Lit(buf.String()) +} + +func (s *lexer) scanKeyword() flag.Flag { + var buf bytes.Buffer + tok := s.NewToken(flag.Keyword) + prev := s.read() + buf.WriteRune(prev) + for { + ch := s.peek() + + if isSpace(ch) || isEOF(ch) || ch == ';' { + break + } + + if ch == '{' { + if prev == '$' { + buf.WriteString(s.readUntil(func(r rune) bool { + return r == '}' + })) + buf.WriteRune(s.read()) //consume latest '}' + } else { + break + } + } + buf.WriteRune(s.read()) + } + + return tok.Lit(buf.String()) +} + +func (s *lexer) scanVariable() flag.Flag { + return s.NewToken(flag.Variable).Lit(s.readUntil(isKeywordTerminator)) +} + +func (s *lexer) read() rune { + ch, _, err := s.reader.ReadRune() + if err != nil { + return rune(flag.EOF) + } + + if ch == '\n' { + s.column = 1 + s.line++ + } else { + s.column++ + } + return ch +} + +func isQuote(ch rune) bool { + return ch == '"' || ch == '\'' || ch == '`' +} + +func isKeywordTerminator(ch rune) bool { + return isSpace(ch) || isEndOfLine(ch) || ch == '{' || ch == ';' +} + +func isSpace(ch rune) bool { + return ch == ' ' || ch == '\t' || isEndOfLine(ch) +} + +func isEOF(ch rune) bool { + return ch == rune(flag.EOF) +} + +func isEndOfLine(ch rune) bool { + return ch == '\r' || ch == '\n' +} + +func isLuaBlock(t flag.Flag) bool { + return t.Type == flag.Keyword && strings.HasSuffix(t.Literal, "_by_lua_block") +} diff --git a/agent/utils/nginx/parser/parser.go b/agent/utils/nginx/parser/parser.go new file mode 100644 index 0000000..cdf8210 --- /dev/null +++ b/agent/utils/nginx/parser/parser.go @@ -0,0 +1,210 @@ +package parser + +import ( + "bufio" + "errors" + "fmt" + "os" + "strings" + + components "github.com/1Panel-dev/1Panel/agent/utils/nginx/components" + "github.com/1Panel-dev/1Panel/agent/utils/nginx/parser/flag" +) + +type Parser struct { + lexer *lexer + currentToken flag.Flag + followingToken flag.Flag + blockWrappers map[string]func(*components.Directive) (components.IDirective, error) + directiveWrappers map[string]func(*components.Directive) components.IDirective +} + +func NewStringParser(str string) *Parser { + return NewParserFromLexer(lex(str)) +} + +func NewParser(filePath string) (*Parser, error) { + f, err := os.Open(filePath) + if err != nil { + return nil, err + } + defer f.Close() + l := newLexer(bufio.NewReader(f)) + l.file = filePath + p := NewParserFromLexer(l) + return p, nil +} + +func NewParserFromLexer(lexer *lexer) *Parser { + parser := &Parser{ + lexer: lexer, + } + + parser.nextToken() + parser.nextToken() + + parser.blockWrappers = map[string]func(*components.Directive) (components.IDirective, error){ + "http": func(directive *components.Directive) (components.IDirective, error) { + return parser.wrapHttp(directive), nil + }, + "server": func(directive *components.Directive) (components.IDirective, error) { + return parser.wrapServer(directive), nil + }, + "location": func(directive *components.Directive) (components.IDirective, error) { + return parser.wrapLocation(directive), nil + }, + "upstream": func(directive *components.Directive) (components.IDirective, error) { + return parser.wrapUpstream(directive), nil + }, + "_by_lua_block": func(directive *components.Directive) (components.IDirective, error) { + return parser.wrapLuaBlock(directive) + }, + } + + parser.directiveWrappers = map[string]func(*components.Directive) components.IDirective{ + "server": func(directive *components.Directive) components.IDirective { + return parser.parseUpstreamServer(directive) + }, + } + + return parser +} + +func (p *Parser) nextToken() { + p.currentToken = p.followingToken + p.followingToken = p.lexer.scan() +} + +func (p *Parser) curTokenIs(t flag.Type) bool { + return p.currentToken.Type == t +} + +func (p *Parser) followingTokenIs(t flag.Type) bool { + return p.followingToken.Type == t +} + +func (p *Parser) Parse() (*components.Config, error) { + parsedBlock, err := p.parseBlock(false) + if err != nil { + return nil, err + } + c := &components.Config{ + FilePath: p.lexer.file, + Block: parsedBlock, + } + return c, err +} + +func (p *Parser) parseBlock(inBlock bool) (*components.Block, error) { + context := &components.Block{ + Comment: "", + Directives: make([]components.IDirective, 0), + Line: p.currentToken.Line, + } + +parsingloop: + for { + switch { + case p.curTokenIs(flag.EOF): + if inBlock { + return nil, errors.New("unexpected eof in block") + } + break parsingloop + case p.curTokenIs(flag.BlockEnd): + break parsingloop + case p.curTokenIs(flag.LuaCode): + context.IsLuaBlock = true + context.LiteralCode = p.currentToken.Literal + case p.curTokenIs(flag.Keyword) || p.curTokenIs(flag.QuotedString): + s, err := p.parseStatement() + if err != nil { + return nil, err + } + context.Directives = append(context.Directives, s) + case p.curTokenIs(flag.Comment): + context.Directives = append(context.Directives, &components.Comment{ + Detail: p.currentToken.Literal, + Line: p.currentToken.Line, + }) + } + p.nextToken() + } + + return context, nil +} + +func (p *Parser) parseStatement() (components.IDirective, error) { + d := &components.Directive{ + Name: p.currentToken.Literal, + Line: p.currentToken.Line, + } + + for p.nextToken(); p.currentToken.IsParameterEligible(); p.nextToken() { + d.Parameters = append(d.Parameters, p.currentToken.Literal) + } + + if p.curTokenIs(flag.Semicolon) { + if dw, ok := p.directiveWrappers[d.Name]; ok { + return dw(d), nil + } + if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line { + d.Comment = p.followingToken.Literal + p.nextToken() + } + return d, nil + } + + if p.curTokenIs(flag.BlockStart) { + inLineComment := "" + if p.followingTokenIs(flag.Comment) && p.currentToken.Line == p.followingToken.Line { + inLineComment = p.followingToken.Literal + p.nextToken() + p.nextToken() + } + block, err := p.parseBlock(false) + if err != nil { + return nil, err + } + + block.Comment = inLineComment + d.Block = block + + if strings.HasSuffix(d.Name, "_by_lua_block") { + return p.blockWrappers["_by_lua_block"](d) + } + + if bw, ok := p.blockWrappers[d.Name]; ok { + return bw(d) + } + return d, nil + } + + panic(fmt.Errorf("unexpected token %s (%s) on line %d, column %d", p.currentToken.Type.String(), p.currentToken.Literal, p.currentToken.Line, p.currentToken.Column)) +} + +func (p *Parser) wrapLocation(directive *components.Directive) *components.Location { + return components.NewLocation(directive) +} + +func (p *Parser) wrapServer(directive *components.Directive) *components.Server { + s, _ := components.NewServer(directive) + return s +} + +func (p *Parser) wrapUpstream(directive *components.Directive) *components.Upstream { + s, _ := components.NewUpstream(directive) + return s +} + +func (p *Parser) wrapHttp(directive *components.Directive) *components.Http { + h, _ := components.NewHttp(directive) + return h +} + +func (p *Parser) wrapLuaBlock(directive *components.Directive) (*components.LuaBlock, error) { + return components.NewLuaBlock(directive) +} + +func (p *Parser) parseUpstreamServer(directive *components.Directive) *components.UpstreamServer { + return components.NewUpstreamServer(directive) +} diff --git a/agent/utils/ntp/ntp.go b/agent/utils/ntp/ntp.go new file mode 100644 index 0000000..c50e9c4 --- /dev/null +++ b/agent/utils/ntp/ntp.go @@ -0,0 +1,82 @@ +package ntp + +import ( + "encoding/binary" + "fmt" + "net" + "runtime" + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/cmd" +) + +const ntpEpochOffset = 2208988800 + +type packet struct { + Settings uint8 + Stratum uint8 + Poll int8 + Precision int8 + RootDelay uint32 + RootDispersion uint32 + ReferenceID uint32 + RefTimeSec uint32 + RefTimeFrac uint32 + OrigTimeSec uint32 + OrigTimeFrac uint32 + RxTimeSec uint32 + RxTimeFrac uint32 + TxTimeSec uint32 + TxTimeFrac uint32 +} + +func GetRemoteTime(site string) (time.Time, error) { + conn, err := net.Dial("udp", site+":123") + if err != nil { + return time.Time{}, fmt.Errorf("failed to connect: %v", err) + } + defer conn.Close() + if err := conn.SetDeadline(time.Now().Add(15 * time.Second)); err != nil { + return time.Time{}, fmt.Errorf("failed to set deadline: %v", err) + } + + req := &packet{Settings: 0x1B} + + if err := binary.Write(conn, binary.BigEndian, req); err != nil { + return time.Time{}, fmt.Errorf("failed to set request: %v", err) + } + + rsp := &packet{} + if err := binary.Read(conn, binary.BigEndian, rsp); err != nil { + return time.Time{}, fmt.Errorf("failed to read server response: %v", err) + } + + secs := float64(rsp.TxTimeSec) - ntpEpochOffset + nanos := (int64(rsp.TxTimeFrac) * 1e9) >> 32 + + showtime := time.Unix(int64(secs), nanos) + + return showtime, nil +} + +func UpdateSystemTime(dateTime string) error { + system := runtime.GOOS + if system == "linux" { + if err := cmd.RunDefaultBashCf(`%s date -s "%s"`, cmd.SudoHandleCmd(), dateTime); err != nil { + return fmt.Errorf("update system time failed, %v", err) + } + return nil + } + return fmt.Errorf("the current system architecture %v does not support synchronization", system) +} + +func UpdateSystemTimeZone(timezone string) error { + system := runtime.GOOS + if system == "linux" { + if err := cmd.RunDefaultBashCf(`%s timedatectl set-timezone "%s"`, cmd.SudoHandleCmd(), timezone); err != nil { + return fmt.Errorf("update system time zone failed, %v", err) + } + return nil + } + return fmt.Errorf("the current system architecture %v does not support synchronization", system) +} diff --git a/agent/utils/postgresql/client.go b/agent/utils/postgresql/client.go new file mode 100644 index 0000000..2a65345 --- /dev/null +++ b/agent/utils/postgresql/client.go @@ -0,0 +1,63 @@ +package postgresql + +import ( + "context" + "database/sql" + "errors" + "fmt" + "net/url" + "time" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/postgresql/client" + _ "github.com/jackc/pgx/v5/stdlib" +) + +type PostgresqlClient interface { + Create(info client.CreateInfo) error + CreateUser(info client.CreateInfo, withDeleteDB bool) error + Delete(info client.DeleteInfo) error + ChangePrivileges(info client.Privileges) error + ChangePassword(info client.PasswordChangeInfo) error + + Backup(info client.BackupInfo) error + Recover(info client.RecoverInfo) error + SyncDB() ([]client.SyncDBInfo, error) + Close() +} + +func NewPostgresqlClient(conn client.DBInfo) (PostgresqlClient, error) { + if conn.From == "local" { + connArgs := []string{"exec", "-e", fmt.Sprintf("PGPASSWORD=%s", conn.Password), conn.Address, "psql", "-t", "-U", conn.Username, "-c"} + return client.NewLocal(connArgs, conn.Address, conn.Username, conn.Password, conn.Database), nil + } + escapedUsername := url.QueryEscape(conn.Username) + escapedPassword := url.QueryEscape(conn.Password) + if len(conn.InitialDB) == 0 { + conn.InitialDB = escapedUsername + } + connArgs := fmt.Sprintf("postgres://%s:%s@%s:%d/%s?sslmode=disable", escapedUsername, escapedPassword, conn.Address, conn.Port, conn.InitialDB) + db, err := sql.Open("pgx", connArgs) + if err != nil { + return nil, err + } + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(conn.Timeout)*time.Second) + defer cancel() + if err := db.PingContext(ctx); err != nil { + return nil, err + } + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return nil, buserr.New("ErrExecTimeOut") + } + + return client.NewRemote(client.Remote{ + Client: db, + From: "remote", + Database: conn.Database, + User: conn.Username, + Password: conn.Password, + Address: conn.Address, + Port: conn.Port, + }), nil +} diff --git a/agent/utils/postgresql/client/info.go b/agent/utils/postgresql/client/info.go new file mode 100644 index 0000000..b51b7b0 --- /dev/null +++ b/agent/utils/postgresql/client/info.go @@ -0,0 +1,88 @@ +package client + +import ( + "github.com/1Panel-dev/1Panel/agent/app/task" + _ "github.com/jackc/pgx/v5/stdlib" +) + +type DBInfo struct { + From string `json:"from"` + Database string `json:"database"` + Address string `json:"address"` + Port uint `json:"port"` + InitialDB string `json:"initialDB"` + Username string `json:"userName"` + Password string `json:"password"` + AppKey string `json:"appKey"` + + Timeout uint `json:"timeout"` // second +} + +type CreateInfo struct { + Name string `json:"name"` + Username string `json:"userName"` + Password string `json:"password"` + SuperUser bool `json:"superUser"` + + Timeout uint `json:"timeout"` // second +} + +type Privileges struct { + Username string `json:"userName"` + SuperUser bool `json:"superUser"` + + Timeout uint `json:"timeout"` // second +} + +type DeleteInfo struct { + Name string `json:"name"` + Username string `json:"userName"` + + ForceDelete bool `json:"forceDelete"` + Timeout uint `json:"timeout"` // second +} + +type PasswordChangeInfo struct { + Username string `json:"userName"` + Password string `json:"password"` + + Timeout uint `json:"timeout"` // second +} + +type BackupInfo struct { + Database string `json:"database"` + Name string `json:"name"` + TargetDir string `json:"targetDir"` + FileName string `json:"fileName"` + + Task *task.Task `json:"-"` + Timeout uint `json:"timeout"` // second +} + +type RecoverInfo struct { + Database string `json:"database"` + Name string `json:"name"` + SourceFile string `json:"sourceFile"` + Username string `json:"username"` + + Task *task.Task `json:"-"` + Timeout uint `json:"timeout"` // second +} + +type SyncDBInfo struct { + Name string `json:"name"` + From string `json:"from"` + PostgresqlName string `json:"postgresqlName"` +} +type Status struct { + Uptime string `json:"uptime"` + Version string `json:"version"` + MaxConnections string `json:"max_connections"` + Autovacuum string `json:"autovacuum"` + CurrentConnections string `json:"current_connections"` + HitRatio string `json:"hit_ratio"` + SharedBuffers string `json:"shared_buffers"` + BuffersClean string `json:"buffers_clean"` + MaxwrittenClean string `json:"maxwritten_clean"` + BuffersBackendFsync string `json:"buffers_backend_fsync"` +} diff --git a/agent/utils/postgresql/client/local.go b/agent/utils/postgresql/client/local.go new file mode 100644 index 0000000..7f830c4 --- /dev/null +++ b/agent/utils/postgresql/client/local.go @@ -0,0 +1,236 @@ +package client + +import ( + "bytes" + "compress/gzip" + "context" + "errors" + "fmt" + "os" + "os/exec" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/files" +) + +type Local struct { + PrefixCommand []string + Database string + Username string + Password string + ContainerName string +} + +func NewLocal(command []string, containerName, username, password, database string) *Local { + return &Local{PrefixCommand: command, ContainerName: containerName, Username: username, Password: password, Database: database} +} + +func (r *Local) Create(info CreateInfo) error { + createSql := fmt.Sprintf("CREATE DATABASE \"%s\"", info.Name) + if err := r.ExecSQL(createSql, info.Timeout); err != nil { + if strings.Contains(strings.ToLower(err.Error()), "already exists") { + return buserr.New("ErrDatabaseIsExist") + } + return err + } + + if err := r.CreateUser(info, true); err != nil { + _ = r.ExecSQL(fmt.Sprintf("DROP DATABASE \"%s\"", info.Name), info.Timeout) + return err + } + + return nil +} + +func (r *Local) ChangePrivileges(info Privileges) error { + super := "SUPERUSER" + if !info.SuperUser { + super = "NOSUPERUSER" + } + changeSql := fmt.Sprintf("ALTER USER \"%s\" WITH %s", info.Username, super) + return r.ExecSQL(changeSql, info.Timeout) +} + +func (r *Local) CreateUser(info CreateInfo, withDeleteDB bool) error { + createSql := fmt.Sprintf("CREATE USER \"%s\" WITH PASSWORD '%s'", info.Username, info.Password) + if err := r.ExecSQL(createSql, info.Timeout); err != nil { + if strings.Contains(strings.ToLower(err.Error()), "already exists") { + return buserr.New("ErrUserIsExist") + } + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Username: info.Username, + ForceDelete: true, + Timeout: 300}) + } + return err + } + if info.SuperUser { + if err := r.ChangePrivileges(Privileges{SuperUser: true, Username: info.Username, Timeout: info.Timeout}); err != nil { + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Username: info.Username, + ForceDelete: true, + Timeout: 300}) + } + return err + } + } + grantStr := fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE \"%s\" TO \"%s\"", info.Name, info.Username) + if err := r.ExecSQL(grantStr, info.Timeout); err != nil { + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Username: info.Username, + ForceDelete: true, + Timeout: 300}) + } + return err + } + return nil +} + +func (r *Local) Delete(info DeleteInfo) error { + if len(info.Name) != 0 { + dropSql := fmt.Sprintf("DROP DATABASE \"%s\"", info.Name) + if err := r.ExecSQL(dropSql, info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop database failed, err: %v", err) + } + } + dropSql := fmt.Sprintf("DROP USER \"%s\"", info.Username) + if err := r.ExecSQL(dropSql, info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop user failed, err: %v", err) + } + return nil +} + +func (r *Local) ChangePassword(info PasswordChangeInfo) error { + changeSql := fmt.Sprintf("ALTER USER \"%s\" WITH PASSWORD '%s'", info.Username, info.Password) + if err := r.ExecSQL(changeSql, info.Timeout); err != nil { + return err + } + + return nil +} + +func (r *Local) Backup(info BackupInfo) error { + fileOp := files.NewFileOp() + if !fileOp.Stat(info.TargetDir) { + if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err) + } + } + outfile, err := os.OpenFile(path.Join(info.TargetDir, info.FileName), os.O_RDWR|os.O_CREATE, constant.DirPerm) + if err != nil { + return fmt.Errorf("open file %s failed, err: %v", path.Join(info.TargetDir, info.FileName), err) + } + defer outfile.Close() + global.LOG.Infof("start to pg_dump | gzip > %s.gzip", info.TargetDir+"/"+info.FileName) + + cmd := exec.Command("docker", "exec", "-i", r.ContainerName, + "sh", "-c", + fmt.Sprintf("PGPASSWORD=%s pg_dump -F c -U %s -d %s", r.Password, r.Username, info.Name), + ) + var stderr bytes.Buffer + cmd.Stderr = &stderr + + gzipCmd := exec.Command("gzip", "-cf") + gzipCmd.Stdin, _ = cmd.StdoutPipe() + gzipCmd.Stdout = outfile + _ = gzipCmd.Start() + + if err := cmd.Run(); err != nil { + return fmt.Errorf("handle backup database failed, err: %v", stderr.String()) + } + _ = gzipCmd.Wait() + return nil +} + +func (r *Local) Recover(info RecoverInfo) error { + fi, _ := os.Open(info.SourceFile) + defer fi.Close() + + cmd := exec.Command("docker", "exec", "-i", r.ContainerName, "sh", "-c", + fmt.Sprintf("PGPASSWORD=%s pg_restore -F c -c --if-exists --no-owner -U %s -d %s", r.Password, r.Username, info.Name), + ) + if strings.HasSuffix(info.SourceFile, ".gz") { + gzipFile, err := os.Open(info.SourceFile) + if err != nil { + return err + } + defer gzipFile.Close() + gzipReader, err := gzip.NewReader(gzipFile) + if err != nil { + return err + } + defer gzipReader.Close() + cmd.Stdin = gzipReader + } else { + cmd.Stdin = fi + } + stdout, err := cmd.CombinedOutput() + if err != nil { + if strings.HasPrefix(string(stdout), "ERROR ") { + return errors.New(string(stdout)) + } + return err + } + return nil +} + +func (r *Local) SyncDB() ([]SyncDBInfo, error) { + var datas []SyncDBInfo + lines, err := r.ExecSQLForRows("SELECT datname FROM pg_database", 300) + if err != nil { + return datas, err + } + for _, line := range lines { + itemLine := strings.TrimLeft(line, " ") + if len(itemLine) == 0 || itemLine == "template1" || itemLine == "template0" || itemLine == r.Username { + continue + } + datas = append(datas, SyncDBInfo{Name: itemLine, From: "local", PostgresqlName: r.Database}) + } + return datas, nil +} + +func (r *Local) Close() {} + +func (r *Local) ExecSQL(command string, timeout uint) error { + itemCommand := r.PrefixCommand[:] + itemCommand = append(itemCommand, command) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, "docker", itemCommand...) + stdout, err := cmd.CombinedOutput() + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return buserr.New("ErrExecTimeOut") + } + if err != nil || strings.HasPrefix(string(stdout), "ERROR ") { + return errors.New(string(stdout)) + } + return nil +} + +func (r *Local) ExecSQLForRows(command string, timeout uint) ([]string, error) { + itemCommand := r.PrefixCommand[:] + itemCommand = append(itemCommand, command) + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + cmd := exec.CommandContext(ctx, "docker", itemCommand...) + stdout, err := cmd.CombinedOutput() + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return nil, buserr.New("ErrExecTimeOut") + } + if err != nil || strings.HasPrefix(string(stdout), "ERROR ") { + return nil, errors.New(string(stdout)) + } + return strings.Split(string(stdout), "\n"), nil +} diff --git a/agent/utils/postgresql/client/remote.go b/agent/utils/postgresql/client/remote.go new file mode 100644 index 0000000..b0a026b --- /dev/null +++ b/agent/utils/postgresql/client/remote.go @@ -0,0 +1,269 @@ +package client + +import ( + "bufio" + "context" + "database/sql" + "fmt" + "io" + "os" + "os/exec" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/i18n" + "github.com/docker/docker/api/types/image" + "github.com/pkg/errors" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/utils/docker" + "github.com/1Panel-dev/1Panel/agent/utils/files" + _ "github.com/jackc/pgx/v5/stdlib" +) + +type Remote struct { + Client *sql.DB + From string + Database string + User string + Password string + Address string + Port uint +} + +func NewRemote(db Remote) *Remote { + return &db +} +func (r *Remote) Create(info CreateInfo) error { + createSql := fmt.Sprintf("CREATE DATABASE \"%s\"", info.Name) + if err := r.ExecSQL(createSql, info.Timeout); err != nil { + if strings.Contains(strings.ToLower(err.Error()), "already exists") { + return buserr.New("ErrDatabaseIsExist") + } + return err + } + if err := r.CreateUser(info, true); err != nil { + return err + } + return nil +} + +func (r *Remote) CreateUser(info CreateInfo, withDeleteDB bool) error { + createSql := fmt.Sprintf("CREATE USER \"%s\" WITH PASSWORD '%s'", info.Username, info.Password) + if err := r.ExecSQL(createSql, info.Timeout); err != nil { + if strings.Contains(strings.ToLower(err.Error()), "already exists") { + return buserr.New("ErrUserIsExist") + } + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Username: info.Username, + ForceDelete: true, + Timeout: 300}) + } + return err + } + if info.SuperUser { + if err := r.ChangePrivileges(Privileges{SuperUser: true, Username: info.Username, Timeout: info.Timeout}); err != nil { + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Username: info.Username, + ForceDelete: true, + Timeout: 300}) + } + return err + } + } + grantSql := fmt.Sprintf("GRANT ALL PRIVILEGES ON DATABASE \"%s\" TO \"%s\"", info.Name, info.Username) + if err := r.ExecSQL(grantSql, info.Timeout); err != nil { + if withDeleteDB { + _ = r.Delete(DeleteInfo{ + Name: info.Name, + Username: info.Username, + ForceDelete: true, + Timeout: 300}) + } + return err + } + + return nil +} + +func (r *Remote) Delete(info DeleteInfo) error { + if len(info.Name) != 0 { + dropSql := fmt.Sprintf("DROP DATABASE \"%s\"", info.Name) + if err := r.ExecSQL(dropSql, info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop database failed, err: %v", err) + } + } + dropSql := fmt.Sprintf("DROP USER \"%s\"", info.Username) + if err := r.ExecSQL(dropSql, info.Timeout); err != nil && !info.ForceDelete { + return fmt.Errorf("drop user failed, err: %v", err) + } + return nil +} + +func (r *Remote) ChangePrivileges(info Privileges) error { + super := "SUPERUSER" + if !info.SuperUser { + super = "NOSUPERUSER" + } + return r.ExecSQL(fmt.Sprintf("ALTER USER \"%s\" WITH %s", info.Username, super), info.Timeout) +} + +func (r *Remote) ChangePassword(info PasswordChangeInfo) error { + return r.ExecSQL(fmt.Sprintf("ALTER USER \"%s\" WITH ENCRYPTED PASSWORD '%s'", info.Username, info.Password), info.Timeout) +} + +func (r *Remote) Backup(info BackupInfo) error { + imageTag, err := loadImageTag(info.Database) + if err != nil { + return err + } + info.Task.Log(i18n.GetWithName("RemoteBackup", imageTag)) + fileOp := files.NewFileOp() + if !fileOp.Stat(info.TargetDir) { + if err := os.MkdirAll(info.TargetDir, os.ModePerm); err != nil { + return fmt.Errorf("mkdir %s failed, err: %v", info.TargetDir, err) + } + } + fileNameItem := info.TargetDir + "/" + strings.TrimSuffix(info.FileName, ".gz") + backupCommand := exec.Command("bash", "-c", + fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD='\\''%s'\\'' pg_dump -h %s -p %d --no-owner -Fc -U %s %s' > %s", + imageTag, r.Password, r.Address, r.Port, r.User, info.Name, fileNameItem)) + _ = backupCommand.Run() + b := make([]byte, 5) + n := []byte{80, 71, 68, 77, 80} + handle, err := os.OpenFile(fileNameItem, os.O_RDONLY, os.ModePerm) + if err != nil { + return fmt.Errorf("backup file not found,err:%v", err) + } + defer handle.Close() + _, _ = handle.Read(b) + if string(b) != string(n) { + errBytes, _ := os.ReadFile(fileNameItem) + return fmt.Errorf("backup failed, err: %s", string(errBytes)) + } + + gzipCmd := exec.Command("gzip", fileNameItem) + stdout, err := gzipCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("gzip file %s failed, stdout: %v, err: %v", strings.TrimSuffix(info.FileName, ".gz"), string(stdout), err) + } + return nil +} + +func (r *Remote) Recover(info RecoverInfo) error { + imageTag, err := loadImageTag(info.Database) + if err != nil { + return err + } + info.Task.Log(i18n.GetWithName("RemoteRecover", imageTag)) + fileName := info.SourceFile + if strings.HasSuffix(info.SourceFile, ".sql.gz") { + fileName = strings.TrimSuffix(info.SourceFile, ".gz") + gzipCmd := exec.Command("gunzip", info.SourceFile) + stdout, err := gzipCmd.CombinedOutput() + if err != nil { + return fmt.Errorf("gunzip file %s failed, stdout: %v, err: %v", info.SourceFile, string(stdout), err) + } + defer func() { + gzipCmd := exec.Command("gzip", fileName) + _, _ = gzipCmd.CombinedOutput() + }() + } + recoverCommand := exec.Command("bash", "-c", + fmt.Sprintf("docker run --rm --net=host -i %s /bin/bash -c 'PGPASSWORD='\\''%s'\\'' pg_restore -h %s -p %d --verbose --clean --no-privileges --no-owner -Fc -c --if-exists --no-owner -U %s -d %s --role=%s' < %s", + imageTag, r.Password, r.Address, r.Port, r.User, info.Name, info.Username, fileName)) + pipe, _ := recoverCommand.StdoutPipe() + stderrPipe, _ := recoverCommand.StderrPipe() + defer pipe.Close() + defer stderrPipe.Close() + if err := recoverCommand.Start(); err != nil { + return err + } + reader := bufio.NewReader(pipe) + for { + readString, err := reader.ReadString('\n') + if errors.Is(err, io.EOF) { + break + } + if err != nil { + all, _ := io.ReadAll(stderrPipe) + global.LOG.Errorf("[PostgreSQL] DB:[%s] Recover Error: %s", info.Name, string(all)) + return err + } + global.LOG.Infof("[PostgreSQL] DB:[%s] Restoring: %s", info.Name, readString) + } + + return nil +} + +func (r *Remote) SyncDB() ([]SyncDBInfo, error) { + ctx, cancel := context.WithTimeout(context.Background(), 300*time.Second) + defer cancel() + + var datas []SyncDBInfo + rows, err := r.Client.Query("SELECT datname FROM pg_database;") + if err != nil { + return nil, err + } + defer rows.Close() + for rows.Next() { + var dbName string + if err := rows.Scan(&dbName); err != nil { + continue + } + if len(dbName) == 0 || dbName == "template1" || dbName == "template0" || dbName == r.User { + continue + } + datas = append(datas, SyncDBInfo{Name: dbName, From: r.From, PostgresqlName: r.Database}) + } + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return nil, buserr.New("ErrExecTimeOut") + } + return datas, nil +} + +func (r *Remote) Close() { + _ = r.Client.Close() +} + +func (r *Remote) ExecSQL(command string, timeout uint) error { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + + if _, err := r.Client.ExecContext(ctx, command); err != nil { + return err + } + if errors.Is(ctx.Err(), context.DeadlineExceeded) { + return buserr.New("ErrExecTimeOut") + } + + return nil +} + +func loadImageTag(database string) (string, error) { + var db model.Database + if err := global.DB.Model(&model.Database{}).Where("name = ?", database).First(&db).Error; err != nil { + return "", fmt.Errorf("load database %s info failed, err: %v", database, err) + } + + client, err := docker.NewDockerClient() + if err != nil { + return "", fmt.Errorf("create docker client failed, err: %v", err) + } + defer client.Close() + images, _ := client.ImageList(context.Background(), image.ListOptions{}) + for _, image := range images { + for _, tag := range image.RepoTags { + if strings.HasPrefix(tag, "postgres:"+strings.TrimSuffix(db.Version, "x")) { + return tag, nil + } + } + } + return "postgres:" + strings.ReplaceAll(db.Version, ".x", "-alpine"), nil +} diff --git a/agent/utils/psutil/cpu.go b/agent/utils/psutil/cpu.go new file mode 100644 index 0000000..3f6a26a --- /dev/null +++ b/agent/utils/psutil/cpu.go @@ -0,0 +1,319 @@ +package psutil + +import ( + "os" + "strconv" + "strings" + "sync" + "time" + + "github.com/shirou/gopsutil/v4/cpu" +) + +const ( + resetInterval = 1 * time.Minute + fastInterval = 3 * time.Second +) + +type CPUStat struct { + Idle uint64 + Total uint64 +} + +type CPUDetailedStat struct { + User uint64 + Nice uint64 + System uint64 + Idle uint64 + Iowait uint64 + Irq uint64 + Softirq uint64 + Steal uint64 + Guest uint64 + GuestNice uint64 + Total uint64 +} + +type CPUDetailedPercent struct { + User float64 `json:"user"` + System float64 `json:"system"` + Nice float64 `json:"nice"` + Idle float64 `json:"idle"` + Iowait float64 `json:"iowait"` + Irq float64 `json:"irq"` + Softirq float64 `json:"softirq"` + Steal float64 `json:"steal"` +} + +func (c *CPUDetailedPercent) GetCPUDetailedPercent() []float64 { + return []float64{c.User, c.System, c.Nice, c.Idle, c.Iowait, c.Irq, c.Softirq, c.Steal} +} + +type CPUUsageState struct { + mu sync.Mutex + lastTotalStat *CPUStat + lastPerCPUStat []CPUStat + lastDetailStat *CPUDetailedStat + lastSampleTime time.Time + + cachedTotalUsage float64 + cachedPerCore []float64 + cachedDetailedPercent CPUDetailedPercent +} + +type CPUInfoState struct { + mu sync.RWMutex + initialized bool + cachedInfo []cpu.InfoStat + cachedPhysCores int + cachedLogicCores int +} + +func (c *CPUUsageState) GetCPUUsage() (float64, []float64, []float64) { + c.mu.Lock() + + now := time.Now() + + if !c.lastSampleTime.IsZero() && now.Sub(c.lastSampleTime) < fastInterval { + result := c.cachedTotalUsage + perCore := c.cachedPerCore + detailed := c.cachedDetailedPercent + c.mu.Unlock() + return result, perCore, detailed.GetCPUDetailedPercent() + } + + needReset := c.lastSampleTime.IsZero() || now.Sub(c.lastSampleTime) >= resetInterval + c.mu.Unlock() + + if needReset { + firstTotal, firstDetail, firstPer := readAllCPUStat() + time.Sleep(100 * time.Millisecond) + secondTotal, secondDetail, secondPer := readAllCPUStat() + + totalUsage := calcCPUPercent(firstTotal, secondTotal) + detailedPercent := calcCPUDetailedPercent(firstDetail, secondDetail) + + perCore := make([]float64, len(secondPer)) + for i := range secondPer { + perCore[i] = calcCPUPercent(firstPer[i], secondPer[i]) + } + + c.mu.Lock() + c.cachedTotalUsage = totalUsage + c.cachedPerCore = perCore + c.cachedDetailedPercent = detailedPercent + c.lastTotalStat = &secondTotal + c.lastDetailStat = &secondDetail + c.lastPerCPUStat = secondPer + c.lastSampleTime = time.Now() + c.mu.Unlock() + + return totalUsage, perCore, detailedPercent.GetCPUDetailedPercent() + } + + curTotal, curDetail, curPer := readAllCPUStat() + + c.mu.Lock() + defer c.mu.Unlock() + + totalUsage := calcCPUPercent(*c.lastTotalStat, curTotal) + detailedPercent := calcCPUDetailedPercent(*c.lastDetailStat, curDetail) + + if len(c.cachedPerCore) != len(curPer) { + c.cachedPerCore = make([]float64, len(curPer)) + } + for i := range curPer { + c.cachedPerCore[i] = calcCPUPercent(c.lastPerCPUStat[i], curPer[i]) + } + + c.cachedTotalUsage = totalUsage + c.cachedPerCore = c.cachedPerCore + c.cachedDetailedPercent = detailedPercent + c.lastTotalStat = &curTotal + c.lastDetailStat = &curDetail + c.lastPerCPUStat = curPer + c.lastSampleTime = time.Now() + + return totalUsage, c.cachedPerCore, detailedPercent.GetCPUDetailedPercent() +} + +func (c *CPUUsageState) NumCPU() int { + c.mu.Lock() + defer c.mu.Unlock() + + return len(c.cachedPerCore) +} + +func (c *CPUInfoState) GetCPUInfo(forceRefresh bool) ([]cpu.InfoStat, error) { + c.mu.RLock() + if c.initialized && c.cachedInfo != nil && !forceRefresh { + defer c.mu.RUnlock() + return c.cachedInfo, nil + } + c.mu.RUnlock() + + info, err := cpu.Info() + if err != nil { + return nil, err + } + + c.mu.Lock() + c.cachedInfo = info + c.initialized = true + c.mu.Unlock() + + return info, nil +} + +func (c *CPUInfoState) GetPhysicalCores(forceRefresh bool) (int, error) { + c.mu.RLock() + if c.initialized && c.cachedPhysCores > 0 && !forceRefresh { + defer c.mu.RUnlock() + return c.cachedPhysCores, nil + } + c.mu.RUnlock() + + cores, err := cpu.Counts(false) + if err != nil { + return 0, err + } + + c.mu.Lock() + c.cachedPhysCores = cores + c.initialized = true + c.mu.Unlock() + + return cores, nil +} + +func (c *CPUInfoState) GetLogicalCores(forceRefresh bool) (int, error) { + c.mu.RLock() + if c.initialized && c.cachedLogicCores > 0 && !forceRefresh { + defer c.mu.RUnlock() + return c.cachedLogicCores, nil + } + c.mu.RUnlock() + + cores, err := cpu.Counts(true) + if err != nil { + return 0, err + } + + c.mu.Lock() + c.cachedLogicCores = cores + c.initialized = true + c.mu.Unlock() + + return cores, nil +} + +func readProcStat() ([]byte, error) { + return os.ReadFile("/proc/stat") +} + +func parseCPUFields(line string) []uint64 { + fields := strings.Fields(line) + if len(fields) <= 1 { + return nil + } + fields = fields[1:] + + nums := make([]uint64, len(fields)) + for i, f := range fields { + v, _ := strconv.ParseUint(f, 10, 64) + nums[i] = v + } + return nums +} + +func calcIdleAndTotal(nums []uint64) (idle, total uint64) { + if len(nums) < 5 { + return 0, 0 + } + idle = nums[3] + nums[4] + for _, v := range nums { + total += v + } + return +} + +func readAllCPUStat() (CPUStat, CPUDetailedStat, []CPUStat) { + data, err := readProcStat() + if err != nil { + return CPUStat{}, CPUDetailedStat{}, nil + } + + lines := strings.Split(string(data), "\n") + if len(lines) == 0 { + return CPUStat{}, CPUDetailedStat{}, nil + } + + firstLine := lines[0] + nums := parseCPUFields(firstLine) + + idle, total := calcIdleAndTotal(nums) + cpuStat := CPUStat{Idle: idle, Total: total} + + if len(nums) < 10 { + padded := make([]uint64, 10) + copy(padded, nums) + nums = padded + } + detailedStat := CPUDetailedStat{ + User: nums[0], + Nice: nums[1], + System: nums[2], + Idle: nums[3], + Iowait: nums[4], + Irq: nums[5], + Softirq: nums[6], + Steal: nums[7], + Guest: nums[8], + GuestNice: nums[9], + } + detailedStat.Total = detailedStat.User + detailedStat.Nice + detailedStat.System + + detailedStat.Idle + detailedStat.Iowait + detailedStat.Irq + detailedStat.Softirq + detailedStat.Steal + + var perCPUStats []CPUStat + for _, line := range lines[1:] { + if !strings.HasPrefix(line, "cpu") { + continue + } + if len(line) < 4 || line[3] < '0' || line[3] > '9' { + continue + } + + perNums := parseCPUFields(line) + perIdle, perTotal := calcIdleAndTotal(perNums) + perCPUStats = append(perCPUStats, CPUStat{Idle: perIdle, Total: perTotal}) + } + + return cpuStat, detailedStat, perCPUStats +} + +func calcCPUPercent(prev, cur CPUStat) float64 { + deltaIdle := float64(cur.Idle - prev.Idle) + deltaTotal := float64(cur.Total - prev.Total) + if deltaTotal <= 0 { + return 0 + } + return (1 - deltaIdle/deltaTotal) * 100 +} + +func calcCPUDetailedPercent(prev, cur CPUDetailedStat) CPUDetailedPercent { + deltaTotal := float64(cur.Total - prev.Total) + if deltaTotal <= 0 { + return CPUDetailedPercent{Idle: 100} + } + + return CPUDetailedPercent{ + User: float64(cur.User-prev.User) / deltaTotal * 100, + System: float64(cur.System-prev.System) / deltaTotal * 100, + Nice: float64(cur.Nice-prev.Nice) / deltaTotal * 100, + Idle: float64(cur.Idle-prev.Idle) / deltaTotal * 100, + Iowait: float64(cur.Iowait-prev.Iowait) / deltaTotal * 100, + Irq: float64(cur.Irq-prev.Irq) / deltaTotal * 100, + Softirq: float64(cur.Softirq-prev.Softirq) / deltaTotal * 100, + Steal: float64(cur.Steal-prev.Steal) / deltaTotal * 100, + } +} diff --git a/agent/utils/psutil/disk.go b/agent/utils/psutil/disk.go new file mode 100644 index 0000000..ddca1ca --- /dev/null +++ b/agent/utils/psutil/disk.go @@ -0,0 +1,92 @@ +package psutil + +import ( + "sync" + "time" + + "github.com/shirou/gopsutil/v4/disk" +) + +const ( + diskUsageCacheInterval = 30 * time.Second + diskPartitionCacheInterval = 10 * time.Minute +) + +type DiskUsageEntry struct { + lastSampleTime time.Time + cachedUsage *disk.UsageStat +} + +type DiskState struct { + usageMu sync.RWMutex + usageCache map[string]*DiskUsageEntry + + partitionMu sync.RWMutex + lastPartitionTime time.Time + cachedPartitions []disk.PartitionStat +} + +func (d *DiskState) GetUsage(path string, forceRefresh bool) (*disk.UsageStat, error) { + d.usageMu.RLock() + if entry, ok := d.usageCache[path]; ok { + if time.Since(entry.lastSampleTime) < diskUsageCacheInterval && !forceRefresh { + defer d.usageMu.RUnlock() + return entry.cachedUsage, nil + } + } + d.usageMu.RUnlock() + + usage, err := disk.Usage(path) + if err != nil { + return nil, err + } + + d.usageMu.Lock() + if d.usageCache == nil { + d.usageCache = make(map[string]*DiskUsageEntry) + } + d.usageCache[path] = &DiskUsageEntry{ + lastSampleTime: time.Now(), + cachedUsage: usage, + } + d.usageMu.Unlock() + + return usage, nil +} + +func (d *DiskState) GetPartitions(all bool, forceRefresh bool) ([]disk.PartitionStat, error) { + d.partitionMu.RLock() + if d.cachedPartitions != nil && time.Since(d.lastPartitionTime) < diskPartitionCacheInterval && !forceRefresh { + defer d.partitionMu.RUnlock() + return d.cachedPartitions, nil + } + d.partitionMu.RUnlock() + + partitions, err := disk.Partitions(all) + if err != nil { + return nil, err + } + + d.partitionMu.Lock() + d.cachedPartitions = partitions + d.lastPartitionTime = time.Now() + d.partitionMu.Unlock() + + return partitions, nil +} + +func (d *DiskState) ClearUsageCache(path string) { + d.usageMu.Lock() + delete(d.usageCache, path) + d.usageMu.Unlock() +} + +func (d *DiskState) ClearAllCache() { + d.usageMu.Lock() + d.usageCache = make(map[string]*DiskUsageEntry) + d.usageMu.Unlock() + + d.partitionMu.Lock() + d.cachedPartitions = nil + d.partitionMu.Unlock() +} diff --git a/agent/utils/psutil/global.go b/agent/utils/psutil/global.go new file mode 100644 index 0000000..1b60498 --- /dev/null +++ b/agent/utils/psutil/global.go @@ -0,0 +1,6 @@ +package psutil + +var CPU = &CPUUsageState{} +var CPUInfo = &CPUInfoState{} +var HOST = &HostInfoState{} +var DISK = &DiskState{} diff --git a/agent/utils/psutil/host.go b/agent/utils/psutil/host.go new file mode 100644 index 0000000..20ee730 --- /dev/null +++ b/agent/utils/psutil/host.go @@ -0,0 +1,91 @@ +package psutil + +import ( + "fmt" + "os" + "strings" + "sync" + "time" + + "github.com/shirou/gopsutil/v4/host" +) + +const hostRefreshInterval = 4 * time.Hour + +type HostInfoState struct { + mu sync.RWMutex + lastSampleTime time.Time + + cachedInfo *host.InfoStat + cachedDistro string +} + +func (h *HostInfoState) GetHostInfo(forceRefresh bool) (*host.InfoStat, error) { + h.mu.RLock() + if h.cachedInfo != nil && time.Since(h.lastSampleTime) < hostRefreshInterval && !forceRefresh { + defer h.mu.RUnlock() + return h.cachedInfo, nil + } + h.mu.RUnlock() + + hostInfo, err := host.Info() + if err != nil { + return nil, err + } + + h.mu.Lock() + h.cachedInfo = hostInfo + h.lastSampleTime = time.Now() + h.mu.Unlock() + + return hostInfo, nil +} + +func (h *HostInfoState) GetDistro() string { + if h.cachedDistro == "" { + h.cachedDistro = detectLinuxDistro() + } + return h.cachedDistro +} + +func detectLinuxDistro() string { + distroFiles := []string{ + "/etc/os-release", + "/usr/lib/os-release", + } + + var targetFile string + for _, f := range distroFiles { + if _, err := os.Stat(f); err == nil { + targetFile = f + break + } + } + + if targetFile != "" { + data, err := os.ReadFile(targetFile) + if err == nil { + content := string(data) + for _, line := range strings.Split(content, "\n") { + idx := strings.Index(line, "=") + if idx == -1 { + continue + } + key := line[:idx] + if key == "PRETTY_NAME" { + d := strings.Trim(line[idx+1:], "\"") + if strings.Contains(d, "(") && strings.Contains(d, ")") { + d = d[:strings.LastIndex(d, "(")] + } + return strings.TrimSpace(d) + } + } + } + } + + if osInfo, err := host.Info(); err == nil { + return fmt.Sprintf("%s %s", osInfo.Platform, osInfo.PlatformVersion) + } + + return "Linux" +} diff --git a/agent/utils/re/re.go b/agent/utils/re/re.go new file mode 100644 index 0000000..680170f --- /dev/null +++ b/agent/utils/re/re.go @@ -0,0 +1,82 @@ +package re + +import ( + "fmt" + "regexp" +) + +const ( + NumberAlphaPattern = `(\d+)([A-Za-z]+)` + ComposeDisallowedCharsPattern = `[^a-z0-9_-]+` + ComposeEnvVarPattern = `\$\{([^}]+)\}` + DiskKeyValuePattern = `([A-Za-z0-9_]+)=("([^"\\]|\\.)*"|[^ \t]+)` + ValidatorNamePattern = `^[a-zA-Z\p{Han}]{1}[a-zA-Z0-9_\p{Han}]{0,30}$` + ValidatorIPPattern = `^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$` + DomainPattern = `^([\w\p{Han}\-\*]{1,100}\.){1,10}([\w\p{Han}\-]{1,24}|[\w\p{Han}\-]{1,24}\.[\w\p{Han}\-]{1,24})(:\d{1,5})?$` + NginxServerNamePattern = `^(?:\*|[\w\p{Han}-]{1,63})(?:\.(?:\*|[\w\p{Han}-]{1,63}))*$` + ProxyCacheZonePattern = `keys_zone=proxy_cache_zone_of_[\w.]+:(\d+)([kmgt]?)` + ProxyCacheMaxSizePattern = `max_size=([0-9.]+)([kmgt]?)` + ProxyCacheMaxSizeValidationPattern = `max_size=\d+(\.\d+)?[kmgt]?` + ProxyCacheInactivePattern = `inactive=(\d+)([smhd])` + NumberWordPattern = `^(\d+)(\w+)$` + TrailingDigitsPattern = `_(\d+)$` + AlertIPPattern = `from\s+([0-9.]+)\s+port\s+(\d+)` + CosDualStackPattern = `.*cos-dualstack\..*` + VersionPattern = `v(\d+\.\d+\.\d+)` + PhpAssignmentPattern = `^\s*([a-z_]+)\s*=\s*(.*)$` + DurationWithOptionalUnitPattern = `^(\d+)([smhdw]?)$` + MysqlGroupPattern = `\[*\]` + AnsiEscapePattern = "\x1b\\[[0-9;?]*[A-Za-z]|\x1b=|\x1b>" + RecycleBinFilePattern = `_1p_file_1p_(.+)_p_(\d+)_(\d+)` +) + +var regexMap = make(map[string]*regexp.Regexp) + +// InitRegex compiles all regex patterns and stores them in the map. +// This function should be called once at program startup. +func Init() { + patterns := []string{ + NumberAlphaPattern, + ComposeDisallowedCharsPattern, + ComposeEnvVarPattern, + DiskKeyValuePattern, + ValidatorNamePattern, + ValidatorIPPattern, + DomainPattern, + NginxServerNamePattern, + ProxyCacheZonePattern, + ProxyCacheMaxSizePattern, + ProxyCacheMaxSizeValidationPattern, + ProxyCacheInactivePattern, + NumberWordPattern, + TrailingDigitsPattern, + AlertIPPattern, + CosDualStackPattern, + VersionPattern, + PhpAssignmentPattern, + DurationWithOptionalUnitPattern, + MysqlGroupPattern, + AnsiEscapePattern, + RecycleBinFilePattern, + } + + for _, pattern := range patterns { + regexMap[pattern] = regexp.MustCompile(pattern) + } +} + +// GetRegex retrieves a compiled regex by its pattern string. +// Panics if the pattern is not found in the map. +func GetRegex(pattern string) *regexp.Regexp { + regex, exists := regexMap[pattern] + if !exists { + panic(fmt.Sprintf("regex pattern not found: %s", pattern)) + } + return regex +} + +// RegisterRegex registers a regex pattern and stores it in the map. +// This function should be called once at program startup. +func RegisterRegex(pattern string) { + regexMap[pattern] = regexp.MustCompile(pattern) +} diff --git a/agent/utils/redis/redis.go b/agent/utils/redis/redis.go new file mode 100644 index 0000000..bd8234b --- /dev/null +++ b/agent/utils/redis/redis.go @@ -0,0 +1,31 @@ +package redis + +import ( + "fmt" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/go-redis/redis" +) + +type DBInfo struct { + Address string `json:"address"` + Port uint `json:"port"` + Password string `json:"password"` + Timeout uint `json:"timeout"` +} + +func NewRedisClient(conn DBInfo) (*redis.Client, error) { + client := redis.NewClient(&redis.Options{ + Addr: fmt.Sprintf("%s:%v", conn.Address, conn.Port), + Password: conn.Password, + DB: 0, + DialTimeout: time.Duration(conn.Timeout) * time.Second, + }) + + if _, err := client.Ping().Result(); err != nil { + global.LOG.Errorf("check redis conn failed, err: %v", err) + return client, err + } + return client, nil +} diff --git a/agent/utils/req_helper/core.go b/agent/utils/req_helper/core.go new file mode 100644 index 0000000..800b229 --- /dev/null +++ b/agent/utils/req_helper/core.go @@ -0,0 +1,43 @@ +package req_helper + +import ( + "bytes" + "crypto/tls" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "net/http" +) + +func PostLocalCore(url string) error { + var serverPortSetting model.Setting + _ = global.CoreDB.Model(&model.Setting{}).Where("key = ?", "ServerPort").First(&serverPortSetting).Error + var sslStatusSetting model.Setting + _ = global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SSL").First(&sslStatusSetting).Error + + var prefix string + if sslStatusSetting.Value == "Disable" { + prefix = "http" + } else { + prefix = "https" + } + + reloadURL := fmt.Sprintf("%s://127.0.0.1:%s/api/v2%s", prefix, serverPortSetting.Value, url) + req, err := http.NewRequest("POST", reloadURL, bytes.NewBuffer([]byte{})) + if err != nil { + return err + } + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + client := &http.Client{ + Transport: tr, + } + defer client.CloseIdleConnections() + resp, err := client.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + return nil +} diff --git a/agent/utils/req_helper/request.go b/agent/utils/req_helper/request.go new file mode 100644 index 0000000..60f6606 --- /dev/null +++ b/agent/utils/req_helper/request.go @@ -0,0 +1,122 @@ +package req_helper + +import ( + "context" + "crypto/tls" + "errors" + "io" + "net" + "net/http" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/utils/xpack" + + "github.com/1Panel-dev/1Panel/agent/buserr" + "github.com/1Panel-dev/1Panel/agent/global" +) + +func HandleGet(url string) (*http.Response, error) { + client := &http.Client{ + Timeout: time.Second * 300, + } + client.Transport = loadRequestTransport() + defer client.CloseIdleConnections() + + req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil) + if err != nil { + return nil, buserr.WithMap("ErrCreateHttpClient", map[string]interface{}{"err": err.Error()}, err) + } + + resp, err := client.Do(req) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + return nil, buserr.WithMap("ErrHttpReqTimeOut", map[string]interface{}{"err": err.Error()}, err) + } else { + if strings.Contains(err.Error(), "no such host") { + return nil, buserr.New("ErrNoSuchHost") + } + return nil, buserr.WithMap("ErrHttpReqFailed", map[string]interface{}{"err": err.Error()}, err) + } + } + if resp.StatusCode == 404 { + return nil, buserr.New("ErrHttpReqNotFound") + } + + return resp, nil +} + +func HandleRequest(url, method string, timeout int) (int, []byte, error) { + transport := xpack.LoadRequestTransport() + client := http.Client{Timeout: time.Duration(timeout) * time.Second, Transport: transport} + return HandleRequestWithClient(&client, url, method, timeout) +} + +func HandleRequestWithClient(client *http.Client, url, method string, timeout int) (int, []byte, error) { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("handle request failed, error message: %v", r) + return + } + }() + + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + request, err := http.NewRequestWithContext(ctx, method, url, nil) + if err != nil { + return 0, nil, err + } + request.Header.Set("Content-Type", "application/json") + resp, err := client.Do(request) + if err != nil { + return 0, nil, err + } + if resp.StatusCode != http.StatusOK { + return 0, nil, errors.New(resp.Status) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, nil, err + } + defer resp.Body.Close() + + return resp.StatusCode, body, nil +} + +func RequestFile(url, method string, timeout int) (io.ReadCloser, context.CancelFunc, error) { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("handle request failed, error message: %v", r) + return + } + }() + transport := xpack.LoadRequestTransport() + client := http.Client{Timeout: time.Duration(timeout) * time.Second, Transport: transport} + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + request, err := http.NewRequestWithContext(ctx, method, url, nil) + if err != nil { + return nil, cancel, err + } + request.Header.Set("Content-Type", "application/json") + resp, err := client.Do(request) + if err != nil { + return nil, cancel, err + } + if resp.StatusCode != http.StatusOK { + return nil, cancel, errors.New(resp.Status) + } + return resp.Body, cancel, nil +} + +func loadRequestTransport() *http.Transport { + return &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DialContext: (&net.Dialer{ + Timeout: 60 * time.Second, + KeepAlive: 60 * time.Second, + }).DialContext, + TLSHandshakeTimeout: 5 * time.Second, + ResponseHeaderTimeout: 10 * time.Second, + IdleConnTimeout: 15 * time.Second, + } +} diff --git a/agent/utils/ssh/ssh.go b/agent/utils/ssh/ssh.go new file mode 100644 index 0000000..d00f700 --- /dev/null +++ b/agent/utils/ssh/ssh.go @@ -0,0 +1,68 @@ +package ssh + +import ( + "fmt" + "net" + "strings" + "time" + + gossh "golang.org/x/crypto/ssh" +) + +type ConnInfo struct { + User string `json:"user"` + Addr string `json:"addr"` + Port int `json:"port"` + AuthMode string `json:"authMode"` + Password string `json:"password"` + PrivateKey []byte `json:"privateKey"` + PassPhrase []byte `json:"passPhrase"` + DialTimeOut time.Duration `json:"dialTimeOut"` +} + +type SSHClient struct { + Client *gossh.Client `json:"client"` +} + +func NewClient(c ConnInfo) (*SSHClient, error) { + config := &gossh.ClientConfig{} + config.SetDefaults() + + addr := net.JoinHostPort(c.Addr, fmt.Sprintf("%d", c.Port)) + config.User = c.User + if c.AuthMode == "password" { + config.Auth = []gossh.AuthMethod{gossh.Password(c.Password)} + } else { + signer, err := makePrivateKeySigner(c.PrivateKey, c.PassPhrase) + if err != nil { + return nil, err + } + config.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)} + } + if c.DialTimeOut == 0 { + c.DialTimeOut = 5 * time.Second + } + config.Timeout = c.DialTimeOut + + config.HostKeyCallback = gossh.InsecureIgnoreHostKey() + proto := "tcp" + if strings.Contains(c.Addr, ":") { + proto = "tcp6" + } + client, err := gossh.Dial(proto, addr, config) + if nil != err { + return nil, err + } + return &SSHClient{Client: client}, nil +} + +func (c *SSHClient) Close() { + _ = c.Client.Close() +} + +func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) { + if len(passPhrase) != 0 { + return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase) + } + return gossh.ParsePrivateKey(privateKey) +} diff --git a/agent/utils/ssl/acme.go b/agent/utils/ssl/acme.go new file mode 100644 index 0000000..fcb63c8 --- /dev/null +++ b/agent/utils/ssl/acme.go @@ -0,0 +1,384 @@ +package ssl + +import ( + "crypto" + "crypto/ecdsa" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/json" + "encoding/pem" + "errors" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/buserr" + "golang.org/x/crypto/acme" + "io" + "net" + "net/http" + "net/url" + "os" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/go-acme/lego/v4/certcrypto" + "github.com/go-acme/lego/v4/lego" + "github.com/go-acme/lego/v4/registration" +) + +var Orders = make(map[uint]*acme.Order) + +type domainError struct { + Domain string + Error error +} + +type zeroSSLRes struct { + Success bool `json:"success"` + EabKid string `json:"eab_kid"` + EabHmacKey string `json:"eab_hmac_key"` +} + +type KeyType = certcrypto.KeyType + +const ( + KeyEC256 = certcrypto.EC256 + KeyEC384 = certcrypto.EC384 + KeyRSA2048 = certcrypto.RSA2048 + KeyRSA3072 = certcrypto.RSA3072 + KeyRSA4096 = certcrypto.RSA4096 +) + +type AcmeUser struct { + Email string + Registration *registration.Resource + Key crypto.PrivateKey +} + +func (u *AcmeUser) GetEmail() string { + return u.Email +} + +func (u *AcmeUser) GetRegistration() *registration.Resource { + return u.Registration +} +func (u *AcmeUser) GetPrivateKey() crypto.PrivateKey { + return u.Key +} + +func GetPrivateKey(priKey crypto.PrivateKey, keyType KeyType) ([]byte, error) { + var ( + marshal []byte + block *pem.Block + err error + ) + + switch keyType { + case KeyEC256, KeyEC384: + key := priKey.(*ecdsa.PrivateKey) + marshal, err = x509.MarshalECPrivateKey(key) + if err != nil { + return nil, err + } + block = &pem.Block{ + Type: "EC PRIVATE KEY", + Bytes: marshal, + } + case KeyRSA2048, KeyRSA3072, KeyRSA4096: + key := priKey.(*rsa.PrivateKey) + marshal = x509.MarshalPKCS1PrivateKey(key) + block = &pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: marshal, + } + } + + return pem.EncodeToMemory(block), nil +} + +func NewRegisterClient(acmeAccount *model.WebsiteAcmeAccount, proxy *dto.SystemProxy) (*AcmeClient, error) { + var ( + priKey crypto.PrivateKey + err error + ) + + if acmeAccount.PrivateKey != "" { + switch KeyType(acmeAccount.KeyType) { + case KeyEC256, KeyEC384: + block, _ := pem.Decode([]byte(acmeAccount.PrivateKey)) + priKey, err = x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, err + } + case KeyRSA2048, KeyRSA3072, KeyRSA4096: + block, _ := pem.Decode([]byte(acmeAccount.PrivateKey)) + priKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + } + + } else { + priKey, err = certcrypto.GeneratePrivateKey(KeyType(acmeAccount.KeyType)) + if err != nil { + return nil, err + } + } + + myUser := &AcmeUser{ + Email: acmeAccount.Email, + Key: priKey, + } + config := NewConfigWithProxy(myUser, acmeAccount.Type, acmeAccount.CaDirURL, proxy) + client, err := lego.NewClient(config) + if err != nil { + return nil, err + } + var reg *registration.Resource + if acmeAccount.Type == "zerossl" || acmeAccount.Type == "google" || acmeAccount.Type == "freessl" || (acmeAccount.Type == "custom" && acmeAccount.UseEAB) { + if acmeAccount.Type == "zerossl" { + var res *zeroSSLRes + res, err = getZeroSSLEabCredentials(acmeAccount.Email) + if err != nil { + return nil, err + } + if res.Success { + acmeAccount.EabKid = res.EabKid + acmeAccount.EabHmacKey = res.EabHmacKey + } else { + return nil, fmt.Errorf("get zero ssl eab credentials failed") + } + } + + eabOptions := registration.RegisterEABOptions{ + TermsOfServiceAgreed: true, + Kid: acmeAccount.EabKid, + HmacEncoded: acmeAccount.EabHmacKey, + } + reg, err = client.Registration.RegisterWithExternalAccountBinding(eabOptions) + if err != nil { + return nil, err + } + } else { + reg, err = client.Registration.Register(registration.RegisterOptions{TermsOfServiceAgreed: true}) + if err != nil { + return nil, err + } + } + myUser.Registration = reg + + acmeClient := &AcmeClient{ + User: myUser, + Client: client, + Config: config, + } + + return acmeClient, nil +} + +func getCaDirURL(accountType, customCaURL string) string { + var caDirURL string + switch accountType { + case "letsencrypt": + caDirURL = "https://acme-v02.api.letsencrypt.org/directory" + caDirURL = "https://acme-staging-v02.api.letsencrypt.org/directory" + case "zerossl": + caDirURL = "https://acme.zerossl.com/v2/DV90" + case "buypass": + caDirURL = "https://api.buypass.com/acme/directory" + case "google": + caDirURL = "https://dv.acme-v02.api.pki.goog/directory" + case "freessl": + caDirURL = "https://acmepro.freessl.cn/v2/DV" + case "custom": + caDirURL = customCaURL + } + return caDirURL +} + +func NewConfigWithProxy(user registration.User, accountType, customCaURL string, systemProxy *dto.SystemProxy) *lego.Config { + var ( + caDirURL string + proxyURL string + proxyUser string + proxyPassword string + ) + caDirURL = getCaDirURL(accountType, customCaURL) + if systemProxy != nil { + proxyURL = fmt.Sprintf("%s://%s:%s", systemProxy.Type, systemProxy.URL, systemProxy.Port) + proxyUser = systemProxy.User + proxyPassword = systemProxy.Password + } + return &lego.Config{ + CADirURL: caDirURL, + UserAgent: "1Panel", + User: user, + HTTPClient: createHTTPClientWithProxy(proxyURL, proxyUser, proxyPassword), + Certificate: lego.CertificateConfig{ + KeyType: certcrypto.RSA2048, + Timeout: 60 * time.Second, + }, + } +} + +func initCertPool() *x509.CertPool { + customCACertsPath := os.Getenv("LEGO_CA_CERTIFICATES") + if customCACertsPath == "" { + return nil + } + + useSystemCertPool, _ := strconv.ParseBool(os.Getenv("LEGO_CA_SYSTEM_CERT_POOL")) + + caCerts := strings.Split(customCACertsPath, string(os.PathListSeparator)) + + certPool, err := lego.CreateCertPool(caCerts, useSystemCertPool) + if err != nil { + panic(fmt.Sprintf("create certificates pool: %v", err)) + } + + return certPool +} + +func createHTTPClientWithProxy(proxyURL, username, password string) *http.Client { + var proxyFunc func(*http.Request) (*url.URL, error) + if proxyURL != "" { + parsedURL, err := url.Parse(proxyURL) + if err != nil { + proxyFunc = http.ProxyFromEnvironment + } else { + if username != "" && password != "" { + parsedURL.User = url.UserPassword(username, password) + } else if username != "" { + parsedURL.User = url.User(username) + } + proxyFunc = http.ProxyURL(parsedURL) + } + } else { + proxyFunc = func(_ *http.Request) (*url.URL, error) { + return nil, nil + } + } + + return &http.Client{ + Timeout: 2 * time.Minute, + Transport: &http.Transport{ + Proxy: proxyFunc, + DialContext: (&net.Dialer{ + Timeout: 60 * time.Second, + KeepAlive: 60 * time.Second, + }).DialContext, + TLSHandshakeTimeout: 60 * time.Second, + ResponseHeaderTimeout: 60 * time.Second, + TLSClientConfig: &tls.Config{ + ServerName: os.Getenv("LEGO_CA_SERVER_NAME"), + RootCAs: initCertPool(), + }, + }, + } +} + +func getZeroSSLEabCredentials(email string) (*zeroSSLRes, error) { + baseURL := "https://api.zerossl.com/acme/eab-credentials-email" + params := url.Values{} + params.Add("email", email) + requestURL := fmt.Sprintf("%s?%s", baseURL, params.Encode()) + + req, err := http.NewRequest("POST", requestURL, nil) + if err != nil { + return nil, err + } + + req.Header.Set("Content-Type", "application/json") + + client := &http.Client{} + defer client.CloseIdleConnections() + resp, err := client.Do(req) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, err + } + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("server returned non-200 status: %d %s", resp.StatusCode, http.StatusText(resp.StatusCode)) + } + + var result zeroSSLRes + err = json.Unmarshal(body, &result) + if err != nil { + return nil, err + } + + return &result, nil +} + +func GetPrivateKeyByType(keyType, sslPrivateKey string) (crypto.PrivateKey, error) { + var ( + privateKey crypto.PrivateKey + err error + ) + kType := KeyType(keyType) + if sslPrivateKey == "" { + privateKey, err = certcrypto.GeneratePrivateKey(kType) + if err != nil { + return nil, err + } + return privateKey, nil + } + block, _ := pem.Decode([]byte(sslPrivateKey)) + if block == nil { + return nil, buserr.New("invalid PEM block") + } + var privKey crypto.PrivateKey + switch kType { + case certcrypto.EC256, certcrypto.EC384: + privKey, err = x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, err + } + case certcrypto.RSA2048, certcrypto.RSA3072, certcrypto.RSA4096: + privKey, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + } + privateKey = privKey + return privateKey, nil +} + +func getWebsiteSSLDomains(websiteSSL *model.WebsiteSSL) []string { + domains := []string{websiteSSL.PrimaryDomain} + if websiteSSL.Domains != "" { + domains = append(domains, strings.Split(websiteSSL.Domains, ",")...) + } + return domains +} + +const ( + maxRetryAttempts = 3 + retryDelayOn503 = 30 * time.Second +) + +// isHTTP503Error checks if an error is an HTTP 503 Service Unavailable error +func isHTTP503Error(err error) bool { + if err == nil { + return false + } + + // Check for golang.org/x/crypto/acme.Error (used in manual_client.go) + var acmeErr *acme.Error + if errors.As(err, &acmeErr) { + return acmeErr.StatusCode == http.StatusServiceUnavailable + } + + // Check error message for 503 (fallback for lego library errors) + errMsg := err.Error() + return strings.Contains(errMsg, "503") || + strings.Contains(errMsg, "Service busy") +} diff --git a/agent/utils/ssl/client.go b/agent/utils/ssl/client.go new file mode 100644 index 0000000..9d349fa --- /dev/null +++ b/agent/utils/ssl/client.go @@ -0,0 +1,167 @@ +package ssl + +import ( + "crypto" + "crypto/rand" + "crypto/x509" + "crypto/x509/pkix" + "net" + "os" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/go-acme/lego/v4/certificate" + "github.com/go-acme/lego/v4/challenge/dns01" + "github.com/go-acme/lego/v4/lego" + "github.com/go-acme/lego/v4/providers/http/webroot" + "github.com/pkg/errors" +) + +type AcmeClientOption func(*AcmeClientOptions) + +type AcmeClientOptions struct { + SystemProxy *dto.SystemProxy +} + +type AcmeClient struct { + Config *lego.Config + Client *lego.Client + User *AcmeUser + ProxyURL string +} + +func NewAcmeClient(acmeAccount *model.WebsiteAcmeAccount, systemProxy *dto.SystemProxy) (*AcmeClient, error) { + if acmeAccount.Email == "" { + return nil, errors.New("email can not blank") + } + + client, err := NewRegisterClient(acmeAccount, systemProxy) + if err != nil { + return nil, err + } + return client, nil +} + +func (c *AcmeClient) UseDns(dnsType DnsType, params string, websiteSSL model.WebsiteSSL) error { + p, err := getDNSProviderConfig(dnsType, params) + if err != nil { + return err + } + var nameservers []string + if websiteSSL.Nameserver1 != "" { + nameservers = append(nameservers, websiteSSL.Nameserver1) + } + if websiteSSL.Nameserver2 != "" { + nameservers = append(nameservers, websiteSSL.Nameserver2) + } + if websiteSSL.DisableCNAME { + _ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "true") + } else { + _ = os.Setenv("LEGO_DISABLE_CNAME_SUPPORT", "false") + } + + return c.Client.Challenge.SetDNS01Provider(p, + dns01.CondOption(len(nameservers) > 0, + dns01.AddRecursiveNameservers(nameservers)), + dns01.CondOption(websiteSSL.SkipDNS, + dns01.DisableAuthoritativeNssPropagationRequirement()), + dns01.AddDNSTimeout(dnsTimeOut), + ) +} + +func (c *AcmeClient) UseHTTP(path string) error { + httpProvider, err := webroot.NewHTTPProvider(path) + if err != nil { + return err + } + + err = c.Client.Challenge.SetHTTP01Provider(httpProvider) + if err != nil { + return err + } + return nil +} + +func (c *AcmeClient) ObtainSSL(domains []string, privateKey crypto.PrivateKey) (certificate.Resource, error) { + request := certificate.ObtainRequest{ + Domains: domains, + Bundle: true, + PrivateKey: privateKey, + } + + var certificates *certificate.Resource + var err error + + for attempt := 1; attempt <= maxRetryAttempts; attempt++ { + certificates, err = c.Client.Certificate.Obtain(request) + if err == nil { + return *certificates, nil + } + + if isHTTP503Error(err) && attempt < maxRetryAttempts { + global.LOG.Warnf("ACME server returned 503, retrying in %v (attempt %d/%d)", + retryDelayOn503, attempt, maxRetryAttempts) + time.Sleep(retryDelayOn503) + continue + } + + // Non-503 error or final attempt, return error + return certificate.Resource{}, err + } + + return certificate.Resource{}, err +} + +func (c *AcmeClient) ObtainIPSSL(ipAddress string, privKey crypto.PrivateKey) (certificate.Resource, error) { + csrTemplate := &x509.CertificateRequest{ + Subject: pkix.Name{ + CommonName: "", + }, + IPAddresses: []net.IP{ + net.ParseIP(ipAddress), + }, + } + csrDER, err := x509.CreateCertificateRequest( + rand.Reader, + csrTemplate, + privKey, + ) + if err != nil { + return certificate.Resource{}, err + } + csr, err := x509.ParseCertificateRequest(csrDER) + if err != nil { + return certificate.Resource{}, err + } + req := certificate.ObtainForCSRRequest{ + CSR: csr, + PrivateKey: privKey, + Profile: "shortlived", + Bundle: true, + } + + var certificates *certificate.Resource + for attempt := 1; attempt <= maxRetryAttempts; attempt++ { + certificates, err = c.Client.Certificate.ObtainForCSR(req) + if err == nil { + return *certificates, nil + } + + if isHTTP503Error(err) && attempt < maxRetryAttempts { + global.LOG.Warnf("ACME server returned 503 for IP SSL, retrying in %v (attempt %d/%d)", + retryDelayOn503, attempt, maxRetryAttempts) + time.Sleep(retryDelayOn503) + continue + } + + return certificate.Resource{}, err + } + + return certificate.Resource{}, err +} + +func (c *AcmeClient) RevokeSSL(pemSSL []byte) error { + return c.Client.Certificate.Revoke(pemSSL) +} diff --git a/agent/utils/ssl/dns_provider.go b/agent/utils/ssl/dns_provider.go new file mode 100644 index 0000000..c4d0f46 --- /dev/null +++ b/agent/utils/ssl/dns_provider.go @@ -0,0 +1,310 @@ +package ssl + +import ( + "encoding/json" + "github.com/go-acme/lego/v4/challenge" + "github.com/go-acme/lego/v4/providers/dns/acmedns" + "github.com/go-acme/lego/v4/providers/dns/alidns" + "github.com/go-acme/lego/v4/providers/dns/aliesa" + "github.com/go-acme/lego/v4/providers/dns/baiducloud" + "github.com/go-acme/lego/v4/providers/dns/clouddns" + "github.com/go-acme/lego/v4/providers/dns/cloudflare" + "github.com/go-acme/lego/v4/providers/dns/cloudns" + "github.com/go-acme/lego/v4/providers/dns/dnspod" + "github.com/go-acme/lego/v4/providers/dns/dynu" + "github.com/go-acme/lego/v4/providers/dns/freemyip" + "github.com/go-acme/lego/v4/providers/dns/godaddy" + "github.com/go-acme/lego/v4/providers/dns/huaweicloud" + "github.com/go-acme/lego/v4/providers/dns/namecheap" + "github.com/go-acme/lego/v4/providers/dns/namedotcom" + "github.com/go-acme/lego/v4/providers/dns/namesilo" + "github.com/go-acme/lego/v4/providers/dns/ovh" + "github.com/go-acme/lego/v4/providers/dns/porkbun" + "github.com/go-acme/lego/v4/providers/dns/rainyun" + "github.com/go-acme/lego/v4/providers/dns/regru" + "github.com/go-acme/lego/v4/providers/dns/route53" + "github.com/go-acme/lego/v4/providers/dns/spaceship" + "github.com/go-acme/lego/v4/providers/dns/tencentcloud" + "github.com/go-acme/lego/v4/providers/dns/vercel" + "github.com/go-acme/lego/v4/providers/dns/volcengine" + "github.com/go-acme/lego/v4/providers/dns/westcn" + "time" +) + +type DnsType string + +const ( + DnsPod DnsType = "DnsPod" + AliYun DnsType = "AliYun" + AliESA DnsType = "AliESA" + AWSRoute53 DnsType = "AWSRoute53" + CloudFlare DnsType = "CloudFlare" + CloudDns DnsType = "CloudDns" + NameSilo DnsType = "NameSilo" + NameCheap DnsType = "NameCheap" + NameCom DnsType = "NameCom" + Godaddy DnsType = "Godaddy" + TencentCloud DnsType = "TencentCloud" + RainYun DnsType = "RainYun" + Volcengine DnsType = "Volcengine" + HuaweiCloud DnsType = "HuaweiCloud" + FreeMyIP DnsType = "FreeMyIP" + Vercel DnsType = "Vercel" + Spaceship DnsType = "Spaceship" + WestCN DnsType = "WestCN" + ClouDNS DnsType = "ClouDNS" + RegRu DnsType = "RegRu" + Dynu DnsType = "Dynu" + BaiduCloud DnsType = "BaiduCloud" + Ovh DnsType = "Ovh" + AcmeDNS DnsType = "AcmeDNS" + PorkBun DnsType = "PorkBun" +) + +type DNSParam struct { + ID string `json:"id"` + Token string `json:"token"` + AccessKey string `json:"accessKey"` + SecretKey string `json:"secretKey"` + Email string `json:"email"` + APIkey string `json:"apiKey"` + APIUser string `json:"apiUser"` + APISecret string `json:"apiSecret"` + SecretID string `json:"secretID"` + ClientID string `json:"clientID"` + Password string `json:"password"` + Region string `json:"region"` + Username string `json:"username"` + AuthID string `json:"authID"` + SubAuthID string `json:"subAuthID"` + AuthPassword string `json:"authPassword"` + Endpoint string `json:"endpoint"` + AccessToken string `json:"accessToken"` + BaseURL string `json:"baseURL"` +} + +var ( + propagationTimeout = 30 * time.Minute + pollingInterval = 10 * time.Second + ttl = 3600 + dnsTimeOut = 30 * time.Minute + manualDnsTimeout = 10 * time.Minute +) + +func getDNSProviderConfig(dnsType DnsType, params string) (challenge.Provider, error) { + var ( + param DNSParam + p challenge.Provider + err error + ) + if err := json.Unmarshal([]byte(params), ¶m); err != nil { + return nil, err + } + switch dnsType { + case DnsPod: + config := dnspod.NewDefaultConfig() + config.LoginToken = param.ID + "," + param.Token + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = dnspod.NewDNSProviderConfig(config) + case AliYun: + config := alidns.NewDefaultConfig() + config.SecretKey = param.SecretKey + config.APIKey = param.AccessKey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = alidns.NewDNSProviderConfig(config) + case AliESA: + config := aliesa.NewDefaultConfig() + config.SecretKey = param.SecretKey + config.APIKey = param.AccessKey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = aliesa.NewDNSProviderConfig(config) + case AWSRoute53: + config := route53.NewDefaultConfig() + config.AccessKeyID = param.AccessKey + config.SecretAccessKey = param.SecretKey + config.Region = param.Region + if config.Region == "" { + config.Region = "us-east-1" + } + config.HostedZoneID = param.Endpoint + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = route53.NewDNSProviderConfig(config) + case CloudFlare: + config := cloudflare.NewDefaultConfig() + config.AuthEmail = param.Email + config.AuthToken = param.APIkey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = cloudflare.NewDNSProviderConfig(config) + case CloudDns: + config := clouddns.NewDefaultConfig() + config.ClientID = param.ClientID + config.Email = param.Email + config.Password = param.Password + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = clouddns.NewDNSProviderConfig(config) + case NameCheap: + config := namecheap.NewDefaultConfig() + config.APIKey = param.APIkey + config.APIUser = param.APIUser + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = namecheap.NewDNSProviderConfig(config) + case NameSilo: + config := namesilo.NewDefaultConfig() + config.APIKey = param.APIkey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = namesilo.NewDNSProviderConfig(config) + case Godaddy: + config := godaddy.NewDefaultConfig() + config.APIKey = param.APIkey + config.APISecret = param.APISecret + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = godaddy.NewDNSProviderConfig(config) + case NameCom: + config := namedotcom.NewDefaultConfig() + config.APIToken = param.Token + config.Username = param.APIUser + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = namedotcom.NewDNSProviderConfig(config) + case TencentCloud: + config := tencentcloud.NewDefaultConfig() + config.SecretID = param.SecretID + config.SecretKey = param.SecretKey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = tencentcloud.NewDNSProviderConfig(config) + case RainYun: + config := rainyun.NewDefaultConfig() + config.APIKey = param.APIkey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = rainyun.NewDNSProviderConfig(config) + case Volcengine: + config := volcengine.NewDefaultConfig() + config.SecretKey = param.SecretKey + config.AccessKey = param.AccessKey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = volcengine.NewDNSProviderConfig(config) + case HuaweiCloud: + config := huaweicloud.NewDefaultConfig() + config.AccessKeyID = param.AccessKey + config.SecretAccessKey = param.SecretKey + config.Region = param.Region + if config.Region == "" { + config.Region = "cn-north-1" + } + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = int32(ttl) + p, err = huaweicloud.NewDNSProviderConfig(config) + case FreeMyIP: + config := freemyip.NewDefaultConfig() + config.Token = param.Token + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + p, err = freemyip.NewDNSProviderConfig(config) + case Vercel: + config := vercel.NewDefaultConfig() + config.AuthToken = param.Token + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + p, err = vercel.NewDNSProviderConfig(config) + case Spaceship: + config := spaceship.NewDefaultConfig() + config.APIKey = param.APIkey + config.APISecret = param.APISecret + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = spaceship.NewDNSProviderConfig(config) + case WestCN: + config := westcn.NewDefaultConfig() + config.Username = param.Username + config.Password = param.Password + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = westcn.NewDNSProviderConfig(config) + case ClouDNS: + config := cloudns.NewDefaultConfig() + config.AuthID = param.AuthID + config.SubAuthID = param.SubAuthID + config.AuthPassword = param.AuthPassword + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = cloudns.NewDNSProviderConfig(config) + case RegRu: + config := regru.NewDefaultConfig() + config.Username = param.Username + config.Password = param.Password + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = regru.NewDNSProviderConfig(config) + case Dynu: + config := dynu.NewDefaultConfig() + config.APIKey = param.APIkey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = dynu.NewDNSProviderConfig(config) + case BaiduCloud: + config := baiducloud.NewDefaultConfig() + config.AccessKeyID = param.AccessKey + config.SecretAccessKey = param.SecretKey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = baiducloud.NewDNSProviderConfig(config) + case Ovh: + config := ovh.NewDefaultConfig() + config.APIEndpoint = param.Endpoint + config.AccessToken = param.AccessToken + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = ovh.NewDNSProviderConfig(config) + case AcmeDNS: + config := acmedns.NewDefaultConfig() + config.APIBase = param.Endpoint + config.StorageBaseURL = param.BaseURL + p, err = acmedns.NewDNSProviderConfig(config) + case PorkBun: + config := porkbun.NewDefaultConfig() + config.APIKey = param.APIkey + config.SecretAPIKey = param.SecretKey + config.PropagationTimeout = propagationTimeout + config.PollingInterval = pollingInterval + config.TTL = ttl + p, err = porkbun.NewDNSProviderConfig(config) + } + + if err != nil { + return nil, err + } + return p, nil +} diff --git a/agent/utils/ssl/manual_client.go b/agent/utils/ssl/manual_client.go new file mode 100644 index 0000000..655de55 --- /dev/null +++ b/agent/utils/ssl/manual_client.go @@ -0,0 +1,439 @@ +package ssl + +import ( + "context" + "crypto" + "crypto/ecdsa" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "crypto/x509/pkix" + "encoding/pem" + "fmt" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/go-acme/lego/v4/certificate" + "github.com/miekg/dns" + "golang.org/x/crypto/acme" + "log" + "net" + "strings" + "time" +) + +type ManualClient struct { + client *acme.Client + account *model.WebsiteAcmeAccount + logger *log.Logger +} + +type RequestCertRequest struct { + WebsiteSSL *model.WebsiteSSL +} + +func NewCustomAcmeClient(acmeAccount *model.WebsiteAcmeAccount, logger *log.Logger) (*ManualClient, error) { + var ( + key crypto.PrivateKey + err error + ) + switch KeyType(acmeAccount.KeyType) { + case KeyEC256, KeyEC384: + block, _ := pem.Decode([]byte(acmeAccount.PrivateKey)) + key, err = x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return nil, err + } + case KeyRSA2048, KeyRSA3072, KeyRSA4096: + block, _ := pem.Decode([]byte(acmeAccount.PrivateKey)) + key, err = x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + } + if logger == nil { + logger = log.Default() + } + + client := &acme.Client{ + Key: key.(crypto.Signer), + DirectoryURL: getCaDirURL(acmeAccount.Type, acmeAccount.CaDirURL), + } + return &ManualClient{ + client: client, + account: acmeAccount, + logger: logger, + }, nil +} + +type Resolve struct { + Key string + Value string + Err string +} + +func (c *ManualClient) GetDNSResolve(ctx context.Context, websiteSSL *model.WebsiteSSL) (map[string]Resolve, error) { + var order *acme.Order + var err error + + // Check if we have an existing valid order for this SSL + existingOrder, exists := Orders[websiteSSL.ID] + if exists && existingOrder != nil { + // Verify the order is still valid (not expired and still pending) + // If Expires is zero, order is still valid (ACME doesn't always set expiry immediately) + isNotExpired := existingOrder.Expires.IsZero() || existingOrder.Expires.After(time.Now()) + if isNotExpired && (existingOrder.Status == acme.StatusPending || existingOrder.Status == acme.StatusReady) { + // Try to reuse the existing order + records, err := c.extractDNSChallenges(ctx, existingOrder) + if err == nil && len(records) > 0 { + return records, nil + } + // If extraction failed, fall through to create a new order + } + // Existing order is expired or invalid, remove it + delete(Orders, websiteSSL.ID) + } + + // Create a new order + order, err = c.client.AuthorizeOrder(ctx, acme.DomainIDs(getWebsiteSSLDomains(websiteSSL)...)) + if err != nil { + return nil, err + } + Orders[websiteSSL.ID] = order + + return c.extractDNSChallenges(ctx, order) +} + +// extractDNSChallenges extracts DNS-01 challenge values from an ACME order +func (c *ManualClient) extractDNSChallenges(ctx context.Context, order *acme.Order) (map[string]Resolve, error) { + records := make(map[string]Resolve) + + for _, authzURL := range order.AuthzURLs { + authz, err := c.client.GetAuthorization(ctx, authzURL) + if err != nil { + return nil, err + } + domain := authz.Identifier.Value + + var dnsChallenge *acme.Challenge + for _, challenge := range authz.Challenges { + if challenge.Type == "dns-01" { + dnsChallenge = challenge + break + } + } + + if dnsChallenge == nil { + return nil, fmt.Errorf("no DNS-01 challenge found for domain %s", domain) + } + + txtValue, err := c.client.DNS01ChallengeRecord(dnsChallenge.Token) + if err != nil { + return nil, err + } + + // Use different map key for wildcard vs non-wildcard to avoid overwriting + // Both use the same DNS record name (_acme-challenge.domain) but different values + mapKey := domain + if authz.Wildcard { + mapKey = "*." + domain + } + + records[mapKey] = Resolve{ + Key: fmt.Sprintf("_acme-challenge.%s", domain), + Value: txtValue, + } + } + return records, nil +} + +func queryDNSRecords(domain string) (map[string][]string, error) { + recordName := fmt.Sprintf("_acme-challenge.%s", domain) + txts, err := net.LookupTXT(recordName) + if err != nil { + return nil, err + } + records := make(map[string][]string) + if len(txts) > 0 { + records[recordName] = txts + } + return records, nil +} + +func (c *ManualClient) handleAuthorization(ctx context.Context, authzURL string, nameservers []string) error { + authz, err := c.client.GetAuthorization(ctx, authzURL) + if err != nil { + return fmt.Errorf("failed to get authorization: %v", err) + } + + domain := authz.Identifier.Value + c.logger.Printf("[INFO] [%s] AuthURL: %s", domain, authzURL) + + if authz.Status == acme.StatusValid { + return nil + } + + var dnsChallenge *acme.Challenge + for _, challenge := range authz.Challenges { + if challenge.Type == "dns-01" { + dnsChallenge = challenge + break + } + } + + c.logger.Printf("[INFO] [%s] acme: use dns-01 solver", domain) + if dnsChallenge == nil { + return fmt.Errorf("no DNS-01 challenge found for domain %s", domain) + } + + deadline := time.Now().Add(manualDnsTimeout) + expectedRecord, err := c.client.DNS01ChallengeRecord(dnsChallenge.Token) + if err != nil { + return fmt.Errorf("failed to compute DNS challenge record: %v", err) + } + c.logger.Printf("[INFO] [%s] acme: Checking TXT record %s", domain, expectedRecord) + + for { + c.logger.Printf("[INFO] [%s] acme: Checking DNS record propagation.", domain) + var currentRecords map[string][]string + var queryErr error + if len(nameservers) == 0 { + currentRecords, queryErr = queryDNSRecords(domain) + } else { + var errs []string + for _, nameserver := range nameservers { + currentRecords, queryErr = queryDNSRecordsWithResolver(ctx, c.logger, domain, nameserver) + errs = append(errs, fmt.Sprintf("%s: %v", nameserver, queryErr)) + } + if queryErr != nil && len(errs) > 0 { + queryErr = fmt.Errorf("all nameservers failed: %s", strings.Join(errs, "; ")) + } + } + if currentRecords == nil && queryErr != nil { + return fmt.Errorf("failed to query DNS records: %v", queryErr) + } + recordName := fmt.Sprintf("_acme-challenge.%s", domain) + providedRecords, exists := currentRecords[recordName] + // Check if expected record is in any of the TXT values + found := false + if exists { + for _, record := range providedRecords { + if record == expectedRecord { + found = true + break + } + } + } + if found { + break + } + if time.Now().After(deadline) { + if !exists || len(providedRecords) == 0 { + return fmt.Errorf("TXT record not provided for domain %s after retrying", domain) + } + c.logger.Printf("[INFO] [%s] TXT record mismatch for %s: expected %s, got %v", domain, domain, expectedRecord, providedRecords) + return fmt.Errorf("TXT record mismatch for %s: expected %s, got %v", domain, expectedRecord, providedRecords) + } + time.Sleep(pollingInterval) + } + + _, err = c.client.Accept(ctx, dnsChallenge) + if err != nil { + return fmt.Errorf("failed to accept challenge: %v", err) + } + for { + time.Sleep(pollingInterval) + authz, err = c.client.GetAuthorization(ctx, authzURL) + if err != nil { + return fmt.Errorf("failed to get authorization while polling: %v", err) + } + if authz.Status == acme.StatusValid { + break + } else if authz.Status == acme.StatusInvalid { + return fmt.Errorf("authorization failed for domain %s", domain) + } + } + return nil +} + +func (c *ManualClient) createCSR(keyType string, privateKey string, domains []string) ([]byte, crypto.PrivateKey, error) { + var certKey crypto.PrivateKey + var err error + certKey, err = GetPrivateKeyByType(keyType, privateKey) + if err != nil { + return nil, nil, err + } + template := x509.CertificateRequest{ + Subject: pkix.Name{CommonName: domains[0]}, + DNSNames: domains, + } + csrBytes, err := x509.CreateCertificateRequest(rand.Reader, &template, certKey) + if err != nil { + return nil, nil, err + } + return csrBytes, certKey, nil +} + +func (c *ManualClient) encodePrivateKey(key crypto.PrivateKey) (string, error) { + var keyBytes []byte + var keyType string + var err error + + switch k := key.(type) { + case *ecdsa.PrivateKey: + keyBytes, err = x509.MarshalECPrivateKey(k) + keyType = "EC PRIVATE KEY" + case *rsa.PrivateKey: + keyBytes = x509.MarshalPKCS1PrivateKey(k) + keyType = "RSA PRIVATE KEY" + default: + return "", fmt.Errorf("unsupported key type") + } + + if err != nil { + return "", err + } + + block := &pem.Block{ + Type: keyType, + Bytes: keyBytes, + } + + return string(pem.EncodeToMemory(block)), nil +} + +func (c *ManualClient) RequestCertificate(ctx context.Context, websiteSSL *model.WebsiteSSL) (certificate.Resource, error) { + var res certificate.Resource + domains := []string{websiteSSL.PrimaryDomain} + if websiteSSL.Domains != "" { + domains = append(domains, strings.Split(websiteSSL.Domains, ",")...) + } + + c.logger.Printf("[INFO] Requesting certificate for domains: %v\n", domains) + csr, certKey, err := c.createCSR(websiteSSL.KeyType, websiteSSL.PrivateKey, domains) + if err != nil { + return res, err + } + + order, ok := Orders[websiteSSL.ID] + if !ok { + return res, fmt.Errorf("order not found") + } + defer delete(Orders, websiteSSL.ID) + + for _, authzURL := range order.AuthzURLs { + if err := c.handleAuthorization(ctx, authzURL, getNameservers(*websiteSSL)); err != nil { + return res, err + } + } + + c.logger.Printf("[INFO] acme: Validations succeeded; requesting certificates") + order, err = c.client.WaitOrder(ctx, order.URI) + if err != nil { + return res, err + } + + if order.Status != acme.StatusReady { + return res, fmt.Errorf("order not ready: %s", order.Status) + } + + certBytes, certURL, err := c.client.CreateOrderCert(ctx, order.FinalizeURL, csr, true) + if err != nil { + return res, fmt.Errorf("failed to finalize order: %v", err) + } + + privateKeyPEM, err := c.encodePrivateKey(certKey) + if err != nil { + return res, err + } + + var certPEM []byte + for _, cert := range certBytes { + block := &pem.Block{ + Type: "CERTIFICATE", + Bytes: cert, + } + certPEM = append(certPEM, pem.EncodeToMemory(block)...) + } + c.logger.Printf("[INFO] acme: Server responded with a certificate.") + resource := certificate.Resource{ + Domain: domains[0], + CertURL: certURL, + CertStableURL: certURL, + PrivateKey: []byte(privateKeyPEM), + Certificate: certPEM, + CSR: csr, + } + return resource, nil +} + +func getNameservers(websiteSSL model.WebsiteSSL) []string { + var nameservers []string + if websiteSSL.Nameserver1 != "" { + nameservers = append(nameservers, handleNameserver(websiteSSL.Nameserver1)) + } + if websiteSSL.Nameserver2 != "" { + nameservers = append(nameservers, handleNameserver(websiteSSL.Nameserver2)) + } + return nameservers +} + +func handleNameserver(nameserver string) string { + if strings.Contains(nameserver, ":") { + return nameserver + } + return fmt.Sprintf("%s:53", nameserver) +} + +func queryDNSRecordsWithResolver(ctx context.Context, logger *log.Logger, domain string, dnsServer string) (map[string][]string, error) { + recordName := fmt.Sprintf("_acme-challenge.%s", domain) + c := new(dns.Client) + c.Timeout = 10 * time.Second + c.Net = "udp" + + m := new(dns.Msg) + m.SetQuestion(dns.Fqdn(recordName), dns.TypeTXT) + m.RecursionDesired = true + + r, _, err := c.ExchangeContext(ctx, m, dnsServer) + if isNetworkError(err) { + logger.Printf("[WARN] Network error occurred while querying DNS: %v", err) + return nil, nil + } + if err != nil { + return nil, fmt.Errorf("DNS query failed: %w", err) + } + + if r.Rcode == dns.RcodeNameError { + logger.Printf("[INFO] DNS record does not exist yet (NXDOMAIN)") + return nil, nil + } + + if r.Rcode != dns.RcodeSuccess { + return nil, fmt.Errorf("DNS query failed with code: %s", dns.RcodeToString[r.Rcode]) + } + + records := make(map[string][]string) + var txtValues []string + + for _, answer := range r.Answer { + if txt, ok := answer.(*dns.TXT); ok { + if len(txt.Txt) > 0 { + txtValues = append(txtValues, txt.Txt[0]) + } + } + } + if len(txtValues) > 0 { + records[recordName] = txtValues + } + + return records, nil +} + +func isNetworkError(err error) bool { + if err == nil { + return false + } + return strings.Contains(err.Error(), "i/o timeout") || + strings.Contains(err.Error(), "connection refused") || + strings.Contains(err.Error(), "network is unreachable") || + strings.Contains(err.Error(), "no route to host") +} diff --git a/agent/utils/terminal/local_cmd.go b/agent/utils/terminal/local_cmd.go new file mode 100644 index 0000000..aecd3e3 --- /dev/null +++ b/agent/utils/terminal/local_cmd.go @@ -0,0 +1,116 @@ +package terminal + +import ( + "os" + "os/exec" + "syscall" + "time" + "unsafe" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/creack/pty" + "github.com/pkg/errors" +) + +const ( + DefaultCloseSignal = syscall.SIGINT + DefaultCloseTimeout = 10 * time.Second +) + +type LocalCommand struct { + closeSignal syscall.Signal + closeTimeout time.Duration + + cmd *exec.Cmd + pty *os.File +} + +func NewCommand(name string, arg ...string) (*LocalCommand, error) { + cmd := exec.Command(name, arg...) + if term := os.Getenv("TERM"); term != "" { + cmd.Env = append(os.Environ(), "TERM="+term) + } else { + cmd.Env = append(os.Environ(), "TERM=xterm") + } + homeDir, _ := os.UserHomeDir() + cmd.Dir = homeDir + + pty, err := pty.Start(cmd) + if err != nil { + return nil, errors.Wrapf(err, "failed to start command") + } + + lcmd := &LocalCommand{ + closeSignal: DefaultCloseSignal, + closeTimeout: DefaultCloseTimeout, + + cmd: cmd, + pty: pty, + } + + return lcmd, nil +} + +func (lcmd *LocalCommand) Read(p []byte) (n int, err error) { + return lcmd.pty.Read(p) +} + +func (lcmd *LocalCommand) Write(p []byte) (n int, err error) { + return lcmd.pty.Write(p) +} + +func (lcmd *LocalCommand) Close() error { + if lcmd.pty != nil { + lcmd.pty.Write([]byte{3}) + time.Sleep(50 * time.Millisecond) + + lcmd.pty.Write([]byte{4}) + time.Sleep(50 * time.Millisecond) + + lcmd.pty.Write([]byte("exit\n")) + time.Sleep(50 * time.Millisecond) + } + if lcmd.cmd != nil && lcmd.cmd.Process != nil { + lcmd.cmd.Process.Signal(syscall.SIGTERM) + time.Sleep(50 * time.Millisecond) + + if lcmd.cmd.ProcessState == nil || !lcmd.cmd.ProcessState.Exited() { + lcmd.cmd.Process.Kill() + } + } + _ = lcmd.pty.Close() + return nil +} + +func (lcmd *LocalCommand) ResizeTerminal(width int, height int) error { + window := struct { + row uint16 + col uint16 + x uint16 + y uint16 + }{ + uint16(height), + uint16(width), + 0, + 0, + } + _, _, errno := syscall.Syscall( + syscall.SYS_IOCTL, + lcmd.pty.Fd(), + syscall.TIOCSWINSZ, + uintptr(unsafe.Pointer(&window)), + ) + if errno != 0 { + return errno + } else { + return nil + } +} + +func (lcmd *LocalCommand) Wait(quitChan chan bool) { + if err := lcmd.cmd.Wait(); err != nil { + global.LOG.Errorf("ssh session wait failed, err: %v", err) + setQuit(quitChan) + } + setQuit(quitChan) +} diff --git a/agent/utils/terminal/ws_local_session.go b/agent/utils/terminal/ws_local_session.go new file mode 100644 index 0000000..da7c1ab --- /dev/null +++ b/agent/utils/terminal/ws_local_session.go @@ -0,0 +1,126 @@ +package terminal + +import ( + "encoding/base64" + "encoding/json" + "sync" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/gorilla/websocket" + "github.com/pkg/errors" +) + +type LocalWsSession struct { + slave *LocalCommand + wsConn *websocket.Conn + + allowCtrlC bool + writeMutex sync.Mutex +} + +func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalCommand, allowCtrlC bool) (*LocalWsSession, error) { + if err := slave.ResizeTerminal(cols, rows); err != nil { + global.LOG.Errorf("ssh pty change windows size failed, err: %v", err) + } + + return &LocalWsSession{ + slave: slave, + wsConn: wsConn, + + allowCtrlC: allowCtrlC, + }, nil +} + +func (sws *LocalWsSession) Start(quitChan chan bool) { + go sws.handleSlaveEvent(quitChan) + go sws.receiveWsMsg(quitChan) +} + +func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) { + defer setQuit(exitCh) + defer global.LOG.Debug("thread of handle slave event has exited now") + + buffer := make([]byte, 1024) + for { + select { + case <-exitCh: + return + default: + n, _ := sws.slave.Read(buffer) + _ = sws.masterWrite(buffer[:n]) + } + } +} + +func (sws *LocalWsSession) masterWrite(data []byte) error { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("A panic occurred during write ws message to master, error message: %v", r) + } + }() + sws.writeMutex.Lock() + defer sws.writeMutex.Unlock() + wsData, err := json.Marshal(WsMsg{ + Type: WsMsgCmd, + Data: base64.StdEncoding.EncodeToString(data), + }) + if err != nil { + return errors.Wrapf(err, "failed to encoding to json") + } + err = sws.wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + return errors.Wrapf(err, "failed to write to master") + } + return nil +} + +func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) { + defer func() { + if r := recover(); r != nil { + setQuit(exitCh) + global.LOG.Errorf("A panic occurred during receive ws message, error message: %v", r) + } + }() + wsConn := sws.wsConn + defer setQuit(exitCh) + defer global.LOG.Debug("thread of receive ws msg has exited now") + for { + select { + case <-exitCh: + return + default: + _, wsData, err := wsConn.ReadMessage() + if err != nil { + global.LOG.Errorf("reading webSocket message failed, err: %v", err) + return + } + msgObj := WsMsg{} + _ = json.Unmarshal(wsData, &msgObj) + switch msgObj.Type { + case WsMsgResize: + if msgObj.Cols > 0 && msgObj.Rows > 0 { + if err := sws.slave.ResizeTerminal(msgObj.Cols, msgObj.Rows); err != nil { + global.LOG.Errorf("ssh pty change windows size failed, err: %v", err) + } + } + case WsMsgCmd: + decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data) + if err != nil { + global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err) + } + sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes) + case WsMsgHeartbeat: + err = wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err) + } + } + } + } +} + +func (sws *LocalWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) { + if _, err := sws.slave.Write(cmdBytes); err != nil { + global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err) + } +} diff --git a/agent/utils/terminal/ws_session.go b/agent/utils/terminal/ws_session.go new file mode 100644 index 0000000..c5fd9dc --- /dev/null +++ b/agent/utils/terminal/ws_session.go @@ -0,0 +1,222 @@ +package terminal + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "io" + "sync" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/gorilla/websocket" + "golang.org/x/crypto/ssh" +) + +type safeBuffer struct { + buffer bytes.Buffer + mu sync.Mutex +} + +func (w *safeBuffer) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + return w.buffer.Write(p) +} +func (w *safeBuffer) Bytes() []byte { + w.mu.Lock() + defer w.mu.Unlock() + return w.buffer.Bytes() +} +func (w *safeBuffer) Reset() { + w.mu.Lock() + defer w.mu.Unlock() + w.buffer.Reset() +} + +const ( + WsMsgCmd = "cmd" + WsMsgResize = "resize" + WsMsgHeartbeat = "heartbeat" +) + +type WsMsg struct { + Type string `json:"type"` + Data string `json:"data,omitempty"` // WsMsgCmd + Cols int `json:"cols,omitempty"` // WsMsgResize + Rows int `json:"rows,omitempty"` // WsMsgResize + Timestamp int `json:"timestamp,omitempty"` // WsMsgHeartbeat +} + +type LogicSshWsSession struct { + stdinPipe io.WriteCloser + comboOutput *safeBuffer + logBuff *safeBuffer + inputFilterBuff *safeBuffer + session *ssh.Session + wsConn *websocket.Conn + isAdmin bool + IsFlagged bool +} + +func NewLogicSshWsSession(cols, rows int, sshClient *ssh.Client, wsConn *websocket.Conn, initCmd string) (*LogicSshWsSession, error) { + sshSession, err := sshClient.NewSession() + if err != nil { + return nil, err + } + + stdinP, err := sshSession.StdinPipe() + if err != nil { + return nil, err + } + + comboWriter := new(safeBuffer) + logBuf := new(safeBuffer) + inputBuf := new(safeBuffer) + sshSession.Stdout = comboWriter + sshSession.Stderr = comboWriter + + modes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil { + return nil, err + } + if err := sshSession.Shell(); err != nil { + return nil, err + } + if len(initCmd) != 0 { + time.Sleep(100 * time.Millisecond) + _, _ = stdinP.Write([]byte(initCmd + "\n")) + } + return &LogicSshWsSession{ + stdinPipe: stdinP, + comboOutput: comboWriter, + logBuff: logBuf, + inputFilterBuff: inputBuf, + session: sshSession, + wsConn: wsConn, + isAdmin: true, + IsFlagged: false, + }, nil +} + +func (sws *LogicSshWsSession) Close() { + if sws.session != nil { + sws.session.Close() + } + if sws.logBuff != nil { + sws.logBuff = nil + } + if sws.comboOutput != nil { + sws.comboOutput = nil + } +} + +func (sws *LogicSshWsSession) Start(quitChan chan bool) { + go sws.receiveWsMsg(quitChan) + go sws.sendComboOutput(quitChan) +} + +func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("[A panic occurred during receive ws message, error message: %v", r) + } + }() + wsConn := sws.wsConn + defer setQuit(exitCh) + for { + select { + case <-exitCh: + return + default: + _, wsData, err := wsConn.ReadMessage() + if err != nil { + return + } + msgObj := WsMsg{} + _ = json.Unmarshal(wsData, &msgObj) + switch msgObj.Type { + case WsMsgResize: + if msgObj.Cols > 0 && msgObj.Rows > 0 { + if err := sws.session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil { + global.LOG.Errorf("ssh pty change windows size failed, err: %v", err) + } + } + case WsMsgCmd: + decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data) + if err != nil { + global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err) + } + sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes) + case WsMsgHeartbeat: + err = wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err) + } + } + } + } +} + +func (sws *LogicSshWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) { + if _, err := sws.stdinPipe.Write(cmdBytes); err != nil { + global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err) + } +} + +func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) { + wsConn := sws.wsConn + defer setQuit(exitCh) + + tick := time.NewTicker(time.Millisecond * time.Duration(60)) + defer tick.Stop() + for { + select { + case <-tick.C: + if sws.comboOutput == nil { + return + } + bs := sws.comboOutput.Bytes() + if len(bs) > 0 { + wsData, err := json.Marshal(WsMsg{ + Type: WsMsgCmd, + Data: base64.StdEncoding.EncodeToString(bs), + }) + if err != nil { + global.LOG.Errorf("encoding combo output to json failed, err: %v", err) + continue + } + err = wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + global.LOG.Errorf("ssh sending combo output to webSocket failed, err: %v", err) + } + _, err = sws.logBuff.Write(bs) + if err != nil { + global.LOG.Errorf("combo output to log buffer failed, err: %v", err) + } + sws.comboOutput.buffer.Reset() + } + if string(bs) == string([]byte{13, 10, 108, 111, 103, 111, 117, 116, 13, 10}) { + sws.Close() + return + } + + case <-exitCh: + return + } + } +} + +func (sws *LogicSshWsSession) Wait(quitChan chan bool) { + if err := sws.session.Wait(); err != nil { + setQuit(quitChan) + } +} + +func setQuit(ch chan bool) { + ch <- true +} diff --git a/agent/utils/toolbox/fail2ban.go b/agent/utils/toolbox/fail2ban.go new file mode 100644 index 0000000..8ac9f5e --- /dev/null +++ b/agent/utils/toolbox/fail2ban.go @@ -0,0 +1,179 @@ +package toolbox + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/controller" +) + +type Fail2ban struct{} + +const defaultPath = "/etc/fail2ban/jail.local" + +type FirewallClient interface { + Status() (bool, bool, bool) + Version() (string, error) + Operate(operate string) error + OperateSSHD(operate, ip string) error +} + +func NewFail2Ban() (*Fail2ban, error) { + isExist, _ := controller.CheckExist("fail2ban.service") + if isExist { + if _, err := os.Stat(defaultPath); err != nil { + if err := initLocalFile(); err != nil { + return nil, err + } + if err := controller.HandleRestart("fail2ban.service"); err != nil { + global.LOG.Errorf("restart fail2ban failed, err: %v", err) + return nil, err + } + } + } + return &Fail2ban{}, nil +} + +func (f *Fail2ban) Status() (bool, bool, bool) { + isEnable, _ := controller.CheckEnable("fail2ban.service") + isActive, _ := controller.CheckActive("fail2ban.service") + isExist, _ := controller.CheckExist("fail2ban.service") + + return isEnable, isActive, isExist +} + +func (f *Fail2ban) Version() string { + stdout, err := cmd.RunDefaultWithStdoutBashC("fail2ban-client version") + if err != nil { + global.LOG.Errorf("load the fail2ban version failed, %v", err) + return "-" + } + return strings.ReplaceAll(stdout, "\n", "") +} + +func (f *Fail2ban) Operate(operate string) error { + switch operate { + case "start", "restart", "stop", "enable", "disable": + if err := controller.Handle(operate, "fail2ban.service"); err != nil { + return fmt.Errorf("%s the fail2ban.service failed, err: %v", operate, err) + } + return nil + case "reload": + if err := cmd.RunDefaultBashC("fail2ban-client reload"); err != nil { + return fmt.Errorf("fail2ban-client reload, %v", err) + } + return nil + default: + return fmt.Errorf("not support such operation: %v", operate) + } +} + +func (f *Fail2ban) ReBanIPs(ips []string) error { + ipItems, _ := f.ListBanned() + stdout, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut("fail2ban-client unban --all", 10*time.Minute) + if err != nil { + stdout1, err := cmd.RunDefaultWithStdoutBashCfAndTimeOut("fail2ban-client set sshd banip %s", 10*time.Minute, strings.Join(ipItems, " ")) + if err != nil { + global.LOG.Errorf("rebanip after fail2ban-client unban --all failed, err: %s", stdout1) + } + return fmt.Errorf("fail2ban-client unban --all failed, err: %s", stdout) + } + stdout, err = cmd.RunDefaultWithStdoutBashCfAndTimeOut("fail2ban-client set sshd banip %s", 10*time.Minute, strings.Join(ips, " ")) + if err != nil { + return fmt.Errorf("handle `fail2ban-client set sshd banip %s` failed, err: %s", strings.Join(ips, " "), stdout) + } + return nil +} + +func (f *Fail2ban) ListBanned() ([]string, error) { + var lists []string + stdout, err := cmd.RunDefaultWithStdoutBashC("fail2ban-client status sshd | grep 'Banned IP list:'") + if err != nil { + return lists, err + } + itemList := strings.Split(strings.Trim(stdout, "\n"), "Banned IP list:") + if len(itemList) != 2 { + return lists, nil + } + + ips := strings.Fields(itemList[1]) + for _, item := range ips { + if len(item) != 0 { + lists = append(lists, item) + } + } + return lists, nil +} + +func (f *Fail2ban) ListIgnore() ([]string, error) { + var lists []string + stdout, err := cmd.RunDefaultWithStdoutBashC("fail2ban-client get sshd ignoreip") + if err != nil { + return lists, err + } + stdout = strings.ReplaceAll(stdout, "|", "") + stdout = strings.ReplaceAll(stdout, "`", "") + stdout = strings.ReplaceAll(stdout, "\n", "") + addrs := strings.Split(stdout, "-") + for _, addr := range addrs { + if !strings.HasPrefix(addr, " ") { + continue + } + lists = append(lists, strings.ReplaceAll(addr, " ", "")) + } + return lists, nil +} + +func initLocalFile() error { + f, err := os.Create(defaultPath) + if err != nil { + return err + } + defer f.Close() + initFile := `#DEFAULT-START +[DEFAULT] +bantime = 600 +findtime = 300 +maxretry = 5 +banaction = $banaction +action = %(action_mwl)s +#DEFAULT-END + +[sshd] +ignoreip = 127.0.0.1/8 +enabled = true +filter = sshd +port = 22 +maxretry = 5 +findtime = 300 +bantime = 600 +banaction = $banaction +action = %(action_mwl)s +logpath = $logpath` + + banaction := "" + if active, _ := controller.CheckActive("firewalld"); active { + banaction = "firewallcmd-ipset" + } else if active, _ := controller.CheckActive("ufw"); active { + banaction = "ufw" + } else { + banaction = "iptables-allports" + } + initFile = strings.ReplaceAll(initFile, "$banaction", banaction) + + logPath := "" + if _, err := os.Stat("/var/log/secure"); err == nil { + logPath = "/var/log/secure" + } else { + logPath = "/var/log/auth.log" + } + initFile = strings.ReplaceAll(initFile, "$logpath", logPath) + if err := os.WriteFile(defaultPath, []byte(initFile), 0640); err != nil { + return err + } + return nil +} diff --git a/agent/utils/toolbox/helper/sha512_crypt.go b/agent/utils/toolbox/helper/sha512_crypt.go new file mode 100644 index 0000000..262033a --- /dev/null +++ b/agent/utils/toolbox/helper/sha512_crypt.go @@ -0,0 +1,224 @@ +package helper + +import ( + "bytes" + "crypto/rand" + "crypto/sha512" + "errors" + "strconv" +) + +const ( + SaltLenMin = 1 + SaltLenMax = 16 + RoundsMin = 1000 + RoundsMax = 999999999 + RoundsDefault = 5000 +) + +var _rounds = []byte("rounds=") + +func Generate(key []byte) (string, error) { + var rounds int + var isRoundsDef bool + + salt := generateWRounds() + magicPrefix := []byte("$6$") + if !bytes.HasPrefix(salt, magicPrefix) { + return "", errors.New("invalid magic prefix") + } + + saltItem := bytes.Split(salt, []byte{'$'}) + if len(saltItem) < 3 { + return "", errors.New("invalid salt format") + } + + if bytes.HasPrefix(saltItem[2], _rounds) { + isRoundsDef = true + pr, err := strconv.ParseInt(string(saltItem[2][7:]), 10, 32) + if err != nil { + return "", errors.New("invalid rounds") + } + rounds = int(pr) + if rounds < RoundsMin { + rounds = RoundsMin + } else if rounds > RoundsMax { + rounds = RoundsMax + } + salt = saltItem[3] + } else { + rounds = RoundsDefault + salt = saltItem[2] + } + + if len(salt) > SaltLenMax { + salt = salt[0:SaltLenMax] + } + + Alternate := sha512.New() + Alternate.Write(key) + Alternate.Write(salt) + Alternate.Write(key) + AlternateSum := Alternate.Sum(nil) + + A := sha512.New() + A.Write(key) + A.Write(salt) + i := len(key) + for ; i > 64; i -= 64 { + A.Write(AlternateSum) + } + A.Write(AlternateSum[0:i]) + + for i = len(key); i > 0; i >>= 1 { + if (i & 1) != 0 { + A.Write(AlternateSum) + } else { + A.Write(key) + } + } + A_sum := A.Sum(nil) + + P := sha512.New() + for i = 0; i < len(key); i++ { + P.Write(key) + } + P_sum := P.Sum(nil) + P_seq := make([]byte, 0, len(key)) + for i = len(key); i > 64; i -= 64 { + P_seq = append(P_seq, P_sum...) + } + P_seq = append(P_seq, P_sum[0:i]...) + + S := sha512.New() + for i = 0; i < (16 + int(A_sum[0])); i++ { + S.Write(salt) + } + S_sum := S.Sum(nil) + S_seq := make([]byte, 0, len(salt)) + for i = len(salt); i > 64; i -= 64 { + S_seq = append(S_seq, S_sum...) + } + S_seq = append(S_seq, S_sum[0:i]...) + + C_sum := A_sum + + for i = 0; i < rounds; i++ { + C := sha512.New() + if (i & 1) != 0 { + C.Write(P_seq) + } else { + C.Write(C_sum) + } + if (i % 3) != 0 { + C.Write(S_seq) + } + if (i % 7) != 0 { + C.Write(P_seq) + } + if (i & 1) != 0 { + C.Write(C_sum) + } else { + C.Write(P_seq) + } + + C_sum = C.Sum(nil) + } + + out := make([]byte, 0, 123) + out = append(out, magicPrefix...) + if isRoundsDef { + out = append(out, []byte("rounds="+strconv.Itoa(rounds)+"$")...) + } + out = append(out, salt...) + out = append(out, '$') + out = append(out, base64_24Bit([]byte{ + C_sum[42], C_sum[21], C_sum[0], + C_sum[1], C_sum[43], C_sum[22], + C_sum[23], C_sum[2], C_sum[44], + C_sum[45], C_sum[24], C_sum[3], + C_sum[4], C_sum[46], C_sum[25], + C_sum[26], C_sum[5], C_sum[47], + C_sum[48], C_sum[27], C_sum[6], + C_sum[7], C_sum[49], C_sum[28], + C_sum[29], C_sum[8], C_sum[50], + C_sum[51], C_sum[30], C_sum[9], + C_sum[10], C_sum[52], C_sum[31], + C_sum[32], C_sum[11], C_sum[53], + C_sum[54], C_sum[33], C_sum[12], + C_sum[13], C_sum[55], C_sum[34], + C_sum[35], C_sum[14], C_sum[56], + C_sum[57], C_sum[36], C_sum[15], + C_sum[16], C_sum[58], C_sum[37], + C_sum[38], C_sum[17], C_sum[59], + C_sum[60], C_sum[39], C_sum[18], + C_sum[19], C_sum[61], C_sum[40], + C_sum[41], C_sum[20], C_sum[62], + C_sum[63], + })...) + + A.Reset() + Alternate.Reset() + P.Reset() + for i = 0; i < len(A_sum); i++ { + A_sum[i] = 0 + } + for i = 0; i < len(AlternateSum); i++ { + AlternateSum[i] = 0 + } + for i = 0; i < len(P_seq); i++ { + P_seq[i] = 0 + } + + return string(out), nil +} + +func generateWRounds() []byte { + salt := make([]byte, 16) + _, _ = rand.Read(salt) + + magicPrefix := "$6$" + out := make([]byte, len(magicPrefix)+5000) + copy(out, magicPrefix) + copy(out[len(magicPrefix):], base64_24Bit(salt)) + return out +} + +func base64_24Bit(src []byte) (hash []byte) { + if len(src) == 0 { + return []byte{} + } + alphabet := "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" + + hashSize := (len(src) * 8) / 6 + if (len(src) % 6) != 0 { + hashSize += 1 + } + hash = make([]byte, hashSize) + + dst := hash + for len(src) > 0 { + switch len(src) { + default: + dst[0] = alphabet[src[0]&0x3f] + dst[1] = alphabet[((src[0]>>6)|(src[1]<<2))&0x3f] + dst[2] = alphabet[((src[1]>>4)|(src[2]<<4))&0x3f] + dst[3] = alphabet[(src[2]>>2)&0x3f] + src = src[3:] + dst = dst[4:] + case 2: + dst[0] = alphabet[src[0]&0x3f] + dst[1] = alphabet[((src[0]>>6)|(src[1]<<2))&0x3f] + dst[2] = alphabet[(src[1]>>4)&0x3f] + src = src[2:] + dst = dst[3:] + case 1: + dst[0] = alphabet[src[0]&0x3f] + dst[1] = alphabet[(src[0]>>6)&0x3f] + src = src[1:] + dst = dst[2:] + } + } + + return +} diff --git a/agent/utils/toolbox/pure-ftpd.go b/agent/utils/toolbox/pure-ftpd.go new file mode 100644 index 0000000..63ac63b --- /dev/null +++ b/agent/utils/toolbox/pure-ftpd.go @@ -0,0 +1,338 @@ +package toolbox + +import ( + "bufio" + "fmt" + "os" + "os/user" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/cmd" + "github.com/1Panel-dev/1Panel/agent/utils/controller" + "github.com/1Panel-dev/1Panel/agent/utils/toolbox/helper" +) + +type Ftp struct { + DefaultUser string + DefaultGroup string +} + +type FtpList struct { + User string + Path string + Status string +} + +type FtpLog struct { + IP string `json:"ip"` + User string `json:"user"` + Time string `json:"time"` + Operation string `json:"operation"` + Status string `json:"status"` + Size string `json:"size"` +} + +type FtpClient interface { + Status() (bool, bool) + Operate(operate string) error + LoadList() ([]FtpList, error) + UserAdd(username, path, passwd string) error + UserDel(username string) error + SetPasswd(username, passwd string) error + Reload() error + LoadLogs() ([]FtpLog, error) +} + +func NewFtpClient() (*Ftp, error) { + userItem, err := user.LookupId("1000") + if err == nil { + groupItem, err := user.LookupGroupId(userItem.Gid) + if err != nil { + return nil, err + } + return &Ftp{DefaultUser: userItem.Username, DefaultGroup: groupItem.Name}, err + } + if err.Error() != user.UnknownUserIdError(1000).Error() { + return nil, err + } + + groupItem, err := user.LookupGroupId("1000") + if err == nil { + if err := cmd.RunDefaultBashCf("useradd -u 1000 -g %s %s", groupItem.Name, "1panel"); err != nil { + return nil, err + } + return &Ftp{DefaultUser: "1panel", DefaultGroup: groupItem.Name}, nil + } + if err.Error() != user.UnknownGroupIdError("1000").Error() { + return nil, err + } + if err := cmd.RunDefaultBashC("groupadd -g 1000 1panel"); err != nil { + return nil, err + } + if err := cmd.RunDefaultBashC("useradd -u 1000 -g 1panel 1panel"); err != nil { + return nil, err + } + return &Ftp{DefaultUser: "1panel", DefaultGroup: "1panel"}, nil +} + +func (f *Ftp) Status() (bool, bool) { + isActive, _ := controller.CheckActive("pure-ftpd.service") + isExist, _ := controller.CheckExist("pure-ftpd.service") + + return isActive, isExist +} + +func (f *Ftp) Operate(operate string) error { + switch operate { + case "start", "restart", "stop": + if err := controller.Handle(operate, "pure-ftpd.service"); err != nil { + return fmt.Errorf("%s the pure-ftpd.service failed, err: %v", operate, err) + } + return nil + default: + return fmt.Errorf("not support such operation: %v", operate) + } +} + +func (f *Ftp) UserAdd(username, passwd, path string) error { + entry, err := generatePureFtpEntrySimple(username, passwd, path) + if err != nil { + return fmt.Errorf("generate pure-ftpd entry failed, err: %v", err) + } + pwdFile, err := os.OpenFile("/etc/pure-ftpd/pureftpd.passwd", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) + if err != nil { + return err + } + defer pwdFile.Close() + + _, err = pwdFile.WriteString("\n" + entry + "\n") + if err != nil { + return err + } + _ = f.Reload() + if err := cmd.RunDefaultBashCf("chown -R %s:%s %s", f.DefaultUser, f.DefaultGroup, path); err != nil { + return err + } + return nil +} + +func (f *Ftp) UserDel(username string) error { + if err := cmd.RunDefaultBashCf("pure-pw userdel %s", username); err != nil { + return err + } + _ = f.Reload() + return nil +} + +func (f *Ftp) SetPasswd(username, passwd string) error { + hashedPassword, err := helper.Generate([]byte(passwd)) + if err != nil { + return err + } + pwdFile, err := os.Open("/etc/pure-ftpd/pureftpd.passwd") + if err != nil { + return err + } + defer pwdFile.Close() + + var entrys []string + scanner := bufio.NewScanner(pwdFile) + for scanner.Scan() { + line := scanner.Text() + if line == "" { + continue + } + userEntry := strings.Split(line, ":") + if len(userEntry) < 2 { + continue + } + if userEntry[0] == username { + userEntry[1] = string(hashedPassword) + line = strings.Join(userEntry, ":") + } + entrys = append(entrys, line) + } + + if err := scanner.Err(); err != nil { + return err + } + pwdFile.Close() + + pwdFile, err = os.Create("/etc/pure-ftpd/pureftpd.passwd") + if err != nil { + return err + } + defer pwdFile.Close() + + for _, entry := range entrys { + _, err := pwdFile.WriteString(entry + "\n") + if err != nil { + return err + } + } + + return nil +} + +func (f *Ftp) SetPath(username, path string) error { + if err := cmd.RunDefaultBashCf("pure-pw usermod %s -d %s", username, path); err != nil { + return err + } + if err := cmd.RunDefaultBashCf("chown -R %s:%s %s", f.DefaultUser, f.DefaultGroup, path); err != nil { + return err + } + return nil +} + +func (f *Ftp) SetStatus(username, status string) error { + statusItem := "''" + if status == constant.StatusDisable { + statusItem = "1" + } + if err := cmd.RunDefaultBashCf("pure-pw usermod %s -r %s", username, statusItem); err != nil { + return err + } + return nil +} + +func (f *Ftp) LoadList() ([]FtpList, error) { + std, err := cmd.RunDefaultWithStdoutBashC("pure-pw list") + if err != nil { + return nil, err + } + var lists []FtpList + lines := strings.Split(std, "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) < 2 { + continue + } + std2, err := cmd.RunDefaultWithStdoutBashCf("pure-pw show %s | grep 'Allowed client IPs :'", parts[0]) + if err != nil { + global.LOG.Errorf("handle pure-pw show %s failed, %v", parts[0], err) + continue + } + status := constant.StatusDisable + itemStd := strings.ReplaceAll(std2, "\n", "") + if len(strings.TrimSpace(strings.ReplaceAll(itemStd, "Allowed client IPs :", ""))) == 0 { + status = constant.StatusEnable + } + lists = append(lists, FtpList{User: parts[0], Path: strings.ReplaceAll(parts[1], "/./", ""), Status: status}) + } + return lists, nil +} + +func (f *Ftp) Reload() error { + if err := cmd.RunDefaultBashC("pure-pw mkdb"); err != nil { + return err + } + return nil +} + +func (f *Ftp) LoadLogs(user, operation string) ([]FtpLog, error) { + var logs []FtpLog + logItem := "" + if _, err := os.Stat("/etc/pure-ftpd/conf"); err != nil && os.IsNotExist(err) { + std, err := cmd.RunDefaultWithStdoutBashC("cat /etc/pure-ftpd/pure-ftpd.conf | grep AltLog | grep clf:") + logItem = "/var/log/pureftpd.log" + if err == nil && !strings.HasPrefix(std, "#") { + logItem = std + } + } else { + if err != nil { + return logs, err + } + std, err := cmd.RunDefaultWithStdoutBashC("cat /etc/pure-ftpd/conf/AltLog") + logItem = "/var/log/pure-ftpd/transfer.log" + if err != nil && !strings.HasPrefix(std, "#") { + logItem = std + } + } + + logItem = strings.ReplaceAll(logItem, "AltLog", "") + logItem = strings.ReplaceAll(logItem, "clf:", "") + logItem = strings.ReplaceAll(logItem, "\n", "") + logPath := strings.Trim(logItem, " ") + + logDir := path.Dir(logPath) + filesItem, err := os.ReadDir(logDir) + if err != nil { + return logs, err + } + var fileList []string + for i := 0; i < len(filesItem); i++ { + if filesItem[i].IsDir() { + continue + } + itemPath := path.Join(logDir, filesItem[i].Name()) + if !strings.HasSuffix(itemPath, ".gz") { + fileList = append(fileList, itemPath) + continue + } + itemFileName := strings.TrimSuffix(itemPath, ".gz") + if _, err := os.Stat(itemFileName); err != nil && os.IsNotExist(err) { + if err := handleGunzip(itemPath); err == nil { + fileList = append(fileList, itemFileName) + } + } + } + logs = loadLogsByFiles(fileList, user, operation) + return logs, nil +} + +func handleGunzip(path string) error { + if err := cmd.RunDefaultBashCf("gunzip %s", path); err != nil { + return err + } + return nil +} + +func loadLogsByFiles(fileList []string, user, operation string) []FtpLog { + var logs []FtpLog + layout := "02/Jan/2006:15:04:05-0700" + for _, file := range fileList { + data, err := os.ReadFile(file) + if err != nil { + continue + } + lines := strings.Split(string(data), "\n") + for _, line := range lines { + parts := strings.Fields(line) + if len(parts) < 9 { + continue + } + if (len(user) != 0 && parts[2] != user) || (len(operation) != 0 && parts[5] != fmt.Sprintf("\"%s", operation)) { + continue + } + timeStr := parts[3] + parts[4] + timeStr = strings.ReplaceAll(timeStr, "[", "") + timeStr = strings.ReplaceAll(timeStr, "]", "") + timeItem, err := time.Parse(layout, timeStr) + if err == nil { + timeStr = timeItem.Format(constant.DateTimeLayout) + } + operateStr := parts[5] + parts[6] + logs = append(logs, FtpLog{ + IP: parts[0], + User: parts[2], + Time: timeStr, + Operation: operateStr, + Status: parts[7], + Size: parts[8], + }) + } + } + return logs +} + +func generatePureFtpEntrySimple(username, password, path string) (string, error) { + passwdAfterSha512, err := helper.Generate([]byte(password)) + if err != nil { + return "", err + } + return fmt.Sprintf("%s:%s:1000:1000::%s/./::::::::::::", username, passwdAfterSha512, path), nil +} diff --git a/agent/utils/version/version.go b/agent/utils/version/version.go new file mode 100644 index 0000000..4f2f6a4 --- /dev/null +++ b/agent/utils/version/version.go @@ -0,0 +1,216 @@ +package version + +import ( + "context" + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net" + "net/http" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/app/dto" + "github.com/1Panel-dev/1Panel/agent/app/model" + "github.com/1Panel-dev/1Panel/agent/constant" + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" +) + +func GetUpgradeVersionInfo() (*dto.UpgradeInfo, error) { + var upgrade dto.UpgradeInfo + var currentVersion model.Setting + if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "SystemVersion").First(¤tVersion).Error; err != nil { + global.LOG.Errorf("load %s from db setting failed, err: %v", "SystemVersion", err) + return nil, err + } + var developerMode model.Setting + if err := global.CoreDB.Model(&model.Setting{}).Where("key = ?", "DeveloperMode").First(&developerMode).Error; err != nil { + global.LOG.Errorf("load %s from db setting failed, err: %v", "DeveloperMode", err) + return nil, err + } + + upgrade.TestVersion, upgrade.NewVersion, upgrade.LatestVersion = loadVersionByMode(developerMode.Value, currentVersion.Value) + var itemVersion string + if len(upgrade.NewVersion) != 0 { + itemVersion = upgrade.NewVersion + } + if (global.CONF.Base.Mode == "dev" || developerMode.Value == constant.StatusEnable) && len(upgrade.TestVersion) != 0 { + itemVersion = upgrade.TestVersion + } + if len(upgrade.LatestVersion) != 0 { + itemVersion = upgrade.LatestVersion + } + if len(itemVersion) == 0 { + return &upgrade, nil + } + mode := global.CONF.Base.Mode + if strings.Contains(itemVersion, "beta") { + mode = "beta" + } + notes, err := loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.RemoteURL.RepoUrl, mode, itemVersion, itemVersion)) + if err != nil { + return nil, fmt.Errorf("load releases-notes of version %s failed, err: %v", itemVersion, err) + } + upgrade.ReleaseNote = notes + return &upgrade, nil +} + +func loadVersionByMode(developer, currentVersion string) (string, string, string) { + var current, latest string + if global.CONF.Base.Mode == "dev" { + betaVersionLatest := loadVersion(true, currentVersion, "beta") + devVersionLatest := loadVersion(true, currentVersion, "dev") + if common.ComparePanelVersion(betaVersionLatest, devVersionLatest) { + return betaVersionLatest, "", "" + } + return devVersionLatest, "", "" + } + + betaVersionLatest := "" + latest = loadVersion(true, currentVersion, "stable") + current = loadVersion(false, currentVersion, "stable") + if developer == constant.StatusEnable { + betaVersionLatest = loadVersion(true, currentVersion, "beta") + } + if current != latest { + return betaVersionLatest, current, latest + } + + versionPart := strings.Split(current, ".") + if len(versionPart) < 3 { + return betaVersionLatest, current, latest + } + num, _ := strconv.Atoi(versionPart[1]) + if num == 0 { + return betaVersionLatest, current, latest + } + if num >= 10 { + if current[:6] == currentVersion[:6] { + return betaVersionLatest, current, "" + } + return betaVersionLatest, "", latest + } + if current[:5] == currentVersion[:5] { + return betaVersionLatest, current, "" + } + return betaVersionLatest, "", latest +} + +func loadVersion(isLatest bool, currentVersion, mode string) string { + path := fmt.Sprintf("%s/%s/latest", global.CONF.RemoteURL.RepoUrl, mode) + if !isLatest { + path = fmt.Sprintf("%s/%s/latest.current", global.CONF.RemoteURL.RepoUrl, mode) + } + _, latestVersionRes, err := HandleRequest(path, http.MethodGet, constant.TimeOut20s) + if err != nil { + global.LOG.Errorf("load latest version from oss failed, err: %v", err) + return "" + } + version := string(latestVersionRes) + if strings.Contains(version, "<") { + global.LOG.Errorf("load latest version from oss failed, err: %v", version) + return "" + } + if isLatest { + return checkVersion(version, currentVersion) + } + + versionMap := make(map[string]string) + if err := json.Unmarshal(latestVersionRes, &versionMap); err != nil { + global.LOG.Errorf("load latest version from oss failed (error unmarshal), err: %v", err) + return "" + } + + versionPart := strings.Split(currentVersion, ".") + if len(versionPart) < 3 { + global.LOG.Errorf("current version is error format: %s", currentVersion) + return "" + } + num, _ := strconv.Atoi(versionPart[1]) + if num >= 10 { + if version, ok := versionMap[currentVersion[0:5]]; ok { + return checkVersion(version, currentVersion) + } + return "" + } + if version, ok := versionMap[currentVersion[0:4]]; ok { + return checkVersion(version, currentVersion) + } + return "" +} + +func checkVersion(v2, v1 string) string { + addSuffix := false + if !strings.Contains(v1, "-") { + v1 = v1 + "-lts" + } + if !strings.Contains(v2, "-") { + addSuffix = true + v2 = v2 + "-lts" + } + if common.ComparePanelVersion(v2, v1) { + if addSuffix { + return strings.TrimSuffix(v2, "-lts") + } + return v2 + } + return "" +} + +func loadReleaseNotes(path string) (string, error) { + _, releaseNotes, err := HandleRequest(path, http.MethodGet, constant.TimeOut20s) + if err != nil { + return "", err + } + return string(releaseNotes), nil +} + +func HandleRequest(url, method string, timeout int) (int, []byte, error) { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("handle request failed, error message: %v", r) + return + } + }() + + transport := loadRequestTransport() + client := http.Client{Timeout: time.Duration(timeout) * time.Second, Transport: transport} + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + request, err := http.NewRequestWithContext(ctx, method, url, nil) + if err != nil { + return 0, nil, err + } + request.Header.Set("Content-Type", "application/json") + resp, err := client.Do(request) + if err != nil { + return 0, nil, err + } + if resp.StatusCode != http.StatusOK { + return 0, nil, errors.New(resp.Status) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, nil, err + } + defer resp.Body.Close() + + return resp.StatusCode, body, nil +} + +func loadRequestTransport() *http.Transport { + return &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DialContext: (&net.Dialer{ + Timeout: 60 * time.Second, + KeepAlive: 60 * time.Second, + }).DialContext, + TLSHandshakeTimeout: 5 * time.Second, + ResponseHeaderTimeout: 10 * time.Second, + IdleConnTimeout: 15 * time.Second, + } +} diff --git a/agent/utils/websocket/client.go b/agent/utils/websocket/client.go new file mode 100644 index 0000000..e9fdf95 --- /dev/null +++ b/agent/utils/websocket/client.go @@ -0,0 +1,59 @@ +package websocket + +import ( + "sync/atomic" + + "github.com/gorilla/websocket" +) + +const MaxMessageQuenue = 32 + +type Client struct { + ID string + Socket *websocket.Conn + Msg chan []byte + closed atomic.Bool +} + +func NewWsClient(ID string, socket *websocket.Conn) *Client { + return &Client{ + ID: ID, + Socket: socket, + Msg: make(chan []byte, MaxMessageQuenue), + } +} + +func (c *Client) Read() { + defer func() { + c.closed.Store(true) + close(c.Msg) + }() + for { + _, message, err := c.Socket.ReadMessage() + if err != nil { + return + } + ProcessData(c, message) + } +} + +func (c *Client) Write() { + defer c.Socket.Close() + for { + message, ok := <-c.Msg + if !ok { + return + } + _ = c.Socket.WriteMessage(websocket.TextMessage, message) + } +} + +func (c *Client) Send(res []byte) { + if c.closed.Load() { + return + } + select { + case c.Msg <- res: + default: + } +} diff --git a/agent/utils/websocket/process_data.go b/agent/utils/websocket/process_data.go new file mode 100644 index 0000000..eddf551 --- /dev/null +++ b/agent/utils/websocket/process_data.go @@ -0,0 +1,411 @@ +package websocket + +import ( + "context" + "encoding/json" + "fmt" + "os" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/agent/global" + "github.com/1Panel-dev/1Panel/agent/utils/common" + "github.com/1Panel-dev/1Panel/agent/utils/files" + "github.com/shirou/gopsutil/v4/host" + "github.com/shirou/gopsutil/v4/net" + "github.com/shirou/gopsutil/v4/process" +) + +const defaultTimeout = 10 * time.Second + +type WsInput struct { + Type string `json:"type"` + DownloadProgress + PsProcessConfig + SSHSessionConfig + NetConfig +} + +type DownloadProgress struct { + Keys []string `json:"keys"` +} + +type PsProcessConfig struct { + Pid int32 `json:"pid"` + Name string `json:"name"` + Username string `json:"username"` +} + +type SSHSessionConfig struct { + LoginUser string `json:"loginUser"` + LoginIP string `json:"loginIP"` +} + +type NetConfig struct { + Port uint32 `json:"port"` + ProcessName string `json:"processName"` + ProcessID int32 `json:"processID"` +} + +type PsProcessData struct { + PID int32 `json:"PID"` + Name string `json:"name"` + PPID int32 `json:"PPID"` + Username string `json:"username"` + Status string `json:"status"` + StartTime string `json:"startTime"` + NumThreads int32 `json:"numThreads"` + NumConnections int `json:"numConnections"` + CpuPercent string `json:"cpuPercent"` + + DiskRead string `json:"diskRead"` + DiskWrite string `json:"diskWrite"` + CmdLine string `json:"cmdLine"` + + Rss string `json:"rss"` + VMS string `json:"vms"` + HWM string `json:"hwm"` + Data string `json:"data"` + Stack string `json:"stack"` + Locked string `json:"locked"` + Swap string `json:"swap"` + Dirty string `json:"dirty"` + PSS string `json:"pss"` + USS string `json:"uss"` + Shared string `json:"shared"` + Text string `json:"text"` + + CpuValue float64 `json:"cpuValue"` + RssValue uint64 `json:"rssValue"` + + Envs []string `json:"envs"` + + OpenFiles []process.OpenFilesStat `json:"openFiles"` + Connects []ProcessConnect `json:"connects"` +} + +type ProcessConnect struct { + Type string `json:"type"` + Status string `json:"status"` + Laddr net.Addr `json:"localaddr"` + Raddr net.Addr `json:"remoteaddr"` + PID int32 `json:"PID"` + Name string `json:"name"` +} + +type ProcessConnects []ProcessConnect + +type sshSession struct { + Username string `json:"username"` + PID int32 `json:"PID"` + Terminal string `json:"terminal"` + Host string `json:"host"` + LoginTime string `json:"loginTime"` +} + +func ProcessData(c *Client, inputMsg []byte) { + wsInput := &WsInput{} + err := json.Unmarshal(inputMsg, wsInput) + if err != nil { + global.LOG.Errorf("unmarshal wsInput error,err %s", err.Error()) + return + } + switch wsInput.Type { + case "wget": + res, err := getDownloadProcess(wsInput.DownloadProgress) + if err != nil { + return + } + c.Send(res) + case "ps": + res, err := getProcessData(wsInput.PsProcessConfig) + if err != nil { + return + } + c.Send(res) + case "ssh": + res, err := getSSHSessions(wsInput.SSHSessionConfig) + if err != nil { + return + } + c.Send(res) + case "net": + res, err := getNetConnections(wsInput.NetConfig) + if err != nil { + return + } + c.Send(res) + } + +} + +func getDownloadProcess(progress DownloadProgress) (res []byte, err error) { + var result []files.Process + for _, k := range progress.Keys { + value := global.CACHE.Get(k) + if value == "" { + return nil, fmt.Errorf("get cache error,err value is nil") + } + downloadProcess := &files.Process{} + _ = json.Unmarshal([]byte(value), downloadProcess) + result = append(result, *downloadProcess) + if downloadProcess.Percent == 100 { + global.CACHE.Del(k) + } + } + res, err = json.Marshal(result) + return +} + +func handleProcessData(proc *process.Process, processConfig *PsProcessConfig, pidConnections map[int32][]net.ConnectionStat) *PsProcessData { + if processConfig.Pid > 0 && processConfig.Pid != proc.Pid { + return nil + } + procData := PsProcessData{ + PID: proc.Pid, + } + if procName, err := proc.Name(); err == nil { + procData.Name = procName + } else { + procData.Name = "" + } + if processConfig.Name != "" && !strings.Contains(procData.Name, processConfig.Name) { + return nil + } + if username, err := proc.Username(); err == nil { + procData.Username = username + } + if processConfig.Username != "" && !strings.Contains(procData.Username, processConfig.Username) { + return nil + } + procData.PPID, _ = proc.Ppid() + statusArray, _ := proc.Status() + if len(statusArray) > 0 { + procData.Status = strings.Join(statusArray, ",") + } + createTime, procErr := proc.CreateTime() + if procErr == nil { + t := time.Unix(createTime/1000, 0) + procData.StartTime = t.Format("2006-1-2 15:04:05") + } + procData.NumThreads, _ = proc.NumThreads() + procData.CpuValue, _ = proc.CPUPercent() + procData.CpuPercent = fmt.Sprintf("%.2f%%", procData.CpuValue) + + if memInfo, err := proc.MemoryInfo(); err == nil { + procData.RssValue = memInfo.RSS + procData.Rss = common.FormatBytes(memInfo.RSS) + } else { + procData.RssValue = 0 + } + + if connections, ok := pidConnections[proc.Pid]; ok { + procData.NumConnections = len(connections) + } + + return &procData +} + +func getProcessData(processConfig PsProcessConfig) (res []byte, err error) { + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + processes, err := process.ProcessesWithContext(ctx) + if err != nil { + return + } + + connections, err := net.ConnectionsMaxWithContext(ctx, "all", 32768) + if err != nil { + return + } + + pidConnections := make(map[int32][]net.ConnectionStat, len(processes)) + for _, conn := range connections { + if conn.Pid == 0 { + continue + } + pidConnections[conn.Pid] = append(pidConnections[conn.Pid], conn) + } + + result := make([]PsProcessData, 0, len(processes)) + + for _, proc := range processes { + procData := handleProcessData(proc, &processConfig, pidConnections) + if procData != nil { + result = append(result, *procData) + } + } + + res, err = json.Marshal(result) + return +} + +func getSSHSessions(config SSHSessionConfig) (res []byte, err error) { + var ( + result []sshSession + users []host.UserStat + processes []*process.Process + ) + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + users, err = host.UsersWithContext(ctx) + if err != nil { + res, err = json.Marshal(result) + return + } + + usersByHost := make(map[string][]host.UserStat) + for _, user := range users { + if user.Host == "" { + continue + } + if config.LoginUser != "" && !strings.Contains(user.User, config.LoginUser) { + continue + } + if config.LoginIP != "" && !strings.Contains(user.Host, config.LoginIP) { + continue + } + usersByHost[user.Host] = append(usersByHost[user.Host], user) + } + + if len(usersByHost) == 0 { + res, err = json.Marshal(result) + return + } + + processes, err = process.ProcessesWithContext(ctx) + if err != nil { + res, err = json.Marshal(result) + return + } + + for _, proc := range processes { + name, _ := proc.Name() + if name != "sshd" || proc.Pid == 0 { + continue + } + connections, _ := proc.Connections() + if len(connections) == 0 { + continue + } + + cmdline, cmdErr := proc.Cmdline() + if cmdErr != nil { + continue + } + + for _, conn := range connections { + matchedUsers, exists := usersByHost[conn.Raddr.IP] + if !exists { + continue + } + + for _, user := range matchedUsers { + if strings.Contains(cmdline, user.Terminal) { + t := time.Unix(int64(user.Started), 0) + result = append(result, sshSession{ + Username: user.User, + Host: user.Host, + Terminal: user.Terminal, + PID: proc.Pid, + LoginTime: t.Format("2006-1-2 15:04:05"), + }) + } + } + } + } + res, err = json.Marshal(result) + return +} + +func getNetConnections(config NetConfig) (res []byte, err error) { + result := make([]ProcessConnect, 0, 1024) + ctx, cancel := context.WithTimeout(context.Background(), defaultTimeout) + defer cancel() + + connections, err := net.ConnectionsMaxWithContext(ctx, "all", 32768) + if err != nil { + res, _ = json.Marshal(result) + return + } + + pidConnectionsMap := make(map[int32][]net.ConnectionStat, 256) + pidNameMap := make(map[int32]string, 256) + + for _, conn := range connections { + if conn.Family != 2 && conn.Family != 10 { + continue + } + + if conn.Pid == 0 { + continue + } + + if config.ProcessID > 0 && conn.Pid != config.ProcessID { + continue + } + + if config.Port > 0 && conn.Laddr.Port != config.Port && conn.Raddr.Port != config.Port { + continue + } + + if _, exists := pidNameMap[conn.Pid]; !exists { + pName, _ := getProcessNameWithContext(ctx, conn.Pid) + if pName == "" { + pName = "" + } + pidNameMap[conn.Pid] = pName + } + + pidConnectionsMap[conn.Pid] = append(pidConnectionsMap[conn.Pid], conn) + } + + for pid, connections := range pidConnectionsMap { + pName := pidNameMap[pid] + if config.ProcessName != "" && !strings.Contains(pName, config.ProcessName) { + continue + } + for _, conn := range connections { + result = append(result, ProcessConnect{ + Type: getConnectionType(conn.Type, conn.Family), + Status: conn.Status, + Laddr: conn.Laddr, + Raddr: conn.Raddr, + PID: conn.Pid, + Name: pName, + }) + } + } + + res, err = json.Marshal(result) + return +} + +func getProcessNameWithContext(ctx context.Context, pid int32) (string, error) { + data, err := os.ReadFile(fmt.Sprintf("/proc/%d/comm", pid)) + if err == nil && len(data) > 0 { + return strings.TrimSpace(string(data)), nil + } + p, err := process.NewProcessWithContext(ctx, pid) + if err != nil { + return "", err + } + return p.Name() +} + +func getConnectionType(connType uint32, family uint32) string { + switch { + case connType == 1 && family == 2: + return "tcp" + case connType == 1 && family == 10: + return "tcp6" + case connType == 2 && family == 2: + return "udp" + case connType == 2 && family == 10: + return "udp6" + default: + return "unknown" + } +} diff --git a/ci/script.sh b/ci/script.sh new file mode 100755 index 0000000..556002a --- /dev/null +++ b/ci/script.sh @@ -0,0 +1,40 @@ +#!/bin/bash + +set -e + +command -v wget >/dev/null || { + echo "wget not found, please install it and try again." + exit 1 +} + +if [ ! -f "1pctl" ]; then + wget https://github.com/1Panel-dev/installer/raw/v2/1pctl +fi + +if [ ! -f "install.sh" ]; then + wget https://github.com/1Panel-dev/installer/raw/v2/install.sh +fi + +if [ ! -d "initscript" ]; then + wget https://github.com/1Panel-dev/installer/raw/v2/initscript/1panel-core.service + wget https://github.com/1Panel-dev/installer/raw/v2/initscript/1panel-agent.service + mkdir -p initscript && cd initscript + for file in 1panel-core.init 1panel-agent.init 1panel-core.openrc 1panel-agent.openrc 1panel-core.procd 1panel-agent.procd 1panel-core.service 1panel-agent.service; do + wget -q https://github.com/1Panel-dev/installer/raw/v2/initscript/$file + done + cd .. +fi + +if [ ! -d "lang" ]; then + mkdir -p lang && cd lang + for lang in en fa pt-BR ru zh; do + wget -q https://github.com/1Panel-dev/installer/raw/v2/lang/$lang.sh + done + cd .. +fi + +if [ ! -f "GeoIP.mmdb" ]; then + wget https://resource.fit2cloud.com/1panel/package/v2/geo/GeoIP.mmdb +fi + +chmod 755 1pctl install.sh diff --git a/core/app/api/v2/auth.go b/core/app/api/v2/auth.go new file mode 100644 index 0000000..4ce6087 --- /dev/null +++ b/core/app/api/v2/auth.go @@ -0,0 +1,253 @@ +package v2 + +import ( + "encoding/base64" + "net/http" + "os" + "path" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/captcha" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/gin-gonic/gin" +) + +type BaseApi struct{} + +// @Tags Auth +// @Summary User login +// @Accept json +// @Param EntranceCode header string true "安全入口 base64 加密串" +// @Param request body dto.Login true "request" +// @Success 200 {object} dto.UserLoginInfo +// @Router /core/auth/login [post] +func (b *BaseApi) Login(c *gin.Context) { + var req dto.Login + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + ip := common.GetRealClientIP(c) + needCaptcha := global.IPTracker.NeedCaptcha(ip) + if needCaptcha { + if errMsg := captcha.VerifyCode(req.CaptchaID, req.Captcha); errMsg != "" { + helper.BadAuth(c, errMsg, nil) + return + } + } + + entranceItem := c.Request.Header.Get("EntranceCode") + var entrance []byte + if len(entranceItem) != 0 { + entrance, _ = base64.StdEncoding.DecodeString(entranceItem) + } + if len(entrance) == 0 { + cookieValue, err := c.Cookie("SecurityEntrance") + if err == nil { + entrance, _ = base64.StdEncoding.DecodeString(cookieValue) + } + } + + user, msgKey, err := authService.Login(c, req, string(entrance)) + go saveLoginLogs(c, err) + if msgKey == "ErrAuth" || msgKey == "ErrEntrance" { + if msgKey == "ErrAuth" { + global.IPTracker.SetNeedCaptcha(ip) + } + helper.BadAuth(c, msgKey, err) + return + } + if err != nil { + global.IPTracker.SetNeedCaptcha(ip) + helper.InternalServer(c, err) + return + } + global.IPTracker.Clear(ip) + helper.SuccessWithData(c, user) +} + +// @Tags Auth +// @Summary User login with mfa +// @Accept json +// @Param request body dto.MFALogin true "request" +// @Success 200 {object} dto.UserLoginInfo +// @Router /core/auth/mfalogin [post] +// @Header 200 {string} EntranceCode "安全入口" +func (b *BaseApi) MFALogin(c *gin.Context) { + var req dto.MFALogin + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + entranceItem := c.Request.Header.Get("EntranceCode") + var entrance []byte + if len(entranceItem) != 0 { + entrance, _ = base64.StdEncoding.DecodeString(entranceItem) + } + + user, msgKey, err := authService.MFALogin(c, req, string(entrance)) + if msgKey == "ErrAuth" { + helper.BadAuth(c, msgKey, err) + return + } + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, user) +} + +// @Tags Auth +// @Summary User login with passkey +// @Success 200 {object} dto.PasskeyBeginResponse +// @Router /core/auth/passkey/begin [post] +func (b *BaseApi) PasskeyBeginLogin(c *gin.Context) { + entrance := loadEntranceFromRequest(c) + res, msgKey, err := authService.PasskeyBeginLogin(c, entrance) + if msgKey != "" { + if msgKey == "ErrEntrance" { + helper.BadAuth(c, msgKey, err) + return + } + if msgKey == "ErrPasskeyNotConfigured" { + helper.ErrorWithDetail(c, http.StatusNotFound, msgKey, err) + return + } + helper.ErrorWithDetail(c, http.StatusBadRequest, msgKey, err) + return + } + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags Auth +// @Summary User login with passkey +// @Success 200 {object} dto.UserLoginInfo +// @Router /core/auth/passkey/finish [post] +func (b *BaseApi) PasskeyFinishLogin(c *gin.Context) { + sessionID := c.GetHeader("Passkey-Session") + entrance := loadEntranceFromRequest(c) + user, msgKey, err := authService.PasskeyFinishLogin(c, sessionID, entrance) + go saveLoginLogs(c, err) + if msgKey == "ErrAuth" || msgKey == "ErrEntrance" { + if msgKey == "ErrAuth" { + global.IPTracker.SetNeedCaptcha(common.GetRealClientIP(c)) + } + helper.BadAuth(c, msgKey, err) + return + } + if msgKey != "" { + helper.ErrorWithDetail(c, http.StatusBadRequest, msgKey, err) + return + } + if err != nil { + global.IPTracker.SetNeedCaptcha(common.GetRealClientIP(c)) + helper.InternalServer(c, err) + return + } + global.IPTracker.Clear(common.GetRealClientIP(c)) + helper.SuccessWithData(c, user) +} + +// @Tags Auth +// @Summary User logout +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/auth/logout [post] +func (b *BaseApi) LogOut(c *gin.Context) { + if err := authService.LogOut(c); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Auth +// @Summary Load captcha +// @Success 200 {object} dto.CaptchaResponse +// @Router /core/auth/captcha [get] +func (b *BaseApi) Captcha(c *gin.Context) { + captcha, err := captcha.CreateCaptcha() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, captcha) +} + +func (b *BaseApi) GetWelcomePage(c *gin.Context) { + count, _, _ := logService.PageLoginLog(c, dto.SearchLgLogWithPage{PageInfo: dto.PageInfo{Page: 1, PageSize: 10}}) + if count != 1 { + helper.Success(c) + return + } + file, err := os.ReadFile(path.Join(global.CONF.Base.InstallDir, "1panel/welcome/index.html")) + if err != nil { + helper.Success(c) + return + } + helper.SuccessWithData(c, string(file)) +} + +// @Tags Auth +// @Summary Get Setting For Login +// @Success 200 {object} dto.SystemSetting +// @Router /core/auth/setting [get] +func (b *BaseApi) GetLoginSetting(c *gin.Context) { + settingInfo, err := settingService.GetSettingInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + ip := common.GetRealClientIP(c) + needCaptcha := global.IPTracker.NeedCaptcha(ip) + res := &dto.LoginSetting{ + IsDemo: global.CONF.Base.IsDemo, + IsIntl: global.CONF.Base.IsIntl, + IsFxplay: global.CONF.Base.IsFxplay, + IsOffLine: global.CONF.Base.IsOffLine, + Language: settingInfo.Language, + MenuTabs: settingInfo.MenuTabs, + PanelName: settingInfo.PanelName, + Theme: settingInfo.Theme, + NeedCaptcha: needCaptcha, + } + res.PasskeySetting = authService.PasskeyStatus(c) + helper.SuccessWithData(c, res) +} + +func saveLoginLogs(c *gin.Context, err error) { + var logs model.LoginLog + if err != nil { + logs.Status = constant.StatusFailed + logs.Message = err.Error() + } else { + logs.Status = constant.StatusSuccess + } + logs.IP = c.ClientIP() + logs.Agent = c.GetHeader("User-Agent") + _ = logService.CreateLoginLog(logs) +} + +func loadEntranceFromRequest(c *gin.Context) string { + entranceItem := c.Request.Header.Get("EntranceCode") + var entrance []byte + if len(entranceItem) != 0 { + entrance, _ = base64.StdEncoding.DecodeString(entranceItem) + } + if len(entrance) == 0 { + cookieValue, err := c.Cookie("SecurityEntrance") + if err == nil { + entrance, _ = base64.StdEncoding.DecodeString(cookieValue) + } + } + return string(entrance) +} diff --git a/core/app/api/v2/backup.go b/core/app/api/v2/backup.go new file mode 100644 index 0000000..1ed528b --- /dev/null +++ b/core/app/api/v2/backup.go @@ -0,0 +1,116 @@ +package v2 + +import ( + "fmt" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Backup Account +// @Summary Create backup account +// @Accept json +// @Param request body dto.BackupOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/backups [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建备份账号 [type]","formatEN":"create backup account [type]"} +func (b *BaseApi) CreateBackup(c *gin.Context) { + var req dto.BackupOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := backupService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Refresh token +// @Accept json +// @Param request body dto.OperateByName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/backups/refresh/token [post] +func (b *BaseApi) RefreshToken(c *gin.Context) { + var req dto.OperateByName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := backupService.RefreshToken(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Load backup account base info +// @Accept json +// @Success 200 {object} dto.BackupClientInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/backups/client/:clientType [get] +func (b *BaseApi) LoadBackupClientInfo(c *gin.Context) { + clientType, ok := c.Params.Get("clientType") + if !ok { + helper.BadRequest(c, fmt.Errorf("error %s in path", "clientType")) + return + } + data, err := backupService.LoadBackupClientInfo(clientType) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags Backup Account +// @Summary Delete backup account +// @Accept json +// @Param request body dto.OperateByName true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/backups/del [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"删除备份账号 [name]","formatEN":"delete backup account [name]"} +func (b *BaseApi) DeleteBackup(c *gin.Context) { + var req dto.OperateByName + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := backupService.Delete(req.Name); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Backup Account +// @Summary Update backup account +// @Accept json +// @Param request body dto.BackupOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/backups/update [post] +// @x-panel-log {"bodyKeys":["type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新备份账号 [types]","formatEN":"update backup account [types]"} +func (b *BaseApi) UpdateBackup(c *gin.Context) { + var req dto.BackupOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := backupService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/core/app/api/v2/command.go b/core/app/api/v2/command.go new file mode 100644 index 0000000..df13922 --- /dev/null +++ b/core/app/api/v2/command.go @@ -0,0 +1,241 @@ +package v2 + +import ( + "encoding/csv" + "errors" + "fmt" + "io" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/gin-gonic/gin" +) + +// @Tags Command +// @Summary Upload command csv for list +// @Success 200 {string} path +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/commands/import [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"上传快速命令文件","formatEN":"upload quick commands with csv"} +func (b *BaseApi) UploadCommandCsv(c *gin.Context) { + form, err := c.MultipartForm() + if err != nil { + helper.BadRequest(c, err) + return + } + files := form.File["file"] + if len(files) == 0 { + helper.BadRequest(c, errors.New("no such files")) + return + } + uploadFile, _ := files[0].Open() + reader := csv.NewReader(uploadFile) + if _, err := reader.Read(); err != nil { + helper.BadRequest(c, fmt.Errorf("read title failed, err: %v", err)) + return + } + groupRepo := repo.NewIGroupRepo() + group, _ := groupRepo.Get(repo.WithByType("command"), groupRepo.WithByDefault(true)) + var commands []dto.CommandInfo + for { + record, err := reader.Read() + if err == io.EOF { + break + } + if err != nil { + helper.BadRequest(c, fmt.Errorf("read content failed, err: %v", err)) + return + } + if len(record) >= 2 { + commands = append(commands, dto.CommandInfo{ + Name: record[0], + Type: "command", + GroupID: group.ID, + Command: record[1], + GroupBelong: group.Name, + }) + } + } + + helper.SuccessWithData(c, commands) +} + +// @Tags Command +// @Summary Export command +// @Success 200 {string} path +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/commands/export [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"导出快速命令","formatEN":"export quick commands"} +func (b *BaseApi) ExportCommands(c *gin.Context) { + file, err := commandService.Export() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, file) +} + +// @Tags Command +// @Summary Import command +// @Success 200 {string} path +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/commands/import [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"导入快速命令","formatEN":"import quick commands"} +func (b *BaseApi) ImportCommands(c *gin.Context) { + var req dto.CommandImport + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + groupRepo := repo.NewIGroupRepo() + group, _ := groupRepo.Get(repo.WithByType("command"), groupRepo.WithByDefault(true)) + for _, item := range req.Items { + if item.GroupID == 0 { + item.GroupID = group.ID + } + _ = commandService.Create(item) + } + helper.Success(c) +} + +// @Tags Command +// @Summary Create command +// @Accept json +// @Param request body dto.CommandOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/commands [post] +// @x-panel-log {"bodyKeys":["name","command"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建快捷命令 [name][command]","formatEN":"create quick command [name][command]"} +func (b *BaseApi) CreateCommand(c *gin.Context) { + var req dto.CommandOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := commandService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Command +// @Summary Page commands +// @Accept json +// @Param request body dto.SearchWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/commands/search [post] +func (b *BaseApi) SearchCommand(c *gin.Context) { + var req dto.SearchCommandWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := commandService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Command +// @Summary Tree commands +// @Accept json +// @Param request body dto.OperateByType true "request" +// @Success 200 {array} dto.CommandTree +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/commands/tree [get] +func (b *BaseApi) SearchCommandTree(c *gin.Context) { + var req dto.OperateByType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := commandService.SearchForTree(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Command +// @Summary List commands +// @Accept json +// @Param request body dto.OperateByType true "request" +// @Success 200 {object} dto.CommandInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/commands/command [get] +func (b *BaseApi) ListCommand(c *gin.Context) { + var req dto.OperateByType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := commandService.List(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} + +// @Tags Command +// @Summary Delete command +// @Accept json +// @Param request body dto.OperateByIDs true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/commands/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"commands","output_column":"name","output_value":"names"}],"formatZH":"删除快捷命令 [names]","formatEN":"delete quick command [names]"} +func (b *BaseApi) DeleteCommand(c *gin.Context) { + var req dto.OperateByIDs + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := commandService.Delete(req.IDs); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Command +// @Summary Update command +// @Accept json +// @Param request body dto.CommandOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/commands/update [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新快捷命令 [name]","formatEN":"update quick command [name]"} +func (b *BaseApi) UpdateCommand(c *gin.Context) { + var req dto.CommandOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := commandService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/core/app/api/v2/entry.go b/core/app/api/v2/entry.go new file mode 100644 index 0000000..2c4f9f0 --- /dev/null +++ b/core/app/api/v2/entry.go @@ -0,0 +1,21 @@ +package v2 + +import "github.com/1Panel-dev/1Panel/core/app/service" + +type ApiGroup struct { + BaseApi +} + +var ApiGroupApp = new(ApiGroup) + +var ( + hostService = service.NewIHostService() + authService = service.NewIAuthService() + backupService = service.NewIBackupService() + settingService = service.NewISettingService() + logService = service.NewILogService() + upgradeService = service.NewIUpgradeService() + groupService = service.NewIGroupService() + commandService = service.NewICommandService() + scriptService = service.NewIScriptService() +) diff --git a/core/app/api/v2/group.go b/core/app/api/v2/group.go new file mode 100644 index 0000000..0e92b9c --- /dev/null +++ b/core/app/api/v2/group.go @@ -0,0 +1,96 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags System Group +// @Summary Create group +// @Accept json +// @Param request body dto.GroupCreate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/groups [post] +// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建组 [name][type]","formatEN":"create group [name][type]"} +func (b *BaseApi) CreateGroup(c *gin.Context) { + var req dto.GroupCreate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := groupService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Group +// @Summary Delete group +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/groups/del [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"name","output_value":"name"},{"input_column":"id","input_value":"id","isList":false,"db":"groups","output_column":"type","output_value":"type"}],"formatZH":"删除组 [type][name]","formatEN":"delete group [type][name]"} +func (b *BaseApi) DeleteGroup(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := groupService.Delete(req.ID); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Group +// @Summary Update group +// @Accept json +// @Param request body dto.GroupUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/groups/update [post] +// @x-panel-log {"bodyKeys":["name","type"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新组 [name][type]","formatEN":"update group [name][type]"} +func (b *BaseApi) UpdateGroup(c *gin.Context) { + var req dto.GroupUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := groupService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Group +// @Summary List groups +// @Accept json +// @Param request body dto.GroupSearch true "request" +// @Success 200 {array} dto.OperateByType +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/groups/search [post] +func (b *BaseApi) ListGroup(c *gin.Context) { + var req dto.OperateByType + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + list, err := groupService.List(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, list) +} diff --git a/core/app/api/v2/helper/helper.go b/core/app/api/v2/helper/helper.go new file mode 100644 index 0000000..dd97c37 --- /dev/null +++ b/core/app/api/v2/helper/helper.go @@ -0,0 +1,102 @@ +package helper + +import ( + "errors" + "fmt" + "github.com/1Panel-dev/1Panel/core/cmd/server/res" + "net/http" + "strconv" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/gin-gonic/gin" +) + +func ErrorWithDetail(ctx *gin.Context, code int, msgKey string, err error) { + res := dto.Response{ + Code: code, + Message: "", + } + if msgKey == "ErrCaptchaCode" || msgKey == "ErrAuth" { + res.Code = 401 + res.Message = msgKey + } + res.Message = i18n.GetMsgWithMap(msgKey, map[string]interface{}{"detail": err}) + ctx.JSON(http.StatusOK, res) + ctx.Abort() +} + +func InternalServer(ctx *gin.Context, err error) { + ErrorWithDetail(ctx, http.StatusInternalServerError, "ErrInternalServer", err) +} + +func BadRequest(ctx *gin.Context, err error) { + ErrorWithDetail(ctx, http.StatusBadRequest, "ErrInvalidParams", err) +} + +func BadAuth(ctx *gin.Context, msgKey string, err error) { + ErrorWithDetail(ctx, http.StatusUnauthorized, msgKey, err) +} + +func SuccessWithData(ctx *gin.Context, data interface{}) { + if data == nil { + data = gin.H{} + } + res := dto.Response{ + Code: http.StatusOK, + Data: data, + } + ctx.JSON(http.StatusOK, res) + ctx.Abort() +} + +func Success(ctx *gin.Context) { + res := dto.Response{ + Code: http.StatusOK, + Message: "success", + } + ctx.JSON(http.StatusOK, res) + ctx.Abort() +} + +func CheckBindAndValidate(req interface{}, c *gin.Context) error { + if err := c.ShouldBindJSON(req); err != nil { + ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", err) + return err + } + if err := global.VALID.Struct(req); err != nil { + ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", err) + return err + } + return nil +} + +func ErrWithHtml(ctx *gin.Context, code int, scope string) { + if code == 444 { + ctx.String(444, "") + ctx.Abort() + return + } + file := fmt.Sprintf("html/%d.html", code) + if code == 200 && scope != "" { + file = fmt.Sprintf("html/200_%s.html", scope) + } + data, err := res.ErrorMsg.ReadFile(file) + if err != nil { + ctx.String(http.StatusInternalServerError, "Internal Server Error") + ctx.Abort() + return + } + ctx.Data(code, "text/html; charset=utf-8", data) + ctx.Abort() +} + +func GetParamID(c *gin.Context) (uint, error) { + idParam, ok := c.Params.Get("id") + if !ok { + return 0, errors.New("error id in path") + } + intNum, _ := strconv.Atoi(idParam) + return uint(intNum), nil +} diff --git a/core/app/api/v2/host.go b/core/app/api/v2/host.go new file mode 100644 index 0000000..621f6d0 --- /dev/null +++ b/core/app/api/v2/host.go @@ -0,0 +1,355 @@ +package v2 + +import ( + "encoding/base64" + "encoding/json" + "net/http" + "strconv" + "time" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/service" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/copier" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/1Panel-dev/1Panel/core/utils/ssh" + "github.com/1Panel-dev/1Panel/core/utils/terminal" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/pkg/errors" +) + +// @Tags Host +// @Summary Create host +// @Accept json +// @Param request body dto.HostOperate true "request" +// @Success 200 {object} dto.HostInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/hosts [post] +// @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"创建主机 [name][addr]","formatEN":"create host [name][addr]"} +func (b *BaseApi) CreateHost(c *gin.Context) { + var req dto.HostOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + host, err := hostService.Create(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, host) +} + +// @Tags Host +// @Summary Test host conn by info +// @Accept json +// @Param request body dto.HostConnTest true "request" +// @Success 200 {boolean} status +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/hosts/test/byinfo [post] +func (b *BaseApi) TestByInfo(c *gin.Context) { + var req dto.HostConnTest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + connStatus := hostService.TestByInfo(req) + helper.SuccessWithData(c, connStatus) +} + +// @Tags Host +// @Summary Test host conn by host id +// @Accept json +// @Param id path integer true "request" +// @Success 200 {boolean} connStatus +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/hosts/test/byid/:id [post] +func (b *BaseApi) TestByID(c *gin.Context) { + idParam, ok := c.Params.Get("id") + if !ok { + helper.BadRequest(c, errors.New("no such params find in request")) + return + } + intNum, err := strconv.Atoi(idParam) + if err != nil { + helper.BadRequest(c, err) + return + } + + connStatus := hostService.TestLocalConn(uint(intNum)) + helper.SuccessWithData(c, connStatus) +} + +// @Tags Host +// @Summary Load host tree +// @Accept json +// @Param request body dto.SearchForTree true "request" +// @Success 200 {array} dto.HostTree +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/hosts/tree [post] +func (b *BaseApi) HostTree(c *gin.Context) { + var req dto.SearchForTree + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + data, err := hostService.SearchForTree(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, data) +} + +// @Tags Host +// @Summary Page host +// @Accept json +// @Param request body dto.SearchPageWithGroup true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/hosts/search [post] +func (b *BaseApi) SearchHost(c *gin.Context) { + var req dto.SearchPageWithGroup + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := hostService.SearchWithPage(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Host +// @Summary Delete host +// @Accept json +// @Param request body dto.OperateByIDs true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/hosts/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"hosts","output_column":"addr","output_value":"addrs"}],"formatZH":"删除主机 [addrs]","formatEN":"delete host [addrs]"} +func (b *BaseApi) DeleteHost(c *gin.Context) { + var req dto.OperateByIDs + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := hostService.Delete(req.IDs); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Host +// @Summary Update host +// @Accept json +// @Param request body dto.HostOperate true "request" +// @Success 200 {object} dto.HostInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/hosts/update [post] +// @x-panel-log {"bodyKeys":["name","addr"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新主机信息 [name][addr]","formatEN":"update host [name][addr]"} +func (b *BaseApi) UpdateHost(c *gin.Context) { + var req dto.HostOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + var err error + if len(req.Password) != 0 && req.AuthMode == "password" { + req.Password, err = hostService.EncryptHost(req.Password) + if err != nil { + helper.BadRequest(c, err) + return + } + req.PrivateKey = "" + req.PassPhrase = "" + } + if len(req.PrivateKey) != 0 && req.AuthMode == "key" { + req.PrivateKey, err = hostService.EncryptHost(req.PrivateKey) + if err != nil { + helper.BadRequest(c, err) + return + } + if len(req.PassPhrase) != 0 { + req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase) + if err != nil { + helper.BadRequest(c, err) + return + } + } + req.Password = "" + } + + upMap := make(map[string]interface{}) + upMap["name"] = req.Name + upMap["group_id"] = req.GroupID + upMap["addr"] = req.Addr + upMap["port"] = req.Port + upMap["user"] = req.User + upMap["auth_mode"] = req.AuthMode + upMap["remember_password"] = req.RememberPassword + if req.AuthMode == "password" { + upMap["password"] = req.Password + upMap["private_key"] = "" + upMap["pass_phrase"] = "" + } else { + upMap["password"] = "" + upMap["private_key"] = req.PrivateKey + upMap["pass_phrase"] = req.PassPhrase + } + upMap["description"] = req.Description + hostItem, err := hostService.Update(req.ID, upMap) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, hostItem) +} + +// @Tags Host +// @Summary Update host group +// @Accept json +// @Param request body dto.ChangeHostGroup true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/hosts/update/group [post] +// @x-panel-log {"bodyKeys":["id","group"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"hosts","output_column":"addr","output_value":"addr"}],"formatZH":"切换主机[addr]分组 => [group]","formatEN":"change host [addr] group => [group]"} +func (b *BaseApi) UpdateHostGroup(c *gin.Context) { + var req dto.ChangeHostGroup + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + upMap := make(map[string]interface{}) + upMap["group_id"] = req.GroupID + if _, err := hostService.Update(req.ID, upMap); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Host +// @Summary Get host info +// @Accept json +// @Param request body dto.OperateByID true "request" +// @Success 200 {object} dto.HostInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/hosts/info [post] +func (b *BaseApi) GetHostByID(c *gin.Context) { + var req dto.OperateByID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + info, err := hostService.GetHostByID(req.ID) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, info) +} + +func (b *BaseApi) WsSsh(c *gin.Context) { + wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + global.LOG.Errorf("gin context http handler failed, err: %v", err) + return + } + defer wsConn.Close() + + if global.CONF.Base.IsDemo { + if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) { + return + } + } + + id, err := strconv.Atoi(c.Query("id")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param id in request")) { + return + } + cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { + return + } + rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { + return + } + host, err := service.GetHostInfo(uint(id)) + if wshandleError(wsConn, errors.WithMessage(err, "load host info by id failed")) { + return + } + var connInfo ssh.ConnInfo + _ = copier.Copy(&connInfo, &host) + connInfo.PrivateKey = []byte(host.PrivateKey) + if len(host.PassPhrase) != 0 { + connInfo.PassPhrase = []byte(host.PassPhrase) + } + + client, err := ssh.NewClient(connInfo) + if wshandleError(wsConn, errors.WithMessage(err, "failed to set up the connection. Please check the host information")) { + return + } + defer client.Close() + sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, "") + if wshandleError(wsConn, err) { + return + } + defer sws.Close() + + quitChan := make(chan bool, 3) + sws.Start(quitChan) + go sws.Wait(quitChan) + + <-quitChan + + dt := time.Now().Add(time.Second) + _ = wsConn.WriteControl(websocket.CloseMessage, nil, dt) +} + +var upGrader = websocket.Upgrader{ + ReadBufferSize: 4096, + WriteBufferSize: 16384, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +func wshandleError(ws *websocket.Conn, err error) bool { + if err != nil { + global.LOG.Errorf("handler ws faled:, err: %v", err) + dt := time.Now().Add(time.Second) + if ctlerr := ws.WriteControl(websocket.CloseMessage, []byte(err.Error()), dt); ctlerr != nil { + wsData, err := json.Marshal(terminal.WsMsg{ + Type: terminal.WsMsgCmd, + Data: base64.StdEncoding.EncodeToString([]byte(err.Error())), + }) + if err != nil { + _ = ws.WriteMessage(websocket.TextMessage, []byte("{\"type\":\"cmd\",\"data\":\"failed to encoding to json\"}")) + } else { + _ = ws.WriteMessage(websocket.TextMessage, wsData) + } + } + return true + } + return false +} diff --git a/core/app/api/v2/logs.go b/core/app/api/v2/logs.go new file mode 100644 index 0000000..50afde2 --- /dev/null +++ b/core/app/api/v2/logs.go @@ -0,0 +1,82 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags Logs +// @Summary Page login logs +// @Accept json +// @Param request body dto.SearchLgLogWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/logs/login [post] +func (b *BaseApi) GetLoginLogs(c *gin.Context) { + var req dto.SearchLgLogWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := logService.PageLoginLog(c, req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Logs +// @Summary Page operation logs +// @Accept json +// @Param request body dto.SearchOpLogWithPage true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/logs/operation [post] +func (b *BaseApi) GetOperationLogs(c *gin.Context) { + var req dto.SearchOpLogWithPage + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := logService.PageOperationLog(req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags Logs +// @Summary Clean operation logs +// @Accept json +// @Param request body dto.CleanLog true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/logs/clean [post] +// @x-panel-log {"bodyKeys":["logType"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"清空 [logType] 日志信息","formatEN":"Clean the [logType] log information"} +func (b *BaseApi) CleanLogs(c *gin.Context) { + var req dto.CleanLog + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := logService.CleanLogs(req.LogType); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} diff --git a/core/app/api/v2/script_library.go b/core/app/api/v2/script_library.go new file mode 100644 index 0000000..a2bda36 --- /dev/null +++ b/core/app/api/v2/script_library.go @@ -0,0 +1,230 @@ +package v2 + +import ( + "encoding/json" + "fmt" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/service" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/ssh" + "github.com/1Panel-dev/1Panel/core/utils/terminal" + "github.com/1Panel-dev/1Panel/core/utils/xpack" + "github.com/gin-gonic/gin" + "github.com/gorilla/websocket" + "github.com/pkg/errors" +) + +// @Tags ScriptLibrary +// @Summary Add script +// @Accept json +// @Param request body dto.ScriptOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/script [post] +// @x-panel-log {"bodyKeys":["name"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"添加脚本库脚本 [name]","formatEN":"add script [name]"} +func (b *BaseApi) CreateScript(c *gin.Context) { + var req dto.ScriptOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := scriptService.Create(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags ScriptLibrary +// @Summary Page script +// @Accept json +// @Param request body dto.SearchPageWithGroup true "request" +// @Success 200 {object} dto.PageResult +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/script/search [post] +func (b *BaseApi) SearchScript(c *gin.Context) { + var req dto.SearchPageWithGroup + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + total, list, err := scriptService.Search(c, req) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, dto.PageResult{ + Items: list, + Total: total, + }) +} + +// @Tags ScriptLibrary +// @Summary Delete script +// @Accept json +// @Param request body dto.OperateByIDs true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/script/del [post] +// @x-panel-log {"bodyKeys":["ids"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"ids","isList":true,"db":"script_librarys","output_column":"name","output_value":"names"}],"formatZH":"删除脚本库脚本 [names]","formatEN":"delete script [names]"} +func (b *BaseApi) DeleteScript(c *gin.Context) { + var req dto.OperateByIDs + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := scriptService.Delete(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags ScriptLibrary +// @Summary Sync script from remote +// @Accept json +// @Param request body dto.OperateByTaskID true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/script/sync [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"同步脚本库脚本","formatEN":"sync scripts"} +func (b *BaseApi) SyncScript(c *gin.Context) { + var req dto.OperateByTaskID + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if err := scriptService.Sync(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags ScriptLibrary +// @Summary Update script +// @Accept json +// @Param request body dto.ScriptOperate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/script/update [post] +// @x-panel-log {"bodyKeys":["id"],"paramKeys":[],"BeforeFunctions":[{"input_column":"id","input_value":"id","isList":false,"db":"cronjobs","output_column":"name","output_value":"name"}],"formatZH":"更新脚本库脚本 [name]","formatEN":"update script [name]"} +func (b *BaseApi) UpdateScript(c *gin.Context) { + var req dto.ScriptOperate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := scriptService.Update(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) RunScript(c *gin.Context) { + wsConn, err := upGrader.Upgrade(c.Writer, c.Request, nil) + if err != nil { + global.LOG.Errorf("gin context http handler failed, err: %v", err) + return + } + defer wsConn.Close() + + if global.CONF.Base.IsDemo { + if wshandleError(wsConn, errors.New(" demo server, prohibit this operation!")) { + return + } + } + + cols, err := strconv.Atoi(c.DefaultQuery("cols", "80")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param cols in request")) { + return + } + rows, err := strconv.Atoi(c.DefaultQuery("rows", "40")) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { + return + } + scriptID := c.Query("script_id") + currentNode := c.Query("operateNode") + intNum, _ := strconv.Atoi(scriptID) + if intNum == 0 { + if wshandleError(wsConn, fmt.Errorf(" no such script %v in library, please check and try again!", scriptID)) { + return + } + } + scriptItem, err := service.LoadScriptInfo(uint(intNum)) + if wshandleError(wsConn, err) { + return + } + + quitChan := make(chan bool, 3) + if currentNode == "local" { + slave, err := terminal.NewCommand(scriptItem.Script) + if wshandleError(wsConn, err) { + return + } + defer slave.Close() + + tty, err := terminal.NewLocalWsSession(cols, rows, wsConn, slave, true) + if wshandleError(wsConn, err) { + return + } + + quitChan := make(chan bool, 3) + tty.Start(quitChan) + go slave.Wait(quitChan) + } else { + connInfo, _, err := xpack.LoadNodeInfo(currentNode) + if wshandleError(wsConn, errors.WithMessage(err, "invalid param rows in request")) { + return + } + + fileName := "" + var translations = make(map[string]string) + _ = json.Unmarshal([]byte(scriptItem.Name), &translations) + if name, ok := translations["en"]; ok { + fileName = strings.ReplaceAll(name, " ", "_") + } else { + fileName = strings.ReplaceAll(scriptItem.Name, " ", "_") + } + client, err := ssh.NewClient(*connInfo) + if wshandleError(wsConn, errors.WithMessage(err, "set up the connection failed. Please check the host information")) { + return + } + sudoItem := client.SudoHandleCmd() + defer func() { + _, _ = client.Runf("%s rm -rf %s", sudoItem, fileName) + client.Close() + }() + std, err := client.Runf("%s touch %s && %s chmod 777 %s && %s cat > %s <<'MYMARKER'\n%s\nMYMARKER\n", sudoItem, fileName, sudoItem, fileName, sudoItem, fileName, scriptItem.Script) + if wshandleError(wsConn, errors.WithMessage(err, fmt.Sprintf("touch script file failed, err: %s. Please check and retry", std))) { + return + } + initCmd := fmt.Sprintf("%s bash %s", sudoItem, fileName) + + sws, err := terminal.NewLogicSshWsSession(cols, rows, client.Client, wsConn, initCmd) + if wshandleError(wsConn, err) { + return + } + defer sws.Close() + sws.Start(quitChan) + go sws.Wait(quitChan) + } + + <-quitChan + + global.LOG.Info("websocket finished") + global.LOG.Info("websocket finished") + dt := time.Now().Add(time.Second) + _ = wsConn.WriteControl(websocket.CloseMessage, nil, dt) +} diff --git a/core/app/api/v2/setting.go b/core/app/api/v2/setting.go new file mode 100644 index 0000000..a33fc95 --- /dev/null +++ b/core/app/api/v2/setting.go @@ -0,0 +1,632 @@ +package v2 + +import ( + "encoding/base64" + "errors" + "net/http" + "os" + "path" + "regexp" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/mfa" + "github.com/gin-gonic/gin" +) + +// @Tags System Setting +// @Summary Load system setting info +// @Success 200 {object} dto.SettingInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/search [post] +func (b *BaseApi) GetSettingInfo(c *gin.Context) { + setting, err := settingService.GetSettingInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, setting) +} + +// @Tags System Setting +// @Summary Load system setting by key +// @Success 200 {string} info +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/by [post] +func (b *BaseApi) GetSettingByKey(c *gin.Context) { + var req dto.SettingKey + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + setting, err := repo.NewISettingRepo().GetValueByKey(req.Key) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, setting) +} + +// @Tags System Setting +// @Summary Load system terminal setting info +// @Success 200 {object} dto.TerminalInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/terminal/search [post] +func (b *BaseApi) GetTerminalSettingInfo(c *gin.Context) { + setting, err := settingService.GetTerminalInfo() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, setting) +} + +// @Tags System Setting +// @Summary Load system available status +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/search/available [get] +func (b *BaseApi) GetSystemAvailable(c *gin.Context) { + helper.Success(c) +} + +// @Tags System Setting +// @Summary Update system setting +// @Accept json +// @Param request body dto.SettingUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/update [post] +// @x-panel-log {"bodyKeys":["key","value"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统配置 [key] => [value]","formatEN":"update system setting [key] => [value]"} +func (b *BaseApi) UpdateSetting(c *gin.Context) { + var req dto.SettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + if req.Key == "SecurityEntrance" { + if !checkEntrancePattern(req.Value) { + helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrInvalidParams", buserr.WithName("ErrEntranceFormat", req.Value)) + return + } + } + + if err := settingService.Update(req.Key, req.Value); err != nil { + helper.InternalServer(c, err) + return + } + if req.Key == "SecurityEntrance" { + entranceValue := base64.StdEncoding.EncodeToString([]byte(req.Value)) + c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true) + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Update system terminal setting +// @Accept json +// @Param request body dto.TerminalInfo true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/terminal/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统终端配置","formatEN":"update system terminal setting"} +func (b *BaseApi) UpdateTerminalSetting(c *gin.Context) { + var req dto.TerminalInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.UpdateTerminal(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Update proxy setting +// @Accept json +// @Param request body dto.ProxyUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/proxy/update [post] +// @x-panel-log {"bodyKeys":["proxyUrl","proxyPort"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"服务器代理配置 [proxyPort]:[proxyPort]","formatEN":"set proxy [proxyPort]:[proxyPort]."} +func (b *BaseApi) UpdateProxy(c *gin.Context) { + var req dto.ProxyUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if len(req.ProxyPasswd) != 0 && len(req.ProxyType) != 0 { + pass, err := base64.StdEncoding.DecodeString(req.ProxyPasswd) + if err != nil { + helper.BadRequest(c, err) + return + } + req.ProxyPasswd = string(pass) + } + + if err := settingService.UpdateProxy(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Update system setting +// @Accept json +// @Param request body dto.SettingUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/menu/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"隐藏高级功能菜单","formatEN":"Hide advanced feature menu."} +func (b *BaseApi) UpdateMenu(c *gin.Context) { + var req dto.SettingUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.Update(req.Key, req.Value); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags Menu Setting +// @Summary Default menu +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/menu/default [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"初始化菜单","formatEN":"Init menu."} +func (b *BaseApi) DefaultMenu(c *gin.Context) { + if err := settingService.DefaultMenu(); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Update system password +// @Accept json +// @Param request body dto.PasswordUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/password/update [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统密码","formatEN":"update system password"} +func (b *BaseApi) UpdatePassword(c *gin.Context) { + var req dto.PasswordUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.UpdatePassword(c, req.OldPassword, req.NewPassword); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Update system ssl +// @Accept json +// @Param request body dto.SSLUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/ssl/update [post] +// @x-panel-log {"bodyKeys":["ssl"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统 ssl => [ssl]","formatEN":"update system ssl => [ssl]"} +func (b *BaseApi) UpdateSSL(c *gin.Context) { + var req dto.SSLUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.UpdateSSL(c, req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Load system cert info +// @Success 200 {object} dto.SSLInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/ssl/info [get] +func (b *BaseApi) LoadFromCert(c *gin.Context) { + info, err := settingService.LoadFromCert() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, info) +} + +// @Tags System Setting +// @Summary Download system cert +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/ssl/download [post] +func (b *BaseApi) DownloadSSL(c *gin.Context) { + pathItem := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt") + if _, err := os.Stat(pathItem); err != nil { + helper.InternalServer(c, err) + return + } + + c.File(pathItem) +} + +// @Tags System Setting +// @Summary Load system address +// @Accept json +// @Success 200 {array} string +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/interface [get] +func (b *BaseApi) LoadInterfaceAddr(c *gin.Context) { + data, err := settingService.LoadInterfaceAddr() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, data) +} + +// @Tags System Setting +// @Summary Update system bind info +// @Accept json +// @Param request body dto.BindInfo true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/bind/update [post] +// @x-panel-log {"bodyKeys":["ipv6", "bindAddress"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统监听信息 => ipv6: [ipv6], 监听 IP: [bindAddress]","formatEN":"update system bind info => ipv6: [ipv6], 监听 IP: [bindAddress]"} +func (b *BaseApi) UpdateBindInfo(c *gin.Context) { + var req dto.BindInfo + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.UpdateBindInfo(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Update system port +// @Accept json +// @Param request body dto.PortUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/port/update [post] +// @x-panel-log {"bodyKeys":["serverPort"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"修改系统端口 => [serverPort]","formatEN":"update system port => [serverPort]"} +func (b *BaseApi) UpdatePort(c *gin.Context) { + var req dto.PortUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.UpdatePort(req.ServerPort); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Reset system password expired +// @Accept json +// @Param request body dto.PasswordUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/expired/handle [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"重置过期密码","formatEN":"reset an expired Password"} +func (b *BaseApi) HandlePasswordExpired(c *gin.Context) { + var req dto.PasswordUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.HandlePasswordExpired(c, req.OldPassword, req.NewPassword); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary Load mfa info +// @Accept json +// @Param request body dto.MfaCredential true "request" +// @Success 200 {object} mfa.Otp +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/mfa [post] +func (b *BaseApi) LoadMFA(c *gin.Context) { + var req dto.MfaRequest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + otp, err := mfa.GetOtp("admin", req.Title, req.Interval) + if err != nil { + helper.InternalServer(c, err) + return + } + + helper.SuccessWithData(c, otp) +} + +// @Tags System Setting +// @Summary Bind mfa +// @Accept json +// @Param request body dto.MfaCredential true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/mfa/bind [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"mfa 绑定","formatEN":"bind mfa"} +func (b *BaseApi) MFABind(c *gin.Context) { + var req dto.MfaCredential + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + success := mfa.ValidCode(req.Code, req.Interval, req.Secret) + if !success { + helper.InternalServer(c, errors.New("code is not valid")) + return + } + + if err := settingService.Update("MFAInterval", req.Interval); err != nil { + helper.InternalServer(c, err) + return + } + + if err := settingService.Update("MFAStatus", constant.StatusEnable); err != nil { + helper.InternalServer(c, err) + return + } + + if err := settingService.Update("MFASecret", req.Secret); err != nil { + helper.InternalServer(c, err) + return + } + + helper.Success(c) +} + +// @Tags System Setting +// @Summary Begin passkey registration +// @Accept json +// @Param request body dto.PasskeyRegisterRequest true "request" +// @Success 200 {object} dto.PasskeyBeginResponse +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/passkey/register/begin [post] +func (b *BaseApi) PasskeyRegisterBegin(c *gin.Context) { + var req dto.PasskeyRegisterRequest + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + res, msgKey, err := authService.PasskeyBeginRegister(c, req.Name) + if msgKey != "" { + helper.ErrorWithDetail(c, http.StatusBadRequest, msgKey, err) + return + } + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +// @Tags System Setting +// @Summary Finish passkey registration +// @Accept json +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/passkey/register/finish [post] +func (b *BaseApi) PasskeyRegisterFinish(c *gin.Context) { + sessionID := c.GetHeader("Passkey-Session") + msgKey, err := authService.PasskeyFinishRegister(c, sessionID) + if msgKey != "" { + helper.ErrorWithDetail(c, http.StatusBadRequest, msgKey, err) + return + } + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary List passkeys +// @Success 200 {array} dto.PasskeyInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/passkey/list [get] +func (b *BaseApi) PasskeyList(c *gin.Context) { + list, err := authService.PasskeyList() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, list) +} + +// @Tags System Setting +// @Summary Delete passkey +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/passkey/{id} [delete] +func (b *BaseApi) PasskeyDelete(c *gin.Context) { + id := c.Param("id") + if id == "" { + helper.BadRequest(c, errors.New("passkey id is required")) + return + } + if err := authService.PasskeyDelete(id); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +func (b *BaseApi) ReloadSSL(c *gin.Context) { + clientIP := c.ClientIP() + if clientIP != "127.0.0.1" { + helper.InternalServer(c, errors.New("only localhost can reload ssl")) + return + } + if err := settingService.UpdateSystemSSL(); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags System Setting +// @Summary generate api key +// @Accept json +// @Success 200 {string} key +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/api/config/generate/key [post] +// @x-panel-log {"bodyKeys":[],"paramKeys":[],"BeforeFunctions":[],"formatZH":"生成 API 接口密钥","formatEN":"generate api key"} +func (b *BaseApi) GenerateApiKey(c *gin.Context) { + panelToken := c.GetHeader("1Panel-Token") + if panelToken != "" { + helper.BadAuth(c, "ErrApiConfigDisable", nil) + return + } + apiKey, err := settingService.GenerateApiKey() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, apiKey) +} + +// @Tags System Setting +// @Summary Update api config +// @Accept json +// @Param request body dto.ApiInterfaceConfig true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/api/config/update [post] +// @x-panel-log {"bodyKeys":["ipWhiteList"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新 API 接口配置 => IP 白名单: [ipWhiteList]","formatEN":"update api config => IP White List: [ipWhiteList]"} +func (b *BaseApi) UpdateApiConfig(c *gin.Context) { + panelToken := c.GetHeader("1Panel-Token") + if panelToken != "" { + helper.BadAuth(c, "ErrApiConfigDisable", nil) + return + } + var req dto.ApiInterfaceConfig + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := settingService.UpdateApiConfig(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags App +// @Summary Update appstore config +// @Accept json +// @Param request body dto.AppstoreUpdate true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/apps/store/update [post] +func (b *BaseApi) UpdateAppstoreConfig(c *gin.Context) { + var req dto.AppstoreUpdate + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + err := settingService.UpdateAppstoreConfig(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} + +// @Tags App +// @Summary Get appstore config +// @Success 200 {object} dto.AppstoreConfig +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/apps/store/config [get] +func (b *BaseApi) GetAppstoreConfig(c *gin.Context) { + res, err := settingService.GetAppstoreConfig() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, res) +} + +func checkEntrancePattern(val string) bool { + if len(val) == 0 { + return true + } + result, _ := regexp.MatchString("^[a-zA-Z0-9]{5,116}$", val) + if !result { + return false + } + lowerVal := strings.ToLower(val) + for key := range constant.WebUrlMap { + if key == "/" || !strings.HasPrefix(key, "/") { + continue + } + if strings.Count(key, "/") != 1 { + continue + } + segment := strings.ToLower(strings.TrimPrefix(key, "/")) + if len(segment) < 5 { + continue + } + if lowerVal == segment { + return false + } + } + assetsList := [2]string{"public", "assets"} + for _, item := range assetsList { + if lowerVal == item { + return false + } + } + return true +} diff --git a/core/app/api/v2/upgrade.go b/core/app/api/v2/upgrade.go new file mode 100644 index 0000000..22e3fb4 --- /dev/null +++ b/core/app/api/v2/upgrade.go @@ -0,0 +1,81 @@ +package v2 + +import ( + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/gin-gonic/gin" +) + +// @Tags System Setting +// @Summary Load upgrade info +// @Success 200 {object} dto.UpgradeInfo +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/upgrade [get] +func (b *BaseApi) GetUpgradeInfo(c *gin.Context) { + info, err := upgradeService.SearchUpgrade() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, info) +} + +// @Tags System Setting +// @Summary Load upgrade notes +// @Success 200 {array} dto.ReleasesNotes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/upgrade/releases [get] +func (b *BaseApi) LoadRelease(c *gin.Context) { + notes, err := upgradeService.LoadRelease() + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, notes) +} + +// @Tags System Setting +// @Summary Load release notes by version +// @Accept json +// @Param request body dto.Upgrade true "request" +// @Success 200 {string} notes +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/upgrade/notes [post] +func (b *BaseApi) GetNotesByVersion(c *gin.Context) { + var req dto.Upgrade + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + notes, err := upgradeService.LoadNotes(req) + if err != nil { + helper.InternalServer(c, err) + return + } + helper.SuccessWithData(c, notes) +} + +// @Tags System Setting +// @Summary Upgrade +// @Accept json +// @Param request body dto.Upgrade true "request" +// @Success 200 +// @Security ApiKeyAuth +// @Security Timestamp +// @Router /core/settings/upgrade [post] +// @x-panel-log {"bodyKeys":["version"],"paramKeys":[],"BeforeFunctions":[],"formatZH":"更新系统 => [version]","formatEN":"upgrade system => [version]"} +func (b *BaseApi) Upgrade(c *gin.Context) { + var req dto.Upgrade + if err := helper.CheckBindAndValidate(&req, c); err != nil { + return + } + + if err := upgradeService.Upgrade(req); err != nil { + helper.InternalServer(c, err) + return + } + helper.Success(c) +} diff --git a/core/app/dto/auth.go b/core/app/dto/auth.go new file mode 100644 index 0000000..9602671 --- /dev/null +++ b/core/app/dto/auth.go @@ -0,0 +1,48 @@ +package dto + +type CaptchaResponse struct { + CaptchaID string `json:"captchaID"` + ImagePath string `json:"imagePath"` +} + +type UserLoginInfo struct { + Name string `json:"name"` + Token string `json:"token"` + MfaStatus string `json:"mfaStatus"` +} + +type PasskeyBeginResponse struct { + SessionID string `json:"sessionId"` + PublicKey interface{} `json:"publicKey"` +} + +type MfaRequest struct { + Title string `json:"title" validate:"required"` + Interval int `json:"interval" validate:"required"` +} + +type MfaCredential struct { + Secret string `json:"secret" validate:"required"` + Code string `json:"code" validate:"required"` + Interval string `json:"interval" validate:"required"` +} + +type Login struct { + Name string `json:"name" validate:"required"` + Password string `json:"password" validate:"required"` + Captcha string `json:"captcha"` + CaptchaID string `json:"captchaID"` + Language string `json:"language" validate:"required,oneof=zh en 'zh-Hant' ko ja ru ms 'pt-BR' tr 'es-ES'"` +} + +type MFALogin struct { + Name string `json:"name" validate:"required"` + Password string `json:"password" validate:"required"` + Code string `json:"code" validate:"required"` +} + +type SystemSetting struct { + IsDemo bool `json:"isDemo"` + Language string `json:"language"` + IsIntl bool `json:"isIntl"` +} diff --git a/core/app/dto/backup.go b/core/app/dto/backup.go new file mode 100644 index 0000000..6932d53 --- /dev/null +++ b/core/app/dto/backup.go @@ -0,0 +1,51 @@ +package dto + +import "time" + +type SyncToAgent struct { + Name string `json:"name" validate:"required"` + Operation string `json:"operation" validate:"required,oneof=create delere update"` + Data string `json:"data"` +} + +type BackupOperate struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type" validate:"required"` + IsPublic bool `json:"isPublic"` + Bucket string `json:"bucket"` + AccessKey string `json:"accessKey"` + Credential string `json:"credential"` + BackupPath string `json:"backupPath"` + Vars string `json:"vars" validate:"required"` + + RememberAuth bool `json:"rememberAuth"` +} + +type BackupInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + IsPublic bool `json:"isPublic"` + Bucket string `json:"bucket"` + AccessKey string `json:"accessKey"` + Credential string `json:"credential"` + BackupPath string `json:"backupPath"` + Vars string `json:"vars"` + CreatedAt time.Time `json:"createdAt"` + + RememberAuth bool `json:"rememberAuth"` +} + +type BackupClientInfo struct { + ClientID string `json:"client_id"` + ClientSecret string `json:"client_secret"` + RedirectUri string `json:"redirect_uri"` +} + +type ForBuckets struct { + Type string `json:"type" validate:"required"` + AccessKey string `json:"accessKey"` + Credential string `json:"credential" validate:"required"` + Vars string `json:"vars" validate:"required"` +} diff --git a/core/app/dto/command.go b/core/app/dto/command.go new file mode 100644 index 0000000..59d4070 --- /dev/null +++ b/core/app/dto/command.go @@ -0,0 +1,43 @@ +package dto + +type SearchCommandWithPage struct { + PageInfo + OrderBy string `json:"orderBy" validate:"required,oneof=name command createdAt"` + Order string `json:"order" validate:"required,oneof=null ascending descending"` + GroupID uint `json:"groupID"` + Type string `json:"type" validate:"required,oneof=redis command"` + Info string `json:"info"` +} + +type CommandImport struct { + Items []CommandOperate `json:"items"` +} + +type CommandOperate struct { + ID uint `json:"id"` + Type string `json:"type"` + GroupID uint `json:"groupID"` + GroupBelong string `json:"groupBelong"` + Name string `json:"name" validate:"required"` + Command string `json:"command" validate:"required"` +} + +type CommandInfo struct { + ID uint `json:"id"` + GroupID uint `json:"groupID"` + Name string `json:"name"` + Type string `json:"type"` + Command string `json:"command"` + GroupBelong string `json:"groupBelong"` +} + +type CommandTree struct { + Label string `json:"label"` + Value string `json:"value"` + Children []CommandTree `json:"children"` +} + +type CommandDelete struct { + Type string `json:"type" validate:"required,oneof=redis command"` + IDs []uint `json:"ids"` +} diff --git a/core/app/dto/common.go b/core/app/dto/common.go new file mode 100644 index 0000000..cc86971 --- /dev/null +++ b/core/app/dto/common.go @@ -0,0 +1,56 @@ +package dto + +type SearchWithPage struct { + PageInfo + Info string `json:"info"` +} + +type SearchPageWithType struct { + PageInfo + Type string `json:"type"` + Info string `json:"info"` +} + +type SearchPageWithGroup struct { + PageInfo + GroupID uint `json:"groupID"` + Info string `json:"info"` +} + +type PageInfo struct { + Page int `json:"page" validate:"required,number"` + PageSize int `json:"pageSize" validate:"required,number"` +} + +type PageResult struct { + Total int64 `json:"total"` + Items interface{} `json:"items"` +} + +type Response struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +type Options struct { + Option string `json:"option"` +} + +type OperateByType struct { + Type string `json:"type"` +} + +type OperateByName struct { + Name string `json:"name"` +} + +type OperateByID struct { + ID uint `json:"id"` +} +type OperateByIDs struct { + IDs []uint `json:"ids"` +} +type OperateByTaskID struct { + TaskID string `json:"taskID"` +} diff --git a/core/app/dto/group.go b/core/app/dto/group.go new file mode 100644 index 0000000..60179d7 --- /dev/null +++ b/core/app/dto/group.go @@ -0,0 +1,25 @@ +package dto + +type GroupCreate struct { + ID uint `json:"id"` + Name string `json:"name" validate:"required"` + Type string `json:"type" validate:"required"` +} + +type GroupSearch struct { + Type string `json:"type" validate:"required"` +} + +type GroupUpdate struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type" validate:"required"` + IsDefault bool `json:"isDefault"` +} + +type GroupInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Type string `json:"type"` + IsDefault bool `json:"isDefault"` +} diff --git a/core/app/dto/host.go b/core/app/dto/host.go new file mode 100644 index 0000000..04e3c3f --- /dev/null +++ b/core/app/dto/host.go @@ -0,0 +1,69 @@ +package dto + +import ( + "time" +) + +type HostOperate struct { + ID uint `json:"id"` + GroupID uint `json:"groupID"` + Name string `json:"name"` + Addr string `json:"addr" validate:"required"` + Port uint `json:"port" validate:"required,number,max=65535,min=1"` + User string `json:"user" validate:"required"` + AuthMode string `json:"authMode" validate:"oneof=password key"` + Password string `json:"password"` + PrivateKey string `json:"privateKey"` + PassPhrase string `json:"passPhrase"` + RememberPassword bool `json:"rememberPassword"` + + Description string `json:"description"` +} + +type HostConnTest struct { + Addr string `json:"addr" validate:"required"` + Port uint `json:"port" validate:"required,number,max=65535,min=1"` + User string `json:"user" validate:"required"` + AuthMode string `json:"authMode" validate:"oneof=password key"` + Password string `json:"password"` + PrivateKey string `json:"privateKey"` + PassPhrase string `json:"passPhrase"` +} + +type SearchForTree struct { + Info string `json:"info"` +} + +type ChangeHostGroup struct { + ID uint `json:"id" validate:"required"` + GroupID uint `json:"groupID" validate:"required"` +} + +type HostInfo struct { + ID uint `json:"id"` + CreatedAt time.Time `json:"createdAt"` + GroupID uint `json:"groupID"` + GroupBelong string `json:"groupBelong"` + Name string `json:"name"` + Addr string `json:"addr"` + Port uint `json:"port"` + User string `json:"user"` + AuthMode string `json:"authMode"` + Password string `json:"password"` + PrivateKey string `json:"privateKey"` + PassPhrase string `json:"passPhrase"` + RememberPassword bool `json:"rememberPassword"` + + Description string `json:"description"` +} + +type HostTree struct { + ID uint `json:"id"` + Label string `json:"label"` + Children []TreeChild `json:"children"` +} + +type TreeChild struct { + ID uint `json:"id"` + Label string `json:"label"` +} diff --git a/core/app/dto/logs.go b/core/app/dto/logs.go new file mode 100644 index 0000000..16a71ef --- /dev/null +++ b/core/app/dto/logs.go @@ -0,0 +1,51 @@ +package dto + +import ( + "time" +) + +type OperationLog struct { + ID uint `json:"id"` + Source string `json:"source"` + Node string `json:"node"` + IP string `json:"ip"` + Path string `json:"path"` + Method string `json:"method"` + UserAgent string `json:"userAgent"` + + Latency time.Duration `json:"latency"` + Status string `json:"status"` + Message string `json:"message"` + + DetailZH string `json:"detailZH"` + DetailEN string `json:"detailEN"` + CreatedAt time.Time `json:"createdAt"` +} + +type SearchOpLogWithPage struct { + PageInfo + Source string `json:"source"` + Status string `json:"status"` + Node string `json:"node"` + Operation string `json:"operation"` +} + +type SearchLgLogWithPage struct { + PageInfo + IP string `json:"ip"` + Status string `json:"status"` +} + +type LoginLog struct { + ID uint `json:"id"` + IP string `json:"ip"` + Address string `json:"address"` + Agent string `json:"agent"` + Status string `json:"status"` + Message string `json:"message"` + CreatedAt time.Time `json:"createdAt"` +} + +type CleanLog struct { + LogType string `json:"logType" validate:"required,oneof=login operation"` +} diff --git a/core/app/dto/script_library.go b/core/app/dto/script_library.go new file mode 100644 index 0000000..40a376e --- /dev/null +++ b/core/app/dto/script_library.go @@ -0,0 +1,25 @@ +package dto + +import "time" + +type ScriptInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + IsInteractive bool `json:"isInteractive"` + Lable string `json:"lable"` + Script string `json:"script"` + GroupList []uint `json:"groupList"` + GroupBelong []string `json:"groupBelong"` + IsSystem bool `json:"isSystem"` + Description string `json:"description"` + CreatedAt time.Time `json:"createdAt"` +} + +type ScriptOperate struct { + ID uint `json:"id"` + IsInteractive bool `json:"isInteractive"` + Name string `json:"name"` + Script string `json:"script"` + Groups string `json:"groups"` + Description string `json:"description"` +} diff --git a/core/app/dto/setting.go b/core/app/dto/setting.go new file mode 100644 index 0000000..bd275b0 --- /dev/null +++ b/core/app/dto/setting.go @@ -0,0 +1,265 @@ +package dto + +import ( + "time" +) + +type SettingInfo struct { + UserName string `json:"userName"` + SystemVersion string `json:"systemVersion"` + DeveloperMode string `json:"developerMode"` + UpgradeBackupCopies string `json:"upgradeBackupCopies"` + + SessionTimeout string `json:"sessionTimeout"` + Port string `json:"port"` + Ipv6 string `json:"ipv6"` + BindAddress string `json:"bindAddress"` + PanelName string `json:"panelName"` + Theme string `json:"theme"` + MenuTabs string `json:"menuTabs"` + Language string `json:"language"` + + ServerPort string `json:"serverPort"` + SSL string `json:"ssl"` + SSLType string `json:"sslType"` + BindDomain string `json:"bindDomain"` + AllowIPs string `json:"allowIPs"` + SecurityEntrance string `json:"securityEntrance"` + ExpirationDays string `json:"expirationDays"` + ExpirationTime string `json:"expirationTime"` + ComplexityVerification string `json:"complexityVerification"` + MFAStatus string `json:"mfaStatus"` + MFASecret string `json:"mfaSecret"` + MFAInterval string `json:"mfaInterval"` + + AppStoreVersion string `json:"appStoreVersion"` + AppStoreLastModified string `json:"appStoreLastModified"` + AppStoreSyncStatus string `json:"appStoreSyncStatus"` + + HideMenu string `json:"hideMenu"` + NoAuthSetting string `json:"noAuthSetting"` + + ProxyUrl string `json:"proxyUrl"` + ProxyType string `json:"proxyType"` + ProxyPort string `json:"proxyPort"` + ProxyUser string `json:"proxyUser"` + ProxyPasswd string `json:"proxyPasswd"` + ProxyPasswdKeep string `json:"proxyPasswdKeep"` + + ApiInterfaceStatus string `json:"apiInterfaceStatus"` + ApiKey string `json:"apiKey"` + IpWhiteList string `json:"ipWhiteList"` + ApiKeyValidityTime string `json:"apiKeyValidityTime"` +} + +type SettingKey struct { + Key string `json:"key" validate:"required,oneof=ScriptSync"` +} + +type SettingUpdate struct { + Key string `json:"key" validate:"required"` + Value string `json:"value"` +} + +type SSLUpdate struct { + SSLType string `json:"sslType" validate:"required,oneof=self select import import-paste import-local"` + Domain string `json:"domain"` + SSL string `json:"ssl" validate:"required,oneof=Enable Disable Mux"` + Cert string `json:"cert"` + Key string `json:"key"` + SSLID uint `json:"sslID"` +} +type SSLInfo struct { + Domain string `json:"domain"` + Timeout string `json:"timeout"` + RootPath string `json:"rootPath"` + Cert string `json:"cert"` + Key string `json:"key"` + SSLID uint `json:"sslID"` +} + +type PasswordUpdate struct { + OldPassword string `json:"oldPassword" validate:"required"` + NewPassword string `json:"newPassword" validate:"required"` +} + +type PortUpdate struct { + ServerPort uint `json:"serverPort" validate:"required,number,max=65535,min=1"` +} + +type SnapshotCreate struct { + ID uint `json:"id"` + From string `json:"from" validate:"required"` + DefaultDownload string `json:"defaultDownload" validate:"required"` + Description string `json:"description" validate:"max=256"` + Secret string `json:"secret"` +} +type SnapshotRecover struct { + IsNew bool `json:"isNew"` + ReDownload bool `json:"reDownload"` + ID uint `json:"id" validate:"required"` + Secret string `json:"secret"` +} +type SnapshotBatchDelete struct { + DeleteWithFile bool `json:"deleteWithFile"` + Ids []uint `json:"ids" validate:"required"` +} +type SnapshotImport struct { + From string `json:"from"` + Names []string `json:"names"` + Description string `json:"description" validate:"max=256"` +} +type SnapshotInfo struct { + ID uint `json:"id"` + Name string `json:"name"` + Description string `json:"description" validate:"max=256"` + From string `json:"from"` + DefaultDownload string `json:"defaultDownload"` + Status string `json:"status"` + Message string `json:"message"` + CreatedAt time.Time `json:"createdAt"` + Version string `json:"version"` + Size int64 `json:"size"` + + InterruptStep string `json:"interruptStep"` + RecoverStatus string `json:"recoverStatus"` + RecoverMessage string `json:"recoverMessage"` + LastRecoveredAt string `json:"lastRecoveredAt"` + RollbackStatus string `json:"rollbackStatus"` + RollbackMessage string `json:"rollbackMessage"` + LastRollbackedAt string `json:"lastRollbackedAt"` +} + +type UpgradeInfo struct { + TestVersion string `json:"testVersion"` + NewVersion string `json:"newVersion"` + LatestVersion string `json:"latestVersion"` + ReleaseNote string `json:"releaseNote"` +} + +type SyncTime struct { + NtpSite string `json:"ntpSite" validate:"required"` +} + +type BindInfo struct { + Ipv6 string `json:"ipv6" validate:"required,oneof=Enable Disable"` + BindAddress string `json:"bindAddress" validate:"required"` +} + +type Upgrade struct { + Version string `json:"version" validate:"required"` +} + +type ReleasesNotes struct { + Version string `json:"version"` + CreatedAt string `json:"createdAt"` + Content string `json:"content"` + NewCount int `json:"newCount"` + OptimizationCount int `json:"optimizationCount"` + FixCount int `json:"fixCount"` +} + +type ProxyUpdate struct { + ProxyUrl string `json:"proxyUrl"` + ProxyType string `json:"proxyType"` + ProxyPort string `json:"proxyPort"` + ProxyUser string `json:"proxyUser"` + ProxyPasswd string `json:"proxyPasswd"` + ProxyPasswdKeep string `json:"proxyPasswdKeep"` + ProxyDocker bool `json:"proxyDocker"` + WithDockerRestart bool `json:"withDockerRestart"` +} + +type CleanData struct { + SystemClean []CleanTree `json:"systemClean"` + UploadClean []CleanTree `json:"uploadClean"` + DownloadClean []CleanTree `json:"downloadClean"` + SystemLogClean []CleanTree `json:"systemLogClean"` + ContainerClean []CleanTree `json:"containerClean"` +} + +type CleanTree struct { + ID string `json:"id"` + Label string `json:"label"` + Children []CleanTree `json:"children"` + + Type string `json:"type"` + Name string `json:"name"` + + Size uint64 `json:"size"` + IsCheck bool `json:"isCheck"` + IsRecommend bool `json:"isRecommend"` +} + +type Clean struct { + TreeType string `json:"treeType"` + Name string `json:"name"` + Size uint64 `json:"size"` +} + +type ShowMenu struct { + ID string `json:"id"` + Label string `json:"label"` + Disabled bool `json:"disabled"` + IsShow bool `json:"isShow"` + Title string `json:"title"` + Path string `json:"path,omitempty"` + Sort int `json:"sort"` + Children []ShowMenu `json:"children,omitempty"` +} + +type MenuLabelSort struct { + Label string `json:"label"` + Sort int `json:"sort"` +} + +type ApiInterfaceConfig struct { + ApiInterfaceStatus string `json:"apiInterfaceStatus"` + ApiKey string `json:"apiKey"` + IpWhiteList string `json:"ipWhiteList"` + ApiKeyValidityTime string `json:"apiKeyValidityTime"` +} + +type TerminalInfo struct { + LineHeight string `json:"lineHeight"` + LetterSpacing string `json:"letterSpacing"` + FontSize string `json:"fontSize"` + CursorBlink string `json:"cursorBlink"` + CursorStyle string `json:"cursorStyle"` + Scrollback string `json:"scrollback"` + ScrollSensitivity string `json:"scrollSensitivity"` +} + +type AppstoreUpdate struct { + Scope string `json:"scope" validate:"required,oneof=UninstallDeleteImage UpgradeBackup UninstallDeleteBackup"` + Status string `json:"status" validate:"required,oneof=Disable Enable"` +} +type AppstoreConfig struct { + UninstallDeleteImage string `json:"uninstallDeleteImage"` + UpgradeBackup string `json:"upgradeBackup"` + UninstallDeleteBackup string `json:"uninstallDeleteBackup"` +} + +type LoginSetting struct { + IsDemo bool `json:"isDemo"` + IsIntl bool `json:"isIntl"` + IsOffLine bool `json:"isOffLine"` + IsFxplay bool `json:"isFxplay"` + Language string `json:"language"` + MenuTabs string `json:"menuTabs"` + PanelName string `json:"panelName"` + Theme string `json:"theme"` + NeedCaptcha bool `json:"needCaptcha"` + PasskeySetting bool `json:"passkeySetting"` +} + +type PasskeyRegisterRequest struct { + Name string `json:"name" validate:"required"` +} + +type PasskeyInfo struct { + ID string `json:"id"` + Name string `json:"name"` + CreatedAt string `json:"createdAt"` + LastUsedAt string `json:"lastUsedAt"` +} diff --git a/core/app/model/agent.go b/core/app/model/agent.go new file mode 100644 index 0000000..b86c4ac --- /dev/null +++ b/core/app/model/agent.go @@ -0,0 +1,47 @@ +package model + +import ( + "time" +) + +type WebsiteSSL struct { + BaseModel + PrimaryDomain string `json:"primaryDomain"` + PrivateKey string `json:"privateKey"` + Pem string `json:"pem"` + Domains string `json:"domains"` + CertURL string `json:"certURL"` + Type string `json:"type"` + Provider string `json:"provider"` + Organization string `json:"organization"` + DnsAccountID uint `json:"dnsAccountId"` + AcmeAccountID uint `gorm:"column:acme_account_id" json:"acmeAccountId" ` + CaID uint `json:"caId"` + AutoRenew bool `json:"autoRenew"` + ExpireDate time.Time `json:"expireDate"` + StartDate time.Time `json:"startDate"` + Status string `json:"status"` + Message string `json:"message"` + KeyType string `json:"keyType"` + PushDir bool `json:"pushDir"` + Dir string `json:"dir"` + Description string `json:"description"` + SkipDNS bool `json:"skipDNS"` + Nameserver1 string `json:"nameserver1"` + Nameserver2 string `json:"nameserver2"` + DisableCNAME bool `json:"disableCNAME"` + ExecShell bool `json:"execShell"` + Shell string `json:"shell"` +} + +func (w WebsiteSSL) TableName() string { + return "website_ssls" +} + +type WebsiteCA struct { + BaseModel + CSR string `gorm:"not null;" json:"csr"` + Name string `gorm:"not null;" json:"name"` + PrivateKey string `gorm:"not null" json:"privateKey"` + KeyType string `gorm:"not null;default:2048" json:"keyType"` +} diff --git a/core/app/model/alert.go b/core/app/model/alert.go new file mode 100644 index 0000000..2043cb5 --- /dev/null +++ b/core/app/model/alert.go @@ -0,0 +1,9 @@ +package model + +type AlertConfig struct { + BaseModel + Type string `json:"type"` + Title string `json:"title"` + Status string `json:"status"` + Config string `json:"config"` +} diff --git a/core/app/model/backup.go b/core/app/model/backup.go new file mode 100644 index 0000000..2619a57 --- /dev/null +++ b/core/app/model/backup.go @@ -0,0 +1,15 @@ +package model + +type BackupAccount struct { + BaseModel + Name string `gorm:"not null;default:''" json:"name"` + Type string `gorm:"not null;default:''" json:"type"` + IsPublic bool `json:"isPublic"` + Bucket string `json:"bucket"` + AccessKey string `json:"accessKey"` + Credential string `json:"credential"` + BackupPath string `json:"backupPath"` + Vars string `json:"vars"` + + RememberAuth bool `json:"rememberAuth"` +} diff --git a/core/app/model/base.go b/core/app/model/base.go new file mode 100644 index 0000000..69921d4 --- /dev/null +++ b/core/app/model/base.go @@ -0,0 +1,9 @@ +package model + +import "time" + +type BaseModel struct { + ID uint `gorm:"primarykey;AUTO_INCREMENT" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` +} diff --git a/core/app/model/command.go b/core/app/model/command.go new file mode 100644 index 0000000..0c76e13 --- /dev/null +++ b/core/app/model/command.go @@ -0,0 +1,9 @@ +package model + +type Command struct { + BaseModel + Type string `gorm:"not null" json:"type"` + Name string `gorm:"not null" json:"name"` + GroupID uint `gorm:"not null" json:"groupID"` + Command string `gorm:"not null" json:"command"` +} diff --git a/core/app/model/group.go b/core/app/model/group.go new file mode 100644 index 0000000..c8999b7 --- /dev/null +++ b/core/app/model/group.go @@ -0,0 +1,8 @@ +package model + +type Group struct { + BaseModel + IsDefault bool `json:"isDefault"` + Name string `json:"name"` + Type string `json:"type"` +} diff --git a/core/app/model/host.go b/core/app/model/host.go new file mode 100644 index 0000000..0521f53 --- /dev/null +++ b/core/app/model/host.go @@ -0,0 +1,18 @@ +package model + +type Host struct { + BaseModel + + GroupID uint `gorm:"not null" json:"group_id"` + Name string `gorm:"not null" json:"name"` + Addr string `gorm:"not null" json:"addr"` + Port int `gorm:"not null" json:"port"` + User string `gorm:"not null" json:"user"` + AuthMode string `gorm:"not null" json:"authMode"` + Password string `json:"password"` + PrivateKey string `json:"privateKey"` + PassPhrase string `json:"passPhrase"` + RememberPassword bool `json:"rememberPassword"` + + Description string `json:"description"` +} diff --git a/core/app/model/logs.go b/core/app/model/logs.go new file mode 100644 index 0000000..99e26c4 --- /dev/null +++ b/core/app/model/logs.go @@ -0,0 +1,31 @@ +package model + +import ( + "time" +) + +type OperationLog struct { + BaseModel + Source string `json:"source"` + IP string `json:"ip"` + Node string `json:"node"` + Path string `json:"path"` + Method string `json:"method"` + UserAgent string `json:"userAgent"` + + Latency time.Duration `json:"latency"` + Status string `json:"status"` + Message string `json:"message"` + + DetailZH string `json:"detailZH"` + DetailEN string `json:"detailEN"` +} + +type LoginLog struct { + BaseModel + IP string `json:"ip"` + Address string `json:"address"` + Agent string `json:"agent"` + Status string `json:"status"` + Message string `json:"message"` +} diff --git a/core/app/model/script_library.go b/core/app/model/script_library.go new file mode 100644 index 0000000..d4a91db --- /dev/null +++ b/core/app/model/script_library.go @@ -0,0 +1,11 @@ +package model + +type ScriptLibrary struct { + BaseModel + Name string `json:"name" gorm:"not null;"` + IsInteractive bool `json:"isInteractive"` + Script string `json:"script" gorm:"not null;"` + Groups string `json:"groups"` + IsSystem bool `json:"isSystem"` + Description string `json:"description"` +} diff --git a/core/app/model/setting.go b/core/app/model/setting.go new file mode 100644 index 0000000..ded5478 --- /dev/null +++ b/core/app/model/setting.go @@ -0,0 +1,8 @@ +package model + +type Setting struct { + BaseModel + Key string `json:"key" gorm:"not null;"` + Value string `json:"value"` + About string `json:"about"` +} diff --git a/core/app/model/task.go b/core/app/model/task.go new file mode 100644 index 0000000..46d0032 --- /dev/null +++ b/core/app/model/task.go @@ -0,0 +1,18 @@ +package model + +import "time" + +type Task struct { + ID string `gorm:"primarykey;" json:"id"` + Name string `json:"name"` + Type string `json:"type"` + Operate string `json:"operate"` + LogFile string `json:"logFile"` + Status string `json:"status"` + ErrorMsg string `json:"errorMsg"` + OperationLogID uint `json:"operationLogID"` + ResourceID uint `json:"resourceID"` + CurrentStep string `json:"currentStep"` + EndAt time.Time `json:"endAt"` + CreatedAt time.Time `json:"createdAt"` +} diff --git a/core/app/model/upgrade_log.go b/core/app/model/upgrade_log.go new file mode 100644 index 0000000..11230b9 --- /dev/null +++ b/core/app/model/upgrade_log.go @@ -0,0 +1,9 @@ +package model + +type UpgradeLog struct { + BaseModel + NodeID uint `json:"nodeID"` + OldVersion string `json:"oldVersion"` + NewVersion string `json:"newVersion"` + BackupFile string `json:"backupFile"` +} diff --git a/core/app/repo/agent.go b/core/app/repo/agent.go new file mode 100644 index 0000000..6a2b884 --- /dev/null +++ b/core/app/repo/agent.go @@ -0,0 +1,37 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" +) + +type AgentRepo struct{} + +type IAgentRepo interface { + GetWebsiteSSL(opts ...global.DBOption) (model.WebsiteSSL, error) + GetCA(opts ...global.DBOption) (model.WebsiteCA, error) +} + +func NewIAgentRepo() IAgentRepo { + return &AgentRepo{} +} + +func (a *AgentRepo) GetWebsiteSSL(opts ...global.DBOption) (model.WebsiteSSL, error) { + var ssl model.WebsiteSSL + db := global.AgentDB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&ssl).Error + return ssl, err +} + +func (a *AgentRepo) GetCA(opts ...global.DBOption) (model.WebsiteCA, error) { + var ca model.WebsiteCA + db := global.AgentDB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&ca).Error + return ca, err +} diff --git a/core/app/repo/backup.go b/core/app/repo/backup.go new file mode 100644 index 0000000..937b2c0 --- /dev/null +++ b/core/app/repo/backup.go @@ -0,0 +1,69 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" +) + +type BackupRepo struct{} + +type IBackupRepo interface { + Get(opts ...global.DBOption) (model.BackupAccount, error) + List(opts ...global.DBOption) ([]model.BackupAccount, error) + Page(limit, offset int, opts ...global.DBOption) (int64, []model.BackupAccount, error) + Create(backup *model.BackupAccount) error + Save(backup *model.BackupAccount) error + Delete(opts ...global.DBOption) error +} + +func NewIBackupRepo() IBackupRepo { + return &BackupRepo{} +} + +func (u *BackupRepo) Get(opts ...global.DBOption) (model.BackupAccount, error) { + var backup model.BackupAccount + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&backup).Error + return backup, err +} + +func (u *BackupRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.BackupAccount, error) { + var ops []model.BackupAccount + db := global.DB.Model(&model.BackupAccount{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error + return count, ops, err +} + +func (u *BackupRepo) List(opts ...global.DBOption) ([]model.BackupAccount, error) { + var ops []model.BackupAccount + db := global.DB.Model(&model.BackupAccount{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&ops).Error + return ops, err +} + +func (u *BackupRepo) Create(backup *model.BackupAccount) error { + return global.DB.Create(backup).Error +} + +func (u *BackupRepo) Save(backup *model.BackupAccount) error { + return global.DB.Save(backup).Error +} + +func (u *BackupRepo) Delete(opts ...global.DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.BackupAccount{}).Error +} diff --git a/core/app/repo/command.go b/core/app/repo/command.go new file mode 100644 index 0000000..807df05 --- /dev/null +++ b/core/app/repo/command.go @@ -0,0 +1,85 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type CommandRepo struct{} + +type ICommandRepo interface { + List(opts ...global.DBOption) ([]model.Command, error) + Page(limit, offset int, opts ...global.DBOption) (int64, []model.Command, error) + Create(command *model.Command) error + Update(id uint, vars map[string]interface{}) error + UpdateGroup(group, newGroup uint) error + Delete(opts ...global.DBOption) error + Get(opts ...global.DBOption) (model.Command, error) + + WithByInfo(info string) global.DBOption +} + +func NewICommandRepo() ICommandRepo { + return &CommandRepo{} +} + +func (u *CommandRepo) Get(opts ...global.DBOption) (model.Command, error) { + var command model.Command + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&command).Error + return command, err +} + +func (u *CommandRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.Command, error) { + var users []model.Command + db := global.DB.Model(&model.Command{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (u *CommandRepo) List(opts ...global.DBOption) ([]model.Command, error) { + var commands []model.Command + db := global.DB.Model(&model.Command{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&commands).Error + return commands, err +} + +func (u *CommandRepo) Create(command *model.Command) error { + return global.DB.Create(command).Error +} + +func (u *CommandRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Command{}).Where("id = ?", id).Updates(vars).Error +} +func (h *CommandRepo) UpdateGroup(group, newGroup uint) error { + return global.DB.Model(&model.Command{}).Where("group_id = ?", group).Updates(map[string]interface{}{"group_id": newGroup}).Error +} + +func (u *CommandRepo) Delete(opts ...global.DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Command{}).Error +} + +func (c *CommandRepo) WithByInfo(info string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(info) == 0 { + return g + } + return g.Where("name like ? or command like ?", "%"+info+"%", "%"+info+"%") + } +} diff --git a/core/app/repo/common.go b/core/app/repo/common.go new file mode 100644 index 0000000..aad17fe --- /dev/null +++ b/core/app/repo/common.go @@ -0,0 +1,89 @@ +package repo + +import ( + "fmt" + + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +func WithByID(id uint) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} +func WithByGroupID(id uint) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("group_id = ?", id) + } +} + +func WithByIDs(ids []uint) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id in (?)", ids) + } +} +func WithByName(name string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`name` = ?", name) + } +} +func WithoutByName(name string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`name` != ?", name) + } +} + +func WithByType(ty string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("`type` = ?", ty) + } +} +func WithByAddr(addr string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("addr = ?", addr) + } +} +func WithByKey(key string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("key = ?", key) + } +} +func WithByStatus(status string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("status = ?", status) + } +} +func WithByNode(node string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("node = ?", node) + } +} + +func WithOrderBy(orderStr string) global.DBOption { + if orderStr == "createdAt" { + orderStr = "created_at" + } + return func(g *gorm.DB) *gorm.DB { + return g.Order(orderStr) + } +} + +func WithOrderRuleBy(orderBy, order string) global.DBOption { + if orderBy == "createdAt" { + orderBy = "created_at" + } + switch order { + case constant.OrderDesc: + order = "desc" + case constant.OrderAsc: + order = "asc" + default: + orderBy = "created_at" + order = "desc" + } + return func(g *gorm.DB) *gorm.DB { + return g.Order(fmt.Sprintf("%s %s", orderBy, order)) + } +} diff --git a/core/app/repo/group.go b/core/app/repo/group.go new file mode 100644 index 0000000..638e06c --- /dev/null +++ b/core/app/repo/group.go @@ -0,0 +1,72 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type GroupRepo struct{} + +type IGroupRepo interface { + Get(opts ...global.DBOption) (model.Group, error) + GetList(opts ...global.DBOption) ([]model.Group, error) + Create(group *model.Group) error + Update(id uint, vars map[string]interface{}) error + Delete(opts ...global.DBOption) error + + WithByDefault(isDefault bool) global.DBOption + CancelDefault(groupType string) error +} + +func NewIGroupRepo() IGroupRepo { + return &GroupRepo{} +} + +func (c *GroupRepo) WithByDefault(isDefault bool) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("is_default = ?", isDefault) + } +} + +func (u *GroupRepo) Get(opts ...global.DBOption) (model.Group, error) { + var group model.Group + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&group).Error + return group, err +} + +func (u *GroupRepo) GetList(opts ...global.DBOption) ([]model.Group, error) { + var groups []model.Group + db := global.DB.Model(&model.Group{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&groups).Error + return groups, err +} + +func (u *GroupRepo) Create(group *model.Group) error { + return global.DB.Create(group).Error +} + +func (u *GroupRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Group{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *GroupRepo) Delete(opts ...global.DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Group{}).Error +} + +func (u *GroupRepo) CancelDefault(groupType string) error { + return global.DB.Model(&model.Group{}). + Where("is_default = ? AND type = ?", 1, groupType). + Updates(map[string]interface{}{"is_default": 0}).Error +} diff --git a/core/app/repo/host.go b/core/app/repo/host.go new file mode 100644 index 0000000..fc029c8 --- /dev/null +++ b/core/app/repo/host.go @@ -0,0 +1,100 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type HostRepo struct{} + +type IHostRepo interface { + Get(opts ...global.DBOption) (model.Host, error) + GetList(opts ...global.DBOption) ([]model.Host, error) + Page(limit, offset int, opts ...global.DBOption) (int64, []model.Host, error) + Create(host *model.Host) error + Update(id uint, vars map[string]interface{}) error + UpdateGroup(group, newGroup uint) error + Delete(opts ...global.DBOption) error + + WithByInfo(info string) global.DBOption + WithByPort(port uint) global.DBOption + WithByUser(user string) global.DBOption +} + +func NewIHostRepo() IHostRepo { + return &HostRepo{} +} + +func (h *HostRepo) Get(opts ...global.DBOption) (model.Host, error) { + var host model.Host + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&host).Error + return host, err +} + +func (h *HostRepo) GetList(opts ...global.DBOption) ([]model.Host, error) { + var hosts []model.Host + db := global.DB.Model(&model.Host{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&hosts).Error + return hosts, err +} + +func (h *HostRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.Host, error) { + var users []model.Host + db := global.DB.Model(&model.Host{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (h *HostRepo) WithByInfo(info string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(info) == 0 { + return g + } + infoStr := "%" + info + "%" + return g.Where("name LIKE ? OR addr LIKE ?", infoStr, infoStr) + } +} + +func (h *HostRepo) WithByPort(port uint) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("port = ?", port) + } +} +func (h *HostRepo) WithByUser(user string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("user = ?", user) + } +} + +func (h *HostRepo) Create(host *model.Host) error { + return global.DB.Create(host).Error +} + +func (h *HostRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.Host{}).Where("id = ?", id).Updates(vars).Error +} + +func (h *HostRepo) UpdateGroup(group, newGroup uint) error { + return global.DB.Model(&model.Host{}).Where("group_id = ?", group).Updates(map[string]interface{}{"group_id": newGroup}).Error +} + +func (h *HostRepo) Delete(opts ...global.DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.Host{}).Error +} diff --git a/core/app/repo/logs.go b/core/app/repo/logs.go new file mode 100644 index 0000000..fbc0971 --- /dev/null +++ b/core/app/repo/logs.go @@ -0,0 +1,99 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type LogRepo struct{} + +type ILogRepo interface { + CleanLogin() error + CreateLoginLog(user *model.LoginLog) error + PageLoginLog(limit, offset int, opts ...global.DBOption) (int64, []model.LoginLog, error) + + CleanOperation() error + CreateOperationLog(user *model.OperationLog) error + PageOperationLog(limit, offset int, opts ...global.DBOption) (int64, []model.OperationLog, error) + + WithByIP(ip string) global.DBOption + WithBySource(source string) global.DBOption + WithByLikeOperation(operation string) global.DBOption +} + +func NewILogRepo() ILogRepo { + return &LogRepo{} +} + +func (u *LogRepo) CleanLogin() error { + return global.DB.Exec("delete from login_logs;").Error +} + +func (u *LogRepo) CreateLoginLog(log *model.LoginLog) error { + return global.DB.Create(log).Error +} + +func (u *LogRepo) PageLoginLog(page, size int, opts ...global.DBOption) (int64, []model.LoginLog, error) { + var ops []model.LoginLog + db := global.DB.Model(&model.LoginLog{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error + return count, ops, err +} + +func (u *LogRepo) CleanOperation() error { + return global.DB.Exec("delete from operation_logs").Error +} + +func (u *LogRepo) CreateOperationLog(log *model.OperationLog) error { + return global.DB.Create(log).Error +} + +func (u *LogRepo) PageOperationLog(page, size int, opts ...global.DBOption) (int64, []model.OperationLog, error) { + var ops []model.OperationLog + db := global.DB.Model(&model.OperationLog{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error + return count, ops, err +} + +func (c *LogRepo) WithByStatus(status string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(status) == 0 { + return g + } + return g.Where("status = ?", status) + } +} +func (c *LogRepo) WithBySource(source string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(source) == 0 { + return g + } + return g.Where("source = ?", source) + } +} +func (c *LogRepo) WithByIP(ip string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("ip LIKE ?", "%"+ip+"%") + } +} + +func (c *LogRepo) WithByLikeOperation(operation string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + if len(operation) == 0 { + return g + } + infoStr := "%" + operation + "%" + return g.Where("detail_zh LIKE ? OR detail_en LIKE ?", infoStr, infoStr) + } +} diff --git a/core/app/repo/script_library.go b/core/app/repo/script_library.go new file mode 100644 index 0000000..69960b3 --- /dev/null +++ b/core/app/repo/script_library.go @@ -0,0 +1,93 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type IScriptRepo interface { + Get(opts ...global.DBOption) (model.ScriptLibrary, error) + GetList(opts ...global.DBOption) ([]model.ScriptLibrary, error) + Create(script *model.ScriptLibrary) error + Update(id uint, vars map[string]interface{}) error + Page(limit, offset int, opts ...global.DBOption) (int64, []model.ScriptLibrary, error) + Delete(opts ...global.DBOption) error + SyncAll(scripts []model.ScriptLibrary) error + + WithByInfo(info string) global.DBOption +} + +func NewIScriptRepo() IScriptRepo { + return &ScriptRepo{} +} + +type ScriptRepo struct{} + +func (u *ScriptRepo) Get(opts ...global.DBOption) (model.ScriptLibrary, error) { + var ScriptLibrary model.ScriptLibrary + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&ScriptLibrary).Error + return ScriptLibrary, err +} + +func (u *ScriptRepo) GetList(opts ...global.DBOption) ([]model.ScriptLibrary, error) { + var scripts []model.ScriptLibrary + db := global.DB.Model(&model.ScriptLibrary{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&scripts).Error + return scripts, err +} + +func (u *ScriptRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.ScriptLibrary, error) { + var users []model.ScriptLibrary + db := global.DB.Model(&model.ScriptLibrary{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&users).Error + return count, users, err +} + +func (u *ScriptRepo) Create(ScriptLibrary *model.ScriptLibrary) error { + return global.DB.Create(ScriptLibrary).Error +} + +func (u *ScriptRepo) Update(id uint, vars map[string]interface{}) error { + return global.DB.Model(&model.ScriptLibrary{}).Where("id = ?", id).Updates(vars).Error +} + +func (u *ScriptRepo) Delete(opts ...global.DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.ScriptLibrary{}).Error +} + +func (u *ScriptRepo) SyncAll(scripts []model.ScriptLibrary) error { + tx := global.DB.Begin() + if err := tx.Where("is_system = ?", 1).Delete(&model.ScriptLibrary{}).Error; err != nil { + tx.Rollback() + return err + } + if err := tx.Save(&scripts).Error; err != nil { + tx.Rollback() + return err + } + tx.Commit() + return nil +} + +func (u *ScriptRepo) WithByInfo(info string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("name LIKE ? OR description LIKE ?", "%"+info+"%", "%"+info+"%") + } +} diff --git a/core/app/repo/setting.go b/core/app/repo/setting.go new file mode 100644 index 0000000..8d41c5f --- /dev/null +++ b/core/app/repo/setting.go @@ -0,0 +1,83 @@ +package repo + +import ( + "errors" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/init/migration/helper" + "gorm.io/gorm" +) + +type SettingRepo struct{} + +type ISettingRepo interface { + List(opts ...global.DBOption) ([]model.Setting, error) + Get(opts ...global.DBOption) (model.Setting, error) + GetValueByKey(key string) (string, error) + Create(key, value string) error + Update(key, value string) error + UpdateOrCreate(key, value string) error + DefaultMenu() error +} + +func NewISettingRepo() ISettingRepo { + return &SettingRepo{} +} + +func (u *SettingRepo) List(opts ...global.DBOption) ([]model.Setting, error) { + var settings []model.Setting + db := global.DB.Model(&model.Setting{}) + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&settings).Error + return settings, err +} + +func (u *SettingRepo) Create(key, value string) error { + setting := &model.Setting{ + Key: key, + Value: value, + } + return global.DB.Create(setting).Error +} + +func (u *SettingRepo) Get(opts ...global.DBOption) (model.Setting, error) { + var settings model.Setting + db := global.DB.Model(&model.Setting{}) + for _, opt := range opts { + db = opt(db) + } + err := db.First(&settings).Error + return settings, err +} + +func (u *SettingRepo) GetValueByKey(key string) (string, error) { + var setting model.Setting + if err := global.DB.Model(&model.Setting{}).Where("key = ?", key).First(&setting).Error; err != nil { + return "", err + } + return setting.Value, nil +} + +func (u *SettingRepo) Update(key, value string) error { + return global.DB.Model(&model.Setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error +} + +func (u *SettingRepo) UpdateOrCreate(key, value string) error { + var setting model.Setting + result := global.DB.Where("key = ?", key).First(&setting) + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return global.DB.Create(&model.Setting{Key: key, Value: value}).Error + } + return result.Error + } + return global.DB.Model(&setting).UpdateColumn("value", value).Error +} + +func (u *SettingRepo) DefaultMenu() error { + return global.DB.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error +} diff --git a/core/app/repo/task.go b/core/app/repo/task.go new file mode 100644 index 0000000..c700b74 --- /dev/null +++ b/core/app/repo/task.go @@ -0,0 +1,90 @@ +package repo + +import ( + "context" + + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type TaskRepo struct { +} + +type ITaskRepo interface { + Save(ctx context.Context, task *model.Task) error + GetFirst(opts ...global.DBOption) (model.Task, error) + Page(page, size int, opts ...global.DBOption) (int64, []model.Task, error) + Update(ctx context.Context, task *model.Task) error + + WithByID(id string) global.DBOption + WithResourceID(id uint) global.DBOption + WithOperate(taskOperate string) global.DBOption +} + +func NewITaskRepo() ITaskRepo { + return &TaskRepo{} +} + +func getTaskDb(opts ...global.DBOption) *gorm.DB { + db := global.TaskDB + for _, opt := range opts { + db = opt(db) + } + return db +} + +func getTaskTx(ctx context.Context, opts ...global.DBOption) *gorm.DB { + tx, ok := ctx.Value("db").(*gorm.DB) + if ok { + for _, opt := range opts { + tx = opt(tx) + } + return tx + } + return getTaskDb(opts...) +} + +func (t TaskRepo) WithByID(id string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("id = ?", id) + } +} + +func (t TaskRepo) WithOperate(taskOperate string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("operate = ?", taskOperate) + } +} + +func (t TaskRepo) WithResourceID(id uint) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("resource_id = ?", id) + } +} + +func (t TaskRepo) Save(ctx context.Context, task *model.Task) error { + return getTaskTx(ctx).Save(&task).Error +} + +func (t TaskRepo) GetFirst(opts ...global.DBOption) (model.Task, error) { + var task model.Task + db := getTaskDb(opts...).Model(&model.Task{}) + if err := db.First(&task).Error; err != nil { + return task, err + } + return task, nil +} + +func (t TaskRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.Task, error) { + var tasks []model.Task + db := getTaskDb(opts...).Model(&model.Task{}) + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&tasks).Error + return count, tasks, err +} + +func (t TaskRepo) Update(ctx context.Context, task *model.Task) error { + return getTaskTx(ctx).Save(&task).Error +} diff --git a/core/app/repo/upgrade_log.go b/core/app/repo/upgrade_log.go new file mode 100644 index 0000000..6e3df81 --- /dev/null +++ b/core/app/repo/upgrade_log.go @@ -0,0 +1,88 @@ +package repo + +import ( + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" + "gorm.io/gorm" +) + +type UpgradeLogRepo struct{} + +type IUpgradeLogRepo interface { + Get(opts ...global.DBOption) (model.UpgradeLog, error) + List(opts ...global.DBOption) ([]model.UpgradeLog, error) + Create(log *model.UpgradeLog) error + Page(limit, offset int, opts ...global.DBOption) (int64, []model.UpgradeLog, error) + Delete(opts ...global.DBOption) error + + WithByNodeID(nodeID uint) global.DBOption + WithByUpgradeVersion(oldVersion, newVersion string) global.DBOption +} + +func NewIUpgradeLogRepo() IUpgradeLogRepo { + return &UpgradeLogRepo{} +} + +func (u *UpgradeLogRepo) Get(opts ...global.DBOption) (model.UpgradeLog, error) { + var log model.UpgradeLog + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.First(&log).Error + return log, err +} + +func (u *UpgradeLogRepo) List(opts ...global.DBOption) ([]model.UpgradeLog, error) { + var logs []model.UpgradeLog + db := global.DB + for _, opt := range opts { + db = opt(db) + } + err := db.Find(&logs).Error + return logs, err +} + +func (u *UpgradeLogRepo) Clean() error { + return global.DB.Exec("delete from upgrade_logs;").Error +} + +func (u *UpgradeLogRepo) Create(log *model.UpgradeLog) error { + return global.DB.Create(log).Error +} + +func (u *UpgradeLogRepo) Save(log *model.UpgradeLog) error { + return global.DB.Save(log).Error +} + +func (u *UpgradeLogRepo) Delete(opts ...global.DBOption) error { + db := global.DB + for _, opt := range opts { + db = opt(db) + } + return db.Delete(&model.UpgradeLog{}).Error +} + +func (u *UpgradeLogRepo) Page(page, size int, opts ...global.DBOption) (int64, []model.UpgradeLog, error) { + var ops []model.UpgradeLog + db := global.DB.Model(&model.UpgradeLog{}) + for _, opt := range opts { + db = opt(db) + } + count := int64(0) + db = db.Count(&count) + err := db.Limit(size).Offset(size * (page - 1)).Find(&ops).Error + return count, ops, err +} + +func (c *UpgradeLogRepo) WithByNodeID(nodeID uint) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("node_id = ?", nodeID) + } +} + +func (c *UpgradeLogRepo) WithByUpgradeVersion(oldVersion, newVersion string) global.DBOption { + return func(g *gorm.DB) *gorm.DB { + return g.Where("old_version = ? AND new_version = ?", oldVersion, newVersion) + } +} diff --git a/core/app/service/auth.go b/core/app/service/auth.go new file mode 100644 index 0000000..580793a --- /dev/null +++ b/core/app/service/auth.go @@ -0,0 +1,719 @@ +package service + +import ( + "crypto/hmac" + "crypto/rand" + "encoding/base64" + "encoding/json" + "fmt" + "net" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/1Panel-dev/1Panel/core/utils/mfa" + "github.com/1Panel-dev/1Panel/core/utils/passkey" + "github.com/gin-gonic/gin" + "github.com/go-webauthn/webauthn/protocol" + "github.com/go-webauthn/webauthn/webauthn" +) + +type AuthService struct{} + +type IAuthService interface { + GetResponsePage() (string, error) + VerifyCode(code string) (bool, error) + Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, string, error) + LogOut(c *gin.Context) error + MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, string, error) + PasskeyBeginLogin(c *gin.Context, entrance string) (*dto.PasskeyBeginResponse, string, error) + PasskeyFinishLogin(c *gin.Context, sessionID, entrance string) (*dto.UserLoginInfo, string, error) + PasskeyBeginRegister(c *gin.Context, name string) (*dto.PasskeyBeginResponse, string, error) + PasskeyFinishRegister(c *gin.Context, sessionID string) (string, error) + PasskeyList() ([]dto.PasskeyInfo, error) + PasskeyDelete(id string) error + PasskeyStatus(c *gin.Context) bool + GetSecurityEntrance() string + IsLogin(c *gin.Context) bool +} + +func NewIAuthService() IAuthService { + return &AuthService{} +} + +func (u *AuthService) Login(c *gin.Context, info dto.Login, entrance string) (*dto.UserLoginInfo, string, error) { + nameSetting, err := settingRepo.Get(repo.WithByKey("UserName")) + if err != nil { + return nil, "", buserr.New("ErrRecordNotFound") + } + if nameSetting.Value != info.Name { + return nil, "ErrAuth", buserr.New("ErrAuth") + } + if err = checkPassword(info.Password); err != nil { + return nil, "ErrAuth", err + } + entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) + if err != nil { + return nil, "", err + } + if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance { + return nil, "ErrEntrance", buserr.New("ErrEntrance") + } + mfa, err := settingRepo.Get(repo.WithByKey("MFAStatus")) + if err != nil { + return nil, "", err + } + if err = settingRepo.Update("Language", info.Language); err != nil { + return nil, "", err + } + if mfa.Value == constant.StatusEnable { + return &dto.UserLoginInfo{Name: nameSetting.Value, MfaStatus: mfa.Value}, "", nil + } + res, err := u.generateSession(c, info.Name) + if err != nil { + return nil, "", err + } + if entrance != "" { + entranceValue := base64.StdEncoding.EncodeToString([]byte(entrance)) + c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true) + } + return res, "", nil +} + +func (u *AuthService) MFALogin(c *gin.Context, info dto.MFALogin, entrance string) (*dto.UserLoginInfo, string, error) { + nameSetting, err := settingRepo.Get(repo.WithByKey("UserName")) + if err != nil { + return nil, "", buserr.New("ErrRecordNotFound") + } + if nameSetting.Value != info.Name { + return nil, "ErrAuth", nil + } + if err = checkPassword(info.Password); err != nil { + return nil, "ErrAuth", err + } + entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) + if err != nil { + return nil, "", err + } + if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance { + return nil, "", buserr.New("ErrEntrance") + } + mfaSecret, err := settingRepo.Get(repo.WithByKey("MFASecret")) + if err != nil { + return nil, "", err + } + mfaInterval, err := settingRepo.Get(repo.WithByKey("MFAInterval")) + if err != nil { + return nil, "", err + } + success := mfa.ValidCode(info.Code, mfaInterval.Value, mfaSecret.Value) + if !success { + return nil, "ErrAuth", nil + } + res, err := u.generateSession(c, info.Name) + if err != nil { + return nil, "", err + } + if entrance != "" { + entranceValue := base64.StdEncoding.EncodeToString([]byte(entrance)) + c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true) + } + return res, "", nil +} + +func (u *AuthService) generateSession(c *gin.Context, name string) (*dto.UserLoginInfo, error) { + setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout")) + if err != nil { + return nil, err + } + httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL")) + if err != nil { + return nil, err + } + lifeTime, err := strconv.Atoi(setting.Value) + if err != nil { + return nil, err + } + + sessionUser, err := global.SESSION.Get(c) + if err != nil { + err := global.SESSION.Set(c, sessionUser, httpsSetting.Value == constant.StatusEnable, lifeTime) + if err != nil { + return nil, err + } + return &dto.UserLoginInfo{Name: name}, nil + } + if err := global.SESSION.Set(c, sessionUser, httpsSetting.Value == constant.StatusEnable, lifeTime); err != nil { + return nil, err + } + + return &dto.UserLoginInfo{Name: name}, nil +} + +func (u *AuthService) LogOut(c *gin.Context) error { + httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL")) + if err != nil { + return err + } + sID, _ := c.Cookie(constant.SessionName) + if sID != "" { + c.SetCookie(constant.SessionName, sID, -1, "", "", httpsSetting.Value == constant.StatusEnable, true) + err := global.SESSION.Delete(c) + if err != nil { + return err + } + } + return nil +} + +func (u *AuthService) VerifyCode(code string) (bool, error) { + setting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) + if err != nil { + return false, err + } + return hmac.Equal([]byte(setting.Value), []byte(code)), nil +} + +func (u *AuthService) GetResponsePage() (string, error) { + pageCode, err := settingRepo.Get(repo.WithByKey("NoAuthSetting")) + if err != nil { + return "", err + } + return pageCode.Value, nil +} + +func (u *AuthService) GetSecurityEntrance() string { + status, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) + if err != nil { + return "" + } + if len(status.Value) == 0 { + return "" + } + return status.Value +} + +func (u *AuthService) IsLogin(c *gin.Context) bool { + _, err := global.SESSION.Get(c) + return err == nil +} + +func (u *AuthService) PasskeyStatus(c *gin.Context) bool { + enabled, err := u.passkeyEnabled(c) + if err != nil { + global.LOG.Errorf("passkey enabled check failed, err: %v", err) + enabled = false + } + configured, err := u.passkeyConfigured() + if err != nil { + global.LOG.Errorf("passkey config check failed, err: %v", err) + configured = false + } + return enabled && configured +} + +func (u *AuthService) PasskeyBeginLogin(c *gin.Context, entrance string) (*dto.PasskeyBeginResponse, string, error) { + if err := u.checkEntrance(entrance); err != nil { + return nil, "ErrEntrance", err + } + + config, msgKey, err := u.passkeyConfig(c) + if err != nil { + return nil, msgKey, err + } + + records, err := loadPasskeyCredentialRecords() + if err != nil { + return nil, "", err + } + if len(records) == 0 { + return nil, "ErrPasskeyNotConfigured", buserr.New("ErrPasskeyNotConfigured") + } + + user, err := u.passkeyUser(records, true) + if err != nil { + return nil, "", err + } + + wa, err := webauthn.New(config) + if err != nil { + return nil, "", err + } + assertion, sessionData, err := wa.BeginLogin(user) + if err != nil { + return nil, "", err + } + passkeySessions := passkey.GetPasskeySessionStore() + sessionID := passkeySessions.Set(passkey.PasskeySessionKindLogin, "", *sessionData) + return &dto.PasskeyBeginResponse{SessionID: sessionID, PublicKey: assertion.Response}, "", nil +} + +func (u *AuthService) PasskeyFinishLogin(c *gin.Context, sessionID, entrance string) (*dto.UserLoginInfo, string, error) { + if sessionID == "" { + return nil, "ErrPasskeySession", buserr.New("ErrPasskeySession") + } + + if err := u.checkEntrance(entrance); err != nil { + return nil, "ErrEntrance", err + } + + config, msgKey, err := u.passkeyConfig(c) + if err != nil { + return nil, msgKey, err + } + + passkeySessions := passkey.GetPasskeySessionStore() + session, ok := passkeySessions.Get(sessionID) + if !ok || session.Kind != passkey.PasskeySessionKindLogin { + return nil, "ErrPasskeySession", buserr.New("ErrPasskeySession") + } + passkeySessions.Delete(sessionID) + + records, err := loadPasskeyCredentialRecords() + if err != nil { + return nil, "", err + } + if len(records) == 0 { + return nil, "ErrPasskeyNotConfigured", buserr.New("ErrPasskeyNotConfigured") + } + + user, err := u.passkeyUser(records, true) + if err != nil { + return nil, "", err + } + + wa, err := webauthn.New(config) + if err != nil { + return nil, "", err + } + credential, err := wa.FinishLogin(user, session.Session, c.Request) + if err != nil { + return nil, "ErrAuth", err + } + + if err := updatePasskeyCredentialRecord(records, credential); err != nil { + return nil, "ErrAuth", err + } + if err := savePasskeyCredentialRecords(records); err != nil { + return nil, "", err + } + + userSetting, err := settingRepo.Get(repo.WithByKey("UserName")) + if err != nil { + return nil, "", err + } + res, err := u.generateSession(c, userSetting.Value) + if err != nil { + return nil, "", err + } + if entrance != "" { + entranceValue := base64.StdEncoding.EncodeToString([]byte(entrance)) + c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true) + } + return res, "", nil +} + +func (u *AuthService) PasskeyBeginRegister(c *gin.Context, name string) (*dto.PasskeyBeginResponse, string, error) { + config, msgKey, err := u.passkeyConfig(c) + if err != nil { + return nil, msgKey, err + } + records, err := loadPasskeyCredentialRecords() + if err != nil { + return nil, "", err + } + if len(records) >= passkey.PasskeyMaxCredentials { + return nil, "ErrPasskeyLimit", buserr.New("ErrPasskeyLimit") + } + user, err := u.passkeyUser(records, true) + if err != nil { + return nil, "", err + } + + wa, err := webauthn.New(config) + if err != nil { + return nil, "", err + } + exclusions := make([]protocol.CredentialDescriptor, len(user.Credentials)) + for i, credential := range user.Credentials { + exclusions[i] = credential.Descriptor() + } + creation, sessionData, err := wa.BeginRegistration(user, webauthn.WithExclusions(exclusions)) + if err != nil { + return nil, "", err + } + + passkeySessions := passkey.GetPasskeySessionStore() + sessionID := passkeySessions.Set(passkey.PasskeySessionKindRegister, strings.TrimSpace(name), *sessionData) + return &dto.PasskeyBeginResponse{SessionID: sessionID, PublicKey: creation.Response}, "", nil +} + +func (u *AuthService) PasskeyFinishRegister(c *gin.Context, sessionID string) (string, error) { + if sessionID == "" { + return "ErrPasskeySession", buserr.New("ErrPasskeySession") + } + config, msgKey, err := u.passkeyConfig(c) + if err != nil { + return msgKey, err + } + + passkeySessions := passkey.GetPasskeySessionStore() + session, ok := passkeySessions.Get(sessionID) + if !ok || session.Kind != passkey.PasskeySessionKindRegister { + return "ErrPasskeySession", buserr.New("ErrPasskeySession") + } + + passkeySessions.Delete(sessionID) + + records, err := loadPasskeyCredentialRecords() + if err != nil { + return "", err + } + if len(records) >= passkey.PasskeyMaxCredentials { + return "ErrPasskeyLimit", buserr.New("ErrPasskeyLimit") + } + + user, err := u.passkeyUser(records, true) + if err != nil { + return "", err + } + + wa, err := webauthn.New(config) + if err != nil { + return "", err + } + credential, err := wa.FinishRegistration(user, session.Session, c.Request) + if err != nil { + return "ErrPasskeyVerify", err + } + + if passkeyCredentialExists(records, credential.ID) { + return "ErrPasskeyDuplicate", buserr.New("ErrPasskeyDuplicate") + } + + displayName := strings.TrimSpace(session.Name) + if displayName == "" { + displayName = fmt.Sprintf("%s-%s", passkey.PasskeyCredentialNameDefault, time.Now().Format("20060102150405")) + } + + records = append(records, passkey.PasskeyCredentialRecord{ + ID: base64.RawURLEncoding.EncodeToString(credential.ID), + Name: displayName, + CreatedAt: time.Now().Format(constant.DateTimeLayout), + LastUsedAt: "", + FlagsValue: credentialFlagsValue(credential.Flags), + Credential: *credential, + }) + + if err := savePasskeyCredentialRecords(records); err != nil { + return "", err + } + return "", nil +} + +func (u *AuthService) PasskeyList() ([]dto.PasskeyInfo, error) { + records, err := loadPasskeyCredentialRecords() + if err != nil { + return nil, err + } + list := make([]dto.PasskeyInfo, 0, len(records)) + for _, record := range records { + list = append(list, dto.PasskeyInfo{ + ID: record.ID, + Name: record.Name, + CreatedAt: record.CreatedAt, + LastUsedAt: record.LastUsedAt, + }) + } + return list, nil +} + +func (u *AuthService) PasskeyDelete(id string) error { + records, err := loadPasskeyCredentialRecords() + if err != nil { + return err + } + index := -1 + for i, record := range records { + if record.ID == id { + index = i + break + } + } + if index == -1 { + return buserr.New("ErrRecordNotFound") + } + records = append(records[:index], records[index+1:]...) + return savePasskeyCredentialRecords(records) +} + +func (u *AuthService) passkeyEnabled(c *gin.Context) (bool, error) { + sslSetting, err := settingRepo.Get(repo.WithByKey("SSL")) + if err != nil { + return false, err + } + if sslSetting.Value == constant.StatusDisable { + return false, nil + } + return strings.EqualFold(passkeyRequestScheme(c), "https"), nil +} + +func (u *AuthService) passkeyConfigured() (bool, error) { + bindDomain, err := settingRepo.Get(repo.WithByKey("BindDomain")) + if err != nil { + return false, err + } + if strings.TrimSpace(bindDomain.Value) == "" { + return false, nil + } + records, err := loadPasskeyCredentialRecords() + if err != nil { + return false, err + } + return len(records) > 0, nil +} + +func (u *AuthService) passkeyUser(records []passkey.PasskeyCredentialRecord, allowCreate bool) (*passkey.PasskeyUser, error) { + userID, err := u.passkeyUserID(allowCreate) + if err != nil { + return nil, err + } + nameSetting, err := settingRepo.Get(repo.WithByKey("UserName")) + if err != nil { + return nil, err + } + credentials := make([]webauthn.Credential, len(records)) + for i, record := range records { + credentials[i] = record.Credential + } + return &passkey.PasskeyUser{ + ID: userID, + Name: nameSetting.Value, + DisplayName: nameSetting.Value, + Credentials: credentials, + }, nil +} + +func (u *AuthService) passkeyUserID(allowCreate bool) ([]byte, error) { + setting, err := settingRepo.Get(repo.WithByKey(passkey.PasskeyUserIDSettingKey)) + if err != nil { + return nil, err + } + if setting.Value == "" { + if !allowCreate { + return nil, buserr.New("ErrPasskeyNotConfigured") + } + raw := make([]byte, 32) + if _, err := rand.Read(raw); err != nil { + return nil, err + } + encoded := base64.RawURLEncoding.EncodeToString(raw) + if err := settingRepo.Update(passkey.PasskeyUserIDSettingKey, encoded); err != nil { + return nil, err + } + return raw, nil + } + raw, err := base64.RawURLEncoding.DecodeString(setting.Value) + if err != nil { + return nil, err + } + return raw, nil +} + +func (u *AuthService) passkeyConfig(c *gin.Context) (*webauthn.Config, string, error) { + enabled, err := u.passkeyEnabled(c) + if err != nil { + return nil, "", err + } + if !enabled { + return nil, "ErrPasskeyDisabled", buserr.New("ErrPasskeyDisabled") + } + origin, rpID, err := passkeyOriginAndRPID(c) + if err != nil { + return nil, "", err + } + panelName, err := settingRepo.Get(repo.WithByKey("PanelName")) + if err != nil { + return nil, "", err + } + return &webauthn.Config{ + RPID: rpID, + RPDisplayName: panelName.Value, + RPOrigins: []string{origin}, + AuthenticatorSelection: protocol.AuthenticatorSelection{ + UserVerification: protocol.VerificationRequired, + }, + }, "", nil +} + +func (u *AuthService) checkEntrance(entrance string) error { + entranceSetting, err := settingRepo.Get(repo.WithByKey("SecurityEntrance")) + if err != nil { + return err + } + if len(entranceSetting.Value) != 0 && entranceSetting.Value != entrance { + return buserr.New("ErrEntrance") + } + return nil +} + +func loadPasskeyCredentialRecords() ([]passkey.PasskeyCredentialRecord, error) { + setting, err := settingRepo.Get(repo.WithByKey(passkey.PasskeyCredentialSettingKey)) + if err != nil { + return nil, err + } + if setting.Value == "" { + return []passkey.PasskeyCredentialRecord{}, nil + } + decrypted, err := encrypt.StringDecrypt(setting.Value) + if err != nil { + return nil, err + } + var records []passkey.PasskeyCredentialRecord + if err := json.Unmarshal([]byte(decrypted), &records); err != nil { + return nil, err + } + for i := range records { + records[i].Credential.Flags = webauthn.NewCredentialFlags(protocol.AuthenticatorFlags(records[i].FlagsValue)) + } + return records, nil +} + +func savePasskeyCredentialRecords(records []passkey.PasskeyCredentialRecord) error { + if len(records) == 0 { + return settingRepo.Update(passkey.PasskeyCredentialSettingKey, "") + } + for i := range records { + records[i].FlagsValue = credentialFlagsValue(records[i].Credential.Flags) + } + raw, err := json.Marshal(records) + if err != nil { + return err + } + encrypted, err := encrypt.StringEncrypt(string(raw)) + if err != nil { + return err + } + return settingRepo.Update(passkey.PasskeyCredentialSettingKey, encrypted) +} + +func passkeyCredentialExists(records []passkey.PasskeyCredentialRecord, credentialID []byte) bool { + encoded := base64.RawURLEncoding.EncodeToString(credentialID) + for _, record := range records { + if record.ID == encoded { + return true + } + } + return false +} + +func updatePasskeyCredentialRecord(records []passkey.PasskeyCredentialRecord, credential *webauthn.Credential) error { + encoded := base64.RawURLEncoding.EncodeToString(credential.ID) + for i := range records { + if records[i].ID == encoded { + records[i].Credential = *credential + records[i].FlagsValue = credentialFlagsValue(credential.Flags) + records[i].LastUsedAt = time.Now().Format(constant.DateTimeLayout) + return nil + } + } + return buserr.New("ErrPasskeyNotConfigured") +} + +func credentialFlagsValue(flags webauthn.CredentialFlags) uint8 { + var value protocol.AuthenticatorFlags + if flags.UserPresent { + value |= protocol.FlagUserPresent + } + if flags.UserVerified { + value |= protocol.FlagUserVerified + } + if flags.BackupEligible { + value |= protocol.FlagBackupEligible + } + if flags.BackupState { + value |= protocol.FlagBackupState + } + return uint8(value) +} + +func passkeyOriginAndRPID(c *gin.Context) (string, string, error) { + host := passkeyRequestHost(c) + if host == "" { + return "", "", fmt.Errorf("missing request host") + } + scheme := passkeyRequestScheme(c) + origin := fmt.Sprintf("%s://%s", scheme, host) + + bindDomain, err := settingRepo.Get(repo.WithByKey("BindDomain")) + if err != nil { + return "", "", err + } + bindDomainValue := strings.TrimSpace(bindDomain.Value) + if bindDomainValue == "" { + return "", "", buserr.New("ErrPasskeyNotConfigured") + } + hostDomain := stripHostPort(host) + bindDomainValue = stripHostPort(bindDomainValue) + if hostDomain == "" || !strings.EqualFold(hostDomain, bindDomainValue) { + return "", "", buserr.New("ErrPasskeyDisabled") + } + return origin, bindDomainValue, nil +} + +func passkeyRequestHost(c *gin.Context) string { + host := c.Request.Host + if strings.Contains(host, ",") { + host = strings.TrimSpace(strings.Split(host, ",")[0]) + } + return strings.TrimSpace(host) +} + +func passkeyRequestScheme(c *gin.Context) string { + if c.Request.TLS != nil { + return "https" + } + return "http" +} + +func stripHostPort(hostport string) string { + if hostport == "" { + return hostport + } + hostport = strings.TrimSpace(hostport) + if host, _, err := net.SplitHostPort(hostport); err == nil { + return strings.Trim(host, "[]") + } + return strings.Trim(hostport, "[]") +} + +func checkPassword(password string) error { + priKey, _ := settingRepo.Get(repo.WithByKey("PASSWORD_PRIVATE_KEY")) + + privateKey, err := encrypt.ParseRSAPrivateKey(priKey.Value) + if err != nil { + return err + } + loginPassword, err := encrypt.DecryptPassword(password, privateKey) + if err != nil { + return err + } + passwordSetting, err := settingRepo.Get(repo.WithByKey("Password")) + if err != nil { + return err + } + existPassword, err := encrypt.StringDecrypt(passwordSetting.Value) + if err != nil { + return err + } + if !hmac.Equal([]byte(loginPassword), []byte(existPassword)) { + return buserr.New("ErrAuth") + } + return nil +} diff --git a/core/app/service/backup.go b/core/app/service/backup.go new file mode 100644 index 0000000..ddd11a0 --- /dev/null +++ b/core/app/service/backup.go @@ -0,0 +1,225 @@ +package service + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "net/http" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/cloud_storage" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/1Panel-dev/1Panel/core/utils/req_helper/proxy_local" + "github.com/1Panel-dev/1Panel/core/utils/xpack" + "github.com/jinzhu/copier" +) + +type BackupService struct{} + +type IBackupService interface { + LoadBackupClientInfo(clientType string) (dto.BackupClientInfo, error) + Create(backupDto dto.BackupOperate) error + Update(req dto.BackupOperate) error + Delete(name string) error + RefreshToken(req dto.OperateByName) error +} + +func NewIBackupService() IBackupService { + return &BackupService{} +} + +func (u *BackupService) LoadBackupClientInfo(clientType string) (dto.BackupClientInfo, error) { + var data dto.BackupClientInfo + clientIDKey := "OneDriveID" + clientIDSc := "OneDriveSc" + data.RedirectUri = constant.OneDriveRedirectURI + clientID, err := settingRepo.Get(repo.WithByKey(clientIDKey)) + if err != nil { + return data, err + } + idItem, err := base64.StdEncoding.DecodeString(clientID.Value) + if err != nil { + return data, err + } + data.ClientID = string(idItem) + clientSecret, err := settingRepo.Get(repo.WithByKey(clientIDSc)) + if err != nil { + return data, err + } + secretItem, err := base64.StdEncoding.DecodeString(clientSecret.Value) + if err != nil { + return data, err + } + data.ClientSecret = string(secretItem) + + return data, err +} + +func (u *BackupService) Create(req dto.BackupOperate) error { + if !req.IsPublic { + return buserr.New("ErrBackupPublic") + } + backup, _ := backupRepo.Get(repo.WithByName(req.Name)) + if backup.ID != 0 { + return buserr.New("ErrRecordExist") + } + if req.Type != constant.Sftp && req.BackupPath != "/" { + req.BackupPath = strings.TrimPrefix(req.BackupPath, "/") + } + if err := copier.Copy(&backup, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + itemAccessKey, err := base64.StdEncoding.DecodeString(backup.AccessKey) + if err != nil { + return err + } + backup.AccessKey = string(itemAccessKey) + itemCredential, err := base64.StdEncoding.DecodeString(backup.Credential) + if err != nil { + return err + } + backup.Credential = string(itemCredential) + + backup.AccessKey, err = encrypt.StringEncrypt(backup.AccessKey) + if err != nil { + return err + } + backup.Credential, err = encrypt.StringEncrypt(backup.Credential) + if err != nil { + return err + } + if err := backupRepo.Create(&backup); err != nil { + return err + } + if err := xpack.Sync(constant.SyncBackupAccounts); err != nil { + global.LOG.Errorf("sync backup account to node failed, err: %v", err) + } + return nil +} + +func (u *BackupService) Delete(name string) error { + backup, _ := backupRepo.Get(repo.WithByName(name)) + if backup.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if !backup.IsPublic { + return buserr.New("ErrBackupPublic") + } + if backup.Type == constant.Local { + return buserr.New("ErrBackupLocal") + } + if _, err := proxy_local.NewLocalClient(fmt.Sprintf("/api/v2/backups/check/%s", name), http.MethodGet, nil, nil); err != nil { + global.LOG.Errorf("check used of local cronjob failed, err: %v", err) + return buserr.New("ErrBackupInUsed") + } + if err := xpack.CheckBackupUsed(name); err != nil { + global.LOG.Errorf("check used of node cronjob failed, err: %v", err) + return buserr.New("ErrBackupInUsed") + } + + if err := backupRepo.Delete(repo.WithByName(name)); err != nil { + return err + } + if err := xpack.Sync(constant.SyncBackupAccounts); err != nil { + global.LOG.Errorf("sync backup account to node failed, err: %v", err) + } + return nil +} + +func (u *BackupService) Update(req dto.BackupOperate) error { + backup, _ := backupRepo.Get(repo.WithByName(req.Name)) + if backup.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if !backup.IsPublic { + return buserr.New("ErrBackupPublic") + } + if backup.Type == constant.Local { + return buserr.New("ErrBackupLocal") + } + if req.Type != constant.Sftp && req.BackupPath != "/" { + req.BackupPath = strings.TrimPrefix(req.BackupPath, "/") + } + var newBackup model.BackupAccount + if err := copier.Copy(&newBackup, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + newBackup.ID = backup.ID + itemAccessKey, err := base64.StdEncoding.DecodeString(newBackup.AccessKey) + if err != nil { + return err + } + newBackup.AccessKey = string(itemAccessKey) + itemCredential, err := base64.StdEncoding.DecodeString(newBackup.Credential) + if err != nil { + return err + } + newBackup.Credential = string(itemCredential) + newBackup.AccessKey, err = encrypt.StringEncrypt(newBackup.AccessKey) + if err != nil { + return err + } + newBackup.Credential, err = encrypt.StringEncrypt(newBackup.Credential) + if err != nil { + return err + } + newBackup.ID = backup.ID + newBackup.CreatedAt = backup.CreatedAt + newBackup.UpdatedAt = backup.UpdatedAt + if err := backupRepo.Save(&newBackup); err != nil { + return err + } + if err := xpack.Sync(constant.SyncBackupAccounts); err != nil { + global.LOG.Errorf("sync backup account to node failed, err: %v", err) + } + return nil +} + +func (u *BackupService) RefreshToken(req dto.OperateByName) error { + backup, _ := backupRepo.Get(repo.WithByName(req.Name)) + if backup.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if !backup.IsPublic { + return buserr.New("ErrBackupPublic") + } + varMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(backup.Vars), &varMap); err != nil { + return fmt.Errorf("failed to refresh %s - %s token, please retry, err: %v", backup.Type, backup.Name, err) + } + var ( + refreshToken string + err error + ) + switch backup.Type { + case constant.OneDrive: + refreshToken, err = cloud_storage.RefreshToken("refresh_token", "refreshToken", varMap) + case constant.ALIYUN: + refreshToken, err = cloud_storage.RefreshALIToken(varMap) + } + if err != nil { + varMap["refresh_status"] = constant.StatusFailed + varMap["refresh_msg"] = err.Error() + return fmt.Errorf("failed to refresh %s-%s token, please retry, err: %v", backup.Type, backup.Name, err) + } + varMap["refresh_status"] = constant.StatusSuccess + varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout) + varMap["refresh_token"] = refreshToken + + varsItem, _ := json.Marshal(varMap) + backup.Vars = string(varsItem) + if err := backupRepo.Save(&backup); err != nil { + return err + } + if err := xpack.Sync(constant.SyncBackupAccounts); err != nil { + global.LOG.Errorf("sync backup account to node failed, err: %v", err) + } + return nil +} diff --git a/core/app/service/command.go b/core/app/service/command.go new file mode 100644 index 0000000..5231688 --- /dev/null +++ b/core/app/service/command.go @@ -0,0 +1,167 @@ +package service + +import ( + "fmt" + "os" + "path" + "time" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/csv" + "github.com/jinzhu/copier" +) + +type CommandService struct{} + +type ICommandService interface { + List(req dto.OperateByType) ([]dto.CommandInfo, error) + SearchForTree(req dto.OperateByType) ([]dto.CommandTree, error) + SearchWithPage(search dto.SearchCommandWithPage) (int64, interface{}, error) + Create(req dto.CommandOperate) error + Update(req dto.CommandOperate) error + Delete(ids []uint) error + + Export() (string, error) +} + +func NewICommandService() ICommandService { + return &CommandService{} +} + +func (u *CommandService) List(req dto.OperateByType) ([]dto.CommandInfo, error) { + commands, err := commandRepo.List(repo.WithOrderBy("name"), repo.WithByType(req.Type)) + if err != nil { + return nil, buserr.New("ErrRecordNotFound") + } + var dtoCommands []dto.CommandInfo + for _, command := range commands { + var item dto.CommandInfo + if err := copier.Copy(&item, &command); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + dtoCommands = append(dtoCommands, item) + } + return dtoCommands, err +} + +func (u *CommandService) SearchForTree(req dto.OperateByType) ([]dto.CommandTree, error) { + cmdList, err := commandRepo.List(repo.WithOrderBy("name"), repo.WithByType(req.Type)) + if err != nil { + return nil, err + } + groups, err := groupRepo.GetList(repo.WithByType(req.Type)) + if err != nil { + return nil, err + } + var lists []dto.CommandTree + for _, group := range groups { + var data dto.CommandTree + data.Label = group.Name + data.Value = group.Name + for _, cmd := range cmdList { + if cmd.GroupID == group.ID { + data.Children = append(data.Children, dto.CommandTree{Label: cmd.Name, Value: cmd.Command}) + } + } + if len(data.Children) != 0 { + lists = append(lists, data) + } + } + return lists, err +} + +func (u *CommandService) SearchWithPage(req dto.SearchCommandWithPage) (int64, interface{}, error) { + options := []global.DBOption{ + repo.WithOrderRuleBy(req.OrderBy, req.Order), + repo.WithByType(req.Type), + } + if len(req.Info) != 0 { + options = append(options, commandRepo.WithByInfo(req.Info)) + } + if req.GroupID != 0 { + options = append(options, repo.WithByGroupID(req.GroupID)) + } + total, commands, err := commandRepo.Page(req.Page, req.PageSize, options...) + if err != nil { + return 0, nil, err + } + groups, _ := groupRepo.GetList(repo.WithByType(req.Type)) + var dtoCommands []dto.CommandInfo + for _, command := range commands { + var item dto.CommandInfo + if err := copier.Copy(&item, &command); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + for _, group := range groups { + if command.GroupID == group.ID { + item.GroupBelong = group.Name + item.GroupID = group.ID + } + } + dtoCommands = append(dtoCommands, item) + } + return total, dtoCommands, err +} + +func (u *CommandService) Export() (string, error) { + commands, err := commandRepo.List(repo.WithByType("command")) + if err != nil { + return "", err + } + var list []csv.CommandTemplate + for _, item := range commands { + list = append(list, csv.CommandTemplate{ + Name: item.Name, + Command: item.Command, + }) + } + tmpFileName := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/export/commands", fmt.Sprintf("1panel-commands-%s.csv", time.Now().Format(constant.DateTimeSlimLayout))) + if _, err := os.Stat(path.Dir(tmpFileName)); err != nil { + _ = os.MkdirAll(path.Dir(tmpFileName), constant.DirPerm) + } + if err := csv.ExportCommands(tmpFileName, list); err != nil { + return "", err + } + return tmpFileName, err +} + +func (u *CommandService) Create(req dto.CommandOperate) error { + command, _ := commandRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type)) + if command.ID != 0 { + return buserr.New("ErrRecordExist") + } + if err := copier.Copy(&command, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if err := commandRepo.Create(&command); err != nil { + return err + } + return nil +} + +func (u *CommandService) Delete(ids []uint) error { + if len(ids) == 1 { + command, _ := commandRepo.Get(repo.WithByID(ids[0])) + if command.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + return commandRepo.Delete(repo.WithByID(ids[0])) + } + return commandRepo.Delete(repo.WithByIDs(ids)) +} + +func (u *CommandService) Update(req dto.CommandOperate) error { + command, _ := commandRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type)) + if command.ID != 0 && command.ID != req.ID { + return buserr.New("ErrRecordExist") + } + upMap := make(map[string]interface{}) + upMap["name"] = req.Name + upMap["group_id"] = req.GroupID + upMap["command"] = req.Command + return commandRepo.Update(req.ID, upMap) +} diff --git a/core/app/service/entry.go b/core/app/service/entry.go new file mode 100644 index 0000000..51a3279 --- /dev/null +++ b/core/app/service/entry.go @@ -0,0 +1,16 @@ +package service + +import "github.com/1Panel-dev/1Panel/core/app/repo" + +var ( + hostRepo = repo.NewIHostRepo() + commandRepo = repo.NewICommandRepo() + settingRepo = repo.NewISettingRepo() + backupRepo = repo.NewIBackupRepo() + logRepo = repo.NewILogRepo() + groupRepo = repo.NewIGroupRepo() + upgradeLogRepo = repo.NewIUpgradeLogRepo() + + agentRepo = repo.NewIAgentRepo() + scriptRepo = repo.NewIScriptRepo() +) diff --git a/core/app/service/group.go b/core/app/service/group.go new file mode 100644 index 0000000..3aea012 --- /dev/null +++ b/core/app/service/group.go @@ -0,0 +1,137 @@ +package service + +import ( + "bytes" + "fmt" + "net/http" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/req_helper/proxy_local" + "github.com/1Panel-dev/1Panel/core/utils/xpack" + "github.com/jinzhu/copier" +) + +type GroupService struct{} + +type IGroupService interface { + List(req dto.OperateByType) ([]dto.GroupInfo, error) + Create(req dto.GroupCreate) error + Update(req dto.GroupUpdate) error + Delete(id uint) error +} + +func NewIGroupService() IGroupService { + return &GroupService{} +} + +func (u *GroupService) List(req dto.OperateByType) ([]dto.GroupInfo, error) { + options := []global.DBOption{ + repo.WithOrderBy("is_default desc"), + repo.WithOrderBy("created_at desc"), + } + if len(req.Type) != 0 { + options = append(options, repo.WithByType(req.Type)) + } + var ( + groups []model.Group + err error + ) + groups, err = groupRepo.GetList(options...) + if err != nil { + return nil, buserr.New("ErrRecordNotFound") + } + var dtoUsers []dto.GroupInfo + for _, group := range groups { + var item dto.GroupInfo + if err := copier.Copy(&item, &group); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + dtoUsers = append(dtoUsers, item) + } + return dtoUsers, err +} + +func (u *GroupService) Create(req dto.GroupCreate) error { + group, _ := groupRepo.Get(repo.WithByName(req.Name), repo.WithByType(req.Type)) + if group.ID != 0 { + return buserr.New("ErrRecordExist") + } + if err := copier.Copy(&group, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if err := groupRepo.Create(&group); err != nil { + return err + } + return nil +} + +func (u *GroupService) Delete(id uint) error { + group, _ := groupRepo.Get(repo.WithByID(id)) + if group.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if group.Type == "script" { + list, _ := scriptRepo.GetList() + if len(list) == 0 { + return groupRepo.Delete(repo.WithByID(id)) + } + for _, itemData := range list { + groupIDs := strings.Split(itemData.Groups, ",") + for _, idItem := range groupIDs { + groupID, _ := strconv.Atoi(idItem) + if uint(groupID) == id { + return buserr.New("ErrGroupIsInUse") + } + } + } + return groupRepo.Delete(repo.WithByID(id)) + } + if group.IsDefault { + return buserr.New("ErrGroupIsDefault") + } + defaultGroup, err := groupRepo.Get(repo.WithByType(group.Type), groupRepo.WithByDefault(true)) + if err != nil { + return err + } + switch group.Type { + case "host": + err = hostRepo.UpdateGroup(id, defaultGroup.ID) + case "script": + err = hostRepo.UpdateGroup(id, defaultGroup.ID) + case "command": + err = commandRepo.UpdateGroup(id, defaultGroup.ID) + case "node": + err = xpack.UpdateGroup("node", id, defaultGroup.ID) + case "website": + bodyItem := []byte(fmt.Sprintf(`{"Group":%v, "NewGroup":%v}`, id, defaultGroup.ID)) + if _, err := proxy_local.NewLocalClient("/api/v2/websites/group/change", http.MethodPost, bytes.NewReader(bodyItem), nil); err != nil { + return err + } + if err := xpack.UpdateGroup("node", id, defaultGroup.ID); err != nil { + return err + } + } + if err != nil { + return err + } + return groupRepo.Delete(repo.WithByID(id)) +} + +func (u *GroupService) Update(req dto.GroupUpdate) error { + if req.IsDefault { + if err := groupRepo.CancelDefault(req.Type); err != nil { + return err + } + } + upMap := make(map[string]interface{}) + upMap["name"] = req.Name + upMap["is_default"] = req.IsDefault + + return groupRepo.Update(req.ID, upMap) +} diff --git a/core/app/service/host.go b/core/app/service/host.go new file mode 100644 index 0000000..9a7b155 --- /dev/null +++ b/core/app/service/host.go @@ -0,0 +1,347 @@ +package service + +import ( + "encoding/base64" + "fmt" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/1Panel-dev/1Panel/core/utils/ssh" + "github.com/jinzhu/copier" + "github.com/pkg/errors" +) + +type HostService struct{} + +type IHostService interface { + TestLocalConn(id uint) bool + TestByInfo(req dto.HostConnTest) bool + GetHostByID(id uint) (*dto.HostInfo, error) + SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error) + SearchWithPage(search dto.SearchPageWithGroup) (int64, interface{}, error) + Create(req dto.HostOperate) (*dto.HostInfo, error) + Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error) + Delete(id []uint) error + + EncryptHost(itemVal string) (string, error) +} + +func NewIHostService() IHostService { + return &HostService{} +} + +func (u *HostService) TestByInfo(req dto.HostConnTest) bool { + if req.AuthMode == "password" && len(req.Password) != 0 { + password, err := base64.StdEncoding.DecodeString(req.Password) + if err != nil { + return false + } + req.Password = string(password) + } + if req.AuthMode == "key" && len(req.PrivateKey) != 0 { + privateKey, err := base64.StdEncoding.DecodeString(req.PrivateKey) + if err != nil { + return false + } + req.PrivateKey = string(privateKey) + } + if len(req.Password) == 0 && len(req.PrivateKey) == 0 { + host, err := hostRepo.Get(repo.WithByAddr(req.Addr), hostRepo.WithByPort(req.Port)) + if err != nil { + return false + } + req.Password = host.Password + req.AuthMode = host.AuthMode + req.PrivateKey = host.PrivateKey + req.PassPhrase = host.PassPhrase + } + + var connInfo ssh.ConnInfo + _ = copier.Copy(&connInfo, &req) + connInfo.PrivateKey = []byte(req.PrivateKey) + if len(req.PassPhrase) != 0 { + connInfo.PassPhrase = []byte(req.PassPhrase) + } + client, err := ssh.NewClient(connInfo) + if err != nil { + return false + } + defer client.Close() + return true +} + +func (u *HostService) TestLocalConn(id uint) bool { + var ( + host model.Host + err error + ) + host, err = hostRepo.Get(repo.WithByID(id)) + if err != nil { + return false + } + var connInfo ssh.ConnInfo + if err := copier.Copy(&connInfo, &host); err != nil { + return false + } + if len(host.Password) != 0 { + host.Password, err = encrypt.StringDecrypt(host.Password) + if err != nil { + return false + } + connInfo.Password = host.Password + } + if len(host.PrivateKey) != 0 { + host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey) + if err != nil { + return false + } + connInfo.PrivateKey = []byte(host.PrivateKey) + } + if len(host.PassPhrase) != 0 { + host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase) + if err != nil { + return false + } + connInfo.PassPhrase = []byte(host.PassPhrase) + } + client, err := ssh.NewClient(connInfo) + if err != nil { + return false + } + defer client.Close() + + return true +} + +func (u *HostService) SearchWithPage(req dto.SearchPageWithGroup) (int64, interface{}, error) { + var options []global.DBOption + if len(req.Info) != 0 { + options = append(options, hostRepo.WithByInfo(req.Info)) + } + if req.GroupID != 0 { + options = append(options, repo.WithByGroupID(req.GroupID)) + } + total, hosts, err := hostRepo.Page(req.Page, req.PageSize, options...) + if err != nil { + return 0, nil, err + } + var dtoHosts []dto.HostInfo + for _, host := range hosts { + var item dto.HostInfo + if err := copier.Copy(&item, &host); err != nil { + return 0, nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + group, _ := groupRepo.Get(repo.WithByID(host.GroupID)) + item.GroupBelong = group.Name + if !item.RememberPassword { + item.Password = "" + item.PrivateKey = "" + item.PassPhrase = "" + } else { + if len(host.Password) != 0 { + item.Password, err = encrypt.StringDecrypt(host.Password) + if err != nil { + return 0, nil, err + } + } + if len(host.PrivateKey) != 0 { + item.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey) + if err != nil { + return 0, nil, err + } + } + if len(host.PassPhrase) != 0 { + item.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase) + if err != nil { + return 0, nil, err + } + } + } + dtoHosts = append(dtoHosts, item) + } + return total, dtoHosts, err +} + +func (u *HostService) SearchForTree(search dto.SearchForTree) ([]dto.HostTree, error) { + hosts, err := hostRepo.GetList(hostRepo.WithByInfo(search.Info)) + if err != nil { + return nil, err + } + groups, err := groupRepo.GetList(repo.WithByType("host")) + if err != nil { + return nil, err + } + var datas []dto.HostTree + for _, group := range groups { + var data dto.HostTree + data.ID = group.ID + 10000 + data.Label = group.Name + for _, host := range hosts { + label := fmt.Sprintf("%s@%s:%d", host.User, host.Addr, host.Port) + if len(host.Name) != 0 { + label = fmt.Sprintf("%s - %s@%s:%d", host.Name, host.User, host.Addr, host.Port) + } + if host.GroupID == group.ID { + data.Children = append(data.Children, dto.TreeChild{ID: host.ID, Label: label}) + } + } + if len(data.Children) != 0 { + datas = append(datas, data) + } + } + return datas, err +} + +func (u *HostService) GetHostByID(id uint) (*dto.HostInfo, error) { + var item dto.HostInfo + var host model.Host + host, _ = hostRepo.Get(repo.WithByID(id)) + if host.ID == 0 { + return nil, buserr.New("ErrRecordNotFound") + } + if err := copier.Copy(&item, &host); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if !item.RememberPassword { + item.Password = "" + item.PrivateKey = "" + item.PassPhrase = "" + return &item, nil + } + var err error + if len(host.Password) != 0 { + item.Password, err = encrypt.StringDecrypt(host.Password) + if err != nil { + return nil, err + } + } + if len(host.PrivateKey) != 0 { + item.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey) + if err != nil { + return nil, err + } + } + if len(host.PassPhrase) != 0 { + item.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase) + if err != nil { + return nil, err + } + } + return &item, err +} + +func (u *HostService) Create(req dto.HostOperate) (*dto.HostInfo, error) { + hostItem, _ := hostRepo.Get(repo.WithByAddr(req.Addr), hostRepo.WithByUser(req.User), hostRepo.WithByPort(req.Port)) + if hostItem.ID != 0 { + return nil, buserr.New("ErrRecordExist") + } + + var err error + if len(req.Password) != 0 && req.AuthMode == "password" { + req.Password, err = u.EncryptHost(req.Password) + if err != nil { + return nil, err + } + req.PrivateKey = "" + req.PassPhrase = "" + } + if len(req.PrivateKey) != 0 && req.AuthMode == "key" { + req.PrivateKey, err = u.EncryptHost(req.PrivateKey) + if err != nil { + return nil, err + } + if len(req.PassPhrase) != 0 { + req.PassPhrase, err = encrypt.StringEncrypt(req.PassPhrase) + if err != nil { + return nil, err + } + } + req.Password = "" + } + var host model.Host + if err := copier.Copy(&host, &req); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if req.GroupID == 0 { + group, err := groupRepo.Get(repo.WithByType("host"), groupRepo.WithByDefault(true)) + if err != nil { + return nil, errors.New("get default group failed") + } + host.GroupID = group.ID + req.GroupID = group.ID + } + + if err := hostRepo.Create(&host); err != nil { + return nil, err + } + var hostinfo dto.HostInfo + if err := copier.Copy(&hostinfo, &host); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + return &hostinfo, nil +} + +func (u *HostService) Delete(ids []uint) error { + for _, id := range ids { + host, _ := hostRepo.Get(repo.WithByID(id)) + if host.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + if err := hostRepo.Delete(repo.WithByID(id)); err != nil { + return err + } + } + return nil +} + +func (u *HostService) Update(id uint, upMap map[string]interface{}) (*dto.HostInfo, error) { + if err := hostRepo.Update(id, upMap); err != nil { + return nil, err + } + hostItem, _ := hostRepo.Get(repo.WithByID(id)) + var hostinfo dto.HostInfo + if err := copier.Copy(&hostinfo, &hostItem); err != nil { + return nil, buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + return &hostinfo, nil +} + +func (u *HostService) EncryptHost(itemVal string) (string, error) { + privateKey, err := base64.StdEncoding.DecodeString(itemVal) + if err != nil { + return "", err + } + keyItem, err := encrypt.StringEncrypt(string(privateKey)) + return keyItem, err +} + +func GetHostInfo(id uint) (*model.Host, error) { + host, err := hostRepo.Get(repo.WithByID(id)) + if err != nil { + return nil, buserr.New("ErrRecordNotFound") + } + if len(host.Password) != 0 { + host.Password, err = encrypt.StringDecrypt(host.Password) + if err != nil { + return nil, err + } + } + if len(host.PrivateKey) != 0 { + host.PrivateKey, err = encrypt.StringDecrypt(host.PrivateKey) + if err != nil { + return nil, err + } + } + + if len(host.PassPhrase) != 0 { + host.PassPhrase, err = encrypt.StringDecrypt(host.PassPhrase) + if err != nil { + return nil, err + } + } + return &host, err +} diff --git a/core/app/service/logs.go b/core/app/service/logs.go new file mode 100644 index 0000000..c390bca --- /dev/null +++ b/core/app/service/logs.go @@ -0,0 +1,113 @@ +package service + +import ( + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/utils/common" + geo2 "github.com/1Panel-dev/1Panel/core/utils/geo" + "github.com/gin-gonic/gin" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/jinzhu/copier" +) + +type LogService struct{} + +const logs = "https://resource.fit2cloud.com/installation-log.sh" + +type ILogService interface { + CreateLoginLog(operation model.LoginLog) error + PageLoginLog(ctx *gin.Context, search dto.SearchLgLogWithPage) (int64, interface{}, error) + + CreateOperationLog(operation *model.OperationLog) error + PageOperationLog(search dto.SearchOpLogWithPage) (int64, interface{}, error) + + CleanLogs(logtype string) error +} + +func NewILogService() ILogService { + return &LogService{} +} + +func (u *LogService) CreateLoginLog(operation model.LoginLog) error { + return logRepo.CreateLoginLog(&operation) +} + +func (u *LogService) PageLoginLog(ctx *gin.Context, req dto.SearchLgLogWithPage) (int64, interface{}, error) { + options := []global.DBOption{ + repo.WithOrderBy("created_at desc"), + } + if len(req.IP) != 0 { + options = append(options, logRepo.WithByIP(req.IP)) + } + if len(req.Status) != 0 { + options = append(options, repo.WithByStatus(req.Status)) + } + total, ops, err := logRepo.PageLoginLog( + req.Page, + req.PageSize, + options..., + ) + var dtoOps []dto.LoginLog + geoDB, _ := geo2.NewGeo() + for _, op := range ops { + var item dto.LoginLog + if err := copier.Copy(&item, &op); err != nil { + return 0, nil, buserr.WithErr("ErrTransform", err) + } + if geoDB != nil { + item.Address, _ = geo2.GetIPLocation(geoDB, item.IP, common.GetLang(ctx)) + } + dtoOps = append(dtoOps, item) + } + return total, dtoOps, err +} + +func (u *LogService) CreateOperationLog(operation *model.OperationLog) error { + return logRepo.CreateOperationLog(operation) +} + +func (u *LogService) PageOperationLog(req dto.SearchOpLogWithPage) (int64, interface{}, error) { + options := []global.DBOption{ + repo.WithOrderBy("created_at desc"), + logRepo.WithByLikeOperation(req.Operation), + } + if len(req.Source) != 0 { + options = append(options, logRepo.WithBySource(req.Source)) + } + if len(req.Status) != 0 { + options = append(options, repo.WithByStatus(req.Status)) + } + if len(req.Node) != 0 { + options = append(options, repo.WithByNode(req.Node)) + } + + total, ops, err := logRepo.PageOperationLog( + req.Page, + req.PageSize, + options..., + ) + var dtoOps []dto.OperationLog + for _, op := range ops { + var item dto.OperationLog + if err := copier.Copy(&item, &op); err != nil { + return 0, nil, buserr.WithErr("ErrTransform", err) + } + dtoOps = append(dtoOps, item) + } + return total, dtoOps, err +} + +func (u *LogService) CleanLogs(logtype string) error { + if logtype == "operation" { + return logRepo.CleanOperation() + } + return logRepo.CleanLogin() +} + +func writeLogs(version string) { + _, _ = cmd.RunDefaultWithStdoutBashCf("curl -sfL %s | sh -s 1p upgrade %s", logs, version) +} diff --git a/core/app/service/script_library.go b/core/app/service/script_library.go new file mode 100644 index 0000000..1ae2460 --- /dev/null +++ b/core/app/service/script_library.go @@ -0,0 +1,295 @@ +package service + +import ( + "encoding/json" + "fmt" + mathRand "math/rand" + "net/http" + "os" + "path" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/app/task" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/files" + "github.com/1Panel-dev/1Panel/core/utils/req_helper" + "github.com/1Panel-dev/1Panel/core/utils/xpack" + "github.com/gin-gonic/gin" + "github.com/jinzhu/copier" + "gopkg.in/yaml.v2" +) + +type ScriptService struct{} + +type IScriptService interface { + Run() + Search(ctx *gin.Context, req dto.SearchPageWithGroup) (int64, interface{}, error) + Create(req dto.ScriptOperate) error + Update(req dto.ScriptOperate) error + Delete(ids dto.OperateByIDs) error + Sync(req dto.OperateByTaskID) error +} + +func NewIScriptService() IScriptService { + return &ScriptService{} +} + +func (u *ScriptService) Search(ctx *gin.Context, req dto.SearchPageWithGroup) (int64, interface{}, error) { + options := []global.DBOption{repo.WithOrderBy("created_at desc")} + if len(req.Info) != 0 { + options = append(options, scriptRepo.WithByInfo(req.Info)) + } + list, err := scriptRepo.GetList(options...) + if err != nil { + return 0, nil, err + } + groups, _ := groupRepo.GetList(repo.WithByType("script")) + groupMap := make(map[uint]string) + for _, item := range groups { + groupMap[item.ID] = item.Name + } + var data []dto.ScriptInfo + for _, itemData := range list { + var item dto.ScriptInfo + if err := copier.Copy(&item, &itemData); err != nil { + global.LOG.Errorf("copy scripts to dto backup info failed, err: %v", err) + } + if item.IsSystem { + lang := strings.ToLower(common.GetLang(ctx)) + var nameMap = make(map[string]string) + _ = json.Unmarshal([]byte(item.Name), &nameMap) + var descriptionMap = make(map[string]string) + _ = json.Unmarshal([]byte(item.Description), &descriptionMap) + if val, ok := nameMap[lang]; ok { + item.Name = val + } + if val, ok := descriptionMap[lang]; ok { + item.Description = val + } + } + matchGroup := false + groupIDs := strings.Split(itemData.Groups, ",") + for _, idItem := range groupIDs { + id, _ := strconv.Atoi(idItem) + if id == 0 { + continue + } + if uint(id) == req.GroupID { + matchGroup = true + } + item.GroupList = append(item.GroupList, uint(id)) + item.GroupBelong = append(item.GroupBelong, groupMap[uint(id)]) + } + if req.GroupID == 0 { + data = append(data, item) + continue + } + if matchGroup { + data = append(data, item) + } + } + var records []dto.ScriptInfo + total, start, end := len(data), (req.Page-1)*req.PageSize, req.Page*req.PageSize + if start > total { + records = make([]dto.ScriptInfo, 0) + } else { + if end >= total { + end = total + } + records = data[start:end] + } + return int64(total), records, nil +} + +func (u *ScriptService) Create(req dto.ScriptOperate) error { + itemData, _ := scriptRepo.Get(repo.WithByName(req.Name)) + if itemData.ID != 0 { + return buserr.New("ErrRecordExist") + } + if err := copier.Copy(&itemData, &req); err != nil { + return buserr.WithDetail("ErrStructTransform", err.Error(), nil) + } + if err := scriptRepo.Create(&itemData); err != nil { + return err + } + if req.IsInteractive { + return nil + } + if err := xpack.Sync(constant.SyncScripts); err != nil { + global.LOG.Errorf("sync scripts to node failed, err: %v", err) + } + return nil +} + +func (u *ScriptService) Delete(req dto.OperateByIDs) error { + for _, item := range req.IDs { + scriptItem, _ := scriptRepo.Get(repo.WithByID(item)) + if scriptItem.ID == 0 || scriptItem.IsSystem { + continue + } + if err := scriptRepo.Delete(repo.WithByID(item)); err != nil { + return err + } + } + if err := xpack.Sync(constant.SyncScripts); err != nil { + global.LOG.Errorf("sync scripts to node failed, err: %v", err) + } + return nil +} + +func (u *ScriptService) Update(req dto.ScriptOperate) error { + itemData, _ := scriptRepo.Get(repo.WithByID(req.ID)) + if itemData.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + updateMap := make(map[string]interface{}) + updateMap["name"] = req.Name + updateMap["script"] = req.Script + updateMap["groups"] = req.Groups + updateMap["is_interactive"] = req.IsInteractive + updateMap["description"] = req.Description + if err := scriptRepo.Update(req.ID, updateMap); err != nil { + return err + } + if err := xpack.Sync(constant.SyncScripts); err != nil { + global.LOG.Errorf("sync scripts to node failed, err: %v", err) + } + return nil +} + +func StartSync() { + if global.ScriptSyncJobID != 0 { + global.Cron.Remove(global.ScriptSyncJobID) + } + service := NewIScriptService() + scriptSync, _ := repo.NewISettingRepo().GetValueByKey("ScriptSync") + if !global.CONF.Base.IsOffLine && scriptSync == constant.StatusEnable { + id, err := global.Cron.AddJob(fmt.Sprintf("%v %v * * *", mathRand.Intn(60), mathRand.Intn(3)), service) + if err != nil { + global.LOG.Errorf("[core] can not add script sync corn job: %s", err.Error()) + } + global.LOG.Info("add job for script library sync successful") + global.ScriptSyncJobID = id + } +} +func (u *ScriptService) Run() { + if err := u.Sync(dto.OperateByTaskID{}); err != nil { + global.LOG.Errorf("sync scripts from remote failed, err: %v", err) + } +} + +func LoadScriptInfo(id uint) (model.ScriptLibrary, error) { + return scriptRepo.Get(repo.WithByID(id)) +} + +func (u *ScriptService) Sync(req dto.OperateByTaskID) error { + if global.CONF.Base.IsOffLine { + return nil + } + syncTask, err := task.NewTaskWithOps(i18n.GetMsgByKey("RemoteScriptLibrary"), task.TaskSync, task.TaskScopeScript, req.TaskID, 0) + if err != nil { + global.LOG.Errorf("create sync task failed %v", err) + return err + } + + syncTask.AddSubTask(task.GetTaskName(i18n.GetMsgByKey("RemoteScriptLibrary"), task.TaskSync, task.TaskScopeScript), func(t *task.Task) (err error) { + versionUrl := fmt.Sprintf("%s/scripts/version.txt", global.CONF.RemoteURL.ResourceURL) + _, versionRes, err := req_helper.HandleRequestWithProxy(versionUrl, http.MethodGet, constant.TimeOut20s) + if err != nil { + return fmt.Errorf("load scripts version from remote failed, err: %v", err) + } + var scriptSetting model.Setting + _ = global.DB.Where("key = ?", "ScriptVersion").First(&scriptSetting).Error + localVersion := strings.ReplaceAll(string(versionRes), "\n", "") + remoteVersion := strings.ReplaceAll(scriptSetting.Value, "\n", "") + + if localVersion == remoteVersion { + syncTask.Log(i18n.GetMsgByKey("ScriptSyncSkip")) + return nil + } + + dataUrl := fmt.Sprintf("%s/scripts/data.yaml", global.CONF.RemoteURL.ResourceURL) + _, dataRes, err := req_helper.HandleRequestWithProxy(dataUrl, http.MethodGet, constant.TimeOut20s) + syncTask.LogWithStatus(i18n.GetMsgByKey("DownloadData"), err) + if err != nil { + return fmt.Errorf("load scripts data.yaml from remote failed, err: %v", err) + } + + var scripts Scripts + if err = yaml.Unmarshal(dataRes, &scripts); err != nil { + return fmt.Errorf("the format of data.yaml is err: %v", err) + } + + tmpDir := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/script") + if _, err := os.Stat(tmpDir); err != nil { + _ = os.MkdirAll(tmpDir, 0755) + } + scriptsUrl := fmt.Sprintf("%s/scripts/scripts.tar.gz", global.CONF.RemoteURL.ResourceURL) + err = files.DownloadFileWithProxy(scriptsUrl, tmpDir+"/scripts.tar.gz") + syncTask.LogWithStatus(i18n.GetMsgByKey("DownloadPackage"), err) + if err != nil { + return fmt.Errorf("download scripts.tar.gz failed, err: %v", err) + } + + if err := files.HandleUnTar(tmpDir+"/scripts.tar.gz", tmpDir, ""); err != nil { + return fmt.Errorf("handle decompress scripts.tar.gz failed, err: %v", err) + } + var scriptsForDB []model.ScriptLibrary + for _, item := range scripts.Scripts.Sh { + itemName, _ := json.Marshal(item.Name) + itemDescription, _ := json.Marshal(item.Description) + shell, _ := os.ReadFile(fmt.Sprintf("%s/scripts/sh/%s.sh", tmpDir, item.Key)) + scriptItem := model.ScriptLibrary{ + Name: string(itemName), + IsInteractive: item.Interactive, + IsSystem: true, + Script: string(shell), + Description: string(itemDescription), + } + scriptsForDB = append(scriptsForDB, scriptItem) + } + + syncTask.Log(i18n.GetMsgByKey("AnalyticCompletion")) + if err := scriptRepo.SyncAll(scriptsForDB); err != nil { + return fmt.Errorf("sync script with db failed, err: %v", err) + } + _ = os.RemoveAll(tmpDir) + if err := global.DB.Model(&model.Setting{}).Where("key = ?", "ScriptVersion").Updates(map[string]interface{}{"value": string(versionRes)}).Error; err != nil { + return fmt.Errorf("update script version in db failed, err: %v", err) + } + if err := xpack.Sync(constant.SyncScripts); err != nil { + global.LOG.Errorf("sync scripts to node failed, err: %v", err) + } + return nil + }, nil) + + if err := syncTask.Execute(); err != nil { + return fmt.Errorf("sync scripts from remote failed, err: %v", err) + } + return nil +} + +type Scripts struct { + Scripts ScriptDetail `json:"scripts"` +} + +type ScriptDetail struct { + Sh []ScriptHelper `json:"sh"` +} + +type ScriptHelper struct { + Key string `json:"key"` + Sort uint `json:"sort"` + Groups string `json:"groups"` + Name map[string]string `json:"name"` + Interactive bool `json:"interactive"` + Description map[string]string `json:"description"` +} diff --git a/core/app/service/setting.go b/core/app/service/setting.go new file mode 100644 index 0000000..a418d42 --- /dev/null +++ b/core/app/service/setting.go @@ -0,0 +1,783 @@ +package service + +import ( + "bytes" + "context" + "crypto/rand" + "crypto/rsa" + "crypto/tls" + "crypto/x509" + "encoding/json" + "encoding/pem" + "fmt" + "io" + "net" + "net/http" + "net/url" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/controller" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/1Panel-dev/1Panel/core/utils/firewall" + "github.com/1Panel-dev/1Panel/core/utils/passkey" + "github.com/1Panel-dev/1Panel/core/utils/req_helper/proxy_local" + "github.com/1Panel-dev/1Panel/core/utils/xpack" + "github.com/gin-gonic/gin" + "golang.org/x/net/proxy" +) + +type SettingService struct{} + +type ISettingService interface { + GetSettingInfo() (*dto.SettingInfo, error) + LoadInterfaceAddr() ([]string, error) + Update(key, value string) error + UpdatePassword(c *gin.Context, old, new string) error + UpdatePort(port uint) error + UpdateBindInfo(req dto.BindInfo) error + UpdateSSL(c *gin.Context, req dto.SSLUpdate) error + LoadFromCert() (*dto.SSLInfo, error) + HandlePasswordExpired(c *gin.Context, old, new string) error + GenerateApiKey() (string, error) + UpdateApiConfig(req dto.ApiInterfaceConfig) error + + UpdateProxy(req dto.ProxyUpdate) error + + GetTerminalInfo() (*dto.TerminalInfo, error) + UpdateTerminal(req dto.TerminalInfo) error + + UpdateSystemSSL() error + GenerateRSAKey() error + + GetLoginSetting() (*dto.SystemSetting, error) + + UpdateAppstoreConfig(req dto.AppstoreUpdate) error + GetAppstoreConfig() (*dto.AppstoreConfig, error) + DefaultMenu() error +} + +func NewISettingService() ISettingService { + return &SettingService{} +} + +func (u *SettingService) GetSettingInfo() (*dto.SettingInfo, error) { + setting, err := settingRepo.List() + if err != nil { + return nil, buserr.New("ErrRecordNotFound") + } + settingMap := make(map[string]string) + for _, set := range setting { + settingMap[set.Key] = set.Value + } + var info dto.SettingInfo + arr, err := json.Marshal(settingMap) + if err != nil { + return nil, err + } + if err := json.Unmarshal(arr, &info); err != nil { + return nil, err + } + if info.ProxyPasswdKeep != constant.StatusEnable { + info.ProxyPasswd = "" + } else { + info.ProxyPasswd, _ = encrypt.StringDecrypt(info.ProxyPasswd) + } + + return &info, err +} + +func (u *SettingService) Update(key, value string) error { + oldVal, err := settingRepo.Get(repo.WithByKey(key)) + if err != nil { + return err + } + if oldVal.Value == value { + return nil + } + switch key { + case "AppStoreLastModified": + exist, _ := settingRepo.Get(repo.WithByKey("AppStoreLastModified")) + if exist.ID == 0 { + _ = settingRepo.Create("AppStoreLastModified", value) + return nil + } + case "HideMenu": + var menus []dto.ShowMenu + if err := json.Unmarshal([]byte(value), &menus); err != nil { + return err + } + for i := 0; i < len(menus); i++ { + if menus[i].Label == "Home-Menu" || menus[i].Label == "App-Menu" || menus[i].Label == "Setting-Menu" { + menus[i].IsShow = true + } + } + menuItem, err := json.Marshal(&menus) + if err != nil { + return err + } + value = string(menuItem) + } + + if err := settingRepo.Update(key, value); err != nil { + return err + } + + switch key { + case "ExpirationDays": + timeout, err := strconv.Atoi(value) + if err != nil { + return err + } + if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format(constant.DateTimeLayout)); err != nil { + return err + } + case "BindDomain": + if len(value) != 0 { + _ = global.SESSION.Clean() + } + if err := u.clearPasskeySettings(); err != nil { + return err + } + case "UserName", "Password": + _ = global.SESSION.Clean() + case "Language": + i18n.SetCachedDBLanguage(value) + if err := xpack.Sync(constant.SyncLanguage); err != nil { + global.LOG.Errorf("sync language to node failed, err: %v", err) + } + case "UpgradeBackupCopies": + dropBackupCopies() + case "ScriptSync": + if value == constant.StatusEnable { + StartSync() + } else { + global.Cron.Remove(global.ScriptSyncJobID) + } + } + + return nil +} + +func (u *SettingService) LoadInterfaceAddr() ([]string, error) { + addrMap := make(map[string]struct{}) + addrs, err := net.InterfaceAddrs() + if err != nil { + return nil, err + } + for _, addr := range addrs { + ipNet, ok := addr.(*net.IPNet) + if ok && ipNet.IP.To16() != nil { + addrMap[ipNet.IP.String()] = struct{}{} + } + } + var data []string + for key := range addrMap { + data = append(data, key) + } + return data, nil +} + +func (u *SettingService) UpdateBindInfo(req dto.BindInfo) error { + if err := settingRepo.Update("Ipv6", req.Ipv6); err != nil { + return err + } + if err := settingRepo.Update("BindAddress", req.BindAddress); err != nil { + return err + } + go func() { + time.Sleep(1 * time.Second) + controller.RestartPanel(true, false, false) + }() + return nil +} + +func (u *SettingService) UpdateProxy(req dto.ProxyUpdate) error { + if req.ProxyType == "https" || req.ProxyType == "http" { + req.ProxyUrl = req.ProxyType + "://" + req.ProxyUrl + } + if err := checkProxy(req); err != nil { + return err + } + if err := settingRepo.Update("ProxyUrl", req.ProxyUrl); err != nil { + return err + } + if err := settingRepo.Update("ProxyType", req.ProxyType); err != nil { + return err + } + if err := settingRepo.Update("ProxyPort", req.ProxyPort); err != nil { + return err + } + if err := settingRepo.Update("ProxyUser", req.ProxyUser); err != nil { + return err + } + pass, _ := encrypt.StringEncrypt(req.ProxyPasswd) + if err := settingRepo.Update("ProxyPasswd", pass); err != nil { + return err + } + if err := settingRepo.Update("ProxyPasswdKeep", req.ProxyPasswdKeep); err != nil { + return err + } + if err := xpack.ProxyDocker(loadDockerProxy(req)); err != nil { + return err + } + syncScope := constant.SyncSystemProxy + if req.WithDockerRestart { + syncScope = constant.SyncSystemProxyWithRestartDocker + } + if err := xpack.Sync(syncScope); err != nil { + global.LOG.Errorf("sync proxy to node failed, err: %v", err) + } + return nil +} + +func (u *SettingService) UpdatePort(port uint) error { + if common.ScanPort(int(port)) { + return buserr.WithDetail("ErrPortInUsed", port, nil) + } + oldPort, err := settingRepo.Get(repo.WithByKey("ServerPort")) + if err != nil { + return err + } + if oldPort.Value == fmt.Sprintf("%v", port) { + return nil + } + if err := firewall.UpdatePort(oldPort.Value, fmt.Sprintf("%v", port)); err != nil { + return err + } + + if err := settingRepo.Update("ServerPort", strconv.Itoa(int(port))); err != nil { + return err + } + if err := u.clearPasskeySettings(); err != nil { + return err + } + go func() { + time.Sleep(1 * time.Second) + controller.RestartPanel(true, false, false) + }() + return nil +} + +func (u *SettingService) UpdateSSL(c *gin.Context, req dto.SSLUpdate) error { + secretDir := path.Join(global.CONF.Base.InstallDir, "1panel/secret") + if req.SSL == constant.StatusDisable { + c.SetCookie(constant.SessionName, "", -1, "/", "", false, true) + if err := settingRepo.Update("SSL", constant.StatusDisable); err != nil { + return err + } + if err := settingRepo.Update("SSLType", "self"); err != nil { + return err + } + if err := u.clearPasskeySettings(); err != nil { + return err + } + _ = os.Remove(path.Join(secretDir, "server.crt")) + _ = os.Remove(path.Join(secretDir, "server.key")) + go func() { + time.Sleep(1 * time.Second) + controller.RestartPanel(true, false, false) + }() + return nil + } + if _, err := os.Stat(secretDir); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(secretDir, os.ModePerm); err != nil { + return err + } + } + if err := settingRepo.Update("SSLType", req.SSLType); err != nil { + return err + } + var ( + secret string + key string + ) + + switch req.SSLType { + case "import-paste": + secret = req.Cert + key = req.Key + case "import-local": + keyFile, err := os.ReadFile(req.Key) + if err != nil { + return err + } + key = string(keyFile) + certFile, err := os.ReadFile(req.Cert) + if err != nil { + return err + } + secret = string(certFile) + case "select": + ssl, err := agentRepo.GetWebsiteSSL(repo.WithByID(req.SSLID)) + if err != nil { + return err + } + secret = ssl.Pem + key = ssl.PrivateKey + if err := settingRepo.Update("SSLID", strconv.Itoa(int(req.SSLID))); err != nil { + return err + } + case "self": + ca, err := agentRepo.GetCA(repo.WithByName("1Panel")) + if err != nil { + return err + } + params := make(map[string]interface{}) + params["domains"] = req.Domain + params["time"] = 10 + params["unit"] = "year" + params["keyType"] = "P256" + params["id"] = ca.ID + jsonData, err := json.Marshal(params) + if err != nil { + return err + } + res, err := proxy_local.NewLocalClient("/api/v2/websites/ca/obtain", http.MethodPost, bytes.NewReader(jsonData), nil) + if err != nil { + return err + } + jsonBytes, err := json.Marshal(res) + if err != nil { + return err + } + var ssl model.WebsiteSSL + if err := json.Unmarshal(jsonBytes, &ssl); err != nil { + return err + } + secret = ssl.Pem + key = ssl.PrivateKey + if err := settingRepo.Update("SSLID", strconv.Itoa(int(ssl.ID))); err != nil { + return err + } + } + + if err := os.WriteFile(path.Join(secretDir, "server.crt.tmp"), []byte(secret), 0600); err != nil { + return err + } + if err := os.WriteFile(path.Join(secretDir, "server.key.tmp"), []byte(key), 0600); err != nil { + return err + } + if err := checkCertValid(); err != nil { + return err + } + if err := os.Rename(path.Join(secretDir, "server.crt.tmp"), path.Join(secretDir, "server.crt")); err != nil { + return err + } + if err := os.Rename(path.Join(secretDir, "server.key.tmp"), path.Join(secretDir, "server.key")); err != nil { + return err + } + status, _ := settingRepo.GetValueByKey("SSL") + if req.SSL != status { + go func() { + time.Sleep(1 * time.Second) + controller.RestartPanel(true, false, false) + }() + } + if err := settingRepo.Update("SSL", req.SSL); err != nil { + return err + } + if err := u.clearPasskeySettings(); err != nil { + return err + } + return u.UpdateSystemSSL() +} + +func (u *SettingService) LoadFromCert() (*dto.SSLInfo, error) { + ssl, err := settingRepo.Get(repo.WithByKey("SSL")) + if err != nil { + return nil, err + } + if ssl.Value == constant.StatusDisable { + return &dto.SSLInfo{}, nil + } + sslType, err := settingRepo.Get(repo.WithByKey("SSLType")) + if err != nil { + return nil, err + } + var data dto.SSLInfo + switch sslType.Value { + case "self": + data, err = loadInfoFromCert() + if err != nil { + return nil, err + } + case "import-paste", "import-local": + data, err = loadInfoFromCert() + if err != nil { + return nil, err + } + if _, err := os.Stat(path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt")); err != nil { + return nil, fmt.Errorf("load server.crt file failed, err: %v", err) + } + certFile, _ := os.ReadFile(path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt")) + data.Cert = string(certFile) + + if _, err := os.Stat(path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.key")); err != nil { + return nil, fmt.Errorf("load server.key file failed, err: %v", err) + } + keyFile, _ := os.ReadFile(path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.key")) + data.Key = string(keyFile) + case "select": + sslID, err := settingRepo.Get(repo.WithByKey("SSLID")) + if err != nil { + return nil, err + } + id, _ := strconv.Atoi(sslID.Value) + ssl, err := agentRepo.GetWebsiteSSL(repo.WithByID(uint(id))) + if err != nil { + return nil, err + } + data.Domain = ssl.PrimaryDomain + data.SSLID = uint(id) + data.Timeout = ssl.ExpireDate.Format(constant.DateTimeLayout) + } + return &data, nil +} + +func (u *SettingService) HandlePasswordExpired(c *gin.Context, old, new string) error { + setting, err := settingRepo.Get(repo.WithByKey("Password")) + if err != nil { + return err + } + passwordFromDB, err := encrypt.StringDecrypt(setting.Value) + if err != nil { + return err + } + if passwordFromDB == old { + newPassword, err := encrypt.StringEncrypt(new) + if err != nil { + return err + } + if err := settingRepo.Update("Password", newPassword); err != nil { + return err + } + + expiredSetting, err := settingRepo.Get(repo.WithByKey("ExpirationDays")) + if err != nil { + return err + } + timeout, _ := strconv.Atoi(expiredSetting.Value) + if err := settingRepo.Update("ExpirationTime", time.Now().AddDate(0, 0, timeout).Format(constant.DateTimeLayout)); err != nil { + return err + } + return nil + } + return buserr.New("ErrInitialPassword") +} + +func (u *SettingService) GetTerminalInfo() (*dto.TerminalInfo, error) { + setting, err := settingRepo.List() + if err != nil { + return nil, buserr.New("ErrRecordNotFound") + } + settingMap := make(map[string]string) + for _, set := range setting { + settingMap[set.Key] = set.Value + } + var info dto.TerminalInfo + arr, err := json.Marshal(settingMap) + if err != nil { + return nil, err + } + if err := json.Unmarshal(arr, &info); err != nil { + return nil, err + } + return &info, err +} +func (u *SettingService) UpdateTerminal(req dto.TerminalInfo) error { + if err := settingRepo.Update("LineHeight", req.LineHeight); err != nil { + return err + } + if err := settingRepo.Update("LetterSpacing", req.LetterSpacing); err != nil { + return err + } + if err := settingRepo.Update("FontSize", req.FontSize); err != nil { + return err + } + if err := settingRepo.Update("CursorBlink", req.CursorBlink); err != nil { + return err + } + if err := settingRepo.Update("CursorStyle", req.CursorStyle); err != nil { + return err + } + if err := settingRepo.Update("Scrollback", req.Scrollback); err != nil { + return err + } + if err := settingRepo.Update("ScrollSensitivity", req.ScrollSensitivity); err != nil { + return err + } + return nil +} + +func (u *SettingService) UpdatePassword(c *gin.Context, old, new string) error { + if err := u.HandlePasswordExpired(c, old, new); err != nil { + return err + } + _ = global.SESSION.Clean() + return nil +} + +func (u *SettingService) clearPasskeySettings() error { + if err := settingRepo.Update(passkey.PasskeyUserIDSettingKey, ""); err != nil { + return err + } + if err := settingRepo.Update(passkey.PasskeyCredentialSettingKey, ""); err != nil { + return err + } + return nil +} + +func (u *SettingService) UpdateSystemSSL() error { + certPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt") + keyPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.key") + certificate, err := os.ReadFile(certPath) + if err != nil { + return err + } + key, err := os.ReadFile(keyPath) + if err != nil { + return err + } + cert, err := tls.X509KeyPair(certificate, key) + if err != nil { + return err + } + constant.CertStore.Store(&cert) + return nil +} + +func (u *SettingService) GenerateApiKey() (string, error) { + apiKey := common.RandStr(32) + if err := settingRepo.Update("ApiKey", apiKey); err != nil { + return global.Api.ApiKey, err + } + global.Api.ApiKey = apiKey + return apiKey, nil +} + +func (u *SettingService) UpdateApiConfig(req dto.ApiInterfaceConfig) error { + if err := settingRepo.UpdateOrCreate("ApiInterfaceStatus", req.ApiInterfaceStatus); err != nil { + return err + } + global.Api.ApiInterfaceStatus = req.ApiInterfaceStatus + if err := settingRepo.UpdateOrCreate("ApiKey", req.ApiKey); err != nil { + return err + } + global.Api.ApiKey = req.ApiKey + if err := settingRepo.UpdateOrCreate("IpWhiteList", req.IpWhiteList); err != nil { + return err + } + global.Api.IpWhiteList = req.IpWhiteList + if err := settingRepo.UpdateOrCreate("ApiKeyValidityTime", req.ApiKeyValidityTime); err != nil { + return err + } + global.Api.ApiKeyValidityTime = req.ApiKeyValidityTime + return nil +} + +func loadInfoFromCert() (dto.SSLInfo, error) { + var info dto.SSLInfo + certFile := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt") + if _, err := os.Stat(certFile); err != nil { + return info, err + } + certData, err := os.ReadFile(certFile) + if err != nil { + return info, err + } + certBlock, _ := pem.Decode(certData) + if certBlock == nil { + return info, err + } + certObj, err := x509.ParseCertificate(certBlock.Bytes) + if err != nil { + return info, err + } + var domains []string + if len(certObj.IPAddresses) != 0 { + for _, ip := range certObj.IPAddresses { + domains = append(domains, ip.String()) + } + } + if len(certObj.DNSNames) != 0 { + domains = append(domains, certObj.DNSNames...) + } + return dto.SSLInfo{ + Domain: strings.Join(domains, ","), + Timeout: certObj.NotAfter.Format(constant.DateTimeLayout), + RootPath: path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt"), + }, nil +} + +func checkCertValid() error { + certificate, err := os.ReadFile(path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt.tmp")) + if err != nil { + return err + } + key, err := os.ReadFile(path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.key.tmp")) + if err != nil { + return err + } + if _, err = tls.X509KeyPair(certificate, key); err != nil { + return err + } + certBlock, _ := pem.Decode(certificate) + if certBlock == nil { + return err + } + if _, err := x509.ParseCertificate(certBlock.Bytes); err != nil { + return err + } + + return nil +} + +func (u *SettingService) GenerateRSAKey() error { + priKey, _ := settingRepo.Get(repo.WithByKey("PASSWORD_PRIVATE_KEY")) + pubKey, _ := settingRepo.Get(repo.WithByKey("PASSWORD_PUBLIC_KEY")) + if priKey.Value != "" && pubKey.Value != "" { + return nil + } + privateKey, err := rsa.GenerateKey(rand.Reader, 2048) + if err != nil { + return err + } + privateKeyPEM := encrypt.ExportPrivateKeyToPEM(privateKey) + publicKeyPEM, err := encrypt.ExportPublicKeyToPEM(&privateKey.PublicKey) + if err != nil { + return err + } + err = settingRepo.UpdateOrCreate("PASSWORD_PRIVATE_KEY", privateKeyPEM) + if err != nil { + return err + } + err = settingRepo.UpdateOrCreate("PASSWORD_PUBLIC_KEY", publicKeyPEM) + if err != nil { + return err + } + return nil +} + +func (u *SettingService) GetLoginSetting() (*dto.SystemSetting, error) { + settingInfo, err := u.GetSettingInfo() + if err != nil { + return nil, err + } + res := &dto.SystemSetting{ + Language: settingInfo.Language, + IsDemo: global.CONF.Base.IsDemo, + IsIntl: global.CONF.Base.IsIntl, + } + return res, nil +} + +func (u *SettingService) UpdateAppstoreConfig(req dto.AppstoreUpdate) error { + return settingRepo.UpdateOrCreate(req.Scope, req.Status) +} + +func (u *SettingService) GetAppstoreConfig() (*dto.AppstoreConfig, error) { + res := &dto.AppstoreConfig{} + res.UninstallDeleteImage, _ = settingRepo.GetValueByKey("UninstallDeleteImage") + if res.UninstallDeleteImage == "" { + res.UninstallDeleteImage = "False" + } + res.UpgradeBackup, _ = settingRepo.GetValueByKey("UpgradeBackup") + if res.UpgradeBackup == "" { + res.UpgradeBackup = "False" + } + res.UninstallDeleteBackup, _ = settingRepo.GetValueByKey("UninstallDeleteBackup") + if res.UninstallDeleteBackup == "" { + res.UninstallDeleteBackup = "False" + } + return res, nil +} + +func loadDockerProxy(req dto.ProxyUpdate) string { + if req.ProxyType == "" || req.ProxyType == "close" || !req.ProxyDocker { + return "" + } + var account string + if req.ProxyUser != "" { + account = req.ProxyUser + if req.ProxyPasswd != "" { + account += ":" + req.ProxyPasswd + } + account += "@" + } + + return fmt.Sprintf("%s://%s%s:%s", req.ProxyType, account, strings.ReplaceAll(req.ProxyUrl, req.ProxyType+"://", ""), req.ProxyPort) +} + +func checkProxy(req dto.ProxyUpdate) error { + var transport http.Transport + proxyItem := fmt.Sprintf("%s:%s", req.ProxyUrl, req.ProxyPort) + switch req.ProxyType { + case "http", "https": + proxyURL, err := url.Parse(proxyItem) + if err != nil { + return buserr.WithErr("ErrProxySetting", fmt.Errorf("parse url %s failed, err: %v", proxyItem, err)) + } + if len(req.ProxyUser) != 0 { + proxyURL.User = url.UserPassword(req.ProxyUser, req.ProxyPasswd) + } + transport = http.Transport{Proxy: http.ProxyURL(proxyURL), TLSClientConfig: &tls.Config{InsecureSkipVerify: true}} + case "socks5": + var auth *proxy.Auth + if len(req.ProxyUser) == 0 { + auth = nil + } else { + auth = &proxy.Auth{User: req.ProxyUser, Password: req.ProxyPasswd} + } + dialer, err := proxy.SOCKS5("tcp", proxyItem, auth, proxy.Direct) + if err != nil { + return buserr.WithErr("ErrProxySetting", fmt.Errorf("new socks5 proxy failed, err: %v", err)) + } + dialContext := func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.Dial(network, addr) + } + transport = http.Transport{DialContext: dialContext} + case "", "close": + default: + return buserr.WithDetail("ErrNotSupportType", req.ProxyType, nil) + } + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("handle request failed, error message: %v", r) + return + } + }() + + client := http.Client{Timeout: 3 * time.Second, Transport: &transport} + ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) + defer cancel() + request, err := http.NewRequestWithContext(ctx, http.MethodGet, "https://1panel.cn/", nil) + if err != nil { + return buserr.WithErr("ErrProxySetting", err) + } + request.Header.Set("Content-Type", "application/json") + resp, err := client.Do(request) + if err != nil { + return buserr.WithErr("ErrProxySetting", err) + } + if _, err := io.ReadAll(resp.Body); err != nil { + return buserr.WithErr("ErrProxySetting", err) + } + defer resp.Body.Close() + return nil +} + +func (u *SettingService) DefaultMenu() error { + return settingRepo.DefaultMenu() +} diff --git a/core/app/service/upgrade.go b/core/app/service/upgrade.go new file mode 100644 index 0000000..db45e6e --- /dev/null +++ b/core/app/service/upgrade.go @@ -0,0 +1,525 @@ +package service + +import ( + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "path" + "sort" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/controller" + "github.com/1Panel-dev/1Panel/core/utils/files" + "github.com/1Panel-dev/1Panel/core/utils/req_helper" + "github.com/1Panel-dev/1Panel/core/utils/xpack" +) + +var ( + svcBasePath, _ = controller.GetServicePath("") + svcCoreName, _ = controller.LoadServiceName("1panel-core") + selCoreName, _ = controller.SelectInitScript("1panel-core") + scriptCoreName, _ = controller.GetScriptName("1panel-core") + svcAgentName, _ = controller.LoadServiceName("1panel-agent") + selAgentName, _ = controller.SelectInitScript("1panel-agent") + scriptAgentName, _ = controller.GetScriptName("1panel-agent") +) + +type UpgradeService struct{} + +type IUpgradeService interface { + Upgrade(req dto.Upgrade) error + Rollback(req dto.OperateByID) error + LoadNotes(req dto.Upgrade) (string, error) + SearchUpgrade() (*dto.UpgradeInfo, error) + LoadRelease() ([]dto.ReleasesNotes, error) +} + +func NewIUpgradeService() IUpgradeService { + return &UpgradeService{} +} + +func (u *UpgradeService) SearchUpgrade() (*dto.UpgradeInfo, error) { + if global.CONF.Base.IsOffLine { + return &dto.UpgradeInfo{}, nil + } + var upgrade dto.UpgradeInfo + currentVersion, err := settingRepo.Get(repo.WithByKey("SystemVersion")) + if err != nil { + return nil, err + } + DeveloperMode, err := settingRepo.Get(repo.WithByKey("DeveloperMode")) + if err != nil { + return nil, err + } + + upgrade.TestVersion, upgrade.NewVersion, upgrade.LatestVersion = u.loadVersionByMode(DeveloperMode.Value, currentVersion.Value) + var itemVersion string + if len(upgrade.NewVersion) != 0 { + itemVersion = upgrade.NewVersion + } + if (global.CONF.Base.Mode == "dev" || DeveloperMode.Value == constant.StatusEnable) && len(upgrade.TestVersion) != 0 { + itemVersion = upgrade.TestVersion + } + if len(upgrade.LatestVersion) != 0 { + itemVersion = upgrade.LatestVersion + } + if len(itemVersion) == 0 { + return &upgrade, nil + } + mode := global.CONF.Base.Mode + if strings.Contains(itemVersion, "beta") { + mode = "beta" + } + if strings.HasPrefix(upgrade.TestVersion, upgrade.LatestVersion+"-beta") { + upgrade.TestVersion = "" + } + notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.RemoteURL.RepoUrl, mode, itemVersion, itemVersion)) + if err != nil { + return nil, fmt.Errorf("load releases-notes of version %s failed, err: %v", itemVersion, err) + } + upgrade.ReleaseNote = notes + return &upgrade, nil +} + +func (u *UpgradeService) LoadNotes(req dto.Upgrade) (string, error) { + mode := global.CONF.Base.Mode + if strings.Contains(req.Version, "beta") { + mode = "beta" + } + notes, err := u.loadReleaseNotes(fmt.Sprintf("%s/%s/%s/release/1panel-%s-release-notes", global.CONF.RemoteURL.RepoUrl, mode, req.Version, req.Version)) + if err != nil { + return "", fmt.Errorf("load releases-notes of version %s failed, err: %v", req.Version, err) + } + return notes, nil +} + +func (u *UpgradeService) Upgrade(req dto.Upgrade) error { + global.LOG.Info("start to upgrade now...") + baseDir := path.Join(global.CONF.Base.InstallDir, fmt.Sprintf("1panel/tmp/upgrade/%s", req.Version)) + downloadDir := path.Join(baseDir, "downloads") + _ = os.RemoveAll(baseDir) + originalDir := path.Join(baseDir, "original") + if err := os.MkdirAll(downloadDir, os.ModePerm); err != nil { + return err + } + if err := os.MkdirAll(originalDir, os.ModePerm); err != nil { + return err + } + itemArch, err := loadArch() + if err != nil { + return err + } + + mode := global.CONF.Base.Mode + if strings.Contains(req.Version, "beta") { + mode = "beta" + } + downloadPath := fmt.Sprintf("%s/%s/%s/release", global.CONF.RemoteURL.RepoUrl, mode, req.Version) + fileName := fmt.Sprintf("1panel-%s-%s-%s.tar.gz", req.Version, "linux", itemArch) + _ = settingRepo.Update("SystemStatus", "Upgrading") + go func() { + oldLang := common.LoadParams("LANGUAGE") + if err := files.DownloadFileWithProxy(downloadPath+"/"+fileName, downloadDir+"/"+fileName); err != nil { + global.LOG.Errorf("download service file failed, err: %v", err) + _ = settingRepo.Update("SystemStatus", "Free") + return + } + global.LOG.Info("download all file successful!") + defer func() { + _ = os.Remove(downloadDir) + }() + if err := files.HandleUnTar(downloadDir+"/"+fileName, downloadDir, ""); err != nil { + global.LOG.Errorf("decompress file failed, err: %v", err) + _ = settingRepo.Update("SystemStatus", "Free") + return + } + tmpDir := downloadDir + "/" + strings.ReplaceAll(fileName, ".tar.gz", "") + + if err := u.handleBackup(originalDir); err != nil { + global.LOG.Errorf("handle backup original file failed, err: %v", err) + _ = settingRepo.Update("SystemStatus", "Free") + return + } + itemLog := model.UpgradeLog{NodeID: 0, OldVersion: global.CONF.Base.Version, NewVersion: req.Version, BackupFile: baseDir} + _ = upgradeLogRepo.Create(&itemLog) + + global.LOG.Info("backup original data successful, now start to upgrade!") + + if err := files.CopyFileWithRename(path.Join(tmpDir, "1panel-core"), "/usr/local/bin/1panel-core"); err != nil { + global.LOG.Errorf("upgrade 1panel-core failed, err: %v", err) + _ = settingRepo.Update("SystemStatus", "Free") + u.handleRollback(originalDir, 1) + return + } + if err := files.CopyFileWithRename(path.Join(tmpDir, "1panel-agent"), "/usr/local/bin/1panel-agent"); err != nil { + global.LOG.Errorf("upgrade 1panel-agent failed, err: %v", err) + _ = settingRepo.Update("SystemStatus", "Free") + u.handleRollback(originalDir, 1) + return + } + + if err := files.CopyItem(false, true, path.Join(tmpDir, "1pctl"), "/usr/local/bin"); err != nil { + global.LOG.Errorf("upgrade 1pctl failed, err: %v", err) + _ = settingRepo.Update("SystemStatus", "Free") + u.handleRollback(originalDir, 2) + return + } + if _, err := cmd.RunDefaultWithStdoutBashCf("sed -i -e 's#BASE_DIR=.*#BASE_DIR=%s#g' /usr/local/bin/1pctl", global.CONF.Base.InstallDir); err != nil { + global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err) + u.handleRollback(originalDir, 2) + return + } + if _, err := cmd.RunDefaultWithStdoutBashCf("sed -i -e 's#LANGUAGE=.*#LANGUAGE=%s#g' /usr/local/bin/1pctl", oldLang); err != nil { + global.LOG.Errorf("upgrade basedir in 1pctl failed, err: %v", err) + u.handleRollback(originalDir, 2) + return + } + initScriptPath := path.Join(tmpDir, "initscript") + + if err := files.CopyItem(false, true, path.Join(initScriptPath, selCoreName), svcBasePath); err != nil { + global.LOG.Errorf("upgrade %s failed, err: %v", svcCoreName, err) + _ = settingRepo.Update("SystemStatus", "Free") + u.handleRollback(originalDir, 3) + return + } + if err := files.CopyItem(false, true, path.Join(initScriptPath, selAgentName), svcBasePath); err != nil { + global.LOG.Errorf("upgrade %s failed, err: %v", svcAgentName, err) + _ = settingRepo.Update("SystemStatus", "Free") + u.handleRollback(originalDir, 3) + return + } + + if err := files.CopyItem(true, true, path.Join(tmpDir, "lang"), "/usr/local/bin"); err != nil { + global.LOG.Errorf("Update language files failed: %v", err) + _ = settingRepo.Update("SystemStatus", "Free") + u.handleRollback(originalDir, 4) + } + if err := files.CopyItem(false, true, path.Join(tmpDir, "GeoIP.mmdb"), path.Join(global.CONF.Base.InstallDir, "1panel/geo")); err != nil { + global.LOG.Warnf("Update GeoIP database failed: %v", err) + _ = settingRepo.Update("SystemStatus", "Free") + u.handleRollback(originalDir, 4) + } + + global.LOG.Info("upgrade successful!") + dropBackupCopies() + xpack.AutoUpgradeWithMaster() + go writeLogs(req.Version) + _ = settingRepo.Update("SystemVersion", req.Version) + _ = global.AgentDB.Model(&model.Setting{}).Where("key = ?", "SystemVersion").Updates(map[string]interface{}{"value": req.Version}).Error + global.CONF.Base.Version = req.Version + _ = os.RemoveAll(downloadDir) + _ = settingRepo.Update("SystemStatus", "Free") + + controller.RestartPanel(true, true, true) + }() + return nil +} + +func (u *UpgradeService) Rollback(req dto.OperateByID) error { + log, _ := upgradeLogRepo.Get(repo.WithByID(req.ID)) + if log.ID == 0 { + return buserr.New("ErrRecordNotFound") + } + u.handleRollback(log.BackupFile, 3) + return nil +} + +type noteHelper struct { + Docs []noteDetailHelper `json:"docs"` +} +type noteDetailHelper struct { + Location string `json:"location"` + Text string `json:"text"` + Title string `json:"title"` +} + +func (u *UpgradeService) LoadRelease() ([]dto.ReleasesNotes, error) { + var notes []dto.ReleasesNotes + resp, err := req_helper.HandleGet("https://1panel.cn/docs/v2/search/search_index.json") + if err != nil { + return notes, err + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return notes, err + } + defer resp.Body.Close() + var nodeItem noteHelper + if err := json.Unmarshal(body, &nodeItem); err != nil { + return notes, err + } + for _, item := range nodeItem.Docs { + if !strings.HasPrefix(item.Location, "changelog/#v") { + continue + } + itemNote := analyzeDoc(item.Title, item.Text) + if len(itemNote.CreatedAt) != 0 { + notes = append(notes, analyzeDoc(item.Title, item.Text)) + } + } + + return notes, nil +} + +func analyzeDoc(version, content string) dto.ReleasesNotes { + var item dto.ReleasesNotes + parts := strings.Split(content, "

") + if len(parts) < 3 { + return item + } + item.CreatedAt = strings.ReplaceAll(strings.TrimSpace(parts[1]), "

", "") + for i := 1; i < len(parts); i++ { + if strings.Contains(parts[i], "问题修复") { + item.FixCount = strings.Count(parts[i], "
  • ") + } + if strings.Contains(parts[i], "新增功能") { + item.NewCount = strings.Count(parts[i], "
  • ") + } + if strings.Contains(parts[i], "功能优化") { + item.OptimizationCount = strings.Count(parts[i], "
  • ") + } + } + item.Content = strings.Replace(content, fmt.Sprintf("

    %s

    ", item.CreatedAt), "", 1) + item.Version = version + return item +} + +func (u *UpgradeService) handleBackup(originalDir string) error { + if err := files.CopyItem(false, true, "/usr/local/bin/1panel-core", originalDir); err != nil { + return err + } + if err := files.CopyItem(false, true, "/usr/local/bin/1panel-agent", originalDir); err != nil { + return err + } + if err := files.CopyItem(false, true, "/usr/local/bin/1pctl", originalDir); err != nil { + return err + } + if err := files.CopyItem(true, true, "/usr/local/bin/lang", originalDir); err != nil { + return err + } + if err := files.CopyItem(false, true, path.Join(svcBasePath, svcCoreName), originalDir); err != nil { + return err + } + if err := files.CopyItem(false, true, path.Join(svcBasePath, svcAgentName), originalDir); err != nil { + return err + } + if err := files.CopyItem(true, true, path.Join(global.CONF.Base.InstallDir, "1panel/db"), originalDir); err != nil { + return err + } + if err := files.CopyItem(false, true, path.Join(global.CONF.Base.InstallDir, "1panel/geo/GeoIP.mmdb"), originalDir); err != nil { + return err + } + return nil +} + +func (u *UpgradeService) handleRollback(originalDir string, errStep int) { + _ = settingRepo.Update("SystemStatus", "Free") + dbPath := path.Join(global.CONF.Base.InstallDir, "1panel") + if _, err := os.Stat(path.Join(originalDir, "db")); err == nil { + if err := files.CopyItem(true, true, path.Join(originalDir, "db"), dbPath); err != nil { + global.LOG.Errorf("rollback 1panel db failed, err: %v", err) + } + } + if err := files.CopyItem(false, true, path.Join(originalDir, "1panel-core"), "/usr/local/bin"); err != nil { + global.LOG.Errorf("rollback 1panel-core failed, err: %v", err) + } + if err := files.CopyItem(false, true, path.Join(originalDir, "1panel-agent"), "/usr/local/bin"); err != nil { + global.LOG.Errorf("rollback 1panel-agent failed, err: %v", err) + } + if errStep == 1 { + return + } + if err := files.CopyItem(false, true, path.Join(originalDir, "1pctl"), "/usr/local/bin"); err != nil { + global.LOG.Errorf("rollback 1pctl failed, err: %v", err) + } + if errStep == 2 { + return + } + if err := files.CopyItem(false, true, path.Join(originalDir, svcCoreName), svcBasePath); err != nil { + global.LOG.Errorf("rollback %s failed, err: %v", svcCoreName, err) + } + if err := files.CopyItem(false, true, path.Join(originalDir, svcAgentName), svcBasePath); err != nil { + global.LOG.Errorf("rollback %s failed, err: %v", svcAgentName, err) + } + if errStep == 3 { + return + } + if err := files.CopyItem(true, true, path.Join(originalDir, "lang"), "/usr/local/bin"); err != nil { + global.LOG.Errorf("rollback language files failed, err: %v", err) + } + if err := files.CopyItem(false, true, path.Join(originalDir, "GeoIP.mmdb"), path.Join(global.CONF.Base.InstallDir, "1panel/geo")); err != nil { + global.LOG.Errorf("rollback GeoIP database failed, err: %v", err) + } +} + +func (u *UpgradeService) loadVersionByMode(developer, currentVersion string) (string, string, string) { + var current, latest string + if global.CONF.Base.Mode == "dev" { + devVersionLatest := u.loadVersion(true, currentVersion, "dev") + return devVersionLatest, "", "" + } + + betaVersionLatest := "" + latest = u.loadVersion(true, currentVersion, "stable") + current = u.loadVersion(false, currentVersion, "stable") + if developer == constant.StatusEnable { + betaVersionLatest = u.loadVersion(true, currentVersion, "beta") + } + if current != latest { + return betaVersionLatest, current, latest + } + + versionPart := strings.Split(current, ".") + if len(versionPart) < 3 { + return betaVersionLatest, "", latest + } + num, _ := strconv.Atoi(versionPart[1]) + if num == 0 { + return betaVersionLatest, "", latest + } + if num >= 10 { + if current[:6] == currentVersion[:6] { + return betaVersionLatest, current, "" + } + return betaVersionLatest, "", latest + } + if current[:5] == currentVersion[:5] { + return betaVersionLatest, "", "" + } + return betaVersionLatest, "", latest +} + +func (u *UpgradeService) loadVersion(isLatest bool, currentVersion, mode string) string { + path := fmt.Sprintf("%s/%s/latest", global.CONF.RemoteURL.RepoUrl, mode) + if !isLatest { + path = fmt.Sprintf("%s/%s/latest.current", global.CONF.RemoteURL.RepoUrl, mode) + } + _, latestVersionRes, err := req_helper.HandleRequestWithProxy(path, http.MethodGet, constant.TimeOut20s) + if err != nil { + global.LOG.Errorf("load latest version from oss failed, err: %v", err) + return "" + } + version := string(latestVersionRes) + if strings.Contains(version, "<") { + global.LOG.Errorf("load latest version from oss failed, err: %v", version) + return "" + } + if isLatest { + return u.checkVersion(version, currentVersion) + } + + versionMap := make(map[string]string) + if err := json.Unmarshal(latestVersionRes, &versionMap); err != nil { + global.LOG.Errorf("load latest version from oss failed (error unmarshal), err: %v", err) + return "" + } + + versionPart := strings.Split(currentVersion, ".") + if len(versionPart) < 3 { + global.LOG.Errorf("current version is error format: %s", currentVersion) + return "" + } + num, _ := strconv.Atoi(versionPart[1]) + if num >= 10 { + if version, ok := versionMap[currentVersion[0:5]]; ok { + return u.checkVersion(version, currentVersion) + } + return "" + } + if version, ok := versionMap[currentVersion[0:4]]; ok { + return u.checkVersion(version, currentVersion) + } + return "" +} + +func (u *UpgradeService) checkVersion(v2, v1 string) string { + addSuffix := false + if !strings.Contains(v1, "-") { + v1 = v1 + "-lts" + } + if !strings.Contains(v2, "-") { + addSuffix = true + v2 = v2 + "-lts" + } + if common.ComparePanelVersion(v2, v1) { + if addSuffix { + return strings.TrimSuffix(v2, "-lts") + } + return v2 + } + return "" +} + +func (u *UpgradeService) loadReleaseNotes(path string) (string, error) { + _, releaseNotes, err := req_helper.HandleRequestWithProxy(path, http.MethodGet, constant.TimeOut20s) + if err != nil { + return "", err + } + return string(releaseNotes), nil +} + +func loadArch() (string, error) { + std, err := cmd.RunDefaultWithStdoutBashC("uname -a") + if err != nil { + return "", fmt.Errorf("std: %s, err: %s", std, err.Error()) + } + if strings.Contains(std, "x86_64") { + return "amd64", nil + } + if strings.Contains(std, "arm64") || strings.Contains(std, "aarch64") { + return "arm64", nil + } + if strings.Contains(std, "armv7l") { + return "armv7", nil + } + if strings.Contains(std, "ppc64le") { + return "ppc64le", nil + } + if strings.Contains(std, "s390x") { + return "s390x", nil + } + if strings.Contains(std, "riscv64") { + return "riscv64", nil + } + return "", fmt.Errorf("unsupported such arch: %s", std) +} + +func dropBackupCopies() { + backupCopies, _ := settingRepo.GetValueByKey("UpgradeBackupCopies") + copies, _ := strconv.Atoi(backupCopies) + if copies == 0 { + return + } + backupDir := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/upgrade") + upgradeDir, err := os.ReadDir(backupDir) + if err != nil { + global.LOG.Errorf("read upgrade dir failed, err: %v", err) + return + } + var versions []string + for _, item := range upgradeDir { + if item.IsDir() && strings.HasPrefix(item.Name(), "v") { + versions = append(versions, item.Name()) + } + } + if len(versions) <= copies { + return + } + sort.Slice(versions, func(i, j int) bool { + return common.ComparePanelVersion(versions[i], versions[j]) + }) + for i := copies; i < len(versions); i++ { + _ = os.RemoveAll(backupDir + "/" + versions[i]) + } +} diff --git a/core/app/task/task.go b/core/app/task/task.go new file mode 100644 index 0000000..b82b822 --- /dev/null +++ b/core/app/task/task.go @@ -0,0 +1,278 @@ +package task + +import ( + "context" + "fmt" + "os" + "path" + "strconv" + "time" + + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/google/uuid" + "github.com/sirupsen/logrus" +) + +type ActionFunc func(*Task) error +type RollbackFunc func(*Task) + +type Task struct { + Name string + TaskID string + Logger *logrus.Logger + SubTasks []*SubTask + Rollbacks []RollbackFunc + logFile *os.File + taskRepo repo.ITaskRepo + Task *model.Task + ParentID string + CancelWhenTimeout bool +} + +type SubTask struct { + RootTask *Task + Name string + StepAlias string + Retry int + Timeout time.Duration + Action ActionFunc + Rollback RollbackFunc + Error error + IgnoreErr bool + CancelWhenTimeout bool +} + +const ( + TaskUpgrade = "TaskUpgrade" + TaskAddNode = "TaskAddNode" + TaskSync = "TaskSync" + TaskSyncForNode = "TaskSyncForNode" + TaskRsync = "TaskRsync" + TaskInstallCluster = "TaskInstallCluster" + TaskCreateCluster = "TaskCreateCluster" + TaskBackup = "TaskBackup" + TaskPush = "TaskPush" +) + +const ( + TaskScopeSystem = "System" + TaskScopeScript = "ScriptLibrary" + TaskScopeNodeFile = "NodeFile" + TaskScopeAppBackup = "AppBackup" + TaskScopeCluster = "Cluster" + TaskScopeAppInstall = "AppInstallTask" +) + +func GetTaskName(resourceName, operate, scope string) string { + return fmt.Sprintf("%s%s [%s]", i18n.GetMsgByKey(operate), i18n.GetMsgByKey(scope), resourceName) +} + +func NewTaskWithOps(resourceName, operate, scope, taskID string, resourceID uint) (*Task, error) { + return NewTask(GetTaskName(resourceName, operate, scope), operate, scope, taskID, resourceID) +} + +func NewTask(name, operate, taskScope, taskID string, resourceID uint) (*Task, error) { + if taskID == "" { + taskID = uuid.New().String() + } + logItem := path.Join(global.CONF.Base.InstallDir, "1panel/log/task") + logDir := path.Join(logItem, taskScope) + if _, err := os.Stat(logDir); os.IsNotExist(err) { + if err = os.MkdirAll(logDir, constant.DirPerm); err != nil { + return nil, fmt.Errorf("failed to create log directory: %w", err) + } + } + logPath := path.Join(logItem, taskScope, taskID+".log") + logger := logrus.New() + logger.SetFormatter(&SimpleFormatter{}) + logFile, err := os.OpenFile(logPath, os.O_TRUNC|os.O_CREATE|os.O_WRONLY, constant.FilePerm) + if err != nil { + return nil, fmt.Errorf("failed to open log file: %w", err) + } + logger.SetOutput(logFile) + taskModel := &model.Task{ + ID: taskID, + Name: name, + Type: taskScope, + LogFile: logPath, + Status: constant.StatusExecuting, + ResourceID: resourceID, + Operate: operate, + } + taskRepo := repo.NewITaskRepo() + task := &Task{Name: name, logFile: logFile, Logger: logger, taskRepo: taskRepo, Task: taskModel} + return task, nil +} + +func (t *Task) AddSubTask(name string, action ActionFunc, rollback RollbackFunc) { + subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (t *Task) AddSubTaskWithAlias(key string, action ActionFunc, rollback RollbackFunc) { + subTask := &SubTask{RootTask: t, Name: i18n.GetMsgByKey(key), StepAlias: key, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: rollback} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (t *Task) AddSubTaskWithOps(name string, action ActionFunc, rollback RollbackFunc, retry int, timeout time.Duration) { + subTask := &SubTask{RootTask: t, Name: name, Retry: retry, Timeout: timeout, Action: action, Rollback: rollback} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (t *Task) AddSubTaskWithIgnoreErr(name string, action ActionFunc) { + subTask := &SubTask{RootTask: t, Name: name, Retry: 0, Timeout: 10 * time.Minute, Action: action, Rollback: nil, IgnoreErr: true} + t.SubTasks = append(t.SubTasks, subTask) +} + +func (s *SubTask) Execute() error { + subTaskName := s.Name + if s.Name == "" { + subTaskName = i18n.GetMsgByKey("SubTask") + } + s.RootTask.LogStart(subTaskName) + var err error + for i := 0; i < s.Retry+1; i++ { + if i > 0 { + s.RootTask.Log(i18n.GetWithName("TaskRetry", strconv.Itoa(i))) + } + ctx, cancel := context.WithTimeout(context.Background(), s.Timeout) + defer cancel() + + done := make(chan error) + go func() { + done <- s.Action(s.RootTask) + }() + + select { + case <-ctx.Done(): + s.RootTask.Log(i18n.GetWithName("TaskTimeout", subTaskName)) + if s.CancelWhenTimeout { + return buserr.New(i18n.GetWithName("TaskTimeout", subTaskName)) + } + case err = <-done: + if err != nil { + s.RootTask.Log(i18n.GetWithNameAndErr("SubTaskFailed", subTaskName, err)) + } else { + s.RootTask.Log(i18n.GetWithName("SubTaskSuccess", subTaskName)) + return nil + } + } + + if i == s.Retry { + if s.Rollback != nil { + s.Rollback(s.RootTask) + } + } + time.Sleep(1 * time.Second) + } + return err +} + +func (t *Task) updateTask(task *model.Task) { + _ = t.taskRepo.Update(context.Background(), task) +} + +func (t *Task) Execute() error { + if err := t.taskRepo.Save(context.Background(), t.Task); err != nil { + return err + } + var err error + t.Log(i18n.GetWithName("TaskStart", t.Name)) + for _, subTask := range t.SubTasks { + subTask.CancelWhenTimeout = t.CancelWhenTimeout + t.Task.CurrentStep = subTask.StepAlias + t.updateTask(t.Task) + if err = subTask.Execute(); err == nil { + if subTask.Rollback != nil { + t.Rollbacks = append(t.Rollbacks, subTask.Rollback) + } + } else { + if subTask.IgnoreErr { + err = nil + continue + } + t.Task.ErrorMsg = err.Error() + t.Task.Status = constant.StatusFailed + for _, rollback := range t.Rollbacks { + rollback(t) + } + t.updateTask(t.Task) + break + } + } + if t.Task.Status == constant.StatusExecuting { + t.Task.Status = constant.StatusSuccess + t.Log(i18n.GetWithName("TaskSuccess", t.Name)) + } else { + t.Log(i18n.GetWithName("TaskFailed", t.Name)) + } + t.Log("[TASK-END]") + t.Task.EndAt = time.Now() + t.updateTask(t.Task) + _ = t.logFile.Close() + return err +} + +func (t *Task) DeleteLogFile() { + _ = os.Remove(t.Task.LogFile) +} + +func (t *Task) LogWithStatus(msg string, err error) { + if err != nil { + t.Logger.Print(i18n.GetWithNameAndErr("FailedStatus", msg, err)) + } else { + t.Logger.Print(i18n.GetWithName("SuccessStatus", msg)) + } +} + +func (t *Task) Log(msg string) { + t.Logger.Print(msg) +} + +func (t *Task) Logf(format string, v ...any) { + t.Logger.Printf(format, v...) +} + +func (t *Task) LogFailed(msg string) { + t.Logger.Print(msg + i18n.GetMsgByKey("Failed")) +} + +func (t *Task) LogFailedWithErr(msg string, err error) { + t.Logger.Printf("%s %s : %s", msg, i18n.GetMsgByKey("Failed"), err.Error()) +} + +func (t *Task) LogSuccess(msg string) { + t.Logger.Print(msg + i18n.GetMsgByKey("Success")) +} +func (t *Task) LogSuccessF(format string, v ...any) { + t.Logger.Print(fmt.Sprintf(format, v...) + i18n.GetMsgByKey("Success")) +} + +func (t *Task) LogStart(msg string) { + t.Logger.Printf("%s%s", i18n.GetMsgByKey("Start"), msg) +} + +func (t *Task) LogWithOps(operate, msg string) { + t.Logger.Printf("%s%s", i18n.GetMsgByKey(operate), msg) +} + +func (t *Task) LogSuccessWithOps(operate, msg string) { + t.Logger.Printf("%s%s%s", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Success")) +} + +func (t *Task) LogFailedWithOps(operate, msg string, err error) { + t.Logger.Printf("%s%s%s : %s ", i18n.GetMsgByKey(operate), msg, i18n.GetMsgByKey("Failed"), err.Error()) +} + +type SimpleFormatter struct{} + +func (f *SimpleFormatter) Format(entry *logrus.Entry) ([]byte, error) { + timestamp := entry.Time.Format("2006/01/02 15:04:05") + message := fmt.Sprintf("%s %s\n", timestamp, entry.Message) + return []byte(message), nil +} diff --git a/core/buserr/errors.go b/core/buserr/errors.go new file mode 100644 index 0000000..aab8f6a --- /dev/null +++ b/core/buserr/errors.go @@ -0,0 +1,107 @@ +package buserr + +import ( + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/pkg/errors" +) + +type BusinessError struct { + Msg string + Detail interface{} + Map map[string]interface{} + Err error +} + +func (e BusinessError) Error() string { + content := "" + if e.Detail != nil { + content = i18n.GetErrMsg(e.Msg, map[string]interface{}{"detail": e.Detail}) + } else if e.Map != nil { + content = i18n.GetErrMsg(e.Msg, e.Map) + } else { + content = i18n.GetErrMsg(e.Msg, nil) + } + if content == "" { + if e.Err != nil { + return e.Err.Error() + } + return errors.New(e.Msg).Error() + } + return content +} + +func New(key string, opts ...Option) BusinessError { + be := BusinessError{ + Msg: key, + } + + for _, opt := range opts { + opt(&be) + } + + return be +} + +func WithErr(Key string, err error) BusinessError { + paramMap := map[string]interface{}{} + if err != nil { + paramMap["err"] = err + } + return BusinessError{ + Msg: Key, + Map: paramMap, + Err: err, + } +} + +func WithDetail(Key string, detail interface{}, err error) BusinessError { + return BusinessError{ + Msg: Key, + Detail: detail, + Err: err, + } +} + +func WithMap(Key string, maps map[string]interface{}, err error) BusinessError { + return BusinessError{ + Msg: Key, + Map: maps, + Err: err, + } +} + +func WithName(Key string, name string) BusinessError { + paramMap := map[string]interface{}{} + if name != "" { + paramMap["name"] = name + } + return BusinessError{ + Msg: Key, + Map: paramMap, + } +} + +type Option func(*BusinessError) + +func WithNameOption(name string) Option { + return func(be *BusinessError) { + if name != "" { + if be.Map == nil { + be.Map = make(map[string]interface{}) + } + be.Map["name"] = name + } + } +} + +func WithErrOption(err error) Option { + return func(be *BusinessError) { + be.Err = err + if err != nil { + if be.Map == nil { + be.Map = make(map[string]interface{}) + } + be.Map["err"] = err + } + } +} diff --git a/core/buserr/multi_err.go b/core/buserr/multi_err.go new file mode 100644 index 0000000..bf0c875 --- /dev/null +++ b/core/buserr/multi_err.go @@ -0,0 +1,23 @@ +package buserr + +import ( + "bytes" + "fmt" + "sort" +) + +type MultiErr map[string]error + +func (e MultiErr) Error() string { + var keys []string + for key := range e { + keys = append(keys, key) + } + sort.Strings(keys) + + buffer := bytes.NewBufferString("") + for _, key := range keys { + buffer.WriteString(fmt.Sprintf("[%s] %s\n", key, e[key])) + } + return buffer.String() +} diff --git a/core/cmd/server/app/app_config.go b/core/cmd/server/app/app_config.go new file mode 100644 index 0000000..d4d1559 --- /dev/null +++ b/core/cmd/server/app/app_config.go @@ -0,0 +1,14 @@ +package app + +import ( + _ "embed" +) + +//go:embed app_config.yml +var Config []byte + +//go:embed logo.png +var Logo []byte + +//go:embed app_param.yml +var Param []byte diff --git a/core/cmd/server/app/app_config.yml b/core/cmd/server/app/app_config.yml new file mode 100644 index 0000000..31dbd25 --- /dev/null +++ b/core/cmd/server/app/app_config.yml @@ -0,0 +1,21 @@ +additionalProperties: + key: #应用的 key ,仅限英文,用于在 Linux 创建文件夹 + name: #应用名称 + tags: + - Tool #应用标签,可以有多个,请参照下方的标签列表 + shortDescZh: #应用中文描述,不要超过30个字 + shortDescEn: #应用英文描述 + type: tool #应用类型,区别于应用分类,只能有一个,请参照下方的类型列表 + crossVersionUpdate: #是否可以跨大版本升级 + limit: #应用安装数量限制,0 代表无限制 + website: #官网地址 + github: #github 地址 + description: + en: + zh: #应用中文描述,不要超过30个字 + zh-Hant: + ja: + ms: + pt-br: + ru: + ko: \ No newline at end of file diff --git a/core/cmd/server/app/app_param.yml b/core/cmd/server/app/app_param.yml new file mode 100644 index 0000000..426fa78 --- /dev/null +++ b/core/cmd/server/app/app_param.yml @@ -0,0 +1,10 @@ +additionalProperties: + formFields: + - default: 8080 + edit: true + envKey: PANEL_APP_PORT_HTTP + labelEn: Port + labelZh: 端口 + required: true + rule: paramPort + type: number diff --git a/core/cmd/server/app/logo.png b/core/cmd/server/app/logo.png new file mode 100644 index 0000000..6f82a12 Binary files /dev/null and b/core/cmd/server/app/logo.png differ diff --git a/core/cmd/server/cmd/app.go b/core/cmd/server/cmd/app.go new file mode 100644 index 0000000..3ea2816 --- /dev/null +++ b/core/cmd/server/cmd/app.go @@ -0,0 +1,159 @@ +package cmd + +import ( + "bytes" + "fmt" + "github.com/1Panel-dev/1Panel/core/cmd/server/app" + "github.com/1Panel-dev/1Panel/core/i18n" + "io" + "os" + + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + appKey string + appVersion string +) + +func init() { + appCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadAppHelper() + }) + initCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadAppInitHelper() + }) + initCmd.Flags().StringVarP(&appKey, "key", "k", "", "") + initCmd.Flags().StringVarP(&appVersion, "version", "v", "", "") + appCmd.AddCommand(initCmd) + RootCmd.AddCommand(appCmd) +} + +var appCmd = &cobra.Command{ + Use: "app", +} + +var initCmd = &cobra.Command{ + Use: "init", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl app init"})) + return nil + } + if len(args) > 0 { + appKey = args[0] + if len(args) > 1 { + appVersion = args[1] + } + } + if appKey == "" { + fmt.Println(i18n.GetMsgByKeyForCmd("AppMissKey")) + return nil + } + if appVersion == "" { + fmt.Println(i18n.GetMsgByKeyForCmd("AppMissVersion")) + return nil + } + appKeyPath := fmt.Sprintf("./%s", appKey) + if err := createFolder(appKeyPath); err != nil { + return err + } + configYamlPath := fmt.Sprintf("%s/data.yml", appKeyPath) + if err := createFile(configYamlPath); err != nil { + return err + } + if err := writeFile(configYamlPath, bytes.NewReader(app.Config)); err != nil { + return err + } + readMePath := fmt.Sprintf("%s/README.md", appKeyPath) + if err := createFile(readMePath); err != nil { + return err + } + logoPath := fmt.Sprintf("%s/logo.png", appKeyPath) + if err := createFile(logoPath); err != nil { + return err + } + if err := writeFile(logoPath, bytes.NewReader(app.Logo)); err != nil { + return err + } + versionPath := fmt.Sprintf("%s/%s", appKeyPath, appVersion) + if _, err := os.Stat(versionPath); err == nil { + return errors.New(i18n.GetMsgByKeyForCmd("AppVersionExist")) + } + if err := createFolder(versionPath); err != nil { + return err + } + versionParamPath := fmt.Sprintf("%s/%s", versionPath, "data.yml") + if err := createFile(versionParamPath); err != nil { + return err + } + if err := writeFile(versionParamPath, bytes.NewReader(app.Param)); err != nil { + return err + } + dockerComposeYamlPath := fmt.Sprintf("%s/%s", versionPath, "docker-compose.yml") + if err := createFile(dockerComposeYamlPath); err != nil { + return err + } + fmt.Println(i18n.GetMsgByKeyForCmd("AppCreateSuccessful")) + return nil + }, +} + +func createFile(filePath string) error { + if _, err := os.Stat(filePath); err == nil { + return nil + } + file, err := os.Create(filePath) + if err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("AppCreateFileErr", map[string]interface{}{"name": filePath, "err": err.Error()})) + return err + } + defer file.Close() + return nil +} + +func createFolder(dirPath string) error { + if _, err := os.Stat(dirPath); err == nil { + return nil + } + if err := os.MkdirAll(dirPath, 0755); err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("AppCreateDirErr", map[string]interface{}{"name": dirPath, "err": err.Error()})) + return err + } + return nil +} + +func writeFile(filePath string, in io.Reader) error { + data, err := io.ReadAll(in) + if err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("AppWriteErr", map[string]interface{}{"name": filePath, "err": err.Error()})) + return err + } + if err := os.WriteFile(filePath, data, 0755); err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("AppWriteErr", map[string]interface{}{"name": filePath, "err": err.Error()})) + return err + } + return nil +} + +func loadAppHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("AppCommands")) + fmt.Println("\nUsage:\n 1panel app [command]\n\nAvailable Commands:") + fmt.Println("\n init " + i18n.GetMsgByKeyForCmd("AppInit")) + fmt.Println("\nFlags:\n -h, --help help for app") + fmt.Println(" -k, --key string " + i18n.GetMsgByKeyForCmd("AppKeyVal")) + fmt.Println(" -v, --version string " + i18n.GetMsgByKeyForCmd("AppVersion")) + fmt.Println("\nUse \"1panel app [command] --help\" for more information about a command.") +} + +func loadAppInitHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("AppInit")) + fmt.Println("\nUsage:\n 1panel app init [flags]") + fmt.Println("\nFlags:\n -h, --help help for app") + fmt.Println(" -k, --key string " + i18n.GetMsgByKeyForCmd("AppKeyVal")) + fmt.Println(" -v, --version string " + i18n.GetMsgByKeyForCmd("AppVersion")) +} diff --git a/core/cmd/server/cmd/listen-ip.go b/core/cmd/server/cmd/listen-ip.go new file mode 100644 index 0000000..554be7d --- /dev/null +++ b/core/cmd/server/cmd/listen-ip.go @@ -0,0 +1,80 @@ +package cmd + +import ( + "fmt" + + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/spf13/cobra" +) + +func init() { + listenCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadListenIPHelper() + }) + + RootCmd.AddCommand(listenCmd) + listenCmd.AddCommand(listenIpv4Cmd) + listenCmd.AddCommand(listenIpv6Cmd) +} + +var listenCmd = &cobra.Command{ + Use: "listen-ip", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + loadListenIPHelper() + return nil + }, +} + +var listenIpv4Cmd = &cobra.Command{ + Use: "ipv4", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + return updateBindInfo("ipv4") + }, +} +var listenIpv6Cmd = &cobra.Command{ + Use: "ipv6", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + return updateBindInfo("ipv6") + }, +} + +func updateBindInfo(protocol string) error { + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl listen-ip ipv6"})) + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + return err + } + ipv6 := constant.StatusDisable + tcp := "tcp4" + address := "0.0.0.0" + if protocol == "ipv6" { + ipv6 = constant.StatusEnable + tcp = "tcp6" + address = "::" + } + if err := setSettingByKey(db, "Ipv6", ipv6); err != nil { + return err + } + if err := setSettingByKey(db, "BindAddress", address); err != nil { + return err + } + fmt.Println(i18n.GetMsgWithMapForCmd("ListenChangeSuccessful", map[string]interface{}{"value": fmt.Sprintf(" %s [%s]", tcp, address)})) + return nil +} + +func loadListenIPHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateCommands")) + fmt.Println("\nUsage:\n 1panel listen-ip [command]\n\nAvailable Commands:") + fmt.Println("\n ipv4 " + i18n.GetMsgByKeyForCmd("ListenIPv4")) + fmt.Println(" ipv6 " + i18n.GetMsgByKeyForCmd("ListenIPv6")) + fmt.Println("\nFlags:\n -h, --help help for listen-ip") + fmt.Println("\nUse \"1panel listen-ip [command] --help\" for more information about a command.") +} diff --git a/core/cmd/server/cmd/reset.go b/core/cmd/server/cmd/reset.go new file mode 100644 index 0000000..de29fb1 --- /dev/null +++ b/core/cmd/server/cmd/reset.go @@ -0,0 +1,147 @@ +package cmd + +import ( + "fmt" + + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/1Panel-dev/1Panel/core/utils/passkey" + "github.com/spf13/cobra" +) + +func init() { + resetCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadResetHelper() + }) + + RootCmd.AddCommand(resetCmd) + resetCmd.AddCommand(resetMFACmd) + resetCmd.AddCommand(resetSSLCmd) + resetCmd.AddCommand(resetEntranceCmd) + resetCmd.AddCommand(resetBindIpsCmd) + resetCmd.AddCommand(resetDomainCmd) + resetCmd.AddCommand(resetPasskeyCmd) +} + +var resetCmd = &cobra.Command{ + Use: "reset", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + loadResetHelper() + return nil + }, +} + +var resetMFACmd = &cobra.Command{ + Use: "mfa", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset mfa"})) + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + return err + } + + return setSettingByKey(db, "MFAStatus", constant.StatusDisable) + }, +} +var resetSSLCmd = &cobra.Command{ + Use: "https", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset https"})) + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + return err + } + + return setSettingByKey(db, "SSL", constant.StatusDisable) + }, +} +var resetEntranceCmd = &cobra.Command{ + Use: "entrance", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset entrance"})) + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + return err + } + + return setSettingByKey(db, "SecurityEntrance", "") + }, +} +var resetBindIpsCmd = &cobra.Command{ + Use: "ips", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset ips"})) + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + return err + } + + return setSettingByKey(db, "AllowIPs", "") + }, +} +var resetDomainCmd = &cobra.Command{ + Use: "domain", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset domain"})) + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + return err + } + + return setSettingByKey(db, "BindDomain", "") + }, +} + +var resetPasskeyCmd = &cobra.Command{ + Use: "passkey", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl reset passkey"})) + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + return err + } + if err := setSettingByKey(db, passkey.PasskeyUserIDSettingKey, ""); err != nil { + return err + } + return setSettingByKey(db, passkey.PasskeyCredentialSettingKey, "") + }, +} + +func loadResetHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("ResetCommands")) + fmt.Println("\nUsage:\n 1panel reset [command]\n\nAvailable Commands:") + fmt.Println("\n domain " + i18n.GetMsgByKeyForCmd("ResetDomain")) + fmt.Println(" entrance " + i18n.GetMsgByKeyForCmd("ResetEntrance")) + fmt.Println(" https " + i18n.GetMsgByKeyForCmd("ResetHttps")) + fmt.Println(" ips " + i18n.GetMsgByKeyForCmd("ResetIPs")) + fmt.Println(" mfa " + i18n.GetMsgByKeyForCmd("ResetMFA")) + fmt.Println(" passkey " + i18n.GetMsgByKeyForCmd("ResetPasskey")) + fmt.Println("\nFlags:\n -h, --help help for reset") + fmt.Println("\nUse \"1panel reset [command] --help\" for more information about a command.") +} diff --git a/core/cmd/server/cmd/restore.go b/core/cmd/server/cmd/restore.go new file mode 100644 index 0000000..732e7b6 --- /dev/null +++ b/core/cmd/server/cmd/restore.go @@ -0,0 +1,142 @@ +package cmd + +import ( + "fmt" + "os" + "path" + "sort" + "strings" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + cmdUtils "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/controller" + "github.com/1Panel-dev/1Panel/core/utils/files" + + "github.com/spf13/cobra" +) + +func init() { + RootCmd.AddCommand(restoreCmd) +} + +var restoreCmd = &cobra.Command{ + Use: "restore", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl restore"})) + return nil + } + stdout, err := cmdUtils.RunDefaultWithStdoutBashC("grep '^BASE_DIR=' /usr/local/bin/1pctl | cut -d'=' -f2") + if err != nil { + return fmt.Errorf("handle load `BASE_DIR` failed, err: %v", err) + } + baseDir := strings.ReplaceAll(stdout, "\n", "") + upgradeDir := path.Join(baseDir, "1panel", "tmp", "upgrade") + + tmpPath, err := loadRestorePath(upgradeDir) + if err != nil { + return err + } + if tmpPath == "no such file" { + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreNoSuchFile")) + return nil + } + tmpPath = path.Join(upgradeDir, tmpPath, "original") + + fmt.Println(i18n.GetMsgWithMapForCmd("RestoreStep1", map[string]interface{}{"name": tmpPath})) + if err := files.CopyItem(false, true, path.Join(tmpPath, "1panel-agent"), "/usr/local/bin"); err != nil { + return err + } + if err := files.CopyItem(false, true, path.Join(tmpPath, "1panel-core"), "/usr/local/bin"); err != nil { + return err + } + if err := files.CopyItem(true, true, path.Join(tmpPath, "lang"), "/usr/local/bin"); err != nil { + return err + } + if err := files.CopyItem(false, true, path.Join(tmpPath, "GeoIP.mmdb"), path.Join(baseDir, "1panel/geo")); err != nil { + return err + } + sudo := cmdUtils.SudoHandleCmd() + _, _ = cmdUtils.RunDefaultWithStdoutBashCf("%s chmod 755 /usr/local/bin/1panel-agent /usr/local/bin/1panel-core", sudo) + + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep2")) + if err := files.CopyItem(false, true, path.Join(tmpPath, "1pctl"), "/usr/local/bin"); err != nil { + return err + } + _, _ = cmdUtils.RunDefaultWithStdoutBashCf("%s chmod 755 /usr/local/bin/1pctl", sudo) + _, _ = cmdUtils.RunDefaultWithStdoutBashCf("cp -r %s /usr/local/bin", path.Join(tmpPath, "lang")) + geoPath := path.Join(global.CONF.Base.InstallDir, "1panel/geo") + _, _ = cmdUtils.RunDefaultWithStdoutBashCf("mkdir %s && cp %s %s/", geoPath, path.Join(tmpPath, "GeoIP.mmdb"), geoPath) + + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep3")) + svcBasePath, _ := controller.GetServicePath("") + svcCoreName, _ := controller.LoadServiceName("1panel-core") + selCoreName, _ := controller.SelectInitScript("1panel-core") + scriptCoreName, _ := controller.GetScriptName("1panel-core") + svcAgentName, _ := controller.LoadServiceName("1panel-agent") + selAgentName, _ := controller.SelectInitScript("1panel-agent") + scriptAgentName, _ := controller.GetScriptName("1panel-agent") + if err := files.CopyItem(false, true, path.Join(tmpPath, svcCoreName), svcBasePath); err != nil { + if err := files.CopyItem(false, true, path.Join(tmpPath, selCoreName), path.Join(svcBasePath, scriptCoreName)); err != nil { + return err + } + } + if err := files.CopyItem(false, true, path.Join(tmpPath, svcAgentName), svcBasePath); err != nil { + if err := files.CopyItem(false, true, path.Join(tmpPath, selAgentName), path.Join(svcBasePath, scriptAgentName)); err != nil { + return err + } + } + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep4")) + if _, err := os.Stat(path.Join(tmpPath, "db")); err == nil { + dbPath := path.Join(baseDir, "1panel") + if err := files.CopyItem(true, true, path.Join(tmpPath, "db"), dbPath); err != nil { + global.LOG.Errorf("rollback 1panel db failed, err: %v", err) + } + } + + fmt.Println(i18n.GetMsgByKeyForCmd("RestoreStep5")) + version := loadRollbackVersion(tmpPath) + fmt.Println(i18n.GetMsgWithMapForCmd("RestoreSuccessful", map[string]interface{}{"version": version})) + + controller.RestartPanel(true, true, true) + return nil + }, +} + +func loadRestorePath(upgradeDir string) (string, error) { + if _, err := os.Stat(upgradeDir); err != nil && os.IsNotExist(err) { + return "no such file", nil + } + files, err := os.ReadDir(upgradeDir) + if err != nil { + return "", err + } + var folders []string + for _, file := range files { + if file.IsDir() { + folders = append(folders, file.Name()) + } + } + if len(folders) == 0 { + return "no such file", nil + } + sort.Slice(folders, func(i, j int) bool { + return common.ComparePanelVersion(folders[i], folders[j]) + }) + return folders[0], nil +} + +func loadRollbackVersion(upgradeDir string) string { + stdout, err := cmdUtils.RunDefaultWithStdoutBashCf("grep '^ORIGINAL_VERSION=' %s/1pctl | cut -d'=' -f2", upgradeDir) + if err != nil { + return "-" + } + info := strings.ReplaceAll(stdout, "\n", "") + if len(info) == 0 || info == `""` { + return "-" + } + return info +} diff --git a/core/cmd/server/cmd/root.go b/core/cmd/server/cmd/root.go new file mode 100644 index 0000000..29b6527 --- /dev/null +++ b/core/cmd/server/cmd/root.go @@ -0,0 +1,84 @@ +package cmd + +import ( + "fmt" + "os/user" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/server" + cmdUtils "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/glebarez/sqlite" + "github.com/spf13/cobra" + "gorm.io/gorm" +) + +var language string + +func init() { + RootCmd.PersistentFlags().StringVarP(&language, "language", "l", "en", "Set the language") +} + +var RootCmd = &cobra.Command{ + Use: "1panel", + RunE: func(cmd *cobra.Command, args []string) error { + server.Start() + return nil + }, +} + +type setting struct { + ID uint `gorm:"primarykey;AUTO_INCREMENT" json:"id"` + CreatedAt time.Time `json:"createdAt"` + UpdatedAt time.Time `json:"updatedAt"` + Key string `json:"key" gorm:"type:varchar(256);not null;"` + Value string `json:"value" gorm:"type:varchar(256)"` + About string `json:"about" gorm:"type:longText"` +} + +func loadDBConn(dbName string) (*gorm.DB, error) { + stdout, err := cmdUtils.RunDefaultWithStdoutBashC("grep '^BASE_DIR=' /usr/local/bin/1pctl | cut -d'=' -f2") + if err != nil { + return nil, fmt.Errorf("handle load `BASE_DIR` failed, err: %v", err) + } + baseDir := strings.ReplaceAll(stdout, "\n", "") + if len(baseDir) == 0 { + return nil, fmt.Errorf("error `BASE_DIR` find in /usr/local/bin/1pctl \n") + } + if strings.HasSuffix(baseDir, "/") { + baseDir = baseDir[:strings.LastIndex(baseDir, "/")] + } + + db, err := gorm.Open(sqlite.Open(path.Join(baseDir, "1panel/db", dbName)), &gorm.Config{}) + if err != nil { + return nil, fmt.Errorf("init my db conn failed, err: %v \n", err) + } + return db, nil +} + +func getSettingByKey(db *gorm.DB, key string) string { + var setting setting + _ = db.Where("key = ?", key).First(&setting).Error + return setting.Value +} + +type LoginLog struct{} + +func isDefault(db *gorm.DB) bool { + logCount := int64(0) + _ = db.Model(&LoginLog{}).Where("status = ?", "Success").Count(&logCount).Error + return logCount == 0 +} + +func setSettingByKey(db *gorm.DB, key, value string) error { + return db.Model(&setting{}).Where("key = ?", key).Updates(map[string]interface{}{"value": value}).Error +} + +func isRoot() bool { + currentUser, err := user.Current() + if err != nil { + return false + } + return currentUser.Uid == "0" +} diff --git a/core/cmd/server/cmd/update.go b/core/cmd/server/cmd/update.go new file mode 100644 index 0000000..4f7802d --- /dev/null +++ b/core/cmd/server/cmd/update.go @@ -0,0 +1,292 @@ +package cmd + +import ( + "bufio" + "fmt" + "os" + "regexp" + "strconv" + "strings" + "unicode" + + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/spf13/cobra" + "golang.org/x/term" +) + +func init() { + updateCmd.SetHelpFunc(func(c *cobra.Command, s []string) { + i18n.UseI18nForCmd(language) + loadUpdateHelper() + }) + + RootCmd.AddCommand(updateCmd) + updateCmd.AddCommand(updateUserName) + updateCmd.AddCommand(updatePassword) + updateCmd.AddCommand(updatePort) + + updateCmd.AddCommand(updateVersion) +} + +var updateCmd = &cobra.Command{ + Use: "update", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + loadUpdateHelper() + return nil + }, +} + +var updateUserName = &cobra.Command{ + Use: "username", + Short: i18n.GetMsgByKeyForCmd("UpdateUser"), + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update username"})) + return nil + } + username() + return nil + }, +} +var updatePassword = &cobra.Command{ + Use: "password", + Short: i18n.GetMsgByKeyForCmd("UpdatePassword"), + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update password"})) + return nil + } + password() + return nil + }, +} +var updatePort = &cobra.Command{ + Use: "port", + Short: i18n.GetMsgByKeyForCmd("UpdatePort"), + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update port"})) + return nil + } + port() + return nil + }, +} +var updateVersion = &cobra.Command{ + Use: "version", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl update version"})) + return nil + } + version := args[0] + if len(version) == 0 || !strings.HasPrefix(version, "v2.") { + fmt.Println("err version in param input") + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()})) + return err + } + if err := setSettingByKey(db, "SystemVersion", version); err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserErr", map[string]interface{}{"err": err.Error()})) + return err + } + return nil + }, +} + +func username() { + reader := bufio.NewReader(os.Stdin) + fmt.Print(i18n.GetMsgByKeyForCmd("UpdateUser") + ": ") + newUsername, _ := reader.ReadString('\n') + newUsername = strings.Trim(newUsername, "\n") + if len(newUsername) == 0 { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateUserNull")) + return + } + if strings.Contains(newUsername, " ") { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateUserBlank")) + return + } + result, err := regexp.MatchString("^[a-zA-Z0-9_\u4e00-\u9fa5]{3,30}$", newUsername) + if !result || err != nil { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateUserFormat")) + return + } + + db, err := loadDBConn("core.db") + if err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()})) + return + } + if err := setSettingByKey(db, "UserName", newUsername); err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserErr", map[string]interface{}{"err": err.Error()})) + return + } + + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful")) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserResult", map[string]interface{}{"name": newUsername})) +} + +func password() { + fmt.Print(i18n.GetMsgByKeyForCmd("UpdatePassword") + ": ") + bytePassword, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + fmt.Println("\n" + i18n.GetMsgWithMapForCmd("UpdatePasswordRead", map[string]interface{}{"err": err.Error()})) + return + } + newPassword := string(bytePassword) + newPassword = strings.Trim(newPassword, "\n") + + if len(newPassword) == 0 { + fmt.Println("\n", i18n.GetMsgByKeyForCmd("UpdatePasswordNull")) + return + } + if strings.Contains(newPassword, " ") { + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateUPasswordBlank")) + return + } + db, err := loadDBConn("core.db") + if err != nil { + fmt.Println("\n" + i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()})) + return + } + complexSetting := getSettingByKey(db, "ComplexityVerification") + if complexSetting == constant.StatusEnable { + if isValidPassword("newPassword") { + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdatePasswordFormat")) + return + } + } + if len(newPassword) < 6 { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePasswordLen")) + return + } + + fmt.Print("\n" + i18n.GetMsgByKeyForCmd("UpdatePasswordRe")) + byteConfirmPassword, err := term.ReadPassword(int(os.Stdin.Fd())) + if err != nil { + fmt.Println("\n" + i18n.GetMsgWithMapForCmd("UpdatePasswordRead", map[string]interface{}{"err": err.Error()})) + return + } + confirmPassword := string(byteConfirmPassword) + confirmPassword = strings.Trim(confirmPassword, "\n") + + if newPassword != confirmPassword { + fmt.Println("\n", i18n.GetMsgByKeyForCmd("UpdatePasswordSame")) + return + } + + p := "" + encryptSetting := getSettingByKey(db, "EncryptKey") + if len(encryptSetting) == 16 { + global.CONF.Base.EncryptKey = encryptSetting + p, _ = encrypt.StringEncrypt(newPassword) + } else { + p = newPassword + } + if err := setSettingByKey(db, "Password", p); err != nil { + fmt.Println("\n", i18n.GetMsgWithMapForCmd("UpdatePortErr", map[string]interface{}{"err": err.Error()})) + return + } + username := getSettingByKey(db, "UserName") + + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful")) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserResult", map[string]interface{}{"name": username})) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePasswordResult", map[string]interface{}{"name": string(newPassword)})) +} + +func port() { + reader := bufio.NewReader(os.Stdin) + fmt.Print(i18n.GetMsgByKeyForCmd("UpdatePort") + ": ") + + newPortStr, _ := reader.ReadString('\n') + newPortStr = strings.Trim(newPortStr, "\n") + newPort, err := strconv.Atoi(strings.TrimSpace(newPortStr)) + if err != nil || newPort < 1 || newPort > 65535 { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePortFormat")) + return + } + if common.ScanPort(newPort) { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdatePortUsed")) + return + } + db, err := loadDBConn("core.db") + if err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("DBConnErr", map[string]interface{}{"err": err.Error()})) + return + } + if err := setSettingByKey(db, "ServerPort", newPortStr); err != nil { + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortErr", map[string]interface{}{"err": err.Error()})) + return + } + + fmt.Println("\n" + i18n.GetMsgByKeyForCmd("UpdateSuccessful")) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePortResult", map[string]interface{}{"name": newPortStr})) + + std, err := cmd.RunDefaultWithStdoutBashC("1pctl restart core") + if err != nil { + fmt.Println(std) + } +} +func isValidPassword(password string) bool { + numCount := 0 + alphaCount := 0 + specialCount := 0 + + for _, char := range password { + switch { + case unicode.IsDigit(char): + numCount++ + case unicode.IsLetter(char): + alphaCount++ + case isSpecialChar(char): + specialCount++ + } + } + + if len(password) < 8 && len(password) > 30 { + return false + } + if (numCount == 0 && alphaCount == 0) || (alphaCount == 0 && specialCount == 0) || (numCount == 0 && specialCount == 0) { + return false + } + return true +} + +func isSpecialChar(char rune) bool { + specialChars := "!@#$%*_,.?" + return unicode.IsPunct(char) && contains(specialChars, char) +} + +func contains(specialChars string, char rune) bool { + for _, c := range specialChars { + if c == char { + return true + } + } + return false +} + +func loadUpdateHelper() { + fmt.Println(i18n.GetMsgByKeyForCmd("UpdateCommands")) + fmt.Println("\nUsage:\n 1panel update [command]\n\nAvailable Commands:") + fmt.Println("\n password " + i18n.GetMsgByKeyForCmd("UpdatePassword")) + fmt.Println(" port " + i18n.GetMsgByKeyForCmd("UpdatePort")) + fmt.Println(" username " + i18n.GetMsgByKeyForCmd("UpdateUser")) + fmt.Println("\nFlags:\n -h, --help help for update") + fmt.Println("\nUse \"1panel update [command] --help\" for more information about a command.") +} diff --git a/core/cmd/server/cmd/user-info.go b/core/cmd/server/cmd/user-info.go new file mode 100644 index 0000000..1bc92f7 --- /dev/null +++ b/core/cmd/server/cmd/user-info.go @@ -0,0 +1,66 @@ +package cmd + +import ( + "fmt" + + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/spf13/cobra" +) + +func init() { + RootCmd.AddCommand(userinfoCmd) +} + +var userinfoCmd = &cobra.Command{ + Use: "user-info", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl user-info"})) + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + return fmt.Errorf("init my db conn failed, err: %v \n", err) + } + agentDB, err := loadDBConn("agent.db") + if err != nil { + return fmt.Errorf("init my agent db conn failed, err: %v \n", err) + } + user := getSettingByKey(db, "UserName") + pass := "********" + if isDefault(db) { + encryptSetting := getSettingByKey(db, "EncryptKey") + pass = getSettingByKey(db, "Password") + if len(encryptSetting) == 16 { + global.CONF.Base.EncryptKey = encryptSetting + pass, _ = encrypt.StringDecrypt(pass) + } + } + port := getSettingByKey(db, "ServerPort") + ssl := getSettingByKey(db, "SSL") + entrance := getSettingByKey(db, "SecurityEntrance") + address := getSettingByKey(agentDB, "SystemIP") + domain := getSettingByKey(db, "BindDomain") + if len(domain) != 0 { + address = domain + } + + protocol := "http" + if ssl == constant.StatusEnable { + protocol = "https" + } + if address == "" { + address = "$LOCAL_IP" + } + + fmt.Println(i18n.GetMsgByKeyForCmd("UserInfoAddr") + fmt.Sprintf("%s://%s:%s/%s ", protocol, address, port, entrance)) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdateUserResult", map[string]interface{}{"name": user})) + fmt.Println(i18n.GetMsgWithMapForCmd("UpdatePasswordResult", map[string]interface{}{"name": pass})) + fmt.Println(i18n.GetMsgByKeyForCmd("UserInfoPassHelp") + "1pctl update password") + return nil + }, +} diff --git a/core/cmd/server/cmd/version.go b/core/cmd/server/cmd/version.go new file mode 100644 index 0000000..99737dc --- /dev/null +++ b/core/cmd/server/cmd/version.go @@ -0,0 +1,41 @@ +package cmd + +import ( + "fmt" + + "github.com/1Panel-dev/1Panel/core/cmd/server/conf" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "gopkg.in/yaml.v3" + + "github.com/spf13/cobra" +) + +func init() { + RootCmd.AddCommand(versionCmd) +} + +var versionCmd = &cobra.Command{ + Use: "version", + RunE: func(cmd *cobra.Command, args []string) error { + i18n.UseI18nForCmd(language) + if !isRoot() { + fmt.Println(i18n.GetMsgWithMapForCmd("SudoHelper", map[string]interface{}{"cmd": "sudo 1pctl version"})) + return nil + } + db, err := loadDBConn("core.db") + if err != nil { + return err + } + version := getSettingByKey(db, "SystemVersion") + + fmt.Println(i18n.GetMsgByKeyForCmd("SystemVersion") + version) + config := global.ServerConfig{} + if err := yaml.Unmarshal(conf.AppYaml, &config); err != nil { + return fmt.Errorf("unmarshal conf.App.Yaml failed, err: %v", err) + } else { + fmt.Println(i18n.GetMsgByKeyForCmd("SystemMode") + config.Base.Mode) + } + return nil + }, +} diff --git a/core/cmd/server/conf/app.yaml b/core/cmd/server/conf/app.yaml new file mode 100644 index 0000000..2427911 --- /dev/null +++ b/core/cmd/server/conf/app.yaml @@ -0,0 +1,23 @@ +base: + install_dir: /opt + mode: dev + is_demo: false + is_intl: false + is_offline: false + is_fxplay: false + port: 9999 + username: admin + password: admin123 + version: v2.0.0 + +remote_url: + app_repo: https://apps-assets.fit2cloud.com + repo_url: https://resource.fit2cloud.com/1panel/package/v2 + resource_url: https://resource.fit2cloud.com/1panel/resource/v2 + +log: + level: debug + time_zone: Asia/Shanghai + log_name: 1Panel-Core + log_suffix: .log + max_backup: 10 \ No newline at end of file diff --git a/core/cmd/server/conf/conf.go b/core/cmd/server/conf/conf.go new file mode 100644 index 0000000..6654d8b --- /dev/null +++ b/core/cmd/server/conf/conf.go @@ -0,0 +1,6 @@ +package conf + +import _ "embed" + +//go:embed app.yaml +var AppYaml []byte diff --git a/core/cmd/server/docs/docs.go b/core/cmd/server/docs/docs.go new file mode 100644 index 0000000..984dfaa --- /dev/null +++ b/core/cmd/server/docs/docs.go @@ -0,0 +1,34810 @@ +package docs + +import "github.com/swaggo/swag" + +const docTemplate = `{ + "swagger": "2.0", + "info": { + "contact": {}, + "description": "Top-Rated Web-based Linux Server Management Tool", + "license": { + "name": "GPL-3.0", + "url": "https://www.gnu.org/licenses/gpl-3.0.html" + }, + "termsOfService": "http://swagger.io/terms/", + "title": "1Panel", + "version": "2.0" + }, + "host": "", + "basePath": "/api/v2", + "paths": { + "/ai/domain/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaBindDomain" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind domain", + "tags": [ + "AI" + ] + } + }, + "/ai/domain/get": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaBindDomainReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OllamaBindDomainRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get bind domain", + "tags": [ + "AI" + ] + } + }, + "/ai/gpu/load": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load gpu / xpu info", + "tags": [ + "AI" + ] + } + }, + "/ai/mcp/domain/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpBindDomain" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind Domain for mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/domain/get": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.McpBindDomainRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get bin Domain for mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/domain/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpBindDomainUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update bind Domain for mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.McpServersRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List mcp servers", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/server": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/server/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/server/op": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/server/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/ollama/close": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaModelName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Close Ollama model conn", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "close conn for Ollama model [name]", + "formatZH": "关闭 Ollama 模型连接 [name]", + "paramKeys": [] + } + } + }, + "/ai/ollama/model": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaModelName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create Ollama model", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "add Ollama model [name]", + "formatZH": "添加 Ollama 模型 [name]", + "paramKeys": [] + } + } + }, + "/ai/ollama/model/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ForceDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete Ollama model", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "ollama_models", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "remove Ollama model [names]", + "formatZH": "删除 Ollama 模型 [names]", + "paramKeys": [] + } + } + }, + "/ai/ollama/model/load": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaModelName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page Ollama models", + "tags": [ + "AI" + ] + } + }, + "/ai/ollama/model/recreate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaModelName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Rereate Ollama model", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "re-add Ollama model [name]", + "formatZH": "添加 Ollama 模型重试 [name]", + "paramKeys": [] + } + } + }, + "/ai/ollama/model/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page Ollama models", + "tags": [ + "AI" + ] + } + }, + "/ai/ollama/model/sync": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.OllamaModelDropList" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync Ollama model list", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "sync Ollama model list", + "formatZH": "同步 Ollama 模型列表", + "paramKeys": [] + } + } + }, + "/apps/:key": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "app key", + "in": "path", + "name": "key", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app by key", + "tags": [ + "App" + ] + } + }, + "/apps/checkupdate": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppUpdateRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get app list update", + "tags": [ + "App" + ] + } + }, + "/apps/detail/:appId/:version/:type": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "app id", + "in": "path", + "name": "appId", + "required": true, + "type": "integer" + }, + { + "description": "app 版本", + "in": "path", + "name": "version", + "required": true, + "type": "string" + }, + { + "description": "app 类型", + "in": "path", + "name": "version", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppDetailDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app detail by appid", + "tags": [ + "App" + ] + } + }, + "/apps/details/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "appId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppDetailDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get app detail by id", + "tags": [ + "App" + ] + } + }, + "/apps/icon/:appId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "app id", + "in": "path", + "name": "appId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "app icon", + "schema": { + "type": "file" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get app icon by app_id", + "tags": [ + "App" + ] + } + }, + "/apps/ignored/cancel": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.ReqWithID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Cancel Ignore Upgrade App", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Cancel ignore application upgrade", + "formatZH": "取消忽略应用升级", + "paramKeys": [] + } + } + }, + "/apps/ignored/detail": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/model.AppIgnoreUpgrade" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List Upgrade Ignored App", + "tags": [ + "App" + ] + } + }, + "/apps/install": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstallCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.AppInstall" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Install app", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Install app [name]", + "formatZH": "安装应用 [name]", + "paramKeys": [] + } + } + }, + "/apps/installed/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstalledInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppInstalledCheck" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check app installed", + "tags": [ + "App" + ] + } + }, + "/apps/installed/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search default config by key", + "tags": [ + "App" + ] + } + }, + "/apps/installed/config/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppConfigUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update app config", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "installID", + "webUI" + ], + "formatEN": "Application config update [installID]", + "formatZH": "应用配置更新 [installID]", + "paramKeys": [] + } + } + }, + "/apps/installed/conninfo": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.DatabaseConn" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app password by key", + "tags": [ + "App" + ] + } + }, + "/apps/installed/delete/check/:appInstallId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "App install id", + "in": "path", + "name": "appInstallId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppResource" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before delete", + "tags": [ + "App" + ] + } + }, + "/apps/installed/ignore": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppIgnoreUpgradeReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Ignore Upgrade App", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Ignore application upgrade", + "formatZH": "忽略应用升级", + "paramKeys": [] + } + } + }, + "/apps/installed/info/:appInstallId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "App install id", + "in": "path", + "name": "appInstallId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.AppInstallInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get app install info", + "tags": [ + "App" + ] + } + }, + "/apps/installed/list": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppInstallInfo" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List app installed", + "tags": [ + "App" + ] + } + }, + "/apps/installed/loadport": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "integer" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app port by key", + "tags": [ + "App" + ] + } + }, + "/apps/installed/op": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstalledOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate installed app", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "app_installs", + "input_column": "id", + "input_value": "installId", + "isList": false, + "output_column": "app_id", + "output_value": "appId" + }, + { + "db": "app_installs", + "input_column": "id", + "input_value": "installId", + "isList": false, + "output_column": "name", + "output_value": "appName" + }, + { + "db": "apps", + "input_column": "id", + "input_value": "appId", + "isList": false, + "output_column": "key", + "output_value": "appKey" + } + ], + "bodyKeys": [ + "installId", + "operate" + ], + "formatEN": "[operate] App [appKey][appName]", + "formatZH": "[operate] 应用 [appKey][appName]", + "paramKeys": [] + } + } + }, + "/apps/installed/params/:appInstallId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "appInstallId", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search params by appInstallId", + "tags": [ + "App" + ] + } + }, + "/apps/installed/params/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstalledUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change app params", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "installId" + ], + "formatEN": "Application param update [installId]", + "formatZH": "应用参数修改 [installId]", + "paramKeys": [] + } + } + }, + "/apps/installed/port/change": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PortUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change app port", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "name", + "port" + ], + "formatEN": "Application port update [key]-[name] =\u003e [port]", + "formatZH": "应用端口修改 [key]-[name] =\u003e [port]", + "paramKeys": [] + } + } + }, + "/apps/installed/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstalledSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page app installed", + "tags": [ + "App" + ] + } + }, + "/apps/installed/sync": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync app installed", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Sync the list of installed apps", + "formatZH": "同步已安装应用列表", + "paramKeys": [] + } + } + }, + "/apps/installed/update/versions": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "appInstallId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppVersion" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app update version by install id", + "tags": [ + "App" + ] + } + }, + "/apps/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List apps", + "tags": [ + "App" + ] + } + }, + "/apps/services/:key": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "key", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.AppService" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app service by key", + "tags": [ + "App" + ] + } + }, + "/apps/sync/local": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync local app list", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "App store synchronization", + "formatZH": "应用商店同步", + "paramKeys": [] + } + } + }, + "/apps/sync/remote": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync remote app list", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "App store synchronization", + "formatZH": "应用商店同步", + "paramKeys": [] + } + } + }, + "/backups": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "create backup account [type]", + "formatZH": "创建备份账号 [type]", + "paramKeys": [] + } + } + }, + "/backups/backup": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommonBackup" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Backup system data", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "name", + "detailName" + ], + "formatEN": "backup [type] data [name][detailName]", + "formatZH": "备份 [type] 数据 [name][detailName]", + "paramKeys": [] + } + } + }, + "/backups/buckets": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ForBuckets" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "object" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List buckets", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check backup account", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "backup_accounts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "type", + "output_value": "types" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete backup account [types]", + "formatZH": "删除备份账号 [types]", + "paramKeys": [] + } + } + }, + "/backups/local": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "get local backup dir", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/options": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.BackupOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load backup account options", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/record/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete backup record", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "backup_records", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "file_name", + "output_value": "files" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete backup records [files]", + "formatZH": "删除备份记录 [files]", + "paramKeys": [] + } + } + }, + "/backups/record/description/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update backup record description", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/record/download": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DownloadRecord" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download backup record", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "source", + "fileName" + ], + "formatEN": "download backup records [source][fileName]", + "formatZH": "下载备份记录 [source][fileName]", + "paramKeys": [] + } + } + }, + "/backups/record/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RecordSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page backup records", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/record/search/bycronjob": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RecordSearchByCronjob" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page backup records by cronjob", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/record/size": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchForSize" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.RecordFileSize" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load backup record size", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/recover": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommonRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Recover system data", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "name", + "detailName", + "file" + ], + "formatEN": "recover [type] data [name][detailName] from [file]", + "formatZH": "从 [file] 恢复 [type] 数据 [name][detailName]", + "paramKeys": [] + } + } + }, + "/backups/recover/byupload": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommonRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Recover system data by upload", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "name", + "detailName", + "file" + ], + "formatEN": "recover [type] data [name][detailName] from [file]", + "formatZH": "从 [file] 恢复 [type] 数据 [name][detailName]", + "paramKeys": [] + } + } + }, + "/backups/refresh/token": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Refresh token", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchPageWithType" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search backup accounts with page", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/search/files": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List files from backup accounts", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "update backup account [types]", + "formatZH": "更新备份账号 [types]", + "paramKeys": [] + } + } + }, + "/backups/upload": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UploadForRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upload file for recover", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "filePath" + ], + "formatEN": "upload backup file [filePath]", + "formatZH": "上传备份文件 [filePath]", + "paramKeys": [] + } + } + }, + "/containers": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "image" + ], + "formatEN": "create container [name][image]", + "formatZH": "创建容器 [name][image]", + "paramKeys": [] + } + } + }, + "/containers/clean/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean container log", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "clean container [name] logs", + "formatZH": "清理容器 [name] 日志", + "paramKeys": [] + } + } + }, + "/containers/command": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerCreateByCommand" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create container by command", + "tags": [ + "Container" + ] + } + }, + "/containers/commit": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerCommit" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Commit Container", + "tags": [ + "Container" + ] + } + }, + "/containers/compose": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create compose", + "tags": [ + "Container Compose" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create compose [name]", + "formatZH": "创建 compose [name]", + "paramKeys": [] + } + } + }, + "/containers/compose/clean/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeLogClean" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean compose log", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "clean compose [name] logs", + "formatZH": "清理容器编排 [name] 日志", + "paramKeys": [] + } + } + }, + "/containers/compose/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeOperation" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate compose", + "tags": [ + "Container Compose" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "operation" + ], + "formatEN": "compose [operation] [name]", + "formatZH": "compose [operation] [name]", + "paramKeys": [] + } + } + }, + "/containers/compose/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page composes", + "tags": [ + "Container Compose" + ] + } + }, + "/containers/compose/test": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Test compose", + "tags": [ + "Container Compose" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "check compose [name]", + "formatZH": "检测 compose [name] 格式", + "paramKeys": [] + } + } + }, + "/containers/compose/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update compose", + "tags": [ + "Container Compose" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "update compose information [name]", + "formatZH": "更新 compose [name]", + "paramKeys": [] + } + } + }, + "/containers/daemonjson": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DaemonJsonConf" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load docker daemon.json", + "tags": [ + "Container Docker" + ] + } + }, + "/containers/daemonjson/file": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load docker daemon.json", + "tags": [ + "Container Docker" + ] + } + }, + "/containers/daemonjson/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update docker daemon.json", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "Updated configuration [key]", + "formatZH": "更新配置 [key]", + "paramKeys": [] + } + } + }, + "/containers/daemonjson/update/byfile": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DaemonJsonUpdateByFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update docker daemon.json by upload file", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Updated configuration file", + "formatZH": "更新配置文件", + "paramKeys": [] + } + } + }, + "/containers/docker/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DockerOperation" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate docker", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] docker service", + "formatZH": "docker 服务 [operation]", + "paramKeys": [] + } + } + }, + "/containers/docker/status": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load docker status", + "tags": [ + "Container Docker" + ] + } + }, + "/containers/image": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Options" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "load images options", + "tags": [ + "Container Image" + ] + } + }, + "/containers/image/all": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ImageInfo" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List all images", + "tags": [ + "Container Image" + ] + } + }, + "/containers/image/build": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageBuild" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Build image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "build image [name]", + "formatZH": "构建镜像 [name]", + "paramKeys": [] + } + } + }, + "/containers/image/load": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageLoad" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "load image from [path]", + "formatZH": "从 [path] 加载镜像", + "paramKeys": [] + } + } + }, + "/containers/image/pull": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImagePull" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Pull image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "repoID", + "isList": false, + "output_column": "name", + "output_value": "reponame" + } + ], + "bodyKeys": [ + "repoID", + "imageName" + ], + "formatEN": "image pull [reponame][imageName]", + "formatZH": "镜像拉取 [reponame][imageName]", + "paramKeys": [] + } + } + }, + "/containers/image/push": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImagePush" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Push image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "repoID", + "isList": false, + "output_column": "name", + "output_value": "reponame" + } + ], + "bodyKeys": [ + "repoID", + "tagName", + "name" + ], + "formatEN": "push [tagName] to [reponame][name]", + "formatZH": "[tagName] 推送到 [reponame][name]", + "paramKeys": [] + } + } + }, + "/containers/image/remove": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names" + ], + "formatEN": "remove image [names]", + "formatZH": "移除镜像 [names]", + "paramKeys": [] + } + } + }, + "/containers/image/save": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageSave" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Save image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "tagName", + "path", + "name" + ], + "formatEN": "save [tagName] as [path]/[name]", + "formatZH": "保留 [tagName] 为 [path]/[name]", + "paramKeys": [] + } + } + }, + "/containers/image/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageImage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page images", + "tags": [ + "Container Image" + ] + } + }, + "/containers/image/tag": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageTag" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Tag image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "repoID", + "isList": false, + "output_column": "name", + "output_value": "reponame" + } + ], + "bodyKeys": [ + "repoID", + "targetName" + ], + "formatEN": "tag image [reponame][targetName]", + "formatZH": "tag 镜像 [reponame][targetName]", + "paramKeys": [] + } + } + }, + "/containers/info": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ContainerOperate" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container info", + "tags": [ + "Container" + ] + } + }, + "/containers/inspect": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.InspectReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Container inspect", + "tags": [ + "Container" + ] + } + }, + "/containers/ipv6option/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LogOption" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update docker daemon.json ipv6 option", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Updated the ipv6 option", + "formatZH": "更新 ipv6 配置", + "paramKeys": [] + } + } + }, + "/containers/item/stats": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ContainerItemStats" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container stats size" + } + }, + "/containers/limit": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ResourceLimit" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container limits" + } + }, + "/containers/list": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ContainerOptions" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List containers", + "tags": [ + "Container" + ] + } + }, + "/containers/list/byimage": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ContainerOptions" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List containers by image", + "tags": [ + "Container" + ] + } + }, + "/containers/list/stats": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ContainerListStats" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container stats" + } + }, + "/containers/logoption/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LogOption" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update docker daemon.json log option", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Updated the log option", + "formatZH": "更新日志配置", + "paramKeys": [] + } + } + }, + "/containers/network": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Options" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List networks", + "tags": [ + "Container Network" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.NetworkCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create network", + "tags": [ + "Container Network" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create container network [name]", + "formatZH": "创建容器网络 name", + "paramKeys": [] + } + } + }, + "/containers/network/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete network", + "tags": [ + "Container Network" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names" + ], + "formatEN": "delete container network [names]", + "formatZH": "删除容器网络 [names]", + "paramKeys": [] + } + } + }, + "/containers/network/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page networks", + "tags": [ + "Container Network" + ] + } + }, + "/containers/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerOperation" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names", + "operation" + ], + "formatEN": "container [operation] [names]", + "formatZH": "容器 [names] 执行 [operation]", + "paramKeys": [] + } + } + }, + "/containers/prune": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerPrune" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "pruneType" + ], + "formatEN": "clean container [pruneType]", + "formatZH": "清理容器 [pruneType]", + "paramKeys": [] + } + } + }, + "/containers/rename": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerRename" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Rename Container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "newName" + ], + "formatEN": "rename container [name] =\u003e [newName]", + "formatZH": "容器重命名 [name] =\u003e [newName]", + "paramKeys": [] + } + } + }, + "/containers/repo": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ImageRepoOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List image repos", + "tags": [ + "Container Image-repo" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageRepoDelete" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create image repo", + "tags": [ + "Container Image-repo" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create image repo [name]", + "formatZH": "创建镜像仓库 [name]", + "paramKeys": [] + } + } + }, + "/containers/repo/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete image repo", + "tags": [ + "Container Image-repo" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete image repo [name]", + "formatZH": "删除镜像仓库 [name]", + "paramKeys": [] + } + } + }, + "/containers/repo/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page image repos", + "tags": [ + "Container Image-repo" + ] + } + }, + "/containers/repo/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load repo status", + "tags": [ + "Container Image-repo" + ] + } + }, + "/containers/repo/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageRepoUpdate" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update image repo", + "tags": [ + "Container Image-repo" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update image repo information [name]", + "formatZH": "更新镜像仓库 [name]", + "paramKeys": [] + } + } + }, + "/containers/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageContainer" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page containers", + "tags": [ + "Container" + ] + } + }, + "/containers/search/log": { + "get": { + "parameters": [ + { + "description": "容器名称", + "in": "query", + "name": "container", + "type": "string" + }, + { + "description": "时间筛选", + "in": "query", + "name": "since", + "type": "string" + }, + { + "description": "是否追踪", + "in": "query", + "name": "follow", + "type": "string" + }, + { + "description": "显示行号", + "in": "query", + "name": "tail", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Container logs", + "tags": [ + "Container" + ] + } + }, + "/containers/stats/:id": { + "get": { + "parameters": [ + { + "description": "容器id", + "in": "path", + "name": "id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ContainerStats" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Container stats", + "tags": [ + "Container" + ] + } + }, + "/containers/status": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ContainerStatus" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load containers status", + "tags": [ + "Container" + ] + } + }, + "/containers/template": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ComposeTemplateInfo" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List compose templates", + "tags": [ + "Container Compose-template" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeTemplateCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create compose template", + "tags": [ + "Container Compose-template" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create compose template [name]", + "formatZH": "创建 compose 模版 [name]", + "paramKeys": [] + } + } + }, + "/containers/template/batch": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeTemplateBatch" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bacth compose template", + "tags": [ + "Container Compose-template" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "batch import compose templates", + "formatZH": "批量导入编排模版", + "paramKeys": [] + } + } + }, + "/containers/template/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete compose template", + "tags": [ + "Container Compose-template" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "compose_templates", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete compose template [names]", + "formatZH": "删除 compose 模版 [names]", + "paramKeys": [] + } + } + }, + "/containers/template/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page compose templates", + "tags": [ + "Container Compose-template" + ] + } + }, + "/containers/template/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeTemplateUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update compose template", + "tags": [ + "Container Compose-template" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "compose_templates", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update compose template information [name]", + "formatZH": "更新 compose 模版 [name]", + "paramKeys": [] + } + } + }, + "/containers/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "image" + ], + "formatEN": "update container [name][image]", + "formatZH": "更新容器 [name][image]", + "paramKeys": [] + } + } + }, + "/containers/upgrade": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerUpgrade" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upgrade container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names", + "image" + ], + "formatEN": "upgrade container image [names][image]", + "formatZH": "更新容器镜像 [names][image]", + "paramKeys": [] + } + } + }, + "/containers/users": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container users", + "tags": [ + "Container" + ] + } + }, + "/containers/volume": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Options" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List volumes", + "tags": [ + "Container Volume" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.VolumeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create volume", + "tags": [ + "Container Volume" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create container volume [name]", + "formatZH": "创建容器存储卷 [name]", + "paramKeys": [] + } + } + }, + "/containers/volume/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete volume", + "tags": [ + "Container Volume" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names" + ], + "formatEN": "delete container volume [names]", + "formatZH": "删除容器存储卷 [names]", + "paramKeys": [] + } + } + }, + "/containers/volume/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page volumes", + "tags": [ + "Container Volume" + ] + } + }, + "/core/auth/captcha": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CaptchaResponse" + } + } + }, + "summary": "Load captcha", + "tags": [ + "Auth" + ] + } + }, + "/core/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "安全入口 base64 加密串", + "in": "header", + "name": "EntranceCode", + "required": true, + "type": "string" + }, + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Login" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserLoginInfo" + } + } + }, + "summary": "User login", + "tags": [ + "Auth" + ] + } + }, + "/core/auth/logout": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "User logout", + "tags": [ + "Auth" + ] + } + }, + "/core/auth/mfalogin": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MFALogin" + } + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "EntranceCode": { + "description": "安全入口", + "type": "string" + } + }, + "schema": { + "$ref": "#/definitions/dto.UserLoginInfo" + } + } + }, + "summary": "User login with mfa", + "tags": [ + "Auth" + ] + } + }, + "/core/auth/setting": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SystemSetting" + } + } + }, + "summary": "Get Setting For Login", + "tags": [ + "Auth" + ] + } + }, + "/core/backups": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "create backup account [type]", + "formatZH": "创建备份账号 [type]", + "paramKeys": [] + } + } + }, + "/core/backups/client/:clientType": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.BackupClientInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load backup account base info", + "tags": [ + "Backup Account" + ] + } + }, + "/core/backups/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "delete backup account [name]", + "formatZH": "删除备份账号 [name]", + "paramKeys": [] + } + } + }, + "/core/backups/refresh/token": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Refresh token", + "tags": [ + "Backup Account" + ] + } + }, + "/core/backups/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "update backup account [types]", + "formatZH": "更新备份账号 [types]", + "paramKeys": [] + } + } + }, + "/core/commands": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommandOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "command" + ], + "formatEN": "create quick command [name][command]", + "formatZH": "创建快捷命令 [name][command]", + "paramKeys": [] + } + } + }, + "/core/commands/command": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CommandInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List commands", + "tags": [ + "Command" + ] + } + }, + "/core/commands/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByIDs" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "commands", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete quick command [names]", + "formatZH": "删除快捷命令 [names]", + "paramKeys": [] + } + } + }, + "/core/commands/export": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Export command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "export quick commands", + "formatZH": "导出快速命令", + "paramKeys": [] + } + } + }, + "/core/commands/import": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Import command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "import quick commands", + "formatZH": "导入快速命令", + "paramKeys": [] + } + } + }, + "/core/commands/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page commands", + "tags": [ + "Command" + ] + } + }, + "/core/commands/tree": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.CommandTree" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Tree commands", + "tags": [ + "Command" + ] + } + }, + "/core/commands/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommandOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "update quick command [name]", + "formatZH": "更新快捷命令 [name]", + "paramKeys": [] + } + } + }, + "/core/groups": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "create group [name][type]", + "formatZH": "创建组 [name][type]", + "paramKeys": [] + } + } + }, + "/core/groups/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "groups", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + }, + { + "db": "groups", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "type", + "output_value": "type" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete group [type][name]", + "formatZH": "删除组 [type][name]", + "paramKeys": [] + } + } + }, + "/core/groups/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.OperateByType" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List groups", + "tags": [ + "System Group" + ] + } + }, + "/core/groups/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "update group [name][type]", + "formatZH": "更新组 [name][type]", + "paramKeys": [] + } + } + }, + "/core/hosts": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.HostOperate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.HostInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create host", + "tags": [ + "Host" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "addr" + ], + "formatEN": "create host [name][addr]", + "formatZH": "创建主机 [name][addr]", + "paramKeys": [] + } + } + }, + "/core/hosts/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByIDs" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete host", + "tags": [ + "Host" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "hosts", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "addr", + "output_value": "addrs" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete host [addrs]", + "formatZH": "删除主机 [addrs]", + "paramKeys": [] + } + } + }, + "/core/hosts/info": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.HostInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get host info", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchPageWithGroup" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page host", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/test/byid/:id": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Test host conn by host id", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/test/byinfo": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.HostConnTest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Test host conn by info", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/tree": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchForTree" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.HostTree" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host tree", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.HostOperate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.HostInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host", + "tags": [ + "Host" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "addr" + ], + "formatEN": "update host [name][addr]", + "formatZH": "更新主机信息 [name][addr]", + "paramKeys": [] + } + } + }, + "/core/hosts/update/group": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeHostGroup" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host group", + "tags": [ + "Host" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "hosts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "addr", + "output_value": "addr" + } + ], + "bodyKeys": [ + "id", + "group" + ], + "formatEN": "change host [addr] group =\u003e [group]", + "formatZH": "切换主机[addr]分组 =\u003e [group]", + "paramKeys": [] + } + } + }, + "/core/logs/clean": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CleanLog" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean operation logs", + "tags": [ + "Logs" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "logType" + ], + "formatEN": "Clean the [logType] log information", + "formatZH": "清空 [logType] 日志信息", + "paramKeys": [] + } + } + }, + "/core/logs/login": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchLgLogWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page login logs", + "tags": [ + "Logs" + ] + } + }, + "/core/logs/operation": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchOpLogWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page operation logs", + "tags": [ + "Logs" + ] + } + }, + "/core/script": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ScriptOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Add script", + "tags": [ + "ScriptLibrary" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "add script [name]", + "formatZH": "添加脚本库脚本 [name]", + "paramKeys": [] + } + } + }, + "/core/script/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByIDs" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete script", + "tags": [ + "ScriptLibrary" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "script_librarys", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete script [names]", + "formatZH": "删除脚本库脚本 [names]", + "paramKeys": [] + } + } + }, + "/core/script/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchPageWithGroup" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page script", + "tags": [ + "ScriptLibrary" + ] + } + }, + "/core/script/sync": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByTaskID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync script from remote", + "tags": [ + "ScriptLibrary" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "sync scripts", + "formatZH": "同步脚本库脚本", + "paramKeys": [] + } + } + }, + "/core/script/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ScriptOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update script", + "tags": [ + "ScriptLibrary" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update script [name]", + "formatZH": "更新脚本库脚本 [name]", + "paramKeys": [] + } + } + }, + "/core/settings/api/config/generate/key": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "generate api key", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "generate api key", + "formatZH": "生成 API 接口密钥", + "paramKeys": [] + } + } + }, + "/core/settings/api/config/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ApiInterfaceConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update api config", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "ipWhiteList" + ], + "formatEN": "update api config =\u003e IP White List: [ipWhiteList]", + "formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList]", + "paramKeys": [] + } + } + }, + "/core/settings/apps/store/config": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.AppstoreConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get appstore config", + "tags": [ + "App" + ] + } + }, + "/core/settings/apps/store/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.AppstoreUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update appstore config", + "tags": [ + "App" + ] + } + }, + "/core/settings/bind/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BindInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system bind info", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "ipv6", + "bindAddress" + ], + "formatEN": "update system bind info =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]", + "formatZH": "修改系统监听信息 =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]", + "paramKeys": [] + } + } + }, + "/core/settings/by": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system setting by key", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/expired/handle": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PasswordUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Reset system password expired", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "reset an expired Password", + "formatZH": "重置过期密码", + "paramKeys": [] + } + } + }, + "/core/settings/interface": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system address", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/menu/default": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Default menu", + "tags": [ + "Menu Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Init menu.", + "formatZH": "初始化菜单", + "paramKeys": [] + } + } + }, + "/core/settings/menu/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Hide advanced feature menu.", + "formatZH": "隐藏高级功能菜单", + "paramKeys": [] + } + } + }, + "/core/settings/mfa": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MfaCredential" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/mfa.Otp" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mfa info", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/mfa/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MfaCredential" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind mfa", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "bind mfa", + "formatZH": "mfa 绑定", + "paramKeys": [] + } + } + }, + "/core/settings/password/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PasswordUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system password", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "update system password", + "formatZH": "修改系统密码", + "paramKeys": [] + } + } + }, + "/core/settings/port/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PortUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system port", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "serverPort" + ], + "formatEN": "update system port =\u003e [serverPort]", + "formatZH": "修改系统端口 =\u003e [serverPort]", + "paramKeys": [] + } + } + }, + "/core/settings/proxy/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ProxyUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update proxy setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "proxyUrl", + "proxyPort" + ], + "formatEN": "set proxy [proxyPort]:[proxyPort].", + "formatZH": "服务器代理配置 [proxyPort]:[proxyPort]", + "paramKeys": [] + } + } + }, + "/core/settings/search": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SettingInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system setting info", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/search/available": { + "get": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system available status", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/ssl/download": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download system cert", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/ssl/info": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SSLInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system cert info", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/ssl/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SSLUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system ssl", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "ssl" + ], + "formatEN": "update system ssl =\u003e [ssl]", + "formatZH": "修改系统 ssl =\u003e [ssl]", + "paramKeys": [] + } + } + }, + "/core/settings/terminal/search": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.TerminalInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system terminal setting info", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/terminal/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TerminalInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system terminal setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "update system terminal setting", + "formatZH": "修改系统终端配置", + "paramKeys": [] + } + } + }, + "/core/settings/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update system setting [key] =\u003e [value]", + "formatZH": "修改系统配置 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/core/settings/upgrade": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UpgradeInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load upgrade info", + "tags": [ + "System Setting" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Upgrade" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upgrade", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "version" + ], + "formatEN": "upgrade system =\u003e [version]", + "formatZH": "更新系统 =\u003e [version]", + "paramKeys": [] + } + } + }, + "/core/settings/upgrade/notes": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Upgrade" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load release notes by version", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/upgrade/releases": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ReleasesNotes" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load upgrade notes", + "tags": [ + "System Setting" + ] + } + }, + "/cronjobs": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create cronjob", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "name" + ], + "formatEN": "create cronjob [type][name]", + "formatZH": "创建计划任务 [type][name]", + "paramKeys": [] + } + } + }, + "/cronjobs/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobBatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete cronjob", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete cronjob [names]", + "formatZH": "删除计划任务 [names]", + "paramKeys": [] + } + } + }, + "/cronjobs/export": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByIDs" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Export cronjob list", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/group/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeGroup" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update cronjob group", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update cronjob group [name]", + "formatZH": "更新计划任务分组 [name]", + "paramKeys": [] + } + } + }, + "/cronjobs/handle": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Handle cronjob once", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "manually execute the cronjob [name]", + "formatZH": "手动执行计划任务 [name]", + "paramKeys": [] + } + } + }, + "/cronjobs/import": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobImport" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Import cronjob list", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/load/info": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load cronjob info", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/next": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobSpec" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load cronjob spec time", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/records/clean": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobClean" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean job records", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "cronjobID", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "cronjobID" + ], + "formatEN": "clean cronjob [name] records", + "formatZH": "清空计划任务记录 [name]", + "paramKeys": [] + } + } + }, + "/cronjobs/records/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load Cronjob record log", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/script/options": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ScriptOptions" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load script options", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageCronjob" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page cronjobs", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/search/records": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchRecord" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page job records", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobUpdateStatus" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update cronjob status", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "status" + ], + "formatEN": "change the status of cronjob [name] to [status].", + "formatZH": "修改计划任务 [name] 状态为 [status]", + "paramKeys": [] + } + } + }, + "/cronjobs/stop": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Handle stop job", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update cronjob", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update cronjob [name]", + "formatZH": "更新计划任务 [name]", + "paramKeys": [] + } + } + }, + "/dashboard/app/launcher": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppLauncher" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load app launcher", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/app/launcher/option": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchByFilter" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.LauncherOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load app launcher options", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/app/launcher/show": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update app Launcher", + "tags": [ + "Dashboard" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "app launcher [key] =\u003e show: [value]", + "formatZH": "首页应用 [key] =\u003e 显示:[value]", + "paramKeys": [] + } + } + }, + "/dashboard/base/:ioOption/:netOption": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "ioOption", + "required": true, + "type": "string" + }, + { + "description": "request", + "in": "path", + "name": "netOption", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DashboardBase" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load dashboard base info", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/base/os": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OsInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load os info", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/current/:ioOption/:netOption": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "ioOption", + "required": true, + "type": "string" + }, + { + "description": "request", + "in": "path", + "name": "netOption", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DashboardCurrent" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load dashboard current info", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/current/node": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.NodeCurrent" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load dashboard current info for node", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/current/top/cpu": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Process" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load top cpu processes", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/current/top/mem": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Process" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load top memory processes", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/quick/change": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeQuicks" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update quick jump", + "tags": [ + "Dashboard" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "change quick jump", + "formatZH": "切换快速跳转", + "paramKeys": [] + } + } + }, + "/dashboard/quick/option": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.QuickJump" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load quick jump options", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/system/restart/:operation": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "operation", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "System restart", + "tags": [ + "Dashboard" + ] + } + }, + "/databases": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlDBCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create mysql database", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create mysql database [name]", + "formatZH": "创建 mysql 数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BindUser" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind user of mysql database", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "database", + "username" + ], + "formatEN": "bind mysql database [database] [username]", + "formatZH": "绑定 mysql 数据库名 [database] [username]", + "paramKeys": [] + } + } + }, + "/databases/change/access": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeDBInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change mysql access", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_mysqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update database [name] access", + "formatZH": "更新数据库 [name] 访问权限", + "paramKeys": [] + } + } + }, + "/databases/change/password": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeDBInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change mysql password", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_mysqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update database [name] password", + "formatZH": "更新数据库 [name] 密码", + "paramKeys": [] + } + } + }, + "/databases/common/info": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DBBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load base info", + "tags": [ + "Database Common" + ] + } + }, + "/databases/common/load/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load Database conf", + "tags": [ + "Database Common" + ] + } + }, + "/databases/common/update/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DBConfUpdateByFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update conf by upload file", + "tags": [ + "Database Common" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "database" + ], + "formatEN": "update the [type] [database] database configuration information", + "formatZH": "更新 [type] 数据库 [database] 配置信息", + "paramKeys": [] + } + } + }, + "/databases/db": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create database", + "tags": [ + "Database" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "create database [name][type]", + "formatZH": "创建远程数据库 [name][type]", + "paramKeys": [] + } + } + }, + "/databases/db/:name": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DatabaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get databases", + "tags": [ + "Database" + ] + } + }, + "/databases/db/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check database", + "tags": [ + "Database" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "check if database [name][type] is connectable", + "formatZH": "检测远程数据库 [name][type] 连接性", + "paramKeys": [] + } + } + }, + "/databases/db/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete database", + "tags": [ + "Database" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "databases", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete database [names]", + "formatZH": "删除远程数据库 [names]", + "paramKeys": [] + } + } + }, + "/databases/db/del/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before delete remote database", + "tags": [ + "Database" + ] + } + }, + "/databases/db/item/:type": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.DatabaseItem" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List databases", + "tags": [ + "Database" + ] + } + }, + "/databases/db/list/:type": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.DatabaseOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List databases", + "tags": [ + "Database" + ] + } + }, + "/databases/db/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page databases", + "tags": [ + "Database" + ] + } + }, + "/databases/db/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update database", + "tags": [ + "Database" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "update database [name]", + "formatZH": "更新远程数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlDBDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete mysql database", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_mysqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete mysql database [name]", + "formatZH": "删除 mysql 数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/del/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlDBDeleteCheck" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before delete mysql database", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/description/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update mysql database description", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_mysqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "description" + ], + "formatEN": "The description of the mysql database [name] is modified =\u003e [description]", + "formatZH": "mysql 数据库 [name] 描述信息修改 [description]", + "paramKeys": [] + } + } + }, + "/databases/format/options": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.MysqlFormatCollationOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List mysql database format collation options", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/load": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlLoadDB" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mysql database from remote", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/pg": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlDBCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create postgresql database", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create postgresql database [name]", + "formatZH": "创建 postgresql 数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/pg/:database/load": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlLoadDB" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load postgresql database from remote", + "tags": [ + "Database PostgreSQL" + ] + } + }, + "/databases/pg/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlBindUser" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind postgresql user", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "username" + ], + "formatEN": "bind postgresql database [name] user [username]", + "formatZH": "绑定 postgresql 数据库 [name] 用户 [username]", + "paramKeys": [] + } + } + }, + "/databases/pg/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlDBDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete postgresql database", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_postgresqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete postgresql database [name]", + "formatZH": "删除 postgresql 数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/pg/del/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlDBDeleteCheck" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before delete postgresql database", + "tags": [ + "Database PostgreSQL" + ] + } + }, + "/databases/pg/description": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update postgresql database description", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_postgresqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "description" + ], + "formatEN": "The description of the postgresql database [name] is modified =\u003e [description]", + "formatZH": "postgresql 数据库 [name] 描述信息修改 [description]", + "paramKeys": [] + } + } + }, + "/databases/pg/password": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeDBInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change postgresql password", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_postgresqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update database [name] password", + "formatZH": "更新数据库 [name] 密码", + "paramKeys": [] + } + } + }, + "/databases/pg/privileges": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeDBInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change postgresql privileges", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "database", + "username" + ], + "formatEN": "Update [user] privileges of database [database]", + "formatZH": "更新数据库 [database] 用户 [username] 权限", + "paramKeys": [] + } + } + }, + "/databases/pg/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlDBSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page postgresql databases", + "tags": [ + "Database PostgreSQL" + ] + } + }, + "/databases/redis/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LoadRedisStatus" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.RedisConf" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load redis conf", + "tags": [ + "Database Redis" + ] + } + }, + "/databases/redis/conf/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RedisConfUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update redis conf", + "tags": [ + "Database Redis" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "update the redis database configuration information", + "formatZH": "更新 redis 数据库配置信息", + "paramKeys": [] + } + } + }, + "/databases/redis/install/cli": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Install redis-cli", + "tags": [ + "Database Redis" + ] + } + }, + "/databases/redis/password": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeRedisPass" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change redis password", + "tags": [ + "Database Redis" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "change the password of the redis database", + "formatZH": "修改 redis 数据库密码", + "paramKeys": [] + } + } + }, + "/databases/redis/persistence/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LoadRedisStatus" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.RedisPersistence" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load redis persistence conf", + "tags": [ + "Database Redis" + ] + } + }, + "/databases/redis/persistence/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RedisConfPersistenceUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update redis persistence conf", + "tags": [ + "Database Redis" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "redis database persistence configuration update", + "formatZH": "redis 数据库持久化配置更新", + "paramKeys": [] + } + } + }, + "/databases/redis/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LoadRedisStatus" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.RedisStatus" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load redis status info", + "tags": [ + "Database Redis" + ] + } + }, + "/databases/remote": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mysql remote access", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlDBSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page mysql databases", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.MysqlStatus" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mysql status info", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/variables": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.MysqlVariables" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mysql variables info", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/variables/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlVariablesUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update mysql variables", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "adjust mysql database performance parameters", + "formatZH": "调整 mysql 数据库性能参数", + "paramKeys": [] + } + } + }, + "/files": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Create dir or file [path]", + "formatZH": "创建文件/文件夹 [path]", + "paramKeys": [] + } + } + }, + "/files/batch/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FilePathsCheck" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.ExistFileInfo" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch check file exist", + "tags": [ + "File" + ] + } + }, + "/files/batch/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileBatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch delete file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "paths" + ], + "formatEN": "Batch delete dir or file [paths]", + "formatZH": "批量删除文件/文件夹 [paths]", + "paramKeys": [] + } + } + }, + "/files/batch/role": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileRoleReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch change file mode and owner", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "paths", + "mode", + "user", + "group" + ], + "formatEN": "Batch change file mode and owner [paths] =\u003e [mode]/[user]/[group]", + "formatZH": "批量修改文件权限和用户/组 [paths] =\u003e [mode]/[user]/[group]", + "paramKeys": [] + } + } + }, + "/files/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FilePathCheck" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check file exist", + "tags": [ + "File" + ] + } + }, + "/files/chunkdownload": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileDownload" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Chunk Download file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Download file [name]", + "formatZH": "下载文件 [name]", + "paramKeys": [] + } + } + }, + "/files/chunkupload": { + "post": { + "parameters": [ + { + "description": "request", + "in": "formData", + "name": "file", + "required": true, + "type": "file" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "ChunkUpload file", + "tags": [ + "File" + ] + } + }, + "/files/compress": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileCompress" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Compress file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Compress file [name]", + "formatZH": "压缩文件 [name]", + "paramKeys": [] + } + } + }, + "/files/content": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileContentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load file content", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Load file content [path]", + "formatZH": "获取文件内容 [path]", + "paramKeys": [] + } + } + }, + "/files/convert": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileConvert" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Convert file", + "tags": [ + "File" + ] + } + }, + "/files/convert/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Convert file", + "tags": [ + "File" + ] + } + }, + "/files/decompress": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileDeCompress" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Decompress file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Decompress file [path]", + "formatZH": "解压 [path]", + "paramKeys": [] + } + } + }, + "/files/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Delete dir or file [path]", + "formatZH": "删除文件/文件夹 [path]", + "paramKeys": [] + } + } + }, + "/files/depth/size": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DirSizeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.DepthDirSizeRes" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Multi file size", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Multi file size [path]", + "formatZH": "获取目录及其第一层子目录文件夹大小 [path]", + "paramKeys": [] + } + } + }, + "/files/download": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download file", + "tags": [ + "File" + ] + } + }, + "/files/favorite": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FavoriteCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Favorite" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create favorite", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "收藏文件/文件夹 [path]", + "formatZH": "收藏文件/文件夹 [path]", + "paramKeys": [] + } + } + }, + "/files/favorite/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FavoriteDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete favorite", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "favorites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "path", + "output_value": "path" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete avorite [path]", + "formatZH": "删除收藏 [path]", + "paramKeys": [] + } + } + }, + "/files/favorite/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List favorites", + "tags": [ + "File" + ] + } + }, + "/files/mode": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change file mode", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path", + "mode" + ], + "formatEN": "Change mode [paths] =\u003e [mode]", + "formatZH": "修改权限 [paths] =\u003e [mode]", + "paramKeys": [] + } + } + }, + "/files/mount": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DiskInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "system mount", + "tags": [ + "File" + ] + } + }, + "/files/move": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileMove" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Move file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "oldPaths", + "newPath" + ], + "formatEN": "Move [oldPaths] =\u003e [newPath]", + "formatZH": "移动文件 [oldPaths] =\u003e [newPath]", + "paramKeys": [] + } + } + }, + "/files/owner": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileRoleUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change file owner", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path", + "user", + "group" + ], + "formatEN": "Change owner [paths] =\u003e [user]/[group]", + "formatZH": "修改用户/组 [paths] =\u003e [user]/[group]", + "paramKeys": [] + } + } + }, + "/files/preview": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileContentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Preview file content", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Preview file content [path]", + "formatZH": "预览文件内容 [path]", + "paramKeys": [] + } + } + }, + "/files/read": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileReadByLineReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileLineContent" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Read file by Line", + "tags": [ + "File" + ] + } + }, + "/files/recycle/clear": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clear RecycleBin files", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "清空回收站", + "formatZH": "清空回收站", + "paramKeys": [] + } + } + }, + "/files/recycle/reduce": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RecycleBinReduce" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Reduce RecycleBin files", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Reduce RecycleBin file [name]", + "formatZH": "还原回收站文件 [name]", + "paramKeys": [] + } + } + }, + "/files/recycle/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List RecycleBin files", + "tags": [ + "File" + ] + } + }, + "/files/recycle/status": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get RecycleBin status", + "tags": [ + "File" + ] + } + }, + "/files/rename": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileRename" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change file name", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "oldName", + "newName" + ], + "formatEN": "Rename [oldName] =\u003e [newName]", + "formatZH": "重命名 [oldName] =\u003e [newName]", + "paramKeys": [] + } + } + }, + "/files/save": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileEdit" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update file content", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Update file content [path]", + "formatZH": "更新文件内容 [path]", + "paramKeys": [] + } + } + }, + "/files/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileOption" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List files", + "tags": [ + "File" + ] + } + }, + "/files/size": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DirSizeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.DirSizeRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load file size", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Load file size [path]", + "formatZH": "获取文件夹大小 [path]", + "paramKeys": [] + } + } + }, + "/files/tree": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileOption" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.FileTree" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load files tree", + "tags": [ + "File" + ] + } + }, + "/files/upload": { + "post": { + "parameters": [ + { + "description": "request", + "in": "formData", + "name": "file", + "required": true, + "type": "file" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upload file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path", + "file" + ], + "formatEN": "Upload file [path]/[file]", + "formatZH": "上传文件 [path]/[file]", + "paramKeys": [] + } + } + }, + "/files/upload/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.SearchUploadWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page file", + "tags": [ + "File" + ] + } + }, + "/files/user/group": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.UserGroupResponse" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "system user and group", + "tags": [ + "File" + ] + } + }, + "/files/wget": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileWget" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileWgetRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Wget file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "url", + "path", + "name" + ], + "formatEN": "Download url =\u003e [path]/[name]", + "formatZH": "下载 url =\u003e [path]/[name]", + "paramKeys": [] + } + } + }, + "/groups": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "create group [name][type]", + "formatZH": "创建组 [name][type]", + "paramKeys": [] + } + } + }, + "/groups/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "groups", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + }, + { + "db": "groups", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "type", + "output_value": "type" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete group [type][name]", + "formatZH": "删除组 [type][name]", + "paramKeys": [] + } + } + }, + "/groups/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.OperateByType" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List groups", + "tags": [ + "System Group" + ] + } + }, + "/groups/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "update group [name][type]", + "formatZH": "更新组 [name][type]", + "paramKeys": [] + } + } + }, + "/hosts/components/{name}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "Component name to check (e.g., rsync, docker)", + "in": "path", + "name": "name", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.ComponentInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check if a system component exists", + "tags": [ + "Host" + ] + } + }, + "/hosts/disks": { + "get": { + "description": "Get information about all disks including partitioned and unpartitioned disks", + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.CompleteDiskInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get complete disk information", + "tags": [ + "Disk Management" + ] + } + }, + "/hosts/disks/mount": { + "post": { + "consumes": [ + "application/json" + ], + "description": "Mount partition to specified mount point", + "parameters": [ + { + "description": "mount request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DiskMountRequest" + } + } + ], + "responses": { + "200": { + "description": "Disk mounted successfully", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Mount disk", + "tags": [ + "Disk Management" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "device", + "mountPoint" + ], + "formatEN": "Mount disk [device] to [mountPoint]", + "formatZH": "挂载磁盘 [device] 到 [mountPoint]", + "paramKeys": [] + } + } + }, + "/hosts/disks/partition": { + "post": { + "consumes": [ + "application/json" + ], + "description": "Create partition and format disk with specified filesystem", + "parameters": [ + { + "description": "partition request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DiskPartitionRequest" + } + } + ], + "responses": { + "200": { + "description": "Partition created successfully", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Partition disk", + "tags": [ + "Disk Management" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "device", + "filesystem", + "mountPoint" + ], + "formatEN": "Partition disk [device] with filesystem [filesystem], mount point [mountPoint]", + "formatZH": "对磁盘 [device] 进行分区,文件系统 [filesystem],挂载点 [mountPoint]", + "paramKeys": [] + } + } + }, + "/hosts/disks/unmount": { + "post": { + "consumes": [ + "application/json" + ], + "description": "Unmount partition from mount point", + "parameters": [ + { + "description": "unmount request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DiskUnmountRequest" + } + } + ], + "responses": { + "200": { + "description": "Disk unmounted successfully", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Unmount disk", + "tags": [ + "Disk Management" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "device", + "mountPoint" + ], + "formatEN": "Unmount disk [device] from [mountPoint]", + "formatZH": "卸载磁盘 [device] 从 [mountPoint]", + "paramKeys": [] + } + } + }, + "/hosts/firewall/base": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FirewallBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load firewall base info", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/batch": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchRuleOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch operate rule", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/filter/chain/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "load chain status with name", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/filter/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.IptablesOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Apply/Unload/Init iptables filter", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] iptables filter firewall", + "formatZH": "[operate] iptables filter 防火墙", + "paramKeys": [] + } + } + }, + "/hosts/firewall/filter/rule/batch": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.IptablesBatchOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch operate iptables filter rules", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/filter/rule/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.IptablesRuleOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate iptables filter rule", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation", + "chain" + ], + "formatEN": "[operation] filter rule to [chain]", + "formatZH": "[operation] filter规则到 [chain]", + "paramKeys": [] + } + } + }, + "/hosts/firewall/filter/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchPageWithType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "search iptables filter rules", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/forward": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ForwardRuleOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate forward rule", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "update port forward rules", + "formatZH": "更新端口转发规则", + "paramKeys": [] + } + } + }, + "/hosts/firewall/ip": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.AddrRuleOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Ip rule", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "strategy", + "address" + ], + "formatEN": "create address rules [strategy][address]", + "formatZH": "添加 ip 规则 [strategy] [address]", + "paramKeys": [] + } + } + }, + "/hosts/firewall/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FirewallOperation" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate firewall", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] firewall", + "formatZH": "[operation] 防火墙", + "paramKeys": [] + } + } + }, + "/hosts/firewall/port": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PortRuleOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create group", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "port", + "strategy" + ], + "formatEN": "create port rules [strategy][port]", + "formatZH": "添加端口规则 [strategy] [port]", + "paramKeys": [] + } + } + }, + "/hosts/firewall/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RuleSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page firewall rules", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/update/addr": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.AddrRuleUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Ip rule", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/update/description": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateFirewallDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update rule description", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/update/port": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PortRuleUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update port rule", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/monitor/clean": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean monitor data", + "tags": [ + "Monitor" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "clean monitor datas", + "formatZH": "清空监控数据", + "paramKeys": [] + } + } + }, + "/hosts/monitor/gpu/search": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MonitorGPUSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.MonitorGPUData" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load monitor data", + "tags": [ + "Monitor" + ] + } + }, + "/hosts/monitor/search": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MonitorSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.MonitorData" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load monitor data", + "tags": [ + "Monitor" + ] + } + }, + "/hosts/monitor/setting": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.MonitorSetting" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load monitor setting", + "tags": [ + "Monitor" + ] + } + }, + "/hosts/monitor/setting/update": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MonitorSettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update monitor setting", + "tags": [ + "Monitor" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update default monitor [name]-[value]", + "formatZH": "修改默认监控网卡 [name]-[value]", + "paramKeys": [] + } + } + }, + "/hosts/ssh/cert": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RootCertOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Generate host SSH secret", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "generate SSH secret", + "formatZH": "生成 SSH 密钥 ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/cert/delete": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ForceDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete host SSH secret", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "delete SSH secret", + "formatZH": "删除 SSH 密钥 ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/cert/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host SSH secret", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/cert/sync": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sycn host SSH secret", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "sync SSH secret", + "formatZH": "同步 SSH 密钥 ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/cert/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RootCertOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host SSH secret", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "generate SSH secret", + "formatZH": "生成 SSH 密钥 ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host SSH conf", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/file/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SSHConf" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host SSH setting by file", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key" + ], + "formatEN": "update SSH conf [key]", + "formatZH": "修改 SSH 配置文件 [key]", + "paramKeys": [] + } + } + }, + "/hosts/ssh/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchSSHLog" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host SSH logs", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/log/export": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchSSHLog" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Export host SSH logs", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate SSH", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] SSH", + "formatZH": "[operation] SSH ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/search": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SSHInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host SSH setting info", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SSHUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host SSH setting", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "newValue" + ], + "formatEN": "update SSH setting [key] =\u003e [newValue]", + "formatZH": "修改 SSH 配置 [key] =\u003e [newValue]", + "paramKeys": [] + } + } + }, + "/hosts/tool": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.HostToolReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.HostToolRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get tool status", + "tags": [ + "Host tool" + ] + } + }, + "/hosts/tool/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.HostToolConfig" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.HostToolConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get tool config", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] tool config", + "formatZH": "[operate] 主机工具配置文件 ", + "paramKeys": [] + } + } + }, + "/hosts/tool/init": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.HostToolCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create Host tool Config", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "create [type] config", + "formatZH": "创建 [type] 配置", + "paramKeys": [] + } + } + }, + "/hosts/tool/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.HostToolReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate tool", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate", + "type" + ], + "formatEN": "[operate] [type]", + "formatZH": "[operate] [type] ", + "paramKeys": [] + } + } + }, + "/hosts/tool/supervisor/process": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.SupervisorProcessConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Supervisor process config", + "tags": [ + "Host tool" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.SupervisorProcessConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create Supervisor process", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] process", + "formatZH": "[operate] 守护进程 ", + "paramKeys": [] + } + } + }, + "/hosts/tool/supervisor/process/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.SupervisorProcessFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Supervisor process config file", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] Supervisor Process Config file", + "formatZH": "[operate] Supervisor 进程文件 ", + "paramKeys": [] + } + } + }, + "/logs/system/files": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system log files", + "tags": [ + "Logs" + ] + } + }, + "/logs/tasks/executing/count": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "integer" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get the number of executing tasks", + "tags": [ + "TaskLog" + ] + } + }, + "/logs/tasks/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchTaskLogReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page task logs", + "tags": [ + "TaskLog" + ] + } + }, + "/openresty": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxFile" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load OpenResty conf", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/build": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxBuildReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Build OpenResty", + "tags": [ + "OpenResty" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Build OpenResty", + "formatZH": "构建 OpenResty", + "paramKeys": [] + } + } + }, + "/openresty/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxConfigFileUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update OpenResty conf by upload file", + "tags": [ + "OpenResty" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Update nginx conf", + "formatZH": "更新 nginx 配置", + "paramKeys": [] + } + } + }, + "/openresty/https": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxConfigRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get default HTTPs status", + "tags": [ + "OpenResty" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxDefaultHTTPSUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate default HTTPs", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/modules": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxBuildConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get OpenResty modules", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/modules/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxModuleUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update OpenResty module", + "tags": [ + "OpenResty" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Update OpenResty module", + "formatZH": "更新 OpenResty 模块", + "paramKeys": [] + } + } + }, + "/openresty/scope": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxScopeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.NginxParam" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load partial OpenResty conf", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/status": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxStatus" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load OpenResty status info", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxConfigUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update OpenResty conf", + "tags": [ + "OpenResty" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "Update nginx conf [domain]", + "formatZH": "更新 nginx 配置 [domain]", + "paramKeys": [] + } + } + }, + "/process/stop": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.ProcessReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Stop Process", + "tags": [ + "Process" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "PID" + ], + "formatEN": "结束进程 [PID]", + "formatZH": "结束进程 [PID]", + "paramKeys": [] + } + } + }, + "/process/{pid}": { + "get": { + "parameters": [ + { + "description": "PID", + "in": "path", + "name": "pid", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Process Info By PID", + "tags": [ + "Process" + ] + } + }, + "/runtimes": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Runtime" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create runtime", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Create runtime [name]", + "formatZH": "创建运行环境 [name]", + "paramKeys": [] + } + } + }, + "/runtimes/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.RuntimeDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get runtime", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete runtime", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete runtime [name]", + "formatZH": "删除运行环境 [name]", + "paramKeys": [] + } + } + }, + "/runtimes/installed/delete/check/:id": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppResource" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete runtime", + "tags": [ + "Website" + ] + } + }, + "/runtimes/node/modules": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodeModuleReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.NodeModule" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Node modules", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/node/modules/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodeModuleReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Node modules", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/node/package": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodePackageReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.PackageScripts" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Node package scripts", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate runtime", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "id" + ], + "formatEN": "Operate runtime [name]", + "formatZH": "操作运行环境 [name]", + "paramKeys": [] + } + } + }, + "/runtimes/php/:id/extensions": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.PHPExtensionRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get php runtime extension", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPConfigUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update runtime php conf", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "[domain] PHP conf update", + "formatZH": "[domain] PHP 配置修改", + "paramKeys": [] + } + } + }, + "/runtimes/php/config/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.PHPConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load php runtime conf", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/container/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.PHPContainerConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get PHP container config", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/container/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPContainerConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update PHP container config", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/extensions": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionsCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create Extensions", + "tags": [ + "PHP Extensions" + ] + } + }, + "/runtimes/php/extensions/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionsDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete Extensions", + "tags": [ + "PHP Extensions" + ] + } + }, + "/runtimes/php/extensions/install": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionInstallReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Install php extension", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/extensions/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionsSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page Extensions", + "tags": [ + "PHP Extensions" + ] + } + }, + "/runtimes/php/extensions/uninstall": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionInstallReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "UnInstall php extension", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/extensions/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionsUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Extensions", + "tags": [ + "PHP Extensions" + ] + } + }, + "/runtimes/php/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get php conf file", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/fpm/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FPMConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update fpm config", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/fpm/config/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.FPMConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get fpm config", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/fpm/status/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "additionalProperties": true, + "type": "object" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get PHP runtime status", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPFileUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update php conf file", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "Nginx conf update [domain]", + "formatZH": "php 配置修改 [domain]", + "paramKeys": [] + } + } + }, + "/runtimes/remark": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeRemark" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update runtime remark", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List runtimes", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/supervisor/process": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPSupervisorProcessConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate supervisor process", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/supervisor/process/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.SupervisorProcessConfig" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get supervisor process", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/supervisor/process/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPSupervisorProcessFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate supervisor process file", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/sync": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "summary": "Sync runtime status", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update runtime", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Update runtime [name]", + "formatZH": "更新运行环境 [name]", + "paramKeys": [] + } + } + }, + "/settings/basedir": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load local backup dir", + "tags": [ + "System Setting" + ] + } + }, + "/settings/description/save": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommonDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Save common description", + "tags": [ + "System Setting" + ] + } + }, + "/settings/get/{key}": { + "get": { + "parameters": [ + { + "description": "key", + "in": "path", + "name": "key", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SettingInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system setting by key", + "tags": [ + "System Setting" + ] + } + }, + "/settings/search": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SettingInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system setting info", + "tags": [ + "System Setting" + ] + } + }, + "/settings/search/available": { + "get": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system available status", + "tags": [ + "System Setting" + ] + } + }, + "/settings/snapshot": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create system snapshot", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "from", + "description" + ], + "formatEN": "Create system backup [description] to [from]", + "formatZH": "创建系统快照 [description] 到 [from]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotBatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete system backup", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "Delete system backup [name]", + "formatZH": "删除系统快照 [name]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/description/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update snapshot description", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "description" + ], + "formatEN": "The description of the snapshot [name] is modified =\u003e [description]", + "formatZH": "快照 [name] 描述信息修改 [description]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/import": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotImport" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Import system snapshot", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "from", + "names" + ], + "formatEN": "Sync system snapshots [names] from [from]", + "formatZH": "从 [from] 同步系统快照 [names]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/load": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SnapshotData" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system snapshot data", + "tags": [ + "System Setting" + ] + } + }, + "/settings/snapshot/recover": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Recover system backup", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Recover from system backup [name]", + "formatZH": "从系统快照 [name] 恢复", + "paramKeys": [] + } + } + }, + "/settings/snapshot/recreate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Recreate system snapshot", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "recrete the snapshot [name]", + "formatZH": "重试创建快照 [name]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/rollback": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Rollback system backup", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Rollback from system backup [name]", + "formatZH": "从系统快照 [name] 回滚", + "paramKeys": [] + } + } + }, + "/settings/snapshot/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageSnapshot" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page system snapshot", + "tags": [ + "System Setting" + ] + } + }, + "/settings/ssh": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Save local conn info", + "tags": [ + "System Setting" + ] + } + }, + "/settings/ssh/check/info": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check local conn info", + "tags": [ + "System Setting" + ] + } + }, + "/settings/ssh/conn": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SSHConnData" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load local conn", + "tags": [ + "System Setting" + ] + } + }, + "/settings/ssh/conn/default": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SSHDefaultConn" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update local is conn", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "defaultConn" + ], + "formatEN": "update system default conn [defaultConn]", + "formatZH": "本地终端默认连接 [defaultConn]", + "paramKeys": [] + } + } + }, + "/settings/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update system setting [key] =\u003e [value]", + "formatZH": "修改系统配置 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/clam": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create clam", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "path" + ], + "formatEN": "create clam [name][path]", + "formatZH": "创建扫描规则 [name][path]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/base": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ClamBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load clam base info", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete clam", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "clams", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete clam [names]", + "formatZH": "删除扫描规则 [names]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/file/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load clam file", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/file/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateByNameAndFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update clam file", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/handle": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Handle clam scan", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "clams", + "input_column": "id", + "input_value": "id", + "isList": true, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "handle clam scan [name]", + "formatZH": "执行病毒扫描 [name]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Clam", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] FTP", + "formatZH": "[operation] Clam", + "paramKeys": [] + } + } + }, + "/toolbox/clam/record/clean": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean clam record", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "clams", + "input_column": "id", + "input_value": "id", + "isList": true, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "clean clam record [name]", + "formatZH": "清空扫描报告 [name]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/record/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamLogSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page clam record", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchClamWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page clam", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/status/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamUpdateStatus" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update clam status", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "clams", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "status" + ], + "formatEN": "change the status of clam [name] to [status].", + "formatZH": "修改扫描规则 [name] 状态为 [status]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update clam", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "path" + ], + "formatEN": "update clam [name][path]", + "formatZH": "修改扫描规则 [name][path]", + "paramKeys": [] + } + } + }, + "/toolbox/clean": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "items": { + "$ref": "#/definitions/dto.Clean" + }, + "type": "array" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean system", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Clean system junk files", + "formatZH": "清理系统垃圾文件", + "paramKeys": [] + } + } + }, + "/toolbox/device/base": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DeviceBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load device base info", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/check/dns": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check device DNS conf", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "load conf", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/update/byconf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateByNameAndFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device conf by file", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/update/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update device conf [key] =\u003e [value]", + "formatZH": "修改主机参数 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/device/update/host": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device hosts", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update device host [key] =\u003e [value]", + "formatZH": "修改主机 Host [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/device/update/passwd": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangePasswd" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device passwd", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/update/swap": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SwapHelper" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device swap", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate", + "path" + ], + "formatEN": "[operate] device swap [path]", + "formatZH": "[operate] 主机 swap [path]", + "paramKeys": [] + } + } + }, + "/toolbox/device/users": { + "get": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load user list", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/zone/options": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "list time zone options", + "tags": [ + "Device" + ] + } + }, + "/toolbox/fail2ban/base": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.Fail2BanBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load fail2ban base info", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/fail2ban/load/conf": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load fail2ban conf", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/fail2ban/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate fail2ban", + "tags": [ + "Fail2ban" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] Fail2ban", + "formatZH": "[operation] Fail2ban", + "paramKeys": [] + } + } + }, + "/toolbox/fail2ban/operate/sshd": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanSet" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate sshd of fail2ban", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/fail2ban/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page fail2ban ip list", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/fail2ban/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update fail2ban conf", + "tags": [ + "Fail2ban" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update fail2ban conf [key] =\u003e [value]", + "formatZH": "修改 Fail2ban 配置 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/fail2ban/update/byconf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateByFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update fail2ban conf by file", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/ftp": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FtpCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create FTP user", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "user", + "path" + ], + "formatEN": "create FTP [user][path]", + "formatZH": "创建 FTP 账户 [user][path]", + "paramKeys": [] + } + } + }, + "/toolbox/ftp/base": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FtpBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load FTP base info", + "tags": [ + "FTP" + ] + } + }, + "/toolbox/ftp/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete FTP user", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "ftps", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "user", + "output_value": "users" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete FTP users [users]", + "formatZH": "删除 FTP 账户 [users]", + "paramKeys": [] + } + } + }, + "/toolbox/ftp/log/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FtpLogSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load FTP operation log", + "tags": [ + "FTP" + ] + } + }, + "/toolbox/ftp/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate FTP", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] FTP", + "formatZH": "[operation] FTP", + "paramKeys": [] + } + } + }, + "/toolbox/ftp/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page FTP user", + "tags": [ + "FTP" + ] + } + }, + "/toolbox/ftp/sync": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync FTP user", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "sync FTP users", + "formatZH": "同步 FTP 账户", + "paramKeys": [] + } + } + }, + "/toolbox/ftp/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FtpUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update FTP user", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "user", + "path" + ], + "formatEN": "update FTP [user][path]", + "formatZH": "修改 FTP 账户 [user][path]", + "paramKeys": [] + } + } + }, + "/toolbox/scan": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CleanData" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Scan system", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "scan System Junk Files", + "formatZH": "扫描系统垃圾文件", + "paramKeys": [] + } + } + }, + "/websites": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "alias" + ], + "formatEN": "Create website [alias]", + "formatZH": "创建网站 [alias]", + "paramKeys": [] + } + } + }, + "/websites/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website by id", + "tags": [ + "Website" + ] + } + }, + "/websites/:id/config/:type": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website nginx by id", + "tags": [ + "Website Nginx" + ] + } + }, + "/websites/:id/https": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteHTTPS" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load https conf", + "tags": [ + "Website HTTPS" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteHTTPSOp" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteHTTPS" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update https conf", + "tags": [ + "Website HTTPS" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "Update website https [domain] conf", + "formatZH": "更新网站 [domain] https 配置", + "paramKeys": [] + } + } + }, + "/websites/acme": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteAcmeAccountCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteAcmeAccountDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website acme account", + "tags": [ + "Website Acme" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "email" + ], + "formatEN": "Create website acme [email]", + "formatZH": "创建网站 acme [email]", + "paramKeys": [] + } + } + }, + "/websites/acme/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website acme account", + "tags": [ + "Website Acme" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_acme_accounts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "email", + "output_value": "email" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete website acme [email]", + "formatZH": "删除网站 acme [email]", + "paramKeys": [] + } + } + }, + "/websites/acme/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page website acme accounts", + "tags": [ + "Website Acme" + ] + } + }, + "/websites/acme/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteAcmeAccountUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteAcmeAccountDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website acme account", + "tags": [ + "Website Acme" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_acme_accounts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "email", + "output_value": "email" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update acme [email]", + "formatZH": "更新 acme [email]", + "paramKeys": [] + } + } + }, + "/websites/auths": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxAuthReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxAuthRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AuthBasic conf", + "tags": [ + "Website" + ] + } + }, + "/websites/auths/path": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxAuthReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxPathAuthRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AuthBasic conf", + "tags": [ + "Website" + ] + } + }, + "/websites/auths/path/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxPathAuthUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AuthBasic conf", + "tags": [ + "Website" + ] + } + }, + "/websites/auths/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxAuthUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AuthBasic conf", + "tags": [ + "Website" + ] + } + }, + "/websites/batch/group": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.BatchWebsiteGroup" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch set website group", + "tags": [ + "Website" + ] + } + }, + "/websites/batch/https": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.BatchWebsiteHttps" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch set HTTPS for websites", + "tags": [ + "Website" + ] + } + }, + "/websites/batch/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.BatchWebsiteOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch operate websites", + "tags": [ + "Website" + ] + } + }, + "/websites/ca": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCACreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.WebsiteCACreate" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website ca", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Create website ca [name]", + "formatZH": "创建网站 ca [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website ca", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_cas", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete website ca [name]", + "formatZH": "删除网站 ca [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/download": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download CA file", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_cas", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "download ca file [name]", + "formatZH": "下载 CA 证书文件 [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/obtain": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCAObtain" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Obtain SSL", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_cas", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Obtain SSL [name]", + "formatZH": "自签 SSL 证书 [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/renew": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCAObtain" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Obtain SSL", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_cas", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Obtain SSL [name]", + "formatZH": "自签 SSL 证书 [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCASearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page website ca", + "tags": [ + "Website CA" + ] + } + }, + "/websites/ca/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteCADTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website ca", + "tags": [ + "Website CA" + ] + } + }, + "/websites/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteInstallCheckReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsitePreInstallCheck" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before create website", + "tags": [ + "Website" + ] + } + }, + "/websites/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxScopeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteNginxConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load nginx conf", + "tags": [ + "Website Nginx" + ] + } + }, + "/websites/config/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxConfigUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update nginx conf", + "tags": [ + "Website Nginx" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "Nginx conf update [domain]", + "formatZH": "nginx 配置修改 [domain]", + "paramKeys": [] + } + } + }, + "/websites/cors/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.CorsConfigReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update CORS Config", + "tags": [ + "Website" + ] + } + }, + "/websites/cors/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.CorsConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get CORS Config", + "tags": [ + "Website" + ] + } + }, + "/websites/crosssite": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.CrossSiteAccessOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Cross Site Access", + "tags": [ + "Website" + ] + } + }, + "/websites/databases": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Database" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get databases", + "tags": [ + "Website" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChangeDatabase" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change website database", + "tags": [ + "Website" + ] + } + }, + "/websites/default/html/:type": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteHtmlRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get default html", + "tags": [ + "Website" + ] + } + }, + "/websites/default/html/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteHtmlUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update default html", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "Update default html", + "formatZH": "更新默认 html", + "paramKeys": [] + } + } + }, + "/websites/default/server": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDefaultUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change default server", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id", + "operate" + ], + "formatEN": "Change default server =\u003e [domain]", + "formatZH": "修改默认 server =\u003e [domain]", + "paramKeys": [] + } + } + }, + "/websites/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete website [domain]", + "formatZH": "删除网站 [domain]", + "paramKeys": [] + } + } + }, + "/websites/dir": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteDirConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website dir", + "tags": [ + "Website" + ] + } + }, + "/websites/dir/permission": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteUpdateDirPermission" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Site Dir permission", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update domain [domain] dir permission", + "formatZH": "更新网站 [domain] 目录权限", + "paramKeys": [] + } + } + }, + "/websites/dir/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteUpdateDir" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Site Dir", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update domain [domain] dir", + "formatZH": "更新网站 [domain] 目录", + "paramKeys": [] + } + } + }, + "/websites/dns": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDnsAccountCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website dns account", + "tags": [ + "Website DNS" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Create website dns [name]", + "formatZH": "创建网站 dns [name]", + "paramKeys": [] + } + } + }, + "/websites/dns/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website dns account", + "tags": [ + "Website DNS" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_dns_accounts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete website dns [name]", + "formatZH": "删除网站 dns [name]", + "paramKeys": [] + } + } + }, + "/websites/dns/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page website dns accounts", + "tags": [ + "Website DNS" + ] + } + }, + "/websites/dns/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDnsAccountUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website dns account", + "tags": [ + "Website DNS" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Update website dns [name]", + "formatZH": "更新网站 dns [name]", + "paramKeys": [] + } + } + }, + "/websites/domains": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDomainCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.WebsiteDomain" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website domain", + "tags": [ + "Website Domain" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "domain" + ], + "formatEN": "Create domain [domain]", + "formatZH": "创建域名 [domain]", + "paramKeys": [] + } + } + }, + "/websites/domains/:websiteId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "websiteId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/model.WebsiteDomain" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website domains by websiteId", + "tags": [ + "Website Domain" + ] + } + }, + "/websites/domains/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDomainDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website domain", + "tags": [ + "Website Domain" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_domains", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete domain [domain]", + "formatZH": "删除域名 [domain]", + "paramKeys": [] + } + } + }, + "/websites/domains/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDomainUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website domain", + "tags": [ + "Website Domain" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_domains", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update domain [domain]", + "formatZH": "更新域名 [domain]", + "paramKeys": [] + } + } + }, + "/websites/exec/composer": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.ExecComposerReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Exec Composer", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.NginxUpstream" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website upstreams", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs/create": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLBCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website upstream", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLBDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website upstream", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLBUpdateFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website upstream file", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLBUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website upstream", + "tags": [ + "Website" + ] + } + }, + "/websites/leech": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxAntiLeechRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AntiLeech conf", + "tags": [ + "Website" + ] + } + }, + "/websites/leech/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxAntiLeechUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update AntiLeech", + "tags": [ + "Website" + ] + } + }, + "/websites/list": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsiteDTO" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List websites", + "tags": [ + "Website" + ] + } + }, + "/websites/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLogReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteLog" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate website log", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id", + "operate" + ], + "formatEN": "[domain][operate] logs", + "formatZH": "[domain][operate] 日志", + "paramKeys": [] + } + } + }, + "/websites/nginx/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteNginxUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website nginx conf", + "tags": [ + "Website Nginx" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "[domain] Nginx conf update", + "formatZH": "[domain] Nginx 配置修改", + "paramKeys": [] + } + } + }, + "/websites/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate website", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id", + "operate" + ], + "formatEN": "[operate] website [domain]", + "formatZH": "[operate] 网站 [domain]", + "paramKeys": [] + } + } + }, + "/websites/options": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsiteOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List website names", + "tags": [ + "Website" + ] + } + }, + "/websites/php/version": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsitePHPVersionReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update php version", + "tags": [ + "Website PHP" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "php version update [domain]", + "formatZH": "php 版本变更 [domain]", + "paramKeys": [] + } + } + }, + "/websites/proxies": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteProxyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/request.WebsiteProxyConfig" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get proxy conf", + "tags": [ + "Website" + ] + } + }, + "/websites/proxies/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxProxyUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update proxy file", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Nginx conf proxy file update [domain]", + "formatZH": "更新反向代理文件 [domain]", + "paramKeys": [] + } + } + }, + "/websites/proxies/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteProxyConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update proxy conf", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update domain [domain] proxy config", + "formatZH": "修改网站 [domain] 反向代理配置 ", + "paramKeys": [] + } + } + }, + "/websites/proxy/clear": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clear Website proxy cache", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Clear nginx proxy cache", + "formatZH": "清理 Openresty 代理缓存", + "paramKeys": [] + } + } + }, + "/websites/proxy/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxProxyCacheUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "update website proxy cache config", + "tags": [ + "Website" + ] + } + }, + "/websites/proxy/config/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxProxyCache" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website proxy cache config" + } + }, + "/websites/realip/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteRealIP" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Set Real IP", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Modify the real IP configuration of [domain] website", + "formatZH": "修改 [domain] 网站真实IP配置 ", + "paramKeys": [] + } + } + }, + "/websites/realip/config/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteRealIP" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Real IP Config", + "tags": [ + "Website" + ] + } + }, + "/websites/redirect": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteProxyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.NginxRedirectConfig" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get redirect conf", + "tags": [ + "Website" + ] + } + }, + "/websites/redirect/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxRedirectUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update redirect file", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Nginx conf redirect file update [domain]", + "formatZH": "更新重定向文件 [domain]", + "paramKeys": [] + } + } + }, + "/websites/redirect/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxRedirectReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update redirect conf", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Update domain [domain] redirect config", + "formatZH": "修改网站 [domain] 重定向配置 ", + "paramKeys": [] + } + } + }, + "/websites/resource/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Resource" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website resource", + "tags": [ + "Website" + ] + } + }, + "/websites/rewrite": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxRewriteReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxRewriteRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get rewrite conf", + "tags": [ + "Website" + ] + } + }, + "/websites/rewrite/custom": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List custom rewrite", + "tags": [ + "Website" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomRewriteOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate custom rewrite", + "tags": [ + "Website" + ] + } + }, + "/websites/rewrite/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxRewriteUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update rewrite conf", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Nginx conf rewrite update [domain]", + "formatZH": "伪静态配置修改 [domain]", + "paramKeys": [] + } + } + }, + "/websites/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page websites", + "tags": [ + "Website" + ] + } + }, + "/websites/ssl": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.WebsiteSSLCreate" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "primaryDomain" + ], + "formatEN": "Create website ssl [primaryDomain]", + "formatZH": "创建网站 ssl [primaryDomain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteSSLDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website ssl by id", + "tags": [ + "Website SSL" + ] + } + }, + "/websites/ssl/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteBatchDelReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "Delete ssl [domain]", + "formatZH": "删除 ssl [domain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/download": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download SSL file", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "download ssl file [domain]", + "formatZH": "下载证书文件 [domain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/obtain": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLApply" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Apply ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "ID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "ID" + ], + "formatEN": "apply ssl [domain]", + "formatZH": "申请证书 [domain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/resolve": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDNSReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsiteDNSRes" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Resolve website ssl", + "tags": [ + "Website SSL" + ] + } + }, + "/websites/ssl/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsiteSSLDTO" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page website ssl", + "tags": [ + "Website SSL" + ] + } + }, + "/websites/ssl/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update ssl config [domain]", + "formatZH": "更新证书设置 [domain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/upload": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLUpload" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upload ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "Upload ssl [type]", + "formatZH": "上传 ssl [type]", + "paramKeys": [] + } + } + }, + "/websites/ssl/upload/file": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "parameters": [ + { + "description": "type", + "in": "formData", + "name": "type", + "required": true, + "type": "string" + }, + { + "description": "description", + "in": "formData", + "name": "description", + "type": "string" + }, + { + "description": "sslID", + "in": "formData", + "name": "sslID", + "type": "string" + }, + { + "description": "privateKeyFile", + "in": "formData", + "name": "privateKeyFile", + "required": true, + "type": "file" + }, + { + "description": "certificateFile", + "in": "formData", + "name": "certificateFile", + "required": true, + "type": "file" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upload SSL file", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "Upload ssl file [type]", + "formatZH": "上传 ssl 文件 [type]", + "paramKeys": [] + } + } + }, + "/websites/ssl/website/:websiteId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "websiteId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteSSLDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website ssl by website id", + "tags": [ + "Website SSL" + ] + } + }, + "/websites/stream/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.StreamUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Stream Config", + "tags": [ + "Website" + ] + } + }, + "/websites/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "primaryDomain" + ], + "formatEN": "Update website [primaryDomain]", + "formatZH": "更新网站 [primaryDomain]", + "paramKeys": [] + } + } + } + }, + "definitions": { + "dto.AddrRuleOperate": { + "properties": { + "address": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "operation": { + "enum": [ + "add", + "remove" + ], + "type": "string" + }, + "strategy": { + "enum": [ + "accept", + "drop" + ], + "type": "string" + } + }, + "required": [ + "address", + "operation", + "strategy" + ], + "type": "object" + }, + "dto.AddrRuleUpdate": { + "properties": { + "newRule": { + "$ref": "#/definitions/dto.AddrRuleOperate" + }, + "oldRule": { + "$ref": "#/definitions/dto.AddrRuleOperate" + } + }, + "type": "object" + }, + "dto.ApiInterfaceConfig": { + "properties": { + "apiInterfaceStatus": { + "type": "string" + }, + "apiKey": { + "type": "string" + }, + "apiKeyValidityTime": { + "type": "string" + }, + "ipWhiteList": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppConfigVersion": { + "properties": { + "additionalProperties": {}, + "downloadCallBackUrl": { + "type": "string" + }, + "downloadUrl": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppDefine": { + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dto.AppProperty" + }, + "icon": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "readMe": { + "type": "string" + }, + "versions": { + "items": { + "$ref": "#/definitions/dto.AppConfigVersion" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.AppInstallInfo": { + "properties": { + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppLauncher": { + "properties": { + "description": { + "type": "string" + }, + "detail": { + "items": { + "$ref": "#/definitions/dto.InstallDetail" + }, + "type": "array" + }, + "icon": { + "type": "string" + }, + "isInstall": { + "type": "boolean" + }, + "isRecommend": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppList": { + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dto.ExtraProperties" + }, + "apps": { + "items": { + "$ref": "#/definitions/dto.AppDefine" + }, + "type": "array" + }, + "lastModified": { + "type": "integer" + }, + "valid": { + "type": "boolean" + }, + "violations": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.AppProperty": { + "properties": { + "Required": { + "items": { + "type": "string" + }, + "type": "array" + }, + "architectures": { + "items": { + "type": "string" + }, + "type": "array" + }, + "crossVersionUpdate": { + "type": "boolean" + }, + "deprecated": { + "type": "number" + }, + "description": { + "$ref": "#/definitions/dto.Locale" + }, + "document": { + "type": "string" + }, + "github": { + "type": "string" + }, + "gpuSupport": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + }, + "memoryRequired": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "shortDescEn": { + "type": "string" + }, + "shortDescZh": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "version": { + "type": "number" + }, + "website": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppResource": { + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppVersion": { + "properties": { + "detailId": { + "type": "integer" + }, + "dockerCompose": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppstoreConfig": { + "properties": { + "uninstallDeleteBackup": { + "type": "string" + }, + "uninstallDeleteImage": { + "type": "string" + }, + "upgradeBackup": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppstoreUpdate": { + "properties": { + "scope": { + "enum": [ + "UninstallDeleteImage", + "UpgradeBackup", + "UninstallDeleteBackup" + ], + "type": "string" + }, + "status": { + "enum": [ + "Disable", + "Enable" + ], + "type": "string" + } + }, + "required": [ + "scope", + "status" + ], + "type": "object" + }, + "dto.BackupClientInfo": { + "properties": { + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + } + }, + "type": "object" + }, + "dto.BackupOperate": { + "properties": { + "accessKey": { + "type": "string" + }, + "backupPath": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "credential": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "rememberAuth": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "vars": { + "type": "string" + } + }, + "required": [ + "type", + "vars" + ], + "type": "object" + }, + "dto.BackupOption": { + "properties": { + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.BatchDelete": { + "properties": { + "force": { + "type": "boolean" + }, + "names": { + "items": { + "type": "string" + }, + "type": "array" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "names" + ], + "type": "object" + }, + "dto.BatchDeleteReq": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.BatchRuleOperate": { + "properties": { + "rules": { + "items": { + "$ref": "#/definitions/dto.PortRuleOperate" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.BindInfo": { + "properties": { + "bindAddress": { + "type": "string" + }, + "ipv6": { + "enum": [ + "Enable", + "Disable" + ], + "type": "string" + } + }, + "required": [ + "bindAddress", + "ipv6" + ], + "type": "object" + }, + "dto.BindUser": { + "properties": { + "database": { + "type": "string" + }, + "db": { + "type": "string" + }, + "password": { + "type": "string" + }, + "permission": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "database", + "db", + "password", + "permission", + "username" + ], + "type": "object" + }, + "dto.CaptchaResponse": { + "properties": { + "captchaID": { + "type": "string" + }, + "imagePath": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ChangeDBInfo": { + "properties": { + "database": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "postgresql", + "redis", + "mysql-cluster", + "postgresql-cluster", + "redis-cluster" + ], + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "database", + "from", + "type", + "value" + ], + "type": "object" + }, + "dto.ChangeGroup": { + "properties": { + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "groupID", + "id" + ], + "type": "object" + }, + "dto.ChangeHostGroup": { + "properties": { + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "groupID", + "id" + ], + "type": "object" + }, + "dto.ChangePasswd": { + "properties": { + "passwd": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ChangeQuicks": { + "properties": { + "quicks": { + "items": { + "$ref": "#/definitions/dto.QuickJump" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.ChangeRedisPass": { + "properties": { + "database": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + }, + "dto.ClamBaseInfo": { + "properties": { + "freshIsActive": { + "type": "boolean" + }, + "freshIsExist": { + "type": "boolean" + }, + "freshVersion": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ClamCreate": { + "properties": { + "alertCount": { + "type": "integer" + }, + "alertMethod": { + "type": "string" + }, + "alertTitle": { + "type": "string" + }, + "description": { + "type": "string" + }, + "infectedDir": { + "type": "string" + }, + "infectedStrategy": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "spec": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timeout": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ClamDelete": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "removeInfected": { + "type": "boolean" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.ClamFileReq": { + "properties": { + "name": { + "type": "string" + }, + "tail": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "dto.ClamLogSearch": { + "properties": { + "clamID": { + "type": "integer" + }, + "endTime": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "startTime": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.ClamUpdate": { + "properties": { + "alertCount": { + "type": "integer" + }, + "alertMethod": { + "type": "string" + }, + "alertTitle": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "infectedDir": { + "type": "string" + }, + "infectedStrategy": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "spec": { + "type": "string" + }, + "timeout": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ClamUpdateStatus": { + "properties": { + "id": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Clean": { + "properties": { + "name": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "treeType": { + "type": "string" + } + }, + "type": "object" + }, + "dto.CleanData": { + "properties": { + "backupClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "containerClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "downloadClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "systemClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "systemLogClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "uploadClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.CleanLog": { + "properties": { + "logType": { + "enum": [ + "login", + "operation" + ], + "type": "string" + } + }, + "required": [ + "logType" + ], + "type": "object" + }, + "dto.CleanTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "isCheck": { + "type": "boolean" + }, + "isRecommend": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.CommandInfo": { + "properties": { + "command": { + "type": "string" + }, + "groupBelong": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.CommandOperate": { + "properties": { + "command": { + "type": "string" + }, + "groupBelong": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "command", + "name" + ], + "type": "object" + }, + "dto.CommandTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/dto.CommandTree" + }, + "type": "array" + }, + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "dto.CommonBackup": { + "properties": { + "description": { + "type": "string" + }, + "detailName": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "type": { + "enum": [ + "app", + "mysql", + "mariadb", + "redis", + "website", + "postgresql", + "mysql-cluster", + "postgresql-cluster", + "redis-cluster" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.CommonDescription": { + "properties": { + "description": { + "type": "string" + }, + "detailType": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isPinned": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "dto.CommonRecover": { + "properties": { + "backupRecordID": { + "type": "integer" + }, + "detailName": { + "type": "string" + }, + "downloadAccountID": { + "type": "integer" + }, + "file": { + "type": "string" + }, + "name": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "timeout": { + "type": "integer" + }, + "type": { + "enum": [ + "app", + "mysql", + "mariadb", + "redis", + "website", + "postgresql", + "mysql-cluster", + "postgresql-cluster", + "redis-cluster" + ], + "type": "string" + } + }, + "required": [ + "downloadAccountID", + "type" + ], + "type": "object" + }, + "dto.ComposeCreate": { + "properties": { + "env": { + "type": "string" + }, + "file": { + "type": "string" + }, + "from": { + "enum": [ + "edit", + "path", + "template" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "template": { + "type": "integer" + } + }, + "required": [ + "from" + ], + "type": "object" + }, + "dto.ComposeLogClean": { + "properties": { + "detailPath": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "dto.ComposeOperation": { + "properties": { + "force": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "operation": { + "enum": [ + "up", + "start", + "restart", + "stop", + "down", + "delete" + ], + "type": "string" + }, + "path": { + "type": "string" + }, + "withFile": { + "type": "boolean" + } + }, + "required": [ + "name", + "operation" + ], + "type": "object" + }, + "dto.ComposeTemplateBatch": { + "properties": { + "templates": { + "items": { + "$ref": "#/definitions/dto.ComposeTemplateCreate" + }, + "type": "array" + } + }, + "required": [ + "templates" + ], + "type": "object" + }, + "dto.ComposeTemplateCreate": { + "properties": { + "content": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "dto.ComposeTemplateInfo": { + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ComposeTemplateUpdate": { + "properties": { + "content": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ComposeUpdate": { + "properties": { + "content": { + "type": "string" + }, + "detailPath": { + "type": "string" + }, + "env": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "content", + "name", + "path" + ], + "type": "object" + }, + "dto.ContainerCommit": { + "properties": { + "author": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "containerID": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "newImageName": { + "type": "string" + }, + "pause": { + "type": "boolean" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "containerID" + ], + "type": "object" + }, + "dto.ContainerCreateByCommand": { + "properties": { + "command": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ContainerItemStats": { + "properties": { + "buildCacheReclaimable": { + "type": "integer" + }, + "buildCacheUsage": { + "type": "integer" + }, + "containerReclaimable": { + "type": "integer" + }, + "containerUsage": { + "type": "integer" + }, + "imageReclaimable": { + "type": "integer" + }, + "imageUsage": { + "type": "integer" + }, + "sizeRootFs": { + "type": "integer" + }, + "sizeRw": { + "type": "integer" + }, + "volumeReclaimable": { + "type": "integer" + }, + "volumeUsage": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ContainerListStats": { + "properties": { + "containerID": { + "type": "string" + }, + "cpuPercent": { + "type": "number" + }, + "cpuTotalUsage": { + "type": "integer" + }, + "memoryCache": { + "type": "integer" + }, + "memoryLimit": { + "type": "integer" + }, + "memoryPercent": { + "type": "number" + }, + "memoryUsage": { + "type": "integer" + }, + "percpuUsage": { + "type": "integer" + }, + "systemUsage": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ContainerNetwork": { + "properties": { + "ipv4": { + "type": "string" + }, + "ipv6": { + "type": "string" + }, + "macAddr": { + "type": "string" + }, + "network": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ContainerOperate": { + "properties": { + "autoRemove": { + "type": "boolean" + }, + "cmd": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cpuShares": { + "type": "integer" + }, + "dns": { + "items": { + "type": "string" + }, + "type": "array" + }, + "domainName": { + "type": "string" + }, + "entrypoint": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "type": "string" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/dto.PortHelper" + }, + "type": "array" + }, + "forcePull": { + "type": "boolean" + }, + "hostname": { + "type": "string" + }, + "image": { + "type": "string" + }, + "labels": { + "items": { + "type": "string" + }, + "type": "array" + }, + "memory": { + "type": "number" + }, + "name": { + "type": "string" + }, + "nanoCPUs": { + "type": "number" + }, + "networks": { + "items": { + "$ref": "#/definitions/dto.ContainerNetwork" + }, + "type": "array" + }, + "openStdin": { + "type": "boolean" + }, + "privileged": { + "type": "boolean" + }, + "publishAllPorts": { + "type": "boolean" + }, + "restartPolicy": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "user": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/dto.VolumeHelper" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "image", + "name" + ], + "type": "object" + }, + "dto.ContainerOperation": { + "properties": { + "names": { + "items": { + "type": "string" + }, + "type": "array" + }, + "operation": { + "enum": [ + "up", + "start", + "stop", + "restart", + "kill", + "pause", + "unpause", + "remove" + ], + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "names", + "operation" + ], + "type": "object" + }, + "dto.ContainerOptions": { + "properties": { + "name": { + "type": "string" + }, + "state": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ContainerPrune": { + "properties": { + "pruneType": { + "enum": [ + "container", + "image", + "volume", + "network", + "buildcache" + ], + "type": "string" + }, + "taskID": { + "type": "string" + }, + "withTagAll": { + "type": "boolean" + } + }, + "required": [ + "pruneType" + ], + "type": "object" + }, + "dto.ContainerRename": { + "properties": { + "name": { + "type": "string" + }, + "newName": { + "type": "string" + } + }, + "required": [ + "name", + "newName" + ], + "type": "object" + }, + "dto.ContainerStats": { + "properties": { + "cache": { + "type": "number" + }, + "cpuPercent": { + "type": "number" + }, + "ioRead": { + "type": "number" + }, + "ioWrite": { + "type": "number" + }, + "memory": { + "type": "number" + }, + "networkRX": { + "type": "number" + }, + "networkTX": { + "type": "number" + }, + "shotTime": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ContainerStatus": { + "properties": { + "composeCount": { + "type": "integer" + }, + "composeTemplateCount": { + "type": "integer" + }, + "containerCount": { + "type": "integer" + }, + "created": { + "type": "integer" + }, + "dead": { + "type": "integer" + }, + "exited": { + "type": "integer" + }, + "imageCount": { + "type": "integer" + }, + "networkCount": { + "type": "integer" + }, + "paused": { + "type": "integer" + }, + "removing": { + "type": "integer" + }, + "repoCount": { + "type": "integer" + }, + "restarting": { + "type": "integer" + }, + "running": { + "type": "integer" + }, + "volumeCount": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ContainerUpgrade": { + "properties": { + "forcePull": { + "type": "boolean" + }, + "image": { + "type": "string" + }, + "names": { + "items": { + "type": "string" + }, + "type": "array" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "image", + "names" + ], + "type": "object" + }, + "dto.CronjobBatchDelete": { + "properties": { + "cleanData": { + "type": "boolean" + }, + "cleanRemoteData": { + "type": "boolean" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.CronjobClean": { + "properties": { + "cleanData": { + "type": "boolean" + }, + "cleanRemoteData": { + "type": "boolean" + }, + "cronjobID": { + "type": "integer" + }, + "isDelete": { + "type": "boolean" + } + }, + "required": [ + "cronjobID" + ], + "type": "object" + }, + "dto.CronjobImport": { + "properties": { + "cronjobs": { + "items": { + "$ref": "#/definitions/dto.CronjobTrans" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.CronjobOperate": { + "properties": { + "alertCount": { + "type": "integer" + }, + "alertMethod": { + "type": "string" + }, + "alertTitle": { + "type": "string" + }, + "appID": { + "type": "string" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "dbName": { + "type": "string" + }, + "dbType": { + "type": "string" + }, + "downloadAccountID": { + "type": "integer" + }, + "exclusionRules": { + "type": "string" + }, + "executor": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "ignoreErr": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "retainCopies": { + "minimum": 1, + "type": "integer" + }, + "retryTimes": { + "minimum": 0, + "type": "integer" + }, + "scopes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "script": { + "type": "string" + }, + "scriptID": { + "type": "integer" + }, + "scriptMode": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "snapshotRule": { + "$ref": "#/definitions/dto.SnapshotRule" + }, + "sourceAccountIDs": { + "type": "string" + }, + "sourceDir": { + "type": "string" + }, + "spec": { + "type": "string" + }, + "specCustom": { + "type": "boolean" + }, + "timeout": { + "minimum": 1, + "type": "integer" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "user": { + "type": "string" + }, + "website": { + "type": "string" + } + }, + "required": [ + "name", + "spec", + "type" + ], + "type": "object" + }, + "dto.CronjobSpec": { + "properties": { + "spec": { + "type": "string" + } + }, + "required": [ + "spec" + ], + "type": "object" + }, + "dto.CronjobTrans": { + "properties": { + "alertCount": { + "type": "integer" + }, + "alertMethod": { + "type": "string" + }, + "alertTitle": { + "type": "string" + }, + "apps": { + "items": { + "$ref": "#/definitions/dto.TransHelper" + }, + "type": "array" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "dbName": { + "items": { + "$ref": "#/definitions/dto.TransHelper" + }, + "type": "array" + }, + "dbType": { + "type": "string" + }, + "downloadAccount": { + "type": "string" + }, + "exclusionRules": { + "type": "string" + }, + "executor": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "ignoreErr": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "retainCopies": { + "type": "integer" + }, + "retryTimes": { + "type": "integer" + }, + "script": { + "type": "string" + }, + "scriptMode": { + "type": "string" + }, + "scriptName": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "snapshotRule": { + "$ref": "#/definitions/dto.SnapshotTransHelper" + }, + "sourceAccounts": { + "items": { + "type": "string" + }, + "type": "array" + }, + "sourceDir": { + "type": "string" + }, + "spec": { + "type": "string" + }, + "specCustom": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "user": { + "type": "string" + }, + "websites": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.CronjobUpdateStatus": { + "properties": { + "id": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "required": [ + "id", + "status" + ], + "type": "object" + }, + "dto.DBBaseInfo": { + "properties": { + "containerName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.DBConfUpdateByFile": { + "properties": { + "database": { + "type": "string" + }, + "file": { + "type": "string" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "postgresql", + "redis", + "mysql-cluster", + "postgresql-cluster", + "redis-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "type" + ], + "type": "object" + }, + "dto.DaemonJsonConf": { + "properties": { + "cgroupDriver": { + "type": "string" + }, + "experimental": { + "type": "boolean" + }, + "fixedCidrV6": { + "type": "string" + }, + "insecureRegistries": { + "items": { + "type": "string" + }, + "type": "array" + }, + "ip6Tables": { + "type": "boolean" + }, + "iptables": { + "type": "boolean" + }, + "ipv6": { + "type": "boolean" + }, + "isSwarm": { + "type": "boolean" + }, + "liveRestore": { + "type": "boolean" + }, + "logMaxFile": { + "type": "string" + }, + "logMaxSize": { + "type": "string" + }, + "registryMirrors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DaemonJsonUpdateByFile": { + "properties": { + "file": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DashboardBase": { + "properties": { + "appInstalledNumber": { + "type": "integer" + }, + "cpuCores": { + "type": "integer" + }, + "cpuLogicalCores": { + "type": "integer" + }, + "cpuMhz": { + "type": "number" + }, + "cpuModelName": { + "type": "string" + }, + "cronjobNumber": { + "type": "integer" + }, + "currentInfo": { + "$ref": "#/definitions/dto.DashboardCurrent" + }, + "databaseNumber": { + "type": "integer" + }, + "hostname": { + "type": "string" + }, + "ipV4Addr": { + "type": "string" + }, + "kernelArch": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "os": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "platformFamily": { + "type": "string" + }, + "platformVersion": { + "type": "string" + }, + "prettyDistro": { + "type": "string" + }, + "quickJump": { + "items": { + "$ref": "#/definitions/dto.QuickJump" + }, + "type": "array" + }, + "systemProxy": { + "type": "string" + }, + "virtualizationSystem": { + "type": "string" + }, + "websiteNumber": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.DashboardCurrent": { + "properties": { + "cpuDetailedPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "cpuPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "cpuTotal": { + "type": "integer" + }, + "cpuUsed": { + "type": "number" + }, + "cpuUsedPercent": { + "type": "number" + }, + "diskData": { + "items": { + "$ref": "#/definitions/dto.DiskInfo" + }, + "type": "array" + }, + "gpuData": { + "items": { + "$ref": "#/definitions/dto.GPUInfo" + }, + "type": "array" + }, + "ioCount": { + "type": "integer" + }, + "ioReadBytes": { + "type": "integer" + }, + "ioReadTime": { + "type": "integer" + }, + "ioWriteBytes": { + "type": "integer" + }, + "ioWriteTime": { + "type": "integer" + }, + "load1": { + "type": "number" + }, + "load15": { + "type": "number" + }, + "load5": { + "type": "number" + }, + "loadUsagePercent": { + "type": "number" + }, + "memoryAvailable": { + "type": "integer" + }, + "memoryCache": { + "type": "integer" + }, + "memoryFree": { + "type": "integer" + }, + "memoryShard": { + "type": "integer" + }, + "memoryTotal": { + "type": "integer" + }, + "memoryUsed": { + "type": "integer" + }, + "memoryUsedPercent": { + "type": "number" + }, + "netBytesRecv": { + "type": "integer" + }, + "netBytesSent": { + "type": "integer" + }, + "procs": { + "type": "integer" + }, + "shotTime": { + "type": "string" + }, + "swapMemoryAvailable": { + "type": "integer" + }, + "swapMemoryTotal": { + "type": "integer" + }, + "swapMemoryUsed": { + "type": "integer" + }, + "swapMemoryUsedPercent": { + "type": "number" + }, + "timeSinceUptime": { + "type": "string" + }, + "topCPUItems": { + "items": { + "$ref": "#/definitions/dto.Process" + }, + "type": "array" + }, + "topMemItems": { + "items": { + "$ref": "#/definitions/dto.Process" + }, + "type": "array" + }, + "uptime": { + "type": "integer" + }, + "xpuData": { + "items": { + "$ref": "#/definitions/dto.XPUInfo" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.DataTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "isCheck": { + "type": "boolean" + }, + "isDisable": { + "type": "boolean" + }, + "isLocal": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "label": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "relationItemID": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.DatabaseCreate": { + "properties": { + "address": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "description": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "initialDB": { + "type": "string" + }, + "name": { + "maxLength": 256, + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "rootCert": { + "type": "string" + }, + "skipVerify": { + "type": "boolean" + }, + "ssl": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "username": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "from", + "name", + "type", + "username", + "version" + ], + "type": "object" + }, + "dto.DatabaseDelete": { + "properties": { + "deleteBackup": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "dto.DatabaseInfo": { + "properties": { + "address": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "from": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "initialDB": { + "type": "string" + }, + "name": { + "maxLength": 256, + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "rootCert": { + "type": "string" + }, + "skipVerify": { + "type": "boolean" + }, + "ssl": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "username": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DatabaseItem": { + "properties": { + "database": { + "type": "string" + }, + "from": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DatabaseOption": { + "properties": { + "address": { + "type": "string" + }, + "database": { + "type": "string" + }, + "from": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DatabaseSearch": { + "properties": { + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.DatabaseUpdate": { + "properties": { + "address": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "initialDB": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "rootCert": { + "type": "string" + }, + "skipVerify": { + "type": "boolean" + }, + "ssl": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "username": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "type", + "username", + "version" + ], + "type": "object" + }, + "dto.DeviceBaseInfo": { + "properties": { + "dns": { + "items": { + "type": "string" + }, + "type": "array" + }, + "hostname": { + "type": "string" + }, + "hosts": { + "items": { + "$ref": "#/definitions/dto.HostHelper" + }, + "type": "array" + }, + "localTime": { + "type": "string" + }, + "maxSize": { + "type": "integer" + }, + "ntp": { + "type": "string" + }, + "swapDetails": { + "items": { + "$ref": "#/definitions/dto.SwapHelper" + }, + "type": "array" + }, + "swapMemoryAvailable": { + "type": "integer" + }, + "swapMemoryTotal": { + "type": "integer" + }, + "swapMemoryUsed": { + "type": "integer" + }, + "timeZone": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DiskInfo": { + "properties": { + "device": { + "type": "string" + }, + "free": { + "type": "integer" + }, + "inodesFree": { + "type": "integer" + }, + "inodesTotal": { + "type": "integer" + }, + "inodesUsed": { + "type": "integer" + }, + "inodesUsedPercent": { + "type": "number" + }, + "path": { + "type": "string" + }, + "total": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "used": { + "type": "integer" + }, + "usedPercent": { + "type": "number" + } + }, + "type": "object" + }, + "dto.DockerOperation": { + "properties": { + "operation": { + "enum": [ + "start", + "restart", + "stop" + ], + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + "dto.DownloadRecord": { + "properties": { + "downloadAccountID": { + "type": "integer" + }, + "fileDir": { + "type": "string" + }, + "fileName": { + "type": "string" + } + }, + "required": [ + "downloadAccountID", + "fileDir", + "fileName" + ], + "type": "object" + }, + "dto.ExtraProperties": { + "properties": { + "tags": { + "items": { + "$ref": "#/definitions/dto.Tag" + }, + "type": "array" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Fail2BanBaseInfo": { + "properties": { + "banAction": { + "type": "string" + }, + "banTime": { + "type": "string" + }, + "findTime": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isEnable": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "logPath": { + "type": "string" + }, + "maxRetry": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Fail2BanSearch": { + "properties": { + "status": { + "enum": [ + "banned", + "ignore" + ], + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "dto.Fail2BanSet": { + "properties": { + "ips": { + "items": { + "type": "string" + }, + "type": "array" + }, + "operate": { + "enum": [ + "banned", + "ignore" + ], + "type": "string" + } + }, + "required": [ + "operate" + ], + "type": "object" + }, + "dto.Fail2BanUpdate": { + "properties": { + "key": { + "enum": [ + "port", + "bantime", + "findtime", + "maxretry", + "banaction", + "logpath", + "port" + ], + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "dto.FirewallBaseInfo": { + "properties": { + "isActive": { + "type": "boolean" + }, + "isBind": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "isInit": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "pingStatus": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.FirewallOperation": { + "properties": { + "operation": { + "enum": [ + "start", + "stop", + "restart", + "disableBanPing", + "enableBanPing" + ], + "type": "string" + }, + "withDockerRestart": { + "type": "boolean" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + "dto.ForBuckets": { + "properties": { + "accessKey": { + "type": "string" + }, + "credential": { + "type": "string" + }, + "type": { + "type": "string" + }, + "vars": { + "type": "string" + } + }, + "required": [ + "credential", + "type", + "vars" + ], + "type": "object" + }, + "dto.ForceDelete": { + "properties": { + "forceDelete": { + "type": "boolean" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.ForwardRuleOperate": { + "properties": { + "forceDelete": { + "type": "boolean" + }, + "rules": { + "items": { + "properties": { + "interface": { + "type": "string" + }, + "num": { + "type": "string" + }, + "operation": { + "enum": [ + "add", + "remove" + ], + "type": "string" + }, + "port": { + "type": "string" + }, + "protocol": { + "enum": [ + "tcp", + "udp", + "tcp/udp" + ], + "type": "string" + }, + "targetIP": { + "type": "string" + }, + "targetPort": { + "type": "string" + } + }, + "required": [ + "operation", + "port", + "protocol", + "targetPort" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.FtpBaseInfo": { + "properties": { + "isActive": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.FtpCreate": { + "properties": { + "description": { + "type": "string" + }, + "password": { + "type": "string" + }, + "path": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "password", + "path", + "user" + ], + "type": "object" + }, + "dto.FtpLogSearch": { + "properties": { + "operation": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "user": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.FtpUpdate": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "password": { + "type": "string" + }, + "path": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "password", + "path" + ], + "type": "object" + }, + "dto.GPUInfo": { + "properties": { + "fanSpeed": { + "type": "string" + }, + "gpuUtil": { + "type": "string" + }, + "index": { + "type": "integer" + }, + "maxPowerLimit": { + "type": "string" + }, + "memTotal": { + "type": "string" + }, + "memUsed": { + "type": "string" + }, + "memoryUsage": { + "type": "string" + }, + "performanceState": { + "type": "string" + }, + "powerDraw": { + "type": "string" + }, + "powerUsage": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "temperature": { + "type": "string" + } + }, + "type": "object" + }, + "dto.GPUProcess": { + "properties": { + "pid": { + "type": "string" + }, + "processName": { + "type": "string" + }, + "type": { + "type": "string" + }, + "usedMemory": { + "type": "string" + } + }, + "type": "object" + }, + "dto.GroupCreate": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "dto.GroupSearch": { + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.GroupUpdate": { + "properties": { + "id": { + "type": "integer" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.HostConnTest": { + "properties": { + "addr": { + "type": "string" + }, + "authMode": { + "enum": [ + "password", + "key" + ], + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "addr", + "port", + "user" + ], + "type": "object" + }, + "dto.HostHelper": { + "properties": { + "host": { + "type": "string" + }, + "ip": { + "type": "string" + } + }, + "type": "object" + }, + "dto.HostInfo": { + "properties": { + "addr": { + "type": "string" + }, + "authMode": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "groupBelong": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "rememberPassword": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "dto.HostOperate": { + "properties": { + "addr": { + "type": "string" + }, + "authMode": { + "enum": [ + "password", + "key" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "rememberPassword": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "required": [ + "addr", + "port", + "user" + ], + "type": "object" + }, + "dto.HostTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/dto.TreeChild" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "label": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ImageBuild": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "dockerfile": { + "type": "string" + }, + "from": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "dockerfile", + "from", + "name" + ], + "type": "object" + }, + "dto.ImageInfo": { + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isPinned": { + "type": "boolean" + }, + "isUsed": { + "type": "boolean" + }, + "size": { + "type": "integer" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.ImageLoad": { + "properties": { + "path": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "dto.ImagePull": { + "properties": { + "imageName": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repoID": { + "type": "integer" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "imageName" + ], + "type": "object" + }, + "dto.ImagePush": { + "properties": { + "name": { + "type": "string" + }, + "repoID": { + "type": "integer" + }, + "tagName": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "name", + "repoID", + "tagName" + ], + "type": "object" + }, + "dto.ImageRepoDelete": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.ImageRepoOption": { + "properties": { + "downloadUrl": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ImageRepoUpdate": { + "properties": { + "auth": { + "type": "boolean" + }, + "downloadUrl": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "password": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ImageSave": { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "tagName": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "name", + "path", + "tagName" + ], + "type": "object" + }, + "dto.ImageTag": { + "properties": { + "sourceID": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "sourceID", + "tags" + ], + "type": "object" + }, + "dto.InspectReq": { + "properties": { + "detail": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "dto.InstallDetail": { + "properties": { + "detailID": { + "type": "integer" + }, + "httpPort": { + "type": "integer" + }, + "httpsPort": { + "type": "integer" + }, + "installID": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "status": { + "type": "string" + }, + "version": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "type": "object" + }, + "dto.IptablesBatchOperate": { + "properties": { + "rules": { + "items": { + "$ref": "#/definitions/dto.IptablesRuleOp" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.IptablesOp": { + "properties": { + "name": { + "enum": [ + "1PANEL_INPUT", + "1PANEL_OUTPUT", + "1PANEL_BASIC" + ], + "type": "string" + }, + "operate": { + "enum": [ + "init-base", + "init-forward", + "init-advance", + "bind-base", + "unbind-base", + "bind", + "unbind" + ], + "type": "string" + } + }, + "required": [ + "name", + "operate" + ], + "type": "object" + }, + "dto.IptablesRuleOp": { + "properties": { + "chain": { + "enum": [ + "1PANEL_BASIC", + "1PANEL_BASIC_BEFORE", + "1PANEL_INPUT", + "1PANEL_OUTPUT" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "dstIP": { + "type": "string" + }, + "dstPort": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "operation": { + "enum": [ + "add", + "remove" + ], + "type": "string" + }, + "protocol": { + "type": "string" + }, + "srcIP": { + "type": "string" + }, + "srcPort": { + "type": "integer" + }, + "strategy": { + "enum": [ + "accept", + "drop", + "reject" + ], + "type": "string" + } + }, + "required": [ + "chain", + "operation", + "strategy" + ], + "type": "object" + }, + "dto.LauncherOption": { + "properties": { + "isShow": { + "type": "boolean" + }, + "key": { + "type": "string" + } + }, + "type": "object" + }, + "dto.LoadRedisStatus": { + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "dto.Locale": { + "properties": { + "en": { + "type": "string" + }, + "es-es": { + "type": "string" + }, + "ja": { + "type": "string" + }, + "ko": { + "type": "string" + }, + "ms": { + "type": "string" + }, + "pt-br": { + "type": "string" + }, + "ru": { + "type": "string" + }, + "tr": { + "type": "string" + }, + "zh": { + "type": "string" + }, + "zh-hant": { + "type": "string" + } + }, + "type": "object" + }, + "dto.LogOption": { + "properties": { + "logMaxFile": { + "type": "string" + }, + "logMaxSize": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Login": { + "properties": { + "captcha": { + "type": "string" + }, + "captchaID": { + "type": "string" + }, + "language": { + "enum": [ + "zh", + "en", + "zh-Hant", + "ko", + "ja", + "ru", + "ms", + "pt-BR", + "tr", + "es-ES" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "language", + "name", + "password" + ], + "type": "object" + }, + "dto.MFALogin": { + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "code", + "name", + "password" + ], + "type": "object" + }, + "dto.MfaCredential": { + "properties": { + "code": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "secret": { + "type": "string" + } + }, + "required": [ + "code", + "interval", + "secret" + ], + "type": "object" + }, + "dto.MonitorData": { + "properties": { + "date": { + "items": { + "type": "string" + }, + "type": "array" + }, + "param": { + "type": "string" + }, + "value": { + "items": {}, + "type": "array" + } + }, + "type": "object" + }, + "dto.MonitorGPUData": { + "properties": { + "date": { + "items": { + "type": "string" + }, + "type": "array" + }, + "gpuProcesses": { + "items": { + "items": { + "$ref": "#/definitions/dto.GPUProcess" + }, + "type": "array" + }, + "type": "array" + }, + "gpuValue": { + "items": { + "type": "number" + }, + "type": "array" + }, + "memoryPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "memoryTotal": { + "items": { + "type": "number" + }, + "type": "array" + }, + "memoryUsed": { + "items": { + "type": "number" + }, + "type": "array" + }, + "powerPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "powerTotal": { + "items": { + "type": "number" + }, + "type": "array" + }, + "powerUsed": { + "items": { + "type": "number" + }, + "type": "array" + }, + "processCount": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "speedValue": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "temperatureValue": { + "items": { + "type": "number" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.MonitorGPUSearch": { + "properties": { + "endTime": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "startTime": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MonitorSearch": { + "properties": { + "endTime": { + "type": "string" + }, + "io": { + "type": "string" + }, + "network": { + "type": "string" + }, + "param": { + "enum": [ + "all", + "cpu", + "memory", + "load", + "io", + "network" + ], + "type": "string" + }, + "startTime": { + "type": "string" + } + }, + "required": [ + "param" + ], + "type": "object" + }, + "dto.MonitorSetting": { + "properties": { + "defaultIO": { + "type": "string" + }, + "defaultNetwork": { + "type": "string" + }, + "monitorInterval": { + "type": "string" + }, + "monitorStatus": { + "type": "string" + }, + "monitorStoreDays": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MonitorSettingUpdate": { + "properties": { + "key": { + "enum": [ + "MonitorStatus", + "MonitorStoreDays", + "MonitorInterval", + "DefaultNetwork", + "DefaultIO" + ], + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "dto.MysqlDBCreate": { + "properties": { + "collation": { + "type": "string" + }, + "database": { + "type": "string" + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "permission": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "database", + "format", + "from", + "name", + "password", + "permission", + "username" + ], + "type": "object" + }, + "dto.MysqlDBDelete": { + "properties": { + "database": { + "type": "string" + }, + "deleteBackup": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "mysql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "id", + "type" + ], + "type": "object" + }, + "dto.MysqlDBDeleteCheck": { + "properties": { + "database": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "mysql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "id", + "type" + ], + "type": "object" + }, + "dto.MysqlDBSearch": { + "properties": { + "database": { + "type": "string" + }, + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "database", + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.MysqlFormatCollationOption": { + "properties": { + "collations": { + "items": { + "type": "string" + }, + "type": "array" + }, + "format": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MysqlLoadDB": { + "properties": { + "database": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "mysql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "from", + "type" + ], + "type": "object" + }, + "dto.MysqlStatus": { + "properties": { + "Aborted_clients": { + "type": "string" + }, + "Aborted_connects": { + "type": "string" + }, + "Bytes_received": { + "type": "string" + }, + "Bytes_sent": { + "type": "string" + }, + "Com_commit": { + "type": "string" + }, + "Com_rollback": { + "type": "string" + }, + "Connections": { + "type": "string" + }, + "Created_tmp_disk_tables": { + "type": "string" + }, + "Created_tmp_tables": { + "type": "string" + }, + "File": { + "type": "string" + }, + "Innodb_buffer_pool_pages_dirty": { + "type": "string" + }, + "Innodb_buffer_pool_read_requests": { + "type": "string" + }, + "Innodb_buffer_pool_reads": { + "type": "string" + }, + "Key_read_requests": { + "type": "string" + }, + "Key_reads": { + "type": "string" + }, + "Key_write_requests": { + "type": "string" + }, + "Key_writes": { + "type": "string" + }, + "Max_used_connections": { + "type": "string" + }, + "Open_tables": { + "type": "string" + }, + "Opened_files": { + "type": "string" + }, + "Opened_tables": { + "type": "string" + }, + "Position": { + "type": "string" + }, + "Qcache_hits": { + "type": "string" + }, + "Qcache_inserts": { + "type": "string" + }, + "Questions": { + "type": "string" + }, + "Run": { + "type": "string" + }, + "Select_full_join": { + "type": "string" + }, + "Select_range_check": { + "type": "string" + }, + "Sort_merge_passes": { + "type": "string" + }, + "Table_locks_waited": { + "type": "string" + }, + "Threads_cached": { + "type": "string" + }, + "Threads_connected": { + "type": "string" + }, + "Threads_created": { + "type": "string" + }, + "Threads_running": { + "type": "string" + }, + "Uptime": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MysqlVariables": { + "properties": { + "binlog_cache_size": { + "type": "string" + }, + "innodb_buffer_pool_size": { + "type": "string" + }, + "innodb_log_buffer_size": { + "type": "string" + }, + "join_buffer_size": { + "type": "string" + }, + "key_buffer_size": { + "type": "string" + }, + "long_query_time": { + "type": "string" + }, + "max_connections": { + "type": "string" + }, + "max_heap_table_size": { + "type": "string" + }, + "query_cache_size": { + "type": "string" + }, + "query_cache_type": { + "type": "string" + }, + "read_buffer_size": { + "type": "string" + }, + "read_rnd_buffer_size": { + "type": "string" + }, + "slow_query_log": { + "type": "string" + }, + "sort_buffer_size": { + "type": "string" + }, + "table_open_cache": { + "type": "string" + }, + "thread_cache_size": { + "type": "string" + }, + "thread_stack": { + "type": "string" + }, + "tmp_table_size": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MysqlVariablesUpdate": { + "properties": { + "database": { + "type": "string" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "mysql-cluster" + ], + "type": "string" + }, + "variables": { + "items": { + "$ref": "#/definitions/dto.MysqlVariablesUpdateHelper" + }, + "type": "array" + } + }, + "required": [ + "database", + "type" + ], + "type": "object" + }, + "dto.MysqlVariablesUpdateHelper": { + "properties": { + "param": { + "type": "string" + }, + "value": {} + }, + "type": "object" + }, + "dto.NetworkCreate": { + "properties": { + "auxAddress": { + "items": { + "$ref": "#/definitions/dto.SettingUpdate" + }, + "type": "array" + }, + "auxAddressV6": { + "items": { + "$ref": "#/definitions/dto.SettingUpdate" + }, + "type": "array" + }, + "driver": { + "type": "string" + }, + "gateway": { + "type": "string" + }, + "gatewayV6": { + "type": "string" + }, + "ipRange": { + "type": "string" + }, + "ipRangeV6": { + "type": "string" + }, + "ipv4": { + "type": "boolean" + }, + "ipv6": { + "type": "boolean" + }, + "labels": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "options": { + "items": { + "type": "string" + }, + "type": "array" + }, + "subnet": { + "type": "string" + }, + "subnetV6": { + "type": "string" + } + }, + "required": [ + "driver", + "name" + ], + "type": "object" + }, + "dto.NginxAuth": { + "properties": { + "remark": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "dto.NginxKey": { + "enum": [ + "index", + "limit-conn", + "ssl", + "cache", + "http-per", + "proxy-cache" + ], + "type": "string", + "x-enum-varnames": [ + "Index", + "LimitConn", + "SSL", + "CACHE", + "HttpPer", + "ProxyCache" + ] + }, + "dto.NginxUpstream": { + "properties": { + "algorithm": { + "type": "string" + }, + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.NginxUpstreamServer": { + "properties": { + "failTimeout": { + "type": "integer" + }, + "failTimeoutUnit": { + "type": "string" + }, + "flag": { + "type": "string" + }, + "maxConns": { + "type": "integer" + }, + "maxFails": { + "type": "integer" + }, + "server": { + "type": "string" + }, + "weight": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.NodeCurrent": { + "properties": { + "cpuDetailedPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "cpuTotal": { + "type": "integer" + }, + "cpuUsed": { + "type": "number" + }, + "cpuUsedPercent": { + "type": "number" + }, + "load1": { + "type": "number" + }, + "load15": { + "type": "number" + }, + "load5": { + "type": "number" + }, + "loadUsagePercent": { + "type": "number" + }, + "memoryAvailable": { + "type": "integer" + }, + "memoryTotal": { + "type": "integer" + }, + "memoryUsed": { + "type": "integer" + }, + "memoryUsedPercent": { + "type": "number" + }, + "swapMemoryAvailable": { + "type": "integer" + }, + "swapMemoryTotal": { + "type": "integer" + }, + "swapMemoryUsed": { + "type": "integer" + }, + "swapMemoryUsedPercent": { + "type": "number" + } + }, + "type": "object" + }, + "dto.OllamaBindDomain": { + "properties": { + "appInstallID": { + "type": "integer" + }, + "domain": { + "type": "string" + }, + "ipList": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "appInstallID", + "domain" + ], + "type": "object" + }, + "dto.OllamaBindDomainReq": { + "properties": { + "appInstallID": { + "type": "integer" + } + }, + "required": [ + "appInstallID" + ], + "type": "object" + }, + "dto.OllamaBindDomainRes": { + "properties": { + "acmeAccountID": { + "type": "integer" + }, + "allowIPs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "connUrl": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.OllamaModelDropList": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OllamaModelName": { + "properties": { + "name": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Operate": { + "properties": { + "operation": { + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + "dto.OperateByID": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "dto.OperateByIDs": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.OperateByName": { + "properties": { + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OperateByTaskID": { + "properties": { + "taskID": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OperateByType": { + "properties": { + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OperationWithName": { + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "dto.OperationWithNameAndType": { + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.Options": { + "properties": { + "option": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OsInfo": { + "properties": { + "diskSize": { + "type": "integer" + }, + "kernelArch": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "os": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "platformFamily": { + "type": "string" + }, + "prettyDistro": { + "type": "string" + } + }, + "type": "object" + }, + "dto.PageContainer": { + "properties": { + "excludeAppStore": { + "type": "boolean" + }, + "filters": { + "type": "string" + }, + "name": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt", + "state" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "state": { + "enum": [ + "all", + "created", + "running", + "paused", + "restarting", + "removing", + "exited", + "dead" + ], + "type": "string" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize", + "state" + ], + "type": "object" + }, + "dto.PageCronjob": { + "properties": { + "groupIDs": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "status", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PageImage": { + "properties": { + "name": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "size", + "tags", + "createdAt", + "isUsed" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PageInfo": { + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PageResult": { + "properties": { + "items": {}, + "total": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.PageSnapshot": { + "properties": { + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PasswordUpdate": { + "properties": { + "newPassword": { + "type": "string" + }, + "oldPassword": { + "type": "string" + } + }, + "required": [ + "newPassword", + "oldPassword" + ], + "type": "object" + }, + "dto.PortHelper": { + "properties": { + "containerPort": { + "type": "string" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "type": "string" + }, + "protocol": { + "type": "string" + } + }, + "type": "object" + }, + "dto.PortRuleOperate": { + "properties": { + "address": { + "type": "string" + }, + "chain": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "operation": { + "enum": [ + "add", + "remove" + ], + "type": "string" + }, + "port": { + "type": "string" + }, + "protocol": { + "enum": [ + "tcp", + "udp", + "tcp/udp" + ], + "type": "string" + }, + "strategy": { + "enum": [ + "accept", + "drop" + ], + "type": "string" + } + }, + "required": [ + "operation", + "port", + "protocol", + "strategy" + ], + "type": "object" + }, + "dto.PortRuleUpdate": { + "properties": { + "newRule": { + "$ref": "#/definitions/dto.PortRuleOperate" + }, + "oldRule": { + "$ref": "#/definitions/dto.PortRuleOperate" + } + }, + "type": "object" + }, + "dto.PortUpdate": { + "properties": { + "serverPort": { + "maximum": 65535, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "serverPort" + ], + "type": "object" + }, + "dto.PostgresqlBindUser": { + "properties": { + "database": { + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "superUser": { + "type": "boolean" + }, + "username": { + "type": "string" + } + }, + "required": [ + "database", + "name", + "password", + "username" + ], + "type": "object" + }, + "dto.PostgresqlDBCreate": { + "properties": { + "database": { + "type": "string" + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "superUser": { + "type": "boolean" + }, + "username": { + "type": "string" + } + }, + "required": [ + "database", + "from", + "name", + "password", + "username" + ], + "type": "object" + }, + "dto.PostgresqlDBDelete": { + "properties": { + "database": { + "type": "string" + }, + "deleteBackup": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "postgresql", + "postgresql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "id", + "type" + ], + "type": "object" + }, + "dto.PostgresqlDBDeleteCheck": { + "properties": { + "database": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "postgresql", + "postgresql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "id", + "type" + ], + "type": "object" + }, + "dto.PostgresqlDBSearch": { + "properties": { + "database": { + "type": "string" + }, + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "database", + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PostgresqlLoadDB": { + "properties": { + "database": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "type": { + "enum": [ + "postgresql", + "postgresql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "from", + "type" + ], + "type": "object" + }, + "dto.Process": { + "properties": { + "cmd": { + "type": "string" + }, + "memory": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "percent": { + "type": "number" + }, + "pid": { + "type": "integer" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ProxyUpdate": { + "properties": { + "proxyDocker": { + "type": "boolean" + }, + "proxyPasswd": { + "type": "string" + }, + "proxyPasswdKeep": { + "type": "string" + }, + "proxyPort": { + "type": "string" + }, + "proxyType": { + "type": "string" + }, + "proxyUrl": { + "type": "string" + }, + "proxyUser": { + "type": "string" + }, + "withDockerRestart": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.QuickJump": { + "properties": { + "alias": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isShow": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "router": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "type": "object" + }, + "dto.RecordFileSize": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.RecordSearch": { + "properties": { + "detailName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "type" + ], + "type": "object" + }, + "dto.RecordSearchByCronjob": { + "properties": { + "cronjobID": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "cronjobID", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.RedisConf": { + "properties": { + "containerName": { + "type": "string" + }, + "database": { + "type": "string" + }, + "maxclients": { + "type": "string" + }, + "maxmemory": { + "type": "string" + }, + "name": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "requirepass": { + "type": "string" + }, + "timeout": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + }, + "dto.RedisConfPersistenceUpdate": { + "properties": { + "appendfsync": { + "type": "string" + }, + "appendonly": { + "type": "string" + }, + "database": { + "type": "string" + }, + "dbType": { + "enum": [ + "redis", + "redis-cluster" + ], + "type": "string" + }, + "save": { + "type": "string" + }, + "type": { + "enum": [ + "aof", + "rbd" + ], + "type": "string" + } + }, + "required": [ + "database", + "dbType", + "type" + ], + "type": "object" + }, + "dto.RedisConfUpdate": { + "properties": { + "database": { + "type": "string" + }, + "dbType": { + "enum": [ + "redis", + "redis-cluster" + ], + "type": "string" + }, + "maxclients": { + "type": "string" + }, + "maxmemory": { + "type": "string" + }, + "timeout": { + "type": "string" + } + }, + "required": [ + "database", + "dbType" + ], + "type": "object" + }, + "dto.RedisPersistence": { + "properties": { + "appendfsync": { + "type": "string" + }, + "appendonly": { + "type": "string" + }, + "database": { + "type": "string" + }, + "save": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + }, + "dto.RedisStatus": { + "properties": { + "connected_clients": { + "type": "string" + }, + "database": { + "type": "string" + }, + "instantaneous_ops_per_sec": { + "type": "string" + }, + "keyspace_hits": { + "type": "string" + }, + "keyspace_misses": { + "type": "string" + }, + "latest_fork_usec": { + "type": "string" + }, + "mem_fragmentation_ratio": { + "type": "string" + }, + "tcp_port": { + "type": "string" + }, + "total_commands_processed": { + "type": "string" + }, + "total_connections_received": { + "type": "string" + }, + "uptime_in_days": { + "type": "string" + }, + "used_memory": { + "type": "string" + }, + "used_memory_peak": { + "type": "string" + }, + "used_memory_rss": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + }, + "dto.ReleasesNotes": { + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "fixCount": { + "type": "integer" + }, + "newCount": { + "type": "integer" + }, + "optimizationCount": { + "type": "integer" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ResourceLimit": { + "properties": { + "cpu": { + "type": "integer" + }, + "memory": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.RootCertOperate": { + "properties": { + "description": { + "type": "string" + }, + "encryptionMode": { + "enum": [ + "rsa", + "ed25519", + "ecdsa", + "dsa" + ], + "type": "string" + }, + "id": { + "type": "integer" + }, + "mode": { + "type": "string" + }, + "name": { + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "publicKey": { + "type": "string" + } + }, + "required": [ + "encryptionMode" + ], + "type": "object" + }, + "dto.RuleSearch": { + "properties": { + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "strategy": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "type" + ], + "type": "object" + }, + "dto.SSHConf": { + "properties": { + "file": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SSHConnData": { + "properties": { + "addr": { + "type": "string" + }, + "authMode": { + "enum": [ + "password", + "key" + ], + "type": "string" + }, + "localSSHConnShow": { + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "addr", + "port", + "user" + ], + "type": "object" + }, + "dto.SSHDefaultConn": { + "properties": { + "defaultConn": { + "type": "string" + }, + "withReset": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.SSHInfo": { + "properties": { + "autoStart": { + "type": "boolean" + }, + "currentUser": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "listenAddress": { + "type": "string" + }, + "message": { + "type": "string" + }, + "passwordAuthentication": { + "type": "string" + }, + "permitRootLogin": { + "type": "string" + }, + "port": { + "type": "string" + }, + "pubkeyAuthentication": { + "type": "string" + }, + "useDNS": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SSHUpdate": { + "properties": { + "key": { + "type": "string" + }, + "newValue": { + "type": "string" + }, + "oldValue": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "dto.SSLInfo": { + "properties": { + "cert": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "key": { + "type": "string" + }, + "rootPath": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "timeout": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SSLUpdate": { + "properties": { + "cert": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "key": { + "type": "string" + }, + "ssl": { + "enum": [ + "Enable", + "Disable", + "Mux" + ], + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "sslType": { + "enum": [ + "self", + "select", + "import", + "import-paste", + "import-local" + ], + "type": "string" + } + }, + "required": [ + "ssl", + "sslType" + ], + "type": "object" + }, + "dto.ScriptOperate": { + "properties": { + "description": { + "type": "string" + }, + "groups": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isInteractive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "script": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ScriptOptions": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SearchByFilter": { + "properties": { + "filter": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SearchClamWithPage": { + "properties": { + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "status", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchForSize": { + "properties": { + "cronjobID": { + "type": "integer" + }, + "detailName": { + "type": "string" + }, + "info": { + "type": "string" + }, + "name": { + "type": "string" + }, + "order": { + "type": "string" + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "type" + ], + "type": "object" + }, + "dto.SearchForTree": { + "properties": { + "info": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SearchLgLogWithPage": { + "properties": { + "ip": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchOpLogWithPage": { + "properties": { + "node": { + "type": "string" + }, + "operation": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "source": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchPageWithGroup": { + "properties": { + "groupID": { + "type": "integer" + }, + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchPageWithType": { + "properties": { + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchRecord": { + "properties": { + "cronjobID": { + "type": "integer" + }, + "endTime": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "startTime": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchSSHLog": { + "properties": { + "Status": { + "enum": [ + "Success", + "Failed", + "All" + ], + "type": "string" + }, + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "Status", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchTaskLogReq": { + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchWithPage": { + "properties": { + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SettingInfo": { + "properties": { + "appStoreLastModified": { + "type": "string" + }, + "appStoreSyncStatus": { + "type": "string" + }, + "appStoreVersion": { + "type": "string" + }, + "defaultIO": { + "type": "string" + }, + "defaultNetwork": { + "type": "string" + }, + "dockerSockPath": { + "type": "string" + }, + "fileRecycleBin": { + "type": "string" + }, + "lastCleanData": { + "type": "string" + }, + "lastCleanSize": { + "type": "string" + }, + "lastCleanTime": { + "type": "string" + }, + "localTime": { + "type": "string" + }, + "monitorInterval": { + "type": "string" + }, + "monitorStatus": { + "type": "string" + }, + "monitorStoreDays": { + "type": "string" + }, + "ntpSite": { + "type": "string" + }, + "systemIP": { + "type": "string" + }, + "systemVersion": { + "type": "string" + }, + "timeZone": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SettingUpdate": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "dto.SnapshotBatchDelete": { + "properties": { + "deleteWithFile": { + "type": "boolean" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.SnapshotCreate": { + "properties": { + "appData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "backupData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "description": { + "maxLength": 256, + "type": "string" + }, + "downloadAccountID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "ignoreFiles": { + "items": { + "type": "string" + }, + "type": "array" + }, + "interruptStep": { + "type": "string" + }, + "name": { + "type": "string" + }, + "panelData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "secret": { + "type": "string" + }, + "sourceAccountIDs": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "timeout": { + "type": "integer" + }, + "withDockerConf": { + "type": "boolean" + }, + "withLoginLog": { + "type": "boolean" + }, + "withMonitorData": { + "type": "boolean" + }, + "withOperationLog": { + "type": "boolean" + }, + "withSystemLog": { + "type": "boolean" + }, + "withTaskLog": { + "type": "boolean" + } + }, + "required": [ + "downloadAccountID", + "sourceAccountIDs" + ], + "type": "object" + }, + "dto.SnapshotData": { + "properties": { + "appData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "backupData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "ignoreFiles": { + "items": { + "type": "string" + }, + "type": "array" + }, + "panelData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "withDockerConf": { + "type": "boolean" + }, + "withLoginLog": { + "type": "boolean" + }, + "withMonitorData": { + "type": "boolean" + }, + "withOperationLog": { + "type": "boolean" + }, + "withSystemLog": { + "type": "boolean" + }, + "withTaskLog": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.SnapshotImport": { + "properties": { + "backupAccountID": { + "type": "integer" + }, + "description": { + "maxLength": 256, + "type": "string" + }, + "names": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.SnapshotRecover": { + "properties": { + "id": { + "type": "integer" + }, + "isNew": { + "type": "boolean" + }, + "reDownload": { + "type": "boolean" + }, + "secret": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "dto.SnapshotRule": { + "properties": { + "ignoreAppIDs": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "withImage": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.SnapshotTransHelper": { + "properties": { + "ignoreApps": { + "items": { + "$ref": "#/definitions/dto.TransHelper" + }, + "type": "array" + }, + "withImage": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.SwapHelper": { + "properties": { + "isNew": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "taskID": { + "type": "string" + }, + "used": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "dto.SystemSetting": { + "properties": { + "isDemo": { + "type": "boolean" + }, + "isIntl": { + "type": "boolean" + }, + "language": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Tag": { + "properties": { + "key": { + "type": "string" + }, + "locales": { + "$ref": "#/definitions/dto.Locale" + }, + "name": { + "type": "string" + }, + "sort": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.TerminalInfo": { + "properties": { + "cursorBlink": { + "type": "string" + }, + "cursorStyle": { + "type": "string" + }, + "fontSize": { + "type": "string" + }, + "letterSpacing": { + "type": "string" + }, + "lineHeight": { + "type": "string" + }, + "scrollSensitivity": { + "type": "string" + }, + "scrollback": { + "type": "string" + } + }, + "type": "object" + }, + "dto.TransHelper": { + "properties": { + "detailName": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.TreeChild": { + "properties": { + "id": { + "type": "integer" + }, + "label": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UpdateByFile": { + "properties": { + "file": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UpdateByNameAndFile": { + "properties": { + "file": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UpdateDescription": { + "properties": { + "description": { + "maxLength": 256, + "type": "string" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "dto.UpdateFirewallDescription": { + "properties": { + "chain": { + "type": "string" + }, + "description": { + "type": "string" + }, + "dstIP": { + "type": "string" + }, + "dstPort": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "srcIP": { + "type": "string" + }, + "srcPort": { + "type": "string" + }, + "strategy": { + "enum": [ + "accept", + "drop" + ], + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "type": "object" + }, + "dto.Upgrade": { + "properties": { + "version": { + "type": "string" + } + }, + "required": [ + "version" + ], + "type": "object" + }, + "dto.UpgradeInfo": { + "properties": { + "latestVersion": { + "type": "string" + }, + "newVersion": { + "type": "string" + }, + "releaseNote": { + "type": "string" + }, + "testVersion": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UploadForRecover": { + "properties": { + "filePath": { + "type": "string" + }, + "targetDir": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UserLoginInfo": { + "properties": { + "mfaStatus": { + "type": "string" + }, + "name": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "dto.VolumeCreate": { + "properties": { + "driver": { + "type": "string" + }, + "labels": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "options": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "driver", + "name" + ], + "type": "object" + }, + "dto.VolumeHelper": { + "properties": { + "containerDir": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "shared": { + "type": "string" + }, + "sourceDir": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.XPUInfo": { + "properties": { + "deviceID": { + "type": "integer" + }, + "deviceName": { + "type": "string" + }, + "memory": { + "type": "string" + }, + "memoryUsed": { + "type": "string" + }, + "memoryUtil": { + "type": "string" + }, + "power": { + "type": "string" + }, + "temperature": { + "type": "string" + } + }, + "type": "object" + }, + "files.FileInfo": { + "properties": { + "content": { + "type": "string" + }, + "extension": { + "type": "string" + }, + "favoriteID": { + "type": "integer" + }, + "gid": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isDetail": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "isHidden": { + "type": "boolean" + }, + "isSymlink": { + "type": "boolean" + }, + "itemTotal": { + "type": "integer" + }, + "items": { + "items": { + "$ref": "#/definitions/files.FileInfo" + }, + "type": "array" + }, + "linkPath": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "modTime": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "updateTime": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "mfa.Otp": { + "properties": { + "qrImage": { + "type": "string" + }, + "secret": { + "type": "string" + } + }, + "type": "object" + }, + "model.App": { + "properties": { + "architectures": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "crossVersionUpdate": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "document": { + "type": "string" + }, + "github": { + "type": "string" + }, + "gpuSupport": { + "type": "boolean" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "memoryRequired": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "readMe": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "required": { + "type": "string" + }, + "requiredPanelVersion": { + "type": "number" + }, + "resource": { + "type": "string" + }, + "shortDescEn": { + "type": "string" + }, + "shortDescZh": { + "type": "string" + }, + "status": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "website": { + "type": "string" + } + }, + "type": "object" + }, + "model.AppIgnoreUpgrade": { + "properties": { + "appDetailID": { + "type": "integer" + }, + "appID": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "scope": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "type": "object" + }, + "model.AppInstall": { + "properties": { + "app": { + "$ref": "#/definitions/model.App" + }, + "appDetailId": { + "type": "integer" + }, + "appId": { + "type": "integer" + }, + "containerName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "dockerCompose": { + "type": "string" + }, + "env": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "httpPort": { + "type": "integer" + }, + "httpsPort": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "param": { + "type": "string" + }, + "serviceName": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "type": "object" + }, + "model.Favorite": { + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isDir": { + "type": "boolean" + }, + "isTxt": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "type": "object" + }, + "model.Runtime": { + "properties": { + "appDetailID": { + "type": "integer" + }, + "codeDir": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dockerCompose": { + "type": "string" + }, + "env": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "params": { + "type": "string" + }, + "port": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "resource": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "string" + }, + "workDir": { + "type": "string" + } + }, + "type": "object" + }, + "model.Website": { + "properties": { + "IPV6": { + "type": "boolean" + }, + "accessLog": { + "type": "boolean" + }, + "alias": { + "type": "string" + }, + "appInstallId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "dbID": { + "type": "integer" + }, + "dbType": { + "type": "string" + }, + "defaultServer": { + "type": "boolean" + }, + "domains": { + "items": { + "$ref": "#/definitions/model.WebsiteDomain" + }, + "type": "array" + }, + "errorLog": { + "type": "boolean" + }, + "expireDate": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "ftpId": { + "type": "integer" + }, + "group": { + "type": "string" + }, + "httpConfig": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "parentWebsiteID": { + "type": "integer" + }, + "primaryDomain": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "proxy": { + "type": "string" + }, + "proxyType": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "rewrite": { + "type": "string" + }, + "runtimeID": { + "type": "integer" + }, + "siteDir": { + "type": "string" + }, + "status": { + "type": "string" + }, + "streamPorts": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "user": { + "type": "string" + }, + "webSiteGroupId": { + "type": "integer" + }, + "webSiteSSL": { + "$ref": "#/definitions/model.WebsiteSSL" + }, + "webSiteSSLId": { + "type": "integer" + } + }, + "type": "object" + }, + "model.WebsiteAcmeAccount": { + "properties": { + "caDirURL": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "eabHmacKey": { + "type": "string" + }, + "eabKid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "keyType": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "url": { + "type": "string" + }, + "useProxy": { + "type": "boolean" + } + }, + "type": "object" + }, + "model.WebsiteDnsAccount": { + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "type": "object" + }, + "model.WebsiteDomain": { + "properties": { + "createdAt": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "ssl": { + "type": "boolean" + }, + "updatedAt": { + "type": "string" + }, + "websiteId": { + "type": "integer" + } + }, + "type": "object" + }, + "model.WebsiteSSL": { + "properties": { + "acmeAccount": { + "$ref": "#/definitions/model.WebsiteAcmeAccount" + }, + "acmeAccountId": { + "type": "integer" + }, + "autoRenew": { + "type": "boolean" + }, + "caId": { + "type": "integer" + }, + "certPath": { + "type": "string" + }, + "certURL": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "disableCNAME": { + "type": "boolean" + }, + "dnsAccount": { + "$ref": "#/definitions/model.WebsiteDnsAccount" + }, + "dnsAccountId": { + "type": "integer" + }, + "domains": { + "type": "string" + }, + "execShell": { + "type": "boolean" + }, + "expireDate": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isIP": { + "type": "boolean" + }, + "keyType": { + "type": "string" + }, + "masterSslId": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "nameserver1": { + "type": "string" + }, + "nameserver2": { + "type": "string" + }, + "nodes": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "pem": { + "type": "string" + }, + "primaryDomain": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "pushNode": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "skipDNS": { + "type": "boolean" + }, + "startDate": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "websites": { + "items": { + "$ref": "#/definitions/model.Website" + }, + "type": "array" + } + }, + "type": "object" + }, + "request.AppConfigUpdate": { + "properties": { + "installID": { + "type": "integer" + }, + "webUI": { + "type": "string" + } + }, + "required": [ + "installID" + ], + "type": "object" + }, + "request.AppIgnoreUpgradeReq": { + "properties": { + "appDetailID": { + "type": "integer" + }, + "appID": { + "type": "integer" + }, + "scope": { + "enum": [ + "all", + "version" + ], + "type": "string" + } + }, + "required": [ + "appID", + "scope" + ], + "type": "object" + }, + "request.AppInstallCreate": { + "properties": { + "advanced": { + "type": "boolean" + }, + "allowPort": { + "type": "boolean" + }, + "appDetailId": { + "type": "integer" + }, + "containerName": { + "type": "string" + }, + "cpuQuota": { + "type": "number" + }, + "dockerCompose": { + "type": "string" + }, + "editCompose": { + "type": "boolean" + }, + "gpuConfig": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "memoryLimit": { + "type": "number" + }, + "memoryUnit": { + "type": "string" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "pullImage": { + "type": "boolean" + }, + "restartPolicy": { + "enum": [ + "always", + "unless-stopped", + "no", + "on-failure" + ], + "type": "string" + }, + "services": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "specifyIP": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "required": [ + "appDetailId", + "name" + ], + "type": "object" + }, + "request.AppInstalledInfo": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "request.AppInstalledOperate": { + "properties": { + "backup": { + "type": "boolean" + }, + "backupId": { + "type": "integer" + }, + "deleteBackup": { + "type": "boolean" + }, + "deleteDB": { + "type": "boolean" + }, + "deleteImage": { + "type": "boolean" + }, + "detailId": { + "type": "integer" + }, + "dockerCompose": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "installId": { + "type": "integer" + }, + "operate": { + "type": "string" + }, + "pullImage": { + "type": "boolean" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "installId", + "operate" + ], + "type": "object" + }, + "request.AppInstalledSearch": { + "properties": { + "all": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "sync": { + "type": "boolean" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "unused": { + "type": "boolean" + }, + "update": { + "type": "boolean" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.AppInstalledUpdate": { + "properties": { + "advanced": { + "type": "boolean" + }, + "allowPort": { + "type": "boolean" + }, + "containerName": { + "type": "string" + }, + "cpuQuota": { + "type": "number" + }, + "dockerCompose": { + "type": "string" + }, + "editCompose": { + "type": "boolean" + }, + "gpuConfig": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "installId": { + "type": "integer" + }, + "memoryLimit": { + "type": "number" + }, + "memoryUnit": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "pullImage": { + "type": "boolean" + }, + "restartPolicy": { + "enum": [ + "always", + "unless-stopped", + "no", + "on-failure" + ], + "type": "string" + }, + "specifyIP": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "required": [ + "installId", + "params" + ], + "type": "object" + }, + "request.AppSearch": { + "properties": { + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "recommend": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "showCurrentArch": { + "type": "boolean" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.BatchWebsiteGroup": { + "properties": { + "groupID": { + "type": "integer" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "groupID", + "ids" + ], + "type": "object" + }, + "request.BatchWebsiteHttps": { + "properties": { + "SSLProtocol": { + "items": { + "type": "string" + }, + "type": "array" + }, + "algorithm": { + "type": "string" + }, + "certificate": { + "type": "string" + }, + "certificatePath": { + "type": "string" + }, + "hsts": { + "type": "boolean" + }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, + "http3": { + "type": "boolean" + }, + "httpConfig": { + "enum": [ + "HTTPSOnly", + "HTTPAlso", + "HTTPToHTTPS" + ], + "type": "string" + }, + "httpsPorts": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "importType": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "type": { + "enum": [ + "existed", + "auto", + "manual" + ], + "type": "string" + }, + "websiteSSLId": { + "type": "integer" + } + }, + "required": [ + "ids", + "taskID" + ], + "type": "object" + }, + "request.BatchWebsiteOp": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "operate": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "ids", + "operate", + "taskID" + ], + "type": "object" + }, + "request.ChangeDatabase": { + "properties": { + "databaseID": { + "type": "integer" + }, + "databaseType": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.CorsConfig": { + "properties": { + "allowCredentials": { + "type": "boolean" + }, + "allowHeaders": { + "type": "string" + }, + "allowMethods": { + "type": "string" + }, + "allowOrigins": { + "type": "string" + }, + "cors": { + "type": "boolean" + }, + "preflight": { + "type": "boolean" + } + }, + "type": "object" + }, + "request.CorsConfigReq": { + "properties": { + "allowCredentials": { + "type": "boolean" + }, + "allowHeaders": { + "type": "string" + }, + "allowMethods": { + "type": "string" + }, + "allowOrigins": { + "type": "string" + }, + "cors": { + "type": "boolean" + }, + "preflight": { + "type": "boolean" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.CrossSiteAccessOp": { + "properties": { + "operation": { + "enum": [ + "Enable", + "Disable" + ], + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "operation", + "websiteID" + ], + "type": "object" + }, + "request.CustomRewriteOperate": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "operate": { + "enum": [ + "create", + "delete" + ], + "type": "string" + } + }, + "required": [ + "operate" + ], + "type": "object" + }, + "request.DirSizeReq": { + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.DiskMountRequest": { + "properties": { + "autoMount": { + "type": "boolean" + }, + "device": { + "type": "string" + }, + "filesystem": { + "enum": [ + "ext4", + "xfs" + ], + "type": "string" + }, + "mountPoint": { + "type": "string" + }, + "noFail": { + "type": "boolean" + } + }, + "required": [ + "device", + "filesystem", + "mountPoint" + ], + "type": "object" + }, + "request.DiskPartitionRequest": { + "properties": { + "autoMount": { + "type": "boolean" + }, + "device": { + "type": "string" + }, + "filesystem": { + "enum": [ + "ext4", + "xfs" + ], + "type": "string" + }, + "label": { + "type": "string" + }, + "mountPoint": { + "type": "string" + } + }, + "required": [ + "device", + "filesystem", + "mountPoint" + ], + "type": "object" + }, + "request.DiskUnmountRequest": { + "properties": { + "mountPoint": { + "type": "string" + } + }, + "required": [ + "mountPoint" + ], + "type": "object" + }, + "request.Environment": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "request.ExecComposerReq": { + "properties": { + "command": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "extCommand": { + "type": "string" + }, + "mirror": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "user": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "command", + "dir", + "mirror", + "taskID", + "user", + "websiteID" + ], + "type": "object" + }, + "request.ExposedPort": { + "properties": { + "containerPort": { + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "type": "integer" + } + }, + "type": "object" + }, + "request.ExtraHost": { + "properties": { + "hostname": { + "type": "string" + }, + "ip": { + "type": "string" + } + }, + "type": "object" + }, + "request.FPMConfig": { + "properties": { + "id": { + "type": "integer" + }, + "params": { + "additionalProperties": true, + "type": "object" + } + }, + "required": [ + "id", + "params" + ], + "type": "object" + }, + "request.FavoriteCreate": { + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FavoriteDelete": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.FileBatchDelete": { + "properties": { + "isDir": { + "type": "boolean" + }, + "paths": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "paths" + ], + "type": "object" + }, + "request.FileCompress": { + "properties": { + "dst": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "replace": { + "type": "boolean" + }, + "secret": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "dst", + "files", + "name", + "type" + ], + "type": "object" + }, + "request.FileContentReq": { + "properties": { + "isDetail": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FileConvert": { + "properties": { + "extension": { + "type": "string" + }, + "inputFile": { + "type": "string" + }, + "outputFormat": { + "type": "string" + }, + "path": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "extension", + "inputFile", + "outputFormat", + "path", + "type" + ], + "type": "object" + }, + "request.FileCreate": { + "properties": { + "content": { + "type": "string" + }, + "isDir": { + "type": "boolean" + }, + "isLink": { + "type": "boolean" + }, + "isSymlink": { + "type": "boolean" + }, + "linkPath": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "sub": { + "type": "boolean" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FileDeCompress": { + "properties": { + "dst": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "dst", + "path", + "type" + ], + "type": "object" + }, + "request.FileDelete": { + "properties": { + "forceDelete": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FileDownload": { + "properties": { + "compress": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "paths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "paths", + "type" + ], + "type": "object" + }, + "request.FileEdit": { + "properties": { + "content": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FileMove": { + "properties": { + "cover": { + "type": "boolean" + }, + "coverPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "newPath": { + "type": "string" + }, + "oldPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "required": [ + "newPath", + "oldPaths", + "type" + ], + "type": "object" + }, + "request.FileOption": { + "properties": { + "containSub": { + "type": "boolean" + }, + "dir": { + "type": "boolean" + }, + "expand": { + "type": "boolean" + }, + "isDetail": { + "type": "boolean" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "search": { + "type": "string" + }, + "showHidden": { + "type": "boolean" + }, + "sortBy": { + "type": "string" + }, + "sortOrder": { + "type": "string" + } + }, + "type": "object" + }, + "request.FilePathCheck": { + "properties": { + "path": { + "type": "string" + }, + "withInit": { + "type": "boolean" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FilePathsCheck": { + "properties": { + "paths": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "paths" + ], + "type": "object" + }, + "request.FileReadByLineReq": { + "properties": { + "ID": { + "type": "integer" + }, + "latest": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "resourceID": { + "type": "integer" + }, + "taskID": { + "type": "string" + }, + "taskOperate": { + "type": "string" + }, + "taskType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "type" + ], + "type": "object" + }, + "request.FileRename": { + "properties": { + "newName": { + "type": "string" + }, + "oldName": { + "type": "string" + } + }, + "required": [ + "newName", + "oldName" + ], + "type": "object" + }, + "request.FileRoleReq": { + "properties": { + "group": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "paths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "sub": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "required": [ + "group", + "mode", + "paths", + "user" + ], + "type": "object" + }, + "request.FileRoleUpdate": { + "properties": { + "group": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sub": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "required": [ + "group", + "path", + "user" + ], + "type": "object" + }, + "request.FileWget": { + "properties": { + "ignoreCertificate": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "name", + "path", + "url" + ], + "type": "object" + }, + "request.HostToolConfig": { + "properties": { + "content": { + "type": "string" + }, + "operate": { + "enum": [ + "get", + "set" + ], + "type": "string" + }, + "type": { + "enum": [ + "supervisord" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "request.HostToolCreate": { + "properties": { + "configPath": { + "type": "string" + }, + "serviceName": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "request.HostToolReq": { + "properties": { + "operate": { + "enum": [ + "status", + "restart", + "start", + "stop" + ], + "type": "string" + }, + "type": { + "enum": [ + "supervisord" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "request.McpBindDomain": { + "properties": { + "domain": { + "type": "string" + }, + "ipList": { + "type": "string" + }, + "sslID": { + "type": "integer" + } + }, + "required": [ + "domain" + ], + "type": "object" + }, + "request.McpBindDomainUpdate": { + "properties": { + "ipList": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.McpServerCreate": { + "properties": { + "baseUrl": { + "type": "string" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "hostIP": { + "type": "string" + }, + "name": { + "type": "string" + }, + "outputTransport": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "ssePath": { + "type": "string" + }, + "streamableHttpPath": { + "type": "string" + }, + "type": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "required": [ + "command", + "name", + "outputTransport", + "port", + "type" + ], + "type": "object" + }, + "request.McpServerDelete": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.McpServerOperate": { + "properties": { + "id": { + "type": "integer" + }, + "operate": { + "type": "string" + } + }, + "required": [ + "id", + "operate" + ], + "type": "object" + }, + "request.McpServerSearch": { + "properties": { + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "sync": { + "type": "boolean" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.McpServerUpdate": { + "properties": { + "baseUrl": { + "type": "string" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "hostIP": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "outputTransport": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "ssePath": { + "type": "string" + }, + "streamableHttpPath": { + "type": "string" + }, + "type": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "required": [ + "command", + "id", + "name", + "outputTransport", + "port", + "type" + ], + "type": "object" + }, + "request.NewAppInstall": { + "properties": { + "advanced": { + "type": "boolean" + }, + "allowPort": { + "type": "boolean" + }, + "appDetailID": { + "type": "integer" + }, + "containerName": { + "type": "string" + }, + "cpuQuota": { + "type": "number" + }, + "dockerCompose": { + "type": "string" + }, + "editCompose": { + "type": "boolean" + }, + "gpuConfig": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "memoryLimit": { + "type": "number" + }, + "memoryUnit": { + "type": "string" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "pullImage": { + "type": "boolean" + }, + "restartPolicy": { + "enum": [ + "always", + "unless-stopped", + "no", + "on-failure" + ], + "type": "string" + }, + "specifyIP": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "type": "object" + }, + "request.NginxAntiLeechUpdate": { + "properties": { + "blocked": { + "type": "boolean" + }, + "cache": { + "type": "boolean" + }, + "cacheTime": { + "type": "integer" + }, + "cacheUint": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "extends": { + "type": "string" + }, + "logEnable": { + "type": "boolean" + }, + "noneRef": { + "type": "boolean" + }, + "return": { + "type": "string" + }, + "serverNames": { + "items": { + "type": "string" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.NginxAuthReq": { + "properties": { + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.NginxAuthUpdate": { + "properties": { + "operate": { + "type": "string" + }, + "password": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "username": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "operate", + "websiteID" + ], + "type": "object" + }, + "request.NginxBuildReq": { + "properties": { + "mirror": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "mirror", + "taskID" + ], + "type": "object" + }, + "request.NginxCommonReq": { + "properties": { + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.NginxConfigFileUpdate": { + "properties": { + "backup": { + "type": "boolean" + }, + "content": { + "type": "string" + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "request.NginxConfigUpdate": { + "properties": { + "operate": { + "enum": [ + "add", + "update", + "delete" + ], + "type": "string" + }, + "params": {}, + "scope": { + "$ref": "#/definitions/dto.NginxKey" + }, + "websiteId": { + "type": "integer" + } + }, + "required": [ + "operate" + ], + "type": "object" + }, + "request.NginxDefaultHTTPSUpdate": { + "properties": { + "operate": { + "enum": [ + "enable", + "disable" + ], + "type": "string" + }, + "sslRejectHandshake": { + "type": "boolean" + } + }, + "required": [ + "operate" + ], + "type": "object" + }, + "request.NginxModuleUpdate": { + "properties": { + "enable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "operate": { + "enum": [ + "create", + "delete", + "update" + ], + "type": "string" + }, + "packages": { + "type": "string" + }, + "params": { + "type": "string" + }, + "script": { + "type": "string" + } + }, + "required": [ + "name", + "operate" + ], + "type": "object" + }, + "request.NginxPathAuthUpdate": { + "properties": { + "name": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "password": { + "type": "string" + }, + "path": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "username": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "operate", + "websiteID" + ], + "type": "object" + }, + "request.NginxProxyCacheUpdate": { + "properties": { + "cacheExpire": { + "type": "integer" + }, + "cacheExpireUnit": { + "type": "string" + }, + "cacheLimit": { + "type": "integer" + }, + "cacheLimitUnit": { + "type": "string" + }, + "open": { + "type": "boolean" + }, + "shareCache": { + "type": "integer" + }, + "shareCacheUnit": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "cacheExpire", + "cacheExpireUnit", + "cacheLimit", + "cacheLimitUnit", + "shareCache", + "shareCacheUnit", + "websiteID" + ], + "type": "object" + }, + "request.NginxProxyUpdate": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "content", + "name", + "websiteID" + ], + "type": "object" + }, + "request.NginxRedirectReq": { + "properties": { + "domains": { + "items": { + "type": "string" + }, + "type": "array" + }, + "enable": { + "type": "boolean" + }, + "keepPath": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "path": { + "type": "string" + }, + "redirect": { + "type": "string" + }, + "redirectRoot": { + "type": "boolean" + }, + "target": { + "type": "string" + }, + "type": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "name", + "operate", + "redirect", + "target", + "type", + "websiteID" + ], + "type": "object" + }, + "request.NginxRedirectUpdate": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "content", + "name", + "websiteID" + ], + "type": "object" + }, + "request.NginxRewriteReq": { + "properties": { + "name": { + "type": "string" + }, + "websiteId": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteId" + ], + "type": "object" + }, + "request.NginxRewriteUpdate": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "websiteId": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteId" + ], + "type": "object" + }, + "request.NginxScopeReq": { + "properties": { + "scope": { + "$ref": "#/definitions/dto.NginxKey" + }, + "websiteId": { + "type": "integer" + } + }, + "required": [ + "scope" + ], + "type": "object" + }, + "request.NodeModuleReq": { + "properties": { + "ID": { + "type": "integer" + } + }, + "required": [ + "ID" + ], + "type": "object" + }, + "request.NodePackageReq": { + "properties": { + "codeDir": { + "type": "string" + } + }, + "type": "object" + }, + "request.PHPConfigUpdate": { + "properties": { + "disableFunctions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "maxExecutionTime": { + "type": "string" + }, + "params": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "scope": { + "type": "string" + }, + "uploadMaxSize": { + "type": "string" + } + }, + "required": [ + "id", + "scope" + ], + "type": "object" + }, + "request.PHPContainerConfig": { + "properties": { + "containerName": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/request.ExposedPort" + }, + "type": "array" + }, + "extraHosts": { + "items": { + "$ref": "#/definitions/request.ExtraHost" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.PHPExtensionInstallReq": { + "properties": { + "ID": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "ID", + "name" + ], + "type": "object" + }, + "request.PHPExtensionsCreate": { + "properties": { + "extensions": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "extensions", + "name" + ], + "type": "object" + }, + "request.PHPExtensionsDelete": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.PHPExtensionsSearch": { + "properties": { + "all": { + "type": "boolean" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.PHPExtensionsUpdate": { + "properties": { + "extensions": { + "type": "string" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "extensions", + "id" + ], + "type": "object" + }, + "request.PHPFileReq": { + "properties": { + "id": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "request.PHPFileUpdate": { + "properties": { + "content": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "type": "object" + }, + "request.PHPSupervisorProcessConfig": { + "properties": { + "autoRestart": { + "type": "string" + }, + "autoStart": { + "type": "string" + }, + "command": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "numprocs": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.PHPSupervisorProcessFileReq": { + "properties": { + "content": { + "type": "string" + }, + "file": { + "enum": [ + "out.log", + "err.log", + "config" + ], + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "operate": { + "enum": [ + "get", + "clear", + "update" + ], + "type": "string" + } + }, + "required": [ + "file", + "id", + "name", + "operate" + ], + "type": "object" + }, + "request.PortUpdate": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "request.ProcessReq": { + "properties": { + "PID": { + "type": "integer" + } + }, + "required": [ + "PID" + ], + "type": "object" + }, + "request.RecycleBinReduce": { + "properties": { + "from": { + "type": "string" + }, + "name": { + "type": "string" + }, + "rName": { + "type": "string" + } + }, + "required": [ + "from", + "rName" + ], + "type": "object" + }, + "request.ReqWithID": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.RuntimeCreate": { + "properties": { + "appDetailId": { + "type": "integer" + }, + "clean": { + "type": "boolean" + }, + "codeDir": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/request.ExposedPort" + }, + "type": "array" + }, + "extraHosts": { + "items": { + "$ref": "#/definitions/request.ExtraHost" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "install": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "remark": { + "type": "string" + }, + "resource": { + "type": "string" + }, + "source": { + "type": "string" + }, + "type": { + "type": "string" + }, + "version": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "type": "object" + }, + "request.RuntimeDelete": { + "properties": { + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + } + }, + "type": "object" + }, + "request.RuntimeOperate": { + "properties": { + "ID": { + "type": "integer" + }, + "operate": { + "type": "string" + } + }, + "type": "object" + }, + "request.RuntimeRemark": { + "properties": { + "id": { + "type": "integer" + }, + "remark": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.RuntimeSearch": { + "properties": { + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.RuntimeUpdate": { + "properties": { + "clean": { + "type": "boolean" + }, + "codeDir": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/request.ExposedPort" + }, + "type": "array" + }, + "extraHosts": { + "items": { + "$ref": "#/definitions/request.ExtraHost" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "install": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "rebuild": { + "type": "boolean" + }, + "remark": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "type": "object" + }, + "request.SearchUploadWithPage": { + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "path": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "path" + ], + "type": "object" + }, + "request.StreamUpdate": { + "properties": { + "algorithm": { + "type": "string" + }, + "name": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "streamPorts": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.SupervisorProcessConfig": { + "properties": { + "autoRestart": { + "type": "string" + }, + "autoStart": { + "type": "string" + }, + "command": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "numprocs": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "request.SupervisorProcessFileReq": { + "properties": { + "content": { + "type": "string" + }, + "file": { + "enum": [ + "out.log", + "err.log", + "config" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "operate": { + "enum": [ + "get", + "clear", + "update" + ], + "type": "string" + } + }, + "required": [ + "file", + "name", + "operate" + ], + "type": "object" + }, + "request.Volume": { + "properties": { + "source": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "type": "object" + }, + "request.WebsiteAcmeAccountCreate": { + "properties": { + "caDirURL": { + "type": "string" + }, + "eabHmacKey": { + "type": "string" + }, + "eabKid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "keyType": { + "enum": [ + "P256", + "P384", + "2048", + "3072", + "4096", + "8192" + ], + "type": "string" + }, + "type": { + "enum": [ + "letsencrypt", + "zerossl", + "buypass", + "google", + "custom" + ], + "type": "string" + }, + "useProxy": { + "type": "boolean" + } + }, + "required": [ + "email", + "keyType", + "type" + ], + "type": "object" + }, + "request.WebsiteAcmeAccountUpdate": { + "properties": { + "id": { + "type": "integer" + }, + "useProxy": { + "type": "boolean" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteBatchDelReq": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "request.WebsiteCACreate": { + "properties": { + "city": { + "type": "string" + }, + "commonName": { + "type": "string" + }, + "country": { + "type": "string" + }, + "keyType": { + "enum": [ + "P256", + "P384", + "2048", + "3072", + "4096", + "8192" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "organizationUint": { + "type": "string" + }, + "province": { + "type": "string" + } + }, + "required": [ + "commonName", + "country", + "keyType", + "name", + "organization" + ], + "type": "object" + }, + "request.WebsiteCAObtain": { + "properties": { + "autoRenew": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "domains": { + "type": "string" + }, + "execShell": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "keyType": { + "enum": [ + "P256", + "P384", + "2048", + "3072", + "4096", + "8192" + ], + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "renew": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "time": { + "type": "integer" + }, + "unit": { + "type": "string" + } + }, + "required": [ + "domains", + "id", + "keyType", + "time", + "unit" + ], + "type": "object" + }, + "request.WebsiteCASearch": { + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.WebsiteCommonReq": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteCreate": { + "properties": { + "IPV6": { + "type": "boolean" + }, + "algorithm": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "appID": { + "type": "integer" + }, + "appInstall": { + "$ref": "#/definitions/request.NewAppInstall" + }, + "appInstallID": { + "type": "integer" + }, + "appType": { + "enum": [ + "new", + "installed" + ], + "type": "string" + }, + "createDb": { + "type": "boolean" + }, + "dbFormat": { + "type": "string" + }, + "dbHost": { + "type": "string" + }, + "dbName": { + "type": "string" + }, + "dbPassword": { + "type": "string" + }, + "dbUser": { + "type": "string" + }, + "domains": { + "items": { + "$ref": "#/definitions/request.WebsiteDomain" + }, + "type": "array" + }, + "enableSSL": { + "type": "boolean" + }, + "ftpPassword": { + "type": "string" + }, + "ftpUser": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parentWebsiteID": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "proxy": { + "type": "string" + }, + "proxyType": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "runtimeID": { + "type": "integer" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "siteDir": { + "type": "string" + }, + "streamPorts": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webSiteGroupID": { + "type": "integer" + }, + "websiteSSLID": { + "type": "integer" + } + }, + "required": [ + "alias", + "type", + "webSiteGroupID" + ], + "type": "object" + }, + "request.WebsiteDNSReq": { + "properties": { + "acmeAccountId": { + "type": "integer" + }, + "websiteSSLId": { + "type": "integer" + } + }, + "required": [ + "acmeAccountId", + "websiteSSLId" + ], + "type": "object" + }, + "request.WebsiteDefaultUpdate": { + "properties": { + "id": { + "type": "integer" + } + }, + "type": "object" + }, + "request.WebsiteDelete": { + "properties": { + "deleteApp": { + "type": "boolean" + }, + "deleteBackup": { + "type": "boolean" + }, + "deleteDB": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteDnsAccountCreate": { + "properties": { + "authorization": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "authorization", + "name", + "type" + ], + "type": "object" + }, + "request.WebsiteDnsAccountUpdate": { + "properties": { + "authorization": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "authorization", + "id", + "name", + "type" + ], + "type": "object" + }, + "request.WebsiteDomain": { + "properties": { + "domain": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "ssl": { + "type": "boolean" + } + }, + "required": [ + "domain" + ], + "type": "object" + }, + "request.WebsiteDomainCreate": { + "properties": { + "domains": { + "items": { + "$ref": "#/definitions/request.WebsiteDomain" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "domains", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteDomainDelete": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteDomainUpdate": { + "properties": { + "id": { + "type": "integer" + }, + "ssl": { + "type": "boolean" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteHTTPSOp": { + "properties": { + "SSLProtocol": { + "items": { + "type": "string" + }, + "type": "array" + }, + "algorithm": { + "type": "string" + }, + "certificate": { + "type": "string" + }, + "certificatePath": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "hsts": { + "type": "boolean" + }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, + "http3": { + "type": "boolean" + }, + "httpConfig": { + "enum": [ + "HTTPSOnly", + "HTTPAlso", + "HTTPToHTTPS" + ], + "type": "string" + }, + "httpsPorts": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "importType": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "type": { + "enum": [ + "existed", + "auto", + "manual" + ], + "type": "string" + }, + "websiteId": { + "type": "integer" + }, + "websiteSSLId": { + "type": "integer" + } + }, + "required": [ + "websiteId" + ], + "type": "object" + }, + "request.WebsiteHtmlUpdate": { + "properties": { + "content": { + "type": "string" + }, + "sync": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "type": "object" + }, + "request.WebsiteInstallCheckReq": { + "properties": { + "InstallIds": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, + "request.WebsiteLBCreate": { + "properties": { + "algorithm": { + "type": "string" + }, + "name": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteLBDelete": { + "properties": { + "name": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteLBUpdate": { + "properties": { + "algorithm": { + "type": "string" + }, + "name": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteLBUpdateFile": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "content", + "name", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteLogReq": { + "properties": { + "id": { + "type": "integer" + }, + "logType": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "id", + "logType", + "operate" + ], + "type": "object" + }, + "request.WebsiteNginxUpdate": { + "properties": { + "content": { + "type": "string" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "content", + "id" + ], + "type": "object" + }, + "request.WebsiteOp": { + "properties": { + "id": { + "type": "integer" + }, + "operate": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsitePHPVersionReq": { + "properties": { + "runtimeID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.WebsiteProxyConfig": { + "properties": { + "allowCredentials": { + "type": "boolean" + }, + "allowHeaders": { + "type": "string" + }, + "allowMethods": { + "type": "string" + }, + "allowOrigins": { + "type": "string" + }, + "cache": { + "type": "boolean" + }, + "cacheTime": { + "type": "integer" + }, + "cacheUnit": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cors": { + "type": "boolean" + }, + "enable": { + "type": "boolean" + }, + "filePath": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "match": { + "type": "string" + }, + "modifier": { + "type": "string" + }, + "name": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "preflight": { + "type": "boolean" + }, + "proxyHost": { + "type": "string" + }, + "proxyPass": { + "type": "string" + }, + "proxySSLName": { + "type": "string" + }, + "replaces": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "serverCacheTime": { + "type": "integer" + }, + "serverCacheUnit": { + "type": "string" + }, + "sni": { + "type": "boolean" + } + }, + "required": [ + "id", + "match", + "name", + "operate", + "proxyHost", + "proxyPass" + ], + "type": "object" + }, + "request.WebsiteProxyReq": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteRealIP": { + "properties": { + "ipFrom": { + "type": "string" + }, + "ipHeader": { + "type": "string" + }, + "ipOther": { + "type": "string" + }, + "open": { + "type": "boolean" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.WebsiteResourceReq": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteSSLApply": { + "properties": { + "ID": { + "type": "integer" + }, + "disableLog": { + "type": "boolean" + }, + "nameservers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "skipDNSCheck": { + "type": "boolean" + } + }, + "required": [ + "ID" + ], + "type": "object" + }, + "request.WebsiteSSLCreate": { + "properties": { + "acmeAccountId": { + "type": "integer" + }, + "apply": { + "type": "boolean" + }, + "autoRenew": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "disableCNAME": { + "type": "boolean" + }, + "dnsAccountId": { + "type": "integer" + }, + "execShell": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "isIp": { + "type": "boolean" + }, + "keyType": { + "type": "string" + }, + "nameserver1": { + "type": "string" + }, + "nameserver2": { + "type": "string" + }, + "nodes": { + "type": "string" + }, + "otherDomains": { + "type": "string" + }, + "primaryDomain": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "pushNode": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "skipDNS": { + "type": "boolean" + } + }, + "required": [ + "acmeAccountId", + "primaryDomain", + "provider" + ], + "type": "object" + }, + "request.WebsiteSSLSearch": { + "properties": { + "acmeAccountID": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "expire_date" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "request.WebsiteSSLUpdate": { + "properties": { + "acmeAccountId": { + "type": "integer" + }, + "apply": { + "type": "boolean" + }, + "autoRenew": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "disableCNAME": { + "type": "boolean" + }, + "dnsAccountId": { + "type": "integer" + }, + "execShell": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "keyType": { + "type": "string" + }, + "nameserver1": { + "type": "string" + }, + "nameserver2": { + "type": "string" + }, + "nodes": { + "type": "string" + }, + "otherDomains": { + "type": "string" + }, + "primaryDomain": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "pushNode": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "skipDNS": { + "type": "boolean" + } + }, + "required": [ + "id", + "primaryDomain", + "provider" + ], + "type": "object" + }, + "request.WebsiteSSLUpload": { + "properties": { + "certificate": { + "type": "string" + }, + "certificatePath": { + "type": "string" + }, + "description": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "type": { + "enum": [ + "paste", + "local" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "request.WebsiteSearch": { + "properties": { + "name": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "primary_domain", + "type", + "status", + "createdAt", + "expire_date", + "created_at", + "favorite" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "websiteGroupId": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "request.WebsiteUpdate": { + "properties": { + "IPV6": { + "type": "boolean" + }, + "expireDate": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "primaryDomain": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "webSiteGroupID": { + "type": "integer" + } + }, + "required": [ + "id", + "primaryDomain" + ], + "type": "object" + }, + "request.WebsiteUpdateDir": { + "properties": { + "id": { + "type": "integer" + }, + "siteDir": { + "type": "string" + } + }, + "required": [ + "id", + "siteDir" + ], + "type": "object" + }, + "request.WebsiteUpdateDirPermission": { + "properties": { + "group": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "user": { + "type": "string" + } + }, + "required": [ + "group", + "id", + "user" + ], + "type": "object" + }, + "response.AppConfig": { + "properties": { + "advanced": { + "type": "boolean" + }, + "allowPort": { + "type": "boolean" + }, + "containerName": { + "type": "string" + }, + "cpuQuota": { + "type": "number" + }, + "dockerCompose": { + "type": "string" + }, + "editCompose": { + "type": "boolean" + }, + "gpuConfig": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "memoryLimit": { + "type": "number" + }, + "memoryUnit": { + "type": "string" + }, + "params": { + "items": { + "$ref": "#/definitions/response.AppParam" + }, + "type": "array" + }, + "pullImage": { + "type": "boolean" + }, + "rawCompose": { + "type": "string" + }, + "restartPolicy": { + "enum": [ + "always", + "unless-stopped", + "no", + "on-failure" + ], + "type": "string" + }, + "specifyIP": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppDTO": { + "properties": { + "architectures": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "crossVersionUpdate": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "document": { + "type": "string" + }, + "github": { + "type": "string" + }, + "gpuSupport": { + "type": "boolean" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "installed": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "memoryRequired": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "readMe": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "required": { + "type": "string" + }, + "requiredPanelVersion": { + "type": "number" + }, + "resource": { + "type": "string" + }, + "shortDescEn": { + "type": "string" + }, + "shortDescZh": { + "type": "string" + }, + "status": { + "type": "string" + }, + "tags": { + "items": { + "$ref": "#/definitions/response.TagDTO" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "versions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "website": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppDetailDTO": { + "properties": { + "appId": { + "type": "integer" + }, + "architectures": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dockerCompose": { + "type": "string" + }, + "downloadCallBackUrl": { + "type": "string" + }, + "downloadUrl": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "gpuSupport": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "lastVersion": { + "type": "string" + }, + "memoryRequired": { + "type": "integer" + }, + "params": {}, + "status": { + "type": "string" + }, + "update": { + "type": "boolean" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppInstalledCheck": { + "properties": { + "app": { + "type": "string" + }, + "appInstallId": { + "type": "integer" + }, + "containerName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "httpPort": { + "type": "integer" + }, + "httpsPort": { + "type": "integer" + }, + "installPath": { + "type": "string" + }, + "isExist": { + "type": "boolean" + }, + "lastBackupAt": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "version": { + "type": "string" + }, + "websiteDir": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppItem": { + "properties": { + "description": { + "type": "string" + }, + "gpuSupport": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "installed": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppParam": { + "properties": { + "edit": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "label": { + "$ref": "#/definitions/dto.Locale" + }, + "labelEn": { + "type": "string" + }, + "labelZh": { + "type": "string" + }, + "multiple": { + "type": "boolean" + }, + "required": { + "type": "boolean" + }, + "rule": { + "type": "string" + }, + "showValue": { + "type": "string" + }, + "type": { + "type": "string" + }, + "value": {}, + "values": {} + }, + "type": "object" + }, + "response.AppRes": { + "properties": { + "items": { + "items": { + "$ref": "#/definitions/response.AppItem" + }, + "type": "array" + }, + "total": { + "type": "integer" + } + }, + "type": "object" + }, + "response.AppService": { + "properties": { + "config": {}, + "from": { + "type": "string" + }, + "label": { + "type": "string" + }, + "status": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppUpdateRes": { + "properties": { + "appList": { + "$ref": "#/definitions/dto.AppList" + }, + "appStoreLastModified": { + "type": "integer" + }, + "canUpdate": { + "type": "boolean" + }, + "isSyncing": { + "type": "boolean" + } + }, + "type": "object" + }, + "response.CompleteDiskInfo": { + "properties": { + "disks": { + "items": { + "$ref": "#/definitions/response.DiskInfo" + }, + "type": "array" + }, + "systemDisks": { + "items": { + "$ref": "#/definitions/response.DiskInfo" + }, + "type": "array" + }, + "totalCapacity": { + "type": "integer" + }, + "totalDisks": { + "type": "integer" + }, + "unpartitionedDisks": { + "items": { + "$ref": "#/definitions/response.DiskBasicInfo" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.ComponentInfo": { + "properties": { + "error": { + "type": "string" + }, + "exists": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "response.Database": { + "properties": { + "databaseName": { + "type": "string" + }, + "from": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "response.DatabaseConn": { + "properties": { + "containerName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "serviceName": { + "type": "string" + }, + "status": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "response.DepthDirSizeRes": { + "properties": { + "path": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object" + }, + "response.DirSizeRes": { + "properties": { + "size": { + "type": "integer" + } + }, + "required": [ + "size" + ], + "type": "object" + }, + "response.DiskBasicInfo": { + "properties": { + "avail": { + "type": "string" + }, + "device": { + "type": "string" + }, + "diskType": { + "type": "string" + }, + "filesystem": { + "type": "string" + }, + "isMounted": { + "type": "boolean" + }, + "isRemovable": { + "type": "boolean" + }, + "isSystem": { + "type": "boolean" + }, + "model": { + "type": "string" + }, + "mountPoint": { + "type": "string" + }, + "serial": { + "type": "string" + }, + "size": { + "type": "string" + }, + "usePercent": { + "type": "integer" + }, + "used": { + "type": "string" + } + }, + "type": "object" + }, + "response.DiskInfo": { + "properties": { + "avail": { + "type": "string" + }, + "device": { + "type": "string" + }, + "diskType": { + "type": "string" + }, + "filesystem": { + "type": "string" + }, + "isMounted": { + "type": "boolean" + }, + "isRemovable": { + "type": "boolean" + }, + "isSystem": { + "type": "boolean" + }, + "model": { + "type": "string" + }, + "mountPoint": { + "type": "string" + }, + "partitions": { + "items": { + "$ref": "#/definitions/response.DiskBasicInfo" + }, + "type": "array" + }, + "serial": { + "type": "string" + }, + "size": { + "type": "string" + }, + "usePercent": { + "type": "integer" + }, + "used": { + "type": "string" + } + }, + "type": "object" + }, + "response.ExistFileInfo": { + "properties": { + "isDir": { + "type": "boolean" + }, + "modTime": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object" + }, + "response.FileInfo": { + "properties": { + "content": { + "type": "string" + }, + "extension": { + "type": "string" + }, + "favoriteID": { + "type": "integer" + }, + "gid": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isDetail": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "isHidden": { + "type": "boolean" + }, + "isSymlink": { + "type": "boolean" + }, + "itemTotal": { + "type": "integer" + }, + "items": { + "items": { + "$ref": "#/definitions/files.FileInfo" + }, + "type": "array" + }, + "linkPath": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "modTime": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "updateTime": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "response.FileLineContent": { + "properties": { + "end": { + "type": "boolean" + }, + "lines": { + "items": { + "type": "string" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "taskStatus": { + "type": "string" + }, + "total": { + "type": "integer" + }, + "totalLines": { + "type": "integer" + } + }, + "type": "object" + }, + "response.FileTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/response.FileTree" + }, + "type": "array" + }, + "extension": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDir": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "type": "object" + }, + "response.FileWgetRes": { + "properties": { + "key": { + "type": "string" + } + }, + "type": "object" + }, + "response.HostToolConfig": { + "properties": { + "content": { + "type": "string" + } + }, + "type": "object" + }, + "response.HostToolRes": { + "properties": { + "config": {}, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "response.McpBindDomainRes": { + "properties": { + "acmeAccountID": { + "type": "integer" + }, + "allowIPs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "connUrl": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "type": "object" + }, + "response.McpServerDTO": { + "properties": { + "baseUrl": { + "type": "string" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "dockerCompose": { + "type": "string" + }, + "env": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "hostIP": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "outputTransport": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "ssePath": { + "type": "string" + }, + "status": { + "type": "string" + }, + "streamableHttpPath": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "type": "object" + }, + "response.McpServersRes": { + "properties": { + "items": { + "items": { + "$ref": "#/definitions/response.McpServerDTO" + }, + "type": "array" + }, + "total": { + "type": "integer" + } + }, + "type": "object" + }, + "response.NginxAntiLeechRes": { + "properties": { + "blocked": { + "type": "boolean" + }, + "cache": { + "type": "boolean" + }, + "cacheTime": { + "type": "integer" + }, + "cacheUint": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "extends": { + "type": "string" + }, + "logEnable": { + "type": "boolean" + }, + "noneRef": { + "type": "boolean" + }, + "return": { + "type": "string" + }, + "serverNames": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.NginxAuthRes": { + "properties": { + "enable": { + "type": "boolean" + }, + "items": { + "items": { + "$ref": "#/definitions/dto.NginxAuth" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.NginxBuildConfig": { + "properties": { + "mirror": { + "type": "string" + }, + "modules": { + "items": { + "$ref": "#/definitions/response.NginxModule" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.NginxConfigRes": { + "properties": { + "https": { + "type": "boolean" + }, + "sslRejectHandshake": { + "type": "boolean" + } + }, + "type": "object" + }, + "response.NginxFile": { + "properties": { + "content": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxModule": { + "properties": { + "enable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "packages": { + "type": "string" + }, + "params": { + "type": "string" + }, + "script": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxParam": { + "properties": { + "name": { + "type": "string" + }, + "params": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.NginxPathAuthRes": { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxProxyCache": { + "properties": { + "cacheExpire": { + "type": "integer" + }, + "cacheExpireUnit": { + "type": "string" + }, + "cacheLimit": { + "type": "number" + }, + "cacheLimitUnit": { + "type": "string" + }, + "open": { + "type": "boolean" + }, + "shareCache": { + "type": "integer" + }, + "shareCacheUnit": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxRedirectConfig": { + "properties": { + "content": { + "type": "string" + }, + "domains": { + "items": { + "type": "string" + }, + "type": "array" + }, + "enable": { + "type": "boolean" + }, + "filePath": { + "type": "string" + }, + "keepPath": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "redirect": { + "type": "string" + }, + "redirectRoot": { + "type": "boolean" + }, + "target": { + "type": "string" + }, + "type": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "type": "object" + }, + "response.NginxRewriteRes": { + "properties": { + "content": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxStatus": { + "properties": { + "accepts": { + "type": "integer" + }, + "active": { + "type": "integer" + }, + "handled": { + "type": "integer" + }, + "reading": { + "type": "integer" + }, + "requests": { + "type": "integer" + }, + "waiting": { + "type": "integer" + }, + "writing": { + "type": "integer" + } + }, + "type": "object" + }, + "response.NodeModule": { + "properties": { + "description": { + "type": "string" + }, + "license": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "response.PHPConfig": { + "properties": { + "disableFunctions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "maxExecutionTime": { + "type": "string" + }, + "params": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "uploadMaxSize": { + "type": "string" + } + }, + "type": "object" + }, + "response.PHPExtensionRes": { + "properties": { + "extensions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "supportExtensions": { + "items": { + "$ref": "#/definitions/response.SupportExtension" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.PackageScripts": { + "properties": { + "name": { + "type": "string" + }, + "script": { + "type": "string" + } + }, + "type": "object" + }, + "response.ProcessStatus": { + "properties": { + "PID": { + "type": "string" + }, + "msg": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "uptime": { + "type": "string" + } + }, + "type": "object" + }, + "response.Resource": { + "properties": { + "detail": {}, + "name": { + "type": "string" + }, + "resourceID": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "response.RuntimeDTO": { + "properties": { + "appDetailID": { + "type": "integer" + }, + "appID": { + "type": "integer" + }, + "appParams": { + "items": { + "$ref": "#/definitions/response.AppParam" + }, + "type": "array" + }, + "codeDir": { + "type": "string" + }, + "container": { + "type": "string" + }, + "containerStatus": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/request.ExposedPort" + }, + "type": "array" + }, + "extraHosts": { + "items": { + "$ref": "#/definitions/request.ExtraHost" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "path": { + "type": "string" + }, + "port": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "resource": { + "type": "string" + }, + "source": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "version": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.SupervisorProcessConfig": { + "properties": { + "autoRestart": { + "type": "string" + }, + "autoStart": { + "type": "string" + }, + "command": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "msg": { + "type": "string" + }, + "name": { + "type": "string" + }, + "numprocs": { + "type": "string" + }, + "status": { + "items": { + "$ref": "#/definitions/response.ProcessStatus" + }, + "type": "array" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "response.SupportExtension": { + "properties": { + "check": { + "type": "string" + }, + "description": { + "type": "string" + }, + "file": { + "type": "string" + }, + "installed": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "versions": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.TagDTO": { + "properties": { + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "response.UserGroupResponse": { + "properties": { + "groups": { + "items": { + "type": "string" + }, + "type": "array" + }, + "users": { + "items": { + "$ref": "#/definitions/response.UserInfo" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.UserInfo": { + "properties": { + "group": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteAcmeAccountDTO": { + "properties": { + "caDirURL": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "eabHmacKey": { + "type": "string" + }, + "eabKid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "keyType": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "url": { + "type": "string" + }, + "useProxy": { + "type": "boolean" + } + }, + "type": "object" + }, + "response.WebsiteCADTO": { + "properties": { + "city": { + "type": "string" + }, + "commonName": { + "type": "string" + }, + "country": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "csr": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "keyType": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "organizationUint": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "province": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteDNSRes": { + "properties": { + "domain": { + "type": "string" + }, + "err": { + "type": "string" + }, + "resolve": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteDTO": { + "properties": { + "IPV6": { + "type": "boolean" + }, + "accessLog": { + "type": "boolean" + }, + "accessLogPath": { + "type": "string" + }, + "algorithm": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "appInstallId": { + "type": "integer" + }, + "appName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dbID": { + "type": "integer" + }, + "dbType": { + "type": "string" + }, + "defaultServer": { + "type": "boolean" + }, + "domains": { + "items": { + "$ref": "#/definitions/model.WebsiteDomain" + }, + "type": "array" + }, + "errorLog": { + "type": "boolean" + }, + "errorLogPath": { + "type": "string" + }, + "expireDate": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "ftpId": { + "type": "integer" + }, + "group": { + "type": "string" + }, + "httpConfig": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "openBaseDir": { + "type": "boolean" + }, + "parentWebsiteID": { + "type": "integer" + }, + "primaryDomain": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "proxy": { + "type": "string" + }, + "proxyType": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "rewrite": { + "type": "string" + }, + "runtimeID": { + "type": "integer" + }, + "runtimeName": { + "type": "string" + }, + "runtimeType": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "siteDir": { + "type": "string" + }, + "sitePath": { + "type": "string" + }, + "status": { + "type": "string" + }, + "streamPorts": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "user": { + "type": "string" + }, + "webSiteGroupId": { + "type": "integer" + }, + "webSiteSSL": { + "$ref": "#/definitions/model.WebsiteSSL" + }, + "webSiteSSLId": { + "type": "integer" + } + }, + "type": "object" + }, + "response.WebsiteDirConfig": { + "properties": { + "dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "msg": { + "type": "string" + }, + "user": { + "type": "string" + }, + "userGroup": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteHTTPS": { + "properties": { + "SSL": { + "$ref": "#/definitions/model.WebsiteSSL" + }, + "SSLProtocol": { + "items": { + "type": "string" + }, + "type": "array" + }, + "algorithm": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "hsts": { + "type": "boolean" + }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, + "http3": { + "type": "boolean" + }, + "httpConfig": { + "type": "string" + }, + "httpsPort": { + "type": "string" + }, + "httpsPorts": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.WebsiteHtmlRes": { + "properties": { + "content": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteLog": { + "properties": { + "content": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "end": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteNginxConfig": { + "properties": { + "enable": { + "type": "boolean" + }, + "params": { + "items": { + "$ref": "#/definitions/response.NginxParam" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.WebsiteOption": { + "properties": { + "alias": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "primaryDomain": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsitePreInstallCheck": { + "properties": { + "appName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteRealIP": { + "properties": { + "ipFrom": { + "type": "string" + }, + "ipHeader": { + "type": "string" + }, + "ipOther": { + "type": "string" + }, + "open": { + "type": "boolean" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "response.WebsiteSSLDTO": { + "properties": { + "acmeAccount": { + "$ref": "#/definitions/model.WebsiteAcmeAccount" + }, + "acmeAccountId": { + "type": "integer" + }, + "autoRenew": { + "type": "boolean" + }, + "caId": { + "type": "integer" + }, + "certPath": { + "type": "string" + }, + "certURL": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "disableCNAME": { + "type": "boolean" + }, + "dnsAccount": { + "$ref": "#/definitions/model.WebsiteDnsAccount" + }, + "dnsAccountId": { + "type": "integer" + }, + "domains": { + "type": "string" + }, + "execShell": { + "type": "boolean" + }, + "expireDate": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isIP": { + "type": "boolean" + }, + "keyType": { + "type": "string" + }, + "logPath": { + "type": "string" + }, + "masterSslId": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "nameserver1": { + "type": "string" + }, + "nameserver2": { + "type": "string" + }, + "nodes": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "pem": { + "type": "string" + }, + "primaryDomain": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "pushNode": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "skipDNS": { + "type": "boolean" + }, + "startDate": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "websites": { + "items": { + "$ref": "#/definitions/model.Website" + }, + "type": "array" + } + }, + "type": "object" + } + } +}` + +var SwaggerInfo = &swag.Spec{ + Version: "2.0", + Host: "localhost", + BasePath: "/api/v2", + Schemes: []string{}, + Title: "1Panel", + Description: "Top-Rated Web-based Linux Server Management Tool", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +} \ No newline at end of file diff --git a/core/cmd/server/docs/swagger.go b/core/cmd/server/docs/swagger.go new file mode 100644 index 0000000..bc2172c --- /dev/null +++ b/core/cmd/server/docs/swagger.go @@ -0,0 +1,6 @@ +package docs + +import _ "embed" + +//go:embed x-log.json +var XLogJson []byte diff --git a/core/cmd/server/docs/swagger.json b/core/cmd/server/docs/swagger.json new file mode 100644 index 0000000..a661603 --- /dev/null +++ b/core/cmd/server/docs/swagger.json @@ -0,0 +1,34789 @@ +{ + "swagger": "2.0", + "info": { + "contact": {}, + "description": "Top-Rated Web-based Linux Server Management Tool", + "license": { + "name": "GPL-3.0", + "url": "https://www.gnu.org/licenses/gpl-3.0.html" + }, + "termsOfService": "http://swagger.io/terms/", + "title": "1Panel", + "version": "2.0" + }, + "host": "", + "basePath": "/api/v2", + "paths": { + "/ai/domain/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaBindDomain" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind domain", + "tags": [ + "AI" + ] + } + }, + "/ai/domain/get": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaBindDomainReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OllamaBindDomainRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get bind domain", + "tags": [ + "AI" + ] + } + }, + "/ai/gpu/load": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load gpu / xpu info", + "tags": [ + "AI" + ] + } + }, + "/ai/mcp/domain/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpBindDomain" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind Domain for mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/domain/get": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.McpBindDomainRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get bin Domain for mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/domain/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpBindDomainUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update bind Domain for mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.McpServersRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List mcp servers", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/server": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/server/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/server/op": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/mcp/server/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.McpServerUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update mcp server", + "tags": [ + "McpServer" + ] + } + }, + "/ai/ollama/close": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaModelName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Close Ollama model conn", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "close conn for Ollama model [name]", + "formatZH": "关闭 Ollama 模型连接 [name]", + "paramKeys": [] + } + } + }, + "/ai/ollama/model": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaModelName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create Ollama model", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "add Ollama model [name]", + "formatZH": "添加 Ollama 模型 [name]", + "paramKeys": [] + } + } + }, + "/ai/ollama/model/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ForceDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete Ollama model", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "ollama_models", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "remove Ollama model [names]", + "formatZH": "删除 Ollama 模型 [names]", + "paramKeys": [] + } + } + }, + "/ai/ollama/model/load": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaModelName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page Ollama models", + "tags": [ + "AI" + ] + } + }, + "/ai/ollama/model/recreate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OllamaModelName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Rereate Ollama model", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "re-add Ollama model [name]", + "formatZH": "添加 Ollama 模型重试 [name]", + "paramKeys": [] + } + } + }, + "/ai/ollama/model/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page Ollama models", + "tags": [ + "AI" + ] + } + }, + "/ai/ollama/model/sync": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.OllamaModelDropList" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync Ollama model list", + "tags": [ + "AI" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "sync Ollama model list", + "formatZH": "同步 Ollama 模型列表", + "paramKeys": [] + } + } + }, + "/apps/:key": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "app key", + "in": "path", + "name": "key", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app by key", + "tags": [ + "App" + ] + } + }, + "/apps/checkupdate": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppUpdateRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get app list update", + "tags": [ + "App" + ] + } + }, + "/apps/detail/:appId/:version/:type": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "app id", + "in": "path", + "name": "appId", + "required": true, + "type": "integer" + }, + { + "description": "app 版本", + "in": "path", + "name": "version", + "required": true, + "type": "string" + }, + { + "description": "app 类型", + "in": "path", + "name": "version", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppDetailDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app detail by appid", + "tags": [ + "App" + ] + } + }, + "/apps/details/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "appId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppDetailDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get app detail by id", + "tags": [ + "App" + ] + } + }, + "/apps/icon/:appId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "app id", + "in": "path", + "name": "appId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "app icon", + "schema": { + "type": "file" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get app icon by app_id", + "tags": [ + "App" + ] + } + }, + "/apps/ignored/cancel": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.ReqWithID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Cancel Ignore Upgrade App", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Cancel ignore application upgrade", + "formatZH": "取消忽略应用升级", + "paramKeys": [] + } + } + }, + "/apps/ignored/detail": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/model.AppIgnoreUpgrade" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List Upgrade Ignored App", + "tags": [ + "App" + ] + } + }, + "/apps/install": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstallCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.AppInstall" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Install app", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Install app [name]", + "formatZH": "安装应用 [name]", + "paramKeys": [] + } + } + }, + "/apps/installed/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstalledInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppInstalledCheck" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check app installed", + "tags": [ + "App" + ] + } + }, + "/apps/installed/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search default config by key", + "tags": [ + "App" + ] + } + }, + "/apps/installed/config/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppConfigUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update app config", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "installID", + "webUI" + ], + "formatEN": "Application config update [installID]", + "formatZH": "应用配置更新 [installID]", + "paramKeys": [] + } + } + }, + "/apps/installed/conninfo": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.DatabaseConn" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app password by key", + "tags": [ + "App" + ] + } + }, + "/apps/installed/delete/check/:appInstallId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "App install id", + "in": "path", + "name": "appInstallId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppResource" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before delete", + "tags": [ + "App" + ] + } + }, + "/apps/installed/ignore": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppIgnoreUpgradeReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Ignore Upgrade App", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Ignore application upgrade", + "formatZH": "忽略应用升级", + "paramKeys": [] + } + } + }, + "/apps/installed/info/:appInstallId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "App install id", + "in": "path", + "name": "appInstallId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.AppInstallInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get app install info", + "tags": [ + "App" + ] + } + }, + "/apps/installed/list": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppInstallInfo" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List app installed", + "tags": [ + "App" + ] + } + }, + "/apps/installed/loadport": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "integer" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app port by key", + "tags": [ + "App" + ] + } + }, + "/apps/installed/op": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstalledOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate installed app", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "app_installs", + "input_column": "id", + "input_value": "installId", + "isList": false, + "output_column": "app_id", + "output_value": "appId" + }, + { + "db": "app_installs", + "input_column": "id", + "input_value": "installId", + "isList": false, + "output_column": "name", + "output_value": "appName" + }, + { + "db": "apps", + "input_column": "id", + "input_value": "appId", + "isList": false, + "output_column": "key", + "output_value": "appKey" + } + ], + "bodyKeys": [ + "installId", + "operate" + ], + "formatEN": "[operate] App [appKey][appName]", + "formatZH": "[operate] 应用 [appKey][appName]", + "paramKeys": [] + } + } + }, + "/apps/installed/params/:appInstallId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "appInstallId", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search params by appInstallId", + "tags": [ + "App" + ] + } + }, + "/apps/installed/params/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstalledUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change app params", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "installId" + ], + "formatEN": "Application param update [installId]", + "formatZH": "应用参数修改 [installId]", + "paramKeys": [] + } + } + }, + "/apps/installed/port/change": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PortUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change app port", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "name", + "port" + ], + "formatEN": "Application port update [key]-[name] =\u003e [port]", + "formatZH": "应用端口修改 [key]-[name] =\u003e [port]", + "paramKeys": [] + } + } + }, + "/apps/installed/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppInstalledSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page app installed", + "tags": [ + "App" + ] + } + }, + "/apps/installed/sync": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync app installed", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Sync the list of installed apps", + "formatZH": "同步已安装应用列表", + "paramKeys": [] + } + } + }, + "/apps/installed/update/versions": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "appInstallId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppVersion" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app update version by install id", + "tags": [ + "App" + ] + } + }, + "/apps/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.AppSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.AppRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List apps", + "tags": [ + "App" + ] + } + }, + "/apps/services/:key": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "key", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.AppService" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search app service by key", + "tags": [ + "App" + ] + } + }, + "/apps/sync/local": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync local app list", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "App store synchronization", + "formatZH": "应用商店同步", + "paramKeys": [] + } + } + }, + "/apps/sync/remote": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync remote app list", + "tags": [ + "App" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "App store synchronization", + "formatZH": "应用商店同步", + "paramKeys": [] + } + } + }, + "/backups": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "create backup account [type]", + "formatZH": "创建备份账号 [type]", + "paramKeys": [] + } + } + }, + "/backups/backup": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommonBackup" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Backup system data", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "name", + "detailName" + ], + "formatEN": "backup [type] data [name][detailName]", + "formatZH": "备份 [type] 数据 [name][detailName]", + "paramKeys": [] + } + } + }, + "/backups/buckets": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ForBuckets" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "object" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List buckets", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check backup account", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "backup_accounts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "type", + "output_value": "types" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete backup account [types]", + "formatZH": "删除备份账号 [types]", + "paramKeys": [] + } + } + }, + "/backups/local": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "get local backup dir", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/options": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.BackupOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load backup account options", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/record/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete backup record", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "backup_records", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "file_name", + "output_value": "files" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete backup records [files]", + "formatZH": "删除备份记录 [files]", + "paramKeys": [] + } + } + }, + "/backups/record/description/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update backup record description", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/record/download": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DownloadRecord" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download backup record", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "source", + "fileName" + ], + "formatEN": "download backup records [source][fileName]", + "formatZH": "下载备份记录 [source][fileName]", + "paramKeys": [] + } + } + }, + "/backups/record/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RecordSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page backup records", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/record/search/bycronjob": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RecordSearchByCronjob" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page backup records by cronjob", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/record/size": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchForSize" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.RecordFileSize" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load backup record size", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/recover": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommonRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Recover system data", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "name", + "detailName", + "file" + ], + "formatEN": "recover [type] data [name][detailName] from [file]", + "formatZH": "从 [file] 恢复 [type] 数据 [name][detailName]", + "paramKeys": [] + } + } + }, + "/backups/recover/byupload": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommonRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Recover system data by upload", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "name", + "detailName", + "file" + ], + "formatEN": "recover [type] data [name][detailName] from [file]", + "formatZH": "从 [file] 恢复 [type] 数据 [name][detailName]", + "paramKeys": [] + } + } + }, + "/backups/refresh/token": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Refresh token", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchPageWithType" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search backup accounts with page", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/search/files": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List files from backup accounts", + "tags": [ + "Backup Account" + ] + } + }, + "/backups/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "update backup account [types]", + "formatZH": "更新备份账号 [types]", + "paramKeys": [] + } + } + }, + "/backups/upload": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UploadForRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upload file for recover", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "filePath" + ], + "formatEN": "upload backup file [filePath]", + "formatZH": "上传备份文件 [filePath]", + "paramKeys": [] + } + } + }, + "/containers": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "image" + ], + "formatEN": "create container [name][image]", + "formatZH": "创建容器 [name][image]", + "paramKeys": [] + } + } + }, + "/containers/clean/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean container log", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "clean container [name] logs", + "formatZH": "清理容器 [name] 日志", + "paramKeys": [] + } + } + }, + "/containers/command": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerCreateByCommand" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create container by command", + "tags": [ + "Container" + ] + } + }, + "/containers/commit": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerCommit" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Commit Container", + "tags": [ + "Container" + ] + } + }, + "/containers/compose": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create compose", + "tags": [ + "Container Compose" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create compose [name]", + "formatZH": "创建 compose [name]", + "paramKeys": [] + } + } + }, + "/containers/compose/clean/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeLogClean" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean compose log", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "clean compose [name] logs", + "formatZH": "清理容器编排 [name] 日志", + "paramKeys": [] + } + } + }, + "/containers/compose/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeOperation" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate compose", + "tags": [ + "Container Compose" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "operation" + ], + "formatEN": "compose [operation] [name]", + "formatZH": "compose [operation] [name]", + "paramKeys": [] + } + } + }, + "/containers/compose/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page composes", + "tags": [ + "Container Compose" + ] + } + }, + "/containers/compose/test": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Test compose", + "tags": [ + "Container Compose" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "check compose [name]", + "formatZH": "检测 compose [name] 格式", + "paramKeys": [] + } + } + }, + "/containers/compose/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update compose", + "tags": [ + "Container Compose" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "update compose information [name]", + "formatZH": "更新 compose [name]", + "paramKeys": [] + } + } + }, + "/containers/daemonjson": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DaemonJsonConf" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load docker daemon.json", + "tags": [ + "Container Docker" + ] + } + }, + "/containers/daemonjson/file": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load docker daemon.json", + "tags": [ + "Container Docker" + ] + } + }, + "/containers/daemonjson/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update docker daemon.json", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "Updated configuration [key]", + "formatZH": "更新配置 [key]", + "paramKeys": [] + } + } + }, + "/containers/daemonjson/update/byfile": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DaemonJsonUpdateByFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update docker daemon.json by upload file", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Updated configuration file", + "formatZH": "更新配置文件", + "paramKeys": [] + } + } + }, + "/containers/docker/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DockerOperation" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate docker", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] docker service", + "formatZH": "docker 服务 [operation]", + "paramKeys": [] + } + } + }, + "/containers/docker/status": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load docker status", + "tags": [ + "Container Docker" + ] + } + }, + "/containers/image": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Options" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "load images options", + "tags": [ + "Container Image" + ] + } + }, + "/containers/image/all": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ImageInfo" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List all images", + "tags": [ + "Container Image" + ] + } + }, + "/containers/image/build": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageBuild" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Build image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "build image [name]", + "formatZH": "构建镜像 [name]", + "paramKeys": [] + } + } + }, + "/containers/image/load": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageLoad" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "load image from [path]", + "formatZH": "从 [path] 加载镜像", + "paramKeys": [] + } + } + }, + "/containers/image/pull": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImagePull" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Pull image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "repoID", + "isList": false, + "output_column": "name", + "output_value": "reponame" + } + ], + "bodyKeys": [ + "repoID", + "imageName" + ], + "formatEN": "image pull [reponame][imageName]", + "formatZH": "镜像拉取 [reponame][imageName]", + "paramKeys": [] + } + } + }, + "/containers/image/push": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImagePush" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Push image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "repoID", + "isList": false, + "output_column": "name", + "output_value": "reponame" + } + ], + "bodyKeys": [ + "repoID", + "tagName", + "name" + ], + "formatEN": "push [tagName] to [reponame][name]", + "formatZH": "[tagName] 推送到 [reponame][name]", + "paramKeys": [] + } + } + }, + "/containers/image/remove": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names" + ], + "formatEN": "remove image [names]", + "formatZH": "移除镜像 [names]", + "paramKeys": [] + } + } + }, + "/containers/image/save": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageSave" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Save image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "tagName", + "path", + "name" + ], + "formatEN": "save [tagName] as [path]/[name]", + "formatZH": "保留 [tagName] 为 [path]/[name]", + "paramKeys": [] + } + } + }, + "/containers/image/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageImage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page images", + "tags": [ + "Container Image" + ] + } + }, + "/containers/image/tag": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageTag" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Tag image", + "tags": [ + "Container Image" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "repoID", + "isList": false, + "output_column": "name", + "output_value": "reponame" + } + ], + "bodyKeys": [ + "repoID", + "targetName" + ], + "formatEN": "tag image [reponame][targetName]", + "formatZH": "tag 镜像 [reponame][targetName]", + "paramKeys": [] + } + } + }, + "/containers/info": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ContainerOperate" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container info", + "tags": [ + "Container" + ] + } + }, + "/containers/inspect": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.InspectReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Container inspect", + "tags": [ + "Container" + ] + } + }, + "/containers/ipv6option/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LogOption" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update docker daemon.json ipv6 option", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Updated the ipv6 option", + "formatZH": "更新 ipv6 配置", + "paramKeys": [] + } + } + }, + "/containers/item/stats": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ContainerItemStats" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container stats size" + } + }, + "/containers/limit": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ResourceLimit" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container limits" + } + }, + "/containers/list": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ContainerOptions" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List containers", + "tags": [ + "Container" + ] + } + }, + "/containers/list/byimage": { + "post": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ContainerOptions" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List containers by image", + "tags": [ + "Container" + ] + } + }, + "/containers/list/stats": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ContainerListStats" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container stats" + } + }, + "/containers/logoption/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LogOption" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update docker daemon.json log option", + "tags": [ + "Container Docker" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Updated the log option", + "formatZH": "更新日志配置", + "paramKeys": [] + } + } + }, + "/containers/network": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Options" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List networks", + "tags": [ + "Container Network" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.NetworkCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create network", + "tags": [ + "Container Network" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create container network [name]", + "formatZH": "创建容器网络 name", + "paramKeys": [] + } + } + }, + "/containers/network/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete network", + "tags": [ + "Container Network" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names" + ], + "formatEN": "delete container network [names]", + "formatZH": "删除容器网络 [names]", + "paramKeys": [] + } + } + }, + "/containers/network/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page networks", + "tags": [ + "Container Network" + ] + } + }, + "/containers/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerOperation" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names", + "operation" + ], + "formatEN": "container [operation] [names]", + "formatZH": "容器 [names] 执行 [operation]", + "paramKeys": [] + } + } + }, + "/containers/prune": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerPrune" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "pruneType" + ], + "formatEN": "clean container [pruneType]", + "formatZH": "清理容器 [pruneType]", + "paramKeys": [] + } + } + }, + "/containers/rename": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerRename" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Rename Container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "newName" + ], + "formatEN": "rename container [name] =\u003e [newName]", + "formatZH": "容器重命名 [name] =\u003e [newName]", + "paramKeys": [] + } + } + }, + "/containers/repo": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ImageRepoOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List image repos", + "tags": [ + "Container Image-repo" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageRepoDelete" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create image repo", + "tags": [ + "Container Image-repo" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create image repo [name]", + "formatZH": "创建镜像仓库 [name]", + "paramKeys": [] + } + } + }, + "/containers/repo/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete image repo", + "tags": [ + "Container Image-repo" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete image repo [name]", + "formatZH": "删除镜像仓库 [name]", + "paramKeys": [] + } + } + }, + "/containers/repo/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page image repos", + "tags": [ + "Container Image-repo" + ] + } + }, + "/containers/repo/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load repo status", + "tags": [ + "Container Image-repo" + ] + } + }, + "/containers/repo/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ImageRepoUpdate" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update image repo", + "tags": [ + "Container Image-repo" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "image_repos", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update image repo information [name]", + "formatZH": "更新镜像仓库 [name]", + "paramKeys": [] + } + } + }, + "/containers/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageContainer" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page containers", + "tags": [ + "Container" + ] + } + }, + "/containers/search/log": { + "get": { + "parameters": [ + { + "description": "容器名称", + "in": "query", + "name": "container", + "type": "string" + }, + { + "description": "时间筛选", + "in": "query", + "name": "since", + "type": "string" + }, + { + "description": "是否追踪", + "in": "query", + "name": "follow", + "type": "string" + }, + { + "description": "显示行号", + "in": "query", + "name": "tail", + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Container logs", + "tags": [ + "Container" + ] + } + }, + "/containers/stats/:id": { + "get": { + "parameters": [ + { + "description": "容器id", + "in": "path", + "name": "id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ContainerStats" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Container stats", + "tags": [ + "Container" + ] + } + }, + "/containers/status": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ContainerStatus" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load containers status", + "tags": [ + "Container" + ] + } + }, + "/containers/template": { + "get": { + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ComposeTemplateInfo" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List compose templates", + "tags": [ + "Container Compose-template" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeTemplateCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create compose template", + "tags": [ + "Container Compose-template" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create compose template [name]", + "formatZH": "创建 compose 模版 [name]", + "paramKeys": [] + } + } + }, + "/containers/template/batch": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeTemplateBatch" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bacth compose template", + "tags": [ + "Container Compose-template" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "batch import compose templates", + "formatZH": "批量导入编排模版", + "paramKeys": [] + } + } + }, + "/containers/template/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete compose template", + "tags": [ + "Container Compose-template" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "compose_templates", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete compose template [names]", + "formatZH": "删除 compose 模版 [names]", + "paramKeys": [] + } + } + }, + "/containers/template/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page compose templates", + "tags": [ + "Container Compose-template" + ] + } + }, + "/containers/template/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ComposeTemplateUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update compose template", + "tags": [ + "Container Compose-template" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "compose_templates", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update compose template information [name]", + "formatZH": "更新 compose 模版 [name]", + "paramKeys": [] + } + } + }, + "/containers/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "image" + ], + "formatEN": "update container [name][image]", + "formatZH": "更新容器 [name][image]", + "paramKeys": [] + } + } + }, + "/containers/upgrade": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ContainerUpgrade" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upgrade container", + "tags": [ + "Container" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names", + "image" + ], + "formatEN": "upgrade container image [names][image]", + "formatZH": "更新容器镜像 [names][image]", + "paramKeys": [] + } + } + }, + "/containers/users": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load container users", + "tags": [ + "Container" + ] + } + }, + "/containers/volume": { + "get": { + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Options" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List volumes", + "tags": [ + "Container Volume" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.VolumeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create volume", + "tags": [ + "Container Volume" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create container volume [name]", + "formatZH": "创建容器存储卷 [name]", + "paramKeys": [] + } + } + }, + "/containers/volume/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete volume", + "tags": [ + "Container Volume" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "names" + ], + "formatEN": "delete container volume [names]", + "formatZH": "删除容器存储卷 [names]", + "paramKeys": [] + } + } + }, + "/containers/volume/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page volumes", + "tags": [ + "Container Volume" + ] + } + }, + "/core/auth/captcha": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CaptchaResponse" + } + } + }, + "summary": "Load captcha", + "tags": [ + "Auth" + ] + } + }, + "/core/auth/login": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "安全入口 base64 加密串", + "in": "header", + "name": "EntranceCode", + "required": true, + "type": "string" + }, + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Login" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UserLoginInfo" + } + } + }, + "summary": "User login", + "tags": [ + "Auth" + ] + } + }, + "/core/auth/logout": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "User logout", + "tags": [ + "Auth" + ] + } + }, + "/core/auth/mfalogin": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MFALogin" + } + } + ], + "responses": { + "200": { + "description": "OK", + "headers": { + "EntranceCode": { + "description": "安全入口", + "type": "string" + } + }, + "schema": { + "$ref": "#/definitions/dto.UserLoginInfo" + } + } + }, + "summary": "User login with mfa", + "tags": [ + "Auth" + ] + } + }, + "/core/auth/setting": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SystemSetting" + } + } + }, + "summary": "Get Setting For Login", + "tags": [ + "Auth" + ] + } + }, + "/core/backups": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "create backup account [type]", + "formatZH": "创建备份账号 [type]", + "paramKeys": [] + } + } + }, + "/core/backups/client/:clientType": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.BackupClientInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load backup account base info", + "tags": [ + "Backup Account" + ] + } + }, + "/core/backups/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "delete backup account [name]", + "formatZH": "删除备份账号 [name]", + "paramKeys": [] + } + } + }, + "/core/backups/refresh/token": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Refresh token", + "tags": [ + "Backup Account" + ] + } + }, + "/core/backups/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BackupOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update backup account", + "tags": [ + "Backup Account" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "update backup account [types]", + "formatZH": "更新备份账号 [types]", + "paramKeys": [] + } + } + }, + "/core/commands": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommandOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "command" + ], + "formatEN": "create quick command [name][command]", + "formatZH": "创建快捷命令 [name][command]", + "paramKeys": [] + } + } + }, + "/core/commands/command": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CommandInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List commands", + "tags": [ + "Command" + ] + } + }, + "/core/commands/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByIDs" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "commands", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete quick command [names]", + "formatZH": "删除快捷命令 [names]", + "paramKeys": [] + } + } + }, + "/core/commands/export": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Export command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "export quick commands", + "formatZH": "导出快速命令", + "paramKeys": [] + } + } + }, + "/core/commands/import": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Import command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "import quick commands", + "formatZH": "导入快速命令", + "paramKeys": [] + } + } + }, + "/core/commands/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page commands", + "tags": [ + "Command" + ] + } + }, + "/core/commands/tree": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.CommandTree" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Tree commands", + "tags": [ + "Command" + ] + } + }, + "/core/commands/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommandOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update command", + "tags": [ + "Command" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "update quick command [name]", + "formatZH": "更新快捷命令 [name]", + "paramKeys": [] + } + } + }, + "/core/groups": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "create group [name][type]", + "formatZH": "创建组 [name][type]", + "paramKeys": [] + } + } + }, + "/core/groups/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "groups", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + }, + { + "db": "groups", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "type", + "output_value": "type" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete group [type][name]", + "formatZH": "删除组 [type][name]", + "paramKeys": [] + } + } + }, + "/core/groups/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.OperateByType" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List groups", + "tags": [ + "System Group" + ] + } + }, + "/core/groups/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "update group [name][type]", + "formatZH": "更新组 [name][type]", + "paramKeys": [] + } + } + }, + "/core/hosts": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.HostOperate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.HostInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create host", + "tags": [ + "Host" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "addr" + ], + "formatEN": "create host [name][addr]", + "formatZH": "创建主机 [name][addr]", + "paramKeys": [] + } + } + }, + "/core/hosts/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByIDs" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete host", + "tags": [ + "Host" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "hosts", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "addr", + "output_value": "addrs" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete host [addrs]", + "formatZH": "删除主机 [addrs]", + "paramKeys": [] + } + } + }, + "/core/hosts/info": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.HostInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get host info", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchPageWithGroup" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page host", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/test/byid/:id": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Test host conn by host id", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/test/byinfo": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.HostConnTest" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Test host conn by info", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/tree": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchForTree" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.HostTree" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host tree", + "tags": [ + "Host" + ] + } + }, + "/core/hosts/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.HostOperate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.HostInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host", + "tags": [ + "Host" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "addr" + ], + "formatEN": "update host [name][addr]", + "formatZH": "更新主机信息 [name][addr]", + "paramKeys": [] + } + } + }, + "/core/hosts/update/group": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeHostGroup" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host group", + "tags": [ + "Host" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "hosts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "addr", + "output_value": "addr" + } + ], + "bodyKeys": [ + "id", + "group" + ], + "formatEN": "change host [addr] group =\u003e [group]", + "formatZH": "切换主机[addr]分组 =\u003e [group]", + "paramKeys": [] + } + } + }, + "/core/logs/clean": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CleanLog" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean operation logs", + "tags": [ + "Logs" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "logType" + ], + "formatEN": "Clean the [logType] log information", + "formatZH": "清空 [logType] 日志信息", + "paramKeys": [] + } + } + }, + "/core/logs/login": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchLgLogWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page login logs", + "tags": [ + "Logs" + ] + } + }, + "/core/logs/operation": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchOpLogWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page operation logs", + "tags": [ + "Logs" + ] + } + }, + "/core/script": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ScriptOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Add script", + "tags": [ + "ScriptLibrary" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "add script [name]", + "formatZH": "添加脚本库脚本 [name]", + "paramKeys": [] + } + } + }, + "/core/script/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByIDs" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete script", + "tags": [ + "ScriptLibrary" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "script_librarys", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete script [names]", + "formatZH": "删除脚本库脚本 [names]", + "paramKeys": [] + } + } + }, + "/core/script/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchPageWithGroup" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page script", + "tags": [ + "ScriptLibrary" + ] + } + }, + "/core/script/sync": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByTaskID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync script from remote", + "tags": [ + "ScriptLibrary" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "sync scripts", + "formatZH": "同步脚本库脚本", + "paramKeys": [] + } + } + }, + "/core/script/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ScriptOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update script", + "tags": [ + "ScriptLibrary" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update script [name]", + "formatZH": "更新脚本库脚本 [name]", + "paramKeys": [] + } + } + }, + "/core/settings/api/config/generate/key": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "generate api key", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "generate api key", + "formatZH": "生成 API 接口密钥", + "paramKeys": [] + } + } + }, + "/core/settings/api/config/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ApiInterfaceConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update api config", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "ipWhiteList" + ], + "formatEN": "update api config =\u003e IP White List: [ipWhiteList]", + "formatZH": "更新 API 接口配置 =\u003e IP 白名单: [ipWhiteList]", + "paramKeys": [] + } + } + }, + "/core/settings/apps/store/config": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.AppstoreConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get appstore config", + "tags": [ + "App" + ] + } + }, + "/core/settings/apps/store/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.AppstoreUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update appstore config", + "tags": [ + "App" + ] + } + }, + "/core/settings/bind/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BindInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system bind info", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "ipv6", + "bindAddress" + ], + "formatEN": "update system bind info =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]", + "formatZH": "修改系统监听信息 =\u003e ipv6: [ipv6], 监听 IP: [bindAddress]", + "paramKeys": [] + } + } + }, + "/core/settings/by": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system setting by key", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/expired/handle": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PasswordUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Reset system password expired", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "reset an expired Password", + "formatZH": "重置过期密码", + "paramKeys": [] + } + } + }, + "/core/settings/interface": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system address", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/menu/default": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Default menu", + "tags": [ + "Menu Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Init menu.", + "formatZH": "初始化菜单", + "paramKeys": [] + } + } + }, + "/core/settings/menu/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Hide advanced feature menu.", + "formatZH": "隐藏高级功能菜单", + "paramKeys": [] + } + } + }, + "/core/settings/mfa": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MfaCredential" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/mfa.Otp" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mfa info", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/mfa/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MfaCredential" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind mfa", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "bind mfa", + "formatZH": "mfa 绑定", + "paramKeys": [] + } + } + }, + "/core/settings/password/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PasswordUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system password", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "update system password", + "formatZH": "修改系统密码", + "paramKeys": [] + } + } + }, + "/core/settings/port/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PortUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system port", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "serverPort" + ], + "formatEN": "update system port =\u003e [serverPort]", + "formatZH": "修改系统端口 =\u003e [serverPort]", + "paramKeys": [] + } + } + }, + "/core/settings/proxy/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ProxyUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update proxy setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "proxyUrl", + "proxyPort" + ], + "formatEN": "set proxy [proxyPort]:[proxyPort].", + "formatZH": "服务器代理配置 [proxyPort]:[proxyPort]", + "paramKeys": [] + } + } + }, + "/core/settings/search": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SettingInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system setting info", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/search/available": { + "get": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system available status", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/ssl/download": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download system cert", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/ssl/info": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SSLInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system cert info", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/ssl/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SSLUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system ssl", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "ssl" + ], + "formatEN": "update system ssl =\u003e [ssl]", + "formatZH": "修改系统 ssl =\u003e [ssl]", + "paramKeys": [] + } + } + }, + "/core/settings/terminal/search": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.TerminalInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system terminal setting info", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/terminal/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.TerminalInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system terminal setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "update system terminal setting", + "formatZH": "修改系统终端配置", + "paramKeys": [] + } + } + }, + "/core/settings/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update system setting [key] =\u003e [value]", + "formatZH": "修改系统配置 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/core/settings/upgrade": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.UpgradeInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load upgrade info", + "tags": [ + "System Setting" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Upgrade" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upgrade", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "version" + ], + "formatEN": "upgrade system =\u003e [version]", + "formatZH": "更新系统 =\u003e [version]", + "paramKeys": [] + } + } + }, + "/core/settings/upgrade/notes": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Upgrade" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load release notes by version", + "tags": [ + "System Setting" + ] + } + }, + "/core/settings/upgrade/releases": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ReleasesNotes" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load upgrade notes", + "tags": [ + "System Setting" + ] + } + }, + "/cronjobs": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create cronjob", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "name" + ], + "formatEN": "create cronjob [type][name]", + "formatZH": "创建计划任务 [type][name]", + "paramKeys": [] + } + } + }, + "/cronjobs/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobBatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete cronjob", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete cronjob [names]", + "formatZH": "删除计划任务 [names]", + "paramKeys": [] + } + } + }, + "/cronjobs/export": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByIDs" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Export cronjob list", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/group/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeGroup" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update cronjob group", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update cronjob group [name]", + "formatZH": "更新计划任务分组 [name]", + "paramKeys": [] + } + } + }, + "/cronjobs/handle": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Handle cronjob once", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "manually execute the cronjob [name]", + "formatZH": "手动执行计划任务 [name]", + "paramKeys": [] + } + } + }, + "/cronjobs/import": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobImport" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Import cronjob list", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/load/info": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load cronjob info", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/next": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobSpec" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load cronjob spec time", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/records/clean": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobClean" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean job records", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "cronjobID", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "cronjobID" + ], + "formatEN": "clean cronjob [name] records", + "formatZH": "清空计划任务记录 [name]", + "paramKeys": [] + } + } + }, + "/cronjobs/records/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load Cronjob record log", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/script/options": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.ScriptOptions" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load script options", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageCronjob" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page cronjobs", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/search/records": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchRecord" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page job records", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobUpdateStatus" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update cronjob status", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "status" + ], + "formatEN": "change the status of cronjob [name] to [status].", + "formatZH": "修改计划任务 [name] 状态为 [status]", + "paramKeys": [] + } + } + }, + "/cronjobs/stop": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Handle stop job", + "tags": [ + "Cronjob" + ] + } + }, + "/cronjobs/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CronjobOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update cronjob", + "tags": [ + "Cronjob" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "cronjobs", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "update cronjob [name]", + "formatZH": "更新计划任务 [name]", + "paramKeys": [] + } + } + }, + "/dashboard/app/launcher": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppLauncher" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load app launcher", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/app/launcher/option": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchByFilter" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.LauncherOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load app launcher options", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/app/launcher/show": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update app Launcher", + "tags": [ + "Dashboard" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "app launcher [key] =\u003e show: [value]", + "formatZH": "首页应用 [key] =\u003e 显示:[value]", + "paramKeys": [] + } + } + }, + "/dashboard/base/:ioOption/:netOption": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "ioOption", + "required": true, + "type": "string" + }, + { + "description": "request", + "in": "path", + "name": "netOption", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DashboardBase" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load dashboard base info", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/base/os": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.OsInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load os info", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/current/:ioOption/:netOption": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "ioOption", + "required": true, + "type": "string" + }, + { + "description": "request", + "in": "path", + "name": "netOption", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DashboardCurrent" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load dashboard current info", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/current/node": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.NodeCurrent" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load dashboard current info for node", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/current/top/cpu": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Process" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load top cpu processes", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/current/top/mem": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.Process" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load top memory processes", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/quick/change": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeQuicks" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update quick jump", + "tags": [ + "Dashboard" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "change quick jump", + "formatZH": "切换快速跳转", + "paramKeys": [] + } + } + }, + "/dashboard/quick/option": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.QuickJump" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load quick jump options", + "tags": [ + "Dashboard" + ] + } + }, + "/dashboard/system/restart/:operation": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "operation", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "System restart", + "tags": [ + "Dashboard" + ] + } + }, + "/databases": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlDBCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create mysql database", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create mysql database [name]", + "formatZH": "创建 mysql 数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BindUser" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind user of mysql database", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "database", + "username" + ], + "formatEN": "bind mysql database [database] [username]", + "formatZH": "绑定 mysql 数据库名 [database] [username]", + "paramKeys": [] + } + } + }, + "/databases/change/access": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeDBInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change mysql access", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_mysqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update database [name] access", + "formatZH": "更新数据库 [name] 访问权限", + "paramKeys": [] + } + } + }, + "/databases/change/password": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeDBInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change mysql password", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_mysqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update database [name] password", + "formatZH": "更新数据库 [name] 密码", + "paramKeys": [] + } + } + }, + "/databases/common/info": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DBBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load base info", + "tags": [ + "Database Common" + ] + } + }, + "/databases/common/load/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load Database conf", + "tags": [ + "Database Common" + ] + } + }, + "/databases/common/update/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DBConfUpdateByFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update conf by upload file", + "tags": [ + "Database Common" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type", + "database" + ], + "formatEN": "update the [type] [database] database configuration information", + "formatZH": "更新 [type] 数据库 [database] 配置信息", + "paramKeys": [] + } + } + }, + "/databases/db": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create database", + "tags": [ + "Database" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "create database [name][type]", + "formatZH": "创建远程数据库 [name][type]", + "paramKeys": [] + } + } + }, + "/databases/db/:name": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DatabaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get databases", + "tags": [ + "Database" + ] + } + }, + "/databases/db/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check database", + "tags": [ + "Database" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "check if database [name][type] is connectable", + "formatZH": "检测远程数据库 [name][type] 连接性", + "paramKeys": [] + } + } + }, + "/databases/db/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete database", + "tags": [ + "Database" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "databases", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete database [names]", + "formatZH": "删除远程数据库 [names]", + "paramKeys": [] + } + } + }, + "/databases/db/del/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before delete remote database", + "tags": [ + "Database" + ] + } + }, + "/databases/db/item/:type": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.DatabaseItem" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List databases", + "tags": [ + "Database" + ] + } + }, + "/databases/db/list/:type": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.DatabaseOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List databases", + "tags": [ + "Database" + ] + } + }, + "/databases/db/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page databases", + "tags": [ + "Database" + ] + } + }, + "/databases/db/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.DatabaseUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update database", + "tags": [ + "Database" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "update database [name]", + "formatZH": "更新远程数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlDBDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete mysql database", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_mysqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete mysql database [name]", + "formatZH": "删除 mysql 数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/del/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlDBDeleteCheck" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before delete mysql database", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/description/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update mysql database description", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_mysqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "description" + ], + "formatEN": "The description of the mysql database [name] is modified =\u003e [description]", + "formatZH": "mysql 数据库 [name] 描述信息修改 [description]", + "paramKeys": [] + } + } + }, + "/databases/format/options": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.MysqlFormatCollationOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List mysql database format collation options", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/load": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlLoadDB" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mysql database from remote", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/pg": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlDBCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create postgresql database", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "create postgresql database [name]", + "formatZH": "创建 postgresql 数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/pg/:database/load": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlLoadDB" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load postgresql database from remote", + "tags": [ + "Database PostgreSQL" + ] + } + }, + "/databases/pg/bind": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlBindUser" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Bind postgresql user", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "username" + ], + "formatEN": "bind postgresql database [name] user [username]", + "formatZH": "绑定 postgresql 数据库 [name] 用户 [username]", + "paramKeys": [] + } + } + }, + "/databases/pg/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlDBDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete postgresql database", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_postgresqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete postgresql database [name]", + "formatZH": "删除 postgresql 数据库 [name]", + "paramKeys": [] + } + } + }, + "/databases/pg/del/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlDBDeleteCheck" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before delete postgresql database", + "tags": [ + "Database PostgreSQL" + ] + } + }, + "/databases/pg/description": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update postgresql database description", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_postgresqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "description" + ], + "formatEN": "The description of the postgresql database [name] is modified =\u003e [description]", + "formatZH": "postgresql 数据库 [name] 描述信息修改 [description]", + "paramKeys": [] + } + } + }, + "/databases/pg/password": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeDBInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change postgresql password", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "database_postgresqls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update database [name] password", + "formatZH": "更新数据库 [name] 密码", + "paramKeys": [] + } + } + }, + "/databases/pg/privileges": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeDBInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change postgresql privileges", + "tags": [ + "Database PostgreSQL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "database", + "username" + ], + "formatEN": "Update [user] privileges of database [database]", + "formatZH": "更新数据库 [database] 用户 [username] 权限", + "paramKeys": [] + } + } + }, + "/databases/pg/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PostgresqlDBSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page postgresql databases", + "tags": [ + "Database PostgreSQL" + ] + } + }, + "/databases/redis/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LoadRedisStatus" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.RedisConf" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load redis conf", + "tags": [ + "Database Redis" + ] + } + }, + "/databases/redis/conf/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RedisConfUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update redis conf", + "tags": [ + "Database Redis" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "update the redis database configuration information", + "formatZH": "更新 redis 数据库配置信息", + "paramKeys": [] + } + } + }, + "/databases/redis/install/cli": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Install redis-cli", + "tags": [ + "Database Redis" + ] + } + }, + "/databases/redis/password": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangeRedisPass" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change redis password", + "tags": [ + "Database Redis" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "change the password of the redis database", + "formatZH": "修改 redis 数据库密码", + "paramKeys": [] + } + } + }, + "/databases/redis/persistence/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LoadRedisStatus" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.RedisPersistence" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load redis persistence conf", + "tags": [ + "Database Redis" + ] + } + }, + "/databases/redis/persistence/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RedisConfPersistenceUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update redis persistence conf", + "tags": [ + "Database Redis" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "redis database persistence configuration update", + "formatZH": "redis 数据库持久化配置更新", + "paramKeys": [] + } + } + }, + "/databases/redis/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.LoadRedisStatus" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.RedisStatus" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load redis status info", + "tags": [ + "Database Redis" + ] + } + }, + "/databases/remote": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mysql remote access", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlDBSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page mysql databases", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.MysqlStatus" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mysql status info", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/variables": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithNameAndType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.MysqlVariables" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load mysql variables info", + "tags": [ + "Database Mysql" + ] + } + }, + "/databases/variables/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MysqlVariablesUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update mysql variables", + "tags": [ + "Database Mysql" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "adjust mysql database performance parameters", + "formatZH": "调整 mysql 数据库性能参数", + "paramKeys": [] + } + } + }, + "/files": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Create dir or file [path]", + "formatZH": "创建文件/文件夹 [path]", + "paramKeys": [] + } + } + }, + "/files/batch/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FilePathsCheck" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.ExistFileInfo" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch check file exist", + "tags": [ + "File" + ] + } + }, + "/files/batch/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileBatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch delete file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "paths" + ], + "formatEN": "Batch delete dir or file [paths]", + "formatZH": "批量删除文件/文件夹 [paths]", + "paramKeys": [] + } + } + }, + "/files/batch/role": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileRoleReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch change file mode and owner", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "paths", + "mode", + "user", + "group" + ], + "formatEN": "Batch change file mode and owner [paths] =\u003e [mode]/[user]/[group]", + "formatZH": "批量修改文件权限和用户/组 [paths] =\u003e [mode]/[user]/[group]", + "paramKeys": [] + } + } + }, + "/files/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FilePathCheck" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check file exist", + "tags": [ + "File" + ] + } + }, + "/files/chunkdownload": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileDownload" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Chunk Download file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Download file [name]", + "formatZH": "下载文件 [name]", + "paramKeys": [] + } + } + }, + "/files/chunkupload": { + "post": { + "parameters": [ + { + "description": "request", + "in": "formData", + "name": "file", + "required": true, + "type": "file" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "ChunkUpload file", + "tags": [ + "File" + ] + } + }, + "/files/compress": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileCompress" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Compress file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Compress file [name]", + "formatZH": "压缩文件 [name]", + "paramKeys": [] + } + } + }, + "/files/content": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileContentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load file content", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Load file content [path]", + "formatZH": "获取文件内容 [path]", + "paramKeys": [] + } + } + }, + "/files/convert": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileConvert" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Convert file", + "tags": [ + "File" + ] + } + }, + "/files/convert/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Convert file", + "tags": [ + "File" + ] + } + }, + "/files/decompress": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileDeCompress" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Decompress file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Decompress file [path]", + "formatZH": "解压 [path]", + "paramKeys": [] + } + } + }, + "/files/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Delete dir or file [path]", + "formatZH": "删除文件/文件夹 [path]", + "paramKeys": [] + } + } + }, + "/files/depth/size": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DirSizeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.DepthDirSizeRes" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Multi file size", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Multi file size [path]", + "formatZH": "获取目录及其第一层子目录文件夹大小 [path]", + "paramKeys": [] + } + } + }, + "/files/download": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download file", + "tags": [ + "File" + ] + } + }, + "/files/favorite": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FavoriteCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Favorite" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create favorite", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "收藏文件/文件夹 [path]", + "formatZH": "收藏文件/文件夹 [path]", + "paramKeys": [] + } + } + }, + "/files/favorite/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FavoriteDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete favorite", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "favorites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "path", + "output_value": "path" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete avorite [path]", + "formatZH": "删除收藏 [path]", + "paramKeys": [] + } + } + }, + "/files/favorite/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List favorites", + "tags": [ + "File" + ] + } + }, + "/files/mode": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change file mode", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path", + "mode" + ], + "formatEN": "Change mode [paths] =\u003e [mode]", + "formatZH": "修改权限 [paths] =\u003e [mode]", + "paramKeys": [] + } + } + }, + "/files/mount": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DiskInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "system mount", + "tags": [ + "File" + ] + } + }, + "/files/move": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileMove" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Move file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "oldPaths", + "newPath" + ], + "formatEN": "Move [oldPaths] =\u003e [newPath]", + "formatZH": "移动文件 [oldPaths] =\u003e [newPath]", + "paramKeys": [] + } + } + }, + "/files/owner": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileRoleUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change file owner", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path", + "user", + "group" + ], + "formatEN": "Change owner [paths] =\u003e [user]/[group]", + "formatZH": "修改用户/组 [paths] =\u003e [user]/[group]", + "paramKeys": [] + } + } + }, + "/files/preview": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileContentReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Preview file content", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Preview file content [path]", + "formatZH": "预览文件内容 [path]", + "paramKeys": [] + } + } + }, + "/files/read": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileReadByLineReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileLineContent" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Read file by Line", + "tags": [ + "File" + ] + } + }, + "/files/recycle/clear": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clear RecycleBin files", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "清空回收站", + "formatZH": "清空回收站", + "paramKeys": [] + } + } + }, + "/files/recycle/reduce": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RecycleBinReduce" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Reduce RecycleBin files", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Reduce RecycleBin file [name]", + "formatZH": "还原回收站文件 [name]", + "paramKeys": [] + } + } + }, + "/files/recycle/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List RecycleBin files", + "tags": [ + "File" + ] + } + }, + "/files/recycle/status": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get RecycleBin status", + "tags": [ + "File" + ] + } + }, + "/files/rename": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileRename" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change file name", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "oldName", + "newName" + ], + "formatEN": "Rename [oldName] =\u003e [newName]", + "formatZH": "重命名 [oldName] =\u003e [newName]", + "paramKeys": [] + } + } + }, + "/files/save": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileEdit" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update file content", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Update file content [path]", + "formatZH": "更新文件内容 [path]", + "paramKeys": [] + } + } + }, + "/files/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileOption" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List files", + "tags": [ + "File" + ] + } + }, + "/files/size": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DirSizeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.DirSizeRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load file size", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path" + ], + "formatEN": "Load file size [path]", + "formatZH": "获取文件夹大小 [path]", + "paramKeys": [] + } + } + }, + "/files/tree": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileOption" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.FileTree" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load files tree", + "tags": [ + "File" + ] + } + }, + "/files/upload": { + "post": { + "parameters": [ + { + "description": "request", + "in": "formData", + "name": "file", + "required": true, + "type": "file" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upload file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "path", + "file" + ], + "formatEN": "Upload file [path]/[file]", + "formatZH": "上传文件 [path]/[file]", + "paramKeys": [] + } + } + }, + "/files/upload/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.SearchUploadWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page file", + "tags": [ + "File" + ] + } + }, + "/files/user/group": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.UserGroupResponse" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "system user and group", + "tags": [ + "File" + ] + } + }, + "/files/wget": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FileWget" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileWgetRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Wget file", + "tags": [ + "File" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "url", + "path", + "name" + ], + "formatEN": "Download url =\u003e [path]/[name]", + "formatZH": "下载 url =\u003e [path]/[name]", + "paramKeys": [] + } + } + }, + "/groups": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "create group [name][type]", + "formatZH": "创建组 [name][type]", + "paramKeys": [] + } + } + }, + "/groups/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "groups", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + }, + { + "db": "groups", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "type", + "output_value": "type" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "delete group [type][name]", + "formatZH": "删除组 [type][name]", + "paramKeys": [] + } + } + }, + "/groups/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.OperateByType" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List groups", + "tags": [ + "System Group" + ] + } + }, + "/groups/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.GroupUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update group", + "tags": [ + "System Group" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "type" + ], + "formatEN": "update group [name][type]", + "formatZH": "更新组 [name][type]", + "paramKeys": [] + } + } + }, + "/hosts/components/{name}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "Component name to check (e.g., rsync, docker)", + "in": "path", + "name": "name", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.ComponentInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check if a system component exists", + "tags": [ + "Host" + ] + } + }, + "/hosts/disks": { + "get": { + "description": "Get information about all disks including partitioned and unpartitioned disks", + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.CompleteDiskInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get complete disk information", + "tags": [ + "Disk Management" + ] + } + }, + "/hosts/disks/mount": { + "post": { + "consumes": [ + "application/json" + ], + "description": "Mount partition to specified mount point", + "parameters": [ + { + "description": "mount request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DiskMountRequest" + } + } + ], + "responses": { + "200": { + "description": "Disk mounted successfully", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Mount disk", + "tags": [ + "Disk Management" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "device", + "mountPoint" + ], + "formatEN": "Mount disk [device] to [mountPoint]", + "formatZH": "挂载磁盘 [device] 到 [mountPoint]", + "paramKeys": [] + } + } + }, + "/hosts/disks/partition": { + "post": { + "consumes": [ + "application/json" + ], + "description": "Create partition and format disk with specified filesystem", + "parameters": [ + { + "description": "partition request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DiskPartitionRequest" + } + } + ], + "responses": { + "200": { + "description": "Partition created successfully", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Partition disk", + "tags": [ + "Disk Management" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "device", + "filesystem", + "mountPoint" + ], + "formatEN": "Partition disk [device] with filesystem [filesystem], mount point [mountPoint]", + "formatZH": "对磁盘 [device] 进行分区,文件系统 [filesystem],挂载点 [mountPoint]", + "paramKeys": [] + } + } + }, + "/hosts/disks/unmount": { + "post": { + "consumes": [ + "application/json" + ], + "description": "Unmount partition from mount point", + "parameters": [ + { + "description": "unmount request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.DiskUnmountRequest" + } + } + ], + "responses": { + "200": { + "description": "Disk unmounted successfully", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Unmount disk", + "tags": [ + "Disk Management" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "device", + "mountPoint" + ], + "formatEN": "Unmount disk [device] from [mountPoint]", + "formatZH": "卸载磁盘 [device] 从 [mountPoint]", + "paramKeys": [] + } + } + }, + "/hosts/firewall/base": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FirewallBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load firewall base info", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/batch": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchRuleOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch operate rule", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/filter/chain/status": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "load chain status with name", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/filter/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.IptablesOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Apply/Unload/Init iptables filter", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] iptables filter firewall", + "formatZH": "[operate] iptables filter 防火墙", + "paramKeys": [] + } + } + }, + "/hosts/firewall/filter/rule/batch": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.IptablesBatchOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch operate iptables filter rules", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/filter/rule/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.IptablesRuleOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate iptables filter rule", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation", + "chain" + ], + "formatEN": "[operation] filter rule to [chain]", + "formatZH": "[operation] filter规则到 [chain]", + "paramKeys": [] + } + } + }, + "/hosts/firewall/filter/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchPageWithType" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "search iptables filter rules", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/forward": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ForwardRuleOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate forward rule", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "update port forward rules", + "formatZH": "更新端口转发规则", + "paramKeys": [] + } + } + }, + "/hosts/firewall/ip": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.AddrRuleOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Ip rule", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "strategy", + "address" + ], + "formatEN": "create address rules [strategy][address]", + "formatZH": "添加 ip 规则 [strategy] [address]", + "paramKeys": [] + } + } + }, + "/hosts/firewall/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FirewallOperation" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate firewall", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] firewall", + "formatZH": "[operation] 防火墙", + "paramKeys": [] + } + } + }, + "/hosts/firewall/port": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PortRuleOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create group", + "tags": [ + "Firewall" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "port", + "strategy" + ], + "formatEN": "create port rules [strategy][port]", + "formatZH": "添加端口规则 [strategy] [port]", + "paramKeys": [] + } + } + }, + "/hosts/firewall/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RuleSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page firewall rules", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/update/addr": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.AddrRuleUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Ip rule", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/update/description": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateFirewallDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update rule description", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/firewall/update/port": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PortRuleUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update port rule", + "tags": [ + "Firewall" + ] + } + }, + "/hosts/monitor/clean": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean monitor data", + "tags": [ + "Monitor" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "clean monitor datas", + "formatZH": "清空监控数据", + "paramKeys": [] + } + } + }, + "/hosts/monitor/gpu/search": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MonitorGPUSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.MonitorGPUData" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load monitor data", + "tags": [ + "Monitor" + ] + } + }, + "/hosts/monitor/search": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MonitorSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.MonitorData" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load monitor data", + "tags": [ + "Monitor" + ] + } + }, + "/hosts/monitor/setting": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.MonitorSetting" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load monitor setting", + "tags": [ + "Monitor" + ] + } + }, + "/hosts/monitor/setting/update": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.MonitorSettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update monitor setting", + "tags": [ + "Monitor" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update default monitor [name]-[value]", + "formatZH": "修改默认监控网卡 [name]-[value]", + "paramKeys": [] + } + } + }, + "/hosts/ssh/cert": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RootCertOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Generate host SSH secret", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "generate SSH secret", + "formatZH": "生成 SSH 密钥 ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/cert/delete": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ForceDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete host SSH secret", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "delete SSH secret", + "formatZH": "删除 SSH 密钥 ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/cert/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host SSH secret", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/cert/sync": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sycn host SSH secret", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "sync SSH secret", + "formatZH": "同步 SSH 密钥 ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/cert/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.RootCertOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host SSH secret", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "generate SSH secret", + "formatZH": "生成 SSH 密钥 ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host SSH conf", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/file/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SSHConf" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host SSH setting by file", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key" + ], + "formatEN": "update SSH conf [key]", + "formatZH": "修改 SSH 配置文件 [key]", + "paramKeys": [] + } + } + }, + "/hosts/ssh/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchSSHLog" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host SSH logs", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/log/export": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchSSHLog" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Export host SSH logs", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate SSH", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] SSH", + "formatZH": "[operation] SSH ", + "paramKeys": [] + } + } + }, + "/hosts/ssh/search": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SSHInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load host SSH setting info", + "tags": [ + "SSH" + ] + } + }, + "/hosts/ssh/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SSHUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update host SSH setting", + "tags": [ + "SSH" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "newValue" + ], + "formatEN": "update SSH setting [key] =\u003e [newValue]", + "formatZH": "修改 SSH 配置 [key] =\u003e [newValue]", + "paramKeys": [] + } + } + }, + "/hosts/tool": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.HostToolReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.HostToolRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get tool status", + "tags": [ + "Host tool" + ] + } + }, + "/hosts/tool/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.HostToolConfig" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.HostToolConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get tool config", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] tool config", + "formatZH": "[operate] 主机工具配置文件 ", + "paramKeys": [] + } + } + }, + "/hosts/tool/init": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.HostToolCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create Host tool Config", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "create [type] config", + "formatZH": "创建 [type] 配置", + "paramKeys": [] + } + } + }, + "/hosts/tool/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.HostToolReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate tool", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate", + "type" + ], + "formatEN": "[operate] [type]", + "formatZH": "[operate] [type] ", + "paramKeys": [] + } + } + }, + "/hosts/tool/supervisor/process": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.SupervisorProcessConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Supervisor process config", + "tags": [ + "Host tool" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.SupervisorProcessConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create Supervisor process", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] process", + "formatZH": "[operate] 守护进程 ", + "paramKeys": [] + } + } + }, + "/hosts/tool/supervisor/process/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.SupervisorProcessFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Supervisor process config file", + "tags": [ + "Host tool" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate" + ], + "formatEN": "[operate] Supervisor Process Config file", + "formatZH": "[operate] Supervisor 进程文件 ", + "paramKeys": [] + } + } + }, + "/logs/system/files": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system log files", + "tags": [ + "Logs" + ] + } + }, + "/logs/tasks/executing/count": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "integer" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get the number of executing tasks", + "tags": [ + "TaskLog" + ] + } + }, + "/logs/tasks/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchTaskLogReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page task logs", + "tags": [ + "TaskLog" + ] + } + }, + "/openresty": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxFile" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load OpenResty conf", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/build": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxBuildReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Build OpenResty", + "tags": [ + "OpenResty" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Build OpenResty", + "formatZH": "构建 OpenResty", + "paramKeys": [] + } + } + }, + "/openresty/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxConfigFileUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update OpenResty conf by upload file", + "tags": [ + "OpenResty" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Update nginx conf", + "formatZH": "更新 nginx 配置", + "paramKeys": [] + } + } + }, + "/openresty/https": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxConfigRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get default HTTPs status", + "tags": [ + "OpenResty" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxDefaultHTTPSUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate default HTTPs", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/modules": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxBuildConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get OpenResty modules", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/modules/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxModuleUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update OpenResty module", + "tags": [ + "OpenResty" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Update OpenResty module", + "formatZH": "更新 OpenResty 模块", + "paramKeys": [] + } + } + }, + "/openresty/scope": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxScopeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.NginxParam" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load partial OpenResty conf", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/status": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxStatus" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load OpenResty status info", + "tags": [ + "OpenResty" + ] + } + }, + "/openresty/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxConfigUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update OpenResty conf", + "tags": [ + "OpenResty" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "Update nginx conf [domain]", + "formatZH": "更新 nginx 配置 [domain]", + "paramKeys": [] + } + } + }, + "/process/stop": { + "post": { + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.ProcessReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Stop Process", + "tags": [ + "Process" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "PID" + ], + "formatEN": "结束进程 [PID]", + "formatZH": "结束进程 [PID]", + "paramKeys": [] + } + } + }, + "/process/{pid}": { + "get": { + "parameters": [ + { + "description": "PID", + "in": "path", + "name": "pid", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Process Info By PID", + "tags": [ + "Process" + ] + } + }, + "/runtimes": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.Runtime" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create runtime", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Create runtime [name]", + "formatZH": "创建运行环境 [name]", + "paramKeys": [] + } + } + }, + "/runtimes/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.RuntimeDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get runtime", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete runtime", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete runtime [name]", + "formatZH": "删除运行环境 [name]", + "paramKeys": [] + } + } + }, + "/runtimes/installed/delete/check/:id": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.AppResource" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete runtime", + "tags": [ + "Website" + ] + } + }, + "/runtimes/node/modules": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodeModuleReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.NodeModule" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Node modules", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/node/modules/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodeModuleReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Node modules", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/node/package": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NodePackageReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.PackageScripts" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Node package scripts", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate runtime", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "id" + ], + "formatEN": "Operate runtime [name]", + "formatZH": "操作运行环境 [name]", + "paramKeys": [] + } + } + }, + "/runtimes/php/:id/extensions": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.PHPExtensionRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get php runtime extension", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPConfigUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update runtime php conf", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "[domain] PHP conf update", + "formatZH": "[domain] PHP 配置修改", + "paramKeys": [] + } + } + }, + "/runtimes/php/config/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.PHPConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load php runtime conf", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/container/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.PHPContainerConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get PHP container config", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/container/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPContainerConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update PHP container config", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/extensions": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionsCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create Extensions", + "tags": [ + "PHP Extensions" + ] + } + }, + "/runtimes/php/extensions/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionsDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete Extensions", + "tags": [ + "PHP Extensions" + ] + } + }, + "/runtimes/php/extensions/install": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionInstallReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Install php extension", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/extensions/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionsSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page Extensions", + "tags": [ + "PHP Extensions" + ] + } + }, + "/runtimes/php/extensions/uninstall": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionInstallReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "UnInstall php extension", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/extensions/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPExtensionsUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Extensions", + "tags": [ + "PHP Extensions" + ] + } + }, + "/runtimes/php/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get php conf file", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/fpm/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.FPMConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update fpm config", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/fpm/config/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.FPMConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get fpm config", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/fpm/status/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "additionalProperties": true, + "type": "object" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get PHP runtime status", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/php/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPFileUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update php conf file", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "Nginx conf update [domain]", + "formatZH": "php 配置修改 [domain]", + "paramKeys": [] + } + } + }, + "/runtimes/remark": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeRemark" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update runtime remark", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List runtimes", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/supervisor/process": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPSupervisorProcessConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate supervisor process", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/supervisor/process/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.SupervisorProcessConfig" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get supervisor process", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/supervisor/process/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.PHPSupervisorProcessFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate supervisor process file", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/sync": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + } + ], + "summary": "Sync runtime status", + "tags": [ + "Runtime" + ] + } + }, + "/runtimes/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.RuntimeUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update runtime", + "tags": [ + "Runtime" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Update runtime [name]", + "formatZH": "更新运行环境 [name]", + "paramKeys": [] + } + } + }, + "/settings/basedir": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load local backup dir", + "tags": [ + "System Setting" + ] + } + }, + "/settings/description/save": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.CommonDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Save common description", + "tags": [ + "System Setting" + ] + } + }, + "/settings/get/{key}": { + "get": { + "parameters": [ + { + "description": "key", + "in": "path", + "name": "key", + "required": true, + "type": "string" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SettingInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system setting by key", + "tags": [ + "System Setting" + ] + } + }, + "/settings/search": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SettingInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system setting info", + "tags": [ + "System Setting" + ] + } + }, + "/settings/search/available": { + "get": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system available status", + "tags": [ + "System Setting" + ] + } + }, + "/settings/snapshot": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create system snapshot", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "from", + "description" + ], + "formatEN": "Create system backup [description] to [from]", + "formatZH": "创建系统快照 [description] 到 [from]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotBatchDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete system backup", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "Delete system backup [name]", + "formatZH": "删除系统快照 [name]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/description/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateDescription" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update snapshot description", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "description" + ], + "formatEN": "The description of the snapshot [name] is modified =\u003e [description]", + "formatZH": "快照 [name] 描述信息修改 [description]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/import": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotImport" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Import system snapshot", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "from", + "names" + ], + "formatEN": "Sync system snapshots [names] from [from]", + "formatZH": "从 [from] 同步系统快照 [names]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/load": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SnapshotData" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load system snapshot data", + "tags": [ + "System Setting" + ] + } + }, + "/settings/snapshot/recover": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Recover system backup", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Recover from system backup [name]", + "formatZH": "从系统快照 [name] 恢复", + "paramKeys": [] + } + } + }, + "/settings/snapshot/recreate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Recreate system snapshot", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "recrete the snapshot [name]", + "formatZH": "重试创建快照 [name]", + "paramKeys": [] + } + } + }, + "/settings/snapshot/rollback": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SnapshotRecover" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Rollback system backup", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "snapshots", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Rollback from system backup [name]", + "formatZH": "从系统快照 [name] 回滚", + "paramKeys": [] + } + } + }, + "/settings/snapshot/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageSnapshot" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page system snapshot", + "tags": [ + "System Setting" + ] + } + }, + "/settings/ssh": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Save local conn info", + "tags": [ + "System Setting" + ] + } + }, + "/settings/ssh/check/info": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check local conn info", + "tags": [ + "System Setting" + ] + } + }, + "/settings/ssh/conn": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.SSHConnData" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load local conn", + "tags": [ + "System Setting" + ] + } + }, + "/settings/ssh/conn/default": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SSHDefaultConn" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update local is conn", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "defaultConn" + ], + "formatEN": "update system default conn [defaultConn]", + "formatZH": "本地终端默认连接 [defaultConn]", + "paramKeys": [] + } + } + }, + "/settings/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update system setting", + "tags": [ + "System Setting" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update system setting [key] =\u003e [value]", + "formatZH": "修改系统配置 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/clam": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create clam", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "path" + ], + "formatEN": "create clam [name][path]", + "formatZH": "创建扫描规则 [name][path]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/base": { + "post": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.ClamBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load clam base info", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete clam", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "clams", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "name", + "output_value": "names" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete clam [names]", + "formatZH": "删除扫描规则 [names]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/file/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamFileReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load clam file", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/file/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateByNameAndFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update clam file", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/handle": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Handle clam scan", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "clams", + "input_column": "id", + "input_value": "id", + "isList": true, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "handle clam scan [name]", + "formatZH": "执行病毒扫描 [name]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Clam", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] FTP", + "formatZH": "[operation] Clam", + "paramKeys": [] + } + } + }, + "/toolbox/clam/record/clean": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperateByID" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean clam record", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "clams", + "input_column": "id", + "input_value": "id", + "isList": true, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "clean clam record [name]", + "formatZH": "清空扫描报告 [name]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/record/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamLogSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page clam record", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchClamWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page clam", + "tags": [ + "Clam" + ] + } + }, + "/toolbox/clam/status/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamUpdateStatus" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update clam status", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "clams", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id", + "status" + ], + "formatEN": "change the status of clam [name] to [status].", + "formatZH": "修改扫描规则 [name] 状态为 [status]", + "paramKeys": [] + } + } + }, + "/toolbox/clam/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ClamUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update clam", + "tags": [ + "Clam" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name", + "path" + ], + "formatEN": "update clam [name][path]", + "formatZH": "修改扫描规则 [name][path]", + "paramKeys": [] + } + } + }, + "/toolbox/clean": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "items": { + "$ref": "#/definitions/dto.Clean" + }, + "type": "array" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clean system", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Clean system junk files", + "formatZH": "清理系统垃圾文件", + "paramKeys": [] + } + } + }, + "/toolbox/device/base": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.DeviceBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load device base info", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/check/dns": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "boolean" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check device DNS conf", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.OperationWithName" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "load conf", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/update/byconf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateByNameAndFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device conf by file", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/update/conf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SettingUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update device conf [key] =\u003e [value]", + "formatZH": "修改主机参数 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/device/update/host": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device hosts", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update device host [key] =\u003e [value]", + "formatZH": "修改主机 Host [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/device/update/passwd": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.ChangePasswd" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device passwd", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/update/swap": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SwapHelper" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update device swap", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operate", + "path" + ], + "formatEN": "[operate] device swap [path]", + "formatZH": "[operate] 主机 swap [path]", + "paramKeys": [] + } + } + }, + "/toolbox/device/users": { + "get": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load user list", + "tags": [ + "Device" + ] + } + }, + "/toolbox/device/zone/options": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "list time zone options", + "tags": [ + "Device" + ] + } + }, + "/toolbox/fail2ban/base": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.Fail2BanBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load fail2ban base info", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/fail2ban/load/conf": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "type": "string" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load fail2ban conf", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/fail2ban/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate fail2ban", + "tags": [ + "Fail2ban" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] Fail2ban", + "formatZH": "[operation] Fail2ban", + "paramKeys": [] + } + } + }, + "/toolbox/fail2ban/operate/sshd": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanSet" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate sshd of fail2ban", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/fail2ban/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page fail2ban ip list", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/fail2ban/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Fail2BanUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update fail2ban conf", + "tags": [ + "Fail2ban" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "key", + "value" + ], + "formatEN": "update fail2ban conf [key] =\u003e [value]", + "formatZH": "修改 Fail2ban 配置 [key] =\u003e [value]", + "paramKeys": [] + } + } + }, + "/toolbox/fail2ban/update/byconf": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.UpdateByFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update fail2ban conf by file", + "tags": [ + "Fail2ban" + ] + } + }, + "/toolbox/ftp": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FtpCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create FTP user", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "user", + "path" + ], + "formatEN": "create FTP [user][path]", + "formatZH": "创建 FTP 账户 [user][path]", + "paramKeys": [] + } + } + }, + "/toolbox/ftp/base": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.FtpBaseInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load FTP base info", + "tags": [ + "FTP" + ] + } + }, + "/toolbox/ftp/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete FTP user", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "ftps", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "user", + "output_value": "users" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "delete FTP users [users]", + "formatZH": "删除 FTP 账户 [users]", + "paramKeys": [] + } + } + }, + "/toolbox/ftp/log/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FtpLogSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load FTP operation log", + "tags": [ + "FTP" + ] + } + }, + "/toolbox/ftp/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.Operate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate FTP", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "operation" + ], + "formatEN": "[operation] FTP", + "formatZH": "[operation] FTP", + "paramKeys": [] + } + } + }, + "/toolbox/ftp/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.SearchWithPage" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page FTP user", + "tags": [ + "FTP" + ] + } + }, + "/toolbox/ftp/sync": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.BatchDeleteReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Sync FTP user", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "sync FTP users", + "formatZH": "同步 FTP 账户", + "paramKeys": [] + } + } + }, + "/toolbox/ftp/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.FtpUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update FTP user", + "tags": [ + "FTP" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "user", + "path" + ], + "formatEN": "update FTP [user][path]", + "formatZH": "修改 FTP 账户 [user][path]", + "paramKeys": [] + } + } + }, + "/toolbox/scan": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.CleanData" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Scan system", + "tags": [ + "Device" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "scan System Junk Files", + "formatZH": "扫描系统垃圾文件", + "paramKeys": [] + } + } + }, + "/websites": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "alias" + ], + "formatEN": "Create website [alias]", + "formatZH": "创建网站 [alias]", + "paramKeys": [] + } + } + }, + "/websites/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website by id", + "tags": [ + "Website" + ] + } + }, + "/websites/:id/config/:type": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.FileInfo" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website nginx by id", + "tags": [ + "Website Nginx" + ] + } + }, + "/websites/:id/https": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteHTTPS" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load https conf", + "tags": [ + "Website HTTPS" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteHTTPSOp" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteHTTPS" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update https conf", + "tags": [ + "Website HTTPS" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "Update website https [domain] conf", + "formatZH": "更新网站 [domain] https 配置", + "paramKeys": [] + } + } + }, + "/websites/acme": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteAcmeAccountCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteAcmeAccountDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website acme account", + "tags": [ + "Website Acme" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "email" + ], + "formatEN": "Create website acme [email]", + "formatZH": "创建网站 acme [email]", + "paramKeys": [] + } + } + }, + "/websites/acme/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website acme account", + "tags": [ + "Website Acme" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_acme_accounts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "email", + "output_value": "email" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete website acme [email]", + "formatZH": "删除网站 acme [email]", + "paramKeys": [] + } + } + }, + "/websites/acme/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page website acme accounts", + "tags": [ + "Website Acme" + ] + } + }, + "/websites/acme/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteAcmeAccountUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteAcmeAccountDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website acme account", + "tags": [ + "Website Acme" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_acme_accounts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "email", + "output_value": "email" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update acme [email]", + "formatZH": "更新 acme [email]", + "paramKeys": [] + } + } + }, + "/websites/auths": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxAuthReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxAuthRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AuthBasic conf", + "tags": [ + "Website" + ] + } + }, + "/websites/auths/path": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxAuthReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxPathAuthRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AuthBasic conf", + "tags": [ + "Website" + ] + } + }, + "/websites/auths/path/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxPathAuthUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AuthBasic conf", + "tags": [ + "Website" + ] + } + }, + "/websites/auths/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxAuthUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AuthBasic conf", + "tags": [ + "Website" + ] + } + }, + "/websites/batch/group": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.BatchWebsiteGroup" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch set website group", + "tags": [ + "Website" + ] + } + }, + "/websites/batch/https": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.BatchWebsiteHttps" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch set HTTPS for websites", + "tags": [ + "Website" + ] + } + }, + "/websites/batch/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.BatchWebsiteOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Batch operate websites", + "tags": [ + "Website" + ] + } + }, + "/websites/ca": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCACreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.WebsiteCACreate" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website ca", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Create website ca [name]", + "formatZH": "创建网站 ca [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website ca", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_cas", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete website ca [name]", + "formatZH": "删除网站 ca [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/download": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download CA file", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_cas", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "download ca file [name]", + "formatZH": "下载 CA 证书文件 [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/obtain": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCAObtain" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Obtain SSL", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_cas", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Obtain SSL [name]", + "formatZH": "自签 SSL 证书 [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/renew": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCAObtain" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Obtain SSL", + "tags": [ + "Website CA" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_cas", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Obtain SSL [name]", + "formatZH": "自签 SSL 证书 [name]", + "paramKeys": [] + } + } + }, + "/websites/ca/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCASearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page website ca", + "tags": [ + "Website CA" + ] + } + }, + "/websites/ca/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteCADTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website ca", + "tags": [ + "Website CA" + ] + } + }, + "/websites/check": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteInstallCheckReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsitePreInstallCheck" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Check before create website", + "tags": [ + "Website" + ] + } + }, + "/websites/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxScopeReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteNginxConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Load nginx conf", + "tags": [ + "Website Nginx" + ] + } + }, + "/websites/config/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxConfigUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update nginx conf", + "tags": [ + "Website Nginx" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "Nginx conf update [domain]", + "formatZH": "nginx 配置修改 [domain]", + "paramKeys": [] + } + } + }, + "/websites/cors/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.CorsConfigReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update CORS Config", + "tags": [ + "Website" + ] + } + }, + "/websites/cors/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.CorsConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get CORS Config", + "tags": [ + "Website" + ] + } + }, + "/websites/crosssite": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.CrossSiteAccessOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate Cross Site Access", + "tags": [ + "Website" + ] + } + }, + "/websites/databases": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Database" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get databases", + "tags": [ + "Website" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.ChangeDatabase" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change website database", + "tags": [ + "Website" + ] + } + }, + "/websites/default/html/:type": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteHtmlRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get default html", + "tags": [ + "Website" + ] + } + }, + "/websites/default/html/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteHtmlUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update default html", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "Update default html", + "formatZH": "更新默认 html", + "paramKeys": [] + } + } + }, + "/websites/default/server": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDefaultUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Change default server", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id", + "operate" + ], + "formatEN": "Change default server =\u003e [domain]", + "formatZH": "修改默认 server =\u003e [domain]", + "paramKeys": [] + } + } + }, + "/websites/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete website [domain]", + "formatZH": "删除网站 [domain]", + "paramKeys": [] + } + } + }, + "/websites/dir": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteDirConfig" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website dir", + "tags": [ + "Website" + ] + } + }, + "/websites/dir/permission": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteUpdateDirPermission" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Site Dir permission", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update domain [domain] dir permission", + "formatZH": "更新网站 [domain] 目录权限", + "paramKeys": [] + } + } + }, + "/websites/dir/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteUpdateDir" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Site Dir", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update domain [domain] dir", + "formatZH": "更新网站 [domain] 目录", + "paramKeys": [] + } + } + }, + "/websites/dns": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDnsAccountCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website dns account", + "tags": [ + "Website DNS" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Create website dns [name]", + "formatZH": "创建网站 dns [name]", + "paramKeys": [] + } + } + }, + "/websites/dns/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website dns account", + "tags": [ + "Website DNS" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_dns_accounts", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "name", + "output_value": "name" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete website dns [name]", + "formatZH": "删除网站 dns [name]", + "paramKeys": [] + } + } + }, + "/websites/dns/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/dto.PageInfo" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page website dns accounts", + "tags": [ + "Website DNS" + ] + } + }, + "/websites/dns/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDnsAccountUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website dns account", + "tags": [ + "Website DNS" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "name" + ], + "formatEN": "Update website dns [name]", + "formatZH": "更新网站 dns [name]", + "paramKeys": [] + } + } + }, + "/websites/domains": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDomainCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/model.WebsiteDomain" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website domain", + "tags": [ + "Website Domain" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "domain" + ], + "formatEN": "Create domain [domain]", + "formatZH": "创建域名 [domain]", + "paramKeys": [] + } + } + }, + "/websites/domains/:websiteId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "websiteId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/model.WebsiteDomain" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website domains by websiteId", + "tags": [ + "Website Domain" + ] + } + }, + "/websites/domains/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDomainDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website domain", + "tags": [ + "Website Domain" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_domains", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Delete domain [domain]", + "formatZH": "删除域名 [domain]", + "paramKeys": [] + } + } + }, + "/websites/domains/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDomainUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website domain", + "tags": [ + "Website Domain" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_domains", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update domain [domain]", + "formatZH": "更新域名 [domain]", + "paramKeys": [] + } + } + }, + "/websites/exec/composer": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.ExecComposerReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Exec Composer", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/dto.NginxUpstream" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website upstreams", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs/create": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLBCreate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website upstream", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLBDelete" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website upstream", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLBUpdateFile" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website upstream file", + "tags": [ + "Website" + ] + } + }, + "/websites/lbs/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLBUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website upstream", + "tags": [ + "Website" + ] + } + }, + "/websites/leech": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxCommonReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxAntiLeechRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get AntiLeech conf", + "tags": [ + "Website" + ] + } + }, + "/websites/leech/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxAntiLeechUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update AntiLeech", + "tags": [ + "Website" + ] + } + }, + "/websites/list": { + "get": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsiteDTO" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List websites", + "tags": [ + "Website" + ] + } + }, + "/websites/log": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteLogReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteLog" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate website log", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id", + "operate" + ], + "formatEN": "[domain][operate] logs", + "formatZH": "[domain][operate] 日志", + "paramKeys": [] + } + } + }, + "/websites/nginx/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteNginxUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website nginx conf", + "tags": [ + "Website Nginx" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "[domain] Nginx conf update", + "formatZH": "[domain] Nginx 配置修改", + "paramKeys": [] + } + } + }, + "/websites/operate": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteOp" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate website", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id", + "operate" + ], + "formatEN": "[operate] website [domain]", + "formatZH": "[operate] 网站 [domain]", + "paramKeys": [] + } + } + }, + "/websites/options": { + "post": { + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsiteOption" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List website names", + "tags": [ + "Website" + ] + } + }, + "/websites/php/version": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsitePHPVersionReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update php version", + "tags": [ + "Website PHP" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteId" + ], + "formatEN": "php version update [domain]", + "formatZH": "php 版本变更 [domain]", + "paramKeys": [] + } + } + }, + "/websites/proxies": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteProxyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/request.WebsiteProxyConfig" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get proxy conf", + "tags": [ + "Website" + ] + } + }, + "/websites/proxies/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxProxyUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update proxy file", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Nginx conf proxy file update [domain]", + "formatZH": "更新反向代理文件 [domain]", + "paramKeys": [] + } + } + }, + "/websites/proxies/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteProxyConfig" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update proxy conf", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update domain [domain] proxy config", + "formatZH": "修改网站 [domain] 反向代理配置 ", + "paramKeys": [] + } + } + }, + "/websites/proxy/clear": { + "post": { + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Clear Website proxy cache", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [], + "formatEN": "Clear nginx proxy cache", + "formatZH": "清理 Openresty 代理缓存", + "paramKeys": [] + } + } + }, + "/websites/proxy/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxProxyCacheUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "update website proxy cache config", + "tags": [ + "Website" + ] + } + }, + "/websites/proxy/config/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxProxyCache" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website proxy cache config" + } + }, + "/websites/realip/config": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteRealIP" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Set Real IP", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Modify the real IP configuration of [domain] website", + "formatZH": "修改 [domain] 网站真实IP配置 ", + "paramKeys": [] + } + } + }, + "/websites/realip/config/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteRealIP" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get Real IP Config", + "tags": [ + "Website" + ] + } + }, + "/websites/redirect": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteProxyReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.NginxRedirectConfig" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get redirect conf", + "tags": [ + "Website" + ] + } + }, + "/websites/redirect/file": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxRedirectUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update redirect file", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Nginx conf redirect file update [domain]", + "formatZH": "更新重定向文件 [domain]", + "paramKeys": [] + } + } + }, + "/websites/redirect/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxRedirectReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update redirect conf", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Update domain [domain] redirect config", + "formatZH": "修改网站 [domain] 重定向配置 ", + "paramKeys": [] + } + } + }, + "/websites/resource/{id}": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "id", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.Resource" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get website resource", + "tags": [ + "Website" + ] + } + }, + "/websites/rewrite": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxRewriteReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.NginxRewriteRes" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Get rewrite conf", + "tags": [ + "Website" + ] + } + }, + "/websites/rewrite/custom": { + "get": { + "consumes": [ + "application/json" + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "type": "string" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "List custom rewrite", + "tags": [ + "Website" + ] + }, + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.CustomRewriteOperate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Operate custom rewrite", + "tags": [ + "Website" + ] + } + }, + "/websites/rewrite/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.NginxRewriteUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update rewrite conf", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "websites", + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "websiteID" + ], + "formatEN": "Nginx conf rewrite update [domain]", + "formatZH": "伪静态配置修改 [domain]", + "paramKeys": [] + } + } + }, + "/websites/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/dto.PageResult" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page websites", + "tags": [ + "Website" + ] + } + }, + "/websites/ssl": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLCreate" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/request.WebsiteSSLCreate" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Create website ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "primaryDomain" + ], + "formatEN": "Create website ssl [primaryDomain]", + "formatZH": "创建网站 ssl [primaryDomain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/:id": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "id", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteSSLDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website ssl by id", + "tags": [ + "Website SSL" + ] + } + }, + "/websites/ssl/del": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteBatchDelReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Delete website ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "ids", + "isList": true, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "ids" + ], + "formatEN": "Delete ssl [domain]", + "formatZH": "删除 ssl [domain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/download": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteResourceReq" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Download SSL file", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "download ssl file [domain]", + "formatZH": "下载证书文件 [domain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/obtain": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLApply" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Apply ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "ID", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "ID" + ], + "formatEN": "apply ssl [domain]", + "formatZH": "申请证书 [domain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/resolve": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteDNSReq" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsiteDNSRes" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Resolve website ssl", + "tags": [ + "Website SSL" + ] + } + }, + "/websites/ssl/search": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLSearch" + } + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "items": { + "$ref": "#/definitions/response.WebsiteSSLDTO" + }, + "type": "array" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Page website ssl", + "tags": [ + "Website SSL" + ] + } + }, + "/websites/ssl/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [ + { + "db": "website_ssls", + "input_column": "id", + "input_value": "id", + "isList": false, + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "bodyKeys": [ + "id" + ], + "formatEN": "Update ssl config [domain]", + "formatZH": "更新证书设置 [domain]", + "paramKeys": [] + } + } + }, + "/websites/ssl/upload": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteSSLUpload" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upload ssl", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "Upload ssl [type]", + "formatZH": "上传 ssl [type]", + "paramKeys": [] + } + } + }, + "/websites/ssl/upload/file": { + "post": { + "consumes": [ + "multipart/form-data" + ], + "parameters": [ + { + "description": "type", + "in": "formData", + "name": "type", + "required": true, + "type": "string" + }, + { + "description": "description", + "in": "formData", + "name": "description", + "type": "string" + }, + { + "description": "sslID", + "in": "formData", + "name": "sslID", + "type": "string" + }, + { + "description": "privateKeyFile", + "in": "formData", + "name": "privateKeyFile", + "required": true, + "type": "file" + }, + { + "description": "certificateFile", + "in": "formData", + "name": "certificateFile", + "required": true, + "type": "file" + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Upload SSL file", + "tags": [ + "Website SSL" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "type" + ], + "formatEN": "Upload ssl file [type]", + "formatZH": "上传 ssl 文件 [type]", + "paramKeys": [] + } + } + }, + "/websites/ssl/website/:websiteId": { + "get": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "path", + "name": "websiteId", + "required": true, + "type": "integer" + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "$ref": "#/definitions/response.WebsiteSSLDTO" + } + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Search website ssl by website id", + "tags": [ + "Website SSL" + ] + } + }, + "/websites/stream/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.StreamUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update Stream Config", + "tags": [ + "Website" + ] + } + }, + "/websites/update": { + "post": { + "consumes": [ + "application/json" + ], + "parameters": [ + { + "description": "request", + "in": "body", + "name": "request", + "required": true, + "schema": { + "$ref": "#/definitions/request.WebsiteUpdate" + } + } + ], + "responses": { + "200": { + "description": "OK" + } + }, + "security": [ + { + "ApiKeyAuth": [] + }, + { + "Timestamp": [] + } + ], + "summary": "Update website", + "tags": [ + "Website" + ], + "x-panel-log": { + "BeforeFunctions": [], + "bodyKeys": [ + "primaryDomain" + ], + "formatEN": "Update website [primaryDomain]", + "formatZH": "更新网站 [primaryDomain]", + "paramKeys": [] + } + } + } + }, + "definitions": { + "dto.AddrRuleOperate": { + "properties": { + "address": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "operation": { + "enum": [ + "add", + "remove" + ], + "type": "string" + }, + "strategy": { + "enum": [ + "accept", + "drop" + ], + "type": "string" + } + }, + "required": [ + "address", + "operation", + "strategy" + ], + "type": "object" + }, + "dto.AddrRuleUpdate": { + "properties": { + "newRule": { + "$ref": "#/definitions/dto.AddrRuleOperate" + }, + "oldRule": { + "$ref": "#/definitions/dto.AddrRuleOperate" + } + }, + "type": "object" + }, + "dto.ApiInterfaceConfig": { + "properties": { + "apiInterfaceStatus": { + "type": "string" + }, + "apiKey": { + "type": "string" + }, + "apiKeyValidityTime": { + "type": "string" + }, + "ipWhiteList": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppConfigVersion": { + "properties": { + "additionalProperties": {}, + "downloadCallBackUrl": { + "type": "string" + }, + "downloadUrl": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppDefine": { + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dto.AppProperty" + }, + "icon": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "readMe": { + "type": "string" + }, + "versions": { + "items": { + "$ref": "#/definitions/dto.AppConfigVersion" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.AppInstallInfo": { + "properties": { + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppLauncher": { + "properties": { + "description": { + "type": "string" + }, + "detail": { + "items": { + "$ref": "#/definitions/dto.InstallDetail" + }, + "type": "array" + }, + "icon": { + "type": "string" + }, + "isInstall": { + "type": "boolean" + }, + "isRecommend": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppList": { + "properties": { + "additionalProperties": { + "$ref": "#/definitions/dto.ExtraProperties" + }, + "apps": { + "items": { + "$ref": "#/definitions/dto.AppDefine" + }, + "type": "array" + }, + "lastModified": { + "type": "integer" + }, + "valid": { + "type": "boolean" + }, + "violations": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.AppProperty": { + "properties": { + "Required": { + "items": { + "type": "string" + }, + "type": "array" + }, + "architectures": { + "items": { + "type": "string" + }, + "type": "array" + }, + "crossVersionUpdate": { + "type": "boolean" + }, + "deprecated": { + "type": "number" + }, + "description": { + "$ref": "#/definitions/dto.Locale" + }, + "document": { + "type": "string" + }, + "github": { + "type": "string" + }, + "gpuSupport": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + }, + "memoryRequired": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "shortDescEn": { + "type": "string" + }, + "shortDescZh": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "version": { + "type": "number" + }, + "website": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppResource": { + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppVersion": { + "properties": { + "detailId": { + "type": "integer" + }, + "dockerCompose": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppstoreConfig": { + "properties": { + "uninstallDeleteBackup": { + "type": "string" + }, + "uninstallDeleteImage": { + "type": "string" + }, + "upgradeBackup": { + "type": "string" + } + }, + "type": "object" + }, + "dto.AppstoreUpdate": { + "properties": { + "scope": { + "enum": [ + "UninstallDeleteImage", + "UpgradeBackup", + "UninstallDeleteBackup" + ], + "type": "string" + }, + "status": { + "enum": [ + "Disable", + "Enable" + ], + "type": "string" + } + }, + "required": [ + "scope", + "status" + ], + "type": "object" + }, + "dto.BackupClientInfo": { + "properties": { + "client_id": { + "type": "string" + }, + "client_secret": { + "type": "string" + }, + "redirect_uri": { + "type": "string" + } + }, + "type": "object" + }, + "dto.BackupOperate": { + "properties": { + "accessKey": { + "type": "string" + }, + "backupPath": { + "type": "string" + }, + "bucket": { + "type": "string" + }, + "credential": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "rememberAuth": { + "type": "boolean" + }, + "type": { + "type": "string" + }, + "vars": { + "type": "string" + } + }, + "required": [ + "type", + "vars" + ], + "type": "object" + }, + "dto.BackupOption": { + "properties": { + "id": { + "type": "integer" + }, + "isPublic": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.BatchDelete": { + "properties": { + "force": { + "type": "boolean" + }, + "names": { + "items": { + "type": "string" + }, + "type": "array" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "names" + ], + "type": "object" + }, + "dto.BatchDeleteReq": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.BatchRuleOperate": { + "properties": { + "rules": { + "items": { + "$ref": "#/definitions/dto.PortRuleOperate" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.BindInfo": { + "properties": { + "bindAddress": { + "type": "string" + }, + "ipv6": { + "enum": [ + "Enable", + "Disable" + ], + "type": "string" + } + }, + "required": [ + "bindAddress", + "ipv6" + ], + "type": "object" + }, + "dto.BindUser": { + "properties": { + "database": { + "type": "string" + }, + "db": { + "type": "string" + }, + "password": { + "type": "string" + }, + "permission": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "database", + "db", + "password", + "permission", + "username" + ], + "type": "object" + }, + "dto.CaptchaResponse": { + "properties": { + "captchaID": { + "type": "string" + }, + "imagePath": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ChangeDBInfo": { + "properties": { + "database": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "postgresql", + "redis", + "mysql-cluster", + "postgresql-cluster", + "redis-cluster" + ], + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "database", + "from", + "type", + "value" + ], + "type": "object" + }, + "dto.ChangeGroup": { + "properties": { + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "groupID", + "id" + ], + "type": "object" + }, + "dto.ChangeHostGroup": { + "properties": { + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "groupID", + "id" + ], + "type": "object" + }, + "dto.ChangePasswd": { + "properties": { + "passwd": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ChangeQuicks": { + "properties": { + "quicks": { + "items": { + "$ref": "#/definitions/dto.QuickJump" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.ChangeRedisPass": { + "properties": { + "database": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + }, + "dto.ClamBaseInfo": { + "properties": { + "freshIsActive": { + "type": "boolean" + }, + "freshIsExist": { + "type": "boolean" + }, + "freshVersion": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ClamCreate": { + "properties": { + "alertCount": { + "type": "integer" + }, + "alertMethod": { + "type": "string" + }, + "alertTitle": { + "type": "string" + }, + "description": { + "type": "string" + }, + "infectedDir": { + "type": "string" + }, + "infectedStrategy": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "spec": { + "type": "string" + }, + "status": { + "type": "string" + }, + "timeout": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ClamDelete": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "removeInfected": { + "type": "boolean" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.ClamFileReq": { + "properties": { + "name": { + "type": "string" + }, + "tail": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "dto.ClamLogSearch": { + "properties": { + "clamID": { + "type": "integer" + }, + "endTime": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "startTime": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.ClamUpdate": { + "properties": { + "alertCount": { + "type": "integer" + }, + "alertMethod": { + "type": "string" + }, + "alertTitle": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "infectedDir": { + "type": "string" + }, + "infectedStrategy": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "spec": { + "type": "string" + }, + "timeout": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ClamUpdateStatus": { + "properties": { + "id": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Clean": { + "properties": { + "name": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "treeType": { + "type": "string" + } + }, + "type": "object" + }, + "dto.CleanData": { + "properties": { + "backupClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "containerClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "downloadClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "systemClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "systemLogClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "uploadClean": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.CleanLog": { + "properties": { + "logType": { + "enum": [ + "login", + "operation" + ], + "type": "string" + } + }, + "required": [ + "logType" + ], + "type": "object" + }, + "dto.CleanTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/dto.CleanTree" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "isCheck": { + "type": "boolean" + }, + "isRecommend": { + "type": "boolean" + }, + "label": { + "type": "string" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.CommandInfo": { + "properties": { + "command": { + "type": "string" + }, + "groupBelong": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.CommandOperate": { + "properties": { + "command": { + "type": "string" + }, + "groupBelong": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "command", + "name" + ], + "type": "object" + }, + "dto.CommandTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/dto.CommandTree" + }, + "type": "array" + }, + "label": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "dto.CommonBackup": { + "properties": { + "description": { + "type": "string" + }, + "detailName": { + "type": "string" + }, + "fileName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "type": { + "enum": [ + "app", + "mysql", + "mariadb", + "redis", + "website", + "postgresql", + "mysql-cluster", + "postgresql-cluster", + "redis-cluster" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.CommonDescription": { + "properties": { + "description": { + "type": "string" + }, + "detailType": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isPinned": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "dto.CommonRecover": { + "properties": { + "backupRecordID": { + "type": "integer" + }, + "detailName": { + "type": "string" + }, + "downloadAccountID": { + "type": "integer" + }, + "file": { + "type": "string" + }, + "name": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "timeout": { + "type": "integer" + }, + "type": { + "enum": [ + "app", + "mysql", + "mariadb", + "redis", + "website", + "postgresql", + "mysql-cluster", + "postgresql-cluster", + "redis-cluster" + ], + "type": "string" + } + }, + "required": [ + "downloadAccountID", + "type" + ], + "type": "object" + }, + "dto.ComposeCreate": { + "properties": { + "env": { + "type": "string" + }, + "file": { + "type": "string" + }, + "from": { + "enum": [ + "edit", + "path", + "template" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "template": { + "type": "integer" + } + }, + "required": [ + "from" + ], + "type": "object" + }, + "dto.ComposeLogClean": { + "properties": { + "detailPath": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "name", + "path" + ], + "type": "object" + }, + "dto.ComposeOperation": { + "properties": { + "force": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "operation": { + "enum": [ + "up", + "start", + "restart", + "stop", + "down", + "delete" + ], + "type": "string" + }, + "path": { + "type": "string" + }, + "withFile": { + "type": "boolean" + } + }, + "required": [ + "name", + "operation" + ], + "type": "object" + }, + "dto.ComposeTemplateBatch": { + "properties": { + "templates": { + "items": { + "$ref": "#/definitions/dto.ComposeTemplateCreate" + }, + "type": "array" + } + }, + "required": [ + "templates" + ], + "type": "object" + }, + "dto.ComposeTemplateCreate": { + "properties": { + "content": { + "type": "string" + }, + "description": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "dto.ComposeTemplateInfo": { + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ComposeTemplateUpdate": { + "properties": { + "content": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ComposeUpdate": { + "properties": { + "content": { + "type": "string" + }, + "detailPath": { + "type": "string" + }, + "env": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "content", + "name", + "path" + ], + "type": "object" + }, + "dto.ContainerCommit": { + "properties": { + "author": { + "type": "string" + }, + "comment": { + "type": "string" + }, + "containerID": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "newImageName": { + "type": "string" + }, + "pause": { + "type": "boolean" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "containerID" + ], + "type": "object" + }, + "dto.ContainerCreateByCommand": { + "properties": { + "command": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ContainerItemStats": { + "properties": { + "buildCacheReclaimable": { + "type": "integer" + }, + "buildCacheUsage": { + "type": "integer" + }, + "containerReclaimable": { + "type": "integer" + }, + "containerUsage": { + "type": "integer" + }, + "imageReclaimable": { + "type": "integer" + }, + "imageUsage": { + "type": "integer" + }, + "sizeRootFs": { + "type": "integer" + }, + "sizeRw": { + "type": "integer" + }, + "volumeReclaimable": { + "type": "integer" + }, + "volumeUsage": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ContainerListStats": { + "properties": { + "containerID": { + "type": "string" + }, + "cpuPercent": { + "type": "number" + }, + "cpuTotalUsage": { + "type": "integer" + }, + "memoryCache": { + "type": "integer" + }, + "memoryLimit": { + "type": "integer" + }, + "memoryPercent": { + "type": "number" + }, + "memoryUsage": { + "type": "integer" + }, + "percpuUsage": { + "type": "integer" + }, + "systemUsage": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ContainerNetwork": { + "properties": { + "ipv4": { + "type": "string" + }, + "ipv6": { + "type": "string" + }, + "macAddr": { + "type": "string" + }, + "network": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ContainerOperate": { + "properties": { + "autoRemove": { + "type": "boolean" + }, + "cmd": { + "items": { + "type": "string" + }, + "type": "array" + }, + "cpuShares": { + "type": "integer" + }, + "dns": { + "items": { + "type": "string" + }, + "type": "array" + }, + "domainName": { + "type": "string" + }, + "entrypoint": { + "items": { + "type": "string" + }, + "type": "array" + }, + "env": { + "items": { + "type": "string" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/dto.PortHelper" + }, + "type": "array" + }, + "forcePull": { + "type": "boolean" + }, + "hostname": { + "type": "string" + }, + "image": { + "type": "string" + }, + "labels": { + "items": { + "type": "string" + }, + "type": "array" + }, + "memory": { + "type": "number" + }, + "name": { + "type": "string" + }, + "nanoCPUs": { + "type": "number" + }, + "networks": { + "items": { + "$ref": "#/definitions/dto.ContainerNetwork" + }, + "type": "array" + }, + "openStdin": { + "type": "boolean" + }, + "privileged": { + "type": "boolean" + }, + "publishAllPorts": { + "type": "boolean" + }, + "restartPolicy": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "tty": { + "type": "boolean" + }, + "user": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/dto.VolumeHelper" + }, + "type": "array" + }, + "workingDir": { + "type": "string" + } + }, + "required": [ + "image", + "name" + ], + "type": "object" + }, + "dto.ContainerOperation": { + "properties": { + "names": { + "items": { + "type": "string" + }, + "type": "array" + }, + "operation": { + "enum": [ + "up", + "start", + "stop", + "restart", + "kill", + "pause", + "unpause", + "remove" + ], + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "names", + "operation" + ], + "type": "object" + }, + "dto.ContainerOptions": { + "properties": { + "name": { + "type": "string" + }, + "state": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ContainerPrune": { + "properties": { + "pruneType": { + "enum": [ + "container", + "image", + "volume", + "network", + "buildcache" + ], + "type": "string" + }, + "taskID": { + "type": "string" + }, + "withTagAll": { + "type": "boolean" + } + }, + "required": [ + "pruneType" + ], + "type": "object" + }, + "dto.ContainerRename": { + "properties": { + "name": { + "type": "string" + }, + "newName": { + "type": "string" + } + }, + "required": [ + "name", + "newName" + ], + "type": "object" + }, + "dto.ContainerStats": { + "properties": { + "cache": { + "type": "number" + }, + "cpuPercent": { + "type": "number" + }, + "ioRead": { + "type": "number" + }, + "ioWrite": { + "type": "number" + }, + "memory": { + "type": "number" + }, + "networkRX": { + "type": "number" + }, + "networkTX": { + "type": "number" + }, + "shotTime": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ContainerStatus": { + "properties": { + "composeCount": { + "type": "integer" + }, + "composeTemplateCount": { + "type": "integer" + }, + "containerCount": { + "type": "integer" + }, + "created": { + "type": "integer" + }, + "dead": { + "type": "integer" + }, + "exited": { + "type": "integer" + }, + "imageCount": { + "type": "integer" + }, + "networkCount": { + "type": "integer" + }, + "paused": { + "type": "integer" + }, + "removing": { + "type": "integer" + }, + "repoCount": { + "type": "integer" + }, + "restarting": { + "type": "integer" + }, + "running": { + "type": "integer" + }, + "volumeCount": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.ContainerUpgrade": { + "properties": { + "forcePull": { + "type": "boolean" + }, + "image": { + "type": "string" + }, + "names": { + "items": { + "type": "string" + }, + "type": "array" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "image", + "names" + ], + "type": "object" + }, + "dto.CronjobBatchDelete": { + "properties": { + "cleanData": { + "type": "boolean" + }, + "cleanRemoteData": { + "type": "boolean" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.CronjobClean": { + "properties": { + "cleanData": { + "type": "boolean" + }, + "cleanRemoteData": { + "type": "boolean" + }, + "cronjobID": { + "type": "integer" + }, + "isDelete": { + "type": "boolean" + } + }, + "required": [ + "cronjobID" + ], + "type": "object" + }, + "dto.CronjobImport": { + "properties": { + "cronjobs": { + "items": { + "$ref": "#/definitions/dto.CronjobTrans" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.CronjobOperate": { + "properties": { + "alertCount": { + "type": "integer" + }, + "alertMethod": { + "type": "string" + }, + "alertTitle": { + "type": "string" + }, + "appID": { + "type": "string" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "dbName": { + "type": "string" + }, + "dbType": { + "type": "string" + }, + "downloadAccountID": { + "type": "integer" + }, + "exclusionRules": { + "type": "string" + }, + "executor": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "ignoreErr": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "retainCopies": { + "minimum": 1, + "type": "integer" + }, + "retryTimes": { + "minimum": 0, + "type": "integer" + }, + "scopes": { + "items": { + "type": "string" + }, + "type": "array" + }, + "script": { + "type": "string" + }, + "scriptID": { + "type": "integer" + }, + "scriptMode": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "snapshotRule": { + "$ref": "#/definitions/dto.SnapshotRule" + }, + "sourceAccountIDs": { + "type": "string" + }, + "sourceDir": { + "type": "string" + }, + "spec": { + "type": "string" + }, + "specCustom": { + "type": "boolean" + }, + "timeout": { + "minimum": 1, + "type": "integer" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "user": { + "type": "string" + }, + "website": { + "type": "string" + } + }, + "required": [ + "name", + "spec", + "type" + ], + "type": "object" + }, + "dto.CronjobSpec": { + "properties": { + "spec": { + "type": "string" + } + }, + "required": [ + "spec" + ], + "type": "object" + }, + "dto.CronjobTrans": { + "properties": { + "alertCount": { + "type": "integer" + }, + "alertMethod": { + "type": "string" + }, + "alertTitle": { + "type": "string" + }, + "apps": { + "items": { + "$ref": "#/definitions/dto.TransHelper" + }, + "type": "array" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "dbName": { + "items": { + "$ref": "#/definitions/dto.TransHelper" + }, + "type": "array" + }, + "dbType": { + "type": "string" + }, + "downloadAccount": { + "type": "string" + }, + "exclusionRules": { + "type": "string" + }, + "executor": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "ignoreErr": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "retainCopies": { + "type": "integer" + }, + "retryTimes": { + "type": "integer" + }, + "script": { + "type": "string" + }, + "scriptMode": { + "type": "string" + }, + "scriptName": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "snapshotRule": { + "$ref": "#/definitions/dto.SnapshotTransHelper" + }, + "sourceAccounts": { + "items": { + "type": "string" + }, + "type": "array" + }, + "sourceDir": { + "type": "string" + }, + "spec": { + "type": "string" + }, + "specCustom": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "url": { + "type": "string" + }, + "user": { + "type": "string" + }, + "websites": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.CronjobUpdateStatus": { + "properties": { + "id": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "required": [ + "id", + "status" + ], + "type": "object" + }, + "dto.DBBaseInfo": { + "properties": { + "containerName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.DBConfUpdateByFile": { + "properties": { + "database": { + "type": "string" + }, + "file": { + "type": "string" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "postgresql", + "redis", + "mysql-cluster", + "postgresql-cluster", + "redis-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "type" + ], + "type": "object" + }, + "dto.DaemonJsonConf": { + "properties": { + "cgroupDriver": { + "type": "string" + }, + "experimental": { + "type": "boolean" + }, + "fixedCidrV6": { + "type": "string" + }, + "insecureRegistries": { + "items": { + "type": "string" + }, + "type": "array" + }, + "ip6Tables": { + "type": "boolean" + }, + "iptables": { + "type": "boolean" + }, + "ipv6": { + "type": "boolean" + }, + "isSwarm": { + "type": "boolean" + }, + "liveRestore": { + "type": "boolean" + }, + "logMaxFile": { + "type": "string" + }, + "logMaxSize": { + "type": "string" + }, + "registryMirrors": { + "items": { + "type": "string" + }, + "type": "array" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DaemonJsonUpdateByFile": { + "properties": { + "file": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DashboardBase": { + "properties": { + "appInstalledNumber": { + "type": "integer" + }, + "cpuCores": { + "type": "integer" + }, + "cpuLogicalCores": { + "type": "integer" + }, + "cpuMhz": { + "type": "number" + }, + "cpuModelName": { + "type": "string" + }, + "cronjobNumber": { + "type": "integer" + }, + "currentInfo": { + "$ref": "#/definitions/dto.DashboardCurrent" + }, + "databaseNumber": { + "type": "integer" + }, + "hostname": { + "type": "string" + }, + "ipV4Addr": { + "type": "string" + }, + "kernelArch": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "os": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "platformFamily": { + "type": "string" + }, + "platformVersion": { + "type": "string" + }, + "prettyDistro": { + "type": "string" + }, + "quickJump": { + "items": { + "$ref": "#/definitions/dto.QuickJump" + }, + "type": "array" + }, + "systemProxy": { + "type": "string" + }, + "virtualizationSystem": { + "type": "string" + }, + "websiteNumber": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.DashboardCurrent": { + "properties": { + "cpuDetailedPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "cpuPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "cpuTotal": { + "type": "integer" + }, + "cpuUsed": { + "type": "number" + }, + "cpuUsedPercent": { + "type": "number" + }, + "diskData": { + "items": { + "$ref": "#/definitions/dto.DiskInfo" + }, + "type": "array" + }, + "gpuData": { + "items": { + "$ref": "#/definitions/dto.GPUInfo" + }, + "type": "array" + }, + "ioCount": { + "type": "integer" + }, + "ioReadBytes": { + "type": "integer" + }, + "ioReadTime": { + "type": "integer" + }, + "ioWriteBytes": { + "type": "integer" + }, + "ioWriteTime": { + "type": "integer" + }, + "load1": { + "type": "number" + }, + "load15": { + "type": "number" + }, + "load5": { + "type": "number" + }, + "loadUsagePercent": { + "type": "number" + }, + "memoryAvailable": { + "type": "integer" + }, + "memoryCache": { + "type": "integer" + }, + "memoryFree": { + "type": "integer" + }, + "memoryShard": { + "type": "integer" + }, + "memoryTotal": { + "type": "integer" + }, + "memoryUsed": { + "type": "integer" + }, + "memoryUsedPercent": { + "type": "number" + }, + "netBytesRecv": { + "type": "integer" + }, + "netBytesSent": { + "type": "integer" + }, + "procs": { + "type": "integer" + }, + "shotTime": { + "type": "string" + }, + "swapMemoryAvailable": { + "type": "integer" + }, + "swapMemoryTotal": { + "type": "integer" + }, + "swapMemoryUsed": { + "type": "integer" + }, + "swapMemoryUsedPercent": { + "type": "number" + }, + "timeSinceUptime": { + "type": "string" + }, + "topCPUItems": { + "items": { + "$ref": "#/definitions/dto.Process" + }, + "type": "array" + }, + "topMemItems": { + "items": { + "$ref": "#/definitions/dto.Process" + }, + "type": "array" + }, + "uptime": { + "type": "integer" + }, + "xpuData": { + "items": { + "$ref": "#/definitions/dto.XPUInfo" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.DataTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "id": { + "type": "string" + }, + "isCheck": { + "type": "boolean" + }, + "isDisable": { + "type": "boolean" + }, + "isLocal": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "label": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "relationItemID": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.DatabaseCreate": { + "properties": { + "address": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "description": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "initialDB": { + "type": "string" + }, + "name": { + "maxLength": 256, + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "rootCert": { + "type": "string" + }, + "skipVerify": { + "type": "boolean" + }, + "ssl": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "username": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "from", + "name", + "type", + "username", + "version" + ], + "type": "object" + }, + "dto.DatabaseDelete": { + "properties": { + "deleteBackup": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "dto.DatabaseInfo": { + "properties": { + "address": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "from": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "initialDB": { + "type": "string" + }, + "name": { + "maxLength": 256, + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "rootCert": { + "type": "string" + }, + "skipVerify": { + "type": "boolean" + }, + "ssl": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "username": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DatabaseItem": { + "properties": { + "database": { + "type": "string" + }, + "from": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DatabaseOption": { + "properties": { + "address": { + "type": "string" + }, + "database": { + "type": "string" + }, + "from": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DatabaseSearch": { + "properties": { + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.DatabaseUpdate": { + "properties": { + "address": { + "type": "string" + }, + "clientCert": { + "type": "string" + }, + "clientKey": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "initialDB": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "rootCert": { + "type": "string" + }, + "skipVerify": { + "type": "boolean" + }, + "ssl": { + "type": "boolean" + }, + "timeout": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "username": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "required": [ + "type", + "username", + "version" + ], + "type": "object" + }, + "dto.DeviceBaseInfo": { + "properties": { + "dns": { + "items": { + "type": "string" + }, + "type": "array" + }, + "hostname": { + "type": "string" + }, + "hosts": { + "items": { + "$ref": "#/definitions/dto.HostHelper" + }, + "type": "array" + }, + "localTime": { + "type": "string" + }, + "maxSize": { + "type": "integer" + }, + "ntp": { + "type": "string" + }, + "swapDetails": { + "items": { + "$ref": "#/definitions/dto.SwapHelper" + }, + "type": "array" + }, + "swapMemoryAvailable": { + "type": "integer" + }, + "swapMemoryTotal": { + "type": "integer" + }, + "swapMemoryUsed": { + "type": "integer" + }, + "timeZone": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "dto.DiskInfo": { + "properties": { + "device": { + "type": "string" + }, + "free": { + "type": "integer" + }, + "inodesFree": { + "type": "integer" + }, + "inodesTotal": { + "type": "integer" + }, + "inodesUsed": { + "type": "integer" + }, + "inodesUsedPercent": { + "type": "number" + }, + "path": { + "type": "string" + }, + "total": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "used": { + "type": "integer" + }, + "usedPercent": { + "type": "number" + } + }, + "type": "object" + }, + "dto.DockerOperation": { + "properties": { + "operation": { + "enum": [ + "start", + "restart", + "stop" + ], + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + "dto.DownloadRecord": { + "properties": { + "downloadAccountID": { + "type": "integer" + }, + "fileDir": { + "type": "string" + }, + "fileName": { + "type": "string" + } + }, + "required": [ + "downloadAccountID", + "fileDir", + "fileName" + ], + "type": "object" + }, + "dto.ExtraProperties": { + "properties": { + "tags": { + "items": { + "$ref": "#/definitions/dto.Tag" + }, + "type": "array" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Fail2BanBaseInfo": { + "properties": { + "banAction": { + "type": "string" + }, + "banTime": { + "type": "string" + }, + "findTime": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isEnable": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "logPath": { + "type": "string" + }, + "maxRetry": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Fail2BanSearch": { + "properties": { + "status": { + "enum": [ + "banned", + "ignore" + ], + "type": "string" + } + }, + "required": [ + "status" + ], + "type": "object" + }, + "dto.Fail2BanSet": { + "properties": { + "ips": { + "items": { + "type": "string" + }, + "type": "array" + }, + "operate": { + "enum": [ + "banned", + "ignore" + ], + "type": "string" + } + }, + "required": [ + "operate" + ], + "type": "object" + }, + "dto.Fail2BanUpdate": { + "properties": { + "key": { + "enum": [ + "port", + "bantime", + "findtime", + "maxretry", + "banaction", + "logpath", + "port" + ], + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "dto.FirewallBaseInfo": { + "properties": { + "isActive": { + "type": "boolean" + }, + "isBind": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "isInit": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "pingStatus": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.FirewallOperation": { + "properties": { + "operation": { + "enum": [ + "start", + "stop", + "restart", + "disableBanPing", + "enableBanPing" + ], + "type": "string" + }, + "withDockerRestart": { + "type": "boolean" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + "dto.ForBuckets": { + "properties": { + "accessKey": { + "type": "string" + }, + "credential": { + "type": "string" + }, + "type": { + "type": "string" + }, + "vars": { + "type": "string" + } + }, + "required": [ + "credential", + "type", + "vars" + ], + "type": "object" + }, + "dto.ForceDelete": { + "properties": { + "forceDelete": { + "type": "boolean" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.ForwardRuleOperate": { + "properties": { + "forceDelete": { + "type": "boolean" + }, + "rules": { + "items": { + "properties": { + "interface": { + "type": "string" + }, + "num": { + "type": "string" + }, + "operation": { + "enum": [ + "add", + "remove" + ], + "type": "string" + }, + "port": { + "type": "string" + }, + "protocol": { + "enum": [ + "tcp", + "udp", + "tcp/udp" + ], + "type": "string" + }, + "targetIP": { + "type": "string" + }, + "targetPort": { + "type": "string" + } + }, + "required": [ + "operation", + "port", + "protocol", + "targetPort" + ], + "type": "object" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.FtpBaseInfo": { + "properties": { + "isActive": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.FtpCreate": { + "properties": { + "description": { + "type": "string" + }, + "password": { + "type": "string" + }, + "path": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "password", + "path", + "user" + ], + "type": "object" + }, + "dto.FtpLogSearch": { + "properties": { + "operation": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "user": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.FtpUpdate": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "password": { + "type": "string" + }, + "path": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "password", + "path" + ], + "type": "object" + }, + "dto.GPUInfo": { + "properties": { + "fanSpeed": { + "type": "string" + }, + "gpuUtil": { + "type": "string" + }, + "index": { + "type": "integer" + }, + "maxPowerLimit": { + "type": "string" + }, + "memTotal": { + "type": "string" + }, + "memUsed": { + "type": "string" + }, + "memoryUsage": { + "type": "string" + }, + "performanceState": { + "type": "string" + }, + "powerDraw": { + "type": "string" + }, + "powerUsage": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "temperature": { + "type": "string" + } + }, + "type": "object" + }, + "dto.GPUProcess": { + "properties": { + "pid": { + "type": "string" + }, + "processName": { + "type": "string" + }, + "type": { + "type": "string" + }, + "usedMemory": { + "type": "string" + } + }, + "type": "object" + }, + "dto.GroupCreate": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "dto.GroupSearch": { + "properties": { + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.GroupUpdate": { + "properties": { + "id": { + "type": "integer" + }, + "isDefault": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.HostConnTest": { + "properties": { + "addr": { + "type": "string" + }, + "authMode": { + "enum": [ + "password", + "key" + ], + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "addr", + "port", + "user" + ], + "type": "object" + }, + "dto.HostHelper": { + "properties": { + "host": { + "type": "string" + }, + "ip": { + "type": "string" + } + }, + "type": "object" + }, + "dto.HostInfo": { + "properties": { + "addr": { + "type": "string" + }, + "authMode": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "groupBelong": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "rememberPassword": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "dto.HostOperate": { + "properties": { + "addr": { + "type": "string" + }, + "authMode": { + "enum": [ + "password", + "key" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "groupID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "rememberPassword": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "required": [ + "addr", + "port", + "user" + ], + "type": "object" + }, + "dto.HostTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/dto.TreeChild" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "label": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ImageBuild": { + "properties": { + "args": { + "items": { + "type": "string" + }, + "type": "array" + }, + "dockerfile": { + "type": "string" + }, + "from": { + "type": "string" + }, + "name": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "dockerfile", + "from", + "name" + ], + "type": "object" + }, + "dto.ImageInfo": { + "properties": { + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isPinned": { + "type": "boolean" + }, + "isUsed": { + "type": "boolean" + }, + "size": { + "type": "integer" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.ImageLoad": { + "properties": { + "path": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "dto.ImagePull": { + "properties": { + "imageName": { + "items": { + "type": "string" + }, + "type": "array" + }, + "repoID": { + "type": "integer" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "imageName" + ], + "type": "object" + }, + "dto.ImagePush": { + "properties": { + "name": { + "type": "string" + }, + "repoID": { + "type": "integer" + }, + "tagName": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "name", + "repoID", + "tagName" + ], + "type": "object" + }, + "dto.ImageRepoDelete": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.ImageRepoOption": { + "properties": { + "downloadUrl": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ImageRepoUpdate": { + "properties": { + "auth": { + "type": "boolean" + }, + "downloadUrl": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "password": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ImageSave": { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "tagName": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "name", + "path", + "tagName" + ], + "type": "object" + }, + "dto.ImageTag": { + "properties": { + "sourceID": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "sourceID", + "tags" + ], + "type": "object" + }, + "dto.InspectReq": { + "properties": { + "detail": { + "type": "string" + }, + "id": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "dto.InstallDetail": { + "properties": { + "detailID": { + "type": "integer" + }, + "httpPort": { + "type": "integer" + }, + "httpsPort": { + "type": "integer" + }, + "installID": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "status": { + "type": "string" + }, + "version": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "type": "object" + }, + "dto.IptablesBatchOperate": { + "properties": { + "rules": { + "items": { + "$ref": "#/definitions/dto.IptablesRuleOp" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.IptablesOp": { + "properties": { + "name": { + "enum": [ + "1PANEL_INPUT", + "1PANEL_OUTPUT", + "1PANEL_BASIC" + ], + "type": "string" + }, + "operate": { + "enum": [ + "init-base", + "init-forward", + "init-advance", + "bind-base", + "unbind-base", + "bind", + "unbind" + ], + "type": "string" + } + }, + "required": [ + "name", + "operate" + ], + "type": "object" + }, + "dto.IptablesRuleOp": { + "properties": { + "chain": { + "enum": [ + "1PANEL_BASIC", + "1PANEL_BASIC_BEFORE", + "1PANEL_INPUT", + "1PANEL_OUTPUT" + ], + "type": "string" + }, + "description": { + "type": "string" + }, + "dstIP": { + "type": "string" + }, + "dstPort": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "operation": { + "enum": [ + "add", + "remove" + ], + "type": "string" + }, + "protocol": { + "type": "string" + }, + "srcIP": { + "type": "string" + }, + "srcPort": { + "type": "integer" + }, + "strategy": { + "enum": [ + "accept", + "drop", + "reject" + ], + "type": "string" + } + }, + "required": [ + "chain", + "operation", + "strategy" + ], + "type": "object" + }, + "dto.LauncherOption": { + "properties": { + "isShow": { + "type": "boolean" + }, + "key": { + "type": "string" + } + }, + "type": "object" + }, + "dto.LoadRedisStatus": { + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "type" + ], + "type": "object" + }, + "dto.Locale": { + "properties": { + "en": { + "type": "string" + }, + "es-es": { + "type": "string" + }, + "ja": { + "type": "string" + }, + "ko": { + "type": "string" + }, + "ms": { + "type": "string" + }, + "pt-br": { + "type": "string" + }, + "ru": { + "type": "string" + }, + "tr": { + "type": "string" + }, + "zh": { + "type": "string" + }, + "zh-hant": { + "type": "string" + } + }, + "type": "object" + }, + "dto.LogOption": { + "properties": { + "logMaxFile": { + "type": "string" + }, + "logMaxSize": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Login": { + "properties": { + "captcha": { + "type": "string" + }, + "captchaID": { + "type": "string" + }, + "language": { + "enum": [ + "zh", + "en", + "zh-Hant", + "ko", + "ja", + "ru", + "ms", + "pt-BR", + "tr", + "es-ES" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "language", + "name", + "password" + ], + "type": "object" + }, + "dto.MFALogin": { + "properties": { + "code": { + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + } + }, + "required": [ + "code", + "name", + "password" + ], + "type": "object" + }, + "dto.MfaCredential": { + "properties": { + "code": { + "type": "string" + }, + "interval": { + "type": "string" + }, + "secret": { + "type": "string" + } + }, + "required": [ + "code", + "interval", + "secret" + ], + "type": "object" + }, + "dto.MonitorData": { + "properties": { + "date": { + "items": { + "type": "string" + }, + "type": "array" + }, + "param": { + "type": "string" + }, + "value": { + "items": {}, + "type": "array" + } + }, + "type": "object" + }, + "dto.MonitorGPUData": { + "properties": { + "date": { + "items": { + "type": "string" + }, + "type": "array" + }, + "gpuProcesses": { + "items": { + "items": { + "$ref": "#/definitions/dto.GPUProcess" + }, + "type": "array" + }, + "type": "array" + }, + "gpuValue": { + "items": { + "type": "number" + }, + "type": "array" + }, + "memoryPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "memoryTotal": { + "items": { + "type": "number" + }, + "type": "array" + }, + "memoryUsed": { + "items": { + "type": "number" + }, + "type": "array" + }, + "powerPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "powerTotal": { + "items": { + "type": "number" + }, + "type": "array" + }, + "powerUsed": { + "items": { + "type": "number" + }, + "type": "array" + }, + "processCount": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "speedValue": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "temperatureValue": { + "items": { + "type": "number" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.MonitorGPUSearch": { + "properties": { + "endTime": { + "type": "string" + }, + "productName": { + "type": "string" + }, + "startTime": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MonitorSearch": { + "properties": { + "endTime": { + "type": "string" + }, + "io": { + "type": "string" + }, + "network": { + "type": "string" + }, + "param": { + "enum": [ + "all", + "cpu", + "memory", + "load", + "io", + "network" + ], + "type": "string" + }, + "startTime": { + "type": "string" + } + }, + "required": [ + "param" + ], + "type": "object" + }, + "dto.MonitorSetting": { + "properties": { + "defaultIO": { + "type": "string" + }, + "defaultNetwork": { + "type": "string" + }, + "monitorInterval": { + "type": "string" + }, + "monitorStatus": { + "type": "string" + }, + "monitorStoreDays": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MonitorSettingUpdate": { + "properties": { + "key": { + "enum": [ + "MonitorStatus", + "MonitorStoreDays", + "MonitorInterval", + "DefaultNetwork", + "DefaultIO" + ], + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "dto.MysqlDBCreate": { + "properties": { + "collation": { + "type": "string" + }, + "database": { + "type": "string" + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "permission": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "required": [ + "database", + "format", + "from", + "name", + "password", + "permission", + "username" + ], + "type": "object" + }, + "dto.MysqlDBDelete": { + "properties": { + "database": { + "type": "string" + }, + "deleteBackup": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "mysql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "id", + "type" + ], + "type": "object" + }, + "dto.MysqlDBDeleteCheck": { + "properties": { + "database": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "mysql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "id", + "type" + ], + "type": "object" + }, + "dto.MysqlDBSearch": { + "properties": { + "database": { + "type": "string" + }, + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "database", + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.MysqlFormatCollationOption": { + "properties": { + "collations": { + "items": { + "type": "string" + }, + "type": "array" + }, + "format": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MysqlLoadDB": { + "properties": { + "database": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "mysql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "from", + "type" + ], + "type": "object" + }, + "dto.MysqlStatus": { + "properties": { + "Aborted_clients": { + "type": "string" + }, + "Aborted_connects": { + "type": "string" + }, + "Bytes_received": { + "type": "string" + }, + "Bytes_sent": { + "type": "string" + }, + "Com_commit": { + "type": "string" + }, + "Com_rollback": { + "type": "string" + }, + "Connections": { + "type": "string" + }, + "Created_tmp_disk_tables": { + "type": "string" + }, + "Created_tmp_tables": { + "type": "string" + }, + "File": { + "type": "string" + }, + "Innodb_buffer_pool_pages_dirty": { + "type": "string" + }, + "Innodb_buffer_pool_read_requests": { + "type": "string" + }, + "Innodb_buffer_pool_reads": { + "type": "string" + }, + "Key_read_requests": { + "type": "string" + }, + "Key_reads": { + "type": "string" + }, + "Key_write_requests": { + "type": "string" + }, + "Key_writes": { + "type": "string" + }, + "Max_used_connections": { + "type": "string" + }, + "Open_tables": { + "type": "string" + }, + "Opened_files": { + "type": "string" + }, + "Opened_tables": { + "type": "string" + }, + "Position": { + "type": "string" + }, + "Qcache_hits": { + "type": "string" + }, + "Qcache_inserts": { + "type": "string" + }, + "Questions": { + "type": "string" + }, + "Run": { + "type": "string" + }, + "Select_full_join": { + "type": "string" + }, + "Select_range_check": { + "type": "string" + }, + "Sort_merge_passes": { + "type": "string" + }, + "Table_locks_waited": { + "type": "string" + }, + "Threads_cached": { + "type": "string" + }, + "Threads_connected": { + "type": "string" + }, + "Threads_created": { + "type": "string" + }, + "Threads_running": { + "type": "string" + }, + "Uptime": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MysqlVariables": { + "properties": { + "binlog_cache_size": { + "type": "string" + }, + "innodb_buffer_pool_size": { + "type": "string" + }, + "innodb_log_buffer_size": { + "type": "string" + }, + "join_buffer_size": { + "type": "string" + }, + "key_buffer_size": { + "type": "string" + }, + "long_query_time": { + "type": "string" + }, + "max_connections": { + "type": "string" + }, + "max_heap_table_size": { + "type": "string" + }, + "query_cache_size": { + "type": "string" + }, + "query_cache_type": { + "type": "string" + }, + "read_buffer_size": { + "type": "string" + }, + "read_rnd_buffer_size": { + "type": "string" + }, + "slow_query_log": { + "type": "string" + }, + "sort_buffer_size": { + "type": "string" + }, + "table_open_cache": { + "type": "string" + }, + "thread_cache_size": { + "type": "string" + }, + "thread_stack": { + "type": "string" + }, + "tmp_table_size": { + "type": "string" + } + }, + "type": "object" + }, + "dto.MysqlVariablesUpdate": { + "properties": { + "database": { + "type": "string" + }, + "type": { + "enum": [ + "mysql", + "mariadb", + "mysql-cluster" + ], + "type": "string" + }, + "variables": { + "items": { + "$ref": "#/definitions/dto.MysqlVariablesUpdateHelper" + }, + "type": "array" + } + }, + "required": [ + "database", + "type" + ], + "type": "object" + }, + "dto.MysqlVariablesUpdateHelper": { + "properties": { + "param": { + "type": "string" + }, + "value": {} + }, + "type": "object" + }, + "dto.NetworkCreate": { + "properties": { + "auxAddress": { + "items": { + "$ref": "#/definitions/dto.SettingUpdate" + }, + "type": "array" + }, + "auxAddressV6": { + "items": { + "$ref": "#/definitions/dto.SettingUpdate" + }, + "type": "array" + }, + "driver": { + "type": "string" + }, + "gateway": { + "type": "string" + }, + "gatewayV6": { + "type": "string" + }, + "ipRange": { + "type": "string" + }, + "ipRangeV6": { + "type": "string" + }, + "ipv4": { + "type": "boolean" + }, + "ipv6": { + "type": "boolean" + }, + "labels": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "options": { + "items": { + "type": "string" + }, + "type": "array" + }, + "subnet": { + "type": "string" + }, + "subnetV6": { + "type": "string" + } + }, + "required": [ + "driver", + "name" + ], + "type": "object" + }, + "dto.NginxAuth": { + "properties": { + "remark": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "dto.NginxKey": { + "enum": [ + "index", + "limit-conn", + "ssl", + "cache", + "http-per", + "proxy-cache" + ], + "type": "string", + "x-enum-varnames": [ + "Index", + "LimitConn", + "SSL", + "CACHE", + "HttpPer", + "ProxyCache" + ] + }, + "dto.NginxUpstream": { + "properties": { + "algorithm": { + "type": "string" + }, + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.NginxUpstreamServer": { + "properties": { + "failTimeout": { + "type": "integer" + }, + "failTimeoutUnit": { + "type": "string" + }, + "flag": { + "type": "string" + }, + "maxConns": { + "type": "integer" + }, + "maxFails": { + "type": "integer" + }, + "server": { + "type": "string" + }, + "weight": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.NodeCurrent": { + "properties": { + "cpuDetailedPercent": { + "items": { + "type": "number" + }, + "type": "array" + }, + "cpuTotal": { + "type": "integer" + }, + "cpuUsed": { + "type": "number" + }, + "cpuUsedPercent": { + "type": "number" + }, + "load1": { + "type": "number" + }, + "load15": { + "type": "number" + }, + "load5": { + "type": "number" + }, + "loadUsagePercent": { + "type": "number" + }, + "memoryAvailable": { + "type": "integer" + }, + "memoryTotal": { + "type": "integer" + }, + "memoryUsed": { + "type": "integer" + }, + "memoryUsedPercent": { + "type": "number" + }, + "swapMemoryAvailable": { + "type": "integer" + }, + "swapMemoryTotal": { + "type": "integer" + }, + "swapMemoryUsed": { + "type": "integer" + }, + "swapMemoryUsedPercent": { + "type": "number" + } + }, + "type": "object" + }, + "dto.OllamaBindDomain": { + "properties": { + "appInstallID": { + "type": "integer" + }, + "domain": { + "type": "string" + }, + "ipList": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "appInstallID", + "domain" + ], + "type": "object" + }, + "dto.OllamaBindDomainReq": { + "properties": { + "appInstallID": { + "type": "integer" + } + }, + "required": [ + "appInstallID" + ], + "type": "object" + }, + "dto.OllamaBindDomainRes": { + "properties": { + "acmeAccountID": { + "type": "integer" + }, + "allowIPs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "connUrl": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.OllamaModelDropList": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OllamaModelName": { + "properties": { + "name": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Operate": { + "properties": { + "operation": { + "type": "string" + } + }, + "required": [ + "operation" + ], + "type": "object" + }, + "dto.OperateByID": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "dto.OperateByIDs": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.OperateByName": { + "properties": { + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OperateByTaskID": { + "properties": { + "taskID": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OperateByType": { + "properties": { + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OperationWithName": { + "properties": { + "name": { + "type": "string" + } + }, + "required": [ + "name" + ], + "type": "object" + }, + "dto.OperationWithNameAndType": { + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "dto.Options": { + "properties": { + "option": { + "type": "string" + } + }, + "type": "object" + }, + "dto.OsInfo": { + "properties": { + "diskSize": { + "type": "integer" + }, + "kernelArch": { + "type": "string" + }, + "kernelVersion": { + "type": "string" + }, + "os": { + "type": "string" + }, + "platform": { + "type": "string" + }, + "platformFamily": { + "type": "string" + }, + "prettyDistro": { + "type": "string" + } + }, + "type": "object" + }, + "dto.PageContainer": { + "properties": { + "excludeAppStore": { + "type": "boolean" + }, + "filters": { + "type": "string" + }, + "name": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt", + "state" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "state": { + "enum": [ + "all", + "created", + "running", + "paused", + "restarting", + "removing", + "exited", + "dead" + ], + "type": "string" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize", + "state" + ], + "type": "object" + }, + "dto.PageCronjob": { + "properties": { + "groupIDs": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "status", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PageImage": { + "properties": { + "name": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "size", + "tags", + "createdAt", + "isUsed" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PageInfo": { + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PageResult": { + "properties": { + "items": {}, + "total": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.PageSnapshot": { + "properties": { + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PasswordUpdate": { + "properties": { + "newPassword": { + "type": "string" + }, + "oldPassword": { + "type": "string" + } + }, + "required": [ + "newPassword", + "oldPassword" + ], + "type": "object" + }, + "dto.PortHelper": { + "properties": { + "containerPort": { + "type": "string" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "type": "string" + }, + "protocol": { + "type": "string" + } + }, + "type": "object" + }, + "dto.PortRuleOperate": { + "properties": { + "address": { + "type": "string" + }, + "chain": { + "type": "string" + }, + "description": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "operation": { + "enum": [ + "add", + "remove" + ], + "type": "string" + }, + "port": { + "type": "string" + }, + "protocol": { + "enum": [ + "tcp", + "udp", + "tcp/udp" + ], + "type": "string" + }, + "strategy": { + "enum": [ + "accept", + "drop" + ], + "type": "string" + } + }, + "required": [ + "operation", + "port", + "protocol", + "strategy" + ], + "type": "object" + }, + "dto.PortRuleUpdate": { + "properties": { + "newRule": { + "$ref": "#/definitions/dto.PortRuleOperate" + }, + "oldRule": { + "$ref": "#/definitions/dto.PortRuleOperate" + } + }, + "type": "object" + }, + "dto.PortUpdate": { + "properties": { + "serverPort": { + "maximum": 65535, + "minimum": 1, + "type": "integer" + } + }, + "required": [ + "serverPort" + ], + "type": "object" + }, + "dto.PostgresqlBindUser": { + "properties": { + "database": { + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "superUser": { + "type": "boolean" + }, + "username": { + "type": "string" + } + }, + "required": [ + "database", + "name", + "password", + "username" + ], + "type": "object" + }, + "dto.PostgresqlDBCreate": { + "properties": { + "database": { + "type": "string" + }, + "description": { + "type": "string" + }, + "format": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "password": { + "type": "string" + }, + "superUser": { + "type": "boolean" + }, + "username": { + "type": "string" + } + }, + "required": [ + "database", + "from", + "name", + "password", + "username" + ], + "type": "object" + }, + "dto.PostgresqlDBDelete": { + "properties": { + "database": { + "type": "string" + }, + "deleteBackup": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "postgresql", + "postgresql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "id", + "type" + ], + "type": "object" + }, + "dto.PostgresqlDBDeleteCheck": { + "properties": { + "database": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "enum": [ + "postgresql", + "postgresql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "id", + "type" + ], + "type": "object" + }, + "dto.PostgresqlDBSearch": { + "properties": { + "database": { + "type": "string" + }, + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "database", + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.PostgresqlLoadDB": { + "properties": { + "database": { + "type": "string" + }, + "from": { + "enum": [ + "local", + "remote" + ], + "type": "string" + }, + "type": { + "enum": [ + "postgresql", + "postgresql-cluster" + ], + "type": "string" + } + }, + "required": [ + "database", + "from", + "type" + ], + "type": "object" + }, + "dto.Process": { + "properties": { + "cmd": { + "type": "string" + }, + "memory": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "percent": { + "type": "number" + }, + "pid": { + "type": "integer" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ProxyUpdate": { + "properties": { + "proxyDocker": { + "type": "boolean" + }, + "proxyPasswd": { + "type": "string" + }, + "proxyPasswdKeep": { + "type": "string" + }, + "proxyPort": { + "type": "string" + }, + "proxyType": { + "type": "string" + }, + "proxyUrl": { + "type": "string" + }, + "proxyUser": { + "type": "string" + }, + "withDockerRestart": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.QuickJump": { + "properties": { + "alias": { + "type": "string" + }, + "detail": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isShow": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "router": { + "type": "string" + }, + "title": { + "type": "string" + } + }, + "type": "object" + }, + "dto.RecordFileSize": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.RecordSearch": { + "properties": { + "detailName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "type" + ], + "type": "object" + }, + "dto.RecordSearchByCronjob": { + "properties": { + "cronjobID": { + "type": "integer" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "cronjobID", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.RedisConf": { + "properties": { + "containerName": { + "type": "string" + }, + "database": { + "type": "string" + }, + "maxclients": { + "type": "string" + }, + "maxmemory": { + "type": "string" + }, + "name": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "requirepass": { + "type": "string" + }, + "timeout": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + }, + "dto.RedisConfPersistenceUpdate": { + "properties": { + "appendfsync": { + "type": "string" + }, + "appendonly": { + "type": "string" + }, + "database": { + "type": "string" + }, + "dbType": { + "enum": [ + "redis", + "redis-cluster" + ], + "type": "string" + }, + "save": { + "type": "string" + }, + "type": { + "enum": [ + "aof", + "rbd" + ], + "type": "string" + } + }, + "required": [ + "database", + "dbType", + "type" + ], + "type": "object" + }, + "dto.RedisConfUpdate": { + "properties": { + "database": { + "type": "string" + }, + "dbType": { + "enum": [ + "redis", + "redis-cluster" + ], + "type": "string" + }, + "maxclients": { + "type": "string" + }, + "maxmemory": { + "type": "string" + }, + "timeout": { + "type": "string" + } + }, + "required": [ + "database", + "dbType" + ], + "type": "object" + }, + "dto.RedisPersistence": { + "properties": { + "appendfsync": { + "type": "string" + }, + "appendonly": { + "type": "string" + }, + "database": { + "type": "string" + }, + "save": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + }, + "dto.RedisStatus": { + "properties": { + "connected_clients": { + "type": "string" + }, + "database": { + "type": "string" + }, + "instantaneous_ops_per_sec": { + "type": "string" + }, + "keyspace_hits": { + "type": "string" + }, + "keyspace_misses": { + "type": "string" + }, + "latest_fork_usec": { + "type": "string" + }, + "mem_fragmentation_ratio": { + "type": "string" + }, + "tcp_port": { + "type": "string" + }, + "total_commands_processed": { + "type": "string" + }, + "total_connections_received": { + "type": "string" + }, + "uptime_in_days": { + "type": "string" + }, + "used_memory": { + "type": "string" + }, + "used_memory_peak": { + "type": "string" + }, + "used_memory_rss": { + "type": "string" + } + }, + "required": [ + "database" + ], + "type": "object" + }, + "dto.ReleasesNotes": { + "properties": { + "content": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "fixCount": { + "type": "integer" + }, + "newCount": { + "type": "integer" + }, + "optimizationCount": { + "type": "integer" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ResourceLimit": { + "properties": { + "cpu": { + "type": "integer" + }, + "memory": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.RootCertOperate": { + "properties": { + "description": { + "type": "string" + }, + "encryptionMode": { + "enum": [ + "rsa", + "ed25519", + "ecdsa", + "dsa" + ], + "type": "string" + }, + "id": { + "type": "integer" + }, + "mode": { + "type": "string" + }, + "name": { + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "publicKey": { + "type": "string" + } + }, + "required": [ + "encryptionMode" + ], + "type": "object" + }, + "dto.RuleSearch": { + "properties": { + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "strategy": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "type" + ], + "type": "object" + }, + "dto.SSHConf": { + "properties": { + "file": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SSHConnData": { + "properties": { + "addr": { + "type": "string" + }, + "authMode": { + "enum": [ + "password", + "key" + ], + "type": "string" + }, + "localSSHConnShow": { + "type": "string" + }, + "passPhrase": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "maximum": 65535, + "minimum": 1, + "type": "integer" + }, + "privateKey": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "addr", + "port", + "user" + ], + "type": "object" + }, + "dto.SSHDefaultConn": { + "properties": { + "defaultConn": { + "type": "string" + }, + "withReset": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.SSHInfo": { + "properties": { + "autoStart": { + "type": "boolean" + }, + "currentUser": { + "type": "string" + }, + "isActive": { + "type": "boolean" + }, + "isExist": { + "type": "boolean" + }, + "listenAddress": { + "type": "string" + }, + "message": { + "type": "string" + }, + "passwordAuthentication": { + "type": "string" + }, + "permitRootLogin": { + "type": "string" + }, + "port": { + "type": "string" + }, + "pubkeyAuthentication": { + "type": "string" + }, + "useDNS": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SSHUpdate": { + "properties": { + "key": { + "type": "string" + }, + "newValue": { + "type": "string" + }, + "oldValue": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "dto.SSLInfo": { + "properties": { + "cert": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "key": { + "type": "string" + }, + "rootPath": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "timeout": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SSLUpdate": { + "properties": { + "cert": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "key": { + "type": "string" + }, + "ssl": { + "enum": [ + "Enable", + "Disable", + "Mux" + ], + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "sslType": { + "enum": [ + "self", + "select", + "import", + "import-paste", + "import-local" + ], + "type": "string" + } + }, + "required": [ + "ssl", + "sslType" + ], + "type": "object" + }, + "dto.ScriptOperate": { + "properties": { + "description": { + "type": "string" + }, + "groups": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isInteractive": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "script": { + "type": "string" + } + }, + "type": "object" + }, + "dto.ScriptOptions": { + "properties": { + "id": { + "type": "integer" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SearchByFilter": { + "properties": { + "filter": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SearchClamWithPage": { + "properties": { + "info": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "name", + "status", + "createdAt" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchForSize": { + "properties": { + "cronjobID": { + "type": "integer" + }, + "detailName": { + "type": "string" + }, + "info": { + "type": "string" + }, + "name": { + "type": "string" + }, + "order": { + "type": "string" + }, + "orderBy": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "type" + ], + "type": "object" + }, + "dto.SearchForTree": { + "properties": { + "info": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SearchLgLogWithPage": { + "properties": { + "ip": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "status": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchOpLogWithPage": { + "properties": { + "node": { + "type": "string" + }, + "operation": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "source": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchPageWithGroup": { + "properties": { + "groupID": { + "type": "integer" + }, + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchPageWithType": { + "properties": { + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchRecord": { + "properties": { + "cronjobID": { + "type": "integer" + }, + "endTime": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "startTime": { + "type": "string" + }, + "status": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchSSHLog": { + "properties": { + "Status": { + "enum": [ + "Success", + "Failed", + "All" + ], + "type": "string" + }, + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "Status", + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchTaskLogReq": { + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SearchWithPage": { + "properties": { + "info": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "dto.SettingInfo": { + "properties": { + "appStoreLastModified": { + "type": "string" + }, + "appStoreSyncStatus": { + "type": "string" + }, + "appStoreVersion": { + "type": "string" + }, + "defaultIO": { + "type": "string" + }, + "defaultNetwork": { + "type": "string" + }, + "dockerSockPath": { + "type": "string" + }, + "fileRecycleBin": { + "type": "string" + }, + "lastCleanData": { + "type": "string" + }, + "lastCleanSize": { + "type": "string" + }, + "lastCleanTime": { + "type": "string" + }, + "localTime": { + "type": "string" + }, + "monitorInterval": { + "type": "string" + }, + "monitorStatus": { + "type": "string" + }, + "monitorStoreDays": { + "type": "string" + }, + "ntpSite": { + "type": "string" + }, + "systemIP": { + "type": "string" + }, + "systemVersion": { + "type": "string" + }, + "timeZone": { + "type": "string" + } + }, + "type": "object" + }, + "dto.SettingUpdate": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "dto.SnapshotBatchDelete": { + "properties": { + "deleteWithFile": { + "type": "boolean" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "dto.SnapshotCreate": { + "properties": { + "appData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "backupData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "description": { + "maxLength": 256, + "type": "string" + }, + "downloadAccountID": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "ignoreFiles": { + "items": { + "type": "string" + }, + "type": "array" + }, + "interruptStep": { + "type": "string" + }, + "name": { + "type": "string" + }, + "panelData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "secret": { + "type": "string" + }, + "sourceAccountIDs": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "timeout": { + "type": "integer" + }, + "withDockerConf": { + "type": "boolean" + }, + "withLoginLog": { + "type": "boolean" + }, + "withMonitorData": { + "type": "boolean" + }, + "withOperationLog": { + "type": "boolean" + }, + "withSystemLog": { + "type": "boolean" + }, + "withTaskLog": { + "type": "boolean" + } + }, + "required": [ + "downloadAccountID", + "sourceAccountIDs" + ], + "type": "object" + }, + "dto.SnapshotData": { + "properties": { + "appData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "backupData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "ignoreFiles": { + "items": { + "type": "string" + }, + "type": "array" + }, + "panelData": { + "items": { + "$ref": "#/definitions/dto.DataTree" + }, + "type": "array" + }, + "withDockerConf": { + "type": "boolean" + }, + "withLoginLog": { + "type": "boolean" + }, + "withMonitorData": { + "type": "boolean" + }, + "withOperationLog": { + "type": "boolean" + }, + "withSystemLog": { + "type": "boolean" + }, + "withTaskLog": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.SnapshotImport": { + "properties": { + "backupAccountID": { + "type": "integer" + }, + "description": { + "maxLength": 256, + "type": "string" + }, + "names": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "dto.SnapshotRecover": { + "properties": { + "id": { + "type": "integer" + }, + "isNew": { + "type": "boolean" + }, + "reDownload": { + "type": "boolean" + }, + "secret": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "dto.SnapshotRule": { + "properties": { + "ignoreAppIDs": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "withImage": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.SnapshotTransHelper": { + "properties": { + "ignoreApps": { + "items": { + "$ref": "#/definitions/dto.TransHelper" + }, + "type": "array" + }, + "withImage": { + "type": "boolean" + } + }, + "type": "object" + }, + "dto.SwapHelper": { + "properties": { + "isNew": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "taskID": { + "type": "string" + }, + "used": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "dto.SystemSetting": { + "properties": { + "isDemo": { + "type": "boolean" + }, + "isIntl": { + "type": "boolean" + }, + "language": { + "type": "string" + } + }, + "type": "object" + }, + "dto.Tag": { + "properties": { + "key": { + "type": "string" + }, + "locales": { + "$ref": "#/definitions/dto.Locale" + }, + "name": { + "type": "string" + }, + "sort": { + "type": "integer" + } + }, + "type": "object" + }, + "dto.TerminalInfo": { + "properties": { + "cursorBlink": { + "type": "string" + }, + "cursorStyle": { + "type": "string" + }, + "fontSize": { + "type": "string" + }, + "letterSpacing": { + "type": "string" + }, + "lineHeight": { + "type": "string" + }, + "scrollSensitivity": { + "type": "string" + }, + "scrollback": { + "type": "string" + } + }, + "type": "object" + }, + "dto.TransHelper": { + "properties": { + "detailName": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.TreeChild": { + "properties": { + "id": { + "type": "integer" + }, + "label": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UpdateByFile": { + "properties": { + "file": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UpdateByNameAndFile": { + "properties": { + "file": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UpdateDescription": { + "properties": { + "description": { + "maxLength": 256, + "type": "string" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "dto.UpdateFirewallDescription": { + "properties": { + "chain": { + "type": "string" + }, + "description": { + "type": "string" + }, + "dstIP": { + "type": "string" + }, + "dstPort": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "srcIP": { + "type": "string" + }, + "srcPort": { + "type": "string" + }, + "strategy": { + "enum": [ + "accept", + "drop" + ], + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "strategy" + ], + "type": "object" + }, + "dto.Upgrade": { + "properties": { + "version": { + "type": "string" + } + }, + "required": [ + "version" + ], + "type": "object" + }, + "dto.UpgradeInfo": { + "properties": { + "latestVersion": { + "type": "string" + }, + "newVersion": { + "type": "string" + }, + "releaseNote": { + "type": "string" + }, + "testVersion": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UploadForRecover": { + "properties": { + "filePath": { + "type": "string" + }, + "targetDir": { + "type": "string" + } + }, + "type": "object" + }, + "dto.UserLoginInfo": { + "properties": { + "mfaStatus": { + "type": "string" + }, + "name": { + "type": "string" + }, + "token": { + "type": "string" + } + }, + "type": "object" + }, + "dto.VolumeCreate": { + "properties": { + "driver": { + "type": "string" + }, + "labels": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "options": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "driver", + "name" + ], + "type": "object" + }, + "dto.VolumeHelper": { + "properties": { + "containerDir": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "shared": { + "type": "string" + }, + "sourceDir": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "dto.XPUInfo": { + "properties": { + "deviceID": { + "type": "integer" + }, + "deviceName": { + "type": "string" + }, + "memory": { + "type": "string" + }, + "memoryUsed": { + "type": "string" + }, + "memoryUtil": { + "type": "string" + }, + "power": { + "type": "string" + }, + "temperature": { + "type": "string" + } + }, + "type": "object" + }, + "files.FileInfo": { + "properties": { + "content": { + "type": "string" + }, + "extension": { + "type": "string" + }, + "favoriteID": { + "type": "integer" + }, + "gid": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isDetail": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "isHidden": { + "type": "boolean" + }, + "isSymlink": { + "type": "boolean" + }, + "itemTotal": { + "type": "integer" + }, + "items": { + "items": { + "$ref": "#/definitions/files.FileInfo" + }, + "type": "array" + }, + "linkPath": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "modTime": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "updateTime": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "mfa.Otp": { + "properties": { + "qrImage": { + "type": "string" + }, + "secret": { + "type": "string" + } + }, + "type": "object" + }, + "model.App": { + "properties": { + "architectures": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "crossVersionUpdate": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "document": { + "type": "string" + }, + "github": { + "type": "string" + }, + "gpuSupport": { + "type": "boolean" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "memoryRequired": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "readMe": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "required": { + "type": "string" + }, + "requiredPanelVersion": { + "type": "number" + }, + "resource": { + "type": "string" + }, + "shortDescEn": { + "type": "string" + }, + "shortDescZh": { + "type": "string" + }, + "status": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "website": { + "type": "string" + } + }, + "type": "object" + }, + "model.AppIgnoreUpgrade": { + "properties": { + "appDetailID": { + "type": "integer" + }, + "appID": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "scope": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "type": "object" + }, + "model.AppInstall": { + "properties": { + "app": { + "$ref": "#/definitions/model.App" + }, + "appDetailId": { + "type": "integer" + }, + "appId": { + "type": "integer" + }, + "containerName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "dockerCompose": { + "type": "string" + }, + "env": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "httpPort": { + "type": "integer" + }, + "httpsPort": { + "type": "integer" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "param": { + "type": "string" + }, + "serviceName": { + "type": "string" + }, + "status": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "type": "object" + }, + "model.Favorite": { + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isDir": { + "type": "boolean" + }, + "isTxt": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "type": "object" + }, + "model.Runtime": { + "properties": { + "appDetailID": { + "type": "integer" + }, + "codeDir": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dockerCompose": { + "type": "string" + }, + "env": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "params": { + "type": "string" + }, + "port": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "resource": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "string" + }, + "workDir": { + "type": "string" + } + }, + "type": "object" + }, + "model.Website": { + "properties": { + "IPV6": { + "type": "boolean" + }, + "accessLog": { + "type": "boolean" + }, + "alias": { + "type": "string" + }, + "appInstallId": { + "type": "integer" + }, + "createdAt": { + "type": "string" + }, + "dbID": { + "type": "integer" + }, + "dbType": { + "type": "string" + }, + "defaultServer": { + "type": "boolean" + }, + "domains": { + "items": { + "$ref": "#/definitions/model.WebsiteDomain" + }, + "type": "array" + }, + "errorLog": { + "type": "boolean" + }, + "expireDate": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "ftpId": { + "type": "integer" + }, + "group": { + "type": "string" + }, + "httpConfig": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "parentWebsiteID": { + "type": "integer" + }, + "primaryDomain": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "proxy": { + "type": "string" + }, + "proxyType": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "rewrite": { + "type": "string" + }, + "runtimeID": { + "type": "integer" + }, + "siteDir": { + "type": "string" + }, + "status": { + "type": "string" + }, + "streamPorts": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "user": { + "type": "string" + }, + "webSiteGroupId": { + "type": "integer" + }, + "webSiteSSL": { + "$ref": "#/definitions/model.WebsiteSSL" + }, + "webSiteSSLId": { + "type": "integer" + } + }, + "type": "object" + }, + "model.WebsiteAcmeAccount": { + "properties": { + "caDirURL": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "eabHmacKey": { + "type": "string" + }, + "eabKid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "keyType": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "url": { + "type": "string" + }, + "useProxy": { + "type": "boolean" + } + }, + "type": "object" + }, + "model.WebsiteDnsAccount": { + "properties": { + "createdAt": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "type": "object" + }, + "model.WebsiteDomain": { + "properties": { + "createdAt": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "ssl": { + "type": "boolean" + }, + "updatedAt": { + "type": "string" + }, + "websiteId": { + "type": "integer" + } + }, + "type": "object" + }, + "model.WebsiteSSL": { + "properties": { + "acmeAccount": { + "$ref": "#/definitions/model.WebsiteAcmeAccount" + }, + "acmeAccountId": { + "type": "integer" + }, + "autoRenew": { + "type": "boolean" + }, + "caId": { + "type": "integer" + }, + "certPath": { + "type": "string" + }, + "certURL": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "disableCNAME": { + "type": "boolean" + }, + "dnsAccount": { + "$ref": "#/definitions/model.WebsiteDnsAccount" + }, + "dnsAccountId": { + "type": "integer" + }, + "domains": { + "type": "string" + }, + "execShell": { + "type": "boolean" + }, + "expireDate": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isIP": { + "type": "boolean" + }, + "keyType": { + "type": "string" + }, + "masterSslId": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "nameserver1": { + "type": "string" + }, + "nameserver2": { + "type": "string" + }, + "nodes": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "pem": { + "type": "string" + }, + "primaryDomain": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "pushNode": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "skipDNS": { + "type": "boolean" + }, + "startDate": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "websites": { + "items": { + "$ref": "#/definitions/model.Website" + }, + "type": "array" + } + }, + "type": "object" + }, + "request.AppConfigUpdate": { + "properties": { + "installID": { + "type": "integer" + }, + "webUI": { + "type": "string" + } + }, + "required": [ + "installID" + ], + "type": "object" + }, + "request.AppIgnoreUpgradeReq": { + "properties": { + "appDetailID": { + "type": "integer" + }, + "appID": { + "type": "integer" + }, + "scope": { + "enum": [ + "all", + "version" + ], + "type": "string" + } + }, + "required": [ + "appID", + "scope" + ], + "type": "object" + }, + "request.AppInstallCreate": { + "properties": { + "advanced": { + "type": "boolean" + }, + "allowPort": { + "type": "boolean" + }, + "appDetailId": { + "type": "integer" + }, + "containerName": { + "type": "string" + }, + "cpuQuota": { + "type": "number" + }, + "dockerCompose": { + "type": "string" + }, + "editCompose": { + "type": "boolean" + }, + "gpuConfig": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "memoryLimit": { + "type": "number" + }, + "memoryUnit": { + "type": "string" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "pullImage": { + "type": "boolean" + }, + "restartPolicy": { + "enum": [ + "always", + "unless-stopped", + "no", + "on-failure" + ], + "type": "string" + }, + "services": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "specifyIP": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "required": [ + "appDetailId", + "name" + ], + "type": "object" + }, + "request.AppInstalledInfo": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "key" + ], + "type": "object" + }, + "request.AppInstalledOperate": { + "properties": { + "backup": { + "type": "boolean" + }, + "backupId": { + "type": "integer" + }, + "deleteBackup": { + "type": "boolean" + }, + "deleteDB": { + "type": "boolean" + }, + "deleteImage": { + "type": "boolean" + }, + "detailId": { + "type": "integer" + }, + "dockerCompose": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "installId": { + "type": "integer" + }, + "operate": { + "type": "string" + }, + "pullImage": { + "type": "boolean" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "installId", + "operate" + ], + "type": "object" + }, + "request.AppInstalledSearch": { + "properties": { + "all": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "sync": { + "type": "boolean" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "unused": { + "type": "boolean" + }, + "update": { + "type": "boolean" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.AppInstalledUpdate": { + "properties": { + "advanced": { + "type": "boolean" + }, + "allowPort": { + "type": "boolean" + }, + "containerName": { + "type": "string" + }, + "cpuQuota": { + "type": "number" + }, + "dockerCompose": { + "type": "string" + }, + "editCompose": { + "type": "boolean" + }, + "gpuConfig": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "installId": { + "type": "integer" + }, + "memoryLimit": { + "type": "number" + }, + "memoryUnit": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "pullImage": { + "type": "boolean" + }, + "restartPolicy": { + "enum": [ + "always", + "unless-stopped", + "no", + "on-failure" + ], + "type": "string" + }, + "specifyIP": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "required": [ + "installId", + "params" + ], + "type": "object" + }, + "request.AppSearch": { + "properties": { + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "recommend": { + "type": "boolean" + }, + "resource": { + "type": "string" + }, + "showCurrentArch": { + "type": "boolean" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.BatchWebsiteGroup": { + "properties": { + "groupID": { + "type": "integer" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "groupID", + "ids" + ], + "type": "object" + }, + "request.BatchWebsiteHttps": { + "properties": { + "SSLProtocol": { + "items": { + "type": "string" + }, + "type": "array" + }, + "algorithm": { + "type": "string" + }, + "certificate": { + "type": "string" + }, + "certificatePath": { + "type": "string" + }, + "hsts": { + "type": "boolean" + }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, + "http3": { + "type": "boolean" + }, + "httpConfig": { + "enum": [ + "HTTPSOnly", + "HTTPAlso", + "HTTPToHTTPS" + ], + "type": "string" + }, + "httpsPorts": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "importType": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "type": { + "enum": [ + "existed", + "auto", + "manual" + ], + "type": "string" + }, + "websiteSSLId": { + "type": "integer" + } + }, + "required": [ + "ids", + "taskID" + ], + "type": "object" + }, + "request.BatchWebsiteOp": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "operate": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "ids", + "operate", + "taskID" + ], + "type": "object" + }, + "request.ChangeDatabase": { + "properties": { + "databaseID": { + "type": "integer" + }, + "databaseType": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.CorsConfig": { + "properties": { + "allowCredentials": { + "type": "boolean" + }, + "allowHeaders": { + "type": "string" + }, + "allowMethods": { + "type": "string" + }, + "allowOrigins": { + "type": "string" + }, + "cors": { + "type": "boolean" + }, + "preflight": { + "type": "boolean" + } + }, + "type": "object" + }, + "request.CorsConfigReq": { + "properties": { + "allowCredentials": { + "type": "boolean" + }, + "allowHeaders": { + "type": "string" + }, + "allowMethods": { + "type": "string" + }, + "allowOrigins": { + "type": "string" + }, + "cors": { + "type": "boolean" + }, + "preflight": { + "type": "boolean" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.CrossSiteAccessOp": { + "properties": { + "operation": { + "enum": [ + "Enable", + "Disable" + ], + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "operation", + "websiteID" + ], + "type": "object" + }, + "request.CustomRewriteOperate": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "operate": { + "enum": [ + "create", + "delete" + ], + "type": "string" + } + }, + "required": [ + "operate" + ], + "type": "object" + }, + "request.DirSizeReq": { + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.DiskMountRequest": { + "properties": { + "autoMount": { + "type": "boolean" + }, + "device": { + "type": "string" + }, + "filesystem": { + "enum": [ + "ext4", + "xfs" + ], + "type": "string" + }, + "mountPoint": { + "type": "string" + }, + "noFail": { + "type": "boolean" + } + }, + "required": [ + "device", + "filesystem", + "mountPoint" + ], + "type": "object" + }, + "request.DiskPartitionRequest": { + "properties": { + "autoMount": { + "type": "boolean" + }, + "device": { + "type": "string" + }, + "filesystem": { + "enum": [ + "ext4", + "xfs" + ], + "type": "string" + }, + "label": { + "type": "string" + }, + "mountPoint": { + "type": "string" + } + }, + "required": [ + "device", + "filesystem", + "mountPoint" + ], + "type": "object" + }, + "request.DiskUnmountRequest": { + "properties": { + "mountPoint": { + "type": "string" + } + }, + "required": [ + "mountPoint" + ], + "type": "object" + }, + "request.Environment": { + "properties": { + "key": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "request.ExecComposerReq": { + "properties": { + "command": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "extCommand": { + "type": "string" + }, + "mirror": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "user": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "command", + "dir", + "mirror", + "taskID", + "user", + "websiteID" + ], + "type": "object" + }, + "request.ExposedPort": { + "properties": { + "containerPort": { + "type": "integer" + }, + "hostIP": { + "type": "string" + }, + "hostPort": { + "type": "integer" + } + }, + "type": "object" + }, + "request.ExtraHost": { + "properties": { + "hostname": { + "type": "string" + }, + "ip": { + "type": "string" + } + }, + "type": "object" + }, + "request.FPMConfig": { + "properties": { + "id": { + "type": "integer" + }, + "params": { + "additionalProperties": true, + "type": "object" + } + }, + "required": [ + "id", + "params" + ], + "type": "object" + }, + "request.FavoriteCreate": { + "properties": { + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FavoriteDelete": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.FileBatchDelete": { + "properties": { + "isDir": { + "type": "boolean" + }, + "paths": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "paths" + ], + "type": "object" + }, + "request.FileCompress": { + "properties": { + "dst": { + "type": "string" + }, + "files": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "replace": { + "type": "boolean" + }, + "secret": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "dst", + "files", + "name", + "type" + ], + "type": "object" + }, + "request.FileContentReq": { + "properties": { + "isDetail": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FileConvert": { + "properties": { + "extension": { + "type": "string" + }, + "inputFile": { + "type": "string" + }, + "outputFormat": { + "type": "string" + }, + "path": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "extension", + "inputFile", + "outputFormat", + "path", + "type" + ], + "type": "object" + }, + "request.FileCreate": { + "properties": { + "content": { + "type": "string" + }, + "isDir": { + "type": "boolean" + }, + "isLink": { + "type": "boolean" + }, + "isSymlink": { + "type": "boolean" + }, + "linkPath": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "sub": { + "type": "boolean" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FileDeCompress": { + "properties": { + "dst": { + "type": "string" + }, + "path": { + "type": "string" + }, + "secret": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "dst", + "path", + "type" + ], + "type": "object" + }, + "request.FileDelete": { + "properties": { + "forceDelete": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FileDownload": { + "properties": { + "compress": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "paths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "required": [ + "name", + "paths", + "type" + ], + "type": "object" + }, + "request.FileEdit": { + "properties": { + "content": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FileMove": { + "properties": { + "cover": { + "type": "boolean" + }, + "coverPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "name": { + "type": "string" + }, + "newPath": { + "type": "string" + }, + "oldPaths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "required": [ + "newPath", + "oldPaths", + "type" + ], + "type": "object" + }, + "request.FileOption": { + "properties": { + "containSub": { + "type": "boolean" + }, + "dir": { + "type": "boolean" + }, + "expand": { + "type": "boolean" + }, + "isDetail": { + "type": "boolean" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "path": { + "type": "string" + }, + "search": { + "type": "string" + }, + "showHidden": { + "type": "boolean" + }, + "sortBy": { + "type": "string" + }, + "sortOrder": { + "type": "string" + } + }, + "type": "object" + }, + "request.FilePathCheck": { + "properties": { + "path": { + "type": "string" + }, + "withInit": { + "type": "boolean" + } + }, + "required": [ + "path" + ], + "type": "object" + }, + "request.FilePathsCheck": { + "properties": { + "paths": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "paths" + ], + "type": "object" + }, + "request.FileReadByLineReq": { + "properties": { + "ID": { + "type": "integer" + }, + "latest": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "resourceID": { + "type": "integer" + }, + "taskID": { + "type": "string" + }, + "taskOperate": { + "type": "string" + }, + "taskType": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "type" + ], + "type": "object" + }, + "request.FileRename": { + "properties": { + "newName": { + "type": "string" + }, + "oldName": { + "type": "string" + } + }, + "required": [ + "newName", + "oldName" + ], + "type": "object" + }, + "request.FileRoleReq": { + "properties": { + "group": { + "type": "string" + }, + "mode": { + "type": "integer" + }, + "paths": { + "items": { + "type": "string" + }, + "type": "array" + }, + "sub": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "required": [ + "group", + "mode", + "paths", + "user" + ], + "type": "object" + }, + "request.FileRoleUpdate": { + "properties": { + "group": { + "type": "string" + }, + "path": { + "type": "string" + }, + "sub": { + "type": "boolean" + }, + "user": { + "type": "string" + } + }, + "required": [ + "group", + "path", + "user" + ], + "type": "object" + }, + "request.FileWget": { + "properties": { + "ignoreCertificate": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "url": { + "type": "string" + } + }, + "required": [ + "name", + "path", + "url" + ], + "type": "object" + }, + "request.HostToolConfig": { + "properties": { + "content": { + "type": "string" + }, + "operate": { + "enum": [ + "get", + "set" + ], + "type": "string" + }, + "type": { + "enum": [ + "supervisord" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "request.HostToolCreate": { + "properties": { + "configPath": { + "type": "string" + }, + "serviceName": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "request.HostToolReq": { + "properties": { + "operate": { + "enum": [ + "status", + "restart", + "start", + "stop" + ], + "type": "string" + }, + "type": { + "enum": [ + "supervisord" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "request.McpBindDomain": { + "properties": { + "domain": { + "type": "string" + }, + "ipList": { + "type": "string" + }, + "sslID": { + "type": "integer" + } + }, + "required": [ + "domain" + ], + "type": "object" + }, + "request.McpBindDomainUpdate": { + "properties": { + "ipList": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.McpServerCreate": { + "properties": { + "baseUrl": { + "type": "string" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "hostIP": { + "type": "string" + }, + "name": { + "type": "string" + }, + "outputTransport": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "ssePath": { + "type": "string" + }, + "streamableHttpPath": { + "type": "string" + }, + "type": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "required": [ + "command", + "name", + "outputTransport", + "port", + "type" + ], + "type": "object" + }, + "request.McpServerDelete": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.McpServerOperate": { + "properties": { + "id": { + "type": "integer" + }, + "operate": { + "type": "string" + } + }, + "required": [ + "id", + "operate" + ], + "type": "object" + }, + "request.McpServerSearch": { + "properties": { + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "sync": { + "type": "boolean" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.McpServerUpdate": { + "properties": { + "baseUrl": { + "type": "string" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "hostIP": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "outputTransport": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "ssePath": { + "type": "string" + }, + "streamableHttpPath": { + "type": "string" + }, + "type": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "required": [ + "command", + "id", + "name", + "outputTransport", + "port", + "type" + ], + "type": "object" + }, + "request.NewAppInstall": { + "properties": { + "advanced": { + "type": "boolean" + }, + "allowPort": { + "type": "boolean" + }, + "appDetailID": { + "type": "integer" + }, + "containerName": { + "type": "string" + }, + "cpuQuota": { + "type": "number" + }, + "dockerCompose": { + "type": "string" + }, + "editCompose": { + "type": "boolean" + }, + "gpuConfig": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "memoryLimit": { + "type": "number" + }, + "memoryUnit": { + "type": "string" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "pullImage": { + "type": "boolean" + }, + "restartPolicy": { + "enum": [ + "always", + "unless-stopped", + "no", + "on-failure" + ], + "type": "string" + }, + "specifyIP": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "type": "object" + }, + "request.NginxAntiLeechUpdate": { + "properties": { + "blocked": { + "type": "boolean" + }, + "cache": { + "type": "boolean" + }, + "cacheTime": { + "type": "integer" + }, + "cacheUint": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "extends": { + "type": "string" + }, + "logEnable": { + "type": "boolean" + }, + "noneRef": { + "type": "boolean" + }, + "return": { + "type": "string" + }, + "serverNames": { + "items": { + "type": "string" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.NginxAuthReq": { + "properties": { + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.NginxAuthUpdate": { + "properties": { + "operate": { + "type": "string" + }, + "password": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "username": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "operate", + "websiteID" + ], + "type": "object" + }, + "request.NginxBuildReq": { + "properties": { + "mirror": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "mirror", + "taskID" + ], + "type": "object" + }, + "request.NginxCommonReq": { + "properties": { + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.NginxConfigFileUpdate": { + "properties": { + "backup": { + "type": "boolean" + }, + "content": { + "type": "string" + } + }, + "required": [ + "content" + ], + "type": "object" + }, + "request.NginxConfigUpdate": { + "properties": { + "operate": { + "enum": [ + "add", + "update", + "delete" + ], + "type": "string" + }, + "params": {}, + "scope": { + "$ref": "#/definitions/dto.NginxKey" + }, + "websiteId": { + "type": "integer" + } + }, + "required": [ + "operate" + ], + "type": "object" + }, + "request.NginxDefaultHTTPSUpdate": { + "properties": { + "operate": { + "enum": [ + "enable", + "disable" + ], + "type": "string" + }, + "sslRejectHandshake": { + "type": "boolean" + } + }, + "required": [ + "operate" + ], + "type": "object" + }, + "request.NginxModuleUpdate": { + "properties": { + "enable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "operate": { + "enum": [ + "create", + "delete", + "update" + ], + "type": "string" + }, + "packages": { + "type": "string" + }, + "params": { + "type": "string" + }, + "script": { + "type": "string" + } + }, + "required": [ + "name", + "operate" + ], + "type": "object" + }, + "request.NginxPathAuthUpdate": { + "properties": { + "name": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "password": { + "type": "string" + }, + "path": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "username": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "operate", + "websiteID" + ], + "type": "object" + }, + "request.NginxProxyCacheUpdate": { + "properties": { + "cacheExpire": { + "type": "integer" + }, + "cacheExpireUnit": { + "type": "string" + }, + "cacheLimit": { + "type": "integer" + }, + "cacheLimitUnit": { + "type": "string" + }, + "open": { + "type": "boolean" + }, + "shareCache": { + "type": "integer" + }, + "shareCacheUnit": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "cacheExpire", + "cacheExpireUnit", + "cacheLimit", + "cacheLimitUnit", + "shareCache", + "shareCacheUnit", + "websiteID" + ], + "type": "object" + }, + "request.NginxProxyUpdate": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "content", + "name", + "websiteID" + ], + "type": "object" + }, + "request.NginxRedirectReq": { + "properties": { + "domains": { + "items": { + "type": "string" + }, + "type": "array" + }, + "enable": { + "type": "boolean" + }, + "keepPath": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "path": { + "type": "string" + }, + "redirect": { + "type": "string" + }, + "redirectRoot": { + "type": "boolean" + }, + "target": { + "type": "string" + }, + "type": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "name", + "operate", + "redirect", + "target", + "type", + "websiteID" + ], + "type": "object" + }, + "request.NginxRedirectUpdate": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "content", + "name", + "websiteID" + ], + "type": "object" + }, + "request.NginxRewriteReq": { + "properties": { + "name": { + "type": "string" + }, + "websiteId": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteId" + ], + "type": "object" + }, + "request.NginxRewriteUpdate": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "websiteId": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteId" + ], + "type": "object" + }, + "request.NginxScopeReq": { + "properties": { + "scope": { + "$ref": "#/definitions/dto.NginxKey" + }, + "websiteId": { + "type": "integer" + } + }, + "required": [ + "scope" + ], + "type": "object" + }, + "request.NodeModuleReq": { + "properties": { + "ID": { + "type": "integer" + } + }, + "required": [ + "ID" + ], + "type": "object" + }, + "request.NodePackageReq": { + "properties": { + "codeDir": { + "type": "string" + } + }, + "type": "object" + }, + "request.PHPConfigUpdate": { + "properties": { + "disableFunctions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "maxExecutionTime": { + "type": "string" + }, + "params": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "scope": { + "type": "string" + }, + "uploadMaxSize": { + "type": "string" + } + }, + "required": [ + "id", + "scope" + ], + "type": "object" + }, + "request.PHPContainerConfig": { + "properties": { + "containerName": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/request.ExposedPort" + }, + "type": "array" + }, + "extraHosts": { + "items": { + "$ref": "#/definitions/request.ExtraHost" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.PHPExtensionInstallReq": { + "properties": { + "ID": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "taskID": { + "type": "string" + } + }, + "required": [ + "ID", + "name" + ], + "type": "object" + }, + "request.PHPExtensionsCreate": { + "properties": { + "extensions": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "extensions", + "name" + ], + "type": "object" + }, + "request.PHPExtensionsDelete": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.PHPExtensionsSearch": { + "properties": { + "all": { + "type": "boolean" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.PHPExtensionsUpdate": { + "properties": { + "extensions": { + "type": "string" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "extensions", + "id" + ], + "type": "object" + }, + "request.PHPFileReq": { + "properties": { + "id": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "id", + "type" + ], + "type": "object" + }, + "request.PHPFileUpdate": { + "properties": { + "content": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "required": [ + "content", + "id", + "type" + ], + "type": "object" + }, + "request.PHPSupervisorProcessConfig": { + "properties": { + "autoRestart": { + "type": "string" + }, + "autoStart": { + "type": "string" + }, + "command": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "numprocs": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.PHPSupervisorProcessFileReq": { + "properties": { + "content": { + "type": "string" + }, + "file": { + "enum": [ + "out.log", + "err.log", + "config" + ], + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "operate": { + "enum": [ + "get", + "clear", + "update" + ], + "type": "string" + } + }, + "required": [ + "file", + "id", + "name", + "operate" + ], + "type": "object" + }, + "request.PortUpdate": { + "properties": { + "key": { + "type": "string" + }, + "name": { + "type": "string" + }, + "port": { + "type": "integer" + } + }, + "type": "object" + }, + "request.ProcessReq": { + "properties": { + "PID": { + "type": "integer" + } + }, + "required": [ + "PID" + ], + "type": "object" + }, + "request.RecycleBinReduce": { + "properties": { + "from": { + "type": "string" + }, + "name": { + "type": "string" + }, + "rName": { + "type": "string" + } + }, + "required": [ + "from", + "rName" + ], + "type": "object" + }, + "request.ReqWithID": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.RuntimeCreate": { + "properties": { + "appDetailId": { + "type": "integer" + }, + "clean": { + "type": "boolean" + }, + "codeDir": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/request.ExposedPort" + }, + "type": "array" + }, + "extraHosts": { + "items": { + "$ref": "#/definitions/request.ExtraHost" + }, + "type": "array" + }, + "image": { + "type": "string" + }, + "install": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "remark": { + "type": "string" + }, + "resource": { + "type": "string" + }, + "source": { + "type": "string" + }, + "type": { + "type": "string" + }, + "version": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "type": "object" + }, + "request.RuntimeDelete": { + "properties": { + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + } + }, + "type": "object" + }, + "request.RuntimeOperate": { + "properties": { + "ID": { + "type": "integer" + }, + "operate": { + "type": "string" + } + }, + "type": "object" + }, + "request.RuntimeRemark": { + "properties": { + "id": { + "type": "integer" + }, + "remark": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.RuntimeSearch": { + "properties": { + "name": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.RuntimeUpdate": { + "properties": { + "clean": { + "type": "boolean" + }, + "codeDir": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/request.ExposedPort" + }, + "type": "array" + }, + "extraHosts": { + "items": { + "$ref": "#/definitions/request.ExtraHost" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "install": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "rebuild": { + "type": "boolean" + }, + "remark": { + "type": "string" + }, + "source": { + "type": "string" + }, + "version": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "type": "object" + }, + "request.SearchUploadWithPage": { + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "path": { + "type": "string" + } + }, + "required": [ + "page", + "pageSize", + "path" + ], + "type": "object" + }, + "request.StreamUpdate": { + "properties": { + "algorithm": { + "type": "string" + }, + "name": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "streamPorts": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.SupervisorProcessConfig": { + "properties": { + "autoRestart": { + "type": "string" + }, + "autoStart": { + "type": "string" + }, + "command": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "name": { + "type": "string" + }, + "numprocs": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "request.SupervisorProcessFileReq": { + "properties": { + "content": { + "type": "string" + }, + "file": { + "enum": [ + "out.log", + "err.log", + "config" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "operate": { + "enum": [ + "get", + "clear", + "update" + ], + "type": "string" + } + }, + "required": [ + "file", + "name", + "operate" + ], + "type": "object" + }, + "request.Volume": { + "properties": { + "source": { + "type": "string" + }, + "target": { + "type": "string" + } + }, + "type": "object" + }, + "request.WebsiteAcmeAccountCreate": { + "properties": { + "caDirURL": { + "type": "string" + }, + "eabHmacKey": { + "type": "string" + }, + "eabKid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "keyType": { + "enum": [ + "P256", + "P384", + "2048", + "3072", + "4096", + "8192" + ], + "type": "string" + }, + "type": { + "enum": [ + "letsencrypt", + "zerossl", + "buypass", + "google", + "custom" + ], + "type": "string" + }, + "useProxy": { + "type": "boolean" + } + }, + "required": [ + "email", + "keyType", + "type" + ], + "type": "object" + }, + "request.WebsiteAcmeAccountUpdate": { + "properties": { + "id": { + "type": "integer" + }, + "useProxy": { + "type": "boolean" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteBatchDelReq": { + "properties": { + "ids": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "required": [ + "ids" + ], + "type": "object" + }, + "request.WebsiteCACreate": { + "properties": { + "city": { + "type": "string" + }, + "commonName": { + "type": "string" + }, + "country": { + "type": "string" + }, + "keyType": { + "enum": [ + "P256", + "P384", + "2048", + "3072", + "4096", + "8192" + ], + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "organizationUint": { + "type": "string" + }, + "province": { + "type": "string" + } + }, + "required": [ + "commonName", + "country", + "keyType", + "name", + "organization" + ], + "type": "object" + }, + "request.WebsiteCAObtain": { + "properties": { + "autoRenew": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "domains": { + "type": "string" + }, + "execShell": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "keyType": { + "enum": [ + "P256", + "P384", + "2048", + "3072", + "4096", + "8192" + ], + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "renew": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "time": { + "type": "integer" + }, + "unit": { + "type": "string" + } + }, + "required": [ + "domains", + "id", + "keyType", + "time", + "unit" + ], + "type": "object" + }, + "request.WebsiteCASearch": { + "properties": { + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "page", + "pageSize" + ], + "type": "object" + }, + "request.WebsiteCommonReq": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteCreate": { + "properties": { + "IPV6": { + "type": "boolean" + }, + "algorithm": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "appID": { + "type": "integer" + }, + "appInstall": { + "$ref": "#/definitions/request.NewAppInstall" + }, + "appInstallID": { + "type": "integer" + }, + "appType": { + "enum": [ + "new", + "installed" + ], + "type": "string" + }, + "createDb": { + "type": "boolean" + }, + "dbFormat": { + "type": "string" + }, + "dbHost": { + "type": "string" + }, + "dbName": { + "type": "string" + }, + "dbPassword": { + "type": "string" + }, + "dbUser": { + "type": "string" + }, + "domains": { + "items": { + "$ref": "#/definitions/request.WebsiteDomain" + }, + "type": "array" + }, + "enableSSL": { + "type": "boolean" + }, + "ftpPassword": { + "type": "string" + }, + "ftpUser": { + "type": "string" + }, + "name": { + "type": "string" + }, + "parentWebsiteID": { + "type": "integer" + }, + "port": { + "type": "integer" + }, + "proxy": { + "type": "string" + }, + "proxyType": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "runtimeID": { + "type": "integer" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "siteDir": { + "type": "string" + }, + "streamPorts": { + "type": "string" + }, + "taskID": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webSiteGroupID": { + "type": "integer" + }, + "websiteSSLID": { + "type": "integer" + } + }, + "required": [ + "alias", + "type", + "webSiteGroupID" + ], + "type": "object" + }, + "request.WebsiteDNSReq": { + "properties": { + "acmeAccountId": { + "type": "integer" + }, + "websiteSSLId": { + "type": "integer" + } + }, + "required": [ + "acmeAccountId", + "websiteSSLId" + ], + "type": "object" + }, + "request.WebsiteDefaultUpdate": { + "properties": { + "id": { + "type": "integer" + } + }, + "type": "object" + }, + "request.WebsiteDelete": { + "properties": { + "deleteApp": { + "type": "boolean" + }, + "deleteBackup": { + "type": "boolean" + }, + "deleteDB": { + "type": "boolean" + }, + "forceDelete": { + "type": "boolean" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteDnsAccountCreate": { + "properties": { + "authorization": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "authorization", + "name", + "type" + ], + "type": "object" + }, + "request.WebsiteDnsAccountUpdate": { + "properties": { + "authorization": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "authorization", + "id", + "name", + "type" + ], + "type": "object" + }, + "request.WebsiteDomain": { + "properties": { + "domain": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "ssl": { + "type": "boolean" + } + }, + "required": [ + "domain" + ], + "type": "object" + }, + "request.WebsiteDomainCreate": { + "properties": { + "domains": { + "items": { + "$ref": "#/definitions/request.WebsiteDomain" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "domains", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteDomainDelete": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteDomainUpdate": { + "properties": { + "id": { + "type": "integer" + }, + "ssl": { + "type": "boolean" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteHTTPSOp": { + "properties": { + "SSLProtocol": { + "items": { + "type": "string" + }, + "type": "array" + }, + "algorithm": { + "type": "string" + }, + "certificate": { + "type": "string" + }, + "certificatePath": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "hsts": { + "type": "boolean" + }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, + "http3": { + "type": "boolean" + }, + "httpConfig": { + "enum": [ + "HTTPSOnly", + "HTTPAlso", + "HTTPToHTTPS" + ], + "type": "string" + }, + "httpsPorts": { + "items": { + "type": "integer" + }, + "type": "array" + }, + "importType": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "type": { + "enum": [ + "existed", + "auto", + "manual" + ], + "type": "string" + }, + "websiteId": { + "type": "integer" + }, + "websiteSSLId": { + "type": "integer" + } + }, + "required": [ + "websiteId" + ], + "type": "object" + }, + "request.WebsiteHtmlUpdate": { + "properties": { + "content": { + "type": "string" + }, + "sync": { + "type": "boolean" + }, + "type": { + "type": "string" + } + }, + "required": [ + "content", + "type" + ], + "type": "object" + }, + "request.WebsiteInstallCheckReq": { + "properties": { + "InstallIds": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, + "request.WebsiteLBCreate": { + "properties": { + "algorithm": { + "type": "string" + }, + "name": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteLBDelete": { + "properties": { + "name": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteLBUpdate": { + "properties": { + "algorithm": { + "type": "string" + }, + "name": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "name", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteLBUpdateFile": { + "properties": { + "content": { + "type": "string" + }, + "name": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "content", + "name", + "websiteID" + ], + "type": "object" + }, + "request.WebsiteLogReq": { + "properties": { + "id": { + "type": "integer" + }, + "logType": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "id", + "logType", + "operate" + ], + "type": "object" + }, + "request.WebsiteNginxUpdate": { + "properties": { + "content": { + "type": "string" + }, + "id": { + "type": "integer" + } + }, + "required": [ + "content", + "id" + ], + "type": "object" + }, + "request.WebsiteOp": { + "properties": { + "id": { + "type": "integer" + }, + "operate": { + "type": "string" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsitePHPVersionReq": { + "properties": { + "runtimeID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.WebsiteProxyConfig": { + "properties": { + "allowCredentials": { + "type": "boolean" + }, + "allowHeaders": { + "type": "string" + }, + "allowMethods": { + "type": "string" + }, + "allowOrigins": { + "type": "string" + }, + "cache": { + "type": "boolean" + }, + "cacheTime": { + "type": "integer" + }, + "cacheUnit": { + "type": "string" + }, + "content": { + "type": "string" + }, + "cors": { + "type": "boolean" + }, + "enable": { + "type": "boolean" + }, + "filePath": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "match": { + "type": "string" + }, + "modifier": { + "type": "string" + }, + "name": { + "type": "string" + }, + "operate": { + "type": "string" + }, + "preflight": { + "type": "boolean" + }, + "proxyHost": { + "type": "string" + }, + "proxyPass": { + "type": "string" + }, + "proxySSLName": { + "type": "string" + }, + "replaces": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "serverCacheTime": { + "type": "integer" + }, + "serverCacheUnit": { + "type": "string" + }, + "sni": { + "type": "boolean" + } + }, + "required": [ + "id", + "match", + "name", + "operate", + "proxyHost", + "proxyPass" + ], + "type": "object" + }, + "request.WebsiteProxyReq": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteRealIP": { + "properties": { + "ipFrom": { + "type": "string" + }, + "ipHeader": { + "type": "string" + }, + "ipOther": { + "type": "string" + }, + "open": { + "type": "boolean" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "request.WebsiteResourceReq": { + "properties": { + "id": { + "type": "integer" + } + }, + "required": [ + "id" + ], + "type": "object" + }, + "request.WebsiteSSLApply": { + "properties": { + "ID": { + "type": "integer" + }, + "disableLog": { + "type": "boolean" + }, + "nameservers": { + "items": { + "type": "string" + }, + "type": "array" + }, + "skipDNSCheck": { + "type": "boolean" + } + }, + "required": [ + "ID" + ], + "type": "object" + }, + "request.WebsiteSSLCreate": { + "properties": { + "acmeAccountId": { + "type": "integer" + }, + "apply": { + "type": "boolean" + }, + "autoRenew": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "disableCNAME": { + "type": "boolean" + }, + "dnsAccountId": { + "type": "integer" + }, + "execShell": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "isIp": { + "type": "boolean" + }, + "keyType": { + "type": "string" + }, + "nameserver1": { + "type": "string" + }, + "nameserver2": { + "type": "string" + }, + "nodes": { + "type": "string" + }, + "otherDomains": { + "type": "string" + }, + "primaryDomain": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "pushNode": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "skipDNS": { + "type": "boolean" + } + }, + "required": [ + "acmeAccountId", + "primaryDomain", + "provider" + ], + "type": "object" + }, + "request.WebsiteSSLSearch": { + "properties": { + "acmeAccountID": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "expire_date" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "request.WebsiteSSLUpdate": { + "properties": { + "acmeAccountId": { + "type": "integer" + }, + "apply": { + "type": "boolean" + }, + "autoRenew": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "disableCNAME": { + "type": "boolean" + }, + "dnsAccountId": { + "type": "integer" + }, + "execShell": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "keyType": { + "type": "string" + }, + "nameserver1": { + "type": "string" + }, + "nameserver2": { + "type": "string" + }, + "nodes": { + "type": "string" + }, + "otherDomains": { + "type": "string" + }, + "primaryDomain": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "pushNode": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "skipDNS": { + "type": "boolean" + } + }, + "required": [ + "id", + "primaryDomain", + "provider" + ], + "type": "object" + }, + "request.WebsiteSSLUpload": { + "properties": { + "certificate": { + "type": "string" + }, + "certificatePath": { + "type": "string" + }, + "description": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "type": { + "enum": [ + "paste", + "local" + ], + "type": "string" + } + }, + "required": [ + "type" + ], + "type": "object" + }, + "request.WebsiteSearch": { + "properties": { + "name": { + "type": "string" + }, + "order": { + "enum": [ + "null", + "ascending", + "descending" + ], + "type": "string" + }, + "orderBy": { + "enum": [ + "primary_domain", + "type", + "status", + "createdAt", + "expire_date", + "created_at", + "favorite" + ], + "type": "string" + }, + "page": { + "type": "integer" + }, + "pageSize": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "websiteGroupId": { + "type": "integer" + } + }, + "required": [ + "order", + "orderBy", + "page", + "pageSize" + ], + "type": "object" + }, + "request.WebsiteUpdate": { + "properties": { + "IPV6": { + "type": "boolean" + }, + "expireDate": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "primaryDomain": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "webSiteGroupID": { + "type": "integer" + } + }, + "required": [ + "id", + "primaryDomain" + ], + "type": "object" + }, + "request.WebsiteUpdateDir": { + "properties": { + "id": { + "type": "integer" + }, + "siteDir": { + "type": "string" + } + }, + "required": [ + "id", + "siteDir" + ], + "type": "object" + }, + "request.WebsiteUpdateDirPermission": { + "properties": { + "group": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "user": { + "type": "string" + } + }, + "required": [ + "group", + "id", + "user" + ], + "type": "object" + }, + "response.AppConfig": { + "properties": { + "advanced": { + "type": "boolean" + }, + "allowPort": { + "type": "boolean" + }, + "containerName": { + "type": "string" + }, + "cpuQuota": { + "type": "number" + }, + "dockerCompose": { + "type": "string" + }, + "editCompose": { + "type": "boolean" + }, + "gpuConfig": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "memoryLimit": { + "type": "number" + }, + "memoryUnit": { + "type": "string" + }, + "params": { + "items": { + "$ref": "#/definitions/response.AppParam" + }, + "type": "array" + }, + "pullImage": { + "type": "boolean" + }, + "rawCompose": { + "type": "string" + }, + "restartPolicy": { + "enum": [ + "always", + "unless-stopped", + "no", + "on-failure" + ], + "type": "string" + }, + "specifyIP": { + "type": "string" + }, + "type": { + "type": "string" + }, + "webUI": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppDTO": { + "properties": { + "architectures": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "crossVersionUpdate": { + "type": "boolean" + }, + "description": { + "type": "string" + }, + "document": { + "type": "string" + }, + "github": { + "type": "string" + }, + "gpuSupport": { + "type": "boolean" + }, + "icon": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "installed": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "limit": { + "type": "integer" + }, + "memoryRequired": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "readMe": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "required": { + "type": "string" + }, + "requiredPanelVersion": { + "type": "number" + }, + "resource": { + "type": "string" + }, + "shortDescEn": { + "type": "string" + }, + "shortDescZh": { + "type": "string" + }, + "status": { + "type": "string" + }, + "tags": { + "items": { + "$ref": "#/definitions/response.TagDTO" + }, + "type": "array" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "versions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "website": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppDetailDTO": { + "properties": { + "appId": { + "type": "integer" + }, + "architectures": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dockerCompose": { + "type": "string" + }, + "downloadCallBackUrl": { + "type": "string" + }, + "downloadUrl": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "gpuSupport": { + "type": "boolean" + }, + "hostMode": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "lastModified": { + "type": "integer" + }, + "lastVersion": { + "type": "string" + }, + "memoryRequired": { + "type": "integer" + }, + "params": {}, + "status": { + "type": "string" + }, + "update": { + "type": "boolean" + }, + "updatedAt": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppInstalledCheck": { + "properties": { + "app": { + "type": "string" + }, + "appInstallId": { + "type": "integer" + }, + "containerName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "httpPort": { + "type": "integer" + }, + "httpsPort": { + "type": "integer" + }, + "installPath": { + "type": "string" + }, + "isExist": { + "type": "boolean" + }, + "lastBackupAt": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "version": { + "type": "string" + }, + "websiteDir": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppItem": { + "properties": { + "description": { + "type": "string" + }, + "gpuSupport": { + "type": "boolean" + }, + "id": { + "type": "integer" + }, + "installed": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "limit": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "recommend": { + "type": "integer" + }, + "status": { + "type": "string" + }, + "tags": { + "items": { + "type": "string" + }, + "type": "array" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppParam": { + "properties": { + "edit": { + "type": "boolean" + }, + "key": { + "type": "string" + }, + "label": { + "$ref": "#/definitions/dto.Locale" + }, + "labelEn": { + "type": "string" + }, + "labelZh": { + "type": "string" + }, + "multiple": { + "type": "boolean" + }, + "required": { + "type": "boolean" + }, + "rule": { + "type": "string" + }, + "showValue": { + "type": "string" + }, + "type": { + "type": "string" + }, + "value": {}, + "values": {} + }, + "type": "object" + }, + "response.AppRes": { + "properties": { + "items": { + "items": { + "$ref": "#/definitions/response.AppItem" + }, + "type": "array" + }, + "total": { + "type": "integer" + } + }, + "type": "object" + }, + "response.AppService": { + "properties": { + "config": {}, + "from": { + "type": "string" + }, + "label": { + "type": "string" + }, + "status": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "response.AppUpdateRes": { + "properties": { + "appList": { + "$ref": "#/definitions/dto.AppList" + }, + "appStoreLastModified": { + "type": "integer" + }, + "canUpdate": { + "type": "boolean" + }, + "isSyncing": { + "type": "boolean" + } + }, + "type": "object" + }, + "response.CompleteDiskInfo": { + "properties": { + "disks": { + "items": { + "$ref": "#/definitions/response.DiskInfo" + }, + "type": "array" + }, + "systemDisks": { + "items": { + "$ref": "#/definitions/response.DiskInfo" + }, + "type": "array" + }, + "totalCapacity": { + "type": "integer" + }, + "totalDisks": { + "type": "integer" + }, + "unpartitionedDisks": { + "items": { + "$ref": "#/definitions/response.DiskBasicInfo" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.ComponentInfo": { + "properties": { + "error": { + "type": "string" + }, + "exists": { + "type": "boolean" + }, + "path": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "response.Database": { + "properties": { + "databaseName": { + "type": "string" + }, + "from": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "response.DatabaseConn": { + "properties": { + "containerName": { + "type": "string" + }, + "password": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "serviceName": { + "type": "string" + }, + "status": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "response.DepthDirSizeRes": { + "properties": { + "path": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object" + }, + "response.DirSizeRes": { + "properties": { + "size": { + "type": "integer" + } + }, + "required": [ + "size" + ], + "type": "object" + }, + "response.DiskBasicInfo": { + "properties": { + "avail": { + "type": "string" + }, + "device": { + "type": "string" + }, + "diskType": { + "type": "string" + }, + "filesystem": { + "type": "string" + }, + "isMounted": { + "type": "boolean" + }, + "isRemovable": { + "type": "boolean" + }, + "isSystem": { + "type": "boolean" + }, + "model": { + "type": "string" + }, + "mountPoint": { + "type": "string" + }, + "serial": { + "type": "string" + }, + "size": { + "type": "string" + }, + "usePercent": { + "type": "integer" + }, + "used": { + "type": "string" + } + }, + "type": "object" + }, + "response.DiskInfo": { + "properties": { + "avail": { + "type": "string" + }, + "device": { + "type": "string" + }, + "diskType": { + "type": "string" + }, + "filesystem": { + "type": "string" + }, + "isMounted": { + "type": "boolean" + }, + "isRemovable": { + "type": "boolean" + }, + "isSystem": { + "type": "boolean" + }, + "model": { + "type": "string" + }, + "mountPoint": { + "type": "string" + }, + "partitions": { + "items": { + "$ref": "#/definitions/response.DiskBasicInfo" + }, + "type": "array" + }, + "serial": { + "type": "string" + }, + "size": { + "type": "string" + }, + "usePercent": { + "type": "integer" + }, + "used": { + "type": "string" + } + }, + "type": "object" + }, + "response.ExistFileInfo": { + "properties": { + "isDir": { + "type": "boolean" + }, + "modTime": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "size": { + "type": "integer" + } + }, + "type": "object" + }, + "response.FileInfo": { + "properties": { + "content": { + "type": "string" + }, + "extension": { + "type": "string" + }, + "favoriteID": { + "type": "integer" + }, + "gid": { + "type": "string" + }, + "group": { + "type": "string" + }, + "isDetail": { + "type": "boolean" + }, + "isDir": { + "type": "boolean" + }, + "isHidden": { + "type": "boolean" + }, + "isSymlink": { + "type": "boolean" + }, + "itemTotal": { + "type": "integer" + }, + "items": { + "items": { + "$ref": "#/definitions/files.FileInfo" + }, + "type": "array" + }, + "linkPath": { + "type": "string" + }, + "mimeType": { + "type": "string" + }, + "modTime": { + "type": "string" + }, + "mode": { + "type": "string" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "size": { + "type": "integer" + }, + "type": { + "type": "string" + }, + "uid": { + "type": "string" + }, + "updateTime": { + "type": "string" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "response.FileLineContent": { + "properties": { + "end": { + "type": "boolean" + }, + "lines": { + "items": { + "type": "string" + }, + "type": "array" + }, + "path": { + "type": "string" + }, + "scope": { + "type": "string" + }, + "taskStatus": { + "type": "string" + }, + "total": { + "type": "integer" + }, + "totalLines": { + "type": "integer" + } + }, + "type": "object" + }, + "response.FileTree": { + "properties": { + "children": { + "items": { + "$ref": "#/definitions/response.FileTree" + }, + "type": "array" + }, + "extension": { + "type": "string" + }, + "id": { + "type": "string" + }, + "isDir": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + } + }, + "type": "object" + }, + "response.FileWgetRes": { + "properties": { + "key": { + "type": "string" + } + }, + "type": "object" + }, + "response.HostToolConfig": { + "properties": { + "content": { + "type": "string" + } + }, + "type": "object" + }, + "response.HostToolRes": { + "properties": { + "config": {}, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "response.McpBindDomainRes": { + "properties": { + "acmeAccountID": { + "type": "integer" + }, + "allowIPs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "connUrl": { + "type": "string" + }, + "domain": { + "type": "string" + }, + "sslID": { + "type": "integer" + }, + "websiteID": { + "type": "integer" + } + }, + "type": "object" + }, + "response.McpServerDTO": { + "properties": { + "baseUrl": { + "type": "string" + }, + "command": { + "type": "string" + }, + "containerName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "dockerCompose": { + "type": "string" + }, + "env": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "hostIP": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "outputTransport": { + "type": "string" + }, + "port": { + "type": "integer" + }, + "ssePath": { + "type": "string" + }, + "status": { + "type": "string" + }, + "streamableHttpPath": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + }, + "websiteID": { + "type": "integer" + } + }, + "type": "object" + }, + "response.McpServersRes": { + "properties": { + "items": { + "items": { + "$ref": "#/definitions/response.McpServerDTO" + }, + "type": "array" + }, + "total": { + "type": "integer" + } + }, + "type": "object" + }, + "response.NginxAntiLeechRes": { + "properties": { + "blocked": { + "type": "boolean" + }, + "cache": { + "type": "boolean" + }, + "cacheTime": { + "type": "integer" + }, + "cacheUint": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "extends": { + "type": "string" + }, + "logEnable": { + "type": "boolean" + }, + "noneRef": { + "type": "boolean" + }, + "return": { + "type": "string" + }, + "serverNames": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.NginxAuthRes": { + "properties": { + "enable": { + "type": "boolean" + }, + "items": { + "items": { + "$ref": "#/definitions/dto.NginxAuth" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.NginxBuildConfig": { + "properties": { + "mirror": { + "type": "string" + }, + "modules": { + "items": { + "$ref": "#/definitions/response.NginxModule" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.NginxConfigRes": { + "properties": { + "https": { + "type": "boolean" + }, + "sslRejectHandshake": { + "type": "boolean" + } + }, + "type": "object" + }, + "response.NginxFile": { + "properties": { + "content": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxModule": { + "properties": { + "enable": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "packages": { + "type": "string" + }, + "params": { + "type": "string" + }, + "script": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxParam": { + "properties": { + "name": { + "type": "string" + }, + "params": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.NginxPathAuthRes": { + "properties": { + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxProxyCache": { + "properties": { + "cacheExpire": { + "type": "integer" + }, + "cacheExpireUnit": { + "type": "string" + }, + "cacheLimit": { + "type": "number" + }, + "cacheLimitUnit": { + "type": "string" + }, + "open": { + "type": "boolean" + }, + "shareCache": { + "type": "integer" + }, + "shareCacheUnit": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxRedirectConfig": { + "properties": { + "content": { + "type": "string" + }, + "domains": { + "items": { + "type": "string" + }, + "type": "array" + }, + "enable": { + "type": "boolean" + }, + "filePath": { + "type": "string" + }, + "keepPath": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "path": { + "type": "string" + }, + "redirect": { + "type": "string" + }, + "redirectRoot": { + "type": "boolean" + }, + "target": { + "type": "string" + }, + "type": { + "type": "string" + }, + "websiteID": { + "type": "integer" + } + }, + "type": "object" + }, + "response.NginxRewriteRes": { + "properties": { + "content": { + "type": "string" + } + }, + "type": "object" + }, + "response.NginxStatus": { + "properties": { + "accepts": { + "type": "integer" + }, + "active": { + "type": "integer" + }, + "handled": { + "type": "integer" + }, + "reading": { + "type": "integer" + }, + "requests": { + "type": "integer" + }, + "waiting": { + "type": "integer" + }, + "writing": { + "type": "integer" + } + }, + "type": "object" + }, + "response.NodeModule": { + "properties": { + "description": { + "type": "string" + }, + "license": { + "type": "string" + }, + "name": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "response.PHPConfig": { + "properties": { + "disableFunctions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "maxExecutionTime": { + "type": "string" + }, + "params": { + "additionalProperties": { + "type": "string" + }, + "type": "object" + }, + "uploadMaxSize": { + "type": "string" + } + }, + "type": "object" + }, + "response.PHPExtensionRes": { + "properties": { + "extensions": { + "items": { + "type": "string" + }, + "type": "array" + }, + "supportExtensions": { + "items": { + "$ref": "#/definitions/response.SupportExtension" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.PackageScripts": { + "properties": { + "name": { + "type": "string" + }, + "script": { + "type": "string" + } + }, + "type": "object" + }, + "response.ProcessStatus": { + "properties": { + "PID": { + "type": "string" + }, + "msg": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "uptime": { + "type": "string" + } + }, + "type": "object" + }, + "response.Resource": { + "properties": { + "detail": {}, + "name": { + "type": "string" + }, + "resourceID": { + "type": "integer" + }, + "type": { + "type": "string" + } + }, + "type": "object" + }, + "response.RuntimeDTO": { + "properties": { + "appDetailID": { + "type": "integer" + }, + "appID": { + "type": "integer" + }, + "appParams": { + "items": { + "$ref": "#/definitions/response.AppParam" + }, + "type": "array" + }, + "codeDir": { + "type": "string" + }, + "container": { + "type": "string" + }, + "containerStatus": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "environments": { + "items": { + "$ref": "#/definitions/request.Environment" + }, + "type": "array" + }, + "exposedPorts": { + "items": { + "$ref": "#/definitions/request.ExposedPort" + }, + "type": "array" + }, + "extraHosts": { + "items": { + "$ref": "#/definitions/request.ExtraHost" + }, + "type": "array" + }, + "id": { + "type": "integer" + }, + "image": { + "type": "string" + }, + "message": { + "type": "string" + }, + "name": { + "type": "string" + }, + "params": { + "additionalProperties": true, + "type": "object" + }, + "path": { + "type": "string" + }, + "port": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "resource": { + "type": "string" + }, + "source": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "version": { + "type": "string" + }, + "volumes": { + "items": { + "$ref": "#/definitions/request.Volume" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.SupervisorProcessConfig": { + "properties": { + "autoRestart": { + "type": "string" + }, + "autoStart": { + "type": "string" + }, + "command": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "msg": { + "type": "string" + }, + "name": { + "type": "string" + }, + "numprocs": { + "type": "string" + }, + "status": { + "items": { + "$ref": "#/definitions/response.ProcessStatus" + }, + "type": "array" + }, + "user": { + "type": "string" + } + }, + "type": "object" + }, + "response.SupportExtension": { + "properties": { + "check": { + "type": "string" + }, + "description": { + "type": "string" + }, + "file": { + "type": "string" + }, + "installed": { + "type": "boolean" + }, + "name": { + "type": "string" + }, + "versions": { + "items": { + "type": "string" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.TagDTO": { + "properties": { + "id": { + "type": "integer" + }, + "key": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "type": "object" + }, + "response.UserGroupResponse": { + "properties": { + "groups": { + "items": { + "type": "string" + }, + "type": "array" + }, + "users": { + "items": { + "$ref": "#/definitions/response.UserInfo" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.UserInfo": { + "properties": { + "group": { + "type": "string" + }, + "username": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteAcmeAccountDTO": { + "properties": { + "caDirURL": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "eabHmacKey": { + "type": "string" + }, + "eabKid": { + "type": "string" + }, + "email": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "keyType": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "url": { + "type": "string" + }, + "useProxy": { + "type": "boolean" + } + }, + "type": "object" + }, + "response.WebsiteCADTO": { + "properties": { + "city": { + "type": "string" + }, + "commonName": { + "type": "string" + }, + "country": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "csr": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "keyType": { + "type": "string" + }, + "name": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "organizationUint": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "province": { + "type": "string" + }, + "updatedAt": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteDNSRes": { + "properties": { + "domain": { + "type": "string" + }, + "err": { + "type": "string" + }, + "resolve": { + "type": "string" + }, + "value": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteDTO": { + "properties": { + "IPV6": { + "type": "boolean" + }, + "accessLog": { + "type": "boolean" + }, + "accessLogPath": { + "type": "string" + }, + "algorithm": { + "type": "string" + }, + "alias": { + "type": "string" + }, + "appInstallId": { + "type": "integer" + }, + "appName": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dbID": { + "type": "integer" + }, + "dbType": { + "type": "string" + }, + "defaultServer": { + "type": "boolean" + }, + "domains": { + "items": { + "$ref": "#/definitions/model.WebsiteDomain" + }, + "type": "array" + }, + "errorLog": { + "type": "boolean" + }, + "errorLogPath": { + "type": "string" + }, + "expireDate": { + "type": "string" + }, + "favorite": { + "type": "boolean" + }, + "ftpId": { + "type": "integer" + }, + "group": { + "type": "string" + }, + "httpConfig": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "openBaseDir": { + "type": "boolean" + }, + "parentWebsiteID": { + "type": "integer" + }, + "primaryDomain": { + "type": "string" + }, + "protocol": { + "type": "string" + }, + "proxy": { + "type": "string" + }, + "proxyType": { + "type": "string" + }, + "remark": { + "type": "string" + }, + "rewrite": { + "type": "string" + }, + "runtimeID": { + "type": "integer" + }, + "runtimeName": { + "type": "string" + }, + "runtimeType": { + "type": "string" + }, + "servers": { + "items": { + "$ref": "#/definitions/dto.NginxUpstreamServer" + }, + "type": "array" + }, + "siteDir": { + "type": "string" + }, + "sitePath": { + "type": "string" + }, + "status": { + "type": "string" + }, + "streamPorts": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "user": { + "type": "string" + }, + "webSiteGroupId": { + "type": "integer" + }, + "webSiteSSL": { + "$ref": "#/definitions/model.WebsiteSSL" + }, + "webSiteSSLId": { + "type": "integer" + } + }, + "type": "object" + }, + "response.WebsiteDirConfig": { + "properties": { + "dirs": { + "items": { + "type": "string" + }, + "type": "array" + }, + "msg": { + "type": "string" + }, + "user": { + "type": "string" + }, + "userGroup": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteHTTPS": { + "properties": { + "SSL": { + "$ref": "#/definitions/model.WebsiteSSL" + }, + "SSLProtocol": { + "items": { + "type": "string" + }, + "type": "array" + }, + "algorithm": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "hsts": { + "type": "boolean" + }, + "hstsIncludeSubDomains": { + "type": "boolean" + }, + "http3": { + "type": "boolean" + }, + "httpConfig": { + "type": "string" + }, + "httpsPort": { + "type": "string" + }, + "httpsPorts": { + "items": { + "type": "integer" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.WebsiteHtmlRes": { + "properties": { + "content": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteLog": { + "properties": { + "content": { + "type": "string" + }, + "enable": { + "type": "boolean" + }, + "end": { + "type": "boolean" + }, + "path": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteNginxConfig": { + "properties": { + "enable": { + "type": "boolean" + }, + "params": { + "items": { + "$ref": "#/definitions/response.NginxParam" + }, + "type": "array" + } + }, + "type": "object" + }, + "response.WebsiteOption": { + "properties": { + "alias": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "primaryDomain": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsitePreInstallCheck": { + "properties": { + "appName": { + "type": "string" + }, + "name": { + "type": "string" + }, + "status": { + "type": "string" + }, + "version": { + "type": "string" + } + }, + "type": "object" + }, + "response.WebsiteRealIP": { + "properties": { + "ipFrom": { + "type": "string" + }, + "ipHeader": { + "type": "string" + }, + "ipOther": { + "type": "string" + }, + "open": { + "type": "boolean" + }, + "websiteID": { + "type": "integer" + } + }, + "required": [ + "websiteID" + ], + "type": "object" + }, + "response.WebsiteSSLDTO": { + "properties": { + "acmeAccount": { + "$ref": "#/definitions/model.WebsiteAcmeAccount" + }, + "acmeAccountId": { + "type": "integer" + }, + "autoRenew": { + "type": "boolean" + }, + "caId": { + "type": "integer" + }, + "certPath": { + "type": "string" + }, + "certURL": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "description": { + "type": "string" + }, + "dir": { + "type": "string" + }, + "disableCNAME": { + "type": "boolean" + }, + "dnsAccount": { + "$ref": "#/definitions/model.WebsiteDnsAccount" + }, + "dnsAccountId": { + "type": "integer" + }, + "domains": { + "type": "string" + }, + "execShell": { + "type": "boolean" + }, + "expireDate": { + "type": "string" + }, + "id": { + "type": "integer" + }, + "isIP": { + "type": "boolean" + }, + "keyType": { + "type": "string" + }, + "logPath": { + "type": "string" + }, + "masterSslId": { + "type": "integer" + }, + "message": { + "type": "string" + }, + "nameserver1": { + "type": "string" + }, + "nameserver2": { + "type": "string" + }, + "nodes": { + "type": "string" + }, + "organization": { + "type": "string" + }, + "pem": { + "type": "string" + }, + "primaryDomain": { + "type": "string" + }, + "privateKey": { + "type": "string" + }, + "privateKeyPath": { + "type": "string" + }, + "provider": { + "type": "string" + }, + "pushDir": { + "type": "boolean" + }, + "pushNode": { + "type": "boolean" + }, + "shell": { + "type": "string" + }, + "skipDNS": { + "type": "boolean" + }, + "startDate": { + "type": "string" + }, + "status": { + "type": "string" + }, + "type": { + "type": "string" + }, + "updatedAt": { + "type": "string" + }, + "websites": { + "items": { + "$ref": "#/definitions/model.Website" + }, + "type": "array" + } + }, + "type": "object" + } + } +} \ No newline at end of file diff --git a/core/cmd/server/docs/swagger_test.go b/core/cmd/server/docs/swagger_test.go new file mode 100644 index 0000000..9f92254 --- /dev/null +++ b/core/cmd/server/docs/swagger_test.go @@ -0,0 +1,206 @@ +package docs + +import ( + "encoding/json" + "fmt" + "go/ast" + "go/parser" + "go/token" + "os" + "os/exec" + "path" + "strings" + "testing" +) + +func TestGenerateXlog(t *testing.T) { + workDir := "/usr/songliu/dev-v2/1Panel" + fset := token.NewFileSet() + + apiDirs := []string{workDir + "/agent/app/api/v2", workDir + "/core/app/api/v2", workDir + "/agent/xpack/app/api/v2", workDir + "/core/xpack/app/api/v2"} + + xlogMap := make(map[string]operationJson) + for _, dir := range apiDirs { + entries, _ := os.ReadDir(dir) + for _, info := range entries { + if info.IsDir() { + continue + } + fileItem, err := parser.ParseFile(fset, path.Join(dir, info.Name()), nil, parser.ParseComments) + if err != nil { + continue + } + for _, decl := range fileItem.Decls { + switch d := decl.(type) { + case *ast.FuncDecl: + if d.Doc != nil { + routerContent := "" + logContent := "" + for _, comment := range d.Doc.List { + if strings.HasPrefix(comment.Text, "// @Router") { + routerContent = replaceStr(comment.Text, "// @Router", "[post]", "[get]", " ") + } + if strings.HasPrefix(comment.Text, "// @x-panel-log") { + logContent = replaceStr(comment.Text, "// @x-panel-log", " ") + } + } + if len(routerContent) != 0 && len(logContent) != 0 { + var item operationJson + if err := json.Unmarshal([]byte(logContent), &item); err != nil { + panic(fmt.Sprintf("json unamrshal failed, err: %v", err)) + } + xlogMap[routerContent] = item + } + } + } + } + } + } + + newJson, err := json.MarshalIndent(xlogMap, "", "\t") + if err != nil { + panic(fmt.Sprintf("json marshal for new file failed, err: %v", err)) + } + if err := os.WriteFile("x-log.json", newJson, 0640); err != nil { + panic(fmt.Sprintf("write new swagger.json failed, err: %v", err)) + } +} + +func TestGenerateSwaggerDoc(t *testing.T) { + workDir := "/usr/songliu/dev-v2/1Panel" + swagBin := "/root/go/bin/swag" + + cmd1 := exec.Command(swagBin, "init", "-o", workDir+"/core/cmd/server/docs/docs_agent", "-d", workDir+"/agent", "-g", "../agent/cmd/server/main.go") + cmd1.Dir = workDir + std1, err := cmd1.CombinedOutput() + if err != nil { + fmt.Printf("generate swagger doc of agent failed, std1: %v, err: %v", string(std1), err) + return + } + cmd2 := exec.Command(swagBin, "init", "-o", workDir+"/core/cmd/server/docs/docs_core", "-d", workDir+"/core", "-g", "./cmd/server/main.go") + cmd2.Dir = workDir + std2, err := cmd2.CombinedOutput() + if err != nil { + fmt.Printf("generate swagger doc of core failed, std1: %v, err: %v", string(std2), err) + return + } + + agentJson := workDir + "/core/cmd/server/docs/docs_agent/swagger.json" + agentFile, err := os.ReadFile(agentJson) + if err != nil { + fmt.Printf("read file docs_agent failed, err: %v", err) + return + } + var agentSwagger Swagger + if err := json.Unmarshal(agentFile, &agentSwagger); err != nil { + fmt.Printf("agent json unmarshal failed, err: %v", err) + return + } + + coreJson := workDir + "/core/cmd/server/docs/docs_core/swagger.json" + coreFile, err := os.ReadFile(coreJson) + if err != nil { + fmt.Printf("read file docs_core failed, err: %v", err) + return + } + var coreSwagger Swagger + if err := json.Unmarshal(coreFile, &coreSwagger); err != nil { + fmt.Printf("core json unmarshal failed, err: %v", err) + return + } + + newSwagger := Swagger{ + Swagger: agentSwagger.Swagger, + Info: agentSwagger.Info, + Host: agentSwagger.Host, + BasePath: agentSwagger.BasePath, + Paths: agentSwagger.Paths, + Definitions: agentSwagger.Definitions, + } + + for key, val := range coreSwagger.Paths { + if _, ok := newSwagger.Paths[key]; !ok { + newSwagger.Paths[key] = val + } + } + for key, val := range coreSwagger.Definitions { + if _, ok := newSwagger.Definitions[key]; !ok { + newSwagger.Definitions[key] = val + } + } + + newJson, err := json.MarshalIndent(newSwagger, "", "\t") + if err != nil { + fmt.Printf("json marshal for new file failed, err: %v", err) + return + } + if err := os.WriteFile("swagger.json", newJson, 0640); err != nil { + fmt.Printf("write new swagger.json failed, err: %v", err) + return + } + docTemplate := strings.ReplaceAll(loadDefaultDocs(), "const docTemplate = \"aa\"", fmt.Sprintf("const docTemplate = `%s`", string(newJson))) + if err := os.WriteFile(workDir+"/core/cmd/server/docs/docs.go", []byte(docTemplate), 0640); err != nil { + fmt.Printf("write new docs.go failed, err: %v", err) + return + } + + _ = os.RemoveAll(workDir + "/core/cmd/server/docs/docs_agent") + _ = os.RemoveAll(workDir + "/core/cmd/server/docs/docs_core") +} + +type Swagger struct { + Swagger string `json:"swagger"` + Info interface{} `json:"info"` + Host string `json:"host"` + BasePath string `json:"basePath"` + Paths map[string]interface{} `json:"paths"` + Definitions map[string]interface{} `json:"definitions"` +} + +func loadDefaultDocs() string { + return `package docs + +import "github.com/swaggo/swag" + +const docTemplate = "aa" + +var SwaggerInfo = &swag.Spec{ + Version: "2.0", + Host: "localhost", + BasePath: "/api/v2", + Schemes: []string{}, + Title: "1Panel", + Description: "Top-Rated Web-based Linux Server Management Tool", + InfoInstanceName: "swagger", + SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", +} + +func init() { + swag.Register(SwaggerInfo.InstanceName(), SwaggerInfo) +}` +} + +func replaceStr(val string, rep ...string) string { + for _, item := range rep { + val = strings.ReplaceAll(val, item, "") + } + return val +} + +type operationJson struct { + BodyKeys []string `json:"bodyKeys"` + ParamKeys []string `json:"paramKeys"` + BeforeFunctions []functionInfo `json:"beforeFunctions"` + FormatZH string `json:"formatZH"` + FormatEN string `json:"formatEN"` +} +type functionInfo struct { + InputColumn string `json:"input_column"` + InputValue string `json:"input_value"` + IsList bool `json:"isList"` + DB string `json:"db"` + OutputColumn string `json:"output_column"` + OutputValue string `json:"output_value"` +} diff --git a/core/cmd/server/docs/x-log.json b/core/cmd/server/docs/x-log.json new file mode 100644 index 0000000..2117d68 --- /dev/null +++ b/core/cmd/server/docs/x-log.json @@ -0,0 +1,3552 @@ +{ + "/ai/ollama/close": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "关闭Ollama模型连接[name]", + "formatEN": "closeconnforOllamamodel[name]" + }, + "/ai/ollama/model": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加Ollama模型[name]", + "formatEN": "addOllamamodel[name]" + }, + "/ai/ollama/model/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "ollama_models", + "output_column": "name", + "output_value": "names" + } + ], + "formatZH": "删除Ollama模型[names]", + "formatEN": "removeOllamamodel[names]" + }, + "/ai/ollama/model/recreate": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加Ollama模型重试[name]", + "formatEN": "re-addOllamamodel[name]" + }, + "/ai/ollama/model/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步Ollama模型列表", + "formatEN": "syncOllamamodellist" + }, + "/apps/ignored/cancel": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "取消忽略应用升级", + "formatEN": "Cancelignoreapplicationupgrade" + }, + "/apps/install": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "安装应用[name]", + "formatEN": "Installapp[name]" + }, + "/apps/installed/config/update": { + "bodyKeys": [ + "installID", + "webUI" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "应用配置更新[installID]", + "formatEN": "Applicationconfigupdate[installID]" + }, + "/apps/installed/ignore": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "忽略应用升级", + "formatEN": "Ignoreapplicationupgrade" + }, + "/apps/installed/op": { + "bodyKeys": [ + "installId", + "operate" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "installId", + "isList": false, + "db": "app_installs", + "output_column": "app_id", + "output_value": "appId" + }, + { + "input_column": "id", + "input_value": "installId", + "isList": false, + "db": "app_installs", + "output_column": "name", + "output_value": "appName" + }, + { + "input_column": "id", + "input_value": "appId", + "isList": false, + "db": "apps", + "output_column": "key", + "output_value": "appKey" + } + ], + "formatZH": "[operate]应用[appKey][appName]", + "formatEN": "[operate]App[appKey][appName]" + }, + "/apps/installed/params/update": { + "bodyKeys": [ + "installId" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "应用参数修改[installId]", + "formatEN": "Applicationparamupdate[installId]" + }, + "/apps/installed/port/change": { + "bodyKeys": [ + "key", + "name", + "port" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "应用端口修改[key]-[name]=\u003e[port]", + "formatEN": "Applicationportupdate[key]-[name]=\u003e[port]" + }, + "/apps/installed/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步已安装应用列表", + "formatEN": "Syncthelistofinstalledapps" + }, + "/apps/sync/local": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "应用商店同步", + "formatEN": "Appstoresynchronization" + }, + "/apps/sync/remote": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "应用商店同步", + "formatEN": "Appstoresynchronization" + }, + "/backups": { + "bodyKeys": [ + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建备份账号[type]", + "formatEN": "createbackupaccount[type]" + }, + "/backups/backup": { + "bodyKeys": [ + "type", + "name", + "detailName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "备份[type]数据[name][detailName]", + "formatEN": "backup[type]data[name][detailName]" + }, + "/backups/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "backup_accounts", + "output_column": "type", + "output_value": "types" + } + ], + "formatZH": "删除备份账号[types]", + "formatEN": "deletebackupaccount[types]" + }, + "/backups/record/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "backup_records", + "output_column": "file_name", + "output_value": "files" + } + ], + "formatZH": "删除备份记录[files]", + "formatEN": "deletebackuprecords[files]" + }, + "/backups/record/download": { + "bodyKeys": [ + "source", + "fileName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "下载备份记录[source][fileName]", + "formatEN": "downloadbackuprecords[source][fileName]" + }, + "/backups/recover": { + "bodyKeys": [ + "type", + "name", + "detailName", + "file" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "从[file]恢复[type]数据[name][detailName]", + "formatEN": "recover[type]data[name][detailName]from[file]" + }, + "/backups/recover/byupload": { + "bodyKeys": [ + "type", + "name", + "detailName", + "file" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "从[file]恢复[type]数据[name][detailName]", + "formatEN": "recover[type]data[name][detailName]from[file]" + }, + "/backups/update": { + "bodyKeys": [ + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新备份账号[types]", + "formatEN": "updatebackupaccount[types]" + }, + "/backups/upload": { + "bodyKeys": [ + "filePath" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "上传备份文件[filePath]", + "formatEN": "uploadbackupfile[filePath]" + }, + "/containers": { + "bodyKeys": [ + "name", + "image" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建容器[name][image]", + "formatEN": "createcontainer[name][image]" + }, + "/containers/clean/log": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清理容器[name]日志", + "formatEN": "cleancontainer[name]logs" + }, + "/containers/compose": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建compose[name]", + "formatEN": "createcompose[name]" + }, + "/containers/compose/clean/log": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清理容器编排[name]日志", + "formatEN": "cleancompose[name]logs" + }, + "/containers/compose/operate": { + "bodyKeys": [ + "name", + "operation" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "compose[operation][name]", + "formatEN": "compose[operation][name]" + }, + "/containers/compose/test": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "检测compose[name]格式", + "formatEN": "checkcompose[name]" + }, + "/containers/compose/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新compose[name]", + "formatEN": "updatecomposeinformation[name]" + }, + "/containers/daemonjson/update": { + "bodyKeys": [ + "key", + "value" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新配置[key]", + "formatEN": "Updatedconfiguration[key]" + }, + "/containers/daemonjson/update/byfile": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新配置文件", + "formatEN": "Updatedconfigurationfile" + }, + "/containers/docker/operate": { + "bodyKeys": [ + "operation" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "docker服务[operation]", + "formatEN": "[operation]dockerservice" + }, + "/containers/image/build": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "构建镜像[name]", + "formatEN": "buildimage[name]" + }, + "/containers/image/load": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "从[path]加载镜像", + "formatEN": "loadimagefrom[path]" + }, + "/containers/image/pull": { + "bodyKeys": [ + "repoID", + "imageName" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "repoID", + "isList": false, + "db": "image_repos", + "output_column": "name", + "output_value": "reponame" + } + ], + "formatZH": "镜像拉取[reponame][imageName]", + "formatEN": "imagepull[reponame][imageName]" + }, + "/containers/image/push": { + "bodyKeys": [ + "repoID", + "tagName", + "name" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "repoID", + "isList": false, + "db": "image_repos", + "output_column": "name", + "output_value": "reponame" + } + ], + "formatZH": "[tagName]推送到[reponame][name]", + "formatEN": "push[tagName]to[reponame][name]" + }, + "/containers/image/remove": { + "bodyKeys": [ + "names" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "移除镜像[names]", + "formatEN": "removeimage[names]" + }, + "/containers/image/save": { + "bodyKeys": [ + "tagName", + "path", + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "保留[tagName]为[path]/[name]", + "formatEN": "save[tagName]as[path]/[name]" + }, + "/containers/image/tag": { + "bodyKeys": [ + "repoID", + "targetName" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "repoID", + "isList": false, + "db": "image_repos", + "output_column": "name", + "output_value": "reponame" + } + ], + "formatZH": "tag镜像[reponame][targetName]", + "formatEN": "tagimage[reponame][targetName]" + }, + "/containers/ipv6option/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新ipv6配置", + "formatEN": "Updatedtheipv6option" + }, + "/containers/logoption/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新日志配置", + "formatEN": "Updatedthelogoption" + }, + "/containers/network": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建容器网络name", + "formatEN": "createcontainernetwork[name]" + }, + "/containers/network/del": { + "bodyKeys": [ + "names" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除容器网络[names]", + "formatEN": "deletecontainernetwork[names]" + }, + "/containers/operate": { + "bodyKeys": [ + "names", + "operation" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "容器[names]执行[operation]", + "formatEN": "container[operation][names]" + }, + "/containers/prune": { + "bodyKeys": [ + "pruneType" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清理容器[pruneType]", + "formatEN": "cleancontainer[pruneType]" + }, + "/containers/rename": { + "bodyKeys": [ + "name", + "newName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "容器重命名[name]=\u003e[newName]", + "formatEN": "renamecontainer[name]=\u003e[newName]" + }, + "/containers/repo": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建镜像仓库[name]", + "formatEN": "createimagerepo[name]" + }, + "/containers/repo/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "image_repos", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除镜像仓库[name]", + "formatEN": "deleteimagerepo[name]" + }, + "/containers/repo/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "image_repos", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新镜像仓库[name]", + "formatEN": "updateimagerepoinformation[name]" + }, + "/containers/template": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建compose模版[name]", + "formatEN": "createcomposetemplate[name]" + }, + "/containers/template/batch": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "批量导入编排模版", + "formatEN": "batchimportcomposetemplates" + }, + "/containers/template/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "compose_templates", + "output_column": "name", + "output_value": "names" + } + ], + "formatZH": "删除compose模版[names]", + "formatEN": "deletecomposetemplate[names]" + }, + "/containers/template/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "compose_templates", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新compose模版[name]", + "formatEN": "updatecomposetemplateinformation[name]" + }, + "/containers/update": { + "bodyKeys": [ + "name", + "image" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新容器[name][image]", + "formatEN": "updatecontainer[name][image]" + }, + "/containers/upgrade": { + "bodyKeys": [ + "names", + "image" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新容器镜像[names][image]", + "formatEN": "upgradecontainerimage[names][image]" + }, + "/containers/volume": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建容器存储卷[name]", + "formatEN": "createcontainervolume[name]" + }, + "/containers/volume/del": { + "bodyKeys": [ + "names" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除容器存储卷[names]", + "formatEN": "deletecontainervolume[names]" + }, + "/core/backups": { + "bodyKeys": [ + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建备份账号[type]", + "formatEN": "createbackupaccount[type]" + }, + "/core/backups/del": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除备份账号[name]", + "formatEN": "deletebackupaccount[name]" + }, + "/core/backups/update": { + "bodyKeys": [ + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新备份账号[types]", + "formatEN": "updatebackupaccount[types]" + }, + "/core/commands": { + "bodyKeys": [ + "name", + "command" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建快捷命令[name][command]", + "formatEN": "createquickcommand[name][command]" + }, + "/core/commands/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "commands", + "output_column": "name", + "output_value": "names" + } + ], + "formatZH": "删除快捷命令[names]", + "formatEN": "deletequickcommand[names]" + }, + "/core/commands/export": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "导出快速命令", + "formatEN": "exportquickcommands" + }, + "/core/commands/import": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "导入快速命令", + "formatEN": "importquickcommands" + }, + "/core/commands/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新快捷命令[name]", + "formatEN": "updatequickcommand[name]" + }, + "/core/groups": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建组[name][type]", + "formatEN": "creategroup[name][type]" + }, + "/core/groups/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "groups", + "output_column": "name", + "output_value": "name" + }, + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "groups", + "output_column": "type", + "output_value": "type" + } + ], + "formatZH": "删除组[type][name]", + "formatEN": "deletegroup[type][name]" + }, + "/core/groups/update": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新组[name][type]", + "formatEN": "updategroup[name][type]" + }, + "/core/hosts": { + "bodyKeys": [ + "name", + "addr" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建主机[name][addr]", + "formatEN": "createhost[name][addr]" + }, + "/core/hosts/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "hosts", + "output_column": "addr", + "output_value": "addrs" + } + ], + "formatZH": "删除主机[addrs]", + "formatEN": "deletehost[addrs]" + }, + "/core/hosts/update": { + "bodyKeys": [ + "name", + "addr" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新主机信息[name][addr]", + "formatEN": "updatehost[name][addr]" + }, + "/core/hosts/update/group": { + "bodyKeys": [ + "id", + "group" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "hosts", + "output_column": "addr", + "output_value": "addr" + } + ], + "formatZH": "切换主机[addr]分组=\u003e[group]", + "formatEN": "changehost[addr]group=\u003e[group]" + }, + "/core/licenses/add": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加许可证", + "formatEN": "importlicense" + }, + "/core/licenses/bind": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "绑定许可证", + "formatEN": "bindlicense" + }, + "/core/licenses/bind/free": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改许可证免费节点绑定", + "formatEN": "changebindoffreenodeforlicense" + }, + "/core/licenses/del": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除许可证", + "formatEN": "deletelicense" + }, + "/core/licenses/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步许可证信息", + "formatEN": "synclicenseinfo" + }, + "/core/licenses/unbind": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "解绑许可证", + "formatEN": "unbindlicense" + }, + "/core/licenses/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "替换许可证", + "formatEN": "changelicense" + }, + "/core/logs/clean": { + "bodyKeys": [ + "logType" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清空[logType]日志信息", + "formatEN": "Cleanthe[logType]loginformation" + }, + "/core/script": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加脚本库脚本[name]", + "formatEN": "addscript[name]" + }, + "/core/script/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "script_librarys", + "output_column": "name", + "output_value": "names" + } + ], + "formatZH": "删除脚本库脚本[names]", + "formatEN": "deletescript[names]" + }, + "/core/script/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步脚本库脚本", + "formatEN": "syncscripts" + }, + "/core/script/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "cronjobs", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新脚本库脚本[name]", + "formatEN": "updatescript[name]" + }, + "/core/settings/api/config/generate/key": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "生成API接口密钥", + "formatEN": "generateapikey" + }, + "/core/settings/api/config/update": { + "bodyKeys": [ + "ipWhiteList" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新API接口配置=\u003eIP白名单:[ipWhiteList]", + "formatEN": "updateapiconfig=\u003eIPWhiteList:[ipWhiteList]" + }, + "/core/settings/bind/update": { + "bodyKeys": [ + "ipv6", + "bindAddress" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改系统监听信息=\u003eipv6:[ipv6],监听IP:[bindAddress]", + "formatEN": "updatesystembindinfo=\u003eipv6:[ipv6],监听IP:[bindAddress]" + }, + "/core/settings/expired/handle": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "重置过期密码", + "formatEN": "resetanexpiredPassword" + }, + "/core/settings/menu/default": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "初始化菜单", + "formatEN": "Initmenu." + }, + "/core/settings/menu/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "隐藏高级功能菜单", + "formatEN": "Hideadvancedfeaturemenu." + }, + "/core/settings/mfa/bind": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "mfa绑定", + "formatEN": "bindmfa" + }, + "/core/settings/password/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改系统密码", + "formatEN": "updatesystempassword" + }, + "/core/settings/port/update": { + "bodyKeys": [ + "serverPort" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改系统端口=\u003e[serverPort]", + "formatEN": "updatesystemport=\u003e[serverPort]" + }, + "/core/settings/proxy/update": { + "bodyKeys": [ + "proxyUrl", + "proxyPort" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "服务器代理配置[proxyPort]:[proxyPort]", + "formatEN": "setproxy[proxyPort]:[proxyPort]." + }, + "/core/settings/ssl/update": { + "bodyKeys": [ + "ssl" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改系统ssl=\u003e[ssl]", + "formatEN": "updatesystemssl=\u003e[ssl]" + }, + "/core/settings/terminal/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改系统终端配置", + "formatEN": "updatesystemterminalsetting" + }, + "/core/settings/update": { + "bodyKeys": [ + "key", + "value" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改系统配置[key]=\u003e[value]", + "formatEN": "updatesystemsetting[key]=\u003e[value]" + }, + "/core/settings/upgrade": { + "bodyKeys": [ + "version" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新系统=\u003e[version]", + "formatEN": "upgradesystem=\u003e[version]" + }, + "/core/xpack/nodes": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加节点[name]", + "formatEN": "addnode[name]" + }, + "/core/xpack/nodes/backup": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "备份主节点数据", + "formatEN": "backupmasterdata" + }, + "/core/xpack/nodes/backup/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改主节点备份设置", + "formatEN": "updatemasterbackupsetting" + }, + "/core/xpack/nodes/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "nodes", + "output_column": "name", + "output_value": "names" + } + ], + "formatZH": "删除节点[names]", + "formatEN": "deletenode[names]" + }, + "/core/xpack/nodes/fix": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "nodes", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "修复节点[name]", + "formatEN": "fixnode[name]" + }, + "/core/xpack/nodes/rollback": { + "bodyKeys": [ + "nodeID" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "nodeID", + "isList": false, + "db": "nodes", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "回滚节点[name]", + "formatEN": "rollbacknode[name]" + }, + "/core/xpack/nodes/simple": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加节点[name]", + "formatEN": "addnode[name]" + }, + "/core/xpack/nodes/simple/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "simple_nodes", + "output_column": "name", + "output_value": "names" + } + ], + "formatZH": "删除节点[names]", + "formatEN": "deletenode[names]" + }, + "/core/xpack/nodes/simple/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[name]", + "formatEN": "updatenode[name]" + }, + "/core/xpack/nodes/simple/update/base": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[name]基础信息", + "formatEN": "updatebaseinfofornode[name]" + }, + "/core/xpack/nodes/sync": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "nodes", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "同步节点数据[name]", + "formatEN": "syncnode[name]" + }, + "/core/xpack/nodes/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[name]", + "formatEN": "updatenode[name]" + }, + "/core/xpack/nodes/update/base": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[name]基础信息", + "formatEN": "updatebaseinfofornode[name]" + }, + "/core/xpack/nodes/upgrade": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "nodes", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "升级节点[name]", + "formatEN": "upgradenode[name]" + }, + "/core/xpack/nodes/upgrade/log/del": { + "bodyKeys": [ + "nodeID" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "nodeID", + "isList": false, + "db": "nodes", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除节点更新日志[name]", + "formatEN": "deletenodeupgradelog[name]" + }, + "/cronjobs": { + "bodyKeys": [ + "type", + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建计划任务[type][name]", + "formatEN": "createcronjob[type][name]" + }, + "/cronjobs/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "cronjobs", + "output_column": "name", + "output_value": "names" + } + ], + "formatZH": "删除计划任务[names]", + "formatEN": "deletecronjob[names]" + }, + "/cronjobs/group/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "cronjobs", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新计划任务分组[name]", + "formatEN": "updatecronjobgroup[name]" + }, + "/cronjobs/handle": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "cronjobs", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "手动执行计划任务[name]", + "formatEN": "manuallyexecutethecronjob[name]" + }, + "/cronjobs/records/clean": { + "bodyKeys": [ + "cronjobID" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "cronjobID", + "isList": false, + "db": "cronjobs", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "清空计划任务记录[name]", + "formatEN": "cleancronjob[name]records" + }, + "/cronjobs/status": { + "bodyKeys": [ + "id", + "status" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "cronjobs", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "修改计划任务[name]状态为[status]", + "formatEN": "changethestatusofcronjob[name]to[status]." + }, + "/cronjobs/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "cronjobs", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新计划任务[name]", + "formatEN": "updatecronjob[name]" + }, + "/dashboard/app/launcher/show": { + "bodyKeys": [ + "key", + "value" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "首页应用[key]=\u003e显示:[value]", + "formatEN": "applauncher[key]=\u003eshow:[value]" + }, + "/dashboard/quick/change": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "切换快速跳转", + "formatEN": "changequickjump" + }, + "/databases": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建mysql数据库[name]", + "formatEN": "createmysqldatabase[name]" + }, + "/databases/bind": { + "bodyKeys": [ + "database", + "username" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "绑定mysql数据库名[database][username]", + "formatEN": "bindmysqldatabase[database][username]" + }, + "/databases/change/access": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "database_mysqls", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新数据库[name]访问权限", + "formatEN": "Updatedatabase[name]access" + }, + "/databases/change/password": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "database_mysqls", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新数据库[name]密码", + "formatEN": "Updatedatabase[name]password" + }, + "/databases/common/update/conf": { + "bodyKeys": [ + "type", + "database" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[type]数据库[database]配置信息", + "formatEN": "updatethe[type][database]databaseconfigurationinformation" + }, + "/databases/db": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建远程数据库[name][type]", + "formatEN": "createdatabase[name][type]" + }, + "/databases/db/check": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "检测远程数据库[name][type]连接性", + "formatEN": "checkifdatabase[name][type]isconnectable" + }, + "/databases/db/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "databases", + "output_column": "name", + "output_value": "names" + } + ], + "formatZH": "删除远程数据库[names]", + "formatEN": "deletedatabase[names]" + }, + "/databases/db/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新远程数据库[name]", + "formatEN": "updatedatabase[name]" + }, + "/databases/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "database_mysqls", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除mysql数据库[name]", + "formatEN": "deletemysqldatabase[name]" + }, + "/databases/description/update": { + "bodyKeys": [ + "id", + "description" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "database_mysqls", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "mysql数据库[name]描述信息修改[description]", + "formatEN": "Thedescriptionofthemysqldatabase[name]ismodified=\u003e[description]" + }, + "/databases/pg": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建postgresql数据库[name]", + "formatEN": "createpostgresqldatabase[name]" + }, + "/databases/pg/bind": { + "bodyKeys": [ + "name", + "username" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "绑定postgresql数据库[name]用户[username]", + "formatEN": "bindpostgresqldatabase[name]user[username]" + }, + "/databases/pg/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "database_postgresqls", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除postgresql数据库[name]", + "formatEN": "deletepostgresqldatabase[name]" + }, + "/databases/pg/description": { + "bodyKeys": [ + "id", + "description" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "database_postgresqls", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "postgresql数据库[name]描述信息修改[description]", + "formatEN": "Thedescriptionofthepostgresqldatabase[name]ismodified=\u003e[description]" + }, + "/databases/pg/password": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "database_postgresqls", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "更新数据库[name]密码", + "formatEN": "Updatedatabase[name]password" + }, + "/databases/pg/privileges": { + "bodyKeys": [ + "database", + "username" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新数据库[database]用户[username]权限", + "formatEN": "Update[user]privilegesofdatabase[database]" + }, + "/databases/redis/conf/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新redis数据库配置信息", + "formatEN": "updatetheredisdatabaseconfigurationinformation" + }, + "/databases/redis/password": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改redis数据库密码", + "formatEN": "changethepasswordoftheredisdatabase" + }, + "/databases/redis/persistence/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "redis数据库持久化配置更新", + "formatEN": "redisdatabasepersistenceconfigurationupdate" + }, + "/databases/variables/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "调整mysql数据库性能参数", + "formatEN": "adjustmysqldatabaseperformanceparameters" + }, + "/files": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建文件/文件夹[path]", + "formatEN": "Createdirorfile[path]" + }, + "/files/batch/del": { + "bodyKeys": [ + "paths" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "批量删除文件/文件夹[paths]", + "formatEN": "Batchdeletedirorfile[paths]" + }, + "/files/batch/role": { + "bodyKeys": [ + "paths", + "mode", + "user", + "group" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "批量修改文件权限和用户/组[paths]=\u003e[mode]/[user]/[group]", + "formatEN": "Batchchangefilemodeandowner[paths]=\u003e[mode]/[user]/[group]" + }, + "/files/chunkdownload": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "下载文件[name]", + "formatEN": "Downloadfile[name]" + }, + "/files/compress": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "压缩文件[name]", + "formatEN": "Compressfile[name]" + }, + "/files/content": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "获取文件内容[path]", + "formatEN": "Loadfilecontent[path]" + }, + "/files/decompress": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "解压[path]", + "formatEN": "Decompressfile[path]" + }, + "/files/del": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除文件/文件夹[path]", + "formatEN": "Deletedirorfile[path]" + }, + "/files/depth/size": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "获取目录及其第一层子目录文件夹大小[path]", + "formatEN": "Multifilesize[path]" + }, + "/files/favorite": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "收藏文件/文件夹[path]", + "formatEN": "收藏文件/文件夹[path]" + }, + "/files/favorite/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "favorites", + "output_column": "path", + "output_value": "path" + } + ], + "formatZH": "删除收藏[path]", + "formatEN": "deleteavorite[path]" + }, + "/files/mode": { + "bodyKeys": [ + "path", + "mode" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改权限[paths]=\u003e[mode]", + "formatEN": "Changemode[paths]=\u003e[mode]" + }, + "/files/move": { + "bodyKeys": [ + "oldPaths", + "newPath" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "移动文件[oldPaths]=\u003e[newPath]", + "formatEN": "Move[oldPaths]=\u003e[newPath]" + }, + "/files/owner": { + "bodyKeys": [ + "path", + "user", + "group" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改用户/组[paths]=\u003e[user]/[group]", + "formatEN": "Changeowner[paths]=\u003e[user]/[group]" + }, + "/files/preview": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "预览文件内容[path]", + "formatEN": "Previewfilecontent[path]" + }, + "/files/recycle/clear": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清空回收站", + "formatEN": "清空回收站" + }, + "/files/recycle/reduce": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "还原回收站文件[name]", + "formatEN": "ReduceRecycleBinfile[name]" + }, + "/files/rename": { + "bodyKeys": [ + "oldName", + "newName" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "重命名[oldName]=\u003e[newName]", + "formatEN": "Rename[oldName]=\u003e[newName]" + }, + "/files/save": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新文件内容[path]", + "formatEN": "Updatefilecontent[path]" + }, + "/files/size": { + "bodyKeys": [ + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "获取文件夹大小[path]", + "formatEN": "Loadfilesize[path]" + }, + "/files/upload": { + "bodyKeys": [ + "path", + "file" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "上传文件[path]/[file]", + "formatEN": "Uploadfile[path]/[file]" + }, + "/files/wget": { + "bodyKeys": [ + "url", + "path", + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "下载url=\u003e[path]/[name]", + "formatEN": "Downloadurl=\u003e[path]/[name]" + }, + "/groups": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建组[name][type]", + "formatEN": "creategroup[name][type]" + }, + "/groups/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "groups", + "output_column": "name", + "output_value": "name" + }, + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "groups", + "output_column": "type", + "output_value": "type" + } + ], + "formatZH": "删除组[type][name]", + "formatEN": "deletegroup[type][name]" + }, + "/groups/update": { + "bodyKeys": [ + "name", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新组[name][type]", + "formatEN": "updategroup[name][type]" + }, + "/hosts/disks/mount": { + "bodyKeys": [ + "device", + "mountPoint" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "挂载磁盘[device]到[mountPoint]", + "formatEN": "Mountdisk[device]to[mountPoint]" + }, + "/hosts/disks/partition": { + "bodyKeys": [ + "device", + "filesystem", + "mountPoint" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "对磁盘[device]进行分区,文件系统[filesystem],挂载点[mountPoint]", + "formatEN": "Partitiondisk[device]withfilesystem[filesystem],mountpoint[mountPoint]" + }, + "/hosts/disks/unmount": { + "bodyKeys": [ + "device", + "mountPoint" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "卸载磁盘[device]从[mountPoint]", + "formatEN": "Unmountdisk[device]from[mountPoint]" + }, + "/hosts/firewall/filter/operate": { + "bodyKeys": [ + "operate" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operate]iptablesfilter防火墙", + "formatEN": "[operate]iptablesfilterfirewall" + }, + "/hosts/firewall/filter/rule/operate": { + "bodyKeys": [ + "operation", + "chain" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operation]filter规则到[chain]", + "formatEN": "[operation]filterruleto[chain]" + }, + "/hosts/firewall/forward": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新端口转发规则", + "formatEN": "updateportforwardrules" + }, + "/hosts/firewall/ip": { + "bodyKeys": [ + "strategy", + "address" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加ip规则[strategy][address]", + "formatEN": "createaddressrules[strategy][address]" + }, + "/hosts/firewall/operate": { + "bodyKeys": [ + "operation" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operation]防火墙", + "formatEN": "[operation]firewall" + }, + "/hosts/firewall/port": { + "bodyKeys": [ + "port", + "strategy" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加端口规则[strategy][port]", + "formatEN": "createportrules[strategy][port]" + }, + "/hosts/monitor/clean": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清空监控数据", + "formatEN": "cleanmonitordatas" + }, + "/hosts/monitor/setting/update": { + "bodyKeys": [ + "key", + "value" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改默认监控网卡[name]-[value]", + "formatEN": "updatedefaultmonitor[name]-[value]" + }, + "/hosts/ssh/cert": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "生成SSH密钥", + "formatEN": "generateSSHsecret" + }, + "/hosts/ssh/cert/delete": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除SSH密钥", + "formatEN": "deleteSSHsecret" + }, + "/hosts/ssh/cert/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步SSH密钥", + "formatEN": "syncSSHsecret" + }, + "/hosts/ssh/cert/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "生成SSH密钥", + "formatEN": "generateSSHsecret" + }, + "/hosts/ssh/file/update": { + "bodyKeys": [ + "key" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改SSH配置文件[key]", + "formatEN": "updateSSHconf[key]" + }, + "/hosts/ssh/operate": { + "bodyKeys": [ + "operation" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operation]SSH", + "formatEN": "[operation]SSH" + }, + "/hosts/ssh/update": { + "bodyKeys": [ + "key", + "newValue" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改SSH配置[key]=\u003e[newValue]", + "formatEN": "updateSSHsetting[key]=\u003e[newValue]" + }, + "/hosts/tool/config": { + "bodyKeys": [ + "operate" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operate]主机工具配置文件", + "formatEN": "[operate]toolconfig" + }, + "/hosts/tool/init": { + "bodyKeys": [ + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建[type]配置", + "formatEN": "create[type]config" + }, + "/hosts/tool/operate": { + "bodyKeys": [ + "operate", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operate][type]", + "formatEN": "[operate][type]" + }, + "/hosts/tool/supervisor/process": { + "bodyKeys": [ + "operate" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operate]守护进程", + "formatEN": "[operate]process" + }, + "/hosts/tool/supervisor/process/file": { + "bodyKeys": [ + "operate" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operate]Supervisor进程文件", + "formatEN": "[operate]SupervisorProcessConfigfile" + }, + "/openresty/build": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "构建OpenResty", + "formatEN": "BuildOpenResty" + }, + "/openresty/file": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新nginx配置", + "formatEN": "Updatenginxconf" + }, + "/openresty/modules/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新OpenResty模块", + "formatEN": "UpdateOpenRestymodule" + }, + "/openresty/update": { + "bodyKeys": [ + "websiteId" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "更新nginx配置[domain]", + "formatEN": "Updatenginxconf[domain]" + }, + "/process/stop": { + "bodyKeys": [ + "PID" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "结束进程[PID]", + "formatEN": "结束进程[PID]" + }, + "/runtimes": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建运行环境[name]", + "formatEN": "Createruntime[name]" + }, + "/runtimes/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除运行环境[name]", + "formatEN": "Deleteruntime[name]" + }, + "/runtimes/operate": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "操作运行环境[name]", + "formatEN": "Operateruntime[name]" + }, + "/runtimes/php/config": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "[domain]PHP配置修改", + "formatEN": "[domain]PHPconfupdate" + }, + "/runtimes/php/update": { + "bodyKeys": [ + "websiteId" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "php配置修改[domain]", + "formatEN": "Nginxconfupdate[domain]" + }, + "/runtimes/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新运行环境[name]", + "formatEN": "Updateruntime[name]" + }, + "/settings/snapshot": { + "bodyKeys": [ + "from", + "description" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建系统快照[description]到[from]", + "formatEN": "Createsystembackup[description]to[from]" + }, + "/settings/snapshot/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "snapshots", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除系统快照[name]", + "formatEN": "Deletesystembackup[name]" + }, + "/settings/snapshot/description/update": { + "bodyKeys": [ + "id", + "description" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "snapshots", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "快照[name]描述信息修改[description]", + "formatEN": "Thedescriptionofthesnapshot[name]ismodified=\u003e[description]" + }, + "/settings/snapshot/import": { + "bodyKeys": [ + "from", + "names" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "从[from]同步系统快照[names]", + "formatEN": "Syncsystemsnapshots[names]from[from]" + }, + "/settings/snapshot/recover": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "snapshots", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "从系统快照[name]恢复", + "formatEN": "Recoverfromsystembackup[name]" + }, + "/settings/snapshot/recreate": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "snapshots", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "重试创建快照[name]", + "formatEN": "recretethesnapshot[name]" + }, + "/settings/snapshot/rollback": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "snapshots", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "从系统快照[name]回滚", + "formatEN": "Rollbackfromsystembackup[name]" + }, + "/settings/ssh/conn/default": { + "bodyKeys": [ + "defaultConn" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "本地终端默认连接[defaultConn]", + "formatEN": "updatesystemdefaultconn[defaultConn]" + }, + "/settings/update": { + "bodyKeys": [ + "key", + "value" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改系统配置[key]=\u003e[value]", + "formatEN": "updatesystemsetting[key]=\u003e[value]" + }, + "/toolbox/clam": { + "bodyKeys": [ + "name", + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建扫描规则[name][path]", + "formatEN": "createclam[name][path]" + }, + "/toolbox/clam/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "clams", + "output_column": "name", + "output_value": "names" + } + ], + "formatZH": "删除扫描规则[names]", + "formatEN": "deleteclam[names]" + }, + "/toolbox/clam/handle": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": true, + "db": "clams", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "执行病毒扫描[name]", + "formatEN": "handleclamscan[name]" + }, + "/toolbox/clam/operate": { + "bodyKeys": [ + "operation" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operation]Clam", + "formatEN": "[operation]FTP" + }, + "/toolbox/clam/record/clean": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": true, + "db": "clams", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "清空扫描报告[name]", + "formatEN": "cleanclamrecord[name]" + }, + "/toolbox/clam/status/update": { + "bodyKeys": [ + "id", + "status" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "clams", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "修改扫描规则[name]状态为[status]", + "formatEN": "changethestatusofclam[name]to[status]." + }, + "/toolbox/clam/update": { + "bodyKeys": [ + "name", + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改扫描规则[name][path]", + "formatEN": "updateclam[name][path]" + }, + "/toolbox/clean": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清理系统垃圾文件", + "formatEN": "Cleansystemjunkfiles" + }, + "/toolbox/device/update/conf": { + "bodyKeys": [ + "key", + "value" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改主机参数[key]=\u003e[value]", + "formatEN": "updatedeviceconf[key]=\u003e[value]" + }, + "/toolbox/device/update/host": { + "bodyKeys": [ + "key", + "value" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改主机Host[key]=\u003e[value]", + "formatEN": "updatedevicehost[key]=\u003e[value]" + }, + "/toolbox/device/update/swap": { + "bodyKeys": [ + "operate", + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operate]主机swap[path]", + "formatEN": "[operate]deviceswap[path]" + }, + "/toolbox/fail2ban/operate": { + "bodyKeys": [ + "operation" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operation]Fail2ban", + "formatEN": "[operation]Fail2ban" + }, + "/toolbox/fail2ban/update": { + "bodyKeys": [ + "key", + "value" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改Fail2ban配置[key]=\u003e[value]", + "formatEN": "updatefail2banconf[key]=\u003e[value]" + }, + "/toolbox/ftp": { + "bodyKeys": [ + "user", + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建FTP账户[user][path]", + "formatEN": "createFTP[user][path]" + }, + "/toolbox/ftp/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "ftps", + "output_column": "user", + "output_value": "users" + } + ], + "formatZH": "删除FTP账户[users]", + "formatEN": "deleteFTPusers[users]" + }, + "/toolbox/ftp/operate": { + "bodyKeys": [ + "operation" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "[operation]FTP", + "formatEN": "[operation]FTP" + }, + "/toolbox/ftp/sync": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "同步FTP账户", + "formatEN": "syncFTPusers" + }, + "/toolbox/ftp/update": { + "bodyKeys": [ + "user", + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "修改FTP账户[user][path]", + "formatEN": "updateFTP[user][path]" + }, + "/toolbox/scan": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "扫描系统垃圾文件", + "formatEN": "scanSystemJunkFiles" + }, + "/websites": { + "bodyKeys": [ + "alias" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建网站[alias]", + "formatEN": "Createwebsite[alias]" + }, + "/websites/:id/https": { + "bodyKeys": [ + "websiteId" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "更新网站[domain]https配置", + "formatEN": "Updatewebsitehttps[domain]conf" + }, + "/websites/acme": { + "bodyKeys": [ + "email" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建网站acme[email]", + "formatEN": "Createwebsiteacme[email]" + }, + "/websites/acme/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_acme_accounts", + "output_column": "email", + "output_value": "email" + } + ], + "formatZH": "删除网站acme[email]", + "formatEN": "Deletewebsiteacme[email]" + }, + "/websites/acme/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_acme_accounts", + "output_column": "email", + "output_value": "email" + } + ], + "formatZH": "更新acme[email]", + "formatEN": "Updateacme[email]" + }, + "/websites/ca": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建网站ca[name]", + "formatEN": "Createwebsiteca[name]" + }, + "/websites/ca/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_cas", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除网站ca[name]", + "formatEN": "Deletewebsiteca[name]" + }, + "/websites/ca/download": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_cas", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "下载CA证书文件[name]", + "formatEN": "downloadcafile[name]" + }, + "/websites/ca/obtain": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_cas", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "自签SSL证书[name]", + "formatEN": "ObtainSSL[name]" + }, + "/websites/ca/renew": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_cas", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "自签SSL证书[name]", + "formatEN": "ObtainSSL[name]" + }, + "/websites/config/update": { + "bodyKeys": [ + "websiteId" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "nginx配置修改[domain]", + "formatEN": "Nginxconfupdate[domain]" + }, + "/websites/default/html/update": { + "bodyKeys": [ + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新默认html", + "formatEN": "Updatedefaulthtml" + }, + "/websites/default/server": { + "bodyKeys": [ + "id", + "operate" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "修改默认server=\u003e[domain]", + "formatEN": "Changedefaultserver=\u003e[domain]" + }, + "/websites/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "删除网站[domain]", + "formatEN": "Deletewebsite[domain]" + }, + "/websites/dir/permission": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "更新网站[domain]目录权限", + "formatEN": "Updatedomain[domain]dirpermission" + }, + "/websites/dir/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "更新网站[domain]目录", + "formatEN": "Updatedomain[domain]dir" + }, + "/websites/dns": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建网站dns[name]", + "formatEN": "Createwebsitedns[name]" + }, + "/websites/dns/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_dns_accounts", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除网站dns[name]", + "formatEN": "Deletewebsitedns[name]" + }, + "/websites/dns/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新网站dns[name]", + "formatEN": "Updatewebsitedns[name]" + }, + "/websites/domains": { + "bodyKeys": [ + "domain" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建域名[domain]", + "formatEN": "Createdomain[domain]" + }, + "/websites/domains/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_domains", + "output_column": "domain", + "output_value": "domain" + } + ], + "formatZH": "删除域名[domain]", + "formatEN": "Deletedomain[domain]" + }, + "/websites/domains/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_domains", + "output_column": "domain", + "output_value": "domain" + } + ], + "formatZH": "更新域名[domain]", + "formatEN": "Updatedomain[domain]" + }, + "/websites/log": { + "bodyKeys": [ + "id", + "operate" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "[domain][operate]日志", + "formatEN": "[domain][operate]logs" + }, + "/websites/nginx/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "[domain]Nginx配置修改", + "formatEN": "[domain]Nginxconfupdate" + }, + "/websites/operate": { + "bodyKeys": [ + "id", + "operate" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "[operate]网站[domain]", + "formatEN": "[operate]website[domain]" + }, + "/websites/php/version": { + "bodyKeys": [ + "websiteId" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteId", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "php版本变更[domain]", + "formatEN": "phpversionupdate[domain]" + }, + "/websites/proxies/file": { + "bodyKeys": [ + "websiteID" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "更新反向代理文件[domain]", + "formatEN": "Nginxconfproxyfileupdate[domain]" + }, + "/websites/proxies/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "修改网站[domain]反向代理配置", + "formatEN": "Updatedomain[domain]proxyconfig" + }, + "/websites/proxy/clear": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清理Openresty代理缓存", + "formatEN": "Clearnginxproxycache" + }, + "/websites/realip/config": { + "bodyKeys": [ + "websiteID" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "修改[domain]网站真实IP配置", + "formatEN": "ModifytherealIPconfigurationof[domain]website" + }, + "/websites/redirect/file": { + "bodyKeys": [ + "websiteID" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "更新重定向文件[domain]", + "formatEN": "Nginxconfredirectfileupdate[domain]" + }, + "/websites/redirect/update": { + "bodyKeys": [ + "websiteID" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "修改网站[domain]重定向配置", + "formatEN": "Updatedomain[domain]redirectconfig" + }, + "/websites/rewrite/update": { + "bodyKeys": [ + "websiteID" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "websiteID", + "isList": false, + "db": "websites", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "伪静态配置修改[domain]", + "formatEN": "Nginxconfrewriteupdate[domain]" + }, + "/websites/ssl": { + "bodyKeys": [ + "primaryDomain" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建网站ssl[primaryDomain]", + "formatEN": "Createwebsitessl[primaryDomain]" + }, + "/websites/ssl/del": { + "bodyKeys": [ + "ids" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ids", + "isList": true, + "db": "website_ssls", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "删除ssl[domain]", + "formatEN": "Deletessl[domain]" + }, + "/websites/ssl/download": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_ssls", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "下载证书文件[domain]", + "formatEN": "downloadsslfile[domain]" + }, + "/websites/ssl/obtain": { + "bodyKeys": [ + "ID" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "ID", + "isList": false, + "db": "website_ssls", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "申请证书[domain]", + "formatEN": "applyssl[domain]" + }, + "/websites/ssl/update": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "website_ssls", + "output_column": "primary_domain", + "output_value": "domain" + } + ], + "formatZH": "更新证书设置[domain]", + "formatEN": "Updatesslconfig[domain]" + }, + "/websites/ssl/upload": { + "bodyKeys": [ + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "上传ssl[type]", + "formatEN": "Uploadssl[type]" + }, + "/websites/ssl/upload/file": { + "bodyKeys": [ + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "上传ssl文件[type]", + "formatEN": "Uploadsslfile[type]" + }, + "/websites/update": { + "bodyKeys": [ + "primaryDomain" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新网站[primaryDomain]", + "formatEN": "Updatewebsite[primaryDomain]" + }, + "/xpack/monitor/config/global": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新全局设置", + "formatEN": "updateglobalconfig" + }, + "/xpack/monitor/log/clear": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清空日志", + "formatEN": "clearlog" + }, + "/xpack/monitor/site/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新网站设置", + "formatEN": "updatewebsiteconfig" + }, + "/xpack/tampers/template": { + "bodyKeys": [ + "name", + "content" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "保存防篡改模版[name]-[content]", + "formatEN": "savetampertemplateinfo[name]-[content]" + }, + "/xpack/tampers/template/del": { + "bodyKeys": [ + "id" + ], + "paramKeys": [], + "beforeFunctions": [ + { + "input_column": "id", + "input_value": "id", + "isList": false, + "db": "tampers", + "output_column": "name", + "output_value": "name" + } + ], + "formatZH": "删除防篡改模版[name]", + "formatEN": "deletetampertemplate[name]" + }, + "/xpack/tampers/update": { + "bodyKeys": [ + "website", + "status", + "path" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新防篡改信息[website][path]=\u003e[status]", + "formatEN": "updatetamperinfo[website][path]=\u003e[status]" + }, + "/xpack/waf/acl/create": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建自定义规则[name]", + "formatEN": "createacl[name]" + }, + "/xpack/waf/acl/delete": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除自定义规则[name]", + "formatEN": "deleteacl[name]" + }, + "/xpack/waf/acl/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新自定义规则[name]", + "formatEN": "updateacl[name]" + }, + "/xpack/waf/block/remove": { + "bodyKeys": [ + "ip" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "解封IP[ip]", + "formatEN": "unblockip[ip]" + }, + "/xpack/waf/cdn/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新CDN配置", + "formatEN": "updateCDNconfig" + }, + "/xpack/waf/config/global/state": { + "bodyKeys": [ + "scope", + "state" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[state]全局配置[scope]", + "formatEN": "update[state]globalconfig[scope]" + }, + "/xpack/waf/config/website/state": { + "bodyKeys": [ + "scope", + "state" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[state]网站配置[scope]", + "formatEN": "update[state]websiteconfig[scope]" + }, + "/xpack/waf/html/revert": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "回滚拦截页面[name]", + "formatEN": "reverthtmlres[name]" + }, + "/xpack/waf/html/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新拦截页面[name]", + "formatEN": "updatehtmlres[name]" + }, + "/xpack/waf/ip/default": { + "bodyKeys": [ + "ip", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加IP[ip]到默认[type]组", + "formatEN": "addip[ip]todefault[type]group" + }, + "/xpack/waf/ip/group/create": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建IP组[name]", + "formatEN": "createipgroup[name]" + }, + "/xpack/waf/ip/group/delete": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除IP组[name]", + "formatEN": "deleteipgroup[name]" + }, + "/xpack/waf/ip/group/update": { + "bodyKeys": [ + "name" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新IP组[name]", + "formatEN": "updateipgroup[name]" + }, + "/xpack/waf/location/update": { + "bodyKeys": [ + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[type]地址库信息", + "formatEN": "update[type]location" + }, + "/xpack/waf/log/clear": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "清空WAF日志", + "formatEN": "clearwaflog" + }, + "/xpack/waf/rule/cc": { + "bodyKeys": [ + "scope" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[scope]频率访问限制", + "formatEN": "update[scope]CCconfig" + }, + "/xpack/waf/rule/common/create": { + "bodyKeys": [ + "scope" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建规则[scope]", + "formatEN": "createrule[scope]" + }, + "/xpack/waf/rule/common/delete": { + "bodyKeys": [ + "scope" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除规则[scope]", + "formatEN": "deleterule[scope]" + }, + "/xpack/waf/rule/common/export": { + "bodyKeys": [ + "scope" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "导出规则[scope]", + "formatEN": "exportrule[scope]" + }, + "/xpack/waf/rule/common/import": { + "bodyKeys": [ + "scope" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "导入规则[scope]", + "formatEN": "importrule[scope]" + }, + "/xpack/waf/rule/common/update": { + "bodyKeys": [ + "scope" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新规则[scope]", + "formatEN": "updaterule[scope]" + }, + "/xpack/waf/rule/geo": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新地区访问限制", + "formatEN": "updategeorestrict" + }, + "/xpack/waf/rule/ip/create": { + "bodyKeys": [ + "scope" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建[scope]IP规则", + "formatEN": "create[scope]IPrule" + }, + "/xpack/waf/rule/ip/delete": { + "bodyKeys": [ + "scope" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除[scope]IP规则", + "formatEN": "delete[scope]IPrule" + }, + "/xpack/waf/rule/ip/update": { + "bodyKeys": [ + "scope" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新[scope]IP规则", + "formatEN": "update[scope]IPrule" + }, + "/xpack/waf/rule/log": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新日志配置", + "formatEN": "updatewaflogconfig" + }, + "/xpack/waf/rule/urlcc/create": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "创建URL频率访问限制规则", + "formatEN": "createURLCCrule" + }, + "/xpack/waf/rule/urlcc/delete": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "删除URL频率访问限制规则", + "formatEN": "deleteURLCCrule" + }, + "/xpack/waf/rule/urlcc/update": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新URL频率访问限制规则", + "formatEN": "updateURLCCrule" + }, + "/xpack/waf/url/default": { + "bodyKeys": [ + "url", + "type" + ], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "添加URL[url]到默认[type]组", + "formatEN": "addURL[url]todefault[type]group" + }, + "/xpack/website/rule/cc": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新网站频率访问限制", + "formatEN": "updatewebsiteCCconfig" + }, + "/xpack/website/rule/geo": { + "bodyKeys": [], + "paramKeys": [], + "beforeFunctions": [], + "formatZH": "更新网站地区访问限制", + "formatEN": "updategeorestrict" + } +} \ No newline at end of file diff --git a/core/cmd/server/main.go b/core/cmd/server/main.go new file mode 100644 index 0000000..73799cd --- /dev/null +++ b/core/cmd/server/main.go @@ -0,0 +1,45 @@ +package main + +import ( + "fmt" + "os" + + _ "net/http/pprof" + + "github.com/1Panel-dev/1Panel/core/cmd/server/cmd" + _ "github.com/1Panel-dev/1Panel/core/cmd/server/docs" +) + +// @title 1Panel +// @version 2.0 +// @description Top-Rated Web-based Linux Server Management Tool +// @termsOfService http://swagger.io/terms/ +// @license.name GPL-3.0 +// @license.url https://www.gnu.org/licenses/gpl-3.0.html +// @BasePath /api/v2 +// @schemes http https + +// @securityDefinitions.apikey ApiKeyAuth +// @description Custom Token Format, Format: md5('1panel' + API-Key + UnixTimestamp). +// @description ``` +// @description eg: +// @description curl -X GET "http://{host}:{port}/api/v2/toolbox/device/base" \ +// @description -H "1Panel-Token: <1panel_token>" \ +// @description -H "1Panel-Timestamp: " +// @description ``` +// @description - `1Panel-Token` is the key for the panel API Key. +// @type apiKey +// @in Header +// @name 1Panel-Token +// @securityDefinitions.apikey Timestamp +// @type apiKey +// @in header +// @name 1Panel-Timestamp +// @description - `1Panel-Timestamp` is the Unix timestamp of the current time in seconds. + +func main() { + if err := cmd.RootCmd.Execute(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} diff --git a/core/cmd/server/res/error_msg.go b/core/cmd/server/res/error_msg.go new file mode 100644 index 0000000..fb958d1 --- /dev/null +++ b/core/cmd/server/res/error_msg.go @@ -0,0 +1,6 @@ +package res + +import "embed" + +//go:embed html/* +var ErrorMsg embed.FS diff --git a/core/cmd/server/res/html/200.html b/core/cmd/server/res/html/200.html new file mode 100644 index 0000000..62598c1 --- /dev/null +++ b/core/cmd/server/res/html/200.html @@ -0,0 +1,55 @@ + + + + + + Access Temporarily Unavailable + + + +
    +

    Access Temporarily Unavailable

    +

    The current environment has enabled secure login access.

    +

    Run the following SSH command to view the panel login URL:

    +

    1pctl user-info

    +
    + + diff --git a/core/cmd/server/res/html/200_err_domain.html b/core/cmd/server/res/html/200_err_domain.html new file mode 100644 index 0000000..8f48fc5 --- /dev/null +++ b/core/cmd/server/res/html/200_err_domain.html @@ -0,0 +1,55 @@ + + + + + + Access Temporarily Unavailable + + + +
    +

    Access Temporarily Unavailable

    +

    The current environment has enabled domain name binding.

    +

    Run the following SSH command to reset the binding information:

    +

    1pctl reset domain

    +
    + + diff --git a/core/cmd/server/res/html/200_err_ip_limit.html b/core/cmd/server/res/html/200_err_ip_limit.html new file mode 100644 index 0000000..662704e --- /dev/null +++ b/core/cmd/server/res/html/200_err_ip_limit.html @@ -0,0 +1,55 @@ + + + + + + Access Temporarily Unavailable + + + +
    +

    Access Temporarily Unavailable

    +

    The current environment has enabled authorized IP access.

    +

    Run the following SSH command to reset the binding information:

    +

    1pctl reset ips

    +
    + + diff --git a/core/cmd/server/res/html/400.html b/core/cmd/server/res/html/400.html new file mode 100644 index 0000000..0d502a0 --- /dev/null +++ b/core/cmd/server/res/html/400.html @@ -0,0 +1,7 @@ + + +400 Bad Request + +

    400 Bad Request

    +
    nginx
    + \ No newline at end of file diff --git a/core/cmd/server/res/html/401.html b/core/cmd/server/res/html/401.html new file mode 100644 index 0000000..60e1498 --- /dev/null +++ b/core/cmd/server/res/html/401.html @@ -0,0 +1,7 @@ + + +401 Unauthorized + +

    401 Unauthorized

    +
    nginx
    + \ No newline at end of file diff --git a/core/cmd/server/res/html/403.html b/core/cmd/server/res/html/403.html new file mode 100644 index 0000000..a77b7cb --- /dev/null +++ b/core/cmd/server/res/html/403.html @@ -0,0 +1,7 @@ + + +403 Forbidden + +

    403 Forbidden

    +
    nginx
    + \ No newline at end of file diff --git a/core/cmd/server/res/html/404.html b/core/cmd/server/res/html/404.html new file mode 100644 index 0000000..6748be2 --- /dev/null +++ b/core/cmd/server/res/html/404.html @@ -0,0 +1,7 @@ + + +404 Not Found + +

    404 Not Found

    +
    nginx
    + \ No newline at end of file diff --git a/core/cmd/server/res/html/408.html b/core/cmd/server/res/html/408.html new file mode 100644 index 0000000..15ba0cd --- /dev/null +++ b/core/cmd/server/res/html/408.html @@ -0,0 +1,7 @@ + + +408 Request Timeout + +

    408 Request Timeout

    +
    nginx
    + \ No newline at end of file diff --git a/core/cmd/server/res/html/416.html b/core/cmd/server/res/html/416.html new file mode 100644 index 0000000..8104e72 --- /dev/null +++ b/core/cmd/server/res/html/416.html @@ -0,0 +1,7 @@ + + +416 Requested Not Satisfiable + +

    416 Requested Not Satisfiable

    +
    nginx
    + \ No newline at end of file diff --git a/core/cmd/server/res/html/500.html b/core/cmd/server/res/html/500.html new file mode 100644 index 0000000..c580daa --- /dev/null +++ b/core/cmd/server/res/html/500.html @@ -0,0 +1,7 @@ + + +Internal Server Error + +

    Internal Server Error

    +
    nginx
    + \ No newline at end of file diff --git a/core/cmd/server/web/favicon.png b/core/cmd/server/web/favicon.png new file mode 100644 index 0000000..c80e8f0 Binary files /dev/null and b/core/cmd/server/web/favicon.png differ diff --git a/core/cmd/server/web/static/china.json b/core/cmd/server/web/static/china.json new file mode 100644 index 0000000..6408303 --- /dev/null +++ b/core/cmd/server/web/static/china.json @@ -0,0 +1,25643 @@ +{ + "type": "FeatureCollection", + "features": [ + { + "type": "Feature", + "properties": { + "adcode": 110000, + "name": "Beijing", + "center": [116.405285, 39.904989], + "centroid": [116.41995, 40.18994], + "childrenNum": 16, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 0, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [117.348611, 40.581141], + [117.389879, 40.561593], + [117.429915, 40.576141], + [117.412669, 40.605226], + [117.467487, 40.649738], + [117.467487, 40.649738], + [117.501364, 40.636569], + [117.514914, 40.660181], + [117.493973, 40.675161], + [117.408973, 40.686961], + [117.342451, 40.673799], + [117.319662, 40.657911], + [117.278394, 40.664267], + [117.208177, 40.694675], + [117.117018, 40.70012], + [117.11209, 40.707379], + [117.012308, 40.693767], + [116.964881, 40.709647], + [116.926692, 40.745022], + [116.924229, 40.773581], + [116.848468, 40.839264], + [116.81336, 40.848319], + [116.759773, 40.889954], + [116.713577, 40.909858], + [116.722201, 40.927495], + [116.677853, 40.970888], + [116.698795, 41.021477], + [116.688324, 41.044501], + [116.647672, 41.059394], + [116.615643, 41.053076], + [116.623034, 41.021026], + [116.598397, 40.974503], + [116.5676, 40.992574], + [116.519557, 40.98128], + [116.519557, 40.98128], + [116.455499, 40.980828], + [116.447492, 40.953715], + [116.477057, 40.899907], + [116.398216, 40.90624], + [116.370499, 40.94377], + [116.339702, 40.929303], + [116.334159, 40.90443], + [116.438253, 40.81934], + [116.46597, 40.774487], + [116.453651, 40.765876], + [116.316912, 40.772221], + [116.311369, 40.754996], + [116.273181, 40.762703], + [116.247311, 40.791707], + [116.22021, 40.744115], + [116.204812, 40.740035], + [116.171551, 40.695582], + [116.162928, 40.662451], + [116.133979, 40.666536], + [116.09887, 40.630665], + [116.005247, 40.583868], + [115.982457, 40.578868], + [115.971986, 40.6025], + [115.907929, 40.617493], + [115.885139, 40.595229], + [115.827857, 40.587504], + [115.819849, 40.55932], + [115.784741, 40.55841], + [115.755176, 40.540221], + [115.736082, 40.503372], + [115.781045, 40.49336], + [115.771806, 40.443734], + [115.864197, 40.359422], + [115.917784, 40.354405], + [115.95166, 40.281852], + [115.968907, 40.264045], + [115.89869, 40.234354], + [115.870356, 40.185909], + [115.855574, 40.188652], + [115.847567, 40.147036], + [115.806299, 40.15344], + [115.773654, 40.176307], + [115.75456, 40.145663], + [115.75456, 40.145663], + [115.599959, 40.119583], + [115.59072, 40.096239], + [115.527278, 40.076092], + [115.485394, 40.040364], + [115.454597, 40.029825], + [115.450286, 39.992697], + [115.428728, 39.984443], + [115.426264, 39.950502], + [115.481083, 39.935819], + [115.522967, 39.899099], + [115.515575, 39.892212], + [115.515575, 39.892212], + [115.526046, 39.87568], + [115.514344, 39.837549], + [115.567314, 39.816407], + [115.552532, 39.794799], + [115.50572, 39.784222], + [115.483547, 39.798477], + [115.483547, 39.798477], + [115.443511, 39.785601], + [115.439815, 39.752022], + [115.486626, 39.741899], + [115.491554, 39.670074], + [115.478619, 39.650723], + [115.478619, 39.650723], + [115.522351, 39.640124], + [115.518039, 39.597252], + [115.545756, 39.618922], + [115.587024, 39.589873], + [115.633836, 39.599557], + [115.633836, 39.599557], + [115.667712, 39.615234], + [115.698509, 39.577881], + [115.698509, 39.577881], + [115.699125, 39.570039], + [115.699125, 39.570039], + [115.716988, 39.56035], + [115.716988, 39.56035], + [115.718835, 39.553891], + [115.718835, 39.553891], + [115.720683, 39.551122], + [115.720683, 39.551122], + [115.722531, 39.5442], + [115.721299, 39.543738], + [115.722531, 39.5442], + [115.722531, 39.543738], + [115.721299, 39.543738], + [115.722531, 39.543738], + [115.724995, 39.5442], + [115.724995, 39.5442], + [115.738545, 39.540046], + [115.738545, 39.539585], + [115.738545, 39.540046], + [115.738545, 39.539585], + [115.752712, 39.515581], + [115.806299, 39.510041], + [115.806299, 39.510041], + [115.821081, 39.522968], + [115.821081, 39.522968], + [115.828473, 39.541431], + [115.867893, 39.546507], + [115.867893, 39.546507], + [115.91532, 39.582955], + [115.91532, 39.582955], + [115.910393, 39.600479], + [115.910393, 39.600479], + [115.957204, 39.560812], + [115.978146, 39.595868], + [115.995392, 39.576958], + [116.026189, 39.587567], + [116.036044, 39.571884], + [116.09887, 39.575113], + [116.130283, 39.567732], + [116.151841, 39.583416], + [116.198652, 39.589412], + [116.240536, 39.564041], + [116.257782, 39.500344], + [116.307057, 39.488337], + [116.337854, 39.455536], + [116.361876, 39.455074], + [116.361876, 39.455074], + [116.434557, 39.442597], + [116.454883, 39.453226], + [116.444412, 39.482332], + [116.411767, 39.482794], + [116.401912, 39.528046], + [116.443796, 39.510041], + [116.437637, 39.526661], + [116.478289, 39.535431], + [116.473361, 39.552968], + [116.50847, 39.551122], + [116.524484, 39.596329], + [116.592237, 39.621227], + [116.592237, 39.621227], + [116.620571, 39.601863], + [116.664918, 39.605552], + [116.723432, 39.59264], + [116.724048, 39.59264], + [116.723432, 39.59264], + [116.724048, 39.59264], + [116.726512, 39.595407], + [116.726512, 39.595407], + [116.709266, 39.618], + [116.748686, 39.619844], + [116.79057, 39.595868], + [116.812128, 39.615695], + [116.8497, 39.66777], + [116.906366, 39.677444], + [116.90575, 39.688037], + [116.889736, 39.687576], + [116.887272, 39.72533], + [116.916837, 39.731314], + [116.902055, 39.763523], + [116.949482, 39.778703], + [116.918069, 39.84628], + [116.907598, 39.832494], + [116.865714, 39.843982], + [116.812128, 39.889916], + [116.78441, 39.891294], + [116.782563, 39.947749], + [116.757925, 39.967934], + [116.781331, 40.034866], + [116.820135, 40.02845], + [116.831222, 40.051359], + [116.867562, 40.041739], + [116.927924, 40.055024], + [116.945171, 40.04128], + [117.025243, 40.030283], + [117.051728, 40.059605], + [117.105315, 40.074261], + [117.105315, 40.074261], + [117.140423, 40.064185], + [117.159517, 40.077008], + [117.204481, 40.069681], + [117.210024, 40.082045], + [117.224191, 40.094865], + [117.224191, 40.094865], + [117.254988, 40.114548], + [117.254988, 40.114548], + [117.254988, 40.114548], + [117.274082, 40.105852], + [117.307343, 40.136971], + [117.349227, 40.136513], + [117.367089, 40.172649], + [117.367089, 40.173106], + [117.367089, 40.173106], + [117.367089, 40.172649], + [117.383719, 40.188195], + [117.389879, 40.227958], + [117.351075, 40.229786], + [117.331365, 40.289613], + [117.295024, 40.2782], + [117.271618, 40.325211], + [117.271618, 40.325211], + [117.243285, 40.369453], + [117.226039, 40.368997], + [117.234046, 40.417312], + [117.263611, 40.442367], + [117.208793, 40.501552], + [117.262995, 40.512927], + [117.247597, 40.539766], + [117.269771, 40.560684], + [117.348611, 40.581141], + [117.348611, 40.581141] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 120000, + "name": "Tianjin", + "center": [117.190182, 39.125596], + "centroid": [117.347043, 39.288036], + "childrenNum": 16, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 1, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [117.765602, 39.400527], + [117.846906, 39.407926], + [117.871543, 39.411625], + [117.870311, 39.455074], + [117.899877, 39.474479], + [117.912195, 39.517428], + [117.912195, 39.517428], + [117.904804, 39.533585], + [117.933753, 39.574191], + [117.868464, 39.59679], + [117.829659, 39.589873], + [117.766834, 39.598635], + [117.753899, 39.579726], + [117.753899, 39.579726], + [117.745276, 39.547892], + [117.715711, 39.529892], + [117.707088, 39.576036], + [117.684914, 39.58895], + [117.654117, 39.575113], + [117.637486, 39.603246], + [117.66274, 39.636437], + [117.668899, 39.666849], + [117.627015, 39.703693], + [117.57774, 39.726711], + [117.595603, 39.74604], + [117.56111, 39.754782], + [117.546327, 39.775943], + [117.561726, 39.799856], + [117.529081, 39.859144], + [117.529081, 39.859144], + [117.508139, 39.901854], + [117.508139, 39.901854], + [117.512451, 39.90874], + [117.512451, 39.90874], + [117.513067, 39.910576], + [117.513067, 39.910576], + [117.514914, 39.946832], + [117.534625, 39.954631], + [117.546327, 39.999116], + [117.594987, 39.994531], + [117.594987, 39.994531], + [117.614697, 39.97252], + [117.671363, 39.973896], + [117.691073, 39.984902], + [117.756363, 39.965181], + [117.781616, 39.966558], + [117.781616, 39.966558], + [117.795167, 39.996823], + [117.795167, 39.996823], + [117.793319, 40.005534], + [117.793319, 40.005534], + [117.768681, 40.022034], + [117.768681, 40.022034], + [117.744044, 40.018368], + [117.74774, 40.047236], + [117.776073, 40.059605], + [117.752667, 40.081588], + [117.71879, 40.082045], + [117.71879, 40.082045], + [117.675059, 40.082045], + [117.655965, 40.109514], + [117.655965, 40.109514], + [117.654117, 40.114548], + [117.654117, 40.114548], + [117.651653, 40.122786], + [117.651653, 40.122786], + [117.613465, 40.158014], + [117.613465, 40.158014], + [117.609769, 40.160301], + [117.609769, 40.160301], + [117.576508, 40.178593], + [117.571581, 40.219276], + [117.548791, 40.232527], + [117.505059, 40.227044], + [117.450241, 40.252627], + [117.415748, 40.248973], + [117.389879, 40.227958], + [117.383719, 40.188195], + [117.367089, 40.172649], + [117.367089, 40.173106], + [117.367089, 40.173106], + [117.367089, 40.172649], + [117.349227, 40.136513], + [117.307343, 40.136971], + [117.274082, 40.105852], + [117.254988, 40.114548], + [117.254988, 40.114548], + [117.254988, 40.114548], + [117.224191, 40.094865], + [117.224191, 40.094865], + [117.210024, 40.082045], + [117.192162, 40.066475], + [117.198322, 39.992697], + [117.150894, 39.944996], + [117.162597, 39.876598], + [117.162597, 39.876598], + [117.227887, 39.852712], + [117.247597, 39.860981], + [117.251908, 39.834332], + [117.192162, 39.832953], + [117.156438, 39.817326], + [117.15767, 39.796638], + [117.205713, 39.763984], + [117.161981, 39.748801], + [117.165061, 39.718886], + [117.165061, 39.718886], + [117.177996, 39.645194], + [117.152742, 39.623532], + [117.10901, 39.625375], + [117.10901, 39.625375], + [117.016004, 39.653949], + [116.983359, 39.638742], + [116.983359, 39.638742], + [116.964265, 39.64335], + [116.948866, 39.680668], + [116.948866, 39.680668], + [116.944555, 39.695405], + [116.944555, 39.695405], + [116.932236, 39.706456], + [116.932236, 39.706456], + [116.90575, 39.688037], + [116.906366, 39.677444], + [116.8497, 39.66777], + [116.812128, 39.615695], + [116.808432, 39.576497], + [116.78749, 39.554352], + [116.819519, 39.528507], + [116.820751, 39.482332], + [116.785026, 39.465702], + [116.832454, 39.435664], + [116.876185, 39.43474], + [116.839845, 39.413474], + [116.840461, 39.378326], + [116.818287, 39.3737], + [116.829374, 39.338994], + [116.870642, 39.357506], + [116.889736, 39.338068], + [116.87249, 39.291304], + [116.881729, 39.225966], + [116.881729, 39.225966], + [116.855859, 39.215766], + [116.870026, 39.153607], + [116.909446, 39.150822], + [116.912526, 39.110898], + [116.91191, 39.111362], + [116.91191, 39.111362], + [116.912526, 39.110898], + [116.871874, 39.054688], + [116.812744, 39.05097], + [116.812744, 39.05097], + [116.783179, 39.05097], + [116.783179, 39.05097], + [116.754229, 39.034701], + [116.754229, 39.034701], + [116.754845, 39.003084], + [116.72836, 38.975174], + [116.708034, 38.931892], + [116.722201, 38.896968], + [116.723432, 38.852706], + [116.75115, 38.831264], + [116.737599, 38.784629], + [116.746222, 38.754299], + [116.794265, 38.744498], + [116.794265, 38.744498], + [116.858939, 38.741231], + [116.877417, 38.680522], + [116.948866, 38.689398], + [116.950714, 38.689398], + [116.95133, 38.689398], + [116.950714, 38.689398], + [116.948866, 38.689398], + [116.95133, 38.689398], + [117.038793, 38.688464], + [117.068358, 38.680522], + [117.055424, 38.639398], + [117.070822, 38.608072], + [117.109626, 38.584685], + [117.150894, 38.617892], + [117.183539, 38.61836], + [117.183539, 38.61836], + [117.213104, 38.639866], + [117.213104, 38.639866], + [117.258684, 38.608072], + [117.258684, 38.608072], + [117.238358, 38.580943], + [117.25314, 38.556143], + [117.368937, 38.564566], + [117.432379, 38.601524], + [117.47919, 38.616489], + [117.55803, 38.613683], + [117.639334, 38.626776], + [117.65658, 38.66043], + [117.729261, 38.680055], + [117.740964, 38.700141], + [117.740964, 38.753833], + [117.671363, 38.772032], + [117.646725, 38.788827], + [117.64611, 38.828933], + [117.752051, 38.847579], + [117.778536, 38.869016], + [117.847522, 38.855502], + [117.875855, 38.920252], + [117.898029, 38.948649], + [117.855529, 38.957492], + [117.837667, 39.057011], + [117.871543, 39.122506], + [117.96455, 39.172631], + [117.977485, 39.206028], + [118.032919, 39.219939], + [118.034767, 39.218548], + [118.064948, 39.231065], + [118.064948, 39.256094], + [118.036615, 39.264898], + [118.024296, 39.289451], + [118.024296, 39.289451], + [117.982412, 39.298714], + [117.982412, 39.298714], + [117.979333, 39.300566], + [117.979333, 39.300566], + [117.973173, 39.312143], + [117.973173, 39.312143], + [117.965782, 39.314921], + [117.965782, 39.314921], + [117.919587, 39.318162], + [117.919587, 39.318162], + [117.88879, 39.332051], + [117.854913, 39.328348], + [117.854297, 39.328348], + [117.854913, 39.328348], + [117.854297, 39.328348], + [117.850601, 39.363984], + [117.850601, 39.363984], + [117.810565, 39.354729], + [117.805022, 39.373237], + [117.784696, 39.376938], + [117.74466, 39.354729], + [117.670747, 39.357969], + [117.669515, 39.322792], + [117.594987, 39.349176], + [117.536472, 39.338068], + [117.521074, 39.357043], + [117.570965, 39.404689], + [117.601146, 39.419485], + [117.614081, 39.407001], + [117.668899, 39.412087], + [117.673211, 39.386652], + [117.699696, 39.407463], + [117.765602, 39.400527] + ] + ], + [ + [ + [117.805022, 39.373237], + [117.852449, 39.380639], + [117.846906, 39.407926], + [117.765602, 39.400527], + [117.784696, 39.376938], + [117.805022, 39.373237] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 130000, + "name": "Hebei", + "center": [114.502461, 38.045474], + "childrenNum": 11, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 2, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [117.467487, 40.649738], + [117.412669, 40.605226], + [117.429915, 40.576141], + [117.389879, 40.561593], + [117.348611, 40.581141], + [117.348611, 40.581141], + [117.269771, 40.560684], + [117.247597, 40.539766], + [117.262995, 40.512927], + [117.208793, 40.501552], + [117.263611, 40.442367], + [117.234046, 40.417312], + [117.226039, 40.368997], + [117.243285, 40.369453], + [117.271618, 40.325211], + [117.271618, 40.325211], + [117.295024, 40.2782], + [117.331365, 40.289613], + [117.351075, 40.229786], + [117.389879, 40.227958], + [117.415748, 40.248973], + [117.450241, 40.252627], + [117.505059, 40.227044], + [117.548791, 40.232527], + [117.571581, 40.219276], + [117.576508, 40.178593], + [117.609769, 40.160301], + [117.609769, 40.160301], + [117.613465, 40.158014], + [117.613465, 40.158014], + [117.651653, 40.122786], + [117.651653, 40.122786], + [117.654117, 40.114548], + [117.654117, 40.114548], + [117.655965, 40.109514], + [117.655965, 40.109514], + [117.675059, 40.082045], + [117.71879, 40.082045], + [117.71879, 40.082045], + [117.752667, 40.081588], + [117.776073, 40.059605], + [117.74774, 40.047236], + [117.744044, 40.018368], + [117.768681, 40.022034], + [117.768681, 40.022034], + [117.793319, 40.005534], + [117.793319, 40.005534], + [117.795167, 39.996823], + [117.795167, 39.996823], + [117.781616, 39.966558], + [117.781616, 39.966558], + [117.756363, 39.965181], + [117.691073, 39.984902], + [117.671363, 39.973896], + [117.614697, 39.97252], + [117.594987, 39.994531], + [117.594987, 39.994531], + [117.546327, 39.999116], + [117.534625, 39.954631], + [117.514914, 39.946832], + [117.513067, 39.910576], + [117.513067, 39.910576], + [117.512451, 39.90874], + [117.512451, 39.90874], + [117.508139, 39.901854], + [117.508139, 39.901854], + [117.529081, 39.859144], + [117.529081, 39.859144], + [117.561726, 39.799856], + [117.546327, 39.775943], + [117.56111, 39.754782], + [117.595603, 39.74604], + [117.57774, 39.726711], + [117.627015, 39.703693], + [117.668899, 39.666849], + [117.66274, 39.636437], + [117.637486, 39.603246], + [117.654117, 39.575113], + [117.684914, 39.58895], + [117.707088, 39.576036], + [117.715711, 39.529892], + [117.745276, 39.547892], + [117.753899, 39.579726], + [117.753899, 39.579726], + [117.766834, 39.598635], + [117.829659, 39.589873], + [117.868464, 39.59679], + [117.933753, 39.574191], + [117.904804, 39.533585], + [117.912195, 39.517428], + [117.912195, 39.517428], + [117.899877, 39.474479], + [117.870311, 39.455074], + [117.871543, 39.411625], + [117.846906, 39.407926], + [117.852449, 39.380639], + [117.805022, 39.373237], + [117.810565, 39.354729], + [117.850601, 39.363984], + [117.850601, 39.363984], + [117.854297, 39.328348], + [117.854913, 39.328348], + [117.854297, 39.328348], + [117.854913, 39.328348], + [117.88879, 39.332051], + [117.919587, 39.318162], + [117.919587, 39.318162], + [117.965782, 39.314921], + [117.965782, 39.314921], + [117.973173, 39.312143], + [117.973173, 39.312143], + [117.979333, 39.300566], + [117.979333, 39.300566], + [117.982412, 39.298714], + [117.982412, 39.298714], + [118.024296, 39.289451], + [118.024296, 39.289451], + [118.036615, 39.264898], + [118.064948, 39.256094], + [118.064948, 39.231065], + [118.034767, 39.218548], + [118.026144, 39.201854], + [118.070492, 39.213911], + [118.077883, 39.201854], + [118.12531, 39.182838], + [118.162883, 39.136433], + [118.1906, 39.080708], + [118.225092, 39.034701], + [118.319331, 39.009594], + [118.366143, 39.016104], + [118.377845, 38.971917], + [118.491178, 38.909077], + [118.539837, 38.910008], + [118.604511, 38.971452], + [118.570634, 38.999363], + [118.533062, 39.090928], + [118.588497, 39.107648], + [118.578642, 39.130863], + [118.637156, 39.157319], + [118.76096, 39.133648], + [118.814546, 39.138754], + [118.857662, 39.162888], + [118.897082, 39.151286], + [118.920488, 39.171703], + [118.951285, 39.178662], + [118.896466, 39.139683], + [118.890307, 39.118792], + [118.926031, 39.123435], + [118.97777, 39.163352], + [119.023966, 39.187012], + [119.038132, 39.211593], + [119.096031, 39.24219], + [119.121284, 39.281576], + [119.185342, 39.342234], + [119.272805, 39.363521], + [119.317153, 39.4107], + [119.316537, 39.437051], + [119.269726, 39.498497], + [119.366428, 39.734996], + [119.474217, 39.813189], + [119.536427, 39.809052], + [119.520413, 39.840306], + [119.540739, 39.888079], + [119.588166, 39.910576], + [119.620195, 39.904609], + [119.642369, 39.925264], + [119.681789, 39.922511], + [119.726137, 39.940867], + [119.787115, 39.950502], + [119.820375, 39.979399], + [119.842549, 39.956007], + [119.872114, 39.960594], + [119.854252, 39.98857], + [119.845629, 40.000949], + [119.845629, 40.000949], + [119.854252, 40.033033], + [119.81668, 40.050443], + [119.81668, 40.050443], + [119.787115, 40.041739], + [119.787115, 40.041739], + [119.783419, 40.046778], + [119.783419, 40.046778], + [119.772332, 40.08113], + [119.736608, 40.104936], + [119.760629, 40.136056], + [119.745847, 40.207851], + [119.716898, 40.195966], + [119.671934, 40.23938], + [119.639289, 40.231613], + [119.639289, 40.231613], + [119.651608, 40.271808], + [119.598021, 40.334335], + [119.586934, 40.375381], + [119.604797, 40.455119], + [119.553674, 40.502007], + [119.572152, 40.523846], + [119.559217, 40.547952], + [119.503783, 40.553864], + [119.477913, 40.533399], + [119.429254, 40.540221], + [119.30237, 40.530215], + [119.256175, 40.543404], + [119.22045, 40.569322], + [119.230921, 40.603863], + [119.177951, 40.609315], + [119.162552, 40.600228], + [119.14469, 40.632482], + [119.184726, 40.680153], + [119.165632, 40.69286], + [119.115125, 40.666536], + [119.054763, 40.664721], + [119.028277, 40.692406], + [119.011031, 40.687414], + [118.96114, 40.72008], + [118.950053, 40.747743], + [118.895234, 40.75409], + [118.907553, 40.775394], + [118.878604, 40.783098], + [118.845959, 40.822057], + [118.873061, 40.847866], + [118.90201, 40.960946], + [118.916792, 40.969984], + [118.977154, 40.959138], + [118.977154, 40.959138], + [119.00056, 40.967273], + [119.013495, 41.007479], + [118.951901, 41.018317], + [118.937118, 41.052625], + [118.964836, 41.079246], + [119.037516, 41.067516], + [119.080632, 41.095936], + [119.081248, 41.131555], + [119.126212, 41.138767], + [119.189038, 41.198234], + [119.169943, 41.222996], + [119.204436, 41.222546], + [119.209364, 41.244599], + [119.2494, 41.279689], + [119.239545, 41.31431], + [119.211827, 41.308016], + [119.197661, 41.282837], + [119.168712, 41.294978], + [119.092951, 41.293629], + [118.980234, 41.305769], + [118.949437, 41.317906], + [118.890923, 41.300823], + [118.844727, 41.342622], + [118.843496, 41.374516], + [118.770199, 41.352956], + [118.741866, 41.324198], + [118.677192, 41.35026], + [118.629765, 41.346666], + [118.528135, 41.355202], + [118.412338, 41.331838], + [118.380309, 41.312062], + [118.348896, 41.342622], + [118.361215, 41.384844], + [118.348896, 41.428384], + [118.327338, 41.450816], + [118.271904, 41.471446], + [118.315636, 41.512688], + [118.302701, 41.55256], + [118.215237, 41.59554], + [118.206614, 41.650566], + [118.159187, 41.67605], + [118.155491, 41.712694], + [118.132702, 41.733241], + [118.140093, 41.784134], + [118.178281, 41.814917], + [118.236179, 41.80778], + [118.247266, 41.773869], + [118.29223, 41.772976], + [118.335346, 41.845241], + [118.340273, 41.87243], + [118.268824, 41.930336], + [118.306396, 41.940131], + [118.313788, 41.98819], + [118.291614, 42.007759], + [118.239875, 42.024655], + [118.286686, 42.033991], + [118.296541, 42.057545], + [118.27252, 42.083312], + [118.239259, 42.092639], + [118.212774, 42.081091], + [118.220165, 42.058434], + [118.194296, 42.031324], + [118.116687, 42.037102], + [118.155491, 42.081091], + [118.097593, 42.105072], + [118.089586, 42.12283], + [118.106216, 42.172082], + [118.033535, 42.199132], + [117.977485, 42.229716], + [117.974405, 42.25054], + [118.047702, 42.280656], + [118.060021, 42.298364], + [118.008898, 42.346595], + [118.024296, 42.385064], + [117.997811, 42.416884], + [117.874007, 42.510038], + [117.856761, 42.539148], + [117.797631, 42.585431], + [117.801326, 42.612744], + [117.779768, 42.61847], + [117.708935, 42.588515], + [117.667051, 42.582347], + [117.60053, 42.603054], + [117.537088, 42.603054], + [117.530313, 42.590278], + [117.475494, 42.602613], + [117.435458, 42.585431], + [117.434226, 42.557224], + [117.387415, 42.517537], + [117.387415, 42.517537], + [117.410205, 42.519743], + [117.413284, 42.471645], + [117.390495, 42.461933], + [117.332596, 42.46105], + [117.332596, 42.46105], + [117.275314, 42.481797], + [117.275314, 42.481797], + [117.188467, 42.468114], + [117.188467, 42.468114], + [117.135496, 42.468996], + [117.09546, 42.484004], + [117.080061, 42.463699], + [117.080061, 42.463699], + [117.01662, 42.456193], + [117.01662, 42.456193], + [117.009228, 42.44957], + [117.009228, 42.44957], + [117.005533, 42.43367], + [117.005533, 42.43367], + [116.99075, 42.425719], + [116.99075, 42.425719], + [116.974736, 42.426603], + [116.974736, 42.426603], + [116.97104, 42.427486], + [116.97104, 42.427486], + [116.944555, 42.415116], + [116.944555, 42.415116], + [116.936547, 42.410256], + [116.936547, 42.410256], + [116.921765, 42.403628], + [116.921765, 42.403628], + [116.910062, 42.395231], + [116.910062, 42.395231], + [116.910678, 42.394789], + [116.910678, 42.394789], + [116.886656, 42.366496], + [116.897743, 42.297479], + [116.918685, 42.229716], + [116.903287, 42.190708], + [116.789338, 42.200462], + [116.825062, 42.155669], + [116.850316, 42.156556], + [116.890352, 42.092639], + [116.879881, 42.018431], + [116.796113, 41.977958], + [116.748686, 41.984186], + [116.727744, 41.951259], + [116.66923, 41.947698], + [116.639049, 41.929891], + [116.597165, 41.935679], + [116.553433, 41.928555], + [116.510933, 41.974399], + [116.4826, 41.975734], + [116.453651, 41.945917], + [116.393289, 41.942802], + [116.414231, 41.982407], + [116.373579, 42.009983], + [116.310137, 41.997086], + [116.298434, 41.96817], + [116.223906, 41.932562], + [116.212819, 41.885352], + [116.194341, 41.861734], + [116.122892, 41.861734], + [116.106877, 41.831419], + [116.129051, 41.805996], + [116.09887, 41.776547], + [116.034196, 41.782795], + [116.007095, 41.79752], + [116.007095, 41.797966], + [116.007095, 41.79752], + [116.007095, 41.797966], + [115.994776, 41.828743], + [115.954124, 41.874213], + [115.916552, 41.945027], + [115.85311, 41.927665], + [115.834632, 41.93835], + [115.811226, 41.912525], + [115.726227, 41.870202], + [115.688038, 41.867528], + [115.654162, 41.829189], + [115.57409, 41.80555], + [115.519887, 41.76762], + [115.488474, 41.760924], + [115.42996, 41.728775], + [115.346808, 41.712247], + [115.319091, 41.691693], + [115.360975, 41.661297], + [115.345576, 41.635807], + [115.377605, 41.603148], + [115.310468, 41.592854], + [115.290142, 41.622835], + [115.26612, 41.616124], + [115.256881, 41.580768], + [115.20391, 41.571367], + [115.195287, 41.602253], + [115.0992, 41.62373], + [115.056085, 41.602253], + [115.016049, 41.615229], + [114.860832, 41.60091], + [114.895325, 41.636255], + [114.902716, 41.695715], + [114.89594, 41.76762], + [114.868839, 41.813579], + [114.922426, 41.825175], + [114.939056, 41.846132], + [114.923658, 41.871093], + [114.915035, 41.960605], + [114.9021, 42.015763], + [114.860832, 42.054879], + [114.86268, 42.097967], + [114.825723, 42.139695], + [114.79431, 42.149457], + [114.789383, 42.130819], + [114.75489, 42.115727], + [114.675434, 42.12061], + [114.647717, 42.109512], + [114.560254, 42.132595], + [114.510978, 42.110844], + [114.502355, 42.06732], + [114.480181, 42.064654], + [114.467863, 42.025989], + [114.511594, 41.981962], + [114.478334, 41.951704], + [114.419203, 41.942356], + [114.352066, 41.953484], + [114.343443, 41.926774], + [114.282465, 41.863517], + [114.200545, 41.789934], + [114.215328, 41.75646], + [114.206704, 41.7386], + [114.237501, 41.698843], + [114.215328, 41.68499], + [114.259059, 41.623282], + [114.226414, 41.616572], + [114.221487, 41.582111], + [114.230726, 41.513584], + [114.101379, 41.537779], + [114.032394, 41.529715], + [113.976959, 41.505966], + [113.953553, 41.483553], + [113.933227, 41.487139], + [113.919677, 41.454404], + [113.877793, 41.431076], + [113.871017, 41.413126], + [113.94493, 41.392477], + [113.92522, 41.325546], + [113.899351, 41.316108], + [113.914749, 41.294529], + [113.95109, 41.282837], + [113.971416, 41.239649], + [113.992357, 41.269794], + [114.016379, 41.231999], + [113.996669, 41.19238], + [113.960945, 41.171211], + [113.920293, 41.172112], + [113.877793, 41.115777], + [113.819279, 41.09774], + [113.868554, 41.06887], + [113.973263, 40.983087], + [113.994821, 40.938798], + [114.057647, 40.925234], + [114.041633, 40.917546], + [114.055183, 40.867782], + [114.073661, 40.857372], + [114.044712, 40.830661], + [114.080437, 40.790348], + [114.104458, 40.797597], + [114.103227, 40.770861], + [114.134639, 40.737314], + [114.162357, 40.71373], + [114.183299, 40.67153], + [114.236269, 40.607043], + [114.283081, 40.590685], + [114.273842, 40.552954], + [114.293552, 40.55159], + [114.282465, 40.494725], + [114.267066, 40.474242], + [114.299711, 40.44009], + [114.286161, 40.425057], + [114.31203, 40.372645], + [114.381015, 40.36307], + [114.390254, 40.351213], + [114.438914, 40.371733], + [114.481413, 40.34802], + [114.530688, 40.345283], + [114.510978, 40.302851], + [114.46971, 40.268155], + [114.406269, 40.246232], + [114.362537, 40.249886], + [114.292936, 40.230242], + [114.255364, 40.236182], + [114.235654, 40.198252], + [114.180219, 40.191395], + [114.135871, 40.175392], + [114.097683, 40.193681], + [114.073046, 40.168533], + [114.073046, 40.168533], + [114.101995, 40.099901], + [114.086596, 40.071513], + [114.045944, 40.056856], + [114.018227, 40.103563], + [113.989278, 40.11226], + [113.959097, 40.033491], + [113.910438, 40.015618], + [114.029314, 39.985819], + [114.028082, 39.959218], + [114.047176, 39.916085], + [114.067502, 39.922511], + [114.17406, 39.897722], + [114.212248, 39.918839], + [114.229494, 39.899558], + [114.204241, 39.885324], + [114.215943, 39.8619], + [114.286776, 39.871087], + [114.285545, 39.858225], + [114.395182, 39.867412], + [114.406885, 39.833413], + [114.390254, 39.819165], + [114.41674, 39.775943], + [114.409964, 39.761683], + [114.408117, 39.652106], + [114.431522, 39.613851], + [114.49558, 39.608318], + [114.51529, 39.564964], + [114.568877, 39.573729], + [114.532536, 39.486027], + [114.501739, 39.476789], + [114.496812, 39.438437], + [114.469095, 39.400989], + [114.466631, 39.329736], + [114.430906, 39.307513], + [114.437066, 39.259337], + [114.416124, 39.242654], + [114.47587, 39.21623], + [114.443841, 39.174023], + [114.388406, 39.176807], + [114.360689, 39.134112], + [114.369928, 39.107648], + [114.345907, 39.075133], + [114.252284, 39.073739], + [114.180835, 39.049111], + [114.157429, 39.061194], + [114.10877, 39.052364], + [114.082901, 39.09325], + [114.082901, 39.09325], + [114.064422, 39.094179], + [114.050872, 39.135969], + [114.006524, 39.122971], + [113.994821, 39.095572], + [113.961561, 39.100681], + [113.930148, 39.063517], + [113.898119, 39.067699], + [113.80696, 38.989595], + [113.776779, 38.986804], + [113.76754, 38.959819], + [113.776163, 38.885788], + [113.795257, 38.860628], + [113.855619, 38.828933], + [113.836525, 38.795824], + [113.839605, 38.7585], + [113.802648, 38.763166], + [113.775547, 38.709949], + [113.720728, 38.713218], + [113.70225, 38.651551], + [113.612939, 38.645942], + [113.603084, 38.587024], + [113.561816, 38.558483], + [113.546417, 38.492936], + [113.583374, 38.459671], + [113.537794, 38.417952], + [113.525475, 38.383245], + [113.557504, 38.343359], + [113.54457, 38.270569], + [113.570439, 38.237202], + [113.598772, 38.22733], + [113.64312, 38.232031], + [113.678844, 38.20523], + [113.711489, 38.213695], + [113.720728, 38.174656], + [113.797105, 38.162894], + [113.831597, 38.16854], + [113.811271, 38.117707], + [113.876561, 38.055059], + [113.872249, 37.990471], + [113.901198, 37.984811], + [113.936307, 37.922993], + [113.959097, 37.906468], + [113.976959, 37.816696], + [114.006524, 37.813386], + [114.044712, 37.761834], + [113.996669, 37.730128], + [113.993589, 37.706932], + [114.068118, 37.721608], + [114.12848, 37.698409], + [114.139567, 37.675676], + [114.115545, 37.619761], + [114.118625, 37.59084], + [114.036705, 37.494037], + [114.014531, 37.42468], + [113.973879, 37.40329], + [113.962792, 37.355734], + [113.90243, 37.310052], + [113.886416, 37.239095], + [113.853155, 37.215269], + [113.832213, 37.167594], + [113.773083, 37.151855], + [113.773699, 37.107004], + [113.758301, 37.075497], + [113.788482, 37.059739], + [113.771851, 37.016745], + [113.791561, 36.98759], + [113.76138, 36.956034], + [113.792793, 36.894796], + [113.773083, 36.85506], + [113.731815, 36.858891], + [113.731815, 36.878521], + [113.696707, 36.882351], + [113.676381, 36.855539], + [113.680692, 36.789907], + [113.600004, 36.752995], + [113.549497, 36.752515], + [113.535946, 36.732373], + [113.499606, 36.740527], + [113.465113, 36.707908], + [113.506997, 36.705029], + [113.476816, 36.655114], + [113.486671, 36.635427], + [113.54457, 36.62342], + [113.539642, 36.594116], + [113.569823, 36.585947], + [113.588917, 36.547974], + [113.559968, 36.528741], + [113.554425, 36.494589], + [113.587069, 36.460904], + [113.635729, 36.451277], + [113.670221, 36.425278], + [113.708409, 36.423352], + [113.731199, 36.363135], + [113.755221, 36.366026], + [113.813119, 36.332285], + [113.856851, 36.329392], + [113.84946, 36.347711], + [113.882104, 36.353977], + [113.911054, 36.314927], + [113.962792, 36.353977], + [113.981887, 36.31782], + [114.002828, 36.334214], + [114.056415, 36.329392], + [114.04348, 36.303353], + [114.080437, 36.269585], + [114.129096, 36.280199], + [114.175907, 36.264759], + [114.170364, 36.245938], + [114.170364, 36.245938], + [114.203009, 36.245456], + [114.2104, 36.272962], + [114.241197, 36.251247], + [114.257827, 36.263794], + [114.299095, 36.245938], + [114.345291, 36.255591], + [114.356378, 36.230492], + [114.408117, 36.224699], + [114.417356, 36.205868], + [114.466015, 36.197658], + [114.480181, 36.177855], + [114.533152, 36.171575], + [114.586739, 36.141133], + [114.588587, 36.118414], + [114.640326, 36.137266], + [114.720398, 36.140166], + [114.734564, 36.15563], + [114.771521, 36.124699], + [114.857752, 36.127599], + [114.858368, 36.144516], + [114.912571, 36.140649], + [114.926737, 36.089403], + [114.914419, 36.052155], + [114.998186, 36.069572], + [115.04623, 36.112613], + [115.048693, 36.161912], + [115.06286, 36.178338], + [115.104744, 36.172058], + [115.12507, 36.209731], + [115.1842, 36.193312], + [115.201446, 36.210214], + [115.201446, 36.210214], + [115.202678, 36.209248], + [115.202678, 36.209248], + [115.202678, 36.208765], + [115.202678, 36.208765], + [115.242098, 36.19138], + [115.279055, 36.13775], + [115.30246, 36.127599], + [115.312931, 36.088436], + [115.365902, 36.099074], + [115.376989, 36.128083], + [115.450902, 36.152248], + [115.465068, 36.170125], + [115.483547, 36.148865], + [115.474923, 36.248352], + [115.466916, 36.258969], + [115.466916, 36.258969], + [115.462605, 36.276339], + [115.417025, 36.292742], + [115.423185, 36.32216], + [115.366518, 36.30914], + [115.368982, 36.342409], + [115.340033, 36.398307], + [115.297533, 36.413239], + [115.317243, 36.454166], + [115.291374, 36.460423], + [115.272895, 36.497476], + [115.33141, 36.550378], + [115.355431, 36.627262], + [115.365902, 36.621979], + [115.420105, 36.686795], + [115.451518, 36.702151], + [115.479851, 36.760187], + [115.524815, 36.763543], + [115.683727, 36.808117], + [115.71206, 36.883308], + [115.75764, 36.902453], + [115.79706, 36.968945], + [115.776734, 36.992848], + [115.85619, 37.060694], + [115.888219, 37.112254], + [115.879596, 37.150901], + [115.91224, 37.177132], + [115.909777, 37.20669], + [115.969523, 37.239572], + [115.975682, 37.337179], + [116.024341, 37.360015], + [116.085935, 37.373809], + [116.106261, 37.368577], + [116.169087, 37.384271], + [116.193109, 37.365723], + [116.236224, 37.361442], + [116.2855, 37.404241], + [116.226369, 37.428007], + [116.243, 37.447965], + [116.224522, 37.479791], + [116.240536, 37.489764], + [116.240536, 37.489764], + [116.27626, 37.466967], + [116.290427, 37.484065], + [116.278724, 37.524895], + [116.295355, 37.554316], + [116.336007, 37.581355], + [116.36742, 37.566177], + [116.379738, 37.522047], + [116.38097, 37.522522], + [116.379738, 37.522047], + [116.38097, 37.522522], + [116.433941, 37.473142], + [116.448108, 37.503059], + [116.4826, 37.521573], + [116.575607, 37.610754], + [116.604556, 37.624975], + [116.66307, 37.686096], + [116.679085, 37.728708], + [116.724664, 37.744327], + [116.753613, 37.77035], + [116.753613, 37.793054], + [116.804736, 37.848837], + [116.837997, 37.835132], + [116.919301, 37.846002], + [117.027091, 37.832296], + [117.074518, 37.848837], + [117.150278, 37.839385], + [117.185387, 37.849783], + [117.271618, 37.839858], + [117.320278, 37.861596], + [117.400966, 37.844584], + [117.438538, 37.854035], + [117.481038, 37.914967], + [117.513067, 37.94329], + [117.524154, 37.989527], + [117.557414, 38.046105], + [117.557414, 38.046105], + [117.586979, 38.071551], + [117.704624, 38.076262], + [117.746508, 38.12524], + [117.771145, 38.134655], + [117.766834, 38.158658], + [117.789007, 38.180772], + [117.808718, 38.22827], + [117.848754, 38.255062], + [117.895565, 38.301572], + [117.948536, 38.346644], + [117.957775, 38.376208], + [117.937449, 38.387936], + [117.84629, 38.368232], + [117.781, 38.373862], + [117.730493, 38.424985], + [117.72495, 38.457328], + [117.678754, 38.477008], + [117.644878, 38.52759], + [117.68553, 38.539293], + [117.638102, 38.54491], + [117.639334, 38.626776], + [117.55803, 38.613683], + [117.47919, 38.616489], + [117.432379, 38.601524], + [117.368937, 38.564566], + [117.25314, 38.556143], + [117.238358, 38.580943], + [117.258684, 38.608072], + [117.258684, 38.608072], + [117.213104, 38.639866], + [117.213104, 38.639866], + [117.183539, 38.61836], + [117.183539, 38.61836], + [117.150894, 38.617892], + [117.109626, 38.584685], + [117.070822, 38.608072], + [117.055424, 38.639398], + [117.068358, 38.680522], + [117.038793, 38.688464], + [116.95133, 38.689398], + [116.948866, 38.689398], + [116.950714, 38.689398], + [116.95133, 38.689398], + [116.950714, 38.689398], + [116.948866, 38.689398], + [116.877417, 38.680522], + [116.858939, 38.741231], + [116.794265, 38.744498], + [116.794265, 38.744498], + [116.746222, 38.754299], + [116.737599, 38.784629], + [116.75115, 38.831264], + [116.723432, 38.852706], + [116.722201, 38.896968], + [116.708034, 38.931892], + [116.72836, 38.975174], + [116.754845, 39.003084], + [116.754229, 39.034701], + [116.754229, 39.034701], + [116.783179, 39.05097], + [116.783179, 39.05097], + [116.812744, 39.05097], + [116.812744, 39.05097], + [116.871874, 39.054688], + [116.912526, 39.110898], + [116.91191, 39.111362], + [116.91191, 39.111362], + [116.912526, 39.110898], + [116.909446, 39.150822], + [116.870026, 39.153607], + [116.855859, 39.215766], + [116.881729, 39.225966], + [116.881729, 39.225966], + [116.87249, 39.291304], + [116.889736, 39.338068], + [116.870642, 39.357506], + [116.829374, 39.338994], + [116.818287, 39.3737], + [116.840461, 39.378326], + [116.839845, 39.413474], + [116.876185, 39.43474], + [116.832454, 39.435664], + [116.785026, 39.465702], + [116.820751, 39.482332], + [116.819519, 39.528507], + [116.78749, 39.554352], + [116.808432, 39.576497], + [116.812128, 39.615695], + [116.79057, 39.595868], + [116.748686, 39.619844], + [116.709266, 39.618], + [116.726512, 39.595407], + [116.726512, 39.595407], + [116.724048, 39.59264], + [116.723432, 39.59264], + [116.724048, 39.59264], + [116.723432, 39.59264], + [116.664918, 39.605552], + [116.620571, 39.601863], + [116.592237, 39.621227], + [116.592237, 39.621227], + [116.524484, 39.596329], + [116.50847, 39.551122], + [116.473361, 39.552968], + [116.478289, 39.535431], + [116.437637, 39.526661], + [116.443796, 39.510041], + [116.401912, 39.528046], + [116.411767, 39.482794], + [116.444412, 39.482332], + [116.454883, 39.453226], + [116.434557, 39.442597], + [116.361876, 39.455074], + [116.361876, 39.455074], + [116.337854, 39.455536], + [116.307057, 39.488337], + [116.257782, 39.500344], + [116.240536, 39.564041], + [116.198652, 39.589412], + [116.151841, 39.583416], + [116.130283, 39.567732], + [116.09887, 39.575113], + [116.036044, 39.571884], + [116.026189, 39.587567], + [115.995392, 39.576958], + [115.978146, 39.595868], + [115.957204, 39.560812], + [115.910393, 39.600479], + [115.910393, 39.600479], + [115.91532, 39.582955], + [115.91532, 39.582955], + [115.867893, 39.546507], + [115.867893, 39.546507], + [115.828473, 39.541431], + [115.821081, 39.522968], + [115.821081, 39.522968], + [115.806299, 39.510041], + [115.806299, 39.510041], + [115.752712, 39.515581], + [115.738545, 39.539585], + [115.738545, 39.540046], + [115.738545, 39.539585], + [115.738545, 39.540046], + [115.724995, 39.5442], + [115.724995, 39.5442], + [115.722531, 39.543738], + [115.721299, 39.543738], + [115.722531, 39.543738], + [115.722531, 39.5442], + [115.721299, 39.543738], + [115.722531, 39.5442], + [115.720683, 39.551122], + [115.720683, 39.551122], + [115.718835, 39.553891], + [115.718835, 39.553891], + [115.716988, 39.56035], + [115.716988, 39.56035], + [115.699125, 39.570039], + [115.699125, 39.570039], + [115.698509, 39.577881], + [115.698509, 39.577881], + [115.667712, 39.615234], + [115.633836, 39.599557], + [115.633836, 39.599557], + [115.587024, 39.589873], + [115.545756, 39.618922], + [115.518039, 39.597252], + [115.522351, 39.640124], + [115.478619, 39.650723], + [115.478619, 39.650723], + [115.491554, 39.670074], + [115.486626, 39.741899], + [115.439815, 39.752022], + [115.443511, 39.785601], + [115.483547, 39.798477], + [115.483547, 39.798477], + [115.50572, 39.784222], + [115.552532, 39.794799], + [115.567314, 39.816407], + [115.514344, 39.837549], + [115.526046, 39.87568], + [115.515575, 39.892212], + [115.515575, 39.892212], + [115.522967, 39.899099], + [115.481083, 39.935819], + [115.426264, 39.950502], + [115.428728, 39.984443], + [115.450286, 39.992697], + [115.454597, 40.029825], + [115.485394, 40.040364], + [115.527278, 40.076092], + [115.59072, 40.096239], + [115.599959, 40.119583], + [115.75456, 40.145663], + [115.75456, 40.145663], + [115.773654, 40.176307], + [115.806299, 40.15344], + [115.847567, 40.147036], + [115.855574, 40.188652], + [115.870356, 40.185909], + [115.89869, 40.234354], + [115.968907, 40.264045], + [115.95166, 40.281852], + [115.917784, 40.354405], + [115.864197, 40.359422], + [115.771806, 40.443734], + [115.781045, 40.49336], + [115.736082, 40.503372], + [115.755176, 40.540221], + [115.784741, 40.55841], + [115.819849, 40.55932], + [115.827857, 40.587504], + [115.885139, 40.595229], + [115.907929, 40.617493], + [115.971986, 40.6025], + [115.982457, 40.578868], + [116.005247, 40.583868], + [116.09887, 40.630665], + [116.133979, 40.666536], + [116.162928, 40.662451], + [116.171551, 40.695582], + [116.204812, 40.740035], + [116.22021, 40.744115], + [116.247311, 40.791707], + [116.273181, 40.762703], + [116.311369, 40.754996], + [116.316912, 40.772221], + [116.453651, 40.765876], + [116.46597, 40.774487], + [116.438253, 40.81934], + [116.334159, 40.90443], + [116.339702, 40.929303], + [116.370499, 40.94377], + [116.398216, 40.90624], + [116.477057, 40.899907], + [116.447492, 40.953715], + [116.455499, 40.980828], + [116.519557, 40.98128], + [116.519557, 40.98128], + [116.5676, 40.992574], + [116.598397, 40.974503], + [116.623034, 41.021026], + [116.615643, 41.053076], + [116.647672, 41.059394], + [116.688324, 41.044501], + [116.698795, 41.021477], + [116.677853, 40.970888], + [116.722201, 40.927495], + [116.713577, 40.909858], + [116.759773, 40.889954], + [116.81336, 40.848319], + [116.848468, 40.839264], + [116.924229, 40.773581], + [116.926692, 40.745022], + [116.964881, 40.709647], + [117.012308, 40.693767], + [117.11209, 40.707379], + [117.117018, 40.70012], + [117.208177, 40.694675], + [117.278394, 40.664267], + [117.319662, 40.657911], + [117.342451, 40.673799], + [117.408973, 40.686961], + [117.493973, 40.675161], + [117.514914, 40.660181], + [117.501364, 40.636569], + [117.467487, 40.649738], + [117.467487, 40.649738] + ] + ], + [ + [ + [117.210024, 40.082045], + [117.204481, 40.069681], + [117.159517, 40.077008], + [117.140423, 40.064185], + [117.105315, 40.074261], + [117.105315, 40.074261], + [117.051728, 40.059605], + [117.025243, 40.030283], + [116.945171, 40.04128], + [116.927924, 40.055024], + [116.867562, 40.041739], + [116.831222, 40.051359], + [116.820135, 40.02845], + [116.781331, 40.034866], + [116.757925, 39.967934], + [116.782563, 39.947749], + [116.78441, 39.891294], + [116.812128, 39.889916], + [116.865714, 39.843982], + [116.907598, 39.832494], + [116.918069, 39.84628], + [116.949482, 39.778703], + [116.902055, 39.763523], + [116.916837, 39.731314], + [116.887272, 39.72533], + [116.889736, 39.687576], + [116.90575, 39.688037], + [116.932236, 39.706456], + [116.932236, 39.706456], + [116.944555, 39.695405], + [116.944555, 39.695405], + [116.948866, 39.680668], + [116.948866, 39.680668], + [116.964265, 39.64335], + [116.983359, 39.638742], + [116.983359, 39.638742], + [117.016004, 39.653949], + [117.10901, 39.625375], + [117.10901, 39.625375], + [117.152742, 39.623532], + [117.177996, 39.645194], + [117.165061, 39.718886], + [117.165061, 39.718886], + [117.161981, 39.748801], + [117.205713, 39.763984], + [117.15767, 39.796638], + [117.156438, 39.817326], + [117.192162, 39.832953], + [117.251908, 39.834332], + [117.247597, 39.860981], + [117.227887, 39.852712], + [117.162597, 39.876598], + [117.162597, 39.876598], + [117.150894, 39.944996], + [117.198322, 39.992697], + [117.192162, 40.066475], + [117.210024, 40.082045] + ] + ], + [ + [ + [117.784696, 39.376938], + [117.765602, 39.400527], + [117.699696, 39.407463], + [117.673211, 39.386652], + [117.668899, 39.412087], + [117.614081, 39.407001], + [117.601146, 39.419485], + [117.570965, 39.404689], + [117.521074, 39.357043], + [117.536472, 39.338068], + [117.594987, 39.349176], + [117.669515, 39.322792], + [117.670747, 39.357969], + [117.74466, 39.354729], + [117.784696, 39.376938] + ] + ], + [ + [ + [118.869365, 39.142932], + [118.82009, 39.108576], + [118.857662, 39.098824], + [118.869365, 39.142932] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 140000, + "name": "Shanxi", + "center": [112.549248, 37.857014], + "centroid": [112.304436, 37.618179], + "childrenNum": 11, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 3, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [110.379257, 34.600612], + [110.424837, 34.588295], + [110.488279, 34.610956], + [110.533242, 34.583368], + [110.610851, 34.607508], + [110.710017, 34.605045], + [110.749437, 34.65232], + [110.791937, 34.649858], + [110.824582, 34.615881], + [110.883712, 34.64395], + [110.903422, 34.669056], + [110.920052, 34.730068], + [110.976103, 34.706456], + [111.035233, 34.740887], + [111.118385, 34.756623], + [111.148566, 34.807742], + [111.232949, 34.789559], + [111.255123, 34.819535], + [111.29208, 34.806759], + [111.345666, 34.831816], + [111.389398, 34.815113], + [111.439289, 34.838202], + [111.502731, 34.829851], + [111.543999, 34.853428], + [111.570484, 34.843114], + [111.592042, 34.881416], + [111.617911, 34.894671], + [111.646861, 34.938836], + [111.681969, 34.9511], + [111.664107, 34.984449], + [111.740483, 35.00455], + [111.807005, 35.032977], + [111.810084, 35.062374], + [111.933272, 35.083435], + [111.97762, 35.067272], + [112.018888, 35.068742], + [112.039214, 35.045717], + [112.062004, 35.056005], + [112.05646, 35.098615], + [112.066315, 35.153437], + [112.03983, 35.194039], + [112.078634, 35.219467], + [112.058924, 35.280069], + [112.13838, 35.271275], + [112.21722, 35.253195], + [112.242474, 35.234622], + [112.304684, 35.251728], + [112.288053, 35.219956], + [112.36751, 35.219956], + [112.390915, 35.239021], + [112.513487, 35.218489], + [112.637291, 35.225822], + [112.628052, 35.263457], + [112.720443, 35.206265], + [112.772798, 35.207732], + [112.822073, 35.258082], + [112.884283, 35.243909], + [112.934174, 35.262968], + [112.936022, 35.284466], + [112.992072, 35.29619], + [112.985913, 35.33965], + [112.996384, 35.362104], + [113.067217, 35.353806], + [113.126347, 35.332327], + [113.149137, 35.350878], + [113.165151, 35.412845], + [113.185477, 35.409431], + [113.189789, 35.44893], + [113.243375, 35.449418], + [113.304353, 35.426989], + [113.31236, 35.481101], + [113.348085, 35.468429], + [113.391817, 35.506925], + [113.439244, 35.507412], + [113.49899, 35.532254], + [113.513773, 35.57364], + [113.55812, 35.621816], + [113.547649, 35.656835], + [113.578446, 35.633491], + [113.625258, 35.632518], + [113.622794, 35.674825], + [113.592613, 35.691838], + [113.587685, 35.736542], + [113.604932, 35.797727], + [113.582758, 35.818111], + [113.660982, 35.837035], + [113.637576, 35.870019], + [113.654207, 35.931586], + [113.648663, 35.994073], + [113.678844, 35.985841], + [113.694859, 36.026991], + [113.660366, 36.034735], + [113.68562, 36.056026], + [113.671453, 36.115514], + [113.655439, 36.125182], + [113.712721, 36.129533], + [113.705946, 36.148865], + [113.651127, 36.174473], + [113.697939, 36.181719], + [113.681924, 36.216491], + [113.716417, 36.262347], + [113.712105, 36.303353], + [113.736127, 36.324571], + [113.731199, 36.363135], + [113.708409, 36.423352], + [113.670221, 36.425278], + [113.635729, 36.451277], + [113.587069, 36.460904], + [113.554425, 36.494589], + [113.559968, 36.528741], + [113.588917, 36.547974], + [113.569823, 36.585947], + [113.539642, 36.594116], + [113.54457, 36.62342], + [113.486671, 36.635427], + [113.476816, 36.655114], + [113.506997, 36.705029], + [113.465113, 36.707908], + [113.499606, 36.740527], + [113.535946, 36.732373], + [113.549497, 36.752515], + [113.600004, 36.752995], + [113.680692, 36.789907], + [113.676381, 36.855539], + [113.696707, 36.882351], + [113.731815, 36.878521], + [113.731815, 36.858891], + [113.773083, 36.85506], + [113.792793, 36.894796], + [113.76138, 36.956034], + [113.791561, 36.98759], + [113.771851, 37.016745], + [113.788482, 37.059739], + [113.758301, 37.075497], + [113.773699, 37.107004], + [113.773083, 37.151855], + [113.832213, 37.167594], + [113.853155, 37.215269], + [113.886416, 37.239095], + [113.90243, 37.310052], + [113.962792, 37.355734], + [113.973879, 37.40329], + [114.014531, 37.42468], + [114.036705, 37.494037], + [114.118625, 37.59084], + [114.115545, 37.619761], + [114.139567, 37.675676], + [114.12848, 37.698409], + [114.068118, 37.721608], + [113.993589, 37.706932], + [113.996669, 37.730128], + [114.044712, 37.761834], + [114.006524, 37.813386], + [113.976959, 37.816696], + [113.959097, 37.906468], + [113.936307, 37.922993], + [113.901198, 37.984811], + [113.872249, 37.990471], + [113.876561, 38.055059], + [113.811271, 38.117707], + [113.831597, 38.16854], + [113.797105, 38.162894], + [113.720728, 38.174656], + [113.711489, 38.213695], + [113.678844, 38.20523], + [113.64312, 38.232031], + [113.598772, 38.22733], + [113.570439, 38.237202], + [113.54457, 38.270569], + [113.557504, 38.343359], + [113.525475, 38.383245], + [113.537794, 38.417952], + [113.583374, 38.459671], + [113.546417, 38.492936], + [113.561816, 38.558483], + [113.603084, 38.587024], + [113.612939, 38.645942], + [113.70225, 38.651551], + [113.720728, 38.713218], + [113.775547, 38.709949], + [113.802648, 38.763166], + [113.839605, 38.7585], + [113.836525, 38.795824], + [113.855619, 38.828933], + [113.795257, 38.860628], + [113.776163, 38.885788], + [113.76754, 38.959819], + [113.776779, 38.986804], + [113.80696, 38.989595], + [113.898119, 39.067699], + [113.930148, 39.063517], + [113.961561, 39.100681], + [113.994821, 39.095572], + [114.006524, 39.122971], + [114.050872, 39.135969], + [114.064422, 39.094179], + [114.082901, 39.09325], + [114.082901, 39.09325], + [114.10877, 39.052364], + [114.157429, 39.061194], + [114.180835, 39.049111], + [114.252284, 39.073739], + [114.345907, 39.075133], + [114.369928, 39.107648], + [114.360689, 39.134112], + [114.388406, 39.176807], + [114.443841, 39.174023], + [114.47587, 39.21623], + [114.416124, 39.242654], + [114.437066, 39.259337], + [114.430906, 39.307513], + [114.466631, 39.329736], + [114.469095, 39.400989], + [114.496812, 39.438437], + [114.501739, 39.476789], + [114.532536, 39.486027], + [114.568877, 39.573729], + [114.51529, 39.564964], + [114.49558, 39.608318], + [114.431522, 39.613851], + [114.408117, 39.652106], + [114.409964, 39.761683], + [114.41674, 39.775943], + [114.390254, 39.819165], + [114.406885, 39.833413], + [114.395182, 39.867412], + [114.285545, 39.858225], + [114.286776, 39.871087], + [114.215943, 39.8619], + [114.204241, 39.885324], + [114.229494, 39.899558], + [114.212248, 39.918839], + [114.17406, 39.897722], + [114.067502, 39.922511], + [114.047176, 39.916085], + [114.028082, 39.959218], + [114.029314, 39.985819], + [113.910438, 40.015618], + [113.959097, 40.033491], + [113.989278, 40.11226], + [114.018227, 40.103563], + [114.045944, 40.056856], + [114.086596, 40.071513], + [114.101995, 40.099901], + [114.073046, 40.168533], + [114.073046, 40.168533], + [114.097683, 40.193681], + [114.135871, 40.175392], + [114.180219, 40.191395], + [114.235654, 40.198252], + [114.255364, 40.236182], + [114.292936, 40.230242], + [114.362537, 40.249886], + [114.406269, 40.246232], + [114.46971, 40.268155], + [114.510978, 40.302851], + [114.530688, 40.345283], + [114.481413, 40.34802], + [114.438914, 40.371733], + [114.390254, 40.351213], + [114.381015, 40.36307], + [114.31203, 40.372645], + [114.286161, 40.425057], + [114.299711, 40.44009], + [114.267066, 40.474242], + [114.282465, 40.494725], + [114.293552, 40.55159], + [114.273842, 40.552954], + [114.283081, 40.590685], + [114.236269, 40.607043], + [114.183299, 40.67153], + [114.162357, 40.71373], + [114.134639, 40.737314], + [114.084748, 40.729605], + [114.063806, 40.706925], + [114.07243, 40.679246], + [114.041633, 40.608861], + [114.076741, 40.575686], + [114.080437, 40.547952], + [114.061959, 40.52885], + [114.011452, 40.515657], + [113.948626, 40.514747], + [113.890112, 40.466503], + [113.850691, 40.460583], + [113.794641, 40.517932], + [113.763228, 40.473787], + [113.688699, 40.448288], + [113.559968, 40.348476], + [113.500222, 40.334335], + [113.387505, 40.319279], + [113.316672, 40.319736], + [113.27602, 40.388601], + [113.251382, 40.413211], + [113.083231, 40.374925], + [113.03334, 40.368997], + [112.898449, 40.329317], + [112.848558, 40.206937], + [112.744464, 40.167161], + [112.712436, 40.178593], + [112.6299, 40.235725], + [112.511639, 40.269068], + [112.456205, 40.300112], + [112.418017, 40.295091], + [112.349031, 40.257194], + [112.310227, 40.256281], + [112.299756, 40.21105], + [112.232619, 40.169905], + [112.232003, 40.133311], + [112.183344, 40.083877], + [112.182112, 40.061437], + [112.142076, 40.027076], + [112.133453, 40.001866], + [112.07617, 39.919298], + [112.042294, 39.886243], + [112.012729, 39.827438], + [111.970229, 39.796638], + [111.959758, 39.692642], + [111.925265, 39.66731], + [111.9382, 39.623071], + [111.87907, 39.606013], + [111.842729, 39.620305], + [111.783599, 39.58895], + [111.722621, 39.606013], + [111.659179, 39.641507], + [111.625303, 39.633672], + [111.525521, 39.662242], + [111.497187, 39.661781], + [111.445448, 39.640124], + [111.460847, 39.606935], + [111.441137, 39.59679], + [111.422043, 39.539123], + [111.431282, 39.508656], + [111.372152, 39.479099], + [111.358601, 39.432428], + [111.337043, 39.420872], + [111.171971, 39.423183], + [111.143022, 39.407926], + [111.125776, 39.366297], + [111.159037, 39.362596], + [111.155341, 39.338531], + [111.186138, 39.35149], + [111.179363, 39.326959], + [111.202152, 39.305197], + [111.247732, 39.302419], + [111.213239, 39.257021], + [111.219399, 39.244044], + [111.163348, 39.152678], + [111.173819, 39.135041], + [111.147334, 39.100681], + [111.138095, 39.064447], + [111.094363, 39.030053], + [111.038313, 39.020289], + [110.998276, 38.998433], + [110.980414, 38.970056], + [111.009979, 38.932823], + [111.016755, 38.889981], + [110.995813, 38.868084], + [111.009363, 38.847579], + [110.965016, 38.755699], + [110.915125, 38.704345], + [110.916357, 38.673981], + [110.880632, 38.626776], + [110.898494, 38.587024], + [110.920052, 38.581878], + [110.907733, 38.521035], + [110.870777, 38.510265], + [110.874473, 38.453579], + [110.840596, 38.439986], + [110.796864, 38.453579], + [110.77777, 38.440924], + [110.746973, 38.366355], + [110.701394, 38.353215], + [110.661358, 38.308617], + [110.601612, 38.308147], + [110.57759, 38.297345], + [110.565887, 38.215105], + [110.528315, 38.211814], + [110.509221, 38.192061], + [110.519692, 38.130889], + [110.501829, 38.097929], + [110.507989, 38.013107], + [110.528315, 37.990471], + [110.522771, 37.955088], + [110.59422, 37.922049], + [110.680452, 37.790216], + [110.735886, 37.77035], + [110.750669, 37.736281], + [110.716792, 37.728708], + [110.706321, 37.705511], + [110.775306, 37.680886], + [110.793169, 37.650567], + [110.763604, 37.639668], + [110.771611, 37.594634], + [110.795017, 37.558586], + [110.770995, 37.538184], + [110.759292, 37.474567], + [110.740198, 37.44939], + [110.644111, 37.435135], + [110.630561, 37.372858], + [110.641648, 37.360015], + [110.695234, 37.34955], + [110.678604, 37.317668], + [110.690307, 37.287201], + [110.661974, 37.281963], + [110.651503, 37.256722], + [110.590525, 37.187145], + [110.53509, 37.138021], + [110.535706, 37.115118], + [110.49567, 37.086956], + [110.460561, 37.044932], + [110.417446, 37.027257], + [110.426685, 37.008621], + [110.382953, 37.022001], + [110.381721, 37.002408], + [110.424221, 36.963685], + [110.408823, 36.892403], + [110.376178, 36.882351], + [110.424221, 36.855539], + [110.406975, 36.824886], + [110.423605, 36.818179], + [110.407591, 36.776007], + [110.447011, 36.737649], + [110.438388, 36.685835], + [110.402663, 36.697352], + [110.394656, 36.676716], + [110.426685, 36.657514], + [110.447627, 36.621018], + [110.496902, 36.582102], + [110.488895, 36.556628], + [110.503677, 36.488335], + [110.47288, 36.453203], + [110.489511, 36.430094], + [110.487047, 36.393972], + [110.459946, 36.327946], + [110.474112, 36.306729], + [110.474112, 36.248352], + [110.45625, 36.22663], + [110.447011, 36.164328], + [110.467953, 36.074893], + [110.491974, 36.034735], + [110.49259, 35.994073], + [110.516612, 35.971796], + [110.502445, 35.947575], + [110.516612, 35.918501], + [110.511684, 35.879718], + [110.549257, 35.877778], + [110.550489, 35.838005], + [110.571431, 35.800639], + [110.57759, 35.701559], + [110.609619, 35.632031], + [110.589293, 35.602355], + [110.567735, 35.539559], + [110.531394, 35.511309], + [110.477808, 35.413821], + [110.45009, 35.327933], + [110.374946, 35.251728], + [110.378642, 35.210666], + [110.364475, 35.197952], + [110.373714, 35.134351], + [110.320743, 35.00504], + [110.262229, 34.944233], + [110.230816, 34.880925], + [110.246831, 34.789068], + [110.243135, 34.725641], + [110.229584, 34.692679], + [110.269004, 34.629671], + [110.29549, 34.610956], + [110.379257, 34.600612] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 150000, + "name": "Inner Mongolia", + "center": [111.670801, 40.818311], + "centroid": [114.077429, 44.331087], + "childrenNum": 12, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 4, + "acroutes": [100000] + }, + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [97.172903, 42.795257], + [97.371235, 42.457076], + [97.500582, 42.243894], + [97.653335, 41.986856], + [97.84674, 41.656379], + [97.613915, 41.477276], + [97.629314, 41.440498], + [97.903407, 41.168057], + [97.971776, 41.09774], + [98.142391, 41.001607], + [98.184891, 40.988056], + [98.25018, 40.93925], + [98.333332, 40.918903], + [98.344419, 40.568413], + [98.627751, 40.677884], + [98.569853, 40.746836], + [98.668403, 40.773128], + [98.689345, 40.691952], + [98.72199, 40.657911], + [98.762642, 40.639748], + [98.802678, 40.607043], + [98.80699, 40.660181], + [98.790975, 40.705564], + [98.984996, 40.782644], + [99.041662, 40.693767], + [99.102025, 40.676522], + [99.12543, 40.715091], + [99.172858, 40.747289], + [99.174705, 40.858278], + [99.565827, 40.846961], + [99.673, 40.93292], + [99.985897, 40.909858], + [100.057346, 40.908049], + [100.107853, 40.875475], + [100.224882, 40.727337], + [100.237201, 40.716905], + [100.242744, 40.618855], + [100.169447, 40.541131], + [100.169447, 40.277743], + [100.007455, 40.20008], + [99.955716, 40.150695], + [99.927383, 40.063727], + [99.841152, 40.013326], + [99.751225, 40.006909], + [99.714268, 39.972061], + [99.533182, 39.891753], + [99.491298, 39.884406], + [99.459885, 39.898181], + [99.440791, 39.885783], + [99.469124, 39.875221], + [99.672384, 39.888079], + [99.822058, 39.860063], + [99.904593, 39.785601], + [99.958796, 39.769504], + [100.040716, 39.757083], + [100.128179, 39.702312], + [100.250135, 39.685274], + [100.314193, 39.606935], + [100.301258, 39.572345], + [100.326512, 39.509118], + [100.44354, 39.485565], + [100.500823, 39.481408], + [100.498975, 39.400527], + [100.606764, 39.387577], + [100.707778, 39.404689], + [100.842053, 39.405614], + [100.842669, 39.199999], + [100.864227, 39.106719], + [100.829118, 39.075133], + [100.835278, 39.025869], + [100.875314, 39.002619], + [100.901799, 39.030053], + [100.961545, 39.005874], + [100.969553, 38.946788], + [101.117378, 38.975174], + [101.228863, 39.020754], + [101.198682, 38.943064], + [101.237486, 38.907214], + [101.24303, 38.860628], + [101.33542, 38.847113], + [101.34158, 38.822406], + [101.307087, 38.80282], + [101.331109, 38.777164], + [101.412413, 38.764099], + [101.562702, 38.713218], + [101.601506, 38.65529], + [101.672955, 38.6908], + [101.777049, 38.66043], + [101.873751, 38.733761], + [101.941505, 38.808883], + [102.075164, 38.891378], + [102.045599, 38.904885], + [101.955055, 38.985874], + [101.926106, 39.000758], + [101.833715, 39.08907], + [101.902701, 39.111827], + [102.012338, 39.127149], + [102.050526, 39.141075], + [102.276576, 39.188868], + [102.3548, 39.231993], + [102.45335, 39.255167], + [102.579002, 39.183301], + [102.616574, 39.171703], + [102.883892, 39.120649], + [103.007696, 39.099753], + [103.133347, 39.192579], + [103.188166, 39.215302], + [103.259615, 39.263971], + [103.344615, 39.331588], + [103.428998, 39.353341], + [103.595302, 39.386652], + [103.728961, 39.430117], + [103.85338, 39.461543], + [103.955626, 39.456923], + [104.089901, 39.419947], + [104.073271, 39.351953], + [104.047401, 39.297788], + [104.171205, 39.160567], + [104.207546, 39.083495], + [104.190915, 39.042139], + [104.196459, 38.9882], + [104.173053, 38.94446], + [104.044322, 38.895105], + [104.011677, 38.85923], + [103.85954, 38.64454], + [103.416063, 38.404821], + [103.465339, 38.353215], + [103.507838, 38.280905], + [103.53494, 38.156776], + [103.368636, 38.08898], + [103.362477, 38.037621], + [103.40744, 37.860651], + [103.627947, 37.797783], + [103.683381, 37.777919], + [103.841062, 37.64725], + [103.874938, 37.604117], + [103.935916, 37.572818], + [104.089285, 37.465067], + [104.183524, 37.406618], + [104.237727, 37.411847], + [104.287002, 37.428007], + [104.322726, 37.44844], + [104.407726, 37.464592], + [104.419429, 37.511604], + [104.433595, 37.515402], + [104.623305, 37.522522], + [104.805007, 37.539133], + [104.866601, 37.566651], + [105.027977, 37.580881], + [105.111128, 37.633981], + [105.187505, 37.657674], + [105.221998, 37.677097], + [105.315004, 37.702197], + [105.4037, 37.710246], + [105.467141, 37.695094], + [105.598952, 37.699356], + [105.616199, 37.722555], + [105.622358, 37.777919], + [105.677177, 37.771769], + [105.760944, 37.799674], + [105.80406, 37.862068], + [105.799749, 37.939986], + [105.840401, 38.004147], + [105.780655, 38.084741], + [105.76772, 38.121474], + [105.775111, 38.186887], + [105.802828, 38.220277], + [105.842248, 38.240962], + [105.86627, 38.296406], + [105.821307, 38.366824], + [105.835473, 38.387467], + [105.827466, 38.432486], + [105.850872, 38.443736], + [105.836705, 38.476071], + [105.863806, 38.53508], + [105.856415, 38.569714], + [105.874277, 38.593105], + [105.852719, 38.641735], + [105.894603, 38.696405], + [105.88598, 38.716953], + [105.908154, 38.737496], + [105.909386, 38.791159], + [105.992538, 38.857366], + [105.97098, 38.909077], + [106.021487, 38.953769], + [106.060907, 38.96866], + [106.087392, 39.006339], + [106.078153, 39.026333], + [106.096631, 39.084889], + [106.145907, 39.153142], + [106.170544, 39.163352], + [106.192718, 39.142932], + [106.251232, 39.131327], + [106.285109, 39.146181], + [106.29558, 39.167992], + [106.280181, 39.262118], + [106.402753, 39.291767], + [106.511774, 39.272311], + [106.525325, 39.308439], + [106.556122, 39.322329], + [106.602318, 39.37555], + [106.643586, 39.357969], + [106.683622, 39.357506], + [106.751375, 39.381564], + [106.781556, 39.371849], + [106.806809, 39.318625], + [106.806193, 39.277407], + [106.790795, 39.241263], + [106.795723, 39.214375], + [106.825288, 39.19397], + [106.859164, 39.107648], + [106.878874, 39.091392], + [106.933693, 39.076527], + [106.96757, 39.054688], + [106.971881, 39.026333], + [106.954019, 38.941202], + [106.837606, 38.847579], + [106.756302, 38.748699], + [106.709491, 38.718821], + [106.66268, 38.601524], + [106.647897, 38.470917], + [106.599854, 38.389812], + [106.482209, 38.319417], + [106.555506, 38.263521], + [106.627571, 38.232501], + [106.654672, 38.22921], + [106.737824, 38.197706], + [106.779092, 38.171833], + [106.858548, 38.156306], + [106.942316, 38.132302], + [107.010069, 38.120532], + [107.051337, 38.122886], + [107.071047, 38.138892], + [107.119091, 38.134185], + [107.138801, 38.161011], + [107.19054, 38.153953], + [107.240431, 38.111586], + [107.33159, 38.086625], + [107.3938, 38.014993], + [107.440611, 37.995659], + [107.411662, 37.948009], + [107.448618, 37.933378], + [107.49235, 37.944706], + [107.560719, 37.893717], + [107.65003, 37.86443], + [107.684523, 37.888522], + [107.732566, 37.84931], + [107.842819, 37.828987], + [107.884703, 37.808186], + [107.982022, 37.787378], + [107.993109, 37.735335], + [108.025753, 37.696041], + [108.012819, 37.66857], + [108.025137, 37.649619], + [108.055318, 37.652462], + [108.134159, 37.622131], + [108.193905, 37.638246], + [108.205608, 37.655779], + [108.24626, 37.665728], + [108.293071, 37.656726], + [108.301078, 37.640616], + [108.422418, 37.648672], + [108.485244, 37.678044], + [108.532671, 37.690832], + [108.628142, 37.651988], + [108.699591, 37.669518], + [108.720533, 37.683728], + [108.777815, 37.683728], + [108.791982, 37.700303], + [108.784591, 37.764673], + [108.799989, 37.784068], + [108.791982, 37.872934], + [108.798141, 37.93385], + [108.82709, 37.989056], + [108.797525, 38.04799], + [108.830786, 38.049875], + [108.883141, 38.01405], + [108.893612, 37.978207], + [108.93488, 37.922521], + [108.9743, 37.931962], + [108.982923, 37.964053], + [109.018648, 37.971602], + [109.037742, 38.021593], + [109.06977, 38.023008], + [109.050676, 38.055059], + [109.069155, 38.091336], + [108.964445, 38.154894], + [108.938575, 38.207582], + [108.976148, 38.245192], + [108.961981, 38.26493], + [109.007561, 38.359316], + [109.051292, 38.385122], + [109.054372, 38.433892], + [109.128901, 38.480288], + [109.175712, 38.518694], + [109.196654, 38.552867], + [109.276726, 38.623035], + [109.331545, 38.597783], + [109.367269, 38.627711], + [109.329081, 38.66043], + [109.338936, 38.701542], + [109.404226, 38.720689], + [109.444262, 38.782763], + [109.511399, 38.833595], + [109.549587, 38.805618], + [109.624116, 38.85457], + [109.672159, 38.928167], + [109.685094, 38.968195], + [109.665384, 38.981687], + [109.72513, 39.018429], + [109.762086, 39.057476], + [109.793499, 39.074204], + [109.851397, 39.122971], + [109.890818, 39.103932], + [109.92223, 39.107183], + [109.893897, 39.141075], + [109.961035, 39.191651], + [109.871723, 39.243581], + [109.90252, 39.271848], + [109.962267, 39.212056], + [110.041107, 39.21623], + [110.109476, 39.249606], + [110.217881, 39.281113], + [110.184005, 39.355192], + [110.161831, 39.387115], + [110.136577, 39.39174], + [110.12549, 39.432891], + [110.152592, 39.45415], + [110.243751, 39.423645], + [110.257917, 39.407001], + [110.385417, 39.310291], + [110.429764, 39.341308], + [110.434692, 39.381101], + [110.482735, 39.360745], + [110.524003, 39.382952], + [110.559728, 39.351027], + [110.566503, 39.320014], + [110.596684, 39.282966], + [110.626249, 39.266751], + [110.702626, 39.273701], + [110.731575, 39.30705], + [110.73835, 39.348713], + [110.782698, 39.38804], + [110.869545, 39.494341], + [110.891103, 39.509118], + [110.958856, 39.519275], + [111.017371, 39.552045], + [111.101138, 39.559428], + [111.136863, 39.587106], + [111.154725, 39.569116], + [111.148566, 39.531277], + [111.10545, 39.497573], + [111.10545, 39.472631], + [111.058639, 39.447681], + [111.064182, 39.400989], + [111.098059, 39.401914], + [111.087588, 39.376013], + [111.125776, 39.366297], + [111.143022, 39.407926], + [111.171971, 39.423183], + [111.337043, 39.420872], + [111.358601, 39.432428], + [111.372152, 39.479099], + [111.431282, 39.508656], + [111.422043, 39.539123], + [111.441137, 39.59679], + [111.460847, 39.606935], + [111.445448, 39.640124], + [111.497187, 39.661781], + [111.525521, 39.662242], + [111.625303, 39.633672], + [111.659179, 39.641507], + [111.722621, 39.606013], + [111.783599, 39.58895], + [111.842729, 39.620305], + [111.87907, 39.606013], + [111.9382, 39.623071], + [111.925265, 39.66731], + [111.959758, 39.692642], + [111.970229, 39.796638], + [112.012729, 39.827438], + [112.042294, 39.886243], + [112.07617, 39.919298], + [112.133453, 40.001866], + [112.142076, 40.027076], + [112.182112, 40.061437], + [112.183344, 40.083877], + [112.232003, 40.133311], + [112.232619, 40.169905], + [112.299756, 40.21105], + [112.310227, 40.256281], + [112.349031, 40.257194], + [112.418017, 40.295091], + [112.456205, 40.300112], + [112.511639, 40.269068], + [112.6299, 40.235725], + [112.712436, 40.178593], + [112.744464, 40.167161], + [112.848558, 40.206937], + [112.898449, 40.329317], + [113.03334, 40.368997], + [113.083231, 40.374925], + [113.251382, 40.413211], + [113.27602, 40.388601], + [113.316672, 40.319736], + [113.387505, 40.319279], + [113.500222, 40.334335], + [113.559968, 40.348476], + [113.688699, 40.448288], + [113.763228, 40.473787], + [113.794641, 40.517932], + [113.850691, 40.460583], + [113.890112, 40.466503], + [113.948626, 40.514747], + [114.011452, 40.515657], + [114.061959, 40.52885], + [114.080437, 40.547952], + [114.076741, 40.575686], + [114.041633, 40.608861], + [114.07243, 40.679246], + [114.063806, 40.706925], + [114.084748, 40.729605], + [114.134639, 40.737314], + [114.103227, 40.770861], + [114.104458, 40.797597], + [114.080437, 40.790348], + [114.044712, 40.830661], + [114.073661, 40.857372], + [114.055183, 40.867782], + [114.041633, 40.917546], + [114.057647, 40.925234], + [113.994821, 40.938798], + [113.973263, 40.983087], + [113.868554, 41.06887], + [113.819279, 41.09774], + [113.877793, 41.115777], + [113.920293, 41.172112], + [113.960945, 41.171211], + [113.996669, 41.19238], + [114.016379, 41.231999], + [113.992357, 41.269794], + [113.971416, 41.239649], + [113.95109, 41.282837], + [113.914749, 41.294529], + [113.899351, 41.316108], + [113.92522, 41.325546], + [113.94493, 41.392477], + [113.871017, 41.413126], + [113.877793, 41.431076], + [113.919677, 41.454404], + [113.933227, 41.487139], + [113.953553, 41.483553], + [113.976959, 41.505966], + [114.032394, 41.529715], + [114.101379, 41.537779], + [114.230726, 41.513584], + [114.221487, 41.582111], + [114.226414, 41.616572], + [114.259059, 41.623282], + [114.215328, 41.68499], + [114.237501, 41.698843], + [114.206704, 41.7386], + [114.215328, 41.75646], + [114.200545, 41.789934], + [114.282465, 41.863517], + [114.343443, 41.926774], + [114.352066, 41.953484], + [114.419203, 41.942356], + [114.478334, 41.951704], + [114.511594, 41.981962], + [114.467863, 42.025989], + [114.480181, 42.064654], + [114.502355, 42.06732], + [114.510978, 42.110844], + [114.560254, 42.132595], + [114.647717, 42.109512], + [114.675434, 42.12061], + [114.75489, 42.115727], + [114.789383, 42.130819], + [114.79431, 42.149457], + [114.825723, 42.139695], + [114.86268, 42.097967], + [114.860832, 42.054879], + [114.9021, 42.015763], + [114.915035, 41.960605], + [114.923658, 41.871093], + [114.939056, 41.846132], + [114.922426, 41.825175], + [114.868839, 41.813579], + [114.89594, 41.76762], + [114.902716, 41.695715], + [114.895325, 41.636255], + [114.860832, 41.60091], + [115.016049, 41.615229], + [115.056085, 41.602253], + [115.0992, 41.62373], + [115.195287, 41.602253], + [115.20391, 41.571367], + [115.256881, 41.580768], + [115.26612, 41.616124], + [115.290142, 41.622835], + [115.310468, 41.592854], + [115.377605, 41.603148], + [115.345576, 41.635807], + [115.360975, 41.661297], + [115.319091, 41.691693], + [115.346808, 41.712247], + [115.42996, 41.728775], + [115.488474, 41.760924], + [115.519887, 41.76762], + [115.57409, 41.80555], + [115.654162, 41.829189], + [115.688038, 41.867528], + [115.726227, 41.870202], + [115.811226, 41.912525], + [115.834632, 41.93835], + [115.85311, 41.927665], + [115.916552, 41.945027], + [115.954124, 41.874213], + [115.994776, 41.828743], + [116.007095, 41.797966], + [116.007095, 41.79752], + [116.034196, 41.782795], + [116.09887, 41.776547], + [116.129051, 41.805996], + [116.106877, 41.831419], + [116.122892, 41.861734], + [116.194341, 41.861734], + [116.212819, 41.885352], + [116.223906, 41.932562], + [116.298434, 41.96817], + [116.310137, 41.997086], + [116.373579, 42.009983], + [116.414231, 41.982407], + [116.393289, 41.942802], + [116.453651, 41.945917], + [116.4826, 41.975734], + [116.510933, 41.974399], + [116.553433, 41.928555], + [116.597165, 41.935679], + [116.639049, 41.929891], + [116.66923, 41.947698], + [116.727744, 41.951259], + [116.748686, 41.984186], + [116.796113, 41.977958], + [116.879881, 42.018431], + [116.890352, 42.092639], + [116.850316, 42.156556], + [116.825062, 42.155669], + [116.789338, 42.200462], + [116.903287, 42.190708], + [116.918685, 42.229716], + [116.897743, 42.297479], + [116.886656, 42.366496], + [116.910678, 42.394789], + [116.910062, 42.395231], + [116.921765, 42.403628], + [116.936547, 42.410256], + [116.944555, 42.415116], + [116.97104, 42.427486], + [116.974736, 42.426603], + [116.99075, 42.425719], + [117.005533, 42.43367], + [117.009228, 42.44957], + [117.01662, 42.456193], + [117.080061, 42.463699], + [117.09546, 42.484004], + [117.135496, 42.468996], + [117.188467, 42.468114], + [117.275314, 42.481797], + [117.332596, 42.46105], + [117.390495, 42.461933], + [117.413284, 42.471645], + [117.410205, 42.519743], + [117.387415, 42.517537], + [117.434226, 42.557224], + [117.435458, 42.585431], + [117.475494, 42.602613], + [117.530313, 42.590278], + [117.537088, 42.603054], + [117.60053, 42.603054], + [117.667051, 42.582347], + [117.708935, 42.588515], + [117.779768, 42.61847], + [117.801326, 42.612744], + [117.797631, 42.585431], + [117.856761, 42.539148], + [117.874007, 42.510038], + [117.997811, 42.416884], + [118.024296, 42.385064], + [118.008898, 42.346595], + [118.060021, 42.298364], + [118.047702, 42.280656], + [117.974405, 42.25054], + [117.977485, 42.229716], + [118.033535, 42.199132], + [118.106216, 42.172082], + [118.089586, 42.12283], + [118.097593, 42.105072], + [118.155491, 42.081091], + [118.116687, 42.037102], + [118.194296, 42.031324], + [118.220165, 42.058434], + [118.212774, 42.081091], + [118.239259, 42.092639], + [118.27252, 42.083312], + [118.296541, 42.057545], + [118.286686, 42.033991], + [118.239875, 42.024655], + [118.291614, 42.007759], + [118.313788, 41.98819], + [118.306396, 41.940131], + [118.268824, 41.930336], + [118.340273, 41.87243], + [118.335346, 41.845241], + [118.29223, 41.772976], + [118.247266, 41.773869], + [118.236179, 41.80778], + [118.178281, 41.814917], + [118.140093, 41.784134], + [118.132702, 41.733241], + [118.155491, 41.712694], + [118.159187, 41.67605], + [118.206614, 41.650566], + [118.215237, 41.59554], + [118.302701, 41.55256], + [118.315636, 41.512688], + [118.271904, 41.471446], + [118.327338, 41.450816], + [118.348896, 41.428384], + [118.361215, 41.384844], + [118.348896, 41.342622], + [118.380309, 41.312062], + [118.412338, 41.331838], + [118.528135, 41.355202], + [118.629765, 41.346666], + [118.677192, 41.35026], + [118.741866, 41.324198], + [118.770199, 41.352956], + [118.843496, 41.374516], + [118.844727, 41.342622], + [118.890923, 41.300823], + [118.949437, 41.317906], + [118.980234, 41.305769], + [119.092951, 41.293629], + [119.168712, 41.294978], + [119.197661, 41.282837], + [119.211827, 41.308016], + [119.239545, 41.31431], + [119.296211, 41.325097], + [119.330704, 41.385293], + [119.309762, 41.405944], + [119.376283, 41.422102], + [119.378131, 41.459787], + [119.401537, 41.472343], + [119.406464, 41.503276], + [119.361501, 41.545841], + [119.362116, 41.566442], + [119.420015, 41.567785], + [119.415703, 41.590169], + [119.342406, 41.617914], + [119.307914, 41.657273], + [119.299907, 41.705545], + [119.319001, 41.727435], + [119.317769, 41.764049], + [119.292515, 41.790827], + [119.312841, 41.80555], + [119.334399, 41.871539], + [119.323312, 41.889807], + [119.340559, 41.926774], + [119.323928, 41.937014], + [119.324544, 41.969505], + [119.375667, 42.023322], + [119.384906, 42.08953], + [119.352261, 42.118391], + [119.314689, 42.119723], + [119.30853, 42.147239], + [119.286972, 42.154781], + [119.277733, 42.185387], + [119.237697, 42.200905], + [119.274037, 42.239021], + [119.280197, 42.260728], + [119.34795, 42.300578], + [119.432949, 42.317396], + [119.482841, 42.347037], + [119.502551, 42.388159], + [119.540123, 42.363401], + [119.572152, 42.359421], + [119.571536, 42.335536], + [119.539507, 42.297922], + [119.557985, 42.289068], + [119.609108, 42.276671], + [119.617115, 42.252755], + [119.679941, 42.240793], + [119.744615, 42.211545], + [119.841933, 42.215534], + [119.854868, 42.170308], + [119.837622, 42.135257], + [119.845629, 42.097079], + [119.87581, 42.077982], + [119.897368, 42.030879], + [119.921389, 42.014429], + [119.924469, 41.98908], + [119.950954, 41.974399], + [119.954034, 41.923212], + [119.989759, 41.899163], + [120.023019, 41.816701], + [120.041498, 41.818932], + [120.050737, 41.776101], + [120.024867, 41.737707], + [120.035954, 41.708226], + [120.096316, 41.697056], + [120.1382, 41.729221], + [120.127113, 41.77253], + [120.183164, 41.826513], + [120.188707, 41.848361], + [120.215808, 41.853265], + [120.251533, 41.884016], + [120.286641, 41.880005], + [120.290337, 41.897381], + [120.260156, 41.904062], + [120.271859, 41.925439], + [120.318054, 41.93746], + [120.309431, 41.951704], + [120.373489, 41.994862], + [120.399358, 41.984631], + [120.456641, 42.016208], + [120.450481, 42.057101], + [120.493597, 42.073539], + [120.466496, 42.105516], + [120.56751, 42.152119], + [120.58414, 42.167203], + [120.624792, 42.154338], + [120.72211, 42.203565], + [120.745516, 42.223512], + [120.79048, 42.218636], + [120.820661, 42.227943], + [120.8299, 42.252755], + [120.883487, 42.242565], + [120.883487, 42.269585], + [120.933994, 42.27977], + [120.992508, 42.264714], + [121.028848, 42.242565], + [121.070732, 42.254083], + [121.087978, 42.278885], + [121.120623, 42.280656], + [121.133558, 42.300135], + [121.184681, 42.333324], + [121.218558, 42.371802], + [121.285079, 42.387717], + [121.314644, 42.42837], + [121.304789, 42.435879], + [121.386093, 42.474294], + [121.434752, 42.475176], + [121.4791, 42.49636], + [121.506201, 42.482239], + [121.570875, 42.487093], + [121.607831, 42.516214], + [121.604136, 42.495037], + [121.66573, 42.437204], + [121.69899, 42.438529], + [121.747649, 42.484887], + [121.803084, 42.514891], + [121.817867, 42.504303], + [121.831417, 42.533856], + [121.844352, 42.522389], + [121.889931, 42.556784], + [121.921344, 42.605697], + [121.915801, 42.656332], + [121.94167, 42.666014], + [121.939207, 42.688453], + [122.018663, 42.69901], + [122.062394, 42.723635], + [122.072865, 42.710444], + [122.160945, 42.684934], + [122.204676, 42.685374], + [122.204676, 42.732867], + [122.261343, 42.695931], + [122.324785, 42.684934], + [122.338951, 42.669975], + [122.396234, 42.684054], + [122.396234, 42.707366], + [122.460907, 42.755282], + [122.439349, 42.770221], + [122.371596, 42.776371], + [122.35127, 42.830378], + [122.436886, 42.843105], + [122.556378, 42.827745], + [122.576088, 42.819405], + [122.580399, 42.789987], + [122.624747, 42.773296], + [122.653696, 42.78252], + [122.733152, 42.786034], + [122.73808, 42.77066], + [122.786123, 42.757479], + [122.848949, 42.712203], + [122.883442, 42.751766], + [122.887137, 42.770221], + [122.925941, 42.772417], + [122.945651, 42.753524], + [122.980144, 42.777689], + [123.058368, 42.768903], + [123.118114, 42.801405], + [123.227752, 42.831695], + [123.169853, 42.859777], + [123.188947, 42.895739], + [123.18402, 42.925983], + [123.259165, 42.993431], + [123.323222, 43.000872], + [123.434707, 43.027565], + [123.474743, 43.042438], + [123.536337, 43.007], + [123.572678, 43.003498], + [123.580685, 43.036314], + [123.631192, 43.088346], + [123.636119, 43.141644], + [123.666916, 43.179623], + [123.645974, 43.208855], + [123.676771, 43.223684], + [123.664453, 43.264663], + [123.698329, 43.272071], + [123.703873, 43.37047], + [123.608402, 43.366119], + [123.54496, 43.415262], + [123.519707, 43.402219], + [123.486446, 43.44525], + [123.442098, 43.437863], + [123.419925, 43.410046], + [123.382968, 43.469143], + [123.36449, 43.483475], + [123.315831, 43.492159], + [123.329998, 43.519071], + [123.304744, 43.550742], + [123.360179, 43.567223], + [123.452569, 43.545971], + [123.461193, 43.568523], + [123.434091, 43.575461], + [123.421157, 43.598435], + [123.5117, 43.592801], + [123.510468, 43.624867], + [123.536953, 43.633964], + [123.518475, 43.682024], + [123.520323, 43.708419], + [123.48275, 43.737396], + [123.498149, 43.771114], + [123.461809, 43.822518], + [123.467968, 43.853599], + [123.397135, 43.954929], + [123.37065, 43.970006], + [123.400831, 43.979481], + [123.365722, 44.013922], + [123.331229, 44.028984], + [123.32815, 44.084035], + [123.350939, 44.092633], + [123.362642, 44.133452], + [123.386664, 44.161794], + [123.323838, 44.179823], + [123.286882, 44.211574], + [123.277027, 44.25274], + [123.196955, 44.34483], + [123.128585, 44.367081], + [123.114419, 44.40258], + [123.142136, 44.428228], + [123.125506, 44.455147], + [123.137209, 44.486322], + [123.12489, 44.5098], + [123.06576, 44.505959], + [123.025108, 44.493153], + [122.85634, 44.398304], + [122.76087, 44.369648], + [122.702971, 44.319145], + [122.675254, 44.285738], + [122.641993, 44.283595], + [122.515726, 44.251025], + [122.483081, 44.236877], + [122.319241, 44.233018], + [122.271198, 44.255741], + [122.291524, 44.310152], + [122.294604, 44.41113], + [122.28598, 44.477783], + [122.228082, 44.480345], + [122.224386, 44.526016], + [122.196053, 44.559712], + [122.13138, 44.577619], + [122.113517, 44.615546], + [122.103046, 44.67388], + [122.117213, 44.701961], + [122.161561, 44.728328], + [122.152322, 44.744057], + [122.10243, 44.736406], + [122.110438, 44.767856], + [122.142467, 44.753833], + [122.168952, 44.770405], + [122.099967, 44.7823], + [122.098119, 44.81882], + [122.04946, 44.912985], + [122.079025, 44.914256], + [122.087032, 44.95281], + [122.074713, 45.006573], + [122.098735, 45.02138], + [122.119677, 45.068739], + [122.109822, 45.142236], + [122.143082, 45.183167], + [122.192358, 45.180636], + [122.22993, 45.206784], + [122.239169, 45.276313], + [122.147394, 45.295682], + [122.146778, 45.374352], + [122.180039, 45.409655], + [122.168336, 45.439897], + [122.064242, 45.472641], + [122.002648, 45.507882], + [121.993409, 45.552741], + [121.966308, 45.596308], + [121.995873, 45.59882], + [122.003264, 45.623102], + [121.970004, 45.692956], + [121.934279, 45.71051], + [121.867142, 45.719703], + [121.812323, 45.704659], + [121.811091, 45.687103], + [121.713773, 45.701734], + [121.666345, 45.727641], + [121.644172, 45.752284], + [121.657106, 45.770238], + [121.697142, 45.76314], + [121.754425, 45.794862], + [121.766744, 45.830318], + [121.769823, 45.84366], + [121.817251, 45.875336], + [121.805548, 45.900746], + [121.821562, 45.918235], + [121.809243, 45.961102], + [121.761816, 45.998947], + [121.819098, 46.023054], + [121.843736, 46.024301], + [121.864062, 46.002272], + [121.923808, 46.004767], + [121.92812, 45.988552], + [122.040221, 45.959022], + [122.085184, 45.912406], + [122.091344, 45.882002], + [122.200981, 45.857], + [122.236705, 45.831569], + [122.253952, 45.7982], + [122.301379, 45.813218], + [122.337719, 45.859917], + [122.372828, 45.856166], + [122.362357, 45.917403], + [122.446125, 45.916986], + [122.496016, 45.85825], + [122.504639, 45.786933], + [122.522501, 45.786933], + [122.556378, 45.82156], + [122.603189, 45.778169], + [122.640761, 45.771072], + [122.650001, 45.731401], + [122.671558, 45.70048], + [122.741775, 45.705077], + [122.751015, 45.735996], + [122.792283, 45.766063], + [122.752246, 45.834905], + [122.772572, 45.856583], + [122.80029, 45.856583], + [122.828623, 45.912406], + [122.792898, 46.073313], + [123.04605, 46.099878], + [123.070071, 46.123527], + [123.112571, 46.130163], + [123.102716, 46.172037], + [123.127354, 46.174523], + [123.128585, 46.210565], + [123.178476, 46.248239], + [123.142136, 46.298293], + [123.089781, 46.347888], + [123.011557, 46.434984], + [123.010325, 46.524823], + [123.002318, 46.574624], + [123.052825, 46.579972], + [123.04605, 46.617803], + [123.077462, 46.622324], + [123.098404, 46.603002], + [123.18094, 46.614103], + [123.228368, 46.588198], + [123.279491, 46.616981], + [123.276411, 46.660947], + [123.318295, 46.662179], + [123.366338, 46.677784], + [123.474743, 46.686817], + [123.603475, 46.68928], + [123.631808, 46.728675], + [123.629344, 46.813524], + [123.580069, 46.827447], + [123.625648, 46.847508], + [123.599163, 46.868378], + [123.605322, 46.891286], + [123.576989, 46.891286], + [123.575757, 46.845461], + [123.562823, 46.82581], + [123.506772, 46.827038], + [123.483366, 46.84587], + [123.52833, 46.944836], + [123.487678, 46.959951], + [123.42362, 46.934212], + [123.337389, 46.988943], + [123.301664, 46.999965], + [123.304128, 46.964852], + [123.360179, 46.970978], + [123.404526, 46.935438], + [123.40699, 46.906416], + [123.374345, 46.837683], + [123.341084, 46.826628], + [123.295505, 46.865105], + [123.221592, 46.850373], + [123.22344, 46.821305], + [123.198802, 46.803283], + [123.163694, 46.74016], + [123.103332, 46.734828], + [123.076846, 46.745082], + [123.026339, 46.718829], + [123.00355, 46.730726], + [122.996774, 46.761483], + [122.906847, 46.80738], + [122.893913, 46.895376], + [122.895144, 46.960359], + [122.83971, 46.937072], + [122.791051, 46.941567], + [122.798442, 46.9575], + [122.77442, 46.973837], + [122.778116, 47.002822], + [122.845869, 47.046881], + [122.852645, 47.072158], + [122.821232, 47.065636], + [122.710363, 47.093349], + [122.679566, 47.094164], + [122.615508, 47.124306], + [122.582863, 47.158092], + [122.531124, 47.198771], + [122.498479, 47.255262], + [122.462755, 47.27841], + [122.441197, 47.310476], + [122.418407, 47.350632], + [122.507103, 47.401291], + [122.543443, 47.495589], + [122.59395, 47.54732], + [122.765181, 47.614333], + [122.848949, 47.67441], + [122.926557, 47.697777], + [123.041122, 47.746492], + [123.161846, 47.781892], + [123.214201, 47.824502], + [123.256085, 47.876711], + [123.300432, 47.953723], + [123.537569, 48.021816], + [123.579453, 48.045427], + [123.705105, 48.152142], + [123.746373, 48.197638], + [123.862785, 48.271782], + [124.019234, 48.39313], + [124.07898, 48.43603], + [124.136878, 48.463023], + [124.25945, 48.536385], + [124.314269, 48.503881], + [124.302566, 48.456673], + [124.330283, 48.435633], + [124.309957, 48.413393], + [124.331515, 48.380015], + [124.317964, 48.35099], + [124.353689, 48.315978], + [124.365392, 48.283731], + [124.422058, 48.245884], + [124.412819, 48.219175], + [124.418978, 48.181679], + [124.475029, 48.173698], + [124.471333, 48.133373], + [124.430065, 48.12099], + [124.415899, 48.08782], + [124.46579, 48.098213], + [124.478108, 48.123387], + [124.505826, 48.124985], + [124.529847, 48.146951], + [124.512601, 48.164518], + [124.547094, 48.200829], + [124.579122, 48.262221], + [124.558796, 48.268197], + [124.579738, 48.297269], + [124.540934, 48.335476], + [124.547094, 48.35775], + [124.51876, 48.378027], + [124.52492, 48.426897], + [124.507674, 48.445558], + [124.555717, 48.467784], + [124.533543, 48.515379], + [124.548941, 48.535593], + [124.520608, 48.556195], + [124.579122, 48.596582], + [124.601912, 48.632587], + [124.624702, 48.701755], + [124.612383, 48.747945], + [124.656115, 48.783842], + [124.644412, 48.80789], + [124.654267, 48.83429], + [124.697383, 48.841775], + [124.715861, 48.885475], + [124.709086, 48.920487], + [124.744194, 48.920487], + [124.756513, 48.967262], + [124.808252, 49.020666], + [124.828578, 49.077933], + [124.809484, 49.115943], + [124.847672, 49.129651], + [124.860607, 49.166448], + [124.906802, 49.184054], + [124.983179, 49.162535], + [125.039845, 49.17623], + [125.034302, 49.157056], + [125.117453, 49.126127], + [125.158721, 49.144921], + [125.187671, 49.186792], + [125.219699, 49.189139], + [125.227707, 49.248947], + [125.214772, 49.277066], + [125.261583, 49.322336], + [125.256656, 49.359769], + [125.277598, 49.379644], + [125.25604, 49.395227], + [125.256656, 49.437275], + [125.270822, 49.454395], + [125.228323, 49.487063], + [125.211076, 49.539908], + [125.233866, 49.536801], + [125.23017, 49.595411], + [125.205533, 49.593859], + [125.16796, 49.629923], + [125.15441, 49.616741], + [125.127308, 49.655113], + [125.132236, 49.672157], + [125.164881, 49.669446], + [125.189518, 49.652401], + [125.185207, 49.634574], + [125.219699, 49.669058], + [125.225243, 49.726349], + [125.204301, 49.734086], + [125.221547, 49.754969], + [125.222779, 49.799026], + [125.177815, 49.829533], + [125.239409, 49.844587], + [125.225243, 49.867351], + [125.245569, 49.87198], + [125.212924, 49.907452], + [125.225859, 49.922481], + [125.199373, 49.935194], + [125.190134, 49.959841], + [125.231402, 49.957531], + [125.241873, 49.987938], + [125.278214, 49.996402], + [125.297924, 50.014481], + [125.283757, 50.036012], + [125.25296, 50.041393], + [125.289916, 50.057917], + [125.315786, 50.04562], + [125.328105, 50.065985], + [125.283757, 50.070211], + [125.287453, 50.093636], + [125.258504, 50.103618], + [125.27883, 50.127411], + [125.311474, 50.140453], + [125.376148, 50.137385], + [125.335496, 50.161161], + [125.382923, 50.172278], + [125.39093, 50.199868], + [125.417416, 50.195654], + [125.448829, 50.216338], + [125.442053, 50.260357], + [125.466075, 50.266861], + [125.463611, 50.295925], + [125.530749, 50.331085], + [125.520278, 50.3498], + [125.546763, 50.358965], + [125.522126, 50.404759], + [125.536292, 50.420014], + [125.567089, 50.402852], + [125.583104, 50.409717], + [125.562162, 50.438314], + [125.580024, 50.449366], + [125.627451, 50.443268], + [125.654553, 50.471082], + [125.699516, 50.487078], + [125.740784, 50.523237], + [125.754335, 50.506874], + [125.770349, 50.531227], + [125.794987, 50.532748], + [125.829479, 50.56165], + [125.807921, 50.60383], + [125.814697, 50.62092], + [125.793139, 50.643316], + [125.804226, 50.658874], + [125.789443, 50.679735], + [125.825784, 50.70362], + [125.78082, 50.725598], + [125.795603, 50.738856], + [125.758646, 50.746809], + [125.804226, 50.773309], + [125.828863, 50.756654], + [125.846726, 50.769524], + [125.836255, 50.793363], + [125.890457, 50.805845], + [125.878138, 50.816812], + [125.913247, 50.825885], + [125.939732, 50.85423], + [125.961906, 50.901054], + [125.997631, 50.872738], + [125.996399, 50.906715], + [126.02042, 50.927466], + [126.042594, 50.92558], + [126.068464, 50.967434], + [126.041978, 50.981753], + [126.033971, 51.011132], + [126.059225, 51.043503], + [125.976073, 51.084498], + [125.993935, 51.119072], + [125.970529, 51.123955], + [125.946508, 51.108176], + [125.909551, 51.138977], + [125.864588, 51.146487], + [125.850421, 51.21364], + [125.819008, 51.227134], + [125.761726, 51.226385], + [125.76111, 51.261976], + [125.740784, 51.27583], + [125.700132, 51.327465], + [125.626219, 51.380163], + [125.623756, 51.387633], + [125.62314, 51.398089], + [125.600966, 51.410409], + [125.60035, 51.413396], + [125.595422, 51.416755], + [125.559082, 51.461521], + [125.528285, 51.488359], + [125.424807, 51.562827], + [125.38046, 51.585516], + [125.35151, 51.623801], + [125.316402, 51.610052], + [125.289301, 51.633831], + [125.228938, 51.640517], + [125.214772, 51.627888], + [125.175968, 51.639403], + [125.130388, 51.635317], + [125.12854, 51.659083], + [125.098975, 51.658341], + [125.060171, 51.59667], + [125.073106, 51.553526], + [125.047236, 51.529704], + [125.004737, 51.529332], + [124.983795, 51.508478], + [124.928976, 51.498419], + [124.917889, 51.474196], + [124.942527, 51.447349], + [124.885244, 51.40817], + [124.864302, 51.37979], + [124.783614, 51.392115], + [124.76452, 51.38726], + [124.752817, 51.35812], + [124.693687, 51.3327], + [124.62655, 51.327465], + [124.58713, 51.363725], + [124.555717, 51.375307], + [124.490427, 51.380537], + [124.478108, 51.36223], + [124.443616, 51.35812], + [124.426985, 51.331953], + [124.430065, 51.301281], + [124.406659, 51.272086], + [124.339522, 51.293422], + [124.297638, 51.298661], + [124.271769, 51.308389], + [124.239124, 51.344664], + [124.192313, 51.33943], + [124.128255, 51.347281], + [124.090067, 51.3413], + [124.071588, 51.320734], + [123.994596, 51.322604], + [123.939777, 51.313253], + [123.926227, 51.300532], + [123.887423, 51.320734], + [123.842459, 51.367462], + [123.794416, 51.361109], + [123.711264, 51.398089], + [123.660141, 51.342795], + [123.661989, 51.319237], + [123.582533, 51.306893], + [123.582533, 51.294545], + [123.46304, 51.286686], + [123.440251, 51.270963], + [123.414381, 51.278825], + [123.376809, 51.266844], + [123.339853, 51.27246], + [123.294273, 51.254111], + [123.231447, 51.268716], + [123.231447, 51.279199], + [123.127969, 51.297913], + [123.069455, 51.321108], + [123.002934, 51.31213], + [122.965977, 51.345786], + [122.965977, 51.386886], + [122.946267, 51.405183], + [122.903768, 51.415262], + [122.900072, 51.445112], + [122.871123, 51.455181], + [122.854492, 51.477551], + [122.880362, 51.511085], + [122.858804, 51.524864], + [122.880362, 51.537894], + [122.874202, 51.561339], + [122.832935, 51.581797], + [122.85634, 51.606707], + [122.820616, 51.633088], + [122.816304, 51.655371], + [122.778732, 51.698048], + [122.749167, 51.746613], + [122.771957, 51.779579], + [122.732536, 51.832495], + [122.725761, 51.87833], + [122.706051, 51.890151], + [122.729457, 51.919321], + [122.726377, 51.978709], + [122.683877, 51.974654], + [122.664783, 51.99861], + [122.650616, 52.058997], + [122.625363, 52.067459], + [122.643841, 52.111585], + [122.629059, 52.13657], + [122.690653, 52.140243], + [122.73808, 52.153464], + [122.769493, 52.179893], + [122.766413, 52.232705], + [122.787355, 52.252494], + [122.76087, 52.26678], + [122.710979, 52.256157], + [122.67895, 52.276667], + [122.585943, 52.266413], + [122.560689, 52.282526], + [122.478153, 52.29607], + [122.484313, 52.341432], + [122.447356, 52.394052], + [122.419023, 52.375057], + [122.378987, 52.395512], + [122.367284, 52.413768], + [122.342031, 52.414133], + [122.326016, 52.459374], + [122.310618, 52.475416], + [122.207756, 52.469218], + [122.178191, 52.48963], + [122.168952, 52.513674], + [122.140003, 52.510032], + [122.142467, 52.495096], + [122.107358, 52.452445], + [122.080873, 52.440407], + [122.091344, 52.427272], + [122.040837, 52.413038], + [122.035909, 52.377615], + [121.976779, 52.343626], + [121.94783, 52.298266], + [121.901018, 52.280695], + [121.841272, 52.282526], + [121.769207, 52.308147], + [121.714389, 52.318025], + [121.715621, 52.342894], + [121.658338, 52.3904], + [121.678664, 52.419973], + [121.63986, 52.44442], + [121.590585, 52.443326], + [121.565331, 52.460468], + [121.519136, 52.456821], + [121.495114, 52.484892], + [121.474172, 52.482706], + [121.416274, 52.499468], + [121.411963, 52.52205], + [121.353448, 52.534793], + [121.323883, 52.573727], + [121.280151, 52.586819], + [121.225333, 52.577364], + [121.182217, 52.59918], + [121.237036, 52.619167], + [121.29247, 52.651855], + [121.309717, 52.676173], + [121.373158, 52.683067], + [121.455078, 52.73528], + [121.476636, 52.772225], + [121.511129, 52.779104], + [121.537614, 52.801542], + [121.591201, 52.824693], + [121.620766, 52.853251], + [121.604136, 52.872401], + [121.610295, 52.892264], + [121.66265, 52.912478], + [121.677432, 52.948192], + [121.715621, 52.997926], + [121.785838, 53.018451], + [121.817867, 53.061631], + [121.775367, 53.089674], + [121.784606, 53.104408], + [121.753193, 53.147501], + [121.722396, 53.145706], + [121.665114, 53.170467], + [121.660186, 53.195213], + [121.67928, 53.199515], + [121.679896, 53.240722], + [121.642324, 53.262564], + [121.615222, 53.258984], + [121.575802, 53.29155], + [121.504969, 53.323018], + [121.499426, 53.337314], + [121.416274, 53.319443], + [121.336818, 53.325877], + [121.308485, 53.301565], + [121.227797, 53.280459], + [121.155732, 53.285468], + [121.129246, 53.277238], + [121.098449, 53.306929], + [121.055334, 53.29155], + [120.950624, 53.29763], + [120.936457, 53.28833], + [120.882871, 53.294411], + [120.867472, 53.278669], + [120.820661, 53.269007], + [120.838523, 53.239648], + [120.821893, 53.241797], + [120.736277, 53.204892], + [120.690698, 53.174771], + [120.687002, 53.142476], + [120.659901, 53.137091], + [120.643886, 53.106923], + [120.562582, 53.082845], + [120.529321, 53.045803], + [120.452945, 53.01017], + [120.411061, 52.957927], + [120.363018, 52.94134], + [120.350699, 52.906343], + [120.295265, 52.891542], + [120.297112, 52.869872], + [120.222584, 52.84277], + [120.181316, 52.806969], + [120.14128, 52.813119], + [120.101244, 52.788877], + [120.031642, 52.773674], + [120.071063, 52.70628], + [120.035338, 52.646409], + [120.049505, 52.598453], + [120.07599, 52.586092], + [120.125265, 52.586819], + [120.194866, 52.578819], + [120.289721, 52.623527], + [120.396895, 52.616261], + [120.462184, 52.64532], + [120.483742, 52.630066], + [120.56135, 52.595544], + [120.605082, 52.589364], + [120.62664, 52.570818], + [120.658669, 52.56718], + [120.690698, 52.547532], + [120.734429, 52.536977], + [120.687002, 52.511489], + [120.706712, 52.492909], + [120.68269, 52.464479], + [120.688234, 52.427637], + [120.64943, 52.3904], + [120.653741, 52.371038], + [120.62356, 52.361172], + [120.627256, 52.323878], + [120.653741, 52.302658], + [120.695625, 52.290214], + [120.715951, 52.261286], + [120.755371, 52.258355], + [120.745516, 52.20594], + [120.786784, 52.15787], + [120.760299, 52.136937], + [120.76769, 52.10938], + [120.753523, 52.085483], + [120.717183, 52.072978], + [120.690698, 52.047221], + [120.691929, 52.026973], + [120.717799, 52.015556], + [120.704864, 51.983501], + [120.66298, 51.958061], + [120.656821, 51.926333], + [120.548416, 51.907877], + [120.549032, 51.882394], + [120.481278, 51.885719], + [120.480046, 51.855049], + [120.40059, 51.833605], + [120.40675, 51.81659], + [120.363634, 51.789945], + [120.317438, 51.785873], + [120.294649, 51.752171], + [120.226279, 51.717703], + [120.172693, 51.679868], + [120.087077, 51.678013], + [120.100628, 51.649058], + [120.05936, 51.634203], + [120.035954, 51.583657], + [120.052584, 51.560967], + [120.017476, 51.52114], + [119.985447, 51.505125], + [119.982367, 51.482396], + [120.002693, 51.459283], + [119.982983, 51.445112], + [119.97128, 51.40033], + [119.910918, 51.390994], + [119.914614, 51.374187], + [119.946643, 51.360736], + [119.883817, 51.336813], + [119.885049, 51.302777], + [119.811136, 51.281071], + [119.828383, 51.263099], + [119.797586, 51.243622], + [119.821607, 51.21439], + [119.784035, 51.22601], + [119.760629, 51.212516], + [119.788346, 51.174636], + [119.771716, 51.124331], + [119.752622, 51.117193], + [119.764325, 51.092017], + [119.719361, 51.075099], + [119.726753, 51.051028], + [119.678093, 51.016404], + [119.630666, 51.00925], + [119.598637, 50.984767], + [119.569688, 50.933879], + [119.491464, 50.87878], + [119.498855, 50.827776], + [119.515485, 50.814165], + [119.496391, 50.771795], + [119.506862, 50.763846], + [119.450196, 50.695281], + [119.430486, 50.684286], + [119.385522, 50.682769], + [119.394145, 50.667219], + [119.361501, 50.632689], + [119.298059, 50.616743], + [119.281428, 50.601551], + [119.295595, 50.573814], + [119.264182, 50.536933], + [119.262334, 50.490124], + [119.250631, 50.448604], + [119.22353, 50.441363], + [119.217371, 50.414675], + [119.165016, 50.422683], + [119.125596, 50.389118], + [119.176719, 50.378814], + [119.155777, 50.364691], + [119.188422, 50.347509], + [119.232153, 50.365455], + [119.259871, 50.345218], + [119.277117, 50.366218], + [119.322696, 50.352474], + [119.358421, 50.358965], + [119.381827, 50.324208], + [119.35103, 50.303953], + [119.339943, 50.244668], + [119.319001, 50.220933], + [119.358421, 50.197953], + [119.339327, 50.192206], + [119.350414, 50.166145], + [119.309762, 50.161161], + [119.290052, 50.121655], + [119.236465, 50.075204], + [119.190269, 50.087877], + [119.193965, 50.069826], + [119.163168, 50.027554], + [119.12498, 50.019095], + [119.090487, 49.985629], + [118.982082, 49.979087], + [118.964836, 49.988708], + [118.791757, 49.955606], + [118.761576, 49.959456], + [118.739402, 49.946364], + [118.672264, 49.955991], + [118.605127, 49.926719], + [118.574946, 49.931342], + [118.531214, 49.887791], + [118.485019, 49.866194], + [118.483787, 49.830691], + [118.443751, 49.835709], + [118.385853, 49.827217], + [118.398787, 49.802502], + [118.384005, 49.783958], + [118.315636, 49.766953], + [118.284223, 49.743755], + [118.220781, 49.729831], + [118.211542, 49.690744], + [118.156723, 49.660149], + [118.129622, 49.669446], + [118.082811, 49.616741], + [118.011362, 49.614803], + [117.995963, 49.623332], + [117.950999, 49.596187], + [117.866, 49.591532], + [117.849369, 49.551557], + [117.809333, 49.521263], + [117.638102, 49.574847], + [117.485349, 49.633024], + [117.278394, 49.636512], + [117.068974, 49.695389], + [116.736367, 49.847674], + [116.717889, 49.847288], + [116.428397, 49.430659], + [116.048363, 48.873274], + [116.077928, 48.822471], + [116.069305, 48.811437], + [115.83032, 48.560156], + [115.799523, 48.514982], + [115.822929, 48.259432], + [115.81061, 48.257042], + [115.529126, 48.155336], + [115.545141, 48.134971], + [115.539597, 48.104607], + [115.580249, 47.921649], + [115.939342, 47.683275], + [115.968291, 47.689721], + [116.111189, 47.811642], + [116.130283, 47.823296], + [116.26579, 47.876711], + [116.453035, 47.837358], + [116.669846, 47.890758], + [116.791186, 47.89758], + [116.879265, 47.893968], + [117.094844, 47.8241], + [117.384335, 47.641356], + [117.493357, 47.758563], + [117.519226, 47.761782], + [117.529081, 47.782697], + [117.813645, 48.016212], + [117.886942, 48.025418], + [117.96147, 48.011007], + [118.052014, 48.01421], + [118.107448, 48.031021], + [118.124694, 48.047427], + [118.150564, 48.036224], + [118.238643, 48.041826], + [118.238027, 48.031422], + [118.284839, 48.011007], + [118.351976, 48.006203], + [118.37415, 48.016612], + [118.422193, 48.01461], + [118.441903, 47.995791], + [118.568171, 47.992187], + [118.773278, 47.771034], + [119.134219, 47.664335], + [119.152081, 47.540453], + [119.205052, 47.520249], + [119.365812, 47.47739], + [119.32208, 47.42721], + [119.365812, 47.423161], + [119.386138, 47.397645], + [119.437877, 47.378602], + [119.450812, 47.353065], + [119.559217, 47.303172], + [119.56784, 47.248357], + [119.627586, 47.247544], + [119.716282, 47.195518], + [119.763093, 47.13082], + [119.806825, 47.055037], + [119.79081, 47.04525], + [119.795122, 47.013024], + [119.845013, 46.964852], + [119.859795, 46.917046], + [119.926933, 46.903963], + [119.920157, 46.853238], + [119.936172, 46.790173], + [119.917078, 46.758203], + [119.93494, 46.712674], + [119.911534, 46.669572], + [119.859179, 46.669572], + [119.804361, 46.68189], + [119.8136, 46.66834], + [119.783419, 46.626023], + [119.739687, 46.615336], + [119.677477, 46.584908], + [119.682405, 46.605058], + [119.656535, 46.625612], + [119.598637, 46.618214], + [119.557985, 46.633832], + [119.491464, 46.629311], + [119.431718, 46.638763], + [119.374435, 46.603414], + [119.357805, 46.619447], + [119.325776, 46.608759], + [119.26295, 46.649034], + [119.20074, 46.648213], + [119.152081, 46.658072], + [119.123132, 46.642872], + [119.073857, 46.676552], + [119.011647, 46.745902], + [118.951285, 46.722111], + [118.912481, 46.733188], + [118.914329, 46.77501], + [118.845343, 46.771731], + [118.788061, 46.717598], + [118.788061, 46.687227], + [118.677192, 46.6979], + [118.639004, 46.721291], + [118.586033, 46.692975], + [118.446831, 46.704467], + [118.41049, 46.728265], + [118.316252, 46.73934], + [118.274984, 46.715957], + [118.238643, 46.709392], + [118.192448, 46.682711], + [118.124078, 46.678195], + [118.04647, 46.631366], + [117.992883, 46.631366], + [117.982412, 46.614925], + [117.914659, 46.607936], + [117.868464, 46.575447], + [117.870927, 46.549935], + [117.813645, 46.530588], + [117.769913, 46.537586], + [117.748355, 46.521941], + [117.704008, 46.516587], + [117.641182, 46.558166], + [117.622704, 46.596012], + [117.596218, 46.603414], + [117.49582, 46.600535], + [117.42006, 46.582029], + [117.447777, 46.528117], + [117.392343, 46.463023], + [117.375712, 46.416421], + [117.383719, 46.394962], + [117.372017, 46.36028], + [117.247597, 46.366888], + [117.097308, 46.356976], + [116.876801, 46.375559], + [116.834302, 46.384229], + [116.81336, 46.355737], + [116.745606, 46.327642], + [116.673541, 46.325163], + [116.585462, 46.292504], + [116.573143, 46.258998], + [116.536187, 46.23251], + [116.439484, 46.137628], + [116.414231, 46.133896], + [116.271949, 45.966926], + [116.243, 45.876169], + [116.288579, 45.839074], + [116.278108, 45.831152], + [116.286731, 45.775247], + [116.260862, 45.776082], + [116.22329, 45.747273], + [116.217746, 45.72221], + [116.17463, 45.688775], + [116.1155, 45.679577], + [116.035428, 45.685013], + [116.026805, 45.661177], + [115.936878, 45.632727], + [115.864197, 45.572853], + [115.699741, 45.45963], + [115.586408, 45.440317], + [115.36467, 45.392427], + [115.178041, 45.396209], + [114.983404, 45.379397], + [114.920578, 45.386122], + [114.745035, 45.438217], + [114.600906, 45.403773], + [114.551014, 45.387383], + [114.539928, 45.325985], + [114.519602, 45.283893], + [114.459855, 45.21353], + [114.409348, 45.179371], + [114.347139, 45.119436], + [114.313262, 45.107189], + [114.19069, 45.036607], + [114.158045, 44.994301], + [114.116777, 44.957045], + [114.065038, 44.931206], + [113.907358, 44.915104], + [113.861778, 44.863377], + [113.798953, 44.849377], + [113.712105, 44.788247], + [113.631417, 44.745333], + [113.540874, 44.759358], + [113.503918, 44.777628], + [113.11526, 44.799714], + [113.037652, 44.822641], + [112.937869, 44.840042], + [112.850406, 44.840466], + [112.712436, 44.879494], + [112.599719, 44.930783], + [112.540589, 45.001072], + [112.438959, 45.071697], + [112.396459, 45.064512], + [112.113743, 45.072965], + [112.071243, 45.096206], + [112.002874, 45.090713], + [111.903707, 45.052252], + [111.764505, 44.969325], + [111.69244, 44.859983], + [111.624687, 44.778477], + [111.585267, 44.705789], + [111.560629, 44.647062], + [111.569868, 44.57634], + [111.530448, 44.55033], + [111.514434, 44.507666], + [111.478709, 44.488884], + [111.427586, 44.394455], + [111.415883, 44.35724], + [111.428818, 44.319573], + [111.507042, 44.294305], + [111.534144, 44.26217], + [111.541535, 44.206855], + [111.559397, 44.171238], + [111.662875, 44.061247], + [111.702295, 44.034147], + [111.773128, 44.010479], + [111.870447, 43.940279], + [111.959758, 43.823382], + [111.970845, 43.748205], + [111.951135, 43.693275], + [111.891388, 43.6738], + [111.79407, 43.672068], + [111.606209, 43.513863], + [111.564325, 43.490422], + [111.456535, 43.494329], + [111.400485, 43.472618], + [111.354289, 43.436125], + [111.183674, 43.396132], + [111.151029, 43.38004], + [111.069725, 43.357852], + [111.02045, 43.329998], + [110.82027, 43.149067], + [110.769763, 43.099272], + [110.736502, 43.089657], + [110.687227, 43.036314], + [110.689691, 43.02144], + [110.631177, 42.936061], + [110.469801, 42.839156], + [110.437156, 42.781203], + [110.34846, 42.742098], + [110.139657, 42.674815], + [110.108244, 42.642687], + [109.906216, 42.635643], + [109.733753, 42.579262], + [109.683862, 42.558988], + [109.544044, 42.472528], + [109.486761, 42.458842], + [109.291509, 42.435879], + [109.026039, 42.458401], + [108.983539, 42.449128], + [108.845569, 42.395673], + [108.798757, 42.415116], + [108.705134, 42.413349], + [108.532671, 42.442945], + [108.298614, 42.438529], + [108.238252, 42.460167], + [108.089195, 42.436321], + [108.022058, 42.433229], + [107.986949, 42.413349], + [107.939522, 42.403628], + [107.736262, 42.415116], + [107.57427, 42.412907], + [107.501589, 42.456635], + [107.46648, 42.458842], + [107.303872, 42.412465], + [107.271844, 42.364285], + [107.051337, 42.319166], + [106.785867, 42.291281], + [106.612789, 42.241679], + [106.372572, 42.161436], + [106.344855, 42.149457], + [106.01348, 42.032213], + [105.74185, 41.949033], + [105.589713, 41.888471], + [105.385221, 41.797073], + [105.291599, 41.749763], + [105.230621, 41.751103], + [105.009498, 41.583007], + [104.923267, 41.654143], + [104.803775, 41.652355], + [104.68921, 41.6452], + [104.524138, 41.661745], + [104.530298, 41.875104], + [104.418813, 41.860397], + [104.30856, 41.840782], + [104.080046, 41.805104], + [103.868779, 41.802427], + [103.454868, 41.877332], + [103.418527, 41.882233], + [103.20726, 41.96283], + [103.021862, 42.028212], + [102.712045, 42.153007], + [102.621502, 42.154338], + [102.540814, 42.162323], + [102.449039, 42.144133], + [102.093642, 42.223512], + [102.070236, 42.232374], + [101.877447, 42.432345], + [101.803534, 42.503861], + [101.770274, 42.509597], + [101.557775, 42.529887], + [101.291689, 42.586312], + [100.862995, 42.671295], + [100.826655, 42.675255], + [100.32528, 42.690213], + [100.272309, 42.636523], + [100.004376, 42.648849], + [99.969267, 42.647969], + [99.51224, 42.568244], + [98.962822, 42.607018], + [98.546447, 42.638284], + [98.195362, 42.653251], + [97.831958, 42.706047], + [97.28254, 42.782081], + [97.172903, 42.795257] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 210000, + "name": "Liaoning", + "center": [123.429096, 41.796767], + "centroid": [122.604994, 41.299712], + "childrenNum": 14, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 5, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [123.534489, 39.788361], + [123.546808, 39.756163], + [123.579453, 39.781002], + [123.612714, 39.775023], + [123.642279, 39.796178], + [123.645358, 39.823761], + [123.674924, 39.826979], + [123.687858, 39.808132], + [123.795032, 39.822842], + [123.812278, 39.831115], + [123.95148, 39.817786], + [124.002603, 39.800316], + [124.103001, 39.823302], + [124.099306, 39.777323], + [124.151045, 39.74558], + [124.173218, 39.841225], + [124.214486, 39.865116], + [124.215102, 39.883487], + [124.21695, 39.894049], + [124.218182, 39.895885], + [124.219414, 39.899099], + [124.241588, 39.928477], + [124.286551, 39.931689], + [124.288399, 39.962888], + [124.349377, 39.989029], + [124.372167, 40.021576], + [124.336442, 40.049985], + [124.346913, 40.079756], + [124.428217, 40.144291], + [124.457782, 40.177679], + [124.490427, 40.18408], + [124.513833, 40.218362], + [124.515065, 40.22019], + [124.62655, 40.291896], + [124.722636, 40.321561], + [124.739267, 40.371733], + [124.834121, 40.423235], + [124.913578, 40.481981], + [124.945606, 40.45603], + [124.985642, 40.475153], + [125.044157, 40.466503], + [125.042925, 40.483802], + [125.004737, 40.496091], + [125.015823, 40.533853], + [125.076801, 40.562048], + [125.113758, 40.569322], + [125.181511, 40.611132], + [125.262815, 40.620218], + [125.279445, 40.655187], + [125.305315, 40.661089], + [125.329337, 40.643835], + [125.375532, 40.658365], + [125.422343, 40.635661], + [125.418648, 40.673345], + [125.453756, 40.676522], + [125.459916, 40.707379], + [125.49564, 40.728697], + [125.544915, 40.729605], + [125.551075, 40.761796], + [125.585567, 40.788535], + [125.61698, 40.763609], + [125.685349, 40.769048], + [125.67611, 40.788082], + [125.641002, 40.798503], + [125.648393, 40.826133], + [125.707523, 40.866877], + [125.687813, 40.897645], + [125.652089, 40.91619], + [125.584335, 40.891764], + [125.589263, 40.931112], + [125.635458, 40.94151], + [125.650241, 40.970888], + [125.674879, 40.974503], + [125.684118, 41.021929], + [125.726617, 41.055332], + [125.739552, 41.08917], + [125.712451, 41.095485], + [125.734009, 41.125695], + [125.759878, 41.132908], + [125.791291, 41.167607], + [125.73832, 41.178418], + [125.758646, 41.232449], + [125.749407, 41.245499], + [125.695205, 41.244599], + [125.685349, 41.273842], + [125.646545, 41.264396], + [125.642234, 41.296327], + [125.62006, 41.318355], + [125.637306, 41.34442], + [125.610205, 41.365084], + [125.589879, 41.359245], + [125.581256, 41.396517], + [125.547995, 41.401006], + [125.534444, 41.428833], + [125.533212, 41.479069], + [125.493176, 41.509103], + [125.507343, 41.534195], + [125.479626, 41.544946], + [125.450061, 41.597777], + [125.461148, 41.642516], + [125.446981, 41.67605], + [125.412488, 41.691246], + [125.344119, 41.672474], + [125.317018, 41.676944], + [125.332416, 41.711354], + [125.336112, 41.768067], + [125.336112, 41.768067], + [125.323177, 41.771191], + [125.323177, 41.771191], + [125.319482, 41.776993], + [125.319482, 41.776993], + [125.294844, 41.822945], + [125.307779, 41.924548], + [125.35151, 41.92811], + [125.291764, 41.958825], + [125.29854, 41.974399], + [125.369989, 42.002868], + [125.363213, 42.017097], + [125.416184, 42.063766], + [125.414336, 42.101964], + [125.446365, 42.098411], + [125.490097, 42.136145], + [125.458068, 42.160105], + [125.458068, 42.160105], + [125.41372, 42.156112], + [125.368141, 42.182726], + [125.357054, 42.145464], + [125.305931, 42.146351], + [125.312706, 42.197359], + [125.280677, 42.175187], + [125.312706, 42.219966], + [125.27575, 42.231045], + [125.27575, 42.266928], + [125.299156, 42.289953], + [125.264047, 42.312528], + [125.224011, 42.30102], + [125.175352, 42.308102], + [125.167345, 42.351903], + [125.203685, 42.366938], + [125.185823, 42.38197], + [125.186439, 42.427928], + [125.140243, 42.44692], + [125.150098, 42.458842], + [125.105135, 42.490624], + [125.068794, 42.499449], + [125.090968, 42.515773], + [125.066946, 42.534738], + [125.089736, 42.567803], + [125.082961, 42.591159], + [125.097127, 42.622433], + [125.038613, 42.615387], + [125.010896, 42.63212], + [125.014592, 42.666014], + [124.99057, 42.677455], + [124.968396, 42.722756], + [124.996729, 42.745174], + [124.975171, 42.802722], + [124.92836, 42.819844], + [124.897563, 42.787791], + [124.874157, 42.789987], + [124.856911, 42.824234], + [124.84952, 42.882585], + [124.87231, 42.962344], + [124.869846, 42.988178], + [124.840897, 43.032377], + [124.88894, 43.074796], + [124.882781, 43.13422], + [124.785462, 43.117185], + [124.755281, 43.074359], + [124.719557, 43.069987], + [124.686912, 43.051185], + [124.677673, 43.002185], + [124.658579, 42.972854], + [124.635173, 42.972854], + [124.632093, 42.949642], + [124.607456, 42.937376], + [124.586514, 42.905384], + [124.466406, 42.847054], + [124.435609, 42.880831], + [124.371551, 42.880831], + [124.38079, 42.912835], + [124.431913, 42.930803], + [124.442384, 42.958841], + [124.42329, 42.975482], + [124.369703, 42.972854], + [124.333363, 42.997371], + [124.425754, 43.076107], + [124.366007, 43.121554], + [124.273617, 43.17875], + [124.287167, 43.207983], + [124.27608, 43.233278], + [124.228653, 43.235022], + [124.215102, 43.255947], + [124.168291, 43.244177], + [124.114088, 43.247229], + [124.117168, 43.2773], + [124.099306, 43.292983], + [124.032784, 43.280786], + [123.964415, 43.34088], + [123.896046, 43.361333], + [123.881263, 43.392218], + [123.881263, 43.392218], + [123.852314, 43.406133], + [123.857858, 43.459153], + [123.857858, 43.459153], + [123.79688, 43.489988], + [123.747604, 43.472184], + [123.749452, 43.439167], + [123.710032, 43.417001], + [123.703873, 43.37047], + [123.698329, 43.272071], + [123.664453, 43.264663], + [123.676771, 43.223684], + [123.645974, 43.208855], + [123.666916, 43.179623], + [123.636119, 43.141644], + [123.631192, 43.088346], + [123.580685, 43.036314], + [123.572678, 43.003498], + [123.536337, 43.007], + [123.474743, 43.042438], + [123.434707, 43.027565], + [123.323222, 43.000872], + [123.259165, 42.993431], + [123.18402, 42.925983], + [123.188947, 42.895739], + [123.169853, 42.859777], + [123.227752, 42.831695], + [123.118114, 42.801405], + [123.058368, 42.768903], + [122.980144, 42.777689], + [122.945651, 42.753524], + [122.925941, 42.772417], + [122.887137, 42.770221], + [122.883442, 42.751766], + [122.883442, 42.751766], + [122.848949, 42.712203], + [122.848949, 42.712203], + [122.786123, 42.757479], + [122.73808, 42.77066], + [122.733152, 42.786034], + [122.653696, 42.78252], + [122.624747, 42.773296], + [122.580399, 42.789987], + [122.576088, 42.819405], + [122.556378, 42.827745], + [122.436886, 42.843105], + [122.35127, 42.830378], + [122.371596, 42.776371], + [122.439349, 42.770221], + [122.460907, 42.755282], + [122.396234, 42.707366], + [122.396234, 42.684054], + [122.338951, 42.669975], + [122.324785, 42.684934], + [122.261343, 42.695931], + [122.204676, 42.732867], + [122.204676, 42.685374], + [122.160945, 42.684934], + [122.072865, 42.710444], + [122.062394, 42.723635], + [122.018663, 42.69901], + [121.939207, 42.688453], + [121.94167, 42.666014], + [121.915801, 42.656332], + [121.921344, 42.605697], + [121.889931, 42.556784], + [121.844352, 42.522389], + [121.831417, 42.533856], + [121.817867, 42.504303], + [121.803084, 42.514891], + [121.747649, 42.484887], + [121.69899, 42.438529], + [121.66573, 42.437204], + [121.604136, 42.495037], + [121.607831, 42.516214], + [121.570875, 42.487093], + [121.506201, 42.482239], + [121.4791, 42.49636], + [121.434752, 42.475176], + [121.386093, 42.474294], + [121.304789, 42.435879], + [121.314644, 42.42837], + [121.285079, 42.387717], + [121.218558, 42.371802], + [121.184681, 42.333324], + [121.133558, 42.300135], + [121.120623, 42.280656], + [121.087978, 42.278885], + [121.070732, 42.254083], + [121.028848, 42.242565], + [120.992508, 42.264714], + [120.933994, 42.27977], + [120.883487, 42.269585], + [120.883487, 42.269585], + [120.883487, 42.242565], + [120.8299, 42.252755], + [120.820661, 42.227943], + [120.79048, 42.218636], + [120.745516, 42.223512], + [120.72211, 42.203565], + [120.624792, 42.154338], + [120.58414, 42.167203], + [120.56751, 42.152119], + [120.466496, 42.105516], + [120.493597, 42.073539], + [120.450481, 42.057101], + [120.456641, 42.016208], + [120.399358, 41.984631], + [120.373489, 41.994862], + [120.309431, 41.951704], + [120.318054, 41.93746], + [120.271859, 41.925439], + [120.260156, 41.904062], + [120.290337, 41.897381], + [120.286641, 41.880005], + [120.251533, 41.884016], + [120.215808, 41.853265], + [120.188707, 41.848361], + [120.183164, 41.826513], + [120.127113, 41.77253], + [120.1382, 41.729221], + [120.096316, 41.697056], + [120.035954, 41.708226], + [120.024867, 41.737707], + [120.050737, 41.776101], + [120.041498, 41.818932], + [120.023019, 41.816701], + [119.989759, 41.899163], + [119.954034, 41.923212], + [119.950954, 41.974399], + [119.924469, 41.98908], + [119.921389, 42.014429], + [119.897368, 42.030879], + [119.87581, 42.077982], + [119.845629, 42.097079], + [119.837622, 42.135257], + [119.854868, 42.170308], + [119.841933, 42.215534], + [119.744615, 42.211545], + [119.679941, 42.240793], + [119.617115, 42.252755], + [119.609108, 42.276671], + [119.557985, 42.289068], + [119.557985, 42.289068], + [119.539507, 42.297922], + [119.571536, 42.335536], + [119.572152, 42.359421], + [119.540123, 42.363401], + [119.502551, 42.388159], + [119.482841, 42.347037], + [119.432949, 42.317396], + [119.34795, 42.300578], + [119.280197, 42.260728], + [119.274037, 42.239021], + [119.237697, 42.200905], + [119.277733, 42.185387], + [119.286972, 42.154781], + [119.30853, 42.147239], + [119.314689, 42.119723], + [119.352261, 42.118391], + [119.384906, 42.08953], + [119.375667, 42.023322], + [119.324544, 41.969505], + [119.323928, 41.937014], + [119.340559, 41.926774], + [119.323312, 41.889807], + [119.334399, 41.871539], + [119.312841, 41.80555], + [119.292515, 41.790827], + [119.317769, 41.764049], + [119.319001, 41.727435], + [119.299907, 41.705545], + [119.307914, 41.657273], + [119.342406, 41.617914], + [119.415703, 41.590169], + [119.420015, 41.567785], + [119.362116, 41.566442], + [119.361501, 41.545841], + [119.406464, 41.503276], + [119.401537, 41.472343], + [119.378131, 41.459787], + [119.376283, 41.422102], + [119.309762, 41.405944], + [119.330704, 41.385293], + [119.296211, 41.325097], + [119.239545, 41.31431], + [119.2494, 41.279689], + [119.209364, 41.244599], + [119.204436, 41.222546], + [119.169943, 41.222996], + [119.189038, 41.198234], + [119.126212, 41.138767], + [119.081248, 41.131555], + [119.080632, 41.095936], + [119.037516, 41.067516], + [118.964836, 41.079246], + [118.937118, 41.052625], + [118.951901, 41.018317], + [119.013495, 41.007479], + [119.00056, 40.967273], + [118.977154, 40.959138], + [118.977154, 40.959138], + [118.916792, 40.969984], + [118.90201, 40.960946], + [118.873061, 40.847866], + [118.845959, 40.822057], + [118.878604, 40.783098], + [118.907553, 40.775394], + [118.895234, 40.75409], + [118.950053, 40.747743], + [118.96114, 40.72008], + [119.011031, 40.687414], + [119.028277, 40.692406], + [119.054763, 40.664721], + [119.115125, 40.666536], + [119.165632, 40.69286], + [119.184726, 40.680153], + [119.14469, 40.632482], + [119.162552, 40.600228], + [119.177951, 40.609315], + [119.230921, 40.603863], + [119.22045, 40.569322], + [119.256175, 40.543404], + [119.30237, 40.530215], + [119.429254, 40.540221], + [119.477913, 40.533399], + [119.503783, 40.553864], + [119.559217, 40.547952], + [119.572152, 40.523846], + [119.553674, 40.502007], + [119.604797, 40.455119], + [119.586934, 40.375381], + [119.598021, 40.334335], + [119.651608, 40.271808], + [119.639289, 40.231613], + [119.639289, 40.231613], + [119.671934, 40.23938], + [119.716898, 40.195966], + [119.745847, 40.207851], + [119.760629, 40.136056], + [119.736608, 40.104936], + [119.772332, 40.08113], + [119.783419, 40.046778], + [119.783419, 40.046778], + [119.787115, 40.041739], + [119.787115, 40.041739], + [119.81668, 40.050443], + [119.81668, 40.050443], + [119.854252, 40.033033], + [119.845629, 40.000949], + [119.845629, 40.000949], + [119.854252, 39.98857], + [119.91831, 39.989946], + [119.941715, 40.009659], + [119.947259, 40.040364], + [120.092005, 40.077466], + [120.134504, 40.074719], + [120.161606, 40.096239], + [120.273091, 40.127362], + [120.371641, 40.174478], + [120.451097, 40.177679], + [120.491749, 40.20008], + [120.523778, 40.256737], + [120.52193, 40.304676], + [120.537329, 40.325211], + [120.602618, 40.36079], + [120.596459, 40.399084], + [120.617401, 40.41959], + [120.616169, 40.444645], + [120.619249, 40.460128], + [120.666676, 40.467413], + [120.693777, 40.505647], + [120.72211, 40.515657], + [120.72827, 40.539311], + [120.822509, 40.59432], + [120.837291, 40.644289], + [120.8299, 40.671076], + [120.861313, 40.684692], + [120.939537, 40.686507], + [120.983269, 40.712822], + [121.032544, 40.709193], + [121.028848, 40.746382], + [120.991276, 40.744115], + [120.980189, 40.766329], + [120.994356, 40.790801], + [120.971566, 40.805751], + [121.00729, 40.807563], + [121.010986, 40.784457], + [121.086747, 40.79805], + [121.076892, 40.815716], + [121.096602, 40.839717], + [121.126167, 40.86914], + [121.177906, 40.873665], + [121.23642, 40.851035], + [121.290622, 40.851488], + [121.439064, 40.830208], + [121.440296, 40.88181], + [121.499426, 40.880001], + [121.526527, 40.85194], + [121.55486, 40.849677], + [121.553013, 40.817528], + [121.576418, 40.837906], + [121.626309, 40.844244], + [121.682976, 40.829755], + [121.732251, 40.846961], + [121.735331, 40.862351], + [121.778446, 40.886787], + [121.816019, 40.894931], + [121.84312, 40.831567], + [121.883772, 40.802127], + [121.934279, 40.79805], + [121.936127, 40.711462], + [121.951525, 40.680607], + [122.025438, 40.674253], + [122.06609, 40.64883], + [122.122141, 40.657457], + [122.148626, 40.671983], + [122.133843, 40.614313], + [122.150474, 40.588413], + [122.245944, 40.519752], + [122.231162, 40.505192], + [122.265038, 40.48016], + [122.221923, 40.481071], + [122.240401, 40.461039], + [122.250872, 40.445555], + [122.229314, 40.424146], + [122.186814, 40.422779], + [122.198517, 40.382219], + [122.152322, 40.357597], + [122.135691, 40.374925], + [122.111054, 40.348932], + [122.138155, 40.338897], + [122.110438, 40.315629], + [122.079641, 40.332967], + [122.040221, 40.322017], + [122.039605, 40.260391], + [122.02667, 40.244862], + [121.940438, 40.242121], + [121.950293, 40.204194], + [121.98109, 40.173106], + [122.003264, 40.172191], + [121.995257, 40.128277], + [121.956453, 40.133311], + [121.910257, 40.072887], + [121.824642, 40.025701], + [121.796309, 39.999116], + [121.779062, 39.942702], + [121.76428, 39.933525], + [121.699606, 39.937196], + [121.626925, 39.882569], + [121.572107, 39.865116], + [121.541926, 39.874302], + [121.530223, 39.851334], + [121.472325, 39.802155], + [121.487107, 39.760303], + [121.45939, 39.747881], + [121.502506, 39.703233], + [121.482796, 39.659478], + [121.451999, 39.658095], + [121.450151, 39.624914], + [121.325731, 39.601402], + [121.299246, 39.606013], + [121.263521, 39.589873], + [121.226565, 39.554814], + [121.224717, 39.519275], + [121.268449, 39.482794], + [121.286927, 39.507271], + [121.301709, 39.476327], + [121.245659, 39.456923], + [121.270296, 39.434277], + [121.246891, 39.421334], + [121.245659, 39.389427], + [121.270296, 39.374162], + [121.307869, 39.391277], + [121.324499, 39.371386], + [121.35468, 39.377863], + [121.432904, 39.357506], + [121.435984, 39.329736], + [121.466781, 39.320014], + [121.474788, 39.296398], + [121.508665, 39.29223], + [121.51544, 39.286672], + [121.562252, 39.322792], + [121.621382, 39.326033], + [121.72486, 39.364447], + [121.711925, 39.33992], + [121.7187, 39.320477], + [121.667577, 39.310754], + [121.672505, 39.275554], + [121.623846, 39.285745], + [121.589353, 39.263044], + [121.631237, 39.22643], + [121.591201, 39.228748], + [121.586889, 39.193506], + [121.604136, 39.166136], + [121.639244, 39.166136], + [121.68236, 39.117863], + [121.631853, 39.077921], + [121.605983, 39.080708], + [121.642324, 39.11972], + [121.590585, 39.154999], + [121.562252, 39.127149], + [121.599208, 39.098824], + [121.581962, 39.075598], + [121.508049, 39.034237], + [121.431057, 39.027263], + [121.370695, 39.060264], + [121.317108, 39.012384], + [121.341129, 38.980757], + [121.275224, 38.971917], + [121.204391, 38.941202], + [121.180369, 38.959819], + [121.128014, 38.958888], + [121.08921, 38.922115], + [121.094138, 38.894173], + [121.129862, 38.879266], + [121.110768, 38.862026], + [121.12863, 38.799089], + [121.112, 38.776231], + [121.13787, 38.723023], + [121.198848, 38.721623], + [121.259825, 38.786495], + [121.280767, 38.786961], + [121.288775, 38.78976], + [121.315876, 38.793958], + [121.359608, 38.822406], + [121.399028, 38.812613], + [121.509897, 38.817743], + [121.564715, 38.874607], + [121.618302, 38.862492], + [121.675585, 38.86156], + [121.708845, 38.872744], + [121.719316, 38.920252], + [121.655874, 38.946788], + [121.618918, 38.950046], + [121.66265, 38.966333], + [121.671273, 39.010059], + [121.73841, 38.998898], + [121.756889, 39.025869], + [121.790149, 39.022614], + [121.804932, 38.970986], + [121.863446, 38.942598], + [121.920728, 38.969591], + [121.905946, 38.997503], + [121.852975, 39.035631], + [121.8887, 39.027263], + [121.929352, 39.024939], + [121.907178, 39.055617], + [121.923192, 39.053758], + [121.963228, 39.030053], + [122.013735, 39.073275], + [122.061778, 39.060264], + [122.071634, 39.074204], + [122.048228, 39.101146], + [122.088264, 39.112291], + [122.127684, 39.144788], + [122.167104, 39.158711], + [122.123988, 39.172631], + [122.117213, 39.213911], + [122.160329, 39.238019], + [122.242865, 39.267678], + [122.274893, 39.322329], + [122.30877, 39.346399], + [122.366053, 39.370461], + [122.412864, 39.411625], + [122.455364, 39.408388], + [122.467682, 39.403301], + [122.51203, 39.413474], + [122.532972, 39.419947], + [122.581631, 39.464316], + [122.637066, 39.488799], + [122.649385, 39.516505], + [122.682645, 39.514658], + [122.808913, 39.559889], + [122.847101, 39.581571], + [122.860652, 39.604629], + [122.941956, 39.604629], + [122.972753, 39.594946], + [122.978912, 39.616156], + [123.021412, 39.64335], + [123.010941, 39.655331], + [123.103332, 39.676983], + [123.146448, 39.647037], + [123.166774, 39.674219], + [123.212969, 39.665928], + [123.215433, 39.696786], + [123.253005, 39.689879], + [123.286882, 39.704154], + [123.270251, 39.714743], + [123.274563, 39.753862], + [123.350939, 39.750641], + [123.388512, 39.74742], + [123.392823, 39.723949], + [123.477823, 39.74696], + [123.521555, 39.772724], + [123.534489, 39.788361] + ] + ], + [ + [ + [122.63953, 39.286209], + [122.593334, 39.278334], + [122.539131, 39.308439], + [122.50895, 39.290377], + [122.57732, 39.269994], + [122.67895, 39.268605], + [122.673406, 39.269531], + [122.662935, 39.273701], + [122.655544, 39.277407], + [122.640761, 39.288061], + [122.63953, 39.286209] + ] + ], + [ + [ + [122.318625, 39.170775], + [122.345111, 39.144788], + [122.366053, 39.174951], + [122.398697, 39.16196], + [122.383299, 39.190723], + [122.393154, 39.213448], + [122.343263, 39.203246], + [122.322321, 39.177271], + [122.322937, 39.174487], + [122.319241, 39.172167], + [122.318625, 39.170775] + ] + ], + [ + [ + [122.691884, 39.23292], + [122.696812, 39.206492], + [122.751631, 39.229675], + [122.740544, 39.248679], + [122.635834, 39.241727], + [122.628443, 39.231993], + [122.690037, 39.234774], + [122.691268, 39.23431], + [122.691884, 39.23292] + ] + ], + [ + [ + [122.738696, 39.034701], + [122.704819, 39.044463], + [122.733152, 39.014244], + [122.75779, 39.009594], + [122.739312, 39.036561], + [122.738696, 39.034701] + ] + ], + [ + [ + [123.022644, 39.546507], + [122.96105, 39.551122], + [122.945035, 39.520198], + [122.995542, 39.495264], + [123.036194, 39.533123], + [123.022644, 39.546507] + ] + ], + [ + [ + [122.503407, 39.241263], + [122.502175, 39.224112], + [122.547755, 39.229211], + [122.503407, 39.241263] + ] + ], + [ + [ + [120.786784, 40.473787], + [120.83298, 40.491995], + [120.8299, 40.516112], + [120.805262, 40.525666], + [120.774465, 40.48016], + [120.786784, 40.473787] + ] + ], + [ + [ + [123.086702, 39.426881], + [123.090397, 39.450915], + [123.054057, 39.457847], + [123.086702, 39.426881] + ] + ], + [ + [ + [123.160614, 39.025404], + [123.205578, 39.057011], + [123.20065, 39.077921], + [123.145832, 39.091857], + [123.143984, 39.038885], + [123.160614, 39.025404] + ] + ], + [ + [ + [123.716807, 39.74512], + [123.756843, 39.754322], + [123.719887, 39.763063], + [123.716807, 39.74512] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 220000, + "name": "Jilin", + "center": [125.3245, 43.886841], + "centroid": [126.171208, 43.703954], + "childrenNum": 9, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 6, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [129.601492, 42.415116], + [129.601492, 42.422627], + [129.591021, 42.447803], + [129.627361, 42.462816], + [129.651999, 42.426603], + [129.704354, 42.427045], + [129.748701, 42.471204], + [129.738846, 42.500332], + [129.749933, 42.546644], + [129.746237, 42.58455], + [129.786889, 42.615387], + [129.754245, 42.645768], + [129.796744, 42.681854], + [129.767179, 42.707806], + [129.78381, 42.762752], + [129.810911, 42.795257], + [129.816454, 42.851003], + [129.835549, 42.866796], + [129.846636, 42.918533], + [129.874969, 42.923792], + [129.856491, 42.951833], + [129.868193, 42.97373], + [129.903918, 42.968475], + [129.897143, 43.001748], + [129.954425, 43.010938], + [129.963664, 42.978547], + [130.002468, 42.981174], + [130.027106, 42.9676], + [130.072685, 42.971541], + [130.10841, 42.989929], + [130.144134, 42.976357], + [130.120729, 42.954461], + [130.127504, 42.932556], + [130.10225, 42.922916], + [130.136127, 42.90363], + [130.17062, 42.912397], + [130.21004, 42.902315], + [130.258083, 42.90626], + [130.277793, 42.892232], + [130.258083, 42.860655], + [130.245148, 42.799209], + [130.242069, 42.738582], + [130.257467, 42.710884], + [130.290112, 42.702968], + [130.333228, 42.64973], + [130.373264, 42.630799], + [130.388046, 42.603054], + [130.420691, 42.617148], + [130.44656, 42.607459], + [130.423771, 42.574855], + [130.435474, 42.553257], + [130.476125, 42.570007], + [130.459495, 42.588075], + [130.482285, 42.626837], + [130.522937, 42.622433], + [130.520473, 42.593362], + [130.558661, 42.495919], + [130.585763, 42.485328], + [130.581451, 42.435437], + [130.645509, 42.426603], + [130.600545, 42.450453], + [130.599929, 42.486211], + [130.565437, 42.506509], + [130.570364, 42.557224], + [130.622719, 42.573092], + [130.633806, 42.603494], + [130.592538, 42.671295], + [130.521089, 42.702089], + [130.464423, 42.688453], + [130.425003, 42.706926], + [130.40714, 42.731548], + [130.46627, 42.772417], + [130.532792, 42.787352], + [130.562357, 42.815015], + [130.603625, 42.819405], + [130.665835, 42.847932], + [130.708335, 42.846615], + [130.719422, 42.831695], + [130.75453, 42.845738], + [130.784095, 42.842227], + [130.801957, 42.879515], + [130.845073, 42.881269], + [130.890653, 42.852758], + [130.912826, 42.870744], + [130.949783, 42.876884], + [130.981812, 42.857145], + [131.043406, 42.862848], + [131.017536, 42.915027], + [131.034167, 42.929051], + [131.114855, 42.915027], + [131.145652, 42.9365], + [131.151195, 42.968475], + [131.115471, 42.975482], + [131.11855, 43.007875], + [131.102536, 43.021002], + [131.120398, 43.068238], + [131.171521, 43.06955], + [131.173985, 43.111506], + [131.207861, 43.1316], + [131.218948, 43.191405], + [131.201086, 43.203185], + [131.206014, 43.237202], + [131.255289, 43.265099], + [131.269455, 43.297775], + [131.275615, 43.369165], + [131.314419, 43.392653], + [131.295941, 43.441774], + [131.314419, 43.461325], + [131.31873, 43.499539], + [131.304564, 43.502144], + [131.294093, 43.470012], + [131.234963, 43.475224], + [131.201086, 43.442209], + [131.175217, 43.444816], + [131.142572, 43.425695], + [131.026775, 43.508655], + [130.959638, 43.48608], + [130.907283, 43.434387], + [130.864167, 43.437863], + [130.841378, 43.454374], + [130.822899, 43.503446], + [130.776704, 43.52341], + [130.727429, 43.560284], + [130.671378, 43.565054], + [130.665835, 43.583698], + [130.623335, 43.589767], + [130.630726, 43.622268], + [130.57098, 43.626167], + [130.57098, 43.626167], + [130.501995, 43.636563], + [130.488444, 43.65605], + [130.437937, 43.646091], + [130.412684, 43.652586], + [130.394206, 43.703227], + [130.423155, 43.745179], + [130.382503, 43.777164], + [130.381887, 43.817768], + [130.362793, 43.844967], + [130.386198, 43.85403], + [130.368336, 43.894151], + [130.381887, 43.910106], + [130.338155, 43.963975], + [130.364025, 43.992399], + [130.365256, 44.044042], + [130.319061, 44.03974], + [130.307358, 44.002731], + [130.27225, 43.981634], + [130.262395, 43.949328], + [130.208192, 43.948466], + [130.153373, 43.915711], + [130.143518, 43.878624], + [130.116417, 43.878192], + [130.110873, 43.852735], + [130.079461, 43.835039], + [130.027722, 43.851872], + [130.009243, 43.889407], + [130.022794, 43.917866], + [130.017867, 43.961821], + [129.979062, 44.015644], + [129.951345, 44.027263], + [129.907614, 44.023821], + [129.881128, 44.000148], + [129.868193, 44.012631], + [129.802904, 43.964837], + [129.780114, 43.892857], + [129.739462, 43.895876], + [129.743158, 43.876035], + [129.699426, 43.8838], + [129.650767, 43.873016], + [129.529427, 43.870427], + [129.467833, 43.874741], + [129.449971, 43.850578], + [129.417942, 43.843672], + [129.406855, 43.819496], + [129.348341, 43.798333], + [129.30892, 43.812155], + [129.289826, 43.797038], + [129.254718, 43.819496], + [129.211602, 43.784509], + [129.232544, 43.709284], + [129.214066, 43.695006], + [129.217146, 43.648689], + [129.232544, 43.635263], + [129.23008, 43.593234], + [129.169102, 43.561585], + [129.145081, 43.570258], + [129.093958, 43.547706], + [129.037907, 43.540332], + [129.013886, 43.522976], + [128.962763, 43.53903], + [128.949828, 43.553779], + [128.878379, 43.539898], + [128.834647, 43.587599], + [128.821097, 43.637429], + [128.78722, 43.686784], + [128.768126, 43.732207], + [128.729322, 43.736964], + [128.760119, 43.755554], + [128.739177, 43.806972], + [128.719467, 43.816905], + [128.760734, 43.857482], + [128.729938, 43.889838], + [128.696061, 43.903207], + [128.636315, 43.891132], + [128.64001, 43.948035], + [128.610445, 43.960529], + [128.584576, 43.990246], + [128.574721, 44.047914], + [128.529141, 44.112401], + [128.471859, 44.157501], + [128.450301, 44.203423], + [128.471859, 44.247596], + [128.453997, 44.257884], + [128.472475, 44.320001], + [128.446605, 44.339694], + [128.475555, 44.346114], + [128.481714, 44.375637], + [128.457076, 44.409848], + [128.463236, 44.431647], + [128.427511, 44.473512], + [128.397946, 44.483761], + [128.372693, 44.514495], + [128.295084, 44.480772], + [128.293237, 44.467961], + [128.228563, 44.445748], + [128.211317, 44.431647], + [128.172512, 44.34697], + [128.137404, 44.357668], + [128.094904, 44.354673], + [128.074578, 44.370075], + [128.049941, 44.349965], + [128.065339, 44.307155], + [128.101679, 44.293449], + [128.064107, 44.251454], + [128.104143, 44.230017], + [128.09244, 44.181539], + [128.060411, 44.168663], + [128.088129, 44.158359], + [128.091208, 44.133022], + [128.042549, 44.103807], + [127.950158, 44.088334], + [127.912586, 44.064687], + [127.862695, 44.062967], + [127.846065, 44.081886], + [127.808492, 44.086615], + [127.783239, 44.071997], + [127.729036, 44.09908], + [127.735811, 44.11412], + [127.712406, 44.199133], + [127.681609, 44.166946], + [127.641573, 44.193555], + [127.626174, 44.187977], + [127.59045, 44.227872], + [127.623711, 44.278025], + [127.579363, 44.310581], + [127.486356, 44.410275], + [127.50853, 44.437202], + [127.463566, 44.484615], + [127.465414, 44.516628], + [127.485124, 44.528576], + [127.536247, 44.522176], + [127.570124, 44.55033], + [127.557189, 44.575488], + [127.392733, 44.632158], + [127.275705, 44.640249], + [127.261538, 44.61299], + [127.214111, 44.624917], + [127.228893, 44.642804], + [127.182082, 44.644507], + [127.138966, 44.607451], + [127.094619, 44.615972], + [127.089691, 44.593816], + [127.049655, 44.566961], + [127.041648, 44.591258], + [127.044112, 44.653874], + [127.030561, 44.673454], + [127.041032, 44.712169], + [126.9973, 44.764882], + [126.984366, 44.823914], + [126.999764, 44.87398], + [127.021938, 44.898997], + [127.073061, 44.907051], + [127.092771, 44.94688], + [127.050271, 45.004034], + [127.018242, 45.024341], + [126.984981, 45.067893], + [126.970815, 45.070852], + [126.96404, 45.132104], + [126.85625, 45.145613], + [126.792808, 45.135481], + [126.787265, 45.159118], + [126.732446, 45.187385], + [126.685635, 45.187807], + [126.640055, 45.214373], + [126.644983, 45.225334], + [126.569222, 45.252725], + [126.540273, 45.23882], + [126.519331, 45.248091], + [126.402919, 45.222805], + [126.356107, 45.185698], + [126.293282, 45.180214], + [126.285274, 45.162494], + [126.235383, 45.140125], + [126.225528, 45.154054], + [126.166398, 45.13337], + [126.142992, 45.147723], + [126.091869, 45.149411], + [126.047522, 45.170933], + [125.998247, 45.162072], + [125.992703, 45.192447], + [125.957595, 45.201303], + [125.915095, 45.196664], + [125.849805, 45.23882], + [125.823936, 45.237978], + [125.815929, 45.264942], + [125.761726, 45.291472], + [125.726001, 45.336503], + [125.695205, 45.352066], + [125.712451, 45.389485], + [125.711835, 45.477677], + [125.687813, 45.514173], + [125.660096, 45.507043], + [125.61698, 45.517947], + [125.583104, 45.491942], + [125.497488, 45.469283], + [125.480242, 45.486488], + [125.424807, 45.485649], + [125.434662, 45.462988], + [125.398322, 45.416797], + [125.361981, 45.392847], + [125.319482, 45.422678], + [125.301619, 45.402092], + [125.248649, 45.417637], + [125.189518, 45.39915], + [125.137779, 45.409655], + [125.097127, 45.38276], + [125.06633, 45.39915], + [125.08912, 45.420998], + [125.0497, 45.428558], + [125.025678, 45.493201], + [124.961005, 45.495299], + [124.936983, 45.53388], + [124.911114, 45.535976], + [124.884628, 45.495299], + [124.886476, 45.442836], + [124.839665, 45.455852], + [124.792853, 45.436958], + [124.776223, 45.468024], + [124.729412, 45.444096], + [124.690607, 45.452493], + [124.625318, 45.437377], + [124.575427, 45.451234], + [124.579738, 45.424358], + [124.544014, 45.411756], + [124.507058, 45.424778], + [124.480572, 45.456271], + [124.398652, 45.440737], + [124.374015, 45.45795], + [124.352457, 45.496557], + [124.369087, 45.512915], + [124.348761, 45.546874], + [124.287783, 45.539329], + [124.264377, 45.555256], + [124.273001, 45.584163], + [124.238508, 45.591702], + [124.226805, 45.633564], + [124.162132, 45.616404], + [124.128255, 45.641933], + [124.147349, 45.665359], + [124.122096, 45.669123], + [124.13503, 45.690448], + [124.10177, 45.700898], + [124.098074, 45.722628], + [124.054342, 45.751449], + [124.014922, 45.749779], + [124.001987, 45.770655], + [124.064197, 45.802372], + [124.03648, 45.83824], + [124.067277, 45.840325], + [124.061118, 45.886168], + [123.996444, 45.906993], + [123.968727, 45.936551], + [123.973654, 45.973997], + [124.011842, 45.981899], + [123.989053, 46.011833], + [124.040176, 46.01973], + [124.034016, 46.045074], + [124.009995, 46.057534], + [124.015538, 46.088257], + [123.99398, 46.101123], + [124.01677, 46.118549], + [123.991516, 46.143019], + [124.001987, 46.166649], + [123.971806, 46.170379], + [123.956408, 46.206009], + [123.979814, 46.228784], + [123.952096, 46.256516], + [123.960103, 46.288369], + [123.936082, 46.286715], + [123.917604, 46.25693], + [123.896046, 46.303668], + [123.84985, 46.302428], + [123.775938, 46.263136], + [123.726047, 46.255688], + [123.673692, 46.258585], + [123.604706, 46.251964], + [123.569598, 46.223816], + [123.569598, 46.223816], + [123.499381, 46.259826], + [123.452569, 46.233338], + [123.430396, 46.243687], + [123.357099, 46.232096], + [123.357099, 46.232096], + [123.320758, 46.254447], + [123.286266, 46.250308], + [123.248078, 46.273065], + [123.178476, 46.248239], + [123.128585, 46.210565], + [123.127354, 46.174523], + [123.102716, 46.172037], + [123.112571, 46.130163], + [123.070071, 46.123527], + [123.04605, 46.099878], + [122.792898, 46.073313], + [122.828623, 45.912406], + [122.80029, 45.856583], + [122.772572, 45.856583], + [122.752246, 45.834905], + [122.792283, 45.766063], + [122.751015, 45.735996], + [122.741775, 45.705077], + [122.671558, 45.70048], + [122.650001, 45.731401], + [122.640761, 45.771072], + [122.603189, 45.778169], + [122.556378, 45.82156], + [122.522501, 45.786933], + [122.504639, 45.786933], + [122.496016, 45.85825], + [122.446125, 45.916986], + [122.362357, 45.917403], + [122.372828, 45.856166], + [122.337719, 45.859917], + [122.301379, 45.813218], + [122.253952, 45.7982], + [122.236705, 45.831569], + [122.200981, 45.857], + [122.091344, 45.882002], + [122.085184, 45.912406], + [122.040221, 45.959022], + [121.92812, 45.988552], + [121.923808, 46.004767], + [121.864062, 46.002272], + [121.843736, 46.024301], + [121.819098, 46.023054], + [121.761816, 45.998947], + [121.809243, 45.961102], + [121.821562, 45.918235], + [121.805548, 45.900746], + [121.817251, 45.875336], + [121.769823, 45.84366], + [121.766744, 45.830318], + [121.766744, 45.830318], + [121.754425, 45.794862], + [121.697142, 45.76314], + [121.657106, 45.770238], + [121.644172, 45.752284], + [121.666345, 45.727641], + [121.713773, 45.701734], + [121.811091, 45.687103], + [121.812323, 45.704659], + [121.867142, 45.719703], + [121.934279, 45.71051], + [121.970004, 45.692956], + [122.003264, 45.623102], + [121.995873, 45.59882], + [121.966308, 45.596308], + [121.993409, 45.552741], + [122.002648, 45.507882], + [122.064242, 45.472641], + [122.168336, 45.439897], + [122.180039, 45.409655], + [122.146778, 45.374352], + [122.147394, 45.295682], + [122.239169, 45.276313], + [122.22993, 45.206784], + [122.192358, 45.180636], + [122.143082, 45.183167], + [122.109822, 45.142236], + [122.119677, 45.068739], + [122.098735, 45.02138], + [122.074713, 45.006573], + [122.087032, 44.95281], + [122.079025, 44.914256], + [122.04946, 44.912985], + [122.098119, 44.81882], + [122.099967, 44.7823], + [122.168952, 44.770405], + [122.142467, 44.753833], + [122.110438, 44.767856], + [122.10243, 44.736406], + [122.152322, 44.744057], + [122.161561, 44.728328], + [122.117213, 44.701961], + [122.103046, 44.67388], + [122.113517, 44.615546], + [122.13138, 44.577619], + [122.196053, 44.559712], + [122.224386, 44.526016], + [122.228082, 44.480345], + [122.28598, 44.477783], + [122.294604, 44.41113], + [122.291524, 44.310152], + [122.271198, 44.255741], + [122.319241, 44.233018], + [122.483081, 44.236877], + [122.515726, 44.251025], + [122.641993, 44.283595], + [122.675254, 44.285738], + [122.702971, 44.319145], + [122.76087, 44.369648], + [122.85634, 44.398304], + [123.025108, 44.493153], + [123.06576, 44.505959], + [123.12489, 44.5098], + [123.137209, 44.486322], + [123.125506, 44.455147], + [123.142136, 44.428228], + [123.114419, 44.40258], + [123.128585, 44.367081], + [123.196955, 44.34483], + [123.277027, 44.25274], + [123.286882, 44.211574], + [123.323838, 44.179823], + [123.386664, 44.161794], + [123.362642, 44.133452], + [123.350939, 44.092633], + [123.32815, 44.084035], + [123.331229, 44.028984], + [123.365722, 44.013922], + [123.400831, 43.979481], + [123.37065, 43.970006], + [123.397135, 43.954929], + [123.467968, 43.853599], + [123.461809, 43.822518], + [123.498149, 43.771114], + [123.48275, 43.737396], + [123.520323, 43.708419], + [123.518475, 43.682024], + [123.536953, 43.633964], + [123.510468, 43.624867], + [123.5117, 43.592801], + [123.421157, 43.598435], + [123.434091, 43.575461], + [123.461193, 43.568523], + [123.452569, 43.545971], + [123.452569, 43.545971], + [123.360179, 43.567223], + [123.304744, 43.550742], + [123.329998, 43.519071], + [123.315831, 43.492159], + [123.36449, 43.483475], + [123.382968, 43.469143], + [123.419925, 43.410046], + [123.442098, 43.437863], + [123.486446, 43.44525], + [123.519707, 43.402219], + [123.54496, 43.415262], + [123.608402, 43.366119], + [123.703873, 43.37047], + [123.710032, 43.417001], + [123.749452, 43.439167], + [123.747604, 43.472184], + [123.79688, 43.489988], + [123.857858, 43.459153], + [123.857858, 43.459153], + [123.852314, 43.406133], + [123.881263, 43.392218], + [123.881263, 43.392218], + [123.896046, 43.361333], + [123.964415, 43.34088], + [124.032784, 43.280786], + [124.099306, 43.292983], + [124.117168, 43.2773], + [124.114088, 43.247229], + [124.168291, 43.244177], + [124.215102, 43.255947], + [124.228653, 43.235022], + [124.27608, 43.233278], + [124.287167, 43.207983], + [124.273617, 43.17875], + [124.366007, 43.121554], + [124.425754, 43.076107], + [124.333363, 42.997371], + [124.369703, 42.972854], + [124.42329, 42.975482], + [124.442384, 42.958841], + [124.431913, 42.930803], + [124.38079, 42.912835], + [124.371551, 42.880831], + [124.435609, 42.880831], + [124.466406, 42.847054], + [124.586514, 42.905384], + [124.607456, 42.937376], + [124.632093, 42.949642], + [124.635173, 42.972854], + [124.658579, 42.972854], + [124.677673, 43.002185], + [124.686912, 43.051185], + [124.719557, 43.069987], + [124.755281, 43.074359], + [124.785462, 43.117185], + [124.882781, 43.13422], + [124.88894, 43.074796], + [124.840897, 43.032377], + [124.869846, 42.988178], + [124.87231, 42.962344], + [124.84952, 42.882585], + [124.856911, 42.824234], + [124.874157, 42.789987], + [124.897563, 42.787791], + [124.92836, 42.819844], + [124.975171, 42.802722], + [124.996729, 42.745174], + [124.968396, 42.722756], + [124.99057, 42.677455], + [125.014592, 42.666014], + [125.010896, 42.63212], + [125.038613, 42.615387], + [125.097127, 42.622433], + [125.082961, 42.591159], + [125.089736, 42.567803], + [125.066946, 42.534738], + [125.090968, 42.515773], + [125.068794, 42.499449], + [125.105135, 42.490624], + [125.150098, 42.458842], + [125.140243, 42.44692], + [125.186439, 42.427928], + [125.185823, 42.38197], + [125.203685, 42.366938], + [125.167345, 42.351903], + [125.175352, 42.308102], + [125.224011, 42.30102], + [125.264047, 42.312528], + [125.299156, 42.289953], + [125.27575, 42.266928], + [125.27575, 42.231045], + [125.312706, 42.219966], + [125.280677, 42.175187], + [125.312706, 42.197359], + [125.305931, 42.146351], + [125.357054, 42.145464], + [125.368141, 42.182726], + [125.41372, 42.156112], + [125.458068, 42.160105], + [125.458068, 42.160105], + [125.490097, 42.136145], + [125.446365, 42.098411], + [125.414336, 42.101964], + [125.416184, 42.063766], + [125.363213, 42.017097], + [125.369989, 42.002868], + [125.29854, 41.974399], + [125.291764, 41.958825], + [125.35151, 41.92811], + [125.307779, 41.924548], + [125.294844, 41.822945], + [125.319482, 41.776993], + [125.319482, 41.776993], + [125.323177, 41.771191], + [125.323177, 41.771191], + [125.336112, 41.768067], + [125.336112, 41.768067], + [125.332416, 41.711354], + [125.317018, 41.676944], + [125.344119, 41.672474], + [125.412488, 41.691246], + [125.446981, 41.67605], + [125.461148, 41.642516], + [125.450061, 41.597777], + [125.479626, 41.544946], + [125.507343, 41.534195], + [125.493176, 41.509103], + [125.533212, 41.479069], + [125.534444, 41.428833], + [125.547995, 41.401006], + [125.581256, 41.396517], + [125.589879, 41.359245], + [125.610205, 41.365084], + [125.637306, 41.34442], + [125.62006, 41.318355], + [125.642234, 41.296327], + [125.646545, 41.264396], + [125.685349, 41.273842], + [125.695205, 41.244599], + [125.749407, 41.245499], + [125.758646, 41.232449], + [125.73832, 41.178418], + [125.791291, 41.167607], + [125.759878, 41.132908], + [125.734009, 41.125695], + [125.712451, 41.095485], + [125.739552, 41.08917], + [125.726617, 41.055332], + [125.684118, 41.021929], + [125.674879, 40.974503], + [125.650241, 40.970888], + [125.635458, 40.94151], + [125.589263, 40.931112], + [125.584335, 40.891764], + [125.652089, 40.91619], + [125.687813, 40.897645], + [125.707523, 40.866877], + [125.778356, 40.897645], + [125.817161, 40.866877], + [125.860892, 40.888597], + [125.875059, 40.908501], + [125.921254, 40.882715], + [125.959442, 40.88181], + [126.008102, 40.936537], + [126.041362, 40.928851], + [126.051833, 40.96185], + [126.08263, 40.976762], + [126.066, 40.997542], + [126.1085, 41.011995], + [126.099877, 41.036376], + [126.133753, 41.063906], + [126.124514, 41.092327], + [126.16763, 41.094583], + [126.187956, 41.113072], + [126.188572, 41.114875], + [126.295129, 41.171661], + [126.332086, 41.236949], + [126.35426, 41.244599], + [126.373354, 41.289133], + [126.437411, 41.353405], + [126.497158, 41.374965], + [126.524259, 41.349362], + [126.539041, 41.366881], + [126.497158, 41.406842], + [126.559983, 41.548081], + [126.582773, 41.563307], + [126.564295, 41.608965], + [126.592628, 41.624624], + [126.608027, 41.669345], + [126.644983, 41.661297], + [126.688099, 41.674262], + [126.724439, 41.710907], + [126.690562, 41.728328], + [126.694874, 41.751103], + [126.723207, 41.753335], + [126.8002, 41.702865], + [126.809439, 41.749317], + [126.848243, 41.734134], + [126.85625, 41.760031], + [126.887047, 41.791719], + [126.931395, 41.812687], + [126.952953, 41.804212], + [126.940018, 41.773423], + [126.979438, 41.776993], + [127.005923, 41.749317], + [127.050887, 41.744852], + [127.057662, 41.703758], + [127.037952, 41.676944], + [127.103242, 41.647883], + [127.093387, 41.629993], + [127.127263, 41.622388], + [127.135887, 41.600463], + [127.178386, 41.600015], + [127.125416, 41.566442], + [127.11864, 41.540018], + [127.164836, 41.542706], + [127.188241, 41.527475], + [127.241212, 41.520754], + [127.28864, 41.501932], + [127.253531, 41.486691], + [127.296031, 41.486243], + [127.360704, 41.466065], + [127.360088, 41.479518], + [127.405668, 41.478621], + [127.419835, 41.460235], + [127.459255, 41.461581], + [127.465414, 41.479069], + [127.526392, 41.467859], + [127.547334, 41.477276], + [127.563964, 41.432871], + [127.618783, 41.432871], + [127.636645, 41.413575], + [127.684073, 41.422999], + [127.780159, 41.427038], + [127.854688, 41.420755], + [127.86947, 41.4037], + [127.882405, 41.448124], + [127.909506, 41.42973], + [127.93168, 41.444984], + [127.970484, 41.438704], + [127.991426, 41.421204], + [128.000049, 41.442741], + [128.040085, 41.393375], + [128.110919, 41.393375], + [128.090593, 41.374516], + [128.114614, 41.364186], + [128.169433, 41.404149], + [128.203925, 41.410882], + [128.243345, 41.477276], + [128.238418, 41.497898], + [128.301244, 41.540018], + [128.317874, 41.575844], + [128.30186, 41.627756], + [128.248889, 41.681414], + [128.208853, 41.688565], + [128.163889, 41.721628], + [128.147875, 41.78101], + [128.112766, 41.793504], + [128.104143, 41.843457], + [128.115846, 41.896935], + [128.106607, 41.949923], + [128.033926, 42.000199], + [128.090593, 42.022877], + [128.294468, 42.026434], + [128.405338, 42.018876], + [128.466316, 42.020654], + [128.49896, 42.000644], + [128.598127, 42.007315], + [128.60675, 42.02999], + [128.637547, 42.035324], + [128.658489, 42.018876], + [128.70222, 42.02021], + [128.737945, 42.050435], + [128.779213, 42.033546], + [128.795227, 42.042436], + [128.898089, 42.016653], + [128.952908, 42.025545], + [128.954755, 42.083756], + [128.971386, 42.097079], + [129.008958, 42.09175], + [129.039139, 42.107736], + [129.048378, 42.137476], + [129.113668, 42.140583], + [129.166639, 42.188047], + [129.215914, 42.208442], + [129.209138, 42.237692], + [129.181421, 42.242122], + [129.183269, 42.262056], + [129.215914, 42.265157], + [129.231312, 42.283755], + [129.208522, 42.293052], + [129.260261, 42.335536], + [129.231312, 42.356325], + [129.240551, 42.376223], + [129.326167, 42.389927], + [129.30892, 42.403628], + [129.331094, 42.429695], + [129.356348, 42.427045], + [129.342181, 42.441179], + [129.368051, 42.459284], + [129.366203, 42.428811], + [129.392688, 42.42837], + [129.400695, 42.449128], + [129.452434, 42.441179], + [129.49863, 42.412023], + [129.546057, 42.361632], + [129.578086, 42.380202], + [129.569463, 42.399208], + [129.601492, 42.415116] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 230000, + "name": "Heilongjiang", + "center": [126.642464, 45.756967], + "centroid": [127.693027, 48.040465], + "childrenNum": 13, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 7, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [123.569598, 46.223816], + [123.604706, 46.251964], + [123.673692, 46.258585], + [123.726047, 46.255688], + [123.775938, 46.263136], + [123.84985, 46.302428], + [123.896046, 46.303668], + [123.917604, 46.25693], + [123.936082, 46.286715], + [123.960103, 46.288369], + [123.952096, 46.256516], + [123.979814, 46.228784], + [123.956408, 46.206009], + [123.971806, 46.170379], + [124.001987, 46.166649], + [123.991516, 46.143019], + [124.01677, 46.118549], + [123.99398, 46.101123], + [124.015538, 46.088257], + [124.009995, 46.057534], + [124.034016, 46.045074], + [124.040176, 46.01973], + [123.989053, 46.011833], + [124.011842, 45.981899], + [123.973654, 45.973997], + [123.968727, 45.936551], + [123.996444, 45.906993], + [124.061118, 45.886168], + [124.067277, 45.840325], + [124.03648, 45.83824], + [124.064197, 45.802372], + [124.001987, 45.770655], + [124.014922, 45.749779], + [124.054342, 45.751449], + [124.098074, 45.722628], + [124.10177, 45.700898], + [124.13503, 45.690448], + [124.122096, 45.669123], + [124.147349, 45.665359], + [124.128255, 45.641933], + [124.162132, 45.616404], + [124.226805, 45.633564], + [124.238508, 45.591702], + [124.273001, 45.584163], + [124.264377, 45.555256], + [124.287783, 45.539329], + [124.348761, 45.546874], + [124.369087, 45.512915], + [124.352457, 45.496557], + [124.374015, 45.45795], + [124.398652, 45.440737], + [124.480572, 45.456271], + [124.507058, 45.424778], + [124.544014, 45.411756], + [124.579738, 45.424358], + [124.575427, 45.451234], + [124.625318, 45.437377], + [124.690607, 45.452493], + [124.729412, 45.444096], + [124.776223, 45.468024], + [124.792853, 45.436958], + [124.839665, 45.455852], + [124.886476, 45.442836], + [124.884628, 45.495299], + [124.911114, 45.535976], + [124.936983, 45.53388], + [124.961005, 45.495299], + [125.025678, 45.493201], + [125.0497, 45.428558], + [125.08912, 45.420998], + [125.06633, 45.39915], + [125.097127, 45.38276], + [125.137779, 45.409655], + [125.189518, 45.39915], + [125.248649, 45.417637], + [125.301619, 45.402092], + [125.319482, 45.422678], + [125.361981, 45.392847], + [125.398322, 45.416797], + [125.434662, 45.462988], + [125.424807, 45.485649], + [125.480242, 45.486488], + [125.497488, 45.469283], + [125.583104, 45.491942], + [125.61698, 45.517947], + [125.660096, 45.507043], + [125.687813, 45.514173], + [125.711835, 45.477677], + [125.712451, 45.389485], + [125.695205, 45.352066], + [125.726001, 45.336503], + [125.761726, 45.291472], + [125.815929, 45.264942], + [125.823936, 45.237978], + [125.849805, 45.23882], + [125.915095, 45.196664], + [125.957595, 45.201303], + [125.992703, 45.192447], + [125.998247, 45.162072], + [126.047522, 45.170933], + [126.091869, 45.149411], + [126.142992, 45.147723], + [126.166398, 45.13337], + [126.225528, 45.154054], + [126.235383, 45.140125], + [126.285274, 45.162494], + [126.293282, 45.180214], + [126.356107, 45.185698], + [126.402919, 45.222805], + [126.519331, 45.248091], + [126.540273, 45.23882], + [126.569222, 45.252725], + [126.644983, 45.225334], + [126.640055, 45.214373], + [126.685635, 45.187807], + [126.732446, 45.187385], + [126.787265, 45.159118], + [126.792808, 45.135481], + [126.85625, 45.145613], + [126.96404, 45.132104], + [126.970815, 45.070852], + [126.984981, 45.067893], + [127.018242, 45.024341], + [127.050271, 45.004034], + [127.092771, 44.94688], + [127.073061, 44.907051], + [127.021938, 44.898997], + [126.999764, 44.87398], + [126.984366, 44.823914], + [126.9973, 44.764882], + [127.041032, 44.712169], + [127.030561, 44.673454], + [127.044112, 44.653874], + [127.041648, 44.591258], + [127.049655, 44.566961], + [127.089691, 44.593816], + [127.094619, 44.615972], + [127.138966, 44.607451], + [127.182082, 44.644507], + [127.228893, 44.642804], + [127.214111, 44.624917], + [127.261538, 44.61299], + [127.275705, 44.640249], + [127.392733, 44.632158], + [127.557189, 44.575488], + [127.570124, 44.55033], + [127.536247, 44.522176], + [127.485124, 44.528576], + [127.465414, 44.516628], + [127.463566, 44.484615], + [127.50853, 44.437202], + [127.486356, 44.410275], + [127.579363, 44.310581], + [127.623711, 44.278025], + [127.59045, 44.227872], + [127.626174, 44.187977], + [127.641573, 44.193555], + [127.681609, 44.166946], + [127.712406, 44.199133], + [127.735811, 44.11412], + [127.729036, 44.09908], + [127.783239, 44.071997], + [127.808492, 44.086615], + [127.846065, 44.081886], + [127.862695, 44.062967], + [127.912586, 44.064687], + [127.950158, 44.088334], + [128.042549, 44.103807], + [128.091208, 44.133022], + [128.088129, 44.158359], + [128.060411, 44.168663], + [128.09244, 44.181539], + [128.104143, 44.230017], + [128.064107, 44.251454], + [128.101679, 44.293449], + [128.065339, 44.307155], + [128.049941, 44.349965], + [128.074578, 44.370075], + [128.094904, 44.354673], + [128.137404, 44.357668], + [128.172512, 44.34697], + [128.211317, 44.431647], + [128.228563, 44.445748], + [128.293237, 44.467961], + [128.295084, 44.480772], + [128.372693, 44.514495], + [128.397946, 44.483761], + [128.427511, 44.473512], + [128.463236, 44.431647], + [128.457076, 44.409848], + [128.481714, 44.375637], + [128.475555, 44.346114], + [128.446605, 44.339694], + [128.472475, 44.320001], + [128.453997, 44.257884], + [128.471859, 44.247596], + [128.450301, 44.203423], + [128.471859, 44.157501], + [128.529141, 44.112401], + [128.574721, 44.047914], + [128.584576, 43.990246], + [128.610445, 43.960529], + [128.64001, 43.948035], + [128.636315, 43.891132], + [128.696061, 43.903207], + [128.729938, 43.889838], + [128.760734, 43.857482], + [128.719467, 43.816905], + [128.739177, 43.806972], + [128.760119, 43.755554], + [128.729322, 43.736964], + [128.768126, 43.732207], + [128.78722, 43.686784], + [128.821097, 43.637429], + [128.834647, 43.587599], + [128.878379, 43.539898], + [128.949828, 43.553779], + [128.962763, 43.53903], + [129.013886, 43.522976], + [129.037907, 43.540332], + [129.093958, 43.547706], + [129.145081, 43.570258], + [129.169102, 43.561585], + [129.23008, 43.593234], + [129.232544, 43.635263], + [129.217146, 43.648689], + [129.214066, 43.695006], + [129.232544, 43.709284], + [129.211602, 43.784509], + [129.254718, 43.819496], + [129.289826, 43.797038], + [129.30892, 43.812155], + [129.348341, 43.798333], + [129.406855, 43.819496], + [129.417942, 43.843672], + [129.449971, 43.850578], + [129.467833, 43.874741], + [129.529427, 43.870427], + [129.650767, 43.873016], + [129.699426, 43.8838], + [129.743158, 43.876035], + [129.739462, 43.895876], + [129.780114, 43.892857], + [129.802904, 43.964837], + [129.868193, 44.012631], + [129.881128, 44.000148], + [129.907614, 44.023821], + [129.951345, 44.027263], + [129.979062, 44.015644], + [130.017867, 43.961821], + [130.022794, 43.917866], + [130.009243, 43.889407], + [130.027722, 43.851872], + [130.079461, 43.835039], + [130.110873, 43.852735], + [130.116417, 43.878192], + [130.143518, 43.878624], + [130.153373, 43.915711], + [130.208192, 43.948466], + [130.262395, 43.949328], + [130.27225, 43.981634], + [130.307358, 44.002731], + [130.319061, 44.03974], + [130.365256, 44.044042], + [130.364025, 43.992399], + [130.338155, 43.963975], + [130.381887, 43.910106], + [130.368336, 43.894151], + [130.386198, 43.85403], + [130.362793, 43.844967], + [130.381887, 43.817768], + [130.382503, 43.777164], + [130.423155, 43.745179], + [130.394206, 43.703227], + [130.412684, 43.652586], + [130.437937, 43.646091], + [130.488444, 43.65605], + [130.501995, 43.636563], + [130.57098, 43.626167], + [130.57098, 43.626167], + [130.630726, 43.622268], + [130.623335, 43.589767], + [130.665835, 43.583698], + [130.671378, 43.565054], + [130.727429, 43.560284], + [130.776704, 43.52341], + [130.822899, 43.503446], + [130.841378, 43.454374], + [130.864167, 43.437863], + [130.907283, 43.434387], + [130.959638, 43.48608], + [131.026775, 43.508655], + [131.142572, 43.425695], + [131.175217, 43.444816], + [131.201086, 43.442209], + [131.234963, 43.475224], + [131.294093, 43.470012], + [131.304564, 43.502144], + [131.276847, 43.495632], + [131.20047, 43.532089], + [131.222028, 43.593234], + [131.216485, 43.613169], + [131.239274, 43.670337], + [131.221412, 43.682024], + [131.215869, 43.72745], + [131.232499, 43.742585], + [131.213405, 43.801357], + [131.2171, 43.836334], + [131.254057, 43.893289], + [131.26268, 43.948897], + [131.245434, 43.95579], + [131.26576, 44.034578], + [131.28239, 44.035868], + [131.287318, 44.03802], + [131.293477, 44.043182], + [131.310723, 44.046623], + [131.111775, 44.710042], + [131.090833, 44.717272], + [131.093297, 44.746183], + [131.069275, 44.759783], + [131.064348, 44.786973], + [131.016304, 44.789521], + [131.015688, 44.814999], + [130.972573, 44.820094], + [130.965181, 44.85065], + [131.07913, 44.881614], + [131.10192, 44.898997], + [131.090217, 44.924427], + [131.16105, 44.948151], + [131.20355, 44.932901], + [131.207861, 44.913833], + [131.263296, 44.929935], + [131.274999, 44.919766], + [131.313803, 44.950692], + [131.313803, 44.965938], + [131.355071, 44.990068], + [131.380324, 44.978216], + [131.409889, 44.985836], + [131.464708, 44.963397], + [131.501664, 44.977793], + [131.484418, 44.99557], + [131.529382, 45.012073], + [131.566338, 45.045487], + [131.63286, 45.075078], + [131.695685, 45.132104], + [131.687678, 45.1511], + [131.650722, 45.159962], + [131.681519, 45.215217], + [131.721555, 45.234606], + [131.759127, 45.213952], + [131.79362, 45.211844], + [131.788692, 45.245984], + [131.825649, 45.291472], + [131.82996, 45.311677], + [131.887858, 45.342393], + [131.917423, 45.339448], + [131.93159, 45.287683], + [131.976554, 45.277156], + [132.003655, 45.25441], + [132.17427, 45.216903], + [132.394161, 45.16376], + [132.76434, 45.081417], + [132.867202, 45.061976], + [132.916477, 45.031109], + [132.954049, 45.023072], + [132.98731, 45.043373], + [133.035969, 45.054366], + [133.070462, 45.097051], + [133.089556, 45.097473], + [133.107418, 45.124504], + [133.139447, 45.127459], + [133.129592, 45.211422], + [133.095715, 45.246827], + [133.110498, 45.266627], + [133.097563, 45.284735], + [133.128976, 45.336924], + [133.119121, 45.352908], + [133.144991, 45.367205], + [133.143759, 45.430658], + [133.164701, 45.437377], + [133.170244, 45.465506], + [133.203505, 45.516689], + [133.246005, 45.517528], + [133.333468, 45.562379], + [133.342707, 45.554836], + [133.393214, 45.580393], + [133.423395, 45.584163], + [133.412924, 45.618079], + [133.471438, 45.631053], + [133.448649, 45.647372], + [133.485605, 45.658667], + [133.484989, 45.691702], + [133.445569, 45.705077], + [133.454192, 45.731819], + [133.486837, 45.740173], + [133.469591, 45.777751], + [133.505315, 45.785681], + [133.469591, 45.799451], + [133.467743, 45.834905], + [133.494228, 45.840325], + [133.491764, 45.867002], + [133.51209, 45.887001], + [133.55459, 45.893249], + [133.583539, 45.868669], + [133.618032, 45.903662], + [133.614952, 45.942794], + [133.676546, 45.94321], + [133.681474, 45.986473], + [133.740604, 46.048812], + [133.745531, 46.075389], + [133.690713, 46.133896], + [133.706111, 46.163333], + [133.764626, 46.17328], + [133.794807, 46.193583], + [133.814517, 46.230854], + [133.849625, 46.203939], + [133.87919, 46.233752], + [133.867487, 46.250722], + [133.909987, 46.254447], + [133.91861, 46.280924], + [133.908139, 46.308216], + [133.922922, 46.330948], + [133.869335, 46.338386], + [133.876726, 46.362345], + [133.940784, 46.38134], + [133.948791, 46.401153], + [133.902596, 46.446119], + [133.852089, 46.450242], + [133.849625, 46.475389], + [133.890893, 46.525235], + [133.919842, 46.596012], + [134.011001, 46.637941], + [134.030711, 46.708981], + [134.033175, 46.759023], + [134.052885, 46.779928], + [134.025168, 46.810657], + [134.041182, 46.848326], + [134.042414, 46.886787], + [134.076291, 46.938298], + [134.063972, 46.979962], + [134.10216, 47.005678], + [134.118175, 47.061968], + [134.142812, 47.093349], + [134.222268, 47.105164], + [134.232739, 47.134892], + [134.230276, 47.182097], + [134.210566, 47.210155], + [134.156979, 47.248357], + [134.177305, 47.326299], + [134.203174, 47.347389], + [134.263536, 47.371307], + [134.266616, 47.391974], + [134.307268, 47.428829], + [134.339297, 47.439759], + [134.490202, 47.446235], + [134.522847, 47.468086], + [134.568426, 47.478199], + [134.576434, 47.519036], + [134.627556, 47.546512], + [134.678064, 47.588507], + [134.689766, 47.63813], + [134.779694, 47.7159], + [134.772918, 47.763391], + [134.678679, 47.819278], + [134.670056, 47.864667], + [134.677448, 47.884738], + [134.658969, 47.901191], + [134.607846, 47.909214], + [134.599839, 47.947711], + [134.55426, 47.982173], + [134.551796, 48.032622], + [134.632484, 48.099412], + [134.67252, 48.170505], + [134.679295, 48.256245], + [134.77107, 48.288908], + [134.864077, 48.332293], + [135.009439, 48.365703], + [135.090743, 48.403461], + [135.09567, 48.437618], + [135.068569, 48.459451], + [135.035924, 48.440795], + [134.996504, 48.439603], + [134.927519, 48.451513], + [134.886867, 48.437618], + [134.848679, 48.393925], + [134.820961, 48.37604], + [134.764295, 48.370076], + [134.704549, 48.405448], + [134.640491, 48.409818], + [134.578281, 48.405448], + [134.501905, 48.418954], + [134.438463, 48.405448], + [134.369478, 48.382797], + [134.20379, 48.3824], + [134.150819, 48.346217], + [134.116327, 48.333089], + [134.0689, 48.338659], + [134.029479, 48.327519], + [133.995603, 48.303639], + [133.940784, 48.302047], + [133.876111, 48.282536], + [133.824372, 48.277359], + [133.791111, 48.261026], + [133.740604, 48.254651], + [133.693177, 48.186866], + [133.667307, 48.183275], + [133.59709, 48.194846], + [133.573068, 48.182078], + [133.545967, 48.121389], + [133.451728, 48.112999], + [133.407997, 48.124585], + [133.302055, 48.103009], + [133.239845, 48.126583], + [133.182563, 48.135769], + [133.130208, 48.134971], + [133.053216, 48.110202], + [133.02673, 48.085421], + [133.016259, 48.054228], + [132.992238, 48.035424], + [132.883216, 48.002599], + [132.819159, 47.936887], + [132.769268, 47.93849], + [132.723072, 47.962941], + [132.691043, 47.962941], + [132.661478, 47.944905], + [132.662094, 47.922451], + [132.687348, 47.88514], + [132.662094, 47.854227], + [132.621442, 47.82852], + [132.599268, 47.792347], + [132.6005, 47.740858], + [132.558, 47.718316], + [132.469305, 47.726368], + [132.371987, 47.765402], + [132.325175, 47.762184], + [132.288835, 47.742065], + [132.272205, 47.718718], + [132.242639, 47.70986], + [132.19706, 47.714289], + [132.157024, 47.70543], + [132.086191, 47.703013], + [132.000575, 47.712276], + [131.976554, 47.673201], + [131.900793, 47.685692], + [131.825649, 47.677231], + [131.741881, 47.706638], + [131.690142, 47.707041], + [131.641483, 47.663932], + [131.59036, 47.660707], + [131.568186, 47.682469], + [131.559563, 47.724757], + [131.543548, 47.736028], + [131.456085, 47.747297], + [131.359998, 47.730796], + [131.273767, 47.738846], + [131.236811, 47.733211], + [131.183224, 47.702611], + [131.115471, 47.689721], + [131.029855, 47.694555], + [130.983659, 47.713081], + [130.966413, 47.733211], + [130.961486, 47.828118], + [130.891269, 47.927263], + [130.870943, 47.943301], + [130.770544, 47.998194], + [130.737284, 48.034223], + [130.699711, 48.044227], + [130.666451, 48.105007], + [130.673842, 48.12818], + [130.765617, 48.18926], + [130.769313, 48.231136], + [130.787791, 48.256643], + [130.817972, 48.265409], + [130.845073, 48.296473], + [130.81982, 48.341444], + [130.785327, 48.357353], + [130.747755, 48.404256], + [130.745907, 48.449131], + [130.776704, 48.480084], + [130.767465, 48.507846], + [130.711414, 48.511414], + [130.647357, 48.484844], + [130.620871, 48.49595], + [130.615944, 48.575601], + [130.605473, 48.594207], + [130.538335, 48.612016], + [130.538951, 48.635751], + [130.576524, 48.688719], + [130.622103, 48.783842], + [130.689856, 48.849651], + [130.680617, 48.881146], + [130.609168, 48.881146], + [130.559277, 48.861071], + [130.501995, 48.865795], + [130.471198, 48.905541], + [130.412068, 48.905148], + [130.279641, 48.866976], + [130.237757, 48.868551], + [130.219895, 48.893739], + [130.113337, 48.956653], + [130.059135, 48.979047], + [130.020946, 49.021058], + [129.937179, 49.040285], + [129.9187, 49.060681], + [129.934715, 49.078717], + [129.913157, 49.1085], + [129.866962, 49.113985], + [129.855259, 49.133567], + [129.864498, 49.158621], + [129.847867, 49.181316], + [129.784426, 49.184054], + [129.753629, 49.208692], + [129.761636, 49.25754], + [129.730223, 49.288387], + [129.696962, 49.298535], + [129.604571, 49.279018], + [129.562687, 49.299706], + [129.546057, 49.395227], + [129.51834, 49.423652], + [129.448739, 49.441167], + [129.390224, 49.432605], + [129.374826, 49.414309], + [129.379138, 49.367175], + [129.358196, 49.355871], + [129.320623, 49.3586], + [129.266421, 49.396006], + [129.215298, 49.399122], + [129.180805, 49.386657], + [129.143849, 49.357431], + [129.084719, 49.359769], + [129.061929, 49.374189], + [129.013886, 49.457119], + [128.932582, 49.46801], + [128.871604, 49.492506], + [128.792147, 49.473065], + [128.76135, 49.482009], + [128.763198, 49.515824], + [128.813089, 49.558157], + [128.802618, 49.58222], + [128.744104, 49.595023], + [128.715155, 49.564756], + [128.656025, 49.577564], + [128.619684, 49.593471], + [128.537764, 49.604332], + [128.500192, 49.593859], + [128.389939, 49.58998], + [128.343128, 49.544956], + [128.287077, 49.566309], + [128.243345, 49.563203], + [128.185447, 49.53952], + [128.122005, 49.55311], + [128.070882, 49.556604], + [128.001281, 49.592307], + [127.949542, 49.596187], + [127.897804, 49.579116], + [127.815268, 49.593859], + [127.782007, 49.630698], + [127.705015, 49.665185], + [127.677913, 49.697712], + [127.674833, 49.764247], + [127.653892, 49.780094], + [127.583059, 49.786277], + [127.531936, 49.826059], + [127.529472, 49.864265], + [127.547334, 49.928645], + [127.543638, 49.944438], + [127.495595, 49.994479], + [127.501755, 50.056764], + [127.58737, 50.137768], + [127.60708, 50.178794], + [127.603385, 50.239309], + [127.44632, 50.270686], + [127.371791, 50.29669], + [127.332371, 50.340634], + [127.369944, 50.403996], + [127.3644, 50.438314], + [127.30527, 50.45432], + [127.293567, 50.46575], + [127.323132, 50.52552], + [127.36132, 50.547582], + [127.370559, 50.581415], + [127.294799, 50.663426], + [127.28864, 50.699451], + [127.305886, 50.733932], + [127.295415, 50.755139], + [127.236285, 50.781256], + [127.143894, 50.910111], + [127.113713, 50.93765], + [127.052119, 50.962911], + [126.985597, 51.029202], + [126.922772, 51.061937], + [126.917844, 51.138977], + [126.899982, 51.200518], + [126.926467, 51.246244], + [126.976358, 51.291551], + [126.98375, 51.318863], + [126.970815, 51.332327], + [126.887047, 51.321856], + [126.877808, 51.300906], + [126.908605, 51.283691], + [126.92154, 51.259729], + [126.908605, 51.246619], + [126.863025, 51.248492], + [126.820526, 51.281071], + [126.813134, 51.311756], + [126.837156, 51.345038], + [126.904293, 51.340552], + [126.930163, 51.359241], + [126.908605, 51.407423], + [126.835308, 51.413769], + [126.791577, 51.432428], + [126.784185, 51.448095], + [126.812518, 51.493948], + [126.843931, 51.521885], + [126.837156, 51.536033], + [126.69549, 51.57845], + [126.67886, 51.602246], + [126.741069, 51.642374], + [126.723823, 51.679126], + [126.734294, 51.711399], + [126.724439, 51.7266], + [126.6727, 51.73179], + [126.658534, 51.762544], + [126.622809, 51.777357], + [126.580925, 51.824728], + [126.555056, 51.874266], + [126.510092, 51.922274], + [126.462665, 51.948471], + [126.468208, 51.982395], + [126.447882, 52.009294], + [126.450962, 52.027709], + [126.487918, 52.041699], + [126.514404, 52.037282], + [126.563679, 52.119302], + [126.556288, 52.136203], + [126.499005, 52.16044], + [126.457121, 52.165212], + [126.403535, 52.185031], + [126.34502, 52.192002], + [126.306832, 52.205574], + [126.312992, 52.235271], + [126.357955, 52.264216], + [126.401071, 52.279597], + [126.436795, 52.277034], + [126.4331, 52.298632], + [126.327774, 52.310342], + [126.320999, 52.342163], + [126.348716, 52.357882], + [126.353644, 52.389304], + [126.326542, 52.424353], + [126.268644, 52.475051], + [126.205202, 52.466302], + [126.192883, 52.492181], + [126.213209, 52.525327], + [126.147304, 52.573], + [126.066616, 52.603905], + [126.055529, 52.582455], + [126.030891, 52.576273], + [125.989008, 52.603178], + [125.968682, 52.630429], + [125.971145, 52.654033], + [125.995783, 52.675085], + [126.061688, 52.673271], + [126.072775, 52.691048], + [126.044442, 52.739628], + [126.112195, 52.757016], + [126.116507, 52.768243], + [126.052449, 52.800095], + [126.02042, 52.795753], + [125.985312, 52.758465], + [125.966834, 52.759914], + [125.937269, 52.786705], + [125.923718, 52.815651], + [125.855349, 52.866259], + [125.854117, 52.891542], + [125.827631, 52.899123], + [125.772197, 52.89804], + [125.751255, 52.88143], + [125.722306, 52.880347], + [125.678574, 52.86084], + [125.666871, 52.869872], + [125.665023, 52.913561], + [125.737088, 52.943504], + [125.742632, 52.993964], + [125.684118, 53.00801], + [125.643466, 53.039686], + [125.640386, 53.06199], + [125.613901, 53.083564], + [125.588647, 53.081047], + [125.530749, 53.0512], + [125.504263, 53.061271], + [125.503647, 53.095424], + [125.452524, 53.107641], + [125.343503, 53.14463], + [125.315786, 53.144989], + [125.252344, 53.18051], + [125.195062, 53.198439], + [125.142091, 53.204175], + [125.038613, 53.202741], + [124.970244, 53.194137], + [124.887708, 53.164368], + [124.909266, 53.118059], + [124.87231, 53.099018], + [124.832889, 53.145347], + [124.787926, 53.140681], + [124.734339, 53.146783], + [124.712165, 53.162574], + [124.720789, 53.192344], + [124.678905, 53.207043], + [124.590209, 53.208476], + [124.563108, 53.201666], + [124.496587, 53.207759], + [124.487348, 53.217436], + [124.435609, 53.223886], + [124.412203, 53.248601], + [124.375863, 53.258984], + [124.327819, 53.331954], + [124.239124, 53.379817], + [124.19416, 53.37339], + [124.125791, 53.348033], + [124.058038, 53.404085], + [124.01369, 53.403371], + [123.985973, 53.434401], + [123.865249, 53.489627], + [123.797495, 53.489983], + [123.746373, 53.500308], + [123.698329, 53.498528], + [123.668764, 53.533756], + [123.620721, 53.550115], + [123.58746, 53.546915], + [123.569598, 53.505291], + [123.53141, 53.507071], + [123.557895, 53.531978], + [123.546808, 53.551537], + [123.517243, 53.558292], + [123.490758, 53.542648], + [123.510468, 53.509206], + [123.499381, 53.497816], + [123.47228, 53.509206], + [123.454417, 53.536602], + [123.394055, 53.538024], + [123.309672, 53.56078], + [123.274563, 53.563269], + [123.231447, 53.549404], + [123.179092, 53.509918], + [123.137209, 53.498172], + [123.093477, 53.508138], + [123.052209, 53.506715], + [122.943804, 53.483929], + [122.894528, 53.462914], + [122.826775, 53.457213], + [122.763949, 53.463626], + [122.673406, 53.459351], + [122.608117, 53.465408], + [122.5379, 53.453293], + [122.496016, 53.458638], + [122.435038, 53.444739], + [122.37406, 53.47467], + [122.350038, 53.505647], + [122.266886, 53.470039], + [122.227466, 53.461845], + [122.161561, 53.468614], + [122.111054, 53.426913], + [122.077177, 53.422277], + [122.026054, 53.428339], + [121.875765, 53.426556], + [121.816019, 53.41336], + [121.754425, 53.389454], + [121.697758, 53.392666], + [121.589969, 53.350891], + [121.499426, 53.337314], + [121.504969, 53.323018], + [121.575802, 53.29155], + [121.615222, 53.258984], + [121.642324, 53.262564], + [121.679896, 53.240722], + [121.67928, 53.199515], + [121.660186, 53.195213], + [121.665114, 53.170467], + [121.722396, 53.145706], + [121.753193, 53.147501], + [121.784606, 53.104408], + [121.775367, 53.089674], + [121.817867, 53.061631], + [121.785838, 53.018451], + [121.715621, 52.997926], + [121.677432, 52.948192], + [121.66265, 52.912478], + [121.610295, 52.892264], + [121.604136, 52.872401], + [121.620766, 52.853251], + [121.591201, 52.824693], + [121.537614, 52.801542], + [121.511129, 52.779104], + [121.476636, 52.772225], + [121.455078, 52.73528], + [121.373158, 52.683067], + [121.309717, 52.676173], + [121.29247, 52.651855], + [121.237036, 52.619167], + [121.182217, 52.59918], + [121.225333, 52.577364], + [121.280151, 52.586819], + [121.323883, 52.573727], + [121.353448, 52.534793], + [121.411963, 52.52205], + [121.416274, 52.499468], + [121.474172, 52.482706], + [121.495114, 52.484892], + [121.519136, 52.456821], + [121.565331, 52.460468], + [121.590585, 52.443326], + [121.63986, 52.44442], + [121.678664, 52.419973], + [121.658338, 52.3904], + [121.715621, 52.342894], + [121.714389, 52.318025], + [121.769207, 52.308147], + [121.841272, 52.282526], + [121.901018, 52.280695], + [121.94783, 52.298266], + [121.976779, 52.343626], + [122.035909, 52.377615], + [122.040837, 52.413038], + [122.091344, 52.427272], + [122.080873, 52.440407], + [122.107358, 52.452445], + [122.142467, 52.495096], + [122.140003, 52.510032], + [122.168952, 52.513674], + [122.178191, 52.48963], + [122.207756, 52.469218], + [122.310618, 52.475416], + [122.326016, 52.459374], + [122.342031, 52.414133], + [122.367284, 52.413768], + [122.378987, 52.395512], + [122.419023, 52.375057], + [122.447356, 52.394052], + [122.484313, 52.341432], + [122.478153, 52.29607], + [122.560689, 52.282526], + [122.585943, 52.266413], + [122.67895, 52.276667], + [122.710979, 52.256157], + [122.76087, 52.26678], + [122.787355, 52.252494], + [122.766413, 52.232705], + [122.769493, 52.179893], + [122.73808, 52.153464], + [122.690653, 52.140243], + [122.629059, 52.13657], + [122.643841, 52.111585], + [122.625363, 52.067459], + [122.650616, 52.058997], + [122.664783, 51.99861], + [122.683877, 51.974654], + [122.726377, 51.978709], + [122.729457, 51.919321], + [122.706051, 51.890151], + [122.725761, 51.87833], + [122.732536, 51.832495], + [122.771957, 51.779579], + [122.749167, 51.746613], + [122.778732, 51.698048], + [122.816304, 51.655371], + [122.820616, 51.633088], + [122.85634, 51.606707], + [122.832935, 51.581797], + [122.874202, 51.561339], + [122.880362, 51.537894], + [122.858804, 51.524864], + [122.880362, 51.511085], + [122.854492, 51.477551], + [122.871123, 51.455181], + [122.900072, 51.445112], + [122.903768, 51.415262], + [122.946267, 51.405183], + [122.965977, 51.386886], + [122.965977, 51.345786], + [123.002934, 51.31213], + [123.069455, 51.321108], + [123.127969, 51.297913], + [123.231447, 51.279199], + [123.231447, 51.268716], + [123.294273, 51.254111], + [123.339853, 51.27246], + [123.376809, 51.266844], + [123.414381, 51.278825], + [123.440251, 51.270963], + [123.46304, 51.286686], + [123.582533, 51.294545], + [123.582533, 51.306893], + [123.661989, 51.319237], + [123.660141, 51.342795], + [123.711264, 51.398089], + [123.794416, 51.361109], + [123.842459, 51.367462], + [123.887423, 51.320734], + [123.926227, 51.300532], + [123.939777, 51.313253], + [123.994596, 51.322604], + [124.071588, 51.320734], + [124.090067, 51.3413], + [124.128255, 51.347281], + [124.192313, 51.33943], + [124.239124, 51.344664], + [124.271769, 51.308389], + [124.297638, 51.298661], + [124.339522, 51.293422], + [124.406659, 51.272086], + [124.430065, 51.301281], + [124.426985, 51.331953], + [124.443616, 51.35812], + [124.478108, 51.36223], + [124.490427, 51.380537], + [124.555717, 51.375307], + [124.58713, 51.363725], + [124.62655, 51.327465], + [124.693687, 51.3327], + [124.752817, 51.35812], + [124.76452, 51.38726], + [124.783614, 51.392115], + [124.864302, 51.37979], + [124.885244, 51.40817], + [124.942527, 51.447349], + [124.917889, 51.474196], + [124.928976, 51.498419], + [124.983795, 51.508478], + [125.004737, 51.529332], + [125.047236, 51.529704], + [125.073106, 51.553526], + [125.060171, 51.59667], + [125.098975, 51.658341], + [125.12854, 51.659083], + [125.130388, 51.635317], + [125.175968, 51.639403], + [125.214772, 51.627888], + [125.228938, 51.640517], + [125.289301, 51.633831], + [125.316402, 51.610052], + [125.35151, 51.623801], + [125.38046, 51.585516], + [125.424807, 51.562827], + [125.528285, 51.488359], + [125.559082, 51.461521], + [125.559082, 51.461521], + [125.595422, 51.416755], + [125.595422, 51.416755], + [125.60035, 51.413396], + [125.60035, 51.413396], + [125.600966, 51.410409], + [125.600966, 51.410409], + [125.62314, 51.398089], + [125.62314, 51.398089], + [125.623756, 51.387633], + [125.623756, 51.387633], + [125.626219, 51.380163], + [125.626219, 51.380163], + [125.700132, 51.327465], + [125.700132, 51.327465], + [125.740784, 51.27583], + [125.740784, 51.27583], + [125.76111, 51.261976], + [125.76111, 51.261976], + [125.761726, 51.226385], + [125.819008, 51.227134], + [125.850421, 51.21364], + [125.864588, 51.146487], + [125.909551, 51.138977], + [125.946508, 51.108176], + [125.970529, 51.123955], + [125.993935, 51.119072], + [125.976073, 51.084498], + [126.059225, 51.043503], + [126.033971, 51.011132], + [126.041978, 50.981753], + [126.068464, 50.967434], + [126.042594, 50.92558], + [126.02042, 50.927466], + [125.996399, 50.906715], + [125.997631, 50.872738], + [125.961906, 50.901054], + [125.939732, 50.85423], + [125.913247, 50.825885], + [125.878138, 50.816812], + [125.890457, 50.805845], + [125.836255, 50.793363], + [125.846726, 50.769524], + [125.828863, 50.756654], + [125.804226, 50.773309], + [125.758646, 50.746809], + [125.795603, 50.738856], + [125.78082, 50.725598], + [125.825784, 50.70362], + [125.789443, 50.679735], + [125.804226, 50.658874], + [125.793139, 50.643316], + [125.814697, 50.62092], + [125.807921, 50.60383], + [125.829479, 50.56165], + [125.794987, 50.532748], + [125.770349, 50.531227], + [125.754335, 50.506874], + [125.740784, 50.523237], + [125.699516, 50.487078], + [125.654553, 50.471082], + [125.627451, 50.443268], + [125.580024, 50.449366], + [125.562162, 50.438314], + [125.583104, 50.409717], + [125.567089, 50.402852], + [125.536292, 50.420014], + [125.522126, 50.404759], + [125.546763, 50.358965], + [125.520278, 50.3498], + [125.530749, 50.331085], + [125.463611, 50.295925], + [125.466075, 50.266861], + [125.442053, 50.260357], + [125.448829, 50.216338], + [125.417416, 50.195654], + [125.39093, 50.199868], + [125.382923, 50.172278], + [125.335496, 50.161161], + [125.376148, 50.137385], + [125.311474, 50.140453], + [125.27883, 50.127411], + [125.258504, 50.103618], + [125.287453, 50.093636], + [125.283757, 50.070211], + [125.328105, 50.065985], + [125.315786, 50.04562], + [125.289916, 50.057917], + [125.25296, 50.041393], + [125.283757, 50.036012], + [125.297924, 50.014481], + [125.278214, 49.996402], + [125.241873, 49.987938], + [125.231402, 49.957531], + [125.190134, 49.959841], + [125.199373, 49.935194], + [125.225859, 49.922481], + [125.212924, 49.907452], + [125.245569, 49.87198], + [125.225243, 49.867351], + [125.239409, 49.844587], + [125.177815, 49.829533], + [125.222779, 49.799026], + [125.221547, 49.754969], + [125.204301, 49.734086], + [125.225243, 49.726349], + [125.219699, 49.669058], + [125.185207, 49.634574], + [125.189518, 49.652401], + [125.164881, 49.669446], + [125.132236, 49.672157], + [125.127308, 49.655113], + [125.15441, 49.616741], + [125.16796, 49.629923], + [125.205533, 49.593859], + [125.23017, 49.595411], + [125.233866, 49.536801], + [125.211076, 49.539908], + [125.228323, 49.487063], + [125.270822, 49.454395], + [125.256656, 49.437275], + [125.25604, 49.395227], + [125.277598, 49.379644], + [125.256656, 49.359769], + [125.261583, 49.322336], + [125.214772, 49.277066], + [125.227707, 49.248947], + [125.219699, 49.189139], + [125.187671, 49.186792], + [125.158721, 49.144921], + [125.117453, 49.126127], + [125.034302, 49.157056], + [125.039845, 49.17623], + [124.983179, 49.162535], + [124.906802, 49.184054], + [124.860607, 49.166448], + [124.847672, 49.129651], + [124.809484, 49.115943], + [124.828578, 49.077933], + [124.808252, 49.020666], + [124.756513, 48.967262], + [124.744194, 48.920487], + [124.709086, 48.920487], + [124.715861, 48.885475], + [124.697383, 48.841775], + [124.654267, 48.83429], + [124.644412, 48.80789], + [124.656115, 48.783842], + [124.612383, 48.747945], + [124.624702, 48.701755], + [124.601912, 48.632587], + [124.579122, 48.596582], + [124.520608, 48.556195], + [124.548941, 48.535593], + [124.533543, 48.515379], + [124.555717, 48.467784], + [124.507674, 48.445558], + [124.52492, 48.426897], + [124.51876, 48.378027], + [124.547094, 48.35775], + [124.540934, 48.335476], + [124.579738, 48.297269], + [124.558796, 48.268197], + [124.579122, 48.262221], + [124.547094, 48.200829], + [124.512601, 48.164518], + [124.529847, 48.146951], + [124.505826, 48.124985], + [124.478108, 48.123387], + [124.46579, 48.098213], + [124.415899, 48.08782], + [124.430065, 48.12099], + [124.471333, 48.133373], + [124.475029, 48.173698], + [124.418978, 48.181679], + [124.412819, 48.219175], + [124.422058, 48.245884], + [124.365392, 48.283731], + [124.353689, 48.315978], + [124.317964, 48.35099], + [124.331515, 48.380015], + [124.309957, 48.413393], + [124.330283, 48.435633], + [124.302566, 48.456673], + [124.314269, 48.503881], + [124.25945, 48.536385], + [124.25945, 48.536385], + [124.136878, 48.463023], + [124.07898, 48.43603], + [124.019234, 48.39313], + [123.862785, 48.271782], + [123.746373, 48.197638], + [123.705105, 48.152142], + [123.579453, 48.045427], + [123.537569, 48.021816], + [123.300432, 47.953723], + [123.256085, 47.876711], + [123.214201, 47.824502], + [123.161846, 47.781892], + [123.041122, 47.746492], + [122.926557, 47.697777], + [122.848949, 47.67441], + [122.765181, 47.614333], + [122.59395, 47.54732], + [122.543443, 47.495589], + [122.507103, 47.401291], + [122.418407, 47.350632], + [122.441197, 47.310476], + [122.441197, 47.310476], + [122.462755, 47.27841], + [122.498479, 47.255262], + [122.531124, 47.198771], + [122.582863, 47.158092], + [122.582863, 47.158092], + [122.615508, 47.124306], + [122.679566, 47.094164], + [122.710363, 47.093349], + [122.710363, 47.093349], + [122.821232, 47.065636], + [122.852645, 47.072158], + [122.845869, 47.046881], + [122.778116, 47.002822], + [122.77442, 46.973837], + [122.798442, 46.9575], + [122.791051, 46.941567], + [122.83971, 46.937072], + [122.895144, 46.960359], + [122.893913, 46.895376], + [122.906847, 46.80738], + [122.996774, 46.761483], + [123.00355, 46.730726], + [123.026339, 46.718829], + [123.076846, 46.745082], + [123.103332, 46.734828], + [123.163694, 46.74016], + [123.198802, 46.803283], + [123.22344, 46.821305], + [123.221592, 46.850373], + [123.295505, 46.865105], + [123.341084, 46.826628], + [123.374345, 46.837683], + [123.40699, 46.906416], + [123.404526, 46.935438], + [123.360179, 46.970978], + [123.304128, 46.964852], + [123.301664, 46.999965], + [123.337389, 46.988943], + [123.42362, 46.934212], + [123.487678, 46.959951], + [123.52833, 46.944836], + [123.483366, 46.84587], + [123.506772, 46.827038], + [123.562823, 46.82581], + [123.575757, 46.845461], + [123.576989, 46.891286], + [123.605322, 46.891286], + [123.599163, 46.868378], + [123.625648, 46.847508], + [123.580069, 46.827447], + [123.629344, 46.813524], + [123.631808, 46.728675], + [123.603475, 46.68928], + [123.474743, 46.686817], + [123.366338, 46.677784], + [123.318295, 46.662179], + [123.276411, 46.660947], + [123.279491, 46.616981], + [123.228368, 46.588198], + [123.18094, 46.614103], + [123.098404, 46.603002], + [123.077462, 46.622324], + [123.04605, 46.617803], + [123.052825, 46.579972], + [123.002318, 46.574624], + [123.010325, 46.524823], + [123.011557, 46.434984], + [123.089781, 46.347888], + [123.142136, 46.298293], + [123.178476, 46.248239], + [123.248078, 46.273065], + [123.286266, 46.250308], + [123.320758, 46.254447], + [123.357099, 46.232096], + [123.357099, 46.232096], + [123.430396, 46.243687], + [123.452569, 46.233338], + [123.499381, 46.259826], + [123.569598, 46.223816], + [123.569598, 46.223816] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 310000, + "name": "Shanghai", + "center": [121.472644, 31.231706], + "centroid": [121.438737, 31.072559], + "childrenNum": 16, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 8, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [120.901349, 31.017327], + [120.940153, 31.010146], + [120.949392, 31.030148], + [120.989428, 31.01425], + [121.000515, 30.938309], + [120.993124, 30.889532], + [121.020225, 30.872069], + [120.991892, 30.837133], + [121.038087, 30.814007], + [121.060261, 30.845354], + [121.097833, 30.857171], + [121.13787, 30.826342], + [121.123087, 30.77905], + [121.174826, 30.771851], + [121.21671, 30.785734], + [121.232108, 30.755909], + [121.272144, 30.723504], + [121.274608, 30.677191], + [121.362071, 30.679764], + [121.426129, 30.730192], + [121.517288, 30.775451], + [121.601056, 30.805269], + [121.681128, 30.818633], + [121.904714, 30.814007], + [121.943518, 30.776993], + [121.970004, 30.789333], + [121.954605, 30.825828], + [121.994025, 30.862823], + [121.990945, 30.96859], + [121.977395, 31.016301], + [121.946598, 31.066039], + [121.809859, 31.196669], + [121.722396, 31.3036], + [121.599208, 31.37465], + [121.520984, 31.394575], + [121.404571, 31.479337], + [121.343593, 31.511996], + [121.301093, 31.49873], + [121.301093, 31.49873], + [121.247507, 31.476785], + [121.241963, 31.493117], + [121.174826, 31.44922], + [121.143413, 31.392021], + [121.113848, 31.37465], + [121.130478, 31.343987], + [121.142797, 31.275472], + [121.090442, 31.291838], + [121.060261, 31.245289], + [121.076892, 31.158267], + [121.018377, 31.134194], + [120.930298, 31.141365], + [120.881023, 31.134706], + [120.859465, 31.100379], + [120.890878, 31.094229], + [120.901349, 31.017327] + ] + ], + [ + [ + [121.974931, 31.61704], + [121.715005, 31.673592], + [121.64294, 31.697527], + [121.599824, 31.703128], + [121.49881, 31.753012], + [121.431673, 31.769295], + [121.384861, 31.833382], + [121.323267, 31.868458], + [121.265369, 31.863883], + [121.200079, 31.834907], + [121.118775, 31.759119], + [121.145261, 31.75403], + [121.289391, 31.61653], + [121.371926, 31.553314], + [121.395332, 31.585437], + [121.434136, 31.590535], + [121.547469, 31.531382], + [121.625693, 31.501792], + [121.682976, 31.491075], + [121.819098, 31.437987], + [121.890547, 31.428795], + [121.981706, 31.464024], + [121.995873, 31.493117], + [121.974931, 31.61704] + ] + ], + [ + [ + [121.795693, 31.330186], + [121.792613, 31.363408], + [121.742106, 31.407345], + [121.585657, 31.454836], + [121.567179, 31.48342], + [121.520984, 31.494137], + [121.509897, 31.4824], + [121.572107, 31.435944], + [121.727939, 31.35472], + [121.76428, 31.31536], + [121.785222, 31.31127], + [121.795693, 31.330186] + ] + ], + [ + [ + [121.801852, 31.356765], + [121.8037, 31.328652], + [121.840656, 31.295418], + [121.932431, 31.283144], + [122.016199, 31.282121], + [122.097503, 31.255522], + [122.122756, 31.307179], + [122.116597, 31.320984], + [122.040837, 31.324051], + [121.951525, 31.337343], + [121.845584, 31.37465], + [121.792613, 31.377715], + [121.801852, 31.356765] + ] + ], + [ + [ + [121.626925, 31.445135], + [121.631853, 31.456878], + [121.579498, 31.479848], + [121.626925, 31.445135] + ] + ], + [ + [ + [121.943518, 31.215608], + [121.959533, 31.159291], + [121.995873, 31.160828], + [122.008808, 31.221238], + [121.950909, 31.228915], + [121.943518, 31.215608] + ] + ], + [ + [ + [121.88254, 31.240684], + [121.909026, 31.195133], + [121.923808, 31.234032], + [121.88254, 31.240684] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 320000, + "name": "Jiangsu", + "center": [118.767413, 32.041544], + "centroid": [119.486506, 32.983991], + "childrenNum": 13, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 9, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [117.311654, 34.561686], + [117.27285, 34.556757], + [117.303647, 34.542463], + [117.267923, 34.532603], + [117.27285, 34.499565], + [117.252524, 34.48674], + [117.248213, 34.451216], + [117.166293, 34.434435], + [117.139191, 34.526687], + [117.15151, 34.559222], + [117.104083, 34.648874], + [117.073286, 34.639026], + [117.061583, 34.675947], + [117.070206, 34.713835], + [117.022163, 34.759081], + [116.969192, 34.771864], + [116.95133, 34.81069], + [116.979047, 34.815113], + [116.966113, 34.844588], + [116.929156, 34.843114], + [116.922381, 34.894671], + [116.858323, 34.928533], + [116.821983, 34.929515], + [116.815823, 34.965324], + [116.789338, 34.975133], + [116.781331, 34.916757], + [116.677853, 34.939327], + [116.622418, 34.939818], + [116.613795, 34.922645], + [116.557745, 34.908905], + [116.445028, 34.895652], + [116.408071, 34.850972], + [116.403144, 34.756131], + [116.369267, 34.749247], + [116.363724, 34.715311], + [116.392057, 34.710391], + [116.374195, 34.640011], + [116.430245, 34.650843], + [116.432709, 34.630163], + [116.477057, 34.614896], + [116.490607, 34.573513], + [116.594085, 34.511894], + [116.592237, 34.493646], + [116.662454, 34.472927], + [116.722816, 34.472434], + [116.773939, 34.453683], + [116.782563, 34.429993], + [116.828142, 34.389012], + [116.909446, 34.408271], + [116.969192, 34.389012], + [116.960569, 34.363821], + [116.983359, 34.348011], + [116.969192, 34.283753], + [117.051112, 34.221425], + [117.025243, 34.167469], + [117.046801, 34.151622], + [117.123793, 34.128342], + [117.130568, 34.101586], + [117.192162, 34.068873], + [117.257452, 34.065899], + [117.277162, 34.078787], + [117.311654, 34.067882], + [117.357234, 34.088205], + [117.404045, 34.03218], + [117.435458, 34.028212], + [117.514914, 34.060941], + [117.543248, 34.038627], + [117.569117, 33.985051], + [117.612849, 34.000433], + [117.629479, 34.028708], + [117.671363, 33.992494], + [117.672595, 33.934916], + [117.715095, 33.879287], + [117.753899, 33.891211], + [117.759442, 33.874318], + [117.739732, 33.758467], + [117.72495, 33.74951], + [117.750203, 33.710688], + [117.791471, 33.733585], + [117.843826, 33.736074], + [117.901724, 33.720146], + [117.972557, 33.74951], + [118.019985, 33.738562], + [118.065564, 33.76593], + [118.117919, 33.766427], + [118.161035, 33.735576], + [118.16781, 33.663381], + [118.112376, 33.617045], + [118.117919, 33.594615], + [118.107448, 33.475391], + [118.050782, 33.491863], + [118.027376, 33.455421], + [118.016905, 33.402978], + [118.029224, 33.374995], + [117.992883, 33.333005], + [117.974405, 33.279487], + [117.939297, 33.262475], + [117.942376, 33.224936], + [117.977485, 33.226437], + [117.988572, 33.180869], + [118.037231, 33.152314], + [118.038463, 33.134776], + [118.149332, 33.169348], + [118.178281, 33.217926], + [118.217085, 33.191888], + [118.219549, 33.114227], + [118.243571, 33.027967], + [118.244803, 32.998359], + [118.26944, 32.969242], + [118.303933, 32.96874], + [118.291614, 32.946143], + [118.252194, 32.936601], + [118.2331, 32.914498], + [118.250346, 32.848157], + [118.301469, 32.846145], + [118.300237, 32.783275], + [118.334114, 32.761637], + [118.363063, 32.770695], + [118.375382, 32.718849], + [118.411106, 32.715828], + [118.450526, 32.743518], + [118.483787, 32.721367], + [118.560163, 32.729926], + [118.572482, 32.719856], + [118.642699, 32.744525], + [118.707373, 32.72036], + [118.756648, 32.737477], + [118.73817, 32.772708], + [118.743097, 32.853184], + [118.743097, 32.853184], + [118.810235, 32.853687], + [118.821322, 32.920527], + [118.846575, 32.922034], + [118.849039, 32.956689], + [118.89585, 32.957694], + [118.89585, 32.957694], + [118.892771, 32.941121], + [118.934039, 32.93861], + [118.993169, 32.958196], + [119.020886, 32.955685], + [119.054763, 32.8748], + [119.113277, 32.823014], + [119.184726, 32.825529], + [119.211827, 32.708275], + [119.208748, 32.641276], + [119.230921, 32.607001], + [119.22045, 32.576748], + [119.152697, 32.557582], + [119.168096, 32.536394], + [119.142226, 32.499556], + [119.084944, 32.452602], + [119.041212, 32.515201], + [118.975923, 32.505108], + [118.922336, 32.557078], + [118.92172, 32.557078], + [118.922336, 32.557078], + [118.92172, 32.557078], + [118.890923, 32.553042], + [118.908169, 32.59238], + [118.84288, 32.56767], + [118.820706, 32.60448], + [118.784981, 32.582295], + [118.757264, 32.603976], + [118.73509, 32.58885], + [118.719076, 32.614059], + [118.719076, 32.614059], + [118.688895, 32.588346], + [118.658714, 32.594397], + [118.632844, 32.578261], + [118.59712, 32.600951], + [118.568787, 32.585825], + [118.564475, 32.562122], + [118.608823, 32.536899], + [118.592192, 32.481383], + [118.628533, 32.467751], + [118.691359, 32.472295], + [118.685199, 32.403604], + [118.703061, 32.328792], + [118.657482, 32.30148], + [118.674728, 32.250375], + [118.643931, 32.209875], + [118.510888, 32.194176], + [118.49549, 32.165304], + [118.501033, 32.121726], + [118.433896, 32.086746], + [118.394476, 32.076098], + [118.389548, 31.985281], + [118.363679, 31.930443], + [118.472084, 31.879639], + [118.466541, 31.857784], + [118.504729, 31.841516], + [118.481939, 31.778453], + [118.533678, 31.76726], + [118.521975, 31.743343], + [118.5577, 31.73011], + [118.571866, 31.746397], + [118.641467, 31.75861], + [118.653786, 31.73011], + [118.697518, 31.709747], + [118.643315, 31.671555], + [118.643315, 31.649651], + [118.736322, 31.633347], + [118.748025, 31.675629], + [118.773894, 31.682759], + [118.802844, 31.619078], + [118.858894, 31.623665], + [118.881684, 31.564023], + [118.885995, 31.519139], + [118.883532, 31.500261], + [118.852119, 31.393553], + [118.824401, 31.375672], + [118.767735, 31.363919], + [118.745561, 31.372606], + [118.720924, 31.322518], + [118.726467, 31.282121], + [118.756648, 31.279564], + [118.794836, 31.229426], + [118.870597, 31.242219], + [118.984546, 31.237102], + [119.014727, 31.241707], + [119.10527, 31.235055], + [119.107118, 31.250917], + [119.158241, 31.294907], + [119.197661, 31.295418], + [119.198277, 31.270357], + [119.266646, 31.250405], + [119.294363, 31.263195], + [119.338095, 31.259103], + [119.350414, 31.301043], + [119.374435, 31.258591], + [119.360269, 31.213049], + [119.391682, 31.174142], + [119.439109, 31.177214], + [119.461283, 31.156219], + [119.532732, 31.159291], + [119.599869, 31.10909], + [119.623891, 31.130096], + [119.678093, 31.167997], + [119.705811, 31.152634], + [119.715666, 31.169533], + [119.779723, 31.17875], + [119.809904, 31.148536], + [119.827151, 31.174142], + [119.878274, 31.160828], + [119.921389, 31.170045], + [119.946027, 31.106016], + [119.988527, 31.059375], + [120.001461, 31.027071], + [120.052584, 31.00553], + [120.111099, 30.955761], + [120.149903, 30.937283], + [120.223816, 30.926502], + [120.316206, 30.933689], + [120.371025, 30.948575], + [120.35809, 30.886964], + [120.42338, 30.902884], + [120.435083, 30.920855], + [120.441858, 30.860768], + [120.460336, 30.839702], + [120.489285, 30.763624], + [120.504684, 30.757967], + [120.563814, 30.835592], + [120.589684, 30.854089], + [120.654973, 30.846896], + [120.68269, 30.882342], + [120.713487, 30.88491], + [120.709176, 30.933176], + [120.684538, 30.955247], + [120.698089, 30.970643], + [120.746132, 30.962432], + [120.770154, 30.996809], + [120.820661, 31.006556], + [120.865624, 30.989627], + [120.901349, 31.017327], + [120.890878, 31.094229], + [120.859465, 31.100379], + [120.881023, 31.134706], + [120.930298, 31.141365], + [121.018377, 31.134194], + [121.076892, 31.158267], + [121.060261, 31.245289], + [121.090442, 31.291838], + [121.142797, 31.275472], + [121.130478, 31.343987], + [121.113848, 31.37465], + [121.143413, 31.392021], + [121.174826, 31.44922], + [121.241963, 31.493117], + [121.247507, 31.476785], + [121.301093, 31.49873], + [121.301093, 31.49873], + [121.343593, 31.511996], + [121.371926, 31.553314], + [121.289391, 31.61653], + [121.145261, 31.75403], + [121.118775, 31.759119], + [121.200079, 31.834907], + [121.265369, 31.863883], + [121.323267, 31.868458], + [121.384861, 31.833382], + [121.431673, 31.769295], + [121.49881, 31.753012], + [121.599824, 31.703128], + [121.64294, 31.697527], + [121.715005, 31.673592], + [121.974931, 31.61704], + [121.970004, 31.718911], + [121.889315, 31.866425], + [121.856055, 31.955328], + [121.772287, 32.032984], + [121.759352, 32.059362], + [121.525295, 32.136423], + [121.542542, 32.152132], + [121.458774, 32.177462], + [121.499426, 32.211394], + [121.493882, 32.263533], + [121.450151, 32.282256], + [121.425513, 32.430885], + [121.390405, 32.460682], + [121.352216, 32.474315], + [121.269681, 32.483402], + [121.153268, 32.52933], + [121.121855, 32.569183], + [121.076892, 32.576243], + [121.020225, 32.605489], + [120.961711, 32.612042], + [120.979573, 32.636236], + [120.963559, 32.68259], + [120.916131, 32.701225], + [120.953088, 32.714318], + [120.972182, 32.761134], + [120.981421, 32.85972], + [120.957399, 32.893395], + [120.932762, 33.005887], + [120.917979, 33.02596], + [120.871784, 33.047032], + [120.874247, 33.093672], + [120.843451, 33.209915], + [120.819429, 33.237951], + [120.833595, 33.274984], + [120.813885, 33.303499], + [120.769538, 33.307], + [120.741205, 33.337505], + [120.717183, 33.436945], + [120.680227, 33.520306], + [120.622944, 33.615051], + [120.611241, 33.627012], + [120.583524, 33.668362], + [120.534249, 33.782346], + [120.48559, 33.859411], + [120.367329, 34.091674], + [120.347619, 34.179352], + [120.314359, 34.255563], + [120.311895, 34.306991], + [120.103707, 34.391481], + [119.962657, 34.459112], + [119.811752, 34.485754], + [119.781571, 34.515839], + [119.641137, 34.569078], + [119.610956, 34.592729], + [119.569072, 34.615389], + [119.465594, 34.672994], + [119.525956, 34.73351], + [119.456971, 34.748264], + [119.381827, 34.752198], + [119.494543, 34.754656], + [119.497007, 34.754164], + [119.439725, 34.785136], + [119.440957, 34.769406], + [119.378747, 34.764489], + [119.312841, 34.774813], + [119.272189, 34.797914], + [119.238313, 34.799388], + [119.217371, 34.827886], + [119.202588, 34.890253], + [119.214907, 34.925589], + [119.211211, 34.981507], + [119.238313, 35.048657], + [119.285124, 35.068252], + [119.291899, 35.028567], + [119.307298, 35.032977], + [119.292515, 35.068742], + [119.306066, 35.076578], + [119.286972, 35.115261], + [119.250016, 35.124562], + [119.217371, 35.106939], + [119.137915, 35.096167], + [119.114509, 35.055026], + [119.027045, 35.055516], + [118.942662, 35.040817], + [118.928495, 35.051106], + [118.86259, 35.025626], + [118.860742, 34.944233], + [118.805307, 34.87307], + [118.80038, 34.843114], + [118.772047, 34.794474], + [118.739402, 34.792508], + [118.719076, 34.745313], + [118.764039, 34.740396], + [118.783749, 34.723181], + [118.739402, 34.693663], + [118.690127, 34.678408], + [118.664257, 34.693663], + [118.607591, 34.694155], + [118.601431, 34.714327], + [118.545997, 34.705964], + [118.460997, 34.656258], + [118.473932, 34.623269], + [118.439439, 34.626223], + [118.424657, 34.595193], + [118.439439, 34.507949], + [118.416034, 34.473914], + [118.404947, 34.427525], + [118.379693, 34.415183], + [118.290382, 34.424563], + [118.277447, 34.404814], + [118.220165, 34.405802], + [118.217701, 34.379134], + [118.179513, 34.379628], + [118.177665, 34.45319], + [118.132702, 34.483287], + [118.16473, 34.50499], + [118.185056, 34.543942], + [118.079115, 34.569571], + [118.114839, 34.614404], + [118.084042, 34.655766], + [118.053861, 34.650843], + [117.951615, 34.678408], + [117.909732, 34.670533], + [117.902956, 34.644443], + [117.793935, 34.651827], + [117.791471, 34.583368], + [117.801942, 34.518798], + [117.684298, 34.547392], + [117.659044, 34.501044], + [117.609769, 34.490686], + [117.592523, 34.462566], + [117.53832, 34.467006], + [117.465023, 34.484767], + [117.402813, 34.550843], + [117.402813, 34.569571], + [117.370785, 34.584846], + [117.325205, 34.573021], + [117.325205, 34.573021], + [117.32151, 34.566614], + [117.32151, 34.566614], + [117.311654, 34.561686], + [117.311654, 34.561686] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 330000, + "name": "Zhejiang", + "center": [120.153576, 30.287459], + "centroid": [120.109913, 29.181466], + "childrenNum": 11, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 10, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [118.433896, 28.288335], + [118.444367, 28.253548], + [118.490562, 28.238259], + [118.493026, 28.262509], + [118.588497, 28.282538], + [118.595272, 28.258292], + [118.651322, 28.277267], + [118.674728, 28.27147], + [118.699366, 28.309939], + [118.719692, 28.312047], + [118.756032, 28.252493], + [118.802228, 28.240368], + [118.804075, 28.207675], + [118.771431, 28.188687], + [118.805923, 28.154923], + [118.802228, 28.117453], + [118.767735, 28.10584], + [118.719076, 28.063601], + [118.733858, 28.027684], + [118.730163, 27.970615], + [118.753568, 27.947885], + [118.818242, 27.916689], + [118.829329, 27.847921], + [118.873677, 27.733563], + [118.879836, 27.667859], + [118.913713, 27.619616], + [118.909401, 27.568168], + [118.869365, 27.540047], + [118.907553, 27.460952], + [118.955597, 27.4498], + [118.986393, 27.47582], + [118.983314, 27.498649], + [119.020886, 27.498118], + [119.03998, 27.478475], + [119.092335, 27.466262], + [119.129907, 27.475289], + [119.121284, 27.438115], + [119.14777, 27.424836], + [119.224146, 27.416868], + [119.26911, 27.42218], + [119.285124, 27.457766], + [119.334399, 27.480067], + [119.360269, 27.524657], + [119.416935, 27.539517], + [119.438493, 27.508734], + [119.466826, 27.526249], + [119.501935, 27.610601], + [119.501319, 27.649837], + [119.541971, 27.666799], + [119.606028, 27.674749], + [119.644217, 27.663619], + [119.626354, 27.620676], + [119.630666, 27.582491], + [119.675014, 27.574534], + [119.659615, 27.540578], + [119.690412, 27.537394], + [119.70889, 27.514042], + [119.703347, 27.446613], + [119.685485, 27.438646], + [119.711354, 27.403054], + [119.750774, 27.373829], + [119.739687, 27.362668], + [119.782187, 27.330241], + [119.768636, 27.307909], + [119.843165, 27.300464], + [119.938636, 27.329709], + [119.960194, 27.365857], + [120.008237, 27.375423], + [120.026099, 27.344063], + [120.052584, 27.338747], + [120.096316, 27.390302], + [120.136968, 27.402523], + [120.134504, 27.420055], + [120.221352, 27.420055], + [120.26262, 27.432804], + [120.273091, 27.38924], + [120.340844, 27.399867], + [120.343924, 27.363199], + [120.430155, 27.258976], + [120.401822, 27.250996], + [120.404286, 27.204166], + [120.461568, 27.142407], + [120.492365, 27.136016], + [120.545952, 27.156785], + [120.574901, 27.234501], + [120.554575, 27.25206], + [120.580444, 27.321203], + [120.665444, 27.357884], + [120.673451, 27.420055], + [120.703016, 27.478475], + [120.637111, 27.561271], + [120.634647, 27.577186], + [120.685154, 27.622797], + [120.709176, 27.682699], + [120.771386, 27.734623], + [120.777545, 27.774873], + [120.809574, 27.775402], + [120.840371, 27.758986], + [120.910588, 27.864852], + [120.942001, 27.896592], + [120.97403, 27.887071], + [121.027616, 27.832574], + [121.070116, 27.834162], + [121.107688, 27.81352], + [121.152036, 27.815638], + [121.134174, 27.787051], + [121.13479, 27.787051], + [121.149572, 27.801345], + [121.149572, 27.801875], + [121.153268, 27.809815], + [121.152652, 27.810344], + [121.192072, 27.822518], + [121.193304, 27.872259], + [121.162507, 27.879136], + [121.162507, 27.90717], + [121.099681, 27.895005], + [121.05595, 27.900294], + [120.991892, 27.95], + [121.015298, 27.981714], + [121.059029, 28.096338], + [121.108304, 28.139092], + [121.121239, 28.12537], + [121.140949, 28.031382], + [121.176058, 28.022401], + [121.261057, 28.034551], + [121.299862, 28.067297], + [121.328195, 28.134343], + [121.373774, 28.133287], + [121.402107, 28.197127], + [121.45631, 28.250385], + [121.488955, 28.301509], + [121.538846, 28.299401], + [121.571491, 28.279376], + [121.580114, 28.240368], + [121.627541, 28.251966], + [121.669425, 28.33312], + [121.660186, 28.355768], + [121.634317, 28.347868], + [121.658954, 28.392628], + [121.692831, 28.407368], + [121.671273, 28.472621], + [121.646019, 28.511544], + [121.634317, 28.562542], + [121.596128, 28.575156], + [121.557324, 28.645033], + [121.540694, 28.655537], + [121.646019, 28.682842], + [121.689135, 28.719062], + [121.704534, 28.804577], + [121.687287, 28.863294], + [121.774751, 28.863818], + [121.772287, 28.898404], + [121.743338, 28.954451], + [121.711309, 28.985865], + [121.712541, 29.028783], + [121.658954, 29.058606], + [121.660186, 29.118226], + [121.616454, 29.143318], + [121.608447, 29.168927], + [121.715621, 29.125022], + [121.750113, 29.136523], + [121.767975, 29.166837], + [121.780294, 29.10986], + [121.811091, 29.10986], + [121.85975, 29.086328], + [121.884388, 29.105677], + [121.966308, 29.052852], + [121.970004, 29.092604], + [121.988482, 29.110906], + [121.986634, 29.154817], + [121.948446, 29.193485], + [121.971851, 29.193485], + [121.966924, 29.249894], + [122.002032, 29.260336], + [122.000185, 29.278608], + [121.94475, 29.28435], + [121.958301, 29.334448], + [121.936127, 29.348012], + [121.937975, 29.384], + [121.975547, 29.411113], + [121.993409, 29.45229], + [121.973083, 29.477821], + [121.968772, 29.515846], + [121.995257, 29.545007], + [122.000185, 29.582486], + [121.966308, 29.636078], + [121.909641, 29.650122], + [121.872685, 29.632437], + [121.833265, 29.653242], + [121.937359, 29.748373], + [122.003264, 29.762401], + [122.043916, 29.822647], + [122.10243, 29.859504], + [122.143082, 29.877668], + [122.140003, 29.901535], + [122.00696, 29.891678], + [122.00388, 29.92021], + [121.971235, 29.955476], + [121.919497, 29.920729], + [121.835113, 29.958068], + [121.78399, 29.99332], + [121.721164, 29.992802], + [121.699606, 30.007832], + [121.652795, 30.071037], + [121.635548, 30.070002], + [121.561636, 30.184395], + [121.497578, 30.258861], + [121.395332, 30.338435], + [121.371926, 30.37097], + [121.328195, 30.397299], + [121.225333, 30.404526], + [121.183449, 30.434458], + [121.092906, 30.515952], + [121.058413, 30.563888], + [121.148956, 30.599953], + [121.188992, 30.632916], + [121.239499, 30.648878], + [121.274608, 30.677191], + [121.272144, 30.723504], + [121.232108, 30.755909], + [121.21671, 30.785734], + [121.174826, 30.771851], + [121.123087, 30.77905], + [121.13787, 30.826342], + [121.097833, 30.857171], + [121.060261, 30.845354], + [121.038087, 30.814007], + [120.991892, 30.837133], + [121.020225, 30.872069], + [120.993124, 30.889532], + [121.000515, 30.938309], + [120.989428, 31.01425], + [120.949392, 31.030148], + [120.940153, 31.010146], + [120.901349, 31.017327], + [120.865624, 30.989627], + [120.820661, 31.006556], + [120.770154, 30.996809], + [120.746132, 30.962432], + [120.698089, 30.970643], + [120.684538, 30.955247], + [120.709176, 30.933176], + [120.713487, 30.88491], + [120.68269, 30.882342], + [120.654973, 30.846896], + [120.589684, 30.854089], + [120.563814, 30.835592], + [120.504684, 30.757967], + [120.489285, 30.763624], + [120.460336, 30.839702], + [120.441858, 30.860768], + [120.435083, 30.920855], + [120.42338, 30.902884], + [120.35809, 30.886964], + [120.371025, 30.948575], + [120.316206, 30.933689], + [120.223816, 30.926502], + [120.149903, 30.937283], + [120.111099, 30.955761], + [120.052584, 31.00553], + [120.001461, 31.027071], + [119.988527, 31.059375], + [119.946027, 31.106016], + [119.921389, 31.170045], + [119.878274, 31.160828], + [119.827151, 31.174142], + [119.809904, 31.148536], + [119.779723, 31.17875], + [119.715666, 31.169533], + [119.705811, 31.152634], + [119.678093, 31.167997], + [119.623891, 31.130096], + [119.649144, 31.104991], + [119.629434, 31.085517], + [119.633746, 31.019379], + [119.580159, 30.967051], + [119.582007, 30.932149], + [119.563529, 30.919315], + [119.557369, 30.874124], + [119.575847, 30.829939], + [119.55429, 30.825828], + [119.527188, 30.77905], + [119.479761, 30.772365], + [119.482841, 30.704467], + [119.444652, 30.650422], + [119.408312, 30.645274], + [119.39045, 30.685941], + [119.343022, 30.664322], + [119.323312, 30.630341], + [119.238929, 30.609225], + [119.265414, 30.574709], + [119.237081, 30.546881], + [119.272189, 30.510281], + [119.326392, 30.532964], + [119.336247, 30.508734], + [119.335015, 30.448389], + [119.36766, 30.38491], + [119.402768, 30.374584], + [119.349182, 30.349281], + [119.326392, 30.372002], + [119.277117, 30.341018], + [119.246936, 30.341018], + [119.236465, 30.297106], + [119.201356, 30.290905], + [119.126828, 30.304856], + [119.091719, 30.323972], + [119.06277, 30.304856], + [118.988857, 30.332237], + [118.954365, 30.360126], + [118.880452, 30.31519], + [118.877988, 30.282637], + [118.905089, 30.216464], + [118.929727, 30.2025], + [118.852735, 30.166805], + [118.852119, 30.149729], + [118.895234, 30.148694], + [118.873677, 30.11505], + [118.878604, 30.064822], + [118.902626, 30.029078], + [118.894619, 29.937845], + [118.838568, 29.934733], + [118.841032, 29.891159], + [118.740634, 29.814859], + [118.744945, 29.73902], + [118.700598, 29.706277], + [118.647011, 29.64336], + [118.61991, 29.654282], + [118.573714, 29.638159], + [118.532446, 29.588731], + [118.500417, 29.57572], + [118.496106, 29.519492], + [118.381541, 29.504909], + [118.347664, 29.474174], + [118.329802, 29.495012], + [118.306396, 29.479384], + [118.316252, 29.422581], + [118.248498, 29.431443], + [118.193064, 29.395472], + [118.205382, 29.343839], + [118.166578, 29.314099], + [118.178281, 29.297921], + [118.138861, 29.283828], + [118.077883, 29.290614], + [118.073571, 29.216993], + [118.042159, 29.210202], + [118.027992, 29.167882], + [118.045238, 29.149068], + [118.037847, 29.102017], + [118.076035, 29.074822], + [118.066796, 29.053898], + [118.097593, 28.998952], + [118.115455, 29.009944], + [118.115455, 29.009944], + [118.133933, 28.983771], + [118.165346, 28.986912], + [118.227556, 28.942406], + [118.195527, 28.904167], + [118.270056, 28.918836], + [118.300237, 28.826075], + [118.364295, 28.813491], + [118.403099, 28.702791], + [118.428352, 28.681267], + [118.428352, 28.617193], + [118.428352, 28.617193], + [118.412338, 28.55676], + [118.4302, 28.515225], + [118.414802, 28.497344], + [118.474548, 28.478934], + [118.456686, 28.424738], + [118.432048, 28.402104], + [118.455454, 28.384204], + [118.480091, 28.327325], + [118.433896, 28.288335] + ] + ], + [ + [ + [122.163408, 29.988137], + [122.239785, 29.962735], + [122.279205, 29.937326], + [122.322321, 29.940438], + [122.341415, 29.976733], + [122.343879, 30.020269], + [122.310002, 30.039958], + [122.290908, 30.074663], + [122.301379, 30.086574], + [122.293988, 30.100554], + [122.152938, 30.113497], + [122.095655, 30.158008], + [122.048844, 30.147141], + [121.955221, 30.183878], + [121.934895, 30.161631], + [121.983554, 30.100554], + [121.989714, 30.077252], + [121.978011, 30.059125], + [122.027902, 29.991247], + [122.106742, 30.005759], + [122.118445, 29.986582], + [122.163408, 29.988137] + ] + ], + [ + [ + [122.213915, 30.186464], + [122.178807, 30.199396], + [122.152938, 30.19112], + [122.143698, 30.163183], + [122.168336, 30.138343], + [122.213915, 30.186464] + ] + ], + [ + [ + [122.229314, 29.711995], + [122.210836, 29.700559], + [122.269966, 29.685482], + [122.231162, 29.710435], + [122.229314, 29.711995] + ] + ], + [ + [ + [122.427646, 30.738422], + [122.427031, 30.697777], + [122.532972, 30.696748], + [122.528045, 30.725047], + [122.475074, 30.714243], + [122.445509, 30.745109], + [122.427646, 30.738422] + ] + ], + [ + [ + [122.162793, 30.329654], + [122.058083, 30.291938], + [122.154169, 30.244903], + [122.231778, 30.234562], + [122.247176, 30.30124], + [122.228082, 30.329654], + [122.191126, 30.329654], + [122.176343, 30.351863], + [122.162793, 30.329654] + ] + ], + [ + [ + [122.317393, 30.249556], + [122.277973, 30.242835], + [122.358661, 30.236113], + [122.365437, 30.255242], + [122.417175, 30.238699], + [122.40732, 30.272817], + [122.333408, 30.272817], + [122.317393, 30.249556] + ] + ], + [ + [ + [122.026054, 29.178333], + [122.013119, 29.151681], + [122.056851, 29.158476], + [122.075945, 29.176243], + [122.036525, 29.20759], + [122.026054, 29.178333] + ] + ], + [ + [ + [122.372212, 29.893234], + [122.386379, 29.834069], + [122.415944, 29.828877], + [122.401777, 29.869884], + [122.433806, 29.883376], + [122.43319, 29.919173], + [122.411632, 29.951846], + [122.398081, 29.9394], + [122.351886, 29.959105], + [122.330944, 29.937845], + [122.338951, 29.911911], + [122.353734, 29.89946], + [122.362973, 29.894272], + [122.372212, 29.893234] + ] + ], + [ + [ + [122.43011, 30.408655], + [122.432574, 30.445294], + [122.37406, 30.461802], + [122.277973, 30.471603], + [122.281669, 30.418461], + [122.318625, 30.407106], + [122.352502, 30.422074], + [122.43011, 30.408655] + ] + ], + [ + [ + [121.837577, 28.770484], + [121.86283, 28.782024], + [121.861598, 28.814016], + [121.837577, 28.770484] + ] + ], + [ + [ + [122.265038, 29.84549], + [122.221307, 29.832512], + [122.248408, 29.804473], + [122.310002, 29.766557], + [122.325401, 29.781621], + [122.299531, 29.819532], + [122.319241, 29.829397], + [122.265038, 29.84549] + ] + ], + [ + [ + [121.790765, 29.082144], + [121.832649, 29.050236], + [121.84312, 29.082144], + [121.82033, 29.099402], + [121.790765, 29.082144] + ] + ], + [ + [ + [121.201311, 27.623328], + [121.197616, 27.618025], + [121.198848, 27.616964], + [121.203775, 27.625979], + [121.201311, 27.623328] + ] + ], + [ + [ + [121.943518, 30.776993], + [121.968156, 30.688514], + [121.997105, 30.658659], + [122.087032, 30.602014], + [122.133227, 30.595317], + [122.075329, 30.647848], + [122.011271, 30.66947], + [121.992793, 30.695204], + [121.987866, 30.753338], + [121.970004, 30.789333], + [121.943518, 30.776993] + ] + ], + [ + [ + [121.889315, 28.471569], + [121.918881, 28.497344], + [121.881924, 28.502603], + [121.889315, 28.471569] + ] + ], + [ + [ + [122.182503, 29.650642], + [122.211452, 29.692241], + [122.200365, 29.712515], + [122.146778, 29.749412], + [122.13138, 29.788893], + [122.083952, 29.78318], + [122.047612, 29.719791], + [122.074097, 29.701599], + [122.095655, 29.716673], + [122.138155, 29.662083], + [122.182503, 29.650642] + ] + ], + [ + [ + [122.461523, 29.944068], + [122.459675, 29.944586], + [122.460291, 29.947179], + [122.451668, 29.943031], + [122.451052, 29.940956], + [122.450436, 29.940956], + [122.449204, 29.9394], + [122.4529, 29.936807], + [122.452284, 29.935252], + [122.45598, 29.926435], + [122.457827, 29.927472], + [122.462755, 29.927991], + [122.467067, 29.928509], + [122.459059, 29.938882], + [122.461523, 29.944068] + ] + ], + [ + [ + [122.570544, 30.644244], + [122.559457, 30.679764], + [122.546523, 30.651967], + [122.570544, 30.644244] + ] + ], + [ + [ + [121.869605, 28.423685], + [121.910873, 28.44], + [121.889931, 28.45105], + [121.869605, 28.423685] + ] + ], + [ + [ + [122.065474, 30.179739], + [122.055619, 30.200431], + [122.017431, 30.186464], + [122.025438, 30.161631], + [122.065474, 30.179739] + ] + ], + [ + [ + [122.391306, 29.970512], + [122.411632, 30.025969], + [122.378371, 30.023896], + [122.3679, 29.980361], + [122.391306, 29.970512] + ] + ], + [ + [ + [121.850511, 29.977251], + [121.874533, 29.964809], + [121.933047, 29.994875], + [121.924424, 30.052391], + [121.88562, 30.094859], + [121.848663, 30.101072], + [121.84004, 30.047211], + [121.844968, 29.982953], + [121.850511, 29.977251] + ] + ], + [ + [ + [121.066421, 27.478475], + [121.066421, 27.461483], + [121.107073, 27.443958], + [121.067036, 27.478475], + [121.066421, 27.478475] + ] + ], + [ + [ + [121.952141, 29.187738], + [121.979243, 29.160043], + [121.976779, 29.191918], + [121.952141, 29.187738] + ] + ], + [ + [ + [122.038373, 29.759284], + [122.011271, 29.746294], + [122.02975, 29.716673], + [122.038373, 29.759284] + ] + ], + [ + [ + [121.940438, 30.114533], + [121.910257, 30.089163], + [121.945982, 30.064304], + [121.962612, 30.106249], + [121.940438, 30.114533] + ] + ], + [ + [ + [121.957685, 30.287804], + [122.0008, 30.308473], + [121.989098, 30.339985], + [121.94167, 30.33327], + [121.921344, 30.30744], + [121.957685, 30.287804] + ] + ], + [ + [ + [122.192974, 29.965327], + [122.163408, 29.988137], + [122.152322, 29.97103], + [122.154169, 29.97103], + [122.155401, 29.970512], + [122.18435, 29.955476], + [122.192974, 29.965327] + ] + ], + [ + [ + [122.287828, 29.723949], + [122.301379, 29.748373], + [122.258263, 29.753569], + [122.241633, 29.784738], + [122.2133, 29.771752], + [122.251488, 29.731225], + [122.287828, 29.723949] + ] + ], + [ + [ + [121.134174, 27.787051], + [121.134174, 27.785992], + [121.13479, 27.787051], + [121.134174, 27.787051] + ] + ], + [ + [ + [122.760254, 30.141966], + [122.784275, 30.130062], + [122.781196, 30.13265], + [122.778116, 30.13679], + [122.770725, 30.138861], + [122.763333, 30.141966], + [122.762101, 30.142484], + [122.760254, 30.141966] + ] + ], + [ + [ + [122.264423, 30.269716], + [122.253952, 30.237147], + [122.315545, 30.250073], + [122.300147, 30.271266], + [122.264423, 30.269716] + ] + ], + [ + [ + [122.282901, 29.860542], + [122.30877, 29.849642], + [122.343263, 29.860542], + [122.343263, 29.882857], + [122.301379, 29.883895], + [122.282901, 29.860542] + ] + ], + [ + [ + [122.781196, 30.694175], + [122.799674, 30.716301], + [122.778732, 30.729677], + [122.757174, 30.713728], + [122.781196, 30.694175] + ] + ], + [ + [ + [121.098449, 27.937311], + [121.152652, 27.961629], + [121.120623, 27.986471], + [121.0695, 27.984357], + [121.038087, 27.948942], + [121.098449, 27.937311] + ] + ], + [ + [ + [121.185913, 27.963215], + [121.237652, 27.988056], + [121.197616, 28.000739], + [121.17113, 27.978543], + [121.185913, 27.963215] + ] + ], + [ + [ + [122.454132, 29.956513], + [122.447972, 29.955994], + [122.445509, 29.952365], + [122.446741, 29.951327], + [122.447972, 29.947698], + [122.459059, 29.950809], + [122.458443, 29.951846], + [122.455364, 29.955994], + [122.454132, 29.956513] + ] + ], + [ + [ + [122.836014, 30.698806], + [122.831087, 30.728648], + [122.807681, 30.714243], + [122.836014, 30.698806] + ] + ], + [ + [ + [122.200365, 29.969475], + [122.233626, 29.946661], + [122.273662, 29.93214], + [122.239785, 29.960142], + [122.200365, 29.969475] + ] + ], + [ + [ + [122.029134, 29.954957], + [122.043916, 29.930584], + [122.058699, 29.955994], + [122.029134, 29.954957] + ] + ], + [ + [ + [121.044247, 27.979072], + [121.089826, 27.998625], + [121.073812, 28.007608], + [121.044247, 27.979072] + ] + ], + [ + [ + [122.471378, 29.927472], + [122.470762, 29.925916], + [122.473226, 29.925397], + [122.47261, 29.927472], + [122.471378, 29.927472] + ] + ], + [ + [ + [122.152322, 29.97103], + [122.155401, 29.970512], + [122.154169, 29.97103], + [122.152322, 29.97103] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 340000, + "name": "Anhui", + "center": [117.283042, 31.86119], + "centroid": [117.226884, 31.849254], + "childrenNum": 16, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 11, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [116.599629, 34.014324], + [116.641512, 33.978103], + [116.64336, 33.896675], + [116.631042, 33.887733], + [116.566984, 33.9081], + [116.558361, 33.881274], + [116.486296, 33.869846], + [116.437637, 33.846489], + [116.437021, 33.801246], + [116.408071, 33.805721], + [116.393905, 33.782843], + [116.316912, 33.771402], + [116.263326, 33.730101], + [116.230065, 33.735078], + [116.155536, 33.709693], + [116.132747, 33.751501], + [116.100102, 33.782843], + [116.074232, 33.781351], + [116.055754, 33.804727], + [116.05945, 33.860902], + [115.982457, 33.917039], + [116.00032, 33.965199], + [115.95782, 34.007875], + [115.904233, 34.009859], + [115.876516, 34.028708], + [115.877132, 34.002913], + [115.85003, 34.004898], + [115.846335, 34.028708], + [115.809378, 34.062428], + [115.768726, 34.061932], + [115.736082, 34.076805], + [115.705901, 34.059949], + [115.658473, 34.061437], + [115.642459, 34.03218], + [115.60735, 34.030196], + [115.579017, 33.974133], + [115.577785, 33.950307], + [115.547604, 33.874815], + [115.631988, 33.869846], + [115.614126, 33.775879], + [115.576553, 33.787817], + [115.563003, 33.772895], + [115.601807, 33.718653], + [115.601191, 33.658898], + [115.639995, 33.585143], + [115.564851, 33.576169], + [115.511264, 33.55323], + [115.463837, 33.567193], + [115.422569, 33.557219], + [115.394851, 33.506335], + [115.366518, 33.5233], + [115.345576, 33.502842], + [115.345576, 33.449928], + [115.324634, 33.457418], + [115.315395, 33.431451], + [115.328946, 33.403477], + [115.313547, 33.376994], + [115.341881, 33.370997], + [115.365286, 33.336005], + [115.361591, 33.298497], + [115.335105, 33.297997], + [115.340033, 33.260973], + [115.300613, 33.204407], + [115.303692, 33.149809], + [115.289526, 33.131769], + [115.245178, 33.135778], + [115.194671, 33.120743], + [115.168186, 33.088658], + [115.041302, 33.086653], + [114.990795, 33.102195], + [114.966158, 33.147304], + [114.932897, 33.153817], + [114.902716, 33.129764], + [114.897172, 33.086653], + [114.913187, 33.083143], + [114.925506, 33.016928], + [114.891629, 33.020441], + [114.883006, 32.990328], + [114.916266, 32.971251], + [114.943368, 32.935094], + [115.009273, 32.940117], + [115.035143, 32.932582], + [115.029599, 32.906962], + [115.139237, 32.897917], + [115.155867, 32.864747], + [115.197135, 32.856201], + [115.189744, 32.812452], + [115.211301, 32.785791], + [115.189744, 32.770695], + [115.179273, 32.726402], + [115.182968, 32.666973], + [115.20083, 32.591876], + [115.24333, 32.593388], + [115.267352, 32.578261], + [115.30554, 32.583303], + [115.304924, 32.553042], + [115.411482, 32.575235], + [115.409018, 32.549007], + [115.497713, 32.492489], + [115.5088, 32.468761], + [115.510648, 32.468761], + [115.510648, 32.468256], + [115.510648, 32.467751], + [115.509416, 32.466741], + [115.522967, 32.441997], + [115.57101, 32.419266], + [115.604271, 32.425833], + [115.626445, 32.40512], + [115.657857, 32.428864], + [115.667712, 32.409667], + [115.704669, 32.495013], + [115.742241, 32.476335], + [115.771806, 32.505108], + [115.789052, 32.468761], + [115.861117, 32.537403], + [115.891298, 32.576243], + [115.910393, 32.567165], + [115.8759, 32.542448], + [115.845719, 32.501575], + [115.883291, 32.487946], + [115.865429, 32.458662], + [115.899306, 32.390971], + [115.912856, 32.227596], + [115.941805, 32.166318], + [115.922095, 32.049725], + [115.928871, 32.003046], + [115.909161, 31.94314], + [115.920248, 31.920285], + [115.894994, 31.8649], + [115.893762, 31.832365], + [115.914704, 31.814567], + [115.886371, 31.776418], + [115.851878, 31.786593], + [115.808147, 31.770313], + [115.808147, 31.770313], + [115.767495, 31.78761], + [115.731154, 31.76726], + [115.676336, 31.778453], + [115.553764, 31.69549], + [115.534054, 31.698545], + [115.495249, 31.673083], + [115.476771, 31.643028], + [115.485394, 31.608885], + [115.439815, 31.588496], + [115.415793, 31.525771], + [115.371446, 31.495668], + [115.389924, 31.450241], + [115.373909, 31.405813], + [115.393004, 31.389977], + [115.372062, 31.349098], + [115.40717, 31.337854], + [115.443511, 31.344498], + [115.473076, 31.265242], + [115.507568, 31.267799], + [115.539597, 31.231985], + [115.540213, 31.194621], + [115.585793, 31.143926], + [115.603655, 31.17363], + [115.655394, 31.211002], + [115.700973, 31.201276], + [115.778582, 31.112164], + [115.797676, 31.128047], + [115.837712, 31.127022], + [115.867277, 31.147512], + [115.887603, 31.10909], + [115.939958, 31.071678], + [115.938726, 31.04707], + [116.006479, 31.034764], + [116.015102, 31.011685], + [116.058834, 31.012711], + [116.071769, 30.956787], + [116.03974, 30.957813], + [115.976298, 30.931636], + [115.932566, 30.889532], + [115.865429, 30.864364], + [115.848799, 30.828397], + [115.863581, 30.815549], + [115.851262, 30.756938], + [115.782893, 30.751795], + [115.762567, 30.685426], + [115.81369, 30.637035], + [115.819234, 30.597893], + [115.848799, 30.602014], + [115.876516, 30.582438], + [115.887603, 30.542758], + [115.910393, 30.519046], + [115.894994, 30.452517], + [115.921479, 30.416397], + [115.885139, 30.379747], + [115.91532, 30.337919], + [115.903001, 30.31364], + [115.985537, 30.290905], + [115.997856, 30.252657], + [116.065609, 30.204569], + [116.055754, 30.180774], + [116.088399, 30.110391], + [116.078544, 30.062233], + [116.091479, 30.036331], + [116.073616, 29.969993], + [116.128435, 29.897904], + [116.13521, 29.819532], + [116.172783, 29.828358], + [116.227601, 29.816936], + [116.250391, 29.785777], + [116.280572, 29.788893], + [116.342782, 29.835626], + [116.467818, 29.896347], + [116.525716, 29.897385], + [116.552201, 29.909836], + [116.585462, 30.045657], + [116.620571, 30.073109], + [116.666766, 30.076734], + [116.720353, 30.053945], + [116.747454, 30.057053], + [116.783794, 30.030632], + [116.802889, 29.99643], + [116.830606, 30.004723], + [116.83307, 29.95755], + [116.868794, 29.980361], + [116.900207, 29.949253], + [116.882961, 29.893753], + [116.780715, 29.792529], + [116.762237, 29.802396], + [116.673541, 29.709916], + [116.698795, 29.707836], + [116.70557, 29.69692], + [116.706802, 29.6964], + [116.704954, 29.688602], + [116.680317, 29.681323], + [116.651983, 29.637118], + [116.716657, 29.590813], + [116.721585, 29.564789], + [116.760389, 29.599139], + [116.780715, 29.569994], + [116.849084, 29.57624], + [116.873722, 29.609546], + [116.939627, 29.648561], + [116.974736, 29.657403], + [116.996294, 29.683403], + [117.041873, 29.680803], + [117.112706, 29.711995], + [117.108395, 29.75201], + [117.136728, 29.775388], + [117.123177, 29.798761], + [117.073286, 29.831992], + [117.127489, 29.86158], + [117.129952, 29.89946], + [117.171836, 29.920729], + [117.2168, 29.926953], + [117.246365, 29.915023], + [117.261763, 29.880781], + [117.25314, 29.834588], + [117.29256, 29.822647], + [117.338756, 29.848085], + [117.359082, 29.812782], + [117.382487, 29.840818], + [117.415132, 29.85068], + [117.408973, 29.802396], + [117.455168, 29.749412], + [117.453936, 29.688082], + [117.490277, 29.660003], + [117.530313, 29.654282], + [117.523538, 29.630356], + [117.543248, 29.588731], + [117.608537, 29.591333], + [117.647957, 29.614749], + [117.678754, 29.595496], + [117.690457, 29.555939], + [117.729877, 29.550213], + [117.795167, 29.570515], + [117.872775, 29.54761], + [117.933753, 29.549172], + [118.00397, 29.578322], + [118.042774, 29.566351], + [118.050782, 29.542924], + [118.095129, 29.534072], + [118.143788, 29.489803], + [118.127774, 29.47209], + [118.136397, 29.418932], + [118.193064, 29.395472], + [118.248498, 29.431443], + [118.316252, 29.422581], + [118.306396, 29.479384], + [118.329802, 29.495012], + [118.347664, 29.474174], + [118.381541, 29.504909], + [118.496106, 29.519492], + [118.500417, 29.57572], + [118.532446, 29.588731], + [118.573714, 29.638159], + [118.61991, 29.654282], + [118.647011, 29.64336], + [118.700598, 29.706277], + [118.744945, 29.73902], + [118.740634, 29.814859], + [118.841032, 29.891159], + [118.838568, 29.934733], + [118.894619, 29.937845], + [118.902626, 30.029078], + [118.878604, 30.064822], + [118.873677, 30.11505], + [118.895234, 30.148694], + [118.852119, 30.149729], + [118.852735, 30.166805], + [118.929727, 30.2025], + [118.905089, 30.216464], + [118.877988, 30.282637], + [118.880452, 30.31519], + [118.954365, 30.360126], + [118.988857, 30.332237], + [119.06277, 30.304856], + [119.091719, 30.323972], + [119.126828, 30.304856], + [119.201356, 30.290905], + [119.236465, 30.297106], + [119.246936, 30.341018], + [119.277117, 30.341018], + [119.326392, 30.372002], + [119.349182, 30.349281], + [119.402768, 30.374584], + [119.36766, 30.38491], + [119.335015, 30.448389], + [119.336247, 30.508734], + [119.326392, 30.532964], + [119.272189, 30.510281], + [119.237081, 30.546881], + [119.265414, 30.574709], + [119.238929, 30.609225], + [119.323312, 30.630341], + [119.343022, 30.664322], + [119.39045, 30.685941], + [119.408312, 30.645274], + [119.444652, 30.650422], + [119.482841, 30.704467], + [119.479761, 30.772365], + [119.527188, 30.77905], + [119.55429, 30.825828], + [119.575847, 30.829939], + [119.557369, 30.874124], + [119.563529, 30.919315], + [119.582007, 30.932149], + [119.580159, 30.967051], + [119.633746, 31.019379], + [119.629434, 31.085517], + [119.649144, 31.104991], + [119.623891, 31.130096], + [119.599869, 31.10909], + [119.532732, 31.159291], + [119.461283, 31.156219], + [119.439109, 31.177214], + [119.391682, 31.174142], + [119.360269, 31.213049], + [119.374435, 31.258591], + [119.350414, 31.301043], + [119.338095, 31.259103], + [119.294363, 31.263195], + [119.266646, 31.250405], + [119.198277, 31.270357], + [119.197661, 31.295418], + [119.158241, 31.294907], + [119.107118, 31.250917], + [119.10527, 31.235055], + [119.014727, 31.241707], + [118.984546, 31.237102], + [118.870597, 31.242219], + [118.794836, 31.229426], + [118.756648, 31.279564], + [118.726467, 31.282121], + [118.720924, 31.322518], + [118.745561, 31.372606], + [118.767735, 31.363919], + [118.824401, 31.375672], + [118.852119, 31.393553], + [118.883532, 31.500261], + [118.857046, 31.506384], + [118.865669, 31.519139], + [118.885995, 31.519139], + [118.881684, 31.564023], + [118.858894, 31.623665], + [118.802844, 31.619078], + [118.773894, 31.682759], + [118.748025, 31.675629], + [118.736322, 31.633347], + [118.643315, 31.649651], + [118.643315, 31.671555], + [118.697518, 31.709747], + [118.653786, 31.73011], + [118.641467, 31.75861], + [118.571866, 31.746397], + [118.5577, 31.73011], + [118.521975, 31.743343], + [118.533678, 31.76726], + [118.481939, 31.778453], + [118.504729, 31.841516], + [118.466541, 31.857784], + [118.472084, 31.879639], + [118.363679, 31.930443], + [118.389548, 31.985281], + [118.394476, 32.076098], + [118.433896, 32.086746], + [118.501033, 32.121726], + [118.49549, 32.165304], + [118.510888, 32.194176], + [118.643931, 32.209875], + [118.674728, 32.250375], + [118.657482, 32.30148], + [118.703061, 32.328792], + [118.685199, 32.403604], + [118.691359, 32.472295], + [118.628533, 32.467751], + [118.592192, 32.481383], + [118.608823, 32.536899], + [118.564475, 32.562122], + [118.568787, 32.585825], + [118.59712, 32.600951], + [118.632844, 32.578261], + [118.658714, 32.594397], + [118.688895, 32.588346], + [118.719076, 32.614059], + [118.719076, 32.614059], + [118.73509, 32.58885], + [118.757264, 32.603976], + [118.784981, 32.582295], + [118.820706, 32.60448], + [118.84288, 32.56767], + [118.908169, 32.59238], + [118.890923, 32.553042], + [118.92172, 32.557078], + [118.922336, 32.557078], + [118.92172, 32.557078], + [118.922336, 32.557078], + [118.975923, 32.505108], + [119.041212, 32.515201], + [119.084944, 32.452602], + [119.142226, 32.499556], + [119.168096, 32.536394], + [119.152697, 32.557582], + [119.22045, 32.576748], + [119.230921, 32.607001], + [119.208748, 32.641276], + [119.211827, 32.708275], + [119.184726, 32.825529], + [119.113277, 32.823014], + [119.054763, 32.8748], + [119.020886, 32.955685], + [118.993169, 32.958196], + [118.934039, 32.93861], + [118.892771, 32.941121], + [118.89585, 32.957694], + [118.89585, 32.957694], + [118.849039, 32.956689], + [118.846575, 32.922034], + [118.821322, 32.920527], + [118.810235, 32.853687], + [118.743097, 32.853184], + [118.743097, 32.853184], + [118.73817, 32.772708], + [118.756648, 32.737477], + [118.707373, 32.72036], + [118.642699, 32.744525], + [118.572482, 32.719856], + [118.560163, 32.729926], + [118.483787, 32.721367], + [118.450526, 32.743518], + [118.411106, 32.715828], + [118.375382, 32.718849], + [118.363063, 32.770695], + [118.334114, 32.761637], + [118.300237, 32.783275], + [118.301469, 32.846145], + [118.250346, 32.848157], + [118.2331, 32.914498], + [118.252194, 32.936601], + [118.291614, 32.946143], + [118.303933, 32.96874], + [118.26944, 32.969242], + [118.244803, 32.998359], + [118.243571, 33.027967], + [118.219549, 33.114227], + [118.217085, 33.191888], + [118.178281, 33.217926], + [118.149332, 33.169348], + [118.038463, 33.134776], + [118.037231, 33.152314], + [117.988572, 33.180869], + [117.977485, 33.226437], + [117.942376, 33.224936], + [117.939297, 33.262475], + [117.974405, 33.279487], + [117.992883, 33.333005], + [118.029224, 33.374995], + [118.016905, 33.402978], + [118.027376, 33.455421], + [118.050782, 33.491863], + [118.107448, 33.475391], + [118.117919, 33.594615], + [118.112376, 33.617045], + [118.16781, 33.663381], + [118.161035, 33.735576], + [118.117919, 33.766427], + [118.065564, 33.76593], + [118.019985, 33.738562], + [117.972557, 33.74951], + [117.901724, 33.720146], + [117.843826, 33.736074], + [117.791471, 33.733585], + [117.750203, 33.710688], + [117.72495, 33.74951], + [117.739732, 33.758467], + [117.759442, 33.874318], + [117.753899, 33.891211], + [117.715095, 33.879287], + [117.672595, 33.934916], + [117.671363, 33.992494], + [117.629479, 34.028708], + [117.612849, 34.000433], + [117.569117, 33.985051], + [117.543248, 34.038627], + [117.514914, 34.060941], + [117.435458, 34.028212], + [117.404045, 34.03218], + [117.357234, 34.088205], + [117.311654, 34.067882], + [117.277162, 34.078787], + [117.257452, 34.065899], + [117.192162, 34.068873], + [117.130568, 34.101586], + [117.123793, 34.128342], + [117.046801, 34.151622], + [117.025243, 34.167469], + [117.051112, 34.221425], + [116.969192, 34.283753], + [116.983359, 34.348011], + [116.960569, 34.363821], + [116.969192, 34.389012], + [116.909446, 34.408271], + [116.828142, 34.389012], + [116.782563, 34.429993], + [116.773939, 34.453683], + [116.722816, 34.472434], + [116.662454, 34.472927], + [116.592237, 34.493646], + [116.594085, 34.511894], + [116.490607, 34.573513], + [116.477057, 34.614896], + [116.432709, 34.630163], + [116.430245, 34.650843], + [116.374195, 34.640011], + [116.334159, 34.620806], + [116.32492, 34.601104], + [116.286116, 34.608986], + [116.247927, 34.551829], + [116.196804, 34.575977], + [116.191261, 34.535561], + [116.204196, 34.508442], + [116.178326, 34.496112], + [116.162312, 34.459605], + [116.178942, 34.430487], + [116.215898, 34.403333], + [116.213435, 34.382098], + [116.255934, 34.376665], + [116.301514, 34.342082], + [116.357564, 34.319843], + [116.372347, 34.26595], + [116.409303, 34.273863], + [116.409303, 34.273863], + [116.456731, 34.268917], + [116.516477, 34.296114], + [116.562056, 34.285731], + [116.582382, 34.266444], + [116.545426, 34.241711], + [116.542962, 34.203608], + [116.565752, 34.16945], + [116.536187, 34.151127], + [116.52818, 34.122892], + [116.576223, 34.068873], + [116.576223, 34.068873], + [116.599629, 34.014324], + [116.599629, 34.014324] + ] + ], + [ + [ + [118.865669, 31.519139], + [118.857046, 31.506384], + [118.883532, 31.500261], + [118.885995, 31.519139], + [118.865669, 31.519139] + ] + ], + [ + [ + [116.698795, 29.707836], + [116.673541, 29.709916], + [116.653831, 29.694841], + [116.680317, 29.681323], + [116.704954, 29.688602], + [116.706802, 29.6964], + [116.70557, 29.69692], + [116.698795, 29.707836] + ] + ], + [ + [ + [115.5088, 32.468761], + [115.509416, 32.466741], + [115.510648, 32.467751], + [115.510648, 32.468256], + [115.510648, 32.468761], + [115.5088, 32.468761] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 350000, + "name": "Fujian", + "center": [119.306239, 26.075302], + "centroid": [118.006468, 26.069925], + "childrenNum": 9, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 12, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [119.004872, 24.970009], + [118.989473, 24.973807], + [119.023966, 25.04377], + [119.016575, 25.058409], + [118.974691, 25.024792], + [118.945126, 25.028588], + [118.892155, 25.092558], + [118.974691, 25.115319], + [118.951901, 25.15162], + [118.985162, 25.168954], + [118.985162, 25.19495], + [118.942046, 25.211195], + [118.940198, 25.21715], + [118.943278, 25.221482], + [118.903242, 25.239347], + [118.900162, 25.242595], + [118.919256, 25.248008], + [118.91556, 25.256668], + [118.918024, 25.25721], + [118.956212, 25.272905], + [118.996864, 25.266411], + [118.975307, 25.237723], + [118.990089, 25.20199], + [119.055379, 25.219316], + [119.074473, 25.211195], + [119.054147, 25.168412], + [119.032589, 25.17437], + [119.028893, 25.139702], + [119.06585, 25.102855], + [119.075705, 25.099604], + [119.134219, 25.106107], + [119.107118, 25.075214], + [119.119436, 25.012861], + [119.146538, 25.056782], + [119.165632, 25.145661], + [119.137299, 25.15487], + [119.108349, 25.193867], + [119.131755, 25.223106], + [119.190269, 25.175995], + [119.231537, 25.188993], + [119.26911, 25.159746], + [119.314689, 25.190076], + [119.294979, 25.237182], + [119.331935, 25.230685], + [119.380595, 25.250173], + [119.333167, 25.287516], + [119.299291, 25.328634], + [119.247552, 25.333502], + [119.240776, 25.316733], + [119.218603, 25.368115], + [119.14469, 25.388121], + [119.151465, 25.426503], + [119.191501, 25.424341], + [119.232153, 25.442176], + [119.219834, 25.468654], + [119.256175, 25.488643], + [119.275269, 25.476758], + [119.26295, 25.428124], + [119.288204, 25.410827], + [119.353493, 25.411908], + [119.343638, 25.472436], + [119.359037, 25.521592], + [119.400921, 25.493505], + [119.45266, 25.493505], + [119.438493, 25.412449], + [119.463131, 25.448661], + [119.491464, 25.443257], + [119.48592, 25.418935], + [119.507478, 25.396231], + [119.486536, 25.369737], + [119.548746, 25.365952], + [119.578927, 25.400556], + [119.555521, 25.429205], + [119.577695, 25.445959], + [119.59063, 25.398394], + [119.582623, 25.374063], + [119.597405, 25.334584], + [119.649144, 25.342697], + [119.665159, 25.3719], + [119.656535, 25.396772], + [119.670086, 25.435691], + [119.622659, 25.434069], + [119.675014, 25.468113], + [119.682405, 25.445959], + [119.688564, 25.441095], + [119.773564, 25.395691], + [119.764325, 25.433529], + [119.804977, 25.457847], + [119.866571, 25.455145], + [119.864107, 25.469734], + [119.862875, 25.474597], + [119.811136, 25.507009], + [119.81668, 25.532393], + [119.861027, 25.531313], + [119.883817, 25.546432], + [119.831462, 25.579905], + [119.843165, 25.597717], + [119.790194, 25.614447], + [119.785883, 25.66786], + [119.700267, 25.616606], + [119.683637, 25.592859], + [119.716898, 25.551292], + [119.715666, 25.51187], + [119.680557, 25.497827], + [119.675014, 25.475137], + [119.634362, 25.475137], + [119.611572, 25.519972], + [119.616499, 25.556691], + [119.586934, 25.59232], + [119.534579, 25.585303], + [119.541355, 25.6247], + [119.478529, 25.631715], + [119.472986, 25.662466], + [119.543819, 25.684581], + [119.602949, 25.68512], + [119.602949, 25.714779], + [119.626354, 25.723406], + [119.628202, 25.87212], + [119.638057, 25.889888], + [119.69534, 25.904424], + [119.723673, 26.011503], + [119.700267, 26.032477], + [119.668854, 26.026024], + [119.654688, 26.090002], + [119.618963, 26.11956], + [119.604181, 26.168985], + [119.664543, 26.202282], + [119.676246, 26.262943], + [119.7711, 26.285481], + [119.802513, 26.268846], + [119.806825, 26.307479], + [119.845013, 26.323036], + [119.862875, 26.307479], + [119.904143, 26.308552], + [119.95465, 26.352534], + [119.946027, 26.374519], + [119.893672, 26.355752], + [119.835774, 26.434019], + [119.83639, 26.454381], + [119.788346, 26.583435], + [119.740303, 26.610727], + [119.670086, 26.618218], + [119.605412, 26.595744], + [119.577695, 26.622498], + [119.619579, 26.649246], + [119.637441, 26.703256], + [119.664543, 26.726243], + [119.711354, 26.686681], + [119.833926, 26.690959], + [119.864107, 26.671174], + [119.873962, 26.642827], + [119.908455, 26.661547], + [119.899216, 26.693098], + [119.938636, 26.747088], + [119.942947, 26.784492], + [120.052584, 26.786629], + [120.061824, 26.768997], + [119.99407, 26.720363], + [119.969433, 26.686681], + [119.972512, 26.654594], + [119.949107, 26.624638], + [119.901679, 26.624638], + [119.851788, 26.595209], + [119.828383, 26.524013], + [119.867187, 26.509019], + [119.947875, 26.56042], + [119.93802, 26.576478], + [119.967585, 26.597885], + [120.007621, 26.595744], + [120.063671, 26.627848], + [120.093852, 26.613938], + [120.1382, 26.638012], + [120.110483, 26.692563], + [120.162222, 26.717691], + [120.151135, 26.750829], + [120.106787, 26.752966], + [120.136352, 26.797847], + [120.103707, 26.794642], + [120.102476, 26.82669], + [120.073526, 26.823485], + [120.054432, 26.863533], + [120.117874, 26.882751], + [120.126497, 26.920644], + [120.130193, 26.917976], + [120.1807, 26.920644], + [120.233055, 26.907837], + [120.25954, 26.982526], + [120.279866, 26.987326], + [120.275554, 27.027315], + [120.29588, 27.035845], + [120.282946, 27.089671], + [120.391967, 27.081146], + [120.403054, 27.10086], + [120.461568, 27.142407], + [120.404286, 27.204166], + [120.401822, 27.250996], + [120.430155, 27.258976], + [120.343924, 27.363199], + [120.340844, 27.399867], + [120.273091, 27.38924], + [120.26262, 27.432804], + [120.221352, 27.420055], + [120.134504, 27.420055], + [120.136968, 27.402523], + [120.096316, 27.390302], + [120.052584, 27.338747], + [120.026099, 27.344063], + [120.008237, 27.375423], + [119.960194, 27.365857], + [119.938636, 27.329709], + [119.843165, 27.300464], + [119.768636, 27.307909], + [119.782187, 27.330241], + [119.739687, 27.362668], + [119.750774, 27.373829], + [119.711354, 27.403054], + [119.685485, 27.438646], + [119.703347, 27.446613], + [119.70889, 27.514042], + [119.690412, 27.537394], + [119.659615, 27.540578], + [119.675014, 27.574534], + [119.630666, 27.582491], + [119.626354, 27.620676], + [119.644217, 27.663619], + [119.606028, 27.674749], + [119.541971, 27.666799], + [119.501319, 27.649837], + [119.501935, 27.610601], + [119.466826, 27.526249], + [119.438493, 27.508734], + [119.416935, 27.539517], + [119.360269, 27.524657], + [119.334399, 27.480067], + [119.285124, 27.457766], + [119.26911, 27.42218], + [119.224146, 27.416868], + [119.14777, 27.424836], + [119.121284, 27.438115], + [119.129907, 27.475289], + [119.092335, 27.466262], + [119.03998, 27.478475], + [119.020886, 27.498118], + [118.983314, 27.498649], + [118.986393, 27.47582], + [118.955597, 27.4498], + [118.907553, 27.460952], + [118.869365, 27.540047], + [118.909401, 27.568168], + [118.913713, 27.619616], + [118.879836, 27.667859], + [118.873677, 27.733563], + [118.829329, 27.847921], + [118.818242, 27.916689], + [118.753568, 27.947885], + [118.730163, 27.970615], + [118.733858, 28.027684], + [118.719076, 28.063601], + [118.767735, 28.10584], + [118.802228, 28.117453], + [118.805923, 28.154923], + [118.771431, 28.188687], + [118.804075, 28.207675], + [118.802228, 28.240368], + [118.756032, 28.252493], + [118.719692, 28.312047], + [118.699366, 28.309939], + [118.674728, 28.27147], + [118.651322, 28.277267], + [118.595272, 28.258292], + [118.588497, 28.282538], + [118.493026, 28.262509], + [118.490562, 28.238259], + [118.444367, 28.253548], + [118.433896, 28.288335], + [118.424041, 28.291497], + [118.314404, 28.221913], + [118.339041, 28.193962], + [118.375382, 28.186577], + [118.361215, 28.155978], + [118.356288, 28.091586], + [118.242339, 28.075746], + [118.199839, 28.049869], + [118.153644, 28.062016], + [118.120999, 28.041946], + [118.129006, 28.017118], + [118.094513, 28.003909], + [118.096977, 27.970615], + [117.999043, 27.991227], + [117.965166, 27.962687], + [117.942992, 27.974315], + [117.910963, 27.949471], + [117.856145, 27.94577], + [117.78716, 27.896063], + [117.788392, 27.855858], + [117.740348, 27.800286], + [117.704624, 27.834162], + [117.68245, 27.823577], + [117.649805, 27.851625], + [117.609769, 27.863265], + [117.556182, 27.966387], + [117.52169, 27.982243], + [117.477958, 27.930966], + [117.453936, 27.939955], + [117.407741, 27.893948], + [117.366473, 27.88231], + [117.341836, 27.855858], + [117.334444, 27.8876], + [117.280242, 27.871201], + [117.276546, 27.847921], + [117.303031, 27.833103], + [117.296256, 27.764282], + [117.245133, 27.71926], + [117.205097, 27.714492], + [117.204481, 27.683759], + [117.174916, 27.677399], + [117.114554, 27.692238], + [117.096076, 27.667329], + [117.11209, 27.645596], + [117.094228, 27.627569], + [117.065279, 27.665739], + [117.040641, 27.669979], + [117.003685, 27.625449], + [117.024627, 27.592569], + [117.01662, 27.563393], + [117.054808, 27.5427], + [117.076982, 27.566046], + [117.103467, 27.533149], + [117.110242, 27.458828], + [117.133032, 27.42218], + [117.107163, 27.393491], + [117.104699, 27.330773], + [117.140423, 27.322798], + [117.136728, 27.303123], + [117.171836, 27.29036], + [117.149662, 27.241419], + [117.044953, 27.146667], + [117.05296, 27.100327], + [116.967344, 27.061962], + [116.936547, 27.019319], + [116.910062, 27.034779], + [116.851548, 27.009188], + [116.817671, 27.018252], + [116.679085, 26.978259], + [116.632889, 26.933984], + [116.602092, 26.888623], + [116.548506, 26.84004], + [116.543578, 26.803723], + [116.557745, 26.773806], + [116.515245, 26.720898], + [116.520172, 26.684543], + [116.566368, 26.650315], + [116.553433, 26.575942], + [116.539267, 26.559349], + [116.597165, 26.512768], + [116.610716, 26.476882], + [116.638433, 26.477418], + [116.608252, 26.429732], + [116.601476, 26.372911], + [116.553433, 26.365404], + [116.553433, 26.400253], + [116.519557, 26.410437], + [116.499846, 26.361651], + [116.459194, 26.345026], + [116.437021, 26.308016], + [116.412999, 26.297822], + [116.385282, 26.238253], + [116.400064, 26.202819], + [116.392057, 26.171133], + [116.435789, 26.159854], + [116.476441, 26.172745], + [116.489375, 26.113649], + [116.384666, 26.030864], + [116.360028, 25.991601], + [116.369883, 25.963088], + [116.326152, 25.956631], + [116.303362, 25.924341], + [116.258398, 25.902809], + [116.225138, 25.908731], + [116.17771, 25.894195], + [116.132131, 25.860273], + [116.131515, 25.824185], + [116.18079, 25.778926], + [116.129667, 25.758985], + [116.106877, 25.701299], + [116.067457, 25.703995], + [116.068689, 25.646282], + [116.041588, 25.62416], + [116.063145, 25.56317], + [116.040356, 25.548052], + [116.03666, 25.514571], + [116.005247, 25.490264], + [116.023109, 25.435691], + [115.992928, 25.374063], + [116.008327, 25.319437], + [115.987385, 25.290221], + [115.949813, 25.292386], + [115.930719, 25.236099], + [115.855574, 25.20957], + [115.860501, 25.165704], + [115.888219, 25.128866], + [115.880212, 25.092016], + [115.908545, 25.084428], + [115.928255, 25.050276], + [115.873436, 25.019911], + [115.925175, 24.960786], + [115.870356, 24.959701], + [115.89253, 24.936911], + [115.907929, 24.923343], + [115.985537, 24.899461], + [116.015102, 24.905975], + [116.068073, 24.850053], + [116.153073, 24.846795], + [116.191877, 24.877203], + [116.221442, 24.829959], + [116.251007, 24.82507], + [116.244232, 24.793563], + [116.297202, 24.801712], + [116.345862, 24.828872], + [116.363724, 24.87123], + [116.395137, 24.877746], + [116.417927, 24.840821], + [116.381586, 24.82507], + [116.375427, 24.803885], + [116.419158, 24.767482], + [116.416079, 24.744113], + [116.44626, 24.714216], + [116.485064, 24.720196], + [116.517709, 24.652225], + [116.506622, 24.621218], + [116.530027, 24.604895], + [116.570679, 24.621762], + [116.600861, 24.654401], + [116.623034, 24.64189], + [116.667382, 24.658752], + [116.777635, 24.679418], + [116.815207, 24.654944], + [116.761005, 24.583128], + [116.759157, 24.545572], + [116.796729, 24.502014], + [116.83307, 24.496568], + [116.860787, 24.460075], + [116.839229, 24.442097], + [116.903903, 24.369614], + [116.895895, 24.350533], + [116.919301, 24.321087], + [116.914374, 24.287817], + [116.938395, 24.28127], + [116.933468, 24.220157], + [116.956257, 24.216883], + [116.998757, 24.179217], + [116.9347, 24.126794], + [116.930388, 24.064514], + [116.953178, 24.008218], + [116.981511, 23.999471], + [116.976583, 23.931659], + [116.955642, 23.922359], + [116.981511, 23.855602], + [117.012308, 23.855054], + [117.019083, 23.801952], + [117.048032, 23.758687], + [117.055424, 23.694038], + [117.123793, 23.647448], + [117.147199, 23.654027], + [117.192778, 23.629356], + [117.192778, 23.5619], + [117.291328, 23.571225], + [117.302415, 23.550379], + [117.387415, 23.555317], + [117.463791, 23.584937], + [117.454552, 23.628259], + [117.493357, 23.642514], + [117.501364, 23.70445], + [117.54448, 23.715956], + [117.601762, 23.70171], + [117.660276, 23.789357], + [117.651653, 23.815093], + [117.671979, 23.878041], + [117.691073, 23.888985], + [117.762522, 23.886796], + [117.792703, 23.906494], + [117.807486, 23.947521], + [117.864768, 24.004938], + [117.910347, 24.012045], + [117.927594, 24.039922], + [117.936217, 24.100029], + [118.000275, 24.152462], + [118.019369, 24.197232], + [118.074803, 24.225615], + [118.115455, 24.229435], + [118.158571, 24.269814], + [118.112376, 24.357075], + [118.081579, 24.35653], + [118.088354, 24.408858], + [118.048934, 24.418122], + [118.084042, 24.528695], + [118.121615, 24.570067], + [118.150564, 24.583673], + [118.169042, 24.559725], + [118.242955, 24.51236], + [118.375382, 24.536317], + [118.363679, 24.567889], + [118.444367, 24.614689], + [118.512736, 24.60816], + [118.557084, 24.572788], + [118.558316, 24.51236], + [118.614366, 24.521617], + [118.680272, 24.58204], + [118.687047, 24.63373], + [118.661178, 24.622306], + [118.652554, 24.653857], + [118.670417, 24.679962], + [118.703677, 24.665278], + [118.778822, 24.743569], + [118.786213, 24.77672], + [118.650707, 24.808774], + [118.647627, 24.843536], + [118.702445, 24.865258], + [118.69875, 24.848967], + [118.748641, 24.84245], + [118.807771, 24.870687], + [118.834256, 24.854397], + [118.864437, 24.887518], + [118.933423, 24.870687], + [118.988857, 24.878831], + [118.987009, 24.898375], + [118.932807, 24.906518], + [118.91864, 24.932569], + [118.945741, 24.954275], + [119.014111, 24.941252], + [119.032589, 24.961328], + [119.032589, 24.961871], + [119.007335, 24.963499], + [119.004872, 24.970009] + ] + ], + [ + [ + [118.412338, 24.514538], + [118.374766, 24.458986], + [118.318715, 24.486765], + [118.298389, 24.477506], + [118.31194, 24.424661], + [118.282375, 24.413218], + [118.329802, 24.382152], + [118.353208, 24.415398], + [118.405563, 24.427931], + [118.457918, 24.412128], + [118.477012, 24.437738], + [118.451758, 24.506915], + [118.412338, 24.514538] + ] + ], + [ + [ + [119.471138, 25.197116], + [119.507478, 25.183036], + [119.52534, 25.157579], + [119.549362, 25.161912], + [119.566608, 25.210112], + [119.540739, 25.20199], + [119.501319, 25.21715], + [119.473601, 25.259916], + [119.44342, 25.238806], + [119.444036, 25.20199], + [119.471138, 25.197116] + ] + ], + [ + [ + [119.580159, 25.627398], + [119.611572, 25.669479], + [119.580775, 25.650059], + [119.580159, 25.627398] + ] + ], + [ + [ + [119.976824, 26.191005], + [120.016244, 26.217316], + [119.998998, 26.235569], + [119.970665, 26.217852], + [119.976824, 26.191005] + ] + ], + [ + [ + [118.230636, 24.401228], + [118.273752, 24.441007], + [118.233716, 24.445911], + [118.230636, 24.401228] + ] + ], + [ + [ + [119.906607, 26.68989], + [119.926933, 26.664756], + [119.950954, 26.692563], + [119.906607, 26.68989] + ] + ], + [ + [ + [118.204151, 24.504737], + [118.191832, 24.536861], + [118.14502, 24.560814], + [118.093281, 24.540672], + [118.068644, 24.463344], + [118.084042, 24.435559], + [118.143173, 24.420847], + [118.19368, 24.463344], + [118.204151, 24.504737] + ] + ], + [ + [ + [119.929397, 26.134067], + [119.960194, 26.146961], + [119.919542, 26.172208], + [119.929397, 26.134067] + ] + ], + [ + [ + [119.642985, 26.129231], + [119.665159, 26.155556], + [119.62697, 26.173282], + [119.606028, 26.15287], + [119.642985, 26.129231] + ] + ], + [ + [ + [120.034106, 26.488667], + [120.066751, 26.498308], + [120.071679, 26.521336], + [120.035954, 26.515981], + [120.034106, 26.488667] + ] + ], + [ + [ + [119.662079, 25.646822], + [119.673782, 25.632794], + [119.718745, 25.634952], + [119.716898, 25.664624], + [119.662079, 25.646822] + ] + ], + [ + [ + [119.760629, 26.613402], + [119.776644, 26.600025], + [119.818527, 26.616613], + [119.796354, 26.630523], + [119.760629, 26.613402] + ] + ], + [ + [ + [120.135736, 26.550784], + [120.167149, 26.571661], + [120.153598, 26.604841], + [120.117874, 26.568984], + [120.135736, 26.550784] + ] + ], + [ + [ + [120.360554, 26.916909], + [120.394431, 26.933984], + [120.363018, 26.967592], + [120.327909, 26.963858], + [120.319286, 26.944654], + [120.360554, 26.916909] + ] + ], + [ + [ + [120.150519, 26.798916], + [120.140048, 26.795176], + [120.163454, 26.798381], + [120.161606, 26.803189], + [120.150519, 26.798916] + ] + ], + [ + [ + [119.668238, 26.628383], + [119.720593, 26.635873], + [119.758781, 26.659408], + [119.748926, 26.681334], + [119.712586, 26.6685], + [119.673782, 26.680799], + [119.651608, 26.657269], + [119.668238, 26.628383] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 360000, + "name": "Jiangxi", + "center": [115.892151, 28.676493], + "centroid": [115.732975, 27.636112], + "childrenNum": 11, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 13, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [118.193064, 29.395472], + [118.136397, 29.418932], + [118.127774, 29.47209], + [118.143788, 29.489803], + [118.095129, 29.534072], + [118.050782, 29.542924], + [118.042774, 29.566351], + [118.00397, 29.578322], + [117.933753, 29.549172], + [117.872775, 29.54761], + [117.795167, 29.570515], + [117.729877, 29.550213], + [117.690457, 29.555939], + [117.678754, 29.595496], + [117.647957, 29.614749], + [117.608537, 29.591333], + [117.543248, 29.588731], + [117.523538, 29.630356], + [117.530313, 29.654282], + [117.490277, 29.660003], + [117.453936, 29.688082], + [117.455168, 29.749412], + [117.408973, 29.802396], + [117.415132, 29.85068], + [117.382487, 29.840818], + [117.359082, 29.812782], + [117.338756, 29.848085], + [117.29256, 29.822647], + [117.25314, 29.834588], + [117.261763, 29.880781], + [117.246365, 29.915023], + [117.2168, 29.926953], + [117.171836, 29.920729], + [117.129952, 29.89946], + [117.127489, 29.86158], + [117.073286, 29.831992], + [117.123177, 29.798761], + [117.136728, 29.775388], + [117.108395, 29.75201], + [117.112706, 29.711995], + [117.041873, 29.680803], + [116.996294, 29.683403], + [116.974736, 29.657403], + [116.939627, 29.648561], + [116.873722, 29.609546], + [116.849084, 29.57624], + [116.780715, 29.569994], + [116.760389, 29.599139], + [116.721585, 29.564789], + [116.716657, 29.590813], + [116.651983, 29.637118], + [116.680317, 29.681323], + [116.653831, 29.694841], + [116.673541, 29.709916], + [116.762237, 29.802396], + [116.780715, 29.792529], + [116.882961, 29.893753], + [116.900207, 29.949253], + [116.868794, 29.980361], + [116.83307, 29.95755], + [116.830606, 30.004723], + [116.802889, 29.99643], + [116.783794, 30.030632], + [116.747454, 30.057053], + [116.720353, 30.053945], + [116.666766, 30.076734], + [116.620571, 30.073109], + [116.585462, 30.045657], + [116.552201, 29.909836], + [116.525716, 29.897385], + [116.467818, 29.896347], + [116.342782, 29.835626], + [116.280572, 29.788893], + [116.250391, 29.785777], + [116.227601, 29.816936], + [116.172783, 29.828358], + [116.13521, 29.819532], + [116.087167, 29.795125], + [116.049595, 29.761881], + [115.965827, 29.724469], + [115.909777, 29.723949], + [115.837096, 29.748373], + [115.762567, 29.793048], + [115.706517, 29.837703], + [115.667712, 29.850161], + [115.611662, 29.841337], + [115.51188, 29.840299], + [115.479235, 29.811224], + [115.470612, 29.739539], + [115.412714, 29.688602], + [115.355431, 29.649602], + [115.304924, 29.637118], + [115.28583, 29.618391], + [115.250722, 29.660003], + [115.176809, 29.654803], + [115.113367, 29.684963], + [115.117679, 29.655843], + [115.143548, 29.645961], + [115.120142, 29.597578], + [115.157099, 29.584568], + [115.154019, 29.510117], + [115.086266, 29.525741], + [115.087498, 29.560104], + [115.033295, 29.546568], + [115.00065, 29.572076], + [114.947679, 29.542924], + [114.966773, 29.522096], + [114.940288, 29.493971], + [114.900868, 29.505951], + [114.860216, 29.476258], + [114.888549, 29.436134], + [114.918114, 29.454374], + [114.90518, 29.473132], + [114.935977, 29.486678], + [114.947063, 29.465317], + [114.931049, 29.422581], + [114.895325, 29.397557], + [114.866375, 29.404335], + [114.812173, 29.383478], + [114.784455, 29.386086], + [114.759818, 29.363139], + [114.740724, 29.386607], + [114.67297, 29.395993], + [114.621847, 29.379828], + [114.589819, 29.352707], + [114.519602, 29.325578], + [114.466015, 29.324013], + [114.440145, 29.341752], + [114.376088, 29.322969], + [114.341595, 29.327665], + [114.307102, 29.365225], + [114.259059, 29.343839], + [114.252284, 29.23475], + [114.169748, 29.216993], + [114.063191, 29.204978], + [114.034857, 29.152204], + [113.98743, 29.126068], + [113.952321, 29.092604], + [113.94185, 29.047097], + [113.961561, 28.999476], + [113.955401, 28.978536], + [113.973879, 28.937692], + [114.008988, 28.955498], + [114.005292, 28.917788], + [114.028082, 28.891069], + [114.060111, 28.902596], + [114.056415, 28.872204], + [114.076741, 28.834464], + [114.124784, 28.843376], + [114.153734, 28.829221], + [114.137719, 28.779926], + [114.157429, 28.761566], + [114.122321, 28.623497], + [114.132176, 28.607211], + [114.08598, 28.558337], + [114.138335, 28.533629], + [114.15435, 28.507337], + [114.218407, 28.48472], + [114.217175, 28.466308], + [114.172212, 28.432632], + [114.214712, 28.403157], + [114.252284, 28.395787], + [114.2529, 28.319423], + [114.198081, 28.29097], + [114.182067, 28.249858], + [114.143879, 28.246694], + [114.109386, 28.205038], + [114.107538, 28.182885], + [114.068734, 28.171806], + [114.012068, 28.174972], + [113.992357, 28.161255], + [114.025002, 28.080499], + [114.047176, 28.057263], + [114.025618, 28.031382], + [113.970184, 28.041418], + [113.966488, 28.017646], + [113.936307, 28.018703], + [113.914133, 27.991227], + [113.864242, 28.004966], + [113.845148, 27.971672], + [113.822974, 27.982243], + [113.752141, 27.93361], + [113.72812, 27.874904], + [113.756453, 27.860091], + [113.763228, 27.799228], + [113.69917, 27.740979], + [113.696707, 27.71979], + [113.652359, 27.663619], + [113.607395, 27.625449], + [113.608627, 27.585143], + [113.579062, 27.545354], + [113.583374, 27.524657], + [113.627105, 27.49971], + [113.591381, 27.467855], + [113.59754, 27.428554], + [113.632033, 27.40518], + [113.605548, 27.38924], + [113.616635, 27.345658], + [113.657902, 27.347253], + [113.699786, 27.331836], + [113.72812, 27.350442], + [113.872865, 27.384988], + [113.872865, 27.346721], + [113.854387, 27.30525], + [113.872865, 27.289828], + [113.846996, 27.222262], + [113.779242, 27.137081], + [113.771851, 27.096598], + [113.803264, 27.099261], + [113.824206, 27.036378], + [113.86301, 27.018252], + [113.892575, 26.964925], + [113.927068, 26.948922], + [113.890112, 26.895562], + [113.877177, 26.859262], + [113.835909, 26.806394], + [113.853771, 26.769532], + [113.860546, 26.664221], + [113.912901, 26.613938], + [113.996669, 26.615543], + [114.019459, 26.587182], + [114.10877, 26.56952], + [114.07243, 26.480096], + [114.110002, 26.482775], + [114.090292, 26.455988], + [114.085364, 26.406149], + [114.062575, 26.406149], + [114.030546, 26.376664], + [114.047792, 26.337518], + [114.021307, 26.288701], + [114.029314, 26.266163], + [113.978807, 26.237716], + [113.972647, 26.20604], + [113.949242, 26.192616], + [113.962792, 26.150722], + [114.013299, 26.184023], + [114.088444, 26.168448], + [114.102611, 26.187783], + [114.181451, 26.214631], + [114.216559, 26.203355], + [114.237501, 26.152333], + [114.188842, 26.121172], + [114.10569, 26.097526], + [114.121089, 26.085702], + [114.087828, 26.06635], + [114.044096, 26.076564], + [114.008372, 26.015806], + [114.028082, 25.98138], + [114.028082, 25.893119], + [113.971416, 25.836036], + [113.961561, 25.77731], + [113.920293, 25.741197], + [113.913517, 25.701299], + [113.957249, 25.611749], + [113.983118, 25.599336], + [113.986198, 25.529153], + [113.962792, 25.528072], + [113.94493, 25.441635], + [114.003444, 25.442716], + [113.983118, 25.415152], + [114.050256, 25.36433], + [114.029314, 25.328093], + [114.017611, 25.273987], + [114.039785, 25.250714], + [114.055799, 25.277775], + [114.083517, 25.275611], + [114.115545, 25.302125], + [114.190074, 25.316733], + [114.204857, 25.29942], + [114.260291, 25.291845], + [114.2954, 25.299961], + [114.31511, 25.33837], + [114.382863, 25.317274], + [114.43029, 25.343779], + [114.438914, 25.376226], + [114.477718, 25.37136], + [114.541159, 25.416773], + [114.599674, 25.385959], + [114.63663, 25.324306], + [114.714238, 25.315651], + [114.743188, 25.274528], + [114.73518, 25.225813], + [114.693912, 25.213902], + [114.685905, 25.173287], + [114.73518, 25.155954], + [114.735796, 25.121822], + [114.664963, 25.10123], + [114.640326, 25.074129], + [114.604601, 25.083886], + [114.561485, 25.077382], + [114.532536, 25.022623], + [114.506051, 24.999844], + [114.45616, 24.99659], + [114.454928, 24.977062], + [114.395798, 24.951019], + [114.403189, 24.877746], + [114.378551, 24.861457], + [114.342211, 24.807145], + [114.336052, 24.749004], + [114.281849, 24.724001], + [114.27261, 24.700624], + [114.169132, 24.689749], + [114.19069, 24.656576], + [114.258443, 24.641346], + [114.289856, 24.619042], + [114.300943, 24.578775], + [114.363769, 24.582584], + [114.391486, 24.563535], + [114.403189, 24.497657], + [114.429058, 24.48622], + [114.534384, 24.559181], + [114.589819, 24.537406], + [114.627391, 24.576598], + [114.664963, 24.583673], + [114.704999, 24.525973], + [114.73826, 24.565168], + [114.729637, 24.608704], + [114.781376, 24.613057], + [114.827571, 24.588026], + [114.846665, 24.602719], + [114.868839, 24.562446], + [114.893477, 24.582584], + [114.909491, 24.661471], + [114.940288, 24.650049], + [115.00373, 24.679418], + [115.024672, 24.669085], + [115.057317, 24.703343], + [115.083802, 24.699537], + [115.104744, 24.667997], + [115.1842, 24.711498], + [115.258729, 24.728894], + [115.269816, 24.749548], + [115.306772, 24.758787], + [115.358511, 24.735416], + [115.372678, 24.774546], + [115.412714, 24.79302], + [115.476771, 24.762591], + [115.522967, 24.702799], + [115.555611, 24.683768], + [115.569778, 24.622306], + [115.605503, 24.62557], + [115.671408, 24.604895], + [115.68927, 24.545027], + [115.752712, 24.546116], + [115.785357, 24.567345], + [115.843871, 24.562446], + [115.840791, 24.584217], + [115.797676, 24.628834], + [115.780429, 24.663103], + [115.801371, 24.705517], + [115.769342, 24.708236], + [115.756408, 24.749004], + [115.776734, 24.774546], + [115.764415, 24.791933], + [115.790284, 24.856027], + [115.807531, 24.862543], + [115.824161, 24.909232], + [115.863581, 24.891318], + [115.861733, 24.863629], + [115.907313, 24.879917], + [115.885139, 24.898918], + [115.89253, 24.936911], + [115.870356, 24.959701], + [115.925175, 24.960786], + [115.873436, 25.019911], + [115.928255, 25.050276], + [115.908545, 25.084428], + [115.880212, 25.092016], + [115.888219, 25.128866], + [115.860501, 25.165704], + [115.855574, 25.20957], + [115.930719, 25.236099], + [115.949813, 25.292386], + [115.987385, 25.290221], + [116.008327, 25.319437], + [115.992928, 25.374063], + [116.023109, 25.435691], + [116.005247, 25.490264], + [116.03666, 25.514571], + [116.040356, 25.548052], + [116.063145, 25.56317], + [116.041588, 25.62416], + [116.068689, 25.646282], + [116.067457, 25.703995], + [116.106877, 25.701299], + [116.129667, 25.758985], + [116.18079, 25.778926], + [116.131515, 25.824185], + [116.132131, 25.860273], + [116.17771, 25.894195], + [116.225138, 25.908731], + [116.258398, 25.902809], + [116.303362, 25.924341], + [116.326152, 25.956631], + [116.369883, 25.963088], + [116.360028, 25.991601], + [116.384666, 26.030864], + [116.489375, 26.113649], + [116.476441, 26.172745], + [116.435789, 26.159854], + [116.392057, 26.171133], + [116.400064, 26.202819], + [116.385282, 26.238253], + [116.412999, 26.297822], + [116.437021, 26.308016], + [116.459194, 26.345026], + [116.499846, 26.361651], + [116.519557, 26.410437], + [116.553433, 26.400253], + [116.553433, 26.365404], + [116.601476, 26.372911], + [116.608252, 26.429732], + [116.638433, 26.477418], + [116.610716, 26.476882], + [116.597165, 26.512768], + [116.539267, 26.559349], + [116.553433, 26.575942], + [116.566368, 26.650315], + [116.520172, 26.684543], + [116.515245, 26.720898], + [116.557745, 26.773806], + [116.543578, 26.803723], + [116.548506, 26.84004], + [116.602092, 26.888623], + [116.632889, 26.933984], + [116.679085, 26.978259], + [116.817671, 27.018252], + [116.851548, 27.009188], + [116.910062, 27.034779], + [116.936547, 27.019319], + [116.967344, 27.061962], + [117.05296, 27.100327], + [117.044953, 27.146667], + [117.149662, 27.241419], + [117.171836, 27.29036], + [117.136728, 27.303123], + [117.140423, 27.322798], + [117.104699, 27.330773], + [117.107163, 27.393491], + [117.133032, 27.42218], + [117.110242, 27.458828], + [117.103467, 27.533149], + [117.076982, 27.566046], + [117.054808, 27.5427], + [117.01662, 27.563393], + [117.024627, 27.592569], + [117.003685, 27.625449], + [117.040641, 27.669979], + [117.065279, 27.665739], + [117.094228, 27.627569], + [117.11209, 27.645596], + [117.096076, 27.667329], + [117.114554, 27.692238], + [117.174916, 27.677399], + [117.204481, 27.683759], + [117.205097, 27.714492], + [117.245133, 27.71926], + [117.296256, 27.764282], + [117.303031, 27.833103], + [117.276546, 27.847921], + [117.280242, 27.871201], + [117.334444, 27.8876], + [117.341836, 27.855858], + [117.366473, 27.88231], + [117.407741, 27.893948], + [117.453936, 27.939955], + [117.477958, 27.930966], + [117.52169, 27.982243], + [117.556182, 27.966387], + [117.609769, 27.863265], + [117.649805, 27.851625], + [117.68245, 27.823577], + [117.704624, 27.834162], + [117.740348, 27.800286], + [117.788392, 27.855858], + [117.78716, 27.896063], + [117.856145, 27.94577], + [117.910963, 27.949471], + [117.942992, 27.974315], + [117.965166, 27.962687], + [117.999043, 27.991227], + [118.096977, 27.970615], + [118.094513, 28.003909], + [118.129006, 28.017118], + [118.120999, 28.041946], + [118.153644, 28.062016], + [118.199839, 28.049869], + [118.242339, 28.075746], + [118.356288, 28.091586], + [118.361215, 28.155978], + [118.375382, 28.186577], + [118.339041, 28.193962], + [118.314404, 28.221913], + [118.424041, 28.291497], + [118.433896, 28.288335], + [118.480091, 28.327325], + [118.455454, 28.384204], + [118.432048, 28.402104], + [118.456686, 28.424738], + [118.474548, 28.478934], + [118.414802, 28.497344], + [118.4302, 28.515225], + [118.412338, 28.55676], + [118.428352, 28.617193], + [118.428352, 28.617193], + [118.428352, 28.681267], + [118.403099, 28.702791], + [118.364295, 28.813491], + [118.300237, 28.826075], + [118.270056, 28.918836], + [118.195527, 28.904167], + [118.227556, 28.942406], + [118.165346, 28.986912], + [118.133933, 28.983771], + [118.115455, 29.009944], + [118.115455, 29.009944], + [118.097593, 28.998952], + [118.066796, 29.053898], + [118.076035, 29.074822], + [118.037847, 29.102017], + [118.045238, 29.149068], + [118.027992, 29.167882], + [118.042159, 29.210202], + [118.073571, 29.216993], + [118.077883, 29.290614], + [118.138861, 29.283828], + [118.178281, 29.297921], + [118.166578, 29.314099], + [118.205382, 29.343839], + [118.193064, 29.395472] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 370000, + "name": "Shandong", + "center": [117.000923, 36.675807], + "centroid": [118.187759, 36.376092], + "childrenNum": 16, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 14, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [116.374195, 34.640011], + [116.392057, 34.710391], + [116.363724, 34.715311], + [116.369267, 34.749247], + [116.403144, 34.756131], + [116.408071, 34.850972], + [116.445028, 34.895652], + [116.557745, 34.908905], + [116.613795, 34.922645], + [116.622418, 34.939818], + [116.677853, 34.939327], + [116.781331, 34.916757], + [116.789338, 34.975133], + [116.815823, 34.965324], + [116.821983, 34.929515], + [116.858323, 34.928533], + [116.922381, 34.894671], + [116.929156, 34.843114], + [116.966113, 34.844588], + [116.979047, 34.815113], + [116.95133, 34.81069], + [116.969192, 34.771864], + [117.022163, 34.759081], + [117.070206, 34.713835], + [117.061583, 34.675947], + [117.073286, 34.639026], + [117.104083, 34.648874], + [117.15151, 34.559222], + [117.139191, 34.526687], + [117.166293, 34.434435], + [117.248213, 34.451216], + [117.252524, 34.48674], + [117.27285, 34.499565], + [117.267923, 34.532603], + [117.303647, 34.542463], + [117.27285, 34.556757], + [117.311654, 34.561686], + [117.311654, 34.561686], + [117.32151, 34.566614], + [117.32151, 34.566614], + [117.325205, 34.573021], + [117.325205, 34.573021], + [117.370785, 34.584846], + [117.402813, 34.569571], + [117.402813, 34.550843], + [117.465023, 34.484767], + [117.53832, 34.467006], + [117.592523, 34.462566], + [117.609769, 34.490686], + [117.659044, 34.501044], + [117.684298, 34.547392], + [117.801942, 34.518798], + [117.791471, 34.583368], + [117.793935, 34.651827], + [117.902956, 34.644443], + [117.909732, 34.670533], + [117.951615, 34.678408], + [118.053861, 34.650843], + [118.084042, 34.655766], + [118.114839, 34.614404], + [118.079115, 34.569571], + [118.185056, 34.543942], + [118.16473, 34.50499], + [118.132702, 34.483287], + [118.177665, 34.45319], + [118.179513, 34.379628], + [118.217701, 34.379134], + [118.220165, 34.405802], + [118.277447, 34.404814], + [118.290382, 34.424563], + [118.379693, 34.415183], + [118.404947, 34.427525], + [118.416034, 34.473914], + [118.439439, 34.507949], + [118.424657, 34.595193], + [118.439439, 34.626223], + [118.473932, 34.623269], + [118.460997, 34.656258], + [118.545997, 34.705964], + [118.601431, 34.714327], + [118.607591, 34.694155], + [118.664257, 34.693663], + [118.690127, 34.678408], + [118.739402, 34.693663], + [118.783749, 34.723181], + [118.764039, 34.740396], + [118.719076, 34.745313], + [118.739402, 34.792508], + [118.772047, 34.794474], + [118.80038, 34.843114], + [118.805307, 34.87307], + [118.860742, 34.944233], + [118.86259, 35.025626], + [118.928495, 35.051106], + [118.942662, 35.040817], + [119.027045, 35.055516], + [119.114509, 35.055026], + [119.137915, 35.096167], + [119.217371, 35.106939], + [119.250016, 35.124562], + [119.286972, 35.115261], + [119.306066, 35.076578], + [119.354109, 35.080007], + [119.373819, 35.078538], + [119.428022, 35.121136], + [119.397841, 35.137777], + [119.411392, 35.231689], + [119.450812, 35.285443], + [119.493312, 35.318655], + [119.538275, 35.296678], + [119.543819, 35.347949], + [119.590014, 35.37284], + [119.579543, 35.406504], + [119.618963, 35.459655], + [119.663311, 35.562931], + [119.662079, 35.589215], + [119.718129, 35.615492], + [119.75139, 35.617924], + [119.772332, 35.578995], + [119.780339, 35.584835], + [119.792658, 35.615492], + [119.824071, 35.646136], + [119.83023, 35.620357], + [119.868419, 35.60868], + [119.925085, 35.637382], + [119.91215, 35.660725], + [119.950339, 35.729741], + [119.920157, 35.739943], + [119.926317, 35.759856], + [119.958346, 35.760342], + [120.01378, 35.714193], + [120.049505, 35.786562], + [120.032258, 35.812288], + [120.064287, 35.873414], + [120.112331, 35.885052], + [120.125265, 35.906868], + [120.152983, 35.907353], + [120.207801, 35.947575], + [120.169613, 35.888446], + [120.202258, 35.89184], + [120.209033, 35.917531], + [120.265699, 35.966468], + [120.30512, 35.971796], + [120.316206, 36.002304], + [120.289721, 36.017311], + [120.285409, 36.01247], + [120.249069, 35.992136], + [120.257076, 36.025055], + [120.198562, 35.995525], + [120.234902, 36.030863], + [120.239214, 36.062316], + [120.181316, 36.066669], + [120.152367, 36.095206], + [120.116642, 36.102943], + [120.108635, 36.127599], + [120.142512, 36.143549], + [120.140664, 36.173507], + [120.181316, 36.203936], + [120.22012, 36.209248], + [120.224432, 36.19138], + [120.260772, 36.198624], + [120.263236, 36.182202], + [120.310047, 36.185101], + [120.297112, 36.225664], + [120.319902, 36.232423], + [120.362402, 36.196209], + [120.35809, 36.174956], + [120.286025, 36.047317], + [120.337764, 36.055058], + [120.429539, 36.056994], + [120.468959, 36.087952], + [120.546568, 36.091821], + [120.546568, 36.107778], + [120.593995, 36.100525], + [120.615553, 36.120348], + [120.64327, 36.114547], + [120.672835, 36.130016], + [120.712255, 36.126632], + [120.696857, 36.15563], + [120.696857, 36.203936], + [120.680843, 36.238698], + [120.686386, 36.279234], + [120.657437, 36.276339], + [120.66298, 36.331803], + [120.744284, 36.327946], + [120.694393, 36.390118], + [120.759683, 36.46283], + [120.828668, 36.46668], + [120.837291, 36.459942], + [120.858849, 36.424797], + [120.848994, 36.403124], + [120.871784, 36.36699], + [120.911204, 36.412276], + [120.917979, 36.417573], + [120.90874, 36.450315], + [120.938305, 36.447908], + [120.965407, 36.466199], + [120.95432, 36.507578], + [120.983269, 36.546051], + [120.962327, 36.562877], + [120.909972, 36.568645], + [120.884718, 36.601323], + [120.847146, 36.618617], + [120.882255, 36.627262], + [120.926602, 36.611892], + [120.955551, 36.575855], + [121.028848, 36.572971], + [121.078123, 36.607568], + [121.161275, 36.651273], + [121.251818, 36.671436], + [121.29863, 36.702151], + [121.31218, 36.702151], + [121.35776, 36.713186], + [121.400876, 36.701191], + [121.3941, 36.738129], + [121.454462, 36.752515], + [121.496962, 36.795179], + [121.506817, 36.803805], + [121.565331, 36.830635], + [121.548701, 36.807638], + [121.485259, 36.786073], + [121.532071, 36.73621], + [121.575186, 36.740047], + [121.556092, 36.764502], + [121.651563, 36.723739], + [121.631853, 36.80093], + [121.6762, 36.819137], + [121.726092, 36.826323], + [121.762432, 36.84644], + [121.767975, 36.874691], + [121.927504, 36.932597], + [121.965076, 36.938337], + [122.008808, 36.96225], + [122.042684, 36.871819], + [122.051923, 36.904846], + [122.093191, 36.913938], + [122.115981, 36.94025], + [122.124604, 36.944077], + [122.141235, 36.938337], + [122.119677, 36.891924], + [122.175727, 36.894317], + [122.188662, 36.866073], + [122.174495, 36.842609], + [122.220691, 36.848835], + [122.275509, 36.83734], + [122.280437, 36.835904], + [122.344495, 36.828239], + [122.378371, 36.844525], + [122.383915, 36.865595], + [122.415944, 36.85937], + [122.454748, 36.879], + [122.452284, 36.88618], + [122.434422, 36.914416], + [122.483081, 36.913938], + [122.48924, 36.886659], + [122.532356, 36.901496], + [122.55761, 36.968467], + [122.544675, 37.004797], + [122.583479, 37.037289], + [122.575472, 37.054485], + [122.494168, 37.033945], + [122.467067, 37.037289], + [122.478769, 37.058784], + [122.484313, 37.128956], + [122.533588, 37.153286], + [122.581015, 37.147562], + [122.573624, 37.176178], + [122.624131, 37.190959], + [122.592718, 37.261485], + [122.567465, 37.25958], + [122.573624, 37.296247], + [122.611196, 37.339558], + [122.607501, 37.364296], + [122.650616, 37.388551], + [122.6925, 37.373809], + [122.714058, 37.392355], + [122.701739, 37.418501], + [122.67587, 37.413273], + [122.641377, 37.428482], + [122.553914, 37.407093], + [122.4954, 37.413748], + [122.487393, 37.43466], + [122.41656, 37.414699], + [122.337103, 37.414223], + [122.281053, 37.430858], + [122.287212, 37.445114], + [122.25272, 37.467917], + [122.194205, 37.456041], + [122.166488, 37.438937], + [122.131996, 37.49926], + [122.163408, 37.519199], + [122.150474, 37.557163], + [122.08888, 37.554316], + [122.075329, 37.540556], + [122.017431, 37.531065], + [121.997721, 37.494512], + [121.923808, 37.473142], + [121.772903, 37.466492], + [121.66573, 37.473617], + [121.635548, 37.494037], + [121.575802, 37.460317], + [121.571491, 37.441313], + [121.477252, 37.475992], + [121.460006, 37.522522], + [121.400876, 37.557638], + [121.395948, 37.589891], + [121.435368, 37.592737], + [121.391021, 37.625449], + [121.349137, 37.635403], + [121.358376, 37.597479], + [121.304789, 37.582778], + [121.217326, 37.582778], + [121.17421, 37.597479], + [121.148956, 37.626397], + [121.161891, 37.646302], + [121.142797, 37.661464], + [121.160043, 37.698882], + [121.136022, 37.723501], + [121.037471, 37.718767], + [120.994356, 37.759468], + [120.943233, 37.785486], + [120.940769, 37.819533], + [120.874863, 37.833241], + [120.845298, 37.826623], + [120.839139, 37.82426], + [120.733197, 37.833714], + [120.656821, 37.793054], + [120.634031, 37.796364], + [120.590915, 37.7642], + [120.517619, 37.750005], + [120.454793, 37.757576], + [120.367945, 37.697935], + [120.227511, 37.693673], + [120.22012, 37.671886], + [120.269395, 37.658622], + [120.272475, 37.636824], + [120.215192, 37.621183], + [120.208417, 37.588469], + [120.246605, 37.556689], + [120.222584, 37.532963], + [120.144359, 37.481691], + [120.086461, 37.465067], + [120.064903, 37.448915], + [120.010085, 37.442263], + [119.949723, 37.419927], + [119.926933, 37.386649], + [119.843781, 37.376662], + [119.837006, 37.346695], + [119.883201, 37.311004], + [119.89244, 37.263866], + [119.865339, 37.233854], + [119.83023, 37.225754], + [119.808057, 37.196203], + [119.740303, 37.133727], + [119.687332, 37.143746], + [119.678709, 37.158056], + [119.576463, 37.127524], + [119.489616, 37.134681], + [119.428022, 37.125616], + [119.361501, 37.125616], + [119.327624, 37.115595], + [119.301138, 37.139452], + [119.298675, 37.197156], + [119.2069, 37.223371], + [119.190885, 37.25958], + [119.204436, 37.280058], + [119.136683, 37.230995], + [119.12806, 37.254816], + [119.091103, 37.257674], + [119.084328, 37.239572], + [119.054147, 37.254816], + [119.03998, 37.30434], + [119.001176, 37.31862], + [118.942662, 37.497361], + [118.939582, 37.527268], + [118.988857, 37.620709], + [119.023966, 37.642037], + [119.153313, 37.655305], + [119.236465, 37.651988], + [119.262334, 37.660517], + [119.280197, 37.692726], + [119.309146, 37.805349], + [119.291899, 37.869627], + [119.24016, 37.878131], + [119.212443, 37.838913], + [119.16132, 37.81906], + [119.12806, 37.847892], + [119.110813, 37.921577], + [119.001792, 37.99613], + [118.974075, 38.094162], + [118.908169, 38.139362], + [118.811467, 38.157717], + [118.703677, 38.151129], + [118.626069, 38.138421], + [118.607591, 38.129006], + [118.597736, 38.079088], + [118.552156, 38.05553], + [118.534294, 38.063541], + [118.517048, 38.088509], + [118.504729, 38.11394], + [118.44991, 38.124299], + [118.431432, 38.106406], + [118.404331, 38.121003], + [118.331034, 38.12524], + [118.217085, 38.146893], + [118.177665, 38.186417], + [118.112376, 38.210403], + [118.045238, 38.214165], + [118.018753, 38.202409], + [117.896797, 38.279495], + [117.895565, 38.301572], + [117.848754, 38.255062], + [117.808718, 38.22827], + [117.789007, 38.180772], + [117.766834, 38.158658], + [117.771145, 38.134655], + [117.746508, 38.12524], + [117.704624, 38.076262], + [117.586979, 38.071551], + [117.557414, 38.046105], + [117.557414, 38.046105], + [117.524154, 37.989527], + [117.513067, 37.94329], + [117.481038, 37.914967], + [117.438538, 37.854035], + [117.400966, 37.844584], + [117.320278, 37.861596], + [117.271618, 37.839858], + [117.185387, 37.849783], + [117.150278, 37.839385], + [117.074518, 37.848837], + [117.027091, 37.832296], + [116.919301, 37.846002], + [116.837997, 37.835132], + [116.804736, 37.848837], + [116.753613, 37.793054], + [116.753613, 37.77035], + [116.724664, 37.744327], + [116.679085, 37.728708], + [116.66307, 37.686096], + [116.604556, 37.624975], + [116.575607, 37.610754], + [116.4826, 37.521573], + [116.448108, 37.503059], + [116.433941, 37.473142], + [116.38097, 37.522522], + [116.379738, 37.522047], + [116.38097, 37.522522], + [116.379738, 37.522047], + [116.36742, 37.566177], + [116.336007, 37.581355], + [116.295355, 37.554316], + [116.278724, 37.524895], + [116.290427, 37.484065], + [116.27626, 37.466967], + [116.240536, 37.489764], + [116.240536, 37.489764], + [116.224522, 37.479791], + [116.243, 37.447965], + [116.226369, 37.428007], + [116.2855, 37.404241], + [116.236224, 37.361442], + [116.193109, 37.365723], + [116.169087, 37.384271], + [116.106261, 37.368577], + [116.085935, 37.373809], + [116.024341, 37.360015], + [115.975682, 37.337179], + [115.969523, 37.239572], + [115.909777, 37.20669], + [115.91224, 37.177132], + [115.879596, 37.150901], + [115.888219, 37.112254], + [115.85619, 37.060694], + [115.776734, 36.992848], + [115.79706, 36.968945], + [115.75764, 36.902453], + [115.71206, 36.883308], + [115.683727, 36.808117], + [115.524815, 36.763543], + [115.479851, 36.760187], + [115.451518, 36.702151], + [115.420105, 36.686795], + [115.365902, 36.621979], + [115.355431, 36.627262], + [115.33141, 36.550378], + [115.272895, 36.497476], + [115.291374, 36.460423], + [115.317243, 36.454166], + [115.297533, 36.413239], + [115.340033, 36.398307], + [115.368982, 36.342409], + [115.366518, 36.30914], + [115.423185, 36.32216], + [115.417025, 36.292742], + [115.462605, 36.276339], + [115.466916, 36.258969], + [115.466916, 36.258969], + [115.474923, 36.248352], + [115.483547, 36.148865], + [115.484163, 36.125666], + [115.449054, 36.047317], + [115.447822, 36.01247], + [115.362822, 35.971796], + [115.353583, 35.938854], + [115.364054, 35.894264], + [115.335105, 35.796756], + [115.363438, 35.779765], + [115.407786, 35.80889], + [115.460141, 35.867594], + [115.487858, 35.880688], + [115.495249, 35.896203], + [115.505104, 35.899112], + [115.513112, 35.890385], + [115.583945, 35.921893], + [115.648618, 35.922863], + [115.699125, 35.966468], + [115.774886, 35.974702], + [115.779813, 35.993588], + [115.817386, 36.012954], + [115.859886, 36.003756], + [115.89869, 36.026507], + [115.989849, 36.045381], + [116.057602, 36.104877], + [116.099486, 36.112129], + [116.063145, 36.028927], + [116.048979, 35.970343], + [115.984921, 35.974218], + [115.911624, 35.960171], + [115.907929, 35.92674], + [115.873436, 35.918985], + [115.882675, 35.879718], + [115.859886, 35.857894], + [115.81677, 35.844312], + [115.773654, 35.854014], + [115.73485, 35.833154], + [115.696046, 35.788989], + [115.693582, 35.754028], + [115.622749, 35.739457], + [115.52851, 35.733628], + [115.48601, 35.710306], + [115.383148, 35.568772], + [115.34496, 35.55368], + [115.356047, 35.490359], + [115.307388, 35.480126], + [115.237171, 35.423087], + [115.172497, 35.426501], + [115.126302, 35.41821], + [115.117679, 35.400163], + [115.091809, 35.416259], + [115.073947, 35.374304], + [115.04315, 35.376744], + [114.957534, 35.261014], + [114.929201, 35.244886], + [114.932281, 35.198441], + [114.861448, 35.182301], + [114.841738, 35.15099], + [114.883006, 35.098615], + [114.835578, 35.076578], + [114.818948, 35.051596], + [114.852209, 35.041797], + [114.824492, 35.012393], + [114.880542, 35.00357], + [114.923658, 34.968757], + [114.950759, 34.989843], + [115.008041, 34.988372], + [115.028983, 34.9717], + [115.075179, 35.000628], + [115.12815, 35.00455], + [115.157099, 34.957968], + [115.219309, 34.96042], + [115.205142, 34.914303], + [115.251953, 34.906451], + [115.239019, 34.87798], + [115.256265, 34.845079], + [115.317243, 34.859321], + [115.42688, 34.805285], + [115.449054, 34.74433], + [115.433655, 34.725149], + [115.461373, 34.637057], + [115.515575, 34.582383], + [115.553148, 34.568586], + [115.622749, 34.574499], + [115.685575, 34.556265], + [115.697278, 34.594207], + [115.787821, 34.580905], + [115.827241, 34.558236], + [115.838328, 34.5676], + [115.984305, 34.589281], + [115.991081, 34.615389], + [116.037276, 34.593222], + [116.101334, 34.60603], + [116.134594, 34.559715], + [116.156768, 34.5538], + [116.196804, 34.575977], + [116.247927, 34.551829], + [116.286116, 34.608986], + [116.32492, 34.601104], + [116.334159, 34.620806], + [116.374195, 34.640011] + ] + ], + [ + [ + [120.729502, 37.947065], + [120.721495, 37.917328], + [120.76461, 37.895134], + [120.76461, 37.923937], + [120.729502, 37.947065] + ] + ], + [ + [ + [120.692545, 37.983867], + [120.732581, 37.961694], + [120.724574, 37.987641], + [120.692545, 37.983867] + ] + ], + [ + [ + [120.990044, 36.413239], + [120.978341, 36.428649], + [120.950624, 36.414684], + [120.990044, 36.413239] + ] + ], + [ + [ + [120.750444, 38.150188], + [120.7874, 38.158658], + [120.742436, 38.199116], + [120.750444, 38.150188] + ] + ], + [ + [ + [120.918595, 38.345236], + [120.914899, 38.373393], + [120.895189, 38.36307], + [120.918595, 38.345236] + ] + ], + [ + [ + [120.159142, 35.765198], + [120.169613, 35.740428], + [120.193019, 35.756942], + [120.172077, 35.785591], + [120.159142, 35.765198] + ] + ], + [ + [ + [120.62664, 37.94565], + [120.631567, 37.981037], + [120.602002, 37.978678], + [120.62664, 37.94565] + ] + ], + [ + [ + [120.802183, 38.284193], + [120.848378, 38.305799], + [120.816349, 38.318008], + [120.802183, 38.284193] + ] + ], + [ + [ + [121.489571, 37.577086], + [121.489571, 37.577561], + [121.489571, 37.578509], + [121.488955, 37.578035], + [121.489571, 37.577086] + ] + ], + [ + [ + [121.485875, 37.578509], + [121.487723, 37.578035], + [121.487723, 37.578509], + [121.485875, 37.578509] + ] + ], + [ + [ + [121.487723, 37.578509], + [121.487723, 37.577561], + [121.488955, 37.578035], + [121.488955, 37.578509], + [121.488339, 37.578509], + [121.487723, 37.578509] + ] + ], + [ + [ + [115.495249, 35.896203], + [115.487858, 35.880688], + [115.513112, 35.890385], + [115.505104, 35.899112], + [115.495249, 35.896203] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 410000, + "name": "Henan", + "center": [113.665412, 34.757975], + "centroid": [113.619717, 33.902648], + "childrenNum": 18, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 15, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [112.716747, 32.357612], + [112.735841, 32.356095], + [112.776493, 32.358623], + [112.860877, 32.396024], + [112.888594, 32.37682], + [112.912, 32.390971], + [112.992072, 32.378336], + [113.000695, 32.41674], + [113.025949, 32.425328], + [113.078919, 32.394508], + [113.107869, 32.398551], + [113.118956, 32.375809], + [113.155912, 32.380863], + [113.158992, 32.410677], + [113.211962, 32.431895], + [113.2366, 32.407141], + [113.333918, 32.336377], + [113.317904, 32.327275], + [113.353628, 32.294904], + [113.376418, 32.298445], + [113.428773, 32.270618], + [113.511925, 32.316654], + [113.624642, 32.36115], + [113.650511, 32.412698], + [113.700402, 32.420782], + [113.735511, 32.410677], + [113.76754, 32.370249], + [113.753989, 32.328286], + [113.768772, 32.30148], + [113.768156, 32.284279], + [113.758301, 32.27669], + [113.749061, 32.272642], + [113.73859, 32.255942], + [113.752757, 32.215951], + [113.782322, 32.184553], + [113.750293, 32.11615], + [113.722576, 32.12426], + [113.728735, 32.083197], + [113.791561, 32.036028], + [113.757685, 31.98985], + [113.817431, 31.964467], + [113.805728, 31.929428], + [113.832213, 31.918761], + [113.830981, 31.87913], + [113.854387, 31.843042], + [113.893807, 31.847109], + [113.914749, 31.877098], + [113.957865, 31.852701], + [113.952321, 31.793714], + [113.988662, 31.749959], + [114.017611, 31.770822], + [114.086596, 31.782014], + [114.121705, 31.809482], + [114.134024, 31.843042], + [114.191922, 31.852192], + [114.235654, 31.833382], + [114.292936, 31.752503], + [114.350218, 31.755557], + [114.403189, 31.746906], + [114.443841, 31.728074], + [114.530688, 31.742834], + [114.549783, 31.766751], + [114.586123, 31.762172], + [114.57134, 31.660858], + [114.547935, 31.623665], + [114.560869, 31.560963], + [114.572572, 31.553824], + [114.61692, 31.585437], + [114.641558, 31.582378], + [114.696376, 31.525771], + [114.778912, 31.520669], + [114.789383, 31.480358], + [114.830035, 31.45892], + [114.870071, 31.479337], + [114.884238, 31.469129], + [114.962462, 31.494648], + [114.995107, 31.471171], + [115.022824, 31.527811], + [115.096121, 31.508425], + [115.114599, 31.530362], + [115.106592, 31.567592], + [115.12507, 31.599201], + [115.16449, 31.604808], + [115.212533, 31.555354], + [115.235939, 31.555354], + [115.218077, 31.515057], + [115.211301, 31.442072], + [115.252569, 31.421646], + [115.250722, 31.392021], + [115.301229, 31.383846], + [115.338801, 31.40428], + [115.373909, 31.405813], + [115.389924, 31.450241], + [115.371446, 31.495668], + [115.415793, 31.525771], + [115.439815, 31.588496], + [115.485394, 31.608885], + [115.476771, 31.643028], + [115.495249, 31.673083], + [115.534054, 31.698545], + [115.553764, 31.69549], + [115.676336, 31.778453], + [115.731154, 31.76726], + [115.767495, 31.78761], + [115.808147, 31.770313], + [115.808147, 31.770313], + [115.851878, 31.786593], + [115.886371, 31.776418], + [115.914704, 31.814567], + [115.893762, 31.832365], + [115.894994, 31.8649], + [115.920248, 31.920285], + [115.909161, 31.94314], + [115.928871, 32.003046], + [115.922095, 32.049725], + [115.941805, 32.166318], + [115.912856, 32.227596], + [115.899306, 32.390971], + [115.865429, 32.458662], + [115.883291, 32.487946], + [115.845719, 32.501575], + [115.8759, 32.542448], + [115.910393, 32.567165], + [115.891298, 32.576243], + [115.861117, 32.537403], + [115.789052, 32.468761], + [115.771806, 32.505108], + [115.742241, 32.476335], + [115.704669, 32.495013], + [115.667712, 32.409667], + [115.657857, 32.428864], + [115.626445, 32.40512], + [115.604271, 32.425833], + [115.57101, 32.419266], + [115.522967, 32.441997], + [115.509416, 32.466741], + [115.5088, 32.468761], + [115.497713, 32.492489], + [115.409018, 32.549007], + [115.411482, 32.575235], + [115.304924, 32.553042], + [115.30554, 32.583303], + [115.267352, 32.578261], + [115.24333, 32.593388], + [115.20083, 32.591876], + [115.182968, 32.666973], + [115.179273, 32.726402], + [115.189744, 32.770695], + [115.211301, 32.785791], + [115.189744, 32.812452], + [115.197135, 32.856201], + [115.155867, 32.864747], + [115.139237, 32.897917], + [115.029599, 32.906962], + [115.035143, 32.932582], + [115.009273, 32.940117], + [114.943368, 32.935094], + [114.916266, 32.971251], + [114.883006, 32.990328], + [114.891629, 33.020441], + [114.925506, 33.016928], + [114.913187, 33.083143], + [114.897172, 33.086653], + [114.902716, 33.129764], + [114.932897, 33.153817], + [114.966158, 33.147304], + [114.990795, 33.102195], + [115.041302, 33.086653], + [115.168186, 33.088658], + [115.194671, 33.120743], + [115.245178, 33.135778], + [115.289526, 33.131769], + [115.303692, 33.149809], + [115.300613, 33.204407], + [115.340033, 33.260973], + [115.335105, 33.297997], + [115.361591, 33.298497], + [115.365286, 33.336005], + [115.341881, 33.370997], + [115.313547, 33.376994], + [115.328946, 33.403477], + [115.315395, 33.431451], + [115.324634, 33.457418], + [115.345576, 33.449928], + [115.345576, 33.502842], + [115.366518, 33.5233], + [115.394851, 33.506335], + [115.422569, 33.557219], + [115.463837, 33.567193], + [115.511264, 33.55323], + [115.564851, 33.576169], + [115.639995, 33.585143], + [115.601191, 33.658898], + [115.601807, 33.718653], + [115.563003, 33.772895], + [115.576553, 33.787817], + [115.614126, 33.775879], + [115.631988, 33.869846], + [115.547604, 33.874815], + [115.577785, 33.950307], + [115.579017, 33.974133], + [115.60735, 34.030196], + [115.642459, 34.03218], + [115.658473, 34.061437], + [115.705901, 34.059949], + [115.736082, 34.076805], + [115.768726, 34.061932], + [115.809378, 34.062428], + [115.846335, 34.028708], + [115.85003, 34.004898], + [115.877132, 34.002913], + [115.876516, 34.028708], + [115.904233, 34.009859], + [115.95782, 34.007875], + [116.00032, 33.965199], + [115.982457, 33.917039], + [116.05945, 33.860902], + [116.055754, 33.804727], + [116.074232, 33.781351], + [116.100102, 33.782843], + [116.132747, 33.751501], + [116.155536, 33.709693], + [116.230065, 33.735078], + [116.263326, 33.730101], + [116.316912, 33.771402], + [116.393905, 33.782843], + [116.408071, 33.805721], + [116.437021, 33.801246], + [116.437637, 33.846489], + [116.486296, 33.869846], + [116.558361, 33.881274], + [116.566984, 33.9081], + [116.631042, 33.887733], + [116.64336, 33.896675], + [116.641512, 33.978103], + [116.599629, 34.014324], + [116.599629, 34.014324], + [116.576223, 34.068873], + [116.576223, 34.068873], + [116.52818, 34.122892], + [116.536187, 34.151127], + [116.565752, 34.16945], + [116.542962, 34.203608], + [116.545426, 34.241711], + [116.582382, 34.266444], + [116.562056, 34.285731], + [116.516477, 34.296114], + [116.456731, 34.268917], + [116.409303, 34.273863], + [116.409303, 34.273863], + [116.372347, 34.26595], + [116.357564, 34.319843], + [116.301514, 34.342082], + [116.255934, 34.376665], + [116.213435, 34.382098], + [116.215898, 34.403333], + [116.178942, 34.430487], + [116.162312, 34.459605], + [116.178326, 34.496112], + [116.204196, 34.508442], + [116.191261, 34.535561], + [116.196804, 34.575977], + [116.156768, 34.5538], + [116.134594, 34.559715], + [116.101334, 34.60603], + [116.037276, 34.593222], + [115.991081, 34.615389], + [115.984305, 34.589281], + [115.838328, 34.5676], + [115.827241, 34.558236], + [115.787821, 34.580905], + [115.697278, 34.594207], + [115.685575, 34.556265], + [115.622749, 34.574499], + [115.553148, 34.568586], + [115.515575, 34.582383], + [115.461373, 34.637057], + [115.433655, 34.725149], + [115.449054, 34.74433], + [115.42688, 34.805285], + [115.317243, 34.859321], + [115.256265, 34.845079], + [115.239019, 34.87798], + [115.251953, 34.906451], + [115.205142, 34.914303], + [115.219309, 34.96042], + [115.157099, 34.957968], + [115.12815, 35.00455], + [115.075179, 35.000628], + [115.028983, 34.9717], + [115.008041, 34.988372], + [114.950759, 34.989843], + [114.923658, 34.968757], + [114.880542, 35.00357], + [114.824492, 35.012393], + [114.852209, 35.041797], + [114.818948, 35.051596], + [114.835578, 35.076578], + [114.883006, 35.098615], + [114.841738, 35.15099], + [114.861448, 35.182301], + [114.932281, 35.198441], + [114.929201, 35.244886], + [114.957534, 35.261014], + [115.04315, 35.376744], + [115.073947, 35.374304], + [115.091809, 35.416259], + [115.117679, 35.400163], + [115.126302, 35.41821], + [115.172497, 35.426501], + [115.237171, 35.423087], + [115.307388, 35.480126], + [115.356047, 35.490359], + [115.34496, 35.55368], + [115.383148, 35.568772], + [115.48601, 35.710306], + [115.52851, 35.733628], + [115.622749, 35.739457], + [115.693582, 35.754028], + [115.696046, 35.788989], + [115.73485, 35.833154], + [115.773654, 35.854014], + [115.81677, 35.844312], + [115.859886, 35.857894], + [115.882675, 35.879718], + [115.873436, 35.918985], + [115.907929, 35.92674], + [115.911624, 35.960171], + [115.984921, 35.974218], + [116.048979, 35.970343], + [116.063145, 36.028927], + [116.099486, 36.112129], + [116.057602, 36.104877], + [115.989849, 36.045381], + [115.89869, 36.026507], + [115.859886, 36.003756], + [115.817386, 36.012954], + [115.779813, 35.993588], + [115.774886, 35.974702], + [115.699125, 35.966468], + [115.648618, 35.922863], + [115.583945, 35.921893], + [115.513112, 35.890385], + [115.487858, 35.880688], + [115.460141, 35.867594], + [115.407786, 35.80889], + [115.363438, 35.779765], + [115.335105, 35.796756], + [115.364054, 35.894264], + [115.353583, 35.938854], + [115.362822, 35.971796], + [115.447822, 36.01247], + [115.449054, 36.047317], + [115.484163, 36.125666], + [115.483547, 36.148865], + [115.465068, 36.170125], + [115.450902, 36.152248], + [115.376989, 36.128083], + [115.365902, 36.099074], + [115.312931, 36.088436], + [115.30246, 36.127599], + [115.279055, 36.13775], + [115.242098, 36.19138], + [115.202678, 36.208765], + [115.202678, 36.208765], + [115.202678, 36.209248], + [115.202678, 36.209248], + [115.201446, 36.210214], + [115.201446, 36.210214], + [115.1842, 36.193312], + [115.12507, 36.209731], + [115.104744, 36.172058], + [115.06286, 36.178338], + [115.048693, 36.161912], + [115.04623, 36.112613], + [114.998186, 36.069572], + [114.914419, 36.052155], + [114.926737, 36.089403], + [114.912571, 36.140649], + [114.858368, 36.144516], + [114.857752, 36.127599], + [114.771521, 36.124699], + [114.734564, 36.15563], + [114.720398, 36.140166], + [114.640326, 36.137266], + [114.588587, 36.118414], + [114.586739, 36.141133], + [114.533152, 36.171575], + [114.480181, 36.177855], + [114.466015, 36.197658], + [114.417356, 36.205868], + [114.408117, 36.224699], + [114.356378, 36.230492], + [114.345291, 36.255591], + [114.299095, 36.245938], + [114.257827, 36.263794], + [114.241197, 36.251247], + [114.2104, 36.272962], + [114.203009, 36.245456], + [114.170364, 36.245938], + [114.170364, 36.245938], + [114.175907, 36.264759], + [114.129096, 36.280199], + [114.080437, 36.269585], + [114.04348, 36.303353], + [114.056415, 36.329392], + [114.002828, 36.334214], + [113.981887, 36.31782], + [113.962792, 36.353977], + [113.911054, 36.314927], + [113.882104, 36.353977], + [113.84946, 36.347711], + [113.856851, 36.329392], + [113.813119, 36.332285], + [113.755221, 36.366026], + [113.731199, 36.363135], + [113.736127, 36.324571], + [113.712105, 36.303353], + [113.716417, 36.262347], + [113.681924, 36.216491], + [113.697939, 36.181719], + [113.651127, 36.174473], + [113.705946, 36.148865], + [113.712721, 36.129533], + [113.655439, 36.125182], + [113.671453, 36.115514], + [113.68562, 36.056026], + [113.660366, 36.034735], + [113.694859, 36.026991], + [113.678844, 35.985841], + [113.648663, 35.994073], + [113.654207, 35.931586], + [113.637576, 35.870019], + [113.660982, 35.837035], + [113.582758, 35.818111], + [113.604932, 35.797727], + [113.587685, 35.736542], + [113.592613, 35.691838], + [113.622794, 35.674825], + [113.625258, 35.632518], + [113.578446, 35.633491], + [113.547649, 35.656835], + [113.55812, 35.621816], + [113.513773, 35.57364], + [113.49899, 35.532254], + [113.439244, 35.507412], + [113.391817, 35.506925], + [113.348085, 35.468429], + [113.31236, 35.481101], + [113.304353, 35.426989], + [113.243375, 35.449418], + [113.189789, 35.44893], + [113.185477, 35.409431], + [113.165151, 35.412845], + [113.149137, 35.350878], + [113.126347, 35.332327], + [113.067217, 35.353806], + [112.996384, 35.362104], + [112.985913, 35.33965], + [112.992072, 35.29619], + [112.936022, 35.284466], + [112.934174, 35.262968], + [112.884283, 35.243909], + [112.822073, 35.258082], + [112.772798, 35.207732], + [112.720443, 35.206265], + [112.628052, 35.263457], + [112.637291, 35.225822], + [112.513487, 35.218489], + [112.390915, 35.239021], + [112.36751, 35.219956], + [112.288053, 35.219956], + [112.304684, 35.251728], + [112.242474, 35.234622], + [112.21722, 35.253195], + [112.13838, 35.271275], + [112.058924, 35.280069], + [112.078634, 35.219467], + [112.03983, 35.194039], + [112.066315, 35.153437], + [112.05646, 35.098615], + [112.062004, 35.056005], + [112.039214, 35.045717], + [112.018888, 35.068742], + [111.97762, 35.067272], + [111.933272, 35.083435], + [111.810084, 35.062374], + [111.807005, 35.032977], + [111.740483, 35.00455], + [111.664107, 34.984449], + [111.681969, 34.9511], + [111.646861, 34.938836], + [111.617911, 34.894671], + [111.592042, 34.881416], + [111.570484, 34.843114], + [111.543999, 34.853428], + [111.502731, 34.829851], + [111.439289, 34.838202], + [111.389398, 34.815113], + [111.345666, 34.831816], + [111.29208, 34.806759], + [111.255123, 34.819535], + [111.232949, 34.789559], + [111.148566, 34.807742], + [111.118385, 34.756623], + [111.035233, 34.740887], + [110.976103, 34.706456], + [110.920052, 34.730068], + [110.903422, 34.669056], + [110.883712, 34.64395], + [110.824582, 34.615881], + [110.791937, 34.649858], + [110.749437, 34.65232], + [110.710017, 34.605045], + [110.610851, 34.607508], + [110.533242, 34.583368], + [110.488279, 34.610956], + [110.424837, 34.588295], + [110.379257, 34.600612], + [110.366939, 34.566614], + [110.404511, 34.557743], + [110.372482, 34.544435], + [110.360779, 34.516825], + [110.403279, 34.433448], + [110.403279, 34.433448], + [110.473496, 34.393457], + [110.503677, 34.33714], + [110.451938, 34.292653], + [110.428533, 34.288203], + [110.43962, 34.243196], + [110.507989, 34.217466], + [110.55172, 34.213012], + [110.55788, 34.193214], + [110.621938, 34.177372], + [110.642264, 34.161032], + [110.61393, 34.113478], + [110.591757, 34.101586], + [110.587445, 34.023252], + [110.620706, 34.035652], + [110.671213, 33.966192], + [110.665669, 33.937895], + [110.627481, 33.925482], + [110.628713, 33.910086], + [110.587445, 33.887733], + [110.612083, 33.852453], + [110.66259, 33.85295], + [110.712481, 33.833564], + [110.74143, 33.798759], + [110.782082, 33.796272], + [110.81719, 33.751003], + [110.831973, 33.713675], + [110.823966, 33.685793], + [110.878784, 33.634486], + [110.966864, 33.609071], + [111.00382, 33.578662], + [111.002588, 33.535772], + [111.02661, 33.478386], + [111.02661, 33.467903], + [110.996429, 33.435946], + [111.025994, 33.375495], + [111.025994, 33.330504], + [110.984726, 33.255469], + [111.046936, 33.202905], + [111.045704, 33.169849], + [111.08882, 33.181871], + [111.12824, 33.15532], + [111.146102, 33.12375], + [111.179363, 33.115229], + [111.192913, 33.071609], + [111.152877, 33.039507], + [111.221862, 33.042517], + [111.258819, 33.006389], + [111.273601, 32.971753], + [111.242804, 32.930573], + [111.255123, 32.883846], + [111.276065, 32.903445], + [111.293311, 32.859217], + [111.380159, 32.829049], + [111.41342, 32.757108], + [111.475629, 32.760127], + [111.458383, 32.726402], + [111.513202, 32.674026], + [111.530448, 32.628172], + [111.577875, 32.593388], + [111.640701, 32.634724], + [111.646245, 32.605993], + [111.713382, 32.606497], + [111.808853, 32.536899], + [111.858128, 32.528826], + [111.890157, 32.503089], + [111.948671, 32.51722], + [111.975772, 32.471791], + [112.014576, 32.450077], + [112.063851, 32.474315], + [112.081098, 32.425833], + [112.155626, 32.377326], + [112.150083, 32.411688], + [112.172873, 32.385412], + [112.206133, 32.392992], + [112.328089, 32.321712], + [112.360118, 32.3657], + [112.390915, 32.37126], + [112.448814, 32.34295], + [112.477147, 32.380863], + [112.530733, 32.37682], + [112.545516, 32.404109], + [112.589248, 32.381369], + [112.612037, 32.386928], + [112.645298, 32.368227], + [112.716747, 32.357612] + ] + ], + [ + [ + [113.768156, 32.284279], + [113.768772, 32.30148], + [113.749061, 32.272642], + [113.758301, 32.27669], + [113.768156, 32.284279] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 420000, + "name": "Hubei", + "center": [114.298572, 30.584355], + "centroid": [112.271301, 30.987527], + "childrenNum": 17, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 16, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [111.045704, 33.169849], + [111.034001, 33.177864], + [111.035849, 33.187881], + [111.046936, 33.202905], + [110.984726, 33.255469], + [110.960704, 33.253967], + [110.9219, 33.203907], + [110.865234, 33.213921], + [110.828893, 33.201403], + [110.824582, 33.158327], + [110.753133, 33.15031], + [110.702626, 33.097182], + [110.650887, 33.157324], + [110.623785, 33.143796], + [110.59422, 33.168346], + [110.57759, 33.250464], + [110.54125, 33.255469], + [110.471032, 33.171352], + [110.398352, 33.176862], + [110.398352, 33.176862], + [110.372482, 33.186379], + [110.33799, 33.160331], + [110.285635, 33.171352], + [110.218497, 33.163336], + [110.164911, 33.209415], + [110.031252, 33.191888], + [109.999223, 33.212419], + [109.973353, 33.203907], + [109.916687, 33.229942], + [109.852013, 33.247961], + [109.813209, 33.236449], + [109.732521, 33.231443], + [109.693101, 33.254468], + [109.649985, 33.251465], + [109.619804, 33.275484], + [109.60687, 33.235949], + [109.514479, 33.237951], + [109.498464, 33.207412], + [109.438718, 33.152314], + [109.468283, 33.140288], + [109.522486, 33.138785], + [109.576073, 33.110216], + [109.688174, 33.116733], + [109.704188, 33.101694], + [109.794731, 33.067095], + [109.785492, 32.987316], + [109.76455, 32.909474], + [109.789804, 32.882339], + [109.847702, 32.893395], + [109.856941, 32.910479], + [109.907448, 32.903947], + [109.927158, 32.887364], + [109.988752, 32.886359], + [110.051578, 32.851676], + [110.105164, 32.832569], + [110.142121, 32.802895], + [110.127338, 32.77774], + [110.159367, 32.767173], + [110.156903, 32.683093], + [110.206179, 32.633212], + [110.153824, 32.593388], + [110.124259, 32.616579], + [110.090382, 32.617083], + [110.084223, 32.580782], + [110.017701, 32.546989], + [109.97089, 32.577756], + [109.910528, 32.592884], + [109.816905, 32.577252], + [109.746072, 32.594901], + [109.726978, 32.608513], + [109.631507, 32.599943], + [109.619804, 32.56767], + [109.637051, 32.540935], + [109.575457, 32.506622], + [109.526797, 32.43341], + [109.529877, 32.405625], + [109.502776, 32.38895], + [109.513247, 32.342444], + [109.495385, 32.300468], + [109.528645, 32.270112], + [109.550203, 32.225065], + [109.592703, 32.219495], + [109.604406, 32.199241], + [109.58716, 32.161251], + [109.621652, 32.106519], + [109.590855, 32.047696], + [109.590855, 32.012688], + [109.631507, 31.962436], + [109.62042, 31.928412], + [109.584696, 31.900472], + [109.60379, 31.885737], + [109.633971, 31.824738], + [109.633971, 31.804396], + [109.592087, 31.789136], + [109.585928, 31.726546], + [109.622268, 31.711783], + [109.683246, 31.719929], + [109.731289, 31.700582], + [109.737449, 31.628761], + [109.76455, 31.602769], + [109.745456, 31.598182], + [109.727594, 31.548214], + [109.837847, 31.555354], + [109.894513, 31.519139], + [109.969658, 31.508935], + [109.94502, 31.47066], + [109.98752, 31.474744], + [110.036795, 31.436966], + [110.054042, 31.410921], + [110.118715, 31.409899], + [110.161831, 31.314338], + [110.155671, 31.279564], + [110.180309, 31.179774], + [110.200019, 31.158779], + [110.180309, 31.121899], + [110.147048, 31.116776], + [110.119947, 31.088592], + [110.120563, 31.0322], + [110.140273, 31.030661], + [110.140889, 30.987062], + [110.172918, 30.978853], + [110.153824, 30.953708], + [110.151976, 30.911613], + [110.082375, 30.799614], + [110.048498, 30.800642], + [110.019549, 30.829425], + [110.008462, 30.883369], + [109.943788, 30.878746], + [109.894513, 30.899803], + [109.828608, 30.864364], + [109.780564, 30.848437], + [109.701724, 30.783677], + [109.656761, 30.760538], + [109.661072, 30.738936], + [109.625348, 30.702923], + [109.590855, 30.69366], + [109.574225, 30.646818], + [109.543428, 30.63961], + [109.535421, 30.664837], + [109.435638, 30.595832], + [109.418392, 30.559766], + [109.35495, 30.487076], + [109.337088, 30.521623], + [109.36111, 30.551004], + [109.314298, 30.599953], + [109.299516, 30.630341], + [109.245313, 30.580892], + [109.191726, 30.545851], + [109.191726, 30.545851], + [109.143683, 30.521108], + [109.103647, 30.565949], + [109.09256, 30.578831], + [109.106111, 30.61077], + [109.111654, 30.646303], + [109.071002, 30.640125], + [109.042669, 30.655571], + [109.006329, 30.626736], + [108.971836, 30.627766], + [108.893612, 30.565434], + [108.838793, 30.503062], + [108.808612, 30.491202], + [108.789518, 30.513374], + [108.743939, 30.494812], + [108.698975, 30.54482], + [108.688504, 30.58759], + [108.642925, 30.578831], + [108.6497, 30.53915], + [108.56778, 30.468508], + [108.556077, 30.487592], + [108.512961, 30.501515], + [108.472925, 30.487076], + [108.42673, 30.492233], + [108.411331, 30.438586], + [108.430425, 30.416397], + [108.402092, 30.376649], + [108.431041, 30.354446], + [108.460606, 30.35961], + [108.501258, 30.314673], + [108.524048, 30.309506], + [108.54499, 30.269716], + [108.581947, 30.255759], + [108.551766, 30.1637], + [108.56778, 30.157491], + [108.546222, 30.104178], + [108.513577, 30.057571], + [108.532055, 30.051873], + [108.536367, 29.983472], + [108.517889, 29.9394], + [108.516041, 29.885451], + [108.467998, 29.864175], + [108.433505, 29.880262], + [108.371295, 29.841337], + [108.424266, 29.815897], + [108.422418, 29.772791], + [108.442744, 29.778505], + [108.437201, 29.741098], + [108.460606, 29.741098], + [108.504338, 29.707836], + [108.504954, 29.728626], + [108.548686, 29.749412], + [108.52528, 29.770713], + [108.556077, 29.818493], + [108.601041, 29.863656], + [108.658939, 29.854833], + [108.680497, 29.800319], + [108.676801, 29.749412], + [108.690968, 29.689642], + [108.752562, 29.649082], + [108.786438, 29.691721], + [108.797525, 29.660003], + [108.781511, 29.635558], + [108.844337, 29.658443], + [108.888068, 29.628795], + [108.870206, 29.596537], + [108.901003, 29.604863], + [108.913322, 29.574679], + [108.878213, 29.539279], + [108.888684, 29.502305], + [108.866511, 29.470527], + [108.884373, 29.440824], + [108.927488, 29.435612], + [108.934264, 29.399643], + [108.919481, 29.3261], + [108.983539, 29.332883], + [108.999553, 29.36366], + [109.034662, 29.360531], + [109.060531, 29.403292], + [109.11227, 29.361053], + [109.106727, 29.288526], + [109.141835, 29.270256], + [109.110422, 29.21647], + [109.139372, 29.168927], + [109.162777, 29.180946], + [109.215748, 29.145409], + [109.232378, 29.119271], + [109.274262, 29.121885], + [109.261328, 29.161089], + [109.275494, 29.202366], + [109.257632, 29.222738], + [109.312451, 29.25146], + [109.352487, 29.284872], + [109.343863, 29.369398], + [109.391291, 29.372005], + [109.368501, 29.413719], + [109.418392, 29.453332], + [109.415928, 29.497617], + [109.436254, 29.488761], + [109.433791, 29.530948], + [109.458428, 29.513242], + [109.467051, 29.560104], + [109.488609, 29.553336], + [109.516326, 29.626194], + [109.558826, 29.606944], + [109.578536, 29.629836], + [109.651833, 29.625674], + [109.664768, 29.599659], + [109.717739, 29.615269], + [109.701108, 29.636078], + [109.714659, 29.673524], + [109.760238, 29.689122], + [109.755311, 29.733304], + [109.779333, 29.757725], + [109.869876, 29.774869], + [109.908064, 29.763959], + [109.941325, 29.774349], + [110.02386, 29.769674], + [110.113788, 29.789932], + [110.160599, 29.753569], + [110.219729, 29.746814], + [110.289946, 29.6964], + [110.302265, 29.661563], + [110.339221, 29.668324], + [110.372482, 29.633477], + [110.447011, 29.664684], + [110.467337, 29.713034], + [110.507373, 29.692241], + [110.562807, 29.712515], + [110.642879, 29.775907], + [110.60038, 29.839779], + [110.549873, 29.848085], + [110.538786, 29.895828], + [110.49875, 29.91243], + [110.517228, 29.961179], + [110.557264, 29.988137], + [110.491358, 30.019751], + [110.497518, 30.055499], + [110.531394, 30.061197], + [110.600996, 30.054463], + [110.650887, 30.07777], + [110.712481, 30.033223], + [110.756212, 30.054463], + [110.746973, 30.112979], + [110.851067, 30.126439], + [110.924364, 30.111426], + [110.929907, 30.063268], + [111.031537, 30.048765], + [111.242188, 30.040476], + [111.266826, 30.01146], + [111.3315, 29.970512], + [111.342587, 29.944586], + [111.382623, 29.95029], + [111.394325, 29.912948], + [111.436825, 29.930065], + [111.475629, 29.918654], + [111.527368, 29.925916], + [111.553854, 29.894272], + [111.669034, 29.888565], + [111.669034, 29.888565], + [111.705375, 29.890121], + [111.723853, 29.909317], + [111.723853, 29.909317], + [111.75773, 29.92021], + [111.8107, 29.901017], + [111.861207, 29.856909], + [111.899396, 29.855871], + [111.899396, 29.855871], + [111.925881, 29.836665], + [111.965917, 29.832512], + [111.95483, 29.796683], + [112.008417, 29.778505], + [112.07617, 29.743696], + [112.065699, 29.681323], + [112.089721, 29.685482], + [112.111279, 29.659483], + [112.178416, 29.656883], + [112.202438, 29.633997], + [112.244322, 29.659483], + [112.233851, 29.61631], + [112.303452, 29.585609], + [112.281278, 29.536676], + [112.291133, 29.517409], + [112.333017, 29.545007], + [112.368741, 29.541362], + [112.424792, 29.598619], + [112.439574, 29.633997], + [112.499321, 29.629316], + [112.54182, 29.60122], + [112.572001, 29.624113], + [112.640371, 29.607985], + [112.650842, 29.592374], + [112.693957, 29.601741], + [112.714283, 29.648561], + [112.733378, 29.645441], + [112.788812, 29.681323], + [112.79374, 29.735902], + [112.861493, 29.78318], + [112.894138, 29.783699], + [112.902145, 29.79149], + [112.929246, 29.77383], + [112.923703, 29.766557], + [112.926782, 29.692241], + [112.944645, 29.682883], + [112.974826, 29.732784], + [113.025949, 29.772791], + [113.005007, 29.693801], + [112.915696, 29.620992], + [112.912, 29.606944], + [112.950188, 29.473132], + [113.034572, 29.523658], + [113.057362, 29.522616], + [113.078304, 29.438218], + [113.099861, 29.459585], + [113.145441, 29.449163], + [113.181781, 29.485636], + [113.222433, 29.543965], + [113.277252, 29.594976], + [113.37765, 29.703158], + [113.571671, 29.849123], + [113.575367, 29.809147], + [113.550729, 29.768115], + [113.558736, 29.727067], + [113.540258, 29.699519], + [113.547033, 29.675603], + [113.606164, 29.666764], + [113.663446, 29.684443], + [113.680692, 29.64336], + [113.704098, 29.634518], + [113.73859, 29.579363], + [113.710257, 29.555419], + [113.630801, 29.523137], + [113.677613, 29.513763], + [113.755221, 29.446557], + [113.731199, 29.393907], + [113.674533, 29.388172], + [113.660982, 29.333405], + [113.632033, 29.316186], + [113.609859, 29.25146], + [113.651743, 29.225872], + [113.693011, 29.226394], + [113.691779, 29.19662], + [113.66283, 29.16945], + [113.690547, 29.114566], + [113.696091, 29.077437], + [113.722576, 29.104631], + [113.749677, 29.060699], + [113.775547, 29.095219], + [113.816199, 29.105154], + [113.852539, 29.058606], + [113.882104, 29.065407], + [113.876561, 29.038202], + [113.898119, 29.029307], + [113.94185, 29.047097], + [113.952321, 29.092604], + [113.98743, 29.126068], + [114.034857, 29.152204], + [114.063191, 29.204978], + [114.169748, 29.216993], + [114.252284, 29.23475], + [114.259059, 29.343839], + [114.307102, 29.365225], + [114.341595, 29.327665], + [114.376088, 29.322969], + [114.440145, 29.341752], + [114.466015, 29.324013], + [114.519602, 29.325578], + [114.589819, 29.352707], + [114.621847, 29.379828], + [114.67297, 29.395993], + [114.740724, 29.386607], + [114.759818, 29.363139], + [114.784455, 29.386086], + [114.812173, 29.383478], + [114.866375, 29.404335], + [114.895325, 29.397557], + [114.931049, 29.422581], + [114.947063, 29.465317], + [114.935977, 29.486678], + [114.90518, 29.473132], + [114.918114, 29.454374], + [114.888549, 29.436134], + [114.860216, 29.476258], + [114.900868, 29.505951], + [114.940288, 29.493971], + [114.966773, 29.522096], + [114.947679, 29.542924], + [115.00065, 29.572076], + [115.033295, 29.546568], + [115.087498, 29.560104], + [115.086266, 29.525741], + [115.154019, 29.510117], + [115.157099, 29.584568], + [115.120142, 29.597578], + [115.143548, 29.645961], + [115.117679, 29.655843], + [115.113367, 29.684963], + [115.176809, 29.654803], + [115.250722, 29.660003], + [115.28583, 29.618391], + [115.304924, 29.637118], + [115.355431, 29.649602], + [115.412714, 29.688602], + [115.470612, 29.739539], + [115.479235, 29.811224], + [115.51188, 29.840299], + [115.611662, 29.841337], + [115.667712, 29.850161], + [115.706517, 29.837703], + [115.762567, 29.793048], + [115.837096, 29.748373], + [115.909777, 29.723949], + [115.965827, 29.724469], + [116.049595, 29.761881], + [116.087167, 29.795125], + [116.13521, 29.819532], + [116.128435, 29.897904], + [116.073616, 29.969993], + [116.091479, 30.036331], + [116.078544, 30.062233], + [116.088399, 30.110391], + [116.055754, 30.180774], + [116.065609, 30.204569], + [115.997856, 30.252657], + [115.985537, 30.290905], + [115.903001, 30.31364], + [115.91532, 30.337919], + [115.885139, 30.379747], + [115.921479, 30.416397], + [115.894994, 30.452517], + [115.910393, 30.519046], + [115.887603, 30.542758], + [115.876516, 30.582438], + [115.848799, 30.602014], + [115.819234, 30.597893], + [115.81369, 30.637035], + [115.762567, 30.685426], + [115.782893, 30.751795], + [115.851262, 30.756938], + [115.863581, 30.815549], + [115.848799, 30.828397], + [115.865429, 30.864364], + [115.932566, 30.889532], + [115.976298, 30.931636], + [116.03974, 30.957813], + [116.071769, 30.956787], + [116.058834, 31.012711], + [116.015102, 31.011685], + [116.006479, 31.034764], + [115.938726, 31.04707], + [115.939958, 31.071678], + [115.887603, 31.10909], + [115.867277, 31.147512], + [115.837712, 31.127022], + [115.797676, 31.128047], + [115.778582, 31.112164], + [115.700973, 31.201276], + [115.655394, 31.211002], + [115.603655, 31.17363], + [115.585793, 31.143926], + [115.540213, 31.194621], + [115.539597, 31.231985], + [115.507568, 31.267799], + [115.473076, 31.265242], + [115.443511, 31.344498], + [115.40717, 31.337854], + [115.372062, 31.349098], + [115.393004, 31.389977], + [115.373909, 31.405813], + [115.338801, 31.40428], + [115.301229, 31.383846], + [115.250722, 31.392021], + [115.252569, 31.421646], + [115.211301, 31.442072], + [115.218077, 31.515057], + [115.235939, 31.555354], + [115.212533, 31.555354], + [115.16449, 31.604808], + [115.12507, 31.599201], + [115.106592, 31.567592], + [115.114599, 31.530362], + [115.096121, 31.508425], + [115.022824, 31.527811], + [114.995107, 31.471171], + [114.962462, 31.494648], + [114.884238, 31.469129], + [114.870071, 31.479337], + [114.830035, 31.45892], + [114.789383, 31.480358], + [114.778912, 31.520669], + [114.696376, 31.525771], + [114.641558, 31.582378], + [114.61692, 31.585437], + [114.572572, 31.553824], + [114.560869, 31.560963], + [114.547935, 31.623665], + [114.57134, 31.660858], + [114.586123, 31.762172], + [114.549783, 31.766751], + [114.530688, 31.742834], + [114.443841, 31.728074], + [114.403189, 31.746906], + [114.350218, 31.755557], + [114.292936, 31.752503], + [114.235654, 31.833382], + [114.191922, 31.852192], + [114.134024, 31.843042], + [114.121705, 31.809482], + [114.086596, 31.782014], + [114.017611, 31.770822], + [113.988662, 31.749959], + [113.952321, 31.793714], + [113.957865, 31.852701], + [113.914749, 31.877098], + [113.893807, 31.847109], + [113.854387, 31.843042], + [113.830981, 31.87913], + [113.832213, 31.918761], + [113.805728, 31.929428], + [113.817431, 31.964467], + [113.757685, 31.98985], + [113.791561, 32.036028], + [113.728735, 32.083197], + [113.722576, 32.12426], + [113.750293, 32.11615], + [113.782322, 32.184553], + [113.752757, 32.215951], + [113.73859, 32.255942], + [113.749061, 32.272642], + [113.768772, 32.30148], + [113.753989, 32.328286], + [113.76754, 32.370249], + [113.735511, 32.410677], + [113.700402, 32.420782], + [113.650511, 32.412698], + [113.624642, 32.36115], + [113.511925, 32.316654], + [113.428773, 32.270618], + [113.376418, 32.298445], + [113.353628, 32.294904], + [113.317904, 32.327275], + [113.333918, 32.336377], + [113.2366, 32.407141], + [113.211962, 32.431895], + [113.158992, 32.410677], + [113.155912, 32.380863], + [113.118956, 32.375809], + [113.107869, 32.398551], + [113.078919, 32.394508], + [113.025949, 32.425328], + [113.000695, 32.41674], + [112.992072, 32.378336], + [112.912, 32.390971], + [112.888594, 32.37682], + [112.860877, 32.396024], + [112.776493, 32.358623], + [112.735841, 32.356095], + [112.733993, 32.356601], + [112.724138, 32.358623], + [112.716747, 32.357612], + [112.645298, 32.368227], + [112.612037, 32.386928], + [112.589248, 32.381369], + [112.545516, 32.404109], + [112.530733, 32.37682], + [112.477147, 32.380863], + [112.448814, 32.34295], + [112.390915, 32.37126], + [112.360118, 32.3657], + [112.328089, 32.321712], + [112.206133, 32.392992], + [112.172873, 32.385412], + [112.150083, 32.411688], + [112.155626, 32.377326], + [112.081098, 32.425833], + [112.063851, 32.474315], + [112.014576, 32.450077], + [111.975772, 32.471791], + [111.948671, 32.51722], + [111.890157, 32.503089], + [111.858128, 32.528826], + [111.808853, 32.536899], + [111.713382, 32.606497], + [111.646245, 32.605993], + [111.640701, 32.634724], + [111.577875, 32.593388], + [111.530448, 32.628172], + [111.513202, 32.674026], + [111.458383, 32.726402], + [111.475629, 32.760127], + [111.41342, 32.757108], + [111.380159, 32.829049], + [111.293311, 32.859217], + [111.276065, 32.903445], + [111.255123, 32.883846], + [111.242804, 32.930573], + [111.273601, 32.971753], + [111.258819, 33.006389], + [111.221862, 33.042517], + [111.152877, 33.039507], + [111.192913, 33.071609], + [111.179363, 33.115229], + [111.146102, 33.12375], + [111.12824, 33.15532], + [111.08882, 33.181871], + [111.045704, 33.169849] + ] + ], + [ + [ + [109.106111, 30.570587], + [109.101183, 30.579346], + [109.09872, 30.579346], + [109.106111, 30.570587] + ] + ], + [ + [ + [111.046936, 33.202905], + [111.035849, 33.187881], + [111.034001, 33.177864], + [111.045704, 33.169849], + [111.046936, 33.202905] + ] + ], + [ + [ + [112.716747, 32.357612], + [112.735841, 32.356095], + [112.733993, 32.356601], + [112.724138, 32.358623], + [112.716747, 32.357612] + ] + ], + [ + [ + [112.902145, 29.79149], + [112.894138, 29.783699], + [112.923703, 29.766557], + [112.929246, 29.77383], + [112.902145, 29.79149] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 430000, + "name": "Hunan", + "center": [112.982279, 28.19409], + "centroid": [111.711649, 27.629216], + "childrenNum": 14, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 17, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [112.024431, 24.740308], + [112.03367, 24.771286], + [112.124214, 24.841364], + [112.149467, 24.837019], + [112.167329, 24.859828], + [112.175337, 24.927685], + [112.119902, 24.963499], + [112.12175, 24.989538], + [112.155626, 25.026419], + [112.151931, 25.055698], + [112.177184, 25.106649], + [112.187039, 25.182494], + [112.246785, 25.185202], + [112.256025, 25.159204], + [112.302836, 25.157037], + [112.315771, 25.175453], + [112.365046, 25.191701], + [112.414937, 25.14241], + [112.44327, 25.185744], + [112.458053, 25.152162], + [112.562762, 25.124531], + [112.628052, 25.140785], + [112.660081, 25.132658], + [112.712436, 25.083344], + [112.714899, 25.025876], + [112.742001, 24.99876], + [112.743233, 24.959701], + [112.778341, 24.947764], + [112.780805, 24.896747], + [112.873812, 24.896747], + [112.904609, 24.921715], + [112.941565, 24.915745], + [112.994536, 24.927142], + [113.009934, 24.977604], + [112.979137, 25.03401], + [113.004391, 25.089306], + [112.96805, 25.141869], + [112.97421, 25.168412], + [113.034572, 25.198199], + [112.992688, 25.247467], + [112.958195, 25.254503], + [112.897833, 25.238264], + [112.867036, 25.249632], + [112.854718, 25.337829], + [112.891058, 25.339993], + [112.924319, 25.296714], + [112.93479, 25.325929], + [112.969898, 25.350269], + [113.013014, 25.352432], + [113.078304, 25.382174], + [113.096782, 25.412449], + [113.131274, 25.414611], + [113.11834, 25.445418], + [113.176854, 25.471355], + [113.226129, 25.50971], + [113.248919, 25.514031], + [113.311129, 25.490264], + [113.314208, 25.442716], + [113.341926, 25.448661], + [113.373338, 25.402719], + [113.407215, 25.401637], + [113.449715, 25.359463], + [113.479896, 25.375145], + [113.535946, 25.368656], + [113.579062, 25.34432], + [113.584606, 25.306453], + [113.611707, 25.327552], + [113.680076, 25.334584], + [113.686852, 25.351891], + [113.753373, 25.362707], + [113.76446, 25.333502], + [113.814967, 25.328634], + [113.839605, 25.363248], + [113.877177, 25.380552], + [113.887032, 25.436772], + [113.94493, 25.441635], + [113.962792, 25.528072], + [113.986198, 25.529153], + [113.983118, 25.599336], + [113.957249, 25.611749], + [113.913517, 25.701299], + [113.920293, 25.741197], + [113.961561, 25.77731], + [113.971416, 25.836036], + [114.028082, 25.893119], + [114.028082, 25.98138], + [114.008372, 26.015806], + [114.044096, 26.076564], + [114.087828, 26.06635], + [114.121089, 26.085702], + [114.10569, 26.097526], + [114.188842, 26.121172], + [114.237501, 26.152333], + [114.216559, 26.203355], + [114.181451, 26.214631], + [114.102611, 26.187783], + [114.088444, 26.168448], + [114.013299, 26.184023], + [113.962792, 26.150722], + [113.949242, 26.192616], + [113.972647, 26.20604], + [113.978807, 26.237716], + [114.029314, 26.266163], + [114.021307, 26.288701], + [114.047792, 26.337518], + [114.030546, 26.376664], + [114.062575, 26.406149], + [114.085364, 26.406149], + [114.090292, 26.455988], + [114.110002, 26.482775], + [114.07243, 26.480096], + [114.10877, 26.56952], + [114.019459, 26.587182], + [113.996669, 26.615543], + [113.912901, 26.613938], + [113.860546, 26.664221], + [113.853771, 26.769532], + [113.835909, 26.806394], + [113.877177, 26.859262], + [113.890112, 26.895562], + [113.927068, 26.948922], + [113.892575, 26.964925], + [113.86301, 27.018252], + [113.824206, 27.036378], + [113.803264, 27.099261], + [113.771851, 27.096598], + [113.779242, 27.137081], + [113.846996, 27.222262], + [113.872865, 27.289828], + [113.854387, 27.30525], + [113.872865, 27.346721], + [113.872865, 27.384988], + [113.72812, 27.350442], + [113.699786, 27.331836], + [113.657902, 27.347253], + [113.616635, 27.345658], + [113.605548, 27.38924], + [113.632033, 27.40518], + [113.59754, 27.428554], + [113.591381, 27.467855], + [113.627105, 27.49971], + [113.583374, 27.524657], + [113.579062, 27.545354], + [113.608627, 27.585143], + [113.607395, 27.625449], + [113.652359, 27.663619], + [113.696707, 27.71979], + [113.69917, 27.740979], + [113.763228, 27.799228], + [113.756453, 27.860091], + [113.72812, 27.874904], + [113.752141, 27.93361], + [113.822974, 27.982243], + [113.845148, 27.971672], + [113.864242, 28.004966], + [113.914133, 27.991227], + [113.936307, 28.018703], + [113.966488, 28.017646], + [113.970184, 28.041418], + [114.025618, 28.031382], + [114.047176, 28.057263], + [114.025002, 28.080499], + [113.992357, 28.161255], + [114.012068, 28.174972], + [114.068734, 28.171806], + [114.107538, 28.182885], + [114.109386, 28.205038], + [114.143879, 28.246694], + [114.182067, 28.249858], + [114.198081, 28.29097], + [114.2529, 28.319423], + [114.252284, 28.395787], + [114.214712, 28.403157], + [114.172212, 28.432632], + [114.217175, 28.466308], + [114.218407, 28.48472], + [114.15435, 28.507337], + [114.138335, 28.533629], + [114.08598, 28.558337], + [114.132176, 28.607211], + [114.122321, 28.623497], + [114.157429, 28.761566], + [114.137719, 28.779926], + [114.153734, 28.829221], + [114.124784, 28.843376], + [114.076741, 28.834464], + [114.056415, 28.872204], + [114.060111, 28.902596], + [114.028082, 28.891069], + [114.005292, 28.917788], + [114.008988, 28.955498], + [113.973879, 28.937692], + [113.955401, 28.978536], + [113.961561, 28.999476], + [113.94185, 29.047097], + [113.898119, 29.029307], + [113.876561, 29.038202], + [113.882104, 29.065407], + [113.852539, 29.058606], + [113.816199, 29.105154], + [113.775547, 29.095219], + [113.749677, 29.060699], + [113.722576, 29.104631], + [113.696091, 29.077437], + [113.690547, 29.114566], + [113.66283, 29.16945], + [113.691779, 29.19662], + [113.693011, 29.226394], + [113.651743, 29.225872], + [113.609859, 29.25146], + [113.632033, 29.316186], + [113.660982, 29.333405], + [113.674533, 29.388172], + [113.731199, 29.393907], + [113.755221, 29.446557], + [113.677613, 29.513763], + [113.630801, 29.523137], + [113.710257, 29.555419], + [113.73859, 29.579363], + [113.704098, 29.634518], + [113.680692, 29.64336], + [113.663446, 29.684443], + [113.606164, 29.666764], + [113.547033, 29.675603], + [113.540258, 29.699519], + [113.558736, 29.727067], + [113.550729, 29.768115], + [113.575367, 29.809147], + [113.571671, 29.849123], + [113.37765, 29.703158], + [113.277252, 29.594976], + [113.222433, 29.543965], + [113.181781, 29.485636], + [113.145441, 29.449163], + [113.099861, 29.459585], + [113.078304, 29.438218], + [113.057362, 29.522616], + [113.034572, 29.523658], + [112.950188, 29.473132], + [112.912, 29.606944], + [112.915696, 29.620992], + [113.005007, 29.693801], + [113.025949, 29.772791], + [112.974826, 29.732784], + [112.944645, 29.682883], + [112.926782, 29.692241], + [112.923703, 29.766557], + [112.894138, 29.783699], + [112.861493, 29.78318], + [112.79374, 29.735902], + [112.788812, 29.681323], + [112.733378, 29.645441], + [112.714283, 29.648561], + [112.693957, 29.601741], + [112.650842, 29.592374], + [112.640371, 29.607985], + [112.572001, 29.624113], + [112.54182, 29.60122], + [112.499321, 29.629316], + [112.439574, 29.633997], + [112.424792, 29.598619], + [112.368741, 29.541362], + [112.333017, 29.545007], + [112.291133, 29.517409], + [112.281278, 29.536676], + [112.303452, 29.585609], + [112.233851, 29.61631], + [112.244322, 29.659483], + [112.202438, 29.633997], + [112.178416, 29.656883], + [112.111279, 29.659483], + [112.089721, 29.685482], + [112.065699, 29.681323], + [112.07617, 29.743696], + [112.008417, 29.778505], + [111.95483, 29.796683], + [111.965917, 29.832512], + [111.925881, 29.836665], + [111.899396, 29.855871], + [111.899396, 29.855871], + [111.861207, 29.856909], + [111.8107, 29.901017], + [111.75773, 29.92021], + [111.723853, 29.909317], + [111.723853, 29.909317], + [111.705375, 29.890121], + [111.669034, 29.888565], + [111.669034, 29.888565], + [111.553854, 29.894272], + [111.527368, 29.925916], + [111.475629, 29.918654], + [111.436825, 29.930065], + [111.394325, 29.912948], + [111.382623, 29.95029], + [111.342587, 29.944586], + [111.3315, 29.970512], + [111.266826, 30.01146], + [111.242188, 30.040476], + [111.031537, 30.048765], + [110.929907, 30.063268], + [110.924364, 30.111426], + [110.851067, 30.126439], + [110.746973, 30.112979], + [110.756212, 30.054463], + [110.712481, 30.033223], + [110.650887, 30.07777], + [110.600996, 30.054463], + [110.531394, 30.061197], + [110.497518, 30.055499], + [110.491358, 30.019751], + [110.557264, 29.988137], + [110.517228, 29.961179], + [110.49875, 29.91243], + [110.538786, 29.895828], + [110.549873, 29.848085], + [110.60038, 29.839779], + [110.642879, 29.775907], + [110.562807, 29.712515], + [110.507373, 29.692241], + [110.467337, 29.713034], + [110.447011, 29.664684], + [110.372482, 29.633477], + [110.339221, 29.668324], + [110.302265, 29.661563], + [110.289946, 29.6964], + [110.219729, 29.746814], + [110.160599, 29.753569], + [110.113788, 29.789932], + [110.02386, 29.769674], + [109.941325, 29.774349], + [109.908064, 29.763959], + [109.869876, 29.774869], + [109.779333, 29.757725], + [109.755311, 29.733304], + [109.760238, 29.689122], + [109.714659, 29.673524], + [109.701108, 29.636078], + [109.717739, 29.615269], + [109.664768, 29.599659], + [109.651833, 29.625674], + [109.578536, 29.629836], + [109.558826, 29.606944], + [109.516326, 29.626194], + [109.488609, 29.553336], + [109.467051, 29.560104], + [109.458428, 29.513242], + [109.433791, 29.530948], + [109.436254, 29.488761], + [109.415928, 29.497617], + [109.418392, 29.453332], + [109.368501, 29.413719], + [109.391291, 29.372005], + [109.343863, 29.369398], + [109.352487, 29.284872], + [109.312451, 29.25146], + [109.257632, 29.222738], + [109.275494, 29.202366], + [109.261328, 29.161089], + [109.274262, 29.121885], + [109.232378, 29.119271], + [109.240386, 29.086328], + [109.312451, 29.066453], + [109.319842, 29.042388], + [109.294588, 29.015177], + [109.292741, 28.987436], + [109.261328, 28.952356], + [109.235458, 28.882161], + [109.246545, 28.80143], + [109.241002, 28.776779], + [109.2989, 28.7474], + [109.294588, 28.722211], + [109.252704, 28.691767], + [109.271183, 28.671816], + [109.192958, 28.636104], + [109.201581, 28.597753], + [109.235458, 28.61982], + [109.252089, 28.606685], + [109.306907, 28.62087], + [109.319842, 28.579886], + [109.273646, 28.53836], + [109.274262, 28.494714], + [109.260712, 28.46473], + [109.264407, 28.392628], + [109.289045, 28.373673], + [109.268719, 28.33786], + [109.275494, 28.313101], + [109.317994, 28.277795], + [109.33524, 28.293605], + [109.388211, 28.268307], + [109.367885, 28.254602], + [109.340168, 28.19027], + [109.33832, 28.141731], + [109.314298, 28.103729], + [109.298284, 28.036136], + [109.335856, 28.063073], + [109.378972, 28.034551], + [109.362342, 28.007608], + [109.319842, 27.988585], + [109.30198, 27.956343], + [109.32169, 27.868027], + [109.346943, 27.838396], + [109.332777, 27.782815], + [109.37774, 27.736741], + [109.366653, 27.721909], + [109.414081, 27.725087], + [109.470747, 27.680049], + [109.45658, 27.673689], + [109.470131, 27.62863], + [109.451037, 27.586204], + [109.461508, 27.567637], + [109.404841, 27.55066], + [109.303211, 27.47582], + [109.300132, 27.423774], + [109.245313, 27.41793], + [109.202197, 27.450331], + [109.167089, 27.41793], + [109.141835, 27.448207], + [109.142451, 27.418461], + [109.103647, 27.336621], + [109.044517, 27.331304], + [109.053756, 27.293551], + [108.983539, 27.26802], + [108.963213, 27.235565], + [108.907778, 27.204699], + [108.926873, 27.160512], + [108.878829, 27.106187], + [108.79075, 27.084343], + [108.877597, 27.01612], + [108.942887, 27.017186], + [108.942887, 27.017186], + [108.940423, 27.044907], + [109.007561, 27.08008], + [109.032814, 27.104056], + [109.128901, 27.122701], + [109.101183, 27.06889], + [109.165857, 27.066758], + [109.21698, 27.114711], + [109.239154, 27.14933], + [109.264407, 27.131755], + [109.33524, 27.139212], + [109.358646, 27.153058], + [109.415312, 27.154123], + [109.441182, 27.117907], + [109.472595, 27.134951], + [109.454733, 27.069423], + [109.486761, 27.053968], + [109.497848, 27.079548], + [109.520022, 27.058764], + [109.555131, 26.946788], + [109.436254, 26.892359], + [109.452885, 26.861932], + [109.486761, 26.895562], + [109.509551, 26.877947], + [109.513247, 26.84004], + [109.497232, 26.815474], + [109.522486, 26.749226], + [109.528645, 26.743881], + [109.554515, 26.73533], + [109.597015, 26.756173], + [109.568065, 26.726243], + [109.528645, 26.743881], + [109.52187, 26.749226], + [109.486761, 26.759913], + [109.447957, 26.759913], + [109.407305, 26.719829], + [109.35495, 26.693098], + [109.283501, 26.698445], + [109.306291, 26.661012], + [109.334008, 26.646036], + [109.35495, 26.658873], + [109.390675, 26.598955], + [109.407305, 26.533116], + [109.381436, 26.518659], + [109.385747, 26.493487], + [109.362342, 26.472061], + [109.38082, 26.454381], + [109.319842, 26.418477], + [109.29582, 26.350389], + [109.271183, 26.327863], + [109.285965, 26.295676], + [109.325385, 26.29031], + [109.351255, 26.264016], + [109.369733, 26.277432], + [109.442414, 26.289774], + [109.467051, 26.313917], + [109.439334, 26.238789], + [109.47629, 26.148035], + [109.513863, 26.128157], + [109.502776, 26.096451], + [109.449805, 26.101826], + [109.452885, 26.055598], + [109.48245, 26.029788], + [109.513247, 25.998056], + [109.560058, 26.021184], + [109.588391, 26.019571], + [109.635203, 26.047533], + [109.649369, 26.016882], + [109.730057, 25.989988], + [109.710963, 25.954478], + [109.693717, 25.959321], + [109.67955, 25.921649], + [109.685094, 25.880197], + [109.768246, 25.890427], + [109.779333, 25.866196], + [109.811361, 25.877504], + [109.826144, 25.911422], + [109.806434, 25.973848], + [109.782412, 25.996981], + [109.814441, 26.041081], + [109.864332, 26.027637], + [109.898825, 26.095377], + [109.904368, 26.135679], + [109.970274, 26.195301], + [110.03002, 26.166299], + [110.099005, 26.168985], + [110.100853, 26.132455], + [110.065128, 26.050221], + [110.100853, 26.020108], + [110.168606, 26.028713], + [110.181541, 26.060437], + [110.24991, 26.010965], + [110.257301, 25.961473], + [110.325671, 25.975462], + [110.373098, 26.088927], + [110.437772, 26.153945], + [110.477808, 26.179727], + [110.495054, 26.166299], + [110.546793, 26.233421], + [110.552952, 26.283335], + [110.584365, 26.296749], + [110.612083, 26.333764], + [110.643495, 26.308552], + [110.673676, 26.317135], + [110.721104, 26.294066], + [110.742046, 26.313917], + [110.73527, 26.270993], + [110.759292, 26.248451], + [110.836284, 26.255966], + [110.939762, 26.286554], + [110.926212, 26.320354], + [110.944074, 26.326791], + [110.94469, 26.373447], + [110.974255, 26.385778], + [111.008747, 26.35897], + [111.008132, 26.336982], + [111.090667, 26.308016], + [111.208928, 26.30426], + [111.204616, 26.276359], + [111.228022, 26.261333], + [111.277913, 26.272066], + [111.293311, 26.222148], + [111.271754, 26.217316], + [111.274833, 26.183486], + [111.258203, 26.151796], + [111.26621, 26.095914], + [111.244652, 26.078177], + [111.267442, 26.058824], + [111.235413, 26.048071], + [111.189834, 25.953402], + [111.230486, 25.916267], + [111.251428, 25.864581], + [111.29208, 25.854349], + [111.297007, 25.874274], + [111.346282, 25.906577], + [111.376463, 25.906039], + [111.383239, 25.881812], + [111.460231, 25.885042], + [111.4861, 25.859196], + [111.43313, 25.84627], + [111.442369, 25.77192], + [111.399869, 25.744431], + [111.30871, 25.720171], + [111.309942, 25.645203], + [111.343202, 25.602574], + [111.324724, 25.564249], + [111.32842, 25.521592], + [111.279145, 25.42326], + [111.210776, 25.363248], + [111.184906, 25.367034], + [111.138711, 25.303748], + [111.103602, 25.285351], + [111.112841, 25.21715], + [110.998892, 25.161371], + [110.98411, 25.101772], + [110.951465, 25.04377], + [110.968711, 24.975434], + [111.009363, 24.921172], + [111.100522, 24.945593], + [111.101754, 25.035095], + [111.139943, 25.042144], + [111.200921, 25.074672], + [111.221862, 25.106649], + [111.274833, 25.151078], + [111.321645, 25.105023], + [111.36784, 25.108817], + [111.375231, 25.128324], + [111.435593, 25.093642], + [111.416499, 25.047566], + [111.467622, 25.02208], + [111.460231, 24.992793], + [111.43313, 24.979774], + [111.434977, 24.951562], + [111.470086, 24.92877], + [111.447296, 24.892947], + [111.449144, 24.857113], + [111.479325, 24.797366], + [111.461463, 24.728894], + [111.431282, 24.687574], + [111.451608, 24.665822], + [111.499035, 24.667997], + [111.526752, 24.637538], + [111.570484, 24.64461], + [111.588962, 24.690837], + [111.641933, 24.684856], + [111.637621, 24.715303], + [111.666571, 24.760961], + [111.708455, 24.788673], + [111.783599, 24.785957], + [111.814396, 24.770199], + [111.868599, 24.771829], + [111.875374, 24.756613], + [111.929577, 24.75607], + [111.951135, 24.769655], + [112.024431, 24.740308] + ] + ], + [ + [ + [109.528645, 26.743881], + [109.522486, 26.749226], + [109.52187, 26.749226], + [109.528645, 26.743881] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 440000, + "name": "Guangdong", + "center": [113.280637, 23.125178], + "centroid": [113.429919, 23.334643], + "childrenNum": 21, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 18, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [113.558736, 22.212244], + [113.594461, 22.228864], + [113.595693, 22.304186], + [113.617866, 22.315259], + [113.604932, 22.339617], + [113.627721, 22.349027], + [113.669605, 22.416539], + [113.66591, 22.438667], + [113.624642, 22.443092], + [113.608627, 22.408793], + [113.573519, 22.41156], + [113.631417, 22.475723], + [113.668373, 22.4807], + [113.691779, 22.514981], + [113.740438, 22.534329], + [113.717033, 22.645391], + [113.678228, 22.726007], + [113.733663, 22.736494], + [113.758301, 22.683496], + [113.765692, 22.665825], + [113.803264, 22.593463], + [113.856851, 22.539857], + [113.869786, 22.459685], + [113.893807, 22.442539], + [113.952937, 22.486783], + [113.954785, 22.491206], + [113.976343, 22.510558], + [114.031778, 22.503923], + [114.082285, 22.512216], + [114.095219, 22.534329], + [114.156813, 22.543726], + [114.166052, 22.559201], + [114.222719, 22.553122], + [114.232574, 22.539857], + [114.294784, 22.563623], + [114.321885, 22.587385], + [114.381631, 22.60175], + [114.427211, 22.589042], + [114.472174, 22.522168], + [114.476486, 22.459132], + [114.506667, 22.438667], + [114.549167, 22.465769], + [114.611377, 22.481806], + [114.628623, 22.513875], + [114.614456, 22.545384], + [114.568261, 22.560859], + [114.559022, 22.583517], + [114.603369, 22.638763], + [114.579964, 22.661407], + [114.51529, 22.655332], + [114.567029, 22.685705], + [114.591666, 22.690122], + [114.601521, 22.730975], + [114.689601, 22.7674], + [114.709927, 22.787817], + [114.749963, 22.764089], + [114.73518, 22.724351], + [114.728405, 22.651466], + [114.743803, 22.632687], + [114.746267, 22.581859], + [114.866375, 22.591805], + [114.88547, 22.538751], + [114.922426, 22.549253], + [114.927969, 22.621639], + [114.945216, 22.645391], + [115.039454, 22.713862], + [115.02344, 22.726007], + [115.053621, 22.747533], + [115.076411, 22.788368], + [115.154635, 22.80161], + [115.190975, 22.77347], + [115.190359, 22.818711], + [115.236555, 22.82533], + [115.230396, 22.776781], + [115.319091, 22.783402], + [115.338185, 22.776781], + [115.349272, 22.712206], + [115.381301, 22.684048], + [115.430576, 22.684048], + [115.471844, 22.697852], + [115.575322, 22.650914], + [115.565467, 22.684048], + [115.609198, 22.753052], + [115.541445, 22.755259], + [115.570394, 22.786713], + [115.583945, 22.82864], + [115.654162, 22.865591], + [115.696046, 22.84298], + [115.760103, 22.834707], + [115.788437, 22.809885], + [115.796444, 22.739254], + [115.829089, 22.734838], + [115.883291, 22.78561], + [115.931334, 22.802713], + [115.965211, 22.800506], + [115.99724, 22.826985], + [116.05637, 22.844635], + [116.104413, 22.816505], + [116.14137, 22.835259], + [116.239304, 22.921275], + [116.259014, 22.932298], + [116.302746, 22.951588], + [116.382818, 22.91907], + [116.449955, 22.936707], + [116.50539, 22.930645], + [116.544194, 22.996769], + [116.576839, 23.014397], + [116.557129, 23.056253], + [116.566368, 23.088738], + [116.550969, 23.109656], + [116.566368, 23.134424], + [116.665534, 23.158086], + [116.701259, 23.198248], + [116.74499, 23.215299], + [116.806584, 23.200998], + [116.821367, 23.240597], + [116.798577, 23.244996], + [116.782563, 23.313714], + [116.871874, 23.4159], + [116.871258, 23.416449], + [116.874338, 23.447199], + [116.874953, 23.447748], + [116.895895, 23.476295], + [116.888504, 23.501543], + [116.92854, 23.530079], + [116.963649, 23.507031], + [117.01046, 23.502641], + [117.044953, 23.539955], + [117.085605, 23.536663], + [117.192778, 23.5619], + [117.192778, 23.629356], + [117.147199, 23.654027], + [117.123793, 23.647448], + [117.055424, 23.694038], + [117.048032, 23.758687], + [117.019083, 23.801952], + [117.012308, 23.855054], + [116.981511, 23.855602], + [116.955642, 23.922359], + [116.976583, 23.931659], + [116.981511, 23.999471], + [116.953178, 24.008218], + [116.930388, 24.064514], + [116.9347, 24.126794], + [116.998757, 24.179217], + [116.956257, 24.216883], + [116.933468, 24.220157], + [116.938395, 24.28127], + [116.914374, 24.287817], + [116.919301, 24.321087], + [116.895895, 24.350533], + [116.903903, 24.369614], + [116.839229, 24.442097], + [116.860787, 24.460075], + [116.83307, 24.496568], + [116.796729, 24.502014], + [116.759157, 24.545572], + [116.761005, 24.583128], + [116.815207, 24.654944], + [116.777635, 24.679418], + [116.667382, 24.658752], + [116.623034, 24.64189], + [116.600861, 24.654401], + [116.570679, 24.621762], + [116.530027, 24.604895], + [116.506622, 24.621218], + [116.517709, 24.652225], + [116.485064, 24.720196], + [116.44626, 24.714216], + [116.416079, 24.744113], + [116.419158, 24.767482], + [116.375427, 24.803885], + [116.381586, 24.82507], + [116.417927, 24.840821], + [116.395137, 24.877746], + [116.363724, 24.87123], + [116.345862, 24.828872], + [116.297202, 24.801712], + [116.244232, 24.793563], + [116.251007, 24.82507], + [116.221442, 24.829959], + [116.191877, 24.877203], + [116.153073, 24.846795], + [116.068073, 24.850053], + [116.015102, 24.905975], + [115.985537, 24.899461], + [115.907929, 24.923343], + [115.89253, 24.936911], + [115.885139, 24.898918], + [115.907313, 24.879917], + [115.861733, 24.863629], + [115.863581, 24.891318], + [115.824161, 24.909232], + [115.807531, 24.862543], + [115.790284, 24.856027], + [115.764415, 24.791933], + [115.776734, 24.774546], + [115.756408, 24.749004], + [115.769342, 24.708236], + [115.801371, 24.705517], + [115.780429, 24.663103], + [115.797676, 24.628834], + [115.840791, 24.584217], + [115.843871, 24.562446], + [115.785357, 24.567345], + [115.752712, 24.546116], + [115.68927, 24.545027], + [115.671408, 24.604895], + [115.605503, 24.62557], + [115.569778, 24.622306], + [115.555611, 24.683768], + [115.522967, 24.702799], + [115.476771, 24.762591], + [115.412714, 24.79302], + [115.372678, 24.774546], + [115.358511, 24.735416], + [115.306772, 24.758787], + [115.269816, 24.749548], + [115.258729, 24.728894], + [115.1842, 24.711498], + [115.104744, 24.667997], + [115.083802, 24.699537], + [115.057317, 24.703343], + [115.024672, 24.669085], + [115.00373, 24.679418], + [114.940288, 24.650049], + [114.909491, 24.661471], + [114.893477, 24.582584], + [114.868839, 24.562446], + [114.846665, 24.602719], + [114.827571, 24.588026], + [114.781376, 24.613057], + [114.729637, 24.608704], + [114.73826, 24.565168], + [114.704999, 24.525973], + [114.664963, 24.583673], + [114.627391, 24.576598], + [114.589819, 24.537406], + [114.534384, 24.559181], + [114.429058, 24.48622], + [114.403189, 24.497657], + [114.391486, 24.563535], + [114.363769, 24.582584], + [114.300943, 24.578775], + [114.289856, 24.619042], + [114.258443, 24.641346], + [114.19069, 24.656576], + [114.169132, 24.689749], + [114.27261, 24.700624], + [114.281849, 24.724001], + [114.336052, 24.749004], + [114.342211, 24.807145], + [114.378551, 24.861457], + [114.403189, 24.877746], + [114.395798, 24.951019], + [114.454928, 24.977062], + [114.45616, 24.99659], + [114.506051, 24.999844], + [114.532536, 25.022623], + [114.561485, 25.077382], + [114.604601, 25.083886], + [114.640326, 25.074129], + [114.664963, 25.10123], + [114.735796, 25.121822], + [114.73518, 25.155954], + [114.685905, 25.173287], + [114.693912, 25.213902], + [114.73518, 25.225813], + [114.743188, 25.274528], + [114.714238, 25.315651], + [114.63663, 25.324306], + [114.599674, 25.385959], + [114.541159, 25.416773], + [114.477718, 25.37136], + [114.438914, 25.376226], + [114.43029, 25.343779], + [114.382863, 25.317274], + [114.31511, 25.33837], + [114.2954, 25.299961], + [114.260291, 25.291845], + [114.204857, 25.29942], + [114.190074, 25.316733], + [114.115545, 25.302125], + [114.083517, 25.275611], + [114.055799, 25.277775], + [114.039785, 25.250714], + [114.017611, 25.273987], + [114.029314, 25.328093], + [114.050256, 25.36433], + [113.983118, 25.415152], + [114.003444, 25.442716], + [113.94493, 25.441635], + [113.887032, 25.436772], + [113.877177, 25.380552], + [113.839605, 25.363248], + [113.814967, 25.328634], + [113.76446, 25.333502], + [113.753373, 25.362707], + [113.686852, 25.351891], + [113.680076, 25.334584], + [113.611707, 25.327552], + [113.584606, 25.306453], + [113.579062, 25.34432], + [113.535946, 25.368656], + [113.479896, 25.375145], + [113.449715, 25.359463], + [113.407215, 25.401637], + [113.373338, 25.402719], + [113.341926, 25.448661], + [113.314208, 25.442716], + [113.311129, 25.490264], + [113.248919, 25.514031], + [113.226129, 25.50971], + [113.176854, 25.471355], + [113.11834, 25.445418], + [113.131274, 25.414611], + [113.096782, 25.412449], + [113.078304, 25.382174], + [113.013014, 25.352432], + [112.969898, 25.350269], + [112.93479, 25.325929], + [112.924319, 25.296714], + [112.891058, 25.339993], + [112.854718, 25.337829], + [112.867036, 25.249632], + [112.897833, 25.238264], + [112.958195, 25.254503], + [112.992688, 25.247467], + [113.034572, 25.198199], + [112.97421, 25.168412], + [112.96805, 25.141869], + [113.004391, 25.089306], + [112.979137, 25.03401], + [113.009934, 24.977604], + [112.994536, 24.927142], + [112.941565, 24.915745], + [112.904609, 24.921715], + [112.873812, 24.896747], + [112.780805, 24.896747], + [112.778341, 24.947764], + [112.743233, 24.959701], + [112.742001, 24.99876], + [112.714899, 25.025876], + [112.712436, 25.083344], + [112.660081, 25.132658], + [112.628052, 25.140785], + [112.562762, 25.124531], + [112.458053, 25.152162], + [112.44327, 25.185744], + [112.414937, 25.14241], + [112.365046, 25.191701], + [112.315771, 25.175453], + [112.302836, 25.157037], + [112.256025, 25.159204], + [112.246785, 25.185202], + [112.187039, 25.182494], + [112.177184, 25.106649], + [112.151931, 25.055698], + [112.155626, 25.026419], + [112.12175, 24.989538], + [112.119902, 24.963499], + [112.175337, 24.927685], + [112.167329, 24.859828], + [112.149467, 24.837019], + [112.124214, 24.841364], + [112.03367, 24.771286], + [112.024431, 24.740308], + [111.961606, 24.721283], + [111.939432, 24.686487], + [111.953598, 24.64733], + [111.927729, 24.629378], + [111.936968, 24.595645], + [111.972077, 24.578775], + [112.007185, 24.534684], + [112.009649, 24.503103], + [111.985011, 24.467701], + [112.025047, 24.438828], + [112.057692, 24.387057], + [112.05954, 24.339628], + [112.026279, 24.294908], + [111.990555, 24.279634], + [111.986243, 24.25672], + [111.958526, 24.263813], + [111.912946, 24.221795], + [111.877222, 24.227252], + [111.871062, 24.176487], + [111.886461, 24.163929], + [111.878454, 24.109862], + [111.92157, 24.012045], + [111.940664, 23.987989], + [111.911714, 23.943693], + [111.854432, 23.947521], + [111.845809, 23.904305], + [111.812548, 23.887343], + [111.824867, 23.832612], + [111.8107, 23.80688], + [111.722621, 23.823305], + [111.683201, 23.822758], + [111.683201, 23.822758], + [111.654868, 23.833159], + [111.627766, 23.78881], + [111.621607, 23.725819], + [111.666571, 23.718696], + [111.614832, 23.65896], + [111.615448, 23.639225], + [111.555702, 23.64087], + [111.487332, 23.626615], + [111.479941, 23.532822], + [111.428818, 23.466414], + [111.399869, 23.469159], + [111.383239, 23.399423], + [111.389398, 23.375804], + [111.363528, 23.340641], + [111.376463, 23.30437], + [111.353058, 23.284582], + [111.36476, 23.240047], + [111.388782, 23.210349], + [111.38447, 23.16744], + [111.365992, 23.14488], + [111.377695, 23.082132], + [111.402333, 23.066165], + [111.43313, 23.073322], + [111.433746, 23.036428], + [111.389398, 23.005583], + [111.403565, 22.99126], + [111.362913, 22.967568], + [111.374615, 22.938361], + [111.358601, 22.889301], + [111.218167, 22.748085], + [111.185522, 22.735942], + [111.118385, 22.744773], + [111.058023, 22.729871], + [111.089435, 22.695643], + [111.055559, 22.648705], + [110.997045, 22.631582], + [110.958856, 22.636553], + [110.950233, 22.61059], + [110.896031, 22.613352], + [110.897878, 22.591805], + [110.812263, 22.576333], + [110.778386, 22.585174], + [110.749437, 22.556991], + [110.762988, 22.518298], + [110.740198, 22.498947], + [110.74143, 22.464109], + [110.688459, 22.477935], + [110.712481, 22.440879], + [110.711249, 22.369506], + [110.74143, 22.361757], + [110.749437, 22.329653], + [110.787009, 22.28259], + [110.759292, 22.274837], + [110.725415, 22.29588], + [110.687843, 22.249914], + [110.646575, 22.220554], + [110.678604, 22.172901], + [110.629329, 22.149068], + [110.598532, 22.162924], + [110.602843, 22.18343], + [110.55788, 22.196175], + [110.505525, 22.14297], + [110.456866, 22.189526], + [110.414366, 22.208365], + [110.378026, 22.164587], + [110.34846, 22.195621], + [110.326287, 22.152393], + [110.364475, 22.125785], + [110.35154, 22.097508], + [110.359547, 22.015973], + [110.352772, 21.97602], + [110.374946, 21.967695], + [110.374946, 21.967695], + [110.378642, 21.939942], + [110.378642, 21.939942], + [110.391576, 21.89386], + [110.337374, 21.887751], + [110.290562, 21.917736], + [110.283787, 21.892194], + [110.224041, 21.882198], + [110.224041, 21.882198], + [110.212338, 21.886085], + [110.212338, 21.886085], + [110.196323, 21.899968], + [110.12857, 21.902744], + [110.101469, 21.86998], + [110.050962, 21.857205], + [109.999839, 21.881643], + [109.94502, 21.84443], + [109.940093, 21.769419], + [109.916071, 21.668787], + [109.888354, 21.652101], + [109.888354, 21.652101], + [109.839695, 21.636525], + [109.786108, 21.637638], + [109.778101, 21.670455], + [109.742992, 21.616497], + [109.754695, 21.556396], + [109.788572, 21.490702], + [109.785492, 21.45673], + [109.819369, 21.445033], + [109.894513, 21.442248], + [109.904368, 21.429992], + [109.868644, 21.365913], + [109.770709, 21.359783], + [109.757775, 21.346963], + [109.763934, 21.226514], + [109.674623, 21.136671], + [109.674007, 21.067997], + [109.655529, 20.929435], + [109.664768, 20.862343], + [109.711579, 20.774519], + [109.730057, 20.719673], + [109.74484, 20.621124], + [109.793499, 20.615522], + [109.813825, 20.574627], + [109.811977, 20.541566], + [109.839695, 20.489439], + [109.888354, 20.475423], + [109.895745, 20.42776], + [109.864948, 20.40196], + [109.861252, 20.376717], + [109.916071, 20.316677], + [109.909296, 20.236961], + [109.929006, 20.211691], + [109.993679, 20.254368], + [110.082375, 20.258859], + [110.118099, 20.219553], + [110.168606, 20.219553], + [110.220345, 20.25156], + [110.296722, 20.249314], + [110.349076, 20.258859], + [110.384185, 20.293103], + [110.425453, 20.291419], + [110.452554, 20.311064], + [110.491358, 20.373912], + [110.54125, 20.42047], + [110.550489, 20.47262], + [110.499982, 20.572386], + [110.487047, 20.640167], + [110.466105, 20.680485], + [110.411286, 20.670966], + [110.392192, 20.682724], + [110.407591, 20.731987], + [110.393424, 20.816479], + [110.350924, 20.84165], + [110.327519, 20.847802], + [110.269004, 20.839972], + [110.209874, 20.860106], + [110.184005, 20.891979], + [110.180925, 20.98197], + [110.204947, 21.003202], + [110.208642, 21.050684], + [110.241903, 21.016051], + [110.24991, 21.045098], + [110.296722, 21.093684], + [110.39096, 21.124949], + [110.422373, 21.190807], + [110.451322, 21.186343], + [110.501213, 21.217588], + [110.534474, 21.204198], + [110.626249, 21.215915], + [110.65951, 21.239902], + [110.713097, 21.3124], + [110.768531, 21.364799], + [110.796248, 21.37483], + [110.888639, 21.367585], + [110.929291, 21.375945], + [111.034617, 21.438906], + [111.103602, 21.455616], + [111.171355, 21.458401], + [111.28284, 21.485691], + [111.276065, 21.443362], + [111.250196, 21.45116], + [111.257587, 21.41495], + [111.28592, 21.41885], + [111.353058, 21.464528], + [111.382623, 21.495714], + [111.444217, 21.514088], + [111.494724, 21.501282], + [111.521825, 21.517429], + [111.560629, 21.50518], + [111.609904, 21.530234], + [111.650556, 21.512418], + [111.677658, 21.529677], + [111.693672, 21.590345], + [111.736788, 21.609821], + [111.794686, 21.61149], + [111.832258, 21.578659], + [111.810084, 21.555283], + [111.887693, 21.578659], + [111.941896, 21.607039], + [111.972692, 21.603144], + [112.026895, 21.633744], + [111.997946, 21.657107], + [111.954214, 21.667674], + [111.956062, 21.710494], + [112.036134, 21.761637], + [112.136532, 21.793871], + [112.192583, 21.789425], + [112.196894, 21.736624], + [112.236315, 21.727173], + [112.238778, 21.702153], + [112.353343, 21.707157], + [112.415553, 21.734956], + [112.427256, 21.789981], + [112.445734, 21.803317], + [112.497473, 21.785535], + [112.535661, 21.753856], + [112.647146, 21.758302], + [112.68595, 21.810541], + [112.792508, 21.921067], + [112.841167, 21.920512], + [112.893522, 21.84443], + [112.929862, 21.838875], + [112.989608, 21.869424], + [113.047507, 21.956595], + [113.053666, 22.012089], + [113.032108, 22.04593], + [113.045659, 22.088636], + [113.086927, 22.12634], + [113.091854, 22.065344], + [113.142977, 22.012089], + [113.1516, 21.979905], + [113.235368, 21.887751], + [113.266781, 21.871646], + [113.319752, 21.909407], + [113.330223, 21.96159], + [113.442324, 22.009315], + [113.45957, 22.043711], + [113.527939, 22.073663], + [113.567359, 22.075327], + [113.554425, 22.107489], + [113.554425, 22.142416], + [113.534715, 22.174009], + [113.53841, 22.209473], + [113.558736, 22.212244] + ] + ], + [ + [ + [117.024627, 23.437865], + [116.982743, 23.460924], + [116.944555, 23.440061], + [116.951946, 23.419744], + [117.027091, 23.41535], + [117.050496, 23.400522], + [117.081909, 23.409309], + [117.124409, 23.389537], + [117.142887, 23.400522], + [117.142887, 23.459826], + [117.129336, 23.483431], + [117.093612, 23.459277], + [117.058503, 23.47355], + [117.029554, 23.443356], + [117.024627, 23.437865] + ] + ], + [ + [ + [112.853486, 21.740515], + [112.876275, 21.772753], + [112.840551, 21.776644], + [112.782653, 21.739959], + [112.724138, 21.719945], + [112.70566, 21.679354], + [112.734609, 21.666562], + [112.780189, 21.671568], + [112.730914, 21.613715], + [112.775261, 21.564189], + [112.817145, 21.590345], + [112.798667, 21.610933], + [112.821457, 21.655994], + [112.804826, 21.686583], + [112.83316, 21.736624], + [112.853486, 21.740515] + ] + ], + [ + [ + [112.530733, 21.583667], + [112.563378, 21.591458], + [112.571385, 21.619835], + [112.621277, 21.606482], + [112.665624, 21.642644], + [112.639139, 21.67268], + [112.66624, 21.683803], + [112.663776, 21.714386], + [112.592327, 21.693256], + [112.560299, 21.666562], + [112.57077, 21.645982], + [112.535045, 21.628737], + [112.530733, 21.583667] + ] + ], + [ + [ + [114.231342, 22.016528], + [114.311414, 22.041493], + [114.302791, 22.050368], + [114.239965, 22.03539], + [114.231342, 22.016528] + ] + ], + [ + [ + [110.43346, 21.171276], + [110.489511, 21.138904], + [110.508605, 21.140579], + [110.544945, 21.083633], + [110.582517, 21.094801], + [110.632409, 21.210893], + [110.589293, 21.194713], + [110.525235, 21.190249], + [110.499366, 21.213125], + [110.445163, 21.184669], + [110.431612, 21.180763], + [110.43346, 21.171276] + ] + ], + [ + [ + [112.435263, 21.663781], + [112.456205, 21.648763], + [112.458669, 21.68992], + [112.435263, 21.663781] + ] + ], + [ + [ + [110.517844, 21.079166], + [110.459946, 21.062971], + [110.398352, 21.096476], + [110.352772, 21.079724], + [110.305961, 21.0881], + [110.27578, 21.033369], + [110.211106, 20.986999], + [110.201251, 20.938378], + [110.309656, 20.963529], + [110.347845, 20.984763], + [110.407591, 20.990351], + [110.47288, 20.983087], + [110.511684, 20.916578], + [110.535706, 20.922727], + [110.539402, 20.987557], + [110.560344, 21.061295], + [110.517844, 21.079166] + ] + ], + [ + [ + [113.765076, 21.962145], + [113.774315, 21.998218], + [113.74167, 21.991559], + [113.765076, 21.962145] + ] + ], + [ + [ + [113.723192, 21.922177], + [113.742902, 21.950489], + [113.71888, 21.951599], + [113.723192, 21.922177] + ] + ], + [ + [ + [113.142977, 21.831653], + [113.162071, 21.853873], + [113.203955, 21.861093], + [113.167615, 21.876644], + [113.136818, 21.868869], + [113.142977, 21.831653] + ] + ], + [ + [ + [113.819894, 22.396068], + [113.813735, 22.419858], + [113.786634, 22.413773], + [113.819894, 22.396068] + ] + ], + [ + [ + [114.190074, 21.986564], + [114.229494, 21.995443], + [114.180835, 22.00987], + [114.190074, 21.986564] + ] + ], + [ + [ + [114.153734, 21.97491], + [114.171596, 22.000437], + [114.124169, 21.985455], + [114.153734, 21.97491] + ] + ], + [ + [ + [116.769628, 20.771721], + [116.761005, 20.750456], + [116.87249, 20.738143], + [116.889736, 20.683284], + [116.849084, 20.628405], + [116.749302, 20.600958], + [116.796113, 20.582471], + [116.862635, 20.588633], + [116.905135, 20.619443], + [116.934084, 20.676565], + [116.925461, 20.726949], + [116.88604, 20.775638], + [116.820135, 20.780674], + [116.769628, 20.771721] + ] + ], + [ + [ + [113.025333, 21.847762], + [113.045659, 21.882753], + [113.007471, 21.869424], + [113.025333, 21.847762] + ] + ], + [ + [ + [110.405127, 20.678245], + [110.437772, 20.677685], + [110.414366, 20.710157], + [110.405127, 20.678245] + ] + ], + [ + [ + [110.644727, 20.935584], + [110.584365, 20.948998], + [110.548641, 20.908752], + [110.562807, 20.861224], + [110.611467, 20.860106], + [110.646575, 20.917137], + [110.644727, 20.935584] + ] + ], + [ + [ + [110.556648, 20.32734], + [110.593604, 20.360447], + [110.586213, 20.381205], + [110.556648, 20.32734] + ] + ], + [ + [ + [115.943037, 21.097592], + [115.953508, 21.064088], + [115.989233, 21.035603], + [116.040356, 21.02052], + [116.067457, 21.04063], + [116.044051, 21.110434], + [116.024341, 21.12439], + [115.965211, 21.123832], + [115.943037, 21.097592] + ] + ], + [ + [ + [115.926407, 20.981411], + [115.939342, 20.945644], + [115.970139, 20.919373], + [115.999088, 20.922727], + [116.000936, 20.948439], + [115.954124, 20.99985], + [115.926407, 20.981411] + ] + ], + [ + [ + [115.834632, 22.722695], + [115.834632, 22.722143], + [115.835248, 22.722695], + [115.834632, 22.722695] + ] + ], + [ + [ + [115.834632, 22.723247], + [115.834632, 22.722695], + [115.835248, 22.722695], + [115.834632, 22.723247] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 450000, + "name": "Guangxi", + "center": [108.320004, 22.82402], + "centroid": [108.7944, 23.833381], + "childrenNum": 14, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 19, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [109.48245, 26.029788], + [109.473211, 26.006663], + [109.408537, 25.967392], + [109.435022, 25.93349], + [109.396834, 25.900117], + [109.359262, 25.836036], + [109.339552, 25.83442], + [109.327849, 25.76168], + [109.340168, 25.731493], + [109.296436, 25.71424], + [109.207125, 25.740119], + [109.206509, 25.788087], + [109.147995, 25.741736], + [109.13198, 25.762758], + [109.143683, 25.795092], + [109.095024, 25.80533], + [109.077778, 25.776771], + [109.048213, 25.790781], + [108.989698, 25.778926], + [108.999553, 25.765453], + [108.963829, 25.732572], + [108.940423, 25.740119], + [108.896076, 25.71424], + [108.900387, 25.682423], + [108.953974, 25.686738], + [108.953974, 25.686738], + [109.007561, 25.734728], + [109.043285, 25.738502], + [109.07901, 25.72071], + [109.075314, 25.693749], + [109.030966, 25.629556], + [109.051908, 25.566949], + [109.088249, 25.550752], + [109.024807, 25.51241], + [108.949046, 25.557231], + [108.8893, 25.543193], + [108.890532, 25.556151], + [108.826474, 25.550212], + [108.814772, 25.526992], + [108.781511, 25.554531], + [108.799989, 25.576666], + [108.783975, 25.628477], + [108.724844, 25.634952], + [108.68912, 25.623081], + [108.68604, 25.587462], + [108.660787, 25.584763], + [108.658323, 25.550212], + [108.68912, 25.533473], + [108.634917, 25.520512], + [108.6072, 25.491885], + [108.600425, 25.432448], + [108.62999, 25.335666], + [108.625062, 25.308076], + [108.589338, 25.335125], + [108.585642, 25.365952], + [108.471693, 25.458928], + [108.418723, 25.443257], + [108.400244, 25.491344], + [108.359592, 25.513491], + [108.348506, 25.536173], + [108.308469, 25.525912], + [108.280752, 25.48], + [108.241332, 25.46217], + [108.251803, 25.430286], + [108.192673, 25.458928], + [108.162492, 25.444878], + [108.193289, 25.405421], + [108.142782, 25.390825], + [108.152021, 25.324306], + [108.143398, 25.269658], + [108.115065, 25.210112], + [108.080572, 25.193867], + [108.001732, 25.196574], + [107.928435, 25.155954], + [107.872384, 25.141327], + [107.839124, 25.115861], + [107.762747, 25.125073], + [107.789233, 25.15487], + [107.760283, 25.188451], + [107.762131, 25.229061], + [107.741805, 25.24043], + [107.700537, 25.194408], + [107.696226, 25.219858], + [107.661733, 25.258833], + [107.659885, 25.316192], + [107.632168, 25.310241], + [107.599523, 25.250714], + [107.576734, 25.256668], + [107.512676, 25.209029], + [107.472024, 25.213902], + [107.489886, 25.276693], + [107.481263, 25.299961], + [107.432604, 25.289139], + [107.409198, 25.347024], + [107.420901, 25.392987], + [107.375937, 25.411908], + [107.358691, 25.393528], + [107.318039, 25.401637], + [107.308184, 25.432988], + [107.336517, 25.461089], + [107.263836, 25.543193], + [107.232423, 25.556691], + [107.228728, 25.604733], + [107.205322, 25.607971], + [107.185612, 25.578825], + [107.064272, 25.559391], + [107.066736, 25.50917], + [107.015613, 25.495666], + [106.996519, 25.442716], + [106.963874, 25.437852], + [106.987896, 25.358922], + [107.012533, 25.352973], + [107.013765, 25.275611], + [106.975577, 25.232851], + [106.933077, 25.250714], + [106.904128, 25.231768], + [106.888113, 25.181953], + [106.853005, 25.186827], + [106.787715, 25.17112], + [106.764926, 25.183036], + [106.732281, 25.162454], + [106.691013, 25.179245], + [106.644817, 25.164621], + [106.63989, 25.132658], + [106.590615, 25.08768], + [106.551195, 25.082802], + [106.519782, 25.054072], + [106.450181, 25.033468], + [106.442173, 25.019369], + [106.332536, 24.988454], + [106.304819, 24.973807], + [106.253696, 24.971094], + [106.215508, 24.981944], + [106.191486, 24.95319], + [106.145291, 24.954275], + [106.197645, 24.885889], + [106.206269, 24.851139], + [106.173008, 24.760417], + [106.150218, 24.762591], + [106.113878, 24.714216], + [106.047356, 24.684312], + [106.024566, 24.633186], + [105.961741, 24.677786], + [105.942031, 24.725088], + [105.863806, 24.729437], + [105.827466, 24.702799], + [105.767104, 24.719109], + [105.70551, 24.768569], + [105.617431, 24.78161], + [105.607576, 24.803885], + [105.573083, 24.797366], + [105.497322, 24.809318], + [105.493011, 24.833217], + [105.457286, 24.87123], + [105.428337, 24.930941], + [105.365511, 24.943423], + [105.334099, 24.9266], + [105.267577, 24.929313], + [105.251563, 24.967296], + [105.212758, 24.995505], + [105.178266, 24.985199], + [105.157324, 24.958616], + [105.131454, 24.959701], + [105.09573, 24.92877], + [105.096346, 24.928228], + [105.082179, 24.915745], + [105.077868, 24.918459], + [105.039064, 24.872859], + [105.026745, 24.815836], + [105.03352, 24.787586], + [104.899245, 24.752809], + [104.865985, 24.730524], + [104.841963, 24.676155], + [104.771746, 24.659839], + [104.729246, 24.617953], + [104.703377, 24.645698], + [104.628848, 24.660927], + [104.595587, 24.709323], + [104.529682, 24.731611], + [104.489646, 24.653313], + [104.520443, 24.535228], + [104.550008, 24.518894], + [104.575877, 24.424661], + [104.616529, 24.421937], + [104.63008, 24.397958], + [104.610986, 24.377246], + [104.641783, 24.367979], + [104.70892, 24.321087], + [104.721239, 24.340173], + [104.703377, 24.419757], + [104.715695, 24.441552], + [104.74834, 24.435559], + [104.765587, 24.45953], + [104.784681, 24.443732], + [104.83642, 24.446456], + [104.914028, 24.426296], + [104.930042, 24.411038], + [104.979933, 24.412673], + [105.042759, 24.442097], + [105.106817, 24.414853], + [105.111744, 24.37234], + [105.138846, 24.376701], + [105.188121, 24.347261], + [105.196744, 24.326541], + [105.164715, 24.288362], + [105.215222, 24.214699], + [105.24294, 24.208695], + [105.229389, 24.165567], + [105.182577, 24.167205], + [105.20044, 24.105491], + [105.260186, 24.061236], + [105.292831, 24.074896], + [105.273121, 24.092927], + [105.320548, 24.116416], + [105.334099, 24.094566], + [105.395692, 24.065607], + [105.406163, 24.043748], + [105.493011, 24.016965], + [105.533663, 24.130071], + [105.594641, 24.137718], + [105.628518, 24.126794], + [105.649459, 24.032816], + [105.704278, 24.0667], + [105.739387, 24.059596], + [105.765256, 24.073804], + [105.802212, 24.051945], + [105.796669, 24.023524], + [105.841633, 24.03063], + [105.859495, 24.056864], + [105.89214, 24.040468], + [105.908154, 24.069432], + [105.901995, 24.099482], + [105.919241, 24.122425], + [105.963589, 24.110954], + [105.998081, 24.120786], + [106.011632, 24.099482], + [106.04982, 24.089649], + [106.053516, 24.051399], + [106.096631, 24.018058], + [106.091088, 23.998924], + [106.128044, 23.956819], + [106.157609, 23.891174], + [106.192718, 23.879135], + [106.173008, 23.861622], + [106.192102, 23.824947], + [106.136667, 23.795381], + [106.157609, 23.724175], + [106.149602, 23.665538], + [106.120653, 23.605229], + [106.141595, 23.569579], + [106.08616, 23.524043], + [106.071994, 23.495506], + [106.039965, 23.484529], + [105.999929, 23.447748], + [105.986378, 23.489469], + [105.935871, 23.508678], + [105.913081, 23.499348], + [105.89214, 23.52514], + [105.852103, 23.526786], + [105.815763, 23.507031], + [105.805908, 23.467512], + [105.758481, 23.459826], + [105.699966, 23.40162], + [105.637757, 23.404366], + [105.694423, 23.363168], + [105.699966, 23.327453], + [105.649459, 23.346136], + [105.593409, 23.312614], + [105.560148, 23.257093], + [105.526272, 23.234548], + [105.542902, 23.184495], + [105.558916, 23.177893], + [105.574931, 23.066165], + [105.625438, 23.064513], + [105.648844, 23.078828], + [105.724604, 23.06231], + [105.74185, 23.030921], + [105.780039, 23.022659], + [105.805908, 22.994565], + [105.839169, 22.987403], + [105.879205, 22.916865], + [105.893987, 22.936707], + [105.959277, 22.948832], + [105.994385, 22.93781], + [106.019639, 22.990709], + [106.08616, 22.996218], + [106.106486, 22.980792], + [106.153914, 22.988505], + [106.206885, 22.978588], + [106.270326, 22.907494], + [106.258007, 22.889852], + [106.286957, 22.867245], + [106.366413, 22.857871], + [106.37134, 22.878273], + [106.41384, 22.877171], + [106.504383, 22.91025], + [106.525941, 22.946628], + [106.562282, 22.923479], + [106.606013, 22.925684], + [106.631267, 22.88103], + [106.657136, 22.863385], + [106.674998, 22.891506], + [106.716882, 22.881582], + [106.709491, 22.866142], + [106.774781, 22.812643], + [106.776012, 22.813746], + [106.778476, 22.814298], + [106.779092, 22.813746], + [106.779708, 22.813195], + [106.78094, 22.813195], + [106.784636, 22.812643], + [106.796338, 22.812091], + [106.801882, 22.815401], + [106.804346, 22.816505], + [106.808657, 22.817608], + [106.813585, 22.817608], + [106.838838, 22.803265], + [106.820976, 22.768504], + [106.768621, 22.739254], + [106.780324, 22.708894], + [106.756302, 22.68957], + [106.711955, 22.575228], + [106.650361, 22.575228], + [106.61402, 22.602303], + [106.585071, 22.517192], + [106.588151, 22.472958], + [106.560434, 22.455813], + [106.588767, 22.374486], + [106.562897, 22.345706], + [106.663296, 22.33076], + [106.670071, 22.283144], + [106.688549, 22.260438], + [106.7021, 22.207257], + [106.673151, 22.182322], + [106.706411, 22.160707], + [106.691629, 22.13521], + [106.71565, 22.089745], + [106.706411, 22.021521], + [106.683006, 21.999882], + [106.698404, 21.959925], + [106.73844, 22.008205], + [106.790179, 22.004876], + [106.802498, 21.98157], + [106.859164, 21.986009], + [106.926302, 21.967695], + [106.935541, 21.933836], + [106.974345, 21.923288], + [106.999598, 21.947714], + [107.05996, 21.914959], + [107.058729, 21.887196], + [107.018693, 21.859427], + [107.018077, 21.81943], + [107.093837, 21.803317], + [107.148656, 21.758858], + [107.194851, 21.736624], + [107.199163, 21.718833], + [107.242279, 21.703265], + [107.271844, 21.727173], + [107.310648, 21.733844], + [107.356843, 21.667674], + [107.363619, 21.602031], + [107.388256, 21.594241], + [107.431372, 21.642088], + [107.477567, 21.659888], + [107.500973, 21.613715], + [107.486806, 21.59591], + [107.547168, 21.58645], + [107.584741, 21.614828], + [107.603219, 21.597579], + [107.712856, 21.616497], + [107.807711, 21.655438], + [107.837892, 21.640419], + [107.863761, 21.650988], + [107.892095, 21.622617], + [107.893942, 21.596466], + [107.929051, 21.585893], + [107.958, 21.534131], + [108.034376, 21.545821], + [108.108289, 21.508521], + [108.193905, 21.519656], + [108.156332, 21.55083], + [108.205608, 21.597579], + [108.241332, 21.599805], + [108.249955, 21.561406], + [108.210535, 21.505737], + [108.230245, 21.491259], + [108.330027, 21.540254], + [108.397781, 21.533017], + [108.492635, 21.554727], + [108.591802, 21.677129], + [108.626294, 21.67991], + [108.658939, 21.643757], + [108.678033, 21.659331], + [108.735931, 21.628181], + [108.734084, 21.626512], + [108.745786, 21.602587], + [108.801837, 21.626512], + [108.83325, 21.610933], + [108.881293, 21.627068], + [108.937959, 21.589789], + [109.093792, 21.579215], + [109.09872, 21.571424], + [109.110422, 21.568085], + [109.138756, 21.567528], + [109.142451, 21.511861], + [109.074698, 21.489589], + [109.039589, 21.457844], + [109.046365, 21.424421], + [109.095024, 21.419407], + [109.138756, 21.388762], + [109.186183, 21.390991], + [109.245929, 21.425536], + [109.41716, 21.438906], + [109.484914, 21.453388], + [109.529877, 21.437234], + [109.540964, 21.466199], + [109.576689, 21.493487], + [109.604406, 21.523553], + [109.612413, 21.556953], + [109.654913, 21.493487], + [109.704188, 21.462857], + [109.785492, 21.45673], + [109.788572, 21.490702], + [109.754695, 21.556396], + [109.742992, 21.616497], + [109.778101, 21.670455], + [109.786108, 21.637638], + [109.839695, 21.636525], + [109.888354, 21.652101], + [109.888354, 21.652101], + [109.916071, 21.668787], + [109.940093, 21.769419], + [109.94502, 21.84443], + [109.999839, 21.881643], + [110.050962, 21.857205], + [110.101469, 21.86998], + [110.12857, 21.902744], + [110.196323, 21.899968], + [110.212338, 21.886085], + [110.212338, 21.886085], + [110.224041, 21.882198], + [110.224041, 21.882198], + [110.283787, 21.892194], + [110.290562, 21.917736], + [110.337374, 21.887751], + [110.391576, 21.89386], + [110.378642, 21.939942], + [110.378642, 21.939942], + [110.374946, 21.967695], + [110.374946, 21.967695], + [110.352772, 21.97602], + [110.359547, 22.015973], + [110.35154, 22.097508], + [110.364475, 22.125785], + [110.326287, 22.152393], + [110.34846, 22.195621], + [110.378026, 22.164587], + [110.414366, 22.208365], + [110.456866, 22.189526], + [110.505525, 22.14297], + [110.55788, 22.196175], + [110.602843, 22.18343], + [110.598532, 22.162924], + [110.629329, 22.149068], + [110.678604, 22.172901], + [110.646575, 22.220554], + [110.687843, 22.249914], + [110.725415, 22.29588], + [110.759292, 22.274837], + [110.787009, 22.28259], + [110.749437, 22.329653], + [110.74143, 22.361757], + [110.711249, 22.369506], + [110.712481, 22.440879], + [110.688459, 22.477935], + [110.74143, 22.464109], + [110.740198, 22.498947], + [110.762988, 22.518298], + [110.749437, 22.556991], + [110.778386, 22.585174], + [110.812263, 22.576333], + [110.897878, 22.591805], + [110.896031, 22.613352], + [110.950233, 22.61059], + [110.958856, 22.636553], + [110.997045, 22.631582], + [111.055559, 22.648705], + [111.089435, 22.695643], + [111.058023, 22.729871], + [111.118385, 22.744773], + [111.185522, 22.735942], + [111.218167, 22.748085], + [111.358601, 22.889301], + [111.374615, 22.938361], + [111.362913, 22.967568], + [111.403565, 22.99126], + [111.389398, 23.005583], + [111.433746, 23.036428], + [111.43313, 23.073322], + [111.402333, 23.066165], + [111.377695, 23.082132], + [111.365992, 23.14488], + [111.38447, 23.16744], + [111.388782, 23.210349], + [111.36476, 23.240047], + [111.353058, 23.284582], + [111.376463, 23.30437], + [111.363528, 23.340641], + [111.389398, 23.375804], + [111.383239, 23.399423], + [111.399869, 23.469159], + [111.428818, 23.466414], + [111.479941, 23.532822], + [111.487332, 23.626615], + [111.555702, 23.64087], + [111.615448, 23.639225], + [111.614832, 23.65896], + [111.666571, 23.718696], + [111.621607, 23.725819], + [111.627766, 23.78881], + [111.654868, 23.833159], + [111.683201, 23.822758], + [111.683201, 23.822758], + [111.722621, 23.823305], + [111.8107, 23.80688], + [111.824867, 23.832612], + [111.812548, 23.887343], + [111.845809, 23.904305], + [111.854432, 23.947521], + [111.911714, 23.943693], + [111.940664, 23.987989], + [111.92157, 24.012045], + [111.878454, 24.109862], + [111.886461, 24.163929], + [111.871062, 24.176487], + [111.877222, 24.227252], + [111.912946, 24.221795], + [111.958526, 24.263813], + [111.986243, 24.25672], + [111.990555, 24.279634], + [112.026279, 24.294908], + [112.05954, 24.339628], + [112.057692, 24.387057], + [112.025047, 24.438828], + [111.985011, 24.467701], + [112.009649, 24.503103], + [112.007185, 24.534684], + [111.972077, 24.578775], + [111.936968, 24.595645], + [111.927729, 24.629378], + [111.953598, 24.64733], + [111.939432, 24.686487], + [111.961606, 24.721283], + [112.024431, 24.740308], + [111.951135, 24.769655], + [111.929577, 24.75607], + [111.875374, 24.756613], + [111.868599, 24.771829], + [111.814396, 24.770199], + [111.783599, 24.785957], + [111.708455, 24.788673], + [111.666571, 24.760961], + [111.637621, 24.715303], + [111.641933, 24.684856], + [111.588962, 24.690837], + [111.570484, 24.64461], + [111.526752, 24.637538], + [111.499035, 24.667997], + [111.451608, 24.665822], + [111.431282, 24.687574], + [111.461463, 24.728894], + [111.479325, 24.797366], + [111.449144, 24.857113], + [111.447296, 24.892947], + [111.470086, 24.92877], + [111.434977, 24.951562], + [111.43313, 24.979774], + [111.460231, 24.992793], + [111.467622, 25.02208], + [111.416499, 25.047566], + [111.435593, 25.093642], + [111.375231, 25.128324], + [111.36784, 25.108817], + [111.321645, 25.105023], + [111.274833, 25.151078], + [111.221862, 25.106649], + [111.200921, 25.074672], + [111.139943, 25.042144], + [111.101754, 25.035095], + [111.100522, 24.945593], + [111.009363, 24.921172], + [110.968711, 24.975434], + [110.951465, 25.04377], + [110.98411, 25.101772], + [110.998892, 25.161371], + [111.112841, 25.21715], + [111.103602, 25.285351], + [111.138711, 25.303748], + [111.184906, 25.367034], + [111.210776, 25.363248], + [111.279145, 25.42326], + [111.32842, 25.521592], + [111.324724, 25.564249], + [111.343202, 25.602574], + [111.309942, 25.645203], + [111.30871, 25.720171], + [111.399869, 25.744431], + [111.442369, 25.77192], + [111.43313, 25.84627], + [111.4861, 25.859196], + [111.460231, 25.885042], + [111.383239, 25.881812], + [111.376463, 25.906039], + [111.346282, 25.906577], + [111.297007, 25.874274], + [111.29208, 25.854349], + [111.251428, 25.864581], + [111.230486, 25.916267], + [111.189834, 25.953402], + [111.235413, 26.048071], + [111.267442, 26.058824], + [111.244652, 26.078177], + [111.26621, 26.095914], + [111.258203, 26.151796], + [111.274833, 26.183486], + [111.271754, 26.217316], + [111.293311, 26.222148], + [111.277913, 26.272066], + [111.228022, 26.261333], + [111.204616, 26.276359], + [111.208928, 26.30426], + [111.090667, 26.308016], + [111.008132, 26.336982], + [111.008747, 26.35897], + [110.974255, 26.385778], + [110.94469, 26.373447], + [110.944074, 26.326791], + [110.926212, 26.320354], + [110.939762, 26.286554], + [110.836284, 26.255966], + [110.759292, 26.248451], + [110.73527, 26.270993], + [110.742046, 26.313917], + [110.721104, 26.294066], + [110.673676, 26.317135], + [110.643495, 26.308552], + [110.612083, 26.333764], + [110.584365, 26.296749], + [110.552952, 26.283335], + [110.546793, 26.233421], + [110.495054, 26.166299], + [110.477808, 26.179727], + [110.437772, 26.153945], + [110.373098, 26.088927], + [110.325671, 25.975462], + [110.257301, 25.961473], + [110.24991, 26.010965], + [110.181541, 26.060437], + [110.168606, 26.028713], + [110.100853, 26.020108], + [110.065128, 26.050221], + [110.100853, 26.132455], + [110.099005, 26.168985], + [110.03002, 26.166299], + [109.970274, 26.195301], + [109.904368, 26.135679], + [109.898825, 26.095377], + [109.864332, 26.027637], + [109.814441, 26.041081], + [109.782412, 25.996981], + [109.806434, 25.973848], + [109.826144, 25.911422], + [109.811361, 25.877504], + [109.779333, 25.866196], + [109.768246, 25.890427], + [109.685094, 25.880197], + [109.67955, 25.921649], + [109.693717, 25.959321], + [109.710963, 25.954478], + [109.730057, 25.989988], + [109.649369, 26.016882], + [109.635203, 26.047533], + [109.588391, 26.019571], + [109.560058, 26.021184], + [109.513247, 25.998056], + [109.48245, 26.029788] + ] + ], + [ + [ + [105.096346, 24.928228], + [105.09573, 24.92877], + [105.077868, 24.918459], + [105.082179, 24.915745], + [105.096346, 24.928228] + ] + ], + [ + [ + [109.088249, 21.014934], + [109.11227, 21.02499], + [109.117814, 21.017727], + [109.144299, 21.041189], + [109.138756, 21.067439], + [109.09256, 21.057386], + [109.088865, 21.031134], + [109.088249, 21.014934] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 460000, + "name": "Hainan", + "center": [110.33119, 20.031971], + "centroid": [109.754859, 19.189767], + "childrenNum": 19, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 20, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [110.106396, 20.026812], + [110.042339, 19.991384], + [109.997375, 19.980136], + [109.965346, 19.993634], + [109.898825, 19.994196], + [109.855093, 19.984073], + [109.814441, 19.993072], + [109.76147, 19.981261], + [109.712195, 20.017253], + [109.657993, 20.01163], + [109.585312, 19.98801], + [109.526797, 19.943573], + [109.498464, 19.873236], + [109.411001, 19.895184], + [109.349407, 19.898561], + [109.300748, 19.917693], + [109.25948, 19.898561], + [109.255784, 19.867045], + [109.231147, 19.863105], + [109.159082, 19.79048], + [109.169553, 19.736411], + [109.147379, 19.704863], + [109.093792, 19.68965], + [109.048829, 19.619764], + [108.993394, 19.587065], + [108.92872, 19.524468], + [108.855424, 19.469182], + [108.806148, 19.450561], + [108.765496, 19.400894], + [108.694047, 19.387346], + [108.644772, 19.349518], + [108.609048, 19.276661], + [108.591186, 19.141592], + [108.598577, 19.055633], + [108.630606, 19.003017], + [108.637997, 18.924346], + [108.595497, 18.872256], + [108.593033, 18.809386], + [108.65278, 18.740258], + [108.663866, 18.67337], + [108.641077, 18.565614], + [108.644772, 18.486738], + [108.68912, 18.447571], + [108.776583, 18.441894], + [108.881293, 18.416344], + [108.905315, 18.389087], + [108.944735, 18.314107], + [109.006329, 18.323198], + [109.108575, 18.323766], + [109.138756, 18.268081], + [109.17448, 18.260125], + [109.287813, 18.264671], + [109.355566, 18.215221], + [109.441182, 18.199303], + [109.467051, 18.173718], + [109.527413, 18.169169], + [109.584696, 18.143579], + [109.661688, 18.175424], + [109.726362, 18.177698], + [109.749767, 18.193618], + [109.785492, 18.339672], + [109.919767, 18.375457], + [110.022629, 18.360121], + [110.070672, 18.376025], + [110.090382, 18.399309], + [110.116867, 18.506602], + [110.214186, 18.578662], + [110.246215, 18.609859], + [110.329366, 18.642185], + [110.367555, 18.631977], + [110.499366, 18.651824], + [110.499366, 18.751592], + [110.578206, 18.784458], + [110.590525, 18.838841], + [110.585597, 18.88075], + [110.619474, 19.152334], + [110.676756, 19.286264], + [110.706321, 19.320153], + [110.729727, 19.378878], + [110.787009, 19.399765], + [110.844292, 19.449996], + [110.888023, 19.518827], + [110.920668, 19.552668], + [111.008747, 19.60398], + [111.061718, 19.612436], + [111.071573, 19.628784], + [111.043856, 19.763448], + [111.013675, 19.850159], + [110.966248, 20.018377], + [110.940994, 20.028499], + [110.871393, 20.01163], + [110.808567, 20.035808], + [110.778386, 20.068415], + [110.744509, 20.074036], + [110.717408, 20.148778], + [110.687843, 20.163947], + [110.655814, 20.134169], + [110.562191, 20.110006], + [110.526467, 20.07516], + [110.495054, 20.077408], + [110.387265, 20.113378], + [110.318279, 20.108882], + [110.28933, 20.056047], + [110.243135, 20.077408], + [110.144585, 20.074598], + [110.106396, 20.026812] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 500000, + "name": "Chongqing", + "center": [106.504962, 29.533155], + "centroid": [107.8839, 30.067297], + "childrenNum": 38, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 21, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [106.37442, 28.525742], + [106.403369, 28.569901], + [106.477282, 28.530474], + [106.504999, 28.544669], + [106.466811, 28.586193], + [106.49268, 28.591448], + [106.502535, 28.661313], + [106.528405, 28.677591], + [106.492064, 28.742153], + [106.461883, 28.761041], + [106.45326, 28.817162], + [106.474202, 28.832891], + [106.561666, 28.756319], + [106.56105, 28.719062], + [106.587535, 28.691767], + [106.6171, 28.691242], + [106.617716, 28.66709], + [106.651593, 28.649235], + [106.618332, 28.645033], + [106.63681, 28.622972], + [106.606629, 28.593024], + [106.615252, 28.549401], + [106.567825, 28.523638], + [106.564745, 28.485247], + [106.632499, 28.503655], + [106.697788, 28.47683], + [106.708259, 28.450524], + [106.747063, 28.467361], + [106.726121, 28.51838], + [106.73844, 28.554657], + [106.77786, 28.563068], + [106.756918, 28.607211], + [106.784636, 28.626649], + [106.807425, 28.589346], + [106.830831, 28.623497], + [106.866556, 28.624548], + [106.889345, 28.695966], + [106.86594, 28.690192], + [106.824056, 28.756319], + [106.845614, 28.780975], + [106.872099, 28.777304], + [106.923222, 28.809821], + [106.951555, 28.766812], + [106.988512, 28.776254], + [106.983584, 28.851239], + [107.019308, 28.861722], + [107.016229, 28.882685], + [107.14188, 28.887925], + [107.206554, 28.868535], + [107.194851, 28.838134], + [107.227496, 28.836037], + [107.210866, 28.817686], + [107.219489, 28.772582], + [107.24659, 28.76209], + [107.261373, 28.792514], + [107.327894, 28.810869], + [107.339597, 28.845997], + [107.383945, 28.848618], + [107.41351, 28.911502], + [107.441227, 28.943977], + [107.412894, 28.960211], + [107.396879, 28.993718], + [107.364235, 29.00942], + [107.395647, 29.041341], + [107.369778, 29.091558], + [107.412278, 29.094696], + [107.427676, 29.128682], + [107.408582, 29.138091], + [107.401807, 29.184603], + [107.441227, 29.203934], + [107.486806, 29.174153], + [107.570574, 29.218037], + [107.589052, 29.150113], + [107.605683, 29.164747], + [107.659885, 29.162656], + [107.700537, 29.141228], + [107.749197, 29.199754], + [107.810791, 29.139137], + [107.784921, 29.048143], + [107.823725, 29.034016], + [107.810175, 28.984295], + [107.867457, 28.960211], + [107.882855, 29.00628], + [107.908725, 29.007327], + [107.925971, 29.032446], + [108.026369, 29.039772], + [108.070717, 29.086328], + [108.150173, 29.053375], + [108.193289, 29.072207], + [108.256115, 29.040295], + [108.277673, 29.091558], + [108.306622, 29.079006], + [108.297999, 29.045527], + [108.319556, 28.961258], + [108.345426, 28.943453], + [108.357745, 28.893165], + [108.346658, 28.859625], + [108.352817, 28.815589], + [108.386078, 28.803003], + [108.385462, 28.772058], + [108.347274, 28.736381], + [108.332491, 28.679166], + [108.439049, 28.634003], + [108.501258, 28.626649], + [108.50249, 28.63768], + [108.575787, 28.659738], + [108.636149, 28.621396], + [108.604736, 28.590922], + [108.610896, 28.539412], + [108.573939, 28.531], + [108.586874, 28.463678], + [108.609664, 28.43579], + [108.609048, 28.407368], + [108.576403, 28.38631], + [108.580099, 28.343128], + [108.611512, 28.324691], + [108.667562, 28.334173], + [108.656475, 28.359981], + [108.697127, 28.401051], + [108.688504, 28.422106], + [108.640461, 28.456838], + [108.657091, 28.47683], + [108.700207, 28.48209], + [108.709446, 28.501026], + [108.746402, 28.45105], + [108.780279, 28.42579], + [108.759953, 28.389995], + [108.783359, 28.380518], + [108.761801, 28.304143], + [108.726692, 28.282011], + [108.738395, 28.228241], + [108.772888, 28.212949], + [108.821547, 28.245113], + [108.855424, 28.199764], + [108.89546, 28.219804], + [108.923793, 28.217167], + [108.929952, 28.19027], + [109.005713, 28.162837], + [109.026655, 28.220331], + [109.086401, 28.184467], + [109.101799, 28.202401], + [109.081473, 28.247749], + [109.117198, 28.277795], + [109.152306, 28.349975], + [109.153538, 28.417369], + [109.191726, 28.471043], + [109.23361, 28.474726], + [109.274262, 28.494714], + [109.273646, 28.53836], + [109.319842, 28.579886], + [109.306907, 28.62087], + [109.252089, 28.606685], + [109.235458, 28.61982], + [109.201581, 28.597753], + [109.192958, 28.636104], + [109.271183, 28.671816], + [109.252704, 28.691767], + [109.294588, 28.722211], + [109.2989, 28.7474], + [109.241002, 28.776779], + [109.246545, 28.80143], + [109.235458, 28.882161], + [109.261328, 28.952356], + [109.292741, 28.987436], + [109.294588, 29.015177], + [109.319842, 29.042388], + [109.312451, 29.066453], + [109.240386, 29.086328], + [109.232378, 29.119271], + [109.215748, 29.145409], + [109.162777, 29.180946], + [109.139372, 29.168927], + [109.110422, 29.21647], + [109.141835, 29.270256], + [109.106727, 29.288526], + [109.11227, 29.361053], + [109.060531, 29.403292], + [109.034662, 29.360531], + [108.999553, 29.36366], + [108.983539, 29.332883], + [108.919481, 29.3261], + [108.934264, 29.399643], + [108.927488, 29.435612], + [108.884373, 29.440824], + [108.866511, 29.470527], + [108.888684, 29.502305], + [108.878213, 29.539279], + [108.913322, 29.574679], + [108.901003, 29.604863], + [108.870206, 29.596537], + [108.888068, 29.628795], + [108.844337, 29.658443], + [108.781511, 29.635558], + [108.797525, 29.660003], + [108.786438, 29.691721], + [108.752562, 29.649082], + [108.690968, 29.689642], + [108.676801, 29.749412], + [108.680497, 29.800319], + [108.658939, 29.854833], + [108.601041, 29.863656], + [108.556077, 29.818493], + [108.52528, 29.770713], + [108.548686, 29.749412], + [108.504954, 29.728626], + [108.504338, 29.707836], + [108.460606, 29.741098], + [108.437201, 29.741098], + [108.442744, 29.778505], + [108.422418, 29.772791], + [108.424266, 29.815897], + [108.371295, 29.841337], + [108.433505, 29.880262], + [108.467998, 29.864175], + [108.516041, 29.885451], + [108.517889, 29.9394], + [108.536367, 29.983472], + [108.532055, 30.051873], + [108.513577, 30.057571], + [108.546222, 30.104178], + [108.56778, 30.157491], + [108.551766, 30.1637], + [108.581947, 30.255759], + [108.54499, 30.269716], + [108.524048, 30.309506], + [108.501258, 30.314673], + [108.460606, 30.35961], + [108.431041, 30.354446], + [108.402092, 30.376649], + [108.430425, 30.416397], + [108.411331, 30.438586], + [108.42673, 30.492233], + [108.472925, 30.487076], + [108.512961, 30.501515], + [108.556077, 30.487592], + [108.56778, 30.468508], + [108.6497, 30.53915], + [108.642925, 30.578831], + [108.688504, 30.58759], + [108.698975, 30.54482], + [108.743939, 30.494812], + [108.789518, 30.513374], + [108.808612, 30.491202], + [108.838793, 30.503062], + [108.893612, 30.565434], + [108.971836, 30.627766], + [109.006329, 30.626736], + [109.042669, 30.655571], + [109.071002, 30.640125], + [109.111654, 30.646303], + [109.106111, 30.61077], + [109.105495, 30.585529], + [109.102415, 30.580377], + [109.101183, 30.579346], + [109.106111, 30.570587], + [109.103647, 30.565949], + [109.143683, 30.521108], + [109.191726, 30.545851], + [109.191726, 30.545851], + [109.245313, 30.580892], + [109.299516, 30.630341], + [109.314298, 30.599953], + [109.36111, 30.551004], + [109.337088, 30.521623], + [109.35495, 30.487076], + [109.418392, 30.559766], + [109.435638, 30.595832], + [109.535421, 30.664837], + [109.543428, 30.63961], + [109.574225, 30.646818], + [109.590855, 30.69366], + [109.625348, 30.702923], + [109.661072, 30.738936], + [109.656761, 30.760538], + [109.701724, 30.783677], + [109.780564, 30.848437], + [109.828608, 30.864364], + [109.894513, 30.899803], + [109.943788, 30.878746], + [110.008462, 30.883369], + [110.019549, 30.829425], + [110.048498, 30.800642], + [110.082375, 30.799614], + [110.151976, 30.911613], + [110.153824, 30.953708], + [110.172918, 30.978853], + [110.140889, 30.987062], + [110.140273, 31.030661], + [110.120563, 31.0322], + [110.119947, 31.088592], + [110.147048, 31.116776], + [110.180309, 31.121899], + [110.200019, 31.158779], + [110.180309, 31.179774], + [110.155671, 31.279564], + [110.161831, 31.314338], + [110.118715, 31.409899], + [110.054042, 31.410921], + [110.036795, 31.436966], + [109.98752, 31.474744], + [109.94502, 31.47066], + [109.969658, 31.508935], + [109.894513, 31.519139], + [109.837847, 31.555354], + [109.727594, 31.548214], + [109.745456, 31.598182], + [109.76455, 31.602769], + [109.737449, 31.628761], + [109.731289, 31.700582], + [109.683246, 31.719929], + [109.622268, 31.711783], + [109.585928, 31.726546], + [109.549587, 31.73011], + [109.502776, 31.716365], + [109.446109, 31.722983], + [109.381436, 31.705165], + [109.281654, 31.716874], + [109.282885, 31.743343], + [109.253936, 31.759628], + [109.279806, 31.776418], + [109.27611, 31.79931], + [109.195422, 31.817618], + [109.191111, 31.85575], + [109.123357, 31.892851], + [109.085785, 31.929428], + [108.986619, 31.980205], + [108.902235, 31.984774], + [108.837561, 32.039072], + [108.78767, 32.04871], + [108.75133, 32.076098], + [108.734084, 32.106519], + [108.676801, 32.10297], + [108.585026, 32.17189], + [108.543758, 32.177969], + [108.509882, 32.201266], + [108.480317, 32.182527], + [108.399013, 32.194176], + [108.370063, 32.172397], + [108.379918, 32.154158], + [108.379918, 32.154158], + [108.379303, 32.153652], + [108.379303, 32.153652], + [108.399628, 32.147065], + [108.452599, 32.090296], + [108.42981, 32.061391], + [108.372527, 32.077112], + [108.344194, 32.067477], + [108.362056, 32.035521], + [108.329411, 32.020299], + [108.370063, 31.988835], + [108.351585, 31.971575], + [108.307238, 31.997463], + [108.259194, 31.967006], + [108.343578, 31.860834], + [108.386078, 31.854226], + [108.391005, 31.829822], + [108.429194, 31.809482], + [108.455063, 31.814059], + [108.462454, 31.780488], + [108.535135, 31.757592], + [108.50557, 31.734182], + [108.514809, 31.693963], + [108.546838, 31.665442], + [108.519121, 31.665952], + [108.468614, 31.636404], + [108.442744, 31.633856], + [108.390389, 31.591555], + [108.386078, 31.544134], + [108.339266, 31.539033], + [108.344194, 31.512506], + [108.254883, 31.49873], + [108.233941, 31.506894], + [108.191441, 31.492096], + [108.193289, 31.467598], + [108.224086, 31.464024], + [108.216079, 31.41041], + [108.153869, 31.371073], + [108.185898, 31.336831], + [108.095354, 31.268311], + [108.038688, 31.252964], + [108.031297, 31.217144], + [108.07626, 31.231985], + [108.089811, 31.204859], + [108.025753, 31.116263], + [108.009123, 31.109602], + [108.026985, 31.061938], + [108.060246, 31.052197], + [108.00358, 31.025533], + [107.983254, 30.983983], + [107.942602, 30.989114], + [107.948145, 30.918802], + [107.994956, 30.908533], + [107.956152, 30.882855], + [107.851443, 30.792931], + [107.788001, 30.81966], + [107.763979, 30.817091], + [107.760899, 30.862823], + [107.739957, 30.884396], + [107.693146, 30.875665], + [107.645103, 30.821202], + [107.57735, 30.847924], + [107.515756, 30.854603], + [107.483111, 30.838675], + [107.498509, 30.809381], + [107.454162, 30.771851], + [107.454162, 30.771851], + [107.424597, 30.74048], + [107.458473, 30.704981], + [107.477567, 30.664837], + [107.516987, 30.644759], + [107.485575, 30.598408], + [107.427676, 30.547397], + [107.443075, 30.53348], + [107.408582, 30.521623], + [107.368546, 30.468508], + [107.338981, 30.386459], + [107.288474, 30.337402], + [107.257677, 30.267131], + [107.221337, 30.213878], + [107.103076, 30.090198], + [107.080286, 30.094341], + [107.084598, 30.063786], + [107.058113, 30.043066], + [107.055649, 30.040476], + [107.054417, 30.040994], + [107.053801, 30.043584], + [107.02054, 30.036849], + [106.981736, 30.08502], + [106.976193, 30.083467], + [106.94478, 30.037367], + [106.913367, 30.025451], + [106.862244, 30.033223], + [106.83699, 30.049801], + [106.825904, 30.03115], + [106.825904, 30.03115], + [106.785252, 30.01716], + [106.732281, 30.027005], + [106.724274, 30.058607], + [106.699636, 30.074145], + [106.700252, 30.111944], + [106.672535, 30.122297], + [106.677462, 30.156974], + [106.631883, 30.186464], + [106.611557, 30.235596], + [106.612173, 30.235596], + [106.611557, 30.235596], + [106.612173, 30.235596], + [106.612173, 30.235596], + [106.612789, 30.235596], + [106.612789, 30.235596], + [106.642354, 30.246454], + [106.611557, 30.292455], + [106.560434, 30.31519], + [106.545035, 30.296589], + [106.49884, 30.295556], + [106.43971, 30.308473], + [106.428623, 30.254725], + [106.401521, 30.242318], + [106.349167, 30.24542], + [106.334384, 30.225772], + [106.306667, 30.238182], + [106.296196, 30.205603], + [106.264167, 30.20974], + [106.260471, 30.19681], + [106.232754, 30.185947], + [106.180399, 30.233011], + [106.168696, 30.303823], + [106.132356, 30.323972], + [106.132972, 30.30279], + [106.07261, 30.333786], + [106.031958, 30.373551], + [105.943263, 30.372002], + [105.900763, 30.405042], + [105.84656, 30.410203], + [105.825618, 30.436006], + [105.792357, 30.427234], + [105.760329, 30.384393], + [105.754785, 30.342567], + [105.714749, 30.322939], + [105.720292, 30.252657], + [105.720292, 30.252657], + [105.670401, 30.254208], + [105.624822, 30.275918], + [105.619894, 30.234045], + [105.662394, 30.210258], + [105.642684, 30.186464], + [105.56138, 30.183878], + [105.550909, 30.179222], + [105.536127, 30.152834], + [105.596489, 30.159043], + [105.574315, 30.130579], + [105.580474, 30.129544], + [105.582938, 30.127474], + [105.582938, 30.12385], + [105.642068, 30.101072], + [105.638988, 30.076216], + [105.676561, 30.06793], + [105.687032, 30.038922], + [105.719677, 30.042548], + [105.753553, 30.018196], + [105.723372, 29.975177], + [105.730763, 29.95755], + [105.70243, 29.924879], + [105.717213, 29.893753], + [105.738771, 29.891159], + [105.707974, 29.840818], + [105.610655, 29.837184], + [105.582938, 29.819013], + [105.574931, 29.744216], + [105.529351, 29.707836], + [105.481924, 29.718232], + [105.476996, 29.674564], + [105.419714, 29.688082], + [105.38091, 29.628275], + [105.347649, 29.621512], + [105.332867, 29.592374], + [105.296526, 29.571035], + [105.305149, 29.53199], + [105.337794, 29.459064], + [105.334099, 29.441345], + [105.387069, 29.455416], + [105.387069, 29.455416], + [105.399388, 29.43874], + [105.372903, 29.421018], + [105.426489, 29.419454], + [105.441888, 29.400686], + [105.418482, 29.352185], + [105.42033, 29.31149], + [105.465294, 29.322969], + [105.459134, 29.288526], + [105.513337, 29.283306], + [105.521344, 29.264513], + [105.557684, 29.278608], + [105.631597, 29.280174], + [105.647612, 29.253027], + [105.695039, 29.287482], + [105.712285, 29.219082], + [105.703662, 29.176766], + [105.728916, 29.134432], + [105.752321, 29.129727], + [105.728916, 29.1062], + [105.757865, 29.069068], + [105.74185, 29.039249], + [105.766488, 29.013607], + [105.762176, 28.9911], + [105.801596, 28.958116], + [105.797285, 28.936121], + [105.830546, 28.944501], + [105.852719, 28.927217], + [105.910002, 28.920407], + [105.969132, 28.965971], + [106.001161, 28.973824], + [106.040581, 28.955498], + [106.049204, 28.906263], + [106.070762, 28.919884], + [106.101559, 28.898928], + [106.14837, 28.901548], + [106.173008, 28.920407], + [106.206885, 28.904691], + [106.264783, 28.845997], + [106.245689, 28.817686], + [106.267863, 28.779402], + [106.274022, 28.739004], + [106.305435, 28.704365], + [106.304203, 28.64976], + [106.346703, 28.583565], + [106.33192, 28.55308], + [106.37442, 28.525742] + ] + ], + [ + [ + [109.105495, 30.585529], + [109.106111, 30.61077], + [109.09256, 30.578831], + [109.09872, 30.579346], + [109.101183, 30.579346], + [109.102415, 30.580377], + [109.105495, 30.585529] + ] + ], + [ + [ + [105.582938, 30.12385], + [105.582938, 30.127474], + [105.580474, 30.129544], + [105.574315, 30.130579], + [105.582938, 30.12385] + ] + ], + [ + [ + [109.09872, 30.579346], + [109.09256, 30.578831], + [109.103647, 30.565949], + [109.106111, 30.570587], + [109.09872, 30.579346] + ] + ], + [ + [ + [107.058113, 30.043066], + [107.053801, 30.043584], + [107.054417, 30.040994], + [107.055649, 30.040476], + [107.058113, 30.043066] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 510000, + "name": "Sichuan", + "center": [104.065735, 30.659462], + "centroid": [102.693453, 30.674545], + "childrenNum": 21, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 22, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [101.167885, 27.198311], + [101.170349, 27.175421], + [101.145095, 27.103523], + [101.157414, 27.094999], + [101.136472, 27.023584], + [101.228863, 26.981992], + [101.227015, 26.959057], + [101.264587, 26.955323], + [101.267667, 26.903034], + [101.311399, 26.903034], + [101.365602, 26.883819], + [101.399478, 26.841642], + [101.358826, 26.771669], + [101.387159, 26.753501], + [101.389623, 26.723036], + [101.435819, 26.740675], + [101.458608, 26.731054], + [101.445674, 26.77434], + [101.466, 26.786629], + [101.513427, 26.768463], + [101.453065, 26.692563], + [101.481398, 26.673313], + [101.461072, 26.640687], + [101.461688, 26.606447], + [101.402558, 26.604841], + [101.395783, 26.591998], + [101.422884, 26.53151], + [101.458608, 26.49563], + [101.506652, 26.499915], + [101.530057, 26.467239], + [101.565782, 26.454381], + [101.637847, 26.388995], + [101.635383, 26.357361], + [101.660636, 26.346635], + [101.64031, 26.318745], + [101.597195, 26.303187], + [101.586108, 26.279579], + [101.630455, 26.224832], + [101.690202, 26.241473], + [101.737013, 26.219463], + [101.773353, 26.168448], + [101.807846, 26.156093], + [101.796759, 26.114723], + [101.839875, 26.082477], + [101.835563, 26.04592], + [101.857737, 26.049146], + [101.899621, 26.099139], + [101.929186, 26.105588], + [101.954439, 26.084627], + [102.020961, 26.096451], + [102.080091, 26.065275], + [102.107808, 26.068501], + [102.152156, 26.10935], + [102.174946, 26.146961], + [102.242699, 26.190468], + [102.245163, 26.212483], + [102.349257, 26.244694], + [102.392372, 26.296749], + [102.440416, 26.300505], + [102.542046, 26.338591], + [102.570995, 26.362723], + [102.629509, 26.336982], + [102.638748, 26.307479], + [102.60056, 26.250598], + [102.659074, 26.221611], + [102.709581, 26.210336], + [102.739762, 26.268846], + [102.785342, 26.298895], + [102.833385, 26.306406], + [102.878964, 26.364332], + [102.893131, 26.338591], + [102.975667, 26.340736], + [102.998457, 26.371839], + [102.988602, 26.413117], + [102.989833, 26.482775], + [103.030485, 26.485989], + [103.052659, 26.514374], + [103.052659, 26.555602], + [103.035413, 26.556673], + [103.026174, 26.664221], + [103.005232, 26.679195], + [103.008312, 26.710741], + [102.983674, 26.76686], + [102.991681, 26.775409], + [102.966428, 26.837904], + [102.949181, 26.843244], + [102.896211, 26.91264], + [102.894979, 27.001724], + [102.870957, 27.026782], + [102.913457, 27.133886], + [102.904218, 27.227584], + [102.883276, 27.258444], + [102.883892, 27.299401], + [102.899906, 27.317481], + [102.941174, 27.405711], + [102.989833, 27.367983], + [103.055739, 27.40943], + [103.080992, 27.396679], + [103.141355, 27.420586], + [103.144434, 27.450331], + [103.19063, 27.523596], + [103.232514, 27.56976], + [103.2861, 27.561802], + [103.29226, 27.632872], + [103.349542, 27.678459], + [103.369868, 27.708664], + [103.393274, 27.709194], + [103.461027, 27.779638], + [103.487512, 27.794992], + [103.509686, 27.843687], + [103.502295, 27.910343], + [103.55465, 27.978543], + [103.515846, 27.965329], + [103.486281, 28.033495], + [103.459179, 28.021345], + [103.430846, 28.044587], + [103.470266, 28.122204], + [103.533092, 28.168641], + [103.573128, 28.230877], + [103.643961, 28.260401], + [103.692004, 28.232459], + [103.701859, 28.198709], + [103.740048, 28.23615], + [103.770845, 28.233514], + [103.828743, 28.285173], + [103.877402, 28.316262], + [103.85338, 28.356822], + [103.860156, 28.383677], + [103.828743, 28.44], + [103.829975, 28.459995], + [103.781931, 28.525216], + [103.802873, 28.563068], + [103.838598, 28.587244], + [103.833054, 28.605109], + [103.850917, 28.66709], + [103.887873, 28.61982], + [103.910047, 28.631377], + [103.953779, 28.600906], + [104.05972, 28.6277], + [104.09606, 28.603533], + [104.117618, 28.634003], + [104.170589, 28.642932], + [104.230951, 28.635579], + [104.252509, 28.660788], + [104.277147, 28.631902], + [104.314719, 28.615617], + [104.372617, 28.649235], + [104.425588, 28.626649], + [104.417581, 28.598279], + [104.375697, 28.5946], + [104.355987, 28.555183], + [104.323342, 28.540989], + [104.260516, 28.536257], + [104.267908, 28.499448], + [104.254357, 28.403683], + [104.282074, 28.343128], + [104.314103, 28.306778], + [104.343052, 28.334173], + [104.384936, 28.329959], + [104.392943, 28.291497], + [104.420045, 28.269889], + [104.44961, 28.269889], + [104.462544, 28.241422], + [104.442834, 28.211366], + [104.402182, 28.202928], + [104.406494, 28.173389], + [104.444682, 28.16231], + [104.448994, 28.113758], + [104.40095, 28.091586], + [104.373233, 28.051454], + [104.304248, 28.050926], + [104.30856, 28.036136], + [104.362762, 28.012891], + [104.40095, 27.952114], + [104.44961, 27.927794], + [104.508124, 27.878078], + [104.52537, 27.889187], + [104.573413, 27.840512], + [104.607906, 27.857974], + [104.63316, 27.850567], + [104.676275, 27.880723], + [104.743413, 27.901881], + [104.761891, 27.884426], + [104.796999, 27.901352], + [104.842579, 27.900294], + [104.888158, 27.914574], + [104.918339, 27.938897], + [104.903557, 27.962158], + [104.975006, 28.020816], + [104.980549, 28.063073], + [105.002107, 28.064129], + [105.061853, 28.096866], + [105.119752, 28.07205], + [105.168411, 28.071522], + [105.186889, 28.054623], + [105.167795, 28.021345], + [105.186273, 27.995454], + [105.218302, 27.990698], + [105.247867, 28.009193], + [105.270657, 27.99704], + [105.284823, 27.935725], + [105.233084, 27.895534], + [105.25957, 27.827811], + [105.313157, 27.810874], + [105.273736, 27.794992], + [105.293447, 27.770637], + [105.290367, 27.712373], + [105.308229, 27.704955], + [105.353809, 27.748924], + [105.44004, 27.775402], + [105.508409, 27.769048], + [105.560148, 27.71979], + [105.605112, 27.715552], + [105.62359, 27.666269], + [105.664242, 27.683759], + [105.720292, 27.683759], + [105.722756, 27.706015], + [105.76772, 27.7182], + [105.848408, 27.707074], + [105.868118, 27.732504], + [105.922937, 27.746805], + [105.92848, 27.729855], + [105.985146, 27.749983], + [106.023335, 27.746805], + [106.063987, 27.776991], + [106.120653, 27.779638], + [106.193334, 27.75422], + [106.242609, 27.767459], + [106.306667, 27.808756], + [106.337464, 27.859033], + [106.325145, 27.898708], + [106.304819, 27.899237], + [106.307899, 27.936782], + [106.328225, 27.952643], + [106.286341, 28.007079], + [106.246305, 28.011835], + [106.266631, 28.066769], + [106.206885, 28.134343], + [106.145291, 28.162837], + [106.093552, 28.162837], + [105.975907, 28.107952], + [105.943878, 28.143314], + [105.895219, 28.119565], + [105.860727, 28.159672], + [105.889676, 28.237732], + [105.848408, 28.255656], + [105.824386, 28.306251], + [105.78743, 28.335753], + [105.76464, 28.308359], + [105.76464, 28.308359], + [105.737539, 28.30309], + [105.730147, 28.271997], + [105.68888, 28.284119], + [105.639604, 28.324164], + [105.655003, 28.362615], + [105.643916, 28.431053], + [105.612503, 28.438947], + [105.62359, 28.517854], + [105.68272, 28.534154], + [105.693191, 28.58882], + [105.712901, 28.586718], + [105.74493, 28.616668], + [105.757249, 28.590397], + [105.78435, 28.610889], + [105.808372, 28.599855], + [105.884748, 28.595126], + [105.889676, 28.670765], + [105.937719, 28.686517], + [105.966668, 28.761041], + [106.001161, 28.743727], + [106.030726, 28.694917], + [106.085544, 28.681792], + [106.103407, 28.636104], + [106.14837, 28.642932], + [106.17116, 28.629275], + [106.184711, 28.58882], + [106.254928, 28.539412], + [106.2925, 28.537309], + [106.304819, 28.505233], + [106.349167, 28.473674], + [106.379348, 28.479986], + [106.37442, 28.525742], + [106.33192, 28.55308], + [106.346703, 28.583565], + [106.304203, 28.64976], + [106.305435, 28.704365], + [106.274022, 28.739004], + [106.267863, 28.779402], + [106.245689, 28.817686], + [106.264783, 28.845997], + [106.206885, 28.904691], + [106.173008, 28.920407], + [106.14837, 28.901548], + [106.101559, 28.898928], + [106.070762, 28.919884], + [106.049204, 28.906263], + [106.040581, 28.955498], + [106.001161, 28.973824], + [105.969132, 28.965971], + [105.910002, 28.920407], + [105.852719, 28.927217], + [105.830546, 28.944501], + [105.797285, 28.936121], + [105.801596, 28.958116], + [105.762176, 28.9911], + [105.766488, 29.013607], + [105.74185, 29.039249], + [105.757865, 29.069068], + [105.728916, 29.1062], + [105.752321, 29.129727], + [105.728916, 29.134432], + [105.703662, 29.176766], + [105.712285, 29.219082], + [105.695039, 29.287482], + [105.647612, 29.253027], + [105.631597, 29.280174], + [105.557684, 29.278608], + [105.521344, 29.264513], + [105.513337, 29.283306], + [105.459134, 29.288526], + [105.465294, 29.322969], + [105.42033, 29.31149], + [105.418482, 29.352185], + [105.441888, 29.400686], + [105.426489, 29.419454], + [105.372903, 29.421018], + [105.399388, 29.43874], + [105.387069, 29.455416], + [105.387069, 29.455416], + [105.334099, 29.441345], + [105.337794, 29.459064], + [105.305149, 29.53199], + [105.296526, 29.571035], + [105.332867, 29.592374], + [105.347649, 29.621512], + [105.38091, 29.628275], + [105.419714, 29.688082], + [105.476996, 29.674564], + [105.481924, 29.718232], + [105.529351, 29.707836], + [105.574931, 29.744216], + [105.582938, 29.819013], + [105.610655, 29.837184], + [105.707974, 29.840818], + [105.738771, 29.891159], + [105.717213, 29.893753], + [105.70243, 29.924879], + [105.730763, 29.95755], + [105.723372, 29.975177], + [105.753553, 30.018196], + [105.719677, 30.042548], + [105.687032, 30.038922], + [105.676561, 30.06793], + [105.638988, 30.076216], + [105.642068, 30.101072], + [105.582938, 30.12385], + [105.574315, 30.130579], + [105.596489, 30.159043], + [105.536127, 30.152834], + [105.550909, 30.179222], + [105.556453, 30.187499], + [105.558916, 30.18543], + [105.56138, 30.183878], + [105.642684, 30.186464], + [105.662394, 30.210258], + [105.619894, 30.234045], + [105.624822, 30.275918], + [105.670401, 30.254208], + [105.720292, 30.252657], + [105.720292, 30.252657], + [105.714749, 30.322939], + [105.754785, 30.342567], + [105.760329, 30.384393], + [105.792357, 30.427234], + [105.825618, 30.436006], + [105.84656, 30.410203], + [105.900763, 30.405042], + [105.943263, 30.372002], + [106.031958, 30.373551], + [106.07261, 30.333786], + [106.132972, 30.30279], + [106.132356, 30.323972], + [106.168696, 30.303823], + [106.180399, 30.233011], + [106.232754, 30.185947], + [106.260471, 30.19681], + [106.260471, 30.204051], + [106.260471, 30.207672], + [106.264167, 30.20974], + [106.296196, 30.205603], + [106.306667, 30.238182], + [106.334384, 30.225772], + [106.349167, 30.24542], + [106.401521, 30.242318], + [106.428623, 30.254725], + [106.43971, 30.308473], + [106.49884, 30.295556], + [106.545035, 30.296589], + [106.560434, 30.31519], + [106.611557, 30.292455], + [106.642354, 30.246454], + [106.612789, 30.235596], + [106.612789, 30.235596], + [106.612173, 30.235596], + [106.612173, 30.235596], + [106.611557, 30.235596], + [106.612173, 30.235596], + [106.611557, 30.235596], + [106.631883, 30.186464], + [106.677462, 30.156974], + [106.672535, 30.122297], + [106.700252, 30.111944], + [106.699636, 30.074145], + [106.724274, 30.058607], + [106.732281, 30.027005], + [106.785252, 30.01716], + [106.825904, 30.03115], + [106.825904, 30.03115], + [106.83699, 30.049801], + [106.862244, 30.033223], + [106.913367, 30.025451], + [106.94478, 30.037367], + [106.976193, 30.083467], + [106.975577, 30.088127], + [106.976809, 30.088127], + [106.977425, 30.087609], + [106.978656, 30.087609], + [106.979888, 30.088127], + [106.980504, 30.087609], + [106.981736, 30.08502], + [107.02054, 30.036849], + [107.053801, 30.043584], + [107.058113, 30.043066], + [107.084598, 30.063786], + [107.080286, 30.094341], + [107.103076, 30.090198], + [107.221337, 30.213878], + [107.257677, 30.267131], + [107.288474, 30.337402], + [107.338981, 30.386459], + [107.368546, 30.468508], + [107.408582, 30.521623], + [107.443075, 30.53348], + [107.427676, 30.547397], + [107.485575, 30.598408], + [107.516987, 30.644759], + [107.477567, 30.664837], + [107.458473, 30.704981], + [107.424597, 30.74048], + [107.454162, 30.771851], + [107.454162, 30.771851], + [107.498509, 30.809381], + [107.483111, 30.838675], + [107.515756, 30.854603], + [107.57735, 30.847924], + [107.645103, 30.821202], + [107.693146, 30.875665], + [107.739957, 30.884396], + [107.760899, 30.862823], + [107.763979, 30.817091], + [107.788001, 30.81966], + [107.851443, 30.792931], + [107.956152, 30.882855], + [107.994956, 30.908533], + [107.948145, 30.918802], + [107.942602, 30.989114], + [107.983254, 30.983983], + [108.00358, 31.025533], + [108.060246, 31.052197], + [108.026985, 31.061938], + [108.009123, 31.109602], + [108.025753, 31.116263], + [108.089811, 31.204859], + [108.07626, 31.231985], + [108.031297, 31.217144], + [108.038688, 31.252964], + [108.095354, 31.268311], + [108.185898, 31.336831], + [108.153869, 31.371073], + [108.216079, 31.41041], + [108.224086, 31.464024], + [108.193289, 31.467598], + [108.191441, 31.492096], + [108.233941, 31.506894], + [108.254883, 31.49873], + [108.344194, 31.512506], + [108.339266, 31.539033], + [108.386078, 31.544134], + [108.390389, 31.591555], + [108.442744, 31.633856], + [108.468614, 31.636404], + [108.519121, 31.665952], + [108.546838, 31.665442], + [108.514809, 31.693963], + [108.50557, 31.734182], + [108.535135, 31.757592], + [108.462454, 31.780488], + [108.455063, 31.814059], + [108.429194, 31.809482], + [108.391005, 31.829822], + [108.386078, 31.854226], + [108.343578, 31.860834], + [108.259194, 31.967006], + [108.307238, 31.997463], + [108.351585, 31.971575], + [108.370063, 31.988835], + [108.329411, 32.020299], + [108.362056, 32.035521], + [108.344194, 32.067477], + [108.372527, 32.077112], + [108.42981, 32.061391], + [108.452599, 32.090296], + [108.399628, 32.147065], + [108.379303, 32.153652], + [108.379303, 32.153652], + [108.379918, 32.154158], + [108.379918, 32.154158], + [108.370063, 32.172397], + [108.399013, 32.194176], + [108.480317, 32.182527], + [108.509882, 32.201266], + [108.507418, 32.245819], + [108.469846, 32.270618], + [108.414411, 32.252399], + [108.389773, 32.263533], + [108.310933, 32.232152], + [108.240716, 32.274666], + [108.179738, 32.221521], + [108.156948, 32.239239], + [108.143398, 32.219495], + [108.086731, 32.233165], + [108.018362, 32.2119], + [108.024521, 32.177462], + [107.979558, 32.146051], + [107.924739, 32.197215], + [107.890247, 32.214432], + [107.864377, 32.201266], + [107.812022, 32.247844], + [107.753508, 32.338399], + [107.707929, 32.331826], + [107.680827, 32.397035], + [107.648183, 32.413709], + [107.598291, 32.411688], + [107.527458, 32.38238], + [107.489886, 32.425328], + [107.456625, 32.41775], + [107.460937, 32.453612], + [107.438763, 32.465732], + [107.436299, 32.529835], + [107.382097, 32.54043], + [107.356843, 32.506622], + [107.313727, 32.489965], + [107.287858, 32.457147], + [107.263836, 32.403099], + [107.212097, 32.428864], + [107.189924, 32.468256], + [107.127098, 32.482393], + [107.080286, 32.542448], + [107.108004, 32.600951], + [107.098765, 32.649338], + [107.05996, 32.686115], + [107.066736, 32.708779], + [107.012533, 32.721367], + [106.912751, 32.704247], + [106.903512, 32.721367], + [106.854853, 32.724388], + [106.82344, 32.705254], + [106.793259, 32.712807], + [106.783404, 32.735967], + [106.733513, 32.739491], + [106.670071, 32.694678], + [106.626955, 32.682086], + [106.585687, 32.68813], + [106.517934, 32.668485], + [106.498224, 32.649338], + [106.451412, 32.65992], + [106.421231, 32.616579], + [106.389203, 32.62666], + [106.347935, 32.671003], + [106.301123, 32.680071], + [106.267863, 32.673522], + [106.254928, 32.693671], + [106.17424, 32.6977], + [106.120037, 32.719856], + [106.071378, 32.758114], + [106.07261, 32.76365], + [106.093552, 32.82402], + [106.071378, 32.828546], + [106.044277, 32.864747], + [106.011632, 32.829552], + [105.969132, 32.849162], + [105.93156, 32.826032], + [105.893371, 32.838603], + [105.849024, 32.817985], + [105.825002, 32.824523], + [105.822538, 32.770192], + [105.779423, 32.750061], + [105.768952, 32.767676], + [105.719061, 32.759624], + [105.677793, 32.726402], + [105.596489, 32.69921], + [105.585402, 32.728919], + [105.563844, 32.724891], + [105.555221, 32.794343], + [105.534279, 32.790822], + [105.524424, 32.847654], + [105.495475, 32.873292], + [105.49917, 32.911986], + [105.467757, 32.930071], + [105.414171, 32.922034], + [105.408011, 32.885857], + [105.38091, 32.876307], + [105.396308, 32.85067], + [105.396308, 32.85067], + [105.427721, 32.784281], + [105.454207, 32.767173], + [105.448663, 32.732946], + [105.368591, 32.712807], + [105.347033, 32.68259], + [105.297758, 32.656897], + [105.263265, 32.652362], + [105.219534, 32.666469], + [105.215222, 32.63674], + [105.185041, 32.617587], + [105.111128, 32.593893], + [105.0791, 32.637244], + [105.026745, 32.650346], + [104.925115, 32.607505], + [104.881999, 32.600951], + [104.845659, 32.653873], + [104.820405, 32.662943], + [104.795768, 32.643292], + [104.739717, 32.635228], + [104.696601, 32.673522], + [104.643015, 32.661935], + [104.592508, 32.695685], + [104.582653, 32.722374], + [104.526602, 32.728416], + [104.51182, 32.753585], + [104.458849, 32.748551], + [104.363994, 32.822511], + [104.294393, 32.835586], + [104.277147, 32.90244], + [104.288234, 32.942628], + [104.345516, 32.940117], + [104.378161, 32.953174], + [104.383704, 32.994343], + [104.426204, 33.010906], + [104.391711, 33.035493], + [104.337509, 33.038002], + [104.378161, 33.109214], + [104.351059, 33.158828], + [104.32827, 33.223934], + [104.323958, 33.26898], + [104.303632, 33.304499], + [104.333813, 33.315502], + [104.386168, 33.298497], + [104.420045, 33.327004], + [104.373849, 33.345004], + [104.292545, 33.336505], + [104.272219, 33.391486], + [104.22048, 33.404477], + [104.213089, 33.446932], + [104.180444, 33.472895], + [104.155191, 33.542755], + [104.176749, 33.5996], + [104.103452, 33.663381], + [104.046169, 33.686291], + [103.980264, 33.670852], + [103.861388, 33.682307], + [103.778236, 33.658898], + [103.690772, 33.69376], + [103.667983, 33.685793], + [103.645809, 33.708697], + [103.593454, 33.716164], + [103.563889, 33.699735], + [103.552186, 33.671351], + [103.520157, 33.678323], + [103.545411, 33.719649], + [103.518309, 33.807213], + [103.464723, 33.80224], + [103.434542, 33.752993], + [103.35447, 33.743539], + [103.278709, 33.774387], + [103.284868, 33.80224], + [103.24976, 33.814175], + [103.228202, 33.79478], + [103.165376, 33.805721], + [103.153673, 33.819147], + [103.181391, 33.900649], + [103.16476, 33.929454], + [103.1315, 33.931937], + [103.120413, 33.953286], + [103.157369, 33.998944], + [103.147514, 34.036644], + [103.119797, 34.03466], + [103.129652, 34.065899], + [103.178927, 34.079779], + [103.121644, 34.112487], + [103.124108, 34.162022], + [103.100087, 34.181828], + [103.052043, 34.195194], + [103.005848, 34.184798], + [102.973203, 34.205588], + [102.977515, 34.252595], + [102.949181, 34.292159], + [102.911609, 34.312923], + [102.85987, 34.301058], + [102.856791, 34.270895], + [102.798276, 34.272874], + [102.779798, 34.236764], + [102.728675, 34.235774], + [102.694799, 34.198659], + [102.664002, 34.192719], + [102.651067, 34.165983], + [102.598712, 34.14766], + [102.655994, 34.113478], + [102.649219, 34.080275], + [102.615958, 34.099604], + [102.511865, 34.086222], + [102.471213, 34.072839], + [102.437336, 34.087214], + [102.406539, 34.033172], + [102.392372, 33.971651], + [102.345561, 33.969666], + [102.315996, 33.993983], + [102.287047, 33.977607], + [102.248858, 33.98654], + [102.226069, 33.963214], + [102.16817, 33.983066], + [102.136142, 33.965199], + [102.25317, 33.861399], + [102.261177, 33.821136], + [102.243315, 33.786823], + [102.296286, 33.783838], + [102.324619, 33.754486], + [102.284583, 33.719151], + [102.342481, 33.725622], + [102.31538, 33.665374], + [102.346793, 33.605582], + [102.440416, 33.574673], + [102.477988, 33.543254], + [102.446575, 33.53228], + [102.461358, 33.501345], + [102.462589, 33.449429], + [102.447807, 33.454922], + [102.392988, 33.404477], + [102.368967, 33.41247], + [102.310452, 33.397982], + [102.296286, 33.413969], + [102.258098, 33.409472], + [102.218062, 33.349503], + [102.192192, 33.337005], + [102.217446, 33.247961], + [102.200815, 33.223434], + [102.160163, 33.242956], + [102.144765, 33.273983], + [102.117047, 33.288492], + [102.08933, 33.227439], + [102.08933, 33.204908], + [102.054838, 33.189884], + [101.99386, 33.1999], + [101.935345, 33.186879], + [101.921795, 33.153817], + [101.887302, 33.135778], + [101.865744, 33.103198], + [101.825708, 33.119239], + [101.841723, 33.184876], + [101.83002, 33.213921], + [101.770274, 33.248962], + [101.769658, 33.26898], + [101.877447, 33.314502], + [101.887302, 33.383991], + [101.915635, 33.425957], + [101.946432, 33.442937], + [101.906396, 33.48188], + [101.907012, 33.539264], + [101.884222, 33.578163], + [101.844186, 33.602591], + [101.831252, 33.554726], + [101.783208, 33.556721], + [101.769042, 33.538765], + [101.777665, 33.533776], + [101.769042, 33.45592], + [101.695745, 33.433948], + [101.663716, 33.383991], + [101.64955, 33.323004], + [101.677883, 33.297497], + [101.735781, 33.279987], + [101.709912, 33.21292], + [101.653861, 33.162835], + [101.661252, 33.135778], + [101.633535, 33.101193], + [101.557775, 33.167344], + [101.515275, 33.192889], + [101.487557, 33.226938], + [101.403174, 33.225436], + [101.386543, 33.207412], + [101.393935, 33.157826], + [101.381616, 33.153316], + [101.297232, 33.262475], + [101.217776, 33.256469], + [101.182668, 33.26948], + [101.156798, 33.236449], + [101.124769, 33.221431], + [101.11553, 33.194893], + [101.169733, 33.10019], + [101.143863, 33.086151], + [101.146327, 33.056563], + [101.184515, 33.041514], + [101.171581, 33.009902], + [101.183899, 32.984304], + [101.129081, 32.989324], + [101.134624, 32.95217], + [101.124153, 32.909976], + [101.178356, 32.892892], + [101.223935, 32.855698], + [101.237486, 32.825026], + [101.22332, 32.725898], + [101.157414, 32.661431], + [101.124769, 32.658408], + [101.077342, 32.68259], + [101.030531, 32.660424], + [100.99727, 32.627668], + [100.956618, 32.621116], + [100.93198, 32.600447], + [100.887633, 32.632708], + [100.834046, 32.648835], + [100.77122, 32.643795], + [100.690532, 32.678056], + [100.71209, 32.645307], + [100.710242, 32.610026], + [100.673286, 32.628172], + [100.661583, 32.616075], + [100.657887, 32.546484], + [100.645568, 32.526303], + [100.603069, 32.553547], + [100.54517, 32.569687], + [100.516837, 32.632204], + [100.470026, 32.694678], + [100.450932, 32.694678], + [100.420135, 32.73194], + [100.378251, 32.698707], + [100.399193, 32.756101], + [100.339447, 32.719353], + [100.258759, 32.742511], + [100.231041, 32.696189], + [100.229809, 32.650346], + [100.208252, 32.606497], + [100.189773, 32.630692], + [100.109701, 32.640268], + [100.088143, 32.668988], + [100.139266, 32.724388], + [100.117093, 32.802392], + [100.123252, 32.837095], + [100.064738, 32.895907], + [100.029629, 32.895907], + [100.038252, 32.929066], + [99.956332, 32.948152], + [99.947709, 32.986814], + [99.877492, 33.045527], + [99.877492, 32.993339], + [99.851007, 32.941623], + [99.805427, 32.940619], + [99.788181, 32.956689], + [99.764159, 32.924545], + [99.791877, 32.883344], + [99.766623, 32.826032], + [99.760464, 32.769689], + [99.717964, 32.732443], + [99.700718, 32.76667], + [99.646515, 32.774721], + [99.640355, 32.790822], + [99.589233, 32.789312], + [99.558436, 32.839106], + [99.45311, 32.862233], + [99.376118, 32.899927], + [99.353944, 32.885354], + [99.268944, 32.878318], + [99.24677, 32.924043], + [99.235067, 32.982296], + [99.214741, 32.991332], + [99.196263, 33.035493], + [99.124814, 33.046028], + [99.090322, 33.079131], + [99.024416, 33.094675], + [99.014561, 33.081137], + [98.971445, 33.098185], + [98.967134, 33.115229], + [98.92217, 33.118738], + [98.858728, 33.150811], + [98.804526, 33.219428], + [98.802062, 33.270481], + [98.759562, 33.276985], + [98.779888, 33.370497], + [98.736157, 33.406975], + [98.742316, 33.477887], + [98.725686, 33.503341], + [98.678258, 33.522801], + [98.648077, 33.548741], + [98.652389, 33.595114], + [98.622824, 33.610067], + [98.61728, 33.637476], + [98.6567, 33.64744], + [98.610505, 33.682805], + [98.582788, 33.731595], + [98.539672, 33.746525], + [98.51873, 33.77389], + [98.494092, 33.768915], + [98.492861, 33.796272], + [98.463295, 33.848477], + [98.434962, 33.843009], + [98.407245, 33.867362], + [98.425723, 33.913066], + [98.415252, 33.956761], + [98.440506, 33.981577], + [98.428187, 34.029204], + [98.396774, 34.053008], + [98.399854, 34.085231], + [98.344419, 34.094648], + [98.258188, 34.083249], + [98.206449, 34.08424], + [98.158405, 34.107037], + [98.098043, 34.122892], + [98.028442, 34.122892], + [97.95453, 34.190739], + [97.898479, 34.209548], + [97.8104, 34.207568], + [97.796849, 34.199154], + [97.796849, 34.199154], + [97.789458, 34.182818], + [97.789458, 34.182818], + [97.766668, 34.158555], + [97.665654, 34.126855], + [97.70261, 34.036644], + [97.652719, 33.998448], + [97.660111, 33.956264], + [97.629314, 33.919523], + [97.601596, 33.929951], + [97.52214, 33.903133], + [97.503662, 33.912073], + [97.460546, 33.887236], + [97.395257, 33.889224], + [97.398336, 33.848477], + [97.371851, 33.842015], + [97.373083, 33.817655], + [97.406344, 33.795278], + [97.422974, 33.754984], + [97.418046, 33.728608], + [97.435293, 33.682307], + [97.415583, 33.605582], + [97.450075, 33.582152], + [97.523372, 33.577166], + [97.511669, 33.520805], + [97.552321, 33.465906], + [97.625618, 33.461412], + [97.674893, 33.432949], + [97.754349, 33.409972], + [97.676125, 33.341004], + [97.622538, 33.337005], + [97.607756, 33.263976], + [97.548626, 33.203907], + [97.487648, 33.168346], + [97.498119, 33.137783], + [97.487032, 33.107209], + [97.517213, 33.097683], + [97.542466, 33.035995], + [97.499966, 33.011408], + [97.523988, 32.988822], + [97.438372, 32.976271], + [97.375547, 32.956689], + [97.347829, 32.895907], + [97.376163, 32.886359], + [97.392793, 32.828546], + [97.386018, 32.77925], + [97.429133, 32.714318], + [97.42359, 32.70475], + [97.48272, 32.654377], + [97.535075, 32.638252], + [97.543698, 32.62162], + [97.607756, 32.614059], + [97.616995, 32.586329], + [97.700763, 32.53488], + [97.730944, 32.527312], + [97.795617, 32.521257], + [97.80732, 32.50006], + [97.863986, 32.499051], + [97.880001, 32.486431], + [97.940363, 32.482393], + [98.079565, 32.415224], + [98.107283, 32.391476], + [98.125145, 32.401077], + [98.218768, 32.342444], + [98.208913, 32.318171], + [98.23047, 32.262521], + [98.218768, 32.234683], + [98.260035, 32.208862], + [98.303151, 32.121726], + [98.357354, 32.087253], + [98.404781, 32.045159], + [98.402933, 32.026896], + [98.434962, 32.007613], + [98.432498, 31.922825], + [98.399238, 31.895899], + [98.426339, 31.856767], + [98.414636, 31.832365], + [98.461448, 31.800327], + [98.508875, 31.751995], + [98.516882, 31.717383], + [98.545831, 31.717383], + [98.553839, 31.660349], + [98.619128, 31.591555], + [98.651157, 31.57881], + [98.696736, 31.538523], + [98.714599, 31.508935], + [98.844562, 31.429817], + [98.84333, 31.416028], + [98.887062, 31.37465], + [98.810685, 31.306668], + [98.805758, 31.279052], + [98.773113, 31.249382], + [98.691809, 31.333253], + [98.643766, 31.338876], + [98.616048, 31.3036], + [98.60373, 31.257568], + [98.62344, 31.221238], + [98.602498, 31.192062], + [98.675179, 31.15417], + [98.710287, 31.1178], + [98.712135, 31.082954], + [98.736772, 31.049121], + [98.774961, 31.031174], + [98.806374, 30.995783], + [98.797135, 30.948575], + [98.774345, 30.908019], + [98.797135, 30.87926], + [98.850105, 30.849465], + [98.904924, 30.782649], + [98.957895, 30.765166], + [98.963438, 30.728134], + [98.907388, 30.698292], + [98.92217, 30.609225], + [98.939417, 30.598923], + [98.926482, 30.569556], + [98.932025, 30.521623], + [98.965286, 30.449937], + [98.967134, 30.33482], + [98.986844, 30.280569], + [98.970829, 30.260928], + [98.993003, 30.215429], + [98.9813, 30.182843], + [98.989308, 30.151799], + [99.044742, 30.079842], + [99.036735, 30.053945], + [99.055213, 29.958587], + [99.068148, 29.931621], + [99.0238, 29.846009], + [99.018873, 29.792009], + [98.992387, 29.677163], + [99.014561, 29.607464], + [99.052133, 29.563748], + [99.044742, 29.520013], + [99.066916, 29.421018], + [99.058909, 29.417368], + [99.075539, 29.316186], + [99.114343, 29.243628], + [99.113727, 29.221171], + [99.105104, 29.162656], + [99.118039, 29.100971], + [99.113727, 29.07273], + [99.132206, 28.94869], + [99.123582, 28.890021], + [99.103872, 28.841803], + [99.114343, 28.765763], + [99.134053, 28.734806], + [99.126662, 28.698066], + [99.147604, 28.640831], + [99.183944, 28.58882], + [99.170394, 28.566221], + [99.191952, 28.494714], + [99.187024, 28.44], + [99.16485, 28.425264], + [99.200575, 28.365774], + [99.229524, 28.350502], + [99.237531, 28.317842], + [99.28927, 28.286227], + [99.306516, 28.227714], + [99.374886, 28.18183], + [99.412458, 28.295186], + [99.392748, 28.318369], + [99.437095, 28.398419], + [99.404451, 28.44421], + [99.426625, 28.454207], + [99.396444, 28.491032], + [99.403219, 28.546246], + [99.463581, 28.549401], + [99.466045, 28.579886], + [99.504233, 28.619294], + [99.540573, 28.623497], + [99.53195, 28.677591], + [99.553508, 28.710664], + [99.614486, 28.740054], + [99.609559, 28.784122], + [99.625573, 28.81454], + [99.676696, 28.810345], + [99.717964, 28.846521], + [99.722275, 28.757369], + [99.755536, 28.701216], + [99.79434, 28.699116], + [99.834992, 28.660788], + [99.834376, 28.628225], + [99.873181, 28.631902], + [99.875644, 28.611939], + [99.91876, 28.599329], + [99.985281, 28.529422], + [99.990209, 28.47683], + [100.073977, 28.426317], + [100.057346, 28.368934], + [100.136803, 28.349975], + [100.176223, 28.325218], + [100.147274, 28.288862], + [100.188541, 28.252493], + [100.153433, 28.208202], + [100.102926, 28.201873], + [100.091223, 28.181302], + [100.062274, 28.193962], + [100.033325, 28.184467], + [100.021006, 28.147008], + [100.05673, 28.097922], + [100.088759, 28.029269], + [100.120788, 28.018703], + [100.196549, 27.936254], + [100.170063, 27.907699], + [100.210715, 27.87702], + [100.30865, 27.861149], + [100.30865, 27.830457], + [100.28586, 27.80611], + [100.304954, 27.788639], + [100.311729, 27.724028], + [100.327744, 27.72032], + [100.350534, 27.755809], + [100.412127, 27.816167], + [100.442924, 27.86644], + [100.504518, 27.852154], + [100.511294, 27.827811], + [100.54517, 27.809286], + [100.609228, 27.859033], + [100.634482, 27.915631], + [100.681293, 27.923035], + [100.719481, 27.858503], + [100.707162, 27.800816], + [100.757053, 27.770107], + [100.775532, 27.743098], + [100.782307, 27.691708], + [100.848212, 27.672099], + [100.827886, 27.615904], + [100.854988, 27.623858], + [100.91227, 27.521473], + [100.901183, 27.453517], + [100.936908, 27.469448], + [100.95169, 27.426961], + [101.021907, 27.332899], + [101.026219, 27.270679], + [101.042233, 27.22173], + [101.071798, 27.194585], + [101.119226, 27.208957], + [101.167885, 27.198311], + [101.167885, 27.198311] + ] + ], + [ + [ + [106.264167, 30.20974], + [106.260471, 30.207672], + [106.260471, 30.204051], + [106.260471, 30.19681], + [106.264167, 30.20974] + ] + ], + [ + [ + [106.976809, 30.088127], + [106.975577, 30.088127], + [106.976193, 30.083467], + [106.981736, 30.08502], + [106.980504, 30.087609], + [106.979888, 30.088127], + [106.978656, 30.087609], + [106.977425, 30.087609], + [106.976809, 30.088127] + ] + ], + [ + [ + [105.558916, 30.18543], + [105.556453, 30.187499], + [105.550909, 30.179222], + [105.56138, 30.183878], + [105.558916, 30.18543] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 520000, + "name": "Guizhou", + "center": [106.713478, 26.578343], + "centroid": [106.880455, 26.826368], + "childrenNum": 9, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 23, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [109.274262, 28.494714], + [109.23361, 28.474726], + [109.191726, 28.471043], + [109.153538, 28.417369], + [109.152306, 28.349975], + [109.117198, 28.277795], + [109.081473, 28.247749], + [109.101799, 28.202401], + [109.086401, 28.184467], + [109.026655, 28.220331], + [109.005713, 28.162837], + [108.929952, 28.19027], + [108.923793, 28.217167], + [108.89546, 28.219804], + [108.855424, 28.199764], + [108.821547, 28.245113], + [108.772888, 28.212949], + [108.738395, 28.228241], + [108.726692, 28.282011], + [108.761801, 28.304143], + [108.783359, 28.380518], + [108.759953, 28.389995], + [108.780279, 28.42579], + [108.746402, 28.45105], + [108.709446, 28.501026], + [108.700207, 28.48209], + [108.657091, 28.47683], + [108.640461, 28.456838], + [108.688504, 28.422106], + [108.697127, 28.401051], + [108.656475, 28.359981], + [108.667562, 28.334173], + [108.611512, 28.324691], + [108.580099, 28.343128], + [108.576403, 28.38631], + [108.609048, 28.407368], + [108.609664, 28.43579], + [108.586874, 28.463678], + [108.573939, 28.531], + [108.610896, 28.539412], + [108.604736, 28.590922], + [108.636149, 28.621396], + [108.575787, 28.659738], + [108.50249, 28.63768], + [108.501258, 28.626649], + [108.439049, 28.634003], + [108.332491, 28.679166], + [108.347274, 28.736381], + [108.385462, 28.772058], + [108.386078, 28.803003], + [108.352817, 28.815589], + [108.346658, 28.859625], + [108.357745, 28.893165], + [108.345426, 28.943453], + [108.319556, 28.961258], + [108.297999, 29.045527], + [108.306622, 29.079006], + [108.277673, 29.091558], + [108.256115, 29.040295], + [108.193289, 29.072207], + [108.150173, 29.053375], + [108.070717, 29.086328], + [108.026369, 29.039772], + [107.925971, 29.032446], + [107.908725, 29.007327], + [107.882855, 29.00628], + [107.867457, 28.960211], + [107.810175, 28.984295], + [107.823725, 29.034016], + [107.784921, 29.048143], + [107.810791, 29.139137], + [107.749197, 29.199754], + [107.700537, 29.141228], + [107.659885, 29.162656], + [107.605683, 29.164747], + [107.589052, 29.150113], + [107.570574, 29.218037], + [107.486806, 29.174153], + [107.441227, 29.203934], + [107.401807, 29.184603], + [107.408582, 29.138091], + [107.427676, 29.128682], + [107.412278, 29.094696], + [107.369778, 29.091558], + [107.395647, 29.041341], + [107.364235, 29.00942], + [107.396879, 28.993718], + [107.412894, 28.960211], + [107.441227, 28.943977], + [107.41351, 28.911502], + [107.383945, 28.848618], + [107.339597, 28.845997], + [107.327894, 28.810869], + [107.261373, 28.792514], + [107.24659, 28.76209], + [107.219489, 28.772582], + [107.210866, 28.817686], + [107.227496, 28.836037], + [107.194851, 28.838134], + [107.206554, 28.868535], + [107.14188, 28.887925], + [107.016229, 28.882685], + [107.019308, 28.861722], + [106.983584, 28.851239], + [106.988512, 28.776254], + [106.951555, 28.766812], + [106.923222, 28.809821], + [106.872099, 28.777304], + [106.845614, 28.780975], + [106.824056, 28.756319], + [106.86594, 28.690192], + [106.889345, 28.695966], + [106.866556, 28.624548], + [106.830831, 28.623497], + [106.807425, 28.589346], + [106.784636, 28.626649], + [106.756918, 28.607211], + [106.77786, 28.563068], + [106.73844, 28.554657], + [106.726121, 28.51838], + [106.747063, 28.467361], + [106.708259, 28.450524], + [106.697788, 28.47683], + [106.632499, 28.503655], + [106.564745, 28.485247], + [106.567825, 28.523638], + [106.615252, 28.549401], + [106.606629, 28.593024], + [106.63681, 28.622972], + [106.618332, 28.645033], + [106.651593, 28.649235], + [106.617716, 28.66709], + [106.6171, 28.691242], + [106.587535, 28.691767], + [106.56105, 28.719062], + [106.561666, 28.756319], + [106.474202, 28.832891], + [106.45326, 28.817162], + [106.461883, 28.761041], + [106.492064, 28.742153], + [106.528405, 28.677591], + [106.502535, 28.661313], + [106.49268, 28.591448], + [106.466811, 28.586193], + [106.504999, 28.544669], + [106.477282, 28.530474], + [106.403369, 28.569901], + [106.37442, 28.525742], + [106.379348, 28.479986], + [106.349167, 28.473674], + [106.304819, 28.505233], + [106.2925, 28.537309], + [106.254928, 28.539412], + [106.184711, 28.58882], + [106.17116, 28.629275], + [106.14837, 28.642932], + [106.103407, 28.636104], + [106.085544, 28.681792], + [106.030726, 28.694917], + [106.001161, 28.743727], + [105.966668, 28.761041], + [105.937719, 28.686517], + [105.889676, 28.670765], + [105.884748, 28.595126], + [105.808372, 28.599855], + [105.78435, 28.610889], + [105.757249, 28.590397], + [105.74493, 28.616668], + [105.712901, 28.586718], + [105.693191, 28.58882], + [105.68272, 28.534154], + [105.62359, 28.517854], + [105.612503, 28.438947], + [105.643916, 28.431053], + [105.655003, 28.362615], + [105.639604, 28.324164], + [105.68888, 28.284119], + [105.730147, 28.271997], + [105.737539, 28.30309], + [105.76464, 28.308359], + [105.76464, 28.308359], + [105.78743, 28.335753], + [105.824386, 28.306251], + [105.848408, 28.255656], + [105.889676, 28.237732], + [105.860727, 28.159672], + [105.895219, 28.119565], + [105.943878, 28.143314], + [105.975907, 28.107952], + [106.093552, 28.162837], + [106.145291, 28.162837], + [106.206885, 28.134343], + [106.266631, 28.066769], + [106.246305, 28.011835], + [106.286341, 28.007079], + [106.328225, 27.952643], + [106.307899, 27.936782], + [106.304819, 27.899237], + [106.325145, 27.898708], + [106.337464, 27.859033], + [106.306667, 27.808756], + [106.242609, 27.767459], + [106.193334, 27.75422], + [106.120653, 27.779638], + [106.063987, 27.776991], + [106.023335, 27.746805], + [105.985146, 27.749983], + [105.92848, 27.729855], + [105.922937, 27.746805], + [105.868118, 27.732504], + [105.848408, 27.707074], + [105.76772, 27.7182], + [105.722756, 27.706015], + [105.720292, 27.683759], + [105.664242, 27.683759], + [105.62359, 27.666269], + [105.605112, 27.715552], + [105.560148, 27.71979], + [105.508409, 27.769048], + [105.44004, 27.775402], + [105.353809, 27.748924], + [105.308229, 27.704955], + [105.29591, 27.631811], + [105.304533, 27.611661], + [105.25649, 27.582491], + [105.232469, 27.546945], + [105.260186, 27.514573], + [105.234316, 27.489093], + [105.233084, 27.436522], + [105.182577, 27.367451], + [105.184425, 27.392959], + [105.120984, 27.418461], + [105.068013, 27.418461], + [105.01073, 27.379143], + [104.913412, 27.327051], + [104.871528, 27.290891], + [104.851818, 27.299401], + [104.856746, 27.332368], + [104.824717, 27.3531], + [104.77113, 27.317481], + [104.7545, 27.345658], + [104.611602, 27.306846], + [104.570334, 27.331836], + [104.539537, 27.327583], + [104.497037, 27.414743], + [104.467472, 27.414211], + [104.363378, 27.467855], + [104.30856, 27.407305], + [104.295625, 27.37436], + [104.247582, 27.336621], + [104.248813, 27.291955], + [104.210625, 27.297273], + [104.173053, 27.263232], + [104.113923, 27.338216], + [104.084358, 27.330773], + [104.01722, 27.383926], + [104.015372, 27.429086], + [103.956242, 27.425367], + [103.932221, 27.443958], + [103.905119, 27.38552], + [103.903271, 27.347785], + [103.874322, 27.331304], + [103.865699, 27.28185], + [103.80041, 27.26536], + [103.801641, 27.250464], + [103.748671, 27.210021], + [103.696316, 27.126429], + [103.63349, 27.12057], + [103.620555, 27.096598], + [103.652584, 27.092868], + [103.659975, 27.065692], + [103.614396, 27.079548], + [103.601461, 27.061962], + [103.623635, 27.035312], + [103.623019, 27.007056], + [103.675374, 27.051836], + [103.704939, 27.049171], + [103.73204, 27.018785], + [103.753598, 26.963858], + [103.775156, 26.951056], + [103.763453, 26.905702], + [103.779468, 26.87421], + [103.722185, 26.851253], + [103.705555, 26.794642], + [103.725265, 26.742812], + [103.773308, 26.716621], + [103.759142, 26.689355], + [103.748671, 26.623568], + [103.763453, 26.585041], + [103.815808, 26.55239], + [103.819504, 26.529903], + [103.865699, 26.512232], + [103.953163, 26.521336], + [104.008597, 26.511697], + [104.067727, 26.51491], + [104.068343, 26.573266], + [104.121314, 26.638012], + [104.160734, 26.646571], + [104.222328, 26.620358], + [104.268524, 26.617683], + [104.274683, 26.633733], + [104.313487, 26.612867], + [104.353523, 26.620893], + [104.398487, 26.686147], + [104.424356, 26.709137], + [104.468088, 26.644431], + [104.459465, 26.602701], + [104.488414, 26.579689], + [104.556783, 26.590393], + [104.579573, 26.568449], + [104.57095, 26.524549], + [104.598667, 26.520801], + [104.638703, 26.477954], + [104.631928, 26.451702], + [104.665804, 26.434019], + [104.664572, 26.397572], + [104.684283, 26.3772], + [104.659645, 26.335373], + [104.592508, 26.317672], + [104.542616, 26.253282], + [104.548776, 26.226979], + [104.518595, 26.165762], + [104.52845, 26.114186], + [104.499501, 26.070651], + [104.460081, 26.085702], + [104.470552, 26.009352], + [104.438523, 25.92757], + [104.414501, 25.909807], + [104.441602, 25.868889], + [104.42374, 25.841961], + [104.397871, 25.76168], + [104.370769, 25.730415], + [104.328886, 25.760602], + [104.310407, 25.647901], + [104.332581, 25.598796], + [104.389248, 25.595558], + [104.428668, 25.576126], + [104.436059, 25.520512], + [104.418813, 25.499447], + [104.434827, 25.472436], + [104.44961, 25.495126], + [104.483486, 25.494585], + [104.524138, 25.526992], + [104.556783, 25.524832], + [104.543232, 25.400556], + [104.566638, 25.402719], + [104.615913, 25.364871], + [104.646094, 25.356759], + [104.639935, 25.295632], + [104.689826, 25.296173], + [104.736021, 25.268034], + [104.816094, 25.262622], + [104.826565, 25.235558], + [104.806854, 25.224189], + [104.822869, 25.170037], + [104.801927, 25.163537], + [104.753884, 25.214443], + [104.724319, 25.195491], + [104.732326, 25.167871], + [104.695369, 25.122364], + [104.685514, 25.078466], + [104.619609, 25.060577], + [104.684898, 25.054072], + [104.713232, 24.996048], + [104.663957, 24.964584], + [104.635623, 24.903803], + [104.586964, 24.872859], + [104.539537, 24.813663], + [104.542616, 24.75607], + [104.529682, 24.731611], + [104.595587, 24.709323], + [104.628848, 24.660927], + [104.703377, 24.645698], + [104.729246, 24.617953], + [104.771746, 24.659839], + [104.841963, 24.676155], + [104.865985, 24.730524], + [104.899245, 24.752809], + [105.03352, 24.787586], + [105.026745, 24.815836], + [105.039064, 24.872859], + [105.077868, 24.918459], + [105.09573, 24.92877], + [105.131454, 24.959701], + [105.157324, 24.958616], + [105.178266, 24.985199], + [105.212758, 24.995505], + [105.251563, 24.967296], + [105.267577, 24.929313], + [105.334099, 24.9266], + [105.365511, 24.943423], + [105.428337, 24.930941], + [105.457286, 24.87123], + [105.493011, 24.833217], + [105.497322, 24.809318], + [105.573083, 24.797366], + [105.607576, 24.803885], + [105.617431, 24.78161], + [105.70551, 24.768569], + [105.767104, 24.719109], + [105.827466, 24.702799], + [105.863806, 24.729437], + [105.942031, 24.725088], + [105.961741, 24.677786], + [106.024566, 24.633186], + [106.047356, 24.684312], + [106.113878, 24.714216], + [106.150218, 24.762591], + [106.173008, 24.760417], + [106.206269, 24.851139], + [106.197645, 24.885889], + [106.145291, 24.954275], + [106.191486, 24.95319], + [106.215508, 24.981944], + [106.253696, 24.971094], + [106.304819, 24.973807], + [106.332536, 24.988454], + [106.442173, 25.019369], + [106.450181, 25.033468], + [106.519782, 25.054072], + [106.551195, 25.082802], + [106.590615, 25.08768], + [106.63989, 25.132658], + [106.644817, 25.164621], + [106.691013, 25.179245], + [106.732281, 25.162454], + [106.764926, 25.183036], + [106.787715, 25.17112], + [106.853005, 25.186827], + [106.888113, 25.181953], + [106.904128, 25.231768], + [106.933077, 25.250714], + [106.975577, 25.232851], + [107.013765, 25.275611], + [107.012533, 25.352973], + [106.987896, 25.358922], + [106.963874, 25.437852], + [106.996519, 25.442716], + [107.015613, 25.495666], + [107.066736, 25.50917], + [107.064272, 25.559391], + [107.185612, 25.578825], + [107.205322, 25.607971], + [107.228728, 25.604733], + [107.232423, 25.556691], + [107.263836, 25.543193], + [107.336517, 25.461089], + [107.308184, 25.432988], + [107.318039, 25.401637], + [107.358691, 25.393528], + [107.375937, 25.411908], + [107.420901, 25.392987], + [107.409198, 25.347024], + [107.432604, 25.289139], + [107.481263, 25.299961], + [107.489886, 25.276693], + [107.472024, 25.213902], + [107.512676, 25.209029], + [107.576734, 25.256668], + [107.599523, 25.250714], + [107.632168, 25.310241], + [107.659885, 25.316192], + [107.661733, 25.258833], + [107.696226, 25.219858], + [107.700537, 25.194408], + [107.741805, 25.24043], + [107.762131, 25.229061], + [107.760283, 25.188451], + [107.789233, 25.15487], + [107.762747, 25.125073], + [107.839124, 25.115861], + [107.872384, 25.141327], + [107.928435, 25.155954], + [108.001732, 25.196574], + [108.080572, 25.193867], + [108.115065, 25.210112], + [108.143398, 25.269658], + [108.152021, 25.324306], + [108.142782, 25.390825], + [108.193289, 25.405421], + [108.162492, 25.444878], + [108.192673, 25.458928], + [108.251803, 25.430286], + [108.241332, 25.46217], + [108.280752, 25.48], + [108.308469, 25.525912], + [108.348506, 25.536173], + [108.359592, 25.513491], + [108.400244, 25.491344], + [108.418723, 25.443257], + [108.471693, 25.458928], + [108.585642, 25.365952], + [108.589338, 25.335125], + [108.625062, 25.308076], + [108.62999, 25.335666], + [108.600425, 25.432448], + [108.6072, 25.491885], + [108.634917, 25.520512], + [108.68912, 25.533473], + [108.658323, 25.550212], + [108.660787, 25.584763], + [108.68604, 25.587462], + [108.68912, 25.623081], + [108.724844, 25.634952], + [108.783975, 25.628477], + [108.799989, 25.576666], + [108.781511, 25.554531], + [108.814772, 25.526992], + [108.826474, 25.550212], + [108.890532, 25.556151], + [108.8893, 25.543193], + [108.949046, 25.557231], + [109.024807, 25.51241], + [109.088249, 25.550752], + [109.051908, 25.566949], + [109.030966, 25.629556], + [109.075314, 25.693749], + [109.07901, 25.72071], + [109.043285, 25.738502], + [109.007561, 25.734728], + [108.953974, 25.686738], + [108.953974, 25.686738], + [108.900387, 25.682423], + [108.896076, 25.71424], + [108.940423, 25.740119], + [108.963829, 25.732572], + [108.999553, 25.765453], + [108.989698, 25.778926], + [109.048213, 25.790781], + [109.077778, 25.776771], + [109.095024, 25.80533], + [109.143683, 25.795092], + [109.13198, 25.762758], + [109.147995, 25.741736], + [109.206509, 25.788087], + [109.207125, 25.740119], + [109.296436, 25.71424], + [109.340168, 25.731493], + [109.327849, 25.76168], + [109.339552, 25.83442], + [109.359262, 25.836036], + [109.396834, 25.900117], + [109.435022, 25.93349], + [109.408537, 25.967392], + [109.473211, 26.006663], + [109.48245, 26.029788], + [109.452885, 26.055598], + [109.449805, 26.101826], + [109.502776, 26.096451], + [109.513863, 26.128157], + [109.47629, 26.148035], + [109.439334, 26.238789], + [109.467051, 26.313917], + [109.442414, 26.289774], + [109.369733, 26.277432], + [109.351255, 26.264016], + [109.325385, 26.29031], + [109.285965, 26.295676], + [109.271183, 26.327863], + [109.29582, 26.350389], + [109.319842, 26.418477], + [109.38082, 26.454381], + [109.362342, 26.472061], + [109.385747, 26.493487], + [109.381436, 26.518659], + [109.407305, 26.533116], + [109.390675, 26.598955], + [109.35495, 26.658873], + [109.334008, 26.646036], + [109.306291, 26.661012], + [109.283501, 26.698445], + [109.35495, 26.693098], + [109.407305, 26.719829], + [109.447957, 26.759913], + [109.486761, 26.759913], + [109.47629, 26.829894], + [109.467051, 26.83203], + [109.452885, 26.861932], + [109.436254, 26.892359], + [109.555131, 26.946788], + [109.520022, 27.058764], + [109.497848, 27.079548], + [109.486761, 27.053968], + [109.454733, 27.069423], + [109.472595, 27.134951], + [109.441182, 27.117907], + [109.415312, 27.154123], + [109.358646, 27.153058], + [109.33524, 27.139212], + [109.264407, 27.131755], + [109.239154, 27.14933], + [109.21698, 27.114711], + [109.165857, 27.066758], + [109.101183, 27.06889], + [109.128901, 27.122701], + [109.032814, 27.104056], + [109.007561, 27.08008], + [108.940423, 27.044907], + [108.942887, 27.017186], + [108.942887, 27.017186], + [108.877597, 27.01612], + [108.79075, 27.084343], + [108.878829, 27.106187], + [108.926873, 27.160512], + [108.907778, 27.204699], + [108.963213, 27.235565], + [108.983539, 27.26802], + [109.053756, 27.293551], + [109.044517, 27.331304], + [109.103647, 27.336621], + [109.142451, 27.418461], + [109.141835, 27.448207], + [109.167089, 27.41793], + [109.202197, 27.450331], + [109.245313, 27.41793], + [109.300132, 27.423774], + [109.303211, 27.47582], + [109.404841, 27.55066], + [109.461508, 27.567637], + [109.451037, 27.586204], + [109.470131, 27.62863], + [109.45658, 27.673689], + [109.470747, 27.680049], + [109.414081, 27.725087], + [109.366653, 27.721909], + [109.37774, 27.736741], + [109.332777, 27.782815], + [109.346943, 27.838396], + [109.32169, 27.868027], + [109.30198, 27.956343], + [109.319842, 27.988585], + [109.362342, 28.007608], + [109.378972, 28.034551], + [109.335856, 28.063073], + [109.298284, 28.036136], + [109.314298, 28.103729], + [109.33832, 28.141731], + [109.340168, 28.19027], + [109.367885, 28.254602], + [109.388211, 28.268307], + [109.33524, 28.293605], + [109.317994, 28.277795], + [109.275494, 28.313101], + [109.268719, 28.33786], + [109.289045, 28.373673], + [109.264407, 28.392628], + [109.260712, 28.46473], + [109.274262, 28.494714] + ] + ], + [ + [ + [109.47629, 26.829894], + [109.486761, 26.759913], + [109.52187, 26.749226], + [109.522486, 26.749226], + [109.497232, 26.815474], + [109.513247, 26.84004], + [109.509551, 26.877947], + [109.486761, 26.895562], + [109.452885, 26.861932], + [109.467051, 26.83203], + [109.47629, 26.829894] + ] + ], + [ + [ + [109.528645, 26.743881], + [109.568065, 26.726243], + [109.597015, 26.756173], + [109.554515, 26.73533], + [109.528645, 26.743881] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 530000, + "name": "Yunnan", + "center": [102.712251, 25.040609], + "centroid": [101.485106, 25.008643], + "childrenNum": 16, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 24, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [105.308229, 27.704955], + [105.290367, 27.712373], + [105.293447, 27.770637], + [105.273736, 27.794992], + [105.313157, 27.810874], + [105.25957, 27.827811], + [105.233084, 27.895534], + [105.284823, 27.935725], + [105.270657, 27.99704], + [105.247867, 28.009193], + [105.218302, 27.990698], + [105.186273, 27.995454], + [105.167795, 28.021345], + [105.186889, 28.054623], + [105.168411, 28.071522], + [105.119752, 28.07205], + [105.061853, 28.096866], + [105.002107, 28.064129], + [104.980549, 28.063073], + [104.975006, 28.020816], + [104.903557, 27.962158], + [104.918339, 27.938897], + [104.888158, 27.914574], + [104.842579, 27.900294], + [104.796999, 27.901352], + [104.761891, 27.884426], + [104.743413, 27.901881], + [104.676275, 27.880723], + [104.63316, 27.850567], + [104.607906, 27.857974], + [104.573413, 27.840512], + [104.52537, 27.889187], + [104.508124, 27.878078], + [104.44961, 27.927794], + [104.40095, 27.952114], + [104.362762, 28.012891], + [104.30856, 28.036136], + [104.304248, 28.050926], + [104.373233, 28.051454], + [104.40095, 28.091586], + [104.448994, 28.113758], + [104.444682, 28.16231], + [104.406494, 28.173389], + [104.402182, 28.202928], + [104.442834, 28.211366], + [104.462544, 28.241422], + [104.44961, 28.269889], + [104.420045, 28.269889], + [104.392943, 28.291497], + [104.384936, 28.329959], + [104.343052, 28.334173], + [104.314103, 28.306778], + [104.282074, 28.343128], + [104.254357, 28.403683], + [104.267908, 28.499448], + [104.260516, 28.536257], + [104.323342, 28.540989], + [104.355987, 28.555183], + [104.375697, 28.5946], + [104.417581, 28.598279], + [104.425588, 28.626649], + [104.372617, 28.649235], + [104.314719, 28.615617], + [104.277147, 28.631902], + [104.252509, 28.660788], + [104.230951, 28.635579], + [104.170589, 28.642932], + [104.117618, 28.634003], + [104.09606, 28.603533], + [104.05972, 28.6277], + [103.953779, 28.600906], + [103.910047, 28.631377], + [103.887873, 28.61982], + [103.850917, 28.66709], + [103.833054, 28.605109], + [103.838598, 28.587244], + [103.802873, 28.563068], + [103.781931, 28.525216], + [103.829975, 28.459995], + [103.828743, 28.44], + [103.860156, 28.383677], + [103.85338, 28.356822], + [103.877402, 28.316262], + [103.828743, 28.285173], + [103.770845, 28.233514], + [103.740048, 28.23615], + [103.701859, 28.198709], + [103.692004, 28.232459], + [103.643961, 28.260401], + [103.573128, 28.230877], + [103.533092, 28.168641], + [103.470266, 28.122204], + [103.430846, 28.044587], + [103.459179, 28.021345], + [103.486281, 28.033495], + [103.515846, 27.965329], + [103.55465, 27.978543], + [103.502295, 27.910343], + [103.509686, 27.843687], + [103.487512, 27.794992], + [103.461027, 27.779638], + [103.393274, 27.709194], + [103.369868, 27.708664], + [103.349542, 27.678459], + [103.29226, 27.632872], + [103.2861, 27.561802], + [103.232514, 27.56976], + [103.19063, 27.523596], + [103.144434, 27.450331], + [103.141355, 27.420586], + [103.080992, 27.396679], + [103.055739, 27.40943], + [102.989833, 27.367983], + [102.941174, 27.405711], + [102.899906, 27.317481], + [102.883892, 27.299401], + [102.883276, 27.258444], + [102.904218, 27.227584], + [102.913457, 27.133886], + [102.870957, 27.026782], + [102.894979, 27.001724], + [102.896211, 26.91264], + [102.949181, 26.843244], + [102.966428, 26.837904], + [102.991681, 26.775409], + [102.983674, 26.76686], + [103.008312, 26.710741], + [103.005232, 26.679195], + [103.026174, 26.664221], + [103.035413, 26.556673], + [103.052659, 26.555602], + [103.052659, 26.514374], + [103.030485, 26.485989], + [102.989833, 26.482775], + [102.988602, 26.413117], + [102.998457, 26.371839], + [102.975667, 26.340736], + [102.893131, 26.338591], + [102.878964, 26.364332], + [102.833385, 26.306406], + [102.785342, 26.298895], + [102.739762, 26.268846], + [102.709581, 26.210336], + [102.659074, 26.221611], + [102.60056, 26.250598], + [102.638748, 26.307479], + [102.629509, 26.336982], + [102.570995, 26.362723], + [102.542046, 26.338591], + [102.440416, 26.300505], + [102.392372, 26.296749], + [102.349257, 26.244694], + [102.245163, 26.212483], + [102.242699, 26.190468], + [102.174946, 26.146961], + [102.152156, 26.10935], + [102.107808, 26.068501], + [102.080091, 26.065275], + [102.020961, 26.096451], + [101.954439, 26.084627], + [101.929186, 26.105588], + [101.899621, 26.099139], + [101.857737, 26.049146], + [101.835563, 26.04592], + [101.839875, 26.082477], + [101.796759, 26.114723], + [101.807846, 26.156093], + [101.773353, 26.168448], + [101.737013, 26.219463], + [101.690202, 26.241473], + [101.630455, 26.224832], + [101.586108, 26.279579], + [101.597195, 26.303187], + [101.64031, 26.318745], + [101.660636, 26.346635], + [101.635383, 26.357361], + [101.637847, 26.388995], + [101.565782, 26.454381], + [101.530057, 26.467239], + [101.506652, 26.499915], + [101.458608, 26.49563], + [101.422884, 26.53151], + [101.395783, 26.591998], + [101.402558, 26.604841], + [101.461688, 26.606447], + [101.461072, 26.640687], + [101.481398, 26.673313], + [101.453065, 26.692563], + [101.513427, 26.768463], + [101.466, 26.786629], + [101.445674, 26.77434], + [101.458608, 26.731054], + [101.435819, 26.740675], + [101.389623, 26.723036], + [101.387159, 26.753501], + [101.358826, 26.771669], + [101.399478, 26.841642], + [101.365602, 26.883819], + [101.311399, 26.903034], + [101.267667, 26.903034], + [101.264587, 26.955323], + [101.227015, 26.959057], + [101.228863, 26.981992], + [101.136472, 27.023584], + [101.157414, 27.094999], + [101.145095, 27.103523], + [101.170349, 27.175421], + [101.167885, 27.198311], + [101.167885, 27.198311], + [101.119226, 27.208957], + [101.071798, 27.194585], + [101.042233, 27.22173], + [101.026219, 27.270679], + [101.021907, 27.332899], + [100.95169, 27.426961], + [100.936908, 27.469448], + [100.901183, 27.453517], + [100.91227, 27.521473], + [100.854988, 27.623858], + [100.827886, 27.615904], + [100.848212, 27.672099], + [100.782307, 27.691708], + [100.775532, 27.743098], + [100.757053, 27.770107], + [100.707162, 27.800816], + [100.719481, 27.858503], + [100.681293, 27.923035], + [100.634482, 27.915631], + [100.609228, 27.859033], + [100.54517, 27.809286], + [100.511294, 27.827811], + [100.504518, 27.852154], + [100.442924, 27.86644], + [100.412127, 27.816167], + [100.350534, 27.755809], + [100.327744, 27.72032], + [100.311729, 27.724028], + [100.304954, 27.788639], + [100.28586, 27.80611], + [100.30865, 27.830457], + [100.30865, 27.861149], + [100.210715, 27.87702], + [100.170063, 27.907699], + [100.196549, 27.936254], + [100.120788, 28.018703], + [100.088759, 28.029269], + [100.05673, 28.097922], + [100.021006, 28.147008], + [100.033325, 28.184467], + [100.062274, 28.193962], + [100.091223, 28.181302], + [100.102926, 28.201873], + [100.153433, 28.208202], + [100.188541, 28.252493], + [100.147274, 28.288862], + [100.176223, 28.325218], + [100.136803, 28.349975], + [100.057346, 28.368934], + [100.073977, 28.426317], + [99.990209, 28.47683], + [99.985281, 28.529422], + [99.91876, 28.599329], + [99.875644, 28.611939], + [99.873181, 28.631902], + [99.834376, 28.628225], + [99.834992, 28.660788], + [99.79434, 28.699116], + [99.755536, 28.701216], + [99.722275, 28.757369], + [99.717964, 28.846521], + [99.676696, 28.810345], + [99.625573, 28.81454], + [99.609559, 28.784122], + [99.614486, 28.740054], + [99.553508, 28.710664], + [99.53195, 28.677591], + [99.540573, 28.623497], + [99.504233, 28.619294], + [99.466045, 28.579886], + [99.463581, 28.549401], + [99.403219, 28.546246], + [99.396444, 28.491032], + [99.426625, 28.454207], + [99.404451, 28.44421], + [99.437095, 28.398419], + [99.392748, 28.318369], + [99.412458, 28.295186], + [99.374886, 28.18183], + [99.306516, 28.227714], + [99.28927, 28.286227], + [99.237531, 28.317842], + [99.229524, 28.350502], + [99.200575, 28.365774], + [99.16485, 28.425264], + [99.187024, 28.44], + [99.191952, 28.494714], + [99.170394, 28.566221], + [99.183944, 28.58882], + [99.147604, 28.640831], + [99.126662, 28.698066], + [99.134053, 28.734806], + [99.114343, 28.765763], + [99.103872, 28.841803], + [99.123582, 28.890021], + [99.132206, 28.94869], + [99.113727, 29.07273], + [99.118039, 29.100971], + [99.105104, 29.162656], + [99.113727, 29.221171], + [99.037351, 29.20759], + [99.024416, 29.188783], + [98.9813, 29.204978], + [98.960974, 29.165792], + [98.967134, 29.128159], + [98.991771, 29.105677], + [99.013329, 29.036632], + [98.925866, 28.978536], + [98.917859, 28.886877], + [98.973909, 28.864867], + [98.972677, 28.832367], + [98.922786, 28.823978], + [98.912931, 28.800906], + [98.852569, 28.798283], + [98.827932, 28.821356], + [98.821772, 28.920931], + [98.786048, 28.998952], + [98.757714, 29.004186], + [98.70228, 28.9644], + [98.655469, 28.976966], + [98.624056, 28.95864], + [98.6567, 28.910454], + [98.643766, 28.895261], + [98.668403, 28.843376], + [98.652389, 28.817162], + [98.683802, 28.740054], + [98.666555, 28.712239], + [98.594491, 28.667615], + [98.637606, 28.552029], + [98.619128, 28.50944], + [98.625903, 28.489455], + [98.673947, 28.478934], + [98.693041, 28.43158], + [98.740468, 28.348395], + [98.746628, 28.321003], + [98.710287, 28.288862], + [98.712135, 28.229296], + [98.649925, 28.200291], + [98.625903, 28.165475], + [98.559382, 28.182885], + [98.494092, 28.141203], + [98.464527, 28.151229], + [98.428803, 28.104785], + [98.389383, 28.114814], + [98.389999, 28.16442], + [98.370289, 28.18394], + [98.37768, 28.246167], + [98.353042, 28.293078], + [98.317934, 28.324691], + [98.301303, 28.384204], + [98.208913, 28.358401], + [98.207681, 28.330486], + [98.231702, 28.314681], + [98.266811, 28.242477], + [98.21692, 28.212949], + [98.169492, 28.206093], + [98.17442, 28.163365], + [98.139311, 28.142259], + [98.160253, 28.101089], + [98.133152, 27.990698], + [98.143007, 27.948942], + [98.187355, 27.939426], + [98.205217, 27.889716], + [98.169492, 27.851096], + [98.215688, 27.810874], + [98.234166, 27.690648], + [98.283441, 27.654608], + [98.310542, 27.583552], + [98.317318, 27.51935], + [98.337644, 27.508734], + [98.388767, 27.515104], + [98.429419, 27.549068], + [98.430035, 27.653547], + [98.444201, 27.665209], + [98.474998, 27.634462], + [98.53536, 27.620676], + [98.554454, 27.646126], + [98.587099, 27.587265], + [98.583404, 27.571351], + [98.650541, 27.567637], + [98.662244, 27.586734], + [98.706591, 27.553313], + [98.685034, 27.484315], + [98.704744, 27.462014], + [98.686881, 27.425367], + [98.702896, 27.412618], + [98.706591, 27.362136], + [98.741084, 27.330241], + [98.734925, 27.287168], + [98.717062, 27.271211], + [98.723222, 27.221198], + [98.696121, 27.211086], + [98.713983, 27.139744], + [98.712751, 27.075817], + [98.765722, 27.05077], + [98.762642, 27.018252], + [98.732461, 27.002257], + [98.757098, 26.877947], + [98.730613, 26.851253], + [98.762026, 26.798916], + [98.746012, 26.696841], + [98.770033, 26.690424], + [98.762642, 26.660478], + [98.781736, 26.620893], + [98.773113, 26.578083], + [98.753403, 26.559349], + [98.757098, 26.491881], + [98.741084, 26.432947], + [98.750323, 26.424372], + [98.733693, 26.350926], + [98.681338, 26.308016], + [98.672715, 26.239863], + [98.713367, 26.231274], + [98.735541, 26.185097], + [98.712751, 26.156093], + [98.720142, 26.127082], + [98.661012, 26.087852], + [98.656084, 26.139977], + [98.632679, 26.145887], + [98.575396, 26.118485], + [98.602498, 26.054523], + [98.614201, 25.968468], + [98.637606, 25.971696], + [98.686881, 25.925955], + [98.705976, 25.855426], + [98.677642, 25.816105], + [98.640686, 25.798864], + [98.553839, 25.845731], + [98.529201, 25.840884], + [98.476846, 25.77731], + [98.461448, 25.735267], + [98.457752, 25.682963], + [98.409709, 25.664084], + [98.402317, 25.593939], + [98.326557, 25.566409], + [98.314854, 25.543193], + [98.247717, 25.607971], + [98.170724, 25.620383], + [98.189818, 25.569108], + [98.163949, 25.524292], + [98.131304, 25.51025], + [98.15779, 25.457307], + [98.137464, 25.381633], + [98.101123, 25.388662], + [98.099891, 25.354055], + [98.06971, 25.311864], + [98.006884, 25.298338], + [98.0075, 25.279399], + [97.940363, 25.214985], + [97.904023, 25.216609], + [97.875689, 25.25721], + [97.839349, 25.27074], + [97.796233, 25.155954], + [97.743262, 25.078466], + [97.719857, 25.080634], + [97.727864, 25.04377], + [97.716777, 24.978147], + [97.729712, 24.908689], + [97.785762, 24.876117], + [97.797465, 24.845709], + [97.765436, 24.823984], + [97.680437, 24.827243], + [97.652103, 24.790846], + [97.569567, 24.765852], + [97.547394, 24.739221], + [97.569567, 24.708236], + [97.570799, 24.602719], + [97.554785, 24.490577], + [97.530147, 24.443187], + [97.588662, 24.435559], + [97.669966, 24.452993], + [97.679821, 24.401228], + [97.716161, 24.358711], + [97.662574, 24.339083], + [97.665038, 24.296544], + [97.721089, 24.295999], + [97.767284, 24.258357], + [97.729712, 24.227252], + [97.72848, 24.183585], + [97.754349, 24.163929], + [97.748806, 24.160653], + [97.743262, 24.159561], + [97.730944, 24.113685], + [97.700763, 24.093473], + [97.697067, 24.092927], + [97.637321, 24.04812], + [97.628698, 24.004938], + [97.572647, 23.983068], + [97.529531, 23.943146], + [97.5283, 23.926736], + [97.618227, 23.888438], + [97.640401, 23.866001], + [97.647176, 23.840823], + [97.684132, 23.876946], + [97.718009, 23.867643], + [97.72848, 23.895551], + [97.763588, 23.907041], + [97.795617, 23.951897], + [97.8104, 23.943146], + [97.863371, 23.978693], + [97.896015, 23.974319], + [97.902175, 24.014231], + [97.984095, 24.031177], + [97.995182, 24.04648], + [98.091268, 24.085824], + [98.096196, 24.08637], + [98.123297, 24.092927], + [98.125761, 24.092927], + [98.132536, 24.09238], + [98.19721, 24.09839], + [98.219999, 24.113685], + [98.343187, 24.098936], + [98.37768, 24.114232], + [98.48239, 24.122425], + [98.487933, 24.123517], + [98.547063, 24.128433], + [98.593875, 24.08036], + [98.646229, 24.106038], + [98.681954, 24.100029], + [98.71891, 24.127887], + [98.818692, 24.133348], + [98.841482, 24.126794], + [98.876591, 24.15137], + [98.895069, 24.098936], + [98.807606, 24.025164], + [98.773729, 24.022431], + [98.727533, 23.970491], + [98.701048, 23.981427], + [98.673331, 23.960647], + [98.701048, 23.946427], + [98.68565, 23.90157], + [98.701664, 23.834254], + [98.669019, 23.800857], + [98.696121, 23.784429], + [98.784816, 23.781691], + [98.824236, 23.727462], + [98.811917, 23.703354], + [98.835939, 23.683625], + [98.847026, 23.632097], + [98.882134, 23.620035], + [98.882134, 23.595358], + [98.844562, 23.578904], + [98.80391, 23.540504], + [98.826084, 23.470257], + [98.874743, 23.483431], + [98.912315, 23.426333], + [98.920938, 23.360971], + [98.872895, 23.329651], + [98.906772, 23.331849], + [98.936953, 23.309866], + [98.928946, 23.26589], + [98.889525, 23.209249], + [98.906772, 23.185595], + [99.002242, 23.160287], + [99.057677, 23.164689], + [99.048438, 23.11461], + [99.106336, 23.086536], + [99.187024, 23.100299], + [99.255393, 23.077727], + [99.281879, 23.101399], + [99.3484, 23.12892], + [99.380429, 23.099748], + [99.440791, 23.079379], + [99.477747, 23.083233], + [99.528255, 23.065614], + [99.517168, 23.006685], + [99.533798, 22.961507], + [99.563363, 22.925684], + [99.531334, 22.897019], + [99.446951, 22.934503], + [99.43648, 22.913557], + [99.462965, 22.844635], + [99.401371, 22.826434], + [99.385357, 22.761882], + [99.326842, 22.751396], + [99.31514, 22.737598], + [99.339777, 22.708894], + [99.385973, 22.57136], + [99.359487, 22.535435], + [99.382277, 22.493418], + [99.297277, 22.41156], + [99.251698, 22.393301], + [99.278183, 22.34626], + [99.233836, 22.296434], + [99.235683, 22.250468], + [99.207966, 22.232188], + [99.175321, 22.185647], + [99.188256, 22.162924], + [99.156227, 22.159599], + [99.219669, 22.110816], + [99.294814, 22.109152], + [99.35456, 22.095845], + [99.400139, 22.100281], + [99.486987, 22.128557], + [99.516552, 22.099726], + [99.562747, 22.113034], + [99.578762, 22.098617], + [99.581841, 22.103053], + [99.648979, 22.100835], + [99.696406, 22.067562], + [99.762927, 22.068117], + [99.870101, 22.029288], + [99.871333, 22.067007], + [99.972347, 22.053141], + [99.965571, 22.014309], + [100.000064, 21.973245], + [99.982202, 21.919401], + [99.960028, 21.907186], + [99.944014, 21.821097], + [99.991441, 21.703821], + [100.049339, 21.669899], + [100.094303, 21.702709], + [100.131875, 21.699929], + [100.169447, 21.663225], + [100.107853, 21.585337], + [100.123252, 21.565302], + [100.131259, 21.504066], + [100.168831, 21.482906], + [100.184846, 21.516315], + [100.206404, 21.509634], + [100.235353, 21.466756], + [100.298795, 21.477894], + [100.349302, 21.528564], + [100.437381, 21.533017], + [100.48296, 21.458958], + [100.526692, 21.471211], + [100.579047, 21.451717], + [100.691764, 21.510748], + [100.730568, 21.518542], + [100.753358, 21.555283], + [100.789082, 21.570867], + [100.804481, 21.609821], + [100.847597, 21.634856], + [100.870386, 21.67268], + [100.896872, 21.68269], + [100.899335, 21.684915], + [100.936292, 21.694368], + [100.937524, 21.693812], + [101.015132, 21.707157], + [101.089661, 21.773865], + [101.123537, 21.771642], + [101.111835, 21.746074], + [101.116762, 21.691032], + [101.153102, 21.669343], + [101.169117, 21.590345], + [101.146943, 21.560293], + [101.209153, 21.55751], + [101.210385, 21.509077], + [101.225167, 21.499055], + [101.193138, 21.473996], + [101.194986, 21.424979], + [101.142631, 21.409379], + [101.183899, 21.334699], + [101.244877, 21.302364], + [101.246725, 21.275598], + [101.222088, 21.234324], + [101.290457, 21.17853], + [101.387775, 21.225956], + [101.439514, 21.227072], + [101.532521, 21.252174], + [101.601506, 21.233208], + [101.588572, 21.191365], + [101.605818, 21.172392], + [101.672339, 21.194713], + [101.703136, 21.14616], + [101.76473, 21.147835], + [101.794911, 21.208104], + [101.834331, 21.204756], + [101.833715, 21.252731], + [101.791832, 21.285636], + [101.745636, 21.297345], + [101.730238, 21.336929], + [101.749948, 21.409379], + [101.741324, 21.482906], + [101.772737, 21.512975], + [101.755491, 21.538027], + [101.754875, 21.58478], + [101.804766, 21.577546], + [101.828788, 21.617054], + [101.807846, 21.644313], + [101.780129, 21.640975], + [101.76781, 21.716054], + [101.747484, 21.729953], + [101.771506, 21.833319], + [101.740093, 21.845541], + [101.735165, 21.875534], + [101.700057, 21.897191], + [101.701288, 21.938832], + [101.666796, 21.934391], + [101.606434, 21.967695], + [101.626144, 22.005986], + [101.573789, 22.115251], + [101.602738, 22.131883], + [101.596579, 22.161262], + [101.547304, 22.238282], + [101.56455, 22.269299], + [101.625528, 22.28259], + [101.671723, 22.372826], + [101.648318, 22.400494], + [101.672339, 22.47517], + [101.715455, 22.477935], + [101.774585, 22.506135], + [101.824476, 22.45692], + [101.823244, 22.42705], + [101.862665, 22.389427], + [101.901469, 22.384447], + [101.907628, 22.437007], + [101.978461, 22.427603], + [102.046214, 22.458026], + [102.131214, 22.430922], + [102.145381, 22.397727], + [102.179257, 22.430369], + [102.270416, 22.419858], + [102.25625, 22.457473], + [102.322771, 22.554227], + [102.356648, 22.563623], + [102.404691, 22.629925], + [102.384365, 22.679631], + [102.43672, 22.699508], + [102.45951, 22.762986], + [102.510633, 22.774574], + [102.551285, 22.743669], + [102.569763, 22.701164], + [102.607335, 22.730975], + [102.657226, 22.687913], + [102.688639, 22.70006], + [102.80074, 22.620534], + [102.82353, 22.623296], + [102.880196, 22.586832], + [102.892515, 22.533223], + [102.930703, 22.482359], + [102.986754, 22.477935], + [103.030485, 22.441432], + [103.081608, 22.454154], + [103.071753, 22.488441], + [103.183238, 22.558649], + [103.161065, 22.590147], + [103.195557, 22.648153], + [103.220195, 22.643734], + [103.283021, 22.678526], + [103.288564, 22.732078], + [103.321209, 22.777885], + [103.323057, 22.807678], + [103.375411, 22.794989], + [103.441317, 22.753052], + [103.436389, 22.6973], + [103.457947, 22.658646], + [103.50907, 22.601198], + [103.529396, 22.59291], + [103.580519, 22.66693], + [103.567585, 22.701164], + [103.642113, 22.794989], + [103.740048, 22.709446], + [103.743127, 22.697852], + [103.766533, 22.688465], + [103.825047, 22.615562], + [103.863851, 22.584069], + [103.875554, 22.565833], + [103.894032, 22.564728], + [103.964865, 22.502265], + [104.009213, 22.517745], + [104.009213, 22.575228], + [104.022148, 22.593463], + [104.04309, 22.67687], + [104.045553, 22.728215], + [104.089901, 22.768504], + [104.117618, 22.808781], + [104.224176, 22.826434], + [104.261748, 22.841877], + [104.274067, 22.828088], + [104.256821, 22.77347], + [104.272835, 22.73815], + [104.323342, 22.728767], + [104.375697, 22.690122], + [104.422508, 22.734838], + [104.498885, 22.774574], + [104.527834, 22.814298], + [104.596203, 22.846289], + [104.674428, 22.817056], + [104.737869, 22.825882], + [104.732942, 22.852356], + [104.760659, 22.862282], + [104.772362, 22.893711], + [104.846275, 22.926235], + [104.860441, 22.970874], + [104.821021, 23.032022], + [104.804391, 23.110207], + [104.874608, 23.123417], + [104.882615, 23.163589], + [104.912796, 23.175693], + [104.949136, 23.152033], + [104.958991, 23.188896], + [105.093266, 23.260942], + [105.122215, 23.247745], + [105.181962, 23.279084], + [105.238012, 23.26424], + [105.260186, 23.31811], + [105.325475, 23.390086], + [105.353809, 23.362069], + [105.372903, 23.317561], + [105.416018, 23.283482], + [105.445584, 23.292827], + [105.50225, 23.202648], + [105.542902, 23.184495], + [105.526272, 23.234548], + [105.560148, 23.257093], + [105.593409, 23.312614], + [105.649459, 23.346136], + [105.699966, 23.327453], + [105.694423, 23.363168], + [105.637757, 23.404366], + [105.699966, 23.40162], + [105.758481, 23.459826], + [105.805908, 23.467512], + [105.815763, 23.507031], + [105.852103, 23.526786], + [105.89214, 23.52514], + [105.913081, 23.499348], + [105.935871, 23.508678], + [105.986378, 23.489469], + [105.999929, 23.447748], + [106.039965, 23.484529], + [106.071994, 23.495506], + [106.08616, 23.524043], + [106.141595, 23.569579], + [106.120653, 23.605229], + [106.149602, 23.665538], + [106.157609, 23.724175], + [106.136667, 23.795381], + [106.192102, 23.824947], + [106.173008, 23.861622], + [106.192718, 23.879135], + [106.157609, 23.891174], + [106.128044, 23.956819], + [106.091088, 23.998924], + [106.096631, 24.018058], + [106.053516, 24.051399], + [106.04982, 24.089649], + [106.011632, 24.099482], + [105.998081, 24.120786], + [105.963589, 24.110954], + [105.919241, 24.122425], + [105.901995, 24.099482], + [105.908154, 24.069432], + [105.89214, 24.040468], + [105.859495, 24.056864], + [105.841633, 24.03063], + [105.796669, 24.023524], + [105.802212, 24.051945], + [105.765256, 24.073804], + [105.739387, 24.059596], + [105.704278, 24.0667], + [105.649459, 24.032816], + [105.628518, 24.126794], + [105.594641, 24.137718], + [105.533663, 24.130071], + [105.493011, 24.016965], + [105.406163, 24.043748], + [105.395692, 24.065607], + [105.334099, 24.094566], + [105.320548, 24.116416], + [105.273121, 24.092927], + [105.292831, 24.074896], + [105.260186, 24.061236], + [105.20044, 24.105491], + [105.182577, 24.167205], + [105.229389, 24.165567], + [105.24294, 24.208695], + [105.215222, 24.214699], + [105.164715, 24.288362], + [105.196744, 24.326541], + [105.188121, 24.347261], + [105.138846, 24.376701], + [105.111744, 24.37234], + [105.106817, 24.414853], + [105.042759, 24.442097], + [104.979933, 24.412673], + [104.930042, 24.411038], + [104.914028, 24.426296], + [104.83642, 24.446456], + [104.784681, 24.443732], + [104.765587, 24.45953], + [104.74834, 24.435559], + [104.715695, 24.441552], + [104.703377, 24.419757], + [104.721239, 24.340173], + [104.70892, 24.321087], + [104.641783, 24.367979], + [104.610986, 24.377246], + [104.63008, 24.397958], + [104.616529, 24.421937], + [104.575877, 24.424661], + [104.550008, 24.518894], + [104.520443, 24.535228], + [104.489646, 24.653313], + [104.529682, 24.731611], + [104.542616, 24.75607], + [104.539537, 24.813663], + [104.586964, 24.872859], + [104.635623, 24.903803], + [104.663957, 24.964584], + [104.713232, 24.996048], + [104.684898, 25.054072], + [104.619609, 25.060577], + [104.685514, 25.078466], + [104.695369, 25.122364], + [104.732326, 25.167871], + [104.724319, 25.195491], + [104.753884, 25.214443], + [104.801927, 25.163537], + [104.822869, 25.170037], + [104.806854, 25.224189], + [104.826565, 25.235558], + [104.816094, 25.262622], + [104.736021, 25.268034], + [104.689826, 25.296173], + [104.639935, 25.295632], + [104.646094, 25.356759], + [104.615913, 25.364871], + [104.566638, 25.402719], + [104.543232, 25.400556], + [104.556783, 25.524832], + [104.524138, 25.526992], + [104.483486, 25.494585], + [104.44961, 25.495126], + [104.434827, 25.472436], + [104.418813, 25.499447], + [104.436059, 25.520512], + [104.428668, 25.576126], + [104.389248, 25.595558], + [104.332581, 25.598796], + [104.310407, 25.647901], + [104.328886, 25.760602], + [104.370769, 25.730415], + [104.397871, 25.76168], + [104.42374, 25.841961], + [104.441602, 25.868889], + [104.414501, 25.909807], + [104.438523, 25.92757], + [104.470552, 26.009352], + [104.460081, 26.085702], + [104.499501, 26.070651], + [104.52845, 26.114186], + [104.518595, 26.165762], + [104.548776, 26.226979], + [104.542616, 26.253282], + [104.592508, 26.317672], + [104.659645, 26.335373], + [104.684283, 26.3772], + [104.664572, 26.397572], + [104.665804, 26.434019], + [104.631928, 26.451702], + [104.638703, 26.477954], + [104.598667, 26.520801], + [104.57095, 26.524549], + [104.579573, 26.568449], + [104.556783, 26.590393], + [104.488414, 26.579689], + [104.459465, 26.602701], + [104.468088, 26.644431], + [104.424356, 26.709137], + [104.398487, 26.686147], + [104.353523, 26.620893], + [104.313487, 26.612867], + [104.274683, 26.633733], + [104.268524, 26.617683], + [104.222328, 26.620358], + [104.160734, 26.646571], + [104.121314, 26.638012], + [104.068343, 26.573266], + [104.067727, 26.51491], + [104.008597, 26.511697], + [103.953163, 26.521336], + [103.865699, 26.512232], + [103.819504, 26.529903], + [103.815808, 26.55239], + [103.763453, 26.585041], + [103.748671, 26.623568], + [103.759142, 26.689355], + [103.773308, 26.716621], + [103.725265, 26.742812], + [103.705555, 26.794642], + [103.722185, 26.851253], + [103.779468, 26.87421], + [103.763453, 26.905702], + [103.775156, 26.951056], + [103.753598, 26.963858], + [103.73204, 27.018785], + [103.704939, 27.049171], + [103.675374, 27.051836], + [103.623019, 27.007056], + [103.623635, 27.035312], + [103.601461, 27.061962], + [103.614396, 27.079548], + [103.659975, 27.065692], + [103.652584, 27.092868], + [103.620555, 27.096598], + [103.63349, 27.12057], + [103.696316, 27.126429], + [103.748671, 27.210021], + [103.801641, 27.250464], + [103.80041, 27.26536], + [103.865699, 27.28185], + [103.874322, 27.331304], + [103.903271, 27.347785], + [103.905119, 27.38552], + [103.932221, 27.443958], + [103.956242, 27.425367], + [104.015372, 27.429086], + [104.01722, 27.383926], + [104.084358, 27.330773], + [104.113923, 27.338216], + [104.173053, 27.263232], + [104.210625, 27.297273], + [104.248813, 27.291955], + [104.247582, 27.336621], + [104.295625, 27.37436], + [104.30856, 27.407305], + [104.363378, 27.467855], + [104.467472, 27.414211], + [104.497037, 27.414743], + [104.539537, 27.327583], + [104.570334, 27.331836], + [104.611602, 27.306846], + [104.7545, 27.345658], + [104.77113, 27.317481], + [104.824717, 27.3531], + [104.856746, 27.332368], + [104.851818, 27.299401], + [104.871528, 27.290891], + [104.913412, 27.327051], + [105.01073, 27.379143], + [105.068013, 27.418461], + [105.120984, 27.418461], + [105.184425, 27.392959], + [105.182577, 27.367451], + [105.233084, 27.436522], + [105.234316, 27.489093], + [105.260186, 27.514573], + [105.232469, 27.546945], + [105.25649, 27.582491], + [105.304533, 27.611661], + [105.29591, 27.631811], + [105.308229, 27.704955] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 540000, + "name": "Tibet", + "center": [91.132212, 29.660361], + "centroid": [88.388277, 31.56375], + "childrenNum": 7, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 25, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [89.711414, 36.093272], + [89.614711, 36.109712], + [89.594385, 36.126632], + [89.490291, 36.151281], + [89.375727, 36.228078], + [89.335075, 36.23725], + [89.292575, 36.231457], + [89.232213, 36.295636], + [89.198952, 36.260417], + [89.126887, 36.254626], + [89.10225, 36.281164], + [89.054822, 36.291777], + [89.013554, 36.315409], + [88.964279, 36.318785], + [88.926091, 36.36458], + [88.870657, 36.348193], + [88.838628, 36.353496], + [88.802903, 36.33807], + [88.783809, 36.291777], + [88.766563, 36.292259], + [88.690186, 36.367954], + [88.623665, 36.389636], + [88.618121, 36.428168], + [88.573158, 36.461386], + [88.498629, 36.446463], + [88.470912, 36.48208], + [88.41055, 36.473418], + [88.356963, 36.477268], + [88.366202, 36.458016], + [88.282434, 36.470049], + [88.241782, 36.468605], + [88.222688, 36.447426], + [88.182652, 36.452721], + [88.134609, 36.427205], + [88.092109, 36.43539], + [88.006494, 36.430575], + [87.983088, 36.437797], + [87.95845, 36.408423], + [87.919646, 36.39349], + [87.838342, 36.383855], + [87.826023, 36.391563], + [87.767509, 36.3747], + [87.731785, 36.384818], + [87.6203, 36.360243], + [87.570409, 36.342409], + [87.470626, 36.354459], + [87.460155, 36.409868], + [87.426895, 36.42576], + [87.386859, 36.412757], + [87.363453, 36.420463], + [87.348055, 36.393008], + [87.292004, 36.358797], + [87.193454, 36.349158], + [87.161425, 36.325535], + [87.149106, 36.297565], + [87.08628, 36.310587], + [87.051788, 36.2966], + [86.996353, 36.308658], + [86.943998, 36.284058], + [86.931064, 36.265242], + [86.887332, 36.262829], + [86.86331, 36.299977], + [86.836209, 36.291294], + [86.746282, 36.291777], + [86.69947, 36.24449], + [86.599072, 36.222285], + [86.531935, 36.227113], + [86.515305, 36.205385], + [86.454943, 36.221319], + [86.392733, 36.206834], + [86.35824, 36.168676], + [86.2794, 36.170608], + [86.248603, 36.141616], + [86.187625, 36.130983], + [86.182081, 36.064734], + [86.199944, 36.047801], + [86.173458, 36.008113], + [86.150668, 36.00424], + [86.129111, 35.941761], + [86.093386, 35.906868], + [86.090306, 35.876809], + [86.05335, 35.842857], + [86.035488, 35.846738], + [85.949256, 35.778794], + [85.903677, 35.78462], + [85.835308, 35.771996], + [85.811286, 35.778794], + [85.691178, 35.751114], + [85.65299, 35.731199], + [85.612953, 35.651486], + [85.566142, 35.6403], + [85.518715, 35.680658], + [85.373969, 35.700101], + [85.341324, 35.753543], + [85.271107, 35.788989], + [85.146071, 35.742371], + [85.053065, 35.752086], + [84.99455, 35.737028], + [84.973608, 35.709334], + [84.920022, 35.696213], + [84.798066, 35.647595], + [84.729081, 35.613546], + [84.704443, 35.616951], + [84.628067, 35.595055], + [84.570168, 35.588242], + [84.513502, 35.564391], + [84.448828, 35.550272], + [84.475929, 35.516181], + [84.45314, 35.473303], + [84.424191, 35.466479], + [84.333032, 35.413821], + [84.274517, 35.404065], + [84.200605, 35.381135], + [84.160569, 35.359663], + [84.140859, 35.379184], + [84.095895, 35.362592], + [84.077417, 35.400163], + [84.005968, 35.422599], + [83.906186, 35.40309], + [83.885244, 35.367472], + [83.79778, 35.354783], + [83.785462, 35.36308], + [83.677672, 35.361128], + [83.622238, 35.335256], + [83.599448, 35.351366], + [83.54155, 35.341603], + [83.540318, 35.364056], + [83.502745, 35.360639], + [83.449159, 35.382111], + [83.405427, 35.380648], + [83.333978, 35.397236], + [83.280391, 35.401138], + [83.251442, 35.417722], + [83.178145, 35.38943], + [83.127022, 35.398699], + [83.088834, 35.425526], + [83.067892, 35.46258], + [82.998907, 35.484512], + [82.971806, 35.548324], + [82.981661, 35.599922], + [82.956407, 35.636409], + [82.967494, 35.667532], + [82.894813, 35.673852], + [82.873871, 35.688922], + [82.795031, 35.688436], + [82.780249, 35.666073], + [82.731589, 35.637868], + [82.652133, 35.67288], + [82.628727, 35.692324], + [82.546192, 35.708362], + [82.501844, 35.701073], + [82.468583, 35.717595], + [82.424852, 35.712736], + [82.392823, 35.656349], + [82.336156, 35.651486], + [82.350323, 35.611113], + [82.328149, 35.559523], + [82.2992, 35.544916], + [82.263475, 35.547837], + [82.234526, 35.520565], + [82.189563, 35.513258], + [82.164925, 35.495719], + [82.086701, 35.467454], + [82.071302, 35.450393], + [82.034346, 35.451855], + [82.029419, 35.426013], + [82.05344, 35.35039], + [82.030034, 35.321585], + [81.99123, 35.30547], + [81.955506, 35.307423], + [81.927789, 35.271275], + [81.853876, 35.25857], + [81.804601, 35.270786], + [81.736847, 35.26248], + [81.68634, 35.235599], + [81.513261, 35.23511], + [81.504638, 35.279092], + [81.447972, 35.318167], + [81.441196, 35.333303], + [81.385762, 35.335256], + [81.363588, 35.354783], + [81.314313, 35.337209], + [81.285364, 35.345508], + [81.26627, 35.322562], + [81.219458, 35.319144], + [81.191741, 35.36552], + [81.142466, 35.365032], + [81.103662, 35.386015], + [81.09935, 35.40748], + [81.054387, 35.402602], + [81.031597, 35.380648], + [81.030981, 35.337209], + [81.002648, 35.334768], + [81.026053, 35.31133], + [80.963844, 35.310842], + [80.924423, 35.330862], + [80.894242, 35.324027], + [80.844351, 35.345508], + [80.759968, 35.334768], + [80.689135, 35.339162], + [80.690982, 35.364544], + [80.65649, 35.393821], + [80.599823, 35.409431], + [80.56841, 35.391381], + [80.532686, 35.404553], + [80.514824, 35.391869], + [80.444607, 35.417235], + [80.432904, 35.449418], + [80.375006, 35.387966], + [80.321419, 35.38699], + [80.286926, 35.35283], + [80.267832, 35.295701], + [80.362687, 35.20871], + [80.257977, 35.203331], + [80.223484, 35.177409], + [80.23026, 35.147565], + [80.118159, 35.066293], + [80.078123, 35.076578], + [80.031311, 35.034447], + [80.04363, 35.022196], + [80.02392, 34.971209], + [80.041782, 34.943252], + [80.034391, 34.902033], + [80.003594, 34.895162], + [79.996819, 34.856375], + [79.961094, 34.862759], + [79.926602, 34.849499], + [79.947544, 34.821008], + [79.898268, 34.732035], + [79.906892, 34.683821], + [79.866856, 34.671517], + [79.88595, 34.642965], + [79.84345, 34.55725], + [79.861312, 34.528166], + [79.801566, 34.478847], + [79.735661, 34.471447], + [79.699936, 34.477861], + [79.675914, 34.451216], + [79.58106, 34.456151], + [79.545335, 34.476381], + [79.504683, 34.45467], + [79.435082, 34.447761], + [79.363017, 34.428018], + [79.326677, 34.44332], + [79.274322, 34.435916], + [79.241677, 34.415183], + [79.179467, 34.422588], + [79.161605, 34.441345], + [79.072294, 34.412714], + [79.039033, 34.421601], + [79.0107, 34.399877], + [79.048888, 34.348506], + [79.039649, 34.33467], + [79.019939, 34.313417], + [78.981751, 34.31836], + [78.958345, 34.230827], + [78.941099, 34.212022], + [78.9257, 34.155584], + [78.910302, 34.143202], + [78.878273, 34.163012], + [78.828998, 34.125369], + [78.801897, 34.137258], + [78.737223, 34.089692], + [78.661462, 34.086718], + [78.656535, 34.030196], + [78.736607, 33.999937], + [78.744614, 33.980585], + [78.734143, 33.918529], + [78.762476, 33.90959], + [78.756317, 33.8773], + [78.766172, 33.823124], + [78.758165, 33.790802], + [78.779723, 33.73259], + [78.692259, 33.676331], + [78.684868, 33.654415], + [78.713201, 33.623025], + [78.755085, 33.623025], + [78.74215, 33.55323], + [78.816679, 33.480882], + [78.84994, 33.419963], + [78.896751, 33.41247], + [78.949722, 33.376495], + [78.9682, 33.334505], + [79.022403, 33.323504], + [79.041497, 33.268479], + [79.083997, 33.245459], + [79.072294, 33.22844], + [79.10925, 33.200401], + [79.152366, 33.184375], + [79.162221, 33.165841], + [79.139431, 33.117735], + [79.162837, 33.01191], + [79.204721, 32.964724], + [79.255844, 32.942628], + [79.227511, 32.89038], + [79.237982, 32.846145], + [79.225047, 32.784281], + [79.275554, 32.778746], + [79.301423, 32.728919], + [79.27309, 32.678056], + [79.299575, 32.637244], + [79.308199, 32.596918], + [79.272474, 32.561113], + [79.252148, 32.516715], + [79.190554, 32.511669], + [79.180083, 32.492994], + [79.135736, 32.472295], + [79.124649, 32.416235], + [79.103091, 32.369744], + [79.067982, 32.380863], + [79.005772, 32.375304], + [78.970664, 32.331826], + [78.904142, 32.374798], + [78.87273, 32.40512], + [78.81052, 32.436441], + [78.782186, 32.480373], + [78.760629, 32.563635], + [78.781571, 32.608009], + [78.74215, 32.654881], + [78.741534, 32.703743], + [78.6861, 32.680071], + [78.675013, 32.658408], + [78.628202, 32.630188], + [78.588782, 32.637748], + [78.577695, 32.615067], + [78.518564, 32.605993], + [78.500086, 32.580782], + [78.424942, 32.565652], + [78.395377, 32.530339], + [78.426174, 32.502584], + [78.472985, 32.435431], + [78.458818, 32.379853], + [78.483456, 32.357106], + [78.480992, 32.329297], + [78.508709, 32.297939], + [78.475449, 32.236708], + [78.430485, 32.212407], + [78.429869, 32.194683], + [78.469905, 32.127808], + [78.509941, 32.147065], + [78.527188, 32.11463], + [78.609107, 32.052768], + [78.60726, 32.023851], + [78.705194, 31.988835], + [78.762476, 31.947203], + [78.768636, 31.92638], + [78.739687, 31.885228], + [78.665158, 31.851684], + [78.654687, 31.819144], + [78.706426, 31.778453], + [78.763092, 31.668499], + [78.798817, 31.675629], + [78.806824, 31.64099], + [78.845628, 31.609905], + [78.833925, 31.584927], + [78.779723, 31.545154], + [78.740303, 31.532912], + [78.729832, 31.478316], + [78.755701, 31.478316], + [78.792041, 31.435944], + [78.760013, 31.392531], + [78.755085, 31.355742], + [78.795121, 31.301043], + [78.859179, 31.289281], + [78.865338, 31.312804], + [78.884432, 31.277006], + [78.923852, 31.246824], + [78.930628, 31.220726], + [78.997765, 31.158779], + [78.97436, 31.115751], + [79.010084, 31.043994], + [79.059359, 31.028097], + [79.096931, 30.992192], + [79.181931, 31.015788], + [79.205953, 31.0004], + [79.227511, 30.949088], + [79.33222, 30.969103], + [79.316206, 31.01784], + [79.35809, 31.031174], + [79.404901, 31.071678], + [79.424611, 31.061425], + [79.427075, 31.018353], + [79.505915, 31.027584], + [79.550879, 30.957813], + [79.59769, 30.925989], + [79.660516, 30.956787], + [79.668523, 30.980392], + [79.729501, 30.941389], + [79.75845, 30.936769], + [79.835443, 30.851006], + [79.890877, 30.855116], + [79.913051, 30.833022], + [79.900732, 30.7991], + [79.961094, 30.771337], + [79.955551, 30.738422], + [79.970333, 30.685941], + [80.014065, 30.661748], + [80.04363, 30.603559], + [80.143412, 30.55822], + [80.214245, 30.586044], + [80.261673, 30.566465], + [80.322035, 30.564403], + [80.357759, 30.520592], + [80.43044, 30.515952], + [80.446454, 30.495327], + [80.504969, 30.483466], + [80.549316, 30.448905], + [80.585041, 30.463866], + [80.633084, 30.458707], + [80.692214, 30.416913], + [80.719316, 30.414848], + [80.81725, 30.321389], + [80.910873, 30.30279], + [80.933662, 30.266614], + [80.996488, 30.267648], + [81.034677, 30.246971], + [81.038372, 30.205086], + [81.082104, 30.151281], + [81.085799, 30.100554], + [81.110437, 30.085538], + [81.09627, 30.052909], + [81.131995, 30.016124], + [81.225618, 30.005759], + [81.256415, 30.011978], + [81.247792, 30.032705], + [81.2829, 30.061197], + [81.293371, 30.094859], + [81.269349, 30.153351], + [81.335871, 30.149729], + [81.393769, 30.199396], + [81.397465, 30.240767], + [81.419023, 30.270232], + [81.406088, 30.291938], + [81.427646, 30.305373], + [81.399929, 30.319323], + [81.406088, 30.369421], + [81.432573, 30.379231], + [81.406704, 30.40401], + [81.418407, 30.420525], + [81.454131, 30.412268], + [81.494783, 30.381296], + [81.555761, 30.369421], + [81.566232, 30.428782], + [81.613044, 30.412784], + [81.63029, 30.446842], + [81.723913, 30.407623], + [81.759021, 30.385426], + [81.872354, 30.373035], + [81.939491, 30.344633], + [81.954274, 30.355995], + [81.99123, 30.322939], + [82.022027, 30.339468], + [82.060215, 30.332237], + [82.104563, 30.346182], + [82.132896, 30.30434], + [82.11873, 30.279019], + [82.114418, 30.226806], + [82.142135, 30.200948], + [82.188947, 30.18543], + [82.207425, 30.143519], + [82.183403, 30.12178], + [82.17786, 30.06793], + [82.246845, 30.071555], + [82.311519, 30.035813], + [82.333693, 30.045138], + [82.368185, 30.014051], + [82.412533, 30.011978], + [82.431011, 29.989692], + [82.474743, 29.973622], + [82.498148, 29.947698], + [82.560974, 29.955476], + [82.609017, 29.886489], + [82.64351, 29.868846], + [82.6238, 29.834588], + [82.703872, 29.847566], + [82.737749, 29.80655], + [82.691553, 29.766037], + [82.757459, 29.761881], + [82.774089, 29.726548], + [82.816589, 29.717192], + [82.830756, 29.687562], + [82.885574, 29.689122], + [82.9484, 29.704718], + [82.966878, 29.658963], + [83.011226, 29.667804], + [83.088834, 29.604863], + [83.12887, 29.623593], + [83.159667, 29.61735], + [83.164595, 29.595496], + [83.217565, 29.60018], + [83.266841, 29.571035], + [83.27608, 29.505951], + [83.325355, 29.502826], + [83.383253, 29.42206], + [83.415898, 29.420496], + [83.423289, 29.361053], + [83.450391, 29.332883], + [83.463941, 29.285916], + [83.492274, 29.280174], + [83.548941, 29.201322], + [83.57789, 29.203934], + [83.596368, 29.174153], + [83.656114, 29.16736], + [83.667201, 29.200277], + [83.727563, 29.244672], + [83.800244, 29.249372], + [83.82057, 29.294267], + [83.851367, 29.294789], + [83.911729, 29.323491], + [83.949301, 29.312533], + [83.986874, 29.325057], + [84.002272, 29.291658], + [84.052163, 29.296877], + [84.116837, 29.286438], + [84.130388, 29.239972], + [84.203068, 29.239972], + [84.197525, 29.210202], + [84.17104, 29.19453], + [84.176583, 29.133909], + [84.20738, 29.118749], + [84.192597, 29.084236], + [84.194445, 29.045004], + [84.224626, 29.049189], + [84.248648, 29.030353], + [84.228322, 28.949738], + [84.234481, 28.889497], + [84.268358, 28.895261], + [84.330568, 28.859101], + [84.340423, 28.866963], + [84.408176, 28.85386], + [84.404481, 28.828173], + [84.434046, 28.823978], + [84.445133, 28.764189], + [84.483321, 28.735331], + [84.557233, 28.74635], + [84.620059, 28.732182], + [84.650856, 28.714338], + [84.669334, 28.680742], + [84.699515, 28.671816], + [84.698284, 28.633478], + [84.773428, 28.610363], + [84.857196, 28.567798], + [84.896616, 28.587244], + [84.981616, 28.586193], + [84.995782, 28.611414], + [85.05676, 28.674441], + [85.126361, 28.676016], + [85.155926, 28.643983], + [85.195963, 28.624022], + [85.18426, 28.587244], + [85.189803, 28.544669], + [85.160238, 28.49261], + [85.108499, 28.461047], + [85.129441, 28.377885], + [85.113427, 28.344708], + [85.179948, 28.324164], + [85.209513, 28.338914], + [85.272339, 28.282538], + [85.349947, 28.298347], + [85.379512, 28.274105], + [85.415853, 28.321003], + [85.458969, 28.332593], + [85.520563, 28.326798], + [85.602483, 28.295712], + [85.601251, 28.254075], + [85.650526, 28.283592], + [85.682555, 28.375779], + [85.720743, 28.372093], + [85.753388, 28.227714], + [85.791576, 28.195544], + [85.854402, 28.172334], + [85.871648, 28.124843], + [85.898749, 28.101617], + [85.901213, 28.053566], + [85.980053, 27.984357], + [85.949256, 27.937311], + [86.002227, 27.90717], + [86.053966, 27.900823], + [86.125415, 27.923035], + [86.082915, 28.018175], + [86.086611, 28.090002], + [86.128495, 28.086835], + [86.140198, 28.114814], + [86.19132, 28.167058], + [86.223965, 28.092642], + [86.206103, 28.084195], + [86.231972, 27.974315], + [86.27324, 27.976958], + [86.308965, 27.950528], + [86.393349, 27.926736], + [86.414906, 27.904526], + [86.450015, 27.908757], + [86.475884, 27.944713], + [86.514689, 27.954757], + [86.513457, 27.996511], + [86.537478, 28.044587], + [86.55842, 28.047757], + [86.568891, 28.103201], + [86.60092, 28.097922], + [86.611391, 28.069938], + [86.647732, 28.06941], + [86.662514, 28.092114], + [86.700086, 28.101617], + [86.74813, 28.089474], + [86.768456, 28.06941], + [86.756753, 28.032967], + [86.827586, 28.012363], + [86.864542, 28.022401], + [86.885484, 27.995983], + [86.926752, 27.985942], + [86.935375, 27.955286], + [87.035157, 27.946299], + [87.080737, 27.910872], + [87.118309, 27.840512], + [87.173744, 27.818284], + [87.227946, 27.812991], + [87.249504, 27.839454], + [87.280917, 27.845275], + [87.317258, 27.826753], + [87.364069, 27.824106], + [87.421967, 27.856916], + [87.418272, 27.825694], + [87.45954, 27.820931], + [87.58088, 27.859562], + [87.598126, 27.814579], + [87.670191, 27.832045], + [87.668343, 27.809815], + [87.727473, 27.802933], + [87.77798, 27.860091], + [87.782292, 27.890774], + [87.826639, 27.927794], + [87.930733, 27.909285], + [87.982472, 27.884426], + [88.037291, 27.901881], + [88.090877, 27.885484], + [88.111819, 27.864852], + [88.137689, 27.878607], + [88.120442, 27.915103], + [88.156783, 27.957929], + [88.203594, 27.943127], + [88.242398, 27.967444], + [88.254101, 27.939426], + [88.357579, 27.986471], + [88.401311, 27.976958], + [88.43334, 28.002852], + [88.469064, 28.009721], + [88.498013, 28.04089], + [88.554064, 28.027684], + [88.565151, 28.083139], + [88.620585, 28.091586], + [88.645223, 28.111119], + [88.67602, 28.068353], + [88.764099, 28.068353], + [88.812142, 28.018175], + [88.842939, 28.006023], + [88.846635, 27.921448], + [88.864497, 27.921448], + [88.888519, 27.846863], + [88.863265, 27.811932], + [88.870657, 27.743098], + [88.850331, 27.710783], + [88.852178, 27.671039], + [88.816454, 27.641354], + [88.813374, 27.606889], + [88.770874, 27.563924], + [88.797976, 27.521473], + [88.783193, 27.467324], + [88.809063, 27.405711], + [88.838012, 27.37808], + [88.867577, 27.3818], + [88.901453, 27.327583], + [88.920548, 27.325456], + [88.911924, 27.272807], + [88.942105, 27.261636], + [88.984605, 27.208957], + [89.067757, 27.240354], + [89.077612, 27.287168], + [89.152757, 27.319076], + [89.182938, 27.373829], + [89.132431, 27.441302], + [89.095474, 27.471572], + [89.109025, 27.537925], + [89.163228, 27.574534], + [89.128735, 27.611131], + [89.131815, 27.633402], + [89.184786, 27.673689], + [89.238988, 27.796581], + [89.295655, 27.84845], + [89.375727, 27.875962], + [89.44348, 27.968501], + [89.461958, 28.03191], + [89.511233, 28.086307], + [89.541414, 28.088418], + [89.605472, 28.161782], + [89.720037, 28.170224], + [89.779167, 28.197127], + [89.789638, 28.240895], + [89.869094, 28.221386], + [89.901739, 28.18183], + [89.976268, 28.189215], + [90.017536, 28.162837], + [90.03355, 28.136981], + [90.07297, 28.155451], + [90.103151, 28.141731], + [90.124709, 28.190797], + [90.166593, 28.187632], + [90.189999, 28.161782], + [90.231882, 28.144897], + [90.297172, 28.153868], + [90.367389, 28.088946], + [90.384019, 28.06096], + [90.43699, 28.063073], + [90.47949, 28.044587], + [90.513983, 28.062016], + [90.569417, 28.044059], + [90.591591, 28.021345], + [90.701844, 28.076274], + [90.741264, 28.053038], + [90.802242, 28.040362], + [90.806554, 28.015005], + [90.853365, 27.969029], + [90.896481, 27.946299], + [90.96177, 27.9537], + [90.976553, 27.935725], + [90.96485, 27.900294], + [91.025828, 27.857445], + [91.113292, 27.846333], + [91.155175, 27.894476], + [91.147784, 27.927794], + [91.162567, 27.968501], + [91.216153, 27.989113], + [91.251878, 27.970615], + [91.309776, 28.057791], + [91.464993, 28.002852], + [91.490246, 27.971672], + [91.486551, 27.937311], + [91.552456, 27.90717], + [91.611586, 27.891303], + [91.618978, 27.856916], + [91.561079, 27.855329], + [91.544449, 27.820401], + [91.610355, 27.819343], + [91.642383, 27.7664], + [91.622673, 27.692238], + [91.570934, 27.650897], + [91.562311, 27.627569], + [91.582637, 27.598933], + [91.564775, 27.58196], + [91.585101, 27.540578], + [91.626985, 27.509265], + [91.663325, 27.507142], + [91.71876, 27.467324], + [91.753868, 27.462545], + [91.839484, 27.489624], + [91.946657, 27.464138], + [92.010715, 27.474758], + [92.021802, 27.444489], + [92.064918, 27.391365], + [92.125896, 27.273339], + [92.091403, 27.264296], + [92.071077, 27.237694], + [92.061222, 27.190327], + [92.032273, 27.167967], + [92.02673, 27.108318], + [92.043976, 27.052902], + [92.076005, 27.041175], + [92.124664, 26.960124], + [92.109265, 26.854991], + [92.197961, 26.86994], + [92.28604, 26.892359], + [92.404916, 26.9025], + [92.496691, 26.921711], + [92.549046, 26.941453], + [92.64698, 26.952656], + [92.682089, 26.947855], + [92.802813, 26.895028], + [92.909371, 26.914241], + [93.050421, 26.883819], + [93.111399, 26.880082], + [93.232739, 26.906769], + [93.56781, 26.938252], + [93.625092, 26.955323], + [93.747048, 27.015587], + [93.817265, 27.025183], + [93.841903, 27.045973], + [93.849294, 27.168499], + [93.970634, 27.30525], + [94.056866, 27.375423], + [94.147409, 27.458297], + [94.220705, 27.536333], + [94.277372, 27.58143], + [94.353132, 27.578778], + [94.399944, 27.589386], + [94.443675, 27.585143], + [94.478168, 27.602116], + [94.524979, 27.596282], + [94.660486, 27.650367], + [94.722696, 27.683759], + [94.78121, 27.699127], + [94.836645, 27.728796], + [94.88592, 27.743098], + [94.947514, 27.792345], + [95.015267, 27.82887], + [95.067006, 27.840512], + [95.28628, 27.939955], + [95.32878, 28.017646], + [95.352802, 28.04089], + [95.371896, 28.110063], + [95.39715, 28.142259], + [95.437802, 28.161782], + [95.528345, 28.182885], + [95.674322, 28.254075], + [95.740228, 28.275159], + [95.787655, 28.270416], + [95.832003, 28.295186], + [95.874502, 28.29782], + [95.899756, 28.278322], + [95.907763, 28.241422], + [95.936096, 28.240368], + [95.989067, 28.198181], + [96.074683, 28.193434], + [96.098088, 28.212421], + [96.194175, 28.212949], + [96.275479, 28.228241], + [96.298269, 28.140148], + [96.367254, 28.118509], + [96.398667, 28.118509], + [96.395587, 28.143842], + [96.426384, 28.161782], + [96.46334, 28.143314], + [96.499681, 28.067297], + [96.538485, 28.075218], + [96.623485, 28.024514], + [96.635188, 27.994926], + [96.690622, 27.948942], + [96.711564, 27.9574], + [96.784245, 27.931495], + [96.810114, 27.890245], + [96.849534, 27.874375], + [96.908049, 27.884426], + [96.972722, 27.861149], + [97.008447, 27.807698], + [97.049099, 27.81405], + [97.062649, 27.742568], + [97.097758, 27.740979], + [97.103301, 27.780697], + [97.167975, 27.811932], + [97.253591, 27.891832], + [97.303482, 27.913516], + [97.324424, 27.880723], + [97.386634, 27.882839], + [97.372467, 27.907699], + [97.379242, 27.970087], + [97.413119, 28.01342], + [97.378626, 28.031382], + [97.375547, 28.062545], + [97.320728, 28.054095], + [97.305945, 28.071522], + [97.340438, 28.104785], + [97.326887, 28.132759], + [97.352757, 28.149646], + [97.362612, 28.199236], + [97.349677, 28.235623], + [97.398336, 28.238786], + [97.402032, 28.279903], + [97.422358, 28.297293], + [97.461162, 28.26778], + [97.469169, 28.30309], + [97.518445, 28.327852], + [97.488879, 28.347341], + [97.485184, 28.38631], + [97.499966, 28.428948], + [97.521524, 28.444736], + [97.507974, 28.46473], + [97.521524, 28.495766], + [97.569567, 28.541515], + [97.60406, 28.515225], + [97.634857, 28.532051], + [97.68598, 28.519958], + [97.737103, 28.465782], + [97.738335, 28.396313], + [97.769748, 28.3742], + [97.801161, 28.326798], + [97.842429, 28.326798], + [97.871378, 28.361561], + [97.907718, 28.363141], + [98.020435, 28.253548], + [98.008116, 28.214003], + [98.03337, 28.187105], + [98.056775, 28.202401], + [98.090036, 28.195544], + [98.097427, 28.166531], + [98.139311, 28.142259], + [98.17442, 28.163365], + [98.169492, 28.206093], + [98.21692, 28.212949], + [98.266811, 28.242477], + [98.231702, 28.314681], + [98.207681, 28.330486], + [98.208913, 28.358401], + [98.301303, 28.384204], + [98.317934, 28.324691], + [98.353042, 28.293078], + [98.37768, 28.246167], + [98.370289, 28.18394], + [98.389999, 28.16442], + [98.389383, 28.114814], + [98.428803, 28.104785], + [98.464527, 28.151229], + [98.494092, 28.141203], + [98.559382, 28.182885], + [98.625903, 28.165475], + [98.649925, 28.200291], + [98.712135, 28.229296], + [98.710287, 28.288862], + [98.746628, 28.321003], + [98.740468, 28.348395], + [98.693041, 28.43158], + [98.673947, 28.478934], + [98.625903, 28.489455], + [98.619128, 28.50944], + [98.637606, 28.552029], + [98.594491, 28.667615], + [98.666555, 28.712239], + [98.683802, 28.740054], + [98.652389, 28.817162], + [98.668403, 28.843376], + [98.643766, 28.895261], + [98.6567, 28.910454], + [98.624056, 28.95864], + [98.655469, 28.976966], + [98.70228, 28.9644], + [98.757714, 29.004186], + [98.786048, 28.998952], + [98.821772, 28.920931], + [98.827932, 28.821356], + [98.852569, 28.798283], + [98.912931, 28.800906], + [98.922786, 28.823978], + [98.972677, 28.832367], + [98.973909, 28.864867], + [98.917859, 28.886877], + [98.925866, 28.978536], + [99.013329, 29.036632], + [98.991771, 29.105677], + [98.967134, 29.128159], + [98.960974, 29.165792], + [98.9813, 29.204978], + [99.024416, 29.188783], + [99.037351, 29.20759], + [99.113727, 29.221171], + [99.114343, 29.243628], + [99.075539, 29.316186], + [99.058909, 29.417368], + [99.066916, 29.421018], + [99.044742, 29.520013], + [99.052133, 29.563748], + [99.014561, 29.607464], + [98.992387, 29.677163], + [99.018873, 29.792009], + [99.0238, 29.846009], + [99.068148, 29.931621], + [99.055213, 29.958587], + [99.036735, 30.053945], + [99.044742, 30.079842], + [98.989308, 30.151799], + [98.9813, 30.182843], + [98.993003, 30.215429], + [98.970829, 30.260928], + [98.986844, 30.280569], + [98.967134, 30.33482], + [98.965286, 30.449937], + [98.932025, 30.521623], + [98.926482, 30.569556], + [98.939417, 30.598923], + [98.92217, 30.609225], + [98.907388, 30.698292], + [98.963438, 30.728134], + [98.957895, 30.765166], + [98.904924, 30.782649], + [98.850105, 30.849465], + [98.797135, 30.87926], + [98.774345, 30.908019], + [98.797135, 30.948575], + [98.806374, 30.995783], + [98.774961, 31.031174], + [98.736772, 31.049121], + [98.712135, 31.082954], + [98.710287, 31.1178], + [98.675179, 31.15417], + [98.602498, 31.192062], + [98.62344, 31.221238], + [98.60373, 31.257568], + [98.616048, 31.3036], + [98.643766, 31.338876], + [98.691809, 31.333253], + [98.773113, 31.249382], + [98.805758, 31.279052], + [98.810685, 31.306668], + [98.887062, 31.37465], + [98.84333, 31.416028], + [98.844562, 31.429817], + [98.714599, 31.508935], + [98.696736, 31.538523], + [98.651157, 31.57881], + [98.619128, 31.591555], + [98.553839, 31.660349], + [98.545831, 31.717383], + [98.516882, 31.717383], + [98.508875, 31.751995], + [98.461448, 31.800327], + [98.414636, 31.832365], + [98.426339, 31.856767], + [98.399238, 31.895899], + [98.432498, 31.922825], + [98.434962, 32.007613], + [98.402933, 32.026896], + [98.404781, 32.045159], + [98.357354, 32.087253], + [98.303151, 32.121726], + [98.260035, 32.208862], + [98.218768, 32.234683], + [98.23047, 32.262521], + [98.208913, 32.318171], + [98.218768, 32.342444], + [98.125145, 32.401077], + [98.107283, 32.391476], + [98.079565, 32.415224], + [97.940363, 32.482393], + [97.880001, 32.486431], + [97.863986, 32.499051], + [97.80732, 32.50006], + [97.795617, 32.521257], + [97.730944, 32.527312], + [97.684132, 32.530339], + [97.670582, 32.51722], + [97.540618, 32.536899], + [97.50243, 32.530844], + [97.463626, 32.55506], + [97.448843, 32.586833], + [97.411887, 32.575235], + [97.374315, 32.546484], + [97.3583, 32.563635], + [97.332431, 32.542448], + [97.334895, 32.514192], + [97.388481, 32.501575], + [97.341054, 32.440987], + [97.387865, 32.427349], + [97.424822, 32.322723], + [97.415583, 32.296421], + [97.371235, 32.273148], + [97.32196, 32.303503], + [97.299786, 32.294904], + [97.264062, 32.182527], + [97.271453, 32.139971], + [97.313953, 32.130342], + [97.293011, 32.096887], + [97.308409, 32.076605], + [97.258518, 32.072041], + [97.219714, 32.109054], + [97.201852, 32.090296], + [97.233881, 32.063927], + [97.214786, 32.042623], + [97.188301, 32.055304], + [97.169823, 32.032984], + [97.127323, 32.044145], + [97.028773, 32.04871], + [97.006599, 32.067984], + [96.935766, 32.048203], + [96.965947, 32.008628], + [96.941925, 31.986297], + [96.894498, 32.013703], + [96.863085, 31.996448], + [96.868629, 31.964975], + [96.824281, 32.007613], + [96.722651, 32.013195], + [96.742977, 32.001016], + [96.753448, 31.944156], + [96.776238, 31.935015], + [96.81073, 31.894375], + [96.794716, 31.869474], + [96.760223, 31.860325], + [96.765767, 31.819144], + [96.799027, 31.792188], + [96.840295, 31.720438], + [96.790404, 31.698545], + [96.778701, 31.675629], + [96.722651, 31.686833], + [96.691854, 31.722474], + [96.661057, 31.705674], + [96.615477, 31.737236], + [96.56805, 31.711783], + [96.519391, 31.74945], + [96.468884, 31.769804], + [96.435623, 31.796258], + [96.407906, 31.845583], + [96.389428, 31.919777], + [96.288414, 31.919777], + [96.253305, 31.929936], + [96.220044, 31.905553], + [96.188632, 31.904028], + [96.214501, 31.876589], + [96.202798, 31.841008], + [96.183088, 31.835924], + [96.178161, 31.775401], + [96.231131, 31.749959], + [96.222508, 31.733164], + [96.252073, 31.697527], + [96.245298, 31.657802], + [96.221892, 31.647613], + [96.207726, 31.598691], + [96.156603, 31.602769], + [96.148595, 31.686324], + [96.135661, 31.70211], + [96.064828, 31.720438], + [95.989067, 31.78761], + [95.983524, 31.816601], + [95.89914, 31.81711], + [95.846169, 31.736218], + [95.853561, 31.714329], + [95.823995, 31.68225], + [95.779648, 31.748941], + [95.634286, 31.782523], + [95.580083, 31.76726], + [95.546823, 31.73978], + [95.511714, 31.750468], + [95.480301, 31.795749], + [95.456896, 31.801853], + [95.406389, 31.896915], + [95.408852, 31.918761], + [95.3682, 31.92892], + [95.360809, 31.95939], + [95.395918, 32.001523], + [95.454432, 32.007613], + [95.421171, 32.033999], + [95.454432, 32.061898], + [95.440265, 32.157705], + [95.406389, 32.182021], + [95.367584, 32.178982], + [95.366968, 32.151118], + [95.31523, 32.148585], + [95.270266, 32.194683], + [95.270266, 32.194683], + [95.239469, 32.287315], + [95.241317, 32.3207], + [95.214216, 32.321712], + [95.20744, 32.297433], + [95.10581, 32.258979], + [95.079325, 32.279726], + [95.096571, 32.322217], + [95.193274, 32.332331], + [95.261643, 32.348006], + [95.228382, 32.363678], + [95.218527, 32.397035], + [95.153853, 32.386423], + [95.081789, 32.384907], + [95.075013, 32.376315], + [95.075013, 32.376315], + [95.057151, 32.395014], + [94.988166, 32.422802], + [94.944434, 32.404109], + [94.912405, 32.41573], + [94.889616, 32.472295], + [94.852043, 32.463712], + [94.80708, 32.486431], + [94.78737, 32.522266], + [94.762116, 32.526303], + [94.737479, 32.587338], + [94.638312, 32.645307], + [94.614291, 32.673522], + [94.591501, 32.640772], + [94.522516, 32.595909], + [94.459074, 32.599439], + [94.463386, 32.572209], + [94.435052, 32.562626], + [94.395016, 32.594397], + [94.371611, 32.524789], + [94.350053, 32.533871], + [94.294002, 32.519743], + [94.292154, 32.502584], + [94.250886, 32.51722], + [94.196684, 32.51621], + [94.176974, 32.454117], + [94.137554, 32.433915], + [94.091974, 32.463207], + [94.049474, 32.469771], + [94.03038, 32.448057], + [93.978641, 32.459672], + [93.960163, 32.484917], + [93.90904, 32.463207], + [93.861613, 32.466237], + [93.851142, 32.50965], + [93.820345, 32.549511], + [93.75136, 32.56313], + [93.721795, 32.578261], + [93.651577, 32.571705], + [93.618933, 32.522771], + [93.516687, 32.47583], + [93.501904, 32.503593], + [93.476651, 32.504603], + [93.4631, 32.556069], + [93.411977, 32.558086], + [93.385492, 32.525294], + [93.33868, 32.5712], + [93.308499, 32.580278], + [93.300492, 32.619604], + [93.260456, 32.62666], + [93.239514, 32.662439], + [93.210565, 32.655385], + [93.176688, 32.6705], + [93.159442, 32.644803], + [93.087993, 32.63674], + [93.069515, 32.626156], + [93.023935, 32.703239], + [93.019624, 32.737477], + [93.00053, 32.741001], + [92.964189, 32.714821], + [92.933392, 32.719353], + [92.866871, 32.698203], + [92.822523, 32.729926], + [92.789262, 32.719856], + [92.756618, 32.743014], + [92.686401, 32.76516], + [92.667922, 32.73194], + [92.634662, 32.720863], + [92.574916, 32.741001], + [92.56814, 32.73194], + [92.484372, 32.745028], + [92.459119, 32.76365], + [92.411076, 32.748048], + [92.355641, 32.764657], + [92.343938, 32.738484], + [92.310062, 32.751571], + [92.255243, 32.720863], + [92.198577, 32.754591], + [92.211511, 32.788306], + [92.193649, 32.801889], + [92.227526, 32.821003], + [92.205352, 32.866255], + [92.145606, 32.885857], + [92.101874, 32.860222], + [92.038432, 32.860725], + [92.018722, 32.829552], + [91.955897, 32.8205], + [91.896766, 32.907967], + [91.857962, 32.90244], + [91.839484, 32.948152], + [91.799448, 32.942126], + [91.752637, 32.969242], + [91.685499, 32.989324], + [91.664557, 33.012913], + [91.583253, 33.0375], + [91.55492, 33.060074], + [91.535826, 33.10019], + [91.49579, 33.109214], + [91.436044, 33.066092], + [91.370138, 33.100691], + [91.311624, 33.108211], + [91.261733, 33.141291], + [91.226624, 33.141792], + [91.18782, 33.106206], + [91.161335, 33.108712], + [91.147784, 33.07211], + [91.072024, 33.113224], + [91.037531, 33.098686], + [91.001807, 33.11573], + [90.927894, 33.120241], + [90.902024, 33.083143], + [90.88293, 33.120241], + [90.803474, 33.114227], + [90.740032, 33.142293], + [90.704308, 33.135778], + [90.627315, 33.180368], + [90.562642, 33.229441], + [90.490577, 33.264977], + [90.405577, 33.260473], + [90.363077, 33.279487], + [90.332896, 33.310501], + [90.246665, 33.423959], + [90.22018, 33.437943], + [90.107463, 33.460913], + [90.088984, 33.478885], + [90.083441, 33.525295], + [90.01076, 33.553728], + [89.984275, 33.612061], + [90.008296, 33.687785], + [89.981195, 33.70322], + [89.983659, 33.725622], + [89.907282, 33.741051], + [89.902355, 33.758467], + [89.942391, 33.801246], + [89.899891, 33.80771], + [89.837065, 33.868853], + [89.795181, 33.865374], + [89.73174, 33.921509], + [89.718805, 33.946832], + [89.688008, 33.959739], + [89.684928, 33.990013], + [89.635037, 34.049537], + [89.656595, 34.057966], + [89.655979, 34.097126], + [89.71203, 34.131809], + [89.756993, 34.124874], + [89.760073, 34.152613], + [89.789638, 34.150632], + [89.816739, 34.16945], + [89.838297, 34.263477], + [89.825362, 34.293642], + [89.86663, 34.324785], + [89.858623, 34.359375], + [89.820435, 34.369255], + [89.799493, 34.39642], + [89.819819, 34.420614], + [89.823515, 34.455657], + [89.814891, 34.548871], + [89.777935, 34.574499], + [89.798877, 34.628686], + [89.74837, 34.641981], + [89.72558, 34.660689], + [89.732356, 34.732035], + [89.799493, 34.743838], + [89.825978, 34.796931], + [89.867862, 34.81069], + [89.838913, 34.865705], + [89.814891, 34.86816], + [89.821051, 34.902033], + [89.78779, 34.921664], + [89.747138, 34.903506], + [89.707102, 34.919701], + [89.670146, 34.887798], + [89.578987, 34.895162], + [89.560509, 34.938836], + [89.59069, 35.057965], + [89.593153, 35.104491], + [89.579603, 35.118688], + [89.519241, 35.133862], + [89.46935, 35.214577], + [89.450255, 35.223867], + [89.48598, 35.256616], + [89.531559, 35.276161], + [89.494603, 35.298632], + [89.516161, 35.330862], + [89.497067, 35.361128], + [89.58761, 35.383575], + [89.619639, 35.412357], + [89.658443, 35.425526], + [89.685544, 35.416259], + [89.739131, 35.468429], + [89.765, 35.482563], + [89.740979, 35.507412], + [89.720037, 35.501566], + [89.699711, 35.544916], + [89.71203, 35.581915], + [89.75145, 35.580942], + [89.765616, 35.599922], + [89.726196, 35.648082], + [89.748986, 35.66267], + [89.747138, 35.7516], + [89.782863, 35.773453], + [89.767464, 35.799183], + [89.801957, 35.848193], + [89.778551, 35.861775], + [89.707718, 35.849163], + [89.654747, 35.848193], + [89.62395, 35.859349], + [89.550654, 35.856924], + [89.554965, 35.873414], + [89.489676, 35.903475], + [89.428082, 35.917531], + [89.434857, 35.992136], + [89.404676, 36.016827], + [89.417611, 36.044897], + [89.474893, 36.022151], + [89.605472, 36.038123], + [89.688624, 36.091337], + [89.711414, 36.093272] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 610000, + "name": "Shaanxi", + "center": [108.948024, 34.263161], + "centroid": [108.887114, 35.263661], + "childrenNum": 10, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 26, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [110.379257, 34.600612], + [110.29549, 34.610956], + [110.269004, 34.629671], + [110.229584, 34.692679], + [110.243135, 34.725641], + [110.246831, 34.789068], + [110.230816, 34.880925], + [110.262229, 34.944233], + [110.320743, 35.00504], + [110.373714, 35.134351], + [110.364475, 35.197952], + [110.378642, 35.210666], + [110.374946, 35.251728], + [110.45009, 35.327933], + [110.477808, 35.413821], + [110.531394, 35.511309], + [110.567735, 35.539559], + [110.589293, 35.602355], + [110.609619, 35.632031], + [110.57759, 35.701559], + [110.571431, 35.800639], + [110.550489, 35.838005], + [110.549257, 35.877778], + [110.511684, 35.879718], + [110.516612, 35.918501], + [110.502445, 35.947575], + [110.516612, 35.971796], + [110.49259, 35.994073], + [110.491974, 36.034735], + [110.467953, 36.074893], + [110.447011, 36.164328], + [110.45625, 36.22663], + [110.474112, 36.248352], + [110.474112, 36.306729], + [110.459946, 36.327946], + [110.487047, 36.393972], + [110.489511, 36.430094], + [110.47288, 36.453203], + [110.503677, 36.488335], + [110.488895, 36.556628], + [110.496902, 36.582102], + [110.447627, 36.621018], + [110.426685, 36.657514], + [110.394656, 36.676716], + [110.402663, 36.697352], + [110.438388, 36.685835], + [110.447011, 36.737649], + [110.407591, 36.776007], + [110.423605, 36.818179], + [110.406975, 36.824886], + [110.424221, 36.855539], + [110.376178, 36.882351], + [110.408823, 36.892403], + [110.424221, 36.963685], + [110.381721, 37.002408], + [110.382953, 37.022001], + [110.426685, 37.008621], + [110.417446, 37.027257], + [110.460561, 37.044932], + [110.49567, 37.086956], + [110.535706, 37.115118], + [110.53509, 37.138021], + [110.590525, 37.187145], + [110.651503, 37.256722], + [110.661974, 37.281963], + [110.690307, 37.287201], + [110.678604, 37.317668], + [110.695234, 37.34955], + [110.641648, 37.360015], + [110.630561, 37.372858], + [110.644111, 37.435135], + [110.740198, 37.44939], + [110.759292, 37.474567], + [110.770995, 37.538184], + [110.795017, 37.558586], + [110.771611, 37.594634], + [110.763604, 37.639668], + [110.793169, 37.650567], + [110.775306, 37.680886], + [110.706321, 37.705511], + [110.716792, 37.728708], + [110.750669, 37.736281], + [110.735886, 37.77035], + [110.680452, 37.790216], + [110.59422, 37.922049], + [110.522771, 37.955088], + [110.528315, 37.990471], + [110.507989, 38.013107], + [110.501829, 38.097929], + [110.519692, 38.130889], + [110.509221, 38.192061], + [110.528315, 38.211814], + [110.565887, 38.215105], + [110.57759, 38.297345], + [110.601612, 38.308147], + [110.661358, 38.308617], + [110.701394, 38.353215], + [110.746973, 38.366355], + [110.77777, 38.440924], + [110.796864, 38.453579], + [110.840596, 38.439986], + [110.874473, 38.453579], + [110.870777, 38.510265], + [110.907733, 38.521035], + [110.920052, 38.581878], + [110.898494, 38.587024], + [110.880632, 38.626776], + [110.916357, 38.673981], + [110.915125, 38.704345], + [110.965016, 38.755699], + [111.009363, 38.847579], + [110.995813, 38.868084], + [111.016755, 38.889981], + [111.009979, 38.932823], + [110.980414, 38.970056], + [110.998276, 38.998433], + [111.038313, 39.020289], + [111.094363, 39.030053], + [111.138095, 39.064447], + [111.147334, 39.100681], + [111.173819, 39.135041], + [111.163348, 39.152678], + [111.219399, 39.244044], + [111.213239, 39.257021], + [111.247732, 39.302419], + [111.202152, 39.305197], + [111.179363, 39.326959], + [111.186138, 39.35149], + [111.155341, 39.338531], + [111.159037, 39.362596], + [111.125776, 39.366297], + [111.087588, 39.376013], + [111.098059, 39.401914], + [111.064182, 39.400989], + [111.058639, 39.447681], + [111.10545, 39.472631], + [111.10545, 39.497573], + [111.148566, 39.531277], + [111.154725, 39.569116], + [111.136863, 39.587106], + [111.101138, 39.559428], + [111.017371, 39.552045], + [110.958856, 39.519275], + [110.891103, 39.509118], + [110.869545, 39.494341], + [110.782698, 39.38804], + [110.73835, 39.348713], + [110.731575, 39.30705], + [110.702626, 39.273701], + [110.626249, 39.266751], + [110.596684, 39.282966], + [110.566503, 39.320014], + [110.559728, 39.351027], + [110.524003, 39.382952], + [110.482735, 39.360745], + [110.434692, 39.381101], + [110.429764, 39.341308], + [110.385417, 39.310291], + [110.257917, 39.407001], + [110.243751, 39.423645], + [110.152592, 39.45415], + [110.12549, 39.432891], + [110.136577, 39.39174], + [110.161831, 39.387115], + [110.184005, 39.355192], + [110.217881, 39.281113], + [110.109476, 39.249606], + [110.041107, 39.21623], + [109.962267, 39.212056], + [109.90252, 39.271848], + [109.871723, 39.243581], + [109.961035, 39.191651], + [109.893897, 39.141075], + [109.92223, 39.107183], + [109.890818, 39.103932], + [109.851397, 39.122971], + [109.793499, 39.074204], + [109.762086, 39.057476], + [109.72513, 39.018429], + [109.665384, 38.981687], + [109.685094, 38.968195], + [109.672159, 38.928167], + [109.624116, 38.85457], + [109.549587, 38.805618], + [109.511399, 38.833595], + [109.444262, 38.782763], + [109.404226, 38.720689], + [109.338936, 38.701542], + [109.329081, 38.66043], + [109.367269, 38.627711], + [109.331545, 38.597783], + [109.276726, 38.623035], + [109.196654, 38.552867], + [109.175712, 38.518694], + [109.128901, 38.480288], + [109.054372, 38.433892], + [109.051292, 38.385122], + [109.007561, 38.359316], + [108.961981, 38.26493], + [108.976148, 38.245192], + [108.938575, 38.207582], + [108.964445, 38.154894], + [109.069155, 38.091336], + [109.050676, 38.055059], + [109.06977, 38.023008], + [109.037742, 38.021593], + [109.018648, 37.971602], + [108.982923, 37.964053], + [108.9743, 37.931962], + [108.93488, 37.922521], + [108.893612, 37.978207], + [108.883141, 38.01405], + [108.830786, 38.049875], + [108.797525, 38.04799], + [108.82709, 37.989056], + [108.798141, 37.93385], + [108.791982, 37.872934], + [108.799989, 37.784068], + [108.784591, 37.764673], + [108.791982, 37.700303], + [108.777815, 37.683728], + [108.720533, 37.683728], + [108.699591, 37.669518], + [108.628142, 37.651988], + [108.532671, 37.690832], + [108.485244, 37.678044], + [108.422418, 37.648672], + [108.301078, 37.640616], + [108.293071, 37.656726], + [108.24626, 37.665728], + [108.205608, 37.655779], + [108.193905, 37.638246], + [108.134159, 37.622131], + [108.055318, 37.652462], + [108.025137, 37.649619], + [108.012819, 37.66857], + [108.025753, 37.696041], + [107.993109, 37.735335], + [107.982022, 37.787378], + [107.884703, 37.808186], + [107.842819, 37.828987], + [107.732566, 37.84931], + [107.684523, 37.888522], + [107.65003, 37.86443], + [107.659269, 37.844112], + [107.646335, 37.805349], + [107.620465, 37.776026], + [107.599523, 37.791162], + [107.57119, 37.776499], + [107.499125, 37.765619], + [107.484959, 37.706458], + [107.425828, 37.684201], + [107.387024, 37.691305], + [107.389488, 37.671413], + [107.422133, 37.665254], + [107.361155, 37.613125], + [107.311264, 37.609806], + [107.330358, 37.584201], + [107.369162, 37.58752], + [107.345756, 37.518725], + [107.284162, 37.481691], + [107.282931, 37.437036], + [107.257677, 37.337179], + [107.273075, 37.29101], + [107.309416, 37.239095], + [107.270612, 37.229089], + [107.317423, 37.200017], + [107.336517, 37.165687], + [107.334669, 37.138975], + [107.306952, 37.100799], + [107.281083, 37.127047], + [107.268764, 37.099367], + [107.28601, 37.054963], + [107.288474, 37.008143], + [107.288474, 37.008143], + [107.291554, 36.979463], + [107.291554, 36.979463], + [107.310032, 36.912502], + [107.336517, 36.925899], + [107.365466, 36.905324], + [107.478183, 36.908196], + [107.533618, 36.867031], + [107.540393, 36.828718], + [107.5909, 36.836382], + [107.642023, 36.819137], + [107.670356, 36.83303], + [107.722095, 36.802367], + [107.742421, 36.811951], + [107.768291, 36.792783], + [107.866841, 36.766899], + [107.907493, 36.750118], + [107.914268, 36.720861], + [107.940754, 36.694953], + [107.938906, 36.655594], + [108.006659, 36.683435], + [108.02329, 36.647912], + [108.001732, 36.639269], + [108.060862, 36.592194], + [108.079956, 36.614294], + [108.092891, 36.587388], + [108.163724, 36.563839], + [108.1976, 36.630144], + [108.222854, 36.631105], + [108.204992, 36.606607], + [108.204992, 36.606607], + [108.210535, 36.577296], + [108.245644, 36.571048], + [108.262274, 36.549417], + [108.340498, 36.559032], + [108.365136, 36.519603], + [108.391621, 36.505654], + [108.408252, 36.45946], + [108.460606, 36.422871], + [108.495099, 36.422389], + [108.514809, 36.445501], + [108.510498, 36.47438], + [108.562852, 36.43876], + [108.618903, 36.433946], + [108.651548, 36.384818], + [108.641693, 36.359279], + [108.646004, 36.254143], + [108.712526, 36.138716], + [108.682345, 36.062316], + [108.688504, 36.021183], + [108.659555, 35.990683], + [108.652164, 35.94806], + [108.593649, 35.950967], + [108.562852, 35.921409], + [108.518505, 35.905414], + [108.499411, 35.872444], + [108.527744, 35.82442], + [108.533903, 35.746257], + [108.517889, 35.699615], + [108.539447, 35.605761], + [108.618287, 35.557088], + [108.625678, 35.537124], + [108.605968, 35.503028], + [108.631222, 35.418698], + [108.61028, 35.355271], + [108.614591, 35.328909], + [108.583178, 35.294724], + [108.547454, 35.304981], + [108.48894, 35.275184], + [108.36144, 35.279581], + [108.345426, 35.300586], + [108.296767, 35.267855], + [108.239484, 35.256127], + [108.221622, 35.296678], + [108.174811, 35.304981], + [108.094739, 35.280069], + [108.049159, 35.253683], + [107.949993, 35.245375], + [107.960464, 35.263457], + [107.867457, 35.256127], + [107.841587, 35.276649], + [107.745501, 35.311819], + [107.737494, 35.267366], + [107.667277, 35.257104], + [107.652494, 35.244886], + [107.686371, 35.218], + [107.715936, 35.168114], + [107.727639, 35.120157], + [107.769523, 35.064333], + [107.769523, 35.064333], + [107.773218, 35.060904], + [107.773218, 35.060904], + [107.814486, 35.024646], + [107.846515, 35.024646], + [107.863145, 34.999158], + [107.842203, 34.979056], + [107.741805, 34.953553], + [107.675284, 34.9511], + [107.638943, 34.935402], + [107.619849, 34.964834], + [107.564415, 34.968757], + [107.523763, 34.909886], + [107.455394, 34.916757], + [107.400575, 34.932949], + [107.369162, 34.917738], + [107.350068, 34.93393], + [107.286626, 34.931968], + [107.252749, 34.880925], + [107.189308, 34.893198], + [107.162206, 34.944233], + [107.119707, 34.950119], + [107.089526, 34.976604], + [107.08275, 35.024156], + [107.012533, 35.029547], + [106.990975, 35.068252], + [106.950323, 35.066782], + [106.901664, 35.094698], + [106.838222, 35.080007], + [106.710723, 35.100574], + [106.706411, 35.081966], + [106.615252, 35.071191], + [106.577064, 35.089312], + [106.541956, 35.083925], + [106.52163, 35.027587], + [106.494528, 35.006021], + [106.494528, 35.006021], + [106.484673, 34.983959], + [106.493296, 34.941289], + [106.527789, 34.876507], + [106.556122, 34.861285], + [106.550579, 34.82936], + [106.575216, 34.769897], + [106.539492, 34.745805], + [106.505615, 34.746789], + [106.487137, 34.715311], + [106.456956, 34.703996], + [106.442173, 34.675455], + [106.471122, 34.634102], + [106.419384, 34.643458], + [106.314058, 34.578934], + [106.341159, 34.568093], + [106.334384, 34.517811], + [106.455108, 34.531617], + [106.514238, 34.511894], + [106.513622, 34.498085], + [106.558586, 34.48822], + [106.610941, 34.454177], + [106.638042, 34.391481], + [106.717498, 34.369255], + [106.691013, 34.337635], + [106.705179, 34.299575], + [106.68239, 34.256057], + [106.652825, 34.24369], + [106.63373, 34.260014], + [106.589383, 34.253584], + [106.577064, 34.280786], + [106.526557, 34.292159], + [106.496376, 34.238248], + [106.5321, 34.254079], + [106.55797, 34.229837], + [106.585071, 34.149641], + [106.560434, 34.109514], + [106.501919, 34.105055], + [106.505615, 34.056479], + [106.471738, 34.024244], + [106.474202, 33.970659], + [106.41076, 33.909093], + [106.428007, 33.866368], + [106.475434, 33.875809], + [106.491448, 33.834559], + [106.461883, 33.789807], + [106.488369, 33.757969], + [106.482825, 33.707203], + [106.534564, 33.695254], + [106.575832, 33.631497], + [106.58076, 33.576169], + [106.540108, 33.512822], + [106.456956, 33.532779], + [106.447101, 33.613058], + [106.384891, 33.612061], + [106.35163, 33.587137], + [106.303587, 33.604585], + [106.237681, 33.564201], + [106.187174, 33.546746], + [106.108334, 33.569686], + [106.117573, 33.602591], + [106.086776, 33.617045], + [106.047356, 33.610067], + [105.971596, 33.613058], + [105.940183, 33.570684], + [105.902611, 33.556222], + [105.871198, 33.511325], + [105.842248, 33.489866], + [105.831162, 33.451926], + [105.837937, 33.410971], + [105.827466, 33.379993], + [105.709822, 33.382991], + [105.755401, 33.329004], + [105.752937, 33.291994], + [105.791741, 33.278486], + [105.799133, 33.258471], + [105.862574, 33.234447], + [105.917393, 33.237951], + [105.965436, 33.204407], + [105.968516, 33.154318], + [105.93156, 33.178365], + [105.897067, 33.146803], + [105.923552, 33.147805], + [105.934639, 33.112221], + [105.914929, 33.066092], + [105.926632, 33.042517], + [105.917393, 32.993841], + [105.861959, 32.939112], + [105.82685, 32.950663], + [105.735691, 32.905454], + [105.656851, 32.895405], + [105.638373, 32.879323], + [105.590329, 32.87681], + [105.565692, 32.906962], + [105.528119, 32.919019], + [105.49917, 32.911986], + [105.495475, 32.873292], + [105.524424, 32.847654], + [105.534279, 32.790822], + [105.555221, 32.794343], + [105.563844, 32.724891], + [105.585402, 32.728919], + [105.596489, 32.69921], + [105.677793, 32.726402], + [105.719061, 32.759624], + [105.768952, 32.767676], + [105.779423, 32.750061], + [105.822538, 32.770192], + [105.825002, 32.824523], + [105.849024, 32.817985], + [105.893371, 32.838603], + [105.93156, 32.826032], + [105.969132, 32.849162], + [106.011632, 32.829552], + [106.044277, 32.864747], + [106.071378, 32.828546], + [106.093552, 32.82402], + [106.07261, 32.76365], + [106.076921, 32.76365], + [106.076305, 32.759121], + [106.071378, 32.758114], + [106.120037, 32.719856], + [106.17424, 32.6977], + [106.254928, 32.693671], + [106.267863, 32.673522], + [106.301123, 32.680071], + [106.347935, 32.671003], + [106.389203, 32.62666], + [106.421231, 32.616579], + [106.451412, 32.65992], + [106.498224, 32.649338], + [106.517934, 32.668485], + [106.585687, 32.68813], + [106.626955, 32.682086], + [106.670071, 32.694678], + [106.733513, 32.739491], + [106.783404, 32.735967], + [106.793259, 32.712807], + [106.82344, 32.705254], + [106.854853, 32.724388], + [106.903512, 32.721367], + [106.912751, 32.704247], + [107.012533, 32.721367], + [107.066736, 32.708779], + [107.05996, 32.686115], + [107.098765, 32.649338], + [107.108004, 32.600951], + [107.080286, 32.542448], + [107.127098, 32.482393], + [107.189924, 32.468256], + [107.212097, 32.428864], + [107.263836, 32.403099], + [107.287858, 32.457147], + [107.313727, 32.489965], + [107.356843, 32.506622], + [107.382097, 32.54043], + [107.436299, 32.529835], + [107.438763, 32.465732], + [107.460937, 32.453612], + [107.456625, 32.41775], + [107.489886, 32.425328], + [107.527458, 32.38238], + [107.598291, 32.411688], + [107.648183, 32.413709], + [107.680827, 32.397035], + [107.707929, 32.331826], + [107.753508, 32.338399], + [107.812022, 32.247844], + [107.864377, 32.201266], + [107.890247, 32.214432], + [107.924739, 32.197215], + [107.979558, 32.146051], + [108.024521, 32.177462], + [108.018362, 32.2119], + [108.086731, 32.233165], + [108.143398, 32.219495], + [108.156948, 32.239239], + [108.179738, 32.221521], + [108.240716, 32.274666], + [108.310933, 32.232152], + [108.389773, 32.263533], + [108.414411, 32.252399], + [108.469846, 32.270618], + [108.507418, 32.245819], + [108.509882, 32.201266], + [108.543758, 32.177969], + [108.585026, 32.17189], + [108.676801, 32.10297], + [108.734084, 32.106519], + [108.75133, 32.076098], + [108.78767, 32.04871], + [108.837561, 32.039072], + [108.902235, 31.984774], + [108.986619, 31.980205], + [109.085785, 31.929428], + [109.123357, 31.892851], + [109.191111, 31.85575], + [109.195422, 31.817618], + [109.27611, 31.79931], + [109.279806, 31.776418], + [109.253936, 31.759628], + [109.282885, 31.743343], + [109.281654, 31.716874], + [109.381436, 31.705165], + [109.446109, 31.722983], + [109.502776, 31.716365], + [109.549587, 31.73011], + [109.585928, 31.726546], + [109.592087, 31.789136], + [109.633971, 31.804396], + [109.633971, 31.824738], + [109.60379, 31.885737], + [109.584696, 31.900472], + [109.62042, 31.928412], + [109.631507, 31.962436], + [109.590855, 32.012688], + [109.590855, 32.047696], + [109.621652, 32.106519], + [109.58716, 32.161251], + [109.604406, 32.199241], + [109.592703, 32.219495], + [109.550203, 32.225065], + [109.528645, 32.270112], + [109.495385, 32.300468], + [109.513247, 32.342444], + [109.502776, 32.38895], + [109.529877, 32.405625], + [109.526797, 32.43341], + [109.575457, 32.506622], + [109.637051, 32.540935], + [109.619804, 32.56767], + [109.631507, 32.599943], + [109.726978, 32.608513], + [109.746072, 32.594901], + [109.816905, 32.577252], + [109.910528, 32.592884], + [109.97089, 32.577756], + [110.017701, 32.546989], + [110.084223, 32.580782], + [110.090382, 32.617083], + [110.124259, 32.616579], + [110.153824, 32.593388], + [110.206179, 32.633212], + [110.156903, 32.683093], + [110.159367, 32.767173], + [110.127338, 32.77774], + [110.142121, 32.802895], + [110.105164, 32.832569], + [110.051578, 32.851676], + [109.988752, 32.886359], + [109.927158, 32.887364], + [109.907448, 32.903947], + [109.856941, 32.910479], + [109.847702, 32.893395], + [109.789804, 32.882339], + [109.76455, 32.909474], + [109.785492, 32.987316], + [109.794731, 33.067095], + [109.704188, 33.101694], + [109.688174, 33.116733], + [109.576073, 33.110216], + [109.522486, 33.138785], + [109.468283, 33.140288], + [109.438718, 33.152314], + [109.498464, 33.207412], + [109.514479, 33.237951], + [109.60687, 33.235949], + [109.619804, 33.275484], + [109.649985, 33.251465], + [109.693101, 33.254468], + [109.732521, 33.231443], + [109.813209, 33.236449], + [109.852013, 33.247961], + [109.916687, 33.229942], + [109.973353, 33.203907], + [109.999223, 33.212419], + [110.031252, 33.191888], + [110.164911, 33.209415], + [110.218497, 33.163336], + [110.285635, 33.171352], + [110.33799, 33.160331], + [110.372482, 33.186379], + [110.398352, 33.176862], + [110.398352, 33.176862], + [110.471032, 33.171352], + [110.54125, 33.255469], + [110.57759, 33.250464], + [110.59422, 33.168346], + [110.623785, 33.143796], + [110.650887, 33.157324], + [110.702626, 33.097182], + [110.753133, 33.15031], + [110.824582, 33.158327], + [110.828893, 33.201403], + [110.865234, 33.213921], + [110.9219, 33.203907], + [110.960704, 33.253967], + [110.984726, 33.255469], + [111.025994, 33.330504], + [111.025994, 33.375495], + [110.996429, 33.435946], + [111.02661, 33.467903], + [111.021066, 33.471397], + [111.021682, 33.476389], + [111.02661, 33.478386], + [111.002588, 33.535772], + [111.00382, 33.578662], + [110.966864, 33.609071], + [110.878784, 33.634486], + [110.823966, 33.685793], + [110.831973, 33.713675], + [110.81719, 33.751003], + [110.782082, 33.796272], + [110.74143, 33.798759], + [110.712481, 33.833564], + [110.66259, 33.85295], + [110.612083, 33.852453], + [110.587445, 33.887733], + [110.628713, 33.910086], + [110.627481, 33.925482], + [110.665669, 33.937895], + [110.671213, 33.966192], + [110.620706, 34.035652], + [110.587445, 34.023252], + [110.591757, 34.101586], + [110.61393, 34.113478], + [110.642264, 34.161032], + [110.621938, 34.177372], + [110.55788, 34.193214], + [110.55172, 34.213012], + [110.507989, 34.217466], + [110.43962, 34.243196], + [110.428533, 34.288203], + [110.451938, 34.292653], + [110.503677, 34.33714], + [110.473496, 34.393457], + [110.403279, 34.433448], + [110.403279, 34.433448], + [110.360779, 34.516825], + [110.372482, 34.544435], + [110.404511, 34.557743], + [110.366939, 34.566614], + [110.379257, 34.600612] + ] + ], + [ + [ + [111.02661, 33.478386], + [111.021682, 33.476389], + [111.021066, 33.471397], + [111.02661, 33.467903], + [111.02661, 33.478386] + ] + ], + [ + [ + [106.076921, 32.76365], + [106.07261, 32.76365], + [106.071378, 32.758114], + [106.076305, 32.759121], + [106.076921, 32.76365] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 620000, + "name": "Gansu", + "center": [103.823557, 36.058039], + "childrenNum": 14, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 27, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [106.506231, 35.737514], + [106.504383, 35.736057], + [106.498224, 35.732656], + [106.49268, 35.732656], + [106.434782, 35.688436], + [106.460036, 35.643705], + [106.47913, 35.575101], + [106.460036, 35.578995], + [106.440941, 35.52641], + [106.465579, 35.481101], + [106.490217, 35.480613], + [106.483441, 35.450393], + [106.503767, 35.415284], + [106.501304, 35.364056], + [106.472354, 35.310842], + [106.415688, 35.276161], + [106.368261, 35.273718], + [106.363333, 35.238532], + [106.319601, 35.265411], + [106.241377, 35.358687], + [106.237681, 35.409431], + [106.196414, 35.409919], + [106.173008, 35.437716], + [106.129892, 35.393333], + [106.113262, 35.361616], + [106.083081, 35.421624], + [106.073226, 35.420649], + [106.067682, 35.436254], + [106.073226, 35.447468], + [106.071378, 35.449418], + [106.06953, 35.458193], + [106.071994, 35.463555], + [106.054132, 35.45478], + [106.034422, 35.469404], + [106.002393, 35.438692], + [105.894603, 35.413821], + [105.897683, 35.451368], + [106.048588, 35.488898], + [106.047356, 35.498155], + [106.023335, 35.49377], + [106.017175, 35.519103], + [105.900147, 35.54735], + [105.868734, 35.540046], + [105.847176, 35.490359], + [105.816379, 35.575101], + [105.800365, 35.564878], + [105.762176, 35.602841], + [105.759097, 35.634464], + [105.713517, 35.650513], + [105.722756, 35.673366], + [105.690727, 35.698643], + [105.723988, 35.725854], + [105.740618, 35.698643], + [105.759097, 35.724883], + [105.70243, 35.733142], + [105.667322, 35.749657], + [105.595873, 35.715651], + [105.481924, 35.727312], + [105.457286, 35.771511], + [105.432033, 35.787533], + [105.428953, 35.819082], + [105.408627, 35.822479], + [105.38091, 35.792873], + [105.371055, 35.844312], + [105.39754, 35.857409], + [105.350113, 35.875839], + [105.324859, 35.941761], + [105.343954, 36.033767], + [105.406163, 36.074409], + [105.430801, 36.10391], + [105.491163, 36.101009], + [105.515185, 36.147415], + [105.478844, 36.213111], + [105.460366, 36.223733], + [105.45975, 36.268137], + [105.476381, 36.293224], + [105.455439, 36.321678], + [105.425873, 36.330357], + [105.401236, 36.369881], + [105.398156, 36.430575], + [105.363048, 36.443093], + [105.362432, 36.496514], + [105.322396, 36.535954], + [105.281744, 36.522489], + [105.252179, 36.553263], + [105.2762, 36.563358], + [105.261418, 36.602764], + [105.22015, 36.631105], + [105.225693, 36.664716], + [105.201056, 36.700711], + [105.218302, 36.730455], + [105.272505, 36.739567], + [105.275584, 36.752515], + [105.319932, 36.742924], + [105.340874, 36.764502], + [105.334714, 36.80093], + [105.303302, 36.820575], + [105.279896, 36.86751], + [105.244787, 36.894796], + [105.178882, 36.892403], + [105.185657, 36.942164], + [105.165331, 36.99476], + [105.128991, 36.996194], + [105.05939, 37.022956], + [105.03968, 37.007187], + [105.004571, 37.035378], + [104.95468, 37.040156], + [104.954064, 37.077407], + [104.914644, 37.097935], + [104.888158, 37.15901], + [104.864753, 37.17284], + [104.85613, 37.211933], + [104.776673, 37.246718], + [104.717543, 37.208597], + [104.638087, 37.201923], + [104.600515, 37.242907], + [104.624536, 37.298627], + [104.651022, 37.290534], + [104.673812, 37.317668], + [104.713848, 37.329566], + [104.662109, 37.367626], + [104.679971, 37.408044], + [104.521059, 37.43466], + [104.499501, 37.421353], + [104.448994, 37.42468], + [104.437907, 37.445589], + [104.365226, 37.418026], + [104.298705, 37.414223], + [104.287002, 37.428007], + [104.237727, 37.411847], + [104.183524, 37.406618], + [104.089285, 37.465067], + [103.935916, 37.572818], + [103.874938, 37.604117], + [103.841062, 37.64725], + [103.683381, 37.777919], + [103.627947, 37.797783], + [103.40744, 37.860651], + [103.362477, 38.037621], + [103.368636, 38.08898], + [103.53494, 38.156776], + [103.507838, 38.280905], + [103.465339, 38.353215], + [103.416063, 38.404821], + [103.85954, 38.64454], + [104.011677, 38.85923], + [104.044322, 38.895105], + [104.173053, 38.94446], + [104.196459, 38.9882], + [104.190915, 39.042139], + [104.207546, 39.083495], + [104.171205, 39.160567], + [104.047401, 39.297788], + [104.073271, 39.351953], + [104.089901, 39.419947], + [103.955626, 39.456923], + [103.85338, 39.461543], + [103.728961, 39.430117], + [103.595302, 39.386652], + [103.428998, 39.353341], + [103.344615, 39.331588], + [103.259615, 39.263971], + [103.188166, 39.215302], + [103.133347, 39.192579], + [103.007696, 39.099753], + [102.883892, 39.120649], + [102.616574, 39.171703], + [102.579002, 39.183301], + [102.45335, 39.255167], + [102.3548, 39.231993], + [102.276576, 39.188868], + [102.050526, 39.141075], + [102.012338, 39.127149], + [101.902701, 39.111827], + [101.833715, 39.08907], + [101.926106, 39.000758], + [101.955055, 38.985874], + [102.045599, 38.904885], + [102.075164, 38.891378], + [101.941505, 38.808883], + [101.873751, 38.733761], + [101.777049, 38.66043], + [101.672955, 38.6908], + [101.601506, 38.65529], + [101.562702, 38.713218], + [101.412413, 38.764099], + [101.331109, 38.777164], + [101.307087, 38.80282], + [101.34158, 38.822406], + [101.33542, 38.847113], + [101.24303, 38.860628], + [101.237486, 38.907214], + [101.198682, 38.943064], + [101.228863, 39.020754], + [101.117378, 38.975174], + [100.969553, 38.946788], + [100.961545, 39.005874], + [100.901799, 39.030053], + [100.875314, 39.002619], + [100.835278, 39.025869], + [100.829118, 39.075133], + [100.864227, 39.106719], + [100.842669, 39.199999], + [100.842053, 39.405614], + [100.707778, 39.404689], + [100.606764, 39.387577], + [100.498975, 39.400527], + [100.500823, 39.481408], + [100.44354, 39.485565], + [100.326512, 39.509118], + [100.301258, 39.572345], + [100.314193, 39.606935], + [100.250135, 39.685274], + [100.128179, 39.702312], + [100.040716, 39.757083], + [99.958796, 39.769504], + [99.904593, 39.785601], + [99.822058, 39.860063], + [99.672384, 39.888079], + [99.469124, 39.875221], + [99.440791, 39.885783], + [99.459885, 39.898181], + [99.491298, 39.884406], + [99.533182, 39.891753], + [99.714268, 39.972061], + [99.751225, 40.006909], + [99.841152, 40.013326], + [99.927383, 40.063727], + [99.955716, 40.150695], + [100.007455, 40.20008], + [100.169447, 40.277743], + [100.169447, 40.541131], + [100.242744, 40.618855], + [100.237201, 40.716905], + [100.224882, 40.727337], + [100.107853, 40.875475], + [100.057346, 40.908049], + [99.985897, 40.909858], + [99.673, 40.93292], + [99.565827, 40.846961], + [99.174705, 40.858278], + [99.172858, 40.747289], + [99.12543, 40.715091], + [99.102025, 40.676522], + [99.041662, 40.693767], + [98.984996, 40.782644], + [98.790975, 40.705564], + [98.80699, 40.660181], + [98.802678, 40.607043], + [98.762642, 40.639748], + [98.72199, 40.657911], + [98.689345, 40.691952], + [98.668403, 40.773128], + [98.569853, 40.746836], + [98.627751, 40.677884], + [98.344419, 40.568413], + [98.333332, 40.918903], + [98.25018, 40.93925], + [98.184891, 40.988056], + [98.142391, 41.001607], + [97.971776, 41.09774], + [97.903407, 41.168057], + [97.629314, 41.440498], + [97.613915, 41.477276], + [97.84674, 41.656379], + [97.653335, 41.986856], + [97.500582, 42.243894], + [97.371235, 42.457076], + [97.172903, 42.795257], + [96.968411, 42.756161], + [96.742361, 42.75704], + [96.386348, 42.727592], + [96.166458, 42.623314], + [96.103632, 42.604375], + [96.072219, 42.569566], + [96.02356, 42.542675], + [96.0174, 42.482239], + [95.978596, 42.436762], + [96.06606, 42.414674], + [96.042038, 42.352787], + [96.040806, 42.326688], + [96.178161, 42.21775], + [96.077147, 42.149457], + [96.13874, 42.05399], + [96.137509, 42.019765], + [96.117183, 41.985966], + [96.054973, 41.936124], + [95.998306, 41.906289], + [95.855408, 41.849699], + [95.801206, 41.848361], + [95.759322, 41.835878], + [95.65646, 41.826067], + [95.57146, 41.796181], + [95.445193, 41.719841], + [95.39407, 41.693481], + [95.335556, 41.644305], + [95.299831, 41.565994], + [95.247476, 41.61344], + [95.194505, 41.694821], + [95.199433, 41.719395], + [95.16494, 41.735474], + [95.135991, 41.772976], + [95.110738, 41.768513], + [95.011572, 41.726541], + [94.969072, 41.718948], + [94.861898, 41.668451], + [94.809543, 41.619256], + [94.750413, 41.538227], + [94.534219, 41.505966], + [94.184365, 41.268444], + [94.01067, 41.114875], + [93.908424, 40.983539], + [93.809874, 40.879548], + [93.820961, 40.793519], + [93.760599, 40.664721], + [93.506216, 40.648376], + [92.928465, 40.572504], + [92.920458, 40.391792], + [92.906907, 40.310609], + [92.796654, 40.153897], + [92.745531, 39.868331], + [92.687632, 39.657174], + [92.639589, 39.514196], + [92.52564, 39.368611], + [92.378431, 39.258411], + [92.339011, 39.236628], + [92.343938, 39.146181], + [92.366112, 39.096037], + [92.366728, 39.059335], + [92.41046, 39.03842], + [92.459119, 39.042604], + [92.459119, 39.063982], + [92.489916, 39.099753], + [92.545966, 39.111362], + [92.659299, 39.109969], + [92.765857, 39.136898], + [92.866871, 39.138754], + [92.889045, 39.160103], + [92.938936, 39.169848], + [92.978356, 39.143396], + [93.043029, 39.146645], + [93.115094, 39.17959], + [93.142196, 39.160567], + [93.131725, 39.108112], + [93.165601, 39.090928], + [93.198246, 39.045857], + [93.179152, 38.923977], + [93.237666, 38.916062], + [93.274007, 38.896036], + [93.453245, 38.915596], + [93.729186, 38.924443], + [93.834511, 38.867618], + [93.884403, 38.867618], + [93.884403, 38.826136], + [93.769838, 38.821007], + [93.756287, 38.807484], + [93.773533, 38.771099], + [93.800019, 38.750566], + [93.885018, 38.720689], + [93.95154, 38.715086], + [93.973098, 38.724891], + [94.281067, 38.7599], + [94.370379, 38.7627], + [94.511429, 38.445142], + [94.527443, 38.425922], + [94.527443, 38.365416], + [94.56132, 38.351807], + [94.582878, 38.36917], + [94.672805, 38.386998], + [94.812623, 38.385591], + [94.861282, 38.393565], + [94.884072, 38.414669], + [94.973999, 38.430142], + [95.045448, 38.418889], + [95.072549, 38.402476], + [95.122441, 38.417014], + [95.140919, 38.392158], + [95.185266, 38.379492], + [95.209904, 38.327868], + [95.229614, 38.330685], + [95.259179, 38.302981], + [95.315846, 38.318947], + [95.408236, 38.300163], + [95.440881, 38.310965], + [95.455664, 38.291709], + [95.487693, 38.314721], + [95.51849, 38.294997], + [95.585011, 38.343359], + [95.608417, 38.339134], + [95.671858, 38.388405], + [95.703887, 38.400131], + [95.723597, 38.378554], + [95.775952, 38.356031], + [95.83693, 38.344298], + [95.852945, 38.287481], + [95.89606, 38.2903], + [95.932401, 38.259291], + [95.93856, 38.237202], + [96.006929, 38.207582], + [96.06606, 38.173245], + [96.109175, 38.187358], + [96.221892, 38.149246], + [96.252689, 38.167599], + [96.264392, 38.145952], + [96.313051, 38.161952], + [96.301964, 38.183124], + [96.335841, 38.246132], + [96.378341, 38.277146], + [96.46334, 38.277616], + [96.665369, 38.23015], + [96.655514, 38.295936], + [96.638883, 38.307208], + [96.626564, 38.356031], + [96.698013, 38.422172], + [96.707868, 38.459203], + [96.6666, 38.483567], + [96.706637, 38.505582], + [96.780549, 38.504177], + [96.800259, 38.52759], + [96.767614, 38.552399], + [96.808882, 38.582346], + [96.7941, 38.608072], + [96.847071, 38.599186], + [96.876636, 38.580475], + [96.961019, 38.558015], + [97.055874, 38.594508], + [97.047251, 38.653888], + [97.057722, 38.67258], + [97.009063, 38.702477], + [97.023229, 38.755699], + [97.00044, 38.7613], + [96.987505, 38.793025], + [96.993664, 38.834993], + [96.983809, 38.869016], + [96.940693, 38.90768], + [96.938846, 38.95563], + [96.965331, 39.017034], + [96.95794, 39.041674], + [96.969643, 39.097895], + [97.012142, 39.142004], + [96.962251, 39.198144], + [97.017686, 39.208347], + [97.060186, 39.19768], + [97.14149, 39.199999], + [97.220946, 39.193042], + [97.315185, 39.164744], + [97.347213, 39.167528], + [97.371235, 39.140611], + [97.401416, 39.146645], + [97.458698, 39.117863], + [97.504894, 39.076527], + [97.58127, 39.052364], + [97.679205, 39.010524], + [97.701379, 38.963076], + [97.828878, 38.93003], + [97.875689, 38.898365], + [98.009348, 38.85923], + [98.029058, 38.834061], + [98.068478, 38.816344], + [98.091884, 38.786495], + [98.167645, 38.840121], + [98.242173, 38.880664], + [98.235398, 38.918855], + [98.276666, 38.963541], + [98.287753, 38.992386], + [98.280977, 39.027263], + [98.316702, 39.040744], + [98.383839, 39.029588], + [98.401086, 39.001688], + [98.432498, 38.996107], + [98.428187, 38.976104], + [98.457752, 38.952838], + [98.526737, 38.95563], + [98.584635, 38.93003], + [98.624056, 38.959353], + [98.612353, 38.977035], + [98.661628, 38.993782], + [98.70536, 39.043533], + [98.730613, 39.057011], + [98.743548, 39.086747], + [98.816845, 39.085818], + [98.818076, 39.064911], + [98.886446, 39.040744], + [98.903076, 39.012384], + [98.951735, 38.987735], + [99.054597, 38.97657], + [99.107568, 38.951907], + [99.071843, 38.921184], + [99.068764, 38.896968], + [99.141445, 38.852706], + [99.222133, 38.788827], + [99.291118, 38.765966], + [99.361951, 38.718354], + [99.375502, 38.684727], + [99.412458, 38.665571], + [99.450646, 38.60433], + [99.501769, 38.612281], + [99.52887, 38.546314], + [99.585537, 38.498556], + [99.63974, 38.474666], + [99.65945, 38.449361], + [99.727203, 38.415607], + [99.758, 38.410449], + [99.826985, 38.370109], + [99.960028, 38.320825], + [100.001912, 38.315191], + [100.049955, 38.283254], + [100.071513, 38.284663], + [100.117093, 38.253652], + [100.126332, 38.231561], + [100.182998, 38.222158], + [100.159592, 38.291239], + [100.163904, 38.328337], + [100.136803, 38.33444], + [100.093071, 38.407166], + [100.022238, 38.432017], + [100.001296, 38.467169], + [100.025933, 38.507923], + [100.064122, 38.518694], + [100.086911, 38.492936], + [100.113397, 38.497151], + [100.163288, 38.461546], + [100.24028, 38.441861], + [100.259374, 38.366355], + [100.301874, 38.388405], + [100.331439, 38.337257], + [100.318505, 38.329276], + [100.396729, 38.293118], + [100.424446, 38.307208], + [100.432453, 38.275267], + [100.459555, 38.2654], + [100.474953, 38.288891], + [100.516837, 38.272448], + [100.545786, 38.247072], + [100.595061, 38.242372], + [100.619083, 38.26587], + [100.71517, 38.253652], + [100.752126, 38.238612], + [100.825423, 38.158658], + [100.860531, 38.148305], + [100.913502, 38.17889], + [100.93814, 38.16007], + [100.91843, 38.129006], + [100.922125, 38.084741], + [100.888864, 38.056001], + [100.895024, 38.013107], + [100.91843, 37.999432], + [100.964009, 38.011221], + [101.077342, 37.941874], + [101.103211, 37.946593], + [101.114298, 37.92016], + [101.152486, 37.891356], + [101.159262, 37.86821], + [101.202994, 37.84742], + [101.276906, 37.83655], + [101.362522, 37.791162], + [101.382848, 37.822369], + [101.459224, 37.86632], + [101.551615, 37.835604], + [101.598427, 37.827569], + [101.670491, 37.754264], + [101.659405, 37.733441], + [101.791832, 37.696041], + [101.815853, 37.654357], + [101.854657, 37.664781], + [101.873135, 37.686569], + [101.946432, 37.728235], + [101.998787, 37.724921], + [102.036359, 37.685149], + [102.048678, 37.651515], + [102.035128, 37.627819], + [102.102265, 37.582304], + [102.131214, 37.54625], + [102.103497, 37.482641], + [102.125055, 37.48549], + [102.176794, 37.458892], + [102.19712, 37.420403], + [102.299981, 37.391404], + [102.29875, 37.370004], + [102.368351, 37.327662], + [102.428097, 37.308624], + [102.419474, 37.294343], + [102.45335, 37.271487], + [102.457662, 37.248147], + [102.490307, 37.223371], + [102.533422, 37.217176], + [102.578386, 37.17284], + [102.599944, 37.174748], + [102.642444, 37.099845], + [102.583314, 37.104618], + [102.488459, 37.078362], + [102.506321, 37.019134], + [102.450271, 36.968467], + [102.499546, 36.954599], + [102.526031, 36.928291], + [102.56114, 36.91968], + [102.587009, 36.869904], + [102.639364, 36.852666], + [102.720052, 36.767858], + [102.692335, 36.775528], + [102.639364, 36.732853], + [102.612879, 36.738129], + [102.601176, 36.710307], + [102.630741, 36.650793], + [102.684328, 36.619097], + [102.724364, 36.613813], + [102.714509, 36.599401], + [102.761936, 36.568645], + [102.734219, 36.562396], + [102.753313, 36.525855], + [102.793349, 36.497957], + [102.771791, 36.47438], + [102.829689, 36.365544], + [102.831537, 36.365544], + [102.838928, 36.345783], + [102.836465, 36.344819], + [102.845704, 36.331803], + [102.896827, 36.331803], + [102.922696, 36.298047], + [103.024942, 36.256556], + [103.021246, 36.232906], + [103.066826, 36.216974], + [103.048964, 36.199107], + [102.986754, 36.193312], + [102.965812, 36.151765], + [102.948566, 36.150798], + [102.941174, 36.104877], + [102.882044, 36.082632], + [102.932551, 36.048285], + [102.968276, 36.044414], + [102.951645, 36.021667], + [102.971971, 35.995525], + [102.942406, 35.92674], + [102.954725, 35.858864], + [102.94487, 35.829757], + [102.914073, 35.845282], + [102.81737, 35.850133], + [102.787189, 35.862745], + [102.739146, 35.821023], + [102.715125, 35.815685], + [102.686175, 35.771996], + [102.707733, 35.70496], + [102.744074, 35.657807], + [102.7644, 35.653431], + [102.763168, 35.612086], + [102.808747, 35.560496], + [102.746537, 35.545403], + [102.729291, 35.523487], + [102.782878, 35.527871], + [102.743458, 35.494745], + [102.695414, 35.528358], + [102.570995, 35.548324], + [102.531575, 35.580455], + [102.503241, 35.585322], + [102.49893, 35.545403], + [102.437952, 35.455268], + [102.447807, 35.437229], + [102.408387, 35.409431], + [102.314764, 35.434303], + [102.293822, 35.424063], + [102.287663, 35.36552], + [102.317844, 35.343067], + [102.311684, 35.31426], + [102.280887, 35.303028], + [102.3123, 35.282512], + [102.370199, 35.263946], + [102.365887, 35.235599], + [102.404075, 35.179366], + [102.346793, 35.164201], + [102.310452, 35.128967], + [102.29567, 35.071681], + [102.252554, 35.048657], + [102.218062, 35.057475], + [102.211286, 35.034937], + [102.176178, 35.032977], + [102.157699, 35.010923], + [102.133678, 35.014844], + [102.094874, 34.986901], + [102.048062, 34.910868], + [102.068388, 34.887798], + [101.985852, 34.90007], + [101.916867, 34.873561], + [101.923027, 34.835746], + [101.917483, 34.705964], + [101.919947, 34.621791], + [101.934729, 34.58731], + [101.956287, 34.582876], + [101.97415, 34.548871], + [102.001867, 34.538519], + [102.093026, 34.536547], + [102.139837, 34.50351], + [102.155852, 34.507456], + [102.169402, 34.457631], + [102.205743, 34.407777], + [102.259329, 34.355917], + [102.237156, 34.34307], + [102.237156, 34.34307], + [102.186649, 34.352952], + [102.149692, 34.271885], + [102.067772, 34.293642], + [102.062229, 34.227858], + [102.01357, 34.218456], + [102.030816, 34.190739], + [102.003099, 34.162022], + [101.965526, 34.167469], + [101.955055, 34.109514], + [101.897773, 34.133791], + [101.874367, 34.130323], + [101.851578, 34.153108], + [101.836795, 34.124378], + [101.788136, 34.131809], + [101.764114, 34.122892], + [101.736397, 34.080275], + [101.718535, 34.083249], + [101.703136, 34.119424], + [101.674187, 34.110506], + [101.6206, 34.178857], + [101.53868, 34.212022], + [101.492485, 34.195689], + [101.482014, 34.218951], + [101.417956, 34.227858], + [101.369913, 34.248143], + [101.327413, 34.24468], + [101.325565, 34.268423], + [101.268899, 34.278808], + [101.228863, 34.298586], + [101.235022, 34.325279], + [101.193754, 34.336646], + [101.178356, 34.320831], + [101.098284, 34.329233], + [101.054552, 34.322808], + [100.986799, 34.374689], + [100.951074, 34.38358], + [100.895024, 34.375183], + [100.868538, 34.332693], + [100.821727, 34.317371], + [100.798321, 34.260014], + [100.809408, 34.247153], + [100.764445, 34.178857], + [100.806329, 34.155584], + [100.848828, 34.089692], + [100.870386, 34.083744], + [100.880857, 34.036644], + [100.93506, 33.990013], + [100.927669, 33.975126], + [100.965857, 33.946832], + [100.994806, 33.891707], + [101.023139, 33.896178], + [101.054552, 33.863386], + [101.153718, 33.8445], + [101.153102, 33.823124], + [101.190675, 33.791796], + [101.186363, 33.741051], + [101.162957, 33.719649], + [101.177124, 33.685295], + [101.166653, 33.659894], + [101.217776, 33.669856], + [101.23687, 33.685793], + [101.302776, 33.657902], + [101.385312, 33.644949], + [101.424732, 33.655411], + [101.428427, 33.680315], + [101.501724, 33.702723], + [101.58426, 33.674339], + [101.585492, 33.645448], + [101.616905, 33.598603], + [101.611977, 33.565199], + [101.622448, 33.502343], + [101.718535, 33.494857], + [101.748716, 33.505337], + [101.769042, 33.538765], + [101.783208, 33.556721], + [101.831252, 33.554726], + [101.844186, 33.602591], + [101.884222, 33.578163], + [101.907012, 33.539264], + [101.906396, 33.48188], + [101.946432, 33.442937], + [101.915635, 33.425957], + [101.887302, 33.383991], + [101.877447, 33.314502], + [101.769658, 33.26898], + [101.770274, 33.248962], + [101.83002, 33.213921], + [101.841723, 33.184876], + [101.825708, 33.119239], + [101.865744, 33.103198], + [101.887302, 33.135778], + [101.921795, 33.153817], + [101.935345, 33.186879], + [101.99386, 33.1999], + [102.054838, 33.189884], + [102.08933, 33.204908], + [102.08933, 33.227439], + [102.117047, 33.288492], + [102.144765, 33.273983], + [102.160163, 33.242956], + [102.200815, 33.223434], + [102.217446, 33.247961], + [102.192192, 33.337005], + [102.218062, 33.349503], + [102.258098, 33.409472], + [102.296286, 33.413969], + [102.310452, 33.397982], + [102.368967, 33.41247], + [102.392988, 33.404477], + [102.447807, 33.454922], + [102.462589, 33.449429], + [102.461358, 33.501345], + [102.446575, 33.53228], + [102.477988, 33.543254], + [102.440416, 33.574673], + [102.346793, 33.605582], + [102.31538, 33.665374], + [102.342481, 33.725622], + [102.284583, 33.719151], + [102.324619, 33.754486], + [102.296286, 33.783838], + [102.243315, 33.786823], + [102.261177, 33.821136], + [102.25317, 33.861399], + [102.136142, 33.965199], + [102.16817, 33.983066], + [102.226069, 33.963214], + [102.248858, 33.98654], + [102.287047, 33.977607], + [102.315996, 33.993983], + [102.345561, 33.969666], + [102.392372, 33.971651], + [102.406539, 34.033172], + [102.437336, 34.087214], + [102.471213, 34.072839], + [102.511865, 34.086222], + [102.615958, 34.099604], + [102.649219, 34.080275], + [102.655994, 34.113478], + [102.598712, 34.14766], + [102.651067, 34.165983], + [102.664002, 34.192719], + [102.694799, 34.198659], + [102.728675, 34.235774], + [102.779798, 34.236764], + [102.798276, 34.272874], + [102.856791, 34.270895], + [102.85987, 34.301058], + [102.911609, 34.312923], + [102.949181, 34.292159], + [102.977515, 34.252595], + [102.973203, 34.205588], + [103.005848, 34.184798], + [103.052043, 34.195194], + [103.100087, 34.181828], + [103.124108, 34.162022], + [103.121644, 34.112487], + [103.178927, 34.079779], + [103.129652, 34.065899], + [103.119797, 34.03466], + [103.147514, 34.036644], + [103.157369, 33.998944], + [103.120413, 33.953286], + [103.1315, 33.931937], + [103.16476, 33.929454], + [103.181391, 33.900649], + [103.153673, 33.819147], + [103.165376, 33.805721], + [103.228202, 33.79478], + [103.24976, 33.814175], + [103.284868, 33.80224], + [103.278709, 33.774387], + [103.35447, 33.743539], + [103.434542, 33.752993], + [103.464723, 33.80224], + [103.518309, 33.807213], + [103.545411, 33.719649], + [103.520157, 33.678323], + [103.552186, 33.671351], + [103.563889, 33.699735], + [103.593454, 33.716164], + [103.645809, 33.708697], + [103.667983, 33.685793], + [103.690772, 33.69376], + [103.778236, 33.658898], + [103.861388, 33.682307], + [103.980264, 33.670852], + [104.046169, 33.686291], + [104.103452, 33.663381], + [104.176749, 33.5996], + [104.155191, 33.542755], + [104.180444, 33.472895], + [104.213089, 33.446932], + [104.22048, 33.404477], + [104.272219, 33.391486], + [104.292545, 33.336505], + [104.373849, 33.345004], + [104.420045, 33.327004], + [104.386168, 33.298497], + [104.333813, 33.315502], + [104.303632, 33.304499], + [104.323958, 33.26898], + [104.32827, 33.223934], + [104.351059, 33.158828], + [104.378161, 33.109214], + [104.337509, 33.038002], + [104.391711, 33.035493], + [104.426204, 33.010906], + [104.383704, 32.994343], + [104.378161, 32.953174], + [104.345516, 32.940117], + [104.288234, 32.942628], + [104.277147, 32.90244], + [104.294393, 32.835586], + [104.363994, 32.822511], + [104.458849, 32.748551], + [104.51182, 32.753585], + [104.526602, 32.728416], + [104.582653, 32.722374], + [104.592508, 32.695685], + [104.643015, 32.661935], + [104.696601, 32.673522], + [104.739717, 32.635228], + [104.795768, 32.643292], + [104.820405, 32.662943], + [104.845659, 32.653873], + [104.881999, 32.600951], + [104.925115, 32.607505], + [105.026745, 32.650346], + [105.0791, 32.637244], + [105.111128, 32.593893], + [105.185041, 32.617587], + [105.215222, 32.63674], + [105.219534, 32.666469], + [105.263265, 32.652362], + [105.297758, 32.656897], + [105.347033, 32.68259], + [105.368591, 32.712807], + [105.448663, 32.732946], + [105.454207, 32.767173], + [105.427721, 32.784281], + [105.396308, 32.85067], + [105.396308, 32.85067], + [105.38091, 32.876307], + [105.408011, 32.885857], + [105.414171, 32.922034], + [105.467757, 32.930071], + [105.49917, 32.911986], + [105.528119, 32.919019], + [105.565692, 32.906962], + [105.590329, 32.87681], + [105.638373, 32.879323], + [105.656851, 32.895405], + [105.735691, 32.905454], + [105.82685, 32.950663], + [105.861959, 32.939112], + [105.917393, 32.993841], + [105.926632, 33.042517], + [105.914929, 33.066092], + [105.934639, 33.112221], + [105.923552, 33.147805], + [105.897067, 33.146803], + [105.93156, 33.178365], + [105.968516, 33.154318], + [105.965436, 33.204407], + [105.917393, 33.237951], + [105.862574, 33.234447], + [105.799133, 33.258471], + [105.791741, 33.278486], + [105.752937, 33.291994], + [105.755401, 33.329004], + [105.709822, 33.382991], + [105.827466, 33.379993], + [105.837937, 33.410971], + [105.831162, 33.451926], + [105.842248, 33.489866], + [105.871198, 33.511325], + [105.902611, 33.556222], + [105.940183, 33.570684], + [105.971596, 33.613058], + [106.047356, 33.610067], + [106.086776, 33.617045], + [106.117573, 33.602591], + [106.108334, 33.569686], + [106.187174, 33.546746], + [106.237681, 33.564201], + [106.303587, 33.604585], + [106.35163, 33.587137], + [106.384891, 33.612061], + [106.447101, 33.613058], + [106.456956, 33.532779], + [106.540108, 33.512822], + [106.58076, 33.576169], + [106.575832, 33.631497], + [106.534564, 33.695254], + [106.482825, 33.707203], + [106.488369, 33.757969], + [106.461883, 33.789807], + [106.491448, 33.834559], + [106.475434, 33.875809], + [106.428007, 33.866368], + [106.41076, 33.909093], + [106.474202, 33.970659], + [106.471738, 34.024244], + [106.505615, 34.056479], + [106.501919, 34.105055], + [106.560434, 34.109514], + [106.585071, 34.149641], + [106.55797, 34.229837], + [106.5321, 34.254079], + [106.496376, 34.238248], + [106.526557, 34.292159], + [106.577064, 34.280786], + [106.589383, 34.253584], + [106.63373, 34.260014], + [106.652825, 34.24369], + [106.68239, 34.256057], + [106.705179, 34.299575], + [106.691013, 34.337635], + [106.717498, 34.369255], + [106.638042, 34.391481], + [106.610941, 34.454177], + [106.558586, 34.48822], + [106.513622, 34.498085], + [106.514238, 34.511894], + [106.455108, 34.531617], + [106.334384, 34.517811], + [106.341159, 34.568093], + [106.314058, 34.578934], + [106.419384, 34.643458], + [106.471122, 34.634102], + [106.442173, 34.675455], + [106.456956, 34.703996], + [106.487137, 34.715311], + [106.505615, 34.746789], + [106.539492, 34.745805], + [106.575216, 34.769897], + [106.550579, 34.82936], + [106.556122, 34.861285], + [106.527789, 34.876507], + [106.493296, 34.941289], + [106.484673, 34.983959], + [106.494528, 35.006021], + [106.494528, 35.006021], + [106.52163, 35.027587], + [106.541956, 35.083925], + [106.577064, 35.089312], + [106.615252, 35.071191], + [106.706411, 35.081966], + [106.710723, 35.100574], + [106.838222, 35.080007], + [106.901664, 35.094698], + [106.950323, 35.066782], + [106.990975, 35.068252], + [107.012533, 35.029547], + [107.08275, 35.024156], + [107.089526, 34.976604], + [107.119707, 34.950119], + [107.162206, 34.944233], + [107.189308, 34.893198], + [107.252749, 34.880925], + [107.286626, 34.931968], + [107.350068, 34.93393], + [107.369162, 34.917738], + [107.400575, 34.932949], + [107.455394, 34.916757], + [107.523763, 34.909886], + [107.564415, 34.968757], + [107.619849, 34.964834], + [107.638943, 34.935402], + [107.675284, 34.9511], + [107.741805, 34.953553], + [107.842203, 34.979056], + [107.863145, 34.999158], + [107.846515, 35.024646], + [107.814486, 35.024646], + [107.773218, 35.060904], + [107.773218, 35.060904], + [107.769523, 35.064333], + [107.769523, 35.064333], + [107.727639, 35.120157], + [107.715936, 35.168114], + [107.686371, 35.218], + [107.652494, 35.244886], + [107.667277, 35.257104], + [107.737494, 35.267366], + [107.745501, 35.311819], + [107.841587, 35.276649], + [107.867457, 35.256127], + [107.960464, 35.263457], + [107.949993, 35.245375], + [108.049159, 35.253683], + [108.094739, 35.280069], + [108.174811, 35.304981], + [108.221622, 35.296678], + [108.239484, 35.256127], + [108.296767, 35.267855], + [108.345426, 35.300586], + [108.36144, 35.279581], + [108.48894, 35.275184], + [108.547454, 35.304981], + [108.583178, 35.294724], + [108.614591, 35.328909], + [108.61028, 35.355271], + [108.631222, 35.418698], + [108.605968, 35.503028], + [108.625678, 35.537124], + [108.618287, 35.557088], + [108.539447, 35.605761], + [108.517889, 35.699615], + [108.533903, 35.746257], + [108.527744, 35.82442], + [108.499411, 35.872444], + [108.518505, 35.905414], + [108.562852, 35.921409], + [108.593649, 35.950967], + [108.652164, 35.94806], + [108.659555, 35.990683], + [108.688504, 36.021183], + [108.682345, 36.062316], + [108.712526, 36.138716], + [108.646004, 36.254143], + [108.641693, 36.359279], + [108.651548, 36.384818], + [108.618903, 36.433946], + [108.562852, 36.43876], + [108.510498, 36.47438], + [108.514809, 36.445501], + [108.495099, 36.422389], + [108.460606, 36.422871], + [108.408252, 36.45946], + [108.391621, 36.505654], + [108.365136, 36.519603], + [108.340498, 36.559032], + [108.262274, 36.549417], + [108.245644, 36.571048], + [108.210535, 36.577296], + [108.204992, 36.606607], + [108.204992, 36.606607], + [108.222854, 36.631105], + [108.1976, 36.630144], + [108.163724, 36.563839], + [108.092891, 36.587388], + [108.079956, 36.614294], + [108.060862, 36.592194], + [108.001732, 36.639269], + [108.02329, 36.647912], + [108.006659, 36.683435], + [107.938906, 36.655594], + [107.940754, 36.694953], + [107.914268, 36.720861], + [107.907493, 36.750118], + [107.866841, 36.766899], + [107.768291, 36.792783], + [107.742421, 36.811951], + [107.722095, 36.802367], + [107.670356, 36.83303], + [107.642023, 36.819137], + [107.5909, 36.836382], + [107.540393, 36.828718], + [107.533618, 36.867031], + [107.478183, 36.908196], + [107.365466, 36.905324], + [107.336517, 36.925899], + [107.310032, 36.912502], + [107.291554, 36.979463], + [107.291554, 36.979463], + [107.288474, 37.008143], + [107.288474, 37.008143], + [107.28601, 37.054963], + [107.268764, 37.099367], + [107.234887, 37.096503], + [107.181916, 37.143269], + [107.133873, 37.134681], + [107.095685, 37.115595], + [107.030395, 37.140883], + [107.031011, 37.108436], + [106.998367, 37.106527], + [106.905976, 37.151378], + [106.912135, 37.110345], + [106.891193, 37.098413], + [106.818512, 37.141838], + [106.776012, 37.158056], + [106.772933, 37.120367], + [106.750143, 37.09889], + [106.728585, 37.121321], + [106.687933, 37.12991], + [106.673151, 37.1113], + [106.6171, 37.135158], + [106.605397, 37.127524], + [106.645433, 37.064992], + [106.666991, 37.016745], + [106.646665, 37.000496], + [106.64297, 36.962729], + [106.594926, 36.967988], + [106.595542, 36.94025], + [106.540108, 36.984244], + [106.549347, 36.941685], + [106.601702, 36.918244], + [106.609709, 36.878521], + [106.609709, 36.878521], + [106.626955, 36.892403], + [106.637426, 36.867031], + [106.637426, 36.867031], + [106.657752, 36.820575], + [106.627571, 36.752995], + [106.644817, 36.72278], + [106.59431, 36.750118], + [106.514238, 36.715584], + [106.519782, 36.708868], + [106.519782, 36.708868], + [106.530869, 36.690154], + [106.490833, 36.685835], + [106.491448, 36.628703], + [106.444637, 36.624861], + [106.465579, 36.583063], + [106.444637, 36.557109], + [106.397826, 36.576816], + [106.392282, 36.556628], + [106.363949, 36.577296], + [106.37134, 36.549417], + [106.39721, 36.548455], + [106.455724, 36.496995], + [106.494528, 36.494589], + [106.523477, 36.468605], + [106.492064, 36.422389], + [106.510543, 36.379037], + [106.497608, 36.31348], + [106.470507, 36.306246], + [106.504383, 36.266207], + [106.54134, 36.25366], + [106.559202, 36.292259], + [106.647897, 36.259451], + [106.685469, 36.273445], + [106.698404, 36.244008], + [106.735976, 36.23725], + [106.772933, 36.212628], + [106.808657, 36.21118], + [106.833295, 36.229044], + [106.858548, 36.206834], + [106.858548, 36.206834], + [106.873947, 36.178338], + [106.873947, 36.178338], + [106.930613, 36.138716], + [106.925686, 36.115997], + [106.957715, 36.091337], + [106.940468, 36.064734], + [106.928149, 36.011502], + [106.94786, 35.988262], + [106.90228, 35.943699], + [106.93862, 35.952905], + [106.940468, 35.931101], + [106.912751, 35.93207], + [106.849925, 35.887476], + [106.927534, 35.810346], + [106.897353, 35.759856], + [106.868403, 35.771996], + [106.867171, 35.738485], + [106.819128, 35.7448], + [106.806193, 35.70982], + [106.750759, 35.725369], + [106.750759, 35.689408], + [106.674998, 35.728284], + [106.66268, 35.70739], + [106.633115, 35.714679], + [106.620796, 35.743829], + [106.595542, 35.727312], + [106.566593, 35.738971], + [106.506231, 35.737514] + ] + ], + [ + [ + [106.047356, 35.498155], + [106.048588, 35.488898], + [106.054132, 35.45478], + [106.071994, 35.463555], + [106.078769, 35.509848], + [106.047356, 35.498155] + ] + ], + [ + [ + [102.831537, 36.365544], + [102.829689, 36.365544], + [102.836465, 36.344819], + [102.838928, 36.345783], + [102.831537, 36.365544] + ] + ], + [ + [ + [106.073226, 35.447468], + [106.067682, 35.436254], + [106.073226, 35.420649], + [106.083081, 35.421624], + [106.073226, 35.447468] + ] + ], + [ + [ + [106.504383, 35.736057], + [106.506231, 35.737514], + [106.49268, 35.732656], + [106.498224, 35.732656], + [106.504383, 35.736057] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 630000, + "name": "Qinghai", + "center": [101.778916, 36.623178], + "centroid": [96.043533, 35.726403], + "childrenNum": 8, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 28, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [102.829689, 36.365544], + [102.771791, 36.47438], + [102.793349, 36.497957], + [102.753313, 36.525855], + [102.734219, 36.562396], + [102.761936, 36.568645], + [102.714509, 36.599401], + [102.724364, 36.613813], + [102.684328, 36.619097], + [102.630741, 36.650793], + [102.601176, 36.710307], + [102.612879, 36.738129], + [102.639364, 36.732853], + [102.692335, 36.775528], + [102.720052, 36.767858], + [102.639364, 36.852666], + [102.587009, 36.869904], + [102.56114, 36.91968], + [102.526031, 36.928291], + [102.499546, 36.954599], + [102.450271, 36.968467], + [102.506321, 37.019134], + [102.488459, 37.078362], + [102.583314, 37.104618], + [102.642444, 37.099845], + [102.599944, 37.174748], + [102.578386, 37.17284], + [102.533422, 37.217176], + [102.490307, 37.223371], + [102.457662, 37.248147], + [102.45335, 37.271487], + [102.419474, 37.294343], + [102.428097, 37.308624], + [102.368351, 37.327662], + [102.29875, 37.370004], + [102.299981, 37.391404], + [102.19712, 37.420403], + [102.176794, 37.458892], + [102.125055, 37.48549], + [102.103497, 37.482641], + [102.131214, 37.54625], + [102.102265, 37.582304], + [102.035128, 37.627819], + [102.048678, 37.651515], + [102.036359, 37.685149], + [101.998787, 37.724921], + [101.946432, 37.728235], + [101.873135, 37.686569], + [101.854657, 37.664781], + [101.815853, 37.654357], + [101.791832, 37.696041], + [101.659405, 37.733441], + [101.670491, 37.754264], + [101.598427, 37.827569], + [101.551615, 37.835604], + [101.459224, 37.86632], + [101.382848, 37.822369], + [101.362522, 37.791162], + [101.276906, 37.83655], + [101.202994, 37.84742], + [101.159262, 37.86821], + [101.152486, 37.891356], + [101.114298, 37.92016], + [101.103211, 37.946593], + [101.077342, 37.941874], + [100.964009, 38.011221], + [100.91843, 37.999432], + [100.895024, 38.013107], + [100.888864, 38.056001], + [100.922125, 38.084741], + [100.91843, 38.129006], + [100.93814, 38.16007], + [100.913502, 38.17889], + [100.860531, 38.148305], + [100.825423, 38.158658], + [100.752126, 38.238612], + [100.71517, 38.253652], + [100.619083, 38.26587], + [100.595061, 38.242372], + [100.545786, 38.247072], + [100.516837, 38.272448], + [100.474953, 38.288891], + [100.459555, 38.2654], + [100.432453, 38.275267], + [100.424446, 38.307208], + [100.396729, 38.293118], + [100.318505, 38.329276], + [100.331439, 38.337257], + [100.301874, 38.388405], + [100.259374, 38.366355], + [100.24028, 38.441861], + [100.163288, 38.461546], + [100.113397, 38.497151], + [100.086911, 38.492936], + [100.064122, 38.518694], + [100.025933, 38.507923], + [100.001296, 38.467169], + [100.022238, 38.432017], + [100.093071, 38.407166], + [100.136803, 38.33444], + [100.163904, 38.328337], + [100.159592, 38.291239], + [100.182998, 38.222158], + [100.126332, 38.231561], + [100.117093, 38.253652], + [100.071513, 38.284663], + [100.049955, 38.283254], + [100.001912, 38.315191], + [99.960028, 38.320825], + [99.826985, 38.370109], + [99.758, 38.410449], + [99.727203, 38.415607], + [99.65945, 38.449361], + [99.63974, 38.474666], + [99.585537, 38.498556], + [99.52887, 38.546314], + [99.501769, 38.612281], + [99.450646, 38.60433], + [99.412458, 38.665571], + [99.375502, 38.684727], + [99.361951, 38.718354], + [99.291118, 38.765966], + [99.222133, 38.788827], + [99.141445, 38.852706], + [99.068764, 38.896968], + [99.071843, 38.921184], + [99.107568, 38.951907], + [99.054597, 38.97657], + [98.951735, 38.987735], + [98.903076, 39.012384], + [98.886446, 39.040744], + [98.818076, 39.064911], + [98.816845, 39.085818], + [98.743548, 39.086747], + [98.730613, 39.057011], + [98.70536, 39.043533], + [98.661628, 38.993782], + [98.612353, 38.977035], + [98.624056, 38.959353], + [98.584635, 38.93003], + [98.526737, 38.95563], + [98.457752, 38.952838], + [98.428187, 38.976104], + [98.432498, 38.996107], + [98.401086, 39.001688], + [98.383839, 39.029588], + [98.316702, 39.040744], + [98.280977, 39.027263], + [98.287753, 38.992386], + [98.276666, 38.963541], + [98.235398, 38.918855], + [98.242173, 38.880664], + [98.167645, 38.840121], + [98.091884, 38.786495], + [98.068478, 38.816344], + [98.029058, 38.834061], + [98.009348, 38.85923], + [97.875689, 38.898365], + [97.828878, 38.93003], + [97.701379, 38.963076], + [97.679205, 39.010524], + [97.58127, 39.052364], + [97.504894, 39.076527], + [97.458698, 39.117863], + [97.401416, 39.146645], + [97.371235, 39.140611], + [97.347213, 39.167528], + [97.315185, 39.164744], + [97.220946, 39.193042], + [97.14149, 39.199999], + [97.060186, 39.19768], + [97.017686, 39.208347], + [96.962251, 39.198144], + [97.012142, 39.142004], + [96.969643, 39.097895], + [96.95794, 39.041674], + [96.965331, 39.017034], + [96.938846, 38.95563], + [96.940693, 38.90768], + [96.983809, 38.869016], + [96.993664, 38.834993], + [96.987505, 38.793025], + [97.00044, 38.7613], + [97.023229, 38.755699], + [97.009063, 38.702477], + [97.057722, 38.67258], + [97.047251, 38.653888], + [97.055874, 38.594508], + [96.961019, 38.558015], + [96.876636, 38.580475], + [96.847071, 38.599186], + [96.7941, 38.608072], + [96.808882, 38.582346], + [96.767614, 38.552399], + [96.800259, 38.52759], + [96.780549, 38.504177], + [96.706637, 38.505582], + [96.6666, 38.483567], + [96.707868, 38.459203], + [96.698013, 38.422172], + [96.626564, 38.356031], + [96.638883, 38.307208], + [96.655514, 38.295936], + [96.665369, 38.23015], + [96.46334, 38.277616], + [96.378341, 38.277146], + [96.335841, 38.246132], + [96.301964, 38.183124], + [96.313051, 38.161952], + [96.264392, 38.145952], + [96.252689, 38.167599], + [96.221892, 38.149246], + [96.109175, 38.187358], + [96.06606, 38.173245], + [96.006929, 38.207582], + [95.93856, 38.237202], + [95.932401, 38.259291], + [95.89606, 38.2903], + [95.852945, 38.287481], + [95.83693, 38.344298], + [95.775952, 38.356031], + [95.723597, 38.378554], + [95.703887, 38.400131], + [95.671858, 38.388405], + [95.608417, 38.339134], + [95.585011, 38.343359], + [95.51849, 38.294997], + [95.487693, 38.314721], + [95.455664, 38.291709], + [95.440881, 38.310965], + [95.408236, 38.300163], + [95.315846, 38.318947], + [95.259179, 38.302981], + [95.229614, 38.330685], + [95.209904, 38.327868], + [95.185266, 38.379492], + [95.140919, 38.392158], + [95.122441, 38.417014], + [95.072549, 38.402476], + [95.045448, 38.418889], + [94.973999, 38.430142], + [94.884072, 38.414669], + [94.861282, 38.393565], + [94.812623, 38.385591], + [94.672805, 38.386998], + [94.582878, 38.36917], + [94.56132, 38.351807], + [94.527443, 38.365416], + [94.527443, 38.425922], + [94.511429, 38.445142], + [94.370379, 38.7627], + [94.281067, 38.7599], + [93.973098, 38.724891], + [93.95154, 38.715086], + [93.885018, 38.720689], + [93.800019, 38.750566], + [93.773533, 38.771099], + [93.756287, 38.807484], + [93.769838, 38.821007], + [93.884403, 38.826136], + [93.884403, 38.867618], + [93.834511, 38.867618], + [93.729186, 38.924443], + [93.453245, 38.915596], + [93.274007, 38.896036], + [93.237666, 38.916062], + [93.179152, 38.923977], + [93.198246, 39.045857], + [93.165601, 39.090928], + [93.131725, 39.108112], + [93.142196, 39.160567], + [93.115094, 39.17959], + [93.043029, 39.146645], + [92.978356, 39.143396], + [92.938936, 39.169848], + [92.889045, 39.160103], + [92.866871, 39.138754], + [92.765857, 39.136898], + [92.659299, 39.109969], + [92.545966, 39.111362], + [92.489916, 39.099753], + [92.459119, 39.063982], + [92.459119, 39.042604], + [92.41046, 39.03842], + [92.416003, 39.010524], + [92.380279, 38.999828], + [92.263866, 39.002153], + [92.197961, 38.983548], + [92.173323, 38.960749], + [92.10865, 38.963541], + [91.966368, 38.930961], + [91.880752, 38.899297], + [91.87952, 38.884391], + [91.806223, 38.872744], + [91.694738, 38.86622], + [91.681188, 38.852706], + [91.501333, 38.815411], + [91.446515, 38.813546], + [91.298689, 38.746365], + [91.242639, 38.752433], + [91.188436, 38.73096], + [90.992567, 38.695003], + [90.970394, 38.697806], + [90.899561, 38.679588], + [90.724634, 38.658094], + [90.65996, 38.674449], + [90.619308, 38.664636], + [90.645794, 38.635191], + [90.606374, 38.610878], + [90.608837, 38.594508], + [90.560794, 38.593573], + [90.525685, 38.561291], + [90.463476, 38.556611], + [90.465323, 38.521971], + [90.427135, 38.493873], + [90.353222, 38.482162], + [90.315034, 38.501835], + [90.248513, 38.491531], + [90.130868, 38.494341], + [90.111774, 38.477945], + [90.111774, 38.418889], + [90.129636, 38.400131], + [90.179528, 38.396848], + [90.137644, 38.340543], + [90.280542, 38.238142], + [90.352607, 38.233441], + [90.361846, 38.300163], + [90.401882, 38.311434], + [90.531229, 38.319886], + [90.516446, 38.207111], + [90.519526, 37.730601], + [90.579272, 37.720661], + [90.586663, 37.703144], + [90.643946, 37.696988], + [90.777605, 37.648672], + [90.820104, 37.613599], + [90.854597, 37.604117], + [90.882314, 37.575664], + [90.865684, 37.53059], + [90.911879, 37.519674], + [90.958075, 37.477891], + [91.019669, 37.493088], + [91.073256, 37.475992], + [91.099741, 37.447965], + [91.113292, 37.387124], + [91.136081, 37.355734], + [91.134849, 37.324331], + [91.194596, 37.273868], + [91.1909, 37.205737], + [91.280211, 37.163779], + [91.286371, 37.105095], + [91.303617, 37.083136], + [91.291298, 37.042544], + [91.303617, 37.012444], + [91.216153, 37.010054], + [91.181045, 37.025345], + [91.133618, 37.007665], + [91.126842, 36.978507], + [91.051698, 36.96751], + [91.036915, 36.929727], + [90.983944, 36.913459], + [90.924198, 36.921115], + [90.853981, 36.915373], + [90.758511, 36.825844], + [90.732025, 36.825844], + [90.727098, 36.755872], + [90.754815, 36.721341], + [90.720938, 36.708868], + [90.706156, 36.658955], + [90.730793, 36.655594], + [90.72217, 36.620058], + [90.741264, 36.585947], + [90.810865, 36.585466], + [90.831191, 36.55807], + [90.905104, 36.560474], + [91.011662, 36.539801], + [91.035683, 36.529703], + [91.039995, 36.474861], + [91.028292, 36.443093], + [91.051698, 36.433946], + [91.026444, 36.323607], + [91.07264, 36.299012], + [91.051698, 36.238215], + [91.096045, 36.219871], + [91.09235, 36.163844], + [91.124994, 36.115514], + [91.081263, 36.088436], + [90.979017, 36.106811], + [90.922966, 36.028927], + [90.850285, 36.016827], + [90.815793, 36.035703], + [90.776373, 36.086501], + [90.659344, 36.13485], + [90.613149, 36.126632], + [90.534925, 36.147899], + [90.478258, 36.13195], + [90.424055, 36.133883], + [90.325505, 36.159496], + [90.23681, 36.160462], + [90.198006, 36.187516], + [90.130252, 36.2078], + [90.145651, 36.239181], + [90.058188, 36.255591], + [90.043405, 36.276822], + [90.003369, 36.278752], + [90.028006, 36.258486], + [90.019999, 36.213594], + [89.997825, 36.168193], + [89.944855, 36.140649], + [89.941159, 36.067637], + [89.914058, 36.079246], + [89.819819, 36.080697], + [89.766848, 36.073925], + [89.711414, 36.093272], + [89.688624, 36.091337], + [89.605472, 36.038123], + [89.474893, 36.022151], + [89.417611, 36.044897], + [89.404676, 36.016827], + [89.434857, 35.992136], + [89.428082, 35.917531], + [89.489676, 35.903475], + [89.554965, 35.873414], + [89.550654, 35.856924], + [89.62395, 35.859349], + [89.654747, 35.848193], + [89.707718, 35.849163], + [89.778551, 35.861775], + [89.801957, 35.848193], + [89.767464, 35.799183], + [89.782863, 35.773453], + [89.747138, 35.7516], + [89.748986, 35.66267], + [89.726196, 35.648082], + [89.765616, 35.599922], + [89.75145, 35.580942], + [89.71203, 35.581915], + [89.699711, 35.544916], + [89.720037, 35.501566], + [89.740979, 35.507412], + [89.765, 35.482563], + [89.739131, 35.468429], + [89.685544, 35.416259], + [89.658443, 35.425526], + [89.619639, 35.412357], + [89.58761, 35.383575], + [89.497067, 35.361128], + [89.516161, 35.330862], + [89.494603, 35.298632], + [89.531559, 35.276161], + [89.48598, 35.256616], + [89.450255, 35.223867], + [89.46935, 35.214577], + [89.519241, 35.133862], + [89.579603, 35.118688], + [89.593153, 35.104491], + [89.59069, 35.057965], + [89.560509, 34.938836], + [89.578987, 34.895162], + [89.670146, 34.887798], + [89.707102, 34.919701], + [89.747138, 34.903506], + [89.78779, 34.921664], + [89.821051, 34.902033], + [89.814891, 34.86816], + [89.838913, 34.865705], + [89.867862, 34.81069], + [89.825978, 34.796931], + [89.799493, 34.743838], + [89.732356, 34.732035], + [89.72558, 34.660689], + [89.74837, 34.641981], + [89.798877, 34.628686], + [89.777935, 34.574499], + [89.814891, 34.548871], + [89.823515, 34.455657], + [89.819819, 34.420614], + [89.799493, 34.39642], + [89.820435, 34.369255], + [89.858623, 34.359375], + [89.86663, 34.324785], + [89.825362, 34.293642], + [89.838297, 34.263477], + [89.816739, 34.16945], + [89.789638, 34.150632], + [89.760073, 34.152613], + [89.756993, 34.124874], + [89.71203, 34.131809], + [89.655979, 34.097126], + [89.656595, 34.057966], + [89.635037, 34.049537], + [89.684928, 33.990013], + [89.688008, 33.959739], + [89.718805, 33.946832], + [89.73174, 33.921509], + [89.795181, 33.865374], + [89.837065, 33.868853], + [89.899891, 33.80771], + [89.942391, 33.801246], + [89.902355, 33.758467], + [89.907282, 33.741051], + [89.983659, 33.725622], + [89.981195, 33.70322], + [90.008296, 33.687785], + [89.984275, 33.612061], + [90.01076, 33.553728], + [90.083441, 33.525295], + [90.088984, 33.478885], + [90.107463, 33.460913], + [90.22018, 33.437943], + [90.246665, 33.423959], + [90.332896, 33.310501], + [90.363077, 33.279487], + [90.405577, 33.260473], + [90.490577, 33.264977], + [90.562642, 33.229441], + [90.627315, 33.180368], + [90.704308, 33.135778], + [90.740032, 33.142293], + [90.803474, 33.114227], + [90.88293, 33.120241], + [90.902024, 33.083143], + [90.927894, 33.120241], + [91.001807, 33.11573], + [91.037531, 33.098686], + [91.072024, 33.113224], + [91.147784, 33.07211], + [91.161335, 33.108712], + [91.18782, 33.106206], + [91.226624, 33.141792], + [91.261733, 33.141291], + [91.311624, 33.108211], + [91.370138, 33.100691], + [91.436044, 33.066092], + [91.49579, 33.109214], + [91.535826, 33.10019], + [91.55492, 33.060074], + [91.583253, 33.0375], + [91.664557, 33.012913], + [91.685499, 32.989324], + [91.752637, 32.969242], + [91.799448, 32.942126], + [91.839484, 32.948152], + [91.857962, 32.90244], + [91.896766, 32.907967], + [91.955897, 32.8205], + [92.018722, 32.829552], + [92.038432, 32.860725], + [92.101874, 32.860222], + [92.145606, 32.885857], + [92.205352, 32.866255], + [92.227526, 32.821003], + [92.193649, 32.801889], + [92.211511, 32.788306], + [92.198577, 32.754591], + [92.255243, 32.720863], + [92.310062, 32.751571], + [92.343938, 32.738484], + [92.355641, 32.764657], + [92.411076, 32.748048], + [92.459119, 32.76365], + [92.484372, 32.745028], + [92.56814, 32.73194], + [92.574916, 32.741001], + [92.634662, 32.720863], + [92.667922, 32.73194], + [92.686401, 32.76516], + [92.756618, 32.743014], + [92.789262, 32.719856], + [92.822523, 32.729926], + [92.866871, 32.698203], + [92.933392, 32.719353], + [92.964189, 32.714821], + [93.00053, 32.741001], + [93.019624, 32.737477], + [93.023935, 32.703239], + [93.069515, 32.626156], + [93.087993, 32.63674], + [93.159442, 32.644803], + [93.176688, 32.6705], + [93.210565, 32.655385], + [93.239514, 32.662439], + [93.260456, 32.62666], + [93.300492, 32.619604], + [93.308499, 32.580278], + [93.33868, 32.5712], + [93.385492, 32.525294], + [93.411977, 32.558086], + [93.4631, 32.556069], + [93.476651, 32.504603], + [93.501904, 32.503593], + [93.516687, 32.47583], + [93.618933, 32.522771], + [93.651577, 32.571705], + [93.721795, 32.578261], + [93.75136, 32.56313], + [93.820345, 32.549511], + [93.851142, 32.50965], + [93.861613, 32.466237], + [93.90904, 32.463207], + [93.960163, 32.484917], + [93.978641, 32.459672], + [94.03038, 32.448057], + [94.049474, 32.469771], + [94.091974, 32.463207], + [94.137554, 32.433915], + [94.176974, 32.454117], + [94.196684, 32.51621], + [94.250886, 32.51722], + [94.292154, 32.502584], + [94.294002, 32.519743], + [94.350053, 32.533871], + [94.371611, 32.524789], + [94.395016, 32.594397], + [94.435052, 32.562626], + [94.463386, 32.572209], + [94.459074, 32.599439], + [94.522516, 32.595909], + [94.591501, 32.640772], + [94.614291, 32.673522], + [94.638312, 32.645307], + [94.737479, 32.587338], + [94.762116, 32.526303], + [94.78737, 32.522266], + [94.80708, 32.486431], + [94.852043, 32.463712], + [94.889616, 32.472295], + [94.912405, 32.41573], + [94.944434, 32.404109], + [94.988166, 32.422802], + [95.057151, 32.395014], + [95.075013, 32.376315], + [95.075013, 32.376315], + [95.081789, 32.384907], + [95.153853, 32.386423], + [95.218527, 32.397035], + [95.228382, 32.363678], + [95.261643, 32.348006], + [95.193274, 32.332331], + [95.096571, 32.322217], + [95.079325, 32.279726], + [95.10581, 32.258979], + [95.20744, 32.297433], + [95.214216, 32.321712], + [95.241317, 32.3207], + [95.239469, 32.287315], + [95.270266, 32.194683], + [95.270266, 32.194683], + [95.31523, 32.148585], + [95.366968, 32.151118], + [95.367584, 32.178982], + [95.406389, 32.182021], + [95.440265, 32.157705], + [95.454432, 32.061898], + [95.421171, 32.033999], + [95.454432, 32.007613], + [95.395918, 32.001523], + [95.360809, 31.95939], + [95.3682, 31.92892], + [95.408852, 31.918761], + [95.406389, 31.896915], + [95.456896, 31.801853], + [95.480301, 31.795749], + [95.511714, 31.750468], + [95.546823, 31.73978], + [95.580083, 31.76726], + [95.634286, 31.782523], + [95.779648, 31.748941], + [95.823995, 31.68225], + [95.853561, 31.714329], + [95.846169, 31.736218], + [95.89914, 31.81711], + [95.983524, 31.816601], + [95.989067, 31.78761], + [96.064828, 31.720438], + [96.135661, 31.70211], + [96.148595, 31.686324], + [96.156603, 31.602769], + [96.207726, 31.598691], + [96.221892, 31.647613], + [96.245298, 31.657802], + [96.252073, 31.697527], + [96.222508, 31.733164], + [96.231131, 31.749959], + [96.178161, 31.775401], + [96.183088, 31.835924], + [96.202798, 31.841008], + [96.214501, 31.876589], + [96.188632, 31.904028], + [96.220044, 31.905553], + [96.253305, 31.929936], + [96.288414, 31.919777], + [96.389428, 31.919777], + [96.407906, 31.845583], + [96.435623, 31.796258], + [96.468884, 31.769804], + [96.519391, 31.74945], + [96.56805, 31.711783], + [96.615477, 31.737236], + [96.661057, 31.705674], + [96.691854, 31.722474], + [96.722651, 31.686833], + [96.778701, 31.675629], + [96.790404, 31.698545], + [96.840295, 31.720438], + [96.799027, 31.792188], + [96.765767, 31.819144], + [96.760223, 31.860325], + [96.794716, 31.869474], + [96.81073, 31.894375], + [96.776238, 31.935015], + [96.753448, 31.944156], + [96.742977, 32.001016], + [96.722651, 32.013195], + [96.824281, 32.007613], + [96.868629, 31.964975], + [96.863085, 31.996448], + [96.894498, 32.013703], + [96.941925, 31.986297], + [96.965947, 32.008628], + [96.935766, 32.048203], + [97.006599, 32.067984], + [97.028773, 32.04871], + [97.127323, 32.044145], + [97.169823, 32.032984], + [97.188301, 32.055304], + [97.214786, 32.042623], + [97.233881, 32.063927], + [97.201852, 32.090296], + [97.219714, 32.109054], + [97.258518, 32.072041], + [97.308409, 32.076605], + [97.293011, 32.096887], + [97.313953, 32.130342], + [97.271453, 32.139971], + [97.264062, 32.182527], + [97.299786, 32.294904], + [97.32196, 32.303503], + [97.371235, 32.273148], + [97.415583, 32.296421], + [97.424822, 32.322723], + [97.387865, 32.427349], + [97.341054, 32.440987], + [97.388481, 32.501575], + [97.334895, 32.514192], + [97.332431, 32.542448], + [97.3583, 32.563635], + [97.374315, 32.546484], + [97.411887, 32.575235], + [97.448843, 32.586833], + [97.463626, 32.55506], + [97.50243, 32.530844], + [97.540618, 32.536899], + [97.670582, 32.51722], + [97.684132, 32.530339], + [97.730944, 32.527312], + [97.700763, 32.53488], + [97.616995, 32.586329], + [97.607756, 32.614059], + [97.543698, 32.62162], + [97.535075, 32.638252], + [97.48272, 32.654377], + [97.42359, 32.70475], + [97.429133, 32.714318], + [97.386018, 32.77925], + [97.392793, 32.828546], + [97.376163, 32.886359], + [97.347829, 32.895907], + [97.375547, 32.956689], + [97.438372, 32.976271], + [97.523988, 32.988822], + [97.499966, 33.011408], + [97.542466, 33.035995], + [97.517213, 33.097683], + [97.487032, 33.107209], + [97.498119, 33.137783], + [97.487648, 33.168346], + [97.548626, 33.203907], + [97.607756, 33.263976], + [97.622538, 33.337005], + [97.676125, 33.341004], + [97.754349, 33.409972], + [97.674893, 33.432949], + [97.625618, 33.461412], + [97.552321, 33.465906], + [97.511669, 33.520805], + [97.523372, 33.577166], + [97.450075, 33.582152], + [97.415583, 33.605582], + [97.435293, 33.682307], + [97.418046, 33.728608], + [97.422974, 33.754984], + [97.406344, 33.795278], + [97.373083, 33.817655], + [97.371851, 33.842015], + [97.398336, 33.848477], + [97.395257, 33.889224], + [97.460546, 33.887236], + [97.503662, 33.912073], + [97.52214, 33.903133], + [97.601596, 33.929951], + [97.629314, 33.919523], + [97.660111, 33.956264], + [97.652719, 33.998448], + [97.70261, 34.036644], + [97.665654, 34.126855], + [97.766668, 34.158555], + [97.789458, 34.182818], + [97.789458, 34.182818], + [97.796849, 34.199154], + [97.796849, 34.199154], + [97.8104, 34.207568], + [97.898479, 34.209548], + [97.95453, 34.190739], + [98.028442, 34.122892], + [98.098043, 34.122892], + [98.158405, 34.107037], + [98.206449, 34.08424], + [98.258188, 34.083249], + [98.344419, 34.094648], + [98.399854, 34.085231], + [98.396774, 34.053008], + [98.428187, 34.029204], + [98.440506, 33.981577], + [98.415252, 33.956761], + [98.425723, 33.913066], + [98.407245, 33.867362], + [98.434962, 33.843009], + [98.463295, 33.848477], + [98.492861, 33.796272], + [98.494092, 33.768915], + [98.51873, 33.77389], + [98.539672, 33.746525], + [98.582788, 33.731595], + [98.610505, 33.682805], + [98.6567, 33.64744], + [98.61728, 33.637476], + [98.622824, 33.610067], + [98.652389, 33.595114], + [98.648077, 33.548741], + [98.678258, 33.522801], + [98.725686, 33.503341], + [98.742316, 33.477887], + [98.736157, 33.406975], + [98.779888, 33.370497], + [98.759562, 33.276985], + [98.802062, 33.270481], + [98.804526, 33.219428], + [98.858728, 33.150811], + [98.92217, 33.118738], + [98.967134, 33.115229], + [98.971445, 33.098185], + [99.014561, 33.081137], + [99.024416, 33.094675], + [99.090322, 33.079131], + [99.124814, 33.046028], + [99.196263, 33.035493], + [99.214741, 32.991332], + [99.235067, 32.982296], + [99.24677, 32.924043], + [99.268944, 32.878318], + [99.353944, 32.885354], + [99.376118, 32.899927], + [99.45311, 32.862233], + [99.558436, 32.839106], + [99.589233, 32.789312], + [99.640355, 32.790822], + [99.646515, 32.774721], + [99.700718, 32.76667], + [99.717964, 32.732443], + [99.760464, 32.769689], + [99.766623, 32.826032], + [99.791877, 32.883344], + [99.764159, 32.924545], + [99.788181, 32.956689], + [99.805427, 32.940619], + [99.851007, 32.941623], + [99.877492, 32.993339], + [99.877492, 33.045527], + [99.947709, 32.986814], + [99.956332, 32.948152], + [100.038252, 32.929066], + [100.029629, 32.895907], + [100.064738, 32.895907], + [100.123252, 32.837095], + [100.117093, 32.802392], + [100.139266, 32.724388], + [100.088143, 32.668988], + [100.109701, 32.640268], + [100.189773, 32.630692], + [100.208252, 32.606497], + [100.229809, 32.650346], + [100.231041, 32.696189], + [100.258759, 32.742511], + [100.339447, 32.719353], + [100.399193, 32.756101], + [100.378251, 32.698707], + [100.420135, 32.73194], + [100.450932, 32.694678], + [100.470026, 32.694678], + [100.516837, 32.632204], + [100.54517, 32.569687], + [100.603069, 32.553547], + [100.645568, 32.526303], + [100.657887, 32.546484], + [100.661583, 32.616075], + [100.673286, 32.628172], + [100.710242, 32.610026], + [100.71209, 32.645307], + [100.690532, 32.678056], + [100.77122, 32.643795], + [100.834046, 32.648835], + [100.887633, 32.632708], + [100.93198, 32.600447], + [100.956618, 32.621116], + [100.99727, 32.627668], + [101.030531, 32.660424], + [101.077342, 32.68259], + [101.124769, 32.658408], + [101.157414, 32.661431], + [101.22332, 32.725898], + [101.237486, 32.825026], + [101.223935, 32.855698], + [101.178356, 32.892892], + [101.124153, 32.909976], + [101.134624, 32.95217], + [101.129081, 32.989324], + [101.183899, 32.984304], + [101.171581, 33.009902], + [101.184515, 33.041514], + [101.146327, 33.056563], + [101.143863, 33.086151], + [101.169733, 33.10019], + [101.11553, 33.194893], + [101.124769, 33.221431], + [101.156798, 33.236449], + [101.182668, 33.26948], + [101.217776, 33.256469], + [101.297232, 33.262475], + [101.381616, 33.153316], + [101.393935, 33.157826], + [101.386543, 33.207412], + [101.403174, 33.225436], + [101.487557, 33.226938], + [101.515275, 33.192889], + [101.557775, 33.167344], + [101.633535, 33.101193], + [101.661252, 33.135778], + [101.653861, 33.162835], + [101.709912, 33.21292], + [101.735781, 33.279987], + [101.677883, 33.297497], + [101.64955, 33.323004], + [101.663716, 33.383991], + [101.695745, 33.433948], + [101.769042, 33.45592], + [101.777665, 33.533776], + [101.769042, 33.538765], + [101.748716, 33.505337], + [101.718535, 33.494857], + [101.622448, 33.502343], + [101.611977, 33.565199], + [101.616905, 33.598603], + [101.585492, 33.645448], + [101.58426, 33.674339], + [101.501724, 33.702723], + [101.428427, 33.680315], + [101.424732, 33.655411], + [101.385312, 33.644949], + [101.302776, 33.657902], + [101.23687, 33.685793], + [101.217776, 33.669856], + [101.166653, 33.659894], + [101.177124, 33.685295], + [101.162957, 33.719649], + [101.186363, 33.741051], + [101.190675, 33.791796], + [101.153102, 33.823124], + [101.153718, 33.8445], + [101.054552, 33.863386], + [101.023139, 33.896178], + [100.994806, 33.891707], + [100.965857, 33.946832], + [100.927669, 33.975126], + [100.93506, 33.990013], + [100.880857, 34.036644], + [100.870386, 34.083744], + [100.848828, 34.089692], + [100.806329, 34.155584], + [100.764445, 34.178857], + [100.809408, 34.247153], + [100.798321, 34.260014], + [100.821727, 34.317371], + [100.868538, 34.332693], + [100.895024, 34.375183], + [100.951074, 34.38358], + [100.986799, 34.374689], + [101.054552, 34.322808], + [101.098284, 34.329233], + [101.178356, 34.320831], + [101.193754, 34.336646], + [101.235022, 34.325279], + [101.228863, 34.298586], + [101.268899, 34.278808], + [101.325565, 34.268423], + [101.327413, 34.24468], + [101.369913, 34.248143], + [101.417956, 34.227858], + [101.482014, 34.218951], + [101.492485, 34.195689], + [101.53868, 34.212022], + [101.6206, 34.178857], + [101.674187, 34.110506], + [101.703136, 34.119424], + [101.718535, 34.083249], + [101.736397, 34.080275], + [101.764114, 34.122892], + [101.788136, 34.131809], + [101.836795, 34.124378], + [101.851578, 34.153108], + [101.874367, 34.130323], + [101.897773, 34.133791], + [101.955055, 34.109514], + [101.965526, 34.167469], + [102.003099, 34.162022], + [102.030816, 34.190739], + [102.01357, 34.218456], + [102.062229, 34.227858], + [102.067772, 34.293642], + [102.149692, 34.271885], + [102.186649, 34.352952], + [102.237156, 34.34307], + [102.237156, 34.34307], + [102.259329, 34.355917], + [102.205743, 34.407777], + [102.169402, 34.457631], + [102.155852, 34.507456], + [102.139837, 34.50351], + [102.093026, 34.536547], + [102.001867, 34.538519], + [101.97415, 34.548871], + [101.956287, 34.582876], + [101.934729, 34.58731], + [101.919947, 34.621791], + [101.917483, 34.705964], + [101.923027, 34.835746], + [101.916867, 34.873561], + [101.985852, 34.90007], + [102.068388, 34.887798], + [102.048062, 34.910868], + [102.094874, 34.986901], + [102.133678, 35.014844], + [102.157699, 35.010923], + [102.176178, 35.032977], + [102.211286, 35.034937], + [102.218062, 35.057475], + [102.252554, 35.048657], + [102.29567, 35.071681], + [102.310452, 35.128967], + [102.346793, 35.164201], + [102.404075, 35.179366], + [102.365887, 35.235599], + [102.370199, 35.263946], + [102.3123, 35.282512], + [102.280887, 35.303028], + [102.311684, 35.31426], + [102.317844, 35.343067], + [102.287663, 35.36552], + [102.293822, 35.424063], + [102.314764, 35.434303], + [102.408387, 35.409431], + [102.447807, 35.437229], + [102.437952, 35.455268], + [102.49893, 35.545403], + [102.503241, 35.585322], + [102.531575, 35.580455], + [102.570995, 35.548324], + [102.695414, 35.528358], + [102.743458, 35.494745], + [102.782878, 35.527871], + [102.729291, 35.523487], + [102.746537, 35.545403], + [102.808747, 35.560496], + [102.763168, 35.612086], + [102.7644, 35.653431], + [102.744074, 35.657807], + [102.707733, 35.70496], + [102.686175, 35.771996], + [102.715125, 35.815685], + [102.739146, 35.821023], + [102.787189, 35.862745], + [102.81737, 35.850133], + [102.914073, 35.845282], + [102.94487, 35.829757], + [102.954725, 35.858864], + [102.942406, 35.92674], + [102.971971, 35.995525], + [102.951645, 36.021667], + [102.968276, 36.044414], + [102.932551, 36.048285], + [102.882044, 36.082632], + [102.941174, 36.104877], + [102.948566, 36.150798], + [102.965812, 36.151765], + [102.986754, 36.193312], + [103.048964, 36.199107], + [103.066826, 36.216974], + [103.021246, 36.232906], + [103.024942, 36.256556], + [102.922696, 36.298047], + [102.896827, 36.331803], + [102.845704, 36.331803], + [102.836465, 36.344819], + [102.829689, 36.365544] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 640000, + "name": "Ningxia", + "center": [106.278179, 38.46637], + "centroid": [106.169866, 37.291332], + "childrenNum": 5, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 29, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [107.268764, 37.099367], + [107.281083, 37.127047], + [107.306952, 37.100799], + [107.334669, 37.138975], + [107.336517, 37.165687], + [107.317423, 37.200017], + [107.270612, 37.229089], + [107.309416, 37.239095], + [107.273075, 37.29101], + [107.257677, 37.337179], + [107.282931, 37.437036], + [107.284162, 37.481691], + [107.345756, 37.518725], + [107.369162, 37.58752], + [107.330358, 37.584201], + [107.311264, 37.609806], + [107.361155, 37.613125], + [107.422133, 37.665254], + [107.389488, 37.671413], + [107.387024, 37.691305], + [107.425828, 37.684201], + [107.484959, 37.706458], + [107.499125, 37.765619], + [107.57119, 37.776499], + [107.599523, 37.791162], + [107.620465, 37.776026], + [107.646335, 37.805349], + [107.659269, 37.844112], + [107.65003, 37.86443], + [107.560719, 37.893717], + [107.49235, 37.944706], + [107.448618, 37.933378], + [107.411662, 37.948009], + [107.440611, 37.995659], + [107.3938, 38.014993], + [107.33159, 38.086625], + [107.240431, 38.111586], + [107.19054, 38.153953], + [107.138801, 38.161011], + [107.119091, 38.134185], + [107.071047, 38.138892], + [107.051337, 38.122886], + [107.010069, 38.120532], + [106.942316, 38.132302], + [106.858548, 38.156306], + [106.779092, 38.171833], + [106.737824, 38.197706], + [106.654672, 38.22921], + [106.627571, 38.232501], + [106.555506, 38.263521], + [106.482209, 38.319417], + [106.599854, 38.389812], + [106.647897, 38.470917], + [106.66268, 38.601524], + [106.709491, 38.718821], + [106.756302, 38.748699], + [106.837606, 38.847579], + [106.954019, 38.941202], + [106.971881, 39.026333], + [106.96757, 39.054688], + [106.933693, 39.076527], + [106.878874, 39.091392], + [106.859164, 39.107648], + [106.825288, 39.19397], + [106.795723, 39.214375], + [106.790795, 39.241263], + [106.806193, 39.277407], + [106.806809, 39.318625], + [106.781556, 39.371849], + [106.751375, 39.381564], + [106.683622, 39.357506], + [106.643586, 39.357969], + [106.602318, 39.37555], + [106.556122, 39.322329], + [106.525325, 39.308439], + [106.511774, 39.272311], + [106.402753, 39.291767], + [106.280181, 39.262118], + [106.29558, 39.167992], + [106.285109, 39.146181], + [106.251232, 39.131327], + [106.192718, 39.142932], + [106.170544, 39.163352], + [106.145907, 39.153142], + [106.096631, 39.084889], + [106.078153, 39.026333], + [106.087392, 39.006339], + [106.060907, 38.96866], + [106.021487, 38.953769], + [105.97098, 38.909077], + [105.992538, 38.857366], + [105.909386, 38.791159], + [105.908154, 38.737496], + [105.88598, 38.716953], + [105.894603, 38.696405], + [105.852719, 38.641735], + [105.874277, 38.593105], + [105.856415, 38.569714], + [105.863806, 38.53508], + [105.836705, 38.476071], + [105.850872, 38.443736], + [105.827466, 38.432486], + [105.835473, 38.387467], + [105.821307, 38.366824], + [105.86627, 38.296406], + [105.842248, 38.240962], + [105.802828, 38.220277], + [105.775111, 38.186887], + [105.76772, 38.121474], + [105.780655, 38.084741], + [105.840401, 38.004147], + [105.799749, 37.939986], + [105.80406, 37.862068], + [105.760944, 37.799674], + [105.677177, 37.771769], + [105.622358, 37.777919], + [105.616199, 37.722555], + [105.598952, 37.699356], + [105.467141, 37.695094], + [105.4037, 37.710246], + [105.315004, 37.702197], + [105.221998, 37.677097], + [105.187505, 37.657674], + [105.111128, 37.633981], + [105.027977, 37.580881], + [104.866601, 37.566651], + [104.805007, 37.539133], + [104.623305, 37.522522], + [104.433595, 37.515402], + [104.419429, 37.511604], + [104.407726, 37.464592], + [104.322726, 37.44844], + [104.287002, 37.428007], + [104.298705, 37.414223], + [104.365226, 37.418026], + [104.437907, 37.445589], + [104.448994, 37.42468], + [104.499501, 37.421353], + [104.521059, 37.43466], + [104.679971, 37.408044], + [104.662109, 37.367626], + [104.713848, 37.329566], + [104.673812, 37.317668], + [104.651022, 37.290534], + [104.624536, 37.298627], + [104.600515, 37.242907], + [104.638087, 37.201923], + [104.717543, 37.208597], + [104.776673, 37.246718], + [104.85613, 37.211933], + [104.864753, 37.17284], + [104.888158, 37.15901], + [104.914644, 37.097935], + [104.954064, 37.077407], + [104.95468, 37.040156], + [105.004571, 37.035378], + [105.03968, 37.007187], + [105.05939, 37.022956], + [105.128991, 36.996194], + [105.165331, 36.99476], + [105.185657, 36.942164], + [105.178882, 36.892403], + [105.244787, 36.894796], + [105.279896, 36.86751], + [105.303302, 36.820575], + [105.334714, 36.80093], + [105.340874, 36.764502], + [105.319932, 36.742924], + [105.275584, 36.752515], + [105.272505, 36.739567], + [105.218302, 36.730455], + [105.201056, 36.700711], + [105.225693, 36.664716], + [105.22015, 36.631105], + [105.261418, 36.602764], + [105.2762, 36.563358], + [105.252179, 36.553263], + [105.281744, 36.522489], + [105.322396, 36.535954], + [105.362432, 36.496514], + [105.363048, 36.443093], + [105.398156, 36.430575], + [105.401236, 36.369881], + [105.425873, 36.330357], + [105.455439, 36.321678], + [105.476381, 36.293224], + [105.45975, 36.268137], + [105.460366, 36.223733], + [105.478844, 36.213111], + [105.515185, 36.147415], + [105.491163, 36.101009], + [105.430801, 36.10391], + [105.406163, 36.074409], + [105.343954, 36.033767], + [105.324859, 35.941761], + [105.350113, 35.875839], + [105.39754, 35.857409], + [105.371055, 35.844312], + [105.38091, 35.792873], + [105.408627, 35.822479], + [105.428953, 35.819082], + [105.432033, 35.787533], + [105.457286, 35.771511], + [105.481924, 35.727312], + [105.595873, 35.715651], + [105.667322, 35.749657], + [105.70243, 35.733142], + [105.759097, 35.724883], + [105.740618, 35.698643], + [105.723988, 35.725854], + [105.690727, 35.698643], + [105.722756, 35.673366], + [105.713517, 35.650513], + [105.759097, 35.634464], + [105.762176, 35.602841], + [105.800365, 35.564878], + [105.816379, 35.575101], + [105.847176, 35.490359], + [105.868734, 35.540046], + [105.900147, 35.54735], + [106.017175, 35.519103], + [106.023335, 35.49377], + [106.047356, 35.498155], + [106.078769, 35.509848], + [106.071994, 35.463555], + [106.06953, 35.458193], + [106.073842, 35.45478], + [106.073226, 35.450393], + [106.071378, 35.449418], + [106.073226, 35.447468], + [106.083081, 35.421624], + [106.113262, 35.361616], + [106.129892, 35.393333], + [106.173008, 35.437716], + [106.196414, 35.409919], + [106.237681, 35.409431], + [106.241377, 35.358687], + [106.319601, 35.265411], + [106.363333, 35.238532], + [106.368261, 35.273718], + [106.415688, 35.276161], + [106.472354, 35.310842], + [106.501304, 35.364056], + [106.503767, 35.415284], + [106.483441, 35.450393], + [106.490217, 35.480613], + [106.465579, 35.481101], + [106.440941, 35.52641], + [106.460036, 35.578995], + [106.47913, 35.575101], + [106.460036, 35.643705], + [106.434782, 35.688436], + [106.49268, 35.732656], + [106.506231, 35.737514], + [106.566593, 35.738971], + [106.595542, 35.727312], + [106.620796, 35.743829], + [106.633115, 35.714679], + [106.66268, 35.70739], + [106.674998, 35.728284], + [106.750759, 35.689408], + [106.750759, 35.725369], + [106.806193, 35.70982], + [106.819128, 35.7448], + [106.867171, 35.738485], + [106.868403, 35.771996], + [106.897353, 35.759856], + [106.927534, 35.810346], + [106.849925, 35.887476], + [106.912751, 35.93207], + [106.940468, 35.931101], + [106.93862, 35.952905], + [106.90228, 35.943699], + [106.94786, 35.988262], + [106.928149, 36.011502], + [106.940468, 36.064734], + [106.957715, 36.091337], + [106.925686, 36.115997], + [106.930613, 36.138716], + [106.873947, 36.178338], + [106.873947, 36.178338], + [106.858548, 36.206834], + [106.858548, 36.206834], + [106.833295, 36.229044], + [106.808657, 36.21118], + [106.772933, 36.212628], + [106.735976, 36.23725], + [106.698404, 36.244008], + [106.685469, 36.273445], + [106.647897, 36.259451], + [106.559202, 36.292259], + [106.54134, 36.25366], + [106.504383, 36.266207], + [106.470507, 36.306246], + [106.497608, 36.31348], + [106.510543, 36.379037], + [106.492064, 36.422389], + [106.523477, 36.468605], + [106.494528, 36.494589], + [106.455724, 36.496995], + [106.39721, 36.548455], + [106.37134, 36.549417], + [106.363949, 36.577296], + [106.392282, 36.556628], + [106.397826, 36.576816], + [106.444637, 36.557109], + [106.465579, 36.583063], + [106.444637, 36.624861], + [106.491448, 36.628703], + [106.490833, 36.685835], + [106.530869, 36.690154], + [106.519782, 36.708868], + [106.519782, 36.708868], + [106.514238, 36.715584], + [106.59431, 36.750118], + [106.644817, 36.72278], + [106.627571, 36.752995], + [106.657752, 36.820575], + [106.637426, 36.867031], + [106.637426, 36.867031], + [106.626955, 36.892403], + [106.609709, 36.878521], + [106.609709, 36.878521], + [106.601702, 36.918244], + [106.549347, 36.941685], + [106.540108, 36.984244], + [106.595542, 36.94025], + [106.594926, 36.967988], + [106.64297, 36.962729], + [106.646665, 37.000496], + [106.666991, 37.016745], + [106.645433, 37.064992], + [106.605397, 37.127524], + [106.6171, 37.135158], + [106.673151, 37.1113], + [106.687933, 37.12991], + [106.728585, 37.121321], + [106.750143, 37.09889], + [106.772933, 37.120367], + [106.776012, 37.158056], + [106.818512, 37.141838], + [106.891193, 37.098413], + [106.912135, 37.110345], + [106.905976, 37.151378], + [106.998367, 37.106527], + [107.031011, 37.108436], + [107.030395, 37.140883], + [107.095685, 37.115595], + [107.133873, 37.134681], + [107.181916, 37.143269], + [107.234887, 37.096503], + [107.268764, 37.099367] + ] + ], + [ + [ + [106.048588, 35.488898], + [105.897683, 35.451368], + [105.894603, 35.413821], + [106.002393, 35.438692], + [106.034422, 35.469404], + [106.054132, 35.45478], + [106.048588, 35.488898] + ] + ], + [ + [ + [106.073842, 35.45478], + [106.06953, 35.458193], + [106.071378, 35.449418], + [106.073226, 35.450393], + [106.073842, 35.45478] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 650000, + "name": "Xinjiang", + "center": [87.617733, 43.792818], + "centroid": [85.294711, 41.371801], + "childrenNum": 24, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 30, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [96.386348, 42.727592], + [96.363558, 42.900562], + [95.921314, 43.229789], + [95.880046, 43.28035], + [95.857872, 43.417436], + [95.735916, 43.597569], + [95.705735, 43.67077], + [95.645373, 43.787966], + [95.623199, 43.855756], + [95.527113, 44.007466], + [95.426099, 44.009618], + [95.377439, 44.025972], + [95.326932, 44.028554], + [95.35157, 44.090054], + [95.355882, 44.166087], + [95.376208, 44.227444], + [95.4107, 44.245024], + [95.43041, 44.281882], + [95.41378, 44.298589], + [95.238853, 44.277169], + [95.1286, 44.269884], + [94.998637, 44.253169], + [94.945666, 44.292592], + [94.826174, 44.320001], + [94.768275, 44.34055], + [94.722696, 44.34055], + [94.673421, 44.397021], + [94.606283, 44.448311], + [94.557008, 44.462408], + [94.470777, 44.509373], + [94.390705, 44.521749], + [94.359292, 44.515775], + [94.329727, 44.582734], + [94.279836, 44.603617], + [94.227481, 44.645785], + [94.215162, 44.667921], + [94.152336, 44.684944], + [94.066105, 44.732154], + [93.723642, 44.865498], + [93.716251, 44.894334], + [93.613389, 44.926546], + [93.509296, 44.968055], + [93.434767, 44.955351], + [93.376869, 44.985412], + [93.314659, 44.995147], + [93.314043, 44.980333], + [93.252449, 44.991761], + [93.174225, 45.015458], + [93.100312, 45.007419], + [93.062124, 45.018419], + [93.002377, 45.009958], + [92.932776, 45.017573], + [92.922921, 45.03703], + [92.884117, 45.046756], + [92.847777, 45.038721], + [92.779407, 45.050561], + [92.683937, 45.02561], + [92.547814, 45.018419], + [92.501003, 45.001072], + [92.414155, 45.018419], + [92.348866, 45.014188], + [92.315605, 45.028994], + [92.240461, 45.015881], + [92.100026, 45.081417], + [92.056911, 45.086911], + [91.885679, 45.078882], + [91.803144, 45.082685], + [91.694738, 45.065357], + [91.561695, 45.075501], + [91.500101, 45.103809], + [91.448978, 45.156586], + [91.429268, 45.156586], + [91.37753, 45.11099], + [91.33503, 45.129571], + [91.242023, 45.13717], + [91.230936, 45.153632], + [91.195827, 45.159118], + [91.17119, 45.199616], + [91.129922, 45.21606], + [91.050466, 45.208892], + [91.007966, 45.218589], + [90.96177, 45.201303], + [90.881698, 45.192025], + [90.866916, 45.209314], + [90.897713, 45.249776], + [90.877387, 45.280946], + [90.831807, 45.300313], + [90.804706, 45.29484], + [90.813329, 45.32851], + [90.773909, 45.405874], + [90.772677, 45.432338], + [90.723402, 45.464667], + [90.671047, 45.487747], + [90.676591, 45.582488], + [90.714779, 45.728895], + [90.799778, 45.834905], + [90.890937, 45.921566], + [91.028292, 46.023054], + [91.014741, 46.06667], + [91.021517, 46.121038], + [90.98456, 46.160431], + [90.94822, 46.219262], + [90.955611, 46.233752], + [90.900177, 46.31235], + [90.983328, 46.374734], + [90.996263, 46.419309], + [91.025828, 46.444057], + [91.038147, 46.500936], + [91.060937, 46.516999], + [91.079415, 46.558989], + [91.068328, 46.579149], + [91.017821, 46.58244], + [91.036299, 46.670393], + [91.054161, 46.717598], + [91.019053, 46.766402], + [90.992567, 46.769682], + [90.992567, 46.790583], + [90.942676, 46.82581], + [90.958075, 46.879425], + [90.929742, 46.893331], + [90.92235, 46.938707], + [90.901408, 46.960768], + [90.830575, 46.995883], + [90.767134, 46.992617], + [90.691989, 47.080717], + [90.653801, 47.111681], + [90.579888, 47.198364], + [90.56141, 47.206903], + [90.521374, 47.2845], + [90.488113, 47.317374], + [90.526301, 47.379007], + [90.507823, 47.400076], + [90.468403, 47.404937], + [90.459164, 47.43895], + [90.474562, 47.462422], + [90.468403, 47.497611], + [90.398186, 47.547724], + [90.376012, 47.603036], + [90.346447, 47.637324], + [90.384635, 47.644179], + [90.331665, 47.681663], + [90.216484, 47.70543], + [90.180144, 47.72516], + [90.13518, 47.723147], + [90.07605, 47.777469], + [90.070506, 47.820483], + [90.086521, 47.86547], + [90.066195, 47.883534], + [90.040941, 47.874704], + [89.960253, 47.885942], + [89.957789, 47.842982], + [89.86971, 47.834144], + [89.761921, 47.835751], + [89.735435, 47.89758], + [89.651052, 47.913627], + [89.645508, 47.947711], + [89.595617, 47.973359], + [89.599313, 48.015811], + [89.569132, 48.037825], + [89.498299, 48.02822], + [89.38127, 48.046227], + [89.359712, 48.026219], + [89.308589, 48.021816], + [89.282104, 47.994189], + [89.231597, 47.98017], + [89.156452, 47.996992], + [89.078228, 47.98698], + [89.044967, 48.009806], + [89.027105, 48.051028], + [88.953808, 48.090618], + [88.939026, 48.115396], + [88.824461, 48.107005], + [88.79736, 48.133772], + [88.721599, 48.160526], + [88.700657, 48.180881], + [88.668628, 48.171303], + [88.638447, 48.183674], + [88.601491, 48.221567], + [88.594716, 48.259831], + [88.575006, 48.277757], + [88.605803, 48.337863], + [88.573774, 48.351785], + [88.573158, 48.369679], + [88.535586, 48.368884], + [88.523267, 48.403461], + [88.503557, 48.412996], + [88.462289, 48.392335], + [88.438267, 48.393528], + [88.360659, 48.433251], + [88.363123, 48.460641], + [88.318159, 48.478497], + [88.229464, 48.498329], + [88.196819, 48.493967], + [88.151855, 48.526478], + [88.130297, 48.521721], + [88.10874, 48.545895], + [88.041602, 48.548272], + [87.973233, 48.575997], + [87.96153, 48.599353], + [88.010805, 48.618742], + [88.02682, 48.65315], + [88.089645, 48.69504], + [88.090877, 48.71992], + [88.064392, 48.712813], + [88.029283, 48.750313], + [87.96153, 48.773588], + [87.93874, 48.757809], + [87.872219, 48.799612], + [87.826639, 48.800795], + [87.803234, 48.824835], + [87.829103, 48.825623], + [87.792147, 48.849258], + [87.78106, 48.872094], + [87.742256, 48.881146], + [87.760118, 48.925992], + [87.793995, 48.927565], + [87.814321, 48.945256], + [87.87653, 48.949186], + [87.871603, 48.963726], + [87.911639, 48.979833], + [87.883922, 48.993971], + [87.883306, 49.023806], + [87.835263, 49.054406], + [87.858052, 49.07362], + [87.844502, 49.090084], + [87.867291, 49.108892], + [87.845733, 49.146096], + [87.82048, 49.148445], + [87.821096, 49.173883], + [87.793379, 49.18249], + [87.762582, 49.172709], + [87.700372, 49.175839], + [87.67635, 49.15549], + [87.602437, 49.152359], + [87.563017, 49.142572], + [87.517438, 49.145704], + [87.49588, 49.132001], + [87.511894, 49.10184], + [87.43675, 49.075188], + [87.388707, 49.097921], + [87.304939, 49.112418], + [87.239033, 49.114376], + [87.211932, 49.140615], + [87.112766, 49.15549], + [87.088128, 49.133567], + [87.000049, 49.142572], + [86.953853, 49.131218], + [86.887948, 49.132001], + [86.854071, 49.109284], + [86.84976, 49.066563], + [86.836209, 49.051269], + [86.772151, 49.02773], + [86.732115, 48.994757], + [86.730267, 48.959797], + [86.757985, 48.894919], + [86.782006, 48.887049], + [86.821426, 48.850439], + [86.818963, 48.831139], + [86.770303, 48.810255], + [86.754289, 48.78463], + [86.780774, 48.731369], + [86.771535, 48.717156], + [86.70255, 48.666195], + [86.693311, 48.64366], + [86.640956, 48.629027], + [86.635413, 48.612016], + [86.594761, 48.576789], + [86.579978, 48.538763], + [86.416138, 48.481671], + [86.38103, 48.49357], + [86.305269, 48.491984], + [86.270161, 48.452307], + [86.225813, 48.432456], + [86.053966, 48.441192], + [85.916612, 48.438015], + [85.791576, 48.418954], + [85.758315, 48.403064], + [85.695489, 48.335078], + [85.695489, 48.302445], + [85.678243, 48.266205], + [85.633895, 48.232731], + [85.622193, 48.202824], + [85.587084, 48.191654], + [85.576613, 48.15853], + [85.55136, 48.127781], + [85.551975, 48.081423], + [85.531649, 48.046227], + [85.547048, 48.008205], + [85.617881, 47.550552], + [85.614801, 47.498015], + [85.685018, 47.428829], + [85.701649, 47.384275], + [85.675779, 47.321837], + [85.701033, 47.28856], + [85.682555, 47.249982], + [85.682555, 47.222757], + [85.641903, 47.18413], + [85.582772, 47.142626], + [85.547048, 47.096609], + [85.545816, 47.057891], + [85.441106, 47.063191], + [85.355491, 47.054629], + [85.325926, 47.044842], + [85.276651, 47.068898], + [85.213825, 47.041172], + [85.175637, 46.997924], + [85.102956, 46.968936], + [85.082014, 46.939933], + [84.987159, 46.918272], + [84.979768, 46.883106], + [84.95513, 46.861013], + [84.934188, 46.863878], + [84.867051, 46.927673], + [84.849189, 46.957092], + [84.781435, 46.979962], + [84.748175, 47.009759], + [84.699515, 47.008535], + [84.668718, 46.995067], + [84.563393, 46.991801], + [84.506726, 46.97302], + [84.425422, 47.008943], + [84.37122, 46.993434], + [84.336727, 47.00527], + [84.2893, 46.994658], + [84.195061, 47.003638], + [84.150098, 46.977512], + [84.086656, 46.965261], + [84.038613, 46.973428], + [84.002888, 46.990576], + [83.951765, 46.98731], + [83.932671, 46.970161], + [83.88586, 46.982003], + [83.766367, 47.026896], + [83.69923, 47.015472], + [83.700462, 47.032199], + [83.576042, 47.059114], + [83.566803, 47.080717], + [83.53847, 47.083977], + [83.463325, 47.132042], + [83.418978, 47.119012], + [83.370318, 47.178436], + [83.324739, 47.167858], + [83.306261, 47.179656], + [83.257602, 47.173147], + [83.221877, 47.186977], + [83.207094, 47.213814], + [83.17445, 47.218286], + [83.15474, 47.236168], + [83.108544, 47.221944], + [83.02724, 47.21544], + [83.031552, 47.168265], + [82.993364, 47.065229], + [82.937929, 47.014248], + [82.923762, 46.932169], + [82.876335, 46.823762], + [82.878183, 46.797138], + [82.829524, 46.772551], + [82.788872, 46.677784], + [82.774089, 46.600124], + [82.726662, 46.494756], + [82.609017, 46.294985], + [82.518474, 46.153798], + [82.461808, 45.97982], + [82.401446, 45.972333], + [82.342932, 45.935303], + [82.336156, 45.882418], + [82.349707, 45.822811], + [82.340468, 45.772742], + [82.289961, 45.71636], + [82.288729, 45.655321], + [82.266555, 45.620172], + [82.281954, 45.53891], + [82.448257, 45.461309], + [82.546808, 45.426038], + [82.60101, 45.346178], + [82.58746, 45.224069], + [82.562822, 45.204676], + [82.487061, 45.181058], + [82.344779, 45.219011], + [82.294272, 45.247669], + [82.206809, 45.236713], + [82.109491, 45.211422], + [82.091012, 45.222383], + [82.09594, 45.249776], + [82.052824, 45.255674], + [81.993078, 45.237978], + [81.921013, 45.233342], + [81.879745, 45.284314], + [81.832318, 45.319673], + [81.78797, 45.3836], + [81.677101, 45.35459], + [81.645072, 45.359216], + [81.582863, 45.336503], + [81.575471, 45.30789], + [81.536667, 45.304101], + [81.52866, 45.285999], + [81.462754, 45.264099], + [81.437501, 45.28263], + [81.398697, 45.275471], + [81.382066, 45.257781], + [81.327864, 45.260729], + [81.284748, 45.23882], + [81.236705, 45.247248], + [81.175111, 45.227863], + [81.170183, 45.211001], + [81.111669, 45.218168], + [81.080872, 45.182745], + [81.024821, 45.162916], + [80.966307, 45.168402], + [80.93551, 45.160384], + [80.897938, 45.127459], + [80.862214, 45.127037], + [80.816634, 45.152788], + [80.731634, 45.156164], + [80.686055, 45.129148], + [80.599207, 45.105921], + [80.519135, 45.108878], + [80.493882, 45.127037], + [80.445839, 45.097895], + [80.443991, 45.077614], + [80.404571, 45.049293], + [80.358375, 45.040836], + [80.328194, 45.070007], + [80.291854, 45.06578], + [80.24381, 45.031532], + [80.195767, 45.030686], + [80.144644, 45.059017], + [80.136021, 45.041259], + [80.111999, 45.052675], + [80.060876, 45.026033], + [80.056565, 45.011227], + [79.98142, 44.964244], + [79.951855, 44.957892], + [79.944464, 44.937985], + [79.887798, 44.90917], + [79.969102, 44.877797], + [79.953703, 44.849377], + [79.991891, 44.830281], + [79.999283, 44.793768], + [80.087978, 44.817122], + [80.115695, 44.815424], + [80.169898, 44.84471], + [80.18776, 44.825612], + [80.178521, 44.796741], + [80.200695, 44.756808], + [80.238883, 44.7228], + [80.313412, 44.704938], + [80.400259, 44.628751], + [80.411962, 44.605321], + [80.350368, 44.484615], + [80.383013, 44.401297], + [80.399027, 44.30587], + [80.413194, 44.264741], + [80.400875, 44.198704], + [80.407034, 44.149772], + [80.3941, 44.127009], + [80.449534, 44.078017], + [80.458773, 44.047054], + [80.457541, 43.981203], + [80.485259, 43.95579], + [80.475404, 43.938124], + [80.511128, 43.906657], + [80.522215, 43.816473], + [80.75504, 43.494329], + [80.761199, 43.446554], + [80.746417, 43.439167], + [80.735946, 43.389609], + [80.686055, 43.333916], + [80.69283, 43.32042], + [80.777214, 43.308227], + [80.769207, 43.265535], + [80.788917, 43.242433], + [80.789533, 43.201876], + [80.804315, 43.178314], + [80.79446, 43.137277], + [80.752576, 43.148194], + [80.73225, 43.131163], + [80.706997, 43.143828], + [80.650946, 43.147321], + [80.593048, 43.133347], + [80.556092, 43.104515], + [80.482795, 43.06955], + [80.416889, 43.05687], + [80.378701, 43.031502], + [80.397795, 42.996933], + [80.487106, 42.948766], + [80.5912, 42.923354], + [80.602903, 42.894424], + [80.503737, 42.882146], + [80.450766, 42.861971], + [80.407034, 42.834767], + [80.338049, 42.831695], + [80.280151, 42.838278], + [80.262289, 42.828623], + [80.259209, 42.790865], + [80.225948, 42.713083], + [80.228412, 42.692852], + [80.179753, 42.670415], + [80.163738, 42.629919], + [80.180985, 42.590718], + [80.221637, 42.533415], + [80.265368, 42.502097], + [80.225948, 42.485769], + [80.206238, 42.431462], + [80.239499, 42.389927], + [80.229028, 42.358536], + [80.283847, 42.320493], + [80.272144, 42.281984], + [80.29247, 42.259842], + [80.28631, 42.233261], + [80.233339, 42.210215], + [80.168666, 42.200462], + [80.163738, 42.152563], + [80.139717, 42.151232], + [80.16805, 42.096635], + [80.193303, 42.081535], + [80.14218, 42.03488], + [80.089826, 42.047325], + [79.923522, 42.042436], + [79.852689, 42.015319], + [79.854537, 41.984186], + [79.822508, 41.963275], + [79.776313, 41.89248], + [79.724574, 41.896935], + [79.640806, 41.884907], + [79.616784, 41.856385], + [79.550879, 41.834094], + [79.500988, 41.835432], + [79.457256, 41.847915], + [79.415372, 41.836769], + [79.356242, 41.795735], + [79.326061, 41.809565], + [79.276786, 41.78101], + [79.271858, 41.767174], + [79.21704, 41.725648], + [79.138199, 41.722968], + [79.10925, 41.697503], + [79.043345, 41.681414], + [79.021787, 41.657273], + [78.99407, 41.664427], + [78.957729, 41.65146], + [78.891824, 41.597777], + [78.86657, 41.593749], + [78.825302, 41.560173], + [78.739071, 41.555695], + [78.696571, 41.54181], + [78.707042, 41.522098], + [78.675629, 41.50238], + [78.650375, 41.467411], + [78.580774, 41.481759], + [78.527188, 41.440947], + [78.454507, 41.412228], + [78.391681, 41.408189], + [78.385522, 41.394721], + [78.338094, 41.397415], + [78.324544, 41.384395], + [78.235232, 41.399211], + [78.163783, 41.383497], + [78.149617, 41.368228], + [78.165015, 41.340825], + [78.136682, 41.279239], + [78.129291, 41.228398], + [78.094798, 41.224347], + [77.972842, 41.173013], + [77.905089, 41.185174], + [77.836104, 41.153189], + [77.814546, 41.13426], + [77.807155, 41.091876], + [77.829328, 41.059394], + [77.796068, 41.049014], + [77.780669, 41.022832], + [77.737553, 41.032313], + [77.684583, 41.00793], + [77.654402, 41.016059], + [77.597119, 41.005221], + [77.591576, 40.992122], + [77.540453, 41.006575], + [77.476395, 40.999349], + [77.473931, 41.022832], + [77.415417, 41.038633], + [77.363062, 41.04089], + [77.296541, 41.004769], + [77.236795, 41.027798], + [77.169041, 41.009285], + [77.108063, 41.038181], + [77.091433, 41.062553], + [77.023064, 41.059394], + [77.002122, 41.073381], + [76.940528, 41.028701], + [76.885709, 41.027347], + [76.85368, 40.97631], + [76.817956, 40.975406], + [76.761905, 40.954167], + [76.741579, 40.912119], + [76.731724, 40.818887], + [76.693536, 40.779472], + [76.646725, 40.759983], + [76.646725, 40.73686], + [76.676906, 40.696036], + [76.654732, 40.652917], + [76.657196, 40.620218], + [76.611, 40.601591], + [76.601145, 40.578868], + [76.556798, 40.542495], + [76.543247, 40.513837], + [76.539551, 40.464226], + [76.508754, 40.429613], + [76.470566, 40.422779], + [76.442233, 40.391336], + [76.390494, 40.37766], + [76.381871, 40.39088], + [76.333212, 40.343459], + [76.327668, 40.391336], + [76.283321, 40.415034], + [76.279625, 40.439179], + [76.22419, 40.401819], + [76.176147, 40.381307], + [76.144118, 40.393615], + [76.081293, 40.39635], + [76.048648, 40.388601], + [76.048648, 40.357141], + [76.026474, 40.355317], + [75.986438, 40.381763], + [75.932235, 40.339353], + [75.921764, 40.291439], + [75.890351, 40.30924], + [75.84046, 40.312434], + [75.831221, 40.327492], + [75.785642, 40.301025], + [75.739446, 40.299199], + [75.709265, 40.280939], + [75.688323, 40.343915], + [75.669845, 40.363982], + [75.686475, 40.418223], + [75.717272, 40.443278], + [75.733287, 40.474242], + [75.646439, 40.516567], + [75.631041, 40.548862], + [75.627345, 40.605226], + [75.636584, 40.624306], + [75.599628, 40.659727], + [75.550353, 40.64883], + [75.467817, 40.599773], + [75.432093, 40.563412], + [75.355716, 40.537947], + [75.292274, 40.483802], + [75.268869, 40.483802], + [75.242383, 40.448743], + [75.206659, 40.447833], + [75.13521, 40.463315], + [75.102565, 40.44009], + [75.051442, 40.449654], + [75.021877, 40.466958], + [74.995392, 40.455119], + [74.963363, 40.464681], + [74.891914, 40.507467], + [74.844486, 40.521117], + [74.819233, 40.505647], + [74.814921, 40.461039], + [74.795211, 40.443278], + [74.908544, 40.338897], + [74.862965, 40.32658], + [74.824776, 40.344371], + [74.700357, 40.346195], + [74.697893, 40.310153], + [74.673255, 40.278656], + [74.618437, 40.27957], + [74.577169, 40.260391], + [74.534669, 40.207851], + [74.485394, 40.182251], + [74.433039, 40.13148], + [74.356662, 40.089371], + [74.316626, 40.106767], + [74.280902, 40.09807], + [74.26304, 40.125074], + [74.126301, 40.104479], + [74.113366, 40.086624], + [74.023439, 40.085251], + [74.008041, 40.050901], + [73.943367, 40.016076], + [73.980324, 40.004617], + [73.910722, 39.934443], + [73.907027, 39.873843], + [73.845433, 39.831115], + [73.841737, 39.756163], + [73.905795, 39.741899], + [73.924273, 39.722108], + [73.953838, 39.600018], + [73.916266, 39.586644], + [73.914418, 39.564041], + [73.883621, 39.540969], + [73.893476, 39.528046], + [73.868223, 39.482794], + [73.836194, 39.472169], + [73.745651, 39.462005], + [73.6471, 39.474479], + [73.61076, 39.465702], + [73.592898, 39.412087], + [73.502355, 39.383877], + [73.554094, 39.350102], + [73.554709, 39.295935], + [73.542391, 39.269531], + [73.564564, 39.266288], + [73.580579, 39.237555], + [73.623079, 39.235237], + [73.639709, 39.220402], + [73.657571, 39.166136], + [73.688368, 39.154999], + [73.719781, 39.108112], + [73.720397, 39.071881], + [73.743187, 39.029588], + [73.780143, 39.026798], + [73.820179, 39.041674], + [73.839889, 39.008199], + [73.846665, 38.962145], + [73.826339, 38.916993], + [73.767824, 38.941202], + [73.742571, 38.933754], + [73.70931, 38.893241], + [73.699455, 38.857832], + [73.729636, 38.837324], + [73.769056, 38.775765], + [73.757353, 38.719755], + [73.809092, 38.634256], + [73.799237, 38.610878], + [73.852208, 38.584217], + [73.89902, 38.579071], + [73.926121, 38.536016], + [74.011736, 38.52478], + [74.034526, 38.541634], + [74.090577, 38.542102], + [74.068403, 38.585621], + [74.088113, 38.610878], + [74.11275, 38.611345], + [74.147859, 38.676785], + [74.229779, 38.656224], + [74.353583, 38.655757], + [74.421952, 38.647812], + [74.455829, 38.632853], + [74.506336, 38.637528], + [74.546988, 38.607604], + [74.613509, 38.593105], + [74.639995, 38.599653], + [74.717603, 38.542102], + [74.78474, 38.538357], + [74.821697, 38.491062], + [74.862965, 38.484035], + [74.868508, 38.403883], + [74.834015, 38.361193], + [74.789668, 38.324581], + [74.806914, 38.285602], + [74.793363, 38.271039], + [74.816769, 38.215576], + [74.80445, 38.167128], + [74.821697, 38.10311], + [74.879595, 38.021122], + [74.92579, 38.01735], + [74.911008, 37.966884], + [74.919015, 37.908357], + [74.936877, 37.876241], + [74.917167, 37.845057], + [74.989848, 37.797783], + [75.006478, 37.770823], + [74.949196, 37.725395], + [74.923327, 37.717347], + [74.920863, 37.684675], + [74.891914, 37.668097], + [74.940573, 37.559061], + [75.000935, 37.53059], + [75.002167, 37.511604], + [75.035428, 37.500685], + [75.078543, 37.511129], + [75.090862, 37.486915], + [75.129666, 37.459367], + [75.153072, 37.414223], + [75.125971, 37.388075], + [75.140137, 37.355258], + [75.125971, 37.322427], + [75.078543, 37.318144], + [75.018181, 37.293867], + [74.927022, 37.277678], + [74.911008, 37.233378], + [74.816153, 37.216699], + [74.800139, 37.248147], + [74.753943, 37.281011], + [74.727458, 37.282916], + [74.665864, 37.23576], + [74.642458, 37.261485], + [74.598727, 37.258151], + [74.578401, 37.231472], + [74.54514, 37.2491], + [74.511263, 37.240048], + [74.477387, 37.19954], + [74.487858, 37.161871], + [74.465068, 37.147085], + [74.496481, 37.116072], + [74.498944, 37.072155], + [74.530357, 37.082182], + [74.56793, 37.032512], + [74.617205, 37.043499], + [74.632603, 37.066425], + [74.70898, 37.084569], + [74.739161, 37.028212], + [74.792747, 37.027257], + [74.806914, 37.054485], + [74.84695, 37.056873], + [74.84387, 37.0134], + [74.86974, 36.990458], + [74.893762, 36.939772], + [74.938725, 36.94312], + [74.927638, 36.978029], + [75.005862, 36.99476], + [75.032348, 37.016745], + [75.063145, 37.006231], + [75.172166, 37.013877], + [75.16847, 36.991892], + [75.244847, 36.963207], + [75.288579, 36.974682], + [75.345861, 36.960816], + [75.413614, 36.954599], + [75.396368, 36.904367], + [75.430245, 36.873255], + [75.434556, 36.83303], + [75.425933, 36.778883], + [75.458578, 36.720861], + [75.504773, 36.743404], + [75.536802, 36.729975], + [75.537418, 36.773131], + [75.588541, 36.762584], + [75.634121, 36.771693], + [75.724048, 36.750597], + [75.8072, 36.707908], + [75.871257, 36.666636], + [75.947018, 36.590752], + [75.924228, 36.566242], + [75.991981, 36.505654], + [76.035097, 36.409386], + [75.991365, 36.35205], + [75.998757, 36.312034], + [76.055423, 36.252695], + [76.060967, 36.225182], + [76.011691, 36.229044], + [76.016619, 36.165294], + [75.96796, 36.159013], + [75.936547, 36.13485], + [75.949482, 36.070056], + [75.982742, 36.031347], + [76.028322, 36.016827], + [76.044336, 36.026991], + [76.097307, 36.022635], + [76.117017, 35.975186], + [76.16506, 35.908807], + [76.146582, 35.839946], + [76.160133, 35.82442], + [76.221727, 35.823449], + [76.228502, 35.837035], + [76.298719, 35.841401], + [76.365857, 35.82442], + [76.369552, 35.86323], + [76.431762, 35.851589], + [76.471798, 35.886021], + [76.51553, 35.881173], + [76.55803, 35.923347], + [76.59745, 35.895718], + [76.579587, 35.866625], + [76.587595, 35.840431], + [76.566037, 35.819082], + [76.593754, 35.771996], + [76.69292, 35.747714], + [76.769297, 35.653917], + [76.848753, 35.668018], + [76.906651, 35.615005], + [76.967013, 35.591649], + [76.99781, 35.611113], + [77.072339, 35.591162], + [77.093281, 35.569746], + [77.195527, 35.519103], + [77.307628, 35.540533], + [77.331649, 35.530793], + [77.355055, 35.494257], + [77.396939, 35.467942], + [77.451758, 35.46063], + [77.518895, 35.482075], + [77.578025, 35.47574], + [77.590344, 35.460143], + [77.639619, 35.45478], + [77.657481, 35.477689], + [77.690742, 35.448443], + [77.735706, 35.461605], + [77.757879, 35.497181], + [77.797299, 35.491334], + [77.816394, 35.518616], + [77.85643, 35.487436], + [77.870596, 35.495232], + [77.914944, 35.465017], + [77.917408, 35.490847], + [77.951284, 35.478664], + [78.009799, 35.491821], + [78.029509, 35.469404], + [78.048603, 35.491334], + [78.140378, 35.494745], + [78.113892, 35.466967], + [78.107117, 35.437229], + [78.046755, 35.384063], + [78.013494, 35.366008], + [78.020885, 35.315237], + [78.01719, 35.228267], + [78.060306, 35.180344], + [78.062769, 35.114772], + [78.078784, 35.100084], + [78.124979, 35.108407], + [78.150849, 35.069721], + [78.123131, 35.036897], + [78.160704, 34.990823], + [78.201972, 34.974642], + [78.182262, 34.936874], + [78.206283, 34.891726], + [78.237696, 34.882398], + [78.230921, 34.776288], + [78.21429, 34.760556], + [78.213059, 34.717771], + [78.267261, 34.705472], + [78.265413, 34.651335], + [78.280812, 34.623269], + [78.346101, 34.60406], + [78.397224, 34.605538], + [78.427405, 34.594207], + [78.436029, 34.543942], + [78.492695, 34.578441], + [78.542586, 34.574499], + [78.559832, 34.55725], + [78.562912, 34.51288], + [78.58139, 34.505483], + [78.634977, 34.538026], + [78.708274, 34.522249], + [78.715049, 34.502031], + [78.758781, 34.481807], + [78.742766, 34.45467], + [78.809288, 34.432955], + [78.878273, 34.391481], + [78.899831, 34.354929], + [78.958961, 34.386049], + [78.973128, 34.362833], + [79.039649, 34.33467], + [79.048888, 34.348506], + [79.0107, 34.399877], + [79.039033, 34.421601], + [79.072294, 34.412714], + [79.161605, 34.441345], + [79.179467, 34.422588], + [79.241677, 34.415183], + [79.274322, 34.435916], + [79.326677, 34.44332], + [79.363017, 34.428018], + [79.435082, 34.447761], + [79.504683, 34.45467], + [79.545335, 34.476381], + [79.58106, 34.456151], + [79.675914, 34.451216], + [79.699936, 34.477861], + [79.735661, 34.471447], + [79.801566, 34.478847], + [79.861312, 34.528166], + [79.84345, 34.55725], + [79.88595, 34.642965], + [79.866856, 34.671517], + [79.906892, 34.683821], + [79.898268, 34.732035], + [79.947544, 34.821008], + [79.926602, 34.849499], + [79.961094, 34.862759], + [79.996819, 34.856375], + [80.003594, 34.895162], + [80.034391, 34.902033], + [80.041782, 34.943252], + [80.02392, 34.971209], + [80.04363, 35.022196], + [80.031311, 35.034447], + [80.078123, 35.076578], + [80.118159, 35.066293], + [80.23026, 35.147565], + [80.223484, 35.177409], + [80.257977, 35.203331], + [80.362687, 35.20871], + [80.267832, 35.295701], + [80.286926, 35.35283], + [80.321419, 35.38699], + [80.375006, 35.387966], + [80.432904, 35.449418], + [80.444607, 35.417235], + [80.514824, 35.391869], + [80.532686, 35.404553], + [80.56841, 35.391381], + [80.599823, 35.409431], + [80.65649, 35.393821], + [80.690982, 35.364544], + [80.689135, 35.339162], + [80.759968, 35.334768], + [80.844351, 35.345508], + [80.894242, 35.324027], + [80.924423, 35.330862], + [80.963844, 35.310842], + [81.026053, 35.31133], + [81.002648, 35.334768], + [81.030981, 35.337209], + [81.031597, 35.380648], + [81.054387, 35.402602], + [81.09935, 35.40748], + [81.103662, 35.386015], + [81.142466, 35.365032], + [81.191741, 35.36552], + [81.219458, 35.319144], + [81.26627, 35.322562], + [81.285364, 35.345508], + [81.314313, 35.337209], + [81.363588, 35.354783], + [81.385762, 35.335256], + [81.441196, 35.333303], + [81.447972, 35.318167], + [81.504638, 35.279092], + [81.513261, 35.23511], + [81.68634, 35.235599], + [81.736847, 35.26248], + [81.804601, 35.270786], + [81.853876, 35.25857], + [81.927789, 35.271275], + [81.955506, 35.307423], + [81.99123, 35.30547], + [82.030034, 35.321585], + [82.05344, 35.35039], + [82.029419, 35.426013], + [82.034346, 35.451855], + [82.071302, 35.450393], + [82.086701, 35.467454], + [82.164925, 35.495719], + [82.189563, 35.513258], + [82.234526, 35.520565], + [82.263475, 35.547837], + [82.2992, 35.544916], + [82.328149, 35.559523], + [82.350323, 35.611113], + [82.336156, 35.651486], + [82.392823, 35.656349], + [82.424852, 35.712736], + [82.468583, 35.717595], + [82.501844, 35.701073], + [82.546192, 35.708362], + [82.628727, 35.692324], + [82.652133, 35.67288], + [82.731589, 35.637868], + [82.780249, 35.666073], + [82.795031, 35.688436], + [82.873871, 35.688922], + [82.894813, 35.673852], + [82.967494, 35.667532], + [82.956407, 35.636409], + [82.981661, 35.599922], + [82.971806, 35.548324], + [82.998907, 35.484512], + [83.067892, 35.46258], + [83.088834, 35.425526], + [83.127022, 35.398699], + [83.178145, 35.38943], + [83.251442, 35.417722], + [83.280391, 35.401138], + [83.333978, 35.397236], + [83.405427, 35.380648], + [83.449159, 35.382111], + [83.502745, 35.360639], + [83.540318, 35.364056], + [83.54155, 35.341603], + [83.599448, 35.351366], + [83.622238, 35.335256], + [83.677672, 35.361128], + [83.785462, 35.36308], + [83.79778, 35.354783], + [83.885244, 35.367472], + [83.906186, 35.40309], + [84.005968, 35.422599], + [84.077417, 35.400163], + [84.095895, 35.362592], + [84.140859, 35.379184], + [84.160569, 35.359663], + [84.200605, 35.381135], + [84.274517, 35.404065], + [84.333032, 35.413821], + [84.424191, 35.466479], + [84.45314, 35.473303], + [84.475929, 35.516181], + [84.448828, 35.550272], + [84.513502, 35.564391], + [84.570168, 35.588242], + [84.628067, 35.595055], + [84.704443, 35.616951], + [84.729081, 35.613546], + [84.798066, 35.647595], + [84.920022, 35.696213], + [84.973608, 35.709334], + [84.99455, 35.737028], + [85.053065, 35.752086], + [85.146071, 35.742371], + [85.271107, 35.788989], + [85.341324, 35.753543], + [85.373969, 35.700101], + [85.518715, 35.680658], + [85.566142, 35.6403], + [85.612953, 35.651486], + [85.65299, 35.731199], + [85.691178, 35.751114], + [85.811286, 35.778794], + [85.835308, 35.771996], + [85.903677, 35.78462], + [85.949256, 35.778794], + [86.035488, 35.846738], + [86.05335, 35.842857], + [86.090306, 35.876809], + [86.093386, 35.906868], + [86.129111, 35.941761], + [86.150668, 36.00424], + [86.173458, 36.008113], + [86.199944, 36.047801], + [86.182081, 36.064734], + [86.187625, 36.130983], + [86.248603, 36.141616], + [86.2794, 36.170608], + [86.35824, 36.168676], + [86.392733, 36.206834], + [86.454943, 36.221319], + [86.515305, 36.205385], + [86.531935, 36.227113], + [86.599072, 36.222285], + [86.69947, 36.24449], + [86.746282, 36.291777], + [86.836209, 36.291294], + [86.86331, 36.299977], + [86.887332, 36.262829], + [86.931064, 36.265242], + [86.943998, 36.284058], + [86.996353, 36.308658], + [87.051788, 36.2966], + [87.08628, 36.310587], + [87.149106, 36.297565], + [87.161425, 36.325535], + [87.193454, 36.349158], + [87.292004, 36.358797], + [87.348055, 36.393008], + [87.363453, 36.420463], + [87.386859, 36.412757], + [87.426895, 36.42576], + [87.460155, 36.409868], + [87.470626, 36.354459], + [87.570409, 36.342409], + [87.6203, 36.360243], + [87.731785, 36.384818], + [87.767509, 36.3747], + [87.826023, 36.391563], + [87.838342, 36.383855], + [87.919646, 36.39349], + [87.95845, 36.408423], + [87.983088, 36.437797], + [88.006494, 36.430575], + [88.092109, 36.43539], + [88.134609, 36.427205], + [88.182652, 36.452721], + [88.222688, 36.447426], + [88.241782, 36.468605], + [88.282434, 36.470049], + [88.366202, 36.458016], + [88.356963, 36.477268], + [88.41055, 36.473418], + [88.470912, 36.48208], + [88.498629, 36.446463], + [88.573158, 36.461386], + [88.618121, 36.428168], + [88.623665, 36.389636], + [88.690186, 36.367954], + [88.766563, 36.292259], + [88.783809, 36.291777], + [88.802903, 36.33807], + [88.838628, 36.353496], + [88.870657, 36.348193], + [88.926091, 36.36458], + [88.964279, 36.318785], + [89.013554, 36.315409], + [89.054822, 36.291777], + [89.10225, 36.281164], + [89.126887, 36.254626], + [89.198952, 36.260417], + [89.232213, 36.295636], + [89.292575, 36.231457], + [89.335075, 36.23725], + [89.375727, 36.228078], + [89.490291, 36.151281], + [89.594385, 36.126632], + [89.614711, 36.109712], + [89.711414, 36.093272], + [89.766848, 36.073925], + [89.819819, 36.080697], + [89.914058, 36.079246], + [89.941159, 36.067637], + [89.944855, 36.140649], + [89.997825, 36.168193], + [90.019999, 36.213594], + [90.028006, 36.258486], + [90.003369, 36.278752], + [90.043405, 36.276822], + [90.058188, 36.255591], + [90.145651, 36.239181], + [90.130252, 36.2078], + [90.198006, 36.187516], + [90.23681, 36.160462], + [90.325505, 36.159496], + [90.424055, 36.133883], + [90.478258, 36.13195], + [90.534925, 36.147899], + [90.613149, 36.126632], + [90.659344, 36.13485], + [90.776373, 36.086501], + [90.815793, 36.035703], + [90.850285, 36.016827], + [90.922966, 36.028927], + [90.979017, 36.106811], + [91.081263, 36.088436], + [91.124994, 36.115514], + [91.09235, 36.163844], + [91.096045, 36.219871], + [91.051698, 36.238215], + [91.07264, 36.299012], + [91.026444, 36.323607], + [91.051698, 36.433946], + [91.028292, 36.443093], + [91.039995, 36.474861], + [91.035683, 36.529703], + [91.011662, 36.539801], + [90.905104, 36.560474], + [90.831191, 36.55807], + [90.810865, 36.585466], + [90.741264, 36.585947], + [90.72217, 36.620058], + [90.730793, 36.655594], + [90.706156, 36.658955], + [90.720938, 36.708868], + [90.754815, 36.721341], + [90.727098, 36.755872], + [90.732025, 36.825844], + [90.758511, 36.825844], + [90.853981, 36.915373], + [90.924198, 36.921115], + [90.983944, 36.913459], + [91.036915, 36.929727], + [91.051698, 36.96751], + [91.126842, 36.978507], + [91.133618, 37.007665], + [91.181045, 37.025345], + [91.216153, 37.010054], + [91.303617, 37.012444], + [91.291298, 37.042544], + [91.303617, 37.083136], + [91.286371, 37.105095], + [91.280211, 37.163779], + [91.1909, 37.205737], + [91.194596, 37.273868], + [91.134849, 37.324331], + [91.136081, 37.355734], + [91.113292, 37.387124], + [91.099741, 37.447965], + [91.073256, 37.475992], + [91.019669, 37.493088], + [90.958075, 37.477891], + [90.911879, 37.519674], + [90.865684, 37.53059], + [90.882314, 37.575664], + [90.854597, 37.604117], + [90.820104, 37.613599], + [90.777605, 37.648672], + [90.643946, 37.696988], + [90.586663, 37.703144], + [90.579272, 37.720661], + [90.519526, 37.730601], + [90.516446, 38.207111], + [90.531229, 38.319886], + [90.401882, 38.311434], + [90.361846, 38.300163], + [90.352607, 38.233441], + [90.280542, 38.238142], + [90.137644, 38.340543], + [90.179528, 38.396848], + [90.129636, 38.400131], + [90.111774, 38.418889], + [90.111774, 38.477945], + [90.130868, 38.494341], + [90.248513, 38.491531], + [90.315034, 38.501835], + [90.353222, 38.482162], + [90.427135, 38.493873], + [90.465323, 38.521971], + [90.463476, 38.556611], + [90.525685, 38.561291], + [90.560794, 38.593573], + [90.608837, 38.594508], + [90.606374, 38.610878], + [90.645794, 38.635191], + [90.619308, 38.664636], + [90.65996, 38.674449], + [90.724634, 38.658094], + [90.899561, 38.679588], + [90.970394, 38.697806], + [90.992567, 38.695003], + [91.188436, 38.73096], + [91.242639, 38.752433], + [91.298689, 38.746365], + [91.446515, 38.813546], + [91.501333, 38.815411], + [91.681188, 38.852706], + [91.694738, 38.86622], + [91.806223, 38.872744], + [91.87952, 38.884391], + [91.880752, 38.899297], + [91.966368, 38.930961], + [92.10865, 38.963541], + [92.173323, 38.960749], + [92.197961, 38.983548], + [92.263866, 39.002153], + [92.380279, 38.999828], + [92.416003, 39.010524], + [92.41046, 39.03842], + [92.366728, 39.059335], + [92.366112, 39.096037], + [92.343938, 39.146181], + [92.339011, 39.236628], + [92.378431, 39.258411], + [92.52564, 39.368611], + [92.639589, 39.514196], + [92.687632, 39.657174], + [92.745531, 39.868331], + [92.796654, 40.153897], + [92.906907, 40.310609], + [92.920458, 40.391792], + [92.928465, 40.572504], + [93.506216, 40.648376], + [93.760599, 40.664721], + [93.820961, 40.793519], + [93.809874, 40.879548], + [93.908424, 40.983539], + [94.01067, 41.114875], + [94.184365, 41.268444], + [94.534219, 41.505966], + [94.750413, 41.538227], + [94.809543, 41.619256], + [94.861898, 41.668451], + [94.969072, 41.718948], + [95.011572, 41.726541], + [95.110738, 41.768513], + [95.135991, 41.772976], + [95.16494, 41.735474], + [95.199433, 41.719395], + [95.194505, 41.694821], + [95.247476, 41.61344], + [95.299831, 41.565994], + [95.335556, 41.644305], + [95.39407, 41.693481], + [95.445193, 41.719841], + [95.57146, 41.796181], + [95.65646, 41.826067], + [95.759322, 41.835878], + [95.801206, 41.848361], + [95.855408, 41.849699], + [95.998306, 41.906289], + [96.054973, 41.936124], + [96.117183, 41.985966], + [96.137509, 42.019765], + [96.13874, 42.05399], + [96.077147, 42.149457], + [96.178161, 42.21775], + [96.040806, 42.326688], + [96.042038, 42.352787], + [96.06606, 42.414674], + [95.978596, 42.436762], + [96.0174, 42.482239], + [96.02356, 42.542675], + [96.072219, 42.569566], + [96.103632, 42.604375], + [96.166458, 42.623314], + [96.386348, 42.727592] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 710000, + "name": "Taiwan", + "center": [121.509062, 25.044332], + "centroid": [120.971485, 23.749452], + "childrenNum": 0, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 31, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [120.443706, 22.441432], + [120.517619, 22.408793], + [120.569973, 22.361757], + [120.640806, 22.241605], + [120.659285, 22.154056], + [120.661748, 22.067007], + [120.651277, 22.033171], + [120.667908, 21.983235], + [120.701784, 21.927174], + [120.743052, 21.915515], + [120.781857, 21.923843], + [120.854537, 21.883309], + [120.873016, 21.897191], + [120.86624, 21.984345], + [120.907508, 22.033171], + [120.912436, 22.086418], + [120.903197, 22.12634], + [120.914899, 22.302525], + [120.981421, 22.528248], + [121.014682, 22.584069], + [121.03316, 22.650914], + [121.078739, 22.669691], + [121.170514, 22.723247], + [121.21055, 22.770711], + [121.237652, 22.836362], + [121.276456, 22.877171], + [121.324499, 22.945526], + [121.35468, 23.00999], + [121.370695, 23.084334], + [121.409499, 23.1025], + [121.430441, 23.137175], + [121.415042, 23.196047], + [121.440296, 23.271937], + [121.479716, 23.322507], + [121.497578, 23.419744], + [121.5216, 23.483431], + [121.522832, 23.538858], + [121.587505, 23.760878], + [121.621382, 23.920718], + [121.65957, 24.007125], + [121.63986, 24.064514], + [121.643556, 24.097843], + [121.678048, 24.133895], + [121.689135, 24.174303], + [121.809243, 24.339083], + [121.82649, 24.423572], + [121.867758, 24.47914], + [121.88562, 24.529784], + [121.892395, 24.617953], + [121.86283, 24.671261], + [121.841272, 24.734329], + [121.844968, 24.836476], + [121.933047, 24.938539], + [122.012503, 25.001471], + [121.98109, 25.030757], + [121.947214, 25.031841], + [121.917033, 25.138076], + [121.841888, 25.135367], + [121.782142, 25.160287], + [121.745186, 25.161912], + [121.707613, 25.191701], + [121.700222, 25.226896], + [121.655259, 25.242054], + [121.62323, 25.29455], + [121.585041, 25.309159], + [121.53515, 25.307535], + [121.444607, 25.27074], + [121.413194, 25.238806], + [121.371926, 25.159746], + [121.319572, 25.140785], + [121.209318, 25.12724], + [121.132942, 25.078466], + [121.102145, 25.075214], + [121.024537, 25.040517], + [121.009754, 24.993878], + [120.961095, 24.940167], + [120.914899, 24.864715], + [120.89211, 24.767482], + [120.82374, 24.688118], + [120.762147, 24.658208], + [120.68885, 24.600542], + [120.642654, 24.490033], + [120.589068, 24.43229], + [120.546568, 24.370159], + [120.520698, 24.311816], + [120.470807, 24.242533], + [120.451713, 24.182493], + [120.391967, 24.118055], + [120.316206, 23.984708], + [120.278018, 23.92783], + [120.245989, 23.840276], + [120.175156, 23.807427], + [120.102476, 23.701162], + [120.095084, 23.58768], + [120.12157, 23.504836], + [120.108019, 23.341191], + [120.081534, 23.291728], + [120.018708, 23.073322], + [120.029795, 23.048544], + [120.133272, 23.000625], + [120.149287, 22.896468], + [120.20041, 22.721039], + [120.274323, 22.560307], + [120.297112, 22.531565], + [120.443706, 22.441432] + ] + ], + [ + [ + [124.542782, 25.903886], + [124.584666, 25.908731], + [124.566804, 25.941563], + [124.542782, 25.903886] + ] + ], + [ + [ + [123.445178, 25.726102], + [123.469816, 25.712623], + [123.50862, 25.722867], + [123.512316, 25.755212], + [123.479055, 25.768687], + [123.445794, 25.749822], + [123.445178, 25.726102] + ] + ], + [ + [ + [119.646064, 23.550928], + [119.691028, 23.547087], + [119.678093, 23.600294], + [119.61034, 23.604132], + [119.601717, 23.575613], + [119.566608, 23.584937], + [119.562297, 23.530627], + [119.578927, 23.502641], + [119.609108, 23.503738], + [119.646064, 23.550928] + ] + ], + [ + [ + [123.666916, 25.914114], + [123.706952, 25.91519], + [123.689706, 25.939949], + [123.666916, 25.914114] + ] + ], + [ + [ + [119.506246, 23.625518], + [119.506246, 23.577259], + [119.47237, 23.556962], + [119.519181, 23.559705], + [119.52534, 23.62497], + [119.506246, 23.625518] + ] + ], + [ + [ + [119.497623, 23.38679], + [119.495159, 23.349982], + [119.516717, 23.349982], + [119.497623, 23.38679] + ] + ], + [ + [ + [119.557369, 23.666634], + [119.608492, 23.620035], + [119.615268, 23.661153], + [119.586318, 23.675952], + [119.557369, 23.666634] + ] + ], + [ + [ + [122.066706, 25.6247], + [122.087032, 25.61067], + [122.092575, 25.639268], + [122.066706, 25.6247] + ] + ], + [ + [ + [121.468013, 22.67687], + [121.474788, 22.643734], + [121.513592, 22.631582], + [121.514824, 22.676318], + [121.468013, 22.67687] + ] + ], + [ + [ + [121.510513, 22.086972], + [121.507433, 22.048704], + [121.533918, 22.022076], + [121.594281, 21.995443], + [121.604752, 22.022631], + [121.575186, 22.037055], + [121.575802, 22.0842], + [121.510513, 22.086972] + ] + ], + [ + [ + [122.097503, 25.499987], + [122.110438, 25.465952], + [122.122141, 25.495666], + [122.097503, 25.499987] + ] + ], + [ + [ + [119.421247, 23.216949], + [119.436029, 23.186146], + [119.453275, 23.216399], + [119.421247, 23.216949] + ] + ], + [ + [ + [120.355011, 22.327439], + [120.395663, 22.342385], + [120.383344, 22.355669], + [120.355011, 22.327439] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 810000, + "name": "HongKong", + "center": [114.173355, 22.320048], + "centroid": [114.134357, 22.377366], + "childrenNum": 18, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 32, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [114.031778, 22.503923], + [114.000981, 22.491206], + [113.977575, 22.45692], + [113.918445, 22.418199], + [113.920293, 22.367845], + [113.951706, 22.355116], + [113.956633, 22.359543], + [113.980039, 22.366185], + [114.026234, 22.34792], + [113.955401, 22.298649], + [113.969568, 22.321349], + [113.898119, 22.308615], + [113.889496, 22.271514], + [113.8433, 22.229418], + [113.84946, 22.191188], + [113.899351, 22.215568], + [113.935691, 22.205041], + [113.981271, 22.229972], + [113.996669, 22.206149], + [114.026234, 22.229418], + [114.004676, 22.239389], + [114.02993, 22.263207], + [114.034857, 22.300864], + [114.069966, 22.326885], + [114.121089, 22.320795], + [114.145726, 22.300864], + [114.120473, 22.272068], + [114.164821, 22.226648], + [114.200545, 22.232188], + [114.203009, 22.206703], + [114.265835, 22.200608], + [114.248588, 22.274837], + [114.262139, 22.294773], + [114.284929, 22.263761], + [114.313262, 22.264315], + [114.315726, 22.299203], + [114.315726, 22.299756], + [114.278153, 22.328546], + [114.283081, 22.386661], + [114.322501, 22.385554], + [114.323117, 22.385554], + [114.323733, 22.385001], + [114.323733, 22.384447], + [114.356994, 22.340171], + [114.394566, 22.361757], + [114.385327, 22.41156], + [114.406269, 22.432582], + [114.406269, 22.433688], + [114.376088, 22.436454], + [114.325581, 22.479041], + [114.278769, 22.435901], + [114.220255, 22.427603], + [114.205473, 22.449729], + [114.23319, 22.466875], + [114.2529, 22.445304], + [114.340979, 22.50337], + [114.309566, 22.497288], + [114.28924, 22.52272], + [114.263987, 22.541515], + [114.263371, 22.541515], + [114.260291, 22.547595], + [114.232574, 22.528801], + [114.232574, 22.539857], + [114.222719, 22.553122], + [114.166052, 22.559201], + [114.156813, 22.543726], + [114.095219, 22.534329], + [114.082285, 22.512216], + [114.031778, 22.503923] + ] + ], + [ + [ + [114.142647, 22.213906], + [114.123553, 22.238836], + [114.120473, 22.177888], + [114.154965, 22.177888], + [114.166668, 22.205041], + [114.142647, 22.213906] + ] + ], + [ + [ + [114.305871, 22.372273], + [114.313878, 22.340724], + [114.332972, 22.353455], + [114.305255, 22.372826], + [114.305871, 22.372273] + ] + ], + [ + [ + [114.320037, 22.381127], + [114.323733, 22.384447], + [114.323733, 22.385001], + [114.323117, 22.385554], + [114.322501, 22.385554], + [114.319421, 22.382234], + [114.320037, 22.38168], + [114.320037, 22.381127] + ] + ], + [ + [ + [114.305871, 22.369506], + [114.305871, 22.372273], + [114.305255, 22.372826], + [114.305871, 22.369506] + ] + ], + [ + [ + [114.315726, 22.299203], + [114.316958, 22.298649], + [114.316342, 22.30031], + [114.315726, 22.299756], + [114.315726, 22.299203] + ] + ], + [ + [ + [114.319421, 22.382234], + [114.320037, 22.381127], + [114.320037, 22.38168], + [114.319421, 22.382234] + ] + ], + [ + [ + [114.372392, 22.32301], + [114.373008, 22.323564], + [114.372392, 22.323564], + [114.372392, 22.32301] + ] + ], + [ + [ + [114.323733, 22.297541], + [114.324349, 22.297541], + [114.323733, 22.298095], + [114.323733, 22.297541] + ] + ] + ] + } + }, + { + "type": "Feature", + "properties": { + "adcode": 820000, + "name": "Macao", + "center": [113.54909, 22.198951], + "centroid": [113.566988, 22.159307], + "childrenNum": 8, + "level": "province", + "parent": { "adcode": 100000 }, + "subFeatureIndex": 33, + "acroutes": [100000] + }, + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [113.554425, 22.107489], + [113.6037, 22.132438], + [113.575983, 22.194513], + [113.558736, 22.212244], + [113.53841, 22.209473], + [113.534715, 22.174009], + [113.554425, 22.142416], + [113.554425, 22.107489] + ] + ], + [ + [ + [113.586453, 22.201162], + [113.575983, 22.201162], + [113.575983, 22.194513], + [113.586453, 22.201162] + ] + ] + ] + } + } + ] +} diff --git a/core/cmd/server/web/static/world.json b/core/cmd/server/web/static/world.json new file mode 100644 index 0000000..1f1dbb1 --- /dev/null +++ b/core/cmd/server/web/static/world.json @@ -0,0 +1,32100 @@ +{ + "type": "FeatureCollection", + "crs": { "type": "name", "properties": { "name": "urn:ogc:def:crs:OGC:1.3:CRS84" } }, + "features": [ + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [47.97822265625001, 7.9970703125], + [46.97822265625001, 7.9970703125], + [43.98378906250002, 9.008837890624989], + [43.482519531250006, 9.379492187499991], + [43.181640625, 9.879980468749991], + [42.84160156250002, 10.203076171874997], + [42.65644531250001, 10.6], + [42.92275390625002, 10.999316406249989], + [43.24599609375002, 11.499804687499989], + [43.85273437500001, 10.784277343749991], + [44.38652343750002, 10.430224609374989], + [44.94296875, 10.43671875], + [45.81669921875002, 10.835888671874997], + [46.565039062500006, 10.745996093749994], + [47.40498046875001, 11.174023437499997], + [48.01923828125001, 11.139355468749997], + [48.57255859375002, 11.320507812499997], + [48.938574218750006, 11.258447265624994], + [50.11005859375001, 11.529296875], + [50.79228515625002, 11.983691406249989], + [51.2548828125, 11.830712890624994], + [51.08427734375002, 11.335644531249997], + [51.140625, 10.656884765624994], + [51.031835937500006, 10.444775390624997], + [51.19296875, 10.554638671874997], + [51.390234375, 10.422607421875], + [50.93007812500002, 10.33554687499999], + [50.825, 9.428173828124997], + [50.10283203125002, 8.199804687499991], + [49.85205078125, 7.962548828124994], + [49.234960937500006, 6.77734375], + [49.04931640625, 6.173632812499989], + [47.97529296875001, 4.497021484374997], + [46.87880859375002, 3.28564453125], + [46.05117187500002, 2.475146484374989], + [44.92021484375002, 1.81015625], + [43.71757812500002, 0.857861328124997], + [41.97988281250002, -0.973046875], + [41.53271484375, -1.6953125], + [41.521875, -1.572265625], + [41.42695312500001, -1.449511718750003], + [41.24980468750002, -1.220507812500003], + [40.97871093750001, -0.870312500000011], + [40.964453125, 2.814648437499997], + [41.341796875, 3.20166015625], + [41.61347656250001, 3.590478515624994], + [41.88398437500001, 3.977734375], + [41.91533203125002, 4.031298828124989], + [42.02412109375001, 4.137939453125], + [42.85664062500001, 4.32421875], + [43.12568359375001, 4.644482421874997], + [43.58349609375, 4.85498046875], + [43.988867187500006, 4.950537109374991], + [44.940527343750006, 4.912011718749994], + [47.97822265625001, 7.9970703125] + ] + ] + }, + "properties": { "name": "Somalia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [9.579979133936737, 47.05856388629306], + [9.409458596647225, 47.02019676540292], + [9.46249431093294, 47.09010747968864], + [9.46249431093294, 47.19858962254578], + [9.527658197470123, 47.27026989773668], + [9.579979133936737, 47.05856388629306] + ] + ] + }, + "properties": { "name": "Liechtenstein", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-8.683349609375, 27.77800740805682], + [-13.038761787013554, 27.81190166624856], + [-12.948925781249926, 27.914160156250034], + [-11.552685546874955, 28.31010742187496], + [-10.486474609374994, 29.06494140625], + [-10.200585937499994, 29.380371093750057], + [-9.667089843749949, 30.10927734375005], + [-9.652929687499977, 30.447558593750045], + [-9.875488281249943, 30.717919921874966], + [-9.80869140624992, 31.42460937499996], + [-9.347460937499932, 32.086376953124955], + [-9.245849609375, 32.572460937499955], + [-8.512841796874994, 33.25244140625003], + [-6.900976562499949, 33.96904296874999], + [-6.353125, 34.77607421875001], + [-5.924804687499943, 35.78579101562502], + [-5.277832031249943, 35.90273437500002], + [-5.252685546874972, 35.61474609374997], + [-4.628320312499966, 35.206396484375006], + [-4.329980468749937, 35.161474609375006], + [-3.693261718749994, 35.27998046874998], + [-3.394726562499926, 35.21181640625005], + [-2.972216796874989, 35.40727539062499], + [-2.839941406249949, 35.127832031249994], + [-2.731396484374955, 35.13520507812498], + [-2.636816406249977, 35.11269531250002], + [-2.423730468749994, 35.12348632812498], + [-2.219628906249966, 35.10419921874998], + [-1.795605468749926, 34.751904296874955], + [-1.67919921875, 33.31865234375002], + [-1.550732421874955, 33.073583984375006], + [-1.510009765625, 32.877636718749955], + [-1.45, 32.784814453124966], + [-1.352148437499977, 32.70336914062497], + [-1.29638671875, 32.67568359375002], + [-1.188232421875, 32.608496093750006], + [-1.111035156249983, 32.55229492187502], + [-1.065527343749949, 32.46831054687496], + [-1.16259765625, 32.399169921875], + [-1.275341796874983, 32.089013671874966], + [-2.863427734374937, 32.07470703124997], + [-2.930859374999926, 32.04252929687499], + [-2.988232421874983, 31.874218749999983], + [-3.01738281249996, 31.834277343750017], + [-3.439794921874949, 31.704541015624983], + [-3.604589843749949, 31.686767578125], + [-3.700244140624989, 31.70009765625005], + [-3.768164062499977, 31.689550781250034], + [-3.837109374999983, 31.512353515624994], + [-3.833398437499937, 31.197802734375045], + [-3.626904296874955, 31.000927734374983], + [-4.148779296874977, 30.8095703125], + [-4.322851562500006, 30.698876953124994], + [-4.52915039062492, 30.62553710937499], + [-4.778515624999926, 30.552392578124994], + [-4.968261718749943, 30.465380859375045], + [-5.061914062499937, 30.326416015625057], + [-5.180126953124955, 30.166162109374994], + [-5.293652343749983, 30.058642578125045], + [-5.44877929687496, 29.956933593750023], + [-6.00429687499999, 29.83125], + [-6.479736328124943, 29.82036132812499], + [-6.520556640624989, 29.659863281249983], + [-6.59775390624992, 29.578955078125006], + [-6.635351562499949, 29.568798828124983], + [-6.755126953125, 29.583837890625034], + [-6.855566406249949, 29.601611328125017], + [-7.142431640624949, 29.61958007812504], + [-7.427685546874983, 29.425], + [-7.485742187499994, 29.392236328124994], + [-8.659912109375, 28.718603515625063], + [-8.683349609375, 27.900390625], + [-8.683349609375, 27.77800740805682] + ] + ] + }, + "properties": { "name": "Morocco", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-13.038761787013554, 27.81190166624856], + [-8.683349609375, 27.77800740805682], + [-8.683349609375, 27.65644531250004], + [-8.817822265624955, 27.65644531250004], + [-8.817822265624951, 27.656445312499997], + [-8.683349609375, 27.656445312499997], + [-8.683349609375, 27.2859375], + [-8.682861328125, 26.921337890624997], + [-8.6826171875, 26.72314453125], + [-8.682324218749983, 26.497705078124994], + [-8.68212890625, 26.273193359375], + [-8.68212890625, 26.10947265625], + [-8.682226562499977, 25.995507812499994], + [-12.016308593749983, 25.995410156250003], + [-12.016308593749983, 25.740136718749994], + [-12.016308593749983, 25.331689453124994], + [-12.016308593749983, 25.059375], + [-12.016308593749983, 24.923242187499994], + [-12.016308593749983, 24.378662109375], + [-12.016308593749983, 23.97021484375], + [-12.0234375, 23.467578125], + [-12.372900390624977, 23.318017578124994], + [-12.559375, 23.290820312500003], + [-12.620410156249989, 23.27133789062499], + [-13.031494140625, 23.000244140625], + [-13.153271484374983, 22.820507812499997], + [-13.12702845982141, 22.703770926339278], + [-13.136540684091575, 22.708182548616723], + [-13.094335937499977, 22.495996093749994], + [-13.051220703124983, 21.854785156250003], + [-13.041748046875, 21.713818359374997], + [-13.0322265625, 21.572070312500003], + [-13.025097656249983, 21.466796875], + [-13.016210937499977, 21.333935546874997], + [-15.231201171875, 21.331298828125], + [-16.964550781249983, 21.329248046874994], + [-17.06396484375, 20.89882812499999], + [-17.048046874999983, 20.80615234375], + [-17.098779296874994, 20.856884765624997], + [-16.930859374999983, 21.9], + [-16.35874023437495, 22.594531250000045], + [-16.21025390624999, 23.097900390625], + [-15.789257812499926, 23.792871093750023], + [-15.980712890624943, 23.670312500000023], + [-15.899316406249966, 23.844433593749955], + [-14.904296875000028, 24.719775390625017], + [-14.794921874999943, 25.404150390625006], + [-14.413867187499932, 26.25371093749999], + [-13.57578125, 26.735107421875], + [-13.175976562499983, 27.655712890624983], + [-13.038761787013554, 27.81190166624856] + ], + [ + [-8.774365234374983, 27.460546875], + [-8.794873046874983, 27.120703125000034], + [-8.794873046874983, 27.120703125], + [-8.774365234374983, 27.460546875] + ] + ] + }, + "properties": { "name": "W. Sahara", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [21.5625, 42.247509765625], + [21.560839843750017, 42.24765625], + [21.389550781250023, 42.21982421875], + [21.28662109375, 42.100390625], + [21.05976562500001, 42.171289062499994], + [20.778125, 42.071044921875], + [20.725, 41.87353515625], + [20.566210937500017, 41.873681640624994], + [20.485449218750006, 42.223388671875], + [20.06396484375, 42.54726562499999], + [20.054296875, 42.760058593749996], + [20.344335937500006, 42.827929687499996], + [20.40996305279786, 42.84373166741877], + [20.344335937500063, 42.82792968750002], + [19.670996093750006, 43.163964843749994], + [19.21875, 43.449951171875], + [19.196484375000068, 43.48500976562502], + [19.19160156250004, 43.52104492187499], + [19.19433593749997, 43.533300781250006], + [19.495117187500057, 43.642871093750045], + [19.245019531249994, 43.96503906250004], + [19.583789062500017, 44.04345703125003], + [19.118457031250074, 44.359960937500006], + [19.348632812500057, 44.88090820312502], + [19.007128906250045, 44.86918945312502], + [19.062890625000023, 45.13720703125], + [19.4, 45.2125], + [19.004687500000074, 45.39951171875006], + [19.064257812500045, 45.51499023437506], + [18.839062499999983, 45.83574218750002], + [18.905371093750006, 45.931738281250034], + [19.421289062500023, 46.064453125], + [19.61347656250001, 46.169189453125], + [19.84443359375001, 46.145898437499966], + [19.934082031250057, 46.161474609375034], + [20.161425781250017, 46.14189453124996], + [20.210156250000068, 46.12602539062502], + [20.241796875000034, 46.10859375000001], + [20.301367187500006, 46.05068359375002], + [20.35859375000004, 45.975488281249994], + [20.581152343749977, 45.86948242187506], + [20.65273437499999, 45.779394531250006], + [20.709277343750074, 45.735253906249994], + [20.727832031250017, 45.73740234374998], + [20.746875, 45.74897460937501], + [20.76015625000005, 45.75810546875002], + [20.775, 45.74980468750002], + [20.794042968750006, 45.467871093750034], + [21.431445312500017, 45.192529296874994], + [21.465429687500006, 45.171875], + [21.357031250000034, 44.99077148437502], + [21.532324218750063, 44.900683593750045], + [21.519921875000023, 44.88081054687498], + [21.442187500000074, 44.87338867187498], + [21.384375, 44.87006835937501], + [21.357910156250057, 44.86181640625003], + [21.36005859375004, 44.82666015624997], + [21.52314453125004, 44.79008789062499], + [21.63613281250005, 44.71044921875], + [21.909277343750034, 44.666113281250034], + [22.026953125, 44.61987304687503], + [22.093066406250074, 44.541943359374955], + [22.200976562500017, 44.560693359374966], + [22.350683593750063, 44.676123046875034], + [22.497656249999977, 44.70625], + [22.64208984375, 44.65097656249998], + [22.720898437499983, 44.605517578125045], + [22.734375, 44.56992187499998], + [22.700781250000063, 44.55551757812498], + [22.620117187500057, 44.562353515625034], + [22.554003906250017, 44.54033203124999], + [22.49453125000005, 44.43544921875002], + [22.687890625000023, 44.248291015625], + [22.42080078125005, 44.00742187500006], + [22.399023437500063, 43.96953125], + [22.36542968750004, 43.86210937500002], + [22.36962890625003, 43.78129882812499], + [22.55458984375005, 43.45449218750002], + [22.767578125, 43.35415039062502], + [22.81972656250005, 43.300732421874955], + [22.85957031250001, 43.252343749999966], + [22.97685546874999, 43.18798828125], + [22.799902343750006, 42.985742187499994], + [22.706152343750006, 42.88393554687505], + [22.466796875, 42.842480468749955], + [22.53242187500004, 42.48120117187497], + [22.523535156250006, 42.440966796875045], + [22.44570312500005, 42.35913085937497], + [22.42207031250004, 42.32885742187503], + [22.344042968750045, 42.31396484375003], + [22.23974609375003, 42.303110028468716], + [21.81464843750001, 42.303125], + [21.5625, 42.24750976562498], + [21.5625, 42.247509765625] + ] + ] + }, + "properties": { "name": "Serbia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [74.54140625000002, 37.02216796875], + [74.03886718750002, 36.825732421874996], + [73.116796875, 36.868554687499994], + [72.24980468750002, 36.734716796875], + [71.23291015625, 36.12177734375], + [71.18505859375, 36.04208984375], + [71.57197265625001, 35.546826171875], + [71.62050781250002, 35.183007812499994], + [70.965625, 34.53037109375], + [71.095703125, 34.369433593749996], + [71.05156250000002, 34.049707031249994], + [70.65400390625001, 33.952294921874994], + [69.8896484375, 34.007275390625], + [70.26113281250002, 33.289013671875], + [69.5015625, 33.020068359374996], + [69.24140625000001, 32.433544921875], + [69.279296875, 31.936816406249996], + [68.86894531250002, 31.634228515624997], + [68.59765625, 31.802978515625], + [68.16103515625002, 31.802978515625], + [67.57822265625, 31.506494140624994], + [67.737890625, 31.343945312499997], + [67.45283203125001, 31.234619140625], + [66.82929687500001, 31.263671875], + [66.346875, 30.802783203124996], + [66.23125, 29.86572265625], + [65.09550781250002, 29.559472656249994], + [64.39375, 29.544335937499994], + [64.09873046875, 29.391943359375], + [63.56757812500001, 29.497998046874997], + [62.4765625, 29.408349609374994], + [62.0009765625, 29.530419921874994], + [61.22441406250002, 29.749414062499994], + [60.843359375, 29.858691406249996], + [61.331640625, 30.363720703124997], + [61.55947265625002, 30.599365234375], + [61.7841796875, 30.831933593749994], + [61.81083984375002, 30.91328125], + [61.81425781250002, 31.072558593749996], + [61.75507812500001, 31.285302734374994], + [61.66015625, 31.382421875], + [61.34648437500002, 31.421630859375], + [61.11074218750002, 31.451123046874997], + [60.854101562500006, 31.483251953125], + [60.82070312500002, 31.495166015624996], + [60.791601562500006, 31.660595703124997], + [60.804296875, 31.73447265625], + [60.7875, 31.877197265625], + [60.78994140625002, 31.987109375], + [60.827246093750006, 32.16796875], + [60.82929687500001, 32.249414062499994], + [60.71044921875, 32.6], + [60.57656250000002, 32.994873046875], + [60.560546875, 33.137841796874994], + [60.9169921875, 33.505224609375], + [60.573828125, 33.588330078125], + [60.4859375, 33.7119140625], + [60.48574218750002, 34.094775390624996], + [60.642675781250006, 34.307177734374996], + [60.88945312500002, 34.31943359375], + [60.80390625000001, 34.418017578124996], + [60.76259765625002, 34.475244140624994], + [60.73613281250002, 34.491796875], + [60.72626953125001, 34.51826171875], + [60.73945312500001, 34.544726562499996], + [60.80234375, 34.554638671875], + [60.8453125, 34.587695312499996], + [60.91474609375001, 34.633984375], + [60.951171875, 34.653857421874996], + [61.080078125, 34.855615234374994], + [61.1, 35.272314453125], + [61.18925781250002, 35.31201171875], + [61.24550781250002, 35.474072265625], + [61.27851562500001, 35.51376953125], + [61.281835937500006, 35.55341796875], + [61.26201171875002, 35.619580078125], + [61.3447265625, 35.6294921875], + [61.62099609375002, 35.43232421875], + [62.08964843750002, 35.3796875], + [62.30781250000001, 35.170800781249994], + [62.688085937500006, 35.255322265625], + [63.056640625, 35.44580078125], + [63.08417968750001, 35.56806640625], + [63.16972656250002, 35.678125], + [63.129980468750006, 35.84619140625], + [63.8625, 36.012353515624994], + [64.184375, 36.14892578125], + [64.51103515625002, 36.340673828125], + [64.56582031250002, 36.427587890625], + [64.6025390625, 36.554541015625], + [64.78242187500001, 37.05927734375], + [64.81630859375002, 37.132080078125], + [64.95156250000002, 37.1935546875], + [65.08964843750002, 37.237939453124994], + [65.30361328125002, 37.24677734375], + [65.55498046875002, 37.251171875], + [65.76503906250002, 37.569140625], + [66.471875, 37.3447265625], + [66.52226562500002, 37.348486328125], + [66.827734375, 37.3712890625], + [67.06884765625, 37.334814453125], + [67.19550781250001, 37.235205078125], + [67.31972656250002, 37.2095703125], + [67.44169921875002, 37.2580078125], + [67.51728515625001, 37.266650390624996], + [67.546484375, 37.235644531249996], + [67.607421875, 37.222509765625], + [67.7, 37.22724609375], + [67.7529296875, 37.1998046875], + [67.75898437500001, 37.172216796875], + [67.76601562500002, 37.14013671875], + [67.83447265625, 37.064208984375], + [67.9580078125, 36.972021484375], + [68.06777343750002, 36.9498046875], + [68.26093750000001, 37.013085937499994], + [68.284765625, 37.036328125], + [68.29951171875001, 37.088427734374996], + [68.38691406250001, 37.1375], + [68.66914062500001, 37.2583984375], + [68.7232421875, 37.268017578125], + [68.78203125000002, 37.2580078125], + [68.82373046875, 37.270703125], + [68.8384765625, 37.30283203125], + [68.85537109375002, 37.316845703125], + [68.88525390625, 37.328076171875], + [68.91181640625001, 37.333935546875], + [68.96044921875, 37.325048828125], + [69.18017578125, 37.15830078125], + [69.26484375000001, 37.1083984375], + [69.30390625000001, 37.116943359375], + [69.35380859375002, 37.150048828124994], + [69.41445312500002, 37.207763671875], + [69.4296875, 37.290869140625], + [69.39921875000002, 37.399316406249994], + [69.42011718750001, 37.48671875], + [69.49208984375002, 37.553076171875], + [69.62578125000002, 37.594042968749996], + [69.8208984375, 37.6095703125], + [69.9849609375, 37.566162109375], + [70.18867187500001, 37.582470703125], + [70.25146484375, 37.66416015625], + [70.25498046875, 37.765380859375], + [70.19941406250001, 37.886035156249996], + [70.21464843750002, 37.9244140625], + [70.41777343750002, 38.075439453125], + [70.7359375, 38.42255859375], + [71.255859375, 38.306982421875], + [71.33271484375001, 38.170263671875], + [71.27851562500001, 37.918408203125], + [71.319921875, 37.90185546875], + [71.3896484375, 37.906298828124996], + [71.48779296875, 37.931884765625], + [71.55195312500001, 37.933154296874996], + [71.58222656250001, 37.910107421875], + [71.43291015625002, 37.1275390625], + [71.530859375, 36.845117187499994], + [71.665625, 36.696923828124994], + [72.65742187500001, 37.029052734375], + [72.8955078125, 37.267529296875], + [73.21113281250001, 37.408496093749996], + [73.38291015625, 37.462255859375], + [73.48134765625002, 37.4716796875], + [73.60468750000001, 37.446044921875], + [73.65712890625002, 37.43046875], + [73.72060546875002, 37.41875], + [73.73378906250002, 37.37578125], + [73.71728515625, 37.329443359375], + [73.6275390625, 37.261572265625], + [73.65351562500001, 37.23935546875], + [73.749609375, 37.231787109375], + [74.16708984375, 37.329443359375], + [74.20351562500002, 37.372460937499994], + [74.25966796875002, 37.415429687499994], + [74.659375, 37.394482421875], + [74.73056640625, 37.35703125], + [74.83046875000002, 37.2859375], + [74.89130859375001, 37.231640625], + [74.84023437500002, 37.225048828125], + [74.76738281250002, 37.249169921874994], + [74.73896484375001, 37.28564453125], + [74.72666015625, 37.29072265625], + [74.6689453125, 37.26669921875], + [74.55898437500002, 37.236621093749996], + [74.37216796875, 37.15771484375], + [74.37617187500001, 37.137353515624994], + [74.49794921875002, 37.0572265625], + [74.52646484375, 37.030664062499994], + [74.54140625000002, 37.02216796875] + ] + ] + }, + "properties": { "name": "Afghanistan", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [17.57958984375, -8.099023437500009], + [17.643359375000017, -8.090722656250009], + [18.00878906250003, -8.107617187499983], + [18.56269531250001, -7.9359375], + [18.944433593750063, -8.001464843750028], + [19.142675781250034, -8.001464843750028], + [19.34082031249997, -7.966601562500031], + [19.369921875000045, -7.706542968749986], + [19.371679687500063, -7.655078124999989], + [19.47988281250008, -7.472167968750028], + [19.48378906250008, -7.279492187500026], + [19.527636718750017, -7.144433593749952], + [19.87519531250004, -6.986328124999986], + [19.99746093750008, -6.976464843750023], + [20.190039062500063, -6.9462890625], + [20.482226562500074, -6.915820312500017], + [20.59003906250001, -6.919921874999957], + [20.598730468750006, -6.935156249999949], + [20.536914062500045, -7.121777343749955], + [20.535839843749983, -7.182812499999955], + [20.558398437500045, -7.244433593749989], + [20.60781250000008, -7.277734375000023], + [20.910937500000017, -7.281445312499983], + [21.190332031250023, -7.284960937499989], + [21.751074218750034, -7.305468749999989], + [21.80605468750005, -7.32861328125], + [21.905371093750034, -8.693359374999943], + [21.813183593750068, -9.46875], + [22.19775390625, -10.040625], + [22.30703125000005, -10.691308593750023], + [22.203515625000023, -10.829492187500009], + [22.226171875, -11.121972656250009], + [22.27880859375, -11.19414062499996], + [22.314941406250057, -11.198632812499994], + [22.39296875000005, -11.159472656250003], + [22.486132812500045, -11.086718750000017], + [22.56103515625003, -11.05585937500004], + [22.814746093750017, -11.08027343750004], + [23.076269531250006, -11.087890624999986], + [23.463964843750034, -10.969335937499991], + [23.83388671875008, -11.013671874999972], + [23.96650390625001, -10.871777343750011], + [23.98388671875, -11.725], + [23.909375, -12.636132812500009], + [23.886523437500045, -12.743261718749991], + [23.882421875, -12.799023437499983], + [23.968066406250045, -12.956933593749994], + [23.962988281250006, -12.988476562500026], + [23.843164062500023, -13.0009765625], + [22.209570312500006, -13.0009765625], + [21.97890625000008, -13.0009765625], + [21.979101562500034, -13.798730468749994], + [21.979296875000074, -14.11962890625], + [21.979394531249994, -14.440527343750006], + [21.97978515624999, -15.955566406250014], + [22.193945312500006, -16.628125], + [23.380664062500017, -17.640625], + [22.32421875, -17.8375], + [20.74550781250008, -18.019726562499983], + [20.194335937500057, -17.86367187499999], + [18.95527343750004, -17.80351562499999], + [18.39638671875005, -17.3994140625], + [16.14843750000003, -17.39023437499999], + [14.017480468750023, -17.40888671874997], + [13.475976562500023, -17.04003906249997], + [13.179492187500017, -16.971679687499986], + [12.548144531250017, -17.212695312499974], + [12.35927734375008, -17.205859375], + [12.318457031250006, -17.21337890625003], + [12.213378906250028, -17.209960937500043], + [12.013964843750074, -17.168554687500034], + [11.902539062500011, -17.226562499999957], + [11.743066406250023, -17.24921875000004], + [11.780078125000017, -16.87128906249997], + [11.818945312500034, -16.704101562500014], + [11.750878906250023, -15.831933593749966], + [12.016113281250057, -15.513671874999957], + [12.55048828125004, -13.437792968750003], + [12.983203124999989, -12.775683593750017], + [13.4169921875, -12.52041015624998], + [13.597949218750017, -12.286132812500028], + [13.785351562499983, -11.81279296874996], + [13.833593750000063, -10.9296875], + [13.33222656250004, -9.998925781250009], + [12.99853515625, -9.048046875], + [13.358984375, -8.687207031250026], + [13.378515625000063, -8.369726562500006], + [12.82343750000004, -6.954785156249955], + [12.283300781250063, -6.12431640624996], + [13.184375, -5.85625], + [13.346484375000017, -5.863378906250006], + [13.978515625, -5.857226562500003], + [16.315234375000074, -5.865625], + [16.431445312500045, -5.90019531249996], + [16.53710937499997, -5.9658203125], + [16.697265625, -6.164257812500026], + [16.74296874999999, -6.618457031250003], + [16.813085937500063, -6.772558593749963], + [16.919433593750057, -6.93398437499998], + [16.98476562500005, -7.257421874999977], + [17.57958984375, -8.099023437500009] + ] + ], + [ + [ + [12.50146484375, -4.5875], + [12.848144531250028, -4.428906249999983], + [12.881054687500068, -4.445117187499989], + [12.971386718750068, -4.551757812499957], + [13.048046875000068, -4.619238281250034], + [13.072753906250028, -4.634765625], + [13.057324218750011, -4.651074218750026], + [12.947460937500011, -4.695312499999986], + [12.829687499999977, -4.73662109374996], + [12.451464843750017, -5.071484374999969], + [12.453222656250034, -5.090625], + [12.52236328125008, -5.148925781250028], + [12.484570312500011, -5.71875], + [12.213671875000074, -5.758691406249994], + [12.177148437499994, -5.324804687499977], + [12.018359375000074, -5.004296874999966], + [12.50146484375, -4.5875] + ] + ] + ] + }, + "properties": { "name": "Angola", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [20.06396484375, 42.54726562499999], + [20.485449218750006, 42.223388671875], + [20.566210937500017, 41.873681640624994], + [20.48896484375001, 41.272607421874994], + [20.709277343750017, 40.928369140624994], + [20.964257812500023, 40.849902343749996], + [20.95576171875001, 40.775292968749994], + [21.030859375, 40.622460937499994], + [20.881640625000017, 40.467919921874994], + [20.65742187500001, 40.1173828125], + [20.4560546875, 40.065576171874994], + [20.408007812500017, 40.049462890624994], + [20.383691406250023, 40.0171875], + [20.338476562500006, 39.991064453125], + [20.311132812500006, 39.979443359375], + [20.311328125000017, 39.95078125], + [20.381640625000017, 39.841796875], + [20.382421875, 39.802636718749994], + [20.206835937500017, 39.653515625], + [20.13105468750001, 39.66162109375], + [20.05976562500001, 39.699121093749994], + [20.022558593750006, 39.710693359375], + [20.001269531250017, 39.709423828125], + [19.851855468750017, 40.0435546875], + [19.322265625, 40.407080078125], + [19.45917968750001, 40.40537109375], + [19.3375, 40.663818359375], + [19.57568359375, 41.640429687499996], + [19.577539062500023, 41.7875], + [19.342382812500006, 41.869091796875], + [19.280664062500023, 42.17255859375], + [19.65449218750001, 42.628564453124994], + [19.78828125000001, 42.476171875], + [20.06396484375, 42.54726562499999] + ] + ] + }, + "properties": { "name": "Albania", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [19.66230468750001, 60.187158203124994], + [19.53652343750005, 60.14497070312501], + [19.551367187500063, 60.24384765625001], + [19.66230468750001, 60.187158203124994] + ] + ], + [ + [ + [19.989550781250074, 60.351171875], + [20.258886718750063, 60.26127929687499], + [19.799804687500057, 60.08173828125001], + [19.68691406250005, 60.267626953125045], + [19.84765625000003, 60.22055664062506], + [19.823046875000074, 60.390185546875045], + [19.989550781250074, 60.351171875] + ] + ] + ] + }, + "properties": { "name": "Aland", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [1.7060546875, 42.503320312499994], + [1.534082031250023, 42.441699218749996], + [1.448828125, 42.437451171875], + [1.428125, 42.46132812499999], + [1.414843750000017, 42.548388671874996], + [1.428320312500006, 42.5958984375], + [1.501367187500023, 42.642724609374994], + [1.568164062500017, 42.635009765625], + [1.709863281250023, 42.604443359375], + [1.739453125000011, 42.575927734375], + [1.740234375, 42.55673828125], + [1.713964843750006, 42.525634765625], + [1.7060546875, 42.503320312499994] + ] + ] + }, + "properties": { "name": "Andorra", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [53.927832031250006, 24.177197265624983], + [53.63447265625004, 24.169775390624977], + [53.83378906250002, 24.258935546875023], + [53.927832031250006, 24.177197265624983] + ] + ], + [ + [ + [53.3322265625001, 24.258593750000045], + [53.19091796874997, 24.290917968749966], + [53.412402343750074, 24.411035156250023], + [53.3322265625001, 24.258593750000045] + ] + ], + [ + [ + [56.29785156250003, 25.650683593750045], + [56.38798828125002, 24.97919921875004], + [56.06386718750005, 24.73876953125], + [56.00058593750006, 24.953222656249977], + [55.795703125000074, 24.868115234374955], + [55.76083984375006, 24.24267578125], + [55.92861328125005, 24.215136718750074], + [55.98515625000002, 24.063378906249966], + [55.4684570312501, 23.94111328125001], + [55.53164062499999, 23.81904296875001], + [55.1999023437501, 23.034765625000034], + [55.185839843750074, 22.7041015625], + [55.104296875000074, 22.621484375000023], + [52.55507812500005, 22.932812499999955], + [51.592578125000074, 24.07885742187503], + [51.56835937500003, 24.286181640625074], + [51.76757812500003, 24.25439453125], + [51.84316406250005, 24.010888671875023], + [52.118554687499994, 23.97109375], + [52.64824218750002, 24.154638671875006], + [53.80175781249997, 24.069482421874966], + [54.14794921875003, 24.17119140624999], + [54.39707031250006, 24.278173828125034], + [54.74677734375004, 24.810449218750023], + [55.94121093750002, 25.793994140625017], + [56.08046875, 26.06264648437505], + [56.16748046875003, 26.047460937499977], + [56.144628906250006, 25.690527343750006], + [56.29785156250003, 25.650683593750045] + ] + ] + ] + }, + "properties": { "name": "United Arab Emirates", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-64.54916992187498, -54.71621093749998], + [-63.81542968749997, -54.725097656250014], + [-64.637353515625, -54.90253906250001], + [-64.75732421875, -54.82656249999999], + [-64.54916992187498, -54.71621093749998] + ] + ], + [ + [ + [-68.65322265624994, -54.85361328124999], + [-68.62993164062499, -52.65263671875004], + [-68.24013671875, -53.08183593749999], + [-68.43115234375, -53.0552734375], + [-68.48852539062497, -53.260937499999976], + [-68.16113281249997, -53.30644531250001], + [-68.00849609374995, -53.5640625], + [-67.29423828125002, -54.049804687500014], + [-66.23564453124999, -54.53349609374997], + [-65.17900390624993, -54.678125], + [-65.47114257812495, -54.91464843749999], + [-66.5111328125, -55.032128906249945], + [-67.127099609375, -54.90380859375001], + [-68.65322265624994, -54.85361328124999] + ] + ], + [ + [ + [-61.084716796875, -23.65644531250001], + [-60.83984375000003, -23.85810546874997], + [-59.89248046874994, -24.093554687499974], + [-59.18725585937497, -24.56230468749999], + [-57.82167968749994, -25.136425781249983], + [-57.56313476562494, -25.473730468749963], + [-57.943115234375, -26.05292968750001], + [-58.18149414062498, -26.30742187499999], + [-58.222070312499994, -26.65], + [-58.618603515624955, -27.13212890624996], + [-58.64174804687494, -27.196093750000017], + [-58.60483398437498, -27.314355468750037], + [-58.16826171874993, -27.27343749999997], + [-56.437158203124966, -27.553808593749977], + [-56.16406250000003, -27.321484374999983], + [-55.95146484374996, -27.325683593749957], + [-55.789990234374926, -27.416406249999966], + [-55.71464843749996, -27.41484375], + [-55.632910156250006, -27.35712890624997], + [-55.59379882812502, -27.288085937500014], + [-55.597265625, -27.207617187499963], + [-55.56489257812498, -27.15], + [-55.496728515624966, -27.11533203124999], + [-55.45063476562498, -27.068359375000014], + [-55.426660156249994, -27.00927734374997], + [-55.13593750000001, -26.931152343749957], + [-54.934472656249994, -26.70253906250001], + [-54.677734375, -26.308789062499997], + [-54.631933593750006, -26.005761718749994], + [-54.615869140624994, -25.576074218750023], + [-54.44394531249998, -25.625], + [-54.15458984374999, -25.523046874999963], + [-53.89116210937499, -25.66884765625001], + [-53.668554687500006, -26.288183593749977], + [-53.83818359375002, -27.121093750000014], + [-54.32700195312495, -27.423535156249997], + [-54.82910156250003, -27.55058593750003], + [-55.10151367187501, -27.866796874999963], + [-55.72548828125002, -28.20410156250003], + [-55.68725585937497, -28.38164062499996], + [-55.890527343749994, -28.370019531249994], + [-56.938623046874994, -29.594824218750034], + [-57.22465820312499, -29.782128906249994], + [-57.40522460937501, -30.03388671875004], + [-57.563867187499994, -30.139941406249974], + [-57.60888671875003, -30.187792968750045], + [-57.65087890624997, -30.295019531250034], + [-57.71269531249996, -30.38447265624997], + [-57.83120117187502, -30.495214843749963], + [-57.87250976562501, -30.591015625000026], + [-57.81059570312499, -30.85859375000001], + [-57.88632812499998, -30.937402343749994], + [-57.86840820312497, -31.104394531249994], + [-57.89335937499999, -31.195312499999957], + [-58.03339843750001, -31.416601562500006], + [-58.053857421874994, -31.494921874999974], + [-58.009667968749966, -31.534375], + [-57.98798828124998, -31.576171875], + [-58.00698242187494, -31.684960937499966], + [-58.04233398437495, -31.769238281249997], + [-58.16748046874997, -31.87265625], + [-58.18901367187499, -31.924218750000037], + [-58.16040039062503, -31.986523437500026], + [-58.156347656250006, -32.0515625], + [-58.17700195312494, -32.11904296875002], + [-58.16479492187494, -32.18486328125002], + [-58.119726562500006, -32.24892578125002], + [-58.12304687499997, -32.321875], + [-58.201171875, -32.471679687500014], + [-58.219970703125, -32.563964843749986], + [-58.17099609374998, -32.95927734374996], + [-58.424462890624994, -33.11152343749998], + [-58.54721679687498, -33.66347656249998], + [-58.392480468749966, -34.192968750000034], + [-58.52548828124998, -34.29619140625002], + [-58.28334960937494, -34.68349609375005], + [-57.54785156250003, -35.018945312499994], + [-57.170654296875, -35.3625], + [-57.35390624999994, -35.72031249999998], + [-57.33544921875, -36.026757812499966], + [-57.07617187499994, -36.296777343749994], + [-56.74946289062501, -36.346484375], + [-56.67202148437494, -36.85126953124998], + [-57.546972656250034, -38.085644531250026], + [-58.17919921874994, -38.435839843750045], + [-59.82832031250001, -38.83818359375003], + [-61.112207031249994, -38.99296875000003], + [-61.84790039062497, -38.961816406249994], + [-62.33476562499993, -38.80009765625], + [-62.29506835937502, -39.24326171874996], + [-62.053662109374955, -39.373828125], + [-62.179345703124994, -39.38046875000002], + [-62.076806640624966, -39.46152343750002], + [-62.131542968749926, -39.82539062499998], + [-62.28691406249996, -39.89531250000002], + [-62.40185546875003, -40.19658203125002], + [-62.24633789062494, -40.674609374999974], + [-62.39501953124997, -40.89082031249997], + [-62.95903320312493, -41.10966796875006], + [-63.621777343749955, -41.15976562499996], + [-64.86948242187503, -40.735839843750014], + [-65.13339843749998, -40.88066406250003], + [-64.98637695312496, -42.102050781249986], + [-64.53774414062494, -42.25458984374998], + [-64.57099609374998, -42.416015625], + [-64.42041015625003, -42.43378906249998], + [-64.10087890624993, -42.395117187500006], + [-64.06118164062494, -42.266113281250014], + [-64.228515625, -42.21826171874996], + [-63.795556640624994, -42.113867187500006], + [-63.6298828125, -42.28271484375003], + [-63.61733398437502, -42.695800781249986], + [-64.03476562499998, -42.88125], + [-64.48784179687499, -42.51347656250006], + [-64.97070312499997, -42.66630859375002], + [-65.02690429687496, -42.75888671874996], + [-64.31914062499999, -42.968945312500026], + [-64.83994140624998, -43.18886718749998], + [-65.25234374999997, -43.571875], + [-65.26552734375, -44.2796875], + [-65.64760742187502, -44.661425781250045], + [-65.63876953125, -45.0078125], + [-66.19013671874995, -44.96474609375002], + [-66.94140625, -45.25732421875003], + [-67.59956054687495, -46.05253906250003], + [-67.5064453125, -46.44277343749995], + [-66.77685546874994, -47.005859375], + [-65.99853515625, -47.09375], + [-65.73808593749999, -47.34492187499998], + [-65.81430664062495, -47.63818359374996], + [-66.22524414062502, -47.826757812500006], + [-65.93422851562497, -47.826757812500006], + [-65.81005859374997, -47.941113281250026], + [-67.46630859375, -48.95175781250004], + [-67.68486328125002, -49.2466796875], + [-67.82597656249999, -49.91962890625005], + [-68.2572265625, -50.104589843749984], + [-68.66757812500003, -49.75253906250003], + [-68.66162109374997, -49.93574218750005], + [-68.97958984375, -50.003027343749984], + [-68.59794921874996, -50.00947265624997], + [-68.421875, -50.15791015625001], + [-69.04477539062495, -50.49912109374998], + [-69.35859374999993, -51.028125], + [-69.20102539062498, -50.99365234375001], + [-69.03530273437497, -51.48896484375002], + [-69.46542968750003, -51.58447265625003], + [-68.96533203125003, -51.67714843749999], + [-68.443359375, -52.35664062500004], + [-69.96025390624993, -52.00820312500002], + [-71.91865234374995, -51.98955078125004], + [-72.40766601562501, -51.54082031250002], + [-72.34023437499997, -50.68183593749999], + [-72.50981445312496, -50.607519531250034], + [-73.15292968749998, -50.73828125000003], + [-73.50126953124996, -50.125292968750024], + [-73.55419921875, -49.463867187500014], + [-73.46157226562497, -49.31386718750001], + [-73.13525390625, -49.30068359374999], + [-73.03364257812501, -49.014355468750004], + [-72.65126953125, -48.84160156249998], + [-72.582861328125, -48.47539062499999], + [-72.35473632812497, -48.36582031250005], + [-72.32832031250001, -48.11005859374998], + [-72.517919921875, -47.87636718749998], + [-72.34594726562497, -47.49267578124997], + [-71.90498046875001, -47.201660156250014], + [-71.94023437499999, -46.83125], + [-71.69965820312501, -46.6513671875], + [-71.87568359374998, -46.160546875], + [-71.63154296874998, -45.95371093749998], + [-71.74619140624998, -45.57890625], + [-71.34931640624995, -45.33193359374995], + [-71.5962890625, -44.97919921875004], + [-72.04169921874998, -44.90419921875004], + [-72.06372070312503, -44.771875], + [-71.26113281250002, -44.763085937499966], + [-71.15971679687496, -44.56025390625004], + [-71.21259765624998, -44.44121093750003], + [-71.82001953124993, -44.38310546875], + [-71.68007812500002, -43.92958984374998], + [-71.90498046875001, -43.34755859374998], + [-71.750634765625, -43.237304687499986], + [-72.14643554687498, -42.990039062499974], + [-72.10820312499993, -42.25185546874995], + [-71.75, -42.04677734375001], + [-71.91127929687497, -41.650390624999986], + [-71.93212890624994, -40.69169921874999], + [-71.70898437499997, -40.381738281249994], + [-71.81831054687493, -40.17666015624995], + [-71.65976562499998, -40.02080078125], + [-71.71992187499995, -39.63525390624997], + [-71.53945312499997, -39.60244140624995], + [-71.40156249999995, -38.93505859374996], + [-70.858642578125, -38.60449218750003], + [-71.16757812499998, -37.76230468749996], + [-71.19218750000002, -36.84365234375004], + [-71.05551757812498, -36.52373046874996], + [-70.40478515625, -36.06171874999998], + [-70.41572265625001, -35.52304687500002], + [-70.55517578125, -35.246875], + [-70.39316406250003, -35.146875], + [-70.05205078124999, -34.30078124999997], + [-69.85244140625, -34.224316406250026], + [-69.81962890624999, -33.28378906249999], + [-70.08486328125002, -33.20175781249998], + [-70.02197265625, -32.88457031250002], + [-70.36376953125, -32.08349609374997], + [-70.25439453125, -31.957714843750026], + [-70.585205078125, -31.569433593749963], + [-70.51958007812493, -31.1484375], + [-70.30908203124994, -31.02265625000004], + [-70.15322265625, -30.360937499999963], + [-69.95634765624996, -30.35820312500003], + [-69.84428710937493, -30.175], + [-69.95996093749997, -30.078320312500026], + [-70.02680664062501, -29.324023437500017], + [-69.82788085937497, -29.10322265624997], + [-69.65693359374995, -28.413574218749986], + [-69.17441406249998, -27.924707031250037], + [-68.84633789062494, -27.153710937499994], + [-68.59208984375002, -27.140039062499966], + [-68.31865234374999, -26.973242187500006], + [-68.59160156249999, -26.47041015624997], + [-68.41450195312498, -26.153710937500023], + [-68.59208984375002, -25.420019531250034], + [-68.38422851562495, -25.091894531249977], + [-68.56201171875, -24.74736328125003], + [-68.25029296875002, -24.391992187500023], + [-67.35620117187503, -24.033789062499963], + [-67.00878906249994, -23.00136718750005], + [-67.19487304687493, -22.821679687500037], + [-66.99111328125, -22.509863281250006], + [-66.71171874999999, -22.216308593749986], + [-66.36518554687501, -22.113769531249957], + [-66.32246093750001, -22.053125], + [-66.28212890624997, -21.94746093750001], + [-66.24760742187496, -21.83046875], + [-66.22016601562495, -21.802539062499974], + [-66.174658203125, -21.805664062499986], + [-66.09858398437495, -21.83505859375002], + [-66.05859375, -21.87949218750002], + [-65.86015624999999, -22.019726562499983], + [-65.77104492187493, -22.099609375000014], + [-65.68618164062497, -22.11025390625005], + [-65.51879882812497, -22.094531250000045], + [-64.99262695312498, -22.109667968750017], + [-64.60551757812499, -22.228808593750045], + [-64.52363281250001, -22.37158203125], + [-64.47773437499998, -22.485351562499986], + [-64.44550781249998, -22.585351562500023], + [-64.37397460937498, -22.761035156250017], + [-64.32529296875, -22.82763671875], + [-64.30791015624993, -22.7953125], + [-64.26640625000002, -22.60332031249996], + [-63.97612304687502, -22.072558593750003], + [-63.92167968749993, -22.028613281250017], + [-62.843359375, -21.997265625000026], + [-62.62597656250003, -22.29042968749998], + [-62.54155273437496, -22.349609374999957], + [-62.37250976562498, -22.439160156249997], + [-62.21416015624996, -22.612402343750034], + [-61.798535156249955, -23.18203125], + [-61.084716796875, -23.65644531250001] + ] + ] + ] + }, + "properties": { "name": "Argentina", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [46.490625, 38.90668945312498], + [46.1144531250001, 38.877783203125034], + [45.977441406249994, 39.24389648437503], + [45.76630859375004, 39.37846679687499], + [45.78447265625002, 39.54560546875001], + [45.456835937500074, 39.494482421875006], + [45.15283203125003, 39.58266601562502], + [45.03164062500005, 39.76513671874997], + [44.76826171875004, 39.70351562500005], + [44.28925781250004, 40.040380859375006], + [43.66621093750004, 40.12636718750002], + [43.56933593750003, 40.48237304687498], + [43.72265624999997, 40.71953124999999], + [43.43945312500003, 41.10712890625001], + [44.077246093750006, 41.182519531249994], + [44.81132812500002, 41.259375], + [45.001367187499994, 41.29096679687498], + [45.188574218750006, 41.14741210937504], + [45.07050781250004, 41.075585937499966], + [45.5875, 40.846923828125], + [45.37890624999997, 40.67358398437506], + [45.45439453125002, 40.532373046874966], + [45.96464843750002, 40.233789062499966], + [45.8859375000001, 40.024853515624955], + [45.57978515625004, 39.9775390625], + [46.202050781249994, 39.59448242187503], + [46.48144531249997, 39.55517578125003], + [46.36523437500003, 39.402490234374994], + [46.584765625000074, 39.22368164062499], + [46.400292968749994, 39.1921875], + [46.490625, 38.90668945312498] + ] + ] + }, + "properties": { "name": "Armenia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-170.72626953125, -14.351171875], + [-170.8205078125, -14.312109375], + [-170.568115234375, -14.266796875000011], + [-170.72626953125, -14.351171875] + ] + ] + }, + "properties": { "name": "American Samoa", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [69.2824218750001, -49.05888671875002], + [69.16718750000004, -48.88291015624996], + [69.36875, -48.89042968749998], + [69.2824218750001, -49.05888671875002] + ] + ], + [ + [ + [69.18486328125002, -49.10957031250004], + [69.59277343749997, -48.97099609375005], + [69.64404296875003, -49.11738281250003], + [69.40507812500002, -49.18173828125], + [69.5423828125, -49.25566406250005], + [70.32021484375005, -49.05859374999996], + [70.55546875000007, -49.201464843750024], + [70.38613281250005, -49.433984374999966], + [70.16582031250002, -49.34296874999998], + [69.75996093750004, -49.430175781249986], + [69.98642578125006, -49.58164062500003], + [70.2477539062501, -49.53066406250003], + [70.12431640625002, -49.70439453124999], + [69.153125, -49.5296875], + [68.99296875000007, -49.704980468750016], + [68.81474609375002, -49.69960937499999], + [68.88339843750006, -49.16494140624995], + [68.76953125000003, -49.06591796875003], + [69.00244140624997, -48.661230468750006], + [69.13613281250005, -48.86103515625003], + [69.05214843750005, -49.08193359375001], + [69.18486328125002, -49.10957031250004] + ] + ], + [ + [ + [51.83457031250006, -46.43994140625], + [51.65927734375006, -46.37363281249999], + [51.7418945312501, -46.32685546874997], + [51.83457031250006, -46.43994140625] + ] + ] + ] + }, + "properties": { "name": "Fr. S. Antarctic Lands", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.71606445312503, 17.037011718749994], + [-61.85966796874996, 17.013330078124966], + [-61.887109374999966, 17.09814453125], + [-61.81728515624994, 17.168945312500057], + [-61.71606445312503, 17.037011718749994] + ] + ] + }, + "properties": { "name": "Antigua and Barb.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [147.35605468750006, -43.396972656250014], + [147.30888671875007, -43.50078125000002], + [147.10498046875003, -43.43115234374996], + [147.28388671875004, -43.278906250000034], + [147.35605468750006, -43.396972656250014] + ] + ], + [ + [ + [145.04296875000003, -40.78671875], + [145.28300781250002, -40.76992187500002], + [146.31748046875006, -41.16347656250001], + [146.72343750000002, -41.07802734375001], + [146.84814453124997, -41.16806640624996], + [146.98984375000006, -40.99238281249997], + [147.45478515625004, -41.00166015624998], + [147.62167968750012, -40.844726562499986], + [147.87294921875005, -40.87255859374997], + [147.96875, -40.779589843750045], + [148.215234375, -40.85488281250002], + [148.34257812500007, -42.21533203124997], + [148.21367187500002, -41.97001953125], + [147.92441406250006, -42.5724609375], + [147.94541015625006, -43.18183593749997], + [147.7858398437501, -43.22001953125002], + [147.69892578125004, -43.12255859374997], + [147.64794921874997, -43.02060546874999], + [147.8, -42.928125], + [147.57382812500006, -42.84570312499997], + [147.4523437500001, -43.03339843750001], + [147.29794921875006, -42.790917968749994], + [147.24501953125005, -43.21591796874999], + [146.99697265625005, -43.15634765625002], + [147.07734375000004, -43.27587890625003], + [146.87392578125, -43.6125], + [146.54853515625004, -43.50888671874999], + [146.04316406250004, -43.547167968749974], + [145.99443359375007, -43.37607421875002], + [146.20800781249997, -43.31621093749999], + [145.8732421875001, -43.29238281250002], + [145.48759765625002, -42.92666015625004], + [145.19882812500006, -42.23085937500004], + [145.46826171874997, -42.492871093750026], + [145.51660156249997, -42.3544921875], + [145.33105468750003, -42.14707031250002], + [145.23486328124997, -42.19697265624997], + [145.23818359375, -42.01962890624999], + [144.76611328125003, -41.39003906249998], + [144.64609375000006, -40.980859375], + [144.71855468750002, -40.67226562500002], + [145.04296875000003, -40.78671875] + ] + ], + [ + [ + [148.23691406250006, -40.515136718749986], + [148.18779296875007, -40.592578125000045], + [148.11728515625012, -40.52148437499996], + [148.23691406250006, -40.515136718749986] + ] + ], + [ + [ + [144.784375, -40.506738281249966], + [144.74804687499997, -40.589453125000034], + [144.7833984375001, -40.434863281249974], + [144.784375, -40.506738281249966] + ] + ], + [ + [ + [148.32626953125006, -40.30693359375003], + [148.40400390625004, -40.486523437500026], + [148.02011718750012, -40.40419921874995], + [148.32626953125006, -40.30693359375003] + ] + ], + [ + [ + [148.000390625, -39.75761718750003], + [148.29736328125003, -39.985742187499966], + [148.31357421875012, -40.173535156250026], + [148.10566406250004, -40.26210937499995], + [147.76718750000012, -39.87031249999998], + [148.000390625, -39.75761718750003] + ] + ], + [ + [ + [143.92792968750004, -40.116113281249966], + [143.83857421875004, -39.90410156250003], + [144.00078125000007, -39.580175781250034], + [144.14101562500005, -39.953808593750026], + [143.92792968750004, -40.116113281249966] + ] + ], + [ + [ + [145.31445312500003, -38.49082031249996], + [145.35507812500012, -38.55703124999995], + [145.12841796875003, -38.52763671875], + [145.31445312500003, -38.49082031249996] + ] + ], + [ + [ + [137.59648437500007, -35.73867187499998], + [137.92890625000004, -35.72607421875], + [138.12343750000005, -35.85234375], + [137.67089843749997, -35.897949218750014], + [137.44843750000004, -36.07480468749999], + [137.20957031250012, -35.982421875], + [136.7550781250001, -36.03310546875002], + [136.540625, -35.89013671875003], + [136.63867187499997, -35.74882812500002], + [137.33408203125006, -35.59248046875004], + [137.58496093749997, -35.620214843750006], + [137.59648437500007, -35.73867187499998] + ] + ], + [ + [ + [153.53876953125004, -27.436425781250037], + [153.42656250000002, -27.70644531249998], + [153.43544921875, -27.40537109375002], + [153.53876953125004, -27.436425781250037] + ] + ], + [ + [ + [113.18300781250005, -26.053125], + [112.96425781250005, -25.78310546875001], + [112.94707031250002, -25.531542968750017], + [113.18300781250005, -26.053125] + ] + ], + [ + [ + [153.07744140625002, -25.75078125], + [152.97666015625012, -25.551367187499963], + [153.03808593750003, -25.193164062500003], + [153.22753906249997, -25.00576171875001], + [153.14375, -24.814843750000023], + [153.25693359375012, -24.72890625], + [153.35019531250012, -25.063085937499963], + [153.07744140625002, -25.75078125] + ] + ], + [ + [ + [151.14658203125006, -23.49082031250002], + [151.24013671875, -23.529687500000037], + [151.23828124999997, -23.77578125], + [151.03330078125006, -23.530175781250037], + [151.14658203125006, -23.49082031250002] + ] + ], + [ + [ + [115.44619140625005, -20.78779296875001], + [115.31806640625004, -20.850585937500014], + [115.43457031249997, -20.66796875000003], + [115.44619140625005, -20.78779296875001] + ] + ], + [ + [ + [149.04375, -20.29150390624997], + [148.93886718750005, -20.283691406249986], + [148.98105468750012, -20.153515625000026], + [149.04375, -20.29150390624997] + ] + ], + [ + [ + [146.27832031249997, -18.23125], + [146.29882812499997, -18.48476562500005], + [146.09882812500004, -18.251757812500003], + [146.27832031249997, -18.23125] + ] + ], + [ + [ + [139.45917968750004, -17.11455078124996], + [139.49277343750006, -16.990429687499983], + [139.57089843750006, -17.09443359375004], + [139.45917968750004, -17.11455078124996] + ] + ], + [ + [ + [139.50781250000003, -16.57304687499996], + [139.1595703125, -16.74169921875003], + [139.29296875000003, -16.467285156249986], + [139.58789062499997, -16.39521484374997], + [139.69775390624997, -16.514941406250017], + [139.50781250000003, -16.57304687499996] + ] + ], + [ + [ + [137.09365234375005, -15.778125], + [136.94267578125002, -15.711718749999989], + [137.00957031250007, -15.594824218749977], + [137.09365234375005, -15.778125] + ] + ], + [ + [ + [124.59726562500006, -15.40195312500002], + [124.52421875000002, -15.421484375], + [124.51933593750002, -15.26748046874998], + [124.59726562500006, -15.40195312500002] + ] + ], + [ + [ + [125.19882812500006, -14.57949218749998], + [125.0912109375, -14.59169921874998], + [125.15996093750002, -14.456054687499972], + [125.19882812500006, -14.57949218749998] + ] + ], + [ + [ + [136.71464843750002, -13.803906249999983], + [136.89082031250004, -13.786621093750014], + [136.74531250000004, -14.072656250000023], + [136.95078125000006, -14.184277343750026], + [136.89433593750002, -14.293066406249977], + [136.33544921875003, -14.211816406250037], + [136.42470703125, -13.864843749999963], + [136.6556640625, -13.675878906250006], + [136.71464843750002, -13.803906249999983] + ] + ], + [ + [ + [136.23740234375006, -13.824511718750003], + [136.12265625000012, -13.816601562499983], + [136.21542968750012, -13.664746093750054], + [136.23740234375006, -13.824511718750003] + ] + ], + [ + [ + [136.33867187500007, -11.602343749999989], + [136.18027343750006, -11.676757812499957], + [136.47929687500002, -11.465917968749991], + [136.33867187500007, -11.602343749999989] + ] + ], + [ + [ + [130.45927734375007, -11.679296875000034], + [130.60625, -11.816601562500026], + [130.04326171875007, -11.787304687500011], + [130.19755859375007, -11.658203125], + [130.15283203124997, -11.477539062499972], + [130.29492187499997, -11.33681640624998], + [130.45927734375007, -11.679296875000034] + ] + ], + [ + [ + [130.6188476562501, -11.376074218749991], + [131.02304687500006, -11.334375], + [131.26826171875004, -11.18984375], + [131.53857421874997, -11.436914062500037], + [130.95097656250007, -11.926464843750026], + [130.51191406250004, -11.617871093749955], + [130.38457031250002, -11.1921875], + [130.6188476562501, -11.376074218749991] + ] + ], + [ + [ + [136.59853515625, -11.378906249999943], + [136.52656250000004, -11.438867187499994], + [136.78027343749997, -11.0125], + [136.59853515625, -11.378906249999943] + ] + ], + [ + [ + [132.59335937500006, -11.302832031249991], + [132.48378906250005, -11.037304687499983], + [132.57880859375004, -10.968847656249977], + [132.59335937500006, -11.302832031249991] + ] + ], + [ + [ + [143.17890625000004, -11.954492187499966], + [143.11025390625, -12.303515625000017], + [143.40156250000004, -12.639941406249989], + [143.5866210937501, -13.443652343750031], + [143.54843750000012, -13.74101562499996], + [143.75634765625003, -14.348828124999969], + [143.96181640625005, -14.462890625000028], + [144.473046875, -14.231835937500023], + [144.64804687500006, -14.492480468750017], + [145.28769531250006, -14.943164062499989], + [145.42607421875002, -16.406152343749966], + [145.75478515625, -16.879492187500034], + [145.91210937499997, -16.9125], + [146.12587890625005, -17.63525390625], + [146.03222656249997, -18.272851562500037], + [146.3332031250001, -18.55371093749997], + [146.38339843750006, -18.97705078124997], + [147.13876953125006, -19.39316406250002], + [147.41855468750012, -19.378125], + [147.7423828125001, -19.770117187499977], + [148.759375, -20.28955078125003], + [148.88476562499997, -20.480859375], + [148.72998046874997, -20.4677734375], + [148.68369140625012, -20.58017578124999], + [149.20488281250007, -21.125097656249977], + [149.45410156249997, -21.57871093750002], + [149.70390625000002, -22.440527343750006], + [149.82246093750004, -22.389843749999983], + [149.97441406250007, -22.55068359374998], + [149.94189453125003, -22.30810546875003], + [150.07617187500003, -22.16445312499998], + [150.54130859375002, -22.55908203125], + [150.56855468750004, -22.38398437500004], + [150.67246093750012, -22.418164062499983], + [150.84316406250005, -23.4580078125], + [151.15380859375003, -23.784082031249994], + [151.83164062500006, -24.12294921875001], + [152.12988281250003, -24.59755859374998], + [152.45634765625007, -24.802441406249983], + [152.65429687499997, -25.201953125000017], + [152.91347656250005, -25.432128906250014], + [152.98496093750012, -25.816210937500003], + [153.16494140625, -25.964160156250045], + [153.11679687500006, -27.194433593750034], + [153.57568359375003, -28.24052734374999], + [153.6168945312501, -28.673046875], + [153.03056640625002, -30.563378906249994], + [152.94394531250012, -31.43486328124999], + [152.5592773437501, -32.045703125], + [152.4704101562501, -32.439062500000034], + [152.13652343750002, -32.678125], + [152.1642578125001, -32.75742187499996], + [151.812890625, -32.90107421875001], + [151.29208984375012, -33.580957031249966], + [151.28027343750003, -33.92666015625005], + [151.12480468750007, -34.00527343749998], + [151.23154296875006, -34.0296875], + [150.8712890625001, -34.49912109374996], + [150.80458984375, -35.01289062500001], + [150.19531249999997, -35.83359374999996], + [149.93271484375012, -37.528515625000026], + [149.480859375, -37.77119140625], + [147.87675781250002, -37.93417968749998], + [146.8568359375, -38.663476562499966], + [146.21748046875004, -38.72744140625004], + [146.33662109375004, -38.89423828125], + [146.46660156250007, -38.84033203125003], + [146.40000000000012, -39.14550781250003], + [146.1583984375001, -38.86572265624996], + [145.93535156250002, -38.90175781250002], + [145.79082031250007, -38.66699218749997], + [145.39726562500002, -38.53535156249998], + [145.54218750000004, -38.39384765625002], + [145.4757812500001, -38.24375], + [145.29277343750002, -38.237597656249974], + [144.95957031250012, -38.500781250000045], + [144.71777343749997, -38.34033203125004], + [144.91142578125007, -38.34404296874999], + [145.11992187500007, -38.091308593750014], + [144.89130859375004, -37.899804687499994], + [144.39550781250003, -38.13691406249998], + [144.6652343750001, -38.20996093750003], + [143.53896484375005, -38.82089843749998], + [142.45585937500002, -38.38632812499999], + [141.725, -38.27138671875002], + [141.5939453125001, -38.38779296875002], + [141.42421875, -38.36347656250004], + [141.0109375000001, -38.07695312500003], + [140.39042968750007, -37.89667968749998], + [139.78427734375012, -37.24580078124998], + [139.85732421875, -36.662109375], + [139.72900390625003, -36.37138671875002], + [138.9689453125001, -35.58076171874997], + [139.17802734375007, -35.52304687500002], + [139.289453125, -35.61132812499997], + [139.28251953125002, -35.375390624999966], + [138.521875, -35.6423828125], + [138.184375, -35.612695312499994], + [138.5111328125, -35.02441406249996], + [138.48994140625004, -34.76357421875002], + [138.0892578125, -34.16982421875002], + [137.69169921875002, -35.14296875000004], + [136.88359375000007, -35.23974609375004], + [137.01425781250012, -34.91582031250003], + [137.39101562500005, -34.91328124999997], + [137.49384765625004, -34.16113281250003], + [137.9318359375001, -33.57910156250003], + [137.85234375000007, -33.20078124999996], + [137.99257812500005, -33.094238281250014], + [137.78320312500003, -32.578125], + [137.79091796875, -32.82324218749996], + [137.44228515625, -33.1935546875], + [137.23730468750003, -33.62949218749999], + [136.43066406249997, -34.02998046875004], + [135.891015625, -34.660937499999974], + [135.96972656249997, -34.98183593749998], + [135.7923828125, -34.863281249999986], + [135.64755859375006, -34.93964843750001], + [135.12304687499997, -34.58574218750003], + [135.21679687499997, -34.48730468749996], + [135.45, -34.58105468749996], + [135.21894531250004, -33.959765625000045], + [134.88876953125012, -33.62636718749998], + [134.79101562499997, -33.32832031250001], + [134.60771484375002, -33.19013671875001], + [134.30126953124997, -33.16503906249996], + [134.17353515625004, -32.979101562500006], + [134.10039062500007, -32.748632812500034], + [134.22714843750006, -32.73056640624999], + [134.23417968750007, -32.54853515625004], + [133.66533203125007, -32.207226562500054], + [133.21210937500004, -32.18378906249998], + [132.75742187500012, -31.95625], + [132.21464843750002, -32.00712890624996], + [131.14365234375006, -31.49570312500005], + [130.78300781250002, -31.604003906249986], + [129.1876953125001, -31.659960937500017], + [127.31982421874997, -32.2640625], + [125.91718750000004, -32.296972656250034], + [124.75878906250003, -32.882714843749994], + [124.24375, -33.01523437499999], + [123.50683593749997, -33.916210937500054], + [122.15097656250006, -33.99179687499999], + [122.06113281250006, -33.874414062499966], + [121.40507812500007, -33.826757812500034], + [119.85410156250012, -33.97470703124998], + [119.45058593750005, -34.368261718750034], + [118.89531250000007, -34.47988281250004], + [118.13554687500002, -34.98662109374999], + [117.58193359375005, -35.09775390624998], + [116.51718750000012, -34.98789062499998], + [115.98671875000005, -34.795019531250034], + [115.56503906250012, -34.42578125000003], + [115.00878906250003, -34.25585937499997], + [114.9938476562501, -33.51533203125], + [115.3587890625, -33.63994140624999], + [115.68300781250005, -33.19287109375003], + [115.6984375000001, -31.694531250000054], + [115.07792968750007, -30.560449218750023], + [114.85683593750005, -29.14296875], + [114.16513671875012, -28.08066406250002], + [114.028125, -27.34726562499999], + [113.18476562500004, -26.182226562499963], + [113.32324218749997, -26.243847656249997], + [113.35605468750012, -26.080468750000023], + [113.58164062500006, -26.558105468749986], + [113.73369140625002, -26.59511718749998], + [113.83642578125003, -26.50058593749999], + [113.85283203125007, -26.33212890625005], + [113.39531250000002, -25.71328125], + [113.4513671875001, -25.599121093750014], + [113.7130859375001, -25.83076171875004], + [113.72373046875006, -26.129785156250037], + [113.85390625, -26.01445312499999], + [113.99199218750007, -26.32148437500001], + [114.09033203124997, -26.393652343749963], + [114.21572265625, -26.289453124999966], + [114.2142578125, -25.851562500000014], + [113.41767578125004, -24.435644531250034], + [113.48984375000012, -23.869628906250014], + [113.7570312500001, -23.418164062500054], + [113.79511718750004, -22.91455078125003], + [113.68281250000004, -22.637792968749963], + [114.02285156250005, -21.881445312499977], + [114.12392578125005, -21.828613281249957], + [114.14160156250003, -22.483105468749983], + [114.37773437500007, -22.341503906249997], + [114.70927734375002, -21.82343749999997], + [115.45615234375012, -21.49169921874997], + [116.0109375000001, -21.030371093749963], + [116.7067382812501, -20.653808593749986], + [117.40625, -20.72119140625003], + [118.19921875000003, -20.37519531249997], + [118.75146484374997, -20.261914062499983], + [119.10449218749997, -19.995312500000026], + [119.58593750000003, -20.03828125], + [120.99794921875, -19.604394531249966], + [121.33769531250002, -19.31992187500002], + [121.83378906250002, -18.477050781249986], + [122.34541015625004, -18.11191406250002], + [122.14746093749997, -17.54902343750001], + [122.2609375000001, -17.135742187500014], + [122.72041015625004, -16.78769531249999], + [122.97070312499997, -16.436816406250003], + [123.56308593750006, -17.520898437499966], + [123.59355468750007, -17.03037109375005], + [123.83105468750003, -17.120800781249997], + [123.8744140625, -16.918652343750026], + [123.4904296875001, -16.49072265624997], + [123.62597656249997, -16.416308593750003], + [123.60703125000006, -16.224023437499994], + [123.72890625, -16.192480468749963], + [123.85917968750007, -16.38232421875], + [124.04443359374997, -16.264941406249974], + [124.30039062500006, -16.388281249999977], + [124.77197265624997, -16.40263671874996], + [124.40488281250006, -16.298925781249977], + [124.41640625, -16.133496093750026], + [124.5768554687501, -16.11367187499998], + [124.64853515625012, -15.870214843750034], + [124.50429687500005, -15.972460937499989], + [124.38164062500002, -15.758203125000037], + [124.43955078125012, -15.493554687500037], + [124.56162109375012, -15.496289062499969], + [124.69257812500004, -15.273632812499997], + [125.06298828125003, -15.44228515624998], + [125.0729492187501, -15.306738281249991], + [124.90917968750003, -15.310058593749957], + [124.83906250000004, -15.160742187500006], + [125.03818359375012, -15.004101562499969], + [125.35566406250004, -15.119824218750011], + [125.17871093749997, -14.714746093749994], + [125.57978515625004, -14.483203124999989], + [125.62773437500002, -14.256640625000017], + [125.70458984374997, -14.29140625], + [125.66162109375003, -14.529492187500011], + [125.81953125000004, -14.469140624999966], + [125.890625, -14.61796875], + [126.0207031250001, -14.49453125], + [126.0539062500001, -13.977246093750026], + [126.1842773437501, -14.00205078125002], + [126.25849609375004, -14.163574218749972], + [126.403125, -14.018945312499994], + [126.5697265625, -14.160937499999974], + [126.7806640625, -13.955175781249977], + [126.77558593750004, -13.788476562500037], + [126.90322265625, -13.744140624999972], + [127.45761718750006, -14.031445312499969], + [128.18046875000007, -14.711621093749983], + [128.06943359375012, -15.329296874999969], + [128.15546875000004, -15.225585937499972], + [128.25468750000002, -15.298535156250011], + [128.175, -15.043164062500026], + [128.57578125000006, -14.774511718750006], + [129.05820312500012, -14.884375], + [129.21582031249997, -15.160253906249991], + [129.26757812500003, -14.871484375000051], + [129.63476562499997, -15.139746093749991], + [129.637109375, -14.850976562500037], + [129.84873046875012, -14.828906249999989], + [129.60468750000004, -14.647070312499977], + [129.69794921875004, -14.557421875000017], + [129.37871093750002, -14.39248046874998], + [129.70986328125, -13.979980468749972], + [129.83886718749997, -13.572949218749997], + [130.25976562500003, -13.30224609375], + [130.1349609375001, -13.145507812499957], + [130.1681640625001, -12.957421875], + [130.39990234374997, -12.68789062499999], + [130.61748046875007, -12.646875], + [130.62265625000006, -12.43105468749998], + [130.8673828125001, -12.557812499999955], + [130.87382812500007, -12.367187500000028], + [131.29160156250006, -12.067871093749972], + [131.43828125000002, -12.27695312500002], + [132.06406250000006, -12.28076171875], + [132.25322265625007, -12.186035156249972], + [132.41103515625, -12.295117187499997], + [132.51054687500002, -12.134863281250034], + [132.71279296875, -12.1234375], + [132.63046875000012, -12.035156249999972], + [132.67421875000005, -11.649023437499991], + [132.47519531250006, -11.491503906249974], + [132.07285156250006, -11.474707031250006], + [131.82246093750004, -11.302441406249997], + [131.96152343750006, -11.180859375000011], + [132.15546875000004, -11.311132812499991], + [132.33398437499997, -11.223535156249994], + [132.6828125000001, -11.505566406249997], + [132.96103515625012, -11.407324218749963], + [133.18525390625004, -11.705664062499991], + [133.90419921875, -11.832031249999972], + [134.4173828125, -12.052734375], + [134.73027343750002, -11.984375], + [135.02968750000005, -12.19375], + [135.2179687500001, -12.221679687499957], + [135.92246093750012, -11.825781250000034], + [135.70439453125007, -12.209863281250037], + [136.00849609375004, -12.19140625], + [136.08183593750007, -12.422460937500006], + [136.26064453125, -12.433789062499997], + [136.32851562500005, -12.305566406249994], + [136.24990234375, -12.173046875], + [136.44335937499997, -11.951464843749974], + [136.7194335937501, -12.226464843749952], + [136.89746093749997, -12.243554687499966], + [136.94746093750004, -12.34990234374996], + [136.53701171875, -12.784277343749991], + [136.59433593750012, -13.003808593750051], + [136.46103515625006, -13.225195312500034], + [136.29414062500004, -13.137988281250031], + [135.92734375000012, -13.304296874999977], + [135.95449218750005, -13.934863281250017], + [135.40517578125005, -14.758203124999966], + [135.4533203125001, -14.923144531250003], + [136.20537109375002, -15.403417968749963], + [136.29140625000005, -15.570117187500003], + [136.70488281250007, -15.685253906250011], + [136.78466796874997, -15.89423828125004], + [137.00214843750004, -15.878320312499994], + [137.70371093750006, -16.233007812499963], + [138.24501953125005, -16.718359374999977], + [139.00986328125006, -16.899316406249994], + [139.2484375, -17.328613281249957], + [140.03583984375004, -17.702636718749957], + [140.51113281250005, -17.62451171875003], + [140.83046875, -17.414453125000037], + [141.29140625, -16.46347656250002], + [141.62548828124997, -15.056640625000014], + [141.52294921875003, -14.470117187499994], + [141.59433593750006, -14.152832031250014], + [141.47255859375, -13.797558593750011], + [141.64541015625, -13.259082031250003], + [141.61357421875002, -12.943457031250006], + [141.92978515625006, -12.73984375], + [141.67773437500003, -12.491406250000011], + [141.68857421875012, -12.351074218750028], + [141.87050781250005, -11.9755859375], + [141.96113281250004, -12.054296874999963], + [142.168359375, -10.946582031249974], + [142.45644531250005, -10.707324218749989], + [142.60507812500012, -10.748242187499983], + [142.55273437500003, -10.874414062500023], + [142.7796875, -11.115332031249977], + [142.87255859374997, -11.821386718750034], + [143.17890625000004, -11.954492187499966] + ] + ], + [ + [ + [142.2748046875, -10.704785156250011], + [142.19140624999997, -10.762011718750031], + [142.1310546875001, -10.640625], + [142.19794921875004, -10.59199218750004], + [142.2748046875, -10.704785156250011] + ] + ] + ] + }, + "properties": { "name": "Australia", "childNum": 30 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [16.953125, 48.598828125], + [16.86542968750001, 48.3869140625], + [17.147363281250023, 48.00595703125], + [17.06660156250001, 47.707568359374996], + [16.421289062500023, 47.674462890624994], + [16.676562500000017, 47.536035156249994], + [16.44287109375, 47.39951171875], + [16.453417968750017, 47.006787109375], + [16.093066406250017, 46.86328125], + [15.957617187500006, 46.677636718749994], + [14.893261718750011, 46.605908203125], + [14.5498046875, 46.399707031249996], + [13.7, 46.520263671875], + [13.490039062500017, 46.555566406249994], + [13.3515625, 46.557910156249996], + [13.16875, 46.57265625], + [12.479199218750011, 46.672509765624994], + [12.38828125, 46.70263671875], + [12.330078125, 46.759814453124996], + [12.267968750000023, 46.835888671875], + [12.154101562500017, 46.93525390625], + [12.130761718750023, 46.98476562499999], + [12.16552734375, 47.028173828125], + [12.201269531250006, 47.060888671875], + [12.197167968750023, 47.075], + [12.16943359375, 47.08212890625], + [11.775683593750017, 46.986083984375], + [11.527539062500011, 46.997412109375], + [11.433203125, 46.983056640624994], + [11.244433593750017, 46.97568359375], + [11.133886718750006, 46.936181640624994], + [11.0634765625, 46.859130859375], + [11.025097656250011, 46.79697265625], + [10.993261718750006, 46.777001953124994], + [10.92734375, 46.769482421875], + [10.828906250000017, 46.775244140625], + [10.759765625, 46.793310546875], + [10.689257812500017, 46.84638671875], + [10.579785156250011, 46.8537109375], + [10.479394531250023, 46.855126953124994], + [10.452832031250011, 46.86494140625], + [10.45458984375, 46.8994140625], + [10.414941406250023, 46.964404296874996], + [10.349414062500017, 46.98476562499999], + [10.133496093750011, 46.851513671875], + [9.580273437500011, 47.057373046875], + [9.527539062500011, 47.270751953125], + [9.625878906250023, 47.467041015625], + [9.524023437500006, 47.52421875], + [9.748925781250023, 47.575537109375], + [9.839160156250017, 47.552294921874996], + [9.971582031250023, 47.505322265625], + [10.034082031250023, 47.473583984375], + [10.059863281250017, 47.449072265625], + [10.066308593750023, 47.393359375], + [10.200292968750006, 47.363427734374994], + [10.183007812500023, 47.27880859375], + [10.369140625, 47.366064453125], + [10.40390625, 47.4169921875], + [10.439453125, 47.5515625], + [10.482812500000023, 47.541796875], + [10.65869140625, 47.547216796875], + [10.741601562500023, 47.52412109375], + [10.873046875, 47.52021484375], + [11.0419921875, 47.393115234374996], + [12.185644531250006, 47.61953125], + [12.203808593750011, 47.646728515625], + [12.196875, 47.70908203125], + [12.209277343750017, 47.71826171875], + [12.268359375000017, 47.702734375], + [12.353540736607165, 47.70264787946429], + [12.492553013392856, 47.68551897321428], + [12.685839843750017, 47.669335937499994], + [12.771386718750023, 47.639404296875], + [12.796191406250017, 47.60703125], + [12.781152343750023, 47.5904296875], + [12.7828125, 47.56416015625], + [12.809375, 47.5421875], + [12.87890625, 47.5064453125], + [12.968066406250017, 47.47568359375], + [13.014355468750011, 47.478076171874996], + [13.031542968750017, 47.5080078125], + [13.047949218750006, 47.579150390624996], + [13.054101562500023, 47.655126953125], + [12.897656250000011, 47.721875], + [12.953515625000023, 47.890625], + [12.760351562500006, 48.106982421874996], + [13.215234375000023, 48.301904296874994], + [13.322851562500006, 48.33125], + [13.409375, 48.394140625], + [13.459863281250023, 48.56455078125], + [13.4716796875, 48.571826171874996], + [13.486621093750017, 48.581835937499996], + [13.636623883928596, 48.580904017857144], + [13.785351562500011, 48.587451171874996], + [13.798828125, 48.6216796875], + [13.802929687500011, 48.747509765625], + [13.814746093750017, 48.766943359375], + [14.049121093750017, 48.602490234375], + [14.691308593750023, 48.59921875], + [15.066796875000023, 48.997851562499996], + [16.057226562500006, 48.754785156249994], + [16.543554687500006, 48.796240234375], + [16.953125, 48.598828125] + ] + ] + }, + "properties": { "name": "Austria", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [46.1144531250001, 38.877783203125034], + [45.4796875000001, 39.00625], + [44.81718750000002, 39.65043945312496], + [44.76826171875004, 39.70351562500005], + [45.03164062500005, 39.76513671874997], + [45.15283203125003, 39.58266601562502], + [45.456835937500074, 39.494482421875006], + [45.78447265625002, 39.54560546875001], + [45.76630859375004, 39.37846679687499], + [45.977441406249994, 39.24389648437503], + [46.1144531250001, 38.877783203125034] + ] + ], + [ + [ + [48.572851562500006, 41.84448242187503], + [49.45673828125004, 40.79985351562502], + [49.77597656250006, 40.583984375], + [50.18251953125005, 40.50478515625002], + [50.3659179687501, 40.279492187499955], + [49.91884765625005, 40.31640625000003], + [49.55117187499999, 40.19414062499999], + [49.3244140625001, 39.60834960937501], + [49.36279296875003, 39.349560546874955], + [49.16533203125002, 39.03027343750003], + [49.013476562500074, 39.13398437500001], + [48.85449218750003, 38.83881835937501], + [48.86875, 38.43549804687498], + [48.59267578125005, 38.41108398437498], + [47.99648437499999, 38.85375976562503], + [48.292089843750006, 39.01884765624999], + [48.10439453125005, 39.241113281249994], + [48.322167968749994, 39.39907226562502], + [47.995898437500074, 39.683935546875034], + [46.490625, 38.90668945312498], + [46.400292968749994, 39.1921875], + [46.584765625000074, 39.22368164062499], + [46.36523437500003, 39.402490234374994], + [46.48144531249997, 39.55517578125003], + [46.202050781249994, 39.59448242187503], + [45.57978515625004, 39.9775390625], + [45.8859375000001, 40.024853515624955], + [45.96464843750002, 40.233789062499966], + [45.45439453125002, 40.532373046874966], + [45.37890624999997, 40.67358398437506], + [45.5875, 40.846923828125], + [45.07050781250004, 41.075585937499966], + [45.188574218750006, 41.14741210937504], + [45.001367187499994, 41.29096679687498], + [45.2171875, 41.423193359375006], + [45.28095703125004, 41.449560546875034], + [46.086523437500006, 41.183837890625], + [46.43095703125002, 41.077050781249994], + [46.534375, 41.08857421875004], + [46.62636718750005, 41.15966796875006], + [46.66240234375002, 41.24550781250002], + [46.67255859375004, 41.28681640625001], + [46.61894531250002, 41.34375], + [46.30546875000002, 41.507714843749994], + [46.18427734375004, 41.70214843749997], + [46.42988281250004, 41.890966796875006], + [46.74931640625002, 41.812597656250006], + [47.31767578125002, 41.28242187500001], + [47.79101562499997, 41.19926757812502], + [48.572851562500006, 41.84448242187503] + ] + ] + ] + }, + "properties": { "name": "Azerbaijan", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [30.553613281250023, -2.400097656250011], + [30.53369140625, -2.42626953125], + [30.441992187500006, -2.613476562500011], + [30.424218750000023, -2.6416015625], + [30.47333984375001, -2.6943359375], + [30.42402343750001, -2.824023437500003], + [30.433496093750023, -2.87451171875], + [30.515039062500023, -2.917578125], + [30.604296875000017, -2.935253906250011], + [30.70947265625, -2.977246093750011], + [30.7802734375, -2.98486328125], + [30.811132812500006, -3.116406250000011], + [30.79023437500001, -3.274609375000011], + [30.4, -3.65390625], + [29.947265625, -4.307324218750011], + [29.7177734375, -4.455859375], + [29.403222656250023, -4.449316406250006], + [29.211816406250023, -3.833789062500003], + [29.224414062500017, -3.053515625], + [29.01435546875001, -2.72021484375], + [29.10205078125, -2.595703125], + [29.390234375, -2.80859375], + [29.698046875000017, -2.794726562500003], + [29.8681640625, -2.71640625], + [29.93017578125, -2.339550781250011], + [30.117285156250006, -2.416601562500006], + [30.408496093750017, -2.31298828125], + [30.553613281250023, -2.400097656250011] + ] + ] + }, + "properties": { "name": "Burundi", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [5.693554687500011, 50.774755859375006], + [5.993945312500017, 50.75043945312504], + [6.340917968750006, 50.451757812500034], + [6.116503906250045, 50.120996093749966], + [6.08906250000004, 50.15458984374996], + [6.054785156249977, 50.154296875], + [5.976269531250068, 50.167187499999955], + [5.866894531250068, 50.08281250000002], + [5.817382812500028, 50.01269531250003], + [5.7880859375, 49.96123046875002], + [5.744042968749994, 49.91962890624998], + [5.789746093749983, 49.53828125000001], + [5.50732421875, 49.51088867187502], + [4.867578125000051, 49.78813476562502], + [4.818652343750045, 50.153173828125034], + [4.545019531250063, 49.96025390624999], + [4.149316406250023, 49.971582031249994], + [4.174609375000017, 50.24648437500005], + [3.689355468750023, 50.30605468750002], + [3.595410156250068, 50.47734374999999], + [3.27333984375008, 50.53154296875002], + [3.10683593750008, 50.779443359374994], + [2.759375, 50.750634765624994], + [2.52490234375, 51.097119140624955], + [3.35009765625, 51.37768554687503], + [3.43251953125008, 51.24575195312505], + [3.902050781250011, 51.20766601562502], + [4.226171875000034, 51.38647460937503], + [5.03095703125004, 51.46909179687498], + [5.214160156250045, 51.278955078124966], + [5.796484375000034, 51.153076171875], + [5.693554687500011, 50.774755859375006] + ] + ] + }, + "properties": { "name": "Belgium", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [3.595410156250011, 11.6962890625], + [3.553906250000011, 11.631884765624989], + [3.490527343750017, 11.49921875], + [3.48779296875, 11.395410156249994], + [3.638867187500011, 11.176855468749991], + [3.65625, 11.154589843749989], + [3.6953125, 11.1203125], + [3.71640625, 11.07958984375], + [3.7568359375, 10.76875], + [3.83447265625, 10.607421875], + [3.771777343750017, 10.417626953124994], + [3.646582031250006, 10.408984374999989], + [3.60205078125, 10.004541015624994], + [3.3251953125, 9.778466796874994], + [3.044921875, 9.083837890624991], + [2.774804687500023, 9.048535156249997], + [2.703125, 8.371826171875], + [2.68603515625, 7.873730468749997], + [2.719335937500006, 7.616259765624989], + [2.7509765625, 7.541894531249994], + [2.78515625, 7.476855468749989], + [2.783984375000017, 7.443408203124989], + [2.765820312500011, 7.422509765624994], + [2.75048828125, 7.395068359374989], + [2.756738281250023, 7.067919921874989], + [2.721386718750011, 6.980273437499989], + [2.731738281250017, 6.852832031249989], + [2.7529296875, 6.771630859374994], + [2.774609375000011, 6.711718749999989], + [2.753710937500017, 6.661767578124994], + [2.735644531250017, 6.595703125], + [2.706445312500023, 6.369238281249991], + [1.62265625, 6.216796875], + [1.777929687500006, 6.294628906249997], + [1.530957031250011, 6.992431640625], + [1.624707031250011, 6.997314453125], + [1.600195312500006, 9.050048828125], + [1.3857421875, 9.361669921874991], + [1.330078125, 9.996972656249994], + [0.763378906250011, 10.386669921874997], + [0.900488281250006, 10.993261718749991], + [1.4267578125, 11.447119140624991], + [1.980371093750023, 11.418408203124997], + [2.38916015625, 11.897070312499991], + [2.366015625000017, 12.221923828125], + [2.805273437500006, 12.383837890624989], + [3.595410156250011, 11.6962890625] + ] + ] + }, + "properties": { "name": "Benin", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0.217480468750011, 14.911474609374991], + [0.163867187500017, 14.497216796874994], + [0.382519531250011, 14.245800781249997], + [0.42919921875, 13.972119140624997], + [0.6181640625, 13.703417968750003], + [1.201171875, 13.357519531249991], + [0.988476562500011, 13.36484375], + [0.9873046875, 13.041894531249994], + [1.56494140625, 12.635400390624994], + [2.104589843750006, 12.701269531249991], + [2.226269531250011, 12.466064453125], + [2.072949218750011, 12.309375], + [2.38916015625, 11.897070312499991], + [1.980371093750023, 11.418408203124997], + [1.4267578125, 11.447119140624991], + [0.900488281250006, 10.993261718749991], + [0.49267578125, 10.954980468749994], + [-0.068603515625, 11.115625], + [-0.299462890624994, 11.166894531249994], + [-0.627148437499983, 10.927392578124994], + [-1.04248046875, 11.010058593749989], + [-2.829931640624977, 10.998388671874991], + [-2.914892578124977, 10.592333984374989], + [-2.791162109374994, 10.432421874999989], + [-2.780517578125, 9.745849609375], + [-2.765966796874977, 9.658056640624991], + [-2.706201171874994, 9.533935546875], + [-2.695849609374989, 9.481347656249994], + [-2.7171875, 9.457128906249991], + [-2.7666015625, 9.424707031249994], + [-2.816748046874977, 9.425830078124989], + [-2.875146484374994, 9.500927734374997], + [-2.90087890625, 9.534619140624997], + [-2.948144531249994, 9.610742187499994], + [-2.98828125, 9.687353515624991], + [-3.042626953124994, 9.720898437499997], + [-3.095800781249977, 9.752099609374994], + [-3.160693359374989, 9.849169921874989], + [-3.223535156249994, 9.895458984374997], + [-3.289697265624994, 9.882226562499994], + [-3.581152343749977, 9.92431640625], + [-3.790625, 9.9171875], + [-4.18115234375, 9.78173828125], + [-4.267187499999977, 9.743261718749991], + [-4.332226562499983, 9.645703125], + [-4.406201171874983, 9.647998046874989], + [-4.526611328125, 9.723486328124991], + [-4.625830078124977, 9.713574218749997], + [-4.721777343749977, 9.756542968749997], + [-5.262304687499977, 10.319677734374991], + [-5.523535156249977, 10.426025390625], + [-5.490478515625, 11.042382812499994], + [-5.250244140625, 11.375781249999989], + [-5.288134765624989, 11.827929687499989], + [-4.699316406249977, 12.076171875], + [-4.4287109375, 12.337597656249997], + [-4.480615234374994, 12.672216796874991], + [-4.227099609374989, 12.793701171875], + [-4.328710937499977, 13.119042968749994], + [-4.151025390624994, 13.306201171875003], + [-3.947314453124989, 13.402197265624991], + [-3.527636718749989, 13.182714843749991], + [-3.3017578125, 13.28076171875], + [-3.248632812499977, 13.658349609374994], + [-2.950830078124994, 13.6484375], + [-2.873925781249994, 13.950732421875003], + [-2.586718749999989, 14.227587890625003], + [-2.113232421874983, 14.16845703125], + [-1.97304687499999, 14.45654296875], + [-1.049560546875, 14.81953125], + [-0.760449218749983, 15.047753906249994], + [-0.235888671874989, 15.059423828124991], + [0.217480468750011, 14.911474609374991] + ] + ] + }, + "properties": { "name": "Burkina Faso", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [91.94921875000003, 21.50805664062503], + [91.85947265625012, 21.532958984375057], + [91.90771484374997, 21.722949218750017], + [91.94921875000003, 21.50805664062503] + ] + ], + [ + [ + [91.87382812500002, 21.832128906249977], + [91.8375976562501, 21.750244140625], + [91.85068359375012, 21.927050781250045], + [91.87382812500002, 21.832128906249977] + ] + ], + [ + [ + [91.15078125000005, 22.175195312499966], + [91.04472656250002, 22.10517578125001], + [91.0794921875, 22.519726562499983], + [91.15078125000005, 22.175195312499966] + ] + ], + [ + [ + [91.55673828125006, 22.38222656250005], + [91.41132812500004, 22.475683593750006], + [91.45605468749997, 22.61650390624999], + [91.55673828125006, 22.38222656250005] + ] + ], + [ + [ + [90.77763671875007, 22.089306640624983], + [90.51503906250005, 22.06513671875001], + [90.68046875000007, 22.327490234375006], + [90.50292968749997, 22.835351562499994], + [90.59648437500002, 22.863525390625057], + [90.86816406250003, 22.48486328125], + [90.77763671875007, 22.089306640624983] + ] + ], + [ + [ + [88.94072265625002, 26.24536132812497], + [88.97041015625004, 26.250878906250023], + [88.95195312500002, 26.412109375], + [89.01865234375012, 26.410253906249977], + [89.28925781250004, 26.03759765625], + [89.54990234375006, 26.005273437499994], + [89.57275390625003, 26.13232421875003], + [89.67089843750003, 26.21381835937504], + [89.8229492187501, 25.94140625000003], + [89.82490234375004, 25.56015625], + [89.80087890625012, 25.33613281250001], + [89.81406250000006, 25.305371093749955], + [89.86630859375012, 25.293164062499955], + [90.11962890625003, 25.21997070312497], + [90.61308593750002, 25.16772460937497], + [92.04970703125005, 25.16948242187499], + [92.46835937500006, 24.94414062499999], + [92.38496093750004, 24.848779296875023], + [92.25126953125007, 24.895068359375045], + [92.22832031250002, 24.88134765625], + [92.22666015625012, 24.77099609374997], + [92.11748046875002, 24.493945312500017], + [92.06416015625004, 24.374365234375006], + [91.84619140624997, 24.17529296875003], + [91.72656250000003, 24.20507812499997], + [91.35019531250012, 24.06049804687501], + [91.16044921875007, 23.66064453125], + [91.359375, 23.06835937500003], + [91.43623046875004, 23.19990234375001], + [91.55351562500002, 22.991552734375006], + [91.61953125, 22.97968750000001], + [91.75097656250003, 23.053515625000017], + [91.75419921875007, 23.287304687499955], + [91.79003906249997, 23.361035156249983], + [91.937890625, 23.504687500000017], + [91.92949218750007, 23.598242187499977], + [91.92958984375, 23.68598632812501], + [91.97851562500003, 23.691992187499977], + [92.04404296875006, 23.677783203125017], + [92.24609375000003, 23.683593750000057], + [92.33378906250002, 23.242382812499955], + [92.36162109375002, 22.929003906250074], + [92.46445312500006, 22.734423828125045], + [92.49140625000004, 22.685400390625006], + [92.5612304687501, 22.04804687500001], + [92.57490234375004, 21.978076171875045], + [92.58281250000002, 21.940332031249994], + [92.5934570312501, 21.46733398437499], + [92.63164062500002, 21.306201171875045], + [92.33056640624997, 21.439794921874977], + [92.17958984375005, 21.293115234375023], + [92.32412109375, 20.791845703125063], + [92.0560546875, 21.1748046875], + [91.86337890625012, 22.350488281249966], + [91.7970703125001, 22.297460937500006], + [91.48007812500006, 22.884814453125045], + [91.2162109375, 22.642236328124994], + [90.94560546875002, 22.597021484375034], + [90.65625, 23.025488281250006], + [90.60400390624997, 23.59135742187499], + [90.55566406249997, 23.42153320312505], + [90.26914062500012, 23.455859375000017], + [90.59091796875012, 23.266406250000045], + [90.43505859374997, 22.751904296874955], + [90.61611328125, 22.362158203125034], + [90.23056640625006, 21.82978515625004], + [90.07119140625005, 21.887255859375017], + [90.20957031250006, 22.156591796875006], + [89.95419921875006, 22.022851562500023], + [89.91806640625012, 22.11616210937501], + [89.98515625000002, 22.466406250000063], + [89.81191406250005, 21.983496093750006], + [89.56855468750004, 21.767431640625034], + [89.48320312500007, 22.275537109374994], + [89.50058593750006, 21.914355468750045], + [89.35371093750004, 21.72109375], + [89.09394531250004, 21.872753906249983], + [89.05, 22.274609374999983], + [88.92070312500002, 22.632031249999955], + [88.89970703125002, 22.843505859375057], + [88.85058593749997, 23.040527343750057], + [88.928125, 23.186621093750063], + [88.72441406250002, 23.254980468750034], + [88.69765625, 23.493017578125034], + [88.63574218749997, 23.55], + [88.56738281249997, 23.674414062500034], + [88.69980468750006, 24.002539062500006], + [88.71376953125, 24.069628906250017], + [88.72656250000003, 24.186230468749955], + [88.7335937500001, 24.23090820312501], + [88.72353515625, 24.27490234375], + [88.64228515625004, 24.325976562500017], + [88.49853515625003, 24.34663085937504], + [88.3375, 24.45385742187503], + [88.225, 24.460644531249983], + [88.14550781250003, 24.485791015624955], + [88.07910156249997, 24.549902343750063], + [88.02343750000003, 24.62783203125005], + [88.03027343749997, 24.66445312500005], + [88.0451171875001, 24.713037109374994], + [88.1498046875, 24.914648437500034], + [88.1888671875, 24.92060546875001], + [88.27949218750004, 24.881933593750034], + [88.31337890625005, 24.8818359375], + [88.37294921875, 24.961523437499977], + [88.45625, 25.18842773437504], + [88.57382812500006, 25.18789062499999], + [88.92978515625012, 25.222998046875063], + [88.94414062500002, 25.290771484375], + [88.85478515625002, 25.333544921875017], + [88.76914062500006, 25.490478515625], + [88.50244140624997, 25.537011718749994], + [88.14746093749997, 25.811425781250023], + [88.1066406250001, 25.841113281250045], + [88.15078125000005, 26.08715820312497], + [88.33398437499997, 26.257519531249955], + [88.44042968749997, 26.369482421875034], + [88.38623046875003, 26.471533203125034], + [88.35146484375005, 26.482568359374966], + [88.36992187500002, 26.564111328124994], + [88.51826171875004, 26.517773437499955], + [88.68281250000004, 26.291699218749983], + [88.94072265625002, 26.24536132812497] + ] + ] + ] + }, + "properties": { "name": "Bangladesh", "childNum": 6 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.585351562500023, 43.742236328124996], + [28.465429687500006, 43.389306640624994], + [28.133691406250023, 43.39560546875], + [27.92890625000001, 43.1861328125], + [27.88886718750001, 42.74970703125], + [27.484765625000023, 42.468066406249996], + [28.014453125000017, 41.969042968749996], + [27.47480468750001, 41.946875], + [27.294921875, 42.079541015625], + [27.24433593750001, 42.09326171875], + [27.01171875, 42.058642578124996], + [26.96875, 42.02685546875], + [26.884863281250006, 41.991845703124994], + [26.615332031250006, 41.964892578124996], + [26.549707031250023, 41.896728515625], + [26.51142578125001, 41.8263671875], + [26.3603515625, 41.8015625], + [26.327246093750006, 41.772802734375], + [26.31796875, 41.744677734374996], + [26.320898437500006, 41.716552734375], + [26.200585937500023, 41.743798828124994], + [26.107421875, 41.72568359375], + [26.085546875, 41.704150390624996], + [26.066015625, 41.673242187499994], + [26.1435546875, 41.521533203124996], + [26.155175781250023, 41.434863281249996], + [26.135351562500006, 41.3857421875], + [26.06640625, 41.35068359375], + [25.92333984375, 41.311914062499994], + [25.784960937500017, 41.330419921875], + [25.52705078125001, 41.2998046875], + [25.381933593750006, 41.26435546875], + [25.25117187500001, 41.243554687499994], + [24.773730468750017, 41.356103515624994], + [24.595996093750017, 41.442724609375], + [24.5693359375, 41.4673828125], + [24.51826171875001, 41.552539062499996], + [24.487890625, 41.555224609374996], + [24.056054687500023, 41.527246093749994], + [24.03291015625001, 41.469091796875], + [24.011328125, 41.46005859375], + [23.635156250000023, 41.386767578124996], + [23.53583984375001, 41.386035156249996], + [23.433398437500017, 41.398730468749996], + [23.3720703125, 41.3896484375], + [23.23984375, 41.3849609375], + [23.15595703125001, 41.322070312499996], + [22.916015625, 41.336279296875], + [23.00361328125001, 41.73984375], + [22.836816406250023, 41.993603515625], + [22.344042968750017, 42.31396484375], + [22.42207031250001, 42.328857421875], + [22.445703125000023, 42.359130859375], + [22.523535156250006, 42.440966796874996], + [22.53242187500001, 42.481201171875], + [22.524218750000017, 42.50390625], + [22.43623046875001, 42.6291015625], + [22.466796875, 42.84248046875], + [22.799902343750006, 42.985742187499994], + [22.976855468750017, 43.18798828125], + [22.85957031250001, 43.25234375], + [22.819726562500023, 43.300732421875], + [22.767578125, 43.354150390624994], + [22.554589843750023, 43.454492187499994], + [22.36962890625, 43.781298828124996], + [22.36542968750001, 43.862109375], + [22.399023437500006, 43.96953125], + [22.420800781250023, 44.007421875], + [22.452529688228115, 44.0510441391688], + [22.547921095934313, 44.113823956634434], + [22.688564844478098, 44.254306249271906], + [23.02851562500001, 44.077978515625], + [22.868261718750006, 43.947900390624994], + [22.919042968750006, 43.83447265625], + [25.4970703125, 43.670800781249994], + [26.2158203125, 44.007275390625], + [27.0869140625, 44.167382812499994], + [27.425390625, 44.0205078125], + [27.88427734375, 43.987353515624996], + [28.221972656250017, 43.772851562499994], + [28.585351562500023, 43.742236328124996] + ] + ] + }, + "properties": { "name": "Bulgaria", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [50.60722656250002, 25.883105468750003], + [50.57490234375001, 25.806787109374994], + [50.465917968750006, 25.965527343749997], + [50.46992187500001, 26.228955078124997], + [50.5859375, 26.24072265625], + [50.60722656250002, 25.883105468750003] + ] + ] + }, + "properties": { "name": "Bahrain", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-73.02685546874994, 21.19238281250003], + [-73.16455078125003, 20.979150390625023], + [-73.68115234375003, 20.9755859375], + [-73.68037109374995, 21.103320312500017], + [-73.52309570312497, 21.190820312499966], + [-73.23535156249997, 21.15449218750004], + [-73.05849609375, 21.313378906249994], + [-73.02685546874994, 21.19238281250003] + ] + ], + [ + [ + [-73.041015625, 22.429052734375006], + [-72.74726562500001, 22.32739257812497], + [-73.16191406250002, 22.380712890625006], + [-73.041015625, 22.429052734375006] + ] + ], + [ + [ + [-74.20673828124998, 22.213769531250023], + [-74.27690429687499, 22.183691406250006], + [-73.906396484375, 22.527441406250063], + [-73.95419921874995, 22.71552734375001], + [-73.84995117187503, 22.731054687500063], + [-73.83652343749998, 22.538427734374977], + [-74.20673828124998, 22.213769531250023] + ] + ], + [ + [ + [-74.05751953124997, 22.723486328125034], + [-74.27460937499995, 22.71166992187503], + [-74.30703125, 22.83959960937497], + [-74.05751953124997, 22.723486328125034] + ] + ], + [ + [ + [-74.84047851562494, 22.894335937500017], + [-75.22333984374995, 23.165332031250074], + [-75.13056640624998, 23.267919921875006], + [-75.31596679687502, 23.668359374999966], + [-74.84047851562494, 22.894335937500017] + ] + ], + [ + [ + [-75.66455078124997, 23.45014648437501], + [-76.03710937500003, 23.60278320312503], + [-76.01044921875001, 23.671386718750057], + [-75.66455078124997, 23.45014648437501] + ] + ], + [ + [ + [-74.42944335937497, 24.068066406249955], + [-74.55092773437502, 23.96894531250001], + [-74.52690429687502, 24.105078125000034], + [-74.42944335937497, 24.068066406249955] + ] + ], + [ + [ + [-77.65771484374994, 24.249462890624955], + [-77.75527343750002, 24.163476562500023], + [-77.61538085937494, 24.216357421875045], + [-77.5615234375, 24.136816406250006], + [-77.57373046875, 23.739160156249994], + [-77.77128906249999, 23.752539062499977], + [-77.99990234374994, 24.219824218750063], + [-77.65771484374994, 24.249462890624955] + ] + ], + [ + [ + [-75.30839843749999, 24.2], + [-75.50322265624996, 24.139062500000023], + [-75.40893554687503, 24.265771484374994], + [-75.72666015625, 24.68935546875005], + [-75.30839843749999, 24.2] + ] + ], + [ + [ + [-77.34755859375, 25.013867187499983], + [-77.56191406249997, 25.030029296875], + [-77.27558593750001, 25.055761718750006], + [-77.34755859375, 25.013867187499983] + ] + ], + [ + [ + [-77.74384765625001, 24.70742187499999], + [-77.74521484375, 24.463476562500034], + [-78.04492187499997, 24.287451171875063], + [-78.14580078125002, 24.493457031250017], + [-78.36650390624993, 24.544189453125057], + [-78.435302734375, 24.627587890624994], + [-78.24272460937493, 24.65380859375], + [-78.21137695312495, 25.191259765624977], + [-77.97529296874998, 25.084814453125063], + [-77.74384765625001, 24.70742187499999] + ] + ], + [ + [ + [-76.64882812499994, 25.487402343750006], + [-76.34379882812496, 25.33203124999997], + [-76.12661132812497, 25.14052734375005], + [-76.16953125, 24.6494140625], + [-76.319970703125, 24.81767578124999], + [-76.21376953124994, 24.822460937499983], + [-76.160400390625, 25.119335937499983], + [-76.36928710937502, 25.312597656250006], + [-76.62070312499998, 25.43164062500003], + [-76.78066406249997, 25.426855468750006], + [-76.71083984374997, 25.564892578124983], + [-76.64882812499994, 25.487402343750006] + ] + ], + [ + [ + [-78.49287109375001, 26.729052734375017], + [-77.92246093749998, 26.69111328125001], + [-78.74365234374994, 26.50068359375004], + [-78.98564453124996, 26.689501953125045], + [-78.79804687500001, 26.58242187499999], + [-78.59711914062493, 26.797949218750006], + [-78.49287109375001, 26.729052734375017] + ] + ], + [ + [ + [-77.22563476562496, 25.904199218750023], + [-77.40317382812498, 26.02470703124996], + [-77.24677734374998, 26.156347656250034], + [-77.238623046875, 26.561132812500006], + [-77.510595703125, 26.845996093750045], + [-77.94375, 26.90356445312503], + [-77.53388671874995, 26.903417968750006], + [-77.06635742187501, 26.530175781249994], + [-77.03828124999998, 26.333447265624983], + [-77.16728515624996, 26.240332031250006], + [-77.22563476562496, 25.904199218750023] + ] + ] + ] + }, + "properties": { "name": "Bahamas", "childNum": 14 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [19.007128906250045, 44.86918945312502], + [19.348632812500057, 44.88090820312502], + [19.118457031250074, 44.359960937500006], + [19.583789062500017, 44.04345703125003], + [19.245019531249994, 43.96503906250004], + [19.495117187500057, 43.642871093750045], + [19.19433593749997, 43.533300781250006], + [19.164355468750017, 43.53544921874999], + [19.11279296874997, 43.52773437500002], + [19.080078125000057, 43.51772460937502], + [19.0283203125, 43.53251953125002], + [18.97421875, 43.54233398437498], + [18.95068359375, 43.52666015624999], + [19.036718750000034, 43.35732421875002], + [19.026660156250017, 43.292431640624955], + [18.97871093750001, 43.28540039062503], + [18.934667968750006, 43.339453125000034], + [18.85107421875003, 43.34633789062502], + [18.749218750000068, 43.283544921875006], + [18.67421875000008, 43.230810546875006], + [18.623632812500063, 43.027685546875034], + [18.488476562500068, 43.01215820312498], + [18.44384765625003, 42.96845703125004], + [18.46601562500001, 42.777246093749994], + [18.54589843750003, 42.64160156249997], + [18.436328125000017, 42.559716796874994], + [17.667578125000063, 42.897119140624994], + [17.585156250000068, 42.93837890625005], + [17.650488281250063, 43.006591796875], + [17.27382812500005, 43.44575195312501], + [16.300097656250017, 44.12451171875], + [16.10341796875008, 44.52099609375006], + [15.736621093750045, 44.76582031250001], + [15.788085937500057, 45.17895507812497], + [16.028320312500057, 45.18959960937502], + [16.29335937500005, 45.00883789062496], + [16.53066406250008, 45.21669921875002], + [16.918652343749983, 45.27656249999998], + [17.812792968750074, 45.078125], + [18.66259765625, 45.07744140624999], + [18.83642578125, 44.883251953124955], + [19.007128906250045, 44.86918945312502] + ] + ] + }, + "properties": { "name": "Bosnia and Herz.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.14794921875, 56.142919921875], + [28.284277343750006, 56.055908203125], + [29.375, 55.938720703125], + [29.353417968750023, 55.784375], + [29.412988281250023, 55.724853515625], + [29.482226562500017, 55.6845703125], + [29.63007812500001, 55.751171875], + [29.6845703125, 55.7697265625], + [29.744140625, 55.77041015625], + [29.82392578125001, 55.7951171875], + [29.881640625000017, 55.832324218749996], + [29.93701171875, 55.845263671874996], + [30.04267578125001, 55.83642578125], + [30.23359375000001, 55.84521484375], + [30.625585937500006, 55.666259765625], + [30.906835937500006, 55.57001953125], + [30.90058593750001, 55.397412109375], + [30.82099609375001, 55.3302734375], + [30.810546875, 55.306982421875], + [30.814453125, 55.2787109375], + [30.87744140625, 55.2234375], + [30.958886718750023, 55.13759765625], + [30.97773437500001, 55.08779296875], + [30.97773437500001, 55.05048828125], + [30.829882812500017, 54.914990234375], + [30.804492187500017, 54.8609375], + [30.791015625, 54.806005859375], + [30.798828125, 54.783251953124996], + [30.984179687500017, 54.6958984375], + [31.12128906250001, 54.648486328124996], + [31.152148437500017, 54.625341796875], + [31.074804687500006, 54.491796875], + [31.18476562500001, 54.452978515625], + [31.299121093750017, 54.29169921875], + [31.403613281250017, 54.195947265625], + [31.62841796875, 54.111181640625], + [31.7919921875, 54.055908203125], + [31.825976562500017, 54.030712890625], + [31.837792968750023, 54.00078125], + [31.825292968750006, 53.935009765625], + [31.783007812500017, 53.85498046875], + [31.754199218750017, 53.81044921875], + [31.82080078125, 53.791943359375], + [31.9921875, 53.796875], + [32.20039062500001, 53.78125], + [32.45097656250002, 53.6533203125], + [32.70429687500001, 53.336328125], + [32.64443359375002, 53.32890625], + [32.57802734375002, 53.31240234375], + [32.469335937500006, 53.2703125], + [32.14199218750002, 53.091162109375], + [31.849707031250006, 53.106201171875], + [31.668261718750017, 53.200927734375], + [31.417871093750023, 53.196044921875], + [31.38837890625001, 53.184814453125], + [31.364550781250017, 53.138964843749996], + [31.30292968750001, 53.060888671875], + [31.2587890625, 53.01669921875], + [31.29511718750001, 52.989794921874996], + [31.35302734375, 52.933447265625], + [31.442773437500023, 52.86181640625], + [31.53515625, 52.7982421875], + [31.564843750000023, 52.759228515625], + [31.585546875, 52.532470703125], + [31.57734375000001, 52.312304687499996], + [31.6015625, 52.284814453125], + [31.64990234375, 52.26220703125], + [31.690625, 52.220654296875], + [31.758593750000017, 52.125830078125], + [31.76337890625001, 52.10107421875], + [31.57373046875, 52.10810546875], + [31.345996093750017, 52.10537109375], + [31.21796875000001, 52.050244140625], + [30.98066406250001, 52.046191406249996], + [30.845703125, 51.953076171875], + [30.755273437500023, 51.895166015625], + [30.667285156250017, 51.814111328125], + [30.583886718750023, 51.68896484375], + [30.533007812500017, 51.596337890624994], + [30.56074218750001, 51.531494140625], + [30.602343750000017, 51.471240234374996], + [30.611718750000023, 51.40634765625], + [30.63251953125001, 51.355419921875], + [30.449511718750017, 51.274316406249994], + [30.160742187500006, 51.477880859375], + [29.346484375000017, 51.382568359375], + [29.10205078125, 51.6275390625], + [29.06074218750001, 51.625439453125], + [29.013085937500023, 51.598925781249996], + [28.97773437500001, 51.57177734375], + [28.927539062500017, 51.562158203124994], + [28.849511718750023, 51.540185546874994], + [28.73125, 51.433398437499996], + [28.690234375000017, 51.438867187499994], + [28.647753906250017, 51.45654296875], + [28.599023437500023, 51.542626953124994], + [28.532031250000017, 51.562451171875], + [27.85859375000001, 51.5923828125], + [27.7, 51.477978515625], + [27.689746093750017, 51.572412109374994], + [27.296289062500023, 51.597412109375], + [27.270117187500006, 51.613574218749996], + [27.141992187500023, 51.75205078125], + [27.074121093750023, 51.76083984375], + [26.95283203125001, 51.75400390625], + [26.7734375, 51.770703125], + [25.785742187500006, 51.923828125], + [24.361914062500006, 51.867529296875], + [24.280078125000017, 51.774707031249996], + [24.126855468750023, 51.6646484375], + [23.978320312500017, 51.59130859375], + [23.951171875, 51.58505859375], + [23.8642578125, 51.623974609375], + [23.79169921875001, 51.637109375], + [23.706835937500017, 51.64130859375], + [23.61376953125, 51.525390625], + [23.605273437500017, 51.517919921875], + [23.652441406250006, 52.040380859375], + [23.175097656250017, 52.28662109375], + [23.915429687500023, 52.770263671875], + [23.484667968750017, 53.939794921875], + [23.55908203125, 53.91982421875], + [23.733691406250017, 53.912255859375], + [24.191308593750023, 53.950439453125], + [24.236621093750017, 53.919970703124996], + [24.31796875, 53.89296875], + [24.620703125, 53.979833984375], + [24.768164062500006, 53.974658203124996], + [24.78925781250001, 53.9982421875], + [24.82568359375, 54.118994140625], + [24.869531250000023, 54.145166015625], + [25.04609375000001, 54.133056640625], + [25.111425781250006, 54.154931640625], + [25.179492187500017, 54.2142578125], + [25.46113281250001, 54.292773437499996], + [25.505664062500017, 54.264941406249996], + [25.52734375, 54.215136718749996], + [25.497363281250017, 54.175244140625], + [25.573046875000017, 54.139892578125], + [25.765234375, 54.17978515625], + [25.702539062500023, 54.29296875], + [25.61689453125001, 54.310107421874996], + [25.557519531250023, 54.310693359375], + [25.54736328125, 54.331835937499996], + [25.56757812500001, 54.37705078125], + [25.62031250000001, 54.460400390625], + [25.68515625, 54.535791015625], + [25.72480468750001, 54.564257812499996], + [25.73164062500001, 54.590380859374996], + [25.722460937500017, 54.71787109375], + [25.859277343750023, 54.919287109375], + [25.964453125, 54.94716796875], + [26.09296875000001, 54.9623046875], + [26.175195312500023, 55.003271484375], + [26.250781250000017, 55.12451171875], + [26.291796875000017, 55.139599609375], + [26.601171875, 55.130175781249996], + [26.6484375, 55.20419921875], + [26.775683593750017, 55.273095703125], + [26.760156250000023, 55.293359375], + [26.68125, 55.306445312499996], + [26.49531250000001, 55.318017578125], + [26.457617187500006, 55.34248046875], + [26.469531250000017, 55.371923828125], + [26.51923828125001, 55.44814453125], + [26.56660156250001, 55.546484375], + [26.5908203125, 55.62265625], + [26.593554687500017, 55.667529296874996], + [27.052539062500017, 55.83056640625], + [27.576757812500006, 55.798779296875], + [28.14794921875, 56.142919921875] + ] + ] + }, + "properties": { "name": "Belarus", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-87.8529296875, 17.4228515625], + [-87.92998046874996, 17.283007812500017], + [-87.826416015625, 17.546289062499994], + [-87.8529296875, 17.4228515625] + ] + ], + [ + [ + [-88.89404296875, 15.890625], + [-89.2328125, 15.888671875], + [-89.16147460937503, 17.81484375], + [-89.13354492187503, 17.970800781249977], + [-88.80634765624998, 17.965527343749983], + [-88.52299804687499, 18.445898437500063], + [-88.29565429687494, 18.47241210937503], + [-88.34926757812494, 18.358837890624983], + [-88.1302734375, 18.350732421875023], + [-88.08525390624999, 18.226123046875045], + [-88.27172851562494, 17.60986328125], + [-88.203466796875, 17.5166015625], + [-88.31342773437501, 16.632763671874983], + [-88.89404296875, 15.890625] + ] + ] + ] + }, + "properties": { "name": "Belize", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-64.73027343749999, 32.29345703125], + [-64.86284179687499, 32.273876953125], + [-64.66831054687499, 32.38193359375], + [-64.73027343749999, 32.29345703125] + ] + ] + }, + "properties": { "name": "Bermuda", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-58.15976562499999, -20.164648437500006], + [-58.18017578125, -19.81787109375], + [-59.09052734375, -19.286230468750006], + [-60.00737304687499, -19.29755859375001], + [-61.7568359375, -19.6453125], + [-62.276318359375, -20.5625], + [-62.27666015624999, -21.066015625], + [-62.65097656249999, -22.233691406250003], + [-62.84335937499999, -21.99726562500001], + [-63.92167968749999, -22.028613281250003], + [-63.97612304687499, -22.072558593750003], + [-64.26640624999999, -22.603320312500003], + [-64.30791015624999, -22.7953125], + [-64.32529296874999, -22.82763671875], + [-64.373974609375, -22.761035156250003], + [-64.4455078125, -22.58535156250001], + [-64.477734375, -22.4853515625], + [-64.5236328125, -22.37158203125], + [-64.60551757812499, -22.228808593750003], + [-64.992626953125, -22.109667968750003], + [-65.518798828125, -22.09453125], + [-65.686181640625, -22.11025390625001], + [-65.77104492187499, -22.099609375], + [-65.86015624999999, -22.01972656250001], + [-66.05859375, -21.879492187500006], + [-66.098583984375, -21.835058593750006], + [-66.17465820312499, -21.8056640625], + [-66.220166015625, -21.802539062500003], + [-66.24760742187499, -21.83046875], + [-66.28212890625, -21.94746093750001], + [-66.3224609375, -22.053125], + [-66.365185546875, -22.11376953125], + [-66.71171874999999, -22.21630859375], + [-66.99111328125, -22.509863281250006], + [-67.19487304687499, -22.82167968750001], + [-67.362255859375, -22.85517578125001], + [-67.57993164062499, -22.891699218750006], + [-67.79443359375, -22.879492187500006], + [-67.87944335937499, -22.82294921875001], + [-67.88173828125, -22.49335937500001], + [-68.18642578125, -21.61855468750001], + [-68.197021484375, -21.30029296875], + [-68.558251953125, -20.901953125], + [-68.484326171875, -20.62841796875], + [-68.74516601562499, -20.45859375], + [-68.75932617187499, -20.115527343750003], + [-68.560693359375, -19.967089843750003], + [-68.559375, -19.90234375], + [-68.578271484375, -19.856542968750006], + [-68.69619140625, -19.74072265625], + [-68.69829101562499, -19.72109375], + [-68.57529296874999, -19.56015625], + [-68.462890625, -19.43281250000001], + [-68.470166015625, -19.409960937500003], + [-68.49199218749999, -19.381933593750006], + [-68.85795898437499, -19.093359375], + [-68.96831054687499, -18.96796875000001], + [-68.97885742187499, -18.81298828125], + [-69.026806640625, -18.65625], + [-69.09228515625, -18.28242187500001], + [-69.145458984375, -18.14404296875], + [-69.0939453125, -18.05048828125001], + [-69.28232421874999, -17.96484375], + [-69.31337890625, -17.943164062500003], + [-69.5109375, -17.50605468750001], + [-69.51108398437499, -17.5048828125], + [-69.510986328125, -17.46035156250001], + [-69.521923828125, -17.388964843750003], + [-69.645703125, -17.24853515625], + [-69.62485351562499, -17.2001953125], + [-69.020703125, -16.6421875], + [-69.03291015625, -16.47597656250001], + [-68.8427734375, -16.337890625], + [-69.21757812499999, -16.14912109375001], + [-69.4208984375, -15.640625], + [-69.17246093749999, -15.236621093750003], + [-69.37470703125, -14.962988281250006], + [-69.35947265624999, -14.7953125], + [-68.87089843749999, -14.169726562500003], + [-69.07412109375, -13.682812500000011], + [-68.97861328124999, -12.880078125000011], + [-68.68525390625, -12.501953125], + [-69.57861328125, -10.951757812500006], + [-69.228515625, -10.955664062500006], + [-68.84833984375, -11.011132812500009], + [-68.678369140625, -11.11279296875], + [-68.39799804687499, -11.01875], + [-68.0716796875, -10.703125], + [-67.99169921875, -10.674414062500006], + [-67.83500976562499, -10.662792968750011], + [-67.72177734374999, -10.68310546875], + [-67.416943359375, -10.389843750000011], + [-66.575341796875, -9.89990234375], + [-65.396142578125, -9.71240234375], + [-65.298583984375, -10.146777343750003], + [-65.31308593749999, -10.253027343750006], + [-65.395458984375, -10.392285156250011], + [-65.4369140625, -10.449023437500003], + [-65.44711914062499, -10.507421875], + [-65.33403320312499, -10.892773437500011], + [-65.32377929687499, -11.024804687500009], + [-65.389892578125, -11.246289062500011], + [-65.1857421875, -11.74951171875], + [-64.783447265625, -12.059375], + [-64.42050781249999, -12.439746093750003], + [-63.68857421874999, -12.47802734375], + [-63.3466796875, -12.680078125], + [-63.06748046874999, -12.669140625000011], + [-62.76547851562499, -12.997265625000011], + [-62.11801757812499, -13.159765625], + [-62.09477539062499, -13.241992187500003], + [-61.944726562499994, -13.40625], + [-61.87412109374999, -13.470410156250011], + [-61.789941406249994, -13.525585937500011], + [-61.57568359375, -13.524804687500009], + [-61.51157226562499, -13.541210937500011], + [-61.41606445312499, -13.526562500000011], + [-61.129150390625, -13.49853515625], + [-61.07700195312499, -13.48974609375], + [-60.506591796875, -13.78984375], + [-60.372705078124994, -14.41875], + [-60.273339843749994, -15.088769531250009], + [-60.402001953124994, -15.0927734375], + [-60.583203125, -15.098339843750011], + [-60.53046875, -15.143164062500006], + [-60.38046875, -15.318261718750009], + [-60.242333984374994, -15.479589843750006], + [-60.20664062499999, -15.901953125], + [-60.18720703125, -16.132128906250003], + [-60.17558593749999, -16.269335937500003], + [-58.53793945312499, -16.328222656250006], + [-58.49658203125, -16.32666015625], + [-58.42368164062499, -16.307910156250003], + [-58.37539062499999, -16.28359375], + [-58.345605468749994, -16.284375], + [-58.35039062499999, -16.490820312500006], + [-58.470605468749994, -16.650195312500003], + [-58.478125, -16.70068359375], + [-58.45981445312499, -16.910742187500006], + [-58.417382812499994, -17.08056640625], + [-58.39599609375, -17.23427734375001], + [-58.34775390624999, -17.28212890625001], + [-57.99091796875, -17.51289062500001], + [-57.905029296875, -17.532324218750006], + [-57.832470703125, -17.512109375], + [-57.78886718749999, -17.573046875], + [-57.780175781249994, -17.67177734375001], + [-57.66166992187499, -17.947363281250006], + [-57.58647460937499, -18.12226562500001], + [-57.49565429687499, -18.214648437500003], + [-57.57402343749999, -18.279296875], + [-57.725, -18.733203125], + [-57.783105468749994, -18.91425781250001], + [-57.716796875, -19.044042968750006], + [-58.131494140624994, -19.74453125], + [-57.860742187499994, -19.979589843750006], + [-57.887597656249994, -20.02041015625001], + [-57.96015625, -20.04072265625001], + [-58.021142578124994, -20.05517578125], + [-58.09375, -20.15107421875001], + [-58.15976562499999, -20.164648437500006] + ] + ] + }, + "properties": { "name": "Bolivia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-48.48588867187493, -27.76699218749998], + [-48.554589843749994, -27.81220703125004], + [-48.542187499999955, -27.57480468749999], + [-48.41489257812495, -27.399609375], + [-48.48588867187493, -27.76699218749998] + ] + ], + [ + [ + [-48.584423828124955, -26.401562499999983], + [-48.665771484375, -26.289648437500006], + [-48.53974609374998, -26.170312500000023], + [-48.584423828124955, -26.401562499999983] + ] + ], + [ + [ + [-45.26025390624997, -23.889160156249986], + [-45.451416015625, -23.895605468749977], + [-45.30234375, -23.727539062500014], + [-45.26025390624997, -23.889160156249986] + ] + ], + [ + [ + [-44.12929687499994, -23.14189453124999], + [-44.36015624999999, -23.17207031250001], + [-44.24287109374998, -23.074121093750037], + [-44.12929687499994, -23.14189453124999] + ] + ], + [ + [ + [-38.90356445312497, -13.473437499999974], + [-38.97758789062496, -13.523535156249963], + [-39.02216796874998, -13.445605468749989], + [-38.907128906249994, -13.401074218749983], + [-38.90356445312497, -13.473437499999974] + ] + ], + [ + [ + [-38.743847656249955, -13.097070312500037], + [-38.668115234374966, -12.880175781249989], + [-38.601171875, -12.99257812499998], + [-38.743847656249955, -13.097070312500037] + ] + ], + [ + [ + [-44.49931640625002, -2.939648437499983], + [-44.597753906250006, -3.037597656249943], + [-44.4814453125, -2.717578125000031], + [-44.49931640625002, -2.939648437499983] + ] + ], + [ + [ + [-44.88310546874996, -1.317871093749986], + [-45.020849609375034, -1.372363281249974], + [-44.978662109374966, -1.267285156249983], + [-44.88310546874996, -1.317871093749986] + ] + ], + [ + [ + [-51.83251953124997, -1.433789062499969], + [-51.938378906249966, -1.452636718749986], + [-51.680029296875006, -1.086132812500026], + [-51.546044921874966, -0.649609375], + [-51.25400390624998, -0.54140625], + [-51.16074218749998, -0.666699218750011], + [-51.27631835937498, -1.02177734374996], + [-51.83251953124997, -1.433789062499969] + ] + ], + [ + [ + [-49.62866210937497, -0.229199218749969], + [-49.11699218749999, -0.163574218750014], + [-48.39267578124995, -0.29736328125], + [-48.83359375, -1.390039062500023], + [-49.038476562499994, -1.5140625], + [-49.17270507812498, -1.41259765625], + [-49.233984375000034, -1.59951171874998], + [-49.50664062499999, -1.511621093750023], + [-49.587890625, -1.712402343749972], + [-49.805126953124955, -1.790234375000026], + [-50.06572265625002, -1.703808593749997], + [-50.50761718749999, -1.787988281250009], + [-50.759765625, -1.240234374999972], + [-50.72949218749997, -1.126757812499946], + [-50.57695312499999, -1.103125], + [-50.709619140624994, -1.07773437499999], + [-50.796093749999955, -0.90625], + [-50.6455078125, -0.27285156249998], + [-50.24824218749998, -0.11640625], + [-49.62866210937497, -0.229199218749969] + ] + ], + [ + [ + [-50.65288085937499, -0.131640624999989], + [-50.926367187500034, -0.327343749999983], + [-51.03808593749994, -0.225878906250003], + [-50.84218750000002, -0.050195312500009], + [-50.65288085937499, -0.131640624999989] + ] + ], + [ + [ + [-49.44389648437499, -0.112402343749977], + [-49.83007812499997, -0.093896484375023], + [-49.50346679687496, 0.083691406250011], + [-49.37231445312497, 0.001074218749963], + [-49.44389648437499, -0.112402343749977] + ] + ], + [ + [ + [-49.73823242187498, 0.26816406250002], + [-49.917089843750006, -0.023193359375014], + [-50.339453125, 0.043359375000051], + [-50.27265624999998, 0.231738281249974], + [-49.73823242187498, 0.26816406250002] + ] + ], + [ + [ + [-50.42612304687498, 0.139257812500048], + [-50.44394531249998, -0.007666015624949], + [-50.623925781249966, 0.054394531249983], + [-50.372753906249955, 0.590869140625031], + [-50.33227539062497, 0.259033203125028], + [-50.42612304687498, 0.139257812500048] + ] + ], + [ + [ + [-50.152929687500006, 0.393017578125054], + [-50.26132812499998, 0.359179687500003], + [-50.281689453124955, 0.51650390624998], + [-50.05883789062503, 0.638037109374963], + [-50.152929687500006, 0.393017578125054] + ] + ], + [ + [ + [-50.29897460937502, 1.93852539062496], + [-50.45610351562496, 1.910498046875034], + [-50.49101562499996, 2.128613281249969], + [-50.34199218749998, 2.14174804687498], + [-50.29897460937502, 1.93852539062496] + ] + ], + [ + [ + [-59.69970703125, 4.353515625], + [-59.73857421874993, 4.226757812500026], + [-59.62021484374998, 4.023144531250026], + [-59.557763671874966, 3.960009765625031], + [-59.551123046875034, 3.933544921874969], + [-59.854394531249994, 3.5875], + [-59.99433593749998, 2.689990234375031], + [-59.88964843749997, 2.362939453125009], + [-59.75522460937495, 2.27412109375004], + [-59.74350585937498, 2.12163085937496], + [-59.75175781249996, 1.962402343750028], + [-59.75620117187498, 1.900634765624972], + [-59.666601562500006, 1.746289062499969], + [-59.53569335937499, 1.7], + [-59.23120117187494, 1.376025390625031], + [-58.82177734374994, 1.201220703125031], + [-58.787207031250006, 1.208496093750014], + [-58.73032226562498, 1.247509765625054], + [-58.68461914062499, 1.28105468749996], + [-58.511865234374966, 1.284667968749986], + [-58.506054687499926, 1.438671875000011], + [-58.39580078124993, 1.481738281249989], + [-58.38037109375, 1.530224609375011], + [-58.34067382812498, 1.587548828125051], + [-58.03466796875, 1.520263671875014], + [-57.9828125, 1.648437500000014], + [-57.87343750000002, 1.667285156250045], + [-57.79565429687497, 1.7], + [-57.59443359375001, 1.704101562499986], + [-57.54575195312495, 1.726074218750028], + [-57.31748046874998, 1.963476562499991], + [-57.27558593749998, 1.959228515625014], + [-57.189599609374966, 1.981591796875037], + [-57.11889648437494, 2.013964843749974], + [-57.09267578125002, 2.005810546874997], + [-57.03759765625, 1.936474609374997], + [-56.96953124999999, 1.91640625], + [-56.48281249999994, 1.942138671874986], + [-56.019921874999966, 1.842236328124983], + [-55.96333007812498, 1.85708007812498], + [-55.929638671874955, 1.8875], + [-55.92163085937503, 1.976660156250006], + [-55.91533203124999, 2.039550781250028], + [-55.96196289062496, 2.09511718749998], + [-56.02006835937499, 2.15815429687504], + [-56.073632812499994, 2.236767578124969], + [-56.13769531249997, 2.259033203124986], + [-56.12939453124997, 2.299511718749969], + [-56.08779296875002, 2.341308593750043], + [-56.045117187499955, 2.364404296875037], + [-56.02036132812498, 2.392773437500054], + [-55.993505859375006, 2.497509765624983], + [-55.9755859375, 2.515966796875006], + [-55.957470703124955, 2.52045898437504], + [-55.730566406250006, 2.406152343750023], + [-55.385351562500006, 2.440625], + [-55.34399414062503, 2.488769531249972], + [-55.28603515625002, 2.49965820312498], + [-55.18769531249998, 2.547509765625037], + [-55.114111328125006, 2.539208984375037], + [-55.07031249999994, 2.548339843750028], + [-55.005810546874955, 2.592968749999983], + [-54.97866210937502, 2.597656250000043], + [-54.968408203124966, 2.548339843750028], + [-54.92656249999999, 2.497363281250045], + [-54.876074218750006, 2.450390624999969], + [-54.72221679687499, 2.441650390624972], + [-54.69741210937502, 2.359814453124997], + [-54.66186523437497, 2.327539062499994], + [-54.61625976562499, 2.326757812500006], + [-54.59194335937502, 2.313769531250031], + [-54.55048828125001, 2.293066406249991], + [-54.51508789062498, 2.245458984374963], + [-54.43310546875, 2.207519531250057], + [-54.13007812499998, 2.121044921875026], + [-53.76777343749998, 2.354833984375048], + [-52.90346679687502, 2.211523437499977], + [-52.58300781250003, 2.528906249999977], + [-52.327880859375, 3.18173828125002], + [-51.65253906249998, 4.061279296874972], + [-51.54707031250001, 4.31088867187502], + [-51.219921874999955, 4.093603515624991], + [-50.71440429687502, 2.134033203125], + [-50.458886718749994, 1.829589843749972], + [-49.957128906250006, 1.65986328125004], + [-49.898876953124955, 1.16298828124998], + [-50.29443359374997, 0.835742187500003], + [-50.755078124999955, 0.222558593749966], + [-51.28291015625001, -0.085205078125028], + [-51.98081054687498, -1.367968749999974], + [-52.22924804687497, -1.3625], + [-52.664160156250034, -1.551757812500028], + [-51.94755859374996, -1.586718749999946], + [-50.89492187500002, -0.937597656249963], + [-50.690039062500006, -1.761718749999986], + [-50.40322265625002, -2.015527343750009], + [-49.999218749999955, -1.831835937499974], + [-49.71953125000002, -1.926367187499963], + [-49.31367187500001, -1.731738281250003], + [-49.63652343749996, -2.656933593750026], + [-49.45751953125, -2.504589843749983], + [-49.21103515624998, -1.916503906249986], + [-48.99130859374998, -1.829785156249997], + [-48.71000976562496, -1.487695312500023], + [-48.46293945312499, -1.613964843749997], + [-48.349804687499926, -1.482128906249955], + [-48.46806640624996, -1.393847656250003], + [-48.44980468749998, -1.145507812499943], + [-48.11508789062498, -0.7375], + [-47.557324218749955, -0.669921874999957], + [-47.418652343749955, -0.765917968749974], + [-47.39809570312502, -0.626660156250026], + [-45.45859374999995, -1.35625], + [-45.32915039062496, -1.71728515625], + [-45.07636718749998, -1.466406249999949], + [-44.72114257812498, -1.733496093750006], + [-44.778515624999955, -1.798828125], + [-44.651269531249966, -1.745800781250026], + [-44.537792968749955, -2.052734374999943], + [-44.75634765624997, -2.265527343749952], + [-44.66240234375002, -2.373242187499955], + [-44.435449218749966, -2.168066406249991], + [-44.38183593749997, -2.365527343749989], + [-44.52011718749998, -2.40546875000004], + [-44.589013671874994, -2.573437499999983], + [-44.72304687500002, -3.204785156249997], + [-44.43754882812496, -2.944433593749977], + [-44.228613281250006, -2.471289062499949], + [-44.105566406250006, -2.493457031250031], + [-44.19267578124999, -2.809570312499943], + [-43.93291015624999, -2.583496093749986], + [-43.45512695312499, -2.502050781250006], + [-43.38007812499998, -2.376074218750006], + [-42.93671874999998, -2.465039062500011], + [-42.24960937499998, -2.7919921875], + [-41.876171874999926, -2.746582031249986], + [-41.479931640624955, -2.916503906249972], + [-40.474560546874926, -2.795605468750026], + [-39.96469726562498, -2.861523437499955], + [-38.475781249999955, -3.717480468749997], + [-38.04882812500003, -4.216406250000034], + [-37.626318359375006, -4.592089843750003], + [-37.30146484375001, -4.713085937499969], + [-37.174658203125006, -4.912402343749974], + [-36.590722656249966, -5.097558593749952], + [-35.549414062500006, -5.129394531249957], + [-35.39257812499994, -5.250878906250009], + [-34.833886718749994, -7.024414062500014], + [-34.83466796874998, -7.97148437499996], + [-35.34086914062499, -9.230664062499983], + [-35.76396484374993, -9.702539062500023], + [-35.890820312499926, -9.687011718749957], + [-35.88544921875001, -9.84765625], + [-36.39833984374994, -10.484082031249983], + [-36.768310546875, -10.671679687500017], + [-37.18281249999998, -11.06845703125002], + [-37.35600585937502, -11.403906249999977], + [-37.35922851562495, -11.252539062499963], + [-37.68872070312503, -12.1], + [-38.019238281249955, -12.591308593750028], + [-38.401757812499994, -12.966210937500023], + [-38.69096679687502, -12.623925781250009], + [-38.85175781250001, -12.790136718750034], + [-38.76372070312502, -12.9072265625], + [-38.835302734375034, -13.147167968750026], + [-39.030908203124994, -13.365136718750023], + [-39.08935546875, -13.588183593749989], + [-38.988623046875006, -13.61503906249996], + [-39.04814453124996, -14.043945312500028], + [-38.94233398437498, -14.030664062499994], + [-39.05957031249997, -14.654785156249957], + [-38.88061523437503, -15.864257812499972], + [-39.20288085937503, -17.178125], + [-39.154003906249926, -17.70390625000003], + [-39.650781249999966, -18.252343750000037], + [-39.78330078124998, -19.571777343749986], + [-40.001367187499994, -19.74199218750003], + [-40.39594726562501, -20.56943359375002], + [-40.78925781250001, -20.90605468750003], + [-40.954541015624926, -21.237890624999963], + [-41.04726562499999, -21.505664062499974], + [-41.00029296875002, -21.99902343750003], + [-41.70551757812498, -22.30966796874999], + [-41.980419921874955, -22.580664062499963], + [-42.042382812499966, -22.947070312500003], + [-42.95830078124996, -22.96708984374999], + [-43.154296875, -22.725195312500006], + [-43.22416992187502, -22.991210937500014], + [-43.898828124999966, -23.10146484375001], + [-43.97382812499998, -23.057324218749983], + [-43.675976562499955, -23.00947265625001], + [-43.86616210937498, -22.910546875000023], + [-44.63725585937496, -23.05546875], + [-44.67382812499994, -23.206640625000034], + [-44.56967773437495, -23.27402343749999], + [-45.32539062499998, -23.59970703124999], + [-45.464306640624955, -23.802539062500017], + [-45.97207031250002, -23.795507812500006], + [-46.86728515624998, -24.236328125000014], + [-47.989160156249994, -25.03574218749999], + [-47.92939453124998, -25.16826171874999], + [-48.20273437499998, -25.41650390625003], + [-48.18593749999994, -25.309863281249974], + [-48.402490234374994, -25.27207031249999], + [-48.47612304687499, -25.44296875], + [-48.73173828124993, -25.36875], + [-48.6921875, -25.49150390625003], + [-48.40117187500002, -25.59736328125001], + [-48.665771484375, -25.844335937499963], + [-48.576318359374994, -25.935449218749966], + [-48.61943359374996, -26.17939453125001], + [-48.74829101562503, -26.26865234374999], + [-48.55415039062498, -27.195996093749997], + [-48.62080078124998, -28.075585937499966], + [-48.799658203125006, -28.575292968749977], + [-49.27128906249999, -28.87119140625005], + [-49.745996093749966, -29.363183593749994], + [-50.299511718749955, -30.42578125000003], + [-50.92138671874997, -31.25839843750002], + [-52.039208984374994, -32.11484374999996], + [-52.063232421875, -31.830371093750017], + [-51.68066406249994, -31.774609375000026], + [-51.272167968749955, -31.476953125000037], + [-51.16142578124996, -31.11884765625001], + [-50.980078125000034, -31.09423828124997], + [-50.94082031249994, -30.903710937499966], + [-50.68930664062495, -30.70419921874999], + [-50.71630859374994, -30.425976562499983], + [-50.58193359375002, -30.438867187500037], + [-50.56352539062499, -30.25361328125004], + [-51.02495117187493, -30.36865234375003], + [-51.29804687499998, -30.03486328124997], + [-51.15727539062499, -30.364257812500014], + [-51.283056640625034, -30.751562499999963], + [-51.35908203124998, -30.674511718749983], + [-51.506298828124955, -31.104492187500014], + [-51.97246093749999, -31.383789062499986], + [-52.19355468749998, -31.885546874999974], + [-52.12739257812501, -32.1677734375], + [-52.652246093749994, -33.137792968750006], + [-53.37060546874997, -33.74218750000003], + [-53.39755859374995, -33.737304687500014], + [-53.46357421875001, -33.70986328125002], + [-53.531347656250034, -33.65546875000004], + [-53.531347656250034, -33.1708984375], + [-53.511865234374966, -33.10869140625003], + [-53.482861328124926, -33.068554687500026], + [-53.39521484375001, -33.01035156249998], + [-53.31010742187499, -32.927050781249974], + [-53.21406249999998, -32.82109375], + [-53.12558593749998, -32.73671875], + [-53.15727539062496, -32.680078125], + [-53.601708984374994, -32.40302734374997], + [-53.76171875, -32.05683593749997], + [-53.920605468749926, -31.95234375], + [-54.220556640625034, -31.855175781249997], + [-54.58764648437503, -31.48515625000003], + [-55.036035156249994, -31.27900390625004], + [-55.091162109375034, -31.31396484374997], + [-55.173535156249926, -31.279589843749974], + [-55.557324218749955, -30.8759765625], + [-55.60302734375003, -30.85078125000001], + [-55.62714843749998, -30.858105468749997], + [-55.650488281250034, -30.89208984375], + [-55.66523437500001, -30.92490234375002], + [-55.807763671874994, -31.036718749999977], + [-55.87368164062502, -31.069628906250017], + [-55.95200195312498, -31.08085937499999], + [-56.0046875, -31.079199218750006], + [-56.01845703125002, -30.991894531249983], + [-55.998974609374955, -30.837207031250003], + [-56.4072265625, -30.44746093750001], + [-56.83271484374998, -30.107226562499974], + [-57.120507812499994, -30.144433593749994], + [-57.21445312499995, -30.283398437499983], + [-57.55229492187496, -30.261230468749986], + [-57.60888671875003, -30.187792968750045], + [-57.563867187499994, -30.139941406249974], + [-57.40522460937501, -30.03388671875004], + [-57.22465820312499, -29.782128906249994], + [-56.938623046874994, -29.594824218750034], + [-55.890527343749994, -28.370019531249994], + [-55.68725585937497, -28.38164062499996], + [-55.72548828125002, -28.20410156250003], + [-55.10151367187501, -27.866796874999963], + [-54.82910156250003, -27.55058593750003], + [-54.32700195312495, -27.423535156249997], + [-53.83818359375002, -27.121093750000014], + [-53.668554687500006, -26.288183593749977], + [-53.89116210937499, -25.66884765625001], + [-54.15458984374999, -25.523046874999963], + [-54.44394531249998, -25.625], + [-54.615869140624994, -25.576074218750023], + [-54.61054687499998, -25.432714843750034], + [-54.47314453124997, -25.22021484375], + [-54.43623046875001, -25.12128906250001], + [-54.281005859375, -24.30605468750001], + [-54.31826171874994, -24.128125], + [-54.26689453124996, -24.06582031250001], + [-54.241796875, -24.047265624999966], + [-54.44023437500002, -23.90175781249998], + [-54.62548828125, -23.8125], + [-54.98266601562494, -23.974511718749966], + [-55.081884765625006, -23.997656249999977], + [-55.1943359375, -24.017480468750023], + [-55.28691406249993, -24.00429687499999], + [-55.366308593750034, -23.99101562499996], + [-55.41591796875002, -23.95136718749997], + [-55.4423828125, -23.86533203125002], + [-55.4423828125, -23.792578125000034], + [-55.458886718749966, -23.686718750000054], + [-55.51845703124994, -23.627246093750017], + [-55.53828124999998, -23.580957031249994], + [-55.61767578125, -22.67148437499999], + [-55.74663085937499, -22.51269531249997], + [-55.753271484375006, -22.410156250000043], + [-55.84916992187499, -22.307617187500014], + [-55.991406249999926, -22.28115234375005], + [-56.18984374999994, -22.28115234375005], + [-56.246044921874926, -22.26464843749997], + [-56.39487304687498, -22.092675781250023], + [-56.44780273437502, -22.07617187500003], + [-56.77519531249999, -22.261328125], + [-57.955908203125034, -22.109179687500003], + [-57.94267578124999, -21.79833984375], + [-57.830224609374994, -20.99794921875001], + [-57.91513671874998, -20.690332031249966], + [-57.97905273437493, -20.65732421874999], + [-58.00224609374996, -20.465429687499977], + [-58.02539062499997, -20.41582031249999], + [-58.05844726562495, -20.38613281249998], + [-58.091503906249926, -20.33320312500004], + [-58.124609375000034, -20.293457031250014], + [-58.13779296874995, -20.237304687500043], + [-58.15976562499998, -20.164648437499977], + [-58.09375, -20.15107421874997], + [-58.021142578124994, -20.05517578124997], + [-57.96015625000001, -20.04072265625004], + [-57.887597656249966, -20.020410156249994], + [-57.860742187499994, -19.97958984375002], + [-58.029931640624994, -19.83271484375004], + [-58.131494140624994, -19.74453125], + [-57.71679687499997, -19.044042968750034], + [-57.73085937499999, -18.91718750000004], + [-57.783105468749994, -18.91425781249997], + [-57.725, -18.73320312500003], + [-57.57402343749993, -18.279296875000014], + [-57.49565429687496, -18.21464843749999], + [-57.58647460937499, -18.122265625], + [-57.66166992187493, -17.94736328124999], + [-57.78017578125002, -17.67177734374998], + [-57.78886718750002, -17.573046875000017], + [-57.83247070312501, -17.512109375000037], + [-57.90502929687497, -17.53232421874999], + [-57.990917968749955, -17.512890625000026], + [-58.20556640625, -17.363085937499974], + [-58.347753906250006, -17.282128906249994], + [-58.39599609374997, -17.234277343750023], + [-58.417382812499994, -17.08056640624997], + [-58.459814453125006, -16.910742187500006], + [-58.478125, -16.70068359375003], + [-58.470605468749994, -16.650195312500045], + [-58.35039062500002, -16.49082031249999], + [-58.34560546875002, -16.284375], + [-58.375390624999966, -16.283593749999966], + [-58.423681640625034, -16.30791015625003], + [-58.49658203124994, -16.32666015625003], + [-58.537939453125034, -16.32822265624999], + [-60.17558593749996, -16.26933593749999], + [-60.187207031249955, -16.132128906250017], + [-60.206640625, -15.90195312500002], + [-60.242333984374994, -15.479589843750034], + [-60.38046874999998, -15.318261718750023], + [-60.53046874999998, -15.143164062499977], + [-60.58320312499998, -15.098339843749983], + [-60.273339843749994, -15.088769531249994], + [-60.372705078124994, -14.41875], + [-60.506591796875, -13.78984375], + [-61.077001953125034, -13.489746093750014], + [-61.129150390625, -13.498535156250028], + [-61.41606445312502, -13.526562499999969], + [-61.511572265625006, -13.541210937500011], + [-61.789941406249966, -13.525585937500026], + [-61.87412109374998, -13.470410156249983], + [-61.944726562499966, -13.40625], + [-62.09477539062499, -13.241992187499989], + [-62.118017578125006, -13.15976562500002], + [-62.765478515625034, -12.99726562500004], + [-63.01518554687502, -12.80556640624998], + [-63.067480468750006, -12.669140624999983], + [-63.34667968749994, -12.68007812499999], + [-63.68857421874998, -12.478027343749957], + [-64.42050781249995, -12.439746093749974], + [-64.783447265625, -12.059375], + [-65.18574218749998, -11.749511718749957], + [-65.389892578125, -11.246289062500011], + [-65.33403320312499, -10.892773437500026], + [-65.44711914062503, -10.507421875000034], + [-65.4369140625, -10.449023437499946], + [-65.39545898437498, -10.392285156250026], + [-65.31308593749998, -10.253027343749991], + [-65.29858398437497, -10.146777343750017], + [-65.39614257812494, -9.712402343749986], + [-66.57534179687502, -9.899902343749986], + [-67.41694335937495, -10.389843749999969], + [-67.72177734374998, -10.683105468749943], + [-67.83500976562496, -10.662792968749983], + [-67.99169921875, -10.674414062499949], + [-68.07167968749994, -10.703125], + [-68.39799804687499, -11.01875], + [-68.678369140625, -11.11279296875], + [-68.84833984374998, -11.01113281249998], + [-69.228515625, -10.955664062499963], + [-69.46254882812497, -10.948144531250023], + [-69.57861328125, -10.951757812499963], + [-69.67402343749998, -10.9541015625], + [-69.83979492187501, -10.93339843749996], + [-69.96035156249997, -10.92988281250004], + [-70.06630859374997, -10.982421875], + [-70.22006835937503, -11.04765625], + [-70.29038085937498, -11.064257812499974], + [-70.34199218750001, -11.066699218750017], + [-70.39228515624995, -11.058593749999972], + [-70.45087890624998, -11.024804687500009], + [-70.53325195312496, -10.946875], + [-70.59653320312498, -10.976855468750017], + [-70.642333984375, -11.010253906249986], + [-70.59916992187499, -9.620507812500009], + [-70.54111328124998, -9.4375], + [-70.60791015625, -9.463671875000031], + [-70.63691406249995, -9.478222656249969], + [-71.041748046875, -9.81875], + [-71.11528320312499, -9.852441406250009], + [-71.33940429687499, -9.988574218750031], + [-72.18159179687495, -10.003710937500003], + [-72.37905273437497, -9.51015625], + [-73.20942382812493, -9.411425781249946], + [-73.08984375, -9.26572265625002], + [-72.970361328125, -9.120117187500028], + [-72.97402343750002, -8.9931640625], + [-73.07050781249995, -8.8828125], + [-73.203125, -8.719335937499991], + [-73.30244140624995, -8.654003906250011], + [-73.36040039062496, -8.479296875000031], + [-73.39814453125001, -8.458984374999986], + [-73.54912109374993, -8.34580078125002], + [-73.73203125, -7.875390625], + [-73.72041015624993, -7.782519531250017], + [-73.76689453124999, -7.753515624999963], + [-73.82207031249996, -7.738964843750026], + [-73.89462890624998, -7.654785156250014], + [-73.946875, -7.611230468750023], + [-73.98173828124996, -7.58505859375002], + [-74.00205078125003, -7.556054687499966], + [-73.98173828124996, -7.535742187500006], + [-73.95849609374994, -7.506640625000031], + [-73.96430664062498, -7.378906250000028], + [-73.74946289062498, -7.335351562500037], + [-73.72041015624993, -7.309277343749969], + [-73.758203125, -7.172753906249952], + [-73.79301757812499, -7.135058593750003], + [-73.75810546874999, -6.90576171875], + [-73.137353515625, -6.4658203125], + [-73.23554687500001, -6.098437500000017], + [-73.209375, -6.028710937500023], + [-73.16289062499996, -5.933398437499974], + [-72.97988281249997, -5.634863281249991], + [-72.88706054687498, -5.122753906250026], + [-72.83193359374994, -5.09375], + [-72.69873046874997, -5.067187499999989], + [-72.60834960937495, -5.009570312499974], + [-72.46899414062497, -4.901269531250023], + [-72.35283203124993, -4.786035156249994], + [-72.25678710937501, -4.74892578124998], + [-71.8447265625, -4.504394531249986], + [-70.97368164062499, -4.350488281249994], + [-70.86601562499999, -4.229589843749963], + [-70.79951171874995, -4.173339843749957], + [-70.72158203124997, -4.15888671875004], + [-70.53066406249997, -4.167578125000034], + [-70.40463867187498, -4.150097656250026], + [-70.34365234375, -4.193652343750017], + [-70.31689453124994, -4.246972656250037], + [-70.23916015625002, -4.30117187499998], + [-70.12880859375, -4.286621093749943], + [-70.05332031249998, -4.333105468750006], + [-70.00395507812496, -4.327246093749963], + [-69.97202148437503, -4.30117187499998], + [-69.96591796875003, -4.2359375], + [-69.94819335937498, -4.200585937500009], + [-69.66904296875003, -2.667675781249997], + [-69.40024414062498, -1.194921874999977], + [-69.63398437500001, -0.50927734375], + [-70.07050781249993, -0.13886718750004], + [-70.05390624999993, 0.578613281250028], + [-69.47211914062498, 0.72993164062504], + [-69.15332031249994, 0.65878906250002], + [-69.31181640624999, 1.050488281249969], + [-69.85214843750003, 1.05952148437504], + [-69.84858398437493, 1.708740234375043], + [-68.17656249999999, 1.719824218749991], + [-68.25595703125, 1.845507812500017], + [-68.19379882812495, 1.987011718749983], + [-67.93623046874998, 1.748486328124969], + [-67.40043945312499, 2.116699218750028], + [-67.11923828124998, 1.703613281249986], + [-67.082275390625, 1.185400390625006], + [-66.87602539062499, 1.223046875000037], + [-66.34711914062498, 0.7671875], + [-66.06005859375003, 0.78535156250004], + [-65.68144531249999, 0.983447265624989], + [-65.52299804687493, 0.843408203124966], + [-65.55605468750002, 0.687988281250014], + [-65.47338867187497, 0.691259765624977], + [-65.10375976562497, 1.108105468749983], + [-64.20502929687493, 1.52949218750004], + [-64.00849609374995, 1.931591796874969], + [-63.43251953124994, 2.155566406250045], + [-63.389257812500006, 2.411914062500045], + [-64.04658203124998, 2.502392578124997], + [-64.22109375000002, 3.587402343749972], + [-64.66899414062496, 4.01181640625002], + [-64.788671875, 4.276025390625023], + [-64.57636718750001, 4.139892578125], + [-64.19248046874995, 4.126855468750009], + [-64.02148437500003, 3.929101562500051], + [-63.33867187500002, 3.943896484375045], + [-62.85698242187502, 3.593457031249969], + [-62.71210937499998, 4.01791992187502], + [-62.41064453124994, 4.156738281249972], + [-62.153125, 4.098388671874986], + [-61.82084960937496, 4.197021484375], + [-61.28007812500002, 4.516894531249974], + [-61.00283203125002, 4.535253906249991], + [-60.603857421875006, 4.94936523437498], + [-60.671972656250034, 5.164355468749989], + [-60.71196289062499, 5.191552734375023], + [-60.742138671874926, 5.202050781250037], + [-60.6513671875, 5.221142578125011], + [-60.45952148437499, 5.188085937500034], + [-60.40878906249998, 5.21015625], + [-60.33520507812497, 5.199316406250006], + [-60.241650390624926, 5.257958984374966], + [-60.14204101562498, 5.238818359374974], + [-59.990673828124955, 5.082861328124991], + [-60.14863281249998, 4.533251953125031], + [-59.69970703125, 4.353515625] + ] + ] + ] + }, + "properties": { "name": "Brazil", "childNum": 17 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-59.493310546874994, 13.081982421874997], + [-59.611328125, 13.102099609374989], + [-59.6466796875, 13.303125], + [-59.427636718749994, 13.152783203124997], + [-59.493310546874994, 13.081982421874997] + ] + ] + }, + "properties": { "name": "Barbados", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [115.02675781250005, 4.899707031249989], + [115.1400390625, 4.899755859374991], + [115.290625, 4.352587890624989], + [115.10703125000006, 4.390429687499974], + [115.02675781250005, 4.899707031249989] + ] + ], + [ + [ + [115.02675781250005, 4.899707031249989], + [114.74667968750006, 4.718066406250017], + [114.84023437500005, 4.393212890625009], + [114.65410156250007, 4.037646484375045], + [114.0638671875, 4.592675781249966], + [114.42441406250006, 4.660400390625], + [114.99541015625002, 5.022363281250023], + [115.02675781250005, 4.899707031249989] + ] + ] + ] + }, + "properties": { "name": "Brunei", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [91.63193359375003, 27.759960937499997], + [91.5947265625, 27.557666015624996], + [91.74306640625002, 27.442529296874994], + [91.85126953125001, 27.438623046874994], + [91.95097656249999, 27.458300781249996], + [91.99082031250003, 27.4501953125], + [92.044921875, 27.364697265624997], + [92.08339843750002, 27.290625], + [92.03115234375002, 27.214306640624997], + [92.00253906250003, 27.147363281249994], + [91.99228515625003, 27.099902343749996], + [91.99863281250003, 27.079296875], + [92.03085937500003, 27.040820312499996], + [92.06816406249999, 26.9751953125], + [92.07343750000001, 26.91484375], + [92.04970703125002, 26.874853515625], + [91.99833984374999, 26.85498046875], + [91.84208984374999, 26.852978515624997], + [91.67158203125001, 26.802001953125], + [91.517578125, 26.807324218749997], + [91.45585937499999, 26.866894531249997], + [91.4267578125, 26.867089843749994], + [91.28652343750002, 26.789941406249994], + [90.73964843750002, 26.771679687499997], + [90.34589843750001, 26.890332031249997], + [90.2060546875, 26.847509765625], + [90.12294921875002, 26.754589843749997], + [89.94316406249999, 26.723925781249996], + [89.76386718750001, 26.7015625], + [89.60996093750003, 26.719433593749997], + [89.58613281250001, 26.778955078124994], + [89.33212890625003, 26.8486328125], + [89.14824218749999, 26.816162109375], + [89.04091796875002, 26.865039062499996], + [88.85761718750001, 26.961474609374996], + [88.73876953125, 27.175585937499996], + [88.76035156250003, 27.218115234375], + [88.88164062499999, 27.2974609375], + [88.89140624999999, 27.316064453124994], + [88.94755859374999, 27.464013671874994], + [89.48066406250001, 28.059960937499994], + [89.53691406249999, 28.107421875], + [89.65273437500002, 28.15830078125], + [89.74980468749999, 28.188183593749997], + [89.81689453125, 28.256298828124997], + [89.89785156250002, 28.294140625], + [89.98105468750003, 28.311181640624994], + [90.34824218750003, 28.243945312499996], + [90.36298828125001, 28.216503906249997], + [90.33310546875003, 28.093994140625], + [90.35273437500001, 28.080224609374994], + [90.47734374999999, 28.070849609374996], + [90.63007812500001, 28.078564453124997], + [90.71572265625002, 28.071728515624997], + [91.02080078124999, 27.970068359375], + [91.07773437500003, 27.974462890625], + [91.22587890624999, 28.071240234374997], + [91.27304687500003, 28.078369140625], + [91.30683593750001, 28.064013671874996], + [91.36757812500002, 28.021630859374994], + [91.64189453124999, 27.923242187499994], + [91.63193359375003, 27.759960937499997] + ] + ] + }, + "properties": { "name": "Bhutan", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [25.2587890625, -17.793554687500006], + [25.242285156250006, -17.969042968750003], + [25.939355468750023, -18.93867187500001], + [26.168066406250006, -19.53828125000001], + [27.17822265625, -20.10097656250001], + [27.28076171875, -20.47871093750001], + [27.679296875, -20.503027343750006], + [27.66943359375, -21.064257812500003], + [28.014062500000023, -21.55419921875], + [29.02558593750001, -21.796875], + [29.042382812500023, -22.018359375], + [29.237207031250023, -22.07949218750001], + [29.315234375000017, -22.15771484375], + [29.36484375, -22.193945312500006], + [29.1298828125, -22.21328125], + [29.013476562500017, -22.278417968750006], + [28.94580078125, -22.395117187500006], + [28.83984375, -22.480859375], + [28.21015625000001, -22.693652343750003], + [27.812597656250006, -23.108007812500006], + [27.7685546875, -23.14892578125], + [27.085546875, -23.577929687500003], + [26.835058593750006, -24.240820312500006], + [26.617773437500006, -24.3955078125], + [26.451757812500006, -24.58271484375001], + [26.39716796875001, -24.613574218750003], + [26.130859375, -24.671484375], + [26.031835937500006, -24.702441406250003], + [25.912109375, -24.74746093750001], + [25.518164062500006, -25.66279296875001], + [25.21337890625, -25.75625], + [24.33056640625, -25.74287109375001], + [24.19296875, -25.632910156250006], + [23.969531250000017, -25.626074218750006], + [23.89375, -25.600878906250003], + [23.389257812500006, -25.29140625], + [23.148730468750017, -25.288671875], + [22.878808593750023, -25.45791015625001], + [22.59765625, -26.13271484375001], + [22.548632812500017, -26.17841796875001], + [22.47089843750001, -26.219042968750003], + [22.217578125000017, -26.38886718750001], + [22.090917968750006, -26.580175781250006], + [22.01093750000001, -26.635839843750006], + [21.78828125000001, -26.710058593750006], + [21.738085937500017, -26.80683593750001], + [21.694726562500023, -26.840917968750006], + [20.73984375, -26.84882812500001], + [20.641406250000017, -26.7421875], + [20.79316406250001, -25.915625], + [20.4306640625, -25.147070312500006], + [19.98046875, -24.77675781250001], + [19.977343750000017, -22.00019531250001], + [20.9794921875, -21.9619140625], + [20.97412109375, -18.31884765625], + [23.219335937500006, -17.99970703125001], + [23.599707031250006, -18.4599609375], + [24.243945312500017, -18.0234375], + [24.530566406250017, -18.052734375], + [24.909082031250023, -17.821386718750006], + [25.2587890625, -17.793554687500006] + ] + ] + }, + "properties": { "name": "Botswana", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [22.86005859375001, 10.919677734375], + [23.646289062500017, 9.822900390624994], + [23.62265625, 9.340625], + [23.46826171875, 9.11474609375], + [23.53730468750001, 8.815820312499994], + [24.147363281250023, 8.665625], + [24.291406250000023, 8.29140625], + [24.853320312500017, 8.137548828124991], + [25.20039062500001, 7.807910156249989], + [25.18134765625001, 7.557226562499991], + [25.27890625, 7.427490234375], + [26.36181640625, 6.635302734374989], + [26.30859375, 6.455322265625], + [26.514257812500006, 6.069238281249994], + [27.143945312500023, 5.722949218749989], + [27.4033203125, 5.109179687499989], + [27.071875, 5.199755859374989], + [26.822070312500017, 5.062402343749994], + [25.52509765625001, 5.31210937499999], + [25.065234375000017, 4.967431640624994], + [24.31982421875, 4.994140625], + [23.41718750000001, 4.663134765624989], + [22.864550781250017, 4.723876953125], + [22.422167968750017, 4.134960937499997], + [20.55810546875, 4.462695312499989], + [20.226367187500017, 4.829638671874989], + [19.806542968750023, 5.089306640624997], + [19.5009765625, 5.127490234374989], + [19.06855468750001, 4.891406249999989], + [18.594140625000023, 4.346240234374989], + [18.6103515625, 3.478417968749994], + [18.474414062500017, 3.622998046874997], + [18.160937500000017, 3.499804687499989], + [17.491601562500023, 3.687304687499989], + [16.610742187500023, 3.50537109375], + [16.468554687500017, 2.831738281249997], + [16.183398437500017, 2.270068359374989], + [16.0634765625, 2.90859375], + [15.128710937500017, 3.826904296875], + [15.063574218750006, 4.284863281249997], + [14.73125, 4.602392578124991], + [14.56298828125, 5.279931640624994], + [14.616894531250011, 5.865136718749994], + [14.43115234375, 6.038720703124994], + [14.7392578125, 6.27978515625], + [15.206738281250011, 7.206152343749991], + [15.480078125, 7.523779296874991], + [15.957617187500006, 7.507568359375], + [16.37890625, 7.683544921874997], + [16.545312500000023, 7.865478515625], + [16.784765625, 7.550976562499997], + [17.6494140625, 7.98359375], + [18.56416015625001, 8.0458984375], + [19.108691406250017, 8.656152343749994], + [18.886035156250017, 8.836035156249991], + [18.95625, 8.938867187499994], + [20.342089843750017, 9.127099609374994], + [20.773242187500017, 9.405664062499994], + [21.682714843750006, 10.289843749999989], + [21.771484375, 10.642822265625], + [22.49384765625001, 10.996240234374994], + [22.86005859375001, 10.919677734375] + ] + ] + }, + "properties": { "name": "Central African Rep.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-59.78759765624997, 43.939599609374994], + [-60.11748046874996, 43.95336914062506], + [-59.727148437500006, 44.002832031249994], + [-59.78759765624997, 43.939599609374994] + ] + ], + [ + [ + [-66.7625, 44.68178710937502], + [-66.8970703125, 44.62890625], + [-66.80214843749994, 44.80537109374998], + [-66.7625, 44.68178710937502] + ] + ], + [ + [ + [-60.961572265624966, 45.48994140625001], + [-61.081738281249926, 45.55781249999998], + [-60.91245117187498, 45.56728515625005], + [-60.961572265624966, 45.48994140625001] + ] + ], + [ + [ + [-73.69531249999997, 45.58549804687502], + [-73.85771484375002, 45.573583984375006], + [-73.57236328124998, 45.69448242187502], + [-73.69531249999997, 45.58549804687502] + ] + ], + [ + [ + [-73.56650390625003, 45.469091796875034], + [-73.960546875, 45.44140624999997], + [-73.68745117187498, 45.561425781249994], + [-73.47607421874997, 45.704736328124994], + [-73.56650390625003, 45.469091796875034] + ] + ], + [ + [ + [-61.10517578124998, 45.94472656250002], + [-60.86523437499997, 45.983496093750006], + [-61.05903320312501, 45.70336914062497], + [-60.73789062499995, 45.75141601562498], + [-60.46059570312494, 45.96870117187501], + [-60.733300781249994, 45.956591796875045], + [-60.297949218750034, 46.31123046874998], + [-60.22646484374994, 46.19555664062506], + [-59.86503906249993, 46.159521484375006], + [-59.8421875, 45.941552734374994], + [-60.67294921874995, 45.59082031250006], + [-61.28369140624994, 45.573876953124966], + [-61.44980468749995, 45.71621093750002], + [-61.40864257812501, 46.17036132812498], + [-60.87016601562499, 46.796777343749966], + [-60.40820312500003, 47.00351562499998], + [-60.332910156249966, 46.737011718749955], + [-60.49453125000002, 46.270263671875], + [-61.10517578124998, 45.94472656250002] + ] + ], + [ + [ + [-63.811279296875, 46.46870117187501], + [-63.68144531249993, 46.561914062499994], + [-63.12939453125, 46.422216796875034], + [-62.02373046874999, 46.42158203125001], + [-62.52607421875001, 46.20288085937503], + [-62.531347656250034, 45.977294921875], + [-63.02207031249998, 46.06660156249998], + [-62.89453125000003, 46.12358398437496], + [-63.056347656249955, 46.22392578124996], + [-62.97846679687498, 46.31635742187498], + [-63.21347656249998, 46.15986328124998], + [-63.641015624999966, 46.23046874999997], + [-63.758642578125034, 46.397607421874994], + [-64.11083984375003, 46.425439453124994], + [-64.13603515624999, 46.59970703125006], + [-64.388037109375, 46.640869140625], + [-63.99355468750002, 47.06157226562502], + [-64.08789062499997, 46.77543945312499], + [-63.811279296875, 46.46870117187501] + ] + ], + [ + [ + [-61.91411132812496, 47.284521484375034], + [-61.77255859374998, 47.25981445312499], + [-62.00830078124994, 47.23427734375002], + [-61.924707031249966, 47.425146484375006], + [-61.3955078125, 47.63764648437504], + [-61.91411132812496, 47.284521484375034] + ] + ], + [ + [ + [-54.227148437500034, 47.44135742187501], + [-54.32597656250002, 47.408105468749994], + [-54.12817382812494, 47.646826171875034], + [-54.227148437500034, 47.44135742187501] + ] + ], + [ + [ + [-74.70888671874997, 45.0038574218751], + [-73.55810546875, 45.425097656250045], + [-73.1595703125, 46.01005859375002], + [-72.10927734374997, 46.55122070312504], + [-71.26118164062495, 46.75625], + [-70.51948242187501, 47.032519531250045], + [-69.47104492187503, 47.96728515625006], + [-68.23818359374994, 48.62641601562504], + [-66.17817382812493, 49.21313476562503], + [-64.83632812499994, 49.191748046875006], + [-64.2162109375, 48.873632812500034], + [-64.51372070312493, 48.84111328124999], + [-64.24609374999994, 48.69111328124998], + [-64.34882812500001, 48.423193359375034], + [-65.259423828125, 48.02124023437503], + [-65.92670898437495, 48.188867187499994], + [-66.70439453125002, 48.0224609375], + [-66.35961914062494, 48.06064453125006], + [-65.84941406250002, 47.91103515625005], + [-65.60722656249996, 47.67001953125006], + [-65.00166015624995, 47.84682617187502], + [-64.70322265625, 47.72485351562503], + [-64.91220703125003, 47.36865234375003], + [-65.31889648437502, 47.101220703124994], + [-64.831396484375, 47.06079101562503], + [-64.88251953124993, 46.822851562500034], + [-64.54150390625, 46.240332031250034], + [-63.91591796875002, 46.165820312500045], + [-63.831933593749966, 46.107177734375], + [-64.05639648437503, 46.021337890625006], + [-63.70288085937494, 45.858007812500034], + [-62.70068359374997, 45.740576171875006], + [-62.750097656250006, 45.64824218750002], + [-62.483056640624966, 45.62182617187506], + [-61.955517578124955, 45.86816406249997], + [-61.776513671874994, 45.655615234375006], + [-61.49228515624998, 45.68701171875], + [-61.350488281249966, 45.57368164062501], + [-61.28198242187494, 45.441064453124994], + [-61.46098632812502, 45.36669921875003], + [-61.03154296875002, 45.29174804687506], + [-63.306298828124994, 44.64257812500003], + [-63.60400390624997, 44.68320312500006], + [-63.60976562499999, 44.47998046875006], + [-63.999707031249926, 44.64492187499999], + [-64.10087890624993, 44.487451171874966], + [-64.1669921875, 44.58666992187503], + [-64.28608398437493, 44.55034179687499], + [-64.27568359374993, 44.33408203124998], + [-65.48168945312497, 43.51806640625], + [-65.73813476562498, 43.56074218750001], + [-65.88691406250001, 43.79521484374999], + [-66.125732421875, 43.813818359375034], + [-66.19306640624995, 44.143847656250045], + [-65.86801757812498, 44.56879882812501], + [-66.14638671875002, 44.43593750000005], + [-66.090625, 44.50493164062499], + [-64.44814453125, 45.33745117187502], + [-64.13549804687497, 45.023046875], + [-64.09316406249997, 45.21708984375002], + [-63.368017578125034, 45.36479492187502], + [-64.87314453124998, 45.35458984375006], + [-64.31464843749998, 45.83569335937503], + [-64.48222656250002, 45.80634765624998], + [-64.63271484375002, 45.94663085937506], + [-64.77851562499998, 45.63842773437497], + [-65.88447265624995, 45.22290039062506], + [-66.10976562500002, 45.316601562499955], + [-66.02656249999995, 45.417578125], + [-66.43984374999994, 45.09589843750001], + [-66.87246093749997, 45.067285156249966], + [-67.12485351562498, 45.16943359375], + [-67.366943359375, 45.17377929687498], + [-67.43266601562496, 45.603125], + [-67.80224609374994, 45.7275390625], + [-67.806787109375, 47.08281249999999], + [-68.23549804687502, 47.34594726562503], + [-68.93720703124998, 47.21123046875002], + [-69.0501953125, 47.426611328125034], + [-69.24287109374998, 47.46298828124998], + [-70.00771484375002, 46.70893554687501], + [-70.296240234375, 45.90610351562506], + [-70.86503906249999, 45.27070312500001], + [-71.327294921875, 45.29008789062496], + [-71.51752929687495, 45.00756835937497], + [-74.663232421875, 45.00390625000003], + [-74.70888671874997, 45.0038574218751] + ] + ], + [ + [ + [-126.09208984374995, 49.35400390625003], + [-126.06401367187499, 49.26362304687501], + [-126.22963867187498, 49.29565429687506], + [-126.09208984374995, 49.35400390625003] + ] + ], + [ + [ + [-54.55439453125001, 49.5888671875], + [-54.786523437499966, 49.496142578125045], + [-54.86357421875002, 49.576074218749966], + [-54.55439453125001, 49.5888671875] + ] + ], + [ + [ + [-54.093701171874955, 49.74443359374999], + [-53.98066406250001, 49.66196289062498], + [-54.28613281249997, 49.595361328124994], + [-54.27763671875002, 49.71147460937502], + [-54.093701171874955, 49.74443359374999] + ] + ], + [ + [ + [-126.64121093749999, 49.605810546875006], + [-126.93857421874999, 49.71845703125004], + [-126.92583007812497, 49.837744140625006], + [-126.73813476562502, 49.84365234375005], + [-126.64121093749999, 49.605810546875006] + ] + ], + [ + [ + [-61.801123046875034, 49.093896484374966], + [-63.04150390624994, 49.224951171875034], + [-64.485205078125, 49.88696289062497], + [-64.13144531249995, 49.94165039062503], + [-62.858544921874966, 49.70546875000005], + [-61.817138671875, 49.28354492187498], + [-61.69614257812495, 49.139013671875006], + [-61.801123046875034, 49.093896484374966] + ] + ], + [ + [ + [-125.18413085937497, 50.09711914062498], + [-125.301171875, 50.4140625], + [-125.07402343750002, 50.22065429687501], + [-125.18413085937497, 50.09711914062498] + ] + ], + [ + [ + [-127.19731445312495, 50.640380859375], + [-125.48208007812501, 50.316796874999966], + [-124.83061523437499, 49.53007812500002], + [-123.99580078125, 49.22402343750002], + [-123.49702148437498, 48.58208007812499], + [-123.38989257812501, 48.67021484374999], + [-123.31064453125003, 48.41103515625002], + [-123.57314453124995, 48.32280273437499], + [-123.91694335937501, 48.386572265625034], + [-125.12070312500002, 48.76079101562496], + [-124.84965820312496, 49.02827148437501], + [-124.81264648437497, 49.212646484375], + [-124.92734374999998, 49.01420898437499], + [-125.489453125, 48.933789062499955], + [-125.82851562499998, 49.09184570312499], + [-125.64423828125001, 49.18579101562506], + [-125.95166015625001, 49.24804687500003], + [-125.93540039062499, 49.401464843750006], + [-126.51914062499999, 49.396777343750045], + [-126.54189453125001, 49.590478515624966], + [-126.13408203124997, 49.672314453124955], + [-126.52524414062499, 49.71958007812498], + [-126.90332031250001, 49.94414062499999], + [-127.114306640625, 49.879736328125034], + [-127.24980468749999, 50.13798828124996], + [-127.34941406249995, 50.05195312500001], + [-127.46713867187503, 50.163427734375006], + [-127.86391601562495, 50.12773437500002], + [-127.90585937499998, 50.44521484375002], + [-127.48652343749998, 50.404638671875034], + [-127.46591796874996, 50.58310546875006], + [-128.05834960937494, 50.498486328124955], + [-128.34604492187503, 50.744238281250006], + [-127.91806640624998, 50.86054687500001], + [-127.19731445312495, 50.640380859375] + ] + ], + [ + [ + [-55.45874023437494, 51.53652343750005], + [-55.58339843749994, 51.38857421875002], + [-56.031103515625034, 51.328369140625], + [-55.8, 51.033300781250034], + [-56.732324218749966, 50.007714843749994], + [-56.822167968749966, 49.613476562499955], + [-56.179394531249955, 50.114990234375], + [-56.161279296874994, 49.94013671874998], + [-55.50292968749997, 49.98315429687503], + [-56.14018554687496, 49.61914062500006], + [-55.869824218749955, 49.67016601562506], + [-56.08730468750002, 49.45195312499999], + [-55.375927734374955, 49.48974609374997], + [-55.34384765624998, 49.37290039062506], + [-55.22954101562496, 49.508154296875006], + [-55.35317382812502, 49.07944335937506], + [-54.50219726562503, 49.52734375], + [-54.44824218749997, 49.329443359375006], + [-53.957714843749955, 49.44184570312498], + [-53.61943359374996, 49.321630859375006], + [-53.57343750000001, 49.141210937500034], + [-54.16127929687494, 48.787695312500034], + [-53.852880859375006, 48.81132812499996], + [-53.966015624999955, 48.70668945312505], + [-53.70634765624999, 48.65551757812503], + [-54.11445312499998, 48.393603515625045], + [-53.027587890625, 48.634716796874955], + [-53.1357421875, 48.40185546875003], + [-53.60976562500002, 48.20771484375001], + [-53.56943359374998, 48.088085937499955], + [-53.869580078124926, 48.019677734374966], + [-53.63823242187496, 48.01464843750003], + [-53.863671874999966, 47.787011718749994], + [-53.67236328125, 47.64824218749999], + [-53.28271484375, 47.99785156249996], + [-52.86601562499993, 48.11298828124998], + [-53.16982421875002, 47.51210937500005], + [-52.945019531249955, 47.55283203124998], + [-52.782421874999955, 47.769433593749966], + [-52.653662109375034, 47.549414062500006], + [-53.11484375, 46.65581054687502], + [-53.32304687499996, 46.71835937499998], + [-53.589794921874955, 46.638867187499955], + [-53.59736328124998, 47.14599609374997], + [-54.00957031249993, 46.839599609375], + [-54.173730468749994, 46.88037109375003], + [-53.84951171875002, 47.440332031249994], + [-53.98901367187503, 47.756201171875034], + [-54.191845703124955, 47.85981445312501], + [-54.488134765625006, 47.40385742187502], + [-54.47392578124996, 47.54707031249998], + [-54.856640624999955, 47.385009765625], + [-55.31572265624993, 46.905712890624955], + [-55.78852539062498, 46.86723632812502], + [-55.91923828124996, 47.01689453124996], + [-55.49150390624996, 47.16064453125003], + [-54.78461914062501, 47.664746093749955], + [-55.366308593750034, 47.66108398437501], + [-55.57612304687498, 47.46523437499999], + [-56.12724609374999, 47.50283203125002], + [-55.867089843749994, 47.592333984375045], + [-55.85791015625, 47.81918945312498], + [-56.774121093749955, 47.56499023437499], + [-58.33686523437501, 47.73085937500002], + [-59.11694335937494, 47.57070312499999], + [-59.32065429687498, 47.736914062500006], + [-59.272070312500034, 47.99555664062504], + [-58.330224609374994, 48.52211914062502], + [-59.16767578124998, 48.558496093749966], + [-58.84179687500003, 48.74643554687498], + [-58.906445312499955, 48.65019531249999], + [-58.716455078124994, 48.59804687500002], + [-58.403662109375034, 49.08432617187498], + [-57.99052734374996, 48.987939453124966], + [-58.09892578124993, 49.07744140624999], + [-57.98007812499998, 49.229638671874994], + [-58.19091796875003, 49.25874023437498], + [-58.21337890625, 49.38666992187501], + [-58.01582031249998, 49.54248046874997], + [-57.79130859374999, 49.48999023437503], + [-57.92617187499999, 49.700830078124994], + [-57.4326171875, 50.50581054687504], + [-57.179589843749966, 50.614843750000034], + [-57.29799804687502, 50.69873046874997], + [-57.03593750000002, 51.01083984374998], + [-56.68242187500002, 51.332763671875], + [-56.025585937499955, 51.56835937500006], + [-55.6904296875, 51.471337890624994], + [-55.666406249999966, 51.57890624999999], + [-55.45874023437494, 51.53652343750005] + ] + ], + [ + [ + [-127.92465820312498, 51.47387695312497], + [-128.14877929687498, 51.62670898437503], + [-128.03173828125006, 51.708398437499966], + [-127.92465820312498, 51.47387695312497] + ] + ], + [ + [ + [-79.38427734374997, 51.951953125000045], + [-79.64375, 52.01005859374996], + [-79.27128906249996, 52.086816406249966], + [-79.38427734374997, 51.951953125000045] + ] + ], + [ + [ + [-128.36875, 52.40087890625], + [-128.43979492187503, 52.696386718750006], + [-128.24726562499998, 52.784375], + [-128.36875, 52.40087890625] + ] + ], + [ + [ + [-80.73168945312494, 52.74726562499998], + [-82.03925781249998, 53.04990234374998], + [-81.84731445312494, 53.18627929687497], + [-81.135595703125, 53.20581054687503], + [-80.73168945312494, 52.74726562499998] + ] + ], + [ + [ + [-131.7537109375, 53.195556640625], + [-131.63466796874997, 52.92216796874999], + [-131.97177734374998, 52.87983398437498], + [-131.45522460937502, 52.70170898437502], + [-131.59057617187494, 52.578222656250006], + [-131.25971679687495, 52.415917968749966], + [-131.31992187499998, 52.30307617187498], + [-131.142626953125, 52.291113281250034], + [-131.221533203125, 52.15361328124999], + [-132.16508789062493, 52.783300781250034], + [-132.14375, 52.99931640624999], + [-132.54677734374997, 53.1375], + [-131.7537109375, 53.195556640625] + ] + ], + [ + [ + [-128.55244140624998, 52.93974609375002], + [-128.50991210937502, 52.51860351562502], + [-128.678955078125, 52.289648437500006], + [-128.74633789062494, 52.763378906249955], + [-128.89980468749997, 52.67382812500003], + [-129.175927734375, 52.964941406250006], + [-129.033251953125, 53.27993164062505], + [-128.63266601562498, 53.1125], + [-128.55244140624998, 52.93974609375002] + ] + ], + [ + [ + [-129.167724609375, 53.11787109374998], + [-129.32387695312502, 53.142138671875045], + [-129.23818359374997, 53.33007812500006], + [-129.167724609375, 53.11787109374998] + ] + ], + [ + [ + [-129.84858398437498, 53.167919921874955], + [-130.51757812500003, 53.54423828124999], + [-130.45200195312498, 53.63115234375002], + [-129.94472656250002, 53.436376953125034], + [-129.75483398437498, 53.244775390624994], + [-129.84858398437498, 53.167919921874955] + ] + ], + [ + [ + [-130.236279296875, 53.95854492187502], + [-130.38422851562504, 53.84394531250001], + [-130.703173828125, 53.892236328124994], + [-130.44799804687497, 54.08901367187502], + [-130.236279296875, 53.95854492187502] + ] + ], + [ + [ + [-132.65551757812503, 54.12749023437496], + [-132.30336914062497, 54.098876953125], + [-132.16611328124998, 53.95522460937505], + [-132.53466796875, 53.651708984375034], + [-132.18696289062504, 53.68481445312503], + [-132.134423828125, 54.03427734374998], + [-131.66762695312502, 54.14135742187503], + [-131.957421875, 53.308691406250034], + [-132.34726562500003, 53.18920898437503], + [-132.747509765625, 53.310498046874955], + [-132.425, 53.33696289062502], + [-132.84501953125, 53.507714843749994], + [-133.07949218749997, 53.837011718750034], + [-133.04838867187493, 54.15893554687497], + [-132.65551757812503, 54.12749023437496] + ] + ], + [ + [ + [-130.92714843749997, 54.47905273437499], + [-130.90683593750003, 54.63178710937504], + [-130.75800781249998, 54.61376953125], + [-130.92714843749997, 54.47905273437499] + ] + ], + [ + [ + [-130.57534179687497, 54.769677734374966], + [-130.2140625, 55.02587890625003], + [-130.34941406249996, 54.814550781250034], + [-130.57534179687497, 54.769677734374966] + ] + ], + [ + [ + [-79.97758789062499, 56.20703125000006], + [-80.057470703125, 56.28735351562497], + [-79.57973632812502, 56.466357421875045], + [-79.97758789062499, 56.20703125000006] + ] + ], + [ + [ + [-78.93559570312496, 56.26606445312498], + [-79.17548828124998, 55.88505859374999], + [-79.18212890625, 56.21215820312503], + [-79.4951171875, 55.87475585937503], + [-79.76474609374995, 55.80678710937505], + [-79.54472656249999, 56.12836914062501], + [-79.9875, 55.89213867187502], + [-79.45888671875, 56.53974609374998], + [-79.53632812499995, 56.180078124999966], + [-79.27241210937493, 56.600439453125006], + [-78.93559570312496, 56.26606445312498] + ] + ], + [ + [ + [-61.743603515624955, 57.55458984375005], + [-61.6375, 57.41606445312499], + [-62.01123046875003, 57.54848632812505], + [-61.743603515624955, 57.55458984375005] + ] + ], + [ + [ + [-79.71650390624998, 57.515527343749994], + [-79.80844726562498, 57.44243164062502], + [-79.74257812499997, 57.60795898437499], + [-79.71650390624998, 57.515527343749994] + ] + ], + [ + [ + [-69.16005859375, 59.04023437500001], + [-69.35283203125002, 58.96074218749999], + [-69.30322265625003, 59.144873046875006], + [-69.16005859375, 59.04023437500001] + ] + ], + [ + [ + [-64.40703125, 60.367089843749966], + [-64.44194335937496, 60.2978515625], + [-64.73793945312497, 60.37563476562502], + [-64.83642578124997, 60.50102539062499], + [-64.40703125, 60.367089843749966] + ] + ], + [ + [ + [-68.23378906250002, 60.24091796875001], + [-68.36787109374998, 60.314746093750045], + [-68.08759765624998, 60.58784179687501], + [-67.81884765624994, 60.449511718750074], + [-68.23378906250002, 60.24091796875001] + ] + ], + [ + [ + [-78.531640625, 60.72856445312499], + [-78.66889648437498, 60.716894531250006], + [-78.24169921875, 60.818652343750045], + [-78.531640625, 60.72856445312499] + ] + ], + [ + [ + [-64.83261718749998, 61.366064453125006], + [-65.43212890625, 61.649511718750034], + [-64.78964843750003, 61.662207031250034], + [-64.83261718749998, 61.366064453125006] + ] + ], + [ + [ + [-65.03056640624999, 61.879052734374966], + [-64.89658203124995, 61.73330078125005], + [-65.23535156249997, 61.89770507812506], + [-65.03056640624999, 61.879052734374966] + ] + ], + [ + [ + [-79.54531250000002, 62.41171875000006], + [-79.28647460937495, 62.247656250000034], + [-79.32392578124995, 62.02607421875001], + [-79.81611328124995, 61.59462890625002], + [-80.26518554687496, 61.818212890625006], + [-80.26005859374996, 62.10903320312502], + [-79.9267578125, 62.39287109375002], + [-79.54531250000002, 62.41171875000006] + ] + ], + [ + [ + [-64.82382812499998, 62.558740234374994], + [-64.46503906249998, 62.535937500000045], + [-64.47832031250002, 62.417871093749966], + [-64.901220703125, 62.421044921874994], + [-64.82382812499998, 62.558740234374994] + ] + ], + [ + [ + [-70.33706054687497, 62.548730468749994], + [-70.76606445312498, 62.596875], + [-71.22011718750002, 62.873925781249966], + [-70.44262695312497, 62.73378906250002], + [-70.33706054687497, 62.548730468749994] + ] + ], + [ + [ + [-82.00048828124997, 62.95419921874998], + [-82.02583007812498, 62.73007812499998], + [-82.56826171875002, 62.403222656249994], + [-83.01582031249998, 62.20991210937498], + [-83.69887695312497, 62.16025390624998], + [-83.91049804687498, 62.45415039062499], + [-83.37641601562498, 62.904931640624994], + [-82.00048828124997, 62.95419921874998] + ] + ], + [ + [ + [-77.87670898437497, 63.470556640625034], + [-77.53271484374997, 63.233642578125], + [-77.94243164062496, 63.11440429687502], + [-78.536767578125, 63.423730468749994], + [-77.87670898437497, 63.470556640625034] + ] + ], + [ + [ + [-76.67758789062503, 63.393945312499966], + [-77.36474609374994, 63.588330078124955], + [-77.13369140624997, 63.68203125000002], + [-76.65244140624998, 63.503564453124994], + [-76.67758789062503, 63.393945312499966] + ] + ], + [ + [ + [-84.91962890624995, 65.26108398437503], + [-84.50112304687497, 65.45844726562501], + [-84.08486328125, 65.21782226562502], + [-82.05, 64.64428710937506], + [-81.67612304687498, 64.21264648437503], + [-81.88710937499997, 64.01640625000002], + [-80.82895507812495, 64.08994140625], + [-80.30205078124999, 63.76220703125003], + [-81.04638671875003, 63.461572265624966], + [-82.378125, 63.706787109375], + [-82.46708984375002, 63.92695312500001], + [-83.30395507812497, 64.14379882812506], + [-84.63291015625, 63.30922851562502], + [-85.39262695312496, 63.119677734375045], + [-85.76894531249997, 63.70034179687502], + [-87.15190429687499, 63.58564453125001], + [-86.93203124999997, 63.90166015625002], + [-86.252099609375, 64.13686523437497], + [-86.37426757812503, 64.56582031249997], + [-86.074609375, 65.533837890625], + [-85.55468750000003, 65.91865234374995], + [-85.17622070312501, 65.746875], + [-85.23994140624993, 65.51030273437499], + [-84.91962890624995, 65.26108398437503] + ] + ], + [ + [ + [-84.67475585937498, 65.575], + [-85.096337890625, 65.756201171875], + [-85.14960937500001, 66.01538085937506], + [-84.75737304687496, 65.85893554687505], + [-84.67475585937498, 65.575] + ] + ], + [ + [ + [-83.72597656249997, 65.796728515625], + [-83.23374023437495, 65.71503906249995], + [-83.332421875, 65.63105468749998], + [-84.11826171874995, 65.77177734375007], + [-84.40717773437501, 66.13100585937497], + [-83.78696289062495, 65.96577148437498], + [-83.72597656249997, 65.796728515625] + ] + ], + [ + [ + [-108.09272460937501, 67.00517578124999], + [-107.80551757812493, 66.99858398437507], + [-107.94394531249999, 66.8578125], + [-108.09272460937501, 67.00517578124999] + ] + ], + [ + [ + [-62.681542968749966, 67.05629882812502], + [-62.87163085937499, 67.06259765625006], + [-62.41679687499996, 67.18847656250003], + [-62.681542968749966, 67.05629882812502] + ] + ], + [ + [ + [-107.89985351562497, 67.40180664062495], + [-107.95024414062503, 67.31821289062498], + [-108.15224609374997, 67.429443359375], + [-108.04897460937498, 67.664892578125], + [-107.89985351562497, 67.40180664062495] + ] + ], + [ + [ + [-73.621728515625, 67.783837890625], + [-74.573388671875, 67.82866210937507], + [-74.70654296875003, 68.06708984374995], + [-73.49375, 68.00063476562502], + [-73.40717773437498, 67.79306640625], + [-73.621728515625, 67.783837890625] + ] + ], + [ + [ + [-86.59555664062498, 67.7359375], + [-86.89252929687498, 67.836572265625], + [-86.95981445312503, 68.10024414062497], + [-86.70209960937501, 68.30561523437498], + [-86.42114257812497, 68.18344726562503], + [-86.59555664062498, 67.7359375] + ] + ], + [ + [ + [-75.67587890624998, 68.32250976562506], + [-75.078125, 68.17314453124999], + [-75.20195312499996, 67.45917968750001], + [-75.78007812499996, 67.28354492187503], + [-76.94418945312498, 67.25029296875002], + [-77.30439453125001, 67.68510742187505], + [-77.12587890624997, 67.94707031250002], + [-76.59580078124998, 68.27895507812497], + [-75.67587890624998, 68.32250976562506] + ] + ], + [ + [ + [-78.98271484374999, 68.19282226562501], + [-79.17475585937493, 68.26445312500002], + [-78.95258789062495, 68.35302734375006], + [-78.98271484374999, 68.19282226562501] + ] + ], + [ + [ + [-104.54067382812497, 68.405908203125], + [-105.05136718749999, 68.55903320312501], + [-104.60200195312503, 68.56152343749997], + [-104.54067382812497, 68.405908203125] + ] + ], + [ + [ + [-74.880859375, 68.34868164062505], + [-75.40024414062503, 68.52548828125], + [-75.28740234374996, 68.68774414062503], + [-74.98364257812497, 68.64760742187502], + [-74.880859375, 68.34868164062505] + ] + ], + [ + [ + [-101.84589843749994, 68.58632812499997], + [-102.30815429687497, 68.681982421875], + [-102.01337890624995, 68.82539062500001], + [-101.73295898437495, 68.75341796875], + [-101.84589843749994, 68.58632812499997] + ] + ], + [ + [ + [-100.21723632812497, 68.80668945312502], + [-100.59653320312496, 68.76640625000007], + [-100.56547851562495, 69.02680664062501], + [-100.21723632812497, 68.80668945312502] + ] + ], + [ + [ + [-99.99467773437502, 69.01352539062503], + [-100.19570312500002, 68.991455078125], + [-100.153125, 69.12949218750003], + [-99.99467773437502, 69.01352539062503] + ] + ], + [ + [ + [-79.21064453124995, 68.845458984375], + [-79.24267578125, 69.04926757812495], + [-78.33256835937496, 69.38603515624999], + [-78.77919921875, 68.95048828124999], + [-79.21064453124995, 68.845458984375] + ] + ], + [ + [ + [-90.1998046875, 69.419091796875], + [-90.33027343749993, 69.252197265625], + [-90.49204101562503, 69.369873046875], + [-90.1998046875, 69.419091796875] + ] + ], + [ + [ + [-76.99536132812503, 69.14375], + [-77.37939453125, 69.2740234375], + [-77.18754882812502, 69.440087890625], + [-76.66884765625002, 69.36616210937504], + [-76.99536132812503, 69.14375] + ] + ], + [ + [ + [-101.171728515625, 69.39707031250003], + [-101.31289062499998, 69.57607421875], + [-101.00063476562497, 69.4619140625], + [-101.171728515625, 69.39707031250003] + ] + ], + [ + [ + [-95.51367187499997, 69.57363281250002], + [-95.43745117187498, 69.37846679687505], + [-95.73012695312502, 69.34755859374997], + [-95.80620117187499, 69.56049804687501], + [-95.89345703125, 69.35175781250004], + [-95.87583007812495, 69.60600585937505], + [-95.51367187499997, 69.57363281250002] + ] + ], + [ + [ + [-67.91469726562494, 69.54096679687504], + [-68.22138671874998, 69.61674804687502], + [-67.908837890625, 69.68183593749995], + [-67.91469726562494, 69.54096679687504] + ] + ], + [ + [ + [-78.02910156249993, 69.71489257812502], + [-78.03999023437495, 69.6083984375], + [-78.84819335937502, 69.4828125], + [-78.02910156249993, 69.71489257812502] + ] + ], + [ + [ + [-79.43066406250003, 69.78779296874995], + [-79.55283203124995, 69.63085937500006], + [-80.04750976562502, 69.63432617187505], + [-79.97783203124993, 69.50966796874997], + [-80.794775390625, 69.68925781250005], + [-80.42421875000002, 69.797607421875], + [-79.43066406250003, 69.78779296874995] + ] + ], + [ + [ + [-97.439453125, 69.64267578125006], + [-96.29995117187494, 69.34438476562505], + [-95.7513671875, 68.89765624999998], + [-95.26777343749998, 68.82607421874997], + [-96.40156249999995, 68.47070312500003], + [-97.47202148437498, 68.543701171875], + [-98.320556640625, 68.84272460937498], + [-98.70380859374993, 68.80278320312502], + [-98.90449218749995, 68.93242187500005], + [-99.25400390625002, 68.86318359374997], + [-99.49467773437493, 68.95957031249998], + [-99.455712890625, 69.13120117187503], + [-98.45595703124997, 69.33466796875001], + [-98.54599609375, 69.57290039062497], + [-98.04135742187498, 69.456640625], + [-98.20048828124996, 69.79697265625006], + [-97.79072265624998, 69.86162109374999], + [-97.439453125, 69.64267578125006] + ] + ], + [ + [ + [-86.91303710937501, 70.11323242187501], + [-86.55766601562499, 69.99531249999995], + [-87.3232421875, 70.08012695312502], + [-86.91303710937501, 70.11323242187501] + ] + ], + [ + [ + [-74.70888671874997, 45.0038574218751], + [-74.76245117187494, 44.99907226562502], + [-74.99614257812496, 44.970117187499966], + [-75.40126953124997, 44.77226562499999], + [-75.81933593749997, 44.468017578125], + [-76.18579101562503, 44.24223632812502], + [-76.819970703125, 43.62880859375011], + [-77.59653320312492, 43.62861328125007], + [-78.45825195312497, 43.63149414062511], + [-78.72041015624993, 43.62495117187501], + [-78.84555664062492, 43.58334960937506], + [-79.171875, 43.466552734375085], + [-79.0830566406249, 43.33139648437509], + [-79.05922851562494, 43.27807617187506], + [-79.066064453125, 43.10610351562502], + [-79.02617187499996, 43.01733398437506], + [-78.98076171874993, 42.98061523437502], + [-78.91508789062496, 42.90913085937504], + [-79.17373046875, 42.74853515625], + [-80.24755859374991, 42.366015625000045], + [-81.02822265624997, 42.247167968750006], + [-81.50732421874997, 42.10346679687504], + [-81.97416992187496, 41.88872070312499], + [-82.43906249999989, 41.6748535156251], + [-82.69003906249995, 41.675195312499994], + [-83.141943359375, 41.97587890624996], + [-83.10952148437497, 42.25068359375001], + [-82.54531249999997, 42.62470703124998], + [-82.19038085937495, 43.47407226562501], + [-82.137841796875, 43.570898437500034], + [-82.48505859374993, 45.08374023437503], + [-82.55107421874987, 45.3473632812501], + [-82.91933593749994, 45.51796875000002], + [-83.59267578125, 45.81713867187506], + [-83.46948242187503, 45.99467773437499], + [-83.61596679687503, 46.116845703124994], + [-83.97778320312494, 46.08491210937507], + [-84.12319335937497, 46.50292968749997], + [-84.44047851562496, 46.49814453125006], + [-84.66577148437503, 46.54326171875002], + [-84.87597656249994, 46.89990234375003], + [-85.07006835937497, 46.97993164062498], + [-85.65224609375, 47.21997070312503], + [-86.67216796874996, 47.636425781249955], + [-87.20800781249997, 47.848486328125006], + [-87.74389648437497, 48.06054687500003], + [-88.37817382812497, 48.30307617187506], + [-89.45566406249992, 47.99624023437508], + [-90.79731445312495, 48.13105468750001], + [-91.04345703124991, 48.19370117187498], + [-91.38720703124997, 48.05854492187498], + [-92.00517578125002, 48.301855468750006], + [-92.3484375, 48.276611328125], + [-92.41459960937493, 48.276611328125], + [-92.50058593749995, 48.43535156250002], + [-92.83671875, 48.567773437499994], + [-93.25795898437497, 48.62885742187501], + [-93.37788085937498, 48.61655273437498], + [-93.70771484374995, 48.525439453125074], + [-93.85161132812496, 48.607275390625034], + [-94.6208984374999, 48.7426269531251], + [-94.71279296874997, 48.863427734374994], + [-94.80346679687497, 49.0029296875], + [-94.86040039062493, 49.258593750000045], + [-94.85434570312495, 49.304589843749994], + [-95.15527343749997, 49.3696777343751], + [-95.16206054687493, 48.991748046875045], + [-95.39790039062493, 48.99316406249997], + [-96.25068359374993, 48.99316406249997], + [-96.67705078124993, 48.99316406249997], + [-97.52983398437493, 48.99316406249997], + [-98.80898437499995, 48.99316406249997], + [-104.77832031249997, 48.993115234375125], + [-110.7476562499999, 48.993066406250136], + [-116.71704101562493, 48.993066406250136], + [-118.84892578124993, 48.993066406250136], + [-119.27534179687494, 48.993066406250136], + [-119.70170898437495, 48.99301757812495], + [-120.98085937499995, 48.99301757812495], + [-122.78876953124994, 48.99301757812495], + [-122.82670898437495, 49.028417968750034], + [-122.9241699218749, 49.07465820312504], + [-122.96269531249993, 49.07460937500005], + [-123.06328125, 48.97773437500001], + [-123.22944335937493, 49.260498046875085], + [-122.87910156249995, 49.39892578125003], + [-123.27675781249997, 49.34394531250001], + [-123.1875, 49.680322265624994], + [-123.53056640624989, 49.39731445312506], + [-124.02861328125002, 49.602880859375006], + [-123.99262695312497, 49.736181640625006], + [-123.81718749999993, 49.58657226562508], + [-123.58247070312498, 49.68125], + [-123.87441406250005, 49.736816406250114], + [-123.82543945312493, 50.14423828124998], + [-123.94589843749995, 50.18393554687509], + [-123.9849121093749, 49.87558593749998], + [-124.28125, 49.77211914062502], + [-124.78237304687492, 50.02011718749992], + [-125.05668945312495, 50.418652343750125], + [-124.8598632812499, 50.872412109375006], + [-125.05878906249993, 50.51386718749998], + [-125.4763183593749, 50.49716796874995], + [-125.53935546874996, 50.64902343749998], + [-125.64130859374994, 50.46621093750005], + [-126.09433593749995, 50.497607421875045], + [-126.44746093750004, 50.58774414062492], + [-125.90410156250002, 50.704931640625006], + [-126.51435546875, 50.679394531250125], + [-126.37460937499995, 50.83735351562498], + [-126.5217773437499, 50.86606445312498], + [-126.51733398437497, 51.0568359375001], + [-126.63178710937494, 50.915136718750006], + [-127.057568359375, 50.86752929687509], + [-127.70810546875, 51.15117187499996], + [-127.41967773437496, 51.608056640625136], + [-126.69145507812502, 51.70341796875002], + [-127.33872070312489, 51.70737304687495], + [-127.66870117187497, 51.47758789062502], + [-127.85053710937498, 51.67319335937509], + [-127.79536132812493, 52.19101562500006], + [-127.43793945312504, 52.356152343750125], + [-127.24223632812496, 52.39511718750009], + [-126.71396484374989, 52.060693359374994], + [-127.19399414062498, 52.45766601562502], + [-126.95136718749994, 52.7510253906251], + [-127.01933593750002, 52.8424804687501], + [-127.06621093749989, 52.65268554687498], + [-127.79189453124994, 52.28935546875002], + [-128.10224609374993, 51.78842773437495], + [-128.3576171875, 52.1588867187501], + [-128.0375, 52.318164062500045], + [-127.94023437499996, 52.545166015625085], + [-128.27153320312493, 52.3629882812501], + [-128.05327148437487, 52.91069335937496], + [-128.3650390624999, 52.82578125000006], + [-128.52470703125002, 53.1406738281251], + [-129.08090820312492, 53.36728515625006], + [-129.1715820312499, 53.53359375000002], + [-128.8545898437499, 53.70454101562504], + [-128.90561523437492, 53.559326171875114], + [-128.5421386718749, 53.420654296875114], + [-128.13271484375002, 53.417773437500045], + [-127.92783203125, 53.274707031250045], + [-128.2072265624999, 53.483203125000074], + [-128.67553710937494, 53.55458984375005], + [-128.76367187500003, 53.746875], + [-128.5321289062499, 53.85810546875007], + [-128.959375, 53.84145507812505], + [-129.2578613281249, 53.417968750000085], + [-129.56372070312506, 53.251464843750114], + [-130.33525390625002, 53.723925781250074], + [-130.04331054687495, 54.13354492187503], + [-129.62602539062493, 54.23027343750002], + [-130.08422851562503, 54.18139648437503], + [-130.4302734375, 54.42099609374998], + [-129.56064453124995, 55.46254882812508], + [-129.79516601562503, 55.559570312500114], + [-130.04848632812494, 55.05727539062511], + [-130.01406249999997, 55.950537109375006], + [-130.09785156249995, 56.10927734375002], + [-130.41313476562487, 56.12250976562507], + [-130.47709960937496, 56.230566406250034], + [-130.649072265625, 56.26367187500003], + [-131.471875, 56.55673828125006], + [-131.82426757812496, 56.58999023437508], + [-131.86616210937495, 56.792822265625006], + [-132.1042968749999, 56.85678710937509], + [-132.062890625, 56.95336914062503], + [-132.33798828124992, 57.07944335937498], + [-132.27939453124998, 57.14536132812506], + [-132.23217773437494, 57.198535156250074], + [-132.30166015625005, 57.2763183593751], + [-132.44248046874986, 57.40673828125003], + [-132.55048828124995, 57.499902343749994], + [-133.00141601562495, 57.948974609375], + [-133.27529296875, 58.22285156250004], + [-133.54638671874997, 58.50346679687499], + [-134.21850585937503, 58.849902343750045], + [-134.32963867187505, 58.93969726562506], + [-134.39306640625, 59.009179687499994], + [-134.67724609374997, 59.19926757812499], + [-134.94375, 59.28828125000001], + [-135.05102539062491, 59.57866210937502], + [-135.36787109374998, 59.743310546874994], + [-135.70258789062504, 59.72875976562506], + [-136.3218261718749, 59.604833984375034], + [-136.27797851562494, 59.48032226562506], + [-136.46635742187493, 59.459082031250006], + [-136.57875976562494, 59.15224609375002], + [-136.81328125000002, 59.15004882812511], + [-137.12622070312491, 59.04096679687507], + [-137.2775390624999, 58.988183593749994], + [-137.43857421874995, 58.903125], + [-137.52089843749994, 58.91538085937506], + [-137.59331054687493, 59.22626953124998], + [-138.317626953125, 59.611132812500074], + [-138.86875, 59.94575195312501], + [-139.18515624999986, 60.083593750000034], + [-139.13696289062494, 60.17270507812506], + [-139.07924804687497, 60.279443359375136], + [-139.07924804687497, 60.3437011718751], + [-139.23476562499997, 60.339746093749994], + [-139.67631835937505, 60.32832031249998], + [-139.97329101562497, 60.183154296875074], + [-140.45283203125004, 60.29970703125002], + [-140.5254394531249, 60.21835937499995], + [-140.76274414062505, 60.25913085937509], + [-141.00214843750004, 60.300244140625125], + [-141.00214843750004, 60.884667968749994], + [-141.00214843750004, 61.761279296875045], + [-141.00214843750004, 63.22226562499998], + [-141.00214843750004, 64.09887695312506], + [-141.00214843750004, 65.55991210937498], + [-141.00214843750004, 66.43652343750006], + [-141.00214843750004, 67.89755859374998], + [-141.00214843750004, 68.77416992187506], + [-141.00214843750004, 69.65078125000011], + [-139.18154296874997, 69.51552734375008], + [-137.25996093749998, 68.96411132812503], + [-136.12236328124993, 68.88222656250002], + [-135.258837890625, 68.68432617187503], + [-135.93901367187487, 68.9741699218751], + [-135.575537109375, 69.02695312500003], + [-135.91020507812487, 69.11147460937502], + [-135.6914550781249, 69.31118164062502], + [-135.29282226562486, 69.30786132812506], + [-135.1408203124999, 69.46782226562496], + [-134.45683593749993, 69.47763671875], + [-134.40893554687494, 69.68178710937502], + [-133.87978515624997, 69.50771484375011], + [-134.17431640624991, 69.25283203125005], + [-133.16313476562496, 69.43388671874999], + [-132.91533203125002, 69.62963867187506], + [-132.40390625, 69.65874023437496], + [-132.48847656249993, 69.73808593749996], + [-132.16342773437498, 69.70498046875014], + [-131.13637695312497, 69.90688476562505], + [-130.66547851562495, 70.12705078124998], + [-129.944970703125, 70.09091796875006], + [-129.675634765625, 70.19296875000009], + [-129.64829101562495, 69.9977539062501], + [-130.83208007812487, 69.65146484375006], + [-131.9377929687499, 69.5347167968751], + [-132.8174804687499, 69.20576171875004], + [-133.41831054687492, 68.84428710937493], + [-133.138037109375, 68.74658203125011], + [-133.33666992187497, 68.83525390625005], + [-132.57763671874997, 68.84780273437514], + [-132.71894531249998, 69.07919921875], + [-131.78837890625002, 69.43198242187495], + [-131.32470703124997, 69.36118164062509], + [-131.06342773437504, 69.45068359375003], + [-130.97065429687495, 69.20908203125], + [-130.1176269531249, 69.720068359375], + [-128.89892578124994, 69.96616210937506], + [-129.15791015624995, 69.80009765624999], + [-129.05434570312502, 69.70107421875005], + [-128.85302734375003, 69.7510253906251], + [-127.68378906249994, 70.26035156249995], + [-128.17011718749998, 70.41845703125], + [-127.99101562499992, 70.57382812500003], + [-127.22597656249992, 70.29614257812497], + [-126.25043945312495, 69.54526367187492], + [-125.52495117187495, 69.35156250000009], + [-125.171875, 69.42797851562503], + [-125.35693359374991, 69.62597656250003], + [-124.767919921875, 69.99003906249996], + [-124.99038085937494, 70.02661132812511], + [-124.55502929687488, 70.15122070312509], + [-124.40693359374991, 69.76743164062506], + [-124.12460937499995, 69.6899902343751], + [-124.33808593749991, 69.36484374999995], + [-123.5284179687499, 69.38935546874995], + [-123.02578125, 69.81000976562504], + [-122.07006835937499, 69.81616210937506], + [-120.96245117187502, 69.66040039062511], + [-120.13999023437488, 69.38056640625013], + [-117.22695312499998, 68.913427734375], + [-116.05947265625, 68.83701171875006], + [-116.2434082031249, 68.9740722656251], + [-115.44228515624994, 68.94091796875009], + [-114.62016601562496, 68.74609375], + [-113.96440429687495, 68.39907226562502], + [-114.09594726562491, 68.26679687500007], + [-114.76528320312494, 68.27021484375004], + [-115.12705078124992, 68.13203124999995], + [-115.43447265624994, 67.90234375000006], + [-115.13320312499994, 67.819189453125], + [-112.50302734374993, 67.6819335937501], + [-110.9900390624999, 67.79082031250007], + [-110.07392578124995, 67.99291992187506], + [-109.63037109374991, 67.73271484374996], + [-109.03803710937504, 67.69116210937503], + [-108.85200195312497, 67.42197265625009], + [-108.61333007812493, 67.59804687500008], + [-107.98872070312495, 67.2563964843751], + [-107.99130859374995, 67.09516601562513], + [-108.49604492187493, 67.09228515625006], + [-107.25947265624998, 66.39853515624995], + [-107.71035156250001, 66.74003906250007], + [-107.7250976562499, 66.98413085937506], + [-107.15649414062497, 66.88173828124997], + [-107.9583984375, 67.81860351562506], + [-107.79829101562498, 68.03691406249996], + [-106.42426757812491, 68.20058593750008], + [-105.7501953125, 68.59228515625011], + [-106.45805664062496, 68.51645507812495], + [-106.60849609374988, 68.35737304687504], + [-107.61933593749994, 68.3310546875], + [-107.73417968749989, 68.17373046875011], + [-108.3228027343749, 68.15410156250002], + [-108.71811523437488, 68.29746093750009], + [-108.31347656249996, 68.61079101562498], + [-106.16445312499992, 68.91987304687507], + [-105.68559570312489, 68.82817382812505], + [-105.3774414062499, 68.413818359375], + [-104.65317382812488, 68.23007812500003], + [-104.48681640624991, 68.06318359374998], + [-103.47412109374993, 68.11503906250005], + [-102.32036132812489, 67.73564453125005], + [-101.55498046874992, 67.69316406250007], + [-100.21293945312489, 67.83857421875004], + [-98.92045898437502, 67.72578124999998], + [-98.41210937499991, 67.80717773437505], + [-98.63154296875004, 68.0725585937501], + [-97.45493164062486, 67.61699218750002], + [-97.20654296874989, 67.85507812500003], + [-97.73911132812495, 67.97817382812505], + [-98.19252929687494, 67.92299804687502], + [-98.65048828124989, 68.36352539062506], + [-98.21855468750002, 68.31743164062507], + [-97.7942382812499, 68.38759765625], + [-97.9250976562499, 68.523681640625], + [-97.41035156249993, 68.49653320312498], + [-96.97670898437497, 68.25541992187505], + [-96.43066406249991, 68.3105957031251], + [-96.72207031250005, 68.03876953124998], + [-95.9703125, 68.24912109375], + [-96.36914062499991, 67.50976562500003], + [-96.14145507812489, 67.27182617187503], + [-95.71992187499998, 67.31679687500014], + [-95.77768554687495, 67.18461914062505], + [-95.41591796875005, 67.15556640624999], + [-95.41889648437504, 67.01323242187493], + [-96.42255859374995, 67.05175781249997], + [-95.7875488281249, 66.616796875], + [-96.03686523437489, 66.9375], + [-95.39965820312503, 66.94946289062509], + [-95.25874023437493, 67.26254882812492], + [-95.65048828124986, 67.73745117187505], + [-95.46069335937503, 68.02138671875], + [-94.74443359374993, 68.07089843749995], + [-93.44892578124998, 68.61889648437503], + [-93.85244140624994, 69.00034179687495], + [-94.06489257812495, 68.78476562500006], + [-94.600439453125, 68.80322265625011], + [-94.08364257812497, 69.12309570312507], + [-94.254736328125, 69.31376953125002], + [-93.61948242187492, 69.41699218750009], + [-93.74853515624991, 69.2261230468751], + [-93.5322753906249, 69.48090820312495], + [-94.2708007812499, 69.45512695312505], + [-94.63383789062496, 69.64965820312506], + [-94.82250976562494, 69.577783203125], + [-95.96494140624989, 69.80278320312499], + [-96.5513671875, 70.21030273437506], + [-96.29770507812492, 70.51137695312511], + [-95.87861328124998, 70.54897460937514], + [-95.88632812499986, 70.69428710937507], + [-96.25800781249993, 70.64228515625013], + [-96.54892578124995, 70.80874023437511], + [-96.44658203124996, 71.23989257812502], + [-96.06201171874997, 71.41386718749993], + [-95.5642578124999, 71.33676757812503], + [-95.40625, 71.49165039062498], + [-95.87231445312494, 71.57314453125005], + [-94.73486328124994, 71.98295898437507], + [-94.30834960937491, 71.76489257812506], + [-93.74628906249998, 71.742822265625], + [-92.94868164062493, 71.26210937500011], + [-92.98144531249994, 70.8522460937501], + [-91.56406249999995, 70.1782714843751], + [-92.32050781250004, 70.2353515625], + [-92.51186523437494, 70.10385742187503], + [-91.976708984375, 70.03867187500009], + [-92.88779296874989, 69.66821289062511], + [-92.31166992187494, 69.67290039062499], + [-91.91196289062495, 69.53125], + [-91.20180664062494, 69.64477539062494], + [-91.43994140624997, 69.52568359375002], + [-90.4155761718749, 69.45698242187507], + [-90.89228515625004, 69.26728515624995], + [-91.23720703125005, 69.28554687500014], + [-90.47900390624994, 68.88115234374999], + [-90.57363281250005, 68.47470703124998], + [-90.20478515625004, 68.25747070312511], + [-89.27954101562491, 69.25546875000003], + [-88.22353515625, 68.91503906249997], + [-87.81357421874986, 68.34570312499997], + [-87.89267578125, 68.24814453125], + [-88.34697265624993, 68.28828125000001], + [-88.313818359375, 67.95034179687508], + [-87.359375, 67.17724609374997], + [-86.56079101562491, 67.48212890625007], + [-85.64316406249992, 68.69970703124997], + [-84.86757812499994, 68.77333984375005], + [-85.10664062499995, 68.84404296875007], + [-84.86220703125, 69.07397460937503], + [-85.38676757812493, 69.23188476562504], + [-85.50737304687487, 69.84526367187493], + [-82.61835937499993, 69.69106445312514], + [-82.39023437499989, 69.60087890625007], + [-82.75483398437493, 69.49438476562506], + [-82.30986328124996, 69.41000976562509], + [-82.22753906249997, 69.24887695312495], + [-81.37783203125005, 69.18564453125003], + [-81.95791015624991, 68.88364257812498], + [-81.38090820312496, 68.85004882812504], + [-81.28154296874987, 68.65722656250003], + [-81.91484374999993, 68.4587890625001], + [-82.55268554687504, 68.44648437500007], + [-82.22241210937489, 68.145263671875], + [-82.0125, 68.19389648437496], + [-81.97646484374997, 67.86201171875001], + [-81.2943359375, 67.497412109375], + [-81.46757812499996, 67.0698730468751], + [-83.40644531249998, 66.37124023437508], + [-84.53847656249994, 66.97280273437505], + [-84.84575195312502, 67.02871093750008], + [-85.11372070312498, 66.90693359375013], + [-84.73774414062504, 66.93359375000006], + [-84.223046875, 66.68247070312506], + [-83.86904296875, 66.2135742187501], + [-84.29306640624995, 66.29179687500005], + [-84.628076171875, 66.20771484374998], + [-85.603857421875, 66.56826171875005], + [-86.708154296875, 66.52304687500009], + [-86.68510742187502, 66.36040039062499], + [-85.95874023437491, 66.11904296875002], + [-87.45288085937503, 65.33896484375009], + [-87.96997070312503, 65.34892578124999], + [-89.7494140625, 65.93603515625006], + [-89.88969726562487, 65.86855468749997], + [-91.42724609374994, 65.94790039062497], + [-91.04111328124989, 65.82983398437509], + [-90.98344726562496, 65.91923828124999], + [-89.92407226562497, 65.78027343750011], + [-88.97402343749994, 65.34829101562502], + [-87.02753906249995, 65.19809570312498], + [-88.10561523437497, 64.18330078125001], + [-88.81772460937489, 63.99223632812499], + [-89.20063476562493, 64.11376953125006], + [-89.13154296874998, 63.96850585937494], + [-89.61582031249995, 64.030615234375], + [-89.8113281249999, 64.18056640625], + [-90.04165039062494, 64.14086914062509], + [-89.85571289062497, 63.9569824218751], + [-90.16816406250004, 63.978759765625085], + [-90.15473632812498, 63.68964843749998], + [-90.81191406249991, 63.580908203125034], + [-91.98222656249996, 63.82241210937502], + [-92.33842773437496, 63.787646484375045], + [-93.69633789062493, 64.14716796875013], + [-93.55981445312491, 63.865283203125074], + [-93.27021484374998, 63.840869140625074], + [-93.37851562499992, 63.94848632812497], + [-92.15688476562491, 63.691699218750045], + [-92.46508789062491, 63.55507812500011], + [-91.84184570312496, 63.69755859374999], + [-90.97006835937489, 63.442773437500136], + [-90.69858398437492, 63.06386718750005], + [-91.44897460937503, 62.804052734375034], + [-92.3612792968749, 62.81938476562496], + [-91.93583984374993, 62.59238281250009], + [-92.55141601562491, 62.546728515625034], + [-92.76596679687492, 62.34995117187509], + [-92.52797851562494, 62.16840820312504], + [-93.20537109374993, 62.364941406250125], + [-92.90551757812503, 62.21513671874996], + [-93.3330566406249, 61.93291015625002], + [-93.58178710937494, 61.94204101562511], + [-93.31201171874997, 61.76728515625004], + [-93.91274414062497, 61.48144531250006], + [-94.509375, 60.60454101562493], + [-94.76171874999991, 60.498242187500125], + [-94.78828124999998, 59.26787109374993], + [-94.95732421874996, 59.068847656250085], + [-94.28706054687493, 58.716015625000125], + [-94.33222656249998, 58.297363281250114], + [-94.12319335937494, 58.73671875000008], + [-93.1787597656249, 58.72563476562496], + [-92.43281249999993, 57.3203125], + [-92.7981445312499, 56.921972656250034], + [-90.89746093750003, 57.25693359375006], + [-88.94848632812489, 56.85131835937503], + [-88.07509765624997, 56.46728515624994], + [-87.48242187499991, 56.021289062500045], + [-85.55932617187491, 55.54018554687508], + [-85.21801757812491, 55.348974609375034], + [-85.3652832031249, 55.07929687499998], + [-85.06093749999997, 55.285644531250085], + [-83.91059570312493, 55.314648437499955], + [-82.39326171874998, 55.067822265625125], + [-82.219384765625, 54.8134765625], + [-82.42416992187486, 54.2445800781251], + [-82.14145507812492, 53.81762695312497], + [-82.29155273437496, 53.03071289062507], + [-81.5994140624999, 52.432617187500085], + [-81.82788085937489, 52.22421875000009], + [-81.46621093749994, 52.204492187500136], + [-80.588037109375, 51.667236328125114], + [-80.4433105468749, 51.38857421875002], + [-80.85122070312497, 51.125], + [-80.47832031249993, 51.30732421874998], + [-80.10356445312487, 51.282861328125136], + [-79.34790039062494, 50.76264648437504], + [-79.737451171875, 51.186279296875], + [-79.33867187500002, 51.62817382812497], + [-79.04052734375003, 51.46376953125005], + [-78.90317382812495, 51.200292968750034], + [-78.73134765624994, 51.497460937499994], + [-78.98164062499993, 51.774560546875136], + [-78.44809570312495, 52.26137695312502], + [-78.74414062499994, 52.65537109374998], + [-79.10034179687497, 53.65664062500005], + [-78.99604492187493, 54.00249023437499], + [-79.241796875, 54.098876953125085], + [-79.14672851562491, 54.16923828125002], + [-79.71235351562495, 54.6718261718751], + [-77.77529296874994, 55.291259765625], + [-76.60405273437496, 56.19956054687495], + [-76.52558593749998, 56.8917968750001], + [-76.80981445312497, 57.65795898437506], + [-77.15678710937496, 58.018896484375034], + [-78.51508789062493, 58.68237304687503], + [-77.76069335937498, 59.38002929687505], + [-77.72617187499995, 59.67587890624992], + [-77.34907226562495, 59.57895507812509], + [-77.48530273437493, 59.684570312500114], + [-77.28920898437494, 60.0220214843751], + [-77.58588867187498, 60.088183593750074], + [-77.45288085937497, 60.1458007812501], + [-77.6814453124999, 60.427099609375034], + [-77.503564453125, 60.54272460937497], + [-77.7908203124999, 60.63984375000004], + [-77.58955078124993, 60.808593750000114], + [-78.18134765624995, 60.81914062499996], + [-77.51435546874998, 61.55629882812505], + [-78.02138671874997, 61.8320800781251], + [-78.13339843749986, 62.28227539062496], + [-77.372412109375, 62.572509765625114], + [-75.81689453124991, 62.31586914062507], + [-75.7898437499999, 62.17958984375002], + [-75.3412109375, 62.312109375], + [-74.63256835937497, 62.115673828125125], + [-74.6458007812499, 62.21113281250004], + [-73.70507812499991, 62.47314453124994], + [-72.68696289062498, 62.12456054687499], + [-72.771630859375, 61.840429687500006], + [-72.50556640624998, 61.922656250000074], + [-72.22612304687487, 61.83159179687499], + [-72.04003906249991, 61.68027343750006], + [-72.21586914062502, 61.58725585937495], + [-71.86611328125, 61.68852539062499], + [-71.63828124999995, 61.6171875], + [-71.85439453124991, 61.43979492187492], + [-71.42270507812489, 61.158935546875085], + [-70.27929687499991, 61.06865234374999], + [-69.99243164062491, 60.8564941406251], + [-69.50332031249994, 61.04042968750011], + [-69.40473632812493, 60.84677734375009], + [-69.75947265624998, 60.440234375000045], + [-69.67373046874994, 60.07587890625007], + [-70.65483398437496, 60.02622070312506], + [-69.73393554687493, 59.918017578125045], + [-69.68188476562489, 59.34174804687507], + [-69.3440429687499, 59.303076171875006], + [-69.53164062499994, 58.86923828125009], + [-69.64838867187493, 58.82080078125], + [-69.78417968749994, 58.95571289062511], + [-70.15434570312496, 58.76059570312498], + [-69.78989257812486, 58.689306640625034], + [-69.27109374999986, 58.88393554687505], + [-68.69819335937495, 58.904541015625], + [-68.38115234374993, 58.74350585937506], + [-68.22939453124994, 58.48457031250007], + [-68.35654296874989, 58.163232421875136], + [-69.04082031249996, 57.902490234375136], + [-68.41357421874997, 58.0517578125], + [-68.02104492187493, 58.48530273437504], + [-67.88828124999989, 58.29575195312495], + [-68.06386718750005, 58.13896484374999], + [-67.75595703124992, 58.4045898437501], + [-67.6782714843749, 57.99111328125008], + [-67.5696289062499, 58.21347656250006], + [-66.72216796874991, 58.49101562499996], + [-66.36240234374989, 58.791162109374994], + [-66.0023925781249, 58.43120117187502], + [-66.04306640624995, 58.82065429687495], + [-65.72099609374996, 59.02377929687495], + [-65.38354492187494, 59.06020507812508], + [-65.7, 59.21333007812501], + [-65.4117187499999, 59.31499023437496], + [-65.47509765624994, 59.47031249999998], + [-65.03823242187494, 59.38789062500007], + [-65.40742187499993, 59.53935546875002], + [-65.4333984374999, 59.776513671874994], + [-65.02817382812495, 59.77070312500007], + [-65.17172851562489, 59.90800781249996], + [-64.81733398437498, 60.3310546875], + [-64.49941406250005, 60.26826171875001], + [-64.41958007812494, 60.17138671874997], + [-64.76845703124997, 60.01210937500005], + [-64.28349609374993, 60.06406249999998], + [-64.22631835937491, 59.741210937500085], + [-64.05605468750005, 59.82255859374996], + [-63.7501953124999, 59.51259765625005], + [-63.945458984374994, 59.380175781250074], + [-63.775878906249915, 59.277148437500045], + [-63.539892578124864, 59.332861328125034], + [-63.41513671874995, 59.194384765625074], + [-63.97114257812498, 59.053808593750034], + [-63.24843749999991, 59.068310546874955], + [-63.28212890624994, 58.86738281250007], + [-63.05029296874997, 58.87817382812494], + [-62.87387695312489, 58.67246093749998], + [-63.537060546874926, 58.329931640625006], + [-63.209960937499886, 58.46694335937502], + [-62.593847656249864, 58.47402343750005], + [-62.81206054687502, 58.20039062500007], + [-63.26152343749993, 58.014697265625074], + [-62.486230468749966, 58.15405273437506], + [-62.30566406249997, 57.97226562499995], + [-61.95864257812505, 57.91176757812508], + [-61.9679687499999, 57.61191406250009], + [-62.495556640624926, 57.489208984375125], + [-61.92114257812497, 57.42080078125005], + [-61.977441406249966, 57.24794921875002], + [-61.33374023437494, 57.01059570312498], + [-61.37163085937502, 56.68081054687511], + [-62.497265624999926, 56.80170898437504], + [-61.73774414062498, 56.52602539062502], + [-61.940429687499886, 56.423583984375114], + [-61.42529296874994, 56.360644531250074], + [-61.713085937499955, 56.230957031250114], + [-61.364697265624926, 56.2160156250001], + [-61.30112304687495, 56.04716796874999], + [-61.4495117187499, 55.99570312499998], + [-61.08935546874997, 55.86635742187511], + [-60.74326171874989, 55.94145507812493], + [-60.56210937499995, 55.727001953125125], + [-60.341015624999926, 55.78466796874997], + [-60.40830078124995, 55.649560546874994], + [-60.19238281249994, 55.4809082031251], + [-60.617138671874955, 55.060205078124994], + [-59.75878906249997, 55.3095703125], + [-59.68906249999989, 55.19633789062502], + [-59.43789062500005, 55.175927734375136], + [-59.837792968749994, 54.813964843750114], + [-59.25957031249996, 55.19995117187506], + [-58.99711914062496, 55.149462890625074], + [-58.780175781249994, 54.838378906250114], + [-58.39814453124998, 54.77412109374998], + [-57.96245117187493, 54.875732421875085], + [-57.40449218750004, 54.59086914062496], + [-57.69926757812496, 54.38657226562506], + [-58.435205078124966, 54.228125], + [-58.63320312499999, 54.04956054687497], + [-59.8230468749999, 53.83442382812504], + [-60.14492187499994, 53.59614257812498], + [-60.395410156249994, 53.653320312500085], + [-60.1002929687499, 53.48696289062511], + [-60.329492187499966, 53.26611328125006], + [-58.652050781249926, 53.97788085937495], + [-57.935986328124955, 54.09116210937492], + [-58.31748046874989, 54.11445312500007], + [-58.192089843749926, 54.228173828125136], + [-57.4160644531249, 54.162744140625136], + [-57.134960937499926, 53.79184570312506], + [-57.524072265624966, 53.61142578125006], + [-57.331738281249955, 53.469091796875034], + [-56.84086914062496, 53.73945312500004], + [-56.46499023437505, 53.76503906250011], + [-55.96611328125002, 53.4711425781251], + [-55.79794921874995, 53.211962890625045], + [-55.80283203124989, 52.64316406249998], + [-56.324902343749926, 52.54453124999998], + [-55.74648437499994, 52.4745605468751], + [-55.7771484374999, 52.3642578125], + [-56.01171874999997, 52.394482421875125], + [-55.695214843749994, 52.13779296875006], + [-56.97597656250005, 51.45766601562505], + [-58.510351562500006, 51.295068359375136], + [-59.88632812499992, 50.316406250000085], + [-61.72485351562503, 50.10405273437499], + [-61.91953124999989, 50.2328613281251], + [-62.71542968749995, 50.30166015625008], + [-66.49550781249991, 50.2118652343751], + [-66.94116210937503, 49.993701171875045], + [-67.37202148437495, 49.348437500000045], + [-68.28193359374998, 49.197167968750136], + [-69.67387695312496, 48.19916992187504], + [-71.01826171874993, 48.455615234375045], + [-69.86552734374993, 48.17226562500005], + [-69.775, 48.09809570312504], + [-69.9944335937499, 47.73989257812508], + [-70.70585937499996, 47.13979492187505], + [-71.26777343749995, 46.79594726562499], + [-71.87958984374998, 46.68681640624996], + [-72.98100585937493, 46.209716796875085], + [-73.4766113281249, 45.738232421874955], + [-74.03784179687494, 45.501855468750136], + [-74.31508789062494, 45.531054687500045], + [-73.97382812499995, 45.345117187499994], + [-74.70888671874997, 45.0038574218751] + ] + ], + [ + [ + [-96.78232421874998, 72.93662109375], + [-97.0927734375, 72.99692382812503], + [-96.86240234374995, 73.18881835937506], + [-96.78232421874998, 72.93662109375] + ] + ], + [ + [ + [-114.52153320312502, 72.592919921875], + [-113.57807617187501, 72.65209960937506], + [-113.2923828125, 72.94980468750003], + [-112.75361328125001, 72.98603515624995], + [-111.26972656249994, 72.71372070312498], + [-111.895166015625, 72.35610351562497], + [-111.67509765625002, 72.30014648437503], + [-110.20512695312495, 72.66127929687497], + [-110.66083984374998, 73.00820312500002], + [-110.00844726562494, 72.983642578125], + [-108.75498046875002, 72.55107421874999], + [-108.18823242187501, 71.72377929687502], + [-107.812841796875, 71.62617187500004], + [-107.30600585937496, 71.89467773437502], + [-108.23740234374999, 73.14990234375003], + [-108.029052734375, 73.34873046875003], + [-106.48212890624998, 73.19619140624997], + [-105.41513671874995, 72.788330078125], + [-104.38593749999997, 71.57695312500005], + [-104.51479492187502, 71.06425781250005], + [-103.58457031249995, 70.63085937500003], + [-103.07719726562497, 70.50883789062505], + [-103.04956054687503, 70.65507812499999], + [-101.67631835937495, 70.27827148437495], + [-101.56240234375001, 70.135009765625], + [-101.04267578125, 70.11079101562504], + [-100.98237304687497, 69.67988281250001], + [-101.483837890625, 69.85019531250006], + [-101.64765624999997, 69.69853515625007], + [-102.18212890624997, 69.845947265625], + [-102.59589843749997, 69.71791992187502], + [-102.62109374999996, 69.55151367187506], + [-103.464892578125, 69.64448242187498], + [-103.04892578124999, 69.47177734375006], + [-103.12021484374995, 69.20458984374997], + [-102.44677734374997, 69.476318359375], + [-102.04594726562493, 69.46484374999997], + [-101.85712890625001, 69.02397460937505], + [-102.89506835937499, 68.8236328125], + [-104.57143554687501, 68.87211914062502], + [-105.105859375, 68.92041015625], + [-105.019580078125, 69.08125], + [-106.27016601562497, 69.19458007812497], + [-106.65908203124997, 69.439599609375], + [-107.43989257812497, 69.00214843749995], + [-108.36499023437497, 68.93476562499998], + [-109.47211914062501, 68.67670898437498], + [-113.12773437500002, 68.49414062500003], + [-113.61684570312501, 68.8384765625], + [-113.69414062499995, 69.19501953124998], + [-115.61811523437495, 69.28295898437506], + [-116.51347656249993, 69.42460937500005], + [-117.19541015624995, 70.05405273437503], + [-114.59233398437497, 70.31245117187498], + [-112.63789062499997, 70.225244140625], + [-111.63256835937497, 70.30883789062497], + [-113.75727539062503, 70.69072265625005], + [-115.99091796874997, 70.586279296875], + [-117.58706054687498, 70.62954101562502], + [-118.2640625, 70.888330078125], + [-118.26909179687493, 71.03471679687505], + [-115.30341796874997, 71.49370117187505], + [-117.93564453125003, 71.39208984375003], + [-118.22646484374995, 71.46708984375005], + [-117.742333984375, 71.65932617187502], + [-118.58300781250003, 71.64902343749998], + [-118.98769531249997, 71.7642578125], + [-118.94462890624997, 71.98554687499995], + [-118.21347656249998, 72.26289062499998], + [-118.481298828125, 72.42768554687498], + [-118.13310546874995, 72.63281250000003], + [-114.63823242187499, 73.37265625000003], + [-114.20639648437495, 73.29780273437495], + [-114.05170898437497, 73.07099609375004], + [-114.52153320312502, 72.592919921875] + ] + ], + [ + [ + [-105.28891601562499, 72.919921875], + [-106.92153320312497, 73.479833984375], + [-106.61396484375001, 73.69560546875002], + [-105.31796874999995, 73.76713867187502], + [-104.5875, 73.57807617187495], + [-104.62172851562495, 73.3111328125], + [-105.28891601562499, 72.919921875] + ] + ], + [ + [ + [-79.53730468749998, 73.65449218749998], + [-78.2865234375, 73.66582031250007], + [-77.20654296874997, 73.49956054687505], + [-76.18339843749999, 72.84306640625005], + [-77.83593750000003, 72.89682617187498], + [-79.3193359375, 72.75771484375], + [-79.820703125, 72.82631835937502], + [-80.18330078124995, 73.22465820312499], + [-80.77641601562502, 73.33417968750001], + [-80.84887695312503, 73.72124023437499], + [-79.53730468749998, 73.65449218749998] + ] + ], + [ + [ + [-86.58935546874997, 71.01079101562507], + [-85.64384765624999, 71.15244140624998], + [-85.09487304687497, 71.15195312500006], + [-84.82373046874997, 71.02861328125005], + [-84.69941406249995, 71.63144531250003], + [-85.33906249999998, 71.69726562500003], + [-85.91162109375, 71.98652343749998], + [-85.321875, 72.23315429687506], + [-84.28374023437499, 72.04448242187499], + [-84.84199218749995, 72.30815429687505], + [-84.62304687500003, 72.37656250000003], + [-85.34111328124993, 72.42153320312497], + [-85.64990234374997, 72.72216796875003], + [-85.26210937500002, 72.95400390625], + [-84.25664062499999, 72.79672851562503], + [-85.454736328125, 73.10546875000003], + [-84.41606445312496, 73.45649414062495], + [-83.781884765625, 73.41689453125], + [-83.72983398437495, 73.57587890624995], + [-81.946142578125, 73.72983398437506], + [-81.40615234374997, 73.634521484375], + [-80.27724609375, 72.77016601562502], + [-81.229345703125, 72.31171874999998], + [-80.61147460937497, 72.450830078125], + [-80.925146484375, 71.90766601562501], + [-80.18193359374996, 72.20878906250007], + [-79.884375, 72.17719726562501], + [-80.10893554687499, 72.33217773437497], + [-79.83129882812503, 72.44628906250003], + [-79.000244140625, 72.27202148437507], + [-79.00781250000003, 72.04291992187501], + [-78.58510742187497, 71.880615234375], + [-78.86274414062495, 72.100830078125], + [-78.69926757812496, 72.35141601562498], + [-77.51650390624997, 72.17778320312505], + [-78.48427734374994, 72.47060546875002], + [-77.75322265624996, 72.72475585937502], + [-75.70429687499998, 72.57153320312497], + [-75.05268554687493, 72.22636718749999], + [-75.92280273437501, 71.71723632812501], + [-74.90317382812503, 72.10048828125002], + [-74.20932617187498, 71.978662109375], + [-74.31572265624999, 71.84267578125], + [-75.20478515625001, 71.70913085937497], + [-74.70078125, 71.67558593750005], + [-74.99619140624998, 71.21811523437503], + [-74.48808593750002, 71.64838867187501], + [-73.8140625, 71.77143554687495], + [-74.197265625, 71.404150390625], + [-73.71284179687498, 71.58759765624998], + [-73.18061523437501, 71.282861328125], + [-73.27822265625, 71.53798828125], + [-72.901953125, 71.67778320312507], + [-71.64067382812499, 71.51625976562502], + [-71.22939453124997, 71.33876953125], + [-71.49501953124997, 71.10512695312502], + [-71.93793945312498, 71.09428710937502], + [-72.63271484374994, 70.83076171874998], + [-71.74252929687495, 71.046875], + [-71.370849609375, 70.97514648437499], + [-70.82607421874994, 71.10874023437503], + [-70.67265625, 71.05219726562498], + [-70.76171874999997, 70.79223632812503], + [-71.89018554687502, 70.43154296875002], + [-71.27587890625, 70.50029296874999], + [-71.42944335937503, 70.12778320312503], + [-70.97978515624999, 70.5810546875], + [-69.94980468750003, 70.84501953125005], + [-68.49575195312502, 70.61025390625], + [-68.363525390625, 70.48125], + [-70.05771484375, 70.042626953125], + [-68.77822265625, 70.20356445312501], + [-69.00830078124997, 69.97895507812501], + [-68.74404296874997, 69.94140625], + [-68.05908203124997, 70.317236328125], + [-67.36367187499994, 70.03442382812503], + [-67.22163085937495, 69.73071289062506], + [-68.02041015625, 69.77006835937499], + [-69.25078124999999, 69.51191406249998], + [-68.51303710937498, 69.57729492187497], + [-67.236962890625, 69.460107421875], + [-66.71674804687495, 69.31186523437498], + [-66.70742187500002, 69.16821289062503], + [-68.40629882812499, 69.23222656250002], + [-69.040625, 69.09799804687503], + [-68.41552734375, 69.17207031250001], + [-67.8326171875, 69.06596679687499], + [-67.88320312500002, 68.78398437499999], + [-69.31909179687497, 68.85698242187505], + [-68.21040039062495, 68.702978515625], + [-67.9384765625, 68.524169921875], + [-66.74272460937502, 68.45776367187497], + [-67.032958984375, 68.32607421874997], + [-66.923095703125, 68.06572265625005], + [-66.72900390624997, 68.12900390625006], + [-66.66269531249995, 68.03442382812497], + [-66.63095703124998, 68.21064453124998], + [-66.21240234374997, 68.280419921875], + [-66.44394531249998, 67.83383789062506], + [-65.94238281250003, 68.07094726562505], + [-65.86435546875003, 67.92285156249997], + [-65.50908203124996, 67.96826171875], + [-65.40126953125002, 67.67485351562499], + [-65.41533203124996, 67.87924804687498], + [-64.92231445312495, 68.03164062500002], + [-65.02109375, 67.78754882812495], + [-64.63779296875, 67.84023437500002], + [-63.850195312500034, 67.56606445312502], + [-64.00795898437502, 67.34731445312497], + [-64.69995117187494, 67.35053710937501], + [-63.83623046874993, 67.26411132812498], + [-63.59160156250002, 67.3775390625], + [-63.040136718750034, 67.235009765625], + [-63.70156249999994, 66.82236328125003], + [-62.962304687499966, 66.94926757812505], + [-62.37973632812495, 66.90537109375], + [-62.12358398437499, 67.046728515625], + [-61.35341796874994, 66.689208984375], + [-61.52783203124994, 66.55810546875003], + [-62.12333984374993, 66.64306640625003], + [-61.57080078125, 66.37290039062506], + [-61.95634765624993, 66.30932617187497], + [-62.553125, 66.40683593750003], + [-62.53359374999994, 66.22700195312498], + [-61.99160156250002, 66.03530273437502], + [-62.624121093750006, 66.01625976562505], + [-62.381982421874966, 65.83330078124999], + [-62.65888671874998, 65.63994140625002], + [-63.16894531249997, 65.65732421875], + [-63.45874023437494, 65.85302734375], + [-63.42089843749997, 65.70859374999998], + [-63.651074218749955, 65.66098632812506], + [-63.33745117187493, 65.61674804687502], + [-63.36337890624998, 65.22973632812503], + [-63.606591796874966, 64.92807617187503], + [-64.345703125, 65.17241210937499], + [-64.26967773437497, 65.40078124999997], + [-64.55507812500002, 65.1166015625], + [-65.401611328125, 65.764013671875], + [-64.44536132812496, 66.31713867187497], + [-65.0044921875, 66.07773437500003], + [-65.82573242187499, 65.996923828125], + [-65.65634765625003, 66.204736328125], + [-66.06372070312497, 66.13271484374997], + [-66.986328125, 66.62749023437505], + [-67.07685546874995, 66.52548828125006], + [-67.30732421874993, 66.5697265625], + [-67.22539062499993, 66.31025390624998], + [-67.88339843749995, 66.46743164062502], + [-67.18320312499995, 66.03442382812503], + [-67.350439453125, 65.92973632812502], + [-67.82802734374997, 65.96518554687503], + [-68.45991210937498, 66.249267578125], + [-68.74892578125, 66.200048828125], + [-68.21718750000002, 66.078857421875], + [-68.18671874999993, 65.87099609375002], + [-67.86645507812497, 65.773681640625], + [-67.936767578125, 65.56489257812501], + [-67.56962890624999, 65.64355468749997], + [-67.11796874999999, 65.44038085937495], + [-67.3365234375, 65.34658203125005], + [-66.69741210937502, 64.81518554687506], + [-66.63549804687503, 65.00034179687503], + [-66.21464843749999, 64.72241210937497], + [-65.93852539062496, 64.88574218750003], + [-65.2748046875, 64.63154296875004], + [-65.52934570312499, 64.50478515624997], + [-65.074609375, 64.43666992187502], + [-65.21298828125003, 64.30327148437502], + [-65.580322265625, 64.29384765624997], + [-65.16987304687495, 64.02817382812503], + [-64.67846679687503, 64.027978515625], + [-64.79814453124999, 63.91596679687498], + [-64.4109375, 63.70634765625002], + [-64.66464843749995, 63.24536132812497], + [-65.19184570312498, 63.764257812500006], + [-65.06894531249998, 63.26347656250002], + [-64.67236328125003, 62.921972656250006], + [-65.16279296875001, 62.93261718750003], + [-65.10849609374998, 62.62646484375], + [-66.22402343749994, 63.10717773437497], + [-66.228662109375, 62.99096679687503], + [-66.41445312500002, 63.027197265625034], + [-66.65498046874998, 63.264746093750006], + [-66.69746093749993, 63.069531249999955], + [-67.89326171874993, 63.733740234375006], + [-67.72255859374997, 63.422753906249966], + [-68.49375, 63.725488281249994], + [-68.91108398437498, 63.703222656250006], + [-68.141259765625, 63.17231445312501], + [-67.67597656249998, 63.093554687500045], + [-67.73696289062497, 63.00957031249999], + [-65.98017578125001, 62.20888671875002], + [-66.12387695312498, 61.89306640625], + [-68.53588867187503, 62.25561523437506], + [-69.12558593749998, 62.423974609374966], + [-69.604736328125, 62.76772460937502], + [-70.23613281250002, 62.76337890625001], + [-70.801416015625, 62.91049804687506], + [-71.10576171874999, 63.00224609375002], + [-70.94604492187497, 63.12070312499998], + [-71.34726562499998, 63.066113281249955], + [-71.99223632812493, 63.41616210937505], + [-71.380859375, 63.580322265625], + [-72.29013671874995, 63.72797851562498], + [-72.17426757812498, 63.893408203125006], + [-72.49843749999994, 63.82348632812497], + [-73.45454101562495, 64.39926757812503], + [-73.27128906250002, 64.58251953125], + [-73.91035156249998, 64.578125], + [-74.064794921875, 64.42465820312498], + [-74.13046874999998, 64.6078125], + [-74.46123046874996, 64.64467773437505], + [-74.68139648437497, 64.8306640625], + [-74.91943359374997, 64.76552734374997], + [-74.69472656250002, 64.49658203124997], + [-75.71503906249995, 64.52436523437495], + [-75.76669921875, 64.39194335937498], + [-76.85615234374998, 64.23764648437498], + [-77.76049804687503, 64.36015624999999], + [-78.04521484374993, 64.499267578125], + [-78.09560546875, 64.93925781250002], + [-77.36088867187496, 65.19653320312503], + [-77.32670898437493, 65.453125], + [-75.82832031249993, 65.22705078125003], + [-75.45209960937495, 64.84160156250002], + [-75.35712890624995, 65.00874023437495], + [-75.79868164062503, 65.297509765625], + [-75.16630859374999, 65.28393554687497], + [-74.13847656250002, 65.50346679687502], + [-73.55078125000003, 65.48525390625005], + [-74.41640624999997, 66.16708984375003], + [-73.03325195312502, 66.72817382812505], + [-72.78881835937494, 67.030615234375], + [-72.22001953124999, 67.25429687500002], + [-73.28447265624993, 68.35698242187505], + [-73.82050781249998, 68.36293945312502], + [-73.82211914062495, 68.68598632812501], + [-74.11796875000002, 68.70092773437506], + [-73.9892578125, 68.54863281250002], + [-74.2701171875, 68.54121093750001], + [-74.89296875, 68.80815429687505], + [-74.71669921874997, 69.04550781249998], + [-76.58505859375, 68.69873046875003], + [-76.55722656250003, 69.00947265625001], + [-75.9537109375, 69.03081054687502], + [-75.64775390625002, 69.212548828125], + [-76.46494140624995, 69.46943359375001], + [-76.23408203125001, 69.66210937500003], + [-76.742333984375, 69.57290039062497], + [-77.08994140625, 69.63510742187503], + [-76.85859374999995, 69.775390625], + [-77.591650390625, 69.84560546875002], + [-77.77402343750003, 70.23852539062503], + [-78.28281250000003, 70.229150390625], + [-79.06640624999997, 70.60356445312507], + [-79.40522460937498, 70.40073242187503], + [-78.86284179687499, 70.24189453125001], + [-78.88964843750003, 69.97749023437495], + [-79.51542968749996, 69.88759765625005], + [-81.65195312500003, 70.09462890625002], + [-80.92172851562503, 69.73090820312501], + [-81.56469726562503, 69.94272460937498], + [-82.29384765624997, 69.83691406250003], + [-83.14995117187493, 70.00908203125002], + [-83.85908203124998, 69.96274414062498], + [-85.43237304687497, 70.11137695312507], + [-85.780029296875, 70.03666992187505], + [-86.32202148437503, 70.14541015625], + [-86.396875, 70.46533203124997], + [-87.838134765625, 70.24658203125], + [-88.78271484374997, 70.49448242187503], + [-89.45590820312498, 71.06171874999995], + [-87.84492187499995, 70.94438476562505], + [-87.14008789062498, 71.01162109374997], + [-89.80537109374993, 71.46230468750005], + [-89.86152343750001, 72.41191406250005], + [-88.70517578124998, 73.40327148437495], + [-87.71977539062496, 73.72290039062497], + [-85.95078124999998, 73.85014648437505], + [-84.94677734375, 73.72163085937498], + [-86.00053710937499, 73.31254882812505], + [-86.65629882812502, 72.72402343750005], + [-86.21845703124998, 71.89912109375004], + [-85.02338867187495, 71.35322265625001], + [-86.58935546874997, 71.01079101562507] + ] + ], + [ + [ + [-100.00190429687497, 73.9458984375], + [-99.15795898437499, 73.73159179687497], + [-97.66997070312499, 73.88774414062499], + [-97.1705078125, 73.82485351562497], + [-97.001708984375, 73.66650390625003], + [-97.62587890624997, 73.50229492187498], + [-97.27250976562502, 73.38681640624998], + [-98.42177734375002, 72.94101562500003], + [-97.63632812499998, 73.02763671874999], + [-97.128125, 72.62758789062502], + [-96.59208984374996, 72.71025390624999], + [-96.44560546874996, 72.55244140624998], + [-96.80146484374998, 72.32241210937502], + [-96.61342773437494, 71.83383789062506], + [-97.58227539062497, 71.62968750000005], + [-98.18134765624998, 71.66245117187503], + [-98.32270507812501, 71.85234375000002], + [-98.19863281249994, 71.44086914062501], + [-98.66289062499993, 71.302099609375], + [-99.22363281249996, 71.387109375], + [-100.594482421875, 72.15234375000003], + [-101.20854492187495, 72.31699218749998], + [-101.72392578124996, 72.31489257812501], + [-102.70874023437496, 72.76450195312503], + [-102.20400390624998, 73.077294921875], + [-101.27319335937497, 72.7216796875], + [-100.48476562500002, 72.77294921874997], + [-100.395703125, 72.97700195312498], + [-100.128125, 72.90668945312495], + [-100.53637695312497, 73.19785156250003], + [-99.82514648437503, 73.2138671875], + [-100.36611328125001, 73.359033203125], + [-100.88935546875003, 73.27534179687501], + [-101.52319335937501, 73.48637695312502], + [-100.97578124999995, 73.59975585937502], + [-100.5216796875, 73.44931640625], + [-100.96298828125002, 73.79140625], + [-99.99111328125, 73.79516601562503], + [-100.00190429687497, 73.9458984375] + ] + ], + [ + [ + [-98.270361328125, 73.86850585937498], + [-98.97392578124997, 73.81206054687502], + [-99.4169921875, 73.89541015625002], + [-97.69824218749997, 74.10869140625005], + [-98.270361328125, 73.86850585937498] + ] + ], + [ + [ + [-93.17084960937498, 74.16098632812506], + [-92.22270507812502, 73.97236328124998], + [-90.62744140625, 73.95170898437505], + [-90.38139648437496, 73.82475585937502], + [-92.11791992187497, 72.75380859375], + [-94.21132812499997, 72.75693359375], + [-93.77055664062496, 72.66821289062506], + [-93.55517578124994, 72.42114257812497], + [-94.03754882812498, 72.02875976562498], + [-95.00786132812496, 72.01279296875], + [-95.60214843749998, 72.88447265624995], + [-95.63291015625003, 73.69545898437497], + [-94.697607421875, 73.66357421874997], + [-95.134130859375, 73.88125], + [-94.97353515625, 74.04140625000002], + [-93.17084960937498, 74.16098632812506] + ] + ], + [ + [ + [-119.73632812499997, 74.11264648437498], + [-119.20595703125002, 74.19799804687503], + [-119.11796874999995, 74.01552734375], + [-118.54399414062499, 74.24462890625003], + [-117.51484375000001, 74.23173828124999], + [-115.51069335937501, 73.61875], + [-115.446875, 73.43886718750002], + [-118.96157226562497, 72.68413085937499], + [-119.51284179687501, 72.30268554687501], + [-120.17988281250001, 72.21264648437506], + [-120.61933593750001, 71.50576171875002], + [-121.47216796875003, 71.38901367187503], + [-121.74936523437502, 71.44477539062501], + [-123.09565429687503, 71.09379882812502], + [-124.00776367187494, 71.67744140624998], + [-125.29667968749999, 71.973046875], + [-125.84531250000002, 71.978662109375], + [-123.79726562499997, 73.76816406250003], + [-124.69624023437497, 74.34819335937499], + [-121.50415039062497, 74.54511718749998], + [-119.56264648437494, 74.23281250000002], + [-119.73632812499997, 74.11264648437498] + ] + ], + [ + [ + [-97.35551757812496, 74.52631835937495], + [-97.75, 74.51054687500005], + [-97.41650390624994, 74.62656250000003], + [-97.35551757812496, 74.52631835937495] + ] + ], + [ + [ + [-95.306640625, 74.50541992187505], + [-95.850732421875, 74.58247070312504], + [-95.51020507812498, 74.63676757812499], + [-95.306640625, 74.50541992187505] + ] + ], + [ + [ + [-104.11992187499995, 75.03632812500004], + [-104.88740234374998, 75.14775390624999], + [-104.34619140624996, 75.42993164062503], + [-103.64350585937497, 75.18657226562499], + [-104.11992187499995, 75.03632812500004] + ] + ], + [ + [ + [-93.54257812499995, 75.0279296875], + [-93.57309570312495, 74.66884765625005], + [-94.53452148437498, 74.63671874999997], + [-96.59960937499997, 75.03178710937499], + [-95.95463867187493, 75.44379882812501], + [-94.878173828125, 75.63002929687502], + [-93.90908203125002, 75.42250976562502], + [-93.54257812499995, 75.0279296875] + ] + ], + [ + [ + [-96.07856445312495, 75.510107421875], + [-96.91513671875003, 75.37968749999999], + [-96.98281249999997, 75.50981445312505], + [-96.367822265625, 75.65463867187506], + [-96.07856445312495, 75.510107421875] + ] + ], + [ + [ + [-94.52656249999995, 75.74931640624999], + [-94.901220703125, 75.93076171875], + [-94.53789062499996, 75.99643554687506], + [-94.52656249999995, 75.74931640624999] + ] + ], + [ + [ + [-118.328125, 75.57968749999998], + [-118.81713867187503, 75.52211914062497], + [-119.39458007812499, 75.617333984375], + [-117.63369140624998, 76.11508789062498], + [-118.328125, 75.57968749999998] + ] + ], + [ + [ + [-79.0630859375, 75.92587890624998], + [-79.63876953124995, 75.84291992187505], + [-79.00932617187499, 76.14589843750005], + [-79.0630859375, 75.92587890624998] + ] + ], + [ + [ + [-102.22734374999995, 76.014892578125], + [-102.00800781250003, 75.93940429687498], + [-102.57958984375003, 75.78022460937498], + [-103.31474609374996, 75.76420898437499], + [-103.04150390624999, 75.91884765624997], + [-103.98525390624997, 75.93310546875003], + [-103.80078124999994, 76.03701171874997], + [-104.24248046874996, 76.04697265625006], + [-104.35063476562497, 76.18232421875001], + [-102.72802734374999, 76.30703125], + [-102.22734374999995, 76.014892578125] + ] + ], + [ + [ + [-104.02285156249998, 76.58310546875003], + [-103.05131835937495, 76.44985351562497], + [-103.31137695312499, 76.34755859375], + [-104.35751953124995, 76.33461914062502], + [-104.58569335937499, 76.60649414062499], + [-104.07451171875003, 76.66611328124998], + [-104.02285156249998, 76.58310546875003] + ] + ], + [ + [ + [-97.70092773437497, 76.46650390624998], + [-97.89052734374997, 75.7603515625], + [-97.40751953124999, 75.67250976562497], + [-97.33603515624998, 75.41982421875], + [-97.65332031249997, 75.50776367187498], + [-97.87822265624996, 75.41611328125003], + [-97.67431640624997, 75.127294921875], + [-98.04531249999997, 75.20083007812497], + [-98.12094726562503, 75.03271484375], + [-100.234375, 75.00771484374997], + [-100.48349609374995, 75.18842773437501], + [-100.14570312499995, 75.24614257812505], + [-100.71191406250003, 75.40634765625], + [-99.19458007812499, 75.698388671875], + [-102.58740234375001, 75.51367187500003], + [-102.79750976562501, 75.59965820312505], + [-102.14472656249998, 75.87504882812502], + [-100.97280273437498, 75.79843750000003], + [-101.414990234375, 75.84584960937502], + [-101.87211914062496, 76.08310546875003], + [-101.52895507812495, 76.21728515625003], + [-102.1046875, 76.33120117187505], + [-101.41518554687495, 76.42490234375003], + [-99.86547851562499, 75.92421875], + [-100.11284179687502, 76.11723632812507], + [-99.54106445312497, 76.14628906250005], + [-100.41420898437495, 76.242529296875], + [-99.97773437500003, 76.31245117187495], + [-100.82973632812497, 76.52387695312495], + [-99.8140625, 76.6322265625], + [-98.89033203125, 76.46557617187497], + [-98.71083984374994, 76.69384765625003], + [-97.70092773437497, 76.46650390624998] + ] + ], + [ + [ + [-101.22612304687497, 76.57934570312497], + [-101.61308593749995, 76.60458984375006], + [-100.26914062499998, 76.73413085937497], + [-101.22612304687497, 76.57934570312497] + ] + ], + [ + [ + [-108.29238281250001, 76.05712890625], + [-107.72348632812502, 75.99541015625002], + [-108.020703125, 75.80478515625], + [-107.21621093749997, 75.89155273437501], + [-106.91352539062503, 75.67963867187501], + [-106.67700195312499, 76.02373046875002], + [-105.63266601562493, 75.94536132812505], + [-105.51948242187497, 75.63237304687505], + [-106.09262695312495, 75.08945312500003], + [-107.15341796874996, 74.9271484375], + [-108.47475585937495, 74.94721679687501], + [-108.83129882812501, 75.06489257812498], + [-112.51933593749997, 74.41684570312503], + [-113.67158203124997, 74.45302734375005], + [-114.31269531250003, 74.71508789062506], + [-112.835986328125, 74.9755859375], + [-111.67109375, 75.01943359374997], + [-111.09345703125001, 75.25629882812498], + [-113.71176757812499, 75.06860351562503], + [-113.85332031249996, 75.259375], + [-113.46708984374996, 75.41611328125003], + [-114.01650390624998, 75.43427734375001], + [-114.16845703124994, 75.23950195312503], + [-114.51381835937497, 75.27548828125], + [-114.45175781250002, 75.08789062499997], + [-115.02011718749999, 74.97617187500003], + [-115.41318359374995, 75.11499023437497], + [-115.72885742187496, 74.968115234375], + [-116.47607421874996, 75.17177734375], + [-117.56523437499997, 75.23334960937504], + [-117.25761718750002, 75.45952148437502], + [-116.07714843749996, 75.49296874999999], + [-115.14184570312501, 75.67851562500005], + [-116.42563476562498, 75.58535156249997], + [-117.16362304687496, 75.64487304687503], + [-116.80214843749995, 75.77158203124998], + [-114.99150390625002, 75.896337890625], + [-116.66455078124999, 75.95756835937505], + [-116.20986328125, 76.19443359374998], + [-114.77861328124999, 76.17260742187497], + [-115.82216796874997, 76.27001953125003], + [-114.99848632812503, 76.4974609375], + [-114.19394531249999, 76.45146484375005], + [-113.82348632812501, 76.20683593750002], + [-112.69760742187496, 76.20170898437505], + [-111.05268554687495, 75.54853515625001], + [-108.94716796875, 75.54179687499999], + [-108.94477539062495, 75.69897460937503], + [-109.8705078125, 75.929052734375], + [-109.48681640624999, 76.14467773437497], + [-110.31445312500001, 76.369384765625], + [-109.09824218749996, 76.811865234375], + [-108.46699218749997, 76.73759765625007], + [-108.29238281250001, 76.05712890625] + ] + ], + [ + [ + [-89.72646484374994, 76.50742187499998], + [-90.55625, 76.73457031249998], + [-90.13632812499995, 76.83696289062505], + [-89.69541015625, 76.74116210937498], + [-89.72646484374994, 76.50742187499998] + ] + ], + [ + [ + [-113.56069335937494, 76.74326171874998], + [-114.83525390624999, 76.79467773437497], + [-113.89165039062495, 76.89487304687503], + [-113.56069335937494, 76.74326171874998] + ] + ], + [ + [ + [-94.29497070312493, 76.91245117187498], + [-93.23002929687496, 76.77026367187497], + [-93.53457031250002, 76.44770507812498], + [-92.99536132812494, 76.62041015624999], + [-91.305029296875, 76.68076171875003], + [-90.54262695312494, 76.495751953125], + [-91.41508789062496, 76.45585937500005], + [-89.28452148437498, 76.30161132812506], + [-89.40659179687498, 76.18916015624998], + [-91.40732421874998, 76.22006835937506], + [-89.27758789062497, 75.79506835937497], + [-89.64604492187499, 75.5650390625], + [-88.91669921874998, 75.45395507812503], + [-88.64497070312495, 75.65844726562503], + [-88.201318359375, 75.51201171875005], + [-87.72973632812503, 75.57563476562495], + [-87.53911132812502, 75.48486328125003], + [-87.25693359374998, 75.61772460937499], + [-85.95146484374993, 75.39501953125], + [-85.97299804687498, 75.5287109375], + [-83.931982421875, 75.81894531250003], + [-83.23710937499993, 75.75083007812503], + [-82.153662109375, 75.83105468750003], + [-80.32197265624998, 75.62910156250001], + [-79.50908203125002, 75.25981445312499], + [-80.38198242187494, 75.03417968750003], + [-79.40141601562502, 74.91762695312502], + [-79.944482421875, 74.83364257812505], + [-80.34775390624998, 74.90297851562505], + [-80.26274414062499, 74.58447265625], + [-81.94018554687494, 74.47270507812505], + [-82.73579101562495, 74.53027343749997], + [-83.5220703125, 74.90146484375], + [-83.53188476562494, 74.58569335937497], + [-84.42553710937503, 74.50810546875007], + [-85.06142578125, 74.60693359375003], + [-85.133447265625, 74.517431640625], + [-85.44233398437495, 74.6005859375], + [-85.80800781249994, 74.49897460937498], + [-88.42304687499995, 74.49414062499997], + [-88.53496093749993, 74.83173828125001], + [-89.55869140624995, 74.55473632812507], + [-90.55327148437499, 74.61274414062498], + [-90.88022460937498, 74.8177734375], + [-91.13457031250002, 74.64985351562498], + [-91.54912109375002, 74.65556640624999], + [-92.3892578125, 75.263330078125], + [-92.18510742187499, 75.84653320312498], + [-93.09174804687495, 76.35400390624997], + [-95.27387695312498, 76.26440429687503], + [-96.03969726562494, 76.48671875000002], + [-95.65097656249998, 76.58466796874998], + [-96.88071289062495, 76.73833007812505], + [-96.40156249999995, 76.79721679687503], + [-96.75830078124997, 76.97177734374998], + [-95.84951171875002, 77.06621093750005], + [-94.29497070312493, 76.91245117187498] + ] + ], + [ + [ + [-115.55126953125001, 77.36328125], + [-116.32919921874996, 77.137060546875], + [-115.81005859374999, 76.939111328125], + [-116.25273437500002, 76.90141601562505], + [-115.94628906250003, 76.71127929687503], + [-116.99921874999995, 76.531591796875], + [-117.23359375000001, 76.28154296875005], + [-117.99296874999999, 76.40581054687505], + [-117.88081054687497, 76.80507812500005], + [-118.79140624999994, 76.51298828125005], + [-119.080712890625, 76.12407226562505], + [-119.58037109375, 76.32651367187498], + [-119.52612304687496, 75.99721679687505], + [-119.91289062499997, 75.85883789062501], + [-120.40888671874995, 75.82563476562498], + [-120.84838867187496, 76.18266601562499], + [-121.21347656249999, 75.98369140625005], + [-122.53305664062498, 75.95092773437503], + [-122.59272460937497, 76.16206054687495], + [-122.90278320312498, 76.13471679687498], + [-122.51938476562503, 76.353173828125], + [-121.56113281250003, 76.453466796875], + [-119.09018554687496, 77.30507812500002], + [-116.84355468749995, 77.33955078124995], + [-117.03974609374995, 77.46513671875005], + [-116.51132812500003, 77.54760742187497], + [-115.55126953125001, 77.36328125] + ] + ], + [ + [ + [-89.83325195312503, 77.26762695312505], + [-90.22827148437503, 77.21245117187499], + [-90.99321289062499, 77.32949218750002], + [-91.01904296875003, 77.64389648437503], + [-89.83896484375003, 77.49140624999998], + [-89.83325195312503, 77.26762695312505] + ] + ], + [ + [ + [-104.55815429687497, 77.14174804687497], + [-105.21508789062496, 77.18208007812501], + [-106.03559570312495, 77.73984375000006], + [-105.58789062499997, 77.73598632812497], + [-104.54223632812501, 77.33774414062503], + [-104.55815429687497, 77.14174804687497] + ] + ], + [ + [ + [-95.484375, 77.79199218750003], + [-93.30097656249995, 77.73979492187505], + [-93.54394531249997, 77.466650390625], + [-95.98706054687497, 77.484130859375], + [-96.19458007812497, 77.70053710937503], + [-95.484375, 77.79199218750003] + ] + ], + [ + [ + [-101.6935546875, 77.69658203125005], + [-102.37783203124995, 77.728125], + [-102.44770507812498, 77.88061523437506], + [-101.19321289062493, 77.82978515624998], + [-101.00205078124998, 77.73510742187497], + [-101.6935546875, 77.69658203125005] + ] + ], + [ + [ + [-113.83247070312497, 77.75463867187506], + [-114.28720703124998, 77.72148437500005], + [-114.98041992187498, 77.91542968750002], + [-114.33037109374997, 78.07753906250002], + [-113.83247070312497, 77.75463867187506] + ] + ], + [ + [ + [-110.45805664062496, 78.10322265625001], + [-109.62226562499995, 78.07475585937499], + [-110.865625, 77.834130859375], + [-110.15273437500002, 77.76293945312506], + [-110.19848632812501, 77.52451171874998], + [-112.37265625000002, 77.36411132812498], + [-113.16435546875002, 77.5302734375], + [-113.21518554687498, 77.90351562500001], + [-110.45805664062496, 78.10322265625001] + ] + ], + [ + [ + [-109.81596679687499, 78.65039062500003], + [-109.48447265624995, 78.31640625], + [-111.16918945312499, 78.38627929687505], + [-111.51748046874997, 78.27470703125005], + [-112.13125, 78.366064453125], + [-113.22304687499998, 78.29790039062505], + [-112.85585937499997, 78.46684570312502], + [-110.877587890625, 78.73505859375004], + [-109.81596679687499, 78.65039062500003] + ] + ], + [ + [ + [-96.20449218749994, 78.53129882812499], + [-94.91538085937495, 78.39052734375002], + [-95.32924804687497, 78.22504882812495], + [-94.93427734374998, 78.07563476562498], + [-96.98964843749994, 77.80600585937503], + [-97.65815429687498, 78.090625], + [-96.944677734375, 78.15185546874997], + [-98.04951171874995, 78.325927734375], + [-98.33261718749998, 78.77353515625006], + [-97.38232421875, 78.78291015625001], + [-96.20449218749994, 78.53129882812499] + ] + ], + [ + [ + [-103.42602539062499, 79.315625], + [-102.57617187499996, 78.87939453125003], + [-101.70366210937502, 79.07890625000002], + [-101.128125, 78.80166015625002], + [-100.43549804687503, 78.8203125], + [-99.60942382812495, 78.58305664062507], + [-99.16640625000002, 77.85693359375003], + [-100.27465820312503, 77.83271484374995], + [-101.07412109375001, 78.19384765625], + [-102.60698242187502, 78.24892578125002], + [-102.73134765624995, 78.37104492187495], + [-103.94658203124999, 78.26000976562497], + [-104.76357421874998, 78.35166015625], + [-104.90961914062498, 78.55263671875], + [-103.57050781250003, 78.53984375000005], + [-104.02084960937502, 78.63491210937497], + [-103.37158203125, 78.73632812500003], + [-104.18500976562498, 78.78129882812505], + [-104.15195312499999, 78.989892578125], + [-104.89550781249996, 78.80815429687502], + [-104.74677734375003, 79.02709960937503], + [-105.53564453124999, 79.03251953125007], + [-105.51455078124995, 79.24248046875002], + [-105.38769531249994, 79.32358398437503], + [-103.42602539062499, 79.315625] + ] + ], + [ + [ + [-98.79160156249995, 79.98110351562505], + [-98.94521484375, 79.72407226562498], + [-100.05683593749997, 79.89824218750005], + [-100.05327148437496, 80.093359375], + [-99.15322265625001, 80.12421874999998], + [-98.79160156249995, 79.98110351562505] + ] + ], + [ + [ + [-91.88554687499999, 81.13286132812505], + [-90.64301757812498, 80.59370117187498], + [-89.23559570312494, 80.51064453125002], + [-88.85732421874997, 80.16621093750001], + [-88.19990234374998, 80.11147460937497], + [-88.5248046875, 80.41801757812507], + [-87.675, 80.37211914062505], + [-87.92231445312501, 80.09770507812499], + [-86.97719726562502, 79.89423828125001], + [-87.29516601562494, 79.58017578124998], + [-86.33696289062496, 79.63496093749995], + [-86.00703124999998, 79.47944335937498], + [-85.6478515625, 79.61142578125006], + [-85.04213867187497, 79.2845703125], + [-86.95717773437502, 78.97490234375005], + [-87.61738281249995, 78.67631835937505], + [-88.04018554687494, 78.99531250000004], + [-87.98286132812498, 78.53706054687501], + [-88.74160156250002, 78.58403320312499], + [-88.82241210937497, 78.18588867187498], + [-90.037109375, 78.60683593750002], + [-89.52568359374999, 78.15961914062495], + [-90.29721679687495, 78.32802734374997], + [-90.614404296875, 78.14985351562501], + [-92.35126953125001, 78.312890625], + [-92.8482421875, 78.46010742187497], + [-91.86689453124998, 78.54267578125001], + [-93.26660156249997, 78.60830078124997], + [-93.63442382812502, 78.75092773437498], + [-93.15986328124998, 78.77563476562503], + [-94.11459960937498, 78.92890625000001], + [-92.54721679687495, 79.28261718750002], + [-91.29990234375003, 79.372705078125], + [-92.82192382812497, 79.44990234375001], + [-93.93315429687496, 79.29072265624998], + [-94.11030273437498, 79.40156250000001], + [-95.10317382812502, 79.289892578125], + [-95.66289062500002, 79.52734374999997], + [-94.40185546874997, 79.736328125], + [-95.73935546874995, 79.66015625000003], + [-96.58906249999995, 79.91665039062497], + [-96.77324218749999, 80.13579101562502], + [-94.64589843749994, 80.04873046874997], + [-94.26259765625002, 80.19487304687499], + [-95.40507812499996, 80.13500976562506], + [-96.39409179687493, 80.31503906250003], + [-95.549072265625, 80.36660156249997], + [-95.92695312499998, 80.72065429687498], + [-93.92792968749995, 80.55917968750003], + [-95.51474609375003, 80.83813476562503], + [-94.98051757812499, 81.04965820312503], + [-93.28671874999998, 81.10029296874998], + [-94.22011718749997, 81.33076171875004], + [-93.03466796874997, 81.3462890625], + [-91.88554687499999, 81.13286132812505] + ] + ], + [ + [ + [-69.4888671875, 83.01679687499998], + [-66.42255859374998, 82.92685546875003], + [-68.46933593749995, 82.65336914062502], + [-65.29902343749995, 82.79960937500005], + [-64.98388671874997, 82.90229492187501], + [-64.50400390625, 82.77841796874998], + [-63.641015624999966, 82.81259765625003], + [-63.246777343749926, 82.4501953125], + [-62.47519531249995, 82.51958007812502], + [-61.392480468749994, 82.44189453125], + [-61.61538085937502, 82.18442382812503], + [-64.43579101562497, 81.74262695312501], + [-66.62573242187497, 81.61640624999995], + [-68.68852539062493, 81.29331054687503], + [-64.78007812499993, 81.49287109375001], + [-69.55068359375, 80.38325195312498], + [-70.71259765625001, 80.53959960937505], + [-70.264892578125, 80.23359374999998], + [-72.05595703124996, 80.12324218749995], + [-70.56840820312493, 80.09370117187498], + [-71.387841796875, 79.76176757812505], + [-72.43652343750003, 79.69438476562499], + [-74.39448242187495, 79.87407226562499], + [-73.47246093749996, 79.7564453125], + [-73.36152343750001, 79.50400390625], + [-75.50341796875, 79.41416015625], + [-76.898828125, 79.5123046875], + [-75.60273437499998, 79.23955078125005], + [-74.48120117187503, 79.22949218750006], + [-74.64091796874996, 79.03554687499997], + [-78.58164062499998, 79.075], + [-77.88276367187498, 78.9423828125], + [-76.255859375, 79.00683593749997], + [-74.486328125, 78.75009765624998], + [-74.87861328124998, 78.54482421875], + [-76.41611328124995, 78.51152343750005], + [-75.19345703125, 78.327734375], + [-75.86596679687497, 78.00981445312499], + [-78.01259765624997, 77.94604492187506], + [-78.07617187500003, 77.51904296875], + [-78.70849609374997, 77.34213867187503], + [-80.57304687499996, 77.31479492187506], + [-81.65908203124997, 77.52543945312499], + [-81.3013671875, 77.34404296875007], + [-82.056787109375, 77.29653320312497], + [-81.75634765624997, 77.20400390625005], + [-79.49726562500001, 77.19609375000005], + [-78.97919921874998, 76.89287109374999], + [-78.28886718750002, 76.97797851562501], + [-77.98330078124994, 76.75498046875006], + [-78.284326171875, 76.57124023437501], + [-80.79970703124997, 76.173583984375], + [-80.97451171874994, 76.470068359375], + [-81.71738281250003, 76.494970703125], + [-82.52983398437499, 76.723291015625], + [-82.23315429687494, 76.46582031250003], + [-83.88569335937501, 76.453125], + [-84.22377929687497, 76.67534179687499], + [-84.27534179687498, 76.35654296875006], + [-85.141259765625, 76.30458984375005], + [-86.45371093750003, 76.58486328125002], + [-86.68022460937499, 76.37661132812497], + [-87.35419921874998, 76.44804687500005], + [-87.48979492187499, 76.58583984374997], + [-87.49755859374997, 76.38627929687499], + [-88.39599609374997, 76.40527343750003], + [-88.49584960937497, 76.77285156249997], + [-88.54580078125002, 76.42089843750003], + [-89.36962890624997, 76.474462890625], + [-89.49975585937503, 76.82680664062502], + [-88.39814453124995, 77.10395507812501], + [-86.81225585937497, 77.18491210937498], + [-87.68144531249996, 77.43637695312503], + [-88.01699218750002, 77.78471679687505], + [-86.75507812499998, 77.86372070312498], + [-85.58847656249998, 77.46113281250004], + [-84.73867187499997, 77.36103515624998], + [-83.72128906249998, 77.41420898437497], + [-82.7103515625, 77.84951171875002], + [-82.5953125, 77.99213867187504], + [-83.77939453125, 77.53261718750002], + [-85.28935546874996, 77.55903320312498], + [-85.54755859374998, 77.92768554687495], + [-84.61542968749998, 78.19570312500002], + [-84.22270507812499, 78.176025390625], + [-84.91035156249993, 78.23969726562501], + [-84.78320312499997, 78.52758789062506], + [-85.5859375, 78.10957031249998], + [-86.21777343750003, 78.08120117187497], + [-85.92006835937494, 78.34287109374998], + [-86.91323242187494, 78.126806640625], + [-87.5517578125, 78.17661132812503], + [-86.80791015624999, 78.77436523437495], + [-85.00375976562495, 78.912255859375], + [-83.27143554687501, 78.77031250000002], + [-81.75009765624995, 78.97578124999995], + [-82.43876953125002, 78.903662109375], + [-84.41201171875002, 78.99658203125003], + [-84.38359375000002, 79.1185546875], + [-83.57587890624995, 79.05366210937501], + [-86.42075195312498, 79.84521484374997], + [-86.49853515625003, 80.25825195312501], + [-83.72363281250003, 80.22895507812501], + [-81.68837890625, 79.685791015625], + [-80.47592773437498, 79.60625], + [-80.12446289062495, 79.66948242187507], + [-81.01015625000002, 79.693115234375], + [-82.98701171874995, 80.32260742187498], + [-76.86298828124995, 80.86479492187505], + [-78.71621093749994, 80.95166015624997], + [-76.88510742187503, 81.43027343750006], + [-81.00703125000001, 80.6548828125], + [-82.88432617187502, 80.57753906249997], + [-82.22236328124998, 80.77231445312503], + [-84.41782226562495, 80.52675781250002], + [-86.250341796875, 80.56577148437506], + [-86.60307617187499, 80.66401367187498], + [-85.63930664062494, 80.92460937500007], + [-83.288818359375, 81.14794921875], + [-85.780859375, 81.03505859375], + [-87.32988281250002, 80.669775390625], + [-88.00366210937497, 80.675390625], + [-89.16689453125, 80.94130859375], + [-86.47675781249993, 81.03574218750006], + [-84.94121093750002, 81.28623046875], + [-87.27509765624995, 81.080810546875], + [-89.623046875, 81.032470703125], + [-89.94731445312499, 81.17265625000005], + [-89.20869140624998, 81.25009765625003], + [-89.67368164062503, 81.32861328125003], + [-87.59702148437498, 81.52583007812498], + [-88.47905273437502, 81.56464843749998], + [-90.41630859374996, 81.40537109375003], + [-89.82167968749997, 81.63486328124998], + [-91.29238281250002, 81.57124023437498], + [-91.64755859374998, 81.68383789062503], + [-88.06318359375001, 82.09648437500007], + [-87.01821289062502, 81.95874023437497], + [-86.62680664062495, 82.05102539062503], + [-85.04482421874997, 81.9828125], + [-86.615625, 82.21855468750007], + [-84.89682617187503, 82.44941406250001], + [-82.63369140625002, 82.07729492187497], + [-82.53691406250002, 82.24726562499995], + [-79.465625, 81.85112304687499], + [-82.44755859374993, 82.39501953125003], + [-81.68115234375003, 82.51865234375], + [-82.11684570312497, 82.62866210937503], + [-80.8625, 82.57153320312503], + [-81.01015625000002, 82.77905273437503], + [-78.748779296875, 82.67939453124998], + [-80.15493164062497, 82.91113281250003], + [-77.61806640624997, 82.89584960937503], + [-76.009375, 82.53515625], + [-75.565625, 82.60854492187502], + [-77.12490234374994, 83.00854492187497], + [-74.41416015624995, 83.01313476562501], + [-72.65869140625, 82.72163085937495], + [-73.44189453124994, 82.90483398437499], + [-72.811669921875, 83.08120117187502], + [-71.98320312499996, 83.10141601562498], + [-70.94038085937495, 82.90224609375], + [-71.08481445312498, 83.08266601562497], + [-69.96992187499995, 83.11611328125005], + [-69.4888671875, 83.01679687499998] + ] + ] + ] + }, + "properties": { "name": "Canada", "childNum": 110 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [9.527658197470123, 47.27026989773668], + [9.46249431093294, 47.19858962254578], + [9.46249431093294, 47.09010747968864], + [9.409458596647225, 47.02019676540292], + [9.579979133936737, 47.05856388629306], + [9.580273437500011, 47.057373046875], + [10.133496093750011, 46.851513671875], + [10.349414062500017, 46.98476562499999], + [10.414941406250023, 46.964404296874996], + [10.45458984375, 46.8994140625], + [10.452832031250011, 46.86494140625], + [10.406054687500017, 46.73486328125], + [10.39794921875, 46.6650390625], + [10.4306640625, 46.550048828125], + [10.195507812500011, 46.62109375], + [10.1375, 46.61435546875], + [10.087011718750006, 46.599902343749996], + [10.061230468750011, 46.546777343749994], + [10.038281250000011, 46.483203125], + [10.045605468750011, 46.447900390624994], + [10.081933593750023, 46.420751953125], + [10.109667968750017, 46.362841796874996], + [10.128320312500023, 46.238232421875], + [10.08056640625, 46.227978515625], + [10.041015625, 46.238085937499996], + [9.939257812500017, 46.36181640625], + [9.884472656250011, 46.3677734375], + [9.787792968750011, 46.346044921875], + [9.639453125000017, 46.2958984375], + [9.57958984375, 46.29609375], + [9.528710937500023, 46.306201171874996], + [9.427636718750023, 46.482324218749994], + [9.399316406250023, 46.4806640625], + [9.304394531250011, 46.495556640625], + [9.203417968750017, 46.21923828125], + [9.11874162946429, 46.014892578125], + [8.97551618303573, 45.81677455357143], + [8.74961495535715, 46.02246372767857], + [8.818554687500011, 46.0771484375], + [8.458398437500023, 46.245898437499996], + [8.370703125, 46.445117187499996], + [8.298535156250011, 46.40341796875], + [8.23193359375, 46.341210937499994], + [8.08154296875, 46.256005859374994], + [7.9931640625, 46.015917968749996], + [7.327929687500017, 45.912353515625], + [7.129003906250006, 45.880419921874996], + [7.055761718750006, 45.90380859375], + [7.02109375, 45.92578125], + [6.953710937500006, 46.017138671874996], + [6.897265625000017, 46.0517578125], + [6.772070312500006, 46.16513671875], + [6.758105468750017, 46.415771484375], + [6.578222656250006, 46.437353515625], + [6.428906250000011, 46.430517578125], + [6.321875, 46.393701171874994], + [6.234667968750017, 46.3326171875], + [6.199414062500011, 46.19306640625], + [6.086621093750011, 46.147021484374996], + [6.006640625000017, 46.142333984375], + [5.971484375000017, 46.151220703125], + [5.970019531250017, 46.214697265625], + [6.0361328125, 46.238085937499996], + [6.095898437500011, 46.27939453125], + [6.129687500000017, 46.5669921875], + [6.41015625, 46.755419921874996], + [6.429003906250017, 46.832275390625], + [6.45625, 46.94833984375], + [6.624804687500017, 47.004345703125], + [6.666894531250023, 47.026513671874994], + [6.688085937500006, 47.058251953124994], + [6.820703125000023, 47.16318359375], + [6.952050781250023, 47.2671875], + [6.978515625, 47.302050781249996], + [7.000585937500006, 47.322509765625], + [7.000585937500006, 47.339453125], + [6.900390625, 47.39423828125], + [6.968359375, 47.45322265625], + [7.136035156250017, 47.48984375], + [7.343164062500023, 47.43310546875], + [7.615625, 47.592724609375], + [8.454003906250023, 47.59619140625], + [8.559472656250023, 47.6240234375], + [8.570507812500011, 47.63779296875], + [8.567089843750011, 47.651904296874996], + [8.55234375, 47.659130859375], + [8.451757812500006, 47.651806640625], + [8.413281250000011, 47.6626953125], + [8.403417968750006, 47.687792968749996], + [8.435742187500011, 47.731347656249994], + [8.572656250000023, 47.775634765625], + [9.524023437500006, 47.52421875], + [9.625878906250023, 47.467041015625], + [9.527539062500011, 47.270751953125], + [9.527658197470123, 47.27026989773668] + ] + ] + }, + "properties": { "name": "Switzerland", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-67.28886718749999, -55.776855468749964], + [-67.55996093749997, -55.72480468750002], + [-67.39736328124997, -55.58515625], + [-67.28886718749999, -55.776855468749964] + ] + ], + [ + [ + [-67.07993164062498, -55.15380859374996], + [-67.33969726562495, -55.292578124999984], + [-67.4947265625, -55.177441406249976], + [-68.07001953124995, -55.22109374999999], + [-68.30136718750003, -54.98066406250003], + [-67.245263671875, -54.977636718750034], + [-67.07993164062498, -55.15380859374996] + ] + ], + [ + [ + [-69.70297851562503, -54.91904296875], + [-68.90078125000002, -55.01777343750004], + [-68.45800781249997, -54.95966796875002], + [-68.61328124999997, -55.155566406250045], + [-68.28266601562495, -55.25517578125], + [-68.04833984375, -55.6431640625], + [-68.86704101562498, -55.45019531250003], + [-68.89008789062498, -55.2412109375], + [-69.19262695312497, -55.171875], + [-69.35922851562498, -55.300683593749945], + [-69.18085937499995, -55.47480468749998], + [-69.41181640624995, -55.44423828124997], + [-69.97978515625002, -55.14746093749999], + [-69.88442382812494, -54.88203125000001], + [-69.70297851562503, -54.91904296875] + ] + ], + [ + [ + [-70.9916015625, -54.86796874999999], + [-70.80483398437497, -54.96767578124996], + [-70.41752929687493, -54.908886718749976], + [-70.29785156249997, -55.11376953124997], + [-70.47558593749994, -55.17705078124998], + [-71.43720703125001, -54.88925781249997], + [-70.9916015625, -54.86796874999999] + ] + ], + [ + [ + [-71.390478515625, -54.03281250000002], + [-71.02192382812495, -54.111816406250036], + [-71.14326171874998, -54.374023437499986], + [-71.473291015625, -54.23115234375001], + [-71.94853515624999, -54.300878906250006], + [-72.21044921874997, -54.04775390624995], + [-71.996484375, -53.884863281249984], + [-71.390478515625, -54.03281250000002] + ] + ], + [ + [ + [-72.92324218749997, -53.481640625], + [-72.88222656249997, -53.578320312499976], + [-72.48227539062503, -53.58808593750001], + [-72.20541992187503, -53.80742187500002], + [-72.408544921875, -54.00380859374997], + [-72.87099609375, -54.12656250000002], + [-72.76376953125, -53.86484375], + [-73.03945312499994, -53.83281250000004], + [-73.08076171875001, -53.99804687499995], + [-73.21064453125001, -53.98583984374995], + [-73.31435546875, -53.72919921874998], + [-73.845458984375, -53.54580078125001], + [-73.44707031249993, -53.41005859374998], + [-72.92324218749997, -53.481640625] + ] + ], + [ + [ + [-74.38574218749994, -52.92236328125001], + [-73.65400390624998, -53.06982421875003], + [-73.13520507812498, -53.35390625], + [-73.56728515625, -53.3068359375], + [-73.86694335937494, -53.096875], + [-74.27021484374995, -53.08154296875002], + [-74.71201171874998, -52.74873046874998], + [-74.38574218749994, -52.92236328125001] + ] + ], + [ + [ + [-68.62993164062499, -52.65263671875004], + [-68.65322265624994, -54.85361328124999], + [-69.48627929687493, -54.85888671875], + [-69.72343750000002, -54.71210937500003], + [-70.49716796875, -54.80957031249999], + [-71.83154296874997, -54.62617187500002], + [-71.92773437500003, -54.52871093749997], + [-71.80014648437498, -54.433984374999945], + [-71.07993164062498, -54.444238281249994], + [-70.79726562500002, -54.32724609374996], + [-70.70112304687498, -54.48544921875004], + [-70.31098632812498, -54.52851562500002], + [-70.86308593749993, -54.11044921875003], + [-70.86772460937499, -53.88417968750002], + [-70.53129882812502, -53.627343750000016], + [-70.37973632812495, -53.98671874999995], + [-70.62983398437493, -54.005566406249976], + [-70.53530273437494, -54.136132812500016], + [-70.16899414062502, -54.37929687499999], + [-69.74184570312494, -54.30585937500005], + [-69.25317382812494, -54.557421875000045], + [-69.04433593749997, -54.40673828124999], + [-69.98813476562503, -54.10908203125001], + [-70.15112304687503, -53.88808593750002], + [-70.09111328124996, -53.72177734374998], + [-69.35595703125003, -53.41630859375001], + [-69.63701171874999, -53.33408203125004], + [-70.32929687499998, -53.37763671875003], + [-70.44335937499994, -53.085546875000034], + [-70.130615234375, -52.942773437499994], + [-70.38012695312494, -52.75195312500002], + [-69.93544921874997, -52.82109374999998], + [-69.41406249999997, -52.48623046874997], + [-69.16704101562499, -52.66757812499997], + [-68.78979492187497, -52.576757812500034], + [-68.62993164062499, -52.65263671875004] + ] + ], + [ + [ + [-74.82294921874993, -51.63017578125001], + [-74.53681640624998, -51.96513671875004], + [-74.69448242187497, -52.27919921874999], + [-74.85180664062494, -52.27070312500003], + [-75.10537109375, -51.78886718750001], + [-74.82294921874993, -51.63017578125001] + ] + ], + [ + [ + [-74.55864257812499, -51.27705078125001], + [-74.62036132812497, -51.395703125000026], + [-75.04736328125, -51.39833984375003], + [-75.28911132812496, -51.625390625000016], + [-75.15366210937498, -51.278808593750014], + [-74.73666992187503, -51.20761718749999], + [-74.55864257812499, -51.27705078125001] + ] + ], + [ + [ + [-75.302001953125, -50.67998046875005], + [-75.411376953125, -50.76435546875001], + [-75.42763671875002, -50.48056640625002], + [-75.11533203124998, -50.510449218749976], + [-75.302001953125, -50.67998046875005] + ] + ], + [ + [ + [-75.05478515625, -50.29609375], + [-75.44912109374997, -50.34335937500004], + [-75.32666015624997, -50.01181640625], + [-74.8759765625, -50.10996093750001], + [-75.05478515625, -50.29609375] + ] + ], + [ + [ + [-75.106689453125, -48.83652343750001], + [-75.38994140624999, -49.15917968750002], + [-75.64116210937499, -49.195410156250034], + [-75.48764648437498, -49.082421875000016], + [-75.58310546874998, -48.85888671874995], + [-75.106689453125, -48.83652343750001] + ] + ], + [ + [ + [-74.47617187499998, -49.14785156250002], + [-74.59472656249997, -50.00664062500001], + [-74.76298828124996, -50.01142578125001], + [-74.88041992187502, -49.72587890625001], + [-74.72382812499998, -49.42382812500003], + [-74.960107421875, -49.533007812499974], + [-75.06601562499998, -49.85234375000002], + [-75.54980468749994, -49.79130859375002], + [-75.30585937499998, -49.49404296875003], + [-75.46748046874995, -49.35888671875003], + [-75.08603515624998, -49.27021484375], + [-75.21015624999995, -49.14804687499998], + [-74.94921875, -48.960156249999976], + [-74.89624023437503, -48.73320312500002], + [-74.54609374999993, -48.76689453125004], + [-74.47617187499998, -49.14785156250002] + ] + ], + [ + [ + [-75.51025390624997, -48.76347656250005], + [-75.65092773437496, -48.58632812500002], + [-75.57148437499993, -48.095898437500026], + [-75.39140625000002, -48.01972656249997], + [-75.15849609374999, -48.62265624999996], + [-75.51025390624997, -48.76347656250005] + ] + ], + [ + [ + [-74.56728515625, -48.591992187500026], + [-74.92304687499998, -48.62646484375003], + [-75.21289062499997, -48.141699218750034], + [-75.19829101562502, -47.974609375000014], + [-74.895654296875, -47.839355468749986], + [-74.56728515625, -48.591992187500026] + ] + ], + [ + [ + [-75.11220703124997, -47.8376953125], + [-75.26103515625002, -47.76386718749998], + [-74.92646484374998, -47.72314453125003], + [-75.11220703124997, -47.8376953125] + ] + ], + [ + [ + [-74.31289062500002, -45.69150390625002], + [-74.46552734374995, -45.757226562499994], + [-74.68984375, -45.66259765625], + [-74.310546875, -45.17265625000002], + [-74.31289062500002, -45.69150390625002] + ] + ], + [ + [ + [-73.63217773437498, -44.82148437499997], + [-73.81845703125, -44.65214843750002], + [-73.72392578124993, -44.544238281249974], + [-73.63217773437498, -44.82148437499997] + ] + ], + [ + [ + [-72.98613281249999, -44.780078124999974], + [-73.22846679687498, -44.85996093749999], + [-73.39707031249998, -44.77431640624995], + [-73.44506835937497, -44.641015624999966], + [-73.20771484374993, -44.33496093749997], + [-72.7763671875, -44.50859374999999], + [-72.98613281249999, -44.780078124999974] + ] + ], + [ + [ + [-73.73535156249997, -44.39453125000003], + [-74.00205078125003, -44.59091796874998], + [-73.728173828125, -45.195898437500034], + [-74.016259765625, -45.344921875000026], + [-74.61777343749998, -44.64794921874996], + [-74.50180664062498, -44.47353515624995], + [-74.09721679687496, -44.38935546875004], + [-73.99492187499999, -44.140234375], + [-73.70322265624998, -44.27412109375001], + [-73.73535156249997, -44.39453125000003] + ] + ], + [ + [ + [-73.81064453125003, -43.827246093750006], + [-73.95566406249998, -43.921972656250034], + [-74.14296874999997, -43.872167968750006], + [-73.81064453125003, -43.827246093750006] + ] + ], + [ + [ + [-73.77338867187498, -43.3458984375], + [-74.114404296875, -43.35791015624996], + [-74.387353515625, -43.231640625], + [-74.03666992187496, -41.79550781249998], + [-73.52783203124997, -41.89628906249999], + [-73.42290039062499, -42.192871093750014], + [-73.47080078124998, -42.46630859375004], + [-73.78925781249993, -42.58574218750003], + [-73.43632812499996, -42.9365234375], + [-73.74965820312494, -43.15908203124995], + [-73.77338867187498, -43.3458984375] + ] + ], + [ + [ + [-78.80415039062501, -33.646484374999986], + [-78.98945312499993, -33.66171874999998], + [-78.87744140625003, -33.57519531250003], + [-78.80415039062501, -33.646484374999986] + ] + ], + [ + [ + [-109.27998046874994, -27.14042968749996], + [-109.434130859375, -27.171289062500023], + [-109.39047851562499, -27.068359375000014], + [-109.27998046874994, -27.14042968749996] + ] + ], + [ + [ + [-67.19487304687493, -22.821679687500037], + [-67.00878906249994, -23.00136718750005], + [-67.35620117187503, -24.033789062499963], + [-68.25029296875002, -24.391992187500023], + [-68.56201171875, -24.74736328125003], + [-68.38422851562495, -25.091894531249977], + [-68.59208984375002, -25.420019531250034], + [-68.41450195312498, -26.153710937500023], + [-68.59160156249999, -26.47041015624997], + [-68.31865234374999, -26.973242187500006], + [-68.59208984375002, -27.140039062499966], + [-68.84633789062494, -27.153710937499994], + [-69.17441406249998, -27.924707031250037], + [-69.65693359374995, -28.413574218749986], + [-69.82788085937497, -29.10322265624997], + [-70.02680664062501, -29.324023437500017], + [-69.95996093749997, -30.078320312500026], + [-69.84428710937493, -30.175], + [-69.95634765624996, -30.35820312500003], + [-70.15322265625, -30.360937499999963], + [-70.30908203124994, -31.02265625000004], + [-70.51958007812493, -31.1484375], + [-70.585205078125, -31.569433593749963], + [-70.25439453125, -31.957714843750026], + [-70.36376953125, -32.08349609374997], + [-70.02197265625, -32.88457031250002], + [-70.08486328125002, -33.20175781249998], + [-69.81962890624999, -33.28378906249999], + [-69.85244140625, -34.224316406250026], + [-70.05205078124999, -34.30078124999997], + [-70.39316406250003, -35.146875], + [-70.55517578125, -35.246875], + [-70.41572265625001, -35.52304687500002], + [-70.40478515625, -36.06171874999998], + [-71.05551757812498, -36.52373046874996], + [-71.19218750000002, -36.84365234375004], + [-71.16757812499998, -37.76230468749996], + [-70.858642578125, -38.60449218750003], + [-71.40156249999995, -38.93505859374996], + [-71.53945312499997, -39.60244140624995], + [-71.71992187499995, -39.63525390624997], + [-71.65976562499998, -40.02080078125], + [-71.81831054687493, -40.17666015624995], + [-71.70898437499997, -40.381738281249994], + [-71.93212890624994, -40.69169921874999], + [-71.91127929687497, -41.650390624999986], + [-71.75, -42.04677734375001], + [-72.10820312499993, -42.25185546874995], + [-72.14643554687498, -42.990039062499974], + [-71.750634765625, -43.237304687499986], + [-71.90498046875001, -43.34755859374998], + [-71.68007812500002, -43.92958984374998], + [-71.82001953124993, -44.38310546875], + [-71.21259765624998, -44.44121093750003], + [-71.15971679687496, -44.56025390625004], + [-71.26113281250002, -44.763085937499966], + [-72.06372070312503, -44.771875], + [-72.04169921874998, -44.90419921875004], + [-71.5962890625, -44.97919921875004], + [-71.34931640624995, -45.33193359374995], + [-71.74619140624998, -45.57890625], + [-71.63154296874998, -45.95371093749998], + [-71.87568359374998, -46.160546875], + [-71.69965820312501, -46.6513671875], + [-71.94023437499999, -46.83125], + [-71.90498046875001, -47.201660156250014], + [-72.34594726562497, -47.49267578124997], + [-72.517919921875, -47.87636718749998], + [-72.32832031250001, -48.11005859374998], + [-72.35473632812497, -48.36582031250005], + [-72.582861328125, -48.47539062499999], + [-72.65126953125, -48.84160156249998], + [-73.03364257812501, -49.014355468750004], + [-73.13525390625, -49.30068359374999], + [-73.46157226562497, -49.31386718750001], + [-73.55419921875, -49.463867187500014], + [-73.50126953124996, -50.125292968750024], + [-73.15292968749998, -50.73828125000003], + [-72.50981445312496, -50.607519531250034], + [-72.34023437499997, -50.68183593749999], + [-72.40766601562501, -51.54082031250002], + [-71.91865234374995, -51.98955078125004], + [-69.96025390624993, -52.00820312500002], + [-68.443359375, -52.35664062500004], + [-69.24101562499996, -52.20546874999997], + [-69.62031249999995, -52.46474609374995], + [-70.79511718749995, -52.76875], + [-70.99584960937497, -53.77929687499997], + [-71.29775390625002, -53.88339843750004], + [-72.1744140625, -53.632324218749964], + [-72.41289062500002, -53.35019531250004], + [-71.94169921874993, -53.23408203125001], + [-71.89169921874998, -53.523535156250006], + [-71.79145507812498, -53.48457031249997], + [-71.74052734374999, -53.232617187499976], + [-71.28896484375002, -53.03369140624995], + [-71.22714843750003, -52.810644531249984], + [-71.38774414062496, -52.76425781250004], + [-72.27802734374998, -53.13232421874997], + [-72.54892578125, -53.4607421875], + [-73.05273437499997, -53.24345703125005], + [-72.72768554687502, -52.7623046875], + [-72.453466796875, -52.814453124999964], + [-72.11757812499997, -52.65], + [-71.51127929687502, -52.60537109375], + [-72.22568359374998, -52.52099609374995], + [-72.43769531250001, -52.62578124999998], + [-72.71210937499995, -52.53554687499999], + [-73.12246093749997, -53.073925781249976], + [-73.64521484374998, -52.83701171875003], + [-73.2408203125, -52.707128906250034], + [-73.12392578125, -52.487988281249976], + [-73.24414062499997, -52.62402343749998], + [-73.58569335937503, -52.68574218750003], + [-74.01445312499999, -52.63935546875], + [-74.26494140624993, -52.1048828125], + [-73.83447265625, -52.23398437500001], + [-73.68432617187494, -52.07773437499998], + [-73.26044921874993, -52.157812500000034], + [-72.79501953124998, -51.94951171875005], + [-72.57084960937496, -52.200097656249945], + [-72.67705078125002, -52.38466796874998], + [-72.52333984374997, -52.255468750000034], + [-72.62460937499998, -51.94648437499997], + [-72.48964843750002, -51.76367187500003], + [-72.76123046875, -51.57324218749996], + [-73.16875, -51.45390624999998], + [-72.60004882812495, -51.79912109374997], + [-73.51816406250003, -52.04101562499996], + [-73.75263671874993, -51.795507812500034], + [-74.19667968749997, -51.68056640624997], + [-73.92978515624995, -51.61787109374999], + [-73.93950195312499, -51.26630859375005], + [-74.81474609374996, -51.06289062499999], + [-75.09467773437495, -50.68125], + [-74.68574218749995, -50.662011718749945], + [-74.77587890625003, -50.46992187499998], + [-74.64448242187498, -50.360937499999984], + [-74.365576171875, -50.487890625], + [-74.13940429687503, -50.81777343749997], + [-73.80654296875, -50.93837890625003], + [-73.654443359375, -50.49267578125], + [-73.97802734375003, -50.827050781249994], + [-74.18559570312493, -50.485351562500014], + [-73.95034179687497, -50.510546875], + [-74.62958984374998, -50.19404296875], + [-74.333740234375, -49.97460937499997], + [-73.95859374999998, -49.994726562499984], + [-74.32392578124995, -49.783398437500004], + [-74.29082031249996, -49.604101562499984], + [-73.83637695312493, -49.609375], + [-74.09443359374993, -49.42968749999998], + [-73.93496093749994, -49.02089843750001], + [-74.2212890625, -49.500585937500034], + [-74.36655273437503, -49.40048828124998], + [-74.34101562499998, -48.59570312499998], + [-74.00908203124996, -48.475], + [-74.47441406249999, -48.46396484374996], + [-74.58466796874998, -47.999023437500014], + [-73.39106445312498, -48.14589843750001], + [-73.60991210937499, -47.993945312500045], + [-73.71586914062499, -47.65546875000001], + [-73.94086914062498, -47.92939453125004], + [-74.22705078124994, -47.96894531250001], + [-74.654931640625, -47.702246093750034], + [-74.5337890625, -47.567675781249974], + [-74.24296874999999, -47.67929687499998], + [-74.13408203125002, -47.590820312499986], + [-74.48266601562497, -47.43046875], + [-74.15839843749998, -47.18251953125002], + [-74.31357421874998, -46.78818359374998], + [-74.45419921875003, -46.76679687499997], + [-74.51225585937496, -46.88515625000002], + [-75.00595703125, -46.74111328124998], + [-74.98417968750002, -46.51210937499995], + [-75.54033203124999, -46.69873046874996], + [-75.43037109374995, -46.93457031249996], + [-75.70639648437498, -46.70527343749997], + [-74.924462890625, -46.159667968750014], + [-75.06669921874993, -45.874902343749994], + [-74.15786132812497, -45.7671875], + [-74.122705078125, -45.49619140625002], + [-73.95717773437494, -45.40439453124998], + [-73.825, -45.446875], + [-74.01992187500002, -46.055859375], + [-74.39296875, -46.21738281250005], + [-73.96757812500002, -46.15410156250003], + [-73.87871093749993, -45.846875], + [-73.73525390624994, -45.81171875], + [-73.70815429687502, -46.070312500000014], + [-73.94863281249997, -46.533105468749966], + [-73.845361328125, -46.56601562500002], + [-73.59184570312493, -45.89912109375004], + [-73.73076171874999, -45.47998046875], + [-73.26621093749995, -45.346191406250014], + [-72.933837890625, -45.45234374999997], + [-73.44497070312497, -45.23818359374995], + [-73.36245117187502, -44.97822265625001], + [-72.73896484375001, -44.73417968750003], + [-72.680078125, -44.59394531249997], + [-72.66386718749999, -44.43642578124995], + [-73.26508789062498, -44.16865234375001], + [-73.22446289062498, -43.89794921875003], + [-73.06879882812495, -43.86201171874998], + [-72.99658203125, -43.63154296875001], + [-73.07597656250002, -43.323632812499994], + [-72.75800781249998, -43.039453125], + [-72.84804687500002, -42.66914062499997], + [-72.77392578125003, -42.505175781250045], + [-72.63183593750003, -42.509667968749994], + [-72.77324218749996, -42.257714843749994], + [-72.63105468749995, -42.199804687500006], + [-72.412353515625, -42.388183593750014], + [-72.49941406249997, -41.98085937499999], + [-72.82407226562503, -41.90878906249996], + [-72.36040039062499, -41.64912109375], + [-72.31826171875, -41.49902343749997], + [-72.54238281250002, -41.690625], + [-72.95283203124995, -41.51474609374998], + [-73.24179687499995, -41.78085937500002], + [-73.62402343750003, -41.77363281249997], + [-73.73515625000002, -41.74248046875002], + [-73.62392578125, -41.581347656250045], + [-73.81074218749995, -41.51748046875001], + [-73.96586914062493, -41.118261718750034], + [-73.67099609375, -39.96318359374999], + [-73.41040039062503, -39.78916015624998], + [-73.22646484375002, -39.22441406250003], + [-73.52021484375001, -38.509375], + [-73.46479492187498, -38.04033203125003], + [-73.66181640624998, -37.69853515625003], + [-73.66240234375002, -37.341015625000026], + [-73.60166015624998, -37.18847656250003], + [-73.21596679687502, -37.16689453124998], + [-73.11806640624997, -36.68837890625002], + [-72.58735351562493, -35.759667968749994], + [-72.62392578125002, -35.5857421875], + [-72.22377929687494, -35.096191406250014], + [-72.00283203124997, -34.16533203125], + [-71.66435546875002, -33.65263671875], + [-71.74296875, -33.09511718750001], + [-71.45224609374998, -32.65957031250001], + [-71.70893554687495, -30.62802734375002], + [-71.66948242187499, -30.33037109374996], + [-71.40039062499997, -30.142968749999966], + [-71.31572265624996, -29.649707031250017], + [-71.51923828124993, -28.926464843750026], + [-71.30673828124998, -28.672460937499963], + [-71.08652343749998, -27.814453124999957], + [-70.92578125, -27.588671874999974], + [-70.64658203124998, -26.329394531250017], + [-70.71372070312498, -25.78417968749997], + [-70.44536132812502, -25.17265624999999], + [-70.57412109374994, -24.644335937500003], + [-70.39233398437494, -23.565917968749957], + [-70.59335937499995, -23.255468750000034], + [-70.56318359374995, -23.057031250000023], + [-70.33168945312494, -22.848632812500014], + [-70.08002929687501, -21.356835937500037], + [-70.19702148437494, -20.725390625], + [-70.15742187499995, -19.70585937500003], + [-70.41826171874999, -18.345605468750023], + [-69.92636718749998, -18.206054687500014], + [-69.80258789062498, -17.990234375000014], + [-69.85209960937493, -17.70380859375001], + [-69.68476562499995, -17.649804687500023], + [-69.58642578125, -17.57324218749997], + [-69.51093749999998, -17.50605468749997], + [-69.31337890624997, -17.943164062500017], + [-69.28232421875003, -17.96484375], + [-69.09394531249993, -18.05048828125004], + [-69.14545898437495, -18.14404296875], + [-69.09228515624994, -18.28242187500004], + [-69.02680664062493, -18.65625], + [-68.97885742187503, -18.81298828125003], + [-68.96831054687502, -18.967968749999983], + [-68.85795898437499, -19.09335937500005], + [-68.62055664062495, -19.29667968749999], + [-68.54785156249997, -19.341113281249974], + [-68.49199218749996, -19.381933593750034], + [-68.47016601562495, -19.409960937499974], + [-68.46289062499997, -19.43281250000001], + [-68.57529296874998, -19.56015625000002], + [-68.69829101562499, -19.721093750000037], + [-68.69619140625, -19.74072265625003], + [-68.57827148437494, -19.856542968750006], + [-68.559375, -19.902343750000014], + [-68.56069335937502, -19.96708984374996], + [-68.75932617187499, -20.115527343750003], + [-68.74516601562493, -20.45859375], + [-68.48432617187498, -20.628417968749957], + [-68.55825195312497, -20.90195312499999], + [-68.197021484375, -21.30029296874997], + [-68.18642578124997, -21.618554687499966], + [-67.88173828124997, -22.493359375000026], + [-67.87944335937496, -22.822949218750026], + [-67.57993164062495, -22.89169921874999], + [-67.36225585937493, -22.85517578125001], + [-67.19487304687493, -22.821679687500037] + ] + ] + ] + }, + "properties": { "name": "Chile", "childNum": 26 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [110.88876953125006, 19.99194335937497], + [111.01367187500003, 19.65546875000001], + [110.64091796875002, 19.291210937499955], + [110.45126953125012, 18.747949218750023], + [110.06738281249997, 18.447558593750045], + [109.51933593750007, 18.21826171875003], + [108.7015625, 18.535253906250034], + [108.66552734375003, 19.304101562499994], + [109.27666015625002, 19.761132812500023], + [109.17744140625004, 19.768457031250023], + [109.26347656250007, 19.882666015625006], + [110.1715820312501, 20.053710937500057], + [110.58818359375002, 19.976367187500017], + [110.6517578125, 20.137744140625017], + [110.88876953125006, 19.99194335937497] + ] + ], + [ + [ + [110.38515625000005, 21.093164062499966], + [110.52158203125006, 21.083105468750063], + [110.50390625000003, 20.96772460937501], + [110.28095703125004, 21.001171874999983], + [110.38515625000005, 21.093164062499966] + ] + ], + [ + [ + [112.64375, 21.63964843750003], + [112.525, 21.62304687500003], + [112.64765625000004, 21.710253906250017], + [112.64375, 21.63964843750003] + ] + ], + [ + [ + [112.79023437500004, 21.601855468750045], + [112.78203125000007, 21.772265625000045], + [112.86259765625002, 21.75263671875004], + [112.79023437500004, 21.601855468750045] + ] + ], + [ + [ + [118.1830078125, 24.496289062499983], + [118.0905273437501, 24.446142578125063], + [118.10380859375002, 24.552343750000034], + [118.1830078125, 24.496289062499983] + ] + ], + [ + [ + [119.82089843750006, 25.45698242187504], + [119.70029296875012, 25.432714843750063], + [119.72255859375005, 25.638818359375023], + [119.83837890625003, 25.591064453125], + [119.82089843750006, 25.45698242187504] + ] + ], + [ + [ + [121.2513671875, 28.086425781250057], + [121.13154296875004, 28.062597656250006], + [121.20546875, 28.204394531250017], + [121.2513671875, 28.086425781250057] + ] + ], + [ + [ + [122.29589843750003, 29.96342773437499], + [122.02402343750012, 30.01333007812505], + [121.96943359375004, 30.143115234375017], + [122.28447265625007, 30.068017578124994], + [122.29589843750003, 29.96342773437499] + ] + ], + [ + [ + [121.86269531250005, 31.492285156249977], + [121.519921875, 31.549609375000017], + [121.2111328125001, 31.80537109375001], + [121.86269531250005, 31.492285156249977] + ] + ], + [ + [ + [130.52695312500012, 42.535400390625], + [130.24667968750012, 42.744824218749955], + [130.24033203125006, 42.891796874999955], + [129.89824218750002, 42.998144531250034], + [129.69785156250012, 42.448144531249994], + [129.3136718750001, 42.41357421874997], + [128.92343750000006, 42.038232421874966], + [128.04521484375007, 41.9875], + [128.28925781250004, 41.60742187500006], + [128.14941406249997, 41.38774414062496], + [127.17968750000003, 41.531347656250006], + [126.95478515625004, 41.76948242187501], + [126.74306640625, 41.724853515625], + [125.98906250000002, 40.904638671875034], + [124.8893554687501, 40.459814453125006], + [124.36210937500002, 40.004052734374994], + [124.10576171875002, 39.84101562499998], + [123.65087890625003, 39.881591796875], + [122.8400390625001, 39.600830078125], + [121.98232421875, 39.05317382812498], + [121.67724609374997, 39.00341796875006], + [121.64990234375003, 38.865087890625034], + [121.16357421874997, 38.73164062500001], + [121.10673828125002, 38.920800781249994], + [121.6798828125001, 39.10869140625002], + [121.62763671875004, 39.22016601562498], + [121.81845703125006, 39.38652343750002], + [121.27548828125006, 39.38476562500003], + [121.26748046875, 39.544677734375], + [121.51757812499997, 39.638964843750045], + [121.51738281250002, 39.84482421875006], + [121.8009765625001, 39.950537109375006], + [122.27500000000012, 40.541845703125034], + [121.83486328125005, 40.97426757812502], + [121.72929687500002, 40.84614257812504], + [121.1745117187501, 40.901269531249994], + [120.47910156250006, 40.23095703125003], + [119.39111328125003, 39.75249023437499], + [118.976953125, 39.182568359374955], + [118.29785156249997, 39.067089843749955], + [118.04091796875, 39.22675781249998], + [117.86572265625003, 39.191259765625034], + [117.61669921875003, 38.852880859375034], + [117.5578125000001, 38.625146484374994], + [117.76669921875012, 38.311669921874994], + [118.01494140625007, 38.18339843749996], + [118.94003906250006, 38.04277343750002], + [119.08916015625007, 37.70073242187496], + [118.95263671875003, 37.33115234374998], + [119.28740234375002, 37.138281250000034], + [119.76054687500007, 37.15507812499999], + [120.31152343750003, 37.62270507812505], + [120.2572265625, 37.67900390624996], + [120.75, 37.83393554687501], + [121.64023437500012, 37.46035156250002], + [122.05664062500003, 37.528906250000034], + [122.66699218750003, 37.40283203125003], + [122.4466796875, 37.06811523437503], + [122.51972656250004, 36.94682617187502], + [122.34091796875012, 36.83222656250004], + [121.93271484375006, 36.95947265625003], + [121.05380859375006, 36.61137695312499], + [120.81083984375007, 36.6328125], + [120.89580078125007, 36.44414062500002], + [120.71152343750006, 36.41328125000004], + [120.6378906250001, 36.129931640625045], + [120.39306640625003, 36.053857421874994], + [120.32773437500006, 36.228173828124994], + [120.18330078125004, 36.20244140624999], + [120.094140625, 36.11889648437503], + [120.28476562500006, 35.98442382812499], + [119.42968749999997, 35.301416015624994], + [119.16533203125002, 34.84882812499998], + [119.20097656250002, 34.748437499999966], + [120.26669921875006, 34.274023437500034], + [120.87109374999997, 33.016503906249994], + [120.8532226562501, 32.66137695312503], + [121.34169921875005, 32.42504882812503], + [121.40390625000006, 32.20625], + [121.85634765625, 31.816455078125045], + [121.86630859375006, 31.703564453124955], + [121.68085937500004, 31.71215820312503], + [121.351953125, 31.85878906250005], + [120.97353515625, 31.86938476562497], + [120.52011718750006, 32.10585937500002], + [120.03593750000002, 31.93627929687503], + [120.7155273437501, 31.983740234375006], + [120.7877929687501, 31.81977539062501], + [121.66064453124997, 31.319726562499994], + [121.87792968750003, 30.91699218750003], + [121.41894531249997, 30.789794921875057], + [120.8214843750001, 30.354638671875023], + [120.44980468750006, 30.38784179687505], + [120.19462890625002, 30.241308593750034], + [120.49453125, 30.303076171875006], + [120.63339843750006, 30.133154296875034], + [121.25800781250004, 30.30410156250005], + [121.67792968750004, 29.979101562500006], + [122.08291015625005, 29.870361328125057], + [121.50625, 29.484570312499955], + [121.94121093750002, 29.605908203124983], + [121.91777343750007, 29.13500976562497], + [121.71748046875004, 29.25634765625], + [121.48710937500007, 29.193164062500017], + [121.67968749999997, 28.953125], + [121.54003906250003, 28.931884765625], + [121.6625, 28.851416015625034], + [121.47519531250006, 28.64140625], + [121.60996093750006, 28.29213867187505], + [121.27226562500002, 28.222119140624983], + [121.14570312500004, 28.32666015624997], + [120.95859375000006, 28.037011718750023], + [120.74765625000006, 28.00996093750001], + [120.83300781249997, 27.891455078125034], + [120.58750000000012, 27.580761718749983], + [120.60751953125012, 27.41240234374996], + [120.2787109375, 27.097070312500023], + [120.08671875000007, 26.67158203125004], + [119.88222656250005, 26.610449218750006], + [119.82421874999997, 26.84638671875001], + [119.71044921874997, 26.728662109375023], + [119.58818359375002, 26.784960937500045], + [119.8810546875001, 26.33417968750004], + [119.46308593750004, 26.05468750000003], + [119.13945312500007, 26.12177734375001], + [119.33203124999997, 25.94873046875003], + [119.61875000000012, 26.003564453124994], + [119.53945312500005, 25.59125976562504], + [119.6224609375, 25.391162109375017], + [119.180078125, 25.449804687499977], + [119.285546875, 25.232226562500074], + [118.97753906249997, 25.209277343750017], + [118.90908203125005, 24.92890625000001], + [118.63691406250004, 24.835546874999977], + [118.65703125000002, 24.621435546874977], + [118.0871093750001, 24.627001953125045], + [118.00595703125006, 24.48198242187499], + [117.84267578125005, 24.47431640625004], + [118.0560546875, 24.24609374999997], + [117.62822265625002, 23.836718750000074], + [117.46640625000012, 23.84057617187497], + [117.36767578124997, 23.58862304687497], + [117.29082031250007, 23.71435546875], + [117.08251953124997, 23.578759765625023], + [116.91064453124997, 23.646679687499983], + [116.86093750000006, 23.453076171874983], + [116.62939453124997, 23.353857421875034], + [116.69882812500006, 23.277783203124983], + [116.53828125000004, 23.17968749999997], + [116.47070312499997, 22.945898437500034], + [116.25185546875005, 22.981347656249994], + [115.85214843750006, 22.801562500000045], + [115.64042968750002, 22.853417968750023], + [115.49833984375002, 22.718847656250063], + [115.19580078125003, 22.81728515625005], + [114.85380859375007, 22.616796875000063], + [114.65166015625002, 22.755273437500023], + [114.55419921874997, 22.52890625], + [114.26601562500005, 22.540966796874983], + [114.01542968750007, 22.51191406250001], + [113.61962890624997, 22.861425781249977], + [113.6205078125, 23.12749023437499], + [113.51972656250004, 23.102099609375074], + [113.33105468749997, 22.912011718749966], + [113.55302734375002, 22.594042968750045], + [113.54912109375002, 22.225195312500034], + [113.14902343750012, 22.075], + [113.08876953125, 22.207958984374983], + [112.95390625000007, 21.907324218750034], + [112.80859374999997, 21.944628906250074], + [112.58632812500005, 21.77685546875], + [112.35966796875007, 21.97802734375003], + [112.30498046875002, 21.74169921875003], + [111.94394531250012, 21.84965820312499], + [111.60273437500004, 21.55908203125003], + [111.01689453125007, 21.51171874999997], + [110.56718750000002, 21.21406250000001], + [110.41093750000007, 21.33813476562497], + [110.15400390625004, 20.944628906250017], + [110.36542968750004, 20.837597656249955], + [110.31308593750012, 20.67167968749999], + [110.51152343750007, 20.51826171875001], + [110.34472656249997, 20.29482421875005], + [109.88251953125004, 20.364062500000045], + [109.96835937500006, 20.448144531250023], + [109.66259765625003, 20.91689453125005], + [109.68125000000012, 21.13164062499999], + [109.93076171875012, 21.480566406250034], + [109.6869140625, 21.52460937500004], + [109.56640624999997, 21.690576171874994], + [109.54404296875012, 21.537939453125006], + [109.14863281250004, 21.425537109375], + [109.1017578125001, 21.59047851562505], + [108.77167968750004, 21.63046875], + [108.59375, 21.901025390624994], + [108.47988281250005, 21.904638671875006], + [108.50214843750004, 21.633447265624994], + [108.32480468750006, 21.693505859374994], + [108.24628906250004, 21.55839843749999], + [107.97265624999997, 21.507958984375023], + [107.75927734374997, 21.655029296875057], + [107.35117187500012, 21.60888671874997], + [106.97099609375002, 21.923925781250034], + [106.66357421875003, 21.97890625000005], + [106.55039062500006, 22.501367187499994], + [106.78027343749997, 22.778906250000034], + [106.54179687500007, 22.908349609375023], + [106.2790039062501, 22.857470703125045], + [106.14843749999997, 22.970068359375006], + [105.8429687500001, 22.922802734374955], + [105.27539062500003, 23.34521484375003], + [104.86474609375003, 23.136376953125023], + [104.68730468750002, 22.822216796874983], + [104.37177734375004, 22.704052734374983], + [104.14306640624997, 22.800146484375006], + [103.94150390625006, 22.540087890625045], + [103.62021484375006, 22.782031250000045], + [103.49296875000007, 22.587988281250034], + [103.32666015625003, 22.769775390625057], + [102.98193359374997, 22.4482421875], + [102.47089843750004, 22.75092773437501], + [102.40644531250004, 22.70800781249997], + [102.2370117187501, 22.466015624999983], + [102.1759765625001, 22.414648437500006], + [102.12744140624997, 22.379199218750045], + [101.84179687500003, 22.38847656249999], + [101.75996093750004, 22.490332031250034], + [101.73876953124997, 22.495263671874994], + [101.70751953125003, 22.486572265625], + [101.67148437500006, 22.462304687500023], + [101.64619140625004, 22.405419921874966], + [101.61992187500002, 22.32744140624999], + [101.56787109374997, 22.27636718749997], + [101.52451171875006, 22.25366210937497], + [101.7365234375001, 21.826513671874977], + [101.74394531250007, 21.77797851562505], + [101.74726562500004, 21.605761718750045], + [101.72294921875007, 21.31494140625003], + [101.80058593750007, 21.212597656249983], + [101.78349609375007, 21.204150390625017], + [101.728125, 21.156396484374994], + [101.7047851562501, 21.15014648437503], + [101.54238281250005, 21.23427734375005], + [101.2814453125001, 21.184130859375045], + [101.24785156250007, 21.197314453125045], + [101.22441406250002, 21.223730468750034], + [101.21181640625, 21.278222656250023], + [101.2199218750001, 21.34243164062505], + [101.17539062500006, 21.407519531250074], + [101.19667968750005, 21.522070312500063], + [101.1388671875001, 21.567480468749977], + [101.07978515625004, 21.75585937499997], + [100.60458984375012, 21.471777343750006], + [100.14765625000004, 21.480517578125017], + [99.94072265625007, 21.75874023437504], + [99.9176757812501, 22.02802734375001], + [99.19296875000006, 22.12597656249997], + [99.50712890625002, 22.959130859374994], + [99.41806640625006, 23.069238281250023], + [98.86376953125003, 23.191259765625034], + [98.8322265625001, 23.624365234374977], + [98.67675781250003, 23.905078125000045], + [98.83505859375006, 24.121191406250034], + [98.2125, 24.110644531250017], + [97.56455078125012, 23.911035156250023], + [97.7082031250001, 24.228759765625], + [97.53144531250004, 24.49169921875003], + [97.58330078125002, 24.77480468750005], + [97.73789062500006, 24.869873046875057], + [97.8195312500001, 25.251855468749994], + [98.01074218749997, 25.292529296875017], + [98.14287109375007, 25.571093750000017], + [98.33378906250007, 25.586767578125006], + [98.65625, 25.86357421874999], + [98.56406250000006, 26.072412109374994], + [98.68554687499997, 26.189355468750023], + [98.7384765625001, 26.785742187500006], + [98.65117187500007, 27.572460937499983], + [98.4525390625, 27.6572265625], + [98.29882812499997, 27.550097656250045], + [98.06162109375012, 28.185888671874977], + [97.59921875000006, 28.51704101562504], + [97.53789062500002, 28.510205078124983], + [97.43144531250002, 28.353906250000023], + [97.35644531249997, 28.254492187500006], + [97.32158929493812, 28.217097107438057], + [97.3027336276825, 28.08710519614969], + [97.34382779482424, 27.982305259167095], + [97.04929369561631, 27.76000444316393], + [96.96494598325154, 27.699301564540924], + [96.19423412199573, 28.04146177926422], + [95.73730002295082, 28.117613231051525], + [95.11298892962586, 27.748338353239472], + [94.07167814294401, 27.588707868507477], + [93.61247595136224, 27.323800298697016], + [93.30681393470121, 26.786120363519142], + [92.74319481218781, 26.833531317384058], + [92.04974640832253, 26.874866505386724], + [92.07342257335648, 26.915311275859864], + [92.06813426293174, 26.9752569185349], + [92.02985139563152, 27.03987087331446], + [91.99856592104459, 27.079255842602592], + [91.99177981607339, 27.100605151743654], + [92.0025114452454, 27.147290053160265], + [92.03101585307499, 27.214271359861193], + [92.08387457645458, 27.29090135496722], + [92.04520857607581, 27.364442429033787], + [91.99069061380867, 27.450181624174498], + [91.95099838734396, 27.45828799115413], + [91.85276579410389, 27.438593286730903], + [91.74366351462741, 27.442853010105477], + [91.59505352446729, 27.557262710287986], + [91.63193359375012, 27.759960937499983], + [91.64189453125002, 27.923242187500023], + [91.36259958579089, 28.02438066407592], + [91.27304687500012, 28.078369140625], + [91.22587890625007, 28.071240234374983], + [91.07773437500012, 27.974462890624977], + [91.02080078125002, 27.970068359374977], + [90.71572265625, 28.071728515624983], + [90.63007812500004, 28.078564453124955], + [90.47734375000007, 28.07084960937499], + [90.3527343750001, 28.080224609375023], + [90.33310546875012, 28.093994140625], + [90.36298828125004, 28.21650390625001], + [90.34824218750006, 28.24394531249999], + [90.22080078125006, 28.27773437500005], + [90.10449218749997, 28.302050781250017], + [89.98105468750006, 28.311181640625023], + [89.8978515625, 28.29414062500001], + [89.81689453125003, 28.25629882812501], + [89.74980468750002, 28.18818359375001], + [89.65273437500005, 28.158300781250034], + [89.53691406250007, 28.10742187499997], + [89.4806640625001, 28.059960937499994], + [88.89140625000002, 27.316064453124966], + [88.83251953125003, 27.36284179687499], + [88.7648437500001, 27.429882812499983], + [88.74902343749997, 27.521875], + [88.82988281250002, 27.76738281249999], + [88.84882812500004, 27.86865234375], + [88.80371093750003, 28.006933593750034], + [88.57792968750002, 28.093359375000034], + [88.42597656250004, 28.01166992187501], + [88.27519531250007, 27.968847656250006], + [88.14111328125003, 27.94892578125001], + [88.10898437500006, 27.933007812499966], + [88.10976562500005, 27.870605468750057], + [87.8607421875, 27.886083984375006], + [87.62255859374997, 27.81518554687503], + [87.29072265625004, 27.821923828124994], + [87.14140625000002, 27.838330078124955], + [87.02011718750006, 27.928662109374983], + [86.9337890625001, 27.96845703125001], + [86.84238281250012, 27.99916992187505], + [86.750390625, 28.022070312500006], + [86.71962890625005, 28.070654296875034], + [86.69052734375006, 28.09492187500001], + [86.61445312500004, 28.10302734374997], + [86.55449218750007, 28.08520507812497], + [86.51689453125007, 27.963525390624966], + [86.40869140625003, 27.928662109374983], + [86.32861328124997, 27.95952148437496], + [86.2179687500001, 28.022070312500006], + [86.13701171875002, 28.114355468750063], + [86.07871093750006, 28.08359375], + [86.0641601562501, 27.934716796874966], + [85.99453125000005, 27.910400390625], + [85.95410156249997, 27.92822265624997], + [85.92167968750002, 27.989697265624983], + [85.84023437500005, 28.135351562499977], + [85.75947265625004, 28.220654296874955], + [85.67832031250012, 28.277441406249977], + [85.41064453125003, 28.27602539062505], + [85.21210937500004, 28.292626953124966], + [85.1224609375, 28.315966796875017], + [85.08857421875004, 28.37226562500001], + [85.121484375, 28.484277343750023], + [85.16015624999997, 28.571875], + [85.15908203125, 28.592236328124983], + [85.1263671875, 28.602636718750063], + [85.06914062500007, 28.60966796874999], + [84.85507812500006, 28.553613281250023], + [84.796875, 28.560205078125023], + [84.2287109375001, 28.911767578124966], + [84.17558593750002, 29.036376953125057], + [84.12783203125005, 29.15629882812496], + [84.10136718750002, 29.21997070312497], + [84.02197265624997, 29.25385742187504], + [83.93593750000005, 29.27949218750001], + [83.58349609375003, 29.18359375000003], + [83.15546875000004, 29.612646484375034], + [82.22070312500003, 30.063867187500023], + [82.04335937500005, 30.326757812500034], + [81.8548828125, 30.362402343750006], + [81.64189453125007, 30.3875], + [81.4171875000001, 30.33759765625001], + [81.25507812500004, 30.09331054687499], + [81.17714843750005, 30.039892578125034], + [80.98544921875006, 30.23710937499999], + [80.87353515625003, 30.290576171875045], + [80.19121093750002, 30.56840820312496], + [80.20712890625006, 30.683740234375023], + [79.92451171875004, 30.888769531250034], + [79.66425781250004, 30.96523437499999], + [79.38847656250007, 31.064208984375], + [79.10712890625004, 31.402636718750017], + [78.74355468750005, 31.323779296875017], + [78.7550781250001, 31.55029296875], + [78.69345703125006, 31.740380859374994], + [78.72558593750003, 31.983789062500023], + [78.49589843750002, 32.21577148437504], + [78.4552734375001, 32.30034179687502], + [78.41748046874997, 32.466699218749994], + [78.38964843749997, 32.51987304687498], + [78.73671875, 32.55839843750002], + [78.75351562500012, 32.49926757812506], + [78.91894531249997, 32.35820312500002], + [79.16992187500003, 32.497216796874994], + [79.14550781250003, 33.00146484375006], + [79.10283203125007, 33.05253906249996], + [79.13515625000005, 33.17192382812496], + [79.1125, 33.22626953125001], + [78.94843750000004, 33.346533203125006], + [78.86503906250002, 33.43110351562501], + [78.78378906250006, 33.80878906250004], + [78.72666015625006, 34.013378906249955], + [78.97060546875, 34.22822265625004], + [78.93642578125, 34.35195312500002], + [78.86484375000006, 34.39033203125001], + [78.32695312500007, 34.60639648437498], + [78.15849609375002, 34.94648437499998], + [78.07578125000006, 35.13491210937502], + [78.0426757812501, 35.47978515625002], + [77.79941406250006, 35.49589843750002], + [77.44648437500004, 35.47558593750006], + [77.29482421875005, 35.508154296875034], + [77.09003906250004, 35.55205078124999], + [76.87890625000003, 35.61328125000003], + [76.76689453125002, 35.661718750000034], + [76.72753906250003, 35.67866210937504], + [76.63183593749997, 35.729394531249966], + [76.56347656249997, 35.77299804687499], + [76.55126953124997, 35.887060546875034], + [76.50205078125006, 35.87822265625002], + [76.38574218750003, 35.837158203125], + [76.25166015625004, 35.8109375], + [76.17783203125012, 35.810546875], + [76.14785156250005, 35.82900390625002], + [76.07089843750006, 35.983007812500034], + [75.91230468750004, 36.048974609374994], + [75.97441406250007, 36.38242187500006], + [75.9518554687501, 36.458105468750034], + [75.9330078125, 36.52158203124998], + [75.840234375, 36.64970703124999], + [75.7721679687501, 36.694921875000034], + [75.6671875000001, 36.741992187500045], + [75.57373046874997, 36.75932617187502], + [75.46025390625002, 36.725048828124955], + [75.42421875000005, 36.73823242187498], + [75.37685546875, 36.88369140625005], + [75.34667968749997, 36.913476562499966], + [75.05390625000004, 36.98715820312498], + [74.94912109375, 36.96835937500006], + [74.88925781250006, 36.95244140625002], + [74.69218750000007, 37.035742187500006], + [74.60058593749997, 37.03666992187502], + [74.54140625, 37.02216796875001], + [74.52646484375006, 37.03066406250005], + [74.49794921875, 37.057226562500034], + [74.37617187500004, 37.13735351562502], + [74.37216796875006, 37.15771484375], + [74.558984375, 37.23662109374999], + [74.66894531250003, 37.266699218750006], + [74.72666015625006, 37.29072265625001], + [74.7389648437501, 37.28564453125003], + [74.76738281250002, 37.249169921874966], + [74.840234375, 37.22504882812504], + [74.89130859375004, 37.231640624999955], + [75.11875, 37.38569335937498], + [74.8942382812501, 37.60141601562498], + [74.81230468750002, 38.46030273437498], + [74.27744140625, 38.659765625000034], + [74.02558593750004, 38.53984375000002], + [73.80166015625, 38.60688476562501], + [73.69609375000007, 38.85429687499996], + [73.8052734375, 38.968652343749994], + [73.60732421875, 39.229199218749955], + [73.63632812500006, 39.396679687499955], + [73.63164062500007, 39.44887695312502], + [73.82294921875004, 39.48896484375004], + [73.90712890625, 39.578515624999966], + [73.9146484375, 39.60649414062499], + [73.88251953125004, 39.71455078124998], + [73.83974609375005, 39.76284179687505], + [73.8353515625, 39.800146484375006], + [73.85625, 39.828662109375045], + [73.88457031250002, 39.87792968750006], + [73.93876953125002, 39.97880859374999], + [73.99160156250005, 40.04311523437502], + [74.83046875, 40.32851562499999], + [74.80126953124997, 40.428515625000045], + [74.83515625000004, 40.482617187499955], + [74.865625, 40.493505859375034], + [75.0044921875, 40.44951171874996], + [75.11132812499997, 40.4541015625], + [75.24101562500002, 40.48027343750002], + [75.52080078125002, 40.627539062500006], + [75.55556640625, 40.625195312499955], + [75.6771484375, 40.305810546874994], + [75.87197265625, 40.30322265625], + [76.25830078124997, 40.43076171875006], + [76.3185546875001, 40.352246093749955], + [76.39638671875005, 40.389794921874966], + [76.4801757812501, 40.44951171874996], + [76.57792968750002, 40.577880859375], + [76.62216796875006, 40.66235351562497], + [76.6398437500001, 40.74223632812499], + [76.66113281249997, 40.77963867187498], + [76.70839843750005, 40.818115234375], + [76.82402343750002, 40.982324218749966], + [76.90771484374997, 41.02416992187497], + [76.98662109375002, 41.039160156250006], + [77.58173828125004, 40.99277343750006], + [77.71933593750012, 41.024316406249994], + [77.81523437500002, 41.05561523437498], + [77.9564453125, 41.05068359375005], + [78.1234375, 41.07563476562498], + [78.34628906250012, 41.28144531249998], + [78.36240234375012, 41.37163085937496], + [78.44287109374997, 41.41752929687499], + [78.742578125, 41.56005859375], + [79.29355468750006, 41.78281249999998], + [79.76611328124997, 41.89887695312501], + [79.84042968750012, 41.99575195312502], + [79.90966796875003, 42.014990234375034], + [80.21621093750005, 42.03242187500004], + [80.23515625000007, 42.04345703124997], + [80.24619140625012, 42.05981445312503], + [80.209375, 42.190039062500006], + [80.20224609375012, 42.73447265624998], + [80.53896484375005, 42.873486328124955], + [80.39023437500006, 43.043115234374966], + [80.78574218750006, 43.16157226562504], + [80.35527343750002, 44.09726562500006], + [80.48154296875006, 44.71464843749999], + [79.871875, 44.88378906249997], + [80.05917968750012, 45.006445312500006], + [81.69199218750012, 45.34936523437497], + [81.94492187500006, 45.16083984375001], + [82.26660156249997, 45.21909179687498], + [82.52148437500003, 45.12548828125], + [82.61162109375007, 45.424267578124955], + [82.31523437500002, 45.59492187499998], + [83.02949218750004, 47.18593750000002], + [84.016015625, 46.97050781250002], + [84.66660156250006, 46.97236328125004], + [84.78613281249997, 46.83071289062505], + [85.484765625, 47.06352539062496], + [85.65664062500005, 47.254638671875], + [85.52597656250006, 47.915625], + [85.7494140625, 48.38505859374999], + [86.54941406250012, 48.52861328125002], + [86.8083007812501, 49.04970703125002], + [87.32285156250012, 49.085791015625006], + [87.41669921875004, 49.07661132812501], + [87.5158203125001, 49.122412109375006], + [87.7625, 49.16582031249996], + [87.81425781250002, 49.162304687499955], + [87.87216796875012, 49.000146484374966], + [87.74316406250003, 48.88164062499999], + [87.83183593750007, 48.79165039062505], + [88.02792968750006, 48.735595703125], + [88.06005859375003, 48.707177734374966], + [87.9796875000001, 48.55512695312498], + [88.30996093750005, 48.47207031250002], + [88.41396484375, 48.403417968750006], + [88.51708984374997, 48.384472656249955], + [88.56679687500005, 48.31743164062496], + [88.57597656250007, 48.220166015624955], + [88.68183593750004, 48.170556640624994], + [88.83828125000005, 48.101708984374994], + [88.91777343750007, 48.089013671874966], + [89.04765625000007, 48.002539062500034], + [89.47919921875004, 48.02905273437503], + [89.5609375, 48.00395507812496], + [89.778125, 47.82700195312498], + [89.83134765625002, 47.82329101562502], + [89.91044921875007, 47.844335937500034], + [89.95869140625004, 47.88632812499998], + [90.02792968750012, 47.877685546875], + [90.1032226562501, 47.74541015624996], + [90.19101562500012, 47.70209960937501], + [90.31328125000007, 47.67617187499999], + [90.33066406250006, 47.655175781249966], + [90.42519531250005, 47.50410156250001], + [90.49619140625012, 47.28515625], + [90.64335937500007, 47.10029296874998], + [90.71552734375004, 47.00385742187498], + [90.7990234375001, 46.98515624999999], + [90.86992187500002, 46.95449218750005], + [90.91054687500005, 46.88325195312501], + [90.9857421875, 46.7490234375], + [90.9115234375, 46.270654296874994], + [90.94755859375002, 46.17729492187499], + [90.99677734375004, 46.10498046875], + [91.00175781250007, 46.03579101562502], + [90.6618164062501, 45.525244140625006], + [90.87724609375002, 45.19609375000002], + [91.05, 45.217431640624994], + [91.584375, 45.07651367187498], + [92.42382812499997, 45.008935546874994], + [92.57890625000002, 45.01098632812506], + [92.78789062500007, 45.035742187500034], + [93.51621093750012, 44.944482421874994], + [94.71201171875012, 44.35083007812503], + [95.35029296875004, 44.27807617187503], + [95.32558593750005, 44.03935546874999], + [95.52558593750004, 43.953955078125006], + [95.85957031250004, 43.27597656249998], + [96.38544921875004, 42.72036132812502], + [97.20566406250012, 42.78979492187506], + [99.46787109375012, 42.568212890625034], + [99.98378906250005, 42.67734375000006], + [100.08632812500005, 42.67075195312506], + [100.51904296875003, 42.61679687499998], + [101.09199218750004, 42.55131835937496], + [101.49531250000004, 42.53876953124998], + [101.57910156249997, 42.52353515624998], + [101.65996093750002, 42.50004882812499], + [101.97294921875002, 42.21586914062502], + [102.15664062500005, 42.158105468749966], + [102.57519531249997, 42.09208984375002], + [103.07285156250006, 42.00595703125006], + [103.7111328125001, 41.75131835937506], + [103.99726562500004, 41.796972656250034], + [104.30517578124997, 41.84614257812501], + [104.49824218750004, 41.87700195312499], + [104.49824218750004, 41.65869140625], + [104.86035156250003, 41.64375], + [104.98203125000012, 41.59550781250002], + [105.05058593750002, 41.61591796875001], + [105.1154296875001, 41.66328124999998], + [105.19707031250002, 41.738037109375], + [105.31435546875005, 41.77089843750005], + [105.86757812500005, 41.993994140625034], + [106.77001953125003, 42.28872070312502], + [108.17119140625002, 42.44731445312502], + [108.68730468750002, 42.416113281250034], + [109.33984374999997, 42.43837890625005], + [109.44316406250002, 42.455957031249994], + [110.40039062499997, 42.77368164062497], + [111.00722656250005, 43.34140624999998], + [111.878125, 43.68017578125], + [111.93173828125012, 43.81494140625], + [111.40224609375005, 44.367285156250006], + [111.89804687500006, 45.064062500000034], + [112.03261718750005, 45.08164062500006], + [112.11289062500006, 45.06293945312498], + [112.41132812500004, 45.05820312499998], + [112.49931640625002, 45.01093750000004], + [112.59677734375006, 44.917675781249955], + [112.7067382812501, 44.883447265624994], + [113.04941406250006, 44.81035156250002], + [113.3009765625001, 44.79165039062502], + [113.50791015625006, 44.76235351562502], + [113.58701171875006, 44.745703125], + [113.65263671875002, 44.76347656249999], + [113.87705078125012, 44.89619140625001], + [114.03027343749997, 44.942578124999955], + [114.08027343750004, 44.97114257812501], + [114.41914062500004, 45.20258789062501], + [114.56015625000012, 45.38999023437498], + [114.73876953124997, 45.41962890624998], + [114.91923828125007, 45.378271484375006], + [115.16259765624997, 45.390234375000034], + [115.6810546875, 45.45825195312503], + [116.19765625, 45.739355468750006], + [116.240625, 45.795996093750006], + [116.22910156250012, 45.84575195312502], + [116.21298828125012, 45.88691406249998], + [116.56259765625012, 46.28979492187497], + [116.85908203125004, 46.387939453125], + [117.3333984375, 46.36201171875004], + [117.35693359375003, 46.391308593749955], + [117.35634765625, 46.436669921874966], + [117.39218750000012, 46.53759765625003], + [117.40556640625007, 46.57089843750006], + [117.43808593750012, 46.58623046874999], + [117.546875, 46.58828125000005], + [117.74121093749997, 46.51816406250006], + [118.07128906249997, 46.666601562500006], + [118.15683593750006, 46.678564453125034], + [118.30869140625012, 46.71704101562497], + [118.40439453125006, 46.70317382812499], + [118.58046875, 46.69189453125], + [118.64873046875002, 46.70166015625006], + [118.72294921875007, 46.69189453125], + [118.8439453125001, 46.76020507812498], + [118.95712890625006, 46.73486328124997], + [119.16210937499997, 46.638671875], + [119.33183593750002, 46.61381835937499], + [119.47402343750005, 46.626660156249955], + [119.62021484375006, 46.60395507812504], + [119.70664062500006, 46.60600585937502], + [119.74746093750005, 46.62719726562497], + [119.86718750000003, 46.67216796874999], + [119.89785156250005, 46.857812499999966], + [119.71113281250004, 47.15], + [119.08193359375, 47.654150390625034], + [119.01757812500003, 47.68535156249999], + [118.88027343750005, 47.72509765625], + [118.75996093750004, 47.75761718749996], + [118.69052734375012, 47.822265625], + [118.56777343750005, 47.94326171875005], + [118.49843750000005, 47.98398437499998], + [117.76835937500002, 47.98789062499998], + [117.3507812500001, 47.65219726562498], + [117.28593750000002, 47.666357421875034], + [117.06972656250005, 47.80639648437506], + [116.95166015624997, 47.836572265624966], + [116.90117187500007, 47.85307617187496], + [116.76054687500002, 47.869775390624994], + [116.65195312500012, 47.86450195312497], + [116.51347656250007, 47.839550781249955], + [116.37822265625002, 47.84404296874999], + [116.31718750000002, 47.85986328125], + [116.2311523437501, 47.85820312500002], + [116.07480468750012, 47.78955078125], + [115.99384765625004, 47.71132812500005], + [115.89824218750002, 47.68691406250005], + [115.6164062500001, 47.874804687500045], + [115.52509765625004, 48.13085937499997], + [115.63945312500007, 48.18623046874998], + [115.785546875, 48.24824218750001], + [115.7965820312501, 48.346337890624994], + [115.7916992187501, 48.455712890624994], + [115.8205078125001, 48.57724609375006], + [116.6833007812501, 49.82377929687499], + [117.8734375, 49.51347656250002], + [118.4515625, 49.84448242187503], + [119.25986328125012, 50.06640625000003], + [119.34628906250012, 50.278955078124994], + [119.16367187500006, 50.40600585937503], + [120.06689453125003, 51.60068359375006], + [120.74980468750007, 52.096533203125006], + [120.65615234375, 52.56665039062503], + [120.0675781250001, 52.632910156250034], + [120.09453125000007, 52.787207031250034], + [120.98544921875012, 53.28457031250002], + [123.6078125, 53.546533203124994], + [124.81230468750002, 53.133837890625045], + [125.075, 53.20366210937496], + [125.64902343750012, 53.042285156250045], + [126.34169921875, 52.36201171875001], + [126.92480468749997, 51.10014648437496], + [127.30703125000005, 50.70795898437501], + [127.33720703125007, 50.35014648437502], + [127.590234375, 50.20898437500003], + [127.55078124999997, 49.801806640625045], + [127.99960937500006, 49.56860351562506], + [128.70400390625, 49.60014648437499], + [129.0651367187501, 49.374658203124966], + [129.49814453125012, 49.38881835937502], + [130.1959960937501, 48.89165039062499], + [130.553125, 48.861181640625006], + [130.5521484375, 48.602490234374955], + [130.80429687500012, 48.34150390624998], + [130.7326171875001, 48.01923828124998], + [130.96191406249997, 47.70932617187498], + [132.47626953125004, 47.714990234374994], + [132.7072265625001, 47.94726562500006], + [133.14404296875003, 48.10566406249998], + [133.46835937500006, 48.09716796875003], + [134.29335937500005, 48.37343750000002], + [134.66523437500004, 48.25390625], + [134.56601562500006, 48.02250976562502], + [134.75234375, 47.71542968749998], + [134.1676757812501, 47.30219726562501], + [133.86132812500003, 46.24775390625004], + [133.43642578125, 45.60468750000004], + [133.18603515625003, 45.49482421875004], + [133.1134765625001, 45.130712890625006], + [132.93603515624997, 45.029931640624994], + [131.85185546875002, 45.32685546875001], + [131.44687500000012, 44.984033203124966], + [130.9816406250001, 44.844335937500034], + [131.2552734375, 44.07158203124999], + [131.25732421875003, 43.378076171874994], + [131.06855468750004, 42.90224609375005], + [130.42480468749997, 42.72705078124997], + [130.52695312500012, 42.535400390625] + ] + ], + [ + [ + [113.9977539062501, 22.210498046875045], + [113.83886718749997, 22.24169921875003], + [114.04394531250003, 22.33339843750005], + [113.9977539062501, 22.210498046875045] + ] + ], + [ + [ + [114.01542968750007, 22.51191406250001], + [114.26601562500005, 22.540966796874983], + [114.26796875, 22.295556640624966], + [113.93730468750002, 22.364990234375], + [114.01542968750007, 22.51191406250001] + ] + ], + [], + [ + [ + [118.4074218750001, 24.522119140624994], + [118.43271484375006, 24.414355468750074], + [118.29511718750004, 24.436328125000017], + [118.4074218750001, 24.522119140624994] + ] + ], + [ + [ + [121.00878906249997, 22.62036132812497], + [120.83984375000003, 21.925], + [120.2328125, 22.71791992187505], + [120.0724609375001, 23.149755859375006], + [120.13212890625007, 23.652929687500034], + [121.040625, 25.032812500000034], + [121.59365234375, 25.275341796874983], + [121.92900390625002, 24.973730468749977], + [121.39746093750003, 23.172509765625023], + [121.00878906249997, 22.62036132812497] + ] + ] + ] + }, + "properties": { "name": "China", "childNum": 15 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-5.262304687499977, 10.319677734374991], + [-4.72177734374992, 9.756542968750026], + [-4.625830078125006, 9.713574218749969], + [-4.526611328124943, 9.723486328125034], + [-4.406201171874926, 9.647998046875031], + [-4.332226562499955, 9.645703125], + [-4.18115234375, 9.78173828125], + [-3.790625, 9.917187499999983], + [-3.581152343749977, 9.924316406250014], + [-3.289697265625023, 9.882226562500051], + [-3.223535156249937, 9.895458984374997], + [-3.160693359374932, 9.849169921874974], + [-3.095800781249949, 9.752099609375009], + [-3.042626953124937, 9.72089843750004], + [-2.988281249999972, 9.687353515624963], + [-2.900878906249943, 9.534619140625026], + [-2.875146484374937, 9.500927734374997], + [-2.816748046874949, 9.425830078124974], + [-2.766601562499943, 9.424707031250009], + [-2.7171875, 9.457128906250048], + [-2.695849609374989, 9.481347656250009], + [-2.686132812499977, 9.43173828125002], + [-2.705761718749983, 9.351367187499989], + [-2.74692382812492, 9.04511718750004], + [-2.689892578124955, 9.02509765625004], + [-2.649218750000017, 8.956591796875031], + [-2.600390625000017, 8.800439453125023], + [-2.505859375000028, 8.208740234375], + [-2.538281249999955, 8.171630859374986], + [-2.61171875, 8.147558593749963], + [-2.619970703125006, 8.12109375], + [-2.600976562499937, 8.082226562499983], + [-2.613378906249977, 8.046679687500017], + [-2.668847656249994, 8.022216796875014], + [-2.789746093749955, 7.931933593750003], + [-2.959082031249977, 7.454541015624997], + [-3.227148437499977, 6.749121093749991], + [-2.998291015624972, 5.711328125000051], + [-2.793652343749955, 5.600097656250028], + [-2.754980468749977, 5.432519531249994], + [-2.815673828125, 5.153027343749997], + [-3.168701171874972, 5.203027343749966], + [-3.199951171874943, 5.3544921875], + [-3.347558593749994, 5.13066406249996], + [-4.120166015625017, 5.309716796875023], + [-4.60888671875, 5.235888671875003], + [-4.037207031249977, 5.23012695312498], + [-4.899707031249932, 5.138330078125023], + [-5.282373046874994, 5.210253906250017], + [-5.36752929687492, 5.15078125], + [-5.061816406249989, 5.13066406249996], + [-5.913769531249926, 5.0109375], + [-7.544970703124989, 4.351318359375], + [-7.574658203124983, 4.572314453124989], + [-7.585058593749977, 4.916748046875], + [-7.39990234375, 5.550585937499989], + [-7.454394531249989, 5.841308593749972], + [-7.636132812499994, 5.90771484375], + [-7.730371093749994, 5.919042968749991], + [-7.800927734374994, 6.038916015624991], + [-7.833251953125, 6.076367187499983], + [-7.855517578125017, 6.150146484375], + [-7.888623046875011, 6.234863281250028], + [-7.981591796874937, 6.2861328125], + [-8.287109375, 6.31904296875004], + [-8.587890625, 6.490527343749989], + [-8.324511718749989, 6.920019531249991], + [-8.408740234374989, 7.411816406249997], + [-8.429980468749989, 7.601855468749989], + [-8.351757812499926, 7.590576171875], + [-8.231884765624955, 7.556738281250034], + [-8.205957031249994, 7.590234375000023], + [-8.115429687499926, 7.760742187500028], + [-8.126855468749937, 7.867724609374974], + [-8.00986328124992, 8.078515625000023], + [-8.048583984375, 8.169726562500045], + [-8.140625, 8.181445312500031], + [-8.217138671874949, 8.219677734375011], + [-8.256103515625, 8.253710937500017], + [-8.244140624999943, 8.407910156249983], + [-8.236962890624994, 8.455664062500034], + [-7.953125, 8.477734375], + [-7.823583984374977, 8.467675781249994], + [-7.738964843749983, 8.375244140624986], + [-7.696093749999932, 8.375585937499977], + [-7.71958007812492, 8.643017578125011], + [-7.950976562499989, 8.786816406249997], + [-7.938183593749983, 8.97978515624996], + [-7.902099609375, 9.017089843750014], + [-7.777978515624937, 9.080859375000031], + [-7.799804687499943, 9.115039062499989], + [-7.839404296875017, 9.151611328124972], + [-7.918066406249949, 9.188525390625031], + [-7.896191406249955, 9.415869140624991], + [-8.136962890624972, 9.49570312499999], + [-8.155175781249937, 9.973193359375017], + [-7.990625, 10.1625], + [-7.661132812500028, 10.427441406250011], + [-7.385058593749989, 10.340136718749989], + [-7.01708984375, 10.143261718750026], + [-6.950341796874994, 10.342333984374989], + [-6.693261718750023, 10.34946289062502], + [-6.669335937499937, 10.39218750000002], + [-6.69199218749992, 10.512011718750017], + [-6.686132812499977, 10.578027343750051], + [-6.676367187499949, 10.633789062500043], + [-6.654150390624949, 10.65644531250004], + [-6.482617187499983, 10.561230468749997], + [-6.250244140625, 10.717919921875037], + [-6.190673828124943, 10.400292968749994], + [-6.192626953124972, 10.369433593750003], + [-6.241308593749949, 10.279199218750009], + [-6.238378906249977, 10.26162109374998], + [-6.117187499999972, 10.201904296874986], + [-6.034570312499937, 10.194824218750057], + [-5.907568359375006, 10.307226562500034], + [-5.896191406249983, 10.354736328125028], + [-5.843847656249977, 10.389550781250023], + [-5.694287109374983, 10.433203125000034], + [-5.556591796874983, 10.439941406249986], + [-5.382275390625011, 10.314013671875003], + [-5.262304687499977, 10.319677734374991] + ] + ] + }, + "properties": { "name": "Côte d'Ivoire", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [15.480078125, 7.523779296874991], + [15.206738281250011, 7.206152343749991], + [14.7392578125, 6.27978515625], + [14.43115234375, 6.038720703124994], + [14.616894531250011, 5.865136718749994], + [14.56298828125, 5.279931640624994], + [14.73125, 4.602392578124991], + [15.063574218750006, 4.284863281249997], + [15.128710937500017, 3.826904296875], + [16.0634765625, 2.90859375], + [16.183398437500017, 2.270068359374989], + [16.059375, 1.676220703124997], + [15.741601562500023, 1.914990234374997], + [14.902441406250006, 2.012304687499991], + [14.578906250000017, 2.199121093749994], + [13.293554687500006, 2.161572265624997], + [13.2203125, 2.256445312499991], + [11.558984375000023, 2.302197265624997], + [11.348437500000017, 2.299707031249994], + [11.328710937500006, 2.167431640624997], + [11.096582031250023, 2.16748046875], + [10.790917968750023, 2.16757812499999], + [9.979882812500023, 2.167773437499989], + [9.8701171875, 2.21328125], + [9.8369140625, 2.242382812499997], + [9.830371093750017, 2.275488281249991], + [9.826171875, 2.297802734374997], + [9.80078125, 2.304443359375], + [9.82177734375, 2.539257812499997], + [9.948437500000011, 3.079052734374997], + [9.672070312500011, 3.53759765625], + [9.765722656250006, 3.623828124999989], + [9.642382812500017, 3.611767578124997], + [9.55615234375, 3.798046875], + [9.739648437500023, 3.852929687499994], + [9.639941406250017, 3.96533203125], + [9.688867187500023, 4.056396484375], + [9.483691406250017, 4.066113281249997], + [9.42529296875, 3.922314453124997], + [9.000097656250006, 4.091601562499989], + [8.918261718750017, 4.553759765624989], + [8.660351562500011, 4.670996093749991], + [8.65625, 4.516357421875], + [8.53955078125, 4.571875], + [8.715625, 5.046875], + [8.997167968750006, 5.917724609375], + [9.490234375, 6.418652343749997], + [9.779882812500006, 6.76015625], + [9.820703125000023, 6.783935546875], + [9.874218750000011, 6.803271484374989], + [10.038867187500017, 6.92138671875], + [10.1435546875, 6.996435546874991], + [10.167773437500017, 6.959179687499997], + [10.185546875, 6.912792968749997], + [10.205468750000023, 6.8916015625], + [10.293066406250006, 6.876757812499989], + [10.413183593750006, 6.877734374999989], + [10.60625, 7.063085937499991], + [10.954199218750006, 6.7765625], + [11.032519531250017, 6.697900390624994], + [11.1064453125, 6.457714843749997], + [11.1533203125, 6.437939453124997], + [11.2373046875, 6.450537109374991], + [11.401757812500023, 6.533935546875], + [11.551660156250023, 6.697265625], + [11.580078125, 6.888867187499997], + [11.657519531250017, 6.9515625], + [11.861425781250006, 7.11640625], + [11.767382812500017, 7.272265624999989], + [11.809179687500006, 7.345068359374991], + [12.016015625000023, 7.589746093749994], + [12.2333984375, 8.282324218749991], + [12.403515625000011, 8.595556640624991], + [12.582714843750011, 8.624121093749991], + [12.651562500000011, 8.667773437499989], + [12.7822265625, 8.81787109375], + [12.806542968750023, 8.886621093749994], + [12.875683593750011, 9.303515624999989], + [12.929492187500017, 9.42626953125], + [13.19873046875, 9.563769531249989], + [13.269921875000023, 10.036181640624989], + [13.41455078125, 10.171435546874989], + [13.535351562500011, 10.60507812499999], + [13.699902343750011, 10.873144531249991], + [13.89208984375, 11.140087890624997], + [13.9814453125, 11.211865234374997], + [14.056738281250006, 11.245019531249994], + [14.143261718750011, 11.24853515625], + [14.202343750000011, 11.268164062499991], + [14.559765625000011, 11.492285156249991], + [14.619726562500006, 12.150976562499991], + [14.518945312500023, 12.298242187499994], + [14.272851562500023, 12.356494140624989], + [14.184863281250017, 12.447216796874997], + [14.06396484375, 13.07851562499999], + [14.244824218750011, 13.07734375], + [14.461718750000017, 13.021777343749989], + [14.847070312500023, 12.502099609374994], + [15.08125, 11.845507812499989], + [15.029882812500006, 11.11367187499999], + [15.132226562500023, 10.648486328124989], + [15.276074218750011, 10.357373046874997], + [15.654882812500006, 10.0078125], + [14.243261718750006, 9.979736328125], + [13.977246093750011, 9.691552734374994], + [14.332324218750017, 9.20351562499999], + [15.1162109375, 8.557324218749997], + [15.5498046875, 7.787890624999989], + [15.480078125, 7.523779296874991] + ] + ] + }, + "properties": { "name": "Cameroon", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [27.4033203125, 5.109179687499989], + [27.7880859375, 4.644677734374994], + [28.19208984375001, 4.350244140624994], + [28.427539062500017, 4.324169921874997], + [28.72705078125, 4.504980468749991], + [29.224902343750017, 4.391894531249989], + [29.469628906250023, 4.61181640625], + [29.676855468750006, 4.5869140625], + [30.194921875, 3.98193359375], + [30.50830078125, 3.835693359375], + [30.586718750000017, 3.62421875], + [30.757226562500023, 3.62421875], + [30.83857421875001, 3.49072265625], + [30.90644531250001, 3.408935546875], + [30.754003906250006, 3.041796874999989], + [30.8466796875, 2.847021484374991], + [30.728613281250006, 2.455371093749989], + [31.176367187500006, 2.270068359374989], + [31.252734375000017, 2.044580078124994], + [29.94287109375, 0.819238281249994], + [29.934472656250023, 0.4990234375], + [29.717675781250023, 0.098339843749997], + [29.576953125000017, -1.387890625000011], + [29.196582031250017, -1.719921875000011], + [29.13154296875001, -2.195117187500003], + [28.876367187500023, -2.400292968750009], + [28.893945312500023, -2.635058593750003], + [29.01435546875001, -2.72021484375], + [29.224414062500017, -3.053515625], + [29.211816406250023, -3.833789062500003], + [29.403222656250023, -4.449316406250006], + [29.404199218750023, -4.496679687500006], + [29.32568359375, -4.835644531250011], + [29.32343750000001, -4.898828125], + [29.3427734375, -4.983105468750011], + [29.542382812500023, -5.499804687500003], + [29.594140625000023, -5.65078125], + [29.60703125, -5.72265625], + [29.59638671875001, -5.775976562500006], + [29.490820312500006, -5.965429687500006], + [29.480078125, -6.025], + [29.50625, -6.172070312500011], + [29.540820312500017, -6.313867187500009], + [29.590625, -6.394433593750009], + [29.70966796875001, -6.616894531250011], + [29.798144531250017, -6.69189453125], + [29.961816406250023, -6.803125], + [30.10625, -6.9150390625], + [30.212695312500017, -7.037890625], + [30.31318359375001, -7.203710937500006], + [30.40673828125, -7.460644531250011], + [30.75117187500001, -8.193652343750003], + [28.89814453125001, -8.485449218750006], + [28.869531250000023, -8.785839843750011], + [28.400683593750017, -9.224804687500011], + [28.60419921875001, -9.678808593750006], + [28.6455078125, -10.550195312500009], + [28.383398437500006, -11.566699218750003], + [28.482519531250006, -11.812109375], + [29.064355468750023, -12.348828125000011], + [29.48554687500001, -12.41845703125], + [29.508203125000023, -12.228222656250011], + [29.79511718750001, -12.155468750000011], + [29.775195312500017, -13.438085937500006], + [29.55419921875, -13.248925781250009], + [29.20185546875001, -13.398339843750009], + [29.014257812500006, -13.368847656250011], + [28.730078125, -12.925488281250011], + [28.550878906250006, -12.836132812500011], + [28.412890625000017, -12.51806640625], + [27.573828125, -12.22705078125], + [27.1591796875, -11.579199218750006], + [26.824023437500017, -11.965234375], + [26.025976562500006, -11.89013671875], + [25.349414062500017, -11.623046875], + [25.28876953125001, -11.21240234375], + [24.3779296875, -11.417089843750006], + [24.36572265625, -11.1298828125], + [23.96650390625001, -10.871777343750011], + [23.901171875000017, -10.983203125], + [23.833886718750023, -11.013671875], + [23.463964843750006, -10.969335937500006], + [23.076269531250006, -11.087890625], + [22.814746093750017, -11.080273437500011], + [22.56103515625, -11.055859375000011], + [22.486132812500017, -11.08671875], + [22.392968750000023, -11.159472656250003], + [22.31494140625, -11.198632812500009], + [22.27880859375, -11.194140625], + [22.226171875, -11.121972656250009], + [22.203515625000023, -10.829492187500009], + [22.307031250000023, -10.691308593750009], + [22.19775390625, -10.040625], + [21.81318359375001, -9.46875], + [21.905371093750006, -8.693359375], + [21.806054687500023, -7.32861328125], + [21.751074218750006, -7.30546875], + [21.190332031250023, -7.284960937500003], + [20.910937500000017, -7.281445312500011], + [20.607812500000023, -7.277734375], + [20.558398437500017, -7.244433593750003], + [20.53583984375001, -7.182812500000011], + [20.536914062500017, -7.121777343750011], + [20.598730468750006, -6.93515625], + [20.59003906250001, -6.919921875], + [20.482226562500017, -6.915820312500003], + [20.190039062500006, -6.9462890625], + [19.997460937500023, -6.976464843750009], + [19.87519531250001, -6.986328125], + [19.527636718750017, -7.144433593750009], + [19.483789062500023, -7.279492187500011], + [19.479882812500023, -7.47216796875], + [19.371679687500006, -7.655078125], + [19.369921875000017, -7.70654296875], + [19.3408203125, -7.966601562500003], + [19.142675781250006, -8.00146484375], + [18.944433593750006, -8.00146484375], + [18.56269531250001, -7.9359375], + [18.0087890625, -8.107617187500011], + [17.643359375000017, -8.090722656250009], + [17.57958984375, -8.099023437500009], + [16.984765625000023, -7.257421875], + [16.91943359375, -6.933984375], + [16.813085937500006, -6.772558593750006], + [16.742968750000017, -6.618457031250003], + [16.697265625, -6.164257812500011], + [16.537109375, -5.9658203125], + [16.431445312500017, -5.900195312500003], + [16.315234375000017, -5.865625], + [13.978515625, -5.857226562500003], + [13.346484375000017, -5.863378906250006], + [13.184375, -5.85625], + [12.452929687500017, -6.00048828125], + [12.213671875000017, -5.758691406250009], + [12.484570312500011, -5.71875], + [12.451464843750017, -5.071484375000011], + [12.502734375000017, -5.036914062500003], + [12.573535156250017, -4.99658203125], + [12.59619140625, -4.978417968750009], + [12.8296875, -4.736621093750003], + [12.947460937500011, -4.6953125], + [13.057324218750011, -4.651074218750011], + [13.07275390625, -4.634765625], + [13.08740234375, -4.601953125], + [13.136621093750023, -4.604296875], + [13.414941406250023, -4.83740234375], + [13.659570312500023, -4.721484375], + [13.717089843750017, -4.454492187500009], + [13.94091796875, -4.484667968750003], + [14.358300781250023, -4.299414062500006], + [14.449804687500006, -4.449511718750003], + [14.365429687500011, -4.585546875], + [14.410742187500006, -4.83125], + [14.707910156250023, -4.881738281250009], + [15.990039062500017, -3.766210937500006], + [16.217382812500006, -3.0302734375], + [16.21533203125, -2.177832031250006], + [16.54072265625001, -1.840136718750003], + [16.8798828125, -1.225878906250003], + [17.752832031250023, -0.549023437500011], + [18.072167968750023, 2.01328125], + [18.49091796875001, 2.924414062499991], + [18.6103515625, 3.478417968749994], + [18.594140625000023, 4.346240234374989], + [19.06855468750001, 4.891406249999989], + [19.5009765625, 5.127490234374989], + [19.806542968750023, 5.089306640624997], + [20.226367187500017, 4.829638671874989], + [20.55810546875, 4.462695312499989], + [22.422167968750017, 4.134960937499997], + [22.864550781250017, 4.723876953125], + [23.41718750000001, 4.663134765624989], + [24.31982421875, 4.994140625], + [25.065234375000017, 4.967431640624994], + [25.52509765625001, 5.31210937499999], + [26.822070312500017, 5.062402343749994], + [27.071875, 5.199755859374989], + [27.4033203125, 5.109179687499989] + ] + ] + }, + "properties": { "name": "Dem. Rep. Congo", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [18.6103515625, 3.478417968749994], + [18.49091796875001, 2.924414062499991], + [18.072167968750023, 2.01328125], + [17.752832031250023, -0.549023437500011], + [16.8798828125, -1.225878906250003], + [16.54072265625001, -1.840136718750003], + [16.21533203125, -2.177832031250006], + [16.217382812500006, -3.0302734375], + [15.990039062500017, -3.766210937500006], + [14.707910156250023, -4.881738281250009], + [14.410742187500006, -4.83125], + [14.365429687500011, -4.585546875], + [14.449804687500006, -4.449511718750003], + [14.358300781250023, -4.299414062500006], + [13.94091796875, -4.484667968750003], + [13.717089843750017, -4.454492187500009], + [13.659570312500023, -4.721484375], + [13.414941406250023, -4.83740234375], + [13.136621093750023, -4.604296875], + [13.08740234375, -4.601953125], + [13.07275390625, -4.634765625], + [13.048046875000011, -4.619238281250006], + [12.971386718750011, -4.5517578125], + [12.881054687500011, -4.445117187500003], + [12.84814453125, -4.428906250000011], + [12.50146484375, -4.5875], + [12.018359375000017, -5.004296875], + [11.777539062500011, -4.565820312500009], + [11.130175781250017, -3.916308593750003], + [11.234472656250006, -3.690820312500009], + [11.504296875000023, -3.5203125], + [11.685742187500011, -3.68203125], + [11.8798828125, -3.665917968750009], + [11.934179687500006, -3.318554687500011], + [11.715429687500006, -3.176953125000011], + [11.760156250000023, -2.983105468750011], + [11.537792968750011, -2.83671875], + [11.60546875, -2.342578125], + [12.064453125, -2.41259765625], + [12.446386718750006, -2.329980468750009], + [12.43212890625, -1.928906250000011], + [12.590429687500006, -1.826855468750011], + [12.793554687500006, -1.931835937500011], + [12.991992187500017, -2.313378906250009], + [13.464941406250006, -2.395410156250009], + [13.733789062500023, -2.138476562500003], + [13.886914062500011, -2.465429687500006], + [13.993847656250011, -2.490625], + [14.199804687500006, -2.354199218750011], + [14.162890625000017, -2.217578125], + [14.383984375000011, -1.890039062500009], + [14.47412109375, -0.573437500000011], + [13.860058593750011, -0.203320312500011], + [13.949609375000023, 0.353808593749989], + [14.32421875, 0.62421875], + [14.429882812500011, 0.901464843749991], + [14.180859375000011, 1.370214843749991], + [13.851367187500017, 1.41875], + [13.21630859375, 1.2484375], + [13.172167968750017, 1.78857421875], + [13.293554687500006, 2.161572265624997], + [14.578906250000017, 2.199121093749994], + [14.902441406250006, 2.012304687499991], + [15.741601562500023, 1.914990234374997], + [16.059375, 1.676220703124997], + [16.183398437500017, 2.270068359374989], + [16.468554687500017, 2.831738281249997], + [16.610742187500023, 3.50537109375], + [17.491601562500023, 3.687304687499989], + [18.160937500000017, 3.499804687499989], + [18.474414062500017, 3.622998046874997], + [18.6103515625, 3.478417968749994] + ] + ] + }, + "properties": { "name": "Congo", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-71.31972656249997, 11.861914062500048], + [-71.95810546875, 11.66640625], + [-72.24848632812501, 11.196435546875009], + [-72.690087890625, 10.835839843749994], + [-72.86933593750001, 10.49125976562496], + [-73.00654296874998, 9.789160156250006], + [-73.36621093749997, 9.194140625000017], + [-73.05839843749999, 9.259570312500031], + [-72.79638671874997, 9.10898437499999], + [-72.66542968749994, 8.62758789062498], + [-72.39033203124995, 8.287060546874969], + [-72.47197265624996, 7.524267578124991], + [-72.20771484374995, 7.37026367187498], + [-72.00664062499993, 7.032617187500023], + [-71.12861328124993, 6.98671875], + [-70.73715820312503, 7.090039062499997], + [-70.12919921874999, 6.95361328125], + [-69.42714843749997, 6.123974609374997], + [-68.47177734375, 6.156542968749974], + [-67.85917968749999, 6.289892578124963], + [-67.48198242187499, 6.18027343750002], + [-67.47387695312503, 5.929980468750003], + [-67.82490234374995, 5.270458984375026], + [-67.85527343750002, 4.506884765624989], + [-67.66162109375, 3.864257812499986], + [-67.3111328125, 3.41586914062502], + [-67.85908203124998, 2.793603515624994], + [-67.61870117187496, 2.793603515624994], + [-67.21083984375, 2.390136718750043], + [-66.87602539062499, 1.223046875000037], + [-67.082275390625, 1.185400390625006], + [-67.11923828124998, 1.703613281249986], + [-67.40043945312499, 2.116699218750028], + [-67.93623046874998, 1.748486328124969], + [-68.19379882812495, 1.987011718749983], + [-68.25595703125, 1.845507812500017], + [-68.17656249999999, 1.719824218749991], + [-69.84858398437493, 1.708740234375043], + [-69.85214843750003, 1.05952148437504], + [-69.31181640624999, 1.050488281249969], + [-69.15332031249994, 0.65878906250002], + [-69.47211914062498, 0.72993164062504], + [-70.05390624999993, 0.578613281250028], + [-70.07050781249993, -0.13886718750004], + [-69.63398437500001, -0.50927734375], + [-69.40024414062498, -1.194921874999977], + [-69.66904296875003, -2.667675781249997], + [-69.94819335937498, -4.200585937500009], + [-69.96591796875003, -4.2359375], + [-70.16752929687499, -4.050195312500009], + [-70.24028320312496, -3.882714843749994], + [-70.2984375, -3.844238281249972], + [-70.33950195312502, -3.814355468750009], + [-70.73510742187497, -3.781542968749989], + [-70.09584960937494, -2.658203125000014], + [-70.16474609374995, -2.639843750000011], + [-70.24443359375002, -2.606542968749977], + [-70.29462890624995, -2.552539062499989], + [-70.57587890624995, -2.418261718749989], + [-70.64799804687499, -2.405761718750014], + [-70.70537109374996, -2.341992187499983], + [-70.91455078125003, -2.218554687499974], + [-70.96855468750002, -2.206835937499989], + [-71.02729492187498, -2.225781250000026], + [-71.11337890625003, -2.245410156250031], + [-71.19638671874998, -2.313085937499963], + [-71.39697265625, -2.334082031249977], + [-71.55947265624997, -2.224218749999977], + [-71.75253906249995, -2.15273437499998], + [-71.80273437499997, -2.166308593749989], + [-71.86728515624998, -2.227734374999983], + [-71.932470703125, -2.288671874999963], + [-71.98427734375, -2.326562499999952], + [-72.21845703125001, -2.400488281250006], + [-72.94111328124998, -2.394042968750028], + [-72.9896484375, -2.33974609374998], + [-73.15449218749993, -2.278222656249966], + [-73.19697265624995, -1.830273437500011], + [-73.49628906249993, -1.69306640625004], + [-73.66430664062497, -1.248828124999946], + [-73.86318359374997, -1.19667968749998], + [-73.92695312500001, -1.125195312499983], + [-73.98681640625003, -1.098144531249986], + [-74.05439453124995, -1.028613281250031], + [-74.18076171875, -0.997753906249955], + [-74.24638671874999, -0.970605468750023], + [-74.28388671874998, -0.927832031250006], + [-74.33442382812498, -0.85087890624996], + [-74.41787109375, -0.580664062499977], + [-74.46518554687498, -0.517675781250034], + [-74.51386718749993, -0.470117187500023], + [-74.555078125, -0.429882812499997], + [-74.61635742187494, -0.370019531249966], + [-74.691650390625, -0.335253906249989], + [-74.75537109375003, -0.298632812499989], + [-74.78046874999998, -0.24453125], + [-74.80175781249997, -0.200097656249994], + [-75.13837890624998, -0.050488281249969], + [-75.28447265624999, -0.10654296875002], + [-75.77666015624999, 0.08925781249998], + [-76.27060546874998, 0.439404296874997], + [-76.49462890624997, 0.23544921875002], + [-77.396337890625, 0.393896484374963], + [-77.46767578124997, 0.636523437500017], + [-77.702880859375, 0.837841796874997], + [-78.1806640625, 0.968554687499974], + [-78.85966796874996, 1.455371093750031], + [-79.02543945312499, 1.623681640625037], + [-78.79296874999994, 1.848730468749963], + [-78.576904296875, 1.773779296874977], + [-78.59169921875, 2.356640624999969], + [-78.41689453125, 2.483496093749963], + [-78.06665039062494, 2.509130859375034], + [-77.81357421875, 2.716357421874974], + [-77.076806640625, 3.913281250000026], + [-77.26352539062503, 3.893212890625023], + [-77.27802734374995, 4.058496093750023], + [-77.35820312499996, 3.944726562500037], + [-77.40874023437496, 4.24775390625004], + [-77.52070312499993, 4.212792968750023], + [-77.35351562499997, 4.398291015624977], + [-77.28632812499995, 4.72172851562496], + [-77.373291015625, 5.323974609375], + [-77.53442382812497, 5.537109374999986], + [-77.24926757812497, 5.780175781250037], + [-77.46943359374995, 6.176757812500014], + [-77.368798828125, 6.575585937499994], + [-77.90117187499999, 7.229345703125048], + [-77.76191406249995, 7.698828125000034], + [-77.53828124999995, 7.56625976562502], + [-77.19599609374995, 7.972460937500003], + [-77.47851562499994, 8.498437500000037], + [-77.37421874999993, 8.65830078125002], + [-76.85185546875002, 8.09047851562498], + [-76.924658203125, 7.973193359374974], + [-76.78657226562493, 7.931591796875026], + [-76.7720703125, 8.310546875000043], + [-76.92045898437496, 8.573730468750014], + [-76.27685546875, 8.989111328124991], + [-76.02724609374997, 9.365771484374989], + [-75.63935546874998, 9.450439453125014], + [-75.680029296875, 9.729785156249989], + [-75.53857421874997, 10.205175781250034], + [-75.708349609375, 10.143408203124963], + [-75.44599609374995, 10.610888671874989], + [-74.84458007812498, 11.109716796875006], + [-74.330224609375, 10.996679687499991], + [-74.51625976562497, 10.8625], + [-74.40087890625, 10.76523437499999], + [-74.14291992187503, 11.320849609375031], + [-73.31337890624997, 11.295751953124991], + [-72.275, 11.88925781250002], + [-72.13574218749994, 12.188574218749977], + [-71.71455078124993, 12.41997070312496], + [-71.26210937499997, 12.335302734375034], + [-71.13730468750003, 12.04633789062504], + [-71.31972656249997, 11.861914062500048] + ] + ] + }, + "properties": { "name": "Colombia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [43.788671875, -12.307031250000023], + [43.85898437500006, -12.368261718749977], + [43.66367187500006, -12.342871093749949], + [43.63134765624997, -12.247070312499972], + [43.788671875, -12.307031250000023] + ] + ], + [ + [ + [44.476367187500074, -12.08154296875], + [44.504980468750006, -12.356542968749991], + [44.220117187499994, -12.171386718750014], + [44.476367187500074, -12.08154296875] + ] + ], + [ + [ + [43.46582031249997, -11.901269531249966], + [43.226660156250006, -11.75185546874998], + [43.2990234375001, -11.374511718750028], + [43.39296875000005, -11.408593749999952], + [43.46582031249997, -11.901269531249966] + ] + ] + ] + }, + "properties": { "name": "Comoros", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-24.308251953124966, 14.856298828124991], + [-24.44052734374992, 14.834814453124963], + [-24.496875, 14.980273437500017], + [-24.329492187499937, 15.019482421875011], + [-24.308251953124966, 14.856298828124991] + ] + ], + [ + [ + [-23.18212890624997, 15.136767578125017], + [-23.210253906250017, 15.32353515625006], + [-23.119335937499955, 15.26840820312502], + [-23.18212890624997, 15.136767578125017] + ] + ], + [ + [ + [-23.444238281249994, 15.00795898437498], + [-23.5046875, 14.916113281250006], + [-23.70537109374999, 14.96132812499998], + [-23.74809570312499, 15.328515625], + [-23.444238281249994, 15.00795898437498] + ] + ], + [ + [ + [-22.917724609375, 16.237255859374955], + [-22.69262695312497, 16.169042968750006], + [-22.710107421874994, 16.043359374999966], + [-22.95927734374996, 16.045117187499983], + [-22.917724609375, 16.237255859374955] + ] + ], + [ + [ + [-24.08769531249999, 16.62250976562501], + [-24.03271484374997, 16.57202148437503], + [-24.243066406250023, 16.599414062500017], + [-24.32236328124992, 16.49311523437504], + [-24.398095703124966, 16.61840820312497], + [-24.08769531249999, 16.62250976562501] + ] + ], + [ + [ + [-22.888330078124966, 16.659082031249994], + [-22.980615234374937, 16.700878906249983], + [-22.93291015624999, 16.84101562500004], + [-22.888330078124966, 16.659082031249994] + ] + ], + [ + [ + [-24.88706054687495, 16.81811523437497], + [-25.09306640624999, 16.83251953125], + [-24.936474609374983, 16.92211914062503], + [-24.88706054687495, 16.81811523437497] + ] + ], + [ + [ + [-25.169824218749994, 16.94648437500001], + [-25.308300781249955, 16.93583984374999], + [-25.337109374999955, 17.091015624999983], + [-25.03466796875, 17.176464843749983], + [-24.979687499999983, 17.09472656250003], + [-25.169824218749994, 16.94648437500001] + ] + ] + ] + }, + "properties": { "name": "Cape Verde", "childNum": 8 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-83.6419921875, 10.917236328125], + [-83.346826171875, 10.315380859374997], + [-82.77841796874999, 9.66953125], + [-82.56357421874999, 9.57666015625], + [-82.56923828125, 9.55820312499999], + [-82.58652343749999, 9.538818359375], + [-82.64409179687499, 9.505859375], + [-82.801025390625, 9.591796875], + [-82.843994140625, 9.57080078125], + [-82.86015624999999, 9.511474609375], + [-82.88896484374999, 9.481005859374989], + [-82.925048828125, 9.469042968749989], + [-82.93984375, 9.449169921874997], + [-82.94033203125, 9.060107421874989], + [-82.88134765625, 9.055859375], + [-82.78305664062499, 8.990283203124989], + [-82.741162109375, 8.951708984374989], + [-82.72783203124999, 8.916064453124989], + [-82.91704101562499, 8.740332031249991], + [-82.855712890625, 8.635302734374989], + [-82.84477539062499, 8.489355468749991], + [-82.86162109374999, 8.45351562499999], + [-83.02734375, 8.337744140624991], + [-82.879345703125, 8.070654296874991], + [-83.12333984374999, 8.353076171874989], + [-83.16240234374999, 8.588183593749989], + [-83.4697265625, 8.706835937499989], + [-83.29150390625, 8.406005859375], + [-83.54375, 8.445849609374989], + [-83.73408203125, 8.614453125], + [-83.613720703125, 8.804052734374991], + [-83.73691406249999, 9.150292968749994], + [-84.58159179687499, 9.568359375], + [-84.71494140624999, 9.8994140625], + [-85.23564453124999, 10.242089843749994], + [-85.2365234375, 10.107373046874997], + [-84.88642578125, 9.820947265624994], + [-85.07705078125, 9.60195312499999], + [-85.31455078124999, 9.8109375], + [-85.62485351562499, 9.902441406249991], + [-85.84965820312499, 10.292041015624989], + [-85.667236328125, 10.745019531249994], + [-85.90800781249999, 10.897558593749991], + [-85.7443359375, 11.06210937499999], + [-85.5841796875, 11.189453125], + [-84.9091796875, 10.9453125], + [-84.6341796875, 11.045605468749997], + [-83.91928710937499, 10.7353515625], + [-83.71293945312499, 10.785888671875], + [-83.6419921875, 10.917236328125] + ] + ] + }, + "properties": { "name": "Costa Rica", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-82.56176757812503, 21.571679687500023], + [-82.959619140625, 21.441308593750023], + [-83.18378906250001, 21.59345703125004], + [-82.97358398437498, 21.592285156250057], + [-83.08251953124997, 21.791406250000023], + [-82.99121093750003, 21.942724609375034], + [-82.71455078124998, 21.890283203125023], + [-82.56176757812503, 21.571679687500023] + ] + ], + [ + [ + [-77.66899414062493, 21.951953125000045], + [-77.91855468749998, 22.088085937499983], + [-77.63369140624994, 22.054003906250074], + [-77.66899414062493, 21.951953125000045] + ] + ], + [ + [ + [-77.87939453125, 22.127539062500034], + [-78.04165039062502, 22.201269531250034], + [-77.99921874999998, 22.298730468749994], + [-77.87939453125, 22.127539062500034] + ] + ], + [ + [ + [-81.83745117187499, 23.163037109374955], + [-81.26235351562497, 23.156835937500034], + [-81.14462890624998, 23.054931640625057], + [-80.65014648437494, 23.10307617187499], + [-80.36489257812502, 22.943408203125074], + [-79.82026367187498, 22.887011718750045], + [-79.27568359374999, 22.407617187499994], + [-78.68647460937493, 22.366845703125023], + [-77.63681640624995, 21.79736328125], + [-77.49711914062502, 21.78833007812503], + [-77.58315429687497, 21.889257812499977], + [-77.49726562499995, 21.871630859375045], + [-77.14414062499995, 21.643603515625017], + [-77.36616210937498, 21.612646484375034], + [-77.25288085937498, 21.483496093750006], + [-77.0986328125, 21.589013671875023], + [-76.86743164062497, 21.330419921875006], + [-75.72294921874996, 21.111035156249983], + [-75.59580078125, 20.99467773437499], + [-75.72456054687493, 20.71455078125004], + [-74.882568359375, 20.65063476562497], + [-74.51313476562495, 20.384570312500045], + [-74.16748046874997, 20.292187499999955], + [-74.15371093750002, 20.168554687500006], + [-75.11640624999995, 19.901416015625017], + [-75.151611328125, 20.008349609375045], + [-75.29047851562495, 19.893115234375017], + [-76.15844726562497, 19.98974609374997], + [-77.715087890625, 19.85546874999997], + [-77.10380859374999, 20.407519531250017], + [-77.22958984374995, 20.64375], + [-78.11635742187497, 20.761865234374994], + [-78.49077148437493, 21.05371093750003], + [-78.72768554687497, 21.592724609374955], + [-79.35742187500003, 21.58515625000001], + [-80.23134765625, 21.872167968750063], + [-80.48544921874998, 22.1234375], + [-81.03564453124997, 22.073583984375063], + [-81.18549804687495, 22.26796875000005], + [-81.284375, 22.109423828125074], + [-81.84941406249993, 22.21367187499999], + [-82.077734375, 22.3876953125], + [-81.71035156250002, 22.496679687500006], + [-81.83881835937498, 22.672460937500034], + [-82.73803710937497, 22.689257812500074], + [-83.37963867187503, 22.222998046875034], + [-83.90073242187495, 22.17011718750001], + [-84.03095703124993, 21.94311523437503], + [-84.502587890625, 21.776171875000045], + [-84.50136718750002, 21.930273437499977], + [-84.88720703125003, 21.856982421875074], + [-84.32636718749998, 22.074316406250034], + [-84.36127929687498, 22.37890625], + [-84.04492187500003, 22.666015625000057], + [-83.25781249999997, 22.967578125000017], + [-81.83745117187499, 23.163037109374955] + ] + ] + ] + }, + "properties": { "name": "Cuba", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-68.75107421874999, 12.059765625], + [-68.9951171875, 12.141845703125], + [-69.15888671875, 12.380273437499994], + [-68.75107421874999, 12.059765625] + ] + ] + }, + "properties": { "name": "Curaçao", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-81.36953124999997, 19.34887695312497], + [-81.10712890624995, 19.305175781250057], + [-81.40478515624994, 19.278417968750006], + [-81.36953124999997, 19.34887695312497] + ] + ], + [ + [ + [-79.823388671875, 19.711914062500057], + [-79.90620117187501, 19.702539062499994], + [-79.74228515625, 19.757128906250017], + [-79.823388671875, 19.711914062500057] + ] + ] + ] + }, + "properties": { "name": "Cayman Is.", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [34.004492187500006, 35.065234375], + [33.47578125000001, 35.000341796875], + [33.3837890625, 35.1626953125], + [32.91953125, 35.087841796875], + [32.71269531250002, 35.171044921874994], + [32.8798828125, 35.180566406249994], + [32.94160156250001, 35.390429687499996], + [33.60761718750001, 35.354150390624994], + [34.55605468750002, 35.662060546875], + [33.941992187500006, 35.292041015624996], + [34.004492187500006, 35.065234375] + ] + ] + }, + "properties": { "name": "N. Cyprus", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [32.71269531250002, 35.171044921874994], + [32.91953125, 35.087841796875], + [33.3837890625, 35.1626953125], + [33.47578125000001, 35.000341796875], + [34.004492187500006, 35.065234375], + [34.05019531250002, 34.98837890625], + [33.69941406250001, 34.969873046874994], + [33.007910156250006, 34.569580078125], + [32.44902343750002, 34.729443359375], + [32.31718750000002, 34.9533203125], + [32.30097656250001, 35.082958984375], + [32.71269531250002, 35.171044921874994] + ] + ] + }, + "properties": { "name": "Cyprus", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [14.809375, 50.858984375], + [14.895800781250017, 50.861376953124996], + [14.98291015625, 50.886572265625], + [14.99375, 51.01435546875], + [16.007226562500023, 50.611621093749996], + [16.2822265625, 50.655615234375], + [16.419726562500017, 50.573632812499994], + [16.210351562500023, 50.423730468749994], + [16.63916015625, 50.1021484375], + [16.989648437500023, 50.2369140625], + [16.88007812500001, 50.427050781249996], + [17.41523437500001, 50.254785156249994], + [17.702246093750006, 50.307177734374996], + [17.627050781250006, 50.11640625], + [17.874804687500017, 49.972265625], + [18.0283203125, 50.03525390625], + [18.562402343750023, 49.879345703125], + [18.83222656250001, 49.510791015624996], + [18.160937500000017, 49.257373046874996], + [18.0859375, 49.06513671875], + [17.75849609375001, 48.888134765625], + [17.135644531250023, 48.841064453125], + [16.953125, 48.598828125], + [16.543554687500006, 48.796240234375], + [16.057226562500006, 48.754785156249994], + [15.066796875000023, 48.997851562499996], + [14.691308593750023, 48.59921875], + [14.049121093750017, 48.602490234375], + [13.814746093750017, 48.766943359375], + [13.769921875000023, 48.815966796874996], + [13.684960937500023, 48.876708984375], + [13.547656250000017, 48.95966796875], + [13.440722656250017, 48.95556640625], + [13.401171875000017, 48.977587890624996], + [12.916699218750011, 49.33046875], + [12.68115234375, 49.414501953125], + [12.390527343750023, 49.739648437499994], + [12.5125, 49.87744140625], + [12.09921875, 50.310986328125], + [12.134863281250006, 50.3109375], + [12.1748046875, 50.288378906249996], + [12.231152343750011, 50.244873046875], + [12.27734375, 50.181445312499996], + [12.3056640625, 50.205712890624994], + [12.549023437500011, 50.393408203125], + [13.016406250000017, 50.490380859374994], + [13.18115234375, 50.510498046875], + [14.369042968750023, 50.898730468749996], + [14.319726562500023, 51.03779296875], + [14.545703125000017, 50.993945312499996], + [14.559667968750006, 50.954931640625], + [14.59521484375, 50.918603515624994], + [14.623828125000017, 50.91474609375], + [14.613574218750017, 50.85556640625], + [14.658203125, 50.8326171875], + [14.723339843750011, 50.814697265625], + [14.766503906250023, 50.818310546875], + [14.797460937500006, 50.842333984374996], + [14.809375, 50.858984375] + ] + ] + }, + "properties": { "name": "Czech Rep.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [14.1982421875, 53.919042968750034], + [13.92578125, 53.879052734374966], + [13.827734375, 54.12724609374999], + [14.1982421875, 53.919042968750034] + ] + ], + [ + [ + [13.709179687500011, 54.382714843749994], + [13.707324218750074, 54.281152343749994], + [13.190039062500034, 54.32563476562501], + [13.336816406249994, 54.697119140625006], + [13.65761718750008, 54.55957031249997], + [13.709179687500011, 54.382714843749994] + ] + ], + [ + [ + [9.739746093750028, 54.82553710937498], + [10.022167968750011, 54.673925781250006], + [9.86865234375, 54.47246093749999], + [10.731542968750006, 54.31625976562506], + [11.013378906250068, 54.37915039062497], + [11.008593750000074, 54.18115234374997], + [10.810742187500068, 54.075146484374955], + [10.917773437500045, 53.99531250000004], + [11.39960937500004, 53.94462890625002], + [12.111328125, 54.168310546875006], + [12.57539062500004, 54.467382812500006], + [13.028613281250017, 54.411035156249994], + [13.448046875000017, 54.14086914062503], + [13.724218750000063, 54.153222656249966], + [13.865527343750074, 53.85336914062498], + [14.258886718750006, 53.729638671874994], + [14.298730468750051, 53.55644531249999], + [14.41455078125, 53.28349609374996], + [14.412304687500011, 53.216748046874955], + [14.410937500000074, 53.19902343749999], + [14.368554687500051, 53.105566406250034], + [14.293164062500068, 53.026757812499966], + [14.138867187500068, 52.93286132812503], + [14.128613281250011, 52.87822265625002], + [14.253710937500017, 52.78251953124996], + [14.514062500000023, 52.645605468750034], + [14.619433593750017, 52.52851562499998], + [14.569726562499994, 52.431103515624955], + [14.554589843750023, 52.35966796874996], + [14.573925781250068, 52.31416015625001], + [14.615625, 52.277636718750045], + [14.679882812500068, 52.25], + [14.752539062500034, 52.08183593750002], + [14.601660156250034, 51.832373046875006], + [14.738671875000051, 51.62714843750004], + [14.7109375, 51.54492187499997], + [14.724707031250063, 51.523876953124955], + [14.90595703125004, 51.463330078124955], + [14.935546875000028, 51.435351562500045], + [14.9638671875, 51.095117187499994], + [14.917480468750057, 51.00874023437498], + [14.814257812499989, 50.871630859375045], + [14.809375, 50.858984375000034], + [14.797460937500034, 50.84233398437502], + [14.766503906250051, 50.81831054687501], + [14.72333984375004, 50.81469726562497], + [14.658203125, 50.832617187500006], + [14.613574218750045, 50.85556640625006], + [14.623828125000017, 50.91474609375004], + [14.595214843750057, 50.91860351562502], + [14.559667968750006, 50.954931640625034], + [14.545703124999989, 50.99394531249999], + [14.319726562500051, 51.037792968749955], + [14.36904296875008, 50.89873046874996], + [13.18115234375, 50.510498046875], + [13.016406250000017, 50.490380859374994], + [12.549023437500011, 50.393408203125034], + [12.3056640625, 50.205712890624994], + [12.27734375, 50.18144531250002], + [12.231152343749983, 50.24487304687497], + [12.174804687500057, 50.28837890624996], + [12.134863281250006, 50.31093750000002], + [12.099218750000034, 50.31098632812504], + [12.089843749999972, 50.30175781250003], + [12.089746093750051, 50.2685546875], + [12.294598214285761, 50.13608119419641], + [12.5125, 49.87744140625], + [12.390527343750051, 49.739648437499994], + [12.68115234375, 49.41450195312501], + [12.91669921875004, 49.33046875000002], + [13.401171875000074, 48.97758789062499], + [13.440722656250045, 48.95556640625003], + [13.547656250000074, 48.95966796874998], + [13.684960937500051, 48.87670898437506], + [13.769921875000051, 48.81596679687502], + [13.814746093750017, 48.76694335937498], + [13.802929687500011, 48.74750976562501], + [13.798828124999972, 48.62167968750006], + [13.785351562499983, 48.58745117187502], + [13.486621093750074, 48.58183593750002], + [13.471679687500028, 48.57182617187502], + [13.459863281250023, 48.564550781250034], + [13.409375, 48.39414062500006], + [13.322851562500006, 48.33125], + [13.215234375000023, 48.301904296874994], + [12.760351562500063, 48.10698242187499], + [12.95351562500008, 47.890625], + [12.897656250000068, 47.721875], + [13.054101562500051, 47.655126953125034], + [13.047949218750034, 47.57915039062502], + [13.031542968750074, 47.50800781250001], + [13.01435546875004, 47.478076171875045], + [12.968066406250017, 47.475683593750006], + [12.878906250000057, 47.506445312500034], + [12.809375, 47.542187499999955], + [12.782812500000034, 47.56416015624998], + [12.781152343750051, 47.590429687500006], + [12.796191406249989, 47.60703125], + [12.771386718750023, 47.63940429687503], + [12.685839843750074, 47.66933593750002], + [12.209277343750074, 47.71826171875003], + [12.196875, 47.709082031250034], + [12.203808593750011, 47.64672851562503], + [12.185644531250063, 47.61953125], + [11.041992187500028, 47.39311523437496], + [10.98085937499999, 47.39814453125001], + [10.893945312500051, 47.470458984375], + [10.870605468750028, 47.500781250000045], + [10.873046874999972, 47.52021484375001], + [10.741601562500023, 47.52412109375001], + [10.65869140625, 47.547216796875006], + [10.482812500000051, 47.54179687499996], + [10.439453125000028, 47.55156249999999], + [10.403906250000063, 47.41699218750003], + [10.369140625, 47.366064453125034], + [10.18300781250008, 47.27880859375003], + [10.200292968750063, 47.36342773437505], + [10.066308593750023, 47.39335937500002], + [10.064575892857171, 47.42369419642856], + [10.059863281250045, 47.44907226562498], + [10.034082031250023, 47.47358398437501], + [9.971582031249994, 47.50532226562498], + [9.839160156250017, 47.55229492187496], + [9.748925781250023, 47.575537109375006], + [9.524023437500034, 47.52421875000002], + [8.572656250000023, 47.775634765625], + [8.435742187500011, 47.73134765625002], + [8.403417968750006, 47.687792968750045], + [8.413281250000068, 47.66269531249998], + [8.451757812500006, 47.65180664062498], + [8.552343750000063, 47.65913085937498], + [8.56708984375004, 47.65190429687502], + [8.57050781250004, 47.63779296874998], + [8.55947265625008, 47.62402343750003], + [8.477636718750034, 47.61269531250002], + [8.454003906249994, 47.59619140625003], + [7.615625, 47.59272460937504], + [7.616601562500023, 48.15678710937502], + [8.134863281250006, 48.97358398437498], + [7.450585937500051, 49.152197265625034], + [6.735449218750006, 49.16059570312498], + [6.344335937500006, 49.45273437499998], + [6.4873046875, 49.798486328124994], + [6.204882812500017, 49.915136718750034], + [6.13818359375, 49.97431640625001], + [6.10976562500008, 50.034375], + [6.116503906250045, 50.120996093749966], + [6.340917968750006, 50.451757812500034], + [5.993945312500017, 50.75043945312504], + [6.048437500000034, 50.90488281250006], + [5.857519531250034, 51.030126953125006], + [6.129980468750034, 51.14741210937501], + [6.198828125000034, 51.45], + [5.948730468750057, 51.80268554687501], + [6.800390625, 51.96738281249998], + [6.724511718749994, 52.080224609374966], + [7.035156250000057, 52.38022460937498], + [6.748828125000074, 52.464013671874994], + [6.710742187500045, 52.61787109374998], + [7.033007812500045, 52.65136718749997], + [7.197265625000028, 53.28227539062499], + [7.074316406250034, 53.477636718750006], + [7.285253906250034, 53.68134765625001], + [8.00927734375, 53.69072265624999], + [8.108496093750063, 53.46767578125002], + [8.245214843750006, 53.44531249999997], + [8.333886718750051, 53.606201171875], + [8.495214843750063, 53.39423828124998], + [8.618945312500045, 53.875], + [9.20556640625, 53.85595703124997], + [9.783984375000074, 53.554638671874955], + [9.31201171875, 53.859130859375], + [8.92041015625, 53.96533203125006], + [8.906640625000023, 54.26079101562502], + [8.625781250000017, 54.35395507812501], + [8.951855468750011, 54.46757812499996], + [8.670312500000023, 54.903417968750034], + [9.739746093750028, 54.82553710937498] + ] + ], + [ + [ + [8.307714843750034, 54.786962890625034], + [8.451464843750017, 55.05537109374998], + [8.3798828125, 54.89985351562501], + [8.629589843750068, 54.891748046874966], + [8.307714843750034, 54.786962890625034] + ] + ] + ] + }, + "properties": { "name": "Germany", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [43.24599609375002, 11.499804687499989], + [42.92275390625002, 10.999316406249989], + [42.557714843750006, 11.080761718749997], + [41.79824218750002, 10.98046875], + [41.79267578125001, 11.68603515625], + [42.378515625, 12.46640625], + [42.40859375000002, 12.494384765625], + [42.45, 12.521337890624991], + [42.47939453125002, 12.513623046874997], + [42.703710937500006, 12.380322265624997], + [42.76748046875002, 12.4228515625], + [42.825292968750006, 12.5693359375], + [42.86591796875001, 12.622802734375], + [42.88330078125, 12.621289062499997], + [43.00566406250002, 12.662304687499997], + [43.11669921875, 12.70859375], + [43.353515625, 12.367041015624991], + [43.38027343750002, 12.091259765624997], + [42.64003906250002, 11.560107421874989], + [42.52177734375002, 11.572167968749994], + [42.58378906250002, 11.496777343749997], + [43.04277343750002, 11.588476562499991], + [43.24599609375002, 11.499804687499989] + ] + ] + }, + "properties": { "name": "Djibouti", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.281689453125, 15.2490234375], + [-61.37539062499999, 15.227294921875], + [-61.45810546874999, 15.633105468750003], + [-61.277246093749994, 15.526708984374991], + [-61.281689453125, 15.2490234375] + ] + ] + }, + "properties": { "name": "Dominica", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [11.361425781250006, 54.891650390625045], + [11.739550781250017, 54.80742187500002], + [11.765917968750074, 54.67944335937506], + [11.457421875000023, 54.628857421874955], + [11.035546875000051, 54.77309570312505], + [11.058593750000028, 54.940576171874966], + [11.361425781250006, 54.891650390625045] + ] + ], + [ + [ + [12.549218750000051, 54.96577148437504], + [12.11884765625004, 54.91440429687506], + [12.274023437500034, 55.064111328124994], + [12.549218750000051, 54.96577148437504] + ] + ], + [ + [ + [10.061230468750068, 54.88637695312502], + [9.80625, 54.90600585937503], + [9.78125, 55.06904296875001], + [10.061230468750068, 54.88637695312502] + ] + ], + [ + [ + [10.734082031250011, 54.750732421875], + [10.621679687500006, 54.851416015625006], + [10.95107421875008, 55.15620117187501], + [10.734082031250011, 54.750732421875] + ] + ], + [ + [ + [15.087695312500017, 55.021875], + [14.684179687500063, 55.10224609375004], + [14.765332031250068, 55.296728515625034], + [15.132617187500017, 55.14453125000003], + [15.087695312500017, 55.021875] + ] + ], + [ + [ + [10.645117187500006, 55.60981445312498], + [10.785253906250034, 55.13339843749998], + [10.44277343750008, 55.04877929687498], + [9.988769531250028, 55.163183593750006], + [9.860644531250045, 55.515478515625034], + [10.645117187500006, 55.60981445312498] + ] + ], + [ + [ + [12.665722656250068, 55.596533203125006], + [12.550878906250034, 55.55625], + [12.59921875, 55.68022460937502], + [12.665722656250068, 55.596533203125006] + ] + ], + [ + [ + [12.56875, 55.785058593749966], + [12.215039062500011, 55.46650390624998], + [12.413085937500028, 55.28618164062502], + [12.089941406250006, 55.18813476562505], + [12.050390625000034, 54.81533203125002], + [11.8623046875, 54.77260742187502], + [11.653808593750057, 55.186914062499966], + [11.286328125000068, 55.20444335937498], + [10.978906250000051, 55.721533203125006], + [11.322265625000028, 55.752539062500006], + [11.627734375000074, 55.95688476562498], + [11.819726562500023, 55.69765625000002], + [11.86640625000004, 55.968164062499966], + [12.218945312499983, 56.11865234374997], + [12.578710937500006, 56.06406250000006], + [12.56875, 55.785058593749966] + ] + ], + [ + [ + [11.052148437500051, 57.25253906250006], + [10.873828125000045, 57.26225585937499], + [11.174511718750011, 57.322900390624994], + [11.052148437500051, 57.25253906250006] + ] + ], + [ + [ + [9.739746093750028, 54.82553710937498], + [8.670312500000023, 54.903417968750034], + [8.61591796875004, 55.41821289062503], + [8.132128906250074, 55.59980468749998], + [8.16396484375008, 56.60688476562498], + [8.671679687500045, 56.49565429687496], + [8.88808593750008, 56.73505859374998], + [9.06708984375004, 56.79384765625005], + [9.196386718750006, 56.70166015625], + [9.2548828125, 57.01171875000003], + [8.992773437499977, 57.01611328125003], + [8.771972656250028, 56.72529296875004], + [8.468359375, 56.66455078125], + [8.284082031250023, 56.85234374999999], + [8.618554687500051, 57.11127929687498], + [9.43359375, 57.17431640625003], + [9.96230468750008, 57.580957031249994], + [10.609960937500034, 57.73691406249998], + [10.282714843750057, 56.620507812499994], + [10.926171875000051, 56.44326171875002], + [10.753417968750028, 56.24199218749999], + [10.31875, 56.212890625], + [10.18300781250008, 55.86518554687504], + [9.903710937500023, 55.84282226562502], + [10.02363281250004, 55.76142578125004], + [9.591113281250017, 55.49321289062502], + [9.670996093750063, 55.26640624999999], + [9.453710937500006, 55.03955078125006], + [9.732324218750023, 54.96801757812506], + [9.739746093750028, 54.82553710937498] + ] + ] + ] + }, + "properties": { "name": "Denmark", "childNum": 10, "cp": [10.2768332, 56.1773879] } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-71.647216796875, 19.195947265624994], + [-71.746484375, 19.285839843749997], + [-71.71147460937499, 19.486572265625], + [-71.75742187499999, 19.688183593749997], + [-71.779248046875, 19.718164062499994], + [-71.6673828125, 19.8486328125], + [-70.95415039062499, 19.913964843749994], + [-70.19384765625, 19.63803710937499], + [-69.95683593749999, 19.671875], + [-69.739404296875, 19.29921875], + [-69.23247070312499, 19.27182617187499], + [-69.60595703125, 19.206494140624997], + [-69.62363281249999, 19.117822265624994], + [-68.684765625, 18.90478515625], + [-68.33916015624999, 18.611523437499997], + [-68.68740234375, 18.21494140624999], + [-68.9349609375, 18.408007812500003], + [-69.27451171874999, 18.43984375], + [-69.770654296875, 18.443554687499997], + [-70.479931640625, 18.21728515625], + [-70.644677734375, 18.336230468750003], + [-71.02783203125, 18.273193359375], + [-71.43896484375, 17.63559570312499], + [-71.63173828125, 17.773632812499997], + [-71.768310546875, 18.03916015624999], + [-71.76376953124999, 18.20395507812499], + [-71.737255859375, 18.270800781250003], + [-71.7619140625, 18.34130859375], + [-71.87255859375, 18.416210937499997], + [-71.940380859375, 18.512597656249994], + [-72.000390625, 18.597900390625], + [-71.98686523437499, 18.6103515625], + [-71.86650390624999, 18.614160156249994], + [-71.74321289062499, 18.73291015625], + [-71.72705078125, 18.80322265625], + [-71.733642578125, 18.856396484374997], + [-71.80712890625, 18.987011718749997], + [-71.647216796875, 19.195947265624994] + ] + ] + }, + "properties": { "name": "Dominican Rep.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [8.207617187500006, 36.518945312499994], + [8.348730468750006, 36.36796875], + [8.318066406250011, 35.654931640624994], + [8.31640625, 35.403125], + [8.35986328125, 35.299609375], + [8.394238281250011, 35.203857421875], + [8.312109375, 35.084619140624994], + [8.27685546875, 34.9794921875], + [8.24560546875, 34.73408203125], + [7.513867187500011, 34.080517578125], + [7.534375, 33.717919921874994], + [7.877246093750017, 33.172119140625], + [8.1125, 33.055322265624994], + [8.333398437500023, 32.543603515624994], + [9.044042968750006, 32.07236328125], + [9.160253906250006, 31.621337890625], + [9.224023437500023, 31.373681640624994], + [9.51875, 30.229394531249994], + [9.310253906250011, 30.115234375], + [9.805273437500006, 29.176953125], + [9.916015625, 27.785693359374996], + [9.74755859375, 27.330859375], + [9.883203125000023, 26.630810546874997], + [9.491406250000011, 26.333740234375], + [9.4482421875, 26.067138671875], + [10.000683593750011, 25.332080078125003], + [10.255859375, 24.591015625], + [10.395898437500023, 24.485595703125], + [10.686132812500006, 24.55136718749999], + [11.507617187500017, 24.314355468749994], + [11.967871093750006, 23.517871093750003], + [7.481738281250017, 20.873095703125003], + [5.836621093750011, 19.479150390624994], + [4.227636718750006, 19.142773437499997], + [3.3564453125, 18.986621093750003], + [3.119726562500006, 19.103173828124994], + [3.255859375, 19.4109375], + [3.130273437500023, 19.85019531249999], + [1.685449218750023, 20.378369140624997], + [1.610644531250017, 20.555566406249994], + [1.165722656250011, 20.817431640625003], + [1.1455078125, 21.102246093749997], + [-1.947900390624994, 23.124804687500003], + [-4.822607421874977, 24.99560546875], + [-8.683349609375, 27.2859375], + [-8.683349609375, 27.656445312499997], + [-8.683349609375, 27.900390625], + [-8.659912109375, 28.718603515625], + [-7.485742187499994, 29.392236328124994], + [-7.427685546874983, 29.425], + [-7.142431640624977, 29.619580078124997], + [-6.855566406249977, 29.601611328124996], + [-6.755126953125, 29.583837890625], + [-6.635351562499977, 29.568798828124997], + [-6.597753906249977, 29.578955078125], + [-6.520556640624989, 29.659863281249997], + [-6.479736328125, 29.820361328124996], + [-6.00429687499999, 29.83125], + [-5.448779296874989, 29.956933593749994], + [-5.293652343749983, 30.058642578124996], + [-5.180126953124983, 30.166162109374994], + [-4.96826171875, 30.465380859374996], + [-4.778515624999983, 30.552392578124994], + [-4.529150390624977, 30.625537109374996], + [-4.322851562499977, 30.698876953124994], + [-4.148779296874977, 30.8095703125], + [-3.626904296874983, 31.000927734374997], + [-3.833398437499994, 31.197802734374996], + [-3.837109374999983, 31.512353515624994], + [-3.768164062499977, 31.68955078125], + [-3.700244140624989, 31.700097656249994], + [-3.604589843749977, 31.686767578125], + [-3.439794921874977, 31.704541015624997], + [-3.017382812499989, 31.834277343749996], + [-2.988232421874983, 31.87421875], + [-2.930859374999983, 32.042529296874996], + [-2.863427734374994, 32.07470703125], + [-1.275341796874983, 32.089013671874994], + [-1.16259765625, 32.399169921875], + [-1.111035156249983, 32.552294921874996], + [-1.188232421875, 32.60849609375], + [-1.29638671875, 32.675683593749994], + [-1.352148437499977, 32.703369140625], + [-1.45, 32.784814453124994], + [-1.510009765625, 32.87763671875], + [-1.550732421874983, 33.073583984375], + [-1.67919921875, 33.318652343749996], + [-1.795605468749983, 34.751904296875], + [-2.131787109374983, 34.970849609374994], + [-2.190771484374977, 35.02978515625], + [-2.219628906249994, 35.10419921875], + [-1.673632812499989, 35.18310546875], + [-0.426123046874977, 35.8615234375], + [-0.048242187499994, 35.8328125], + [0.312207031250011, 36.162353515625], + [0.9716796875, 36.4439453125], + [2.593359375, 36.60068359375], + [2.972851562500011, 36.784472656249996], + [3.779003906250011, 36.89619140625], + [4.758105468750017, 36.896337890625], + [5.29541015625, 36.648242187499996], + [6.486523437500011, 37.085742187499996], + [6.927539062500017, 36.91943359375], + [7.238476562500011, 36.968505859375], + [7.204296875000011, 37.0923828125], + [7.910449218750017, 36.856347656249994], + [8.576562500000023, 36.93720703125], + [8.601269531250011, 36.833935546875], + [8.207617187500006, 36.518945312499994] + ] + ] + }, + "properties": { "name": "Algeria", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-80.131591796875, -2.973144531249957], + [-80.27294921875003, -2.995898437499974], + [-80.22368164062502, -2.753125], + [-80.08076171874995, -2.668847656249966], + [-79.90903320312495, -2.725585937499972], + [-80.131591796875, -2.973144531249957] + ] + ], + [ + [ + [-90.42392578125, -1.339941406250034], + [-90.51953124999994, -1.299121093749974], + [-90.47719726562494, -1.22099609374996], + [-90.42392578125, -1.339941406250034] + ] + ], + [ + [ + [-89.41889648437498, -0.911035156249966], + [-89.60859374999998, -0.888574218750009], + [-89.28784179687503, -0.689843750000023], + [-89.41889648437498, -0.911035156249966] + ] + ], + [ + [ + [-90.33486328125, -0.771582031249977], + [-90.54213867187502, -0.676464843749955], + [-90.53168945312493, -0.581445312499966], + [-90.26938476562498, -0.48466796874996], + [-90.19272460937498, -0.658789062500006], + [-90.33486328125, -0.771582031249977] + ] + ], + [ + [ + [-91.42597656249995, -0.460839843749994], + [-91.61074218749994, -0.44394531250002], + [-91.64667968749998, -0.284472656249946], + [-91.46015625000001, -0.255664062500031], + [-91.42597656249995, -0.460839843749994] + ] + ], + [ + [ + [-90.57392578124993, -0.333984375], + [-90.8677734375, -0.271386718750037], + [-90.78037109374998, -0.160449218749989], + [-90.57392578124993, -0.333984375] + ] + ], + [ + [ + [-91.27216796874998, 0.025146484374986], + [-90.799658203125, -0.752050781249991], + [-90.90551757812497, -0.94052734375002], + [-91.13105468750001, -1.019628906249977], + [-91.41904296874998, -0.996679687500006], + [-91.49541015624999, -0.860937499999977], + [-91.120947265625, -0.559082031250028], + [-91.36918945312493, -0.287207031249977], + [-91.42885742187502, -0.023388671874955], + [-91.59682617187497, 0.002099609374994], + [-91.36137695312496, 0.125830078124977], + [-91.27216796874998, 0.025146484374986] + ] + ], + [ + [ + [-78.90922851562502, 1.252783203124977], + [-78.99169921875003, 1.293212890625043], + [-78.89980468749997, 1.359765625], + [-78.90922851562502, 1.252783203124977] + ] + ], + [ + [ + [-75.28447265624999, -0.10654296875002], + [-75.62626953124999, -0.122851562499974], + [-75.63203125000001, -0.157617187500037], + [-75.56059570312502, -0.200097656249994], + [-75.49106445312498, -0.24833984374996], + [-75.42470703124997, -0.408886718749983], + [-75.259375, -0.59013671874996], + [-75.24960937499998, -0.951855468750026], + [-75.34819335937499, -0.966796874999957], + [-75.38012695312503, -0.94023437499996], + [-75.40805664062503, -0.92431640625], + [-75.42041015624997, -0.962207031250003], + [-75.570556640625, -1.53125], + [-76.08979492187501, -2.133105468749974], + [-76.6791015625, -2.562597656249991], + [-77.860595703125, -2.981640625000011], + [-78.240380859375, -3.472558593750009], + [-78.345361328125, -3.397363281249966], + [-78.64799804687499, -4.248144531250006], + [-78.68603515625003, -4.562402343749994], + [-78.86152343749998, -4.665039062499943], + [-78.90761718749997, -4.714453124999977], + [-78.92578125, -4.770703124999983], + [-78.91420898437497, -4.818652343749974], + [-78.919189453125, -4.858398437499986], + [-78.97539062499999, -4.873242187499997], + [-78.99526367187497, -4.908007812499974], + [-79.03330078124998, -4.96914062499999], + [-79.07626953125003, -4.990625], + [-79.18666992187497, -4.958203124999983], + [-79.26811523437493, -4.957617187499949], + [-79.33095703124997, -4.92783203125002], + [-79.39941406249997, -4.840039062499983], + [-79.45576171874998, -4.766210937499949], + [-79.50190429687495, -4.670605468750011], + [-79.51616210937493, -4.539160156249963], + [-79.57768554687496, -4.50058593750002], + [-79.638525390625, -4.454882812500031], + [-79.71098632812502, -4.467578124999946], + [-79.79726562500002, -4.47636718749996], + [-79.8451171875, -4.445898437499977], + [-79.962890625, -4.390332031250026], + [-80.06352539062499, -4.327539062500023], + [-80.13955078125002, -4.296093750000011], + [-80.19746093750001, -4.311035156249943], + [-80.293359375, -4.416796875], + [-80.38349609374998, -4.46367187499996], + [-80.424169921875, -4.461425781250028], + [-80.47856445312499, -4.430078125000037], + [-80.48847656249995, -4.393652343749991], + [-80.44384765625003, -4.335839843750023], + [-80.35288085937495, -4.208496093750014], + [-80.453759765625, -4.205175781249963], + [-80.48847656249995, -4.165527343749972], + [-80.49345703124999, -4.119140625000014], + [-80.510009765625, -4.06953125000004], + [-80.49013671874994, -4.010058593750003], + [-80.43720703125001, -3.978613281249991], + [-80.30327148437499, -4.005078124999969], + [-80.26689453124993, -3.948828124999963], + [-80.23051757812499, -3.924023437499969], + [-80.19414062499996, -3.905859375], + [-80.24375, -3.576757812500006], + [-80.32465820312498, -3.387890625], + [-79.96333007812501, -3.15771484375], + [-79.72988281249997, -2.579101562499972], + [-79.842138671875, -2.0673828125], + [-79.92558593749996, -2.548535156249969], + [-80.03017578124994, -2.556738281249949], + [-80.00664062499993, -2.353808593750003], + [-80.28471679687502, -2.706738281249955], + [-80.93217773437493, -2.269140624999977], + [-80.76059570312498, -1.934570312500028], + [-80.90239257812499, -1.078906249999974], + [-80.55390624999998, -0.847949218749989], + [-80.45546875, -0.585449218749986], + [-80.282373046875, -0.620507812500023], + [-80.48227539062503, -0.368261718749963], + [-80.046142578125, 0.155371093750048], + [-80.08828124999997, 0.78476562500002], + [-78.89965820312503, 1.20625], + [-78.85966796874996, 1.455371093750031], + [-78.1806640625, 0.968554687499974], + [-77.702880859375, 0.837841796874997], + [-77.46767578124997, 0.636523437500017], + [-77.396337890625, 0.393896484374963], + [-76.49462890624997, 0.23544921875002], + [-76.27060546874998, 0.439404296874997], + [-75.77666015624999, 0.08925781249998], + [-75.28447265624999, -0.10654296875002] + ] + ] + ] + }, + "properties": { "name": "Ecuador", "childNum": 9 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [34.24531250000001, 31.208300781249996], + [34.904296875, 29.47734375], + [34.736425781250006, 29.27060546875], + [34.39970703125002, 28.01601562499999], + [34.22011718750002, 27.764306640624994], + [33.76025390625, 28.04765625], + [33.24775390625001, 28.567724609375], + [32.56572265625002, 29.973974609375], + [32.35976562500002, 29.630664062499996], + [32.89824218750002, 28.565234375], + [33.54707031250001, 27.898144531249997], + [33.5498046875, 27.607373046874997], + [33.84931640625001, 27.184912109375], + [33.959082031250006, 26.6490234375], + [35.19414062500002, 24.475146484375003], + [35.78388671875001, 23.937792968750003], + [35.54082031250002, 23.920654296875], + [35.50439453125, 23.779296875], + [35.697851562500006, 22.946191406249994], + [36.22968750000001, 22.628808593749994], + [36.87138671875002, 21.996728515624994], + [31.434472656250023, 21.995849609375], + [31.486132812500017, 22.14780273437499], + [31.400292968750023, 22.202441406250003], + [31.260644531250023, 22.00229492187499], + [31.092675781250023, 21.994873046875], + [28.036425781250017, 21.995361328125], + [24.980273437500017, 21.995849609375], + [24.980273437500017, 25.5888671875], + [24.980273437500017, 29.181884765625], + [24.703222656250006, 30.201074218749994], + [24.96142578125, 30.678515625], + [24.85273437500001, 31.334814453125], + [25.150488281250006, 31.654980468749997], + [25.382226562500023, 31.51279296875], + [25.89326171875001, 31.620898437499996], + [27.248046875, 31.377880859374997], + [27.5400390625, 31.212695312499996], + [28.51484375000001, 31.050439453124994], + [29.072070312500017, 30.830273437499997], + [29.929785156250006, 31.227490234374997], + [30.22265625, 31.2583984375], + [30.395117187500006, 31.4576171875], + [30.92353515625001, 31.566845703124997], + [30.56298828125, 31.4169921875], + [31.001757812500017, 31.462792968749994], + [31.082910156250023, 31.603320312499996], + [31.5244140625, 31.458251953125], + [31.888964843750017, 31.54140625], + [32.13603515625002, 31.341064453125], + [31.8921875, 31.482470703124996], + [31.77109375, 31.292578125], + [32.10175781250001, 31.092822265624996], + [32.281835937500006, 31.200878906249997], + [32.21621093750002, 31.29375], + [32.60332031250002, 31.06875], + [33.66650390625, 31.130419921874996], + [34.19814453125002, 31.322607421875], + [34.24531250000001, 31.208300781249996] + ] + ] + }, + "properties": { "name": "Egypt", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [40.141210937500006, 15.696142578125034], + [40.399023437500006, 15.579882812500045], + [39.975195312500006, 15.612451171875023], + [39.94746093750004, 15.696142578125034], + [40.07050781250004, 15.676611328125034], + [39.93994140625003, 15.744531250000023], + [39.9567382812501, 15.889404296875057], + [40.141210937500006, 15.696142578125034] + ] + ], + [ + [ + [40.07646484375002, 16.082421875000023], + [40.11005859375004, 15.985742187500051], + [39.99609375000003, 16.04267578125001], + [40.07646484375002, 16.082421875000023] + ] + ], + [ + [ + [40.938574218750006, 13.983105468749997], + [40.82011718750002, 14.111669921874991], + [40.22148437500002, 14.431152343749972], + [39.531835937500006, 14.53671875], + [39.198046875000074, 14.479394531250037], + [39.1354492187501, 14.581884765625034], + [39.07421874999997, 14.628222656249974], + [39.02382812499999, 14.628222656249974], + [38.99570312500006, 14.586865234374983], + [38.81201171875003, 14.482324218750009], + [38.50439453124997, 14.42441406250002], + [38.43144531250002, 14.428613281249994], + [38.221484375000074, 14.649658203124986], + [38.002539062500006, 14.737109375000045], + [37.94345703125006, 14.810546875], + [37.884179687499994, 14.852294921874972], + [37.82031250000003, 14.708496093749986], + [37.70839843750005, 14.45722656250004], + [37.64843750000003, 14.32255859375006], + [37.571191406249994, 14.149072265624966], + [37.546777343749994, 14.143847656249974], + [37.507226562499994, 14.156396484375037], + [37.257226562499994, 14.453759765625051], + [37.024511718750006, 14.271972656250057], + [36.81191406250005, 14.315039062500034], + [36.67910156250005, 14.307568359375026], + [36.542382812499994, 14.25820312499999], + [36.52431640625005, 14.256835937499986], + [36.492285156250006, 14.544335937500023], + [36.470800781250006, 14.736474609375009], + [36.448144531249994, 14.940087890625009], + [36.42675781249997, 15.132080078125043], + [36.566015625, 15.362109375], + [36.9137695312501, 16.296191406250045], + [36.887792968750006, 16.624658203124994], + [36.9787109375001, 16.800585937500045], + [36.9757812500001, 16.866552734375006], + [36.99521484375006, 17.020556640625017], + [37.00898437500004, 17.058886718750017], + [37.06152343749997, 17.061279296875057], + [37.16953125000006, 17.04140625], + [37.41103515625005, 17.061718749999955], + [37.452929687500074, 17.108691406250017], + [37.51015625, 17.28813476562499], + [37.54746093750006, 17.32412109375005], + [37.78242187500004, 17.458007812500057], + [38.253515625, 17.584765625000017], + [38.26728515625004, 17.616699218750057], + [38.28984375000002, 17.637011718750017], + [38.34736328125004, 17.68359375], + [38.37373046875004, 17.717333984375045], + [38.42246093750006, 17.823925781249983], + [38.60947265625006, 18.00507812500004], + [39.03447265625002, 17.085546875000034], + [39.298925781250006, 15.921093750000011], + [39.78554687499999, 15.124853515624991], + [39.86376953124997, 15.470312500000034], + [40.20410156250003, 15.014111328124983], + [41.17646484375004, 14.620312500000054], + [41.65820312499997, 13.983056640624994], + [42.24511718749997, 13.587646484374986], + [42.39931640625005, 13.212597656249969], + [42.522851562499994, 13.221484375], + [42.796191406250074, 12.864257812500057], + [42.96953125000002, 12.808349609375028], + [42.99902343750003, 12.899511718750048], + [43.08291015625005, 12.824609374999966], + [43.11669921874997, 12.708593749999963], + [43.00566406250002, 12.66230468750004], + [42.88330078124997, 12.621289062500026], + [42.86591796875004, 12.622802734374986], + [42.82529296875006, 12.569335937500014], + [42.767480468749994, 12.422851562500014], + [42.70371093750006, 12.380322265625054], + [42.479394531249994, 12.513623046875026], + [42.45, 12.521337890625006], + [42.40859375, 12.494384765625014], + [42.37851562500006, 12.46640625], + [42.28994140625005, 12.570214843750009], + [42.225, 12.661962890624963], + [42.13427734374997, 12.771435546874969], + [41.95214843749997, 12.88232421875], + [41.85957031250004, 13.025878906250028], + [41.76503906250005, 13.183935546874991], + [41.362890625, 13.499804687500031], + [40.938574218750006, 13.983105468749997] + ] + ] + ] + }, + "properties": { "name": "Eritrea", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-17.887939453125, 27.809570312500057], + [-17.984765625000023, 27.646386718750023], + [-18.160546874999937, 27.76147460937503], + [-17.887939453125, 27.809570312500057] + ] + ], + [ + [ + [-15.400585937499955, 28.147363281250023], + [-15.436767578124972, 27.810693359375023], + [-15.71030273437492, 27.784082031250023], + [-15.809472656249966, 27.994482421874977], + [-15.682763671874994, 28.15405273437497], + [-15.400585937499955, 28.147363281250023] + ] + ], + [ + [ + [-17.184667968749977, 28.02197265624997], + [-17.324902343749955, 28.11767578125003], + [-17.25859375, 28.203173828125045], + [-17.103759765624943, 28.111132812500017], + [-17.184667968749977, 28.02197265624997] + ] + ], + [ + [ + [-16.33447265624997, 28.37993164062499], + [-16.41821289062497, 28.15141601562496], + [-16.65800781249999, 28.007177734374977], + [-16.905322265625017, 28.33959960937503], + [-16.12363281249992, 28.57597656249996], + [-16.33447265624997, 28.37993164062499] + ] + ], + [ + [ + [-14.196777343749943, 28.169287109375063], + [-14.332617187500006, 28.056005859374977], + [-14.49179687499992, 28.100927734374977], + [-14.231982421875017, 28.21582031250003], + [-14.003369140624983, 28.706689453125023], + [-13.85722656249996, 28.73803710937503], + [-13.928027343749989, 28.25346679687499], + [-14.196777343749943, 28.169287109375063] + ] + ], + [ + [ + [-17.83427734374999, 28.49321289062496], + [-18.00078124999999, 28.758251953124955], + [-17.928808593749977, 28.844580078125063], + [-17.7265625, 28.724462890625006], + [-17.83427734374999, 28.49321289062496] + ] + ], + [ + [ + [-13.715966796874966, 28.911230468750034], + [-13.85991210937496, 28.869091796874983], + [-13.823632812499966, 29.013330078124966], + [-13.463574218749955, 29.237207031250023], + [-13.477929687499966, 29.00659179687503], + [-13.715966796874966, 28.911230468750034] + ] + ], + [ + [ + [1.593945312500068, 38.672070312499955], + [1.40576171875, 38.670996093750006], + [1.436328125000017, 38.768212890624994], + [1.593945312500068, 38.672070312499955] + ] + ], + [ + [ + [1.445214843750051, 38.91870117187503], + [1.223339843750068, 38.90385742187502], + [1.3486328125, 39.080810546875], + [1.564453125, 39.12104492187504], + [1.623632812499977, 39.03881835937497], + [1.445214843750051, 38.91870117187503] + ] + ], + [ + [ + [3.145312500000017, 39.79008789062499], + [3.461816406250023, 39.69775390625003], + [3.072851562500006, 39.30126953124997], + [2.799804687500057, 39.38505859374999], + [2.700585937500023, 39.54213867187502], + [2.49951171875, 39.47788085937498], + [2.37001953125008, 39.57207031249999], + [3.15869140625, 39.97050781249999], + [3.145312500000017, 39.79008789062499] + ] + ], + [ + [ + [4.293652343750011, 39.84184570312499], + [3.8671875, 39.958740234375], + [3.853417968750051, 40.06303710937502], + [4.22578125000004, 40.032373046874966], + [4.293652343750011, 39.84184570312499] + ] + ], + [ + [ + [-1.794042968749949, 43.407324218750006], + [-1.410693359374932, 43.240087890625034], + [-1.460839843749937, 43.05175781250006], + [-1.300048828124943, 43.10097656250002], + [-0.586425781249943, 42.798974609374966], + [0.631640625000045, 42.689599609374994], + [0.696875, 42.84511718750005], + [1.428320312499977, 42.59589843749998], + [1.414843750000074, 42.54838867187499], + [1.448828124999977, 42.43745117187504], + [1.534082031250051, 42.44169921875002], + [1.7060546875, 42.50332031250005], + [1.859765625000051, 42.457080078125045], + [1.927929687500068, 42.42631835937499], + [2.032714843750028, 42.353515625], + [3.21142578125, 42.43115234375], + [3.248046875, 41.94423828125002], + [3.0048828125, 41.76743164062506], + [2.082617187500063, 41.287402343750045], + [1.032910156250068, 41.06206054687496], + [0.714648437500074, 40.822851562500006], + [0.891113281250057, 40.72236328125004], + [0.59609375000008, 40.614501953125], + [-0.327001953124949, 39.519873046875006], + [-0.204931640624949, 39.062597656250034], + [0.20156250000008, 38.75917968750002], + [-0.520800781249989, 38.317285156249966], + [-0.814648437500011, 37.76992187500002], + [-0.721582031249966, 37.63105468749998], + [-1.327539062499937, 37.561132812500034], + [-1.640966796874949, 37.38696289062497], + [-2.111523437499983, 36.77666015624999], + [-4.366845703124994, 36.71811523437506], + [-4.67412109374996, 36.506445312500006], + [-5.171484374999949, 36.423779296874955], + [-5.3609375, 36.134912109374994], + [-5.62548828125, 36.02592773437499], + [-6.040673828124937, 36.18842773437498], + [-6.38413085937492, 36.63701171874996], + [-6.216796875000028, 36.91357421875], + [-6.396191406249983, 36.831640625], + [-6.863769531250028, 37.27890625], + [-7.406152343749937, 37.17944335937497], + [-7.44394531249992, 37.72827148437497], + [-6.957568359374932, 38.18789062499999], + [-7.106396484374983, 38.181005859375006], + [-7.343017578124943, 38.45742187500002], + [-6.997949218749994, 39.05644531250002], + [-7.53569335937496, 39.66157226562501], + [-7.117675781249972, 39.681689453125045], + [-6.975390624999932, 39.79838867187502], + [-6.896093749999949, 40.02182617187506], + [-7.032617187499966, 40.16791992187498], + [-6.8101562499999, 40.343115234375034], + [-6.928466796874972, 41.009130859375006], + [-6.2125, 41.53203125], + [-6.542187499999955, 41.672509765624994], + [-6.61826171874992, 41.9423828125], + [-7.147119140625023, 41.98115234374998], + [-7.40361328124996, 41.833691406249955], + [-8.152490234374937, 41.81196289062498], + [-8.266064453124983, 42.13740234375001], + [-8.777148437500017, 41.941064453124994], + [-8.887207031249943, 42.105273437500045], + [-8.690917968749943, 42.274169921875], + [-8.815820312499966, 42.285253906250034], + [-8.730029296874989, 42.411718750000034], + [-8.8115234375, 42.64033203124998], + [-9.033105468750023, 42.593847656250006], + [-8.927197265624926, 42.79858398437497], + [-9.235205078124977, 42.97690429687498], + [-9.178076171874977, 43.17402343749998], + [-8.248925781249937, 43.43940429687498], + [-8.256738281249937, 43.57988281249999], + [-8.004687499999932, 43.69438476562496], + [-7.503613281249983, 43.73994140625001], + [-7.060986328124955, 43.55395507812503], + [-5.846679687499943, 43.645068359375045], + [-4.52304687499992, 43.41572265625004], + [-3.604638671874966, 43.51948242187504], + [-3.045605468749926, 43.37158203125], + [-2.875048828125017, 43.454443359375006], + [-2.337109374999926, 43.32802734375002], + [-1.794042968749949, 43.407324218750006] + ] + ] + ] + }, + "properties": { "name": "Spain", "childNum": 12, "cp": [-2.9366964, 40.3438963] } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [22.61738281250004, 58.62124023437502], + [23.323242187500057, 58.45083007812502], + [22.730273437500045, 58.23066406250001], + [22.371679687499977, 58.217138671875006], + [21.996875, 57.93134765624998], + [22.187695312500068, 58.15434570312502], + [21.88212890624999, 58.262353515624994], + [21.862304687500057, 58.497167968750034], + [22.61738281250004, 58.62124023437502] + ] + ], + [ + [ + [23.343554687500017, 58.550341796875045], + [23.10908203125004, 58.65922851562502], + [23.332812500000045, 58.648583984374994], + [23.343554687500017, 58.550341796875045] + ] + ], + [ + [ + [22.923730468750023, 58.826904296875], + [22.54218750000001, 58.68999023437499], + [22.411035156250023, 58.863378906250034], + [22.05625, 58.94360351562506], + [22.6494140625, 59.08710937499998], + [22.90986328125004, 58.99121093749997], + [22.923730468750023, 58.826904296875] + ] + ], + [ + [ + [28.0125, 59.484277343749966], + [28.15107421875004, 59.374414062499966], + [27.434179687500006, 58.787255859374994], + [27.502441406250057, 58.221337890624994], + [27.778515625000068, 57.87070312500006], + [27.542089843750063, 57.799414062500006], + [27.4, 57.66679687499999], + [27.35195312500005, 57.528125], + [26.96601562500001, 57.60913085937506], + [26.532617187499994, 57.53100585937503], + [26.29804687500001, 57.60107421875], + [25.66015625, 57.920166015625], + [25.27265625000001, 58.009375], + [25.11103515625004, 58.06342773437498], + [24.45888671875005, 57.907861328124994], + [24.3625, 57.86616210937501], + [24.322558593750074, 57.87060546875003], + [24.529101562500045, 58.35424804687497], + [24.114843750000034, 58.26611328125006], + [23.767578125000057, 58.36083984374997], + [23.50927734375003, 58.65854492187498], + [23.680761718750063, 58.787158203125074], + [23.43203125, 58.920654296875], + [23.494433593750017, 59.19565429687498], + [24.083398437500023, 59.29189453125005], + [24.38037109375003, 59.47265625], + [25.44375, 59.52114257812502], + [25.50927734374997, 59.63901367187506], + [26.974707031250006, 59.450634765624955], + [28.0125, 59.484277343749966] + ] + ] + ] + }, + "properties": { "name": "Estonia", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [38.43144531250002, 14.428613281249994], + [38.50439453125, 14.424414062499991], + [38.81201171875, 14.482324218749994], + [38.995703125, 14.586865234374997], + [39.02382812500002, 14.628222656250003], + [39.07421875, 14.628222656250003], + [39.13544921875001, 14.581884765624991], + [39.19804687500002, 14.479394531249994], + [39.531835937500006, 14.53671875], + [40.22148437500002, 14.43115234375], + [40.82011718750002, 14.111669921874991], + [40.938574218750006, 13.983105468749997], + [41.362890625, 13.499804687500003], + [41.76503906250002, 13.183935546874991], + [41.85957031250001, 13.02587890625], + [41.9521484375, 12.88232421875], + [42.13427734375, 12.771435546874997], + [42.225, 12.661962890624991], + [42.28994140625002, 12.570214843749994], + [42.378515625, 12.46640625], + [41.79267578125001, 11.68603515625], + [41.79824218750002, 10.98046875], + [42.557714843750006, 11.080761718749997], + [42.92275390625002, 10.999316406249989], + [42.65644531250001, 10.6], + [42.84160156250002, 10.203076171874997], + [43.181640625, 9.879980468749991], + [43.482519531250006, 9.379492187499991], + [43.98378906250002, 9.008837890624989], + [46.97822265625001, 7.9970703125], + [47.97822265625001, 7.9970703125], + [44.940527343750006, 4.912011718749994], + [43.988867187500006, 4.950537109374991], + [43.58349609375, 4.85498046875], + [43.12568359375001, 4.644482421874997], + [42.85664062500001, 4.32421875], + [42.02412109375001, 4.137939453125], + [41.91533203125002, 4.031298828124989], + [41.88398437500001, 3.977734375], + [41.73769531250002, 3.979052734374989], + [41.48193359375, 3.96328125], + [41.37246093750002, 3.946191406249994], + [41.22089843750001, 3.943554687499997], + [41.02080078125002, 4.057470703124991], + [40.765234375, 4.27304687499999], + [39.84218750000002, 3.851464843749994], + [39.79033203125002, 3.754248046874991], + [39.65751953125002, 3.577832031249997], + [39.49443359375002, 3.456103515624989], + [38.608007812500006, 3.60009765625], + [38.45156250000002, 3.604833984374991], + [38.22529296875001, 3.618994140624991], + [38.08613281250001, 3.64882812499999], + [37.15458984375002, 4.254541015624994], + [36.90556640625002, 4.411474609374991], + [36.02197265625, 4.468115234374991], + [35.76308593750002, 4.808007812499994], + [35.75615234375002, 4.950488281249989], + [35.779296875, 5.105566406249991], + [35.80029296875, 5.156933593749997], + [35.74501953125002, 5.343994140625], + [35.325292968750006, 5.364892578124994], + [35.2646484375, 5.412060546874997], + [35.26386718750001, 5.457910156249994], + [35.26835937500002, 5.492285156249991], + [34.98359375000001, 5.858300781249994], + [34.71064453125001, 6.660302734374994], + [34.06425781250002, 7.225732421874994], + [33.902441406250006, 7.509521484375], + [32.99892578125002, 7.899511718749991], + [33.28105468750002, 8.437255859375], + [33.95332031250001, 8.443505859374994], + [34.07275390625, 8.545263671874991], + [34.078125, 9.461523437499991], + [34.31123046875001, 10.190869140624997], + [34.34394531250001, 10.658642578124997], + [34.571875, 10.880175781249989], + [34.77128906250002, 10.746191406249991], + [34.93144531250002, 10.864794921874989], + [35.1123046875, 11.816552734374994], + [35.67021484375002, 12.623730468749997], + [36.12519531250001, 12.75703125], + [36.52431640625002, 14.2568359375], + [36.54238281250002, 14.25820312499999], + [36.67910156250002, 14.307568359374997], + [36.81191406250002, 14.315039062499991], + [37.024511718750006, 14.27197265625], + [37.25722656250002, 14.453759765624994], + [37.50722656250002, 14.156396484374994], + [37.54677734375002, 14.143847656250003], + [37.57119140625002, 14.149072265624994], + [37.6484375, 14.322558593750003], + [37.70839843750002, 14.457226562499997], + [37.8203125, 14.70849609375], + [37.88417968750002, 14.852294921875], + [37.943457031250006, 14.810546875], + [38.002539062500006, 14.737109375], + [38.22148437500002, 14.649658203125], + [38.43144531250002, 14.428613281249994] + ] + ] + }, + "properties": { "name": "Ethiopia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [22.17509765624999, 60.370751953124994], + [22.41552734375003, 60.30336914062505], + [22.36054687500004, 60.165576171875045], + [22.07714843750003, 60.286328124999955], + [22.17509765624999, 60.370751953124994] + ] + ], + [ + [ + [21.450878906250068, 60.529589843750045], + [21.3, 60.47978515625002], + [21.224707031250006, 60.62060546875003], + [21.450878906250068, 60.529589843750045] + ] + ], + [ + [ + [21.2177734375, 63.241308593750034], + [21.415625, 63.19736328125006], + [21.25341796875, 63.152001953124966], + [21.08388671875008, 63.277539062499955], + [21.2177734375, 63.241308593750034] + ] + ], + [ + [ + [24.848242187500034, 64.99101562499999], + [24.576562500000023, 65.04287109375], + [24.970605468750023, 65.05532226562502], + [24.848242187500034, 64.99101562499999] + ] + ], + [ + [ + [28.96582031250003, 69.02197265625], + [28.414062500000057, 68.90415039062506], + [28.77285156250005, 68.84003906249995], + [28.470703125000057, 68.48837890625], + [28.685156250000034, 68.189794921875], + [29.343847656250006, 68.06186523437506], + [29.988085937500017, 67.66826171874999], + [29.066210937500045, 66.89174804687497], + [30.102734375000097, 65.72626953125004], + [29.715917968750063, 65.62456054687502], + [29.608007812500006, 65.248681640625], + [29.826953125000017, 65.14506835937502], + [29.60419921875004, 64.968408203125], + [30.072851562500063, 64.76503906250005], + [30.04189453125005, 64.44335937499997], + [30.513769531250006, 64.2], + [30.50390625000003, 64.02060546875], + [29.991503906250074, 63.73515625000002], + [31.180859375000097, 63.208300781250074], + [31.533984375000017, 62.885400390624994], + [31.18671875000004, 62.48139648437504], + [29.69013671875004, 61.54609375000001], + [27.797656250000074, 60.53613281250003], + [26.53466796874997, 60.412890625000074], + [26.56933593750003, 60.62456054687502], + [26.377734375000074, 60.42407226562503], + [25.955957031250023, 60.474218750000034], + [26.03583984375004, 60.34150390625001], + [25.75800781250004, 60.26752929687504], + [25.65644531250004, 60.33320312499998], + [24.44560546874999, 60.021289062500045], + [23.46357421875004, 59.986230468749994], + [23.021289062500074, 59.81601562500006], + [23.19843750000001, 60.02182617187498], + [22.911718750000063, 60.20971679687497], + [22.749804687500017, 60.057275390624994], + [22.462695312500045, 60.029199218749966], + [22.5849609375, 60.380566406249955], + [21.436035156250057, 60.596386718749955], + [21.605957031250057, 61.59155273437503], + [21.255957031250063, 61.98964843750005], + [21.143847656250045, 62.73999023437506], + [21.650976562500063, 63.039306640625], + [21.545117187499983, 63.204296874999955], + [22.31972656250005, 63.310449218749994], + [22.532324218750034, 63.647851562499994], + [23.598925781250074, 64.04091796874997], + [24.557910156250045, 64.801025390625], + [25.288183593750063, 64.8603515625], + [25.34785156250004, 65.47924804687497], + [24.674902343750006, 65.67070312499999], + [24.628027343750034, 65.85917968750002], + [24.15546875000004, 65.80527343750006], + [23.700292968750034, 66.25263671874998], + [23.988574218750045, 66.81054687500003], + [23.64150390625005, 67.12939453124997], + [23.733593750000068, 67.42290039062499], + [23.454882812500045, 67.46025390625007], + [23.63886718750004, 67.95439453125002], + [22.854101562500034, 68.36733398437502], + [21.99746093750005, 68.52060546874998], + [20.622167968750006, 69.036865234375], + [21.065722656250017, 69.04174804687503], + [21.06611328125001, 69.21411132812497], + [21.59375, 69.273583984375], + [22.410937500000074, 68.719873046875], + [23.324023437500017, 68.64897460937502], + [23.85400390625, 68.80590820312503], + [24.94140625000003, 68.59326171875006], + [25.748339843750017, 68.99013671875], + [26.07246093750004, 69.69155273437497], + [26.525390625000057, 69.91503906250003], + [27.127539062500063, 69.90649414062497], + [27.747851562500045, 70.06484375], + [29.14160156250003, 69.67143554687505], + [29.33339843750005, 69.47299804687503], + [28.846289062500006, 69.17690429687502], + [28.96582031250003, 69.02197265625] + ] + ] + ] + }, + "properties": { "name": "Finland", "childNum": 5 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [178.48789062500018, -18.97412109375], + [177.95869140624998, -19.121582031250014], + [178.33427734375013, -18.93447265625001], + [178.48789062500018, -18.97412109375] + ] + ], + [ + [ + [179.34931640625015, -18.10234375000003], + [179.25351562500018, -18.030566406249974], + [179.30644531250013, -17.944042968750026], + [179.34931640625015, -18.10234375000003] + ] + ], + [ + [ + [178.28017578124994, -17.37197265625001], + [178.59160156249996, -17.651464843750006], + [178.66767578125004, -18.080859375], + [177.95546875000005, -18.264062500000023], + [177.32138671875, -18.077539062500037], + [177.26396484375007, -17.86347656250004], + [177.5044921875, -17.539550781250043], + [177.81796875000012, -17.38847656249999], + [178.28017578124994, -17.37197265625001] + ] + ], + [ + [ + [180, -16.96308593750001], + [179.89697265625003, -16.96406250000004], + [180, -16.785742187500034], + [180, -16.96308593750001] + ] + ], + [ + [ + [-179.97490234374996, -16.92480468750003], + [-180, -16.96298828124999], + [-180, -16.907812500000034], + [-180, -16.82431640624999], + [-180, -16.78554687499999], + [-179.86098632812502, -16.68828124999999], + [-179.97490234374996, -16.92480468750003] + ] + ], + [ + [ + [-179.92944335937503, -16.502832031250037], + [-179.999951171875, -16.540039062499986], + [-179.900927734375, -16.431542968749994], + [-179.92944335937503, -16.502832031250037] + ] + ], + [ + [ + [179.99921875000004, -16.168554687499977], + [179.56416015625004, -16.636914062499997], + [179.56816406249996, -16.747460937499966], + [179.93037109375004, -16.51943359375005], + [179.9279296875001, -16.74443359374996], + [179.41933593750005, -16.80654296875001], + [179.20234375000004, -16.71269531249999], + [179.00683593750003, -16.90019531249999], + [178.70664062500018, -16.97617187500002], + [178.4974609375, -16.78789062500003], + [178.58359375000012, -16.621875], + [178.80507812499994, -16.631445312500034], + [179.55175781250003, -16.249902343750023], + [180, -16.15292968749999], + [179.99921875000004, -16.168554687499977] + ] + ] + ] + }, + "properties": { "name": "Fiji", "childNum": 7 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-61.01875, -51.7857421875], + [-60.87597656250003, -51.79423828125004], + [-60.94755859374996, -51.94628906250002], + [-61.14501953125003, -51.83945312500001], + [-61.01875, -51.7857421875] + ] + ], + [ + [ + [-60.28623046874995, -51.461914062500014], + [-59.38759765625002, -51.35996093750003], + [-59.26806640625, -51.42753906250003], + [-59.92138671874997, -51.969531250000045], + [-60.246337890625, -51.98642578125003], + [-60.35346679687498, -52.13994140625004], + [-60.686376953125034, -52.18837890624996], + [-60.96142578125003, -52.05732421874999], + [-60.23847656249998, -51.771972656250036], + [-60.58251953125, -51.71269531250004], + [-60.24516601562493, -51.638867187500004], + [-60.56845703124998, -51.357812499999945], + [-60.28623046874995, -51.461914062500014] + ] + ], + [ + [ + [-60.11171875000002, -51.39589843749998], + [-60.275341796874955, -51.28056640625002], + [-60.06982421875, -51.307910156249996], + [-60.11171875000002, -51.39589843749998] + ] + ], + [ + [ + [-58.85019531249995, -51.26992187499998], + [-58.42583007812502, -51.32421875000003], + [-58.508935546874994, -51.48359375], + [-58.271582031250034, -51.57470703124999], + [-58.25922851562501, -51.417089843750034], + [-57.976513671874955, -51.384375], + [-57.80849609375002, -51.51796875], + [-57.96044921874997, -51.58320312500003], + [-57.79179687499999, -51.63613281249998], + [-58.68349609375002, -51.93623046875001], + [-58.65278320312498, -52.09921875], + [-59.19584960937496, -52.01767578125], + [-59.06801757812502, -52.17304687500003], + [-59.341503906249955, -52.19599609375], + [-59.395654296874966, -52.308007812499994], + [-59.64873046875002, -52.134375], + [-59.57080078124994, -51.92539062500003], + [-59.05952148437498, -51.685449218749994], + [-59.09663085937498, -51.49140624999998], + [-58.85019531249995, -51.26992187499998] + ] + ] + ] + }, + "properties": { "name": "Falkland Is.", "childNum": 4 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [55.79736328125003, -21.33935546875003], + [55.36269531250005, -21.27363281250004], + [55.23281250000005, -21.05839843749999], + [55.311328125000074, -20.90410156249999], + [55.661914062500074, -20.90625], + [55.8390625000001, -21.13857421874998], + [55.79736328125003, -21.33935546875003] + ] + ], + [ + [ + [45.180273437500006, -12.97675781250004], + [45.069433593750006, -12.895605468750034], + [45.09238281250006, -12.653027343749997], + [45.22314453124997, -12.752148437500026], + [45.180273437500006, -12.97675781250004] + ] + ], + [ + [ + [-51.65253906249998, 4.061279296874972], + [-52.327880859375, 3.18173828125002], + [-52.58300781250003, 2.528906249999977], + [-52.90346679687502, 2.211523437499977], + [-53.76777343749998, 2.354833984375048], + [-54.13007812499998, 2.121044921875026], + [-54.43310546875, 2.207519531250057], + [-54.51508789062498, 2.245458984374963], + [-54.55048828125001, 2.293066406249991], + [-54.59194335937502, 2.313769531250031], + [-54.61625976562499, 2.326757812500006], + [-54.60473632812497, 2.335791015624991], + [-54.56840820312502, 2.342578125000031], + [-54.53593749999999, 2.343310546875003], + [-54.48554687500001, 2.416113281250006], + [-54.402001953124966, 2.46152343750002], + [-54.25673828125002, 2.713720703124977], + [-54.19550781249998, 2.817871093750057], + [-54.17070312499999, 2.993603515624969], + [-54.203125, 3.138183593750028], + [-54.18803710937499, 3.178759765625031], + [-54.063183593749955, 3.353320312499989], + [-54.00957031249993, 3.448535156250017], + [-54.03422851562499, 3.62939453125], + [-54.350732421874994, 4.054101562500023], + [-54.47968749999998, 4.836523437499991], + [-53.91992187499997, 5.768994140624983], + [-52.899316406249966, 5.425048828124986], + [-52.29052734375003, 4.942187500000031], + [-52.324609374999966, 4.770898437500037], + [-52.21997070312494, 4.862792968750014], + [-52.05810546875003, 4.717382812499963], + [-52.00292968749997, 4.352294921875014], + [-51.82753906250002, 4.635693359375026], + [-51.65253906249998, 4.061279296874972] + ] + ], + [ + [ + [-60.826269531250006, 14.494482421874991], + [-61.063720703125, 14.467089843750017], + [-61.01132812499998, 14.601904296875034], + [-61.21333007812501, 14.848583984375011], + [-60.927148437499966, 14.755175781249989], + [-60.826269531250006, 14.494482421874991] + ] + ], + [ + [ + [-61.23046875000003, 15.889941406250074], + [-61.310742187499955, 15.894677734374966], + [-61.25, 16.006298828124983], + [-61.23046875000003, 15.889941406250074] + ] + ], + [ + [ + [-61.58955078125001, 16.006933593750006], + [-61.759423828124966, 16.062060546875045], + [-61.74804687499997, 16.355273437500017], + [-61.55234374999998, 16.270898437499966], + [-61.58955078125001, 16.006933593750006] + ] + ], + [ + [ + [-61.3271484375, 16.230419921874983], + [-61.522167968749955, 16.22802734375003], + [-61.47119140624994, 16.506640625000045], + [-61.17260742187497, 16.25610351562497], + [-61.3271484375, 16.230419921874983] + ] + ], + [ + [ + [9.480371093750023, 42.80541992187503], + [9.550683593750051, 42.12973632812506], + [9.186132812500034, 41.38491210937502], + [8.80751953125008, 41.58837890625], + [8.886816406249977, 41.70068359375003], + [8.621875, 41.93071289062502], + [8.700976562500045, 42.09560546875002], + [8.565625, 42.35771484374996], + [8.81484375000008, 42.60791015625003], + [9.313378906250023, 42.71318359374999], + [9.363183593750051, 43.01738281249996], + [9.480371093750023, 42.80541992187503] + ] + ], + [ + [ + [-1.17832031249992, 45.904052734375], + [-1.213574218750011, 45.81660156250004], + [-1.388671874999972, 46.05039062500006], + [-1.17832031249992, 45.904052734375] + ] + ], + [ + [ + [5.789746093749983, 49.53828125000001], + [5.823437500000011, 49.50507812499998], + [5.9013671875, 49.48974609374997], + [5.928906250000011, 49.47753906249997], + [5.959472656250028, 49.45463867187502], + [6.01142578125004, 49.44545898437502], + [6.074121093750023, 49.45463867187502], + [6.119921875000017, 49.485205078125034], + [6.181054687500051, 49.498925781249966], + [6.344335937500006, 49.45273437499998], + [6.735449218750006, 49.16059570312498], + [7.450585937500051, 49.152197265625034], + [8.134863281250006, 48.97358398437498], + [7.616601562500023, 48.15678710937502], + [7.615625, 47.59272460937504], + [7.343164062499994, 47.43310546875003], + [7.136035156249989, 47.489843750000034], + [6.968359375000034, 47.453222656250034], + [6.900390625000028, 47.39423828125001], + [7.000585937500034, 47.339453125000034], + [7.000585937500034, 47.32250976562506], + [6.978515625000057, 47.30205078124996], + [6.95205078125008, 47.26718750000006], + [6.820703125000051, 47.163183593750006], + [6.688085937500034, 47.05825195312505], + [6.66689453125008, 47.026513671874966], + [6.624804687500017, 47.00434570312498], + [6.45625, 46.948339843750034], + [6.438646763392874, 46.774418247767855], + [6.129687500000045, 46.56699218750006], + [6.118111049107182, 46.447459542410726], + [6.095898437500011, 46.279394531250006], + [5.970019531250045, 46.214697265625034], + [5.971484375000074, 46.151220703125006], + [6.006640625000045, 46.14233398437506], + [6.086621093750068, 46.14702148437502], + [6.19941406250004, 46.19306640624998], + [6.234667968750045, 46.332617187500006], + [6.321875, 46.39370117187502], + [6.428906250000011, 46.43051757812506], + [6.578222656250034, 46.437353515625034], + [6.758105468750017, 46.41577148437497], + [6.772070312500006, 46.16513671874998], + [6.897265625000017, 46.05175781249997], + [6.953710937500063, 46.017138671875045], + [7.00390625, 45.95883789062506], + [7.021093750000034, 45.92578124999997], + [6.790917968750023, 45.740869140624966], + [7.146386718750051, 45.381738281249994], + [7.07832031250004, 45.23994140624998], + [6.634765625000028, 45.06816406249996], + [6.99267578125, 44.82729492187502], + [6.900195312499989, 44.33574218749996], + [7.318554687500068, 44.13798828125002], + [7.637207031250057, 44.16484375], + [7.4931640625, 43.767138671875045], + [6.570214843750023, 43.199072265625034], + [6.115917968750011, 43.07236328124998], + [5.406542968750074, 43.228515625], + [5.05976562500004, 43.44453125000004], + [4.712109375000011, 43.373291015625], + [3.910839843750011, 43.563085937500034], + [3.258886718750063, 43.193212890625006], + [3.051757812500057, 42.915136718750006], + [3.21142578125, 42.43115234375], + [2.032714843750028, 42.353515625], + [1.927929687500068, 42.42631835937499], + [1.859765625000051, 42.457080078125045], + [1.7060546875, 42.50332031250005], + [1.709863281250051, 42.604443359374955], + [1.568164062500045, 42.63500976562506], + [1.501367187500023, 42.64272460937502], + [1.428320312499977, 42.59589843749998], + [0.696875, 42.84511718750005], + [0.631640625000045, 42.689599609374994], + [-0.586425781249943, 42.798974609374966], + [-1.300048828124943, 43.10097656250002], + [-1.460839843749937, 43.05175781250006], + [-1.410693359374932, 43.240087890625034], + [-1.794042968749949, 43.407324218750006], + [-1.484863281249943, 43.56376953124999], + [-1.245507812499937, 44.55986328124999], + [-1.07695312499996, 44.68984375], + [-1.152880859374989, 44.764013671875006], + [-1.245214843749977, 44.66669921874998], + [-1.081005859374983, 45.532421874999955], + [-0.548486328124966, 45.00058593750006], + [-0.790771484375028, 45.46801757812497], + [-1.195996093749983, 45.714453125], + [-1.03173828125, 45.741064453125006], + [-1.14628906249996, 46.311376953125034], + [-1.786523437499937, 46.51484375000001], + [-2.059375, 46.81030273437497], + [-2.01889648437492, 47.03764648437502], + [-2.197070312499989, 47.16293945312506], + [-2.027587890625028, 47.27358398437502], + [-1.742529296874949, 47.21596679687502], + [-1.97539062499996, 47.31069335937505], + [-2.503125, 47.31206054687496], + [-2.427685546874983, 47.47089843749998], + [-2.770312499999989, 47.513867187499955], + [-2.787207031249949, 47.62553710937496], + [-4.312109374999949, 47.82290039062502], + [-4.678808593749949, 48.03950195312501], + [-4.32944335937492, 48.169970703125045], + [-4.577148437499943, 48.2900390625], + [-4.241406249999926, 48.30366210937501], + [-4.719384765624966, 48.363134765625034], + [-4.7625, 48.45024414062502], + [-4.531201171874983, 48.61997070312506], + [-3.231445312499972, 48.84082031250003], + [-2.692333984374983, 48.53681640624998], + [-2.446191406249937, 48.64829101562506], + [-2.00371093749996, 48.58208007812499], + [-1.905712890624955, 48.69711914062506], + [-1.376464843749972, 48.65258789062503], + [-1.565478515624932, 48.805517578125034], + [-1.583105468749977, 49.20239257812506], + [-1.856445312499972, 49.68378906249998], + [-1.258642578124949, 49.68017578125006], + [-1.138525390624977, 49.38789062500001], + [-0.163476562499937, 49.296777343749994], + [0.41689453125008, 49.448388671874994], + [0.129394531250028, 49.508447265624966], + [0.186718749999983, 49.703027343749994], + [1.245507812500051, 49.99824218750001], + [1.5927734375, 50.25219726562506], + [1.672265625000023, 50.885009765625], + [2.52490234375, 51.097119140624955], + [2.759375, 50.750634765624994], + [3.10683593750008, 50.779443359374994], + [3.27333984375008, 50.53154296875002], + [3.595410156250068, 50.47734374999999], + [3.689355468750023, 50.30605468750002], + [4.174609375000017, 50.24648437500005], + [4.149316406250023, 49.971582031249994], + [4.545019531250063, 49.96025390624999], + [4.818652343750045, 50.153173828125034], + [4.867578125000051, 49.78813476562502], + [5.50732421875, 49.51088867187502], + [5.789746093749983, 49.53828125000001] + ] + ] + ] + }, + "properties": { "name": "France", "childNum": 10, "cp": [2.8719426, 46.8222422] } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-7.186865234374949, 62.139306640624966], + [-7.116796874999977, 62.046826171874955], + [-7.379101562499926, 62.07480468749998], + [-7.186865234374949, 62.139306640624966] + ] + ], + [ + [ + [-6.631054687499955, 62.22788085937498], + [-6.655810546874932, 62.09360351562498], + [-6.840527343749983, 62.119287109374994], + [-6.725195312499949, 61.95146484374999], + [-7.17216796874996, 62.28559570312501], + [-6.631054687499955, 62.22788085937498] + ] + ], + [ + [ + [-6.406054687499932, 62.258642578125034], + [-6.544140624999926, 62.20561523437499], + [-6.554589843749994, 62.35566406250001], + [-6.406054687499932, 62.258642578125034] + ] + ] + ] + }, + "properties": { "name": "Faeroe Is.", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [158.31484375, 6.813671875], + [158.18339843750002, 6.801269531250057], + [158.13476562499997, 6.944824218749986], + [158.29462890625004, 6.951074218750023], + [158.31484375, 6.813671875] + ] + ], + [ + [ + [138.14267578125006, 9.50068359375004], + [138.06708984375004, 9.419042968750006], + [138.18583984375007, 9.593310546874989], + [138.14267578125006, 9.50068359375004] + ] + ] + ] + }, + "properties": { "name": "Micronesia", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [13.293554687500006, 2.161572265624997], + [13.172167968750017, 1.78857421875], + [13.21630859375, 1.2484375], + [13.851367187500017, 1.41875], + [14.180859375000011, 1.370214843749991], + [14.429882812500011, 0.901464843749991], + [14.32421875, 0.62421875], + [13.949609375000023, 0.353808593749989], + [13.860058593750011, -0.203320312500011], + [14.47412109375, -0.573437500000011], + [14.383984375000011, -1.890039062500009], + [14.162890625000017, -2.217578125], + [14.199804687500006, -2.354199218750011], + [13.993847656250011, -2.490625], + [13.886914062500011, -2.465429687500006], + [13.733789062500023, -2.138476562500003], + [13.464941406250006, -2.395410156250009], + [12.991992187500017, -2.313378906250009], + [12.793554687500006, -1.931835937500011], + [12.590429687500006, -1.826855468750011], + [12.43212890625, -1.928906250000011], + [12.446386718750006, -2.329980468750009], + [12.064453125, -2.41259765625], + [11.60546875, -2.342578125], + [11.537792968750011, -2.83671875], + [11.760156250000023, -2.983105468750011], + [11.715429687500006, -3.176953125000011], + [11.934179687500006, -3.318554687500011], + [11.8798828125, -3.665917968750009], + [11.685742187500011, -3.68203125], + [11.504296875000023, -3.5203125], + [11.234472656250006, -3.690820312500009], + [11.130175781250017, -3.916308593750003], + [10.34765625, -3.013085937500009], + [9.722070312500023, -2.467578125], + [10.06201171875, -2.549902343750006], + [9.624609375, -2.367089843750009], + [9.298925781250006, -1.903027343750011], + [9.483203125000017, -1.894628906250006], + [9.265625, -1.825097656250009], + [9.036328125000011, -1.308886718750003], + [9.31884765625, -1.632031250000011], + [9.501074218750006, -1.55517578125], + [9.295800781250023, -1.515234375], + [9.3466796875, -1.325], + [9.203808593750011, -1.382421875], + [9.064648437500011, -1.29833984375], + [8.703125, -0.591015625000011], + [8.946386718750006, -0.688769531250003], + [9.296679687500017, -0.351269531250011], + [9.354882812500023, 0.343603515624991], + [9.468164062500023, 0.15976562499999], + [9.796777343750023, 0.044238281249989], + [10.00146484375, 0.194970703124994], + [9.546484375, 0.295947265624989], + [9.324804687500006, 0.552099609374991], + [9.495312500000011, 0.664843749999989], + [9.617968750000017, 0.576513671874991], + [9.5908203125, 1.031982421875], + [9.636132812500023, 1.046679687499989], + [9.676464843750011, 1.07470703125], + [9.70458984375, 1.079980468749994], + [9.760546875000017, 1.07470703125], + [9.788671875, 1.025683593749989], + [9.803906250000011, 0.998730468749997], + [9.90673828125, 0.960107421874994], + [11.335351562500023, 0.999707031249997], + [11.332324218750017, 1.528369140624989], + [11.328710937500006, 2.167431640624997], + [11.348437500000017, 2.299707031249994], + [11.558984375000023, 2.302197265624997], + [13.2203125, 2.256445312499991], + [13.293554687500006, 2.161572265624997] + ] + ] + }, + "properties": { "name": "Gabon", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-1.065576171874966, 50.69023437500002], + [-1.25146484375, 50.58881835937498], + [-1.563427734374955, 50.666113281250006], + [-1.31279296874996, 50.77348632812502], + [-1.065576171874966, 50.69023437500002] + ] + ], + [ + [ + [-4.196777343749972, 53.321435546874966], + [-4.04936523437496, 53.30576171874998], + [-4.373046875, 53.13417968750002], + [-4.56787109375, 53.386474609375], + [-4.315087890625023, 53.41723632812503], + [-4.196777343749972, 53.321435546874966] + ] + ], + [ + [ + [-6.218017578125, 54.08872070312506], + [-6.649804687499937, 54.05864257812496], + [-7.007714843749937, 54.40668945312501], + [-7.324511718750017, 54.13344726562502], + [-7.606542968750006, 54.14384765625002], + [-8.118261718749977, 54.41425781250004], + [-7.75439453125, 54.59492187499998], + [-7.910595703124955, 54.698339843750006], + [-7.55039062499992, 54.767968749999966], + [-7.218652343749937, 55.09199218749998], + [-6.475048828124955, 55.24101562499999], + [-6.035791015624994, 55.14453125000003], + [-5.71684570312496, 54.817480468750034], + [-5.878613281249955, 54.64130859375001], + [-5.582519531249943, 54.66342773437498], + [-5.470410156249926, 54.500195312499955], + [-5.671093749999955, 54.54975585937501], + [-5.60678710937492, 54.272558593750034], + [-6.019042968749972, 54.05126953124997], + [-6.218017578125, 54.08872070312506] + ] + ], + [ + [ + [-5.105419921875011, 55.448828125000034], + [-5.331494140624955, 55.481054687500034], + [-5.318115234375, 55.709179687499955], + [-5.105419921875011, 55.448828125000034] + ] + ], + [ + [ + [-6.128906249999972, 55.93056640625002], + [-6.055322265624994, 55.69531249999997], + [-6.305078124999966, 55.60693359375], + [-6.286425781249989, 55.77250976562499], + [-6.491357421874994, 55.697314453125045], + [-6.462841796874955, 55.808251953124994], + [-6.128906249999972, 55.93056640625002] + ] + ], + [ + [ + [-5.970068359374949, 55.814550781250034], + [-6.071972656250011, 55.893115234375045], + [-5.72514648437496, 56.118554687499966], + [-5.970068359374949, 55.814550781250034] + ] + ], + [ + [ + [-5.77788085937496, 56.344335937500034], + [-6.313427734374983, 56.29365234375001], + [-6.138867187499955, 56.490625], + [-6.286328124999983, 56.61186523437502], + [-6.102734374999955, 56.645654296874966], + [-5.760839843749949, 56.49067382812501], + [-5.77788085937496, 56.344335937500034] + ] + ], + [ + [ + [-7.249853515624977, 57.115332031250006], + [-7.410546874999937, 57.38110351562506], + [-7.26713867187496, 57.37177734375001], + [-7.249853515624977, 57.115332031250006] + ] + ], + [ + [ + [-6.144726562499983, 57.50498046874998], + [-6.135546874999989, 57.31425781250002], + [-5.672460937499977, 57.252685546875], + [-5.94907226562492, 57.045166015625], + [-6.034375, 57.20122070312499], + [-6.322705078124926, 57.20249023437498], + [-6.761132812499994, 57.4423828125], + [-6.305957031249989, 57.67197265624998], + [-6.144726562499983, 57.50498046874998] + ] + ], + [ + [ + [-7.205566406250028, 57.682958984375006], + [-7.182617187499972, 57.53330078125006], + [-7.514746093749949, 57.60195312500002], + [-7.205566406250028, 57.682958984375006] + ] + ], + [ + [ + [-6.198681640624983, 58.36328125000003], + [-6.554589843749994, 58.092871093750006], + [-6.425195312499937, 58.02128906249999], + [-6.983105468749983, 57.75], + [-7.083447265624926, 57.81376953124999], + [-6.856835937499937, 57.92353515624998], + [-7.085253906249932, 58.18217773437499], + [-6.726464843749937, 58.189404296874955], + [-6.776464843750006, 58.30151367187497], + [-6.237451171874966, 58.50283203125005], + [-6.198681640624983, 58.36328125000003] + ] + ], + [ + [ + [-3.109667968749932, 58.515478515625034], + [-3.212353515624983, 58.32124023437501], + [-3.99003906249996, 57.95903320312502], + [-4.035595703124926, 57.85200195312498], + [-3.857128906249983, 57.81855468750001], + [-4.134521484375, 57.57773437500006], + [-3.402783203124955, 57.708251953125], + [-2.074072265624977, 57.70239257812506], + [-1.780664062499994, 57.474023437499966], + [-2.592675781249937, 56.56157226562499], + [-3.309960937499966, 56.36347656250004], + [-2.885156249999937, 56.397509765625045], + [-2.674267578124955, 56.25341796875], + [-3.362255859374955, 56.02763671875002], + [-3.789062499999972, 56.09521484375], + [-3.048730468749937, 55.951953125000045], + [-2.599316406249955, 56.02729492187501], + [-2.14707031249992, 55.90297851562502], + [-1.655371093749949, 55.57036132812502], + [-1.232421874999943, 54.703710937500034], + [-0.084375, 54.118066406249994], + [-0.20556640625, 54.021728515625], + [0.115332031250006, 53.609277343749994], + [-0.270019531249972, 53.73676757812504], + [-0.659912109375, 53.72402343750002], + [-0.293701171875, 53.69233398437504], + [0.270996093750028, 53.33549804687499], + [0.355761718750045, 53.15996093750002], + [0.0458984375, 52.90561523437498], + [0.279785156250028, 52.80869140625006], + [0.55878906250004, 52.96694335937505], + [1.05556640625008, 52.95898437500003], + [1.656738281249972, 52.753710937500045], + [1.74658203125, 52.46899414062503], + [1.59140625, 52.11977539062502], + [1.232421875000057, 51.97124023437496], + [1.188476562500057, 51.803369140624966], + [0.752246093750017, 51.729589843750034], + [0.890917968750017, 51.571435546874966], + [0.42451171875004, 51.465625], + [1.414941406250023, 51.36328125], + [1.397558593750034, 51.18203125000002], + [0.960156250000011, 50.92587890624998], + [0.299707031249994, 50.775976562500006], + [-0.785253906249949, 50.76542968749999], + [-1.416455078124955, 50.896875], + [-1.334472656249943, 50.82080078124997], + [-1.516748046874937, 50.747460937499966], + [-2.031054687499932, 50.72539062499999], + [-2.035839843749926, 50.603076171875045], + [-2.999414062499937, 50.71660156249999], + [-3.40458984374996, 50.63242187499998], + [-3.679785156250006, 50.239941406249955], + [-4.194580078124972, 50.39331054687503], + [-4.727978515624926, 50.29047851562504], + [-5.11850585937492, 50.038330078125], + [-5.622119140624932, 50.05068359375002], + [-4.188183593749926, 51.18852539062502], + [-3.135986328124972, 51.20502929687501], + [-2.433056640624926, 51.74072265625], + [-3.293115234374994, 51.390429687500045], + [-3.890771484374994, 51.591650390625006], + [-4.234570312499955, 51.56909179687503], + [-4.091015624999926, 51.65991210937506], + [-4.38627929687496, 51.74106445312506], + [-4.902294921874926, 51.626269531250045], + [-5.168359374999937, 51.74072265625], + [-5.183349609374972, 51.94965820312501], + [-4.217724609374983, 52.277441406250006], + [-3.980322265624949, 52.54174804687503], + [-4.101464843750023, 52.915478515624955], + [-4.683056640624926, 52.80615234374997], + [-4.268554687499943, 53.14453125], + [-3.427734374999972, 53.34067382812498], + [-3.097558593749937, 53.260302734375045], + [-3.064746093749932, 53.426855468750034], + [-2.74951171875, 53.310205078124994], + [-3.064599609374994, 53.512841796874966], + [-2.84648437499996, 54.135302734375045], + [-3.165966796874955, 54.12792968750006], + [-3.56938476562496, 54.46757812499996], + [-3.464599609374943, 54.77309570312505], + [-3.036230468749977, 54.95307617187501], + [-3.550439453124937, 54.94741210937502], + [-3.957910156249994, 54.780957031249955], + [-4.818066406249983, 54.84614257812501], + [-4.911230468749949, 54.68945312500006], + [-5.032324218749949, 54.76137695312505], + [-5.172705078124949, 54.98588867187496], + [-4.676757812499972, 55.50131835937498], + [-4.871679687499977, 55.87392578125005], + [-4.58408203124992, 55.93867187500001], + [-4.844091796874949, 56.05117187499999], + [-4.80029296875, 56.158349609374994], + [-5.228222656249983, 55.886328125], + [-5.084326171874977, 56.197460937499955], + [-5.41044921874996, 55.995361328125], + [-5.55644531249996, 55.389599609374955], + [-5.730664062499926, 55.33413085937502], + [-5.504492187499949, 55.80239257812502], + [-5.609570312499955, 56.055273437500034], + [-5.188378906249937, 56.75805664062503], + [-5.652441406249977, 56.531982421875], + [-6.133691406249966, 56.706689453124966], + [-5.730615234374994, 56.853076171875045], + [-5.86142578124992, 56.902685546875006], + [-5.561914062499994, 57.23271484375002], + [-5.794921874999972, 57.37880859375002], + [-5.581787109374972, 57.546777343749966], + [-5.744921874999989, 57.668310546875034], + [-5.608349609374955, 57.88134765625], + [-5.157226562499972, 57.88134765625], + [-5.413183593750006, 58.06972656250002], + [-5.338281250000023, 58.23872070312498], + [-5.008300781250028, 58.262646484374955], + [-5.016748046874966, 58.566552734374966], + [-4.433251953124937, 58.51284179687505], + [-3.25913085937492, 58.65], + [-3.053076171874949, 58.63481445312502], + [-3.109667968749932, 58.515478515625034] + ] + ], + [ + [ + [-3.057421874999932, 59.02963867187498], + [-2.793017578124989, 58.906933593749955], + [-3.331640624999949, 58.97124023437499], + [-3.31035156249996, 59.13081054687498], + [-3.057421874999932, 59.02963867187498] + ] + ], + [ + [ + [-1.30810546875, 60.5375], + [-1.052441406249955, 60.44448242187502], + [-1.299462890624994, 59.87866210937503], + [-1.290917968749937, 60.153466796874966], + [-1.663769531249983, 60.282519531250074], + [-1.374609374999949, 60.33291015625002], + [-1.571777343749972, 60.494433593750074], + [-1.363964843750011, 60.60957031249998], + [-1.30810546875, 60.5375] + ] + ] + ] + }, + "properties": { "name": "United Kingdom", "childNum": 14, "cp": [-2.5830348, 54.4598409] } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [46.30546875000002, 41.507714843749994], + [46.61894531250002, 41.34375], + [46.67255859375001, 41.28681640625], + [46.66240234375002, 41.245507812499994], + [46.62636718750002, 41.15966796875], + [46.534375, 41.08857421875], + [46.43095703125002, 41.077050781249994], + [46.086523437500006, 41.183837890625], + [45.28095703125001, 41.449560546875], + [45.21718750000002, 41.423193359375], + [45.00136718750002, 41.290966796875], + [44.97587890625002, 41.277490234374994], + [44.81132812500002, 41.259375], + [44.077246093750006, 41.182519531249994], + [43.43339843750002, 41.155517578125], + [43.20546875000002, 41.199169921875], + [43.15283203125, 41.23642578125], + [43.14101562500002, 41.26484375], + [43.17128906250002, 41.287939453125], + [43.149023437500006, 41.30712890625], + [43.05712890625, 41.352832031249996], + [42.90673828125, 41.466845703124996], + [42.82167968750002, 41.4923828125], + [42.78789062500002, 41.563720703125], + [42.75410156250001, 41.57890625], + [42.68242187500002, 41.585742187499996], + [42.60683593750002, 41.57880859375], + [42.590429687500006, 41.57070312499999], + [42.5673828125, 41.55927734375], + [42.46640625, 41.43984375], + [41.92578125, 41.495654296874996], + [41.82353515625002, 41.432373046875], + [41.779394531250006, 41.44052734375], + [41.701757812500006, 41.471582031249994], + [41.57656250000002, 41.497314453125], + [41.51005859375002, 41.517480468749994], + [41.701757812500006, 41.705419921875], + [41.76298828125002, 41.970019531249996], + [41.48876953125, 42.659326171874994], + [40.83662109375001, 43.0634765625], + [40.46210937500001, 43.145703125], + [39.97832031250002, 43.419824218749994], + [40.02373046875002, 43.48486328125], + [40.084570312500006, 43.553125], + [40.648046875, 43.53388671875], + [40.941992187500006, 43.41806640625], + [41.083105468750006, 43.374462890625], + [41.35820312500002, 43.333398437499994], + [41.46074218750002, 43.276318359375], + [41.58056640625, 43.21923828125], + [42.76064453125002, 43.169580078124994], + [42.99160156250002, 43.09150390625], + [43.00019531250001, 43.049658203125], + [43.08916015625002, 42.9890625], + [43.55781250000001, 42.844482421875], + [43.623046875, 42.80771484375], + [43.78261718750002, 42.747021484375], + [43.79873046875002, 42.727783203125], + [43.79541015625, 42.702978515625], + [43.74990234375002, 42.657519531249996], + [43.738378906250006, 42.616992187499996], + [43.759863281250006, 42.59384765625], + [43.82597656250002, 42.571533203125], + [43.95742187500002, 42.566552734374994], + [44.00468750000002, 42.595605468749994], + [44.10273437500001, 42.616357421874994], + [44.32949218750002, 42.70351562499999], + [44.505859375, 42.7486328125], + [44.77109375, 42.616796875], + [44.85048828125002, 42.746826171875], + [44.87099609375002, 42.756396484374996], + [44.943359375, 42.730273437499996], + [45.07158203125002, 42.694140625], + [45.160253906250006, 42.675], + [45.34375, 42.52978515625], + [45.56289062500002, 42.5357421875], + [45.70527343750001, 42.498095703124996], + [45.7275390625, 42.475048828125], + [45.63427734375, 42.234716796875], + [45.63857421875002, 42.205078125], + [46.21269531250002, 41.989892578124994], + [46.42988281250001, 41.890966796875], + [46.18427734375001, 41.7021484375], + [46.30546875000002, 41.507714843749994] + ] + ] + }, + "properties": { "name": "Georgia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-0.068603515625, 11.115625], + [0.009423828125023, 11.02099609375], + [-0.08632812499999, 10.673046875], + [0.380859375, 10.291845703124991], + [0.264550781250023, 9.644726562499997], + [0.342578125000017, 9.604150390624994], + [0.2333984375, 9.463525390624994], + [0.525683593750017, 9.398486328124989], + [0.48876953125, 8.851464843749994], + [0.37255859375, 8.75927734375], + [0.686328125000017, 8.354882812499994], + [0.5, 7.546875], + [0.634765625, 7.353662109374994], + [0.525585937500011, 6.850927734374991], + [0.736914062500006, 6.452587890624997], + [1.187207031250011, 6.089404296874989], + [0.94970703125, 5.810253906249997], + [0.259667968750023, 5.75732421875], + [-2.001855468749994, 4.762451171875], + [-3.114013671875, 5.088671874999989], + [-2.815673828125, 5.153027343749997], + [-2.754980468749977, 5.432519531249994], + [-2.793652343749983, 5.60009765625], + [-2.998291015625, 5.71132812499999], + [-3.227148437499977, 6.749121093749991], + [-2.959082031249977, 7.454541015624997], + [-2.789746093749983, 7.931933593749989], + [-2.668847656249994, 8.022216796875], + [-2.613378906249977, 8.046679687499989], + [-2.600976562499994, 8.082226562499997], + [-2.619970703124977, 8.12109375], + [-2.61171875, 8.147558593749991], + [-2.538281249999983, 8.171630859375], + [-2.505859375, 8.208740234375], + [-2.600390624999989, 8.800439453124994], + [-2.649218749999989, 8.956591796874989], + [-2.689892578124983, 9.025097656249997], + [-2.746923828124977, 9.045117187499997], + [-2.705761718749983, 9.351367187499989], + [-2.695849609374989, 9.481347656249994], + [-2.706201171874994, 9.533935546875], + [-2.765966796874977, 9.658056640624991], + [-2.780517578125, 9.745849609375], + [-2.791162109374994, 10.432421874999989], + [-2.914892578124977, 10.592333984374989], + [-2.829931640624977, 10.998388671874991], + [-1.04248046875, 11.010058593749989], + [-0.627148437499983, 10.927392578124994], + [-0.299462890624994, 11.166894531249994], + [-0.068603515625, 11.115625] + ] + ] + }, + "properties": { "name": "Ghana", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-11.389404296875, 12.404394531249991], + [-11.502197265625, 12.198632812499994], + [-11.30517578125, 12.015429687499989], + [-10.933203124999977, 12.205175781249991], + [-10.709228515625, 11.898730468749989], + [-10.274853515624983, 12.212646484375], + [-9.754003906249977, 12.029931640624994], + [-9.358105468749983, 12.255419921874989], + [-9.395361328124977, 12.464648437499989], + [-9.043066406249977, 12.40234375], + [-8.818310546874983, 11.922509765624994], + [-8.822021484375, 11.673242187499994], + [-8.398535156249977, 11.366552734374991], + [-8.666699218749983, 11.009472656249997], + [-8.33740234375, 10.990625], + [-8.266650390624989, 10.485986328124994], + [-8.007275390624983, 10.321875], + [-7.990625, 10.1625], + [-8.155175781249994, 9.973193359374989], + [-8.136962890625, 9.49570312499999], + [-7.896191406249983, 9.415869140624991], + [-7.918066406249977, 9.188525390624989], + [-7.839404296874989, 9.151611328125], + [-7.7998046875, 9.115039062499989], + [-7.777978515624994, 9.080859374999989], + [-7.902099609375, 9.01708984375], + [-7.938183593749983, 8.979785156249989], + [-7.950976562499989, 8.786816406249997], + [-7.719580078124977, 8.643017578124997], + [-7.696093749999989, 8.375585937499991], + [-7.823583984374977, 8.467675781249994], + [-7.953125, 8.477734375], + [-8.236962890624994, 8.455664062499991], + [-8.244140625, 8.407910156249997], + [-8.256103515625, 8.253710937499989], + [-8.217138671874977, 8.219677734374997], + [-8.140625, 8.181445312499989], + [-8.048583984375, 8.169726562499989], + [-8.009863281249977, 8.07851562499999], + [-8.126855468749994, 7.867724609374989], + [-8.115429687499983, 7.7607421875], + [-8.205957031249994, 7.59023437499999], + [-8.231884765624983, 7.556738281249991], + [-8.429980468749989, 7.601855468749989], + [-8.486425781249977, 7.558496093749994], + [-8.659765624999977, 7.688378906249994], + [-8.8896484375, 7.2626953125], + [-9.11757812499999, 7.215917968749991], + [-9.463818359374983, 7.415869140624991], + [-9.369140625, 7.703808593749997], + [-9.518261718749983, 8.34609375], + [-9.781982421875, 8.537695312499991], + [-10.064355468749994, 8.429882812499997], + [-10.147412109374983, 8.519726562499997], + [-10.233056640624994, 8.488818359374989], + [-10.283203125, 8.485156249999989], + [-10.360058593749983, 8.495507812499994], + [-10.394433593749994, 8.48095703125], + [-10.496435546874977, 8.362109374999989], + [-10.557714843749977, 8.315673828125], + [-10.686962890624983, 8.321679687499994], + [-10.712109374999983, 8.335253906249989], + [-10.677343749999977, 8.400585937499997], + [-10.500537109374989, 8.687548828124989], + [-10.615966796875, 9.059179687499991], + [-10.726855468749989, 9.081689453124994], + [-10.747021484374983, 9.095263671874989], + [-10.749951171874983, 9.122363281249989], + [-10.687646484374994, 9.261132812499994], + [-10.682714843749977, 9.289355468749989], + [-10.758593749999989, 9.385351562499991], + [-11.047460937499977, 9.786328125], + [-11.180859374999983, 9.925341796874989], + [-11.205664062499977, 9.977734375], + [-11.273632812499983, 9.996533203124997], + [-11.911083984374983, 9.993017578124991], + [-12.142333984375, 9.87539062499999], + [-12.427978515625, 9.898144531249997], + [-12.557861328125, 9.704980468749994], + [-12.755859375, 9.373583984374989], + [-12.958789062499989, 9.263330078124994], + [-13.077294921874994, 9.069628906249989], + [-13.292675781249983, 9.04921875], + [-13.436279296875, 9.4203125], + [-13.691357421874983, 9.535791015624994], + [-13.689794921874977, 9.927783203124989], + [-13.820117187499989, 9.88720703125], + [-14.045019531249977, 10.141259765624994], + [-14.426904296874994, 10.248339843749989], + [-14.609570312499983, 10.549853515624989], + [-14.593505859375, 10.766699218749991], + [-14.677343749999977, 10.68896484375], + [-14.775927734374989, 10.931640625], + [-14.88671875, 10.968066406249989], + [-14.975, 10.803417968749997], + [-15.051220703124983, 10.834570312499991], + [-15.043017578124989, 10.940136718749997], + [-14.9990234375, 10.9921875], + [-14.944433593749977, 11.072167968749994], + [-14.779296875, 11.405517578125], + [-14.720263671874989, 11.48193359375], + [-14.682958984374977, 11.508496093749997], + [-14.604785156249989, 11.511621093749994], + [-14.452441406249989, 11.556201171874989], + [-14.327832031249983, 11.629785156249994], + [-14.265576171874983, 11.659912109375], + [-14.122314453125, 11.65195312499999], + [-13.953222656249977, 11.664599609374989], + [-13.732763671874977, 11.736035156249997], + [-13.730664062499983, 11.959863281249994], + [-13.737988281249983, 12.009667968749994], + [-13.816308593749994, 12.054492187499989], + [-13.948876953124994, 12.178173828124997], + [-13.8875, 12.246875], + [-13.759765625, 12.262353515624994], + [-13.673535156249983, 12.478515625], + [-13.732617187499983, 12.592822265624989], + [-13.729248046875, 12.673925781249991], + [-13.082910156249994, 12.633544921875], + [-13.061279296875, 12.489990234375], + [-12.930712890624989, 12.532275390624989], + [-12.399072265624994, 12.340087890625], + [-11.389404296875, 12.404394531249991] + ] + ] + }, + "properties": { "name": "Guinea", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-16.763330078124994, 13.064160156249997], + [-16.824804687499977, 13.341064453125], + [-16.669335937499994, 13.475], + [-16.41337890624999, 13.269726562499997], + [-15.427490234375, 13.46835937499999], + [-16.135449218749983, 13.4482421875], + [-16.351806640625, 13.34335937499999], + [-16.56230468749999, 13.587304687499994], + [-15.509667968749994, 13.586230468750003], + [-15.426855468749977, 13.727001953124997], + [-15.108349609374983, 13.81210937499999], + [-14.405468749999983, 13.503710937500003], + [-13.977392578124977, 13.54345703125], + [-13.826708984374989, 13.4078125], + [-14.246777343749983, 13.23583984375], + [-15.151123046875, 13.556494140624991], + [-15.286230468749977, 13.39599609375], + [-15.814404296874983, 13.325146484374997], + [-15.834277343749989, 13.156445312499997], + [-16.648779296874977, 13.154150390624991], + [-16.763330078124994, 13.064160156249997] + ] + ] + }, + "properties": { "name": "Gambia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-15.895898437499966, 11.082470703124969], + [-15.963964843749977, 11.05898437499998], + [-15.937695312499955, 11.192773437499966], + [-15.895898437499966, 11.082470703124969] + ] + ], + [ + [ + [-16.11450195312503, 11.059423828124977], + [-16.236425781249977, 11.113427734374966], + [-16.06733398437501, 11.197216796874983], + [-16.11450195312503, 11.059423828124977] + ] + ], + [ + [ + [-15.901806640624926, 11.4658203125], + [-16.02319335937497, 11.477148437499991], + [-15.964550781249926, 11.59829101562498], + [-15.901806640624926, 11.4658203125] + ] + ], + [ + [ + [-15.986425781249949, 11.882031249999969], + [-16.038330078124943, 11.759716796875011], + [-16.15244140624992, 11.876806640624963], + [-15.986425781249949, 11.882031249999969] + ] + ], + [ + [ + [-13.759765625, 12.262353515624994], + [-13.8875, 12.246875], + [-13.948876953124966, 12.178173828124997], + [-13.737988281250011, 12.009667968750037], + [-13.730664062499926, 11.959863281250009], + [-13.73276367187492, 11.736035156249983], + [-13.953222656249977, 11.664599609374989], + [-14.265576171874926, 11.659912109375014], + [-14.327832031250011, 11.629785156250009], + [-14.452441406249989, 11.556201171875017], + [-14.604785156249932, 11.511621093749994], + [-14.682958984374949, 11.508496093749983], + [-14.720263671875017, 11.481933593749986], + [-14.779296874999972, 11.405517578125057], + [-14.944433593749949, 11.072167968749994], + [-14.999023437499972, 10.992187500000043], + [-15.04301757812496, 10.940136718750011], + [-15.09375, 11.011035156249974], + [-15.054589843749994, 11.141943359375006], + [-15.222119140624926, 11.030908203125037], + [-15.216699218749994, 11.15625], + [-15.39311523437496, 11.217236328124983], + [-15.354687499999955, 11.396337890624963], + [-15.479492187499972, 11.410302734374966], + [-15.072656249999937, 11.597802734374966], + [-15.230371093750023, 11.686767578124972], + [-15.412988281249994, 11.615234374999972], + [-15.501904296875011, 11.723779296874966], + [-15.467187499999937, 11.842822265624974], + [-15.078271484374937, 11.968994140625014], + [-15.941748046875006, 11.786621093749986], + [-15.92021484374996, 11.93779296874996], + [-16.138427734375, 11.917285156250045], + [-16.32807617187501, 12.051611328124963], + [-16.244580078124955, 12.237109375], + [-16.43681640624996, 12.204150390625045], + [-16.711816406249937, 12.354833984375006], + [-16.656933593749955, 12.364355468749991], + [-16.52133789062495, 12.348632812499986], + [-16.41630859374996, 12.367675781250057], + [-16.24150390624996, 12.443310546875011], + [-16.144189453124937, 12.457421875000037], + [-15.839550781249955, 12.437890624999966], + [-15.57480468749992, 12.490380859375009], + [-15.19609375, 12.679931640624986], + [-14.3492187499999, 12.67641601562498], + [-14.064843749999966, 12.675292968750014], + [-13.729248046875, 12.673925781250006], + [-13.732617187499983, 12.592822265625003], + [-13.673535156249926, 12.478515624999986], + [-13.759765625, 12.262353515624994] + ] + ] + ] + }, + "properties": { "name": "Guinea-Bissau", "childNum": 5 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [11.332324218750017, 1.528369140624989], + [11.335351562500023, 0.999707031250011], + [9.906738281250028, 0.960107421875037], + [9.80390625000004, 0.998730468749997], + [9.788671875000034, 1.025683593749974], + [9.760546874999989, 1.074707031250014], + [9.704589843750057, 1.079980468750023], + [9.676464843750011, 1.074707031250014], + [9.636132812500051, 1.046679687499989], + [9.590820312500057, 1.031982421875014], + [9.599414062500045, 1.054443359374972], + [9.509863281250006, 1.114794921875017], + [9.385937500000068, 1.13925781250002], + [9.807031250000051, 1.927490234375028], + [9.77968750000008, 2.068212890625006], + [9.800781250000028, 2.304443359375], + [9.826171875000057, 2.297802734374969], + [9.8369140625, 2.242382812500054], + [9.870117187500028, 2.21328125], + [9.979882812499994, 2.167773437500045], + [10.790917968750023, 2.167578125], + [11.096582031250051, 2.167480468749986], + [11.328710937500006, 2.167431640624969], + [11.332324218750017, 1.528369140624989] + ] + ], + [ + [ + [8.735742187500023, 3.758300781249972], + [8.910058593750023, 3.758203125000051], + [8.946093750000074, 3.627539062499977], + [8.704003906250051, 3.223632812500028], + [8.474902343749989, 3.264648437500043], + [8.464648437500045, 3.450585937499994], + [8.735742187500023, 3.758300781249972] + ] + ] + ] + }, + "properties": { "name": "Eq. Guinea", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [23.852246093749983, 35.53544921874999], + [24.166015625000057, 35.59521484375], + [24.108984374999977, 35.49580078124998], + [24.35400390625, 35.359472656250034], + [25.73017578125004, 35.34858398437501], + [25.791308593750074, 35.122851562500045], + [26.32021484375008, 35.315136718749955], + [26.165625, 35.018603515625045], + [24.79980468750003, 34.93447265625002], + [24.70888671875008, 35.08906250000001], + [24.463671875000045, 35.160351562499955], + [23.59277343749997, 35.257226562499966], + [23.56982421875, 35.534765625000034], + [23.67265624999999, 35.51391601562506], + [23.736914062500034, 35.65551757812503], + [23.852246093749983, 35.53544921874999] + ] + ], + [ + [ + [27.17607421874999, 35.46528320312498], + [27.070703125000023, 35.59775390624998], + [27.22314453125, 35.820458984374966], + [27.17607421874999, 35.46528320312498] + ] + ], + [ + [ + [23.053808593750034, 36.18979492187498], + [22.91083984375004, 36.220996093750045], + [22.950488281250045, 36.38393554687502], + [23.053808593750034, 36.18979492187498] + ] + ], + [ + [ + [27.84277343750003, 35.929296875000034], + [27.71552734375004, 35.95732421874996], + [27.71630859375003, 36.17158203125001], + [28.23183593750005, 36.43364257812502], + [28.087792968750023, 36.06533203125002], + [27.84277343750003, 35.929296875000034] + ] + ], + [ + [ + [25.48242187500003, 36.39262695312502], + [25.37050781250005, 36.35893554687499], + [25.408984375000074, 36.473730468750006], + [25.48242187500003, 36.39262695312502] + ] + ], + [ + [ + [26.46064453125001, 36.58540039062501], + [26.270019531250057, 36.54692382812499], + [26.370019531250023, 36.63857421875002], + [26.46064453125001, 36.58540039062501] + ] + ], + [ + [ + [26.94960937500005, 36.72709960937502], + [27.214941406250006, 36.89863281249998], + [27.352148437499977, 36.86889648437506], + [26.94960937500005, 36.72709960937502] + ] + ], + [ + [ + [25.859375, 36.79042968750005], + [25.74316406250003, 36.78974609374998], + [26.06445312500003, 36.90273437500002], + [25.859375, 36.79042968750005] + ] + ], + [ + [ + [27.01972656250004, 36.95903320312502], + [26.91992187500003, 36.94521484375005], + [26.88867187499997, 37.087255859375034], + [27.01972656250004, 36.95903320312502] + ] + ], + [ + [ + [25.278906250000034, 37.06840820312502], + [25.105468750000057, 37.034960937500045], + [25.235058593750068, 37.148535156250006], + [25.278906250000034, 37.06840820312502] + ] + ], + [ + [ + [25.54589843749997, 36.96757812499999], + [25.45673828125001, 36.9296875], + [25.361914062500063, 37.07041015624998], + [25.52529296875005, 37.19638671875006], + [25.54589843749997, 36.96757812499999] + ] + ], + [ + [ + [24.523535156250063, 37.125097656250006], + [24.42480468750003, 37.131982421874994], + [24.48378906250005, 37.21020507812503], + [24.523535156250063, 37.125097656250006] + ] + ], + [ + [ + [25.402734375000023, 37.419140624999955], + [25.312695312500068, 37.48930664062496], + [25.462988281250063, 37.47109375], + [25.402734375000023, 37.419140624999955] + ] + ], + [ + [ + [26.029296875000057, 37.529394531250034], + [26.086328125000023, 37.63491210937505], + [26.351367187500017, 37.67431640625], + [26.029296875000057, 37.529394531250034] + ] + ], + [ + [ + [25.255859375000057, 37.59960937500006], + [25.156347656250034, 37.54506835937505], + [24.99648437500005, 37.676904296874994], + [25.255859375000057, 37.59960937500006] + ] + ], + [ + [ + [24.35595703125003, 37.57685546875004], + [24.28896484375005, 37.52827148437498], + [24.37910156250004, 37.682714843750006], + [24.35595703125003, 37.57685546875004] + ] + ], + [ + [ + [26.82441406250004, 37.81142578125005], + [27.05507812500005, 37.70927734375002], + [26.84492187500004, 37.64472656250001], + [26.58105468750003, 37.723730468750034], + [26.82441406250004, 37.81142578125005] + ] + ], + [ + [ + [20.888476562500074, 37.805371093749955], + [20.993945312500074, 37.70800781250003], + [20.81855468750004, 37.66474609375001], + [20.61953125000008, 37.855029296875045], + [20.691503906250006, 37.929541015625034], + [20.888476562500074, 37.805371093749955] + ] + ], + [ + [ + [24.991699218750057, 37.75961914062506], + [24.962207031250074, 37.69238281250003], + [24.7001953125, 37.961669921875], + [24.956347656250045, 37.90478515625006], + [24.991699218750057, 37.75961914062506] + ] + ], + [ + [ + [20.61230468750003, 38.38334960937502], + [20.761328125, 38.07055664062497], + [20.523535156250063, 38.106640624999955], + [20.4521484375, 38.23417968750002], + [20.35253906250003, 38.179882812499955], + [20.563183593750068, 38.474951171875034], + [20.61230468750003, 38.38334960937502] + ] + ], + [ + [ + [26.094042968750017, 38.21806640625002], + [25.891894531250045, 38.243310546874994], + [25.991406250000068, 38.353515625], + [25.846093750000023, 38.57402343749996], + [26.16035156250001, 38.54072265625001], + [26.094042968750017, 38.21806640625002] + ] + ], + [ + [ + [20.68671875000001, 38.60869140625002], + [20.5546875, 38.58256835937502], + [20.69414062499999, 38.84423828125003], + [20.68671875000001, 38.60869140625002] + ] + ], + [ + [ + [24.67470703125005, 38.80922851562502], + [24.54101562499997, 38.788671875], + [24.485644531250074, 38.980273437500045], + [24.67470703125005, 38.80922851562502] + ] + ], + [ + [ + [23.41542968750008, 38.958642578124994], + [23.525, 38.8134765625], + [24.127539062500034, 38.648486328125045], + [24.27578125000005, 38.22001953124996], + [24.58837890625003, 38.12397460937504], + [24.53652343750005, 37.97973632812506], + [24.212011718750006, 38.11752929687506], + [24.040136718750006, 38.389990234375034], + [23.65078125000008, 38.44306640625001], + [23.25214843750004, 38.80122070312498], + [22.870312500000068, 38.870507812499966], + [23.258203125000023, 39.03134765625006], + [23.41542968750008, 38.958642578124994] + ] + ], + [ + [ + [26.41015625000003, 39.329443359375034], + [26.59560546875005, 39.04882812499997], + [26.488671875000023, 39.074804687500034], + [26.46875, 38.97280273437502], + [26.10791015625, 39.08105468749997], + [26.273144531249983, 39.19755859374999], + [26.072363281250034, 39.095605468749994], + [25.84414062500008, 39.20004882812506], + [26.16542968750008, 39.37353515625006], + [26.41015625000003, 39.329443359375034] + ] + ], + [ + [ + [20.077929687500045, 39.432714843750034], + [19.883984375000068, 39.461523437500034], + [19.646484375, 39.76708984375003], + [19.926074218750017, 39.773730468750045], + [19.8466796875, 39.66811523437502], + [20.077929687500045, 39.432714843750034] + ] + ], + [ + [ + [25.43769531250004, 39.98330078125002], + [25.357031250000063, 39.80810546875003], + [25.24941406250005, 39.89414062500006], + [25.06220703125004, 39.852392578125006], + [25.05800781250005, 39.999658203124966], + [25.43769531250004, 39.98330078125002] + ] + ], + [ + [ + [24.774218750000074, 40.615185546874955], + [24.515527343750023, 40.64702148437496], + [24.623339843750045, 40.79291992187501], + [24.774218750000074, 40.615185546874955] + ] + ], + [ + [ + [26.03896484375008, 40.726757812499955], + [25.10449218750003, 40.994726562500006], + [24.792968750000057, 40.857519531250034], + [24.47705078125, 40.94775390625003], + [24.082324218750074, 40.72407226562504], + [23.762792968750063, 40.74780273437497], + [23.866796875000034, 40.41855468750006], + [24.21279296875008, 40.32778320312502], + [24.343359375000034, 40.14770507812503], + [23.913183593750063, 40.35878906250005], + [23.72792968750008, 40.329736328124994], + [23.96748046875001, 40.11455078125002], + [23.947070312500045, 39.96557617187506], + [23.66455078125003, 40.22382812499998], + [23.42626953125, 40.26396484374999], + [23.62734375, 39.92407226562503], + [22.896484375000057, 40.39990234374997], + [22.92226562500008, 40.59086914062499], + [22.629492187500034, 40.49555664062501], + [22.59218750000005, 40.03691406250002], + [23.327734374999977, 39.174902343750006], + [23.15468750000008, 39.10146484375005], + [23.16171875, 39.25776367187501], + [22.92138671874997, 39.30634765625004], + [22.886035156250074, 39.16997070312496], + [23.066699218750017, 39.03793945312498], + [22.569140625000074, 38.86748046874999], + [23.25292968750003, 38.66123046875006], + [23.68398437500008, 38.35244140625002], + [23.96699218750001, 38.275], + [24.024511718750006, 38.139794921874966], + [24.01972656250001, 37.67773437499997], + [23.50175781249999, 38.03486328124998], + [23.03632812500004, 37.87836914062501], + [23.48925781250003, 37.440185546875], + [23.16152343750005, 37.333837890625006], + [22.725390625000017, 37.542138671874966], + [23.16015625000003, 36.448095703125034], + [22.717187500000023, 36.79394531250006], + [22.42773437500003, 36.47578124999998], + [22.08046875000008, 37.028955078124966], + [21.95556640625003, 36.990087890625034], + [21.892382812500045, 36.73730468749997], + [21.58291015625005, 37.080957031249994], + [21.678906250000068, 37.38720703125003], + [21.124707031250068, 37.89160156250003], + [21.40371093750005, 38.19667968750002], + [21.658398437500068, 38.17509765624996], + [21.82470703125003, 38.328125], + [22.846386718750068, 37.96757812499996], + [23.18349609375008, 38.133691406249966], + [22.421679687500045, 38.43852539062499], + [22.319921875, 38.35683593750005], + [21.96533203124997, 38.412451171875006], + [21.47255859375005, 38.321386718750006], + [21.3310546875, 38.48730468749997], + [21.303320312500034, 38.373925781249966], + [21.113183593750023, 38.38466796875002], + [20.768554687500057, 38.874414062499966], + [21.111621093750045, 38.89628906249999], + [21.11835937500001, 39.029980468749955], + [20.71337890625, 39.03515625000003], + [20.300781250000057, 39.32709960937501], + [20.19140625, 39.545800781249966], + [20.099414062500074, 39.641259765624966], + [20.001269531250074, 39.70942382812501], + [20.022558593750063, 39.710693359375], + [20.059765624999983, 39.69912109375002], + [20.13105468750004, 39.66162109375003], + [20.206835937500017, 39.65351562499998], + [20.382421875, 39.802636718749994], + [20.381640625000017, 39.84179687500006], + [20.311328125000074, 39.95078125000006], + [20.311132812500034, 39.97944335937504], + [20.338476562500006, 39.991064453125006], + [20.38369140625008, 40.0171875], + [20.408007812500074, 40.049462890624994], + [20.4560546875, 40.065576171874994], + [20.657421875000068, 40.11738281249998], + [20.881640625000017, 40.467919921874994], + [21.030859375000034, 40.62246093750002], + [20.95576171875001, 40.775292968749994], + [20.96425781250005, 40.84990234374999], + [21.575781250000034, 40.86894531249996], + [21.627539062500006, 40.896337890625034], + [21.77949218750004, 40.95043945312506], + [21.99335937500001, 41.13095703125006], + [22.18447265625005, 41.15864257812501], + [22.49355468750005, 41.118505859375006], + [22.603613281249977, 41.14018554687499], + [22.724804687500068, 41.17851562499999], + [22.78388671875004, 41.33198242187498], + [23.155957031250068, 41.32207031249999], + [23.239843750000034, 41.38496093750001], + [23.372070312500057, 41.3896484375], + [23.433398437500017, 41.39873046874999], + [23.53583984375001, 41.38603515624999], + [23.63515625000008, 41.386767578125045], + [24.011328124999977, 41.460058593750034], + [24.03291015625004, 41.469091796875034], + [24.05605468750005, 41.527246093749966], + [24.38671875, 41.523535156250006], + [24.487890625, 41.55522460937499], + [24.518261718750068, 41.55253906249996], + [24.773730468750045, 41.356103515624994], + [24.99355468750008, 41.36499023437503], + [25.133398437500063, 41.31577148437506], + [25.251171875000068, 41.243554687499994], + [25.923339843750057, 41.311914062499966], + [26.066406250000057, 41.35068359375006], + [26.135351562499977, 41.3857421875], + [26.155175781250023, 41.43486328124999], + [26.143554687500057, 41.52153320312496], + [26.085546875000063, 41.704150390625045], + [26.10742187499997, 41.72568359374998], + [26.20058593750005, 41.74379882812502], + [26.320898437500034, 41.716552734375], + [26.581347656250074, 41.60126953125004], + [26.62490234375008, 41.401757812499994], + [26.330664062499977, 41.23876953125], + [26.331054687500057, 40.954492187499994], + [26.03896484375008, 40.726757812499955] + ] + ] + ] + }, + "properties": { "name": "Greece", "childNum": 29 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.71552734375, 12.012646484374997], + [-61.714990234374994, 12.18515625], + [-61.60703125, 12.223291015624994], + [-61.71552734375, 12.012646484374997] + ] + ] + }, + "properties": { "name": "Grenada", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-46.266699218750006, 60.781396484374994], + [-46.381542968749955, 60.66030273437502], + [-46.7880859375, 60.758398437500034], + [-46.205224609374994, 60.943505859374994], + [-46.266699218750006, 60.781396484374994] + ] + ], + [ + [ + [-37.03125, 65.53198242187497], + [-37.23842773437494, 65.60986328125003], + [-37.047509765624966, 65.722265625], + [-37.03125, 65.53198242187497] + ] + ], + [ + [ + [-51.01367187499994, 69.55249023437497], + [-51.202050781249966, 69.525], + [-51.33886718749994, 69.73203125000006], + [-51.094580078125006, 69.92416992187503], + [-50.67900390624999, 69.84853515625], + [-51.01367187499994, 69.55249023437497] + ] + ], + [ + [ + [-52.73115234375001, 69.94472656250005], + [-52.0453125, 69.8072265625], + [-51.90019531249999, 69.60478515625007], + [-53.57841796874996, 69.25664062500002], + [-54.18271484374995, 69.40351562500001], + [-53.65830078124998, 69.46513671875005], + [-53.825, 69.54033203124999], + [-54.91914062499998, 69.71362304687503], + [-54.78789062499996, 69.94985351562502], + [-54.322607421875034, 69.94189453125], + [-54.83076171875001, 70.13295898437502], + [-54.37163085937499, 70.31728515625], + [-53.296728515625034, 70.20537109375002], + [-52.73115234375001, 69.94472656250005] + ] + ], + [ + [ + [-51.67514648437498, 70.855224609375], + [-52.11938476562497, 70.87065429687502], + [-52.10673828124999, 70.96801757812497], + [-51.67514648437498, 70.855224609375] + ] + ], + [ + [ + [-25.43232421875001, 70.92133789062495], + [-25.402246093749994, 70.65268554687503], + [-26.217871093749977, 70.45405273437498], + [-26.604687499999926, 70.55336914062497], + [-28.03525390624995, 70.48681640625], + [-27.61723632812496, 70.91376953125001], + [-26.621777343749955, 70.87563476562497], + [-25.81889648437499, 71.04365234375001], + [-25.43232421875001, 70.92133789062495] + ] + ], + [ + [ + [-53.53520507812493, 71.04082031250005], + [-53.9578125, 71.12773437499999], + [-53.58447265625003, 71.29707031249995], + [-53.53520507812493, 71.04082031250005] + ] + ], + [ + [ + [-55.01689453124999, 72.79111328125003], + [-55.56660156249998, 72.56435546875002], + [-56.214794921874955, 72.71918945312495], + [-55.01689453124999, 72.79111328125003] + ] + ], + [ + [ + [-18.000537109374932, 75.40732421875003], + [-17.391992187499937, 75.03691406250007], + [-18.670800781249966, 75.00166015624998], + [-18.856054687499977, 75.31914062500002], + [-18.000537109374932, 75.40732421875003] + ] + ], + [ + [ + [-18.58261718749995, 76.042333984375], + [-19.085351562499966, 76.43037109375001], + [-18.882470703124937, 76.70380859375001], + [-18.58261718749995, 76.042333984375] + ] + ], + [ + [ + [-71.667333984375, 77.32529296874998], + [-72.48955078124999, 77.43164062499997], + [-71.43344726562495, 77.394384765625], + [-71.667333984375, 77.32529296874998] + ] + ], + [ + [ + [-17.6125, 79.82587890624995], + [-18.662011718749966, 79.72001953125005], + [-19.13828125, 79.85234375000002], + [-17.98291015625, 80.05517578125003], + [-17.471386718749955, 80.02871093749997], + [-17.6125, 79.82587890624995] + ] + ], + [ + [ + [-44.86455078124999, 82.08364257812502], + [-46.75190429687501, 82.34819335937502], + [-47.27226562499996, 82.65693359375001], + [-46.399169921875, 82.692138671875], + [-44.91748046875003, 82.48051757812505], + [-44.86455078124999, 82.08364257812502] + ] + ], + [ + [ + [-29.952880859375, 83.56484374999997], + [-25.795068359374994, 83.26098632812497], + [-31.99267578125, 83.0853515625], + [-32.03271484374997, 82.98344726562502], + [-25.12338867187495, 83.15961914062501], + [-24.47031249999995, 82.87739257812498], + [-21.582519531249943, 82.6341796875], + [-23.118066406249966, 82.32470703125003], + [-29.57939453124996, 82.16118164062502], + [-29.887402343749983, 82.05483398437502], + [-29.543847656249994, 81.93994140624997], + [-27.839501953124966, 82.04887695312505], + [-25.148828124999966, 82.001123046875], + [-24.293066406249977, 81.70097656250005], + [-23.103710937499983, 82.01181640625003], + [-21.337988281249977, 82.068701171875], + [-21.230517578125017, 81.60136718749999], + [-23.11772460937499, 80.77817382812498], + [-19.62993164062499, 81.63989257812503], + [-17.456054687499943, 81.397705078125], + [-16.12070312499995, 81.776611328125], + [-14.241992187500017, 81.81386718750005], + [-12.434423828125006, 81.68251953125002], + [-11.430664062499972, 81.45683593750005], + [-13.126220703124972, 81.08779296875], + [-14.452343749999955, 80.99311523437498], + [-14.503564453124994, 80.76328125000006], + [-16.76059570312492, 80.573388671875], + [-15.937255859374972, 80.42763671874997], + [-16.48876953124997, 80.25195312499997], + [-18.070947265624994, 80.17207031249995], + [-19.429199218749943, 80.25771484375], + [-20.150146484375, 80.01123046874997], + [-18.99199218749996, 79.17836914062502], + [-21.133740234374926, 78.65864257812501], + [-21.729589843749977, 77.70854492187499], + [-20.862597656249932, 77.91186523437503], + [-19.490429687499983, 77.71889648437497], + [-19.46752929687503, 77.56582031250005], + [-20.162060546874926, 77.68984375], + [-20.680810546875023, 77.61899414062503], + [-20.23193359374997, 77.36840820312497], + [-19.30029296874997, 77.22236328124995], + [-18.442626953124943, 77.259375], + [-18.51030273437496, 76.77817382812498], + [-20.48671875, 76.92080078125], + [-21.614697265624926, 76.68789062499997], + [-22.18525390625001, 76.79409179687502], + [-22.609326171874983, 76.70429687500004], + [-21.877343749999966, 76.57348632812503], + [-21.488232421874926, 76.271875], + [-20.10361328124992, 76.21909179687503], + [-19.508984374999926, 75.75751953124995], + [-19.52636718750003, 75.18022460937505], + [-20.484960937500006, 75.31425781249999], + [-21.649316406249966, 75.02343749999997], + [-22.232861328124926, 75.11972656249998], + [-21.69511718749999, 74.96445312500003], + [-20.985791015624983, 75.07436523437497], + [-20.86157226562497, 74.63593750000001], + [-20.41708984374995, 74.9751953125], + [-19.98491210937499, 74.9751953125], + [-19.287011718750023, 74.54638671875006], + [-19.36914062499997, 74.28403320312498], + [-20.256445312499977, 74.2828125], + [-20.653125, 74.13735351562502], + [-21.954931640624977, 74.24428710937497], + [-21.942919921874932, 74.56572265624999], + [-22.32158203124999, 74.30253906250002], + [-22.134814453124932, 73.99047851562503], + [-20.36728515624992, 73.8482421875], + [-20.509667968749966, 73.49287109375001], + [-22.346875, 73.26923828125001], + [-23.23320312499999, 73.39770507812497], + [-24.157714843749943, 73.76445312499999], + [-24.67724609375, 73.602197265625], + [-25.521289062500017, 73.85161132812499], + [-24.79125976562497, 73.51127929687502], + [-26.062304687500017, 73.25302734375], + [-27.270410156250023, 73.43627929687503], + [-26.541845703125006, 73.24897460937495], + [-27.561621093750006, 73.13847656250002], + [-27.348046875000023, 73.06782226562501], + [-25.057031250000023, 73.396484375], + [-24.132666015625006, 73.409375], + [-22.036328124999955, 72.91845703125006], + [-22.29321289062497, 72.11953125], + [-24.06904296875001, 72.49873046874998], + [-24.629980468749977, 73.03764648437499], + [-26.657617187499966, 72.71582031249997], + [-24.81333007812492, 72.90151367187497], + [-24.65, 72.58251953125], + [-25.117871093749983, 72.34697265625005], + [-24.66684570312492, 72.437353515625], + [-21.959667968749955, 71.74467773437502], + [-22.479638671874937, 71.38344726562497], + [-22.417578125, 71.24868164062505], + [-22.29902343750001, 71.43232421874998], + [-21.75224609374999, 71.47832031250002], + [-21.522656249999926, 70.52622070312503], + [-22.38413085937492, 70.46240234375], + [-22.437011718749943, 70.860009765625], + [-22.690673828124943, 70.43730468750002], + [-23.327832031249983, 70.45097656250007], + [-23.97138671875001, 70.64946289062499], + [-24.562207031249926, 71.22353515624997], + [-25.885156249999966, 71.571923828125], + [-27.08720703124999, 71.6265625], + [-27.107031250000034, 71.53266601562498], + [-25.842724609374955, 71.48017578124995], + [-25.74223632812499, 71.18359375], + [-26.717919921874994, 70.95048828125005], + [-28.39843749999997, 70.99291992187497], + [-27.99218749999997, 70.89521484374998], + [-28.06987304687499, 70.69902343750005], + [-29.07207031249999, 70.444970703125], + [-26.621777343749955, 70.46337890625], + [-26.576806640625023, 70.35708007812502], + [-27.560839843749932, 70.12446289062498], + [-27.384179687500023, 69.9916015625], + [-27.027734374999966, 70.20122070312499], + [-25.529882812499977, 70.35317382812502], + [-23.66733398437495, 70.139306640625], + [-22.28447265624996, 70.12583007812498], + [-22.287060546874955, 70.03339843749998], + [-23.03364257812501, 69.90083007812498], + [-23.04956054687497, 69.79272460937497], + [-23.86572265624997, 69.73671875000002], + [-23.739404296874994, 69.58862304687497], + [-24.296679687500017, 69.58554687500006], + [-24.295556640624966, 69.439306640625], + [-25.188574218750006, 69.26054687500002], + [-25.092431640624937, 69.16518554687502], + [-25.697998046874943, 68.889892578125], + [-26.48291015624997, 68.67592773437502], + [-29.24951171874997, 68.29877929687501], + [-29.86850585937495, 68.31157226562505], + [-30.318115234375, 68.19331054687501], + [-30.72001953124999, 68.25117187499998], + [-30.610742187499994, 68.11791992187503], + [-30.97856445312499, 68.06132812500005], + [-32.32744140624999, 68.43730468749999], + [-32.16455078125, 67.99111328125002], + [-33.15698242187497, 67.62670898437506], + [-34.1982421875, 66.65507812499999], + [-35.18857421874995, 66.25029296875002], + [-35.86723632812502, 66.44140624999997], + [-35.630078124999926, 66.13994140625002], + [-36.37919921874996, 65.830810546875], + [-36.52724609375002, 66.00771484375], + [-36.665185546874966, 65.79008789062507], + [-37.06279296874996, 65.87143554687503], + [-37.410058593749994, 65.65634765625], + [-37.954785156249955, 65.63359375000007], + [-37.278710937499994, 66.30439453124995], + [-38.156640624999966, 66.38559570312498], + [-37.75234375000002, 66.26152343750002], + [-38.13994140625002, 65.90351562499998], + [-38.52036132812498, 66.00966796875002], + [-38.20336914062497, 65.71171874999999], + [-40.17353515624998, 65.55615234375], + [-39.57792968749996, 65.34077148437501], + [-39.937255859375, 65.14160156250003], + [-40.253125, 65.04887695312505], + [-41.08442382812501, 65.10083007812497], + [-40.966015624999955, 64.86884765624995], + [-40.655468749999926, 64.91533203125002], + [-40.18222656249998, 64.47993164062495], + [-40.78173828125, 64.22177734375003], + [-41.581005859374926, 64.29833984375], + [-41.03056640624996, 64.12104492187504], + [-40.61777343749998, 64.13173828125], + [-40.550390625000034, 63.72524414062505], + [-40.77519531249999, 63.53364257812501], + [-41.04873046875002, 63.51381835937505], + [-41.387890624999926, 63.06186523437498], + [-41.84448242187497, 63.07026367187501], + [-42.174511718749955, 63.20878906249999], + [-41.63447265624998, 62.972460937500074], + [-41.90898437499996, 62.73710937499999], + [-42.94165039062503, 62.72021484375003], + [-42.15297851562502, 62.568457031250006], + [-42.32148437499998, 62.15273437500005], + [-42.110205078125006, 61.857226562500074], + [-42.58530273437498, 61.71748046875001], + [-42.34736328125001, 61.61743164062497], + [-42.717041015625, 60.767480468749994], + [-43.04409179687502, 60.523681640625], + [-43.92270507812495, 60.59536132812502], + [-43.21298828124998, 60.390673828125074], + [-43.122900390625006, 60.06123046875001], + [-43.32011718749993, 59.928125], + [-43.95502929687498, 60.025488281250006], + [-43.65791015625001, 59.85864257812503], + [-43.90654296874996, 59.815478515625045], + [-44.11699218750002, 59.83193359375002], + [-44.06547851562499, 59.92480468750003], + [-44.412939453125006, 59.922607421875], + [-44.22436523437494, 60.273535156250006], + [-44.61328124999997, 60.01665039062499], + [-45.37924804687495, 60.20292968750002], + [-45.367773437500006, 60.37294921875002], + [-44.97470703124995, 60.457226562499955], + [-44.756738281249966, 60.66459960937502], + [-45.38051757812494, 60.444921875], + [-46.04663085937503, 60.61572265625], + [-46.141943359375006, 60.776513671874994], + [-45.87021484374998, 61.21831054687502], + [-46.87446289062501, 60.81640625000003], + [-48.180810546874966, 60.76923828125001], + [-47.77031249999999, 60.99775390625001], + [-48.386425781249926, 61.004736328125034], + [-48.42817382812501, 61.18740234375002], + [-48.92207031249998, 61.27744140624998], + [-49.28906249999997, 61.58994140625006], + [-49.380273437499994, 61.89018554687502], + [-48.82871093749998, 62.0796875], + [-49.62377929687494, 61.99858398437499], + [-49.553466796875, 62.23271484374999], + [-50.319238281249966, 62.473193359375045], + [-50.298730468749966, 62.72197265625002], + [-49.793115234374994, 63.04462890625004], + [-50.39008789062501, 62.82202148437497], + [-51.46884765624995, 63.64228515625001], + [-51.547509765624994, 64.00610351562497], + [-50.260693359374955, 64.21425781250002], + [-50.48662109374996, 64.20888671875], + [-50.43706054687499, 64.31284179687503], + [-51.58491210937498, 64.10317382812502], + [-51.70786132812498, 64.205078125], + [-51.403759765624926, 64.46318359375002], + [-50.49208984375002, 64.69316406250005], + [-50.00898437500001, 64.44726562499997], + [-50.12163085937493, 64.703759765625], + [-50.51699218750002, 64.76650390625], + [-50.96064453124998, 65.20112304687498], + [-50.721582031249966, 64.79760742187503], + [-51.22060546875002, 64.62846679687502], + [-51.25537109375, 64.75810546875005], + [-51.92260742187503, 64.21875], + [-52.259033203125, 65.154931640625], + [-52.537695312500034, 65.32880859374998], + [-51.61914062500003, 65.71318359375002], + [-51.091894531250006, 65.77578125], + [-51.7234375, 65.723486328125], + [-52.55126953125003, 65.46137695312498], + [-52.760937499999926, 65.59082031249997], + [-53.198974609375, 65.59404296875002], + [-53.106347656249966, 65.97714843749998], + [-53.39204101562498, 66.04833984375], + [-51.225, 66.88154296875001], + [-53.035791015624966, 66.20141601562503], + [-53.538769531249955, 66.13935546874998], + [-53.41875, 66.64853515624998], + [-53.038281249999955, 66.82680664062497], + [-52.38686523437502, 66.88115234375005], + [-53.44360351562503, 66.924658203125], + [-53.88442382812502, 67.13554687499999], + [-53.79858398437494, 67.41816406250001], + [-52.666455078124955, 67.74970703124995], + [-50.613476562499955, 67.5279296875], + [-51.171044921874966, 67.693603515625], + [-50.96884765624998, 67.80664062500003], + [-51.765234375000034, 67.73784179687505], + [-52.34482421874998, 67.83691406249997], + [-53.735205078125006, 67.54902343750004], + [-53.151562499999926, 68.20776367187503], + [-51.779980468749926, 68.05673828124998], + [-51.456494140624926, 68.116064453125], + [-51.21015625000001, 68.419921875], + [-52.19853515624993, 68.22080078125], + [-53.38315429687495, 68.29736328124997], + [-53.03945312500002, 68.61088867187499], + [-52.60458984374998, 68.70874023437503], + [-51.62314453124995, 68.53481445312505], + [-50.945703124999966, 68.68266601562505], + [-50.807714843750006, 68.81699218749998], + [-51.24941406250002, 68.73994140625001], + [-51.084863281249994, 69.12827148437498], + [-50.29736328124994, 69.17060546874998], + [-51.07695312499996, 69.20947265625], + [-50.291699218749955, 70.01445312500005], + [-52.254638671875, 70.05893554687503], + [-53.02304687499995, 70.30190429687497], + [-54.01445312499996, 70.42167968750005], + [-54.53076171875, 70.69926757812502], + [-54.16582031249999, 70.82011718750005], + [-52.801953124999955, 70.7505859375], + [-50.87236328124993, 70.36489257812502], + [-50.66328124999998, 70.417578125], + [-51.32285156249998, 70.58876953124997], + [-51.25659179687497, 70.85268554687502], + [-51.77431640625002, 71.01044921875001], + [-51.018945312499966, 71.001318359375], + [-51.37666015625001, 71.11904296875], + [-53.007568359375, 71.17998046874999], + [-52.89184570312497, 71.457666015625], + [-51.76992187500002, 71.67172851562498], + [-53.44008789062502, 71.57900390625002], + [-53.14453125000003, 71.80742187500002], + [-53.65214843749996, 72.36264648437506], + [-53.92773437499997, 72.31879882812501], + [-53.47758789062502, 71.84995117187506], + [-54.01992187500002, 71.657861328125], + [-53.96298828124995, 71.45898437499997], + [-54.6890625, 71.36723632812505], + [-55.59404296874999, 71.55351562500005], + [-55.315576171874994, 72.11069335937498], + [-54.84013671874996, 72.35610351562497], + [-55.581445312499994, 72.178857421875], + [-55.63583984374998, 72.300439453125], + [-55.29570312499996, 72.35439453124997], + [-55.60170898437494, 72.453466796875], + [-54.924951171874994, 72.57197265624998], + [-54.737939453124994, 72.87250976562501], + [-55.07309570312498, 73.01513671875003], + [-55.28891601562498, 72.93320312500003], + [-55.66855468749998, 73.00791015624998], + [-55.288281249999955, 73.32709960937498], + [-56.10405273437496, 73.55815429687499], + [-55.83828125, 73.75971679687501], + [-56.22539062499999, 74.12910156249995], + [-57.23056640624995, 74.12529296875007], + [-56.70634765625002, 74.21918945312501], + [-56.717675781249994, 74.42924804687499], + [-56.25546874999998, 74.52680664062498], + [-58.56552734374998, 75.35273437500001], + [-58.249658203124994, 75.50668945312503], + [-58.51621093749995, 75.68906250000006], + [-61.18823242187494, 76.157861328125], + [-63.29130859374996, 76.35205078125003], + [-63.84306640624999, 76.21713867187498], + [-64.307275390625, 76.31650390624998], + [-65.36992187499993, 76.13056640625004], + [-65.87573242187494, 76.23833007812505], + [-66.46577148437498, 76.13916015625], + [-66.99257812500002, 76.21293945312502], + [-66.67480468750003, 75.977392578125], + [-68.14873046875002, 76.06704101562497], + [-69.48408203125001, 76.39916992187503], + [-68.1142578125, 76.65063476562503], + [-69.67382812499994, 76.73588867187507], + [-69.69423828125002, 76.98945312500004], + [-70.613134765625, 76.82182617187499], + [-71.14145507812498, 77.02866210937503], + [-70.86284179687496, 77.175439453125], + [-68.97832031250002, 77.19531250000006], + [-68.13554687499999, 77.37958984375001], + [-66.38945312499999, 77.28027343750003], + [-66.69121093749999, 77.68120117187502], + [-67.68808593749995, 77.523779296875], + [-68.62153320312498, 77.60185546875002], + [-69.35136718749999, 77.467138671875], + [-70.53540039062497, 77.699560546875], + [-70.11445312500001, 77.84135742187505], + [-71.27163085937494, 77.81313476562497], + [-72.81806640624995, 78.1943359375], + [-72.47250976562498, 78.48203125], + [-71.65131835937493, 78.62314453124998], + [-68.99345703124999, 78.857421875], + [-68.37705078124998, 79.037841796875], + [-65.82553710937503, 79.17373046874997], + [-64.79228515624993, 80.00063476562502], + [-64.17915039062498, 80.09926757812497], + [-66.84365234374997, 80.07622070312507], + [-67.05063476562503, 80.384521484375], + [-64.51552734374997, 81], + [-63.72197265624993, 81.05732421875001], + [-63.028662109375006, 80.88955078125002], + [-62.90336914062496, 81.21835937500003], + [-61.43598632812498, 81.13359375000002], + [-60.842871093750034, 81.85537109374997], + [-59.28193359374998, 81.88403320312503], + [-56.615136718749994, 81.362890625], + [-59.26181640624998, 82.00664062500005], + [-54.54887695312496, 82.35063476562505], + [-53.671337890624955, 82.16406249999997], + [-53.55566406250003, 81.65327148437501], + [-53.022558593750034, 82.32172851562504], + [-50.894433593749994, 81.89521484375001], + [-49.54106445312496, 81.91806640625003], + [-50.93554687500003, 82.38281250000003], + [-50.03710937499994, 82.472412109375], + [-44.7294921875, 81.77983398437505], + [-44.23886718749998, 82.3681640625], + [-45.55654296875002, 82.74702148437498], + [-41.87646484375, 82.680322265625], + [-41.36962890625003, 82.75], + [-46.136816406250006, 82.85883789062504], + [-46.169042968750006, 83.06386718749997], + [-45.41459960937496, 83.01767578124998], + [-43.00927734375003, 83.26459960937501], + [-41.300146484375006, 83.10078125000004], + [-40.35683593750002, 83.332177734375], + [-38.15625, 82.9986328125], + [-38.74956054687496, 83.37084960937497], + [-37.72333984374998, 83.49775390624998], + [-29.952880859375, 83.56484374999997] + ] + ] + ] + }, + "properties": { "name": "Greenland", "childNum": 14 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-89.2328125, 15.888671875], + [-88.89404296875, 15.890625], + [-88.60336914062499, 15.76416015625], + [-88.5939453125, 15.950292968749991], + [-88.22832031249999, 15.72900390625], + [-88.271435546875, 15.694873046875003], + [-88.36455078124999, 15.616015625], + [-88.68447265625, 15.360498046874994], + [-88.96098632812499, 15.152441406249991], + [-89.142578125, 15.072314453125003], + [-89.22236328125, 14.866064453124991], + [-89.16220703124999, 14.669238281250003], + [-89.17177734375, 14.606884765624997], + [-89.28671875, 14.529980468749997], + [-89.36259765624999, 14.416015625], + [-89.5736328125, 14.390087890624997], + [-89.54716796874999, 14.241259765625003], + [-90.04814453124999, 13.904052734375], + [-90.09521484375, 13.736523437499997], + [-90.60693359375, 13.929003906250003], + [-91.37734375, 13.990185546874997], + [-92.23515624999999, 14.54541015625], + [-92.15854492187499, 14.963574218749997], + [-92.14423828125, 15.001953125], + [-92.09873046874999, 15.026757812499994], + [-92.07480468749999, 15.07421875], + [-92.187158203125, 15.320898437499991], + [-92.08212890624999, 15.495556640624997], + [-91.9572265625, 15.703222656249991], + [-91.736572265625, 16.07016601562499], + [-91.433984375, 16.070458984374994], + [-90.97958984374999, 16.07080078125], + [-90.70322265624999, 16.071044921875], + [-90.52197265625, 16.071191406249994], + [-90.44716796875, 16.072705078124997], + [-90.45986328125, 16.162353515625], + [-90.450146484375, 16.261376953124994], + [-90.4169921875, 16.351318359375], + [-90.4169921875, 16.39101562499999], + [-90.47109375, 16.43955078124999], + [-90.57578125, 16.467822265625003], + [-90.63408203124999, 16.5107421875], + [-90.634375, 16.565136718749997], + [-90.65996093749999, 16.630908203125003], + [-90.710693359375, 16.70810546874999], + [-90.975830078125, 16.867822265624994], + [-91.409619140625, 17.255859375], + [-91.1955078125, 17.254101562499997], + [-90.99296874999999, 17.25244140625], + [-90.98916015625, 17.81640625], + [-89.16147460937499, 17.81484375], + [-89.2328125, 15.888671875] + ] + ] + }, + "properties": { "name": "Guatemala", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [144.74179687500003, 13.25927734375], + [144.64931640625002, 13.4287109375], + [144.87539062500002, 13.614648437499994], + [144.94082031250002, 13.5703125], + [144.74179687500003, 13.25927734375] + ] + ] + }, + "properties": { "name": "Guam", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-57.194775390625, 5.5484375], + [-57.3185546875, 5.335351562499994], + [-57.20981445312499, 5.195410156249991], + [-57.331005859375, 5.020166015624994], + [-57.711083984374994, 4.991064453124991], + [-57.91704101562499, 4.820410156249991], + [-57.84599609374999, 4.668164062499997], + [-58.05429687499999, 4.101660156249991], + [-57.646728515625, 3.39453125], + [-57.303662109375, 3.377099609374994], + [-57.19736328124999, 2.853271484375], + [-56.704345703125, 2.036474609374991], + [-56.4828125, 1.942138671875], + [-56.96953124999999, 1.91640625], + [-57.03759765625, 1.936474609374997], + [-57.092675781249994, 2.005810546874997], + [-57.118896484375, 2.013964843749989], + [-57.31748046874999, 1.963476562499991], + [-57.41269531249999, 1.908935546875], + [-57.500439453125, 1.77382812499999], + [-57.54575195312499, 1.72607421875], + [-57.59443359375, 1.7041015625], + [-57.795654296875, 1.7], + [-57.8734375, 1.667285156249989], + [-57.9828125, 1.6484375], + [-58.03466796875, 1.520263671875], + [-58.34067382812499, 1.587548828124994], + [-58.38037109375, 1.530224609374997], + [-58.39580078124999, 1.481738281249989], + [-58.5060546875, 1.438671875], + [-58.511865234374994, 1.28466796875], + [-58.68461914062499, 1.281054687499989], + [-58.73032226562499, 1.247509765624997], + [-58.78720703124999, 1.20849609375], + [-58.82177734375, 1.201220703124989], + [-59.231201171875, 1.376025390624989], + [-59.53569335937499, 1.7], + [-59.66660156249999, 1.746289062499997], + [-59.66850585937499, 1.842333984374989], + [-59.74072265625, 1.874169921874994], + [-59.75620117187499, 1.900634765625], + [-59.75522460937499, 2.274121093749997], + [-59.8896484375, 2.362939453124994], + [-59.9943359375, 2.689990234374989], + [-59.854394531249994, 3.5875], + [-59.55112304687499, 3.933544921874997], + [-59.557763671874994, 3.960009765624989], + [-59.62021484374999, 4.023144531249997], + [-59.73857421874999, 4.226757812499997], + [-59.69970703125, 4.353515625], + [-60.1486328125, 4.533251953124989], + [-59.990673828125, 5.082861328124991], + [-60.142041015625, 5.238818359374989], + [-60.241650390625, 5.257958984374994], + [-60.335205078125, 5.199316406249991], + [-60.45952148437499, 5.188085937499991], + [-60.6513671875, 5.221142578124997], + [-60.742138671875, 5.202050781249994], + [-61.37680664062499, 5.906982421875], + [-61.3908203125, 5.938769531249989], + [-61.303125, 6.049511718749997], + [-61.22495117187499, 6.129199218749989], + [-61.15947265624999, 6.174414062499991], + [-61.12871093749999, 6.214306640624997], + [-61.152294921875, 6.385107421874991], + [-61.151025390624994, 6.446533203125], + [-61.181591796875, 6.513378906249997], + [-61.20361328125, 6.58837890625], + [-61.14560546874999, 6.69453125], + [-60.717919921874994, 6.768310546875], + [-60.35209960937499, 7.002880859374997], + [-60.32207031249999, 7.092041015625], + [-60.32548828124999, 7.133984375], + [-60.34506835937499, 7.15], + [-60.46494140624999, 7.166552734374989], + [-60.523193359375, 7.143701171874994], + [-60.583203125, 7.156201171874997], + [-60.63330078125, 7.211083984374994], + [-60.718652343749994, 7.535937499999989], + [-60.513623046875, 7.813183593749997], + [-60.032421875, 8.053564453124991], + [-59.99072265625, 8.162011718749994], + [-59.96484375, 8.191601562499997], + [-59.849072265625, 8.248681640624994], + [-59.83164062499999, 8.305957031249989], + [-60.017529296875, 8.54931640625], + [-59.20024414062499, 8.07460937499999], + [-58.51108398437499, 7.39804687499999], + [-58.48056640624999, 7.038134765624989], + [-58.67294921874999, 6.390771484374994], + [-58.414990234375, 6.85117187499999], + [-57.982568359374994, 6.785888671875], + [-57.54013671874999, 6.33154296875], + [-57.2275390625, 6.178417968749997], + [-57.194775390625, 5.5484375] + ] + ] + }, + "properties": { "name": "Guyana", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [73.70742187500002, -53.13710937499999], + [73.46513671875002, -53.184179687500006], + [73.25117187500001, -52.97578125000001], + [73.83779296875002, -53.11279296875], + [73.70742187500002, -53.13710937499999] + ] + ] + }, + "properties": { "name": "Heard I. and McDonald Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-83.635498046875, 14.876416015624997], + [-84.53764648437496, 14.633398437499963], + [-84.64594726562498, 14.661083984375011], + [-84.86044921874998, 14.809765625000011], + [-84.98515624999999, 14.752441406249972], + [-85.059375, 14.582958984374997], + [-85.20834960937498, 14.311816406250003], + [-85.73393554687496, 13.85869140625006], + [-85.75341796875, 13.852050781250028], + [-85.78671874999995, 13.844433593749997], + [-85.98378906249997, 13.965673828125006], + [-86.04038085937503, 14.050146484374977], + [-86.33173828124995, 13.770068359375031], + [-86.37695312500003, 13.755664062500031], + [-86.61025390624997, 13.774853515625026], + [-86.73364257812494, 13.763476562500017], + [-86.75898437499995, 13.746142578125045], + [-86.77060546875003, 13.698730468749972], + [-86.763525390625, 13.635253906250014], + [-86.72958984375, 13.4072265625], + [-86.710693359375, 13.31337890624998], + [-86.72929687499996, 13.284375], + [-86.79213867187497, 13.279785156249972], + [-86.87353515624994, 13.266503906250023], + [-86.918212890625, 13.223583984374983], + [-87.00932617187499, 13.007812499999986], + [-87.0591796875, 12.991455078125028], + [-87.337255859375, 12.979248046875028], + [-87.48911132812503, 13.352929687500051], + [-87.814208984375, 13.399169921875057], + [-87.781884765625, 13.521386718749994], + [-87.71533203125003, 13.812695312500011], + [-87.73144531250003, 13.841064453125014], + [-87.80224609374997, 13.889990234375034], + [-87.89199218749997, 13.894970703124983], + [-87.99101562499996, 13.879638671874972], + [-88.15102539062497, 13.987353515624974], + [-88.44912109374994, 13.850976562499994], + [-88.48266601562503, 13.854248046875043], + [-88.49765624999998, 13.904541015624986], + [-88.50434570312501, 13.964208984374963], + [-88.51254882812498, 13.97895507812504], + [-89.12050781249994, 14.370214843749991], + [-89.36259765624996, 14.416015625], + [-89.17177734375, 14.606884765624983], + [-89.16220703125, 14.669238281249989], + [-89.22236328125001, 14.86606445312502], + [-89.142578125, 15.072314453125031], + [-88.96098632812496, 15.15244140625002], + [-88.68447265625002, 15.360498046875037], + [-88.36455078124996, 15.616015625000045], + [-88.27143554687498, 15.694873046875045], + [-88.22832031249999, 15.729003906249972], + [-88.131103515625, 15.701025390625034], + [-87.87495117187495, 15.879345703124955], + [-86.35664062499998, 15.783203125], + [-85.93627929687497, 15.953417968750045], + [-85.98564453124999, 16.02416992187497], + [-85.48369140624996, 15.899511718749977], + [-84.97373046874998, 15.989892578124994], + [-84.55966796875, 15.802001953125], + [-84.26142578124998, 15.822607421875034], + [-83.765283203125, 15.405468750000054], + [-83.972802734375, 15.519628906250034], + [-84.11132812499997, 15.492431640625], + [-84.09506835937503, 15.400927734375017], + [-83.92744140624998, 15.394042968750028], + [-83.76044921874998, 15.220361328124994], + [-83.49794921874997, 15.222119140624997], + [-83.64638671875, 15.368408203125043], + [-83.36918945312493, 15.239990234375], + [-83.29086914062498, 15.078906250000045], + [-83.2255859375, 15.042285156250045], + [-83.15751953124999, 14.993066406249966], + [-83.41503906249994, 15.008056640625], + [-83.5365234375, 14.977001953124983], + [-83.635498046875, 14.876416015624997] + ] + ] + }, + "properties": { "name": "Honduras", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [17.60781250000005, 42.76904296875], + [17.744238281250063, 42.70034179687505], + [17.34414062500008, 42.790380859375006], + [17.60781250000005, 42.76904296875] + ] + ], + [ + [ + [16.650683593750017, 42.99658203125], + [17.188281250000045, 42.917041015625045], + [16.850683593750006, 42.8955078125], + [16.650683593750017, 42.99658203125] + ] + ], + [ + [ + [17.667578125000063, 42.897119140624994], + [18.436328125000017, 42.559716796874994], + [18.517480468750023, 42.43291015624999], + [17.823828125, 42.79741210937502], + [17.045410156250057, 43.014892578125], + [17.667578125000063, 42.897119140624994] + ] + ], + [ + [ + [16.785253906250006, 43.270654296874966], + [16.490332031250034, 43.28618164062502], + [16.44892578125004, 43.38706054687506], + [16.89130859375001, 43.314648437499955], + [16.785253906250006, 43.270654296874966] + ] + ], + [ + [ + [15.371386718750074, 43.973828124999955], + [15.437207031250068, 43.899511718750006], + [15.270019531250028, 44.01074218750003], + [15.371386718750074, 43.973828124999955] + ] + ], + [ + [ + [14.488085937500074, 44.66005859375005], + [14.31240234375008, 44.90039062499997], + [14.33125, 45.16499023437498], + [14.488085937500074, 44.66005859375005] + ] + ], + [ + [ + [14.810253906250068, 44.97705078124997], + [14.45039062500004, 45.079199218750006], + [14.571093750000017, 45.224755859374994], + [14.810253906250068, 44.97705078124997] + ] + ], + [ + [ + [18.905371093750006, 45.931738281250034], + [18.839062499999983, 45.83574218750002], + [19.064257812500045, 45.51499023437506], + [19.004687500000074, 45.39951171875006], + [19.4, 45.2125], + [19.062890625000023, 45.13720703125], + [19.007128906250045, 44.86918945312502], + [18.83642578125, 44.883251953124955], + [18.66259765625, 45.07744140624999], + [17.812792968750074, 45.078125], + [16.918652343749983, 45.27656249999998], + [16.53066406250008, 45.21669921875002], + [16.29335937500005, 45.00883789062496], + [16.028320312500057, 45.18959960937502], + [15.788085937500057, 45.17895507812497], + [15.736621093750045, 44.76582031250001], + [16.10341796875008, 44.52099609375006], + [16.300097656250017, 44.12451171875], + [17.27382812500005, 43.44575195312501], + [17.650488281250063, 43.006591796875], + [17.585156250000068, 42.93837890625005], + [16.903125, 43.392431640625006], + [16.393945312500023, 43.54335937500002], + [15.985546875000068, 43.519775390625], + [15.185839843750017, 44.17211914062503], + [15.122949218749994, 44.256787109374955], + [15.470996093750045, 44.27197265625003], + [14.981347656250023, 44.60292968750005], + [14.854589843750034, 45.08100585937501], + [14.550488281249983, 45.297705078125006], + [14.31269531250004, 45.33779296875002], + [13.86074218750008, 44.83740234375003], + [13.517187500000063, 45.481787109375034], + [13.878710937500017, 45.428369140624994], + [14.369921875000074, 45.48144531250006], + [14.427343750000034, 45.50576171875002], + [14.56884765625, 45.65722656249997], + [14.591796875000057, 45.65126953125002], + [14.649511718750006, 45.57148437500001], + [14.793066406250034, 45.47822265625001], + [14.95458984375, 45.499902343749994], + [15.110449218750034, 45.450781250000034], + [15.242089843750023, 45.44140624999997], + [15.339453125000063, 45.46704101562506], + [15.326660156250028, 45.502294921875034], + [15.291210937500011, 45.541552734375045], + [15.283593750000051, 45.5796875], + [15.35371093750004, 45.659912109375], + [15.27705078125004, 45.73261718749998], + [15.652148437500074, 45.86215820312498], + [15.675585937500045, 45.98369140624996], + [15.666210937500011, 46.04848632812502], + [15.596875, 46.10922851562506], + [15.592578125000017, 46.139990234375006], + [15.608984374999977, 46.171923828125045], + [16.1064453125, 46.382226562499994], + [16.32119140625005, 46.53461914062504], + [16.42763671875005, 46.5244140625], + [16.516210937499977, 46.499902343749966], + [16.569921875, 46.48500976562505], + [16.748046875000057, 46.41640625000002], + [16.87148437500008, 46.33930664062504], + [17.310644531250006, 45.99614257812502], + [17.80712890625, 45.79042968750002], + [18.358300781250023, 45.75302734375006], + [18.533593750000023, 45.79614257812503], + [18.56464843750004, 45.81328124999999], + [18.666015625, 45.90747070312497], + [18.905371093750006, 45.931738281250034] + ] + ] + ] + }, + "properties": { "name": "Croatia", "childNum": 8 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-72.80458984374997, 18.777685546875063], + [-72.82221679687501, 18.707128906249977], + [-73.07797851562498, 18.790917968749994], + [-73.27641601562499, 18.95405273437501], + [-72.80458984374997, 18.777685546875063] + ] + ], + [ + [ + [-71.647216796875, 19.195947265624994], + [-71.80712890624997, 18.987011718749983], + [-71.733642578125, 18.85639648437501], + [-71.72705078125, 18.80322265625003], + [-71.74321289062502, 18.73291015625], + [-71.86650390624999, 18.61416015625005], + [-71.98686523437499, 18.61035156249997], + [-72.000390625, 18.59790039062503], + [-71.94038085937493, 18.51259765625005], + [-71.87255859374997, 18.416210937499955], + [-71.76191406249998, 18.34130859374997], + [-71.73725585937495, 18.27080078124999], + [-71.76831054687497, 18.039160156250063], + [-71.85292968749997, 18.119140625], + [-71.94609375, 18.186083984375045], + [-72.05986328124993, 18.228564453125017], + [-72.87666015624998, 18.151757812499994], + [-73.38515625000002, 18.251171874999983], + [-73.747314453125, 18.190234375000017], + [-73.88496093749998, 18.041894531249994], + [-74.478125, 18.45], + [-74.3875, 18.624707031249983], + [-74.22773437499998, 18.662695312499977], + [-72.78935546874996, 18.434814453125], + [-72.37607421874998, 18.57446289062503], + [-72.34765624999994, 18.674951171874994], + [-72.81108398437496, 19.071582031250074], + [-72.70322265625, 19.441064453125023], + [-73.43837890624994, 19.722119140624983], + [-73.21777343750003, 19.88369140625005], + [-72.63701171875002, 19.90087890625], + [-72.21982421875003, 19.744628906250057], + [-71.834716796875, 19.696728515624983], + [-71.77924804687498, 19.718164062499994], + [-71.75742187499998, 19.68818359375001], + [-71.71147460937493, 19.486572265625057], + [-71.74648437499997, 19.28583984375001], + [-71.647216796875, 19.195947265624994] + ] + ] + ] + }, + "properties": { "name": "Haiti", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [22.1318359375, 48.405322265624996], + [22.253710937500017, 48.407373046874994], + [22.582421875000023, 48.134033203125], + [22.769140625, 48.109619140625], + [22.87666015625001, 47.947265625], + [21.99970703125001, 47.505029296874994], + [21.121679687500006, 46.282421875], + [20.76025390625, 46.246240234374994], + [20.613671875000023, 46.13349609375], + [20.508105468750017, 46.166943359375], + [20.28095703125001, 46.1330078125], + [20.241796875, 46.10859375], + [20.21015625000001, 46.126025390624996], + [20.161425781250017, 46.141894531249996], + [19.93408203125, 46.161474609375], + [19.84443359375001, 46.145898437499994], + [19.61347656250001, 46.169189453125], + [19.421289062500023, 46.064453125], + [18.666015625, 45.907470703125], + [18.56464843750001, 45.81328125], + [18.533593750000023, 45.796142578125], + [18.358300781250023, 45.75302734375], + [17.80712890625, 45.790429687499994], + [17.310644531250006, 45.996142578124996], + [16.871484375000023, 46.339306640625], + [16.748046875, 46.41640625], + [16.569921875, 46.485009765624994], + [16.516210937500006, 46.499902343749994], + [16.283593750000023, 46.857275390625], + [16.093066406250017, 46.86328125], + [16.453417968750017, 47.006787109375], + [16.44287109375, 47.39951171875], + [16.676562500000017, 47.536035156249994], + [16.421289062500023, 47.674462890624994], + [17.06660156250001, 47.707568359374996], + [17.147363281250023, 48.00595703125], + [17.76191406250001, 47.770166015624994], + [18.72421875, 47.787158203124996], + [18.791894531250023, 48.000292968749996], + [19.625390625000023, 48.223095703125], + [19.95039062500001, 48.146630859374994], + [20.333789062500017, 48.295556640624994], + [20.490039062500017, 48.526904296874996], + [21.45136718750001, 48.55224609375], + [21.766992187500023, 48.3380859375], + [22.1318359375, 48.405322265624996] + ] + ] + }, + "properties": { "name": "Hungary", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [122.9489257812501, -10.90927734375002], + [122.82617187500003, -10.899121093749983], + [122.84570312500003, -10.761816406249991], + [123.37109375000003, -10.474902343749989], + [123.41816406250004, -10.651269531250037], + [122.9489257812501, -10.90927734375002] + ] + ], + [ + [ + [121.8830078125001, -10.590332031249957], + [121.70468750000006, -10.5556640625], + [121.99833984375002, -10.446972656249983], + [121.8830078125001, -10.590332031249957] + ] + ], + [ + [ + [123.41621093750004, -10.302636718749966], + [123.3255859375, -10.264160156249943], + [123.45878906250002, -10.13994140624996], + [123.41621093750004, -10.302636718749966] + ] + ], + [ + [ + [120.0125, -9.374707031250026], + [120.78447265625002, -9.95703125], + [120.83261718750006, -10.0375], + [120.69804687500002, -10.206640624999949], + [120.4391601562501, -10.294042968749991], + [120.14482421875002, -10.200097656249952], + [119.60107421874997, -9.773535156250006], + [119.08544921875003, -9.706933593750023], + [118.95878906250002, -9.519335937500003], + [119.29589843749997, -9.3671875], + [119.9420898437501, -9.301464843750026], + [120.0125, -9.374707031250026] + ] + ], + [ + [ + [125.06816406250002, -9.511914062499997], + [124.42753906250002, -10.14863281250004], + [123.7472656250001, -10.347167968749986], + [123.60478515625002, -10.270117187500006], + [123.71640625000012, -10.078613281249986], + [123.5892578125, -9.966796875000028], + [123.709375, -9.61484375], + [124.0363281250001, -9.341601562500031], + [124.28232421875012, -9.427929687500026], + [124.44443359375012, -9.190332031250023], + [124.92226562500005, -8.942480468749977], + [124.93681640625007, -9.053417968750026], + [125.14902343750012, -9.042578125000034], + [125.10048828125, -9.189843750000023], + [124.96015625000004, -9.213769531250009], + [125.06816406250002, -9.511914062499997] + ] + ], + [ + [ + [115.60996093750012, -8.769824218749974], + [115.48046875000003, -8.715429687500006], + [115.56142578125, -8.669921874999972], + [115.60996093750012, -8.769824218749974] + ] + ], + [ + [ + [122.97734375000002, -8.54521484374996], + [122.88779296875006, -8.587304687500009], + [123.01054687500002, -8.448339843750034], + [123.153125, -8.475781250000026], + [122.97734375000002, -8.54521484374996] + ] + ], + [ + [ + [119.46406250000004, -8.741015624999974], + [119.38554687500002, -8.736035156250026], + [119.4464843750001, -8.429199218749957], + [119.55722656250012, -8.518847656250003], + [119.46406250000004, -8.741015624999974] + ] + ], + [ + [ + [123.31748046875012, -8.354785156249974], + [123.02500000000012, -8.395507812500014], + [123.21708984375002, -8.235449218750006], + [123.33603515625006, -8.269042968750014], + [123.31748046875012, -8.354785156249974] + ] + ], + [ + [ + [116.64082031250004, -8.613867187500006], + [116.51425781250012, -8.820996093750011], + [116.58652343750012, -8.886132812499966], + [116.23935546875006, -8.912109375000014], + [115.85732421875005, -8.787890625000017], + [116.07646484375002, -8.744921874999974], + [116.06113281250006, -8.437402343750023], + [116.4015625000001, -8.204199218750034], + [116.7189453125001, -8.336035156249977], + [116.64082031250004, -8.613867187500006] + ] + ], + [ + [ + [124.28662109375003, -8.32949218749998], + [124.14667968750004, -8.531445312499997], + [123.92773437500003, -8.448925781249969], + [124.23955078125002, -8.20341796874996], + [124.28662109375003, -8.32949218749998] + ] + ], + [ + [ + [123.92480468750003, -8.2724609375], + [123.55302734375007, -8.566796875], + [123.23007812500006, -8.530664062500023], + [123.47587890625007, -8.322265625000014], + [123.39121093750012, -8.280468750000026], + [123.77597656250006, -8.190429687499986], + [123.92480468750003, -8.2724609375] + ] + ], + [ + [ + [138.89511718750006, -8.388671874999957], + [138.56337890625, -8.30908203125], + [138.79619140625007, -8.173632812500017], + [138.89511718750006, -8.388671874999957] + ] + ], + [ + [ + [117.55634765625004, -8.367285156249949], + [117.49052734375007, -8.183398437499974], + [117.66503906249997, -8.148242187500003], + [117.55634765625004, -8.367285156249949] + ] + ], + [ + [ + [124.5755859375, -8.140820312499997], + [125.05029296874997, -8.179589843749994], + [125.13173828125, -8.326464843749989], + [124.38066406250002, -8.41513671875002], + [124.43066406249997, -8.18320312500002], + [124.5755859375, -8.140820312499997] + ] + ], + [ + [ + [127.8234375000001, -8.098828124999969], + [128.11923828125012, -8.17070312499996], + [128.02353515625006, -8.255371093749972], + [127.82089843750012, -8.190234375000031], + [127.8234375000001, -8.098828124999969] + ] + ], + [ + [ + [122.7829101562501, -8.61171875], + [121.65136718749997, -8.898730468749946], + [121.41464843750006, -8.81484375], + [121.32832031250004, -8.916894531250009], + [121.03525390625012, -8.935449218749966], + [120.55048828125004, -8.80185546875002], + [119.909375, -8.857617187500011], + [119.80791015625002, -8.697656250000023], + [119.87480468750007, -8.419824218749994], + [120.61025390625005, -8.24042968750004], + [121.44453125000004, -8.57783203125004], + [121.96650390625004, -8.455175781250006], + [122.32324218749997, -8.628320312500023], + [122.85048828125, -8.304394531250011], + [122.91914062500004, -8.221875], + [122.75859375000002, -8.185937499999952], + [122.91699218749997, -8.105566406250006], + [123.00595703125006, -8.329101562499986], + [122.7829101562501, -8.61171875] + ] + ], + [ + [ + [130.86220703125, -8.31875], + [130.77519531250002, -8.34990234374996], + [131.02011718750012, -8.091308593749943], + [131.17636718750006, -8.130761718749994], + [130.86220703125, -8.31875] + ] + ], + [ + [ + [118.24238281250004, -8.317773437499994], + [118.61191406250006, -8.28066406249998], + [118.71386718749997, -8.41494140624998], + [118.926171875, -8.297656249999974], + [119.12968750000002, -8.668164062499969], + [118.74589843750002, -8.735449218749991], + [118.83261718750012, -8.833398437499966], + [118.47861328125012, -8.856445312499957], + [118.37890625000003, -8.674609375000031], + [118.18994140624997, -8.840527343749997], + [117.06132812500002, -9.099023437499994], + [116.78847656250005, -9.006347656250028], + [116.83505859375012, -8.532421875000026], + [117.16484375000007, -8.367187500000014], + [117.56708984375004, -8.426367187499991], + [117.80605468750005, -8.711132812500011], + [117.96953125000002, -8.728027343749986], + [118.23486328124997, -8.591894531249963], + [117.81484375000005, -8.342089843749974], + [117.7552734375, -8.149511718749991], + [118.11748046875007, -8.12226562500004], + [118.24238281250004, -8.317773437499994] + ] + ], + [ + [ + [115.44785156250012, -8.155175781249994], + [115.70429687500004, -8.40712890624998], + [115.14492187500005, -8.849023437500037], + [115.05507812500005, -8.573046874999946], + [114.61318359375, -8.37832031249998], + [114.46757812500007, -8.166308593749946], + [114.93847656249997, -8.18710937500002], + [115.15400390625004, -8.065722656249974], + [115.44785156250012, -8.155175781249994] + ] + ], + [ + [ + [129.83886718749997, -7.954589843749986], + [129.71347656250012, -8.04072265625004], + [129.60898437500006, -7.803417968750011], + [129.81298828124997, -7.819726562499952], + [129.83886718749997, -7.954589843749986] + ] + ], + [ + [ + [126.80097656250004, -7.667871093750009], + [126.4720703125, -7.950390625000011], + [126.04003906250003, -7.885839843750006], + [125.79824218750005, -7.984570312499969], + [125.97529296875004, -7.663378906249989], + [126.21367187500002, -7.706738281250026], + [126.60957031250004, -7.571777343749972], + [126.80097656250004, -7.667871093750009] + ] + ], + [ + [ + [127.41943359375003, -7.623046875000028], + [127.37070312500012, -7.512792968749949], + [127.47519531250012, -7.531054687500031], + [127.41943359375003, -7.623046875000028] + ] + ], + [ + [ + [138.53535156250004, -8.273632812499969], + [138.2962890625, -8.405175781250037], + [137.65039062499997, -8.386132812499966], + [138.08183593750002, -7.566210937500003], + [138.29550781250012, -7.4384765625], + [138.76982421875002, -7.390429687499974], + [138.98906250000002, -7.696093749999989], + [138.53535156250004, -8.273632812499969] + ] + ], + [ + [ + [131.3255859375, -7.999511718749986], + [131.11376953125003, -7.997363281249989], + [131.13779296875012, -7.684863281250017], + [131.64345703125, -7.11279296875], + [131.73613281250007, -7.197070312500017], + [131.64384765625002, -7.266894531249946], + [131.62441406250005, -7.626171874999955], + [131.3255859375, -7.999511718749986] + ] + ], + [ + [ + [131.98203125000006, -7.202050781249966], + [131.75078125000002, -7.116796875], + [131.92226562500005, -7.104492187499986], + [131.98203125000006, -7.202050781249966] + ] + ], + [ + [ + [128.6701171875001, -7.183300781249969], + [128.52978515625003, -7.134570312499989], + [128.62773437500007, -7.06875], + [128.6701171875001, -7.183300781249969] + ] + ], + [ + [ + [120.77441406250003, -7.118945312500003], + [120.64082031250004, -7.115820312499991], + [120.63339843750006, -7.018261718750011], + [120.77441406250003, -7.118945312500003] + ] + ], + [ + [ + [113.84453125000007, -7.105371093749994], + [113.12695312499997, -7.224121093750028], + [112.72587890625007, -7.072753906250014], + [112.86806640625, -6.899902343749972], + [113.06738281250003, -6.879980468749991], + [113.97470703125012, -6.873046875], + [114.0736328125, -6.960156249999983], + [113.84453125000007, -7.105371093749994] + ] + ], + [ + [ + [115.37705078125006, -6.97080078125002], + [115.22031250000012, -6.952539062500037], + [115.24052734375007, -6.861230468749994], + [115.54609375000004, -6.938671874999955], + [115.37705078125006, -6.97080078125002] + ] + ], + [ + [ + [105.25283203125005, -6.640429687499946], + [105.12138671875007, -6.614941406249997], + [105.26054687500002, -6.523925781250014], + [105.25283203125005, -6.640429687499946] + ] + ], + [ + [ + [134.53681640625004, -6.442285156249994], + [134.32275390624997, -6.84873046875002], + [134.09082031249997, -6.833789062500003], + [134.10703125000006, -6.471582031250009], + [134.19462890625007, -6.459765625], + [134.11464843750005, -6.190820312500009], + [134.53681640625004, -6.442285156249994] + ] + ], + [ + [ + [107.37392578125005, -6.007617187499989], + [107.66679687500002, -6.215820312499957], + [108.33017578125012, -6.286035156249966], + [108.67783203125006, -6.790527343749972], + [110.42626953124997, -6.947265625000028], + [110.83476562500002, -6.424218749999952], + [110.97226562500012, -6.435644531249977], + [111.18154296875005, -6.686718749999969], + [111.54033203125002, -6.648242187500031], + [112.0873046875, -6.89335937499996], + [112.53925781250004, -6.926464843749955], + [112.64873046875007, -7.221289062499977], + [112.7943359375, -7.304492187499974], + [112.79453125000012, -7.55244140625004], + [113.01357421875005, -7.657714843749986], + [113.49765625000006, -7.723828124999955], + [114.07070312500005, -7.633007812500011], + [114.40927734375012, -7.79248046875], + [114.38691406250004, -8.405175781250037], + [114.58378906250002, -8.769628906250034], + [113.25332031250005, -8.286718749999963], + [112.67880859375006, -8.409179687499957], + [111.50996093750004, -8.30507812499998], + [110.60722656250002, -8.149414062499972], + [109.28164062500005, -7.704882812500003], + [108.74121093749997, -7.667089843750034], + [108.45175781250006, -7.79697265625002], + [107.91748046875003, -7.724121093750014], + [107.28496093750007, -7.471679687500014], + [106.45527343750004, -7.368652343749986], + [106.51972656250004, -7.053710937499943], + [106.19824218749997, -6.927832031249977], + [105.25546875000012, -6.835253906250031], + [105.37089843750002, -6.664355468750031], + [105.48369140625007, -6.781542968750017], + [105.65507812500002, -6.469531249999946], + [105.78691406250002, -6.456933593749966], + [105.86826171875006, -6.11640625000004], + [106.075, -5.914160156249963], + [106.82519531249997, -6.098242187499977], + [107.0462890625, -5.90419921874998], + [107.37392578125005, -6.007617187499989] + ] + ], + [ + [ + [120.52832031249997, -6.2984375], + [120.48730468749997, -6.464843749999972], + [120.47734375000007, -5.775292968750009], + [120.52832031249997, -6.2984375] + ] + ], + [ + [ + [112.7194335937501, -5.81103515625], + [112.58603515625006, -5.803613281249994], + [112.69003906250006, -5.726171875000034], + [112.7194335937501, -5.81103515625] + ] + ], + [ + [ + [132.80712890625003, -5.850781250000011], + [132.68144531250002, -5.91259765625], + [132.63017578125002, -5.60703125], + [132.80712890625003, -5.850781250000011] + ] + ], + [ + [ + [134.74697265625, -5.707031249999957], + [134.71416015625007, -6.29511718750004], + [134.44111328125004, -6.334863281249966], + [134.15488281250006, -6.06289062499998], + [134.3019531250001, -6.009765624999986], + [134.34306640625002, -5.833007812499943], + [134.20537109375002, -5.707226562499997], + [134.34130859375003, -5.712890624999986], + [134.57080078124997, -5.42734375], + [134.74697265625, -5.707031249999957] + ] + ], + [ + [ + [132.92626953124997, -5.902050781249983], + [132.84501953125002, -5.987988281249997], + [133.13847656250002, -5.317871093749986], + [133.11962890625003, -5.575976562499989], + [132.92626953124997, -5.902050781249983] + ] + ], + [ + [ + [102.36718750000003, -5.478710937499983], + [102.1107421875, -5.32255859374996], + [102.3717773437501, -5.366406250000011], + [102.36718750000003, -5.478710937499983] + ] + ], + [ + [ + [123.62675781250007, -5.271582031249963], + [123.58261718750006, -5.36738281250004], + [123.54277343750002, -5.271093749999963], + [123.62675781250007, -5.271582031249963] + ] + ], + [ + [ + [122.04296874999997, -5.437988281250028], + [121.80849609375, -5.256152343750017], + [121.91367187500012, -5.072265624999957], + [122.04101562500003, -5.158789062499991], + [122.04296874999997, -5.437988281250028] + ] + ], + [ + [ + [122.64511718750012, -5.26943359374998], + [122.5638671875, -5.3875], + [122.28310546875, -5.319531249999969], + [122.39628906250002, -5.069824218749986], + [122.36894531250007, -4.767187499999977], + [122.70195312500002, -4.61865234375], + [122.75986328125012, -4.933886718750003], + [122.61406250000007, -5.138671874999986], + [122.64511718750012, -5.26943359374998] + ] + ], + [ + [ + [123.17978515625006, -4.551171875000023], + [123.195703125, -4.82265625], + [123.05517578124997, -4.748242187500026], + [122.97167968750003, -5.138476562500031], + [123.18730468750007, -5.333007812499957], + [122.96875, -5.405761718749943], + [122.81210937500012, -5.671289062499952], + [122.64501953124997, -5.663378906250031], + [122.58642578124997, -5.488867187500006], + [122.76650390625005, -5.210156249999983], + [122.85332031250007, -4.618359375000026], + [123.074609375, -4.38691406250004], + [123.17978515625006, -4.551171875000023] + ] + ], + [ + [ + [133.57080078124997, -4.245898437500003], + [133.621875, -4.299316406249957], + [133.32089843750006, -4.111035156249969], + [133.57080078124997, -4.245898437500003] + ] + ], + [ + [ + [123.2423828125001, -4.112988281250011], + [123.07617187499997, -4.227148437499991], + [122.96904296875002, -4.029980468749969], + [123.21191406250003, -3.997558593750028], + [123.2423828125001, -4.112988281250011] + ] + ], + [ + [ + [128.56259765625012, -3.58544921875], + [128.39160156250003, -3.637890625000026], + [128.45156250000005, -3.514746093749991], + [128.56259765625012, -3.58544921875] + ] + ], + [ + [ + [128.2755859375001, -3.67460937499996], + [127.97802734374997, -3.770996093749972], + [127.925, -3.69931640625002], + [128.32910156249997, -3.51591796874996], + [128.2755859375001, -3.67460937499996] + ] + ], + [ + [ + [116.42412109375007, -3.464453124999963], + [116.38779296875012, -3.636718749999972], + [116.3265625, -3.539062499999972], + [116.42412109375007, -3.464453124999963] + ] + ], + [ + [ + [116.30332031250006, -3.868164062499957], + [116.05878906250004, -4.006933593749991], + [116.06357421875006, -3.457910156249952], + [116.26972656250004, -3.251074218750006], + [116.30332031250006, -3.868164062499957] + ] + ], + [ + [ + [126.86113281250007, -3.087890624999986], + [127.22734375000007, -3.391015625], + [127.22958984375006, -3.633007812500011], + [126.68632812500007, -3.823632812500037], + [126.21455078125004, -3.605175781250026], + [126.05654296875, -3.420996093749991], + [126.08828125, -3.105468750000014], + [126.86113281250007, -3.087890624999986] + ] + ], + [ + [ + [106.88642578125004, -3.005273437500023], + [106.7428710937501, -2.932812500000011], + [106.91064453124997, -2.93398437499998], + [106.88642578125004, -3.005273437500023] + ] + ], + [ + [ + [129.75468750000007, -2.865820312500034], + [130.3791015625001, -2.989355468749977], + [130.56992187500006, -3.130859375000028], + [130.85996093750006, -3.570312500000028], + [130.805078125, -3.85771484374996], + [129.844140625, -3.327148437499957], + [129.51171875000003, -3.32851562499998], + [129.46767578125005, -3.453222656249977], + [128.8625, -3.234960937500006], + [128.51660156249997, -3.449121093750037], + [128.13203125000004, -3.157421875000026], + [127.90234374999997, -3.496289062499955], + [127.87792968749997, -3.222070312499966], + [128.19853515625002, -2.865917968749969], + [128.99111328125, -2.82851562499998], + [129.17441406250006, -2.933496093749966], + [129.48417968750002, -2.785742187499977], + [129.75468750000007, -2.865820312500034] + ] + ], + [ + [ + [100.42509765625007, -3.182910156249974], + [100.46513671875007, -3.32851562499998], + [100.20429687500004, -2.98681640625], + [100.19853515625002, -2.785546875000023], + [100.45458984375003, -3.001953124999972], + [100.42509765625007, -3.182910156249974] + ] + ], + [ + [ + [108.2072265625001, -2.997656249999977], + [108.05527343750006, -3.22685546874996], + [107.85820312500002, -3.086328125000023], + [107.61445312500004, -3.209375], + [107.56347656250003, -2.920117187499997], + [107.66630859375002, -2.566308593750037], + [107.83779296875005, -2.530273437499972], + [108.21513671875002, -2.696972656250011], + [108.29062500000012, -2.829980468750023], + [108.2072265625001, -2.997656249999977] + ] + ], + [ + [ + [100.20410156249997, -2.741015625000017], + [100.01494140625007, -2.819726562499966], + [99.98789062500006, -2.525390624999957], + [100.20410156249997, -2.741015625000017] + ] + ], + [ + [ + [99.84306640625007, -2.343066406250031], + [99.60703125000012, -2.257519531250011], + [99.57216796875005, -2.025781249999966], + [99.84306640625007, -2.343066406250031] + ] + ], + [ + [ + [126.055078125, -2.451269531249963], + [125.86289062500006, -2.077148437499943], + [125.92275390625, -1.974804687499969], + [126.055078125, -2.451269531249963] + ] + ], + [ + [ + [126.02421875000007, -1.789746093750011], + [126.33173828125004, -1.822851562500006], + [125.47919921875004, -1.940039062499991], + [125.38720703124997, -1.843066406249946], + [126.02421875000007, -1.789746093750011] + ] + ], + [ + [ + [130.35332031250007, -1.690527343749963], + [130.41884765625, -1.971289062499963], + [130.24804687500003, -2.047753906249994], + [129.7376953125, -1.866894531250011], + [130.35332031250007, -1.690527343749963] + ] + ], + [ + [ + [124.96953125000007, -1.70546875], + [125.18789062500005, -1.712890624999986], + [125.31406250000006, -1.877148437499969], + [124.41777343750002, -2.005175781250031], + [124.32968750000012, -1.858886718749972], + [124.41757812500006, -1.659277343749991], + [124.96953125000007, -1.70546875] + ] + ], + [ + [ + [135.47421875000006, -1.591796875000014], + [136.89257812500003, -1.799707031249994], + [136.22812500000012, -1.893652343749949], + [135.47421875000006, -1.591796875000014] + ] + ], + [ + [ + [108.953125, -1.61962890625], + [108.83789062499997, -1.661621093750028], + [108.80371093750003, -1.567773437499994], + [108.953125, -1.61962890625] + ] + ], + [ + [ + [106.04570312500002, -1.669433593750014], + [106.36591796875004, -2.464843749999972], + [106.81845703125006, -2.573339843749963], + [106.6120117187501, -2.895507812499957], + [106.66718750000004, -3.071777343749986], + [105.99873046875004, -2.824902343749955], + [105.7858398437501, -2.18134765625004], + [105.13339843750012, -2.042578125], + [105.45957031250006, -1.574707031249986], + [105.58544921875003, -1.526757812499994], + [105.7008789062501, -1.731054687499963], + [105.7204101562501, -1.533886718750026], + [105.91005859375, -1.504980468749991], + [106.04570312500002, -1.669433593750014] + ] + ], + [ + [ + [123.59755859375, -1.704296875000011], + [123.48251953125006, -1.681445312499974], + [123.52851562500004, -1.502832031250009], + [123.59755859375, -1.704296875000011] + ] + ], + [ + [ + [128.1530273437501, -1.66054687499998], + [127.56162109375012, -1.728515624999972], + [127.39501953125003, -1.589843749999972], + [127.64667968750004, -1.332421875], + [128.1530273437501, -1.66054687499998] + ] + ], + [ + [ + [123.2123046875, -1.171289062499966], + [123.23779296874997, -1.389355468749983], + [123.43476562500004, -1.236816406249986], + [123.54726562500005, -1.337402343749957], + [123.51191406250004, -1.447363281249977], + [123.27490234374997, -1.437207031249955], + [123.17294921875006, -1.616015624999974], + [123.15039062500003, -1.304492187500003], + [122.89042968750007, -1.58720703124996], + [122.81083984375002, -1.432128906249986], + [122.90800781250002, -1.182226562499963], + [123.2123046875, -1.171289062499966] + ] + ], + [ + [ + [109.71025390625007, -1.1806640625], + [109.46367187500002, -1.277539062500026], + [109.4759765625, -0.9853515625], + [109.74335937500004, -1.039355468749989], + [109.71025390625007, -1.1806640625] + ] + ], + [ + [ + [134.96533203124997, -1.116015624999974], + [134.86171875, -1.114160156249952], + [134.82792968750002, -0.978808593750003], + [134.99628906250004, -1.03408203124998], + [134.96533203124997, -1.116015624999974] + ] + ], + [ + [ + [99.16386718750007, -1.777929687500006], + [98.82773437500006, -1.609960937499977], + [98.60175781250004, -1.197851562499949], + [98.67607421875007, -0.970507812500003], + [98.93261718750003, -0.954003906250009], + [99.2672851562501, -1.62773437499996], + [99.16386718750007, -1.777929687500006] + ] + ], + [ + [ + [131.00185546875005, -1.315527343750034], + [130.78232421875006, -1.255468749999963], + [130.67294921875006, -0.959765625000031], + [131.03300781250007, -0.917578124999963], + [131.00185546875005, -1.315527343750034] + ] + ], + [ + [ + [135.38300781250004, -0.6513671875], + [135.89355468749997, -0.725781249999969], + [136.37529296875007, -1.094042968750031], + [136.1647460937501, -1.214746093750023], + [135.91503906250003, -1.178417968749997], + [135.74707031249997, -0.823046874999974], + [135.64570312500004, -0.881933593749991], + [135.38300781250004, -0.6513671875] + ] + ], + [ + [ + [127.30039062500012, -0.780957031250026], + [127.1564453125001, -0.760937500000026], + [127.20908203125006, -0.619335937499955], + [127.30039062500012, -0.780957031250026] + ] + ], + [ + [ + [130.6266601562501, -0.528710937499966], + [130.46542968750006, -0.486523437499983], + [130.6159179687501, -0.417285156250003], + [130.6266601562501, -0.528710937499966] + ] + ], + [ + [ + [121.86435546875012, -0.406835937500006], + [121.88125, -0.502636718749983], + [121.65527343749997, -0.526171874999989], + [121.86435546875012, -0.406835937500006] + ] + ], + [ + [ + [140.97343750000007, -2.609765625], + [140.97353515625, -2.803417968750026], + [140.975, -6.346093750000023], + [140.86230468749997, -6.740039062499989], + [140.97519531250006, -6.90537109375002], + [140.97617187500012, -9.11875], + [140.00292968749997, -8.19550781250004], + [140.11699218750002, -7.923730468750009], + [139.93476562500004, -8.101171875], + [139.38564453125, -8.189062499999963], + [139.24882812500002, -7.982421874999972], + [138.890625, -8.237792968749943], + [139.08798828125012, -7.587207031250017], + [138.74794921875, -7.25146484375], + [139.17685546875006, -7.1904296875], + [138.84570312500003, -7.13632812499999], + [138.60136718750007, -6.936523437499972], + [138.86455078125007, -6.858398437499943], + [138.43867187500004, -6.343359375], + [138.2962890625, -5.94902343749996], + [138.37460937500006, -5.84365234374998], + [138.19960937500005, -5.80703125], + [138.33964843750007, -5.675683593749966], + [138.08710937500004, -5.70917968750004], + [138.06083984375002, -5.46523437499998], + [137.27978515624997, -4.945410156249949], + [136.61884765625004, -4.81875], + [135.97968750000004, -4.530859374999963], + [135.19560546875007, -4.450683593749972], + [134.67968749999997, -4.079101562499943], + [134.70654296875003, -3.954785156250026], + [134.88652343750007, -3.938476562499986], + [134.26621093750012, -3.945800781249972], + [134.14707031250006, -3.79677734374998], + [133.97382812500004, -3.817968750000034], + [133.67832031250006, -3.4794921875], + [133.8415039062501, -3.054785156249991], + [133.70039062500004, -3.0875], + [133.653125, -3.364355468749991], + [133.51816406250012, -3.411914062500003], + [133.40087890625003, -3.899023437500034], + [133.24873046875004, -4.062304687499989], + [132.91445312500005, -4.05693359374996], + [132.75390625000003, -3.703613281250014], + [132.86972656250006, -3.550976562499997], + [132.75136718750005, -3.294628906249997], + [131.97119140624997, -2.788574218750014], + [132.2306640625001, -2.680371093749997], + [132.725, -2.789062500000028], + [133.19101562500006, -2.43779296874996], + [133.70009765625005, -2.624609375], + [133.75332031250005, -2.450683593750014], + [133.90488281250012, -2.390917968750003], + [133.79101562500003, -2.293652343749997], + [133.92158203125004, -2.102050781249957], + [132.96279296875005, -2.272558593749963], + [132.30761718749997, -2.24228515625002], + [132.02343749999997, -1.99033203125002], + [131.93037109375004, -1.559667968750034], + [131.29375, -1.393457031250009], + [130.99589843750007, -1.42470703124998], + [131.1908203125, -1.165820312500003], + [131.2572265625, -0.855468750000014], + [131.80429687500006, -0.703808593750026], + [132.39375, -0.355468750000028], + [132.85644531250003, -0.417382812500023], + [133.47265624999997, -0.726171874999963], + [133.97451171875, -0.744335937500026], + [134.11152343750004, -0.84677734375002], + [134.07197265625004, -1.001855468749994], + [134.25957031250007, -1.362988281250026], + [134.105859375, -1.720996093749946], + [134.19482421875003, -2.309082031249943], + [134.45996093749997, -2.83232421874996], + [134.48330078125, -2.583007812499972], + [134.62744140624997, -2.536718749999963], + [134.70214843749997, -2.933593749999986], + [134.84335937500006, -2.909179687499986], + [134.88681640625006, -3.209863281249966], + [135.25156250000012, -3.368554687499966], + [135.48662109375002, -3.34511718749998], + [135.85917968750002, -2.99531250000004], + [136.38994140625002, -2.273339843750037], + [137.07207031250002, -2.105078124999949], + [137.1710937500001, -2.025488281249991], + [137.1234375, -1.840917968749963], + [137.80625000000012, -1.483203125], + [139.78955078125003, -2.34824218750002], + [140.62255859374997, -2.44580078125], + [140.74746093750005, -2.607128906249997], + [140.97343750000007, -2.609765625] + ] + ], + [ + [ + [104.47421875000012, -0.334667968749955], + [104.59013671875002, -0.466601562500017], + [104.36318359375, -0.658593749999966], + [104.25712890625002, -0.463281249999966], + [104.47421875000012, -0.334667968749955] + ] + ], + [ + [ + [127.56699218750006, -0.318945312499949], + [127.68242187500002, -0.46835937500002], + [127.60498046874997, -0.610156249999946], + [127.88017578125002, -0.808691406249991], + [127.7611328125, -0.883691406249994], + [127.62382812500002, -0.76601562499999], + [127.46269531250002, -0.80595703124996], + [127.46865234375, -0.64296875], + [127.3, -0.500292968749946], + [127.32509765625, -0.335839843750023], + [127.45517578125012, -0.406347656249991], + [127.56699218750006, -0.318945312499949] + ] + ], + [ + [ + [127.24990234375005, -0.4953125], + [127.11914062500003, -0.520507812499986], + [127.12646484375003, -0.278613281250003], + [127.29003906250003, -0.284375], + [127.24990234375005, -0.4953125] + ] + ], + [ + [ + [103.73652343750004, -0.347949218750003], + [103.461328125, -0.357617187500011], + [103.54892578125006, -0.227539062499986], + [103.73652343750004, -0.347949218750003] + ] + ], + [ + [ + [130.81328125000007, -0.004101562500026], + [131.27685546875003, -0.149804687499952], + [131.33974609375005, -0.290332031249989], + [131.00537109374997, -0.360742187500037], + [130.62216796875006, -0.0859375], + [130.89921875000002, -0.344433593749997], + [130.7501953125001, -0.44384765625], + [130.6886718750001, -0.296582031250011], + [130.55078124999997, -0.366406250000026], + [130.23662109375002, -0.209667968749983], + [130.3625, -0.072851562500006], + [130.81328125000007, -0.004101562500026] + ] + ], + [ + [ + [98.45927734375007, -0.530468749999969], + [98.30966796875012, -0.531835937499977], + [98.4271484375, -0.226464843750037], + [98.3229492187501, -0.000781249999974], + [98.54414062500004, -0.257617187499989], + [98.45927734375007, -0.530468749999969] + ] + ], + [ + [ + [104.77861328125007, -0.175976562499955], + [105.00537109374997, -0.282812499999963], + [104.44707031250002, -0.189160156249983], + [104.54267578125004, 0.01772460937498], + [104.77861328125007, -0.175976562499955] + ] + ], + [ + [ + [103.28447265625002, 0.541943359375011], + [103.13955078125, 0.549072265625043], + [103.18740234375, 0.699755859375017], + [103.28447265625002, 0.541943359375011] + ] + ], + [ + [ + [103.0275390625001, 0.746630859374974], + [102.4904296875001, 0.856640625], + [102.50664062500002, 1.088769531250037], + [103.00244140624997, 0.859277343750009], + [103.0275390625001, 0.746630859374974] + ] + ], + [ + [ + [103.42392578125012, 1.048339843749972], + [103.31542968750003, 1.071289062500028], + [103.37998046875006, 1.133642578125034], + [103.42392578125012, 1.048339843749972] + ] + ], + [ + [ + [103.16640625000005, 0.870166015625003], + [102.7018554687501, 1.0537109375], + [102.72558593749997, 1.158837890625023], + [102.99941406250005, 1.067773437500023], + [103.16640625000005, 0.870166015625003] + ] + ], + [ + [ + [104.02480468750005, 1.180566406250009], + [104.13984375000004, 1.165576171874974], + [104.06611328125004, 0.989550781249989], + [103.93222656250012, 1.071386718749963], + [104.02480468750005, 1.180566406250009] + ] + ], + [ + [ + [104.58535156250005, 1.21611328124996], + [104.66289062500002, 1.04951171875004], + [104.57519531250003, 0.831933593750037], + [104.43925781250002, 1.050439453125051], + [104.25195312499997, 1.014892578125], + [104.36181640624997, 1.18149414062502], + [104.58535156250005, 1.21611328124996] + ] + ], + [ + [ + [102.4271484375, 0.990136718750023], + [102.27958984375002, 1.075683593750043], + [102.25634765625003, 1.397070312499963], + [102.44287109374997, 1.234228515625006], + [102.4271484375, 0.990136718750023] + ] + ], + [ + [ + [97.48154296875006, 1.465087890624972], + [97.93193359375002, 0.973925781250003], + [97.82041015625012, 0.564453124999986], + [97.683984375, 0.596093750000037], + [97.60390625000005, 0.83388671874998], + [97.40537109375012, 0.946972656250026], + [97.07919921875006, 1.425488281249983], + [97.35595703124997, 1.539746093749997], + [97.48154296875006, 1.465087890624972] + ] + ], + [ + [ + [102.49189453125004, 1.459179687500011], + [102.49941406250005, 1.330908203124991], + [102.02402343750012, 1.607958984375031], + [102.49189453125004, 1.459179687500011] + ] + ], + [ + [ + [124.88886718750004, 0.995312500000011], + [124.42753906250002, 0.470605468750051], + [123.75380859375, 0.305517578124991], + [123.26542968750007, 0.326611328125026], + [122.996875, 0.493505859375006], + [121.01298828125002, 0.441699218750017], + [120.57900390625, 0.5283203125], + [120.19228515625, 0.268505859374997], + [120.01328125000012, -0.196191406249994], + [120.062890625, -0.555566406250023], + [120.240625, -0.868261718749949], + [120.51757812499997, -1.039453125], + [120.66738281250005, -1.370117187499972], + [121.14853515625012, -1.33945312500002], + [121.5755859375, -0.828515625000023], + [121.96962890625005, -0.933300781249969], + [122.27998046875004, -0.757031250000026], + [122.88876953125006, -0.755175781250003], + [122.8294921875, -0.658886718750026], + [123.17148437500012, -0.57070312499999], + [123.37968750000002, -0.648535156249949], + [123.43417968750006, -0.778222656249994], + [123.37792968749997, -1.004101562500011], + [122.90283203125003, -0.900976562499963], + [122.25068359375004, -1.555273437500034], + [121.8585937500001, -1.69326171874998], + [121.65097656250006, -1.895410156249952], + [121.35546874999997, -1.878222656250003], + [122.29169921875004, -2.907617187500023], + [122.39902343750006, -3.200878906249997], + [122.25292968749997, -3.620410156250017], + [122.68964843750004, -4.084472656249972], + [122.84794921875002, -4.064550781250006], + [122.8722656250001, -4.391992187500009], + [122.71972656250003, -4.340722656249952], + [122.11425781250003, -4.540234375000011], + [122.03808593749997, -4.832421875000023], + [121.58867187500007, -4.759570312500017], + [121.48652343750004, -4.581054687499972], + [121.61806640625, -4.092675781249952], + [120.89179687500004, -3.520605468750034], + [121.05429687500012, -3.167089843749949], + [121.0521484375, -2.751660156249955], + [120.87939453124997, -2.64560546875002], + [120.65361328125002, -2.667578124999977], + [120.26103515625007, -2.949316406249991], + [120.43662109375012, -3.70732421874996], + [120.42011718750004, -4.617382812500011], + [120.27929687499997, -5.146093749999977], + [120.4303710937501, -5.591015625000026], + [119.9515625, -5.577636718749972], + [119.71728515625003, -5.693359375000014], + [119.55742187500007, -5.611035156250026], + [119.36035156249997, -5.314160156250026], + [119.59404296875007, -4.523144531249997], + [119.62363281250006, -4.034375], + [119.46748046875004, -3.512988281249989], + [118.99462890624997, -3.537597656250028], + [118.86767578124997, -3.39804687500002], + [118.78330078125006, -2.720800781249977], + [119.09218750000005, -2.482910156250014], + [119.32187500000012, -1.929687500000014], + [119.308984375, -1.408203125], + [119.508203125, -0.906738281249972], + [119.71132812500005, -0.680761718750034], + [119.84433593750006, -0.861914062499991], + [119.721875, -0.088476562499991], + [119.865625, 0.040087890625003], + [119.80927734375004, 0.238671875000051], + [119.9132812500001, 0.445068359375], + [120.26953125000003, 0.970800781249991], + [120.60253906249997, 0.854394531249994], + [120.86796875000007, 1.25283203124998], + [121.0817382812501, 1.327636718750028], + [121.40410156250002, 1.243603515624969], + [121.59179687499997, 1.067968749999977], + [122.43662109375006, 1.018066406250028], + [122.83828125, 0.845703125], + [123.06650390625006, 0.941796875000037], + [123.93076171875006, 0.850439453124977], + [124.53369140624997, 1.230468750000043], + [124.94707031250002, 1.672167968749974], + [125.11093750000012, 1.685693359374966], + [125.2337890625, 1.502294921875006], + [124.88886718750004, 0.995312500000011] + ] + ], + [ + [ + [101.70810546875006, 2.078417968750045], + [101.71943359375004, 1.789160156250006], + [101.50078125000002, 1.733203124999974], + [101.40966796875003, 2.021679687500026], + [101.70810546875006, 2.078417968750045] + ] + ], + [ + [ + [127.73271484375007, 0.848144531250043], + [127.8810546875001, 0.832128906249977], + [127.96728515624997, 1.042578125000048], + [128.16074218750006, 1.1578125], + [128.22246093750002, 1.400634765624986], + [128.68837890625, 1.572558593750017], + [128.70263671874997, 1.106396484374997], + [128.29882812500003, 0.87680664062502], + [128.26064453125, 0.733789062500023], + [128.61123046875, 0.549951171875051], + [128.89960937500004, 0.216259765625011], + [127.9831054687501, 0.471875], + [127.88740234375004, 0.298339843750043], + [127.97783203125002, -0.24833984374996], + [128.4254882812501, -0.892675781249949], + [128.04638671875003, -0.706054687499943], + [127.69160156250004, -0.241894531249983], + [127.70869140625004, 0.288085937499986], + [127.53710937500003, 0.610888671875031], + [127.60800781250006, 0.848242187499977], + [127.42851562500002, 1.139990234374991], + [127.63173828125, 1.843701171875011], + [128.03642578125002, 2.199023437500017], + [127.88681640625012, 1.83295898437504], + [128.0109375000001, 1.701220703125031], + [128.01171874999997, 1.331738281249983], + [127.65283203124997, 1.013867187499969], + [127.73271484375007, 0.848144531250043] + ] + ], + [ + [ + [97.3341796875001, 2.075634765625011], + [97.10830078125, 2.216894531250006], + [97.29140625, 2.200830078125023], + [97.3341796875001, 2.075634765625011] + ] + ], + [ + [ + [128.45390625000002, 2.051757812500028], + [128.29589843749997, 2.034716796875017], + [128.2179687500001, 2.297460937499991], + [128.60214843750012, 2.59760742187504], + [128.68847656250003, 2.473681640625017], + [128.62324218750004, 2.224414062500031], + [128.45390625000002, 2.051757812500028] + ] + ], + [ + [ + [96.46367187500002, 2.360009765625037], + [95.80859374999997, 2.655615234375034], + [95.7171875, 2.825976562500017], + [95.89580078125007, 2.8890625], + [96.41728515625007, 2.515185546875031], + [96.46367187500002, 2.360009765625037] + ] + ], + [ + [ + [108.8875, 2.905419921875037], + [108.7865234375, 2.885644531250009], + [108.88574218750003, 2.998974609374997], + [108.8875, 2.905419921875037] + ] + ], + [ + [ + [105.76035156250006, 2.863037109375014], + [105.69218750000002, 3.0625], + [105.83671875000007, 2.97651367187504], + [105.76035156250006, 2.863037109375014] + ] + ], + [ + [ + [106.28525390625006, 3.15712890624998], + [106.28369140624997, 3.088232421874977], + [106.20097656250002, 3.204882812500031], + [106.28525390625006, 3.15712890624998] + ] + ], + [ + [ + [117.65839843750004, 3.280517578124986], + [117.54785156250003, 3.43198242187502], + [117.68085937500004, 3.407519531250017], + [117.65839843750004, 3.280517578124986] + ] + ], + [ + [ + [125.65810546875, 3.436035156250043], + [125.51152343750007, 3.461132812500011], + [125.46884765625006, 3.73325195312502], + [125.65810546875, 3.436035156250043] + ] + ], + [ + [ + [117.88476562499997, 4.186132812500006], + [117.92285156250003, 4.054296874999977], + [117.73681640624997, 4.004003906250034], + [117.64902343750012, 4.168994140624974], + [117.88476562499997, 4.186132812500006] + ] + ], + [ + [ + [108.31601562500006, 3.689648437500026], + [108.10039062500002, 3.70454101562504], + [108.24326171875006, 3.810351562500017], + [108.00234375, 3.982861328124983], + [108.24833984375002, 4.21713867187502], + [108.39287109375007, 3.986181640625034], + [108.31601562500006, 3.689648437500026] + ] + ], + [ + [ + [117.5744140625001, 4.17060546875004], + [117.46533203124997, 4.076074218749966], + [117.77724609375005, 3.689257812500031], + [117.05595703125007, 3.622656249999963], + [117.34628906250006, 3.426611328124991], + [117.35244140625, 3.19375], + [117.61064453125002, 3.064355468749994], + [117.56914062500002, 2.92929687500002], + [117.69765625, 2.887304687499991], + [117.6388671875001, 2.825292968749963], + [118.0666015625001, 2.317822265624969], + [117.7892578125001, 2.026855468750014], + [118.98496093750006, 0.982128906249983], + [118.53476562500006, 0.813525390625017], + [118.19609375000002, 0.874365234374977], + [117.91162109374997, 1.098681640625017], + [117.96425781250005, 0.889550781250051], + [117.74511718749997, 0.72963867187498], + [117.52216796875004, 0.235888671875017], + [117.46289062500003, -0.323730468749957], + [117.5625, -0.770898437500009], + [116.91396484375, -1.223632812499972], + [116.73984375000006, -1.044238281250017], + [116.75341796874997, -1.327343749999955], + [116.27548828125006, -1.784863281249997], + [116.42431640625003, -1.784863281249997], + [116.45195312500002, -1.923144531250017], + [116.31396484374997, -2.139843750000011], + [116.56542968749997, -2.299707031249994], + [116.52929687499997, -2.51054687499996], + [116.31679687500005, -2.55185546875002], + [116.33066406250012, -2.902148437499974], + [116.16630859375002, -2.934570312500014], + [116.2572265625, -3.126367187500009], + [115.95615234375012, -3.595019531250003], + [114.6935546875001, -4.169726562500017], + [114.5255859375001, -3.376660156250011], + [114.44599609375004, -3.481835937500037], + [114.34433593750012, -3.444433593749963], + [114.34433593750012, -3.23515625], + [114.23632812500003, -3.36113281249996], + [114.0822265625001, -3.27890625], + [113.70507812499997, -3.45527343750004], + [113.6100585937501, -3.195703125], + [113.34316406250005, -3.246484374999966], + [113.03398437500002, -2.933496093749966], + [112.97148437500002, -3.187109375000034], + [112.75800781250004, -3.322167968750009], + [112.60029296875004, -3.400488281249977], + [112.28496093750002, -3.32099609375004], + [111.85810546875004, -3.551855468750006], + [111.82304687500007, -3.057226562499949], + [111.69472656250005, -2.88945312499996], + [110.93007812500005, -3.071093750000017], + [110.82968750000012, -2.9951171875], + [110.89931640625, -2.908593749999952], + [110.703125, -3.020898437500009], + [110.57402343750007, -2.89140625], + [110.25605468750004, -2.966113281249946], + [110.09658203125, -2.001367187499966], + [109.95986328125, -1.862792968749972], + [109.98330078125, -1.274804687499994], + [109.78740234375007, -1.011328124999963], + [109.25878906250003, -0.807421874999989], + [109.37275390625004, -0.638183593749972], + [109.12109375000003, -0.39091796874996], + [109.2575195312501, 0.031152343750051], + [108.94453125000004, 0.355664062499997], + [108.91679687500007, 0.912646484375045], + [108.95859375000006, 1.134619140624963], + [109.1315429687501, 1.253857421875011], + [109.01025390624997, 1.239648437500051], + [109.07587890625004, 1.495898437500031], + [109.37851562500006, 1.922705078125034], + [109.62890625000003, 2.027539062499983], + [109.53896484375, 1.89619140625004], + [109.65400390625004, 1.614892578125023], + [110.50576171875005, 0.861962890625023], + [111.10136718750002, 1.050537109374986], + [111.80898437500005, 1.011669921874969], + [112.078515625, 1.143359374999974], + [112.1857421875001, 1.4390625], + [112.47617187500006, 1.559082031250028], + [112.94296875000006, 1.566992187500034], + [113.00654296875004, 1.433886718750003], + [113.6222656250001, 1.2359375], + [113.90234375000003, 1.434277343749997], + [114.5125, 1.452001953124963], + [114.83056640625003, 1.980029296874989], + [114.78642578125002, 2.250488281250014], + [115.1791015625, 2.523193359374972], + [115.08076171875004, 2.63422851562504], + [115.117578125, 2.89487304687502], + [115.24697265625005, 3.025927734374989], + [115.45439453125002, 3.034326171875009], + [115.67880859375006, 4.193017578124994], + [115.86074218750005, 4.348046875000037], + [116.51474609375006, 4.370800781249969], + [117.10058593750003, 4.337060546875023], + [117.5744140625001, 4.17060546875004] + ] + ], + [ + [ + [126.81660156250004, 4.033496093750003], + [126.70449218750005, 4.070996093749997], + [126.81357421875006, 4.258496093750011], + [126.72207031250005, 4.344189453124969], + [126.75732421874997, 4.547900390624989], + [126.9210937500001, 4.291015624999972], + [126.81660156250004, 4.033496093750003] + ] + ], + [ + [ + [96.49257812500005, 5.229345703124991], + [97.54716796875002, 5.205859375], + [98.2484375, 4.41455078125], + [98.3073242187501, 4.09287109375002], + [99.73232421875005, 3.183056640625026], + [100.523828125, 2.18916015625004], + [100.88789062500004, 1.948242187499986], + [100.82822265625012, 2.242578125], + [101.04619140625002, 2.257470703125023], + [101.47666015625006, 1.693066406250054], + [102.019921875, 1.442138671875], + [102.38994140625007, 0.84199218750004], + [103.03183593750006, 0.57890625], + [103.0075195312501, 0.415332031249974], + [102.55, 0.216455078124966], + [103.33896484375012, 0.513720703125045], + [103.67265625000007, 0.288916015624977], + [103.78671875000012, 0.046972656249991], + [103.42851562500007, -0.19179687499998], + [103.40517578125005, -0.36220703124998], + [103.5091796875, -0.465527343749969], + [103.43857421875006, -0.575585937500009], + [103.72109375, -0.886718749999986], + [104.36054687500004, -1.038378906249974], + [104.51591796875002, -1.81943359375002], + [104.84521484375003, -2.092968749999969], + [104.65078125000005, -2.595214843749972], + [104.97080078125012, -2.370898437500017], + [105.39697265624997, -2.380175781249946], + [106.0443359375, -3.10625], + [105.84375, -3.61367187499998], + [105.93046875000007, -3.833007812499986], + [105.83144531250005, -4.16289062499996], + [105.88720703124997, -5.009570312499974], + [105.74833984375007, -5.818261718749966], + [105.34941406250007, -5.549511718750011], + [105.08134765625002, -5.74550781249998], + [104.63955078125005, -5.520410156250037], + [104.68398437500005, -5.89267578125002], + [104.60156249999997, -5.90458984374996], + [103.8314453125, -5.079589843750028], + [102.53769531250006, -4.152148437499989], + [102.12753906250006, -3.599218749999963], + [101.57861328124997, -3.166992187500014], + [100.88955078125, -2.248535156249957], + [100.85527343750002, -1.934179687499949], + [100.30820312500006, -0.82666015625], + [99.66982421875005, 0.045068359375037], + [99.15917968749997, 0.351757812499997], + [98.59531250000006, 1.864599609375006], + [97.70078125000006, 2.358544921875009], + [97.59082031249997, 2.846582031250037], + [97.3913085937501, 2.975292968749969], + [96.9689453125001, 3.575146484374969], + [96.44472656250005, 3.81630859374998], + [95.57861328125003, 4.661962890625048], + [95.20664062500006, 5.284033203125034], + [95.22783203125002, 5.564794921875034], + [95.62890625000003, 5.609082031249997], + [96.13330078125003, 5.294287109374991], + [96.49257812500005, 5.229345703124991] + ] + ] + ] + }, + "properties": { "name": "Indonesia", "childNum": 107 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-4.412060546874983, 54.185351562499996], + [-4.785351562499983, 54.073046875], + [-4.424707031249994, 54.407177734375], + [-4.412060546874983, 54.185351562499996] + ] + ] + }, + "properties": { "name": "Isle of Man", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [75.32221348233018, 32.28516356678968], + [75.62496871116024, 32.28516356678968], + [75.73585997688717, 32.78417426256088], + [76.32728006076415, 32.87658365066666], + [76.62299010270264, 33.32014871357439], + [77.06655516561037, 33.301666835953235], + [77.71342088235082, 32.6917648744551], + [78.10154031239509, 32.87658365066666], + [78.49194250885338, 32.53122786149202], + [78.38964843749997, 32.51987304687498], + [78.41748046874997, 32.466699218749994], + [78.4552734375001, 32.30034179687502], + [78.49589843750002, 32.21577148437504], + [78.72558593750009, 31.983789062500023], + [78.71972656250009, 31.887646484374983], + [78.69345703125006, 31.740380859374994], + [78.7550781250001, 31.55029296875], + [78.74355468750005, 31.323779296875017], + [79.10712890625004, 31.402636718750102], + [79.38847656250013, 31.064208984375085], + [79.66425781250004, 30.96523437499999], + [79.92451171875004, 30.888769531250034], + [80.20712890625006, 30.683740234375023], + [80.19121093750002, 30.56840820312496], + [80.87353515625003, 30.290576171875045], + [80.98544921875006, 30.23710937499999], + [81.01025390625014, 30.164501953125097], + [80.96611328125002, 30.180029296875063], + [80.90761718750005, 30.171923828125017], + [80.84814453125009, 30.139746093750034], + [80.81992187500012, 30.119335937499955], + [80.68408203125014, 29.994335937500068], + [80.54902343750015, 29.899804687499994], + [80.40185546875003, 29.730273437500102], + [80.31689453125014, 29.572070312500017], + [80.25488281250009, 29.423339843750114], + [80.25595703125006, 29.318017578125136], + [80.23300781250006, 29.194628906250045], + [80.16953125000012, 29.124316406250102], + [80.13046875000006, 29.100390625000045], + [80.08457031249995, 28.994189453125074], + [80.05166015625, 28.870312500000068], + [80.07070312500005, 28.830175781250063], + [80.22656250000003, 28.723339843750125], + [80.32480468750012, 28.66640625000008], + [80.41855468749995, 28.61201171875001], + [80.47910156250012, 28.604882812499994], + [80.49580078125015, 28.635791015625074], + [80.51787109375002, 28.665185546875023], + [80.58701171875006, 28.64960937500004], + [81.16894531250014, 28.335009765625074], + [81.85263671875018, 27.867089843750136], + [81.89687500000011, 27.87446289062504], + [81.94521484375005, 27.89926757812495], + [81.98769531250016, 27.91376953125004], + [82.03701171875, 27.90058593750004], + [82.11191406250006, 27.86494140625004], + [82.28769531250018, 27.756542968749983], + [82.45136718750004, 27.671826171874955], + [82.62988281249997, 27.687060546875045], + [82.67734375000006, 27.67343749999995], + [82.71083984375005, 27.596679687500114], + [82.73339843750003, 27.518994140625097], + [83.28974609375004, 27.370996093750136], + [83.36943359375002, 27.410253906249977], + [83.38398437500004, 27.444824218750085], + [83.44716796875011, 27.46533203125], + [83.55166015625011, 27.456347656249932], + [83.74697265625011, 27.395947265625068], + [83.8288085937501, 27.377832031250108], + [84.09101562499993, 27.491357421875136], + [84.22978515625007, 27.427832031250006], + [84.48085937500005, 27.348193359375102], + [84.61015625000002, 27.298681640624977], + [84.64072265625012, 27.249853515624977], + [84.65478515625014, 27.20366210937499], + [84.65380859375009, 27.09169921875008], + [84.68535156250013, 27.041015625000057], + [85.19179687500011, 26.766552734375097], + [85.29296875000009, 26.741015625000045], + [85.56845703125012, 26.839843750000114], + [85.64843749999997, 26.829003906250023], + [85.69990234375004, 26.781640624999966], + [85.73730468750003, 26.639746093750034], + [85.79453125000006, 26.60415039062505], + [86.00732421875009, 26.64936523437504], + [86.70136718750015, 26.435058593750057], + [87.01640625000002, 26.555419921875085], + [87.2874023437499, 26.360302734375125], + [87.41357421875014, 26.42294921875009], + [87.84921875000006, 26.43691406250008], + [87.99511718750014, 26.38237304687499], + [88.02695312500023, 26.395019531250085], + [88.05488281250004, 26.43002929687492], + [88.11152343750004, 26.58642578125], + [88.1615234375, 26.724804687500125], + [88.15722656250009, 26.807324218750068], + [88.1110351562501, 26.928466796875057], + [87.99316406250009, 27.086083984374994], + [87.984375, 27.133935546874994], + [88.14697265625014, 27.749218750000097], + [88.15029296875011, 27.843310546875074], + [88.10976562500005, 27.87060546874997], + [88.10898437499995, 27.93300781250005], + [88.14111328125003, 27.948925781250097], + [88.27519531250013, 27.96884765625009], + [88.42597656250015, 28.011669921875097], + [88.57792968750002, 28.093359375000034], + [88.80371093750003, 28.006933593750034], + [88.74902343749997, 27.521875000000136], + [88.7648437500001, 27.429882812500068], + [88.83251953125003, 27.362841796875074], + [88.89140625000002, 27.316064453125136], + [88.88164062500007, 27.29746093750009], + [88.76035156250006, 27.21811523437509], + [88.73876953125009, 27.175585937499932], + [88.85761718750015, 26.961474609375017], + [89.14824218750002, 26.816162109375085], + [89.33212890625018, 26.848632812500114], + [89.58613281250004, 26.778955078125136], + [89.60996093750012, 26.719433593750097], + [89.71093750000009, 26.713916015625045], + [89.76386718750004, 26.7015625], + [89.94316406250013, 26.723925781249932], + [90.12294921875011, 26.754589843749983], + [90.20605468749997, 26.847509765625063], + [90.34589843750004, 26.890332031250097], + [90.73964843750005, 26.771679687500068], + [91.2865234375, 26.78994140625008], + [91.42675781249997, 26.867089843749966], + [91.45585937500013, 26.866894531250125], + [91.51757812500009, 26.807324218750068], + [91.67158203124993, 26.80200195312503], + [91.84208984375013, 26.852978515625125], + [91.94375, 26.860839843750114], + [91.99833984375013, 26.85498046875], + [92.04970703125016, 26.87485351562495], + [92.73155507489682, 26.833697862861648], + [93.30975376159499, 26.784950522650554], + [93.61047043679247, 27.32239435188504], + [94.06979001484449, 27.589407158584788], + [95.10800937321915, 27.749636881153737], + [95.74000740838363, 28.116850432722256], + [96.19577594042592, 28.04291597700983], + [96.96279296875, 27.698291015625017], + [96.88359375000013, 27.514843750000125], + [96.90195312500012, 27.43959960937508], + [97.10371093749993, 27.163330078125114], + [97.10205078125003, 27.115429687500125], + [96.95341796875013, 27.13330078125003], + [96.79785156249997, 27.29619140624999], + [96.19082031250005, 27.26127929687499], + [95.20146484375007, 26.641406250000017], + [95.05976562500015, 26.473974609375006], + [95.06894531250006, 26.191113281250097], + [95.10839843750014, 26.091406250000034], + [95.12929687500011, 26.070410156250034], + [95.13242187500006, 26.041259765624943], + [94.99199218750002, 25.77045898437504], + [94.66777343750007, 25.458886718749966], + [94.55302734375013, 25.215722656249994], + [94.70371093750012, 25.097851562499955], + [94.49316406250003, 24.637646484374983], + [94.37724609375002, 24.473730468750006], + [94.29306640625012, 24.321875], + [94.07480468750006, 23.8720703125], + [93.68339843750007, 24.00654296875004], + [93.45214843750003, 23.987402343750034], + [93.32626953125006, 24.064208984375057], + [93.36601562500007, 23.132519531249955], + [93.34941406250007, 23.08496093750003], + [93.20390625000002, 23.03701171875005], + [93.07871093750018, 22.718212890625097], + [93.16201171875, 22.360205078125006], + [93.07060546875002, 22.20942382812501], + [92.96455078125015, 22.003759765625034], + [92.90947265625013, 21.988916015625023], + [92.85429687500002, 22.010156250000108], + [92.77138671875, 22.104785156250017], + [92.68896484375009, 22.130957031250006], + [92.63037109375014, 22.011328124999977], + [92.57490234374993, 21.97807617187496], + [92.5612304687501, 22.04804687500001], + [92.49140625000004, 22.685400390625006], + [92.46445312500006, 22.734423828125045], + [92.36162109375002, 22.929003906250074], + [92.33378906250002, 23.242382812499955], + [92.24609375000003, 23.68359374999997], + [92.04404296875006, 23.677783203125017], + [91.97851562500003, 23.691992187500063], + [91.92958984375011, 23.685986328125097], + [91.92949218750019, 23.598242187499977], + [91.93789062500011, 23.504687500000102], + [91.75419921875013, 23.28730468750004], + [91.75097656250003, 23.053515625000017], + [91.55351562500013, 22.991552734375006], + [91.43623046875004, 23.19990234375001], + [91.359375, 23.06835937500003], + [91.16044921875019, 23.660644531250085], + [91.35019531250012, 24.06049804687501], + [91.72656250000003, 24.20507812499997], + [91.84619140624997, 24.175292968749943], + [92.06416015625004, 24.374365234375006], + [92.11748046875002, 24.493945312500017], + [92.22666015625012, 24.77099609374997], + [92.22832031250002, 24.881347656250085], + [92.2512695312499, 24.895068359375045], + [92.38496093750004, 24.848779296875023], + [92.46835937500018, 24.944140625000074], + [92.04970703125016, 25.16948242187499], + [90.61308593750002, 25.16772460937497], + [90.11962890625003, 25.21997070312497], + [89.86630859375012, 25.293164062499955], + [89.81406250000006, 25.305371093749955], + [89.80087890625012, 25.33613281250001], + [89.82490234375004, 25.56015625], + [89.82294921875015, 25.94140625000003], + [89.67089843750009, 26.213818359375125], + [89.57275390625003, 26.13232421875003], + [89.54990234375006, 26.00527343750008], + [89.28925781250015, 26.037597656250085], + [89.01865234375012, 26.410253906249977], + [88.95195312500002, 26.412109375], + [88.97041015625004, 26.250878906250023], + [88.94072265625002, 26.24536132812497], + [88.68281250000004, 26.291699218749983], + [88.51826171875004, 26.51777343750004], + [88.36992187500002, 26.56411132812508], + [88.35146484375005, 26.482568359374966], + [88.38623046875003, 26.471533203125034], + [88.44042968749997, 26.369482421875034], + [88.33398437499997, 26.257519531249955], + [88.15078125000005, 26.087158203125057], + [88.1066406250001, 25.841113281250045], + [88.14746093749997, 25.811425781250023], + [88.50244140625009, 25.53701171875008], + [88.76914062500006, 25.490478515625], + [88.85478515625002, 25.333544921875017], + [88.94414062500002, 25.290771484375], + [88.92978515625012, 25.222998046875063], + [88.57382812500006, 25.18789062499999], + [88.45625, 25.188427734375125], + [88.37294921875016, 24.961523437500063], + [88.31337890625011, 24.8818359375], + [88.27949218750015, 24.881933593750034], + [88.18886718750016, 24.920605468750097], + [88.14980468750011, 24.91464843749995], + [88.04511718750015, 24.71303710937508], + [88.03027343750009, 24.664453125000136], + [88.02343750000003, 24.627832031250136], + [88.07910156250009, 24.549902343750063], + [88.14550781250003, 24.485791015624955], + [88.225, 24.460644531249983], + [88.3375, 24.45385742187503], + [88.49853515625003, 24.34663085937504], + [88.64228515625015, 24.325976562500102], + [88.72353515625011, 24.27490234375], + [88.7335937500001, 24.230908203125097], + [88.72656250000009, 24.18623046875004], + [88.71376953125016, 24.069628906250102], + [88.69980468750006, 24.00253906249992], + [88.56738281250009, 23.674414062500034], + [88.63574218749997, 23.55], + [88.69765625, 23.493017578125034], + [88.72441406250002, 23.254980468750034], + [88.89707031250018, 23.21040039062501], + [88.92812500000011, 23.186621093749977], + [88.89970703125002, 22.843505859375057], + [88.92070312500002, 22.632031249999955], + [89.05, 22.274609374999983], + [89.02792968750023, 21.937207031249983], + [88.94931640625018, 21.937939453125125], + [89.05166015625, 21.654101562500045], + [88.85751953125012, 21.744677734375017], + [88.74501953125011, 21.584375], + [88.74023437500003, 22.005419921875017], + [88.64160156250003, 22.121972656250136], + [88.58466796875015, 21.659716796874932], + [88.44599609375004, 21.614257812500085], + [88.28750000000016, 21.758203125000108], + [88.25371093750002, 21.622314453124943], + [88.0568359375001, 21.694140625000017], + [88.19628906249997, 22.139550781249994], + [87.94140625000003, 22.374316406250045], + [88.15927734375018, 22.12172851562508], + [87.82373046875003, 21.727343750000045], + [87.20068359375009, 21.544873046874983], + [86.95410156250014, 21.365332031250006], + [86.84228515625009, 21.106347656249994], + [86.97548828125005, 20.70014648437501], + [86.75039062500011, 20.313232421875057], + [86.37656250000006, 20.006738281249966], + [86.24521484375012, 20.05302734374999], + [86.27949218750021, 19.919433593749943], + [85.575, 19.69291992187499], + [85.496875, 19.696923828125108], + [85.50410156250004, 19.887695312500057], + [85.24863281250006, 19.757666015625034], + [85.18076171875018, 19.59487304687508], + [85.44160156249993, 19.626562499999977], + [84.77099609375009, 19.125390625000023], + [84.10410156250018, 18.29267578125001], + [82.35957031250004, 17.09619140624997], + [82.25878906250014, 16.55986328124996], + [81.76191406250015, 16.32949218750008], + [81.28613281249997, 16.337060546875023], + [80.97871093750004, 15.758349609375074], + [80.64658203125006, 15.895019531250028], + [80.29345703125014, 15.710742187499989], + [80.0534179687501, 15.074023437499932], + [80.17871093750003, 14.478320312500074], + [80.11171875000005, 14.212207031250045], + [80.30654296875016, 13.485058593750054], + [80.15625, 13.713769531250108], + [80.06210937500006, 13.60625], + [80.34238281250006, 13.361328125000071], + [80.22910156250018, 12.690332031249966], + [79.85849609375018, 11.988769531250043], + [79.69316406250007, 11.312548828124946], + [79.79902343750004, 11.338671874999932], + [79.84863281250009, 11.196875], + [79.83818359375002, 10.322558593750045], + [79.31455078125018, 10.256689453124949], + [78.93994140625009, 9.565771484375063], + [79.01992187500005, 9.333349609374963], + [79.41142578125002, 9.192382812500014], + [78.97958984375018, 9.268554687500085], + [78.42148437500006, 9.105029296874989], + [78.19248046874995, 8.890869140625057], + [78.06015625000006, 8.384570312499932], + [77.51757812500003, 8.078320312500068], + [77.06591796875003, 8.315917968749986], + [76.5534179687501, 8.902783203124997], + [76.32460937500016, 9.452099609374997], + [76.24238281250004, 9.927099609374949], + [76.37558593750006, 9.539892578124935], + [76.45878906250013, 9.536230468750077], + [76.34648437500002, 9.922119140625], + [76.19560546875002, 10.086132812500026], + [75.72382812500015, 11.361767578125026], + [74.94550781250004, 12.56455078124992], + [74.38222656250005, 14.494726562500048], + [73.94921875000014, 15.074755859375088], + [73.80078125000009, 15.39697265625], + [73.93193359375013, 15.39697265625], + [73.77177734375013, 15.573046874999989], + [73.83281250000013, 15.659375], + [73.67988281250015, 15.708886718750136], + [73.47607421875003, 16.05424804687496], + [72.87548828124997, 18.642822265625114], + [72.97207031250011, 19.15332031250003], + [72.8346679687501, 18.975585937500057], + [72.80302734375013, 19.07929687500004], + [72.81162109375, 19.298925781250006], + [72.98720703125, 19.27744140625009], + [72.78789062500013, 19.362988281250097], + [72.66777343750019, 19.83095703125005], + [72.89375, 20.672753906250136], + [72.81386718750011, 21.117187500000085], + [72.62382812500002, 21.371972656250108], + [72.73476562500016, 21.470800781250006], + [72.61328125000009, 21.461816406250108], + [73.1125, 21.750439453125125], + [72.54306640625, 21.69658203124999], + [72.70019531250003, 21.971923828124943], + [72.52226562500013, 21.976220703125108], + [72.55302734375007, 22.159960937500074], + [72.80917968749995, 22.23330078125008], + [72.18281250000015, 22.26972656250004], + [72.30644531250002, 22.18920898437497], + [72.27441406250009, 22.089746093749966], + [72.03720703125006, 21.82304687499999], + [72.2103515625, 21.72822265625004], + [72.25400390625006, 21.531005859375], + [72.01523437500012, 21.155712890625097], + [71.0246093750001, 20.73886718750009], + [70.71933593750006, 20.740429687500068], + [70.12734375, 21.094677734375097], + [68.96992187500021, 22.29028320312497], + [69.05166015625016, 22.437304687500074], + [69.27656250000004, 22.285498046875063], + [70.17724609375014, 22.57275390624997], + [70.48925781250009, 23.08950195312508], + [70.33945312500012, 22.939746093749932], + [69.66464843750006, 22.759082031250074], + [69.23593749999995, 22.848535156250023], + [68.64072265625006, 23.189941406250114], + [68.41748046875009, 23.57148437500004], + [68.7767578125, 23.852099609375017], + [68.23496093749995, 23.596972656250074], + [68.16503906250009, 23.857324218749994], + [68.28251953125013, 23.927978515625], + [68.38125000000016, 23.950878906250068], + [68.48867187500011, 23.96723632812501], + [68.5866210937501, 23.966601562500074], + [68.72412109375003, 23.964697265625034], + [68.72812500000012, 24.265625], + [68.73964843750016, 24.291992187500085], + [68.75898437499993, 24.307226562500006], + [68.78115234375011, 24.313720703125085], + [68.8, 24.30908203125003], + [68.82832031250004, 24.26401367187509], + [68.86347656250015, 24.26650390625005], + [68.90078125000011, 24.29243164062501], + [68.98457031250015, 24.273095703124966], + [69.05156250000013, 24.28632812500001], + [69.11953125000011, 24.26865234374995], + [69.23505859374993, 24.268261718750068], + [69.44345703124995, 24.275390625000085], + [69.55917968750006, 24.273095703124966], + [69.80517578125009, 24.16523437500004], + [70.0982421875, 24.2875], + [70.28906250000009, 24.356298828125063], + [70.54677734375, 24.418310546875063], + [70.56503906250006, 24.385791015625017], + [70.55585937500015, 24.331103515625074], + [70.57929687500015, 24.279052734374943], + [70.65947265625013, 24.24609374999997], + [70.71630859375009, 24.237988281250097], + [70.7672851562501, 24.245410156250017], + [70.80507812500011, 24.26196289062503], + [70.88623046875014, 24.34375], + [70.92812500000016, 24.362353515625045], + [70.98281250000011, 24.361035156250125], + [71.04404296875006, 24.400097656250097], + [71.04531250000005, 24.42998046874996], + [70.96982421875012, 24.571875], + [70.97636718750013, 24.61875], + [71.00234375000016, 24.6539062499999], + [71.04785156250003, 24.687744140625085], + [71.02070312500021, 24.75766601562492], + [70.95087890625015, 24.89160156250003], + [70.87773437500019, 25.06298828124997], + [70.65205078125004, 25.422900390625102], + [70.64843750000003, 25.666943359375068], + [70.5695312500001, 25.705957031250023], + [70.50585937500009, 25.685302734375085], + [70.44853515625013, 25.681347656249983], + [70.26464843750009, 25.70654296874997], + [70.10019531250006, 25.91005859375005], + [70.14921875000002, 26.347558593749994], + [70.11464843750016, 26.548046874999983], + [69.47001953125002, 26.804443359375], + [69.56796875, 27.174609375000102], + [69.89628906250007, 27.473632812500085], + [70.04980468750009, 27.694726562500023], + [70.14453125000003, 27.849023437499994], + [70.19394531250006, 27.89487304687492], + [70.24433593750004, 27.934130859375102], + [70.4037109375, 28.025048828124994], + [70.48857421875013, 28.023144531250125], + [70.62910156250015, 27.937451171875068], + [70.6916015625001, 27.76899414062504], + [70.79794921875012, 27.709619140625023], + [70.87490234375016, 27.71445312499995], + [71.18476562500004, 27.831640625], + [71.54296875000003, 27.869873046875], + [71.8703125000001, 27.9625], + [71.88886718750004, 28.04746093749992], + [71.94804687500002, 28.177294921875102], + [72.12851562500012, 28.34633789062508], + [72.29199218750003, 28.69726562499997], + [72.34189453125006, 28.751904296875097], + [72.90332031250003, 29.02875976562501], + [73.38164062500013, 29.934375], + [73.8091796875, 30.093359375], + [73.88652343750013, 30.162011718750136], + [73.93339843750002, 30.222070312500108], + [73.92460937500007, 30.28164062499999], + [73.88271484375, 30.352148437499977], + [73.89931640625, 30.435351562500045], + [74.00898437500004, 30.519677734374994], + [74.33935546875003, 30.893554687499943], + [74.38037109375003, 30.89340820312509], + [74.50976562500009, 30.959667968750097], + [74.63281250000014, 31.034667968750114], + [74.62578125000002, 31.068750000000108], + [74.61035156250009, 31.112841796875045], + [74.51767578125012, 31.185595703124932], + [74.53496093750007, 31.261376953125108], + [74.59394531249993, 31.465380859375102], + [74.58183593750013, 31.523925781250114], + [74.50996093750015, 31.712939453125074], + [74.52597656249995, 31.765136718750057], + [74.55556640625011, 31.818554687500097], + [74.63574218750003, 31.889746093750034], + [74.73945312500015, 31.948828125], + [75.07148437500015, 32.08935546875003], + [75.13876953125, 32.10478515624999], + [75.25410156250004, 32.140332031250125], + [75.33349609374997, 32.279199218749994], + [75.32221348233018, 32.28516356678968] + ] + ] + ] + }, + "properties": { "name": "India", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [72.49199218750002, -7.37744140625], + [72.42910156250002, -7.435351562500003], + [72.34970703125, -7.263378906250011], + [72.447265625, -7.395703125000011], + [72.44560546875002, -7.220410156250011], + [72.49199218750002, -7.37744140625] + ] + ] + }, + "properties": { "name": "Br. Indian Ocean Ter.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-9.948193359374926, 53.91313476562499], + [-10.265722656249949, 53.977685546874994], + [-9.99638671874996, 54.00361328125004], + [-9.948193359374926, 53.91313476562499] + ] + ], + [ + [ + [-6.218017578125, 54.08872070312506], + [-6.347607421874926, 53.94130859375005], + [-6.027392578124989, 52.927099609375006], + [-6.463183593749932, 52.345361328124994], + [-6.325, 52.246679687500034], + [-6.890234375, 52.15922851562499], + [-6.965771484374926, 52.24951171875], + [-8.057812499999926, 51.82558593750005], + [-8.4091796875, 51.888769531250034], + [-8.349121093749943, 51.73930664062496], + [-8.813427734374926, 51.584912109374955], + [-9.737304687499943, 51.473730468750034], + [-9.524902343750028, 51.68110351562501], + [-10.120751953124994, 51.60068359375006], + [-9.598828124999983, 51.87441406250005], + [-10.341064453124943, 51.798925781250034], + [-9.909667968749972, 52.122949218749966], + [-10.39023437499992, 52.134912109374994], + [-10.356689453125, 52.20693359375002], + [-9.772119140624937, 52.250097656250034], + [-9.90605468749996, 52.403710937499966], + [-9.632226562499937, 52.54692382812502], + [-8.783447265624943, 52.679638671874955], + [-8.990283203124989, 52.755419921875045], + [-9.175390624999949, 52.634912109374994], + [-9.916601562499977, 52.56972656250005], + [-9.46489257812496, 52.82319335937498], + [-9.299218749999966, 53.09755859375002], + [-8.930126953124983, 53.207080078125045], + [-9.51420898437496, 53.23823242187498], + [-10.091259765624926, 53.41284179687503], + [-10.116992187499932, 53.548535156249955], + [-9.720654296874926, 53.6044921875], + [-9.901611328124943, 53.72719726562502], + [-9.578222656249949, 53.80541992187497], + [-9.578857421875, 53.879833984374955], + [-9.9140625, 53.863720703124955], + [-9.856445312499972, 54.095361328124994], + [-10.092675781249966, 54.15576171875003], + [-10.056396484374943, 54.25781250000006], + [-8.545556640624994, 54.24121093750003], + [-8.623144531249977, 54.346875], + [-8.133447265624966, 54.64082031250001], + [-8.763916015624972, 54.68120117187496], + [-8.377294921874977, 54.88945312500002], + [-8.274609374999955, 55.146289062500045], + [-7.667089843749977, 55.25649414062502], + [-7.65874023437496, 54.97094726562503], + [-7.308789062500011, 55.365820312500006], + [-6.961669921874972, 55.23789062500006], + [-7.218652343749937, 55.09199218749998], + [-7.55039062499992, 54.767968749999966], + [-7.910595703124955, 54.698339843750006], + [-7.75439453125, 54.59492187499998], + [-8.118261718749977, 54.41425781250004], + [-7.606542968750006, 54.14384765625002], + [-7.324511718750017, 54.13344726562502], + [-7.007714843749937, 54.40668945312501], + [-6.649804687499937, 54.05864257812496], + [-6.218017578125, 54.08872070312506] + ] + ] + ] + }, + "properties": { "name": "Ireland", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [56.18798828125003, 26.92114257812497], + [55.95429687500004, 26.70112304687501], + [55.31152343749997, 26.592626953125006], + [55.76259765625005, 26.81196289062504], + [55.75761718750002, 26.94765625000005], + [56.279394531250006, 26.952099609374983], + [56.18798828125003, 26.92114257812497] + ] + ], + [ + [ + [46.1144531250001, 38.877783203125034], + [46.490625, 38.90668945312498], + [47.995898437500074, 39.683935546875034], + [48.322167968749994, 39.39907226562502], + [48.10439453125005, 39.241113281249994], + [48.292089843750006, 39.01884765624999], + [47.99648437499999, 38.85375976562503], + [48.59267578125005, 38.41108398437498], + [48.86875, 38.43549804687498], + [48.95996093750003, 37.89013671875], + [49.171191406250074, 37.60058593749997], + [50.13046875, 37.407128906249994], + [50.53320312499997, 37.01367187500006], + [51.11855468750005, 36.742578124999966], + [52.19013671875004, 36.62172851562505], + [53.76767578125006, 36.93032226562502], + [53.91542968750005, 36.93032226562502], + [53.67949218750002, 36.853125], + [53.970117187499994, 36.818310546874955], + [53.91416015625006, 37.34355468750002], + [54.6994140625001, 37.47016601562498], + [54.90009765625004, 37.77792968750006], + [55.38085937500003, 38.051123046875034], + [56.272070312500006, 38.080419921875034], + [56.440625, 38.249414062499994], + [57.1935546875001, 38.216406250000034], + [57.35371093750004, 37.97333984374998], + [58.261621093749994, 37.665820312500045], + [58.81542968750003, 37.683496093749994], + [59.30175781249997, 37.51064453125005], + [59.454980468749994, 37.25283203125002], + [60.06279296875002, 36.962890625], + [60.34130859375003, 36.63764648437501], + [61.11962890625003, 36.64257812500003], + [61.212011718750006, 36.190527343750034], + [61.15292968750006, 35.97675781250001], + [61.25214843750004, 35.86762695312498], + [61.26201171875002, 35.61958007812498], + [61.28183593750006, 35.55341796875001], + [61.2785156250001, 35.513769531250006], + [61.245507812499994, 35.47407226562501], + [61.18925781250002, 35.31201171875003], + [61.1, 35.272314453125034], + [61.08007812499997, 34.85561523437505], + [60.95117187499997, 34.65385742187499], + [60.91474609375004, 34.63398437500001], + [60.80234375000006, 34.55463867187501], + [60.73945312500004, 34.544726562500045], + [60.7262695312501, 34.51826171874998], + [60.736132812500074, 34.491796875], + [60.76259765625005, 34.475244140624994], + [60.88945312500002, 34.31943359375006], + [60.642675781250006, 34.30717773437496], + [60.48574218750005, 34.09477539062502], + [60.4859375, 33.7119140625], + [60.57382812500006, 33.58833007812498], + [60.91699218749997, 33.505224609375006], + [60.56054687499997, 33.13784179687502], + [60.5765625, 32.99487304687503], + [60.71044921874997, 32.6], + [60.82929687500004, 32.24941406250005], + [60.82724609375006, 32.16796874999997], + [60.789941406249994, 31.98710937499999], + [60.7875, 31.87719726562497], + [60.791601562500006, 31.660595703124983], + [60.82070312499999, 31.495166015625045], + [60.854101562500006, 31.483251953125006], + [61.110742187499994, 31.45112304687504], + [61.346484375000074, 31.42163085937497], + [61.66015625000003, 31.382421874999977], + [61.7550781250001, 31.285302734374994], + [61.814257812500074, 31.072558593750017], + [61.810839843750074, 30.913281249999983], + [61.78417968749997, 30.831933593750023], + [61.55947265625005, 30.59936523437497], + [61.33164062500006, 30.36372070312501], + [60.84335937500006, 29.85869140624999], + [61.03417968750003, 29.663427734374977], + [61.15214843750002, 29.542724609375], + [61.8898437500001, 28.546533203124994], + [62.7625, 28.202050781249994], + [62.782324218750006, 27.800537109375], + [62.75273437500002, 27.265625], + [63.16679687500002, 27.25249023437499], + [63.19609375000002, 27.243945312500017], + [63.25625, 27.20791015625005], + [63.30156250000002, 27.151464843750006], + [63.30517578124997, 27.124560546875017], + [63.242089843749994, 27.07768554687499], + [63.25039062499999, 26.879248046875063], + [63.24160156250005, 26.86474609375003], + [63.18613281250006, 26.83759765625001], + [63.168066406250006, 26.66557617187496], + [62.31230468750002, 26.490869140624994], + [62.23935546875006, 26.357031249999977], + [62.12597656249997, 26.368994140625034], + [61.842382812500006, 26.225927734375006], + [61.809960937499994, 26.165283203125], + [61.78076171874997, 25.99584960937503], + [61.75439453125003, 25.843359375000063], + [61.737695312499994, 25.821093750000045], + [61.66865234375004, 25.76899414062501], + [61.6618164062501, 25.751269531250017], + [61.67138671874997, 25.69238281250003], + [61.64013671875003, 25.584619140624994], + [61.61542968750004, 25.28613281250003], + [61.58789062499997, 25.20234375000001], + [61.533105468749994, 25.195507812499955], + [61.41220703125006, 25.102099609375017], + [60.66386718750002, 25.28222656250003], + [60.51054687500002, 25.437060546875045], + [60.40019531250002, 25.311572265625074], + [59.45605468749997, 25.481494140625045], + [59.0460937500001, 25.417285156250017], + [58.79785156249997, 25.554589843750023], + [57.334570312500006, 25.791552734375074], + [57.03603515625005, 26.80068359375005], + [56.728125, 27.127685546875057], + [56.118066406249994, 27.14311523437499], + [54.75927734375003, 26.50507812500004], + [54.24707031250003, 26.696630859374977], + [53.70576171875004, 26.72558593750003], + [52.69160156250004, 27.323388671875023], + [52.475878906250074, 27.61650390624999], + [52.03076171874997, 27.824414062499955], + [51.58906250000004, 27.864208984374983], + [51.27890625, 28.13134765624997], + [51.06201171874997, 28.72612304687499], + [50.86699218750002, 28.870166015625017], + [50.87578125000002, 29.062695312499983], + [50.67519531250005, 29.146582031250034], + [50.64960937500004, 29.420068359374966], + [50.16894531250003, 29.921240234375034], + [50.071582031250074, 30.198535156250017], + [49.55488281250004, 30.028955078125023], + [49.028125, 30.333447265624983], + [49.224511718749994, 30.472314453125023], + [49.00195312500003, 30.506542968749983], + [48.91914062500004, 30.120898437500017], + [48.54648437500006, 29.962353515624955], + [48.47851562499997, 30.003808593749966], + [48.43457031249997, 30.03759765625], + [48.33105468749997, 30.28544921874996], + [48.01494140625002, 30.465625], + [48.01064453125005, 30.989794921875017], + [47.679492187500074, 31.00239257812501], + [47.679492187500074, 31.400585937499955], + [47.75390624999997, 31.601367187500017], + [47.829980468749994, 31.79443359375], + [47.71455078125004, 31.936425781249966], + [47.5915039062501, 32.087988281250034], + [47.51191406250004, 32.15083007812504], + [47.3297851562501, 32.45551757812501], + [47.28515625000003, 32.474023437499966], + [47.121386718750074, 32.46660156249996], + [46.569921875, 32.83393554687501], + [46.37705078125006, 32.92924804687499], + [46.29824218750005, 32.95024414062502], + [46.11279296875003, 32.957666015624994], + [46.09306640625002, 32.97587890624999], + [46.08046875, 33.028222656249994], + [46.0807617187501, 33.08652343750006], + [46.14111328125003, 33.174414062500034], + [46.145898437499994, 33.229638671874994], + [46.01992187500005, 33.41572265624998], + [45.39707031250006, 33.970849609374994], + [45.542773437500074, 34.21552734375004], + [45.459375, 34.470361328124994], + [45.50078125000002, 34.58159179687499], + [45.6375, 34.573828125], + [45.678125, 34.798437500000034], + [45.92089843750003, 35.02851562500001], + [46.04179687500002, 35.08017578125006], + [46.13378906249997, 35.127636718749955], + [46.15468750000005, 35.19672851562498], + [46.112109375000074, 35.32167968750005], + [45.97109375000005, 35.524169921875], + [46.03740234375002, 35.67314453124999], + [46.180957031250074, 35.71137695312504], + [46.2625, 35.74414062500006], + [46.27343749999997, 35.77324218750002], + [46.16748046874997, 35.820556640625], + [45.77636718749997, 35.82182617187499], + [45.36162109375002, 36.015332031249955], + [45.241113281249994, 36.35595703125], + [45.20654296874997, 36.397167968749955], + [45.15527343749997, 36.407373046874994], + [45.11240234375006, 36.409277343750034], + [45.053125, 36.47163085937501], + [44.76513671875003, 37.142431640625006], + [44.79414062500004, 37.290380859375034], + [44.574023437500074, 37.435400390625006], + [44.589941406250006, 37.710351562499966], + [44.21132812499999, 37.908056640625006], + [44.4499023437501, 38.33422851562506], + [44.2985351562501, 38.38627929687499], + [44.27167968750004, 38.83603515625006], + [44.02324218750002, 39.37744140625006], + [44.3893554687501, 39.422119140625], + [44.58710937500004, 39.76855468750006], + [44.81718750000002, 39.65043945312496], + [45.4796875000001, 39.00625], + [46.1144531250001, 38.877783203125034] + ] + ] + ] + }, + "properties": { "name": "Iran", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [45.6375, 34.573828125], + [45.50078125000002, 34.581591796874996], + [45.459375, 34.470361328124994], + [45.54277343750002, 34.21552734375], + [45.397070312500006, 33.970849609374994], + [46.01992187500002, 33.41572265625], + [46.14589843750002, 33.229638671874994], + [46.14111328125, 33.1744140625], + [46.08076171875001, 33.0865234375], + [46.08046875000002, 33.028222656249994], + [46.09306640625002, 32.975878906249996], + [46.11279296875, 32.957666015624994], + [46.377050781250006, 32.929248046874996], + [46.569921875, 32.833935546875], + [47.12138671875002, 32.466601562499996], + [47.28515625, 32.474023437499994], + [47.32978515625001, 32.455517578125], + [47.51191406250001, 32.150830078125], + [47.59150390625001, 32.08798828125], + [47.71455078125001, 31.936425781249994], + [47.82998046875002, 31.79443359375], + [47.75390625, 31.601367187499996], + [47.67949218750002, 31.400585937499997], + [47.67949218750002, 31.002392578124997], + [48.01064453125002, 30.989794921874996], + [48.01494140625002, 30.465625], + [48.3310546875, 30.285449218749996], + [48.546484375, 29.962353515624997], + [48.454199218750006, 29.9384765625], + [48.354589843750006, 29.956738281249997], + [48.141699218750006, 30.040917968749994], + [47.982519531250006, 30.011328125], + [47.97871093750001, 29.9828125], + [47.64375, 30.097314453124994], + [47.14824218750002, 30.0009765625], + [46.905859375, 29.5375], + [46.76933593750002, 29.347460937499996], + [46.69375, 29.259667968749994], + [46.53144531250001, 29.096240234374996], + [46.3564453125, 29.063671875], + [44.71650390625001, 29.193603515625], + [43.77373046875002, 29.84921875], + [42.07441406250001, 31.080371093749996], + [40.47890625000002, 31.893359375], + [40.36933593750001, 31.93896484375], + [40.02783203125, 31.995019531249994], + [39.7041015625, 32.042529296874996], + [39.14541015625002, 32.12451171875], + [39.29277343750002, 32.24384765625], + [39.24746093750002, 32.350976562499994], + [39.04140625000002, 32.3056640625], + [38.773535156250006, 33.372216796874994], + [40.98701171875001, 34.429052734375], + [41.19472656250002, 34.768994140625], + [41.354101562500006, 35.640429687499996], + [41.295996093750006, 36.383349609374996], + [41.41679687500002, 36.5146484375], + [41.78857421875, 36.59716796875], + [42.358984375, 37.10859375], + [42.45585937500002, 37.128710937499996], + [42.63544921875001, 37.249267578125], + [42.74111328125002, 37.3619140625], + [42.77460937500001, 37.371875], + [42.869140625, 37.334912109375], + [42.936621093750006, 37.324755859374996], + [43.09248046875001, 37.3673828125], + [43.67578125, 37.22724609375], + [43.83642578125, 37.223535156249994], + [44.01318359375, 37.313525390624996], + [44.11445312500001, 37.30185546875], + [44.15625, 37.282958984375], + [44.19179687500002, 37.249853515625], + [44.20839843750002, 37.20263671875], + [44.20166015625, 37.051806640624996], + [44.281835937500006, 36.97802734375], + [44.32558593750002, 37.0107421875], + [44.401953125, 37.058496093749994], + [44.60595703125, 37.176025390625], + [44.66933593750002, 37.173583984375], + [44.73095703125, 37.165283203125], + [44.76513671875, 37.142431640625], + [45.053125, 36.471630859375], + [45.112402343750006, 36.40927734375], + [45.1552734375, 36.407373046874994], + [45.20654296875, 36.39716796875], + [45.24111328125002, 36.35595703125], + [45.36162109375002, 36.01533203125], + [45.7763671875, 35.821826171874996], + [46.16748046875, 35.820556640625], + [46.2734375, 35.773242187499996], + [46.2625, 35.744140625], + [46.18095703125002, 35.711376953125], + [46.03740234375002, 35.673144531249996], + [45.97109375000002, 35.524169921875], + [46.11210937500002, 35.321679687499994], + [46.15468750000002, 35.196728515625], + [46.1337890625, 35.12763671875], + [46.04179687500002, 35.08017578125], + [45.9208984375, 35.028515625], + [45.678125, 34.7984375], + [45.6375, 34.573828125] + ] + ] + }, + "properties": { "name": "Iraq", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-15.543115234374994, 66.228515625], + [-14.595849609374994, 66.38154296875], + [-15.117382812499983, 66.125634765625], + [-14.698193359374983, 66.02021484375], + [-14.827099609374983, 65.7642578125], + [-14.391845703125, 65.78740234375], + [-14.473388671875, 65.575341796875], + [-14.166943359374983, 65.64228515625], + [-13.617871093749983, 65.5193359375], + [-13.804785156249977, 65.35478515625], + [-13.599316406249983, 65.0359375], + [-14.04443359375, 64.74189453125], + [-14.385107421874977, 64.74521484375], + [-14.475390624999989, 64.493994140625], + [-14.927392578124994, 64.319677734375], + [-15.832910156249994, 64.17666015625], + [-16.640332031249983, 63.865478515625], + [-17.81572265624999, 63.71298828125], + [-17.946923828124994, 63.5357421875], + [-18.65361328124999, 63.406689453125], + [-20.198144531249994, 63.555810546874994], + [-20.494042968749994, 63.687353515625], + [-20.413964843749994, 63.80517578125], + [-20.65092773437499, 63.73740234375], + [-21.15239257812499, 63.94453125], + [-22.652197265624977, 63.827734375], + [-22.701171875, 64.083203125], + [-22.51005859374999, 63.991455078125], + [-22.187597656249977, 64.039208984375], + [-21.463330078124983, 64.379150390625], + [-22.053369140624994, 64.313916015625], + [-21.950341796874994, 64.514990234375], + [-21.590625, 64.6263671875], + [-22.10600585937499, 64.533056640625], + [-22.467041015625, 64.794970703125], + [-23.818994140624994, 64.73916015625], + [-24.02617187499999, 64.863427734375], + [-22.7880859375, 65.046484375], + [-21.89213867187499, 65.048779296875], + [-21.779980468749983, 65.1876953125], + [-22.50908203124999, 65.19677734375], + [-21.844384765624994, 65.44736328125], + [-22.902490234374994, 65.58046875], + [-23.89990234375, 65.407568359375], + [-24.475683593749977, 65.5251953125], + [-24.248925781249994, 65.614990234375], + [-23.85673828124999, 65.53837890625], + [-24.092626953124977, 65.77646484375], + [-23.615917968749983, 65.67958984375], + [-23.285351562499983, 65.75], + [-23.832617187499977, 65.84921875], + [-23.52495117187499, 65.880029296875], + [-23.77734375, 66.017578125], + [-23.434472656249994, 66.02421875], + [-23.452539062499994, 66.181005859375], + [-23.018994140624983, 65.98212890625], + [-22.659863281249983, 66.025927734375], + [-22.61601562499999, 65.86748046875], + [-22.44169921874999, 65.90830078125], + [-22.4453125, 66.07001953125], + [-22.947900390624994, 66.212744140625], + [-22.48442382812499, 66.26630859375], + [-23.116943359375, 66.338720703125], + [-22.9443359375, 66.429443359375], + [-22.426123046874977, 66.430126953125], + [-21.406884765624994, 66.0255859375], + [-21.374902343749994, 65.74189453125], + [-21.658447265625, 65.723583984375], + [-21.12968749999999, 65.2666015625], + [-20.804345703124994, 65.63642578125], + [-20.454833984375, 65.571044921875], + [-20.20751953125, 66.10009765625], + [-19.489697265624983, 65.76806640625], + [-19.382958984374994, 66.07568359375], + [-18.845898437499983, 66.183935546875], + [-18.141943359374977, 65.73408203125], + [-18.29716796874999, 66.157421875], + [-17.906982421875, 66.143310546875], + [-17.550439453124994, 65.964404296875], + [-17.153027343749983, 66.20283203125], + [-16.838037109374994, 66.125244140625], + [-16.485009765624994, 66.195947265625], + [-16.540673828124994, 66.446728515625], + [-16.24931640624999, 66.522900390625], + [-15.985400390624989, 66.5146484375], + [-15.543115234374994, 66.228515625] + ] + ] + }, + "properties": { "name": "Iceland", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [35.78730468750001, 32.734912109374996], + [35.572851562500006, 32.640869140625], + [35.56904296875001, 32.619873046875], + [35.55146484375001, 32.3955078125], + [35.484375, 32.401660156249996], + [35.40263671875002, 32.450634765625], + [35.38671875, 32.493017578125], + [35.303808593750006, 32.512939453125], + [35.19326171875002, 32.534423828125], + [35.065039062500006, 32.46044921875], + [35.01054687500002, 32.338183593749996], + [34.95595703125002, 32.1609375], + [34.98974609375, 31.91328125], + [34.97832031250002, 31.86640625], + [34.95380859375001, 31.841259765624997], + [34.96113281250001, 31.82333984375], + [34.983007812500006, 31.81679687499999], + [35.05322265625, 31.837939453124996], + [35.12714843750001, 31.816748046875], + [35.203710937500006, 31.75], + [34.95097656250002, 31.602294921875], + [34.88046875, 31.3681640625], + [35.45058593750002, 31.479296875], + [34.97343750000002, 29.555029296875], + [34.904296875, 29.47734375], + [34.24531250000001, 31.208300781249996], + [34.34833984375001, 31.292919921874997], + [34.350195312500006, 31.362744140624997], + [34.52558593750001, 31.525634765625], + [34.47734375000002, 31.584863281249994], + [34.483984375, 31.59228515625], + [34.67841796875001, 31.895703125], + [35.10859375000001, 33.08369140625], + [35.411230468750006, 33.07568359375], + [35.869140625, 33.43173828125], + [35.91347656250002, 32.94960937499999], + [35.78730468750001, 32.734912109374996] + ] + ] + }, + "properties": { "name": "Israel", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [15.576562500000051, 38.220312500000034], + [15.099511718750023, 37.45859375], + [15.295703125000017, 37.05517578124997], + [15.112597656250017, 36.687841796875006], + [14.501855468750023, 36.798681640625034], + [14.142968750000023, 37.103662109374994], + [13.90546875000004, 37.10063476562502], + [13.169921875000028, 37.47929687499996], + [12.640234375000034, 37.594335937500034], + [12.435546874999972, 37.819775390624955], + [12.734375, 38.18305664062498], + [12.902734375000023, 38.03486328124998], + [13.291113281250034, 38.19145507812502], + [13.788867187499989, 37.981201171875], + [15.11875, 38.15273437500002], + [15.498730468750011, 38.290869140625006], + [15.576562500000051, 38.220312500000034] + ] + ], + [ + [ + [8.478906250000023, 39.067529296874966], + [8.421484375000034, 38.968652343749994], + [8.366796875, 39.115917968749955], + [8.478906250000023, 39.067529296874966] + ] + ], + [ + [ + [8.28603515625008, 41.03984375], + [8.205664062500034, 40.99746093750005], + [8.320214843750023, 41.121875], + [8.28603515625008, 41.03984375] + ] + ], + [ + [ + [9.632031250000011, 40.88203124999998], + [9.805273437500063, 40.499560546875045], + [9.642968750000023, 40.268408203125006], + [9.5625, 39.16601562500006], + [9.056347656250068, 39.23916015625002], + [8.966601562500074, 38.963720703125034], + [8.648535156250034, 38.92656250000002], + [8.418164062500068, 39.205712890624966], + [8.547753906250023, 39.83920898437506], + [8.4078125, 39.91723632812497], + [8.471289062500063, 40.29267578124998], + [8.189941406250028, 40.651611328125], + [8.22421875, 40.91333007812503], + [8.571875, 40.85019531250006], + [9.228417968750023, 41.257080078125], + [9.615332031249977, 41.01728515624998], + [9.632031250000011, 40.88203124999998] + ] + ], + [ + [ + [10.395117187500034, 42.85815429687503], + [10.419335937499994, 42.71318359374999], + [10.13125, 42.742041015625006], + [10.395117187500034, 42.85815429687503] + ] + ], + [ + [ + [13.420996093750006, 46.212304687499994], + [13.63251953125004, 46.17705078125002], + [13.634960937499983, 46.15776367187499], + [13.61660156250008, 46.133105468750045], + [13.54804687500004, 46.08911132812503], + [13.486425781250034, 46.03955078124997], + [13.480273437500017, 46.00922851562501], + [13.487695312500023, 45.987109375000045], + [13.509179687500051, 45.973779296874994], + [13.6005859375, 45.97978515624996], + [13.663476562500023, 45.7919921875], + [13.831152343750006, 45.680419921875], + [13.719824218750063, 45.58759765625001], + [13.628320312500051, 45.77094726562498], + [13.206347656250074, 45.771386718749966], + [12.27431640625008, 45.44604492187503], + [12.225683593750034, 45.24150390625002], + [12.523437500000028, 44.96796874999998], + [12.248339843750045, 44.72250976562498], + [12.396289062500074, 44.223876953125], + [13.56416015625004, 43.57128906250003], + [14.010449218750011, 42.68955078125006], + [14.54072265625004, 42.24428710937502], + [15.16875, 41.93403320312498], + [16.164648437500034, 41.89619140624998], + [15.900488281250034, 41.51206054687498], + [17.954980468749994, 40.65517578125002], + [18.460644531249983, 40.221044921875034], + [18.34375, 39.82138671874998], + [18.077929687500017, 39.93696289062498], + [17.865039062500074, 40.28017578125002], + [17.395800781250045, 40.34023437499999], + [17.179980468750045, 40.50278320312498], + [16.92822265625, 40.45805664062502], + [16.521875, 39.74755859375003], + [17.114550781250017, 39.38061523437497], + [17.174609375000017, 38.998095703125045], + [16.61669921875003, 38.800146484375034], + [16.54560546875001, 38.40908203125002], + [16.05683593750001, 37.941845703124955], + [15.72451171875008, 37.93911132812502], + [15.645800781250017, 38.034228515625045], + [15.87890625, 38.61391601562502], + [16.19677734375, 38.759228515624955], + [16.20996093750003, 38.94111328124998], + [15.692773437499994, 39.99018554687501], + [14.95087890625004, 40.23901367187497], + [14.94765625000008, 40.469335937500006], + [14.765722656250063, 40.66840820312498], + [14.339941406250006, 40.59882812500001], + [14.460546875000063, 40.72871093750001], + [14.04433593750008, 40.81225585937506], + [13.733398437500057, 41.23564453124999], + [13.088671875000074, 41.243847656249955], + [12.630859374999972, 41.469677734374955], + [11.637304687500063, 42.287548828124955], + [11.141210937499977, 42.38989257812503], + [11.167773437500074, 42.53515625000006], + [10.708398437500023, 42.93632812499999], + [10.514843750000011, 42.96752929687503], + [10.188085937500063, 43.947509765625], + [8.76582031250004, 44.42231445312501], + [8.004980468750006, 43.87675781249999], + [7.4931640625, 43.767138671875045], + [7.637207031250057, 44.16484375], + [7.318554687500068, 44.13798828125002], + [6.900195312499989, 44.33574218749996], + [6.99267578125, 44.82729492187502], + [6.634765625000028, 45.06816406249996], + [7.07832031250004, 45.23994140624998], + [7.146386718750051, 45.381738281249994], + [6.790917968750023, 45.740869140624966], + [7.021093750000034, 45.92578124999997], + [7.055761718749977, 45.90380859375003], + [7.129003906249977, 45.88041992187499], + [7.327929687500017, 45.912353515625], + [7.9931640625, 46.01591796874996], + [8.081542968750057, 46.25600585937502], + [8.231933593750057, 46.341210937499966], + [8.29853515625004, 46.403417968750034], + [8.370703125, 46.44511718750002], + [8.458398437500023, 46.24589843750002], + [8.818554687500011, 46.0771484375], + [8.826757812500006, 46.06103515625], + [8.77802734375004, 45.996191406250034], + [8.953710937500034, 45.83002929687501], + [9.023730468750074, 45.845703125], + [9.203417968750017, 46.21923828125], + [9.304394531250068, 46.49555664062498], + [9.399316406250023, 46.480664062499955], + [9.427636718750023, 46.48232421875002], + [9.528710937500023, 46.306201171875045], + [9.57958984375, 46.29609375000001], + [9.639453125000017, 46.29589843749997], + [9.78779296875004, 46.34604492187498], + [9.884472656250011, 46.36777343750006], + [9.939257812500074, 46.36181640625], + [10.041015625000028, 46.23808593750002], + [10.08056640625, 46.22797851562501], + [10.128320312500051, 46.238232421874955], + [10.109667968750074, 46.36284179687502], + [10.081933593750023, 46.420751953125006], + [10.045605468750068, 46.44790039062505], + [10.038281250000011, 46.483203125000045], + [10.061230468750068, 46.54677734375002], + [10.087011718750063, 46.59990234375002], + [10.1375, 46.614355468750034], + [10.195507812500068, 46.62109374999997], + [10.4306640625, 46.55004882812497], + [10.409352678571473, 46.6092047991071], + [10.39794921875, 46.66503906250006], + [10.406054687500045, 46.73486328124997], + [10.452832031249983, 46.86494140625001], + [10.47939453125008, 46.85512695312505], + [10.579785156250011, 46.85371093750001], + [10.689257812500017, 46.846386718749955], + [10.759765625, 46.79331054687498], + [10.828906250000045, 46.775244140625034], + [10.927343750000034, 46.76948242187501], + [10.993261718750034, 46.77700195312502], + [11.02509765625004, 46.796972656250006], + [11.063476562500057, 46.85913085937497], + [11.133886718750006, 46.93618164062505], + [11.244433593750045, 46.975683593750006], + [11.433203125000063, 46.983056640624994], + [11.527539062500011, 46.99741210937498], + [11.775683593750017, 46.986083984375], + [12.169433593750028, 47.082128906250006], + [12.19716796875008, 47.075], + [12.201269531250034, 47.060888671875034], + [12.165527343750028, 47.028173828125034], + [12.130761718750051, 46.98476562499999], + [12.154101562500017, 46.93525390625004], + [12.267968750000023, 46.83588867187504], + [12.330078125, 46.75981445312499], + [12.388281250000034, 46.70263671874997], + [12.479199218749983, 46.672509765624966], + [13.16875, 46.572656249999966], + [13.3515625, 46.55791015624999], + [13.490039062500045, 46.55556640625002], + [13.7, 46.52026367187503], + [13.679687500000057, 46.46289062499997], + [13.63710937500008, 46.44853515624999], + [13.563281250000045, 46.41508789062502], + [13.399511718749977, 46.31752929687502], + [13.420996093750006, 46.212304687499994] + ] + ] + ] + }, + "properties": { "name": "Italy", "childNum": 6 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-77.261474609375, 18.45742187499999], + [-76.349853515625, 18.15185546875], + [-76.21079101562499, 17.913525390624997], + [-76.524609375, 17.8662109375], + [-76.85322265625, 17.97373046874999], + [-76.94414062499999, 17.848779296874994], + [-77.11948242187499, 17.880078125], + [-77.20498046875, 17.71494140624999], + [-77.36142578124999, 17.833691406249997], + [-77.76816406249999, 17.877392578124997], + [-78.04448242187499, 18.173828125], + [-78.339501953125, 18.28720703124999], + [-78.21669921875, 18.44809570312499], + [-77.8734375, 18.522216796875], + [-77.261474609375, 18.45742187499999] + ] + ] + }, + "properties": { "name": "Jamaica", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-2.018652343749977, 49.23125], + [-2.23583984375, 49.1763671875], + [-2.220507812499989, 49.266357421875], + [-2.018652343749977, 49.23125] + ] + ] + }, + "properties": { "name": "Jersey", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [39.14541015625002, 32.12451171875], + [38.9970703125, 32.007470703124994], + [38.96230468750002, 31.994921875], + [38.37548828125, 31.847460937499996], + [38.111425781250006, 31.781152343749994], + [37.49335937500001, 31.625878906249994], + [37.215625, 31.556103515624997], + [36.95859375, 31.491503906249996], + [37.980078125, 30.5], + [37.862890625, 30.442626953125], + [37.66972656250002, 30.34814453125], + [37.64990234375, 30.330957031249994], + [37.63359375000002, 30.31328125], + [37.55361328125002, 30.144580078124996], + [37.49072265625, 30.01171875], + [37.46923828125, 29.995068359374997], + [36.75527343750002, 29.866015625], + [36.70390625000002, 29.831640625], + [36.591796875, 29.66611328125], + [36.47607421875, 29.4951171875], + [36.2828125, 29.355371093749994], + [36.068457031250006, 29.200537109375], + [34.95078125, 29.353515625], + [34.97343750000002, 29.555029296875], + [35.45058593750002, 31.479296875], + [35.57207031250002, 32.237890625], + [35.55146484375001, 32.3955078125], + [35.56904296875001, 32.619873046875], + [35.572851562500006, 32.640869140625], + [35.78730468750001, 32.734912109374996], + [36.3720703125, 32.3869140625], + [36.818359375, 32.317285156249994], + [38.773535156250006, 33.372216796874994], + [39.04140625000002, 32.3056640625], + [39.24746093750002, 32.350976562499994], + [39.29277343750002, 32.24384765625], + [39.14541015625002, 32.12451171875] + ] + ] + }, + "properties": { "name": "Jordan", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [123.88867187499997, 24.280126953124977], + [123.67978515625012, 24.317773437500023], + [123.77148437499997, 24.41445312499999], + [123.93486328125002, 24.362011718749983], + [123.88867187499997, 24.280126953124977] + ] + ], + [ + [ + [124.29316406250004, 24.515917968750074], + [124.13574218750003, 24.347607421874983], + [124.08476562500002, 24.435839843750017], + [124.30195312500004, 24.58710937500001], + [124.29316406250004, 24.515917968750074] + ] + ], + [ + [ + [125.44414062500002, 24.7431640625], + [125.26894531250005, 24.732519531250063], + [125.28359375, 24.871923828125034], + [125.44414062500002, 24.7431640625] + ] + ], + [ + [ + [128.25878906249997, 26.65278320312501], + [127.86708984375, 26.442480468749977], + [127.80361328125005, 26.152539062499983], + [127.653125, 26.0947265625], + [127.90722656250003, 26.69360351562497], + [128.09765624999997, 26.66777343749996], + [128.25488281249997, 26.88188476562496], + [128.25878906249997, 26.65278320312501] + ] + ], + [ + [ + [128.99814453125012, 27.720800781250006], + [128.90000000000012, 27.727783203125], + [128.9076171875, 27.897998046875045], + [128.99814453125012, 27.720800781250006] + ] + ], + [ + [ + [129.45253906250005, 28.20898437499997], + [129.3664062500001, 28.127734375000045], + [129.16464843750012, 28.24975585937503], + [129.68955078125012, 28.517480468750023], + [129.45253906250005, 28.20898437499997] + ] + ], + [ + [ + [130.6227539062501, 30.262988281250017], + [130.44560546875002, 30.264697265625017], + [130.38808593750005, 30.38818359375003], + [130.49716796875006, 30.465527343749983], + [130.64355468749997, 30.388964843750017], + [130.6227539062501, 30.262988281250017] + ] + ], + [ + [ + [130.95976562500007, 30.39692382812504], + [130.87031250000004, 30.444238281249994], + [131.06035156250007, 30.828466796875006], + [130.95976562500007, 30.39692382812504] + ] + ], + [ + [ + [130.38105468750004, 32.42373046875002], + [130.24169921874997, 32.462792968749994], + [130.46142578124997, 32.515722656250034], + [130.38105468750004, 32.42373046875002] + ] + ], + [ + [ + [130.08251953124997, 32.22968750000001], + [129.9601562500001, 32.24375], + [130.00976562499997, 32.521630859374994], + [130.16777343750002, 32.54121093749998], + [130.19951171875002, 32.34057617187506], + [130.08251953124997, 32.22968750000001] + ] + ], + [ + [ + [128.66533203125002, 32.783886718749955], + [128.89453124999997, 32.65214843750002], + [128.69296875000012, 32.60473632812506], + [128.66533203125002, 32.783886718749955] + ] + ], + [ + [ + [129.07695312500002, 32.84028320312498], + [128.99726562500004, 32.95185546874998], + [129.10976562500005, 33.13256835937503], + [129.18193359375002, 32.99311523437504], + [129.07695312500002, 32.84028320312498] + ] + ], + [ + [ + [129.49179687500006, 33.22304687499999], + [129.37041015625002, 33.176025390625], + [129.56992187500006, 33.36103515625004], + [129.49179687500006, 33.22304687499999] + ] + ], + [ + [ + [129.79570312500007, 33.74882812499999], + [129.67480468749997, 33.73969726562498], + [129.71728515624997, 33.8583984375], + [129.79570312500007, 33.74882812499999] + ] + ], + [ + [ + [131.17460937500007, 33.602587890625045], + [131.69628906250003, 33.60283203124999], + [131.53740234375007, 33.274072265624994], + [131.89658203125006, 33.25458984375001], + [131.8478515625001, 33.118066406249994], + [132.0021484375001, 32.882373046875045], + [131.6603515625001, 32.465625], + [131.33720703125007, 31.4046875], + [131.07080078124997, 31.436865234374977], + [131.09843750000002, 31.256152343750017], + [130.68574218750004, 31.01513671875003], + [130.77626953125, 31.70629882812497], + [130.65507812500002, 31.71840820312505], + [130.5560546875, 31.563085937500034], + [130.58876953125, 31.178515625000017], + [130.20068359374997, 31.291894531250023], + [130.14726562500002, 31.40849609374996], + [130.2941406250001, 31.45068359375003], + [130.3219726562501, 31.601464843750023], + [130.18789062500005, 31.768847656250017], + [130.19443359375012, 32.090771484374955], + [130.64052734375005, 32.61923828124998], + [130.49785156250002, 32.65693359375001], + [130.547265625, 32.83159179687499], + [130.2375, 33.177636718749966], + [130.12685546875005, 33.10483398437506], + [130.175, 32.851318359375], + [130.32646484375002, 32.852636718750006], + [130.34042968750012, 32.70185546875004], + [130.05410156250005, 32.770800781250045], + [129.76855468749997, 32.57099609375001], + [129.82675781250006, 32.72534179687503], + [129.67910156250005, 33.059960937499966], + [129.99169921875003, 32.85156249999997], + [129.58007812500003, 33.23627929687501], + [129.61015625000002, 33.34365234375005], + [129.844140625, 33.32177734375003], + [129.82568359374997, 33.43701171875006], + [130.36503906250007, 33.634472656249955], + [130.4837890625, 33.834619140624966], + [130.715625, 33.92778320312502], + [130.953125, 33.87202148437504], + [131.17460937500007, 33.602587890625045] + ] + ], + [ + [ + [132.266015625, 33.945166015625006], + [132.44492187500006, 33.91318359374998], + [132.20878906250007, 33.87285156250002], + [132.266015625, 33.945166015625006] + ] + ], + [ + [ + [129.27949218750004, 34.123388671875006], + [129.18642578125, 34.14501953125006], + [129.21484374999997, 34.320654296875034], + [129.3371093750001, 34.284765625], + [129.27949218750004, 34.123388671875006] + ] + ], + [ + [ + [134.35742187500003, 34.25634765625], + [134.6375, 34.22661132812499], + [134.73886718750012, 33.82050781250001], + [134.37705078125012, 33.60839843749997], + [134.18164062500003, 33.24721679687502], + [133.95869140625004, 33.44833984375006], + [133.63203125000004, 33.51098632812503], + [133.28593750000007, 33.35996093749998], + [132.97724609375004, 32.84199218749998], + [132.80429687500006, 32.75200195312502], + [132.6417968750001, 32.76245117187503], + [132.70898437500003, 32.90249023437505], + [132.49511718749997, 32.91660156249998], + [132.41279296875004, 33.43046875], + [132.0326171875, 33.339990234374994], + [132.64306640624997, 33.68994140624997], + [132.93515625000006, 34.09531250000006], + [133.19306640625004, 33.93320312499998], + [133.58203124999997, 34.01713867187502], + [133.60263671875006, 34.24384765625001], + [133.94833984375006, 34.34804687500002], + [134.35742187500003, 34.25634765625] + ] + ], + [ + [ + [134.35185546875002, 34.48364257812503], + [134.25185546875, 34.42304687500004], + [134.18212890625003, 34.51923828124998], + [134.35185546875002, 34.48364257812503] + ] + ], + [ + [ + [134.9328125000001, 34.28813476562499], + [134.82441406250004, 34.202929687500045], + [134.66787109375005, 34.294140624999955], + [135.00468750000002, 34.54404296874998], + [134.9328125000001, 34.28813476562499] + ] + ], + [ + [ + [129.38564453125, 34.35366210937502], + [129.26669921875012, 34.37045898437506], + [129.45107421875005, 34.68657226562499], + [129.38564453125, 34.35366210937502] + ] + ], + [ + [ + [133.37050781250005, 36.203857421875], + [133.23925781249997, 36.178759765625045], + [133.20615234375006, 36.293408203124955], + [133.29570312500002, 36.34013671874996], + [133.37050781250005, 36.203857421875] + ] + ], + [ + [ + [138.34404296875007, 37.822119140625006], + [138.22519531250006, 37.82939453124996], + [138.25, 38.078466796875006], + [138.50361328125004, 38.31591796875006], + [138.45361328124997, 38.07568359375006], + [138.57519531249997, 38.065527343750034], + [138.34404296875007, 37.822119140625006] + ] + ], + [ + [ + [141.22929687500007, 41.37265625], + [141.45546875000005, 41.404736328124955], + [141.43046875000002, 40.72333984374998], + [141.7970703125001, 40.29116210937502], + [141.97695312500005, 39.428808593750034], + [141.90078125, 39.111328125], + [141.5462890625, 38.762841796874966], + [141.4674804687501, 38.404150390625006], + [141.10839843750003, 38.33793945312502], + [140.9621093750001, 38.148876953124955], + [141.00166015625004, 37.11464843750002], + [140.57353515625007, 36.23134765625002], + [140.87402343749997, 35.72495117187506], + [140.457421875, 35.51025390625], + [140.35468750000004, 35.18144531249999], + [139.8439453125001, 34.914892578125034], + [139.82646484375002, 35.29667968750002], + [140.096875, 35.58515624999998], + [139.83476562500002, 35.658056640625006], + [139.65000000000012, 35.40913085937501], + [139.675, 35.149267578125006], + [139.47441406250002, 35.298535156249955], + [139.24941406250005, 35.27802734375004], + [139.08603515625006, 34.83916015624999], + [138.8375, 34.619238281250034], + [138.80273437499997, 34.97480468749998], + [138.90361328125002, 35.02524414062506], + [138.71962890625, 35.12407226562502], + [138.18906250000012, 34.596337890624994], + [137.543359375, 34.66420898437505], + [137.06171875000004, 34.58281249999999], + [137.27519531250002, 34.77250976562499], + [136.96328125000005, 34.83491210937501], + [136.87128906250004, 34.733105468749955], + [136.89707031250006, 35.03554687500002], + [136.80419921874997, 35.05029296875], + [136.53300781250007, 34.678369140624994], + [136.8802734375, 34.43359375000006], + [136.8537109375001, 34.324072265625034], + [136.32988281250007, 34.17685546875006], + [135.91621093750004, 33.561718749999955], + [135.69531250000003, 33.48696289062502], + [135.4528320312501, 33.55336914062505], + [135.12792968749997, 34.006982421874994], + [135.10009765624997, 34.288378906250045], + [135.41591796875, 34.61748046875002], + [134.74003906250007, 34.765234375], + [134.246875, 34.71386718750003], + [133.96826171874997, 34.52729492187504], + [133.14238281250002, 34.30244140624998], + [132.65654296875007, 34.24609375000003], + [132.31259765625006, 34.32495117187503], + [132.14648437499997, 33.83876953125002], + [131.74052734375007, 34.05205078125002], + [130.91884765625, 33.97573242187502], + [130.88925781250012, 34.261816406250034], + [131.00419921875007, 34.39257812500003], + [131.35439453125, 34.41318359375006], + [132.92294921875006, 35.511279296875045], + [133.98125, 35.50722656250002], + [135.17431640625003, 35.74707031250003], + [135.32695312500002, 35.52553710937502], + [135.68027343750006, 35.503125], + [135.903125, 35.60688476562498], + [136.09531250000006, 35.767626953125045], + [136.06748046875006, 36.11684570312505], + [136.69814453125005, 36.742041015625034], + [136.84345703125004, 37.38212890624999], + [137.32265625, 37.52207031249998], + [136.89990234375003, 37.11767578125], + [137.01669921875006, 36.83720703124999], + [137.24628906250004, 36.753173828125], + [137.5140625, 36.95156250000002], + [138.31992187500012, 37.21840820312502], + [138.88505859375007, 37.84394531250001], + [139.36386718750006, 38.09902343750002], + [139.80195312500004, 38.881591796875], + [140.06474609375002, 39.624414062499994], + [139.99472656250006, 39.855078125], + [139.74150390625002, 39.92084960937498], + [140.01113281250005, 40.26035156250006], + [139.92285156250003, 40.59843750000002], + [140.28125, 40.84609375000002], + [140.3444335937501, 41.203320312499955], + [140.62763671875004, 41.195410156250034], + [140.74863281250012, 40.830322265625], + [140.93603515625003, 40.940771484375034], + [141.1185546875, 40.88227539062501], + [141.24423828125006, 41.20561523437499], + [140.80058593750002, 41.138818359374966], + [140.80185546875012, 41.253662109375], + [140.9369140625, 41.50556640624998], + [141.22929687500007, 41.37265625] + ] + ], + [ + [ + [139.48125, 42.08100585937498], + [139.43134765625004, 42.19956054687498], + [139.55839843750002, 42.235205078125034], + [139.48125, 42.08100585937498] + ] + ], + [ + [ + [141.29541015625003, 45.11933593750001], + [141.14531250000002, 45.153906250000034], + [141.19375, 45.24785156249999], + [141.29541015625003, 45.11933593750001] + ] + ], + [ + [ + [141.07275390624997, 45.33286132812498], + [141.03398437500007, 45.26933593750002], + [140.97167968749997, 45.465478515624994], + [141.07275390624997, 45.33286132812498] + ] + ], + [ + [ + [143.82431640625012, 44.11699218749999], + [144.71523437500005, 43.92797851562503], + [145.36953125000005, 44.32739257812506], + [145.13964843750003, 43.6625], + [145.34082031249997, 43.30253906249999], + [145.83300781249997, 43.38593750000001], + [144.92138671874997, 43.00092773437498], + [143.96933593750006, 42.88139648437499], + [143.42949218750002, 42.41889648437498], + [143.2365234375001, 42.000195312499955], + [141.85136718750007, 42.57905273437501], + [141.40664062500005, 42.54692382812496], + [140.98613281250002, 42.34213867187498], + [140.70976562500002, 42.555615234374955], + [140.48046875000003, 42.559375], + [140.32666015625003, 42.29335937499999], + [141.15097656250012, 41.80507812499999], + [140.99951171874997, 41.73740234375006], + [140.65986328125004, 41.815576171874994], + [140.3849609375001, 41.51928710937503], + [140.08515625000004, 41.43408203125], + [139.99531250000004, 41.57641601562503], + [140.10839843749997, 41.912939453125034], + [139.83544921874997, 42.278076171875], + [139.86015625000002, 42.58173828125004], + [140.43222656250012, 42.95410156250006], + [140.39238281250002, 43.303125], + [141.13818359374997, 43.17993164062506], + [141.37412109375006, 43.279638671875006], + [141.7609375000001, 44.482519531250034], + [141.58300781250003, 45.15595703125001], + [141.66796874999997, 45.401269531249966], + [141.93769531250004, 45.509521484375], + [142.88476562499997, 44.670117187499955], + [143.82431640625012, 44.11699218749999] + ] + ] + ] + }, + "properties": { "name": "Japan", "childNum": 28 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [77.04863281249999, 35.109912109374996], + [76.927734375, 35.346630859375], + [76.88222656250002, 35.4357421875], + [76.81279296874999, 35.571826171874996], + [76.76689453124999, 35.66171875], + [76.87890625, 35.61328125], + [77.09003906250001, 35.552050781249996], + [77.29482421875002, 35.508154296875], + [77.44648437500001, 35.4755859375], + [77.57255859374999, 35.471826171874994], + [77.72402343750002, 35.48056640625], + [77.79941406250003, 35.495898437499996], + [77.42343750000003, 35.302587890625], + [77.16855468750003, 35.171533203124994], + [77.04863281249999, 35.109912109374996] + ] + ] + }, + "properties": { "name": "Siachen Glacier", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [50.184472656249994, 44.854638671874994], + [49.99511718750003, 44.93696289062498], + [50.10986328124997, 45.08193359375002], + [50.038867187500074, 44.949121093749966], + [50.184472656249994, 44.854638671874994] + ] + ], + [ + [ + [87.32285156250012, 49.085791015625006], + [86.8083007812501, 49.04970703125002], + [86.54941406250012, 48.52861328125002], + [85.7494140625, 48.38505859374999], + [85.52597656250006, 47.915625], + [85.65664062500005, 47.254638671875], + [85.484765625, 47.06352539062496], + [84.78613281249997, 46.83071289062505], + [84.66660156250006, 46.97236328125004], + [84.016015625, 46.97050781250002], + [83.02949218750004, 47.18593750000002], + [82.31523437500002, 45.59492187499998], + [82.61162109375007, 45.424267578124955], + [82.52148437500003, 45.12548828125], + [82.26660156249997, 45.21909179687498], + [81.94492187500006, 45.16083984375001], + [81.69199218750012, 45.34936523437497], + [80.05917968750012, 45.006445312500006], + [79.871875, 44.88378906249997], + [80.48154296875006, 44.71464843749999], + [80.35527343750002, 44.09726562500006], + [80.78574218750006, 43.16157226562504], + [80.39023437500006, 43.043115234374966], + [80.53896484375005, 42.873486328124955], + [80.20224609375012, 42.73447265624998], + [80.209375, 42.190039062500006], + [80.07128906249997, 42.302978515625], + [79.92109375000004, 42.41313476562496], + [79.49013671875, 42.45756835937496], + [79.42822265624997, 42.483496093750006], + [79.20302734375005, 42.66601562499997], + [79.16484375000007, 42.759033203125], + [79.1266601562501, 42.775732421875034], + [76.98808593750007, 42.97358398437501], + [76.64648437500003, 42.928808593750034], + [76.50917968750005, 42.91889648437498], + [75.9322265625, 42.92851562499999], + [75.84033203125003, 42.9375], + [75.78955078124997, 42.93291015624999], + [75.68173828125, 42.83046875], + [75.04765625000007, 42.904394531250034], + [74.20908203125006, 43.24038085937502], + [73.88603515625002, 43.132568359375], + [73.55625, 43.002783203125006], + [73.45019531249997, 42.703027343749966], + [73.421875, 42.59350585937503], + [73.49296875000007, 42.409033203125034], + [73.41162109375003, 42.41977539062498], + [73.316015625, 42.46699218750001], + [73.2829101562501, 42.50410156250004], + [72.85507812500006, 42.561132812500006], + [72.75292968750003, 42.63789062500001], + [72.54316406250004, 42.67773437500006], + [72.27578125, 42.757666015625006], + [71.76054687500002, 42.82148437500004], + [71.5142578125, 42.766943359375006], + [71.42207031250004, 42.78315429687504], + [71.25664062500002, 42.733544921874966], + [70.89287109375007, 42.339990234374994], + [70.94677734374997, 42.24868164062505], + [69.15361328125002, 41.42524414062498], + [68.58408203125, 40.876269531250045], + [68.57265625, 40.62265624999998], + [68.29189453125, 40.656103515625034], + [68.04765625000007, 40.80927734374998], + [68.11308593750007, 41.02861328124999], + [67.9357421875001, 41.19658203125002], + [66.70966796875004, 41.17915039062501], + [66.49863281250006, 41.99487304687503], + [66.00957031250007, 42.00488281250003], + [66.1002929687501, 42.99082031249998], + [65.80302734375002, 42.87695312500006], + [65.49619140625, 43.310546875], + [64.9054687500001, 43.714697265625006], + [64.44316406250007, 43.55117187499999], + [63.20703125000003, 43.62797851562502], + [61.99023437500003, 43.492138671874955], + [61.007910156250006, 44.39379882812497], + [58.555273437500006, 45.55537109375001], + [55.97568359375006, 44.99492187499996], + [55.97744140625005, 41.32221679687504], + [55.434375, 41.296289062499994], + [54.85380859375002, 41.965185546875006], + [54.120996093749994, 42.335205078125], + [53.0558593750001, 42.14775390624999], + [52.4938476562501, 41.780371093750034], + [52.59658203125005, 42.760156249999966], + [51.898242187500074, 42.86962890624997], + [51.61601562500002, 43.15844726562503], + [51.29541015624997, 43.17412109375002], + [51.30175781249997, 43.48237304687501], + [50.8307617187501, 44.192773437499966], + [50.331152343750006, 44.32548828125002], + [50.25292968749997, 44.461523437500006], + [50.409472656250074, 44.6240234375], + [51.543554687500006, 44.53100585937506], + [51.009375, 44.92182617187501], + [51.4157226562501, 45.35786132812501], + [53.20039062500004, 45.33198242187498], + [52.77382812499999, 45.57275390625], + [53.13525390625003, 46.19165039062497], + [53.069433593750006, 46.85605468750006], + [52.48320312500002, 46.99067382812504], + [52.13828125, 46.82861328124997], + [51.178027343750074, 47.110156250000045], + [49.886328125, 46.59565429687504], + [49.347460937500074, 46.51914062499998], + [49.232226562500074, 46.33715820312503], + [48.54121093750004, 46.60561523437502], + [48.558398437500074, 46.75712890624999], + [48.959375, 46.77460937499998], + [48.16699218750003, 47.70878906249996], + [47.48193359374997, 47.80390624999998], + [47.292382812499994, 47.74091796875004], + [47.06464843750004, 48.23247070312499], + [46.660937500000074, 48.41225585937502], + [46.70263671875003, 48.80556640625002], + [47.031347656250006, 49.150292968749994], + [46.80205078125002, 49.36708984375002], + [46.889550781249994, 49.69697265625001], + [47.42919921874997, 50.35795898437502], + [47.7057617187501, 50.37797851562502], + [48.33496093750003, 49.858251953125006], + [48.7589843750001, 49.92832031250006], + [48.625097656250006, 50.61269531250005], + [49.32343750000004, 50.851708984374966], + [49.49804687500003, 51.08359375000006], + [50.246875, 51.28950195312498], + [50.79394531249997, 51.729199218749955], + [51.16347656250005, 51.6474609375], + [51.344531250000074, 51.47534179687503], + [52.21914062499999, 51.709375], + [52.57119140625005, 51.481640624999955], + [53.33808593750004, 51.48237304687504], + [54.139746093750006, 51.04077148437503], + [54.555273437500006, 50.535791015624994], + [54.64160156250003, 51.011572265625034], + [55.68623046875004, 50.582861328125006], + [56.49140625000004, 51.01953124999997], + [57.01171874999997, 51.06518554687503], + [57.44218750000002, 50.88886718749998], + [57.83886718750003, 51.091650390625006], + [58.359179687500074, 51.063818359375034], + [58.88369140625005, 50.694433593750006], + [59.4523437500001, 50.62041015625002], + [59.523046875, 50.492871093749955], + [59.812402343749994, 50.58203125], + [60.05859374999997, 50.850292968749955], + [60.42480468749997, 50.67915039062498], + [60.94228515625005, 50.69550781250004], + [61.38945312500002, 50.86103515625001], + [61.55468750000003, 51.32460937500005], + [60.464746093749994, 51.651171875000045], + [60.03027343749997, 51.93325195312505], + [60.99453125000005, 52.33686523437504], + [60.77441406249997, 52.67578124999997], + [61.047460937500006, 52.97246093750002], + [62.08271484375004, 53.00541992187499], + [61.65986328125004, 53.22846679687504], + [61.19921874999997, 53.28715820312502], + [61.22890625, 53.445898437500006], + [61.53496093750002, 53.52329101562506], + [60.97949218749997, 53.62172851562505], + [61.231054687500006, 54.01948242187498], + [61.92871093750003, 53.94648437500004], + [64.46123046875002, 54.38417968750002], + [65.08837890624997, 54.340185546875034], + [65.476953125, 54.62329101562497], + [68.15585937500006, 54.97670898437505], + [68.20625, 55.16093750000002], + [68.9772460937501, 55.389599609374955], + [70.18242187500002, 55.162451171875034], + [70.73808593750007, 55.30517578125], + [71.18554687500003, 54.59931640624998], + [71.09316406250005, 54.21220703124999], + [72.00449218750006, 54.20566406249998], + [72.18603515625003, 54.32563476562501], + [72.44677734375003, 53.94184570312498], + [72.62226562500004, 54.13432617187502], + [73.22988281250005, 53.957812500000045], + [73.71240234375003, 54.04238281250002], + [73.30566406250003, 53.707226562499955], + [73.40693359375004, 53.44755859374999], + [73.85898437500006, 53.61972656249998], + [74.35156250000003, 53.487646484375006], + [74.45195312500007, 53.64726562500002], + [75.22021484374997, 53.89379882812506], + [75.43720703125004, 54.08964843749999], + [76.8373046875, 54.4423828125], + [76.65458984375007, 54.14526367187503], + [76.42167968750007, 54.151513671874966], + [76.48476562500005, 54.02255859374998], + [77.85996093750006, 53.269189453124994], + [79.98623046875, 50.774560546874966], + [80.42363281250002, 50.94628906249997], + [80.44804687500002, 51.18334960937503], + [80.73525390625, 51.29340820312498], + [81.12724609375002, 51.19106445312502], + [81.0714843750001, 50.96875], + [81.38828125000006, 50.95649414062501], + [81.46591796875006, 50.73984375], + [82.49394531250007, 50.72758789062499], + [82.76083984375012, 50.89335937500002], + [83.35732421875005, 50.99458007812504], + [83.94511718750007, 50.774658203125], + [84.32324218749997, 50.239160156249966], + [84.9894531250001, 50.061425781249994], + [85.2326171875001, 49.61582031249998], + [86.1808593750001, 49.49931640624996], + [86.67548828125004, 49.77729492187501], + [86.62646484374997, 49.56269531250001], + [87.32285156250012, 49.085791015625006] + ] + ] + ] + }, + "properties": { "name": "Kazakhstan", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [35.325292968750006, 5.364892578124994], + [35.745019531249994, 5.343994140625], + [35.80029296874997, 5.156933593749983], + [35.77929687499997, 5.105566406250006], + [35.756152343750074, 4.950488281250031], + [35.76308593750005, 4.808007812500051], + [36.02197265625003, 4.468115234374991], + [36.90556640625002, 4.411474609374991], + [37.15458984375002, 4.254541015624994], + [37.944921875, 3.746728515625023], + [38.0861328125001, 3.648828124999966], + [38.22529296875004, 3.61899414062502], + [38.45156250000005, 3.604833984374977], + [38.608007812500006, 3.600097656249986], + [39.49443359375002, 3.45610351562496], + [39.65751953125002, 3.577832031249983], + [39.79033203125002, 3.754248046875034], + [39.8421875, 3.851464843750037], + [40.765234375, 4.273046875000034], + [41.02080078125002, 4.057470703124991], + [41.22089843750004, 3.943554687499969], + [41.372460937499994, 3.94619140624998], + [41.48193359375003, 3.96328125], + [41.737695312499994, 3.979052734375003], + [41.88398437500004, 3.977734375000011], + [41.6134765625001, 3.59047851562498], + [41.34179687499997, 3.20166015625], + [40.964453125, 2.814648437500026], + [40.9787109375001, -0.870312500000011], + [41.249804687500074, -1.220507812499946], + [41.4269531250001, -1.449511718749974], + [41.521875, -1.572265625000028], + [41.53271484374997, -1.695312499999957], + [41.26748046875005, -1.945019531250026], + [40.889746093750006, -2.023535156250034], + [40.89824218750002, -2.269921874999966], + [40.64414062500006, -2.53945312499998], + [40.22246093750002, -2.688378906250037], + [40.1154296875001, -3.250585937499991], + [39.8609375, -3.576757812500006], + [39.49091796875004, -4.478417968750023], + [39.221777343750006, -4.692382812500014], + [37.608203125000074, -3.497070312500028], + [37.643847656250074, -3.045410156250028], + [33.90322265625005, -1.002050781250034], + [33.94316406250002, 0.173779296874969], + [34.160937500000074, 0.605175781250026], + [34.4108398437501, 0.867285156250034], + [34.48173828125002, 1.042138671875051], + [34.79863281250002, 1.24453125], + [34.976464843749994, 1.719628906250051], + [34.97753906249997, 1.861914062499991], + [34.9640625000001, 2.06240234374998], + [34.8830078125001, 2.417919921875026], + [34.90576171875003, 2.4796875], + [34.44785156250006, 3.163476562500037], + [34.40722656249997, 3.357519531250034], + [34.39941406249997, 3.412695312500006], + [34.44179687499999, 3.60625], + [34.43769531250004, 3.650585937499969], + [34.392871093750074, 3.691503906250048], + [34.26708984375003, 3.733154296875], + [34.16503906250003, 3.812988281250014], + [34.18574218750004, 3.869775390625037], + [34.13203125000004, 3.889160156249986], + [33.97607421874997, 4.220214843750028], + [34.176855468750006, 4.419091796875037], + [34.38017578125002, 4.620654296874974], + [34.6398437500001, 4.875488281250028], + [34.878320312499994, 5.109570312500026], + [35.08447265624997, 5.31186523437502], + [35.268359375000074, 5.492285156250006], + [35.325292968750006, 5.364892578124994] + ] + ] + }, + "properties": { "name": "Kenya", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [72.63994140625002, 39.385986328125], + [72.22998046875, 39.20751953125], + [72.14736328125002, 39.2607421875], + [72.08417968750001, 39.31064453125], + [72.04277343750002, 39.3521484375], + [71.77861328125002, 39.277978515624994], + [71.73222656250002, 39.422998046874994], + [71.50332031250002, 39.478808593749996], + [71.51738281250002, 39.553857421874994], + [71.50302734375, 39.582177734374994], + [71.4703125, 39.603662109374994], + [70.79931640625, 39.3947265625], + [70.50117187500001, 39.587353515625], + [69.29765625000002, 39.524804687499994], + [69.2447265625, 39.827099609375], + [69.27880859375, 39.917773437499996], + [69.3072265625, 39.968554687499996], + [69.36542968750001, 39.947070312499996], + [69.43193359375002, 39.909765625], + [69.47626953125001, 39.919726562499996], + [69.47099609375002, 39.990625], + [69.46875, 40.020751953125], + [69.966796875, 40.20224609375], + [70.59921875, 39.974511718749994], + [70.990625, 40.2548828125], + [71.3046875, 40.286914062499996], + [71.69248046875, 40.15234375], + [72.13125, 40.438623046874994], + [72.3892578125, 40.427392578124994], + [72.40205078125001, 40.578076171875], + [72.6041015625, 40.525439453124996], + [73.13212890625002, 40.82851562499999], + [72.65830078125, 40.869921875], + [72.36406250000002, 41.04345703125], + [72.294921875, 41.039941406249994], + [72.21308593750001, 41.0142578125], + [72.18730468750002, 41.025927734374996], + [72.18095703125002, 41.118457031249996], + [72.16425781250001, 41.173730468749994], + [72.11542968750001, 41.186572265624996], + [72.05244140625001, 41.16474609375], + [71.95849609375, 41.187060546874996], + [71.87861328125001, 41.19501953125], + [71.8580078125, 41.311376953125], + [71.79248046875, 41.413134765624996], + [71.75771484375002, 41.428027343749996], + [71.70068359375, 41.454003906249994], + [71.66494140625002, 41.5412109375], + [71.6375, 41.5341796875], + [71.60224609375001, 41.503271484375], + [71.60625, 41.367431640625], + [71.54560546875001, 41.308056640625], + [71.5, 41.307470703125], + [71.4208984375, 41.34189453125], + [71.40839843750001, 41.136035156249996], + [71.39306640625, 41.123388671875], + [71.11074218750002, 41.152636718749996], + [70.86044921875, 41.224902343749996], + [70.734375, 41.400537109374994], + [70.18095703125002, 41.571435546874994], + [70.85664062500001, 42.030810546874996], + [71.0322265625, 42.077783203124994], + [71.228515625, 42.162890625], + [71.23232421875002, 42.186279296875], + [71.21269531250002, 42.206445312499994], + [71.12998046875, 42.25], + [71.03603515625002, 42.28466796875], + [70.97900390625, 42.266552734375], + [70.94677734375, 42.248681640624994], + [70.89287109375002, 42.339990234374994], + [71.25664062500002, 42.733544921874994], + [71.42207031250001, 42.783154296875], + [71.5142578125, 42.766943359375], + [71.76054687500002, 42.821484375], + [72.16181640625001, 42.760693359375], + [72.27578125000002, 42.757666015625], + [72.54316406250001, 42.677734375], + [72.7529296875, 42.637890625], + [72.855078125, 42.5611328125], + [73.28291015625001, 42.5041015625], + [73.316015625, 42.4669921875], + [73.41162109375, 42.419775390625], + [73.49296875000002, 42.409033203125], + [73.421875, 42.593505859375], + [73.4501953125, 42.703027343749994], + [73.55625, 43.002783203125], + [73.88603515625002, 43.132568359375], + [74.20908203125, 43.240380859374994], + [75.04765625000002, 42.90439453125], + [75.68173828125, 42.83046875], + [75.78955078125, 42.932910156249996], + [75.84033203125, 42.9375], + [75.9322265625, 42.928515625], + [76.50917968750002, 42.918896484375], + [76.646484375, 42.92880859375], + [76.98808593749999, 42.973583984375], + [79.12666015625001, 42.775732421875], + [79.20302734375002, 42.666015625], + [79.29550781250003, 42.604833984375], + [79.36777343750003, 42.547216796875], + [79.42822265625, 42.48349609375], + [79.92109375000001, 42.413134765624996], + [80.0712890625, 42.302978515625], + [80.209375, 42.1900390625], + [80.24619140625003, 42.059814453125], + [80.23515624999999, 42.04345703125], + [80.21621093750002, 42.032421875], + [79.90966796875, 42.014990234375], + [79.84042968750003, 41.995751953124994], + [79.76611328125, 41.898876953125], + [78.74257812500002, 41.56005859375], + [78.54316406250001, 41.4595703125], + [78.44287109375, 41.417529296874996], + [78.36240234375003, 41.371630859374996], + [78.34628906250003, 41.2814453125], + [78.12343750000002, 41.075634765625], + [77.95644531250002, 41.050683593749994], + [77.81523437499999, 41.055615234375], + [77.71933593750003, 41.024316406249994], + [77.58173828125001, 40.9927734375], + [76.98662109374999, 41.03916015625], + [76.90771484375, 41.024169921875], + [76.82402343749999, 40.982324218749994], + [76.70839843750002, 40.818115234375], + [76.6611328125, 40.779638671875], + [76.63984375000001, 40.742236328124996], + [76.62216796875003, 40.662353515625], + [76.57792968749999, 40.577880859375], + [76.48017578125001, 40.449511718749996], + [76.39638671875002, 40.389794921874994], + [76.31855468750001, 40.35224609375], + [76.25830078125, 40.43076171875], + [75.87197265625002, 40.30322265625], + [75.67714843750002, 40.305810546874994], + [75.55556640625002, 40.6251953125], + [75.52080078125002, 40.6275390625], + [75.24101562500002, 40.480273437499996], + [75.111328125, 40.4541015625], + [75.0044921875, 40.449511718749996], + [74.865625, 40.493505859375], + [74.80126953125, 40.428515625], + [74.83046875000002, 40.32851562499999], + [74.41191406250002, 40.13720703125], + [74.24267578125, 40.092041015625], + [74.08515625000001, 40.07431640625], + [73.99160156250002, 40.043115234374994], + [73.93876953125002, 39.978808593749996], + [73.88457031250002, 39.8779296875], + [73.85625, 39.828662109374996], + [73.83535156250002, 39.800146484375], + [73.83974609375002, 39.762841796874994], + [73.88251953125001, 39.71455078125], + [73.9146484375, 39.606494140624996], + [73.90712890625002, 39.57851562499999], + [73.87275390625001, 39.53330078125], + [73.82294921875001, 39.48896484375], + [73.71572265625002, 39.462255859375], + [73.63164062500002, 39.448876953124994], + [73.47041015625001, 39.460595703124994], + [73.38740234375001, 39.442724609375], + [73.33613281250001, 39.412353515625], + [73.2349609375, 39.374560546874996], + [73.10927734375002, 39.3619140625], + [72.63994140625002, 39.385986328125] + ], + [ + [70.66416015625, 39.85546875], + [70.56708984375001, 39.866601562499994], + [70.49775390625001, 39.882421875], + [70.48281250000002, 39.882714843749994], + [70.4892578125, 39.863037109375], + [70.5595703125, 39.790917968749994], + [70.61210937500002, 39.786767578124994], + [70.70166015625, 39.82529296875], + [70.66416015625, 39.85546875] + ], + [ + [71.20615234375, 39.892578125], + [71.22871093750001, 40.048144531249996], + [71.08037109375002, 40.079882812499996], + [71.02412109375001, 40.149169921875], + [71.00546875, 40.152294921875], + [70.96064453125001, 40.08798828125], + [71.04482421875002, 39.992529296875], + [71.04365234375001, 39.976318359375], + [71.01171875, 39.8951171875], + [71.06425781250002, 39.884912109374994], + [71.15625, 39.883447265624994], + [71.20615234375, 39.892578125] + ] + ] + }, + "properties": { "name": "Kyrgyzstan", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [104.42636718750006, 10.411230468749991], + [103.87050781250005, 10.655126953125034], + [103.58710937500004, 10.552197265625026], + [103.54042968750005, 10.668701171875043], + [103.721875, 10.890136718750043], + [103.5324218750001, 11.146679687499997], + [103.35361328125006, 10.921582031250054], + [103.15283203124997, 10.913720703125051], + [103.12548828124997, 11.460644531250011], + [102.9486328125, 11.773486328124974], + [102.93388671875002, 11.706689453125037], + [102.73662109375007, 12.089794921875011], + [102.75566406250002, 12.42626953125], + [102.49960937500012, 12.669970703125003], + [102.33632812500005, 13.560302734375014], + [102.546875, 13.585693359375043], + [102.90927734375006, 14.136718750000028], + [103.19941406250004, 14.332617187499977], + [104.77900390625004, 14.427832031250006], + [105.07412109375005, 14.227441406250037], + [105.12597656250003, 14.280957031250011], + [105.16914062500004, 14.336083984374966], + [105.1833007812501, 14.346240234374989], + [105.18554687500003, 14.319091796874972], + [105.20703125000003, 14.259375], + [105.24570312500006, 14.200537109374977], + [105.35019531250006, 14.109570312500011], + [105.53154296875007, 14.156152343749994], + [105.73974609375003, 14.084960937500057], + [105.83144531250005, 13.976611328125003], + [105.9044921875001, 13.924511718750054], + [106.06679687500005, 13.921191406250003], + [106.12470703125004, 14.049121093750031], + [106.09667968749997, 14.127099609375023], + [106.00410156250004, 14.262890624999983], + [105.97890625, 14.343017578125043], + [106.00839843750012, 14.357177734375], + [106.1652343750001, 14.372363281249989], + [106.19072265625007, 14.388134765624997], + [106.22539062500002, 14.476220703125009], + [106.26796875, 14.466210937500009], + [106.35498046875003, 14.454785156249997], + [106.44697265625004, 14.515039062500009], + [106.50146484375003, 14.578222656250006], + [106.53115234375005, 14.549414062499991], + [106.5636718750001, 14.505078125000026], + [106.59921875000006, 14.479394531250037], + [106.66542968750005, 14.441308593749994], + [106.73818359375005, 14.387744140625017], + [106.78349609375002, 14.335107421875037], + [106.81992187500006, 14.314697265625057], + [106.91318359375006, 14.329394531250031], + [106.93808593750006, 14.327343750000054], + [106.99218750000003, 14.391015624999966], + [107.03017578125, 14.425683593750009], + [107.06240234375, 14.415771484375043], + [107.109375, 14.416699218750054], + [107.29267578125004, 14.592382812500048], + [107.37988281250003, 14.555322265625051], + [107.41474609375004, 14.56289062499999], + [107.51943359375005, 14.705078125], + [107.3314453125, 14.126611328125009], + [107.60546874999997, 13.437792968750017], + [107.47539062500002, 13.030371093749963], + [107.50644531250006, 12.364550781250031], + [107.39335937500002, 12.260498046874972], + [107.21210937500004, 12.30400390624996], + [106.70009765625, 11.979296874999974], + [106.41386718750002, 11.9484375], + [106.39921875000007, 11.687011718750028], + [106.0060546875001, 11.758007812500011], + [105.85146484375005, 11.635009765625], + [105.85605468750006, 11.294287109375048], + [106.16093750000002, 11.037109375000057], + [106.16396484375005, 10.794921875], + [105.85332031250007, 10.86357421874996], + [105.75507812500004, 10.989990234375043], + [105.40576171875003, 10.95161132812504], + [105.3146484375001, 10.845166015625026], + [105.04570312500002, 10.911376953125014], + [105.04638671874997, 10.701660156250014], + [104.85058593749997, 10.534472656249974], + [104.42636718750006, 10.411230468749991] + ] + ] + }, + "properties": { "name": "Cambodia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-157.34213867187503, 1.855566406250034], + [-157.17578125, 1.73984375], + [-157.57895507812498, 1.902050781249997], + [-157.43583984374993, 1.84726562500002], + [-157.365185546875, 1.94609375], + [-157.44189453125003, 2.025048828125009], + [-157.321875, 1.968554687500045], + [-157.34213867187503, 1.855566406250034] + ] + ], + [ + [ + [-159.3390625, 3.923535156249983], + [-159.27475585937503, 3.796582031250054], + [-159.40903320312503, 3.87324218750004], + [-159.3390625, 3.923535156249983] + ] + ] + ] + }, + "properties": { "name": "Kiribati", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [126.32695312500002, 33.2236328125], + [126.16562500000012, 33.31201171875], + [126.33769531250002, 33.46040039062501], + [126.90117187500002, 33.51513671874997], + [126.87285156250002, 33.34116210937498], + [126.32695312500002, 33.2236328125] + ] + ], + [ + [ + [126.23369140625002, 34.370507812499994], + [126.12285156250002, 34.443945312500034], + [126.34384765625012, 34.544921875], + [126.23369140625002, 34.370507812499994] + ] + ], + [ + [ + [126.17197265625006, 34.73115234375001], + [126.00751953125004, 34.86748046874999], + [126.07841796875002, 34.914843750000045], + [126.17197265625006, 34.73115234375001] + ] + ], + [ + [ + [128.0658203125, 34.80585937500004], + [128.05468750000003, 34.70805664062502], + [127.87343750000005, 34.73496093749998], + [127.8322265625001, 34.87451171875], + [128.0658203125, 34.80585937500004] + ] + ], + [ + [ + [128.74101562500007, 34.798535156249955], + [128.64667968750004, 34.73686523437502], + [128.48925781250003, 34.86528320312496], + [128.66796875000003, 35.0087890625], + [128.74101562500007, 34.798535156249955] + ] + ], + [ + [ + [126.52070312500004, 37.73681640625003], + [126.516015625, 37.60468750000001], + [126.42333984375003, 37.62363281250006], + [126.41162109374997, 37.82265625000002], + [126.52070312500004, 37.73681640625003] + ] + ], + [ + [ + [128.37460937500012, 38.6234375], + [129.41826171875002, 37.059033203124955], + [129.40351562500004, 36.052148437499994], + [129.57285156250006, 36.05053710937503], + [129.4191406250001, 35.49785156249996], + [129.07675781250006, 35.12270507812502], + [128.5109375000001, 35.10097656250002], + [128.44394531250012, 34.87036132812503], + [128.03623046875006, 35.02197265625], + [127.71484374999997, 34.95468749999998], + [127.71542968750012, 34.72104492187498], + [127.40429687499997, 34.823095703125006], + [127.47910156250012, 34.625244140625], + [127.324609375, 34.463281249999966], + [127.17343750000006, 34.54614257812497], + [127.24707031249997, 34.755126953125], + [126.89746093749997, 34.438867187499966], + [126.75478515625005, 34.511865234374994], + [126.53144531250004, 34.31425781249999], + [126.26445312500002, 34.67324218750002], + [126.52451171875006, 34.697900390624966], + [126.59335937500012, 34.824365234374994], + [126.42070312500002, 34.823388671874966], + [126.29111328125012, 35.154150390625034], + [126.61406250000007, 35.57099609375004], + [126.4884765625001, 35.647070312500006], + [126.75302734375006, 35.871972656249994], + [126.5404296875, 36.166162109374966], + [126.4876953125, 36.69379882812498], + [126.18085937500004, 36.69160156249998], + [126.16054687500005, 36.77192382812501], + [126.48701171875004, 37.00747070312502], + [126.78447265625007, 36.94843749999998], + [126.87207031249997, 36.82446289062506], + [126.97685546875002, 36.93940429687501], + [126.74638671875002, 37.19355468750001], + [126.63388671875012, 37.78183593750006], + [127.09033203125003, 38.28388671875001], + [128.03896484375, 38.30854492187498], + [128.37460937500012, 38.6234375] + ] + ] + ] + }, + "properties": { "name": "Korea", "childNum": 7 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [48.27539062499997, 29.624316406250017], + [48.17968750000003, 29.611425781250063], + [48.081445312499994, 29.798925781250063], + [48.1847656250001, 29.978857421875034], + [48.348242187500006, 29.78266601562504], + [48.27539062499997, 29.624316406250017] + ] + ], + [ + [ + [48.442480468750006, 28.542919921874983], + [47.671289062499994, 28.53315429687504], + [47.433203125, 28.989550781250017], + [46.53144531250004, 29.09624023437499], + [46.69375, 29.259667968749966], + [46.76933593750002, 29.347460937500017], + [46.90585937500006, 29.5375], + [47.14824218750002, 30.0009765625], + [47.64375, 30.097314453125023], + [47.75390624999997, 30.076611328124955], + [47.97871093750004, 29.98281250000005], + [48.00566406250002, 29.835791015625034], + [48.143457031249994, 29.57246093750001], + [47.96962890625005, 29.61669921874997], + [47.72265624999997, 29.393017578124955], + [48.0514648437501, 29.355371093750023], + [48.442480468750006, 28.542919921874983] + ] + ] + ] + }, + "properties": { "name": "Kuwait", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [102.12744140625011, 22.37919921874999], + [102.58251953125006, 21.904296875000057], + [102.66201171875008, 21.676025390625057], + [102.73857421875005, 21.677929687500125], + [102.77109375000015, 21.70966796875001], + [102.79824218750014, 21.797949218750034], + [102.81591796875, 21.807373046875], + [102.94960937500008, 21.681347656250068], + [102.85117187500009, 21.26591796874999], + [102.8837890625, 21.202587890625068], + [103.1044921875, 20.89165039062499], + [103.21074218749999, 20.840625], + [103.46357421874995, 20.779833984375102], + [103.6350585937501, 20.697070312500102], + [104.10136718750005, 20.945507812500125], + [104.1953125, 20.91396484375008], + [104.349609375, 20.82109374999999], + [104.58320312500001, 20.646679687499955], + [104.53271484375, 20.554882812500125], + [104.47861328124998, 20.529589843750102], + [104.40781250000015, 20.485742187500023], + [104.36777343750015, 20.441406250000057], + [104.39218750000015, 20.424755859375068], + [104.49619140625003, 20.41367187499992], + [104.61884765624995, 20.374511718750114], + [104.65644531250001, 20.328515624999966], + [104.66191406250005, 20.289013671875125], + [104.67695312500007, 20.224707031249977], + [104.69873046875006, 20.205322265625114], + [104.84785156250007, 20.202441406250045], + [104.88867187500006, 20.169091796875023], + [104.92919921874994, 20.082812500000045], + [104.92792968750007, 20.01811523437499], + [104.81513671875001, 19.90400390625001], + [104.80175781250011, 19.836132812500068], + [104.74316406250006, 19.754736328124977], + [104.58789062500006, 19.61875], + [104.54628906250014, 19.610546875000068], + [104.25986328125003, 19.685498046875068], + [104.06279296875005, 19.678417968750068], + [104.03203124999999, 19.67514648437492], + [104.0134765625001, 19.646484374999943], + [104.05156250000005, 19.564160156250068], + [104.06289062500002, 19.482568359375136], + [104.02753906250013, 19.420458984375102], + [103.93203125000002, 19.366064453125034], + [103.89638671875002, 19.339990234375023], + [103.89160156249994, 19.30498046874999], + [105.146484375, 18.650976562499977], + [105.14541015625014, 18.616796874999977], + [105.08701171875015, 18.49624023437508], + [105.11455078125005, 18.405273437500057], + [105.45820312500007, 18.154296875000057], + [105.51855468750011, 18.077441406250045], + [105.58847656250015, 17.983691406249932], + [105.69140625, 17.737841796874932], + [106.00625, 17.415283203124943], + [106.26953125, 17.216796875000057], + [106.33339843750002, 17.14370117187508], + [106.42597656250007, 17.00253906250009], + [106.50224609374999, 16.9541015625], + [106.52597656250003, 16.876611328125023], + [106.53369140625, 16.821044921875057], + [106.54619140625005, 16.650732421874977], + [106.65644531250013, 16.492626953125125], + [106.73955078124999, 16.452539062500136], + [106.79160156250015, 16.490332031249977], + [106.83242187500008, 16.526269531250023], + [106.85107421875, 16.515625], + [106.89277343750013, 16.396533203125102], + [106.93066406250006, 16.353125], + [107.39638671875008, 16.04301757812499], + [107.39199218750008, 15.951660156250057], + [107.36064453125005, 15.921728515624977], + [107.18886718750008, 15.838623046875114], + [107.16591796875002, 15.802490234375], + [107.27939453125003, 15.618701171875045], + [107.33876953125002, 15.560498046875125], + [107.56425781249999, 15.3916015625], + [107.62167968750015, 15.309863281250045], + [107.653125, 15.255224609375091], + [107.63369140625008, 15.18984375000008], + [107.58964843749999, 15.118457031250102], + [107.55527343750009, 15.057031250000023], + [107.48037109375014, 14.979882812500136], + [107.5046875000001, 14.91591796875008], + [107.52451171875003, 14.871826171874943], + [107.51376953124998, 14.817382812500057], + [107.51943359375008, 14.705078125000114], + [107.46513671875005, 14.664990234375125], + [107.41474609375007, 14.56289062500008], + [107.37988281250006, 14.555322265625136], + [107.29267578125007, 14.592382812500034], + [107.109375, 14.416699218749955], + [107.06240234375008, 14.415771484374943], + [107.03017578125008, 14.425683593750023], + [106.99218749999994, 14.39101562500008], + [106.93808593750015, 14.327343750000068], + [106.91318359375003, 14.329394531249932], + [106.81992187500003, 14.314697265624943], + [106.7834960937501, 14.335107421875023], + [106.73818359375008, 14.387744140625102], + [106.66542968750002, 14.441308593750023], + [106.59921875000003, 14.479394531250136], + [106.56367187500007, 14.505078125000011], + [106.53115234375002, 14.549414062499977], + [106.50146484375, 14.578222656250034], + [106.22539062500005, 14.476220703125023], + [106.1907226562501, 14.388134765625011], + [106.16523437500007, 14.372363281249989], + [106.00839843750009, 14.357177734375114], + [105.97890625000014, 14.343017578125057], + [106.00410156250013, 14.262890625000068], + [106.09667968750011, 14.127099609375136], + [106.12470703124995, 14.049121093750045], + [106.06679687500008, 13.921191406250102], + [105.90449218750007, 13.924511718750068], + [105.83144531250008, 13.976611328124989], + [105.73974609375006, 14.084960937500057], + [105.5315429687501, 14.156152343750023], + [105.35019531250009, 14.109570312500125], + [105.24570312500015, 14.200537109374977], + [105.20703125000006, 14.259375], + [105.18554687499994, 14.319091796875], + [105.18330078125001, 14.346240234374989], + [105.24365234375006, 14.367871093749955], + [105.34218750000008, 14.416699218749955], + [105.42265624999993, 14.471630859374955], + [105.47558593750006, 14.530126953124977], + [105.49736328125005, 14.590673828125034], + [105.52304687500015, 14.843310546874989], + [105.54667968749999, 14.932470703125034], + [105.53339843750013, 15.041601562500091], + [105.49042968750007, 15.127587890625023], + [105.49042968750007, 15.256591796875], + [105.615625, 15.488281249999943], + [105.63886718750013, 15.585937499999943], + [105.64101562500002, 15.656542968749932], + [105.62207031250006, 15.699951171875114], + [105.39892578125011, 15.829882812500102], + [105.40625, 15.987451171875023], + [105.33066406250003, 16.037890625000045], + [105.1487304687501, 16.09355468749999], + [105.04716796874999, 16.16025390625009], + [104.81933593749994, 16.466064453125057], + [104.75058593750015, 16.647558593750034], + [104.74355468750014, 16.884375], + [104.75898437500013, 17.0771484375], + [104.81601562499998, 17.30029296875], + [104.73964843750008, 17.461669921875], + [104.428125, 17.698974609375057], + [104.32265625000002, 17.815820312500023], + [104.19619140625002, 17.988378906250034], + [104.04873046875002, 18.216699218749966], + [103.94960937500008, 18.318994140625023], + [103.89882812500002, 18.295312500000023], + [103.79228515624999, 18.31650390625009], + [103.62968750000005, 18.382568359375057], + [103.48798828124995, 18.41816406250001], + [103.36699218750005, 18.42333984375], + [103.28828124999995, 18.408398437499955], + [103.25175781249999, 18.373486328125125], + [103.24892578125014, 18.338964843750034], + [103.27958984374999, 18.304980468750045], + [103.26318359375, 18.278466796875136], + [103.19970703125006, 18.25947265625001], + [103.14853515625009, 18.221728515624932], + [103.09121093750014, 18.13823242187499], + [103.05136718750003, 18.02851562500001], + [102.80742187500005, 17.945556640625], + [102.71757812500005, 17.892236328125136], + [102.67519531250014, 17.851757812500068], + [102.68007812500008, 17.824121093750136], + [102.66064453125, 17.8179687499999], + [102.61679687500015, 17.833349609375034], + [102.59824218750009, 17.926757812500057], + [102.55253906249999, 17.965087890625057], + [102.4587890625001, 17.984619140624943], + [102.35185546874999, 18.045947265625045], + [102.14824218750005, 18.203857421875057], + [102.10146484375014, 18.21064453125001], + [102.03457031250002, 18.169824218750023], + [101.94746093750001, 18.081494140624955], + [101.87548828125011, 18.046435546874932], + [101.81865234375005, 18.064648437500125], + [101.77480468750002, 18.033398437500125], + [101.6875, 17.889404296875114], + [101.56367187500001, 17.820507812500125], + [101.55507812500002, 17.812353515625034], + [101.41367187500015, 17.71875], + [101.16748046875011, 17.4990234375], + [101.10517578125001, 17.479541015625102], + [100.9084960937501, 17.583886718750023], + [101.14394531250008, 18.14262695312499], + [101.1375, 18.286865234375057], + [101.0505859375001, 18.407031250000045], + [101.04697265625003, 18.441992187500034], + [101.28632812499995, 18.977148437500034], + [101.19755859374999, 19.327929687500045], + [101.22080078125015, 19.486621093750045], + [101.21191406250011, 19.548339843750057], + [100.51357421875008, 19.553466796875], + [100.39765625000013, 19.756103515625057], + [100.51953125000006, 20.177929687500068], + [100.31796875000003, 20.385888671875136], + [100.2180664062501, 20.339599609375114], + [100.13974609375015, 20.245410156250102], + [100.11494140625007, 20.25766601562492], + [100.12246093750002, 20.316650390625057], + [100.12968750000005, 20.372216796875023], + [100.1838867187501, 20.589111328124943], + [100.2493164062501, 20.730273437499932], + [100.32607421875008, 20.795703124999932], + [100.40742187499995, 20.823242187500057], + [100.56513671875013, 20.82509765625008], + [100.62294921875002, 20.85957031250001], + [100.61767578125, 20.87924804687509], + [100.54931640625011, 20.884228515625068], + [100.5222656250001, 20.921923828125102], + [100.53613281250006, 20.992382812500068], + [100.703125, 21.25136718750008], + [101.0803710937501, 21.46865234375008], + [101.13886718750013, 21.567480468749977], + [101.19667968750002, 21.522070312499977], + [101.17539062500009, 21.407519531250102], + [101.21992187500013, 21.342431640625136], + [101.21181640625008, 21.278222656250023], + [101.22441406249999, 21.22373046874992], + [101.24785156249993, 21.197314453125045], + [101.28144531250007, 21.184130859375045], + [101.44355468750001, 21.230810546874977], + [101.54238281250008, 21.234277343750136], + [101.70478515625013, 21.150146484375057], + [101.728125, 21.15639648437508], + [101.78349609374999, 21.204150390625045], + [101.8005859375001, 21.212597656249955], + [101.7229492187501, 21.314941406250057], + [101.74726562500007, 21.60576171874999], + [101.7439453125001, 21.77797851562508], + [101.73652343750001, 21.826513671874977], + [101.52451171874998, 22.253662109375], + [101.56787109375011, 22.2763671875], + [101.6199218750001, 22.327441406250102], + [101.67148437500009, 22.462304687500023], + [101.70751953125, 22.486572265625], + [101.73876953125011, 22.495263671874966], + [101.75996093750001, 22.490332031250034], + [101.841796875, 22.388476562500102], + [102.02441406250006, 22.439208984375114], + [102.09150390625007, 22.412255859375136], + [102.12744140625011, 22.37919921874999] + ] + ] + }, + "properties": { "name": "Lao PDR", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [35.869140625, 33.43173828125], + [35.411230468750006, 33.07568359375], + [35.10859375000001, 33.08369140625], + [35.64785156250002, 34.2482421875], + [35.97626953125001, 34.629199218749996], + [36.383886718750006, 34.65791015625], + [36.32988281250002, 34.499609375], + [36.50439453125, 34.432373046875], + [36.5849609375, 34.221240234374996], + [36.27783203125, 33.92529296875], + [36.36503906250002, 33.83935546875], + [35.98613281250002, 33.75263671875], + [36.03447265625002, 33.58505859375], + [35.869140625, 33.43173828125] + ] + ] + }, + "properties": { "name": "Lebanon", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-8.486425781249977, 7.558496093749994], + [-8.408740234374989, 7.411816406249997], + [-8.324511718749989, 6.920019531249991], + [-8.587890625, 6.490527343749989], + [-8.287109375, 6.319042968749997], + [-7.981591796874994, 6.2861328125], + [-7.888623046874983, 6.23486328125], + [-7.800927734374994, 6.038916015624991], + [-7.730371093749994, 5.919042968749991], + [-7.636132812499994, 5.90771484375], + [-7.454394531249989, 5.84130859375], + [-7.39990234375, 5.550585937499989], + [-7.585058593749977, 4.916748046875], + [-7.574658203124983, 4.572314453124989], + [-7.544970703124989, 4.351318359375], + [-8.259033203125, 4.589990234374994], + [-9.132177734374977, 5.054638671874997], + [-10.2763671875, 6.07763671875], + [-11.291601562499977, 6.688232421875], + [-11.507519531249983, 6.906542968749989], + [-11.267675781249977, 7.232617187499997], + [-10.878076171874994, 7.538232421874994], + [-10.6474609375, 7.759375], + [-10.570849609374989, 8.071142578124991], + [-10.516748046874994, 8.125292968749989], + [-10.359814453124983, 8.187939453124997], + [-10.283203125, 8.485156249999989], + [-10.233056640624994, 8.488818359374989], + [-10.147412109374983, 8.519726562499997], + [-10.064355468749994, 8.429882812499997], + [-9.781982421875, 8.537695312499991], + [-9.518261718749983, 8.34609375], + [-9.369140625, 7.703808593749997], + [-9.463818359374983, 7.415869140624991], + [-9.11757812499999, 7.215917968749991], + [-8.8896484375, 7.2626953125], + [-8.659765624999977, 7.688378906249994], + [-8.486425781249977, 7.558496093749994] + ] + ] + }, + "properties": { "name": "Liberia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [25.150488281250006, 31.654980468749997], + [24.85273437500001, 31.334814453125], + [24.96142578125, 30.678515625], + [24.703222656250006, 30.201074218749994], + [24.980273437500017, 29.181884765625], + [24.980273437500017, 25.5888671875], + [24.980273437500017, 21.995849609375], + [24.9794921875, 20.002587890624994], + [23.980273437500017, 19.99594726562499], + [23.980273437500017, 19.496630859375003], + [20.14765625000001, 21.38925781249999], + [15.984082031250011, 23.445214843749994], + [14.97900390625, 22.99619140624999], + [14.215527343750011, 22.619677734375003], + [13.48125, 23.18017578125], + [11.967871093750006, 23.517871093750003], + [11.507617187500017, 24.314355468749994], + [10.686132812500006, 24.55136718749999], + [10.395898437500023, 24.485595703125], + [10.255859375, 24.591015625], + [10.000683593750011, 25.332080078125003], + [9.4482421875, 26.067138671875], + [9.491406250000011, 26.333740234375], + [9.883203125000023, 26.630810546874997], + [9.74755859375, 27.330859375], + [9.916015625, 27.785693359374996], + [9.805273437500006, 29.176953125], + [9.310253906250011, 30.115234375], + [9.51875, 30.229394531249994], + [9.89501953125, 30.3873046875], + [9.932519531250023, 30.425341796874996], + [10.059765625000011, 30.580078125], + [10.21640625, 30.783203125], + [10.114941406250011, 31.463769531249994], + [10.274609375000011, 31.684960937499994], + [10.475781250000011, 31.736035156249997], + [10.60888671875, 31.929541015625], + [10.826367187500011, 32.0806640625], + [11.005175781250017, 32.172705078125], + [11.168261718750017, 32.256738281249994], + [11.358007812500006, 32.34521484375], + [11.504980468750006, 32.413671875], + [11.535937500000017, 32.47333984375], + [11.533789062500006, 32.524951171874996], + [11.453906250000017, 32.642578125], + [11.453906250000017, 32.781689453125], + [11.459179687500011, 32.897363281249994], + [11.467187500000023, 32.965722656249994], + [11.504589843750011, 33.181933593749996], + [11.657128906250023, 33.118896484375], + [11.8134765625, 33.093701171875], + [12.279882812500006, 32.858544921874994], + [12.753515625, 32.801074218749996], + [13.283496093750017, 32.9146484375], + [15.176562500000017, 32.391162109374996], + [15.705957031250023, 31.426416015624994], + [17.830468750000023, 30.927587890625], + [18.669824218750023, 30.415673828124994], + [19.12373046875001, 30.26611328125], + [19.713281250000023, 30.48837890625], + [20.11152343750001, 30.963720703125], + [19.926367187500006, 31.817529296874994], + [20.121484375000023, 32.21875], + [20.62109375, 32.58017578125], + [21.63593750000001, 32.937304687499996], + [22.187402343750023, 32.918261718749996], + [23.090625, 32.61875], + [23.10625, 32.331445312499994], + [23.28632812500001, 32.213818359375], + [24.129687500000017, 32.009228515625], + [24.878515625, 31.984277343749994], + [25.150488281250006, 31.654980468749997] + ] + ] + }, + "properties": { "name": "Libya", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-60.89521484375, 13.821972656249997], + [-60.951416015625, 13.717578125], + [-61.073144531249994, 13.865576171874991], + [-60.908105468749994, 14.09335937499999], + [-60.89521484375, 13.821972656249997] + ] + ] + }, + "properties": { "name": "Saint Lucia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [79.87480468750002, 9.050732421875026], + [79.90371093750005, 8.975], + [79.74765625000006, 9.104589843749991], + [79.87480468750002, 9.050732421875026] + ] + ], + [ + [ + [79.98232421875, 9.812695312500011], + [80.25283203125005, 9.796337890625054], + [80.71113281250004, 9.366357421875023], + [81.226953125, 8.50551757812498], + [81.37285156250002, 8.431445312499989], + [81.42216796875007, 8.147851562500023], + [81.87412109375012, 7.288330078124986], + [81.86142578125012, 6.901269531249994], + [81.63740234375004, 6.425146484374991], + [80.72412109375003, 5.97905273437496], + [80.26738281250007, 6.009765625], + [80.09531250000012, 6.153173828125006], + [79.859375, 6.829296874999983], + [79.71298828125012, 8.18232421875004], + [79.74980468750007, 8.294238281250003], + [79.78349609375007, 8.018457031250051], + [79.92890625000004, 8.899218749999974], + [80.09960937499997, 9.209960937500043], + [80.08632812500005, 9.577832031250026], + [80.42832031250006, 9.480957031250014], + [80.04580078125005, 9.649902343749972], + [79.98232421875, 9.812695312500011] + ] + ] + ] + }, + "properties": { "name": "Sri Lanka", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.646875, -30.1265625], + [28.39208984375, -30.147558593750006], + [28.128710937500017, -30.52509765625001], + [28.05683593750001, -30.63105468750001], + [27.753125, -30.6], + [27.364062500000017, -30.27919921875001], + [27.19355468750001, -29.94130859375001], + [27.056933593750017, -29.625585937500006], + [27.29453125, -29.519335937500003], + [27.73554687500001, -28.940039062500006], + [27.959863281250023, -28.873339843750003], + [28.084375, -28.77998046875001], + [28.23261718750001, -28.701269531250006], + [28.471875, -28.615820312500006], + [28.583398437500023, -28.594140625], + [28.625781250000017, -28.58173828125001], + [29.301367187500006, -29.08984375], + [29.38671875, -29.31972656250001], + [29.34882812500001, -29.441992187500006], + [29.293554687500006, -29.56689453125], + [29.1421875, -29.700976562500003], + [29.098046875000023, -29.919042968750006], + [28.646875, -30.1265625] + ] + ] + }, + "properties": { "name": "Lesotho", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [20.957812500000074, 55.27890625000006], + [20.89980468750008, 55.286669921875045], + [21.11484375, 55.61650390624999], + [20.957812500000074, 55.27890625000006] + ] + ], + [ + [ + [25.573046875000017, 54.139892578125], + [25.497363281250045, 54.17524414062501], + [25.52734375000003, 54.21513671874996], + [25.505664062500045, 54.26494140624999], + [25.46113281250004, 54.29277343749996], + [25.179492187500017, 54.214257812499966], + [25.111425781250006, 54.15493164062505], + [25.04609375000004, 54.13305664062503], + [24.869531250000023, 54.14516601562502], + [24.82568359374997, 54.118994140625006], + [24.78925781250001, 53.99824218750001], + [24.768164062499977, 53.97465820312499], + [24.31796875, 53.892968749999966], + [24.236621093750045, 53.91997070312496], + [24.19130859375005, 53.95043945312503], + [23.559082031250057, 53.91982421875002], + [23.484667968750074, 53.939794921875006], + [23.453613281250057, 54.14345703125002], + [23.3701171875, 54.20048828124999], + [23.282324218750063, 54.240332031250034], + [23.17031250000008, 54.28144531249998], + [23.0875, 54.299462890624994], + [23.042187500000068, 54.30419921875], + [23.01552734375005, 54.34833984375001], + [22.976757812500068, 54.36635742187505], + [22.89394531250008, 54.390527343749994], + [22.82373046874997, 54.39580078124999], + [22.766210937499977, 54.356787109375034], + [22.679882812500068, 54.493017578125006], + [22.684472656250023, 54.56293945312504], + [22.82470703125, 54.87128906249998], + [22.56728515625005, 55.05913085937496], + [22.072363281250034, 55.06367187499998], + [21.235742187500023, 55.26411132812498], + [21.237890625000034, 55.455029296874955], + [21.06191406250005, 55.81342773437498], + [21.053808593750006, 56.02294921875003], + [21.04609375000004, 56.07006835937503], + [21.31464843750004, 56.18813476562502], + [21.65351562500004, 56.314550781250006], + [22.084570312500034, 56.40673828125006], + [22.875585937500063, 56.39643554687501], + [22.96826171875003, 56.38041992187502], + [23.042968750000057, 56.324072265625006], + [23.119824218749983, 56.330664062500006], + [23.195898437500034, 56.36713867187498], + [24.120703125000063, 56.26425781249998], + [24.90302734375001, 56.398193359375], + [25.069921875, 56.20039062500004], + [25.663183593750063, 56.104833984375006], + [26.593554687500074, 55.66752929687502], + [26.590820312500057, 55.62265625], + [26.56660156250001, 55.546484375000034], + [26.51923828125004, 55.448144531249994], + [26.469531250000045, 55.371923828125006], + [26.457617187500006, 55.342480468749955], + [26.49531250000004, 55.31801757812502], + [26.68125, 55.30644531249999], + [26.76015625000008, 55.29335937499999], + [26.775683593750045, 55.27309570312502], + [26.601171875000034, 55.130175781250045], + [26.291796875000074, 55.13959960937501], + [26.250781250000045, 55.12451171875006], + [26.175195312500023, 55.003271484375034], + [26.092968750000068, 54.96230468750005], + [25.964453124999977, 54.947167968749966], + [25.85927734375005, 54.91928710937498], + [25.722460937500074, 54.71787109374998], + [25.731640625000068, 54.59038085937502], + [25.72480468750001, 54.564257812500045], + [25.68515625, 54.53579101562502], + [25.62031250000004, 54.46040039062501], + [25.56757812500004, 54.377050781250006], + [25.54736328125, 54.33183593750002], + [25.55751953125005, 54.310693359374994], + [25.702539062499994, 54.29296875], + [25.765234374999977, 54.179785156250034], + [25.573046875000017, 54.139892578125] + ] + ] + ] + }, + "properties": { "name": "Lithuania", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [6.4873046875, 49.798486328124994], + [6.344335937500006, 49.452734375], + [6.181054687500023, 49.498925781249994], + [6.119921875000017, 49.485205078125], + [6.074121093750023, 49.454638671874996], + [6.011425781250011, 49.445458984374994], + [5.95947265625, 49.454638671874996], + [5.928906250000011, 49.4775390625], + [5.9013671875, 49.48974609375], + [5.823437500000011, 49.505078125], + [5.789746093750011, 49.53828125], + [5.776710379464286, 49.639953962053575], + [5.744042968750023, 49.91962890625], + [5.7880859375, 49.961230468749996], + [5.8173828125, 50.0126953125], + [5.866894531250011, 50.0828125], + [5.976269531250011, 50.1671875], + [6.089062500000011, 50.154589843749996], + [6.110058593750011, 50.123779296875], + [6.116503906250017, 50.120996093749994], + [6.109765625000023, 50.034375], + [6.13818359375, 49.97431640625], + [6.204882812500017, 49.91513671875], + [6.272327008928583, 49.887234933035714], + [6.4873046875, 49.798486328124994] + ] + ] + }, + "properties": { "name": "Luxembourg", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.14794921875, 56.142919921875], + [27.576757812500006, 55.798779296875], + [27.052539062500017, 55.83056640625], + [26.593554687500017, 55.667529296874996], + [25.663183593750006, 56.104833984375], + [25.069921875, 56.200390625], + [24.90302734375001, 56.398193359375], + [24.120703125, 56.2642578125], + [23.81269531250001, 56.329248046875], + [23.195898437500006, 56.367138671875], + [23.11982421875001, 56.3306640625], + [23.04296875, 56.324072265625], + [22.875585937500006, 56.396435546875], + [22.084570312500006, 56.40673828125], + [21.730566406250006, 56.325976562499996], + [21.65351562500001, 56.31455078125], + [21.31464843750001, 56.188134765625], + [21.04609375000001, 56.070068359375], + [21.0712890625, 56.82373046875], + [21.72871093750001, 57.57099609375], + [22.554589843750023, 57.724267578125], + [23.28730468750001, 57.08974609375], + [23.647753906250017, 56.971044921875], + [24.382617187500017, 57.250048828124996], + [24.322558593750017, 57.87060546875], + [24.3625, 57.866162109375], + [24.458886718750023, 57.907861328125], + [25.11103515625001, 58.063427734375], + [25.27265625000001, 58.009375], + [25.66015625, 57.920166015625], + [26.29804687500001, 57.60107421875], + [26.532617187500023, 57.531005859375], + [26.96601562500001, 57.609130859375], + [27.187109375, 57.538330078125], + [27.326562500000023, 57.52548828125], + [27.4697265625, 57.5240234375], + [27.538671875, 57.42978515625], + [27.796875, 57.316943359374996], + [27.82861328125, 57.293310546875], + [27.838281250000023, 57.247705078125], + [27.83027343750001, 57.194482421875], + [27.639453125000017, 56.845654296875], + [27.806054687500023, 56.86708984375], + [27.8486328125, 56.85341796875], + [27.89208984375, 56.741064453125], + [28.00751953125001, 56.599853515625], + [28.103125, 56.545703125], + [28.11083984375, 56.510693359375], + [28.169238281250017, 56.386865234375], + [28.191699218750017, 56.315576171875], + [28.202050781250023, 56.260400390625], + [28.14794921875, 56.142919921875] + ] + ] + }, + "properties": { "name": "Latvia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.2125, 45.450439453125], + [28.07470703125, 45.598974609375], + [28.23945312500001, 46.6408203125], + [28.07177734375, 46.978417968749994], + [27.614062500000017, 47.34052734375], + [26.980761718750017, 48.155029296875], + [26.618945312500017, 48.25986328125], + [26.640429687500017, 48.294140625], + [26.847070312500023, 48.387158203125], + [26.90058593750001, 48.371923828125], + [27.228515625, 48.371435546875], + [27.549218750000023, 48.477734375], + [28.34052734375001, 48.144433593749994], + [28.42304687500001, 48.146875], + [29.125390625000023, 47.96455078125], + [29.134863281250006, 47.489697265625], + [29.455664062500006, 47.292626953124994], + [29.57197265625001, 46.964013671874994], + [29.7197265625, 46.88291015625], + [29.877832031250023, 46.82890625], + [29.942480468750006, 46.723779296874994], + [29.93476562500001, 46.625], + [29.92431640625, 46.538867187499996], + [30.13105468750001, 46.423095703125], + [30.07568359375, 46.377832031249994], + [29.878027343750006, 46.360205078125], + [29.837890625, 46.350537109375], + [29.458789062500017, 46.453759765624994], + [29.30488281250001, 46.466601562499996], + [29.22382812500001, 46.376953125], + [29.20458984375, 46.379345703125], + [29.20078125, 46.50498046875], + [29.18623046875001, 46.523974609374996], + [29.146289062500017, 46.526904296874996], + [28.958398437500023, 46.45849609375], + [28.92744140625001, 46.424121093749996], + [28.930566406250023, 46.362255859375], + [28.94375, 46.288427734375], + [29.00625, 46.17646484375], + [28.971875, 46.12763671875], + [28.94775390625, 46.049951171874994], + [28.849511718750023, 45.978662109374994], + [28.73876953125, 45.937158203124994], + [28.729296875000017, 45.852001953125], + [28.667578125, 45.793847656249994], + [28.562304687500017, 45.735791015625], + [28.491601562500023, 45.665771484375], + [28.4990234375, 45.517724609374994], + [28.310351562500017, 45.498583984374996], + [28.26484375000001, 45.48388671875], + [28.2125, 45.450439453125] + ] + ] + }, + "properties": { "name": "Moldova", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [49.936425781249994, -16.90292968750002], + [49.82402343750002, -17.08652343750002], + [50.02304687500006, -16.6953125], + [49.936425781249994, -16.90292968750002] + ] + ], + [ + [ + [48.3421875, -13.363867187500034], + [48.21191406250003, -13.385253906249957], + [48.191210937500074, -13.259960937500011], + [48.308886718750074, -13.198242187499957], + [48.3421875, -13.363867187500034] + ] + ], + [ + [ + [49.53828125000004, -12.432128906250014], + [49.9375, -13.072265624999957], + [50.23535156249997, -14.732031249999963], + [50.482714843750074, -15.385644531249994], + [50.20898437499997, -15.960449218750028], + [50.02041015625005, -15.801757812500028], + [49.89257812500003, -15.457714843750011], + [49.664355468750074, -15.521582031249977], + [49.83906250000004, -16.486523437499997], + [49.76718750000006, -16.815136718749983], + [49.44931640625006, -17.240625], + [49.477832031250074, -17.89853515624999], + [49.362890625, -18.336328125], + [47.934472656249994, -22.393945312500023], + [47.55800781250005, -23.874609374999963], + [47.17734375, -24.787207031249977], + [46.72851562499997, -25.14990234374997], + [46.15869140624997, -25.230371093750023], + [45.5080078125001, -25.56318359374997], + [45.2057617187501, -25.57050781250004], + [44.0353515625001, -24.995703125], + [43.670019531250006, -24.30029296875], + [43.722265625, -23.529687500000037], + [43.2648437500001, -22.38359375], + [43.29052734374997, -21.93251953124998], + [43.50185546875005, -21.356445312499957], + [43.800195312499994, -21.179199218749986], + [44.40468750000005, -19.922070312500026], + [44.44882812500006, -19.42871093749997], + [44.23876953124997, -19.075195312499986], + [44.23310546875004, -18.740625], + [44.04003906249997, -18.288476562500023], + [43.979394531249994, -17.3916015625], + [44.42138671874997, -16.70263671874997], + [44.476171875, -16.217285156249957], + [44.90917968749997, -16.174511718750026], + [45.2228515625001, -15.95048828124996], + [45.3421875, -16.03671875000002], + [45.598242187500006, -15.992578125], + [45.70019531249997, -15.813769531249989], + [46.157519531250074, -15.738281249999972], + [46.3996093750001, -15.924609375000017], + [46.331445312499994, -15.713671875000031], + [46.47509765625003, -15.513476562500003], + [46.942285156249994, -15.219042968749974], + [47.09921875, -15.43417968750002], + [47.092578125000074, -15.150097656249969], + [47.35195312500005, -14.766113281249986], + [47.46474609375005, -14.713281249999966], + [47.47832031250002, -15.009375], + [47.77402343750006, -14.63671875], + [47.964160156250074, -14.672558593750026], + [47.773339843749994, -14.369921875], + [47.995507812499994, -13.960449218749986], + [47.88359375000002, -13.807519531250009], + [47.94101562500006, -13.662402343750017], + [48.03984375000002, -13.596289062499963], + [48.25527343750005, -13.719335937499977], + [48.796484375, -13.267480468750023], + [48.91943359375003, -12.839062499999969], + [48.78632812500004, -12.470898437500011], + [48.931738281250006, -12.4390625], + [49.20703124999997, -12.079589843749957], + [49.53828125000004, -12.432128906250014] + ] + ] + ] + }, + "properties": { "name": "Madagascar", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-91.68369140624998, 18.677343750000034], + [-91.81611328124995, 18.675878906250006], + [-91.53671874999998, 18.760009765625], + [-91.68369140624998, 18.677343750000034] + ] + ], + [ + [ + [-86.93964843750001, 20.303320312500006], + [-86.97797851562498, 20.489794921875074], + [-86.76328124999995, 20.579052734374955], + [-86.93964843750001, 20.303320312500006] + ] + ], + [ + [ + [-106.50224609374999, 21.61083984375003], + [-106.60703124999993, 21.561474609374983], + [-106.63935546874995, 21.697851562499977], + [-106.50224609374999, 21.61083984375003] + ] + ], + [ + [ + [-110.56738281249994, 25.003466796875017], + [-110.5388671875, 24.89155273437504], + [-110.69926757812499, 25.081445312499994], + [-110.56738281249994, 25.003466796875017] + ] + ], + [ + [ + [-112.05727539062498, 24.545703125000017], + [-112.29677734375002, 24.789648437500063], + [-112.15942382812501, 25.28564453125003], + [-112.19501953124998, 24.841064453125057], + [-112.05727539062498, 24.545703125000017] + ] + ], + [ + [ + [-111.10029296874998, 26.020605468750006], + [-111.224658203125, 25.83588867187504], + [-111.18291015625002, 26.040625], + [-111.10029296874998, 26.020605468750006] + ] + ], + [ + [ + [-115.17060546875001, 28.06938476562496], + [-115.35292968750002, 28.103955078124983], + [-115.23354492187495, 28.36835937500004], + [-115.17060546875001, 28.06938476562496] + ] + ], + [ + [ + [-112.20307617187503, 29.00532226562504], + [-112.27841796875, 28.769335937500017], + [-112.51406249999997, 28.847607421874955], + [-112.42353515625, 29.203662109375017], + [-112.28505859374994, 29.240429687499955], + [-112.20307617187503, 29.00532226562504] + ] + ], + [ + [ + [-113.15561523437502, 29.05224609375], + [-113.49633789062497, 29.30761718749997], + [-113.58720703125002, 29.57304687499996], + [-113.20214843749999, 29.301855468750034], + [-113.15561523437502, 29.05224609375] + ] + ], + [ + [ + [-97.14624023437494, 25.961474609375045], + [-97.66767578124995, 24.389990234374977], + [-97.84248046874995, 22.510302734375017], + [-97.76328124999998, 22.105859374999966], + [-97.31450195312496, 21.56420898437503], + [-97.40917968749997, 21.272558593750034], + [-97.38344726562497, 21.56669921874999], + [-97.75380859375002, 22.02666015624999], + [-97.18632812499996, 20.717041015625], + [-96.45605468749994, 19.869775390624966], + [-96.28955078124994, 19.34375], + [-95.778125, 18.805517578125034], + [-95.92036132812495, 18.81958007812497], + [-95.62680664062503, 18.690576171874994], + [-95.71982421874998, 18.768359375000017], + [-95.18183593749995, 18.700732421875017], + [-94.79814453124996, 18.51459960937501], + [-94.45976562499993, 18.166650390624994], + [-93.55234375, 18.430468750000017], + [-92.88476562499997, 18.468652343749966], + [-92.44101562499998, 18.67529296874997], + [-91.97377929687502, 18.715869140625074], + [-91.91357421875, 18.52851562500001], + [-91.53398437499993, 18.45654296875], + [-91.27524414062498, 18.62446289062501], + [-91.34306640624996, 18.900585937499955], + [-91.43666992187502, 18.889794921874966], + [-90.73925781249994, 19.352246093749955], + [-90.69316406249996, 19.729882812499966], + [-90.49169921874997, 19.94677734375003], + [-90.353125, 21.009423828124966], + [-89.81977539062495, 21.274609374999983], + [-88.46669921874997, 21.569384765625017], + [-88.0068359375, 21.604052734375045], + [-87.25087890625, 21.44697265625004], + [-87.18828124999993, 21.546435546875045], + [-87.36850585937498, 21.57373046875], + [-87.034765625, 21.592236328124955], + [-86.824072265625, 21.421679687500017], + [-86.77177734374999, 21.150537109375023], + [-86.92622070312493, 20.786474609375034], + [-87.42138671875, 20.23139648437501], + [-87.44174804687498, 19.861523437499983], + [-87.68769531249998, 19.63710937499999], + [-87.6453125, 19.55390625000001], + [-87.42475585937498, 19.583349609375063], + [-87.65869140625003, 19.352343750000074], + [-87.65576171874997, 19.25786132812499], + [-87.50107421874998, 19.287792968749983], + [-87.76181640624998, 18.446142578125006], + [-87.88198242187497, 18.27387695312501], + [-88.05644531249996, 18.524462890625074], + [-88.03173828125, 18.838916015625017], + [-88.29565429687494, 18.47241210937503], + [-88.52299804687499, 18.445898437500063], + [-88.80634765624998, 17.965527343749983], + [-89.13354492187503, 17.970800781249977], + [-89.16147460937503, 17.81484375], + [-90.98916015624997, 17.81640624999997], + [-90.99296874999993, 17.25244140625], + [-91.19550781249998, 17.254101562499983], + [-91.40961914062501, 17.255859375], + [-90.975830078125, 16.867822265624994], + [-90.710693359375, 16.708105468750034], + [-90.65996093749996, 16.630908203125045], + [-90.634375, 16.565136718749955], + [-90.63408203125002, 16.51074218749997], + [-90.57578124999995, 16.467822265625017], + [-90.47109374999994, 16.439550781250034], + [-90.41699218750003, 16.391015625000023], + [-90.41699218750003, 16.351318359375], + [-90.45014648437493, 16.261376953124994], + [-90.45986328124997, 16.16235351562497], + [-90.44716796874994, 16.07270507812501], + [-90.52197265625, 16.07119140625005], + [-90.70322265624998, 16.07104492187503], + [-90.97958984374998, 16.07080078124997], + [-91.433984375, 16.070458984374994], + [-91.736572265625, 16.070166015625006], + [-91.95722656250001, 15.703222656250034], + [-92.08212890624998, 15.495556640625011], + [-92.18715820312497, 15.320898437499963], + [-92.07480468749998, 15.074218749999972], + [-92.09873046874998, 15.026757812499994], + [-92.14423828125001, 15.001953125], + [-92.158544921875, 14.963574218749997], + [-92.23515625, 14.545410156249986], + [-93.91606445312493, 16.053564453125006], + [-94.374169921875, 16.284765625000034], + [-94.426416015625, 16.22626953125001], + [-94.00126953124996, 16.018945312499966], + [-94.66152343750002, 16.20190429687503], + [-94.58710937499995, 16.315820312499966], + [-94.79082031249999, 16.28715820312499], + [-94.85869140624996, 16.41972656249999], + [-95.02084960937503, 16.277636718750017], + [-94.79941406249995, 16.20966796875001], + [-95.134375, 16.17695312500001], + [-96.21357421874993, 15.693066406250011], + [-96.80795898437495, 15.726416015624977], + [-97.18466796874998, 15.909277343750006], + [-97.75478515624994, 15.966845703125017], + [-98.52031249999993, 16.30483398437505], + [-98.76220703125, 16.534765624999977], + [-99.69067382812499, 16.719628906249994], + [-100.847802734375, 17.20048828124999], + [-101.91870117187494, 17.959765625000045], + [-102.69956054687495, 18.062841796875006], + [-103.44160156249995, 18.32539062500001], + [-103.91245117187496, 18.828466796875006], + [-104.9384765625, 19.309375], + [-105.482080078125, 19.97607421875003], + [-105.66943359374997, 20.385595703124977], + [-105.26015625, 20.579052734374955], + [-105.32705078124994, 20.752978515625045], + [-105.51083984374999, 20.808740234375023], + [-105.23706054687499, 21.119189453125045], + [-105.20869140624998, 21.490820312499977], + [-105.43144531249997, 21.618261718750006], + [-105.64912109375001, 21.988085937500045], + [-105.64550781249999, 22.32690429687497], + [-105.79179687500003, 22.627490234375017], + [-106.93549804687497, 23.88125], + [-107.76494140625002, 24.47192382812497], + [-107.52724609375001, 24.36005859375001], + [-107.51191406249998, 24.489160156250023], + [-107.95117187499994, 24.614892578124966], + [-108.28076171874994, 25.08154296875], + [-108.05146484374995, 25.067041015624994], + [-108.69638671874998, 25.382910156250034], + [-108.78725585937502, 25.53803710937501], + [-109.02880859375003, 25.48046875000003], + [-108.886572265625, 25.733447265625045], + [-109.19648437499998, 25.59252929687503], + [-109.38496093750001, 25.727148437500006], + [-109.42563476562495, 26.032568359375063], + [-109.19970703125003, 26.30522460937499], + [-109.11669921874999, 26.25273437499996], + [-109.27626953125, 26.533886718749955], + [-109.48286132812498, 26.710351562500023], + [-109.75478515624995, 26.702929687500017], + [-109.94399414062495, 27.079345703125057], + [-110.37729492187495, 27.233300781249966], + [-110.59267578124995, 27.544335937500023], + [-110.52988281249995, 27.864208984374983], + [-111.12138671875002, 27.966992187499983], + [-112.16176757812495, 29.018896484375034], + [-113.05766601562496, 30.651025390625023], + [-113.04672851562495, 31.17924804687499], + [-113.62348632812494, 31.34589843750001], + [-113.75942382812501, 31.557763671874994], + [-113.94775390625001, 31.62934570312501], + [-114.14931640624995, 31.507373046875045], + [-114.93359374999994, 31.900732421874977], + [-114.78989257812498, 31.647119140624994], + [-114.88188476562499, 31.156396484375023], + [-114.55048828124997, 30.02226562499999], + [-113.75546875, 29.367480468750017], + [-113.49970703124995, 28.92670898437501], + [-113.20556640624997, 28.798779296874955], + [-113.09365234375001, 28.511767578125017], + [-112.870849609375, 28.42421875000005], + [-112.73403320312501, 27.825976562500017], + [-112.32919921874996, 27.52343750000003], + [-111.86264648437495, 26.678515625000017], + [-111.6994140625, 26.58095703125005], + [-111.79526367187499, 26.8796875], + [-111.56967773437495, 26.707617187500006], + [-111.29160156249996, 25.78979492187497], + [-110.68676757812501, 24.867675781250057], + [-110.65932617187502, 24.34145507812505], + [-110.36743164062497, 24.100488281249994], + [-110.30375976562497, 24.339453125], + [-110.02280273437502, 24.17460937499999], + [-109.6765625, 23.66157226562501], + [-109.42084960937495, 23.480126953124994], + [-109.49570312500002, 23.159814453125023], + [-110.00625, 22.894042968750057], + [-110.3626953125, 23.60493164062501], + [-111.68291015625002, 24.555810546875023], + [-111.80249023437494, 24.542529296875074], + [-112.07255859374999, 24.84003906250001], + [-112.06987304687497, 25.572851562500006], + [-112.37724609374997, 26.21391601562496], + [-113.02075195312499, 26.58325195312497], + [-113.15581054687496, 26.94624023437504], + [-113.27226562499997, 26.79096679687501], + [-113.59853515625001, 26.721289062500034], + [-113.84096679687502, 26.966503906249983], + [-114.44526367187503, 27.218164062499994], + [-114.53989257812495, 27.431103515624955], + [-114.99350585937499, 27.736035156249983], + [-115.03647460937495, 27.84184570312496], + [-114.57001953124995, 27.78393554687497], + [-114.30058593749995, 27.87299804687501], + [-114.30224609375003, 27.775732421875006], + [-114.0693359375, 27.67568359375005], + [-114.15839843750003, 27.919677734375], + [-114.26586914062499, 27.934472656249994], + [-114.04848632812502, 28.42617187499999], + [-114.93730468749999, 29.35161132812496], + [-115.67382812500003, 29.756396484375017], + [-116.06215820312501, 30.80415039062504], + [-116.29628906250001, 30.97050781249999], + [-116.33344726562494, 31.202783203124994], + [-116.66215820312495, 31.56489257812504], + [-116.72207031249998, 31.734570312499955], + [-116.62080078124995, 31.85107421874997], + [-116.84799804687496, 31.997363281250045], + [-117.12827148437495, 32.533349609374994], + [-114.72475585937495, 32.71533203125003], + [-114.83593749999994, 32.50830078125003], + [-111.0419921875, 31.32421875000003], + [-108.21445312499993, 31.329443359375034], + [-108.21181640625002, 31.779345703125017], + [-106.44541015624996, 31.768408203125006], + [-106.14804687499995, 31.450927734375], + [-104.97880859374996, 30.645947265624955], + [-104.50400390624995, 29.677685546874955], + [-104.110595703125, 29.386132812499994], + [-103.16831054687498, 28.998193359374994], + [-102.8919921875, 29.216406250000034], + [-102.61494140624994, 29.75234375], + [-102.26894531249998, 29.871191406250034], + [-101.44038085937503, 29.77685546875], + [-100.75458984375001, 29.182519531249994], + [-100.29604492187495, 28.32768554687499], + [-99.50532226562497, 27.54833984375003], + [-99.45654296874999, 27.05668945312496], + [-99.10776367187498, 26.446923828124994], + [-97.37563476562497, 25.871826171875], + [-97.14624023437494, 25.961474609375045] + ] + ] + ] + }, + "properties": { "name": "Mexico", "childNum": 10 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [22.344042968750017, 42.31396484375], + [22.836816406250023, 41.993603515625], + [23.00361328125001, 41.73984375], + [22.916015625, 41.336279296875], + [22.78388671875001, 41.331982421875], + [22.72480468750001, 41.178515625], + [22.603613281250006, 41.140185546874996], + [22.493554687500023, 41.118505859375], + [22.184472656250023, 41.158642578125], + [21.99335937500001, 41.13095703125], + [21.77949218750001, 40.950439453125], + [21.627539062500006, 40.896337890625], + [21.57578125, 40.868945312499996], + [20.964257812500023, 40.849902343749996], + [20.709277343750017, 40.928369140624994], + [20.48896484375001, 41.272607421874994], + [20.566210937500017, 41.873681640624994], + [20.725, 41.87353515625], + [20.778125, 42.071044921875], + [21.05976562500001, 42.171289062499994], + [21.28662109375, 42.100390625], + [21.389550781250023, 42.21982421875], + [21.560839843750017, 42.24765625], + [21.5625, 42.247509765625], + [21.81464843750001, 42.303125], + [22.344042968750017, 42.31396484375] + ] + ] + }, + "properties": { "name": "Macedonia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [4.227636718750006, 19.142773437499997], + [4.234667968750017, 16.996386718750003], + [4.121289062500011, 16.357714843750003], + [3.842968750000011, 15.701708984375003], + [3.5205078125, 15.483105468749997], + [3.504296875000023, 15.356347656249994], + [3.06015625, 15.427197265624997], + [3.001074218750006, 15.340966796874994], + [1.300195312500023, 15.272265625], + [0.947460937500011, 14.982128906249997], + [0.217480468750011, 14.911474609374991], + [-0.235888671874989, 15.059423828124991], + [-0.760449218749983, 15.047753906249994], + [-1.049560546875, 14.81953125], + [-1.97304687499999, 14.45654296875], + [-2.113232421874983, 14.16845703125], + [-2.586718749999989, 14.227587890625003], + [-2.873925781249994, 13.950732421875003], + [-2.950830078124994, 13.6484375], + [-3.248632812499977, 13.658349609374994], + [-3.3017578125, 13.28076171875], + [-3.527636718749989, 13.182714843749991], + [-3.947314453124989, 13.402197265624991], + [-4.151025390624994, 13.306201171875003], + [-4.328710937499977, 13.119042968749994], + [-4.227099609374989, 12.793701171875], + [-4.480615234374994, 12.672216796874991], + [-4.4287109375, 12.337597656249997], + [-4.699316406249977, 12.076171875], + [-5.288134765624989, 11.827929687499989], + [-5.250244140625, 11.375781249999989], + [-5.490478515625, 11.042382812499994], + [-5.523535156249977, 10.426025390625], + [-5.556591796874983, 10.43994140625], + [-5.694287109374983, 10.43320312499999], + [-5.843847656249977, 10.389550781249994], + [-5.896191406249983, 10.354736328125], + [-5.907568359374977, 10.307226562499991], + [-6.034570312499994, 10.19482421875], + [-6.1171875, 10.201904296875], + [-6.238378906249977, 10.261621093749994], + [-6.241308593749977, 10.279199218749994], + [-6.192626953125, 10.369433593749989], + [-6.190673828125, 10.400292968749994], + [-6.250244140625, 10.717919921874994], + [-6.482617187499983, 10.561230468749997], + [-6.564599609374994, 10.58642578125], + [-6.654150390624977, 10.656445312499997], + [-6.676367187499977, 10.6337890625], + [-6.686132812499977, 10.578027343749994], + [-6.691992187499977, 10.512011718749989], + [-6.669335937499994, 10.3921875], + [-6.693261718749994, 10.349462890624991], + [-6.950341796874994, 10.342333984374989], + [-7.01708984375, 10.143261718749997], + [-7.385058593749989, 10.340136718749989], + [-7.6611328125, 10.427441406249997], + [-7.990625, 10.1625], + [-8.007275390624983, 10.321875], + [-8.266650390624989, 10.485986328124994], + [-8.33740234375, 10.990625], + [-8.666699218749983, 11.009472656249997], + [-8.398535156249977, 11.366552734374991], + [-8.822021484375, 11.673242187499994], + [-8.818310546874983, 11.922509765624994], + [-9.043066406249977, 12.40234375], + [-9.395361328124977, 12.464648437499989], + [-9.358105468749983, 12.255419921874989], + [-9.754003906249977, 12.029931640624994], + [-10.274853515624983, 12.212646484375], + [-10.709228515625, 11.898730468749989], + [-10.933203124999977, 12.205175781249991], + [-11.30517578125, 12.015429687499989], + [-11.502197265625, 12.198632812499994], + [-11.389404296875, 12.404394531249991], + [-11.390380859375, 12.941992187499991], + [-11.634960937499983, 13.369873046875], + [-11.831689453124994, 13.315820312499994], + [-12.05419921875, 13.633056640625], + [-11.960888671874983, 13.875292968750003], + [-12.019189453124994, 14.206494140624997], + [-12.228417968749994, 14.45859375], + [-12.280615234374977, 14.809033203124997], + [-12.104687499999983, 14.745361328125], + [-12.08154296875, 14.766357421875], + [-12.021582031249977, 14.804931640625], + [-11.76015625, 15.425537109375], + [-11.675878906249977, 15.512060546874991], + [-11.502685546875, 15.636816406249991], + [-11.455224609374994, 15.62539062499999], + [-10.9482421875, 15.151123046875], + [-10.696582031249989, 15.42265625], + [-9.94140625, 15.373779296875], + [-9.446923828124994, 15.458203125], + [-9.447705078124983, 15.574853515624994], + [-9.426562499999989, 15.623046875], + [-9.3505859375, 15.677392578124994], + [-9.33544921875, 15.525683593750003], + [-9.293701171875, 15.502832031249994], + [-5.5125, 15.496289062499997], + [-5.359912109374989, 16.282861328124994], + [-5.509619140624977, 16.442041015624994], + [-5.628662109375, 16.568652343750003], + [-5.65625, 16.8095703125], + [-5.684765624999983, 17.058251953124994], + [-5.713183593749989, 17.306884765625], + [-5.74169921875, 17.555566406249994], + [-5.827099609374983, 18.3015625], + [-6.026416015624989, 20.0421875], + [-6.396582031249977, 23.274804687499994], + [-6.482031249999977, 24.020800781250003], + [-6.538964843749994, 24.51816406249999], + [-6.5673828125, 24.766796875], + [-6.594091796874977, 24.99462890625], + [-6.287207031249977, 24.994824218749997], + [-5.959814453124977, 24.99497070312499], + [-5.640771484374994, 24.995166015625003], + [-4.822607421874977, 24.99560546875], + [-1.947900390624994, 23.124804687500003], + [1.1455078125, 21.102246093749997], + [1.165722656250011, 20.817431640625003], + [1.610644531250017, 20.555566406249994], + [1.685449218750023, 20.378369140624997], + [3.130273437500023, 19.85019531249999], + [3.255859375, 19.4109375], + [3.119726562500006, 19.103173828124994], + [3.3564453125, 18.986621093750003], + [4.227636718750006, 19.142773437499997] + ] + ] + }, + "properties": { "name": "Mali", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [14.566210937499989, 35.85273437499998], + [14.436425781250023, 35.82167968750005], + [14.351269531250011, 35.978417968749994], + [14.566210937499989, 35.85273437499998] + ] + ] + }, + "properties": { "name": "Malta", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [98.18261718749997, 9.933447265625006], + [98.11806640625, 9.877880859375054], + [98.2916992187501, 10.051318359375031], + [98.18261718749997, 9.933447265625006] + ] + ], + [ + [ + [98.20976562500002, 10.952734375], + [98.27148437499997, 10.73989257812498], + [98.08046875000005, 10.886621093750037], + [98.20976562500002, 10.952734375] + ] + ], + [ + [ + [98.55380859375012, 11.744873046875], + [98.52841796875012, 11.538671875], + [98.43476562500004, 11.567089843750026], + [98.37646484374997, 11.79150390625], + [98.55380859375012, 11.744873046875] + ] + ], + [ + [ + [98.516015625, 11.905029296875028], + [98.46621093750005, 12.08427734374996], + [98.60957031250004, 11.956640624999977], + [98.516015625, 11.905029296875028] + ] + ], + [ + [ + [98.06611328125004, 12.389794921875023], + [98.00234375000005, 12.279003906250011], + [97.93867187500004, 12.34609375], + [98.06611328125004, 12.389794921875023] + ] + ], + [ + [ + [98.41396484375005, 12.597949218749974], + [98.45947265625003, 12.473730468749991], + [98.3138671875, 12.335986328124989], + [98.31210937500006, 12.678173828124983], + [98.41396484375005, 12.597949218749974] + ] + ], + [ + [ + [98.31542968749997, 13.099072265625026], + [98.30917968750012, 12.934716796875023], + [98.26533203125004, 13.202246093749991], + [98.31542968749997, 13.099072265625026] + ] + ], + [ + [ + [94.80488281250004, 15.8193359375], + [94.73349609375006, 15.823046875000045], + [94.82802734375005, 15.933007812499966], + [94.80488281250004, 15.8193359375] + ] + ], + [ + [ + [94.47675781250004, 15.945947265625023], + [94.41191406250007, 15.848388671875057], + [94.3878906250001, 15.994140624999972], + [94.60126953125004, 16.205517578124983], + [94.47675781250004, 15.945947265625023] + ] + ], + [ + [ + [97.575, 16.253222656250017], + [97.48037109375, 16.305712890625045], + [97.54199218749997, 16.505078124999983], + [97.575, 16.253222656250017] + ] + ], + [ + [ + [93.6908203125, 18.68427734375004], + [93.4875, 18.867529296875063], + [93.74472656250006, 18.865527343750017], + [93.6908203125, 18.68427734375004] + ] + ], + [ + [ + [93.71484374999997, 19.558251953124994], + [93.94570312500005, 19.428613281249966], + [93.90195312500012, 19.33203125], + [93.75585937500003, 19.325683593750057], + [93.64404296874997, 19.49506835937501], + [93.71484374999997, 19.558251953124994] + ] + ], + [ + [ + [93.49179687500012, 19.892578125], + [93.51328125000006, 19.754785156249994], + [93.41289062500002, 19.950341796875023], + [93.49179687500012, 19.892578125] + ] + ], + [ + [ + [93.01015625000005, 19.923925781249977], + [93.02324218750007, 19.82885742187497], + [92.91464843750006, 20.086474609375045], + [93.01015625000005, 19.923925781249977] + ] + ], + [ + [ + [101.1388671875001, 21.567480468749977], + [101.08037109375007, 21.468652343749994], + [100.703125, 21.251367187499966], + [100.613671875, 21.059326171875], + [100.56660156250004, 21.038183593750063], + [100.53613281250003, 20.992382812499955], + [100.52226562500007, 20.92192382812499], + [100.54931640624997, 20.884228515624955], + [100.61767578125003, 20.879248046875006], + [100.62294921875005, 20.859570312499983], + [100.5651367187501, 20.825097656249994], + [100.4074218750001, 20.823242187500057], + [100.32607421875005, 20.795703125000045], + [100.24931640625002, 20.730273437500045], + [100.18388671875002, 20.589111328125057], + [100.12968750000002, 20.372216796874994], + [100.12246093750005, 20.316650390625057], + [100.0036132812501, 20.37958984375001], + [99.9542968750001, 20.415429687500023], + [99.8903320312501, 20.424414062499977], + [99.72011718750005, 20.32543945312497], + [99.45888671875005, 20.363037109375], + [99.48593750000006, 20.14985351562501], + [99.07421875000003, 20.09936523437503], + [98.9166992187501, 19.77290039062504], + [98.37128906250004, 19.68916015625004], + [98.01503906250005, 19.74951171874997], + [97.816796875, 19.459960937500057], + [97.74589843750002, 18.58818359374999], + [97.37392578125, 18.51796875], + [97.63222656250005, 18.290332031250074], + [97.7064453125, 17.79711914062503], + [98.4388671875, 16.975683593750034], + [98.66074218750006, 16.330419921875006], + [98.83544921875003, 16.417578125], + [98.88828125000006, 16.351904296875034], + [98.81796875000012, 16.180810546874994], + [98.59238281250006, 16.05068359375005], + [98.55693359375007, 15.367675781249986], + [98.19101562500012, 15.204101562499972], + [98.20214843749997, 14.97592773437502], + [98.57001953125004, 14.359912109375031], + [99.13681640625006, 13.716699218749994], + [99.12392578125, 13.030761718750043], + [99.40507812500002, 12.547900390625003], + [99.61474609374997, 11.781201171875026], + [99.1901367187501, 11.105273437499989], + [98.7572265625, 10.660937499999974], + [98.70253906250005, 10.19038085937504], + [98.56259765625006, 10.034960937499989], + [98.46494140625006, 10.675830078124989], + [98.67558593750007, 10.986914062500034], + [98.74140625000004, 11.591699218749966], + [98.87597656250003, 11.719726562500028], + [98.63632812500006, 11.738378906250006], + [98.69628906250003, 12.225244140624994], + [98.6002929687501, 12.2453125], + [98.67871093749997, 12.348486328124963], + [98.57597656250002, 13.161914062500031], + [98.20039062500004, 13.980175781250026], + [98.14951171875012, 13.647607421875037], + [98.11064453125007, 13.712890625000014], + [98.10019531250006, 14.161523437500023], + [97.90976562500012, 14.652685546874991], + [98.01875, 14.652587890625057], + [97.81230468750007, 14.858935546874989], + [97.7103515625, 15.875537109375074], + [97.58427734375007, 16.019580078125017], + [97.72597656250005, 16.56855468750004], + [97.37587890625005, 16.52294921874997], + [97.20019531249997, 17.095410156249983], + [96.85146484375005, 17.401025390624994], + [96.90859375000005, 17.03095703125001], + [96.76542968750002, 16.710351562499966], + [96.43115234374997, 16.504931640625045], + [96.18906250000012, 16.768310546875057], + [96.32431640625006, 16.444433593750063], + [95.76328125000006, 16.169042968750006], + [95.38955078125005, 15.722753906250034], + [95.30146484375004, 15.756152343749989], + [95.34677734375012, 16.09760742187501], + [95.17695312500004, 15.825683593750028], + [94.9425781250001, 15.818261718750023], + [94.89316406250006, 16.182812499999955], + [94.66152343750005, 15.904394531250006], + [94.70332031250004, 16.511914062499955], + [94.4416015625001, 16.094384765624966], + [94.22382812500004, 16.016455078125006], + [94.58896484375006, 17.5693359375], + [94.17070312500007, 18.73242187499997], + [94.24570312500006, 18.741162109374983], + [94.07001953125004, 18.893408203125006], + [94.04492187500003, 19.287402343750074], + [93.92919921874997, 18.89965820312503], + [93.70546875000005, 19.026904296875017], + [93.49306640625005, 19.369482421875006], + [93.82490234375004, 19.238476562499955], + [93.99814453125006, 19.440869140624983], + [93.61171875000005, 19.776074218749983], + [93.70703125000003, 19.912158203125074], + [93.25, 20.070117187500017], + [93.12949218750012, 19.858007812500063], + [93.00195312499997, 20.074853515624994], + [93.06679687500005, 20.377636718749955], + [92.82832031250004, 20.177587890625063], + [92.89111328124997, 20.34033203125], + [92.73564453125007, 20.56269531250001], + [92.72285156250004, 20.29560546875004], + [92.32412109375, 20.791845703125063], + [92.17958984375005, 21.293115234375023], + [92.33056640624997, 21.439794921874977], + [92.63164062500002, 21.306201171875045], + [92.5934570312501, 21.46733398437499], + [92.58281250000002, 21.940332031249994], + [92.57490234375004, 21.978076171875045], + [92.68896484374997, 22.130957031250006], + [92.72099609375002, 22.132421875000063], + [92.77138671875, 22.104785156250017], + [92.9645507812501, 22.003759765625034], + [93.07060546875002, 22.20942382812501], + [93.16201171875, 22.360205078125006], + [93.07871093750006, 22.71821289062501], + [93.20390625000002, 23.03701171875005], + [93.34941406250007, 23.08496093750003], + [93.36601562500007, 23.132519531249955], + [93.32626953125006, 24.064208984375057], + [93.45214843750003, 23.987402343750034], + [93.68339843750007, 24.00654296875004], + [94.07480468750006, 23.8720703125], + [94.29306640625012, 24.321875], + [94.37724609375002, 24.473730468750006], + [94.49316406250003, 24.637646484374983], + [94.70371093750012, 25.097851562499955], + [94.55302734375007, 25.215722656249994], + [94.66777343750007, 25.458886718749966], + [94.99199218750002, 25.77045898437504], + [95.01523437500006, 25.912939453125006], + [95.0929687500001, 25.98730468749997], + [95.13242187500006, 26.041259765625057], + [95.12929687500005, 26.070410156250034], + [95.10839843749997, 26.091406250000034], + [95.06894531250006, 26.19111328125001], + [95.0597656250001, 26.473974609375006], + [95.20146484375007, 26.641406250000017], + [96.19082031250005, 27.26127929687499], + [96.79785156249997, 27.29619140624999], + [96.95341796875002, 27.13330078125003], + [97.10205078125003, 27.11542968750004], + [97.10371093750004, 27.16333007812503], + [96.90195312500012, 27.439599609374994], + [96.88359375000002, 27.514843749999955], + [96.96279296875, 27.698291015625017], + [97.04970703125005, 27.760009765625], + [97.34355468750002, 27.982324218749994], + [97.30273437499997, 28.08598632812496], + [97.3224609375001, 28.21796875000004], + [97.35644531249997, 28.254492187500006], + [97.43144531250002, 28.353906250000023], + [97.53789062500002, 28.510205078124983], + [97.59921875000006, 28.51704101562504], + [98.06162109375012, 28.185888671874977], + [98.29882812499997, 27.550097656250045], + [98.4525390625, 27.6572265625], + [98.65117187500007, 27.572460937499983], + [98.7384765625001, 26.785742187500006], + [98.68554687499997, 26.189355468750023], + [98.56406250000006, 26.072412109374994], + [98.65625, 25.86357421874999], + [98.33378906250007, 25.586767578125006], + [98.14287109375007, 25.571093750000017], + [98.01074218749997, 25.292529296875017], + [97.8195312500001, 25.251855468749994], + [97.73789062500006, 24.869873046875057], + [97.58330078125002, 24.77480468750005], + [97.53144531250004, 24.49169921875003], + [97.7082031250001, 24.228759765625], + [97.56455078125012, 23.911035156250023], + [98.2125, 24.110644531250017], + [98.83505859375006, 24.121191406250034], + [98.67675781250003, 23.905078125000045], + [98.8322265625001, 23.624365234374977], + [98.86376953125003, 23.191259765625034], + [99.41806640625006, 23.069238281250023], + [99.50712890625002, 22.959130859374994], + [99.19296875000006, 22.12597656249997], + [99.9176757812501, 22.02802734375001], + [99.94072265625007, 21.75874023437504], + [100.14765625000004, 21.480517578125017], + [100.60458984375012, 21.471777343750006], + [101.07978515625004, 21.75585937499997], + [101.1388671875001, 21.567480468749977] + ] + ] + ] + }, + "properties": { "name": "Myanmar", "childNum": 15 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [19.21875, 43.449951171875], + [19.670996093750006, 43.163964843749994], + [20.344335937500006, 42.827929687499996], + [20.054296875, 42.760058593749996], + [20.06396484375, 42.54726562499999], + [19.78828125000001, 42.476171875], + [19.65449218750001, 42.628564453124994], + [19.280664062500023, 42.17255859375], + [19.342382812500006, 41.869091796875], + [18.436328125000017, 42.559716796874994], + [18.5458984375, 42.6416015625], + [18.46601562500001, 42.777246093749994], + [18.44384765625, 42.96845703125], + [18.46015625000001, 42.997900390625], + [18.48847656250001, 43.012158203125], + [18.623632812500006, 43.027685546875], + [18.621875, 43.124609375], + [18.674218750000023, 43.230810546875], + [18.74921875000001, 43.283544921875], + [18.85107421875, 43.346337890624994], + [18.934667968750006, 43.339453125], + [18.97871093750001, 43.285400390625], + [19.026660156250017, 43.292431640625], + [19.03671875, 43.357324218749994], + [18.940234375000017, 43.496728515624994], + [18.95068359375, 43.526660156249996], + [18.97421875, 43.542333984375], + [19.0283203125, 43.532519531249996], + [19.080078125, 43.517724609374994], + [19.11279296875, 43.52773437499999], + [19.164355468750017, 43.535449218749996], + [19.1943359375, 43.53330078125], + [19.21875, 43.449951171875] + ] + ] + }, + "properties": { "name": "Montenegro", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [111.878125, 43.68017578125], + [111.00722656250002, 43.34140625], + [110.400390625, 42.773681640625], + [109.44316406249999, 42.455957031249994], + [109.33984375, 42.438378906249994], + [108.68730468749999, 42.41611328125], + [108.17119140624999, 42.447314453124996], + [106.77001953125, 42.288720703124994], + [105.86757812500002, 41.993994140625], + [105.31435546875002, 41.770898437499994], + [105.19707031249999, 41.738037109375], + [105.11542968750001, 41.66328125], + [105.05058593749999, 41.61591796875], + [104.98203125000003, 41.595507812499996], + [104.49824218750001, 41.65869140625], + [104.49824218750001, 41.877001953124996], + [104.30517578125, 41.846142578125], + [103.99726562500001, 41.79697265625], + [103.71113281250001, 41.751318359375], + [103.07285156250003, 42.00595703125], + [102.5751953125, 42.092089843749996], + [102.15664062500002, 42.158105468749994], + [101.97294921874999, 42.215869140624996], + [101.65996093749999, 42.500048828124996], + [101.5791015625, 42.52353515625], + [101.49531250000001, 42.53876953125], + [101.09199218750001, 42.551318359374996], + [100.51904296875, 42.616796875], + [100.08632812500002, 42.670751953125], + [99.98378906250002, 42.67734375], + [99.46787109375003, 42.568212890625], + [97.20566406250003, 42.789794921875], + [96.38544921875001, 42.720361328124994], + [95.85957031250001, 43.2759765625], + [95.52558593750001, 43.953955078125], + [95.32558593750002, 44.039355468749996], + [95.35029296875001, 44.278076171875], + [94.71201171875003, 44.350830078125], + [93.51621093750003, 44.944482421874994], + [92.78789062499999, 45.0357421875], + [92.57890624999999, 45.010986328125], + [92.423828125, 45.008935546874994], + [92.17265624999999, 45.03525390625], + [92.02978515625, 45.068505859374994], + [91.584375, 45.076513671875], + [91.05, 45.217431640624994], + [90.87724609374999, 45.19609375], + [90.66181640625001, 45.525244140625], + [91.00175781249999, 46.035791015624994], + [90.99677734375001, 46.10498046875], + [90.94755859374999, 46.177294921874996], + [90.91152343750002, 46.270654296874994], + [90.98574218750002, 46.7490234375], + [90.91054687500002, 46.883251953125], + [90.86992187499999, 46.954492187499994], + [90.79902343750001, 46.98515625], + [90.71552734375001, 47.003857421875], + [90.49619140625003, 47.28515625], + [90.42519531250002, 47.5041015625], + [90.34746093749999, 47.596972656249996], + [90.33066406250003, 47.655175781249994], + [90.31328124999999, 47.67617187499999], + [90.19101562500003, 47.702099609375], + [90.10322265625001, 47.745410156249996], + [90.02792968750003, 47.877685546875], + [89.95869140625001, 47.886328125], + [89.91044921874999, 47.8443359375], + [89.83134765624999, 47.823291015624996], + [89.778125, 47.827001953125], + [89.56093750000002, 48.003955078124996], + [89.47919921875001, 48.029052734375], + [89.04765624999999, 48.0025390625], + [88.97109375000002, 48.049951171874994], + [88.91777343749999, 48.089013671874994], + [88.83828125000002, 48.101708984374994], + [88.68183593750001, 48.170556640624994], + [88.57597656249999, 48.220166015625], + [88.56679687500002, 48.317431640624996], + [88.51708984375, 48.38447265625], + [88.41396484375002, 48.40341796875], + [88.30996093750002, 48.472070312499994], + [87.97968750000001, 48.555126953125], + [88.06005859375, 48.707177734374994], + [87.83183593749999, 48.791650390624994], + [87.7431640625, 48.881640625], + [87.87216796875003, 49.000146484374994], + [87.81630859375002, 49.0802734375], + [87.8251953125, 49.11630859375], + [87.81425781249999, 49.1623046875], + [87.93476562500001, 49.16455078125], + [87.98808593749999, 49.186914062499994], + [88.02851562500001, 49.219775390624996], + [88.11572265625, 49.256298828125], + [88.19257812500001, 49.451708984374996], + [88.63320312500002, 49.486132812499996], + [88.83164062500003, 49.4484375], + [88.86386718750003, 49.527636718749996], + [88.90019531249999, 49.539697265624994], + [88.94541015625003, 49.507666015625], + [88.97060546875002, 49.483740234375], + [89.00839843750003, 49.472802734374994], + [89.10947265625003, 49.501367187499994], + [89.17998046874999, 49.5322265625], + [89.20292968749999, 49.595703125], + [89.24394531249999, 49.62705078125], + [89.39560546875003, 49.6115234375], + [89.475, 49.66054687499999], + [89.57919921875003, 49.69970703125], + [89.65410156249999, 49.71748046875], + [89.64384765624999, 49.90302734375], + [90.0537109375, 50.09375], + [90.65507812499999, 50.22236328125], + [90.71435546875, 50.259423828124994], + [90.7607421875, 50.305957031249996], + [91.02158203125003, 50.415478515625], + [91.23378906250002, 50.452392578125], + [91.30058593749999, 50.46337890625], + [91.3408203125, 50.470068359375], + [91.4150390625, 50.468017578125], + [91.44648437500001, 50.52216796875], + [91.80429687500003, 50.693603515625], + [92.10400390625, 50.6919921875], + [92.1923828125, 50.700585937499994], + [92.35478515624999, 50.864160156249994], + [92.42636718750003, 50.803076171875], + [92.62666015625001, 50.68828125], + [92.68134765625001, 50.683203125], + [92.73867187500002, 50.7109375], + [92.779296875, 50.778662109375], + [92.8564453125, 50.789111328124996], + [92.94130859375002, 50.778222656249994], + [93.103125, 50.60390625], + [94.25107421875003, 50.556396484375], + [94.35468750000001, 50.221826171874994], + [94.61474609375, 50.023730468749996], + [94.67548828125001, 50.028076171875], + [94.71806640624999, 50.043261718749996], + [94.93027343750003, 50.04375], + [95.11142578125003, 49.935449218749994], + [95.52265625000001, 49.91123046875], + [96.06552734375003, 49.99873046875], + [96.31503906250003, 49.901123046875], + [96.98574218750002, 49.8828125], + [97.20859375000003, 49.730810546875], + [97.35976562500002, 49.741455078125], + [97.58935546875, 49.911474609375], + [98.00390625, 50.0142578125], + [98.25029296874999, 50.30244140625], + [98.27949218750001, 50.533251953124996], + [98.14501953125, 50.5685546875], + [98.07890624999999, 50.603808593749996], + [98.02978515625, 50.64462890625], + [97.82529296875003, 50.985253906249994], + [98.103125, 51.483544921874994], + [98.64052734375002, 51.801171875], + [98.89316406250003, 52.11728515625], + [99.92167968749999, 51.755517578125], + [100.03457031250002, 51.737109375], + [100.23037109375002, 51.729833984375], + [100.46894531250001, 51.72607421875], + [100.53623046875003, 51.7134765625], + [101.38125, 51.45263671875], + [101.57089843750003, 51.4671875], + [101.82119140625002, 51.421044921874994], + [102.11152343750001, 51.353466796875], + [102.15566406250002, 51.313769531249996], + [102.16005859375002, 51.26083984375], + [102.14238281249999, 51.216064453125], + [102.15195312500003, 51.10751953125], + [102.19453125000001, 51.050683593749994], + [102.21503906250001, 50.829443359375], + [102.31660156250001, 50.71845703125], + [102.28837890624999, 50.585107421874994], + [103.30439453125001, 50.20029296875], + [103.63291015625003, 50.138574218749994], + [103.72324218750003, 50.153857421874996], + [103.80263671875002, 50.176074218749996], + [104.07871093750003, 50.154248046875], + [105.38359374999999, 50.47373046875], + [106.21787109375003, 50.304589843749994], + [106.36845703124999, 50.317578125], + [106.57441406250001, 50.32880859375], + [106.71113281250001, 50.31259765625], + [106.94130859375002, 50.196679687499994], + [107.04023437500001, 50.086474609374996], + [107.14306640625, 50.033007812499996], + [107.23330078125002, 49.989404296874994], + [107.34707031250002, 49.986669921875], + [107.63095703125003, 49.98310546875], + [107.91660156250003, 49.947802734374996], + [107.96542968750003, 49.653515625], + [108.40693359375001, 49.396386718749994], + [108.5224609375, 49.34150390625], + [108.61367187500002, 49.322802734374996], + [109.23671875000002, 49.334912109375], + [109.45371093750003, 49.296337890625], + [109.52871093750002, 49.269873046875], + [110.19990234375001, 49.17041015625], + [110.42783203125003, 49.219970703125], + [110.70976562499999, 49.14296875], + [110.82792968749999, 49.166162109374994], + [111.20419921875003, 49.304296875], + [111.33662109375001, 49.35585937499999], + [111.42929687500003, 49.342626953125], + [112.07968750000003, 49.42421875], + [112.49492187499999, 49.53232421875], + [112.69736328125003, 49.507275390625], + [112.80644531249999, 49.523583984374994], + [112.91484374999999, 49.569238281249994], + [113.05556640625002, 49.616259765624996], + [113.09208984374999, 49.692529296874994], + [113.16416015625003, 49.797167968749996], + [113.31904296875001, 49.874316406249996], + [113.44550781250001, 49.9416015625], + [113.57421875, 50.00703125], + [114.29707031250001, 50.2744140625], + [114.7431640625, 50.233691406249996], + [115.00332031250002, 50.138574218749994], + [115.27451171875003, 49.948876953124994], + [115.36503906249999, 49.911767578124994], + [115.42919921875, 49.896484375], + [115.58798828125003, 49.886035156249996], + [115.7177734375, 49.880615234375], + [115.79521484374999, 49.905908203124994], + [115.92597656250001, 49.9521484375], + [116.13457031249999, 50.010791015624996], + [116.216796875, 50.00927734375], + [116.35117187500003, 49.978076171874996], + [116.55117187500002, 49.9203125], + [116.68330078125001, 49.823779296874996], + [115.82050781250001, 48.57724609375], + [115.79169921875001, 48.455712890624994], + [115.79658203125001, 48.346337890624994], + [115.78554687500002, 48.2482421875], + [115.63945312499999, 48.18623046875], + [115.52509765625001, 48.130859375], + [115.61640625000001, 47.874804687499996], + [115.89824218749999, 47.686914062499994], + [115.99384765625001, 47.71132812499999], + [116.07480468750003, 47.78955078125], + [116.23115234375001, 47.858203125], + [116.31718749999999, 47.85986328125], + [116.37822265624999, 47.844042968749996], + [116.51347656249999, 47.83955078125], + [116.65195312500003, 47.864501953125], + [116.76054687499999, 47.869775390624994], + [116.90117187499999, 47.853076171874996], + [116.95166015625, 47.836572265624994], + [117.06972656250002, 47.806396484375], + [117.28593749999999, 47.666357421875], + [117.35078125000001, 47.652197265625], + [117.76835937499999, 47.987890625], + [118.49843750000002, 47.983984375], + [118.56777343750002, 47.943261718749994], + [118.69052734375003, 47.822265625], + [118.75996093750001, 47.757617187499996], + [118.88027343750002, 47.72509765625], + [119.017578125, 47.685351562499996], + [119.08193359375002, 47.654150390625], + [119.71113281250001, 47.15], + [119.89785156250002, 46.8578125], + [119.8671875, 46.672167968749996], + [119.74746093750002, 46.627197265625], + [119.70664062500003, 46.606005859374996], + [119.62021484375003, 46.603955078125], + [119.47402343750002, 46.62666015625], + [119.33183593749999, 46.613818359374996], + [119.162109375, 46.638671875], + [118.95712890625003, 46.73486328125], + [118.84394531250001, 46.760205078125], + [118.79033203124999, 46.7470703125], + [118.72294921874999, 46.69189453125], + [118.64873046874999, 46.70166015625], + [118.58046875000002, 46.69189453125], + [118.40439453125003, 46.703173828124996], + [118.30869140625003, 46.717041015625], + [118.15683593750003, 46.678564453125], + [118.0712890625, 46.6666015625], + [117.7412109375, 46.5181640625], + [117.546875, 46.58828125], + [117.43808593750003, 46.586230468749996], + [117.40556640624999, 46.5708984375], + [117.39218750000003, 46.53759765625], + [117.35634765625002, 46.436669921874994], + [117.35693359375, 46.39130859375], + [117.33339843750002, 46.36201171875], + [116.85908203125001, 46.387939453125], + [116.56259765625003, 46.289794921875], + [116.21298828125003, 45.8869140625], + [116.22910156250003, 45.845751953124996], + [116.240625, 45.79599609375], + [116.19765625000002, 45.73935546875], + [115.68105468750002, 45.458251953125], + [115.16259765625, 45.390234375], + [114.91923828124999, 45.378271484375], + [114.73876953125, 45.41962890625], + [114.56015625000003, 45.389990234375], + [114.41914062500001, 45.202587890625], + [114.16738281250002, 45.049853515624996], + [114.08027343750001, 44.971142578125], + [113.87705078125003, 44.89619140625], + [113.65263671874999, 44.763476562499996], + [113.58701171875003, 44.745703125], + [113.04941406250003, 44.810351562499996], + [112.70673828125001, 44.883447265624994], + [112.59677734375003, 44.91767578125], + [112.49931640624999, 45.0109375], + [112.41132812500001, 45.058203125], + [112.11289062500003, 45.062939453125], + [112.03261718750002, 45.081640625], + [111.89804687500003, 45.0640625], + [111.40224609375002, 44.36728515625], + [111.93173828125003, 43.81494140625], + [111.878125, 43.68017578125] + ] + ] + }, + "properties": { "name": "Mongolia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [145.75195312499997, 15.133154296874991], + [145.71318359375007, 15.215283203125026], + [145.821875, 15.265380859375014], + [145.75195312499997, 15.133154296874991] + ] + ] + }, + "properties": { "name": "N. Mariana Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [32.112890625, -26.839453125], + [32.10595703125, -26.52001953125], + [32.04140625000002, -26.28125], + [32.060546875, -26.018359375], + [31.9482421875, -25.957617187500006], + [31.98583984375, -24.46064453125001], + [31.799609375000017, -23.8921875], + [31.54560546875001, -23.48232421875001], + [31.287890625000017, -22.40205078125001], + [31.429492187500017, -22.298828125], + [32.429785156250006, -21.29707031250001], + [32.353613281250006, -21.136523437500003], + [32.49238281250001, -20.659765625], + [32.992773437500006, -19.98486328125], + [32.77763671875002, -19.388769531250006], + [32.84980468750001, -19.10439453125001], + [32.69970703125, -18.94091796875], + [32.99306640625002, -18.35957031250001], + [32.87626953125002, -16.88359375], + [32.94804687500002, -16.71230468750001], + [31.939843750000023, -16.428808593750006], + [31.236230468750023, -16.02363281250001], + [30.437792968750017, -15.995312500000011], + [30.39609375, -15.64306640625], + [30.231835937500023, -14.990332031250006], + [33.201757812500006, -14.013378906250011], + [33.63642578125001, -14.568164062500003], + [34.375, -14.4248046875], + [34.50527343750002, -14.59814453125], + [34.54082031250002, -15.297265625], + [34.24609375, -15.829394531250003], + [34.528125, -16.319140625], + [34.93339843750002, -16.760351562500006], + [35.11210937500002, -16.898535156250006], + [35.06464843750001, -17.07861328125], + [35.124609375, -17.127246093750003], + [35.20136718750001, -17.13105468750001], + [35.272558593750006, -17.118457031250003], + [35.29042968750002, -17.096972656250003], + [35.28115234375002, -16.80781250000001], + [35.22978515625002, -16.639257812500006], + [35.178320312500006, -16.573339843750006], + [35.16718750000001, -16.56025390625001], + [35.242773437500006, -16.375390625], + [35.358496093750006, -16.160546875], + [35.59931640625001, -16.12587890625001], + [35.70888671875002, -16.095800781250006], + [35.75527343750002, -16.05830078125001], + [35.79121093750001, -15.958691406250011], + [35.89277343750001, -14.891796875000011], + [35.86669921875, -14.86376953125], + [35.84716796875, -14.6708984375], + [35.6904296875, -14.465527343750011], + [35.48847656250001, -14.201074218750009], + [35.37578125000002, -14.058691406250006], + [35.24746093750002, -13.896875], + [35.01386718750001, -13.643457031250009], + [34.61152343750001, -13.437890625], + [34.54570312500002, -13.21630859375], + [34.542578125, -13.108691406250003], + [34.35781250000002, -12.164746093750011], + [34.60625, -11.690039062500006], + [34.65957031250002, -11.588671875], + [34.82656250000002, -11.57568359375], + [34.95947265625, -11.578125], + [35.1826171875, -11.574804687500006], + [35.41826171875002, -11.583203125000011], + [35.50439453125, -11.604785156250003], + [35.56435546875002, -11.60234375], + [35.630957031250006, -11.58203125], + [35.78544921875002, -11.452929687500003], + [35.91132812500001, -11.4546875], + [36.08222656250001, -11.537304687500011], + [36.17548828125001, -11.609277343750009], + [36.19130859375002, -11.670703125], + [36.3056640625, -11.706347656250003], + [36.97890625000002, -11.566992187500006], + [37.37285156250002, -11.71044921875], + [37.54169921875001, -11.675097656250003], + [37.72480468750001, -11.580664062500006], + [37.92021484375002, -11.294726562500003], + [38.491796875, -11.413281250000011], + [38.9875, -11.167285156250003], + [39.81708984375001, -10.912402343750003], + [39.98867187500002, -10.82080078125], + [40.46357421875001, -10.46435546875], + [40.61171875000002, -10.661523437500009], + [40.48662109375002, -10.76513671875], + [40.59716796875, -10.830664062500006], + [40.40283203125, -11.33203125], + [40.53154296875002, -12.004589843750011], + [40.48710937500002, -12.4921875], + [40.58085937500002, -12.635546875], + [40.43681640625002, -12.983105468750011], + [40.56875, -12.984667968750003], + [40.595703125, -14.122851562500003], + [40.715625, -14.214453125], + [40.64609375, -14.538671875], + [40.775, -14.421289062500009], + [40.84453125000002, -14.718652343750009], + [40.617773437500006, -15.115527343750003], + [40.650976562500006, -15.260937500000011], + [39.98359375000001, -16.22548828125001], + [39.79091796875002, -16.29453125], + [39.84462890625002, -16.435644531250006], + [39.084375, -16.97285156250001], + [38.14492187500002, -17.242773437500006], + [37.24453125000002, -17.73994140625001], + [36.93935546875002, -17.993457031250003], + [36.40371093750002, -18.76972656250001], + [36.26289062500001, -18.71962890625001], + [36.23564453125002, -18.861328125], + [35.85371093750001, -18.99335937500001], + [34.947851562500006, -19.81269531250001], + [34.6494140625, -19.70136718750001], + [34.75576171875002, -19.82197265625001], + [34.705078125, -20.473046875], + [34.98232421875002, -20.80625], + [35.267675781250006, -21.650976562500006], + [35.31572265625002, -22.396875], + [35.38300781250001, -22.45458984375], + [35.45634765625002, -22.11591796875001], + [35.53007812500002, -22.248144531250006], + [35.57539062500001, -22.96308593750001], + [35.37041015625002, -23.79824218750001], + [35.5419921875, -23.82441406250001], + [35.48964843750002, -24.065527343750006], + [34.99208984375002, -24.65058593750001], + [32.96113281250001, -25.49042968750001], + [32.590429687500006, -26.00410156250001], + [32.84882812500001, -26.26806640625], + [32.95488281250002, -26.08359375], + [32.93359375, -26.25234375], + [32.88916015625, -26.83046875], + [32.88613281250002, -26.84931640625001], + [32.353515625, -26.861621093750003], + [32.19960937500002, -26.83349609375], + [32.112890625, -26.839453125] + ] + ] + }, + "properties": { "name": "Mozambique", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-16.37333984374999, 19.706445312499994], + [-16.437548828124932, 19.609277343749994], + [-16.477001953124983, 19.710351562499994], + [-16.343652343749994, 19.86621093750003], + [-16.37333984374999, 19.706445312499994] + ] + ], + [ + [ + [-5.359912109374989, 16.282861328124994], + [-5.5125, 15.496289062499983], + [-9.293701171875, 15.502832031249994], + [-9.350585937499943, 15.677392578125023], + [-9.38535156249992, 15.667626953124994], + [-9.4265625, 15.623046875000057], + [-9.447705078124926, 15.574853515624994], + [-9.446923828124937, 15.458203124999955], + [-9.941406249999972, 15.373779296874986], + [-10.696582031249989, 15.42265625], + [-10.9482421875, 15.151123046875014], + [-11.455224609374994, 15.62539062499999], + [-11.760156249999937, 15.425537109375057], + [-11.828759765624966, 15.244873046875014], + [-11.872851562499989, 14.995166015625031], + [-12.02158203124992, 14.804931640625], + [-12.081542968749972, 14.766357421875057], + [-12.104687499999955, 14.745361328125043], + [-12.40869140625, 14.889013671874991], + [-12.735253906249994, 15.13125], + [-13.105273437499989, 15.57177734375], + [-13.40966796875, 16.059179687500006], + [-13.756640624999989, 16.172509765624994], + [-13.868457031249932, 16.14814453125001], + [-14.300097656249932, 16.58027343750001], + [-14.990625, 16.676904296874994], + [-15.768212890624994, 16.485107421875], + [-16.23901367187497, 16.53129882812499], + [-16.44101562499992, 16.20454101562504], + [-16.480078124999977, 16.097216796875017], + [-16.50205078124992, 15.917333984375063], + [-16.53525390624995, 15.838378906250057], + [-16.53574218749995, 16.28681640625001], + [-16.463623046875, 16.60151367187501], + [-16.030322265625017, 17.88793945312497], + [-16.213085937499926, 19.003320312500023], + [-16.51445312499996, 19.361962890624994], + [-16.305273437499977, 19.51264648437504], + [-16.44487304687499, 19.47314453124997], + [-16.21044921875003, 20.227929687500023], + [-16.42978515624995, 20.652343750000057], + [-16.622509765624955, 20.634179687499994], + [-16.87607421874992, 21.086132812499955], + [-16.998242187499926, 21.039697265625023], + [-17.048046874999955, 20.80615234375003], + [-17.06396484375, 20.89882812499999], + [-16.96455078125001, 21.329248046875023], + [-15.231201171875, 21.331298828125], + [-14.084667968749926, 21.33271484375001], + [-13.626025390624932, 21.33325195312503], + [-13.396728515624943, 21.333544921875017], + [-13.167431640624926, 21.333789062500074], + [-13.016210937499949, 21.33393554687501], + [-13.025097656249983, 21.46679687499997], + [-13.032226562500028, 21.572070312500017], + [-13.041748046875, 21.71381835937504], + [-13.051220703124983, 21.854785156250074], + [-13.094335937499977, 22.49599609375005], + [-13.153271484374983, 22.820507812499983], + [-13.031494140624943, 23.000244140625], + [-12.895996093749972, 23.08955078125001], + [-12.739599609375006, 23.192724609375063], + [-12.62041015624996, 23.271337890625006], + [-12.559375, 23.290820312500045], + [-12.372900390624977, 23.318017578124994], + [-12.023437499999943, 23.467578125000017], + [-12.016308593749983, 23.97021484375], + [-12.016308593749983, 24.378662109375], + [-12.016308593749983, 24.923242187499994], + [-12.016308593749983, 25.059375], + [-12.016308593749983, 25.331689453124994], + [-12.016308593749983, 25.740136718749994], + [-12.016308593749983, 25.995410156250017], + [-10.376123046874966, 25.995458984375034], + [-9.444531249999983, 25.99550781250005], + [-9.071923828124937, 25.99550781250005], + [-8.885644531249994, 25.99550781250005], + [-8.682226562499949, 25.99550781250005], + [-8.68212890625, 26.109472656250006], + [-8.68212890625, 26.273193359375057], + [-8.682324218749955, 26.49770507812505], + [-8.682617187500028, 26.723144531250057], + [-8.682861328124972, 26.92133789062501], + [-8.683349609375, 27.285937500000045], + [-4.822607421874949, 24.99560546875], + [-5.640771484374994, 24.99516601562499], + [-5.959814453124977, 24.994970703125063], + [-6.287207031249977, 24.99482421875001], + [-6.594091796874977, 24.99462890624997], + [-6.396582031249977, 23.274804687499994], + [-6.02641601562496, 20.04218750000001], + [-5.827099609374955, 18.301562500000045], + [-5.741699218749943, 17.555566406250023], + [-5.713183593750017, 17.306884765625], + [-5.684765624999983, 17.058251953124966], + [-5.628662109375028, 16.568652343750045], + [-5.50961914062492, 16.442041015625023], + [-5.359912109374989, 16.282861328124994] + ] + ] + ] + }, + "properties": { "name": "Mauritania", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-62.1484375, 16.74033203124999], + [-62.221630859375, 16.699511718750003], + [-62.191357421875, 16.804394531249997], + [-62.1484375, 16.74033203124999] + ] + ] + }, + "properties": { "name": "Montserrat", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [57.65126953125002, -20.48486328125], + [57.31767578125002, -20.42763671875001], + [57.416015625, -20.18378906250001], + [57.65654296875002, -19.98994140625001], + [57.7919921875, -20.21259765625001], + [57.65126953125002, -20.48486328125] + ] + ] + }, + "properties": { "name": "Mauritius", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [34.95947265625003, -11.578125], + [34.82656250000005, -11.575683593749972], + [34.65957031250005, -11.58867187499996], + [34.61855468750005, -11.620214843749991], + [34.60625, -11.690039062500006], + [34.3578125, -12.164746093749997], + [34.542578125, -13.108691406250003], + [34.54570312500002, -13.21630859375], + [34.6115234375001, -13.437890625000023], + [35.0138671875001, -13.64345703124998], + [35.247460937499994, -13.896875], + [35.37578125000002, -14.05869140625002], + [35.48847656250004, -14.20107421874998], + [35.69042968749997, -14.465527343750026], + [35.84716796875003, -14.670898437500043], + [35.8927734375001, -14.891796875000011], + [35.7912109375001, -15.958691406250026], + [35.75527343750005, -16.058300781249983], + [35.708886718749994, -16.095800781249977], + [35.5993164062501, -16.12587890624998], + [35.35849609375006, -16.160546875000023], + [35.242773437500006, -16.375390625], + [35.16718750000004, -16.56025390625001], + [35.178320312500006, -16.57333984375002], + [35.22978515625002, -16.639257812500034], + [35.281152343749994, -16.8078125], + [35.29042968750005, -17.096972656250017], + [35.27255859375006, -17.11845703124996], + [35.2013671875001, -17.13105468750004], + [35.124609375, -17.127246093749974], + [35.06464843750004, -17.078613281250014], + [35.11210937500002, -16.898535156250006], + [34.93339843750002, -16.760351562500006], + [34.528125, -16.319140625], + [34.24609374999997, -15.829394531249974], + [34.54082031250002, -15.297265625], + [34.50527343750005, -14.598144531249957], + [34.375, -14.4248046875], + [33.63642578125004, -14.568164062499974], + [33.148046875, -13.94091796875], + [32.98125, -14.009375], + [32.797460937500006, -13.6884765625], + [32.67041015624997, -13.590429687500006], + [32.96757812500002, -13.225], + [32.97519531250006, -12.701367187499983], + [33.51230468750006, -12.347753906249977], + [33.340136718750074, -12.308300781250011], + [33.25234375000005, -12.112597656250031], + [33.3039062500001, -11.69082031249998], + [33.23271484375002, -11.417675781250026], + [33.26835937500002, -11.403906249999977], + [33.379785156249994, -11.15791015625004], + [33.29277343750002, -10.85234375], + [33.661523437499994, -10.553125], + [33.55371093749997, -10.391308593750011], + [33.53759765624997, -10.351562499999986], + [33.52890625, -10.234667968749974], + [33.31152343750003, -10.037988281249966], + [33.3371093750001, -9.954003906249994], + [33.350976562499994, -9.862207031250037], + [33.25, -9.759570312500003], + [33.148046875, -9.603515625], + [32.99599609375005, -9.622851562499946], + [32.91992187500003, -9.407421875000026], + [33.88886718750004, -9.670117187499983], + [33.99560546875003, -9.495410156250003], + [34.32089843750006, -9.731542968749977], + [34.56992187500006, -10.241113281249966], + [34.66708984375006, -10.792480468750028], + [34.60791015624997, -11.08046875], + [34.77382812500005, -11.341699218750009], + [34.890625, -11.3935546875], + [34.93701171874997, -11.463476562500034], + [34.95947265625003, -11.578125] + ] + ] + }, + "properties": { "name": "Malawi", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [111.38925781250006, 2.415332031250031], + [111.31152343749997, 2.437597656250034], + [111.33349609374997, 2.768310546875], + [111.38925781250006, 2.415332031250031] + ] + ], + [ + [ + [104.22158203125, 2.731738281250003], + [104.1291015625001, 2.767236328125037], + [104.18476562500004, 2.871728515625009], + [104.22158203125, 2.731738281250003] + ] + ], + [ + [ + [117.88476562499997, 4.186132812500006], + [117.64902343750012, 4.168994140624974], + [117.70800781249997, 4.262402343749997], + [117.88476562499997, 4.186132812500006] + ] + ], + [ + [ + [100.28896484375005, 5.294726562499989], + [100.19101562500006, 5.28286132812498], + [100.2455078125, 5.467773437499986], + [100.33886718749997, 5.410058593750037], + [100.28896484375005, 5.294726562499989] + ] + ], + [ + [ + [99.848046875, 6.465722656249994], + [99.9186523437501, 6.358593750000011], + [99.74375, 6.263281249999963], + [99.64628906250002, 6.418359375000023], + [99.848046875, 6.465722656249994] + ] + ], + [ + [ + [102.10107421874997, 6.242236328125031], + [102.34013671875002, 6.172021484375023], + [102.534375, 5.862548828125028], + [103.09707031250005, 5.408447265624986], + [103.41582031250007, 4.85029296875004], + [103.43945312499997, 2.93310546875], + [103.8122070312501, 2.58046875], + [104.21855468750002, 1.722851562499997], + [104.25009765625012, 1.388574218750009], + [104.11494140625004, 1.412255859375037], + [103.98144531250003, 1.623632812500034], + [103.99150390625002, 1.454785156249997], + [103.6945312500001, 1.449658203125026], + [103.48027343750007, 1.329492187499966], + [103.35683593750005, 1.546142578125057], + [102.72714843750012, 1.855566406250034], + [101.29550781250012, 2.885205078125011], + [101.29990234375012, 3.253271484375034], + [100.71542968750006, 3.966210937499966], + [100.79550781250012, 4.023388671874983], + [100.61455078125002, 4.3734375], + [100.34326171874997, 5.984179687500031], + [100.11914062499997, 6.441992187500048], + [100.26142578125004, 6.682714843749963], + [100.3454101562501, 6.549902343750006], + [100.75449218750012, 6.460058593749991], + [100.87392578125, 6.24541015624996], + [101.05351562500002, 6.242578125], + [100.98164062500004, 5.771044921875045], + [101.1139648437501, 5.636767578125045], + [101.5560546875, 5.907763671875003], + [101.67841796875004, 5.778808593750028], + [101.87363281250012, 5.825292968749991], + [102.10107421874997, 6.242236328125031] + ] + ], + [ + [ + [117.5744140625001, 4.17060546875004], + [117.10058593750003, 4.337060546875023], + [116.51474609375006, 4.370800781249969], + [115.86074218750005, 4.348046875000037], + [115.67880859375006, 4.193017578124994], + [115.45439453125002, 3.034326171875009], + [115.24697265625005, 3.025927734374989], + [115.117578125, 2.89487304687502], + [115.08076171875004, 2.63422851562504], + [115.1791015625, 2.523193359374972], + [114.78642578125002, 2.250488281250014], + [114.83056640625003, 1.980029296874989], + [114.5125, 1.452001953124963], + [113.90234375000003, 1.434277343749997], + [113.6222656250001, 1.2359375], + [113.00654296875004, 1.433886718750003], + [112.94296875000006, 1.566992187500034], + [112.47617187500006, 1.559082031250028], + [112.1857421875001, 1.4390625], + [112.078515625, 1.143359374999974], + [111.80898437500005, 1.011669921874969], + [111.10136718750002, 1.050537109374986], + [110.50576171875005, 0.861962890625023], + [109.65400390625004, 1.614892578125023], + [109.53896484375, 1.89619140625004], + [109.62890625000003, 2.027539062499983], + [109.86484375000012, 1.764453125000031], + [110.34921875000012, 1.719726562499972], + [111.22324218750012, 1.395849609374991], + [111.0287109375, 1.557812500000026], + [111.26816406250012, 2.13974609375002], + [111.20859375000012, 2.379638671875043], + [111.44384765625003, 2.381542968749983], + [111.5125, 2.743017578124991], + [112.98789062500006, 3.161914062499974], + [113.92392578125006, 4.243212890625003], + [114.0638671875, 4.592675781249966], + [114.65410156250007, 4.037646484375045], + [114.84023437500005, 4.393212890625009], + [114.74667968750006, 4.718066406250017], + [115.02675781250005, 4.899707031249989], + [115.10703125000006, 4.390429687499974], + [115.290625, 4.352587890624989], + [115.1400390625, 4.899755859374991], + [115.37490234375, 4.932763671874966], + [115.55449218750007, 5.093554687500045], + [115.41904296875012, 5.413183593749963], + [115.60390625, 5.603417968749994], + [115.74082031250012, 5.533007812500045], + [115.8771484375001, 5.613525390625014], + [116.74980468750007, 6.977099609374989], + [116.8498046875001, 6.826708984374989], + [116.78808593749997, 6.606103515624994], + [117.12851562500012, 6.968896484375009], + [117.2298828125, 6.939990234374974], + [117.29404296875006, 6.676904296875023], + [117.60966796875002, 6.512646484375054], + [117.69375, 6.35], + [117.64453124999997, 6.001855468749994], + [117.5011718750001, 5.884667968750009], + [118.00380859375, 6.053320312499991], + [118.11582031250006, 5.8625], + [117.93476562500004, 5.7875], + [117.97363281249997, 5.70625], + [118.35312500000012, 5.80605468749998], + [118.59482421875006, 5.592089843750003], + [119.22343750000007, 5.412646484375031], + [119.2663085937501, 5.308105468750057], + [119.21962890625, 5.159814453125037], + [118.9125, 5.02290039062504], + [118.26054687500007, 4.988867187500034], + [118.18535156250002, 4.828515625000051], + [118.5625, 4.502148437499997], + [118.54833984375003, 4.379248046875006], + [118.008203125, 4.250244140625014], + [117.6964843750001, 4.342822265625045], + [117.5744140625001, 4.17060546875004] + ] + ], + [ + [ + [117.14160156250003, 7.168212890625028], + [117.08066406250006, 7.115283203124989], + [117.06425781250007, 7.26069335937504], + [117.2640625, 7.351660156250006], + [117.26679687500004, 7.220800781249991], + [117.14160156250003, 7.168212890625028] + ] + ] + ] + }, + "properties": { "name": "Malaysia", "childNum": 8 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [23.380664062500017, -17.640625], + [24.27490234375, -17.481054687500006], + [24.73291015625, -17.51777343750001], + [25.001757812500017, -17.56855468750001], + [25.2587890625, -17.793554687500006], + [24.909082031250023, -17.821386718750006], + [24.530566406250017, -18.052734375], + [24.243945312500017, -18.0234375], + [23.599707031250006, -18.4599609375], + [23.219335937500006, -17.99970703125001], + [20.97412109375, -18.31884765625], + [20.9794921875, -21.9619140625], + [19.977343750000017, -22.00019531250001], + [19.98046875, -24.77675781250001], + [19.98046875, -28.310351562500003], + [19.98046875, -28.451269531250006], + [19.539843750000017, -28.574609375], + [19.31269531250001, -28.73330078125001], + [19.24580078125001, -28.901660156250003], + [19.16171875, -28.938769531250003], + [18.310839843750017, -28.88623046875], + [17.44794921875001, -28.69814453125001], + [17.34785156250001, -28.50117187500001], + [17.358691406250017, -28.26943359375001], + [17.1884765625, -28.13251953125001], + [17.05625, -28.031054687500003], + [16.93330078125001, -28.069628906250003], + [16.875292968750017, -28.1279296875], + [16.841210937500023, -28.21894531250001], + [16.81015625, -28.26455078125001], + [16.7875, -28.39472656250001], + [16.755761718750023, -28.4521484375], + [16.62617187500001, -28.487890625], + [16.487109375000017, -28.572851562500006], + [16.447558593750017, -28.617578125], + [15.719042968750017, -27.9658203125], + [15.341503906250011, -27.386523437500003], + [15.139062500000023, -26.50800781250001], + [14.9677734375, -26.31806640625001], + [14.837109375000011, -25.033203125], + [14.5015625, -24.201953125], + [14.462792968750023, -22.44912109375001], + [13.450585937500023, -20.91669921875001], + [13.168359375000023, -20.184667968750006], + [12.458203125000011, -18.9267578125], + [11.77587890625, -18.001757812500003], + [11.733496093750006, -17.7509765625], + [11.743066406250023, -17.24921875000001], + [11.902539062500011, -17.2265625], + [12.013964843750017, -17.168554687500006], + [12.21337890625, -17.2099609375], + [12.318457031250006, -17.21337890625], + [12.359277343750023, -17.205859375], + [12.548144531250017, -17.212695312500003], + [13.179492187500017, -16.9716796875], + [13.475976562500023, -17.0400390625], + [14.017480468750023, -17.40888671875001], + [16.1484375, -17.390234375], + [18.396386718750023, -17.3994140625], + [18.95527343750001, -17.803515625], + [20.1943359375, -17.863671875], + [20.745507812500023, -18.01972656250001], + [22.32421875, -17.8375], + [23.380664062500017, -17.640625] + ] + ] + }, + "properties": { "name": "Namibia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [167.54443359375003, -22.62324218750001], + [167.44375, -22.63916015624997], + [167.44345703125006, -22.541406250000037], + [167.54443359375003, -22.62324218750001] + ] + ], + [ + [ + [168.01093750000004, -21.429980468750017], + [168.1390625, -21.44521484375001], + [168.12070312500012, -21.615820312500034], + [167.96679687500003, -21.641601562499957], + [167.81542968749997, -21.392675781249963], + [167.9884765625001, -21.337890624999986], + [168.01093750000004, -21.429980468750017] + ] + ], + [ + [ + [167.40087890625003, -21.16064453125003], + [167.07265625, -20.99726562499997], + [167.03271484374997, -20.922558593750026], + [167.18945312500003, -20.803515625000017], + [167.05576171875012, -20.720214843750014], + [167.29794921875006, -20.732519531250034], + [167.40087890625003, -21.16064453125003] + ] + ], + [ + [ + [164.20234375000004, -20.246093749999957], + [164.4359375, -20.282226562499957], + [165.191796875, -20.768847656249974], + [165.66279296875004, -21.267187499999977], + [166.94238281250003, -22.09013671875003], + [166.97031250000012, -22.32285156250002], + [166.77412109375004, -22.37617187500004], + [166.4679687500001, -22.256054687499997], + [164.92744140625004, -21.289843749999974], + [164.16972656250007, -20.48017578125004], + [164.05966796875012, -20.141503906249966], + [164.20234375000004, -20.246093749999957] + ] + ] + ] + }, + "properties": { "name": "New Caledonia", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [14.97900390625, 22.99619140624999], + [15.181835937500011, 21.523388671874997], + [15.607324218750023, 20.954394531250003], + [15.587109375000011, 20.733300781249994], + [15.963183593750017, 20.34619140625], + [15.735058593750011, 19.904052734375], + [15.474316406250011, 16.908398437499997], + [14.367968750000017, 15.750146484374994], + [13.4482421875, 14.380664062500003], + [13.505761718750023, 14.134423828124994], + [13.606347656250023, 13.70458984375], + [13.426953125000011, 13.701757812499991], + [13.323828125, 13.670849609374997], + [12.871679687500006, 13.449023437500003], + [12.65478515625, 13.3265625], + [12.463183593750017, 13.09375], + [10.958886718750023, 13.371533203124997], + [10.475878906250017, 13.330224609374994], + [10.229589843750006, 13.281005859375], + [10.184667968750006, 13.270117187499991], + [9.615917968750011, 12.810644531249991], + [9.201562500000023, 12.821484375], + [8.750585937500006, 12.908154296874997], + [8.4560546875, 13.059667968749991], + [8.095019531250017, 13.291162109374994], + [7.955761718750011, 13.32275390625], + [7.788671875, 13.337890625], + [7.056738281250006, 13.000195312499997], + [6.804296875, 13.107666015625], + [6.2998046875, 13.658789062499991], + [6.184277343750011, 13.66367187499999], + [5.838183593750017, 13.765380859375], + [5.491992187500017, 13.872851562500003], + [5.415820312500017, 13.859179687500003], + [5.361621093750017, 13.836865234374997], + [5.241894531250011, 13.757226562499994], + [4.664843750000017, 13.733203125], + [4.147558593750006, 13.457714843749997], + [3.947851562500006, 12.775048828124994], + [3.646679687500011, 12.529980468749997], + [3.595410156250011, 11.6962890625], + [2.805273437500006, 12.383837890624989], + [2.366015625000017, 12.221923828125], + [2.38916015625, 11.897070312499991], + [2.072949218750011, 12.309375], + [2.226269531250011, 12.466064453125], + [2.104589843750006, 12.701269531249991], + [1.56494140625, 12.635400390624994], + [0.9873046875, 13.041894531249994], + [0.988476562500011, 13.36484375], + [1.201171875, 13.357519531249991], + [0.6181640625, 13.703417968750003], + [0.42919921875, 13.972119140624997], + [0.382519531250011, 14.245800781249997], + [0.163867187500017, 14.497216796874994], + [0.217480468750011, 14.911474609374991], + [0.947460937500011, 14.982128906249997], + [1.300195312500023, 15.272265625], + [3.001074218750006, 15.340966796874994], + [3.06015625, 15.427197265624997], + [3.504296875000023, 15.356347656249994], + [3.5205078125, 15.483105468749997], + [3.842968750000011, 15.701708984375003], + [4.121289062500011, 16.357714843750003], + [4.234667968750017, 16.996386718750003], + [4.227636718750006, 19.142773437499997], + [5.836621093750011, 19.479150390624994], + [7.481738281250017, 20.873095703125003], + [11.967871093750006, 23.517871093750003], + [13.48125, 23.18017578125], + [14.215527343750011, 22.619677734375003], + [14.97900390625, 22.99619140624999] + ] + ] + }, + "properties": { "name": "Niger", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [7.30078125, 4.418164062500026], + [7.140429687500017, 4.395117187500034], + [7.227343750000045, 4.527343749999972], + [7.30078125, 4.418164062500026] + ] + ], + [ + [ + [6.804296875, 13.107666015625], + [7.056738281250006, 13.00019531250004], + [7.788671875, 13.337890625], + [7.955761718750011, 13.322753906250028], + [8.095019531250045, 13.29116210937498], + [8.750585937500034, 12.908154296875026], + [9.20156250000008, 12.82148437500004], + [9.615917968750011, 12.810644531249963], + [10.184667968750063, 13.270117187499963], + [10.229589843749977, 13.281005859375043], + [10.475878906250074, 13.330224609375037], + [10.958886718750051, 13.371533203125011], + [12.463183593750017, 13.09375], + [12.654785156250057, 13.3265625], + [13.426953125000068, 13.701757812499963], + [13.606347656250023, 13.704589843750014], + [13.932324218750011, 13.258496093749997], + [14.06396484375, 13.078515625], + [14.160058593750023, 12.612792968749986], + [14.184863281250017, 12.447216796874997], + [14.272851562500023, 12.356494140624989], + [14.518945312500051, 12.298242187500023], + [14.619726562500063, 12.150976562500048], + [14.559765625000011, 11.492285156249963], + [14.20234375000004, 11.268164062499963], + [14.143261718750068, 11.248535156250043], + [14.056738281250034, 11.245019531250037], + [13.981445312500057, 11.21186523437504], + [13.892089843750057, 11.140087890624983], + [13.699902343749983, 10.873144531250048], + [13.53535156250004, 10.605078124999963], + [13.414550781250028, 10.171435546874989], + [13.269921875000051, 10.036181640624974], + [13.198730468750028, 9.563769531250003], + [12.929492187500074, 9.426269531249972], + [12.87568359375004, 9.303515625000017], + [12.80654296875008, 8.886621093749994], + [12.7822265625, 8.817871093750014], + [12.651562500000011, 8.667773437499989], + [12.40351562500004, 8.59555664062502], + [12.311328125000074, 8.419726562499989], + [12.2333984375, 8.282324218749977], + [12.016015625000051, 7.589746093750009], + [11.809179687500006, 7.345068359374991], + [11.767382812500017, 7.272265624999989], + [11.861425781249977, 7.11640625000004], + [11.657519531250017, 6.951562500000023], + [11.580078125000057, 6.88886718750004], + [11.551660156250023, 6.697265625], + [11.153320312500057, 6.437939453125011], + [11.1064453125, 6.457714843750054], + [11.032519531250045, 6.697900390625037], + [10.954199218750006, 6.7765625], + [10.60625, 7.063085937500006], + [10.413183593750006, 6.877734375], + [10.293066406250034, 6.876757812499974], + [10.205468750000051, 6.891601562499986], + [10.185546874999972, 6.91279296875004], + [10.167773437500017, 6.959179687499983], + [10.143554687500057, 6.99643554687502], + [10.038867187500045, 6.921386718750014], + [9.874218750000068, 6.803271484375017], + [9.82070312500008, 6.783935546874986], + [9.779882812500034, 6.760156250000023], + [9.725585937499972, 6.65], + [9.659960937500017, 6.531982421874986], + [9.490234375, 6.418652343749997], + [8.997167968750006, 5.917724609375], + [8.715625, 5.046875], + [8.514843750000068, 4.724707031250034], + [8.23378906250008, 4.907470703124972], + [8.293066406250006, 4.557617187500014], + [7.644238281250068, 4.525341796875011], + [7.530761718750028, 4.655175781249994], + [7.284375, 4.547656250000031], + [7.076562500000051, 4.716162109374991], + [7.15468750000008, 4.514404296875], + [6.92324218750008, 4.390673828125017], + [6.767675781250006, 4.724707031250034], + [6.860351562500057, 4.373339843750045], + [6.633007812500011, 4.340234375000051], + [6.579980468750051, 4.475976562499994], + [6.554589843750023, 4.34140625000002], + [6.263671875, 4.309423828124991], + [6.270996093749972, 4.432128906250028], + [6.173339843749972, 4.277392578125031], + [5.970703125, 4.338574218749983], + [5.587792968750051, 4.647216796874972], + [5.448144531250023, 4.945849609374974], + [5.383300781250057, 5.129003906249977], + [5.475976562500023, 5.153857421874989], + [5.370019531250023, 5.195019531250026], + [5.367968750000045, 5.337744140624963], + [5.549707031250023, 5.474218749999963], + [5.385839843750034, 5.401757812500037], + [5.199218750000028, 5.533544921874977], + [5.456640624999977, 5.61171875], + [5.327343750000011, 5.707519531249986], + [5.112402343750034, 5.64155273437504], + [4.861035156250068, 6.026318359374997], + [4.431347656250011, 6.348583984375026], + [3.450781249999977, 6.427050781250017], + [3.71699218750004, 6.597949218750017], + [3.430175781250057, 6.525], + [3.335546875000063, 6.396923828125011], + [2.706445312500051, 6.369238281249963], + [2.735644531250045, 6.595703125], + [2.753710937499989, 6.661767578124966], + [2.774609374999983, 6.711718750000017], + [2.752929687500028, 6.771630859374966], + [2.731738281250045, 6.852832031249989], + [2.721386718750068, 6.980273437500017], + [2.75673828125008, 7.067919921875017], + [2.750488281250057, 7.39506835937496], + [2.765820312500068, 7.422509765625051], + [2.783984375000045, 7.443408203125045], + [2.78515625, 7.476855468750017], + [2.703125, 8.371826171875], + [2.774804687500023, 9.048535156250026], + [3.044921875, 9.08383789062502], + [3.325195312499972, 9.778466796875051], + [3.60205078125, 10.004541015625009], + [3.646582031250006, 10.408984374999989], + [3.771777343750017, 10.417626953124966], + [3.83447265625, 10.607421875], + [3.7568359375, 10.76875], + [3.71640625, 11.07958984375], + [3.695312499999972, 11.12031250000004], + [3.63886718750004, 11.176855468750006], + [3.487792968749972, 11.395410156250037], + [3.490527343750017, 11.499218750000054], + [3.55390625000004, 11.631884765624989], + [3.595410156250068, 11.696289062500057], + [3.664746093750068, 11.762451171875028], + [3.646679687500011, 12.529980468749983], + [3.947851562500006, 12.775048828124994], + [4.147558593750006, 13.457714843749983], + [4.664843750000045, 13.733203124999974], + [5.241894531250011, 13.757226562499994], + [5.361621093750074, 13.836865234375054], + [5.415820312500017, 13.859179687499974], + [5.491992187500074, 13.872851562500003], + [6.2998046875, 13.658789062500006], + [6.804296875, 13.107666015625] + ] + ] + ] + }, + "properties": { "name": "Nigeria", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-83.6419921875, 10.917236328125], + [-83.71293945312499, 10.785888671875], + [-83.91928710937499, 10.7353515625], + [-84.6341796875, 11.045605468749997], + [-84.9091796875, 10.9453125], + [-85.5841796875, 11.189453125], + [-85.7443359375, 11.06210937499999], + [-87.670166015625, 12.965673828124991], + [-87.58505859374999, 13.043310546874991], + [-87.42436523437499, 12.921142578125], + [-87.33725585937499, 12.979248046875], + [-87.05917968749999, 12.991455078125], + [-87.00932617187499, 13.0078125], + [-86.918212890625, 13.223583984374997], + [-86.87353515625, 13.266503906249994], + [-86.792138671875, 13.27978515625], + [-86.72929687499999, 13.284375], + [-86.710693359375, 13.313378906249994], + [-86.76352539062499, 13.63525390625], + [-86.77060546874999, 13.69873046875], + [-86.758984375, 13.746142578125003], + [-86.733642578125, 13.763476562500003], + [-86.61025390625, 13.774853515624997], + [-86.376953125, 13.755664062500003], + [-86.33173828125, 13.770068359375003], + [-86.238232421875, 13.899462890625003], + [-86.15122070312499, 13.994580078124997], + [-86.0892578125, 14.037207031249991], + [-86.04038085937499, 14.050146484374991], + [-85.9837890625, 13.965673828124991], + [-85.78671875, 13.844433593749997], + [-85.75341796875, 13.85205078125], + [-85.73393554687499, 13.858691406250003], + [-85.727734375, 13.876074218749991], + [-85.731201171875, 13.931835937499997], + [-85.68193359374999, 13.982568359374994], + [-85.20834960937499, 14.311816406250003], + [-85.059375, 14.582958984374997], + [-84.86044921874999, 14.809765625], + [-84.645947265625, 14.661083984374997], + [-84.53764648437499, 14.633398437499991], + [-83.635498046875, 14.876416015624997], + [-83.5365234375, 14.977001953124997], + [-83.4150390625, 15.008056640625], + [-83.15751953124999, 14.993066406249994], + [-83.18535156249999, 14.956396484374991], + [-83.21591796874999, 14.932373046875], + [-83.27988281249999, 14.812792968750003], + [-83.344384765625, 14.902099609375], + [-83.413720703125, 14.825341796874994], + [-83.29921875, 14.7490234375], + [-83.187744140625, 14.340087890625], + [-83.4123046875, 13.99648437499999], + [-83.567333984375, 13.3203125], + [-83.5109375, 12.411816406249997], + [-83.627197265625, 12.459326171874991], + [-83.59335937499999, 12.713085937499997], + [-83.75424804687499, 12.501953125], + [-83.680419921875, 12.024316406249994], + [-83.7671875, 12.059277343749997], + [-83.82890624999999, 11.861035156249997], + [-83.70458984375, 11.824560546874991], + [-83.6517578125, 11.642041015624997], + [-83.86787109375, 11.300048828125], + [-83.6419921875, 10.917236328125] + ] + ] + }, + "properties": { "name": "Nicaragua", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-169.80341796875, -19.0830078125], + [-169.94833984375, -19.072851562500006], + [-169.834033203125, -18.96601562500001], + [-169.80341796875, -19.0830078125] + ] + ] + }, + "properties": { "name": "Niue", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-68.205810546875, 12.144580078124989], + [-68.25434570312495, 12.032080078124977], + [-68.36923828125, 12.301953124999983], + [-68.205810546875, 12.144580078124989] + ] + ], + [ + [ + [4.226171875000034, 51.38647460937503], + [3.902050781250011, 51.20766601562502], + [3.43251953125008, 51.24575195312505], + [3.35009765625, 51.37768554687503], + [4.226171875000034, 51.38647460937503] + ] + ], + [ + [ + [3.94912109375008, 51.73945312500001], + [4.07509765625008, 51.648779296875006], + [3.699023437500017, 51.70991210937501], + [3.94912109375008, 51.73945312500001] + ] + ], + [ + [ + [4.886132812500023, 53.07070312500005], + [4.70917968750004, 53.036035156249994], + [4.886425781249983, 53.18330078124998], + [4.886132812500023, 53.07070312500005] + ] + ], + [ + [ + [4.226171875000034, 51.38647460937503], + [3.448925781250068, 51.54077148437503], + [3.743945312500017, 51.596044921875006], + [4.27412109375004, 51.47163085937498], + [4.004785156250051, 51.595849609374966], + [4.182617187500057, 51.61030273437498], + [3.946875, 51.810546875], + [4.482812500000023, 52.30917968749998], + [4.76875, 52.941308593749966], + [5.061230468750068, 52.96064453125001], + [5.532031250000074, 53.268701171874966], + [6.062207031250068, 53.407080078125006], + [6.816210937500045, 53.44116210937503], + [7.197265625000028, 53.28227539062499], + [7.033007812500045, 52.65136718749997], + [6.710742187500045, 52.61787109374998], + [6.748828125000074, 52.464013671874994], + [7.035156250000057, 52.38022460937498], + [6.724511718749994, 52.080224609374966], + [6.800390625, 51.96738281249998], + [5.948730468750057, 51.80268554687501], + [6.198828125000034, 51.45], + [6.129980468750034, 51.14741210937501], + [5.857519531250034, 51.030126953125006], + [6.048437500000034, 50.90488281250006], + [5.993945312500017, 50.75043945312504], + [5.693554687500011, 50.774755859375006], + [5.796484375000034, 51.153076171875], + [5.214160156250045, 51.278955078124966], + [5.03095703125004, 51.46909179687498], + [4.226171875000034, 51.38647460937503] + ] + ], + [ + [ + [5.325781250000063, 53.38574218750003], + [5.190234375000074, 53.39179687500001], + [5.582617187500063, 53.438085937500034], + [5.325781250000063, 53.38574218750003] + ] + ] + ] + }, + "properties": { "name": "Netherlands", "childNum": 6, "cp": [5.0752777, 52.358465] } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [5.085839843750023, 60.30756835937501], + [5.089062500000068, 60.188769531250045], + [4.95722656250004, 60.44726562500006], + [5.085839843750023, 60.30756835937501] + ] + ], + [ + [ + [4.958691406250068, 61.084570312500034], + [4.79902343750004, 61.08271484375001], + [4.861621093749989, 61.19384765625], + [4.958691406250068, 61.084570312500034] + ] + ], + [ + [ + [8.10273437500004, 63.33759765625004], + [7.804003906250017, 63.413916015625034], + [8.073535156250045, 63.47080078124998], + [8.10273437500004, 63.33759765625004] + ] + ], + [ + [ + [8.470800781250063, 63.66713867187502], + [8.287109375000028, 63.68715820312502], + [8.764648437500057, 63.804638671874955], + [8.78652343750008, 63.703466796875034], + [8.470800781250063, 63.66713867187502] + ] + ], + [ + [ + [11.2314453125, 64.865869140625], + [10.739843750000034, 64.87031250000001], + [11.02099609375, 64.97871093749995], + [11.2314453125, 64.865869140625] + ] + ], + [ + [ + [12.971777343750063, 67.87412109375], + [12.824023437500074, 67.82124023437498], + [13.068066406250068, 68.07133789062505], + [12.971777343750063, 67.87412109375] + ] + ], + [ + [ + [13.872851562500045, 68.26533203125004], + [14.096777343750034, 68.218603515625], + [13.229394531250051, 67.995361328125], + [13.300195312499994, 68.16044921875007], + [13.872851562500045, 68.26533203125004] + ] + ], + [ + [ + [15.207128906250006, 68.943115234375], + [15.222070312500023, 68.61630859375003], + [14.404687500000051, 68.663232421875], + [15.037792968750068, 69.00053710937507], + [15.207128906250006, 68.943115234375] + ] + ], + [ + [ + [15.760351562500006, 68.56123046875001], + [16.328906250000017, 68.87631835937498], + [16.519238281250068, 68.63300781249998], + [15.975292968750011, 68.402490234375], + [14.257519531249983, 68.19077148437503], + [15.412597656250028, 68.61582031250003], + [15.483007812500006, 69.04345703125003], + [16.04804687500001, 69.30205078125002], + [15.760351562500006, 68.56123046875001] + ] + ], + [ + [ + [17.503027343750034, 69.59624023437502], + [18.004101562500068, 69.50498046874998], + [17.95068359375003, 69.19814453125], + [17.487890625000063, 69.19682617187499], + [17.08251953124997, 69.013671875], + [16.81044921875008, 69.07070312499997], + [17.001757812500045, 69.36191406250006], + [17.36083984375003, 69.38149414062497], + [17.503027343750034, 69.59624023437502] + ] + ], + [ + [ + [29.956152343750006, 69.79677734375002], + [29.766210937500006, 69.76752929687501], + [29.835839843749994, 69.90556640625005], + [29.956152343750006, 69.79677734375002] + ] + ], + [ + [ + [20.779199218750023, 70.08974609375002], + [20.46425781250005, 70.0765625], + [20.492773437500006, 70.20332031249995], + [20.78603515625008, 70.21953124999999], + [20.779199218750023, 70.08974609375002] + ] + ], + [ + [ + [19.25507812500001, 70.06640625000006], + [19.607812500000023, 70.019140625], + [19.334765625000074, 69.82026367187501], + [18.784765625000034, 69.57900390624997], + [18.12988281250003, 69.557861328125], + [18.34931640625004, 69.76787109374999], + [18.67402343750004, 69.78164062500002], + [19.13271484375005, 70.24414062500003], + [19.25507812500001, 70.06640625000006] + ] + ], + [ + [ + [19.76748046875005, 70.21669921875002], + [20.005957031250034, 70.07622070312502], + [19.599023437499994, 70.26616210937507], + [19.76748046875005, 70.21669921875002] + ] + ], + [ + [ + [23.615332031250034, 70.54931640625003], + [23.15917968750003, 70.28261718750005], + [22.941015625000063, 70.444580078125], + [23.546679687500017, 70.61708984374997], + [23.615332031250034, 70.54931640625003] + ] + ], + [ + [ + [24.01757812500003, 70.56738281249997], + [23.716601562500074, 70.561865234375], + [23.778417968750063, 70.74736328125005], + [24.01757812500003, 70.56738281249997] + ] + ], + [ + [ + [23.440527343750063, 70.81577148437503], + [22.8291015625, 70.54155273437505], + [22.358691406250017, 70.514794921875], + [21.99453125000008, 70.65712890624997], + [23.440527343750063, 70.81577148437503] + ] + ], + [ + [ + [30.869726562500006, 69.78344726562506], + [30.860742187499994, 69.53842773437503], + [30.18017578124997, 69.63583984375], + [30.08730468750005, 69.43286132812503], + [29.38828125, 69.29814453125005], + [28.96582031250003, 69.02197265625], + [28.846289062500006, 69.17690429687502], + [29.33339843750005, 69.47299804687503], + [29.14160156250003, 69.67143554687505], + [27.747851562500045, 70.06484375], + [27.127539062500063, 69.90649414062497], + [26.525390625000057, 69.91503906250003], + [26.07246093750004, 69.69155273437497], + [25.748339843750017, 68.99013671875], + [24.94140625000003, 68.59326171875006], + [23.85400390625, 68.80590820312503], + [23.324023437500017, 68.64897460937502], + [22.410937500000074, 68.719873046875], + [21.59375, 69.273583984375], + [21.06611328125001, 69.21411132812497], + [21.065722656250017, 69.04174804687503], + [20.622167968750006, 69.036865234375], + [20.116699218750057, 69.02089843750005], + [20.348046875000023, 68.84873046875003], + [19.969824218750063, 68.35639648437501], + [18.303027343750045, 68.55541992187497], + [17.91669921875001, 67.96489257812502], + [17.324609375000023, 68.10380859374999], + [16.783593750000023, 67.89501953125], + [16.12744140625, 67.42583007812507], + [16.40351562500004, 67.05498046875002], + [15.422949218750006, 66.48984374999998], + [15.483789062500051, 66.30595703124999], + [14.543261718750045, 66.12934570312498], + [14.47968750000004, 65.30146484374998], + [13.650292968750023, 64.58154296874997], + [14.077636718750028, 64.464013671875], + [14.141210937500006, 64.17353515624998], + [13.960546875000063, 64.01401367187498], + [13.203515625000023, 64.07509765625], + [12.792773437500017, 64], + [12.175195312500051, 63.595947265625], + [11.999902343750051, 63.29169921875001], + [12.303515625000074, 62.28559570312501], + [12.155371093750006, 61.720751953125045], + [12.88076171875008, 61.35229492187506], + [12.706054687500028, 61.059863281250074], + [12.29414062500004, 61.00268554687506], + [12.588671874999989, 60.450732421875045], + [12.486132812500074, 60.10678710937506], + [11.680761718750034, 59.59228515625003], + [11.798144531250074, 59.28989257812498], + [11.64277343750004, 58.92607421875002], + [11.470703125000057, 58.909521484375034], + [11.388281250000063, 59.036523437499966], + [10.834472656250028, 59.18393554687498], + [10.595312500000063, 59.764550781249966], + [10.179394531250068, 59.00927734375003], + [9.842578125000017, 58.95849609374997], + [9.557226562500063, 59.11269531250002], + [9.65693359375004, 58.97119140624997], + [8.166113281250063, 58.145312500000045], + [7.0048828125, 58.024218750000074], + [6.877050781250006, 58.15073242187498], + [6.590527343750068, 58.09731445312502], + [6.659863281250068, 58.26274414062499], + [5.706835937500074, 58.52363281250001], + [5.55556640625008, 58.975195312500006], + [6.099023437500023, 58.87026367187502], + [6.363281250000028, 59.00092773437501], + [6.099414062500017, 58.951953125000074], + [5.88916015625, 59.097949218750045], + [5.951855468750068, 59.299072265625], + [6.415332031250074, 59.547119140625], + [5.17324218750008, 59.16254882812498], + [5.2421875, 59.564306640625034], + [5.472460937500017, 59.713085937499955], + [5.77216796875004, 59.66093749999999], + [6.216601562499989, 59.818359375], + [5.73046875, 59.863085937500045], + [6.348730468750006, 60.35297851562504], + [6.57363281250008, 60.36059570312497], + [6.526855468750057, 60.152929687500034], + [6.995703125, 60.511962890625], + [6.1533203125, 60.34624023437499], + [5.145800781250074, 59.63881835937502], + [5.205664062500006, 60.087939453125045], + [5.688574218749977, 60.12319335937502], + [5.285839843750011, 60.20571289062505], + [5.13710937500008, 60.445605468750074], + [5.648339843750051, 60.68798828124997], + [5.244042968750023, 60.569580078125], + [5.115820312500006, 60.63598632812503], + [5.008593750000017, 61.038183593750006], + [6.777832031250028, 61.142431640625006], + [7.038671875000063, 60.952929687500045], + [7.040136718750006, 61.091162109375034], + [7.604492187500057, 61.210546875000034], + [7.34658203125008, 61.30058593749999], + [7.442578125000011, 61.43461914062502], + [7.173535156250011, 61.16596679687501], + [6.599902343750017, 61.28964843749998], + [6.383496093750068, 61.133886718750034], + [5.451269531250034, 61.10234375000002], + [5.106738281250017, 61.187548828125045], + [5.002734375000074, 61.43359375], + [5.338671875000017, 61.485498046874994], + [4.927832031249977, 61.71069335937506], + [4.93007812499999, 61.878320312499994], + [6.01582031250004, 61.7875], + [6.730761718750045, 61.86977539062505], + [5.266894531250045, 61.935595703125045], + [5.143164062500063, 62.159912109375], + [5.908300781249977, 62.41601562500003], + [6.083496093750057, 62.349609375], + [6.580078125000057, 62.407275390625045], + [6.692382812500028, 62.46806640624999], + [6.136132812500051, 62.40747070312497], + [6.352929687500051, 62.61113281249999], + [7.653125, 62.56401367187499], + [7.538378906250074, 62.67207031249998], + [8.045507812500006, 62.77124023437503], + [6.734960937500006, 62.72070312500003], + [6.940429687500028, 62.930468750000045], + [7.571875, 63.09951171875002], + [8.100585937500028, 63.090966796874966], + [8.623144531250006, 62.84624023437502], + [8.158007812500017, 63.16152343750005], + [8.635546875000045, 63.34233398437502], + [8.360742187500023, 63.498876953125034], + [8.576171875000028, 63.60117187499998], + [9.135839843750006, 63.593652343749966], + [9.156054687500045, 63.459326171875034], + [9.696875, 63.624560546875045], + [10.020996093750028, 63.39082031250004], + [10.76015625000008, 63.461279296875006], + [10.725292968750068, 63.625], + [11.370703125000034, 63.804833984374994], + [11.175585937500074, 63.89887695312498], + [11.457617187500063, 64.00297851562505], + [11.306640625000028, 64.04887695312499], + [10.91425781250004, 63.92109374999998], + [10.934863281250045, 63.770214843749955], + [10.055078125000051, 63.5126953125], + [9.567285156250051, 63.70615234374998], + [10.565625, 64.418310546875], + [11.523828125000051, 64.744384765625], + [11.632910156250063, 64.81391601562495], + [11.296777343750051, 64.75478515625], + [11.489355468750034, 64.975830078125], + [12.15966796875, 65.178955078125], + [12.508398437499977, 65.09941406250005], + [12.915527343750057, 65.33925781249997], + [12.417578125000063, 65.18408203124997], + [12.133886718749977, 65.27915039062498], + [12.68886718750008, 65.90219726562498], + [13.033105468750051, 65.95625], + [12.783789062500063, 66.10043945312506], + [14.034179687500057, 66.29755859374998], + [13.118847656250011, 66.23066406250004], + [13.211425781250028, 66.64082031250001], + [13.959472656250028, 66.79433593750002], + [13.651562500000011, 66.90708007812498], + [14.10878906250008, 67.11923828125003], + [15.41572265625004, 67.20244140625002], + [14.441699218750045, 67.27138671875005], + [14.961914062500057, 67.57426757812502], + [15.59443359375004, 67.34853515625005], + [15.691503906250006, 67.52138671875], + [15.24873046875004, 67.6021484375], + [15.303906250000011, 67.76528320312502], + [14.854687500000068, 67.66333007812506], + [14.798925781250063, 67.80932617187503], + [15.13427734375, 67.97270507812502], + [15.621386718750017, 67.94829101562502], + [15.316015624999977, 68.06875], + [16.007910156250006, 68.22871093750004], + [16.312304687500017, 67.88144531249998], + [16.20380859375001, 68.31674804687503], + [17.552832031250063, 68.42626953125006], + [16.51435546875004, 68.53256835937503], + [18.101464843749994, 69.15629882812499], + [18.259765625, 69.47060546875], + [18.915917968750023, 69.33559570312502], + [18.614453125000068, 69.49057617187498], + [19.197265625000057, 69.74785156249999], + [19.722460937500017, 69.78164062500002], + [19.64150390625005, 69.42402343750001], + [20.324218750000057, 69.94531249999997], + [20.054492187500074, 69.33266601562497], + [20.486718750000023, 69.54208984375], + [20.739453124999983, 69.52050781250003], + [20.622070312500057, 69.91391601562498], + [21.163085937500057, 69.88950195312498], + [21.432910156250045, 70.01318359375006], + [21.974707031250034, 69.83457031249998], + [21.355761718750045, 70.23339843749997], + [22.321972656250068, 70.264501953125], + [22.684570312500057, 70.374755859375], + [23.35390625000008, 69.98339843750003], + [23.3291015625, 70.20722656249995], + [24.420019531250034, 70.70200195312503], + [24.263476562500017, 70.82631835937497], + [24.658007812500017, 71.00102539062505], + [25.264648437500057, 70.843505859375], + [25.768164062500063, 70.85317382812502], + [25.043847656250023, 70.10903320312502], + [26.66132812500004, 70.93974609374999], + [26.585058593750034, 70.41000976562498], + [26.989355468750063, 70.51137695312502], + [27.183691406250034, 70.74404296875], + [27.546484375000063, 70.80400390625005], + [27.23525390625008, 70.94721679687498], + [27.59707031250005, 71.09130859375003], + [28.392285156250068, 70.97529296875004], + [27.898046875, 70.67792968750001], + [28.271777343750017, 70.66796875000003], + [28.192968750000034, 70.24858398437505], + [28.83154296875003, 70.86396484375001], + [29.7375, 70.646826171875], + [30.065136718750097, 70.70297851562498], + [30.944140625000017, 70.27441406249997], + [30.262988281250074, 70.12470703125004], + [28.804296875000063, 70.09252929687506], + [29.601367187500017, 69.97675781249998], + [29.792089843750063, 69.727880859375], + [30.08828125, 69.71757812500005], + [30.237597656250017, 69.86220703125002], + [30.428320312500006, 69.722265625], + [30.869726562500006, 69.78344726562506] + ] + ], + [ + [ + [25.58632812500005, 71.14208984375], + [26.13378906250003, 70.99580078125004], + [25.582031250000057, 70.960791015625], + [25.31494140625, 71.03413085937504], + [25.58632812500005, 71.14208984375] + ] + ], + [ + [ + [-8.953564453124983, 70.83916015625002], + [-8.001367187499966, 71.17768554687495], + [-8.002099609374937, 71.04125976562497], + [-8.953564453124983, 70.83916015625002] + ] + ], + [ + [ + [19.219335937500006, 74.39101562500002], + [18.86123046875008, 74.51416015624997], + [19.182910156250045, 74.51791992187503], + [19.219335937500006, 74.39101562500002] + ] + ], + [ + [ + [21.60810546875004, 78.59570312499997], + [22.04316406250004, 78.57695312500007], + [22.29951171875004, 78.22817382812497], + [23.451953125000074, 78.14946289062502], + [23.11669921874997, 77.99150390624999], + [24.901855468750057, 77.756591796875], + [22.55371093750003, 77.26665039062502], + [22.685351562500045, 77.55351562500002], + [20.928125, 77.45966796874998], + [21.653125, 77.92353515624998], + [20.22792968750005, 78.47783203125005], + [21.60810546875004, 78.59570312499997] + ] + ], + [ + [ + [11.250292968750017, 78.610693359375], + [12.116406250000068, 78.232568359375], + [11.121289062500011, 78.46328125], + [10.558203125000063, 78.90292968750003], + [11.250292968750017, 78.610693359375] + ] + ], + [ + [ + [29.047070312500068, 78.91206054687504], + [29.69667968750005, 78.90473632812495], + [27.88906250000005, 78.8521484375], + [28.511132812500023, 78.96733398437502], + [29.047070312500068, 78.91206054687504] + ] + ], + [ + [ + [16.786718750000034, 79.90673828125], + [17.834570312499977, 79.80004882812503], + [17.66875, 79.38593750000004], + [18.39736328125008, 79.60517578125001], + [18.677832031250006, 79.26171875000003], + [19.893554687500057, 79.05620117187499], + [20.61103515625004, 79.10664062499998], + [21.388769531250034, 78.74042968749998], + [19.67675781250003, 78.60957031249995], + [16.700488281250045, 76.57929687499995], + [14.365820312500034, 77.23447265625003], + [13.995703125000034, 77.50820312500002], + [14.69501953125004, 77.525048828125], + [14.920800781250023, 77.68881835937506], + [17.033300781250006, 77.79770507812503], + [16.91406250000003, 77.89799804687505], + [14.089941406250063, 77.77138671875], + [13.680566406250051, 78.028125], + [14.307226562500006, 78.00507812500001], + [15.783886718750011, 78.32705078125005], + [17.00292968750003, 78.36938476562497], + [16.44863281250008, 78.50356445312502], + [16.78261718750008, 78.66362304687505], + [15.417382812500023, 78.47324218749998], + [15.384179687500023, 78.77119140625001], + [15.01630859375004, 78.63012695312497], + [14.689257812500017, 78.720947265625], + [14.638281250000034, 78.41459960937502], + [14.110449218750063, 78.27089843749997], + [13.150195312499989, 78.2375], + [11.365429687500011, 78.95039062500004], + [12.323437500000068, 78.91425781249995], + [12.083984375000028, 79.26752929687498], + [11.579785156250068, 79.28349609375005], + [11.208105468750034, 79.12963867187503], + [10.737597656250017, 79.52016601562502], + [10.804003906250045, 79.79877929687504], + [11.150390625, 79.71699218749998], + [11.702343750000011, 79.82060546875005], + [12.287792968750068, 79.713134765625], + [12.279980468749983, 79.81596679687507], + [13.692871093749972, 79.860986328125], + [13.777539062500011, 79.71528320312498], + [12.555371093750068, 79.56948242187502], + [13.333789062500017, 79.57480468750006], + [14.029589843750017, 79.34414062500005], + [14.59365234375008, 79.79873046875002], + [16.34375, 78.97612304687502], + [15.816113281250011, 79.68183593750001], + [16.245703125000034, 80.04946289062502], + [16.786718750000034, 79.90673828125] + ] + ], + [ + [ + [32.52597656250006, 80.119140625], + [31.48193359374997, 80.10791015625003], + [33.62929687499999, 80.21743164062497], + [32.52597656250006, 80.119140625] + ] + ], + [ + [ + [20.897851562500023, 80.24995117187501], + [22.289746093749983, 80.04921874999997], + [22.450781250000034, 80.40224609375005], + [23.00800781250004, 80.473974609375], + [23.114550781250074, 80.18696289062498], + [24.29755859375004, 80.36040039062505], + [26.86083984375, 80.16000976562498], + [27.19863281250008, 79.90659179687506], + [25.641210937500034, 79.40302734374995], + [23.94775390625, 79.19428710937498], + [22.903710937500023, 79.23066406250001], + [22.865527343750045, 79.41186523437497], + [20.861132812500017, 79.39785156249997], + [20.128222656250074, 79.489599609375], + [19.674609375000045, 79.591162109375], + [20.784082031250023, 79.74858398437502], + [18.725, 79.7607421875], + [18.25537109375, 79.92919921875003], + [18.855957031250057, 80.03662109375], + [17.91689453125005, 80.14311523437502], + [19.343359375000063, 80.11640624999998], + [19.733300781249994, 80.47783203124999], + [20.897851562500023, 80.24995117187501] + ] + ] + ] + }, + "properties": { "name": "Norway", "childNum": 27 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [87.984375, 27.133935546874994], + [87.9931640625, 27.086083984374994], + [88.11103515625001, 26.928466796875], + [88.1572265625, 26.807324218749997], + [88.16152343750002, 26.724804687499997], + [88.11152343750001, 26.58642578125], + [88.05488281250001, 26.430029296875], + [88.02695312500003, 26.39501953125], + [87.9951171875, 26.382373046874996], + [87.28740234374999, 26.360302734374997], + [87.01640624999999, 26.555419921875], + [86.70136718750001, 26.43505859375], + [86.00732421875, 26.649365234374997], + [85.79453125000003, 26.604150390624994], + [85.7373046875, 26.63974609375], + [85.6484375, 26.829003906249994], + [85.56845703125003, 26.83984375], + [85.29296875, 26.741015625], + [85.19179687500002, 26.766552734374997], + [84.68535156249999, 27.041015625], + [84.65380859375, 27.091699218749994], + [84.65478515625, 27.203662109374996], + [84.64072265625003, 27.249853515625], + [84.61015624999999, 27.298681640625], + [84.48085937500002, 27.348193359374996], + [84.22978515624999, 27.42783203125], + [84.09101562500001, 27.491357421874994], + [83.82880859375001, 27.377832031249994], + [83.74697265625002, 27.395947265624997], + [83.55166015625002, 27.456347656249996], + [83.44716796875002, 27.46533203125], + [83.38398437500001, 27.44482421875], + [83.36943359374999, 27.41025390625], + [83.28974609375001, 27.370996093749994], + [82.7333984375, 27.518994140624997], + [82.71083984375002, 27.5966796875], + [82.67734375000003, 27.6734375], + [82.6298828125, 27.687060546874996], + [82.45136718750001, 27.671826171874997], + [82.28769531250003, 27.756542968749997], + [82.11191406250003, 27.864941406249997], + [82.03701171875002, 27.900585937499997], + [81.98769531250002, 27.913769531249997], + [81.94521484375002, 27.899267578125], + [81.896875, 27.874462890624997], + [81.85263671875003, 27.867089843749994], + [81.1689453125, 28.335009765624996], + [80.58701171875003, 28.649609375], + [80.51787109374999, 28.665185546874994], + [80.49580078125001, 28.635791015624996], + [80.47910156250003, 28.604882812499994], + [80.41855468750003, 28.612011718749997], + [80.32480468750003, 28.66640625], + [80.2265625, 28.723339843749997], + [80.07070312500002, 28.83017578125], + [80.05166015625002, 28.8703125], + [80.08457031250003, 28.994189453124996], + [80.13046875000003, 29.100390625], + [80.16953125000003, 29.124316406249996], + [80.23300781250003, 29.194628906249996], + [80.25595703125003, 29.318017578124994], + [80.2548828125, 29.42333984375], + [80.31689453125, 29.572070312499996], + [80.40185546875, 29.730273437499996], + [80.54902343750001, 29.899804687499994], + [80.81992187500003, 30.119335937499997], + [80.84814453125, 30.13974609375], + [80.90761718750002, 30.171923828124996], + [80.96611328124999, 30.180029296875], + [81.17714843750002, 30.039892578125], + [81.25507812500001, 30.093310546874996], + [81.41718750000001, 30.337597656249997], + [81.64189453124999, 30.3875], + [81.85488281250002, 30.36240234375], + [82.04335937500002, 30.3267578125], + [82.220703125, 30.063867187499994], + [83.15546875000001, 29.612646484375], + [83.58349609375, 29.18359375], + [83.93593750000002, 29.279492187499997], + [84.02197265625, 29.253857421874997], + [84.10136718749999, 29.219970703125], + [84.12783203125002, 29.156298828124996], + [84.17558593749999, 29.036376953125], + [84.22871093750001, 28.911767578124994], + [84.796875, 28.560205078124994], + [84.85507812500003, 28.553613281249994], + [85.06914062499999, 28.609667968749996], + [85.12636718750002, 28.60263671875], + [85.15908203125002, 28.592236328124997], + [85.16015625, 28.571875], + [85.12148437500002, 28.484277343749994], + [85.08857421875001, 28.372265625], + [85.12246093750002, 28.315966796874996], + [85.21210937500001, 28.292626953124994], + [85.41064453125, 28.276025390624994], + [85.67832031250003, 28.27744140625], + [85.75947265625001, 28.220654296874997], + [85.84023437500002, 28.1353515625], + [85.92167968749999, 27.989697265624997], + [85.9541015625, 27.92822265625], + [85.99453125000002, 27.910400390625], + [86.06416015625001, 27.934716796874994], + [86.07871093750003, 28.08359375], + [86.13701171874999, 28.11435546875], + [86.21796875000001, 28.0220703125], + [86.32861328125, 27.959521484374996], + [86.40869140625, 27.928662109374997], + [86.51689453124999, 27.963525390624994], + [86.55449218749999, 28.085205078125], + [86.61445312500001, 28.10302734375], + [86.69052734375003, 28.094921875], + [86.71962890625002, 28.070654296875], + [86.75039062500002, 28.0220703125], + [86.93378906250001, 27.968457031249997], + [87.02011718750003, 27.928662109374997], + [87.14140624999999, 27.838330078124997], + [87.29072265625001, 27.821923828124994], + [87.62255859375, 27.815185546875], + [87.86074218750002, 27.886083984375], + [88.10976562500002, 27.87060546875], + [87.984375, 27.133935546874994] + ] + ] + }, + "properties": { "name": "Nepal", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [169.17822265624997, -52.497265625], + [169.12753906250006, -52.570312499999964], + [169.02177734375002, -52.49541015624998], + [169.17822265624997, -52.497265625] + ] + ], + [ + [ + [166.22109375, -50.76152343749997], + [166.2428710937501, -50.84570312499998], + [165.88916015624997, -50.80771484374996], + [166.10136718750002, -50.538964843750016], + [166.26748046875005, -50.558593750000014], + [166.22109375, -50.76152343749997] + ] + ], + [ + [ + [168.14492187500005, -46.862207031249966], + [168.04316406250004, -46.9326171875], + [168.2409179687501, -47.070019531250026], + [167.52197265624997, -47.258691406249994], + [167.80078125000003, -46.90654296875002], + [167.78398437500007, -46.699804687500006], + [167.9557617187501, -46.69443359374998], + [168.14492187500005, -46.862207031249966] + ] + ], + [ + [ + [166.97949218749997, -45.17968750000003], + [167.02265625000004, -45.299804687499986], + [166.89267578125012, -45.24052734374999], + [166.97949218749997, -45.17968750000003] + ] + ], + [ + [ + [-176.17763671874997, -43.74033203124998], + [-176.38173828124997, -43.86679687499998], + [-176.40737304687497, -43.7609375], + [-176.516552734375, -43.78476562499996], + [-176.33359375000003, -44.02529296875004], + [-176.51552734374997, -44.11660156249998], + [-176.62934570312495, -44.036132812500014], + [-176.55512695312504, -43.85195312499998], + [-176.84765625000003, -43.82392578125004], + [-176.56611328124995, -43.717578125000045], + [-176.17763671874997, -43.74033203124998] + ] + ], + [ + [ + [173.91464843750018, -40.86367187500004], + [173.78085937500012, -40.921777343749966], + [173.964453125, -40.71298828124998], + [173.91464843750018, -40.86367187500004] + ] + ], + [ + [ + [173.11533203125006, -41.27929687499997], + [173.94716796875005, -40.92412109375], + [173.79785156250003, -41.271972656249986], + [173.99941406250005, -40.99326171874996], + [174.30253906249996, -41.019531249999986], + [174.03857421875003, -41.24189453125], + [174.37011718750009, -41.1037109375], + [174.06933593750009, -41.42949218750002], + [174.08369140625015, -41.67080078124998], + [174.2831054687501, -41.740625], + [173.22119140624997, -42.976562499999986], + [172.62402343749997, -43.27246093749996], + [172.73476562500005, -43.35478515625003], + [172.52666015625002, -43.464746093749966], + [172.69345703125006, -43.444335937499986], + [172.80703125000005, -43.620996093749994], + [173.07324218750003, -43.676171874999966], + [173.065625, -43.87460937499998], + [172.50273437500002, -43.84365234374998], + [172.48037109375, -43.726660156250034], + [172.29658203125004, -43.867871093750026], + [172.035546875, -43.70175781250002], + [172.17978515625006, -43.895996093749986], + [171.24072265624997, -44.26416015625003], + [171.14628906250002, -44.9123046875], + [170.99902343750003, -44.91142578124999], + [171.11328125000003, -45.03925781250001], + [170.7005859375, -45.68427734374997], + [170.77626953125005, -45.870898437499974], + [170.4191406250001, -45.94101562499996], + [169.68662109375006, -46.55166015625002], + [169.34228515625003, -46.62050781250001], + [168.38212890625007, -46.60537109374995], + [168.1891601562501, -46.362207031249966], + [167.8419921875001, -46.366210937499986], + [167.539453125, -46.14853515624996], + [167.36894531250007, -46.24150390624999], + [166.73154296875006, -46.19785156249998], + [166.91669921875004, -45.95722656249998], + [166.64990234374997, -46.04169921875004], + [166.71796875000004, -45.88935546875001], + [166.49316406249997, -45.9638671875], + [166.48828124999997, -45.83183593750002], + [167.0033203125, -45.71210937500004], + [166.79765625000002, -45.64560546874999], + [166.99082031250012, -45.531738281249986], + [166.73398437500012, -45.54355468749999], + [166.74306640625, -45.46845703124997], + [166.91992187499997, -45.40791015624998], + [166.86923828125006, -45.31123046875], + [167.15566406250005, -45.410937499999974], + [167.23007812500012, -45.29033203125], + [167.02587890624997, -45.12363281249998], + [167.25947265625004, -45.08222656249997], + [167.19453125000004, -44.963476562500034], + [167.41074218750006, -44.82792968750003], + [167.4662109375, -44.958300781250045], + [167.48496093750006, -44.77138671874998], + [167.78701171875, -44.59501953125002], + [167.90898437500002, -44.66474609375001], + [167.85654296875012, -44.50068359374998], + [168.45742187500005, -44.030566406250045], + [169.17890625000004, -43.9130859375], + [169.16953125000006, -43.77705078125], + [169.83388671875, -43.53701171875004], + [170.24023437499997, -43.163867187500045], + [170.39609375000012, -43.18222656249996], + [170.30283203125012, -43.10761718750004], + [170.61181640625003, -43.091796875000014], + [170.5236328125001, -43.00898437500001], + [170.6654296875, -42.961230468749974], + [170.73525390625005, -43.029785156249986], + [170.96992187500004, -42.71835937499996], + [171.01171875000003, -42.88505859374999], + [171.027734375, -42.696093750000045], + [171.31337890625005, -42.460156250000026], + [171.48623046875, -41.7947265625], + [171.94804687500002, -41.53867187499996], + [172.13945312500002, -40.947265625000014], + [172.640625, -40.51826171875001], + [172.94365234375007, -40.51875], + [172.73261718750004, -40.54375], + [172.70439453125002, -40.6677734375], + [172.988671875, -40.84824218749999], + [173.11533203125006, -41.27929687499997] + ] + ], + [ + [ + [175.54316406250015, -36.279296874999986], + [175.34619140624997, -36.217773437499986], + [175.3895507812501, -36.07773437499996], + [175.54316406250015, -36.279296874999986] + ] + ], + [ + [ + [173.26943359375, -34.93476562499998], + [173.44785156250012, -34.844335937500034], + [173.47265625000003, -34.94697265624998], + [174.10400390625003, -35.14287109375002], + [174.1431640625, -35.3], + [174.32031250000003, -35.246679687500034], + [174.58066406250018, -35.78554687500004], + [174.39580078124996, -35.79736328124996], + [174.8021484375, -36.30947265625001], + [174.72246093750007, -36.84121093749998], + [175.29951171875004, -36.99326171874996], + [175.38535156250012, -37.206933593749966], + [175.54248046874997, -37.2013671875], + [175.46083984375005, -36.475683593750034], + [175.77216796875004, -36.73515625], + [176.10839843749997, -37.64511718749998], + [177.27402343750012, -37.993457031249974], + [178.0091796875, -37.55488281249998], + [178.53623046875006, -37.69208984375004], + [178.26767578125006, -38.551171875], + [177.976171875, -38.72226562500005], + [177.90878906250012, -39.23955078125], + [177.52294921875003, -39.07382812499999], + [177.07675781250012, -39.22177734375002], + [176.93925781249996, -39.55527343750002], + [177.10986328125009, -39.673144531250045], + [176.8421875000001, -40.15781250000002], + [175.98291015625003, -41.21328125000002], + [175.30976562499998, -41.610644531249974], + [175.16562500000012, -41.41738281249995], + [174.88134765624997, -41.42402343749997], + [174.8656250000001, -41.223046874999966], + [174.63535156250012, -41.28945312499999], + [175.1625, -40.62158203125], + [175.25410156250004, -40.28935546875], + [175.1559570312501, -40.11494140625], + [175.00927734375009, -39.95214843749996], + [173.93437500000013, -39.50908203125002], + [173.76367187499997, -39.31875], + [173.84433593750006, -39.13935546875001], + [174.39843749999997, -38.96259765624998], + [174.59736328124998, -38.78505859374995], + [174.80166015625005, -37.895507812500014], + [174.92802734375002, -37.80449218750003], + [174.58583984374994, -37.09775390625002], + [174.73427734375, -37.21523437499998], + [174.92890625000004, -37.084765625000045], + [174.78203125000013, -36.94375], + [174.47558593750009, -36.94189453124997], + [174.1888671875001, -36.492285156250034], + [174.4015625000001, -36.60195312499999], + [174.39277343750004, -36.24003906249999], + [174.26787109375002, -36.16308593750003], + [174.25371093749996, -36.24912109374998], + [174.03642578125013, -36.12246093750001], + [173.91445312499994, -35.908691406249986], + [173.91728515625002, -36.01816406249999], + [174.16640624999994, -36.327636718749986], + [174.05468749999991, -36.35976562500004], + [173.41220703125012, -35.542578125], + [173.62617187500004, -35.31914062499996], + [173.3763671875001, -35.50009765624996], + [173.31396484375003, -35.44335937499996], + [173.11669921874997, -35.205273437500026], + [173.190625, -35.01621093749998], + [172.70595703125005, -34.45517578124998], + [173.04394531249997, -34.429101562499994], + [172.96376953125, -34.53515625000003], + [173.26943359375, -34.93476562499998] + ] + ] + ] + }, + "properties": { "name": "New Zealand", "childNum": 9 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [58.722070312499994, 20.21875], + [58.640917968750074, 20.210693359375057], + [58.64121093750006, 20.33735351562501], + [58.884375, 20.680566406250023], + [58.95078125000006, 20.516162109375017], + [58.722070312499994, 20.21875] + ] + ], + [ + [ + [56.38798828125002, 24.97919921875004], + [56.640625, 24.4703125], + [57.12304687500003, 23.980712890625], + [58.773046875, 23.517187499999977], + [59.42939453125004, 22.660839843749955], + [59.82324218749997, 22.50898437500004], + [59.8, 22.21992187500001], + [59.37148437500005, 21.498828125000017], + [58.89570312500004, 21.11279296874997], + [58.47421875000006, 20.406884765624966], + [58.20898437500003, 20.423974609374994], + [58.245019531249994, 20.599218749999977], + [58.16943359375003, 20.58950195312505], + [57.86181640624997, 20.24414062500003], + [57.71416015625002, 19.678417968749983], + [57.81162109375006, 19.01708984374997], + [56.825976562500074, 18.753515625], + [56.3834960937501, 17.98798828125001], + [55.479101562500006, 17.84326171875003], + [55.25537109375003, 17.58564453125004], + [55.275195312500074, 17.320898437500006], + [55.06416015625004, 17.038916015625034], + [54.06816406250002, 17.005517578124966], + [53.60986328124997, 16.75996093750004], + [53.08564453125004, 16.648388671874955], + [51.977636718750006, 18.996142578125074], + [54.97734375000002, 19.995947265625006], + [55.64101562499999, 22.001855468749994], + [55.185839843750074, 22.7041015625], + [55.1999023437501, 23.034765625000034], + [55.53164062499999, 23.81904296875001], + [55.4684570312501, 23.94111328125001], + [55.98515625000002, 24.063378906249966], + [55.92861328125005, 24.215136718750074], + [55.76083984375006, 24.24267578125], + [55.795703125000074, 24.868115234374955], + [56.00058593750006, 24.953222656249977], + [56.06386718750005, 24.73876953125], + [56.38798828125002, 24.97919921875004] + ] + ], + [ + [ + [56.29785156250003, 25.650683593750045], + [56.144628906250006, 25.690527343750006], + [56.16748046875003, 26.047460937499977], + [56.08046875, 26.06264648437505], + [56.41308593749997, 26.351171875000034], + [56.29785156250003, 25.650683593750045] + ] + ] + ] + }, + "properties": { "name": "Oman", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [73.08961802927895, 36.86435907947333], + [73.08203125000107, 36.43949943991182], + [72.31128647748268, 35.77290936638241], + [73.13410859949555, 34.82510160558277], + [73.19895048106557, 33.88770931468204], + [74.00809389139292, 33.25375789331485], + [73.98984375, 33.22119140625], + [74.30361328125002, 32.991796875], + [74.30546875000002, 32.810449218749994], + [74.35458984375, 32.768701171874994], + [74.58828125000002, 32.753222656249996], + [74.632421875, 32.770898437499994], + [74.66328125000001, 32.757666015625], + [74.64335937500002, 32.607714843749996], + [74.68574218750001, 32.493798828124994], + [74.78886718750002, 32.4578125], + [74.9873046875, 32.462207031249996], + [75.33349609375, 32.279199218749994], + [75.25410156250001, 32.14033203125], + [75.13876953125, 32.104785156249996], + [75.07148437500001, 32.08935546875], + [74.73945312500001, 31.948828125], + [74.6357421875, 31.88974609375], + [74.55556640625002, 31.818554687499997], + [74.5259765625, 31.76513671875], + [74.50996093750001, 31.712939453124996], + [74.58183593750002, 31.52392578125], + [74.59394531250001, 31.465380859374996], + [74.53496093750002, 31.261376953124994], + [74.51767578125, 31.185595703124996], + [74.6103515625, 31.112841796874996], + [74.62578125000002, 31.06875], + [74.6328125, 31.03466796875], + [74.509765625, 30.959667968749997], + [74.38037109375, 30.893408203125], + [74.33935546875, 30.8935546875], + [74.00898437500001, 30.519677734374994], + [73.89931640625002, 30.435351562499996], + [73.88271484375002, 30.3521484375], + [73.92460937500002, 30.28164062499999], + [73.93339843750002, 30.222070312499994], + [73.88652343750002, 30.162011718749994], + [73.8091796875, 30.093359375], + [73.38164062500002, 29.934375], + [72.9033203125, 29.028759765624997], + [72.34189453125, 28.751904296874997], + [72.2919921875, 28.697265625], + [72.128515625, 28.346337890624994], + [71.94804687500002, 28.177294921874996], + [71.88886718750001, 28.0474609375], + [71.87031250000001, 27.9625], + [71.54296875, 27.869873046875], + [71.18476562500001, 27.831640625], + [70.87490234375002, 27.714453125], + [70.79794921875, 27.709619140624994], + [70.69160156250001, 27.768994140624997], + [70.62910156250001, 27.937451171874997], + [70.40371093750002, 28.025048828124994], + [70.24433593750001, 27.934130859374996], + [70.1939453125, 27.894873046875], + [70.14453125, 27.849023437499994], + [70.0498046875, 27.694726562499994], + [69.89628906250002, 27.4736328125], + [69.56796875, 27.174609375], + [69.47001953125002, 26.804443359375], + [70.11464843750002, 26.548046875], + [70.14921875000002, 26.347558593749994], + [70.1001953125, 25.910058593749994], + [70.2646484375, 25.70654296875], + [70.3251953125, 25.685742187499997], + [70.44853515625002, 25.681347656249997], + [70.505859375, 25.685302734375], + [70.56953125000001, 25.705957031249994], + [70.6484375, 25.666943359374997], + [70.65205078125001, 25.422900390625003], + [70.87773437500002, 25.06298828125], + [70.95087890625001, 24.8916015625], + [71.02070312500001, 24.75766601562499], + [71.0478515625, 24.687744140625], + [71.00234375000002, 24.65390625], + [70.97636718750002, 24.61875], + [70.96982421875, 24.571875], + [71.04531250000002, 24.429980468750003], + [71.04404296875, 24.400097656249997], + [70.98281250000002, 24.361035156249997], + [70.928125, 24.362353515625003], + [70.88623046875, 24.34375], + [70.80507812500002, 24.261962890625], + [70.76728515625001, 24.245410156250003], + [70.71630859375, 24.237988281249997], + [70.65947265625002, 24.24609375], + [70.57929687500001, 24.279052734375], + [70.55585937500001, 24.331103515625003], + [70.5650390625, 24.385791015625003], + [70.54677734375002, 24.41831054687499], + [70.2890625, 24.35629882812499], + [70.0982421875, 24.2875], + [69.80517578125, 24.165234375], + [69.71621093750002, 24.172607421875], + [69.63417968750002, 24.22519531249999], + [69.5591796875, 24.273095703124994], + [69.44345703125, 24.275390625], + [69.23505859375001, 24.268261718749997], + [69.11953125000002, 24.26865234374999], + [69.05156250000002, 24.286328125], + [68.98457031250001, 24.273095703124994], + [68.90078125000002, 24.292431640624997], + [68.86347656250001, 24.266503906249994], + [68.82832031250001, 24.26401367187499], + [68.78115234375002, 24.313720703125], + [68.75898437500001, 24.30722656249999], + [68.73964843750002, 24.2919921875], + [68.728125, 24.265625], + [68.72412109375, 23.96469726562499], + [68.48867187500002, 23.967236328124997], + [68.38125, 23.950878906249997], + [68.28251953125002, 23.927978515625], + [68.1650390625, 23.857324218749994], + [68.11552734375002, 23.753369140624997], + [67.8599609375, 23.90268554687499], + [67.66845703125, 23.810986328124997], + [67.309375, 24.1748046875], + [67.171484375, 24.756103515625], + [66.70302734375002, 24.8609375], + [66.69863281250002, 25.226318359375], + [66.32421875, 25.601806640625], + [66.13115234375002, 25.49326171874999], + [66.46767578125002, 25.4453125], + [64.77666015625002, 25.307324218749997], + [64.65898437500002, 25.18408203125], + [64.059375, 25.40292968749999], + [63.556640625, 25.353173828124994], + [63.49140625000001, 25.210839843749994], + [61.56689453125, 25.186328125], + [61.587890625, 25.20234375], + [61.61542968750001, 25.2861328125], + [61.64013671875, 25.584619140624994], + [61.67138671875, 25.6923828125], + [61.66181640625001, 25.751269531250003], + [61.66865234375001, 25.768994140624997], + [61.73769531250002, 25.82109375], + [61.75439453125, 25.84335937499999], + [61.78076171875, 25.995849609375], + [61.80996093750002, 26.165283203125], + [61.842382812500006, 26.225927734375], + [62.1259765625, 26.368994140625], + [62.239355468750006, 26.35703125], + [62.31230468750002, 26.490869140624994], + [63.168066406250006, 26.665576171874996], + [63.186132812500006, 26.837597656249997], + [63.24160156250002, 26.86474609375], + [63.25039062500002, 26.879248046875], + [63.24208984375002, 27.077685546874996], + [63.30517578125, 27.124560546874996], + [63.30156250000002, 27.15146484375], + [63.25625, 27.207910156249994], + [63.19609375000002, 27.243945312499996], + [63.16679687500002, 27.252490234374996], + [62.75273437500002, 27.265625], + [62.782324218750006, 27.800537109375], + [62.7625, 28.202050781249994], + [61.88984375000001, 28.546533203124994], + [61.15214843750002, 29.542724609375], + [61.0341796875, 29.663427734375], + [60.843359375, 29.858691406249996], + [61.22441406250002, 29.749414062499994], + [62.0009765625, 29.530419921874994], + [62.4765625, 29.408349609374994], + [63.56757812500001, 29.497998046874997], + [64.09873046875, 29.391943359375], + [64.39375, 29.544335937499994], + [65.09550781250002, 29.559472656249994], + [66.23125, 29.86572265625], + [66.346875, 30.802783203124996], + [66.82929687500001, 31.263671875], + [67.45283203125001, 31.234619140625], + [67.737890625, 31.343945312499997], + [67.57822265625, 31.506494140624994], + [68.16103515625002, 31.802978515625], + [68.59765625, 31.802978515625], + [68.86894531250002, 31.634228515624997], + [69.279296875, 31.936816406249996], + [69.24140625000001, 32.433544921875], + [69.5015625, 33.020068359374996], + [70.26113281250002, 33.289013671875], + [69.8896484375, 34.007275390625], + [70.65400390625001, 33.952294921874994], + [71.05156250000002, 34.049707031249994], + [71.095703125, 34.369433593749996], + [70.965625, 34.53037109375], + [71.62050781250002, 35.183007812499994], + [71.57197265625001, 35.546826171875], + [71.18505859375, 36.04208984375], + [71.23291015625, 36.12177734375], + [72.24980468750002, 36.734716796875], + [73.08961802927895, 36.86435907947333] + ] + ] + }, + "properties": { "name": "Pakistan", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-81.60327148437497, 7.332812499999989], + [-81.85205078125003, 7.453320312500026], + [-81.812158203125, 7.59238281250002], + [-81.72875976562494, 7.62119140625002], + [-81.60327148437497, 7.332812499999989] + ] + ], + [ + [ + [-78.89833984375002, 8.27426757812502], + [-78.960595703125, 8.435839843749989], + [-78.88325195312495, 8.460253906249989], + [-78.89833984375002, 8.27426757812502] + ] + ], + [ + [ + [-77.37421874999993, 8.65830078125002], + [-77.47851562499994, 8.498437500000037], + [-77.19599609374995, 7.972460937500003], + [-77.53828124999995, 7.56625976562502], + [-77.76191406249995, 7.698828125000034], + [-77.90117187499999, 7.229345703125048], + [-78.42158203124995, 8.060986328125011], + [-78.28735351562497, 8.091796874999972], + [-78.14189453125002, 8.386083984374977], + [-77.76054687499993, 8.133251953124983], + [-78.09946289062498, 8.496972656250009], + [-78.22304687500002, 8.396630859374994], + [-78.39921874999993, 8.505664062500003], + [-78.40986328124998, 8.35532226562502], + [-78.51406249999997, 8.628173828125], + [-79.08637695312495, 8.997167968750034], + [-79.50708007812494, 8.97006835937502], + [-79.68745117187493, 8.850976562500009], + [-79.81591796875, 8.639208984375031], + [-79.75043945312498, 8.595507812500017], + [-80.458984375, 8.213867187499972], + [-80.45810546875, 8.077050781249994], + [-80.01123046875, 7.500048828125031], + [-80.66669921874995, 7.225683593750006], + [-80.90122070312503, 7.277148437500017], + [-81.06386718749994, 7.89975585937502], + [-81.26840820312495, 7.625488281250014], + [-81.50415039062503, 7.721191406249972], + [-81.72763671875, 8.137548828124977], + [-82.15986328124995, 8.19482421875], + [-82.23544921874998, 8.311035156250057], + [-82.67954101562503, 8.321972656249969], + [-82.86611328124994, 8.246337890625014], + [-82.87934570312498, 8.07065429687502], + [-83.02734375, 8.337744140624991], + [-82.86162109374999, 8.453515625000037], + [-82.84477539062493, 8.489355468749963], + [-82.85571289062494, 8.635302734375031], + [-82.91704101562502, 8.740332031250034], + [-82.88198242187497, 8.805322265625037], + [-82.72783203125002, 8.916064453125031], + [-82.78305664062498, 8.990283203124974], + [-82.88134765625003, 9.055859375000011], + [-82.94033203124997, 9.060107421874989], + [-82.93984374999994, 9.449169921875026], + [-82.92504882812494, 9.469042968749989], + [-82.88896484374999, 9.481005859375017], + [-82.86015625, 9.511474609375014], + [-82.84399414062497, 9.570800781250014], + [-82.801025390625, 9.591796875000028], + [-82.64409179687502, 9.505859375000028], + [-82.56357421875003, 9.576660156249972], + [-82.50034179687503, 9.523242187500017], + [-82.37080078124993, 9.428564453124991], + [-82.33974609375, 9.209179687499983], + [-82.18813476562502, 9.191748046874977], + [-82.24418945312499, 9.031494140625014], + [-82.07788085937503, 8.93486328124996], + [-81.78022460937495, 8.957226562499983], + [-81.89448242187495, 9.140429687500003], + [-81.35478515624996, 8.78056640624996], + [-80.83867187499999, 8.887207031250014], + [-80.12709960937497, 9.20991210937504], + [-79.57729492187497, 9.597851562500026], + [-78.08276367187494, 9.236279296874997], + [-77.37421874999993, 8.65830078125002] + ] + ] + ] + }, + "properties": { "name": "Panama", "childNum": 3 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-73.137353515625, -6.4658203125], + [-73.75810546874999, -6.90576171875], + [-73.79301757812499, -7.135058593750003], + [-73.758203125, -7.172753906250009], + [-73.72041015625, -7.309277343750011], + [-73.964306640625, -7.37890625], + [-73.95849609375, -7.506640625], + [-73.98173828124999, -7.535742187500006], + [-74.00205078124999, -7.556054687500009], + [-73.98173828124999, -7.585058593750006], + [-73.946875, -7.611230468750009], + [-73.89462890624999, -7.65478515625], + [-73.82207031249999, -7.738964843750011], + [-73.76689453124999, -7.753515625], + [-73.72041015625, -7.782519531250003], + [-73.73203125, -7.875390625], + [-73.54912109374999, -8.345800781250006], + [-73.39814453125, -8.458984375], + [-73.36040039062499, -8.479296875], + [-73.351708984375, -8.51416015625], + [-73.35673828124999, -8.566992187500006], + [-73.30244140625, -8.654003906250011], + [-73.203125, -8.719335937500006], + [-73.0705078125, -8.8828125], + [-72.9740234375, -8.9931640625], + [-72.970361328125, -9.1201171875], + [-73.08984375, -9.265722656250006], + [-73.209423828125, -9.411425781250003], + [-72.379052734375, -9.51015625], + [-72.181591796875, -10.003710937500003], + [-71.33940429687499, -9.988574218750003], + [-71.11528320312499, -9.852441406250009], + [-71.041748046875, -9.81875], + [-70.6369140625, -9.478222656250011], + [-70.60791015625, -9.463671875], + [-70.54111328124999, -9.4375], + [-70.57016601562499, -9.48984375], + [-70.592236328125, -9.54345703125], + [-70.59916992187499, -9.620507812500009], + [-70.642333984375, -11.01025390625], + [-70.59653320312499, -10.976855468750003], + [-70.53325195312499, -10.946875], + [-70.45087890625, -11.024804687500009], + [-70.39228515625, -11.05859375], + [-70.3419921875, -11.066699218750003], + [-70.29038085937499, -11.064257812500003], + [-70.22006835937499, -11.04765625], + [-70.06630859375, -10.982421875], + [-69.9603515625, -10.929882812500011], + [-69.839794921875, -10.933398437500003], + [-69.6740234375, -10.9541015625], + [-69.57861328125, -10.951757812500006], + [-68.68525390625, -12.501953125], + [-68.97861328124999, -12.880078125000011], + [-69.07412109375, -13.682812500000011], + [-68.87089843749999, -14.169726562500003], + [-69.35947265624999, -14.7953125], + [-69.37470703125, -14.962988281250006], + [-69.17246093749999, -15.236621093750003], + [-69.4208984375, -15.640625], + [-69.21757812499999, -16.14912109375001], + [-68.8427734375, -16.337890625], + [-69.03291015625, -16.47597656250001], + [-69.020703125, -16.6421875], + [-69.62485351562499, -17.2001953125], + [-69.645703125, -17.24853515625], + [-69.521923828125, -17.388964843750003], + [-69.510986328125, -17.46035156250001], + [-69.51108398437499, -17.5048828125], + [-69.5109375, -17.50605468750001], + [-69.58642578125, -17.5732421875], + [-69.684765625, -17.64980468750001], + [-69.85209960937499, -17.70380859375001], + [-69.80258789062499, -17.990234375], + [-69.92636718749999, -18.2060546875], + [-70.41826171874999, -18.34560546875001], + [-71.33696289062499, -17.68251953125001], + [-71.5322265625, -17.29433593750001], + [-72.46767578125, -16.708105468750006], + [-73.727685546875, -16.20166015625], + [-75.104248046875, -15.411914062500003], + [-75.533642578125, -14.89921875], + [-75.93388671874999, -14.63359375], + [-76.37646484375, -13.863085937500003], + [-76.259228515625, -13.802832031250006], + [-76.2236328125, -13.371191406250006], + [-76.83212890624999, -12.348730468750006], + [-77.152734375, -12.060351562500003], + [-77.2203125, -11.663378906250003], + [-77.633203125, -11.287792968750011], + [-77.736083984375, -10.83671875], + [-78.18559570312499, -10.089062500000011], + [-78.76225585937499, -8.616992187500003], + [-79.37724609374999, -7.835546875], + [-79.99497070312499, -6.768945312500009], + [-81.142041015625, -6.056738281250006], + [-81.164306640625, -5.875292968750003], + [-80.9306640625, -5.8408203125], + [-80.88193359374999, -5.635058593750003], + [-81.33662109375, -4.66953125], + [-81.283203125, -4.322265625], + [-80.503662109375, -3.49609375], + [-80.324658203125, -3.387890625000011], + [-80.24375, -3.576757812500006], + [-80.19414062499999, -3.905859375], + [-80.23051757812499, -3.924023437500011], + [-80.26689453124999, -3.948828125], + [-80.30327148437499, -4.005078125000011], + [-80.43720703125, -3.978613281250006], + [-80.49013671875, -4.010058593750003], + [-80.510009765625, -4.069531250000011], + [-80.49345703124999, -4.119140625], + [-80.4884765625, -4.16552734375], + [-80.453759765625, -4.205175781250006], + [-80.35288085937499, -4.20849609375], + [-80.44384765625, -4.335839843750009], + [-80.4884765625, -4.393652343750006], + [-80.47856445312499, -4.430078125], + [-80.42416992187499, -4.46142578125], + [-80.38349609375, -4.463671875], + [-80.293359375, -4.416796875], + [-80.1974609375, -4.31103515625], + [-80.13955078125, -4.296093750000011], + [-80.06352539062499, -4.327539062500009], + [-79.962890625, -4.390332031250011], + [-79.8451171875, -4.445898437500006], + [-79.797265625, -4.476367187500003], + [-79.71098632812499, -4.467578125], + [-79.63852539062499, -4.454882812500003], + [-79.57768554687499, -4.500585937500006], + [-79.51616210937499, -4.539160156250006], + [-79.501904296875, -4.670605468750011], + [-79.45576171875, -4.766210937500006], + [-79.3994140625, -4.840039062500011], + [-79.33095703125, -4.927832031250006], + [-79.26811523437499, -4.957617187500006], + [-79.186669921875, -4.958203125000011], + [-79.07626953124999, -4.990625], + [-79.03330078124999, -4.969140625], + [-78.995263671875, -4.908007812500003], + [-78.97539062499999, -4.873242187500011], + [-78.919189453125, -4.8583984375], + [-78.92578125, -4.770703125000011], + [-78.9076171875, -4.714453125], + [-78.8615234375, -4.6650390625], + [-78.68603515625, -4.562402343750009], + [-78.64799804687499, -4.248144531250006], + [-78.345361328125, -3.397363281250009], + [-78.240380859375, -3.472558593750009], + [-77.860595703125, -2.981640625000011], + [-76.6791015625, -2.562597656250006], + [-76.089794921875, -2.133105468750003], + [-75.570556640625, -1.53125], + [-75.42041015625, -0.962207031250003], + [-75.40805664062499, -0.92431640625], + [-75.24960937499999, -0.951855468750011], + [-75.259375, -0.590136718750003], + [-75.42470703125, -0.408886718750011], + [-75.49106445312499, -0.248339843750003], + [-75.56059570312499, -0.200097656250009], + [-75.63203125, -0.157617187500009], + [-75.62626953124999, -0.122851562500003], + [-75.340478515625, -0.1421875], + [-75.13837890625, -0.050488281250011], + [-74.8017578125, -0.200097656250009], + [-74.78046875, -0.24453125], + [-74.75537109375, -0.298632812500003], + [-74.691650390625, -0.335253906250003], + [-74.616357421875, -0.370019531250009], + [-74.555078125, -0.429882812500011], + [-74.5138671875, -0.470117187500009], + [-74.46518554687499, -0.517675781250006], + [-74.41787109375, -0.580664062500006], + [-74.334423828125, -0.850878906250003], + [-74.28388671875, -0.927832031250006], + [-74.24638671874999, -0.970605468750009], + [-74.05439453125, -1.028613281250003], + [-73.98681640625, -1.09814453125], + [-73.926953125, -1.125195312500011], + [-73.86318359375, -1.196679687500009], + [-73.664306640625, -1.248828125], + [-73.4962890625, -1.693066406250011], + [-73.19697265625, -1.830273437500011], + [-73.1544921875, -2.278222656250009], + [-72.9896484375, -2.339746093750009], + [-72.94111328125, -2.39404296875], + [-72.21845703125, -2.400488281250006], + [-71.98427734375, -2.3265625], + [-71.93247070312499, -2.288671875], + [-71.86728515624999, -2.227734375000011], + [-71.802734375, -2.166308593750003], + [-71.75253906249999, -2.152734375], + [-71.55947265625, -2.22421875], + [-71.39697265625, -2.334082031250006], + [-71.19638671874999, -2.313085937500006], + [-71.11337890624999, -2.245410156250003], + [-71.027294921875, -2.225781250000011], + [-70.96855468749999, -2.206835937500003], + [-70.70537109374999, -2.341992187500011], + [-70.64799804687499, -2.40576171875], + [-70.57587890625, -2.418261718750003], + [-70.29462890625, -2.552539062500003], + [-70.24443359374999, -2.606542968750006], + [-70.16474609375, -2.639843750000011], + [-70.095849609375, -2.658203125], + [-70.735107421875, -3.781542968750003], + [-70.5296875, -3.866406250000011], + [-70.48583984375, -3.869335937500011], + [-70.42109375, -3.849609375], + [-70.37919921874999, -3.81875], + [-70.339501953125, -3.814355468750009], + [-70.2984375, -3.84423828125], + [-70.24028320312499, -3.882714843750009], + [-70.16752929687499, -4.050195312500009], + [-70.0171875, -4.162011718750009], + [-69.96591796874999, -4.2359375], + [-69.97202148437499, -4.301171875], + [-70.00395507812499, -4.327246093750006], + [-70.05332031249999, -4.333105468750006], + [-70.12880859375, -4.28662109375], + [-70.23916015625, -4.301171875], + [-70.31689453125, -4.246972656250009], + [-70.34365234375, -4.193652343750003], + [-70.40463867187499, -4.150097656250011], + [-70.5306640625, -4.167578125], + [-70.72158203125, -4.158886718750011], + [-70.79951171875, -4.17333984375], + [-70.97368164062499, -4.350488281250009], + [-71.8447265625, -4.50439453125], + [-72.256787109375, -4.748925781250009], + [-72.35283203124999, -4.786035156250009], + [-72.468994140625, -4.901269531250009], + [-72.608349609375, -5.009570312500003], + [-72.69873046875, -5.0671875], + [-72.83193359375, -5.09375], + [-72.88706054687499, -5.122753906250011], + [-72.9798828125, -5.634863281250006], + [-73.16289062499999, -5.933398437500003], + [-73.209375, -6.028710937500009], + [-73.235546875, -6.0984375], + [-73.137353515625, -6.4658203125] + ] + ] + }, + "properties": { "name": "Peru", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [120.250390625, 5.256591796875043], + [119.82148437500004, 5.06953125000004], + [120.1652343750001, 5.332421875000037], + [120.250390625, 5.256591796875043] + ] + ], + [ + [ + [121.159375, 6.075634765625011], + [121.41103515625005, 5.939843749999966], + [121.29443359374997, 5.869970703125034], + [120.8763671875, 5.95263671875], + [121.159375, 6.075634765625011] + ] + ], + [ + [ + [122.09287109375012, 6.428320312500006], + [121.95917968750004, 6.415820312500045], + [121.83203125000003, 6.664062499999986], + [122.0583007812501, 6.740722656249972], + [122.32353515625002, 6.602246093750011], + [122.09287109375012, 6.428320312500006] + ] + ], + [ + [ + [122.93710937500006, 7.409130859374983], + [122.80468750000003, 7.315966796875017], + [122.82216796875, 7.428466796875014], + [122.93710937500006, 7.409130859374983] + ] + ], + [ + [ + [117.07988281250007, 7.883398437499977], + [117.02832031249997, 7.807519531249966], + [116.96953125000007, 7.894921875], + [116.9935546875, 8.050537109375014], + [117.07705078125, 8.069140624999974], + [117.07988281250007, 7.883398437499977] + ] + ], + [ + [ + [117.35527343750002, 8.21464843749996], + [117.28701171875, 8.191015625000034], + [117.28085937500006, 8.314990234374974], + [117.35527343750002, 8.21464843749996] + ] + ], + [ + [ + [124.80664062500003, 9.142626953125003], + [124.66582031250002, 9.132324218750043], + [124.65332031250003, 9.225830078125], + [124.80664062500003, 9.142626953125003] + ] + ], + [ + [ + [123.69765625000005, 9.237304687500028], + [123.61445312500004, 9.103320312499989], + [123.49345703125002, 9.192089843750054], + [123.69765625000005, 9.237304687500028] + ] + ], + [ + [ + [126.00595703125006, 9.320947265625009], + [126.19335937499997, 9.276708984374963], + [126.30458984375, 8.952050781249994], + [126.13955078125005, 8.59565429687504], + [126.36533203125012, 8.483886718750014], + [126.45869140625004, 8.20283203125004], + [126.43535156250002, 7.832812499999974], + [126.57011718750002, 7.677246093749986], + [126.58154296875003, 7.247753906249969], + [126.1920898437501, 6.852539062500014], + [126.18935546875, 6.309667968749991], + [125.82441406250004, 7.333300781249989], + [125.68925781250007, 7.263037109374977], + [125.38066406250007, 6.689941406250014], + [125.58847656250012, 6.465771484374997], + [125.66796874999997, 5.97866210937498], + [125.34648437500002, 5.598974609374977], + [125.23154296875006, 6.069531250000011], + [124.92734375000006, 5.875341796874977], + [124.21279296875, 6.233251953124977], + [124.078125, 6.404443359375037], + [123.98525390625, 6.993701171875003], + [124.20664062500006, 7.396435546874983], + [123.66582031250002, 7.817773437500023], + [123.49306640625, 7.80791015624996], + [123.39091796875007, 7.407519531250017], + [123.09667968749997, 7.700439453125], + [122.8429687500001, 7.529296875000043], + [122.79179687500002, 7.72246093749996], + [122.61621093749997, 7.763134765624983], + [122.14248046875, 6.949658203124997], + [121.96425781250005, 6.96821289062504], + [121.92460937500002, 7.199511718750003], + [122.24335937500004, 7.945117187500031], + [122.91113281250003, 8.156445312499997], + [123.05058593750002, 8.433935546875048], + [123.43457031249997, 8.70332031250004], + [123.84921875000006, 8.432714843749977], + [123.79941406250006, 8.049121093749989], + [124.19765625, 8.229541015624974], + [124.40488281250006, 8.599853515625014], + [124.7311523437501, 8.562988281250043], + [124.86894531250002, 8.972265625000034], + [125.141015625, 8.86875], + [125.20966796875004, 9.027148437500017], + [125.49873046875004, 9.014746093749977], + [125.47128906250006, 9.756787109374983], + [126.00595703125006, 9.320947265625009] + ] + ], + [ + [ + [126.059375, 9.766210937500034], + [125.99121093750003, 9.838525390625023], + [126.07382812500006, 10.059228515625051], + [126.1725585937501, 9.79995117187498], + [126.059375, 9.766210937500034] + ] + ], + [ + [ + [124.59384765625006, 9.787207031249963], + [124.1224609375, 9.599316406249969], + [123.93564453125012, 9.623974609375011], + [123.81718750000002, 9.817382812499986], + [124.17285156250003, 10.135205078124983], + [124.33574218750002, 10.159912109375043], + [124.57714843749997, 10.026708984374991], + [124.59384765625006, 9.787207031249963] + ] + ], + [ + [ + [125.69023437500007, 9.914453125000037], + [125.49482421875004, 10.118701171875003], + [125.66679687500002, 10.440136718750026], + [125.69023437500007, 9.914453125000037] + ] + ], + [ + [ + [119.91621093750004, 10.485986328125037], + [119.79316406250004, 10.455273437499997], + [119.85205078124997, 10.64013671875], + [120.00839843750012, 10.570117187500031], + [119.91621093750004, 10.485986328125037] + ] + ], + [ + [ + [122.64951171875012, 10.472705078125003], + [122.53837890625002, 10.424951171875037], + [122.5375, 10.607568359375023], + [122.70126953125006, 10.740625], + [122.64951171875012, 10.472705078125003] + ] + ], + [ + [ + [123.13085937500003, 9.064111328124994], + [122.99472656250006, 9.058837890624986], + [122.8666015625, 9.319824218750043], + [122.5625, 9.482812500000037], + [122.39951171875006, 9.823046874999989], + [122.47148437500007, 9.961523437500034], + [122.85556640625006, 10.0869140625], + [122.81699218750012, 10.503808593750023], + [122.98330078125, 10.886621093750037], + [123.25664062500007, 10.99394531249996], + [123.51064453125005, 10.923046875], + [123.5675781250001, 10.780761718750057], + [123.16201171875, 9.864257812500028], + [123.1498046875, 9.606152343750026], + [123.32050781250004, 9.27294921875], + [123.13085937500003, 9.064111328124994] + ] + ], + [ + [ + [123.37031250000004, 9.449609375000023], + [123.38623046874997, 9.967089843750017], + [124.03886718750002, 11.273535156249991], + [124.00498046875012, 10.40009765625004], + [123.70048828125007, 10.128320312500009], + [123.37031250000004, 9.449609375000023] + ] + ], + [ + [ + [123.75703125000004, 11.28330078125002], + [123.815625, 11.15073242187502], + [123.73671875, 11.151464843749991], + [123.75703125000004, 11.28330078125002] + ] + ], + [ + [ + [117.31113281250012, 8.439599609375051], + [117.21855468750007, 8.367285156249963], + [117.34990234375002, 8.713574218749997], + [119.22382812500004, 10.477294921875043], + [119.30566406250003, 10.9736328125], + [119.55332031250012, 11.31352539062496], + [119.52666015625002, 10.953173828125003], + [119.68691406250005, 10.500341796875034], + [119.36933593750004, 10.327294921875037], + [119.19150390625012, 10.061083984374989], + [118.78212890625005, 9.91611328125002], + [118.4349609375, 9.256005859375009], + [117.31113281250012, 8.439599609375051] + ] + ], + [ + [ + [119.86142578125006, 11.52534179687504], + [119.83066406250012, 11.375683593750011], + [119.72998046874997, 11.431933593750017], + [119.86142578125006, 11.52534179687504] + ] + ], + [ + [ + [124.574609375, 11.343066406250031], + [124.92998046875002, 11.372851562499974], + [125.02656250000004, 11.21171875], + [125.01318359374997, 10.785693359374989], + [125.26845703125005, 10.307714843750048], + [125.14257812499997, 10.189453125000028], + [124.9875, 10.36757812499998], + [125.02656250000004, 10.033105468749966], + [124.78076171874997, 10.16806640625002], + [124.78671875000012, 10.781396484375009], + [124.66269531250006, 10.961962890625017], + [124.44550781250004, 10.923583984375014], + [124.33066406250012, 11.535205078125003], + [124.574609375, 11.343066406250031] + ] + ], + [ + [ + [124.60839843750003, 11.492187500000043], + [124.48349609375006, 11.485839843749986], + [124.36035156250003, 11.665917968749994], + [124.5109375000001, 11.687109375000048], + [124.60839843750003, 11.492187500000043] + ] + ], + [ + [ + [122.49619140625006, 11.615087890625034], + [122.83808593750004, 11.595654296874983], + [122.89453125000003, 11.44130859374998], + [123.15830078125012, 11.53554687499999], + [123.11953125, 11.286816406250026], + [122.8029296875001, 10.99003906249996], + [122.76992187500005, 10.823828125000034], + [121.95400390625, 10.444384765625003], + [122.10351562499997, 11.64291992187502], + [121.91601562499997, 11.854345703125006], + [122.02919921875005, 11.895410156250023], + [122.49619140625006, 11.615087890625034] + ] + ], + [ + [ + [120.03876953125004, 11.703320312499969], + [119.94492187500006, 11.690722656249989], + [119.86093750000006, 11.953955078124963], + [120.03593750000002, 11.917236328125028], + [120.03876953125004, 11.703320312499969] + ] + ], + [ + [ + [120.1, 12.167675781249983], + [120.22822265625004, 12.219824218750034], + [120.31455078125012, 12.012402343749969], + [120.01054687500002, 12.008251953125011], + [119.88574218749997, 12.299853515625003], + [120.1, 12.167675781249983] + ] + ], + [ + [ + [122.65449218750004, 12.309033203125011], + [122.42294921875006, 12.455078125], + [122.60361328125006, 12.49160156249998], + [122.65449218750004, 12.309033203125011] + ] + ], + [ + [ + [125.23955078125002, 12.527880859375003], + [125.32021484375, 12.321826171875031], + [125.53564453125003, 12.191406250000028], + [125.49179687500006, 11.594335937499977], + [125.57353515625002, 11.238232421874997], + [125.73564453125002, 11.049609375000017], + [125.23339843749997, 11.145068359375017], + [125.03427734375012, 11.341259765625026], + [124.91699218750003, 11.558398437500031], + [124.99501953125, 11.764941406250003], + [124.445703125, 12.152783203124969], + [124.29472656250007, 12.569335937500014], + [125.23955078125002, 12.527880859375003] + ] + ], + [ + [ + [123.71660156250007, 12.287353515625028], + [124.04033203125002, 11.966796875], + [124.04550781250012, 11.752441406250028], + [123.47373046875006, 12.21665039062502], + [123.15781250000012, 11.925634765624963], + [123.23642578125012, 12.583496093750057], + [123.71660156250007, 12.287353515625028] + ] + ], + [ + [ + [122.09404296875002, 12.354882812500023], + [122.01396484375002, 12.105615234375037], + [121.9232421875, 12.331298828125014], + [122.00156250000006, 12.598535156250009], + [122.14501953124997, 12.652636718750017], + [122.09404296875002, 12.354882812500023] + ] + ], + [ + [ + [123.77539062499997, 12.453906250000031], + [123.77910156250002, 12.366259765625031], + [123.62148437500005, 12.67490234375002], + [123.77539062499997, 12.453906250000031] + ] + ], + [ + [ + [123.28183593750006, 12.85341796874998], + [123.36718750000003, 12.70083007812498], + [122.95751953124997, 13.107177734374986], + [123.28183593750006, 12.85341796874998] + ] + ], + [ + [ + [120.70439453125002, 13.479492187499986], + [121.20273437500006, 13.432324218749969], + [121.52275390625007, 13.131201171874991], + [121.540625, 12.63818359375], + [121.39433593750002, 12.300585937499974], + [121.23671875000005, 12.218798828125003], + [120.92216796875002, 12.51162109374998], + [120.65136718749997, 13.169140625], + [120.33847656250012, 13.412353515624986], + [120.40126953125, 13.517041015624997], + [120.70439453125002, 13.479492187499986] + ] + ], + [ + [ + [121.91484375000002, 13.540332031250031], + [122.11455078125002, 13.463183593750031], + [122.00488281249997, 13.204980468750009], + [121.82919921875006, 13.328613281249972], + [121.91484375000002, 13.540332031250031] + ] + ], + [ + [ + [124.35361328125006, 13.632226562500009], + [124.17539062500012, 13.531542968750017], + [124.03886718750002, 13.663134765625003], + [124.22490234375007, 14.077587890624969], + [124.41718750000004, 13.871044921874997], + [124.35361328125006, 13.632226562500009] + ] + ], + [ + [ + [122.03349609375002, 15.005029296875009], + [121.93300781250005, 14.656054687500045], + [121.83984374999997, 15.038134765625003], + [122.03349609375002, 15.005029296875009] + ] + ], + [ + [ + [121.10156249999997, 18.615283203125017], + [121.84560546875, 18.29541015625003], + [122.03847656250005, 18.32792968749999], + [122.14667968750004, 18.486572265625], + [122.26552734375005, 18.458837890625034], + [122.15234374999997, 17.664404296875006], + [122.51914062500012, 17.124853515625034], + [122.13515625000005, 16.18481445312503], + [121.59531250000012, 15.933251953125023], + [121.60703125000006, 15.669824218749994], + [121.39228515625004, 15.324414062499969], + [121.69541015625006, 14.7373046875], + [121.62792968749997, 14.581152343749977], + [121.76660156249997, 14.16806640625002], + [122.21171875000002, 13.930175781250057], + [122.2875, 13.996191406250006], + [122.19970703125003, 14.148046875000034], + [122.6271484375001, 14.317529296875009], + [122.93417968750012, 14.18808593750002], + [123.101953125, 13.750244140624986], + [123.29697265625012, 13.836425781250043], + [123.32031249999997, 14.061669921875023], + [123.81572265625002, 13.837109375000011], + [123.80625000000012, 13.721728515625045], + [123.54960937500007, 13.645751953125014], + [123.81923828125, 13.269482421875011], + [123.78515625000003, 13.110546875000054], + [124.14277343750004, 13.035791015625009], + [124.0597656250001, 12.567089843749997], + [123.87783203125005, 12.689697265625014], + [123.94853515625007, 12.916406250000023], + [123.31093750000005, 13.044091796875009], + [123.16328125000004, 13.44174804687502], + [122.59521484374997, 13.90761718749998], + [122.46796875000004, 13.886718749999986], + [122.66787109375, 13.395361328124991], + [122.59990234375002, 13.194140625000031], + [122.37656250000012, 13.520605468750006], + [121.77792968750006, 13.93764648437498], + [121.50107421875006, 13.8421875], + [121.344140625, 13.649121093749997], + [121.09550781250007, 13.679492187500045], + [120.84072265625, 13.884716796875026], + [120.637109375, 13.804492187500031], + [120.61679687500006, 14.188037109375003], + [120.9220703125001, 14.493115234374983], + [120.94130859375, 14.645068359375031], + [120.58369140625004, 14.88125], + [120.58867187500002, 14.483105468749983], + [120.43876953125002, 14.453369140624972], + [120.25078125000002, 14.793310546875034], + [120.08212890625012, 14.851074218749986], + [119.77255859375012, 16.25512695312503], + [119.83076171875004, 16.326562500000023], + [120.15976562500012, 16.047656250000045], + [120.36875, 16.109570312499955], + [120.35839843749997, 17.63818359375], + [120.59970703125012, 18.507861328125074], + [121.10156249999997, 18.615283203125017] + ] + ], + [ + [ + [121.92167968750007, 18.89472656250001], + [121.82519531250003, 18.842724609374983], + [121.94335937500003, 19.010449218749955], + [121.92167968750007, 18.89472656250001] + ] + ], + [ + [ + [121.52089843750005, 19.361962890624994], + [121.53125, 19.271337890625006], + [121.37460937500006, 19.356298828124977], + [121.52089843750005, 19.361962890624994] + ] + ] + ] + }, + "properties": { "name": "Philippines", "childNum": 37 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [134.5954101562501, 7.382031249999969], + [134.51572265625012, 7.525781250000037], + [134.65117187500002, 7.712109374999983], + [134.5954101562501, 7.382031249999969] + ] + ] + }, + "properties": { "name": "Palau", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [153.53613281249997, -11.476171874999949], + [153.75986328125006, -11.586328125], + [153.55371093749997, -11.630566406249969], + [153.28681640625004, -11.516992187500009], + [153.20361328124997, -11.32412109374998], + [153.53613281249997, -11.476171874999949] + ] + ], + [ + [ + [154.28076171874997, -11.36142578125002], + [154.12119140625006, -11.425683593749966], + [154.02343750000003, -11.347949218750031], + [154.28076171874997, -11.36142578125002] + ] + ], + [ + [ + [150.89873046875002, -10.565332031250023], + [150.88466796875, -10.643457031250037], + [150.78574218750006, -10.603417968749966], + [150.89873046875002, -10.565332031250023] + ] + ], + [ + [ + [151.08095703125, -10.020117187499963], + [151.29648437500012, -9.956738281250026], + [151.230859375, -10.194726562500009], + [150.95917968750004, -10.092578124999989], + [150.77607421875004, -9.70908203125002], + [151.08095703125, -10.020117187499963] + ] + ], + [ + [ + [150.52841796875006, -9.34658203124998], + [150.78867187500006, -9.417968749999957], + [150.89404296875003, -9.667480468749986], + [150.43623046875004, -9.624609374999949], + [150.5084960937501, -9.536132812499957], + [150.43730468750007, -9.359960937500034], + [150.52841796875006, -9.34658203124998] + ] + ], + [ + [ + [150.3454101562501, -9.493847656249955], + [150.10976562500005, -9.361914062499991], + [150.20830078125002, -9.206347656250003], + [150.32011718750007, -9.264160156249972], + [150.3454101562501, -9.493847656249955] + ] + ], + [ + [ + [152.63095703125012, -8.959375], + [152.95292968750007, -9.07011718749996], + [152.96689453125006, -9.208984375000014], + [152.51513671874997, -9.009863281250034], + [152.63095703125012, -8.959375] + ] + ], + [ + [ + [151.10683593750005, -8.733496093749949], + [151.12412109375012, -8.804882812500011], + [151.00498046875006, -8.523828124999952], + [151.117578125, -8.41884765624998], + [151.10683593750005, -8.733496093749949] + ] + ], + [ + [ + [143.58681640625005, -8.481738281250003], + [143.321875, -8.367578125], + [143.5814453125, -8.390917968749974], + [143.58681640625005, -8.481738281250003] + ] + ], + [ + [ + [148.02578125, -5.826367187500011], + [147.78105468750007, -5.627246093749946], + [147.7946289062501, -5.492382812500011], + [148.05478515625006, -5.61152343750004], + [148.02578125, -5.826367187500011] + ] + ], + [ + [ + [155.95761718750006, -6.686816406249989], + [155.71933593750012, -6.862792968749957], + [155.34404296875007, -6.721679687499986], + [155.20214843750003, -6.3076171875], + [154.75927734375003, -5.931347656249997], + [154.72929687500002, -5.444433593750006], + [155.09384765625006, -5.620214843750034], + [155.46699218750004, -6.145117187500034], + [155.82255859375002, -6.38046875000002], + [155.95761718750006, -6.686816406249989] + ] + ], + [ + [ + [147.17626953124997, -5.431933593749946], + [147.00585937499997, -5.30703125], + [147.1310546875001, -5.190820312500037], + [147.17626953124997, -5.431933593749946] + ] + ], + [ + [ + [154.64726562500002, -5.43271484375002], + [154.54003906250003, -5.11083984375], + [154.63261718750007, -5.013867187499955], + [154.72714843750006, -5.218066406249989], + [154.64726562500002, -5.43271484375002] + ] + ], + [ + [ + [146.01933593750007, -4.726171874999963], + [145.88359375000007, -4.66748046875], + [145.9958007812501, -4.539257812499983], + [146.01933593750007, -4.726171874999963] + ] + ], + [ + [ + [151.915625, -4.296777343749966], + [152.11718749999997, -4.212207031249974], + [152.40566406250005, -4.340722656249952], + [152.35117187500006, -4.82216796874998], + [151.98369140625007, -5.07441406250004], + [152.14296875, -5.357031249999963], + [152.07705078125, -5.458300781249989], + [151.86542968750004, -5.564843750000023], + [151.51513671874997, -5.552343749999963], + [151.22929687500002, -5.919921874999986], + [150.47353515625, -6.263378906249969], + [149.65253906250004, -6.290429687499966], + [149.38232421874997, -6.078125], + [149.0990234375, -6.116992187499989], + [148.33720703125007, -5.669433593750014], + [148.43203125, -5.471777343749991], + [149.35888671875003, -5.583984375000014], + [149.8314453125, -5.524121093749997], + [149.96279296875, -5.447753906249972], + [150.0900390625001, -5.011816406249977], + [150.1703125, -5.070605468749974], + [150.0724609375001, -5.309570312499986], + [150.18310546874997, -5.523632812499983], + [150.90029296875005, -5.447167968750037], + [151.32656250000005, -4.96035156249998], + [151.67119140625007, -4.88330078125], + [151.59306640625007, -4.200781249999949], + [151.915625, -4.296777343749966] + ] + ], + [ + [ + [152.67060546875004, -3.13339843750002], + [152.64619140625004, -3.221191406249957], + [152.54326171875002, -3.095605468749952], + [152.63876953125012, -3.042773437500031], + [152.67060546875004, -3.13339843750002] + ] + ], + [ + [ + [140.97617187500012, -9.11875], + [140.97519531250006, -6.90537109375002], + [140.86230468749997, -6.740039062499989], + [140.975, -6.346093750000023], + [140.97353515625, -2.803417968750026], + [140.97343750000007, -2.609765625], + [142.90517578125, -3.32070312499998], + [143.50898437500004, -3.431152343750014], + [144.06640625000003, -3.80517578125], + [144.4777343750001, -3.82529296875002], + [145.08779296875, -4.349121093749972], + [145.33457031250012, -4.385253906249972], + [145.7669921875, -4.823046874999989], + [145.74521484375012, -5.402441406249977], + [147.56669921875002, -6.056933593750003], + [147.80205078125002, -6.31523437499996], + [147.84550781250007, -6.662402343749989], + [147.11914062499997, -6.721679687499986], + [146.95361328124997, -6.834082031249963], + [147.19003906250012, -7.378125], + [148.12675781250007, -8.103613281249963], + [148.246875, -8.554296875000034], + [148.45117187499997, -8.694531250000011], + [148.58310546875006, -9.051757812499957], + [149.19833984375006, -9.03125], + [149.26318359374997, -9.497851562499974], + [150.01103515625007, -9.688183593750026], + [149.76123046874997, -9.805859375000011], + [149.87441406250005, -10.012988281250031], + [150.84951171875, -10.236035156249997], + [150.44609375000007, -10.30732421875004], + [150.6471679687501, -10.517968749999966], + [150.31992187500012, -10.654882812499963], + [150.0167968750001, -10.577148437500028], + [149.75410156250004, -10.353027343750028], + [147.76865234375012, -10.070117187500031], + [147.01718750000006, -9.38789062500004], + [146.96376953125, -9.059570312499943], + [146.63085937499997, -8.951171874999972], + [146.03320312499997, -8.076367187500011], + [144.97382812500004, -7.802148437500009], + [144.86425781249997, -7.631542968749983], + [144.50986328125006, -7.567382812499972], + [144.14287109375007, -7.757226562500009], + [143.65488281250012, -7.460351562500009], + [143.94228515625005, -7.944238281250009], + [143.8333984375, -8.029101562499974], + [143.51816406250006, -8.000683593749955], + [143.61376953125003, -8.200390624999969], + [142.52412109375004, -8.32167968749998], + [142.34746093750002, -8.167480468750014], + [142.20683593750002, -8.195800781250014], + [142.47480468750004, -8.369433593750031], + [142.79794921875006, -8.345019531250031], + [143.11181640624997, -8.474511718750037], + [143.37724609375007, -8.762207031250028], + [143.36621093750003, -8.961035156250034], + [142.6471679687501, -9.327832031249969], + [142.22958984375012, -9.169921874999957], + [141.13320312500005, -9.221289062500034], + [140.97617187500012, -9.11875] + ] + ], + [ + [ + [152.96582031249997, -4.756347656249986], + [152.89169921875006, -4.832421875000023], + [152.73994140625004, -4.635839843750034], + [152.66816406250004, -4.131835937500028], + [152.27939453125006, -3.582421875], + [151.06679687500005, -2.829003906249994], + [150.74609374999997, -2.73886718750002], + [150.8253906250001, -2.572949218749969], + [152.03291015625004, -3.25136718749998], + [153.01679687500004, -4.105664062500026], + [153.1325195312501, -4.352441406250037], + [152.96582031249997, -4.756347656249986] + ] + ], + [ + [ + [150.43662109375012, -2.66181640625004], + [150.16572265625004, -2.660253906249991], + [149.96162109375004, -2.473828125000026], + [150.22714843750006, -2.384179687499966], + [150.42949218750007, -2.47041015625004], + [150.43662109375012, -2.66181640625004] + ] + ], + [ + [ + [147.06757812500004, -1.96015625], + [147.43808593750012, -2.05898437499998], + [147.20634765625007, -2.181933593749974], + [146.54648437500012, -2.20859375], + [146.65625, -1.97402343749998], + [147.06757812500004, -1.96015625] + ] + ], + [ + [ + [149.76542968750007, -1.553027343750017], + [149.54589843749997, -1.471679687499957], + [149.58095703125005, -1.353222656249983], + [149.76542968750007, -1.553027343750017] + ] + ] + ] + }, + "properties": { "name": "Papua New Guinea", "childNum": 21 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [23.484667968750017, 53.939794921875], + [23.915429687500023, 52.770263671875], + [23.175097656250017, 52.28662109375], + [23.652441406250006, 52.040380859375], + [23.605273437500017, 51.517919921875], + [23.664453125000023, 51.31005859375], + [24.095800781250006, 50.87275390625], + [23.9970703125, 50.809375], + [24.089941406250006, 50.53046875], + [23.97265625, 50.410058593749994], + [23.711718750000017, 50.37734375], + [23.03632812500001, 49.899072265624994], + [22.706152343750006, 49.606201171875], + [22.6494140625, 49.539013671875], + [22.66064453125, 49.483691406249996], + [22.71992187500001, 49.353808593749996], + [22.732421875, 49.295166015625], + [22.705664062500006, 49.171191406249996], + [22.847070312500023, 49.08125], + [22.538671875, 49.072705078125], + [22.473046875000023, 49.081298828125], + [22.020117187500006, 49.209521484374996], + [21.6396484375, 49.411962890625], + [21.079394531250017, 49.418261718749996], + [20.868457031250017, 49.314697265625], + [20.36298828125001, 49.38525390625], + [20.0576171875, 49.181298828124994], + [19.756640625000017, 49.204394531249996], + [19.77392578125, 49.37216796875], + [19.44160156250001, 49.597705078124996], + [19.1494140625, 49.4], + [18.83222656250001, 49.510791015624996], + [18.562402343750023, 49.879345703125], + [18.0283203125, 50.03525390625], + [17.874804687500017, 49.972265625], + [17.627050781250006, 50.11640625], + [17.702246093750006, 50.307177734374996], + [17.41523437500001, 50.254785156249994], + [16.88007812500001, 50.427050781249996], + [16.989648437500023, 50.2369140625], + [16.63916015625, 50.1021484375], + [16.210351562500023, 50.423730468749994], + [16.419726562500017, 50.573632812499994], + [16.2822265625, 50.655615234375], + [16.007226562500023, 50.611621093749996], + [14.99375, 51.01435546875], + [14.98291015625, 50.886572265625], + [14.895800781250017, 50.861376953124996], + [14.809375, 50.858984375], + [14.814257812500017, 50.871630859374996], + [14.91748046875, 51.008740234375], + [14.9638671875, 51.095117187499994], + [14.935546875, 51.435351562499996], + [14.905957031250011, 51.463330078125], + [14.724707031250006, 51.523876953125], + [14.7109375, 51.544921875], + [14.738671875000023, 51.6271484375], + [14.601660156250006, 51.832373046875], + [14.752539062500006, 52.081835937499996], + [14.679882812500011, 52.25], + [14.615625, 52.277636718749996], + [14.573925781250011, 52.31416015625], + [14.554589843750023, 52.359667968749996], + [14.569726562500023, 52.431103515625], + [14.619433593750017, 52.528515625], + [14.514062500000023, 52.64560546875], + [14.253710937500017, 52.782519531249996], + [14.128613281250011, 52.878222656249996], + [14.138867187500011, 52.932861328125], + [14.293164062500011, 53.0267578125], + [14.368554687500023, 53.10556640625], + [14.410937500000017, 53.199023437499996], + [14.412304687500011, 53.216748046875], + [14.41455078125, 53.283496093749996], + [14.258886718750006, 53.729638671875], + [14.58349609375, 53.63935546875], + [14.558398437500017, 53.823193359375], + [14.21142578125, 53.950341796875], + [16.186328125000017, 54.290380859375], + [16.55976562500001, 54.55380859375], + [18.32343750000001, 54.838183593749996], + [18.75927734375, 54.6845703125], + [18.43623046875001, 54.7447265625], + [18.83642578125, 54.369580078125], + [19.604394531250023, 54.4591796875], + [20.20820312500001, 54.420751953125], + [22.16845703125, 54.35986328125], + [22.731835937500023, 54.35009765625], + [22.766210937500006, 54.356787109375], + [22.82373046875, 54.395800781249996], + [22.893945312500023, 54.39052734375], + [22.97675781250001, 54.366357421875], + [23.015527343750023, 54.34833984375], + [23.04218750000001, 54.30419921875], + [23.0875, 54.299462890625], + [23.170312500000023, 54.2814453125], + [23.282324218750006, 54.24033203125], + [23.3701171875, 54.200488281249996], + [23.45361328125, 54.14345703125], + [23.484667968750017, 53.939794921875] + ] + ] + }, + "properties": { "name": "Poland", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-65.42558593749999, 18.105615234374994], + [-65.57221679687493, 18.137304687499977], + [-65.29487304687501, 18.133349609375045], + [-65.42558593749999, 18.105615234374994] + ] + ], + [ + [ + [-66.12939453125003, 18.444921875000034], + [-65.62880859375, 18.381396484375045], + [-65.62084960937497, 18.242333984374966], + [-65.97080078124995, 17.974365234375], + [-67.196875, 17.994189453125045], + [-67.2640625, 18.364599609375006], + [-67.15864257812501, 18.499218749999983], + [-66.12939453125003, 18.444921875000034] + ] + ] + ] + }, + "properties": { "name": "Puerto Rico", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [130.52695312500012, 42.535400390625], + [130.68730468750007, 42.30253906249999], + [130.2357421875, 42.183203125000034], + [129.75634765624997, 41.712255859375006], + [129.70869140625004, 40.857324218749994], + [129.34111328125002, 40.72631835937506], + [128.51123046874997, 40.130224609375006], + [127.56816406250002, 39.78198242187503], + [127.39453125000003, 39.207910156249966], + [127.78613281250003, 39.084130859374966], + [128.37460937500012, 38.6234375], + [128.03896484375, 38.30854492187498], + [127.09033203125003, 38.28388671875001], + [126.63388671875012, 37.78183593750006], + [126.36992187500007, 37.87836914062501], + [126.11669921875003, 37.74291992187503], + [125.76914062500006, 37.98535156250003], + [125.35781250000005, 37.72480468749998], + [125.31074218750004, 37.843505859375], + [124.98876953124997, 37.93144531249999], + [125.2067382812501, 38.08154296875], + [124.69091796874997, 38.12919921875002], + [125.06738281250003, 38.556738281250006], + [125.55449218750002, 38.68623046875001], + [125.16884765625, 38.80551757812506], + [125.40966796875003, 39.28837890625002], + [125.36083984375003, 39.52661132812497], + [124.77529296875, 39.75805664062506], + [124.63828125000006, 39.61508789062506], + [124.36210937500002, 40.004052734374994], + [124.8893554687501, 40.459814453125006], + [125.98906250000002, 40.904638671875034], + [126.74306640625, 41.724853515625], + [126.95478515625004, 41.76948242187501], + [127.17968750000003, 41.531347656250006], + [128.14941406249997, 41.38774414062496], + [128.28925781250004, 41.60742187500006], + [128.04521484375007, 41.9875], + [128.92343750000006, 42.038232421874966], + [129.3136718750001, 42.41357421874997], + [129.69785156250012, 42.448144531249994], + [129.89824218750002, 42.998144531250034], + [130.24033203125006, 42.891796874999955], + [130.24667968750012, 42.744824218749955], + [130.52695312500012, 42.535400390625] + ] + ] + }, + "properties": { "name": "Dem. Rep. Korea", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-17.190869140624926, 32.86860351562498], + [-16.693261718749966, 32.75800781250001], + [-17.018261718749926, 32.66279296874998], + [-17.226025390624983, 32.76684570312503], + [-17.190869140624926, 32.86860351562498] + ] + ], + [ + [ + [-25.64897460937499, 37.840917968750006], + [-25.18193359374996, 37.837890625], + [-25.19072265624999, 37.764355468749955], + [-25.73447265624992, 37.76289062500001], + [-25.845898437499983, 37.89404296875], + [-25.64897460937499, 37.840917968750006] + ] + ], + [ + [ + [-28.14726562499996, 38.45268554687502], + [-28.064794921875034, 38.412744140624966], + [-28.454492187500023, 38.40864257812504], + [-28.54882812499997, 38.51855468750003], + [-28.14726562499996, 38.45268554687502] + ] + ], + [ + [ + [-28.641308593749983, 38.525], + [-28.842041015625, 38.5984375], + [-28.69775390625, 38.638476562500045], + [-28.641308593749983, 38.525] + ] + ], + [ + [ + [-27.07524414062496, 38.643457031249994], + [-27.38593750000001, 38.765820312499955], + [-27.127001953125017, 38.78984375], + [-27.07524414062496, 38.643457031249994] + ] + ], + [ + [ + [-31.137109374999937, 39.40693359375001], + [-31.282958984375, 39.39409179687496], + [-31.260839843750034, 39.49677734375001], + [-31.137109374999937, 39.40693359375001] + ] + ], + [ + [ + [-7.406152343749937, 37.17944335937497], + [-7.834130859374994, 37.005712890625034], + [-8.597656249999943, 37.12133789062506], + [-8.997802734375028, 37.03227539062502], + [-8.814160156249983, 37.43081054687502], + [-8.881103515624943, 38.44667968750005], + [-8.668310546874949, 38.42431640625003], + [-8.798876953124989, 38.518164062500034], + [-9.213281249999937, 38.44809570312498], + [-9.250390624999966, 38.65673828125003], + [-9.021484374999943, 38.746875], + [-8.79160156249992, 39.07817382812502], + [-9.13579101562496, 38.74277343749998], + [-9.35673828124996, 38.697900390624994], + [-9.479736328124972, 38.79877929687501], + [-9.374755859374972, 39.338281249999966], + [-8.837841796874926, 40.11567382812498], + [-8.684619140624989, 40.75253906250006], + [-8.755419921874932, 41.69838867187502], + [-8.887597656249937, 41.76459960937501], + [-8.777148437500017, 41.941064453124994], + [-8.266064453124983, 42.13740234375001], + [-8.152490234374937, 41.81196289062498], + [-7.40361328124996, 41.833691406249955], + [-7.147119140625023, 41.98115234374998], + [-6.61826171874992, 41.9423828125], + [-6.542187499999955, 41.672509765624994], + [-6.2125, 41.53203125], + [-6.928466796874972, 41.009130859375006], + [-6.8101562499999, 40.343115234375034], + [-7.032617187499966, 40.16791992187498], + [-6.896093749999949, 40.02182617187506], + [-6.975390624999932, 39.79838867187502], + [-7.117675781249972, 39.681689453125045], + [-7.53569335937496, 39.66157226562501], + [-6.997949218749994, 39.05644531250002], + [-7.343017578124943, 38.45742187500002], + [-7.106396484374983, 38.181005859375006], + [-6.957568359374932, 38.18789062499999], + [-7.44394531249992, 37.72827148437497], + [-7.406152343749937, 37.17944335937497] + ] + ] + ] + }, + "properties": { "name": "Portugal", "childNum": 7, "cp": [-8.7440694, 39.9251454] } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-58.15976562499999, -20.164648437500006], + [-58.13779296874999, -20.2373046875], + [-58.12460937499999, -20.29345703125], + [-58.09150390625, -20.33320312500001], + [-58.05844726562499, -20.38613281250001], + [-58.025390625, -20.415820312500003], + [-58.00224609374999, -20.465429687500006], + [-57.97905273437499, -20.657324218750006], + [-57.91513671874999, -20.69033203125001], + [-57.830224609374994, -20.99794921875001], + [-57.94267578124999, -21.79833984375], + [-57.95590820312499, -22.109179687500003], + [-56.77519531249999, -22.261328125], + [-56.44780273437499, -22.076171875], + [-56.39487304687499, -22.09267578125001], + [-56.35185546874999, -22.17861328125001], + [-56.246044921875, -22.2646484375], + [-56.18984375, -22.28115234375001], + [-55.99140625, -22.28115234375001], + [-55.84916992187499, -22.3076171875], + [-55.75327148437499, -22.41015625], + [-55.74663085937499, -22.5126953125], + [-55.61767578125, -22.671484375], + [-55.53828125, -23.58095703125001], + [-55.518457031249994, -23.627246093750003], + [-55.458886718749994, -23.68671875000001], + [-55.4423828125, -23.792578125], + [-55.4423828125, -23.865332031250006], + [-55.415917968749994, -23.95136718750001], + [-55.36630859374999, -23.991015625], + [-55.28691406249999, -24.004296875], + [-55.1943359375, -24.01748046875001], + [-55.08188476562499, -23.99765625], + [-54.982666015625, -23.97451171875001], + [-54.62548828125, -23.8125], + [-54.44023437499999, -23.90175781250001], + [-54.37080078125, -23.97119140625], + [-54.24179687499999, -24.047265625], + [-54.281005859375, -24.30605468750001], + [-54.43623046875, -25.12128906250001], + [-54.47314453125, -25.22021484375], + [-54.610546875, -25.432714843750006], + [-54.615869140624994, -25.57607421875001], + [-54.63193359374999, -26.00576171875001], + [-54.677734375, -26.30878906250001], + [-54.934472656249994, -26.70253906250001], + [-55.1359375, -26.93115234375], + [-55.426660156249994, -27.00927734375], + [-55.450634765625, -27.068359375], + [-55.496728515624994, -27.115332031250006], + [-55.564892578125, -27.15], + [-55.59726562499999, -27.207617187500006], + [-55.59379882812499, -27.2880859375], + [-55.63291015624999, -27.35712890625001], + [-55.71464843749999, -27.41484375], + [-55.789990234375, -27.41640625], + [-55.95146484374999, -27.32568359375], + [-56.1640625, -27.32148437500001], + [-56.437158203124994, -27.553808593750006], + [-58.16826171874999, -27.2734375], + [-58.60483398437499, -27.31435546875001], + [-58.641748046874994, -27.19609375], + [-58.618603515625, -27.132128906250003], + [-58.222070312499994, -26.65], + [-58.18149414062499, -26.307421875], + [-57.943115234375, -26.05292968750001], + [-57.563134765624994, -25.473730468750006], + [-57.821679687499994, -25.13642578125001], + [-59.187255859375, -24.562304687500003], + [-59.892480468749994, -24.093554687500003], + [-60.83984375, -23.85810546875001], + [-61.084716796875, -23.65644531250001], + [-61.79853515625, -23.18203125], + [-62.21416015624999, -22.612402343750006], + [-62.372509765625, -22.43916015625001], + [-62.54155273437499, -22.349609375], + [-62.6259765625, -22.29042968750001], + [-62.62568359375, -22.261523437500003], + [-62.65097656249999, -22.233691406250003], + [-62.27666015624999, -21.066015625], + [-62.276318359375, -20.5625], + [-61.7568359375, -19.6453125], + [-60.00737304687499, -19.29755859375001], + [-59.09052734375, -19.286230468750006], + [-58.18017578125, -19.81787109375], + [-58.15976562499999, -20.164648437500006] + ] + ] + }, + "properties": { "name": "Paraguay", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [34.34833984375004, 31.292919921874955], + [34.2453125000001, 31.208300781250045], + [34.2125, 31.292285156250017], + [34.198144531249994, 31.322607421875063], + [34.47734375000002, 31.584863281250023], + [34.52412109375004, 31.541650390624994], + [34.5255859375001, 31.52563476562503], + [34.34833984375004, 31.292919921874955] + ] + ], + [ + [ + [34.88046875, 31.3681640625], + [34.950976562500074, 31.60229492187503], + [35.20371093750006, 31.75], + [35.1271484375001, 31.816748046875006], + [35.05322265625003, 31.83793945312496], + [34.983007812500006, 31.816796875000023], + [34.9611328125001, 31.823339843750006], + [34.95380859375004, 31.84125976562504], + [34.98974609374997, 31.913281249999955], + [34.955957031249994, 32.1609375], + [35.01054687500002, 32.33818359375002], + [35.06503906250006, 32.46044921875006], + [35.19326171875005, 32.53442382812503], + [35.303808593750006, 32.512939453125], + [35.38671875000003, 32.493017578125034], + [35.402636718750074, 32.45063476562501], + [35.484375, 32.40166015624999], + [35.5514648437501, 32.39550781250006], + [35.57207031250002, 32.237890625], + [35.450585937499994, 31.479296875000017], + [34.88046875, 31.3681640625] + ] + ] + ] + }, + "properties": { "name": "Palestine", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-149.321533203125, -17.690039062499963], + [-149.177685546875, -17.736621093750045], + [-149.18178710937497, -17.86230468749997], + [-149.34111328125, -17.732421874999986], + [-149.57890624999993, -17.734960937499963], + [-149.635009765625, -17.564257812500003], + [-149.37919921874993, -17.522363281249994], + [-149.321533203125, -17.690039062499963] + ] + ], + [ + [ + [-143.44057617187497, -16.619726562499963], + [-143.38618164062498, -16.668847656250023], + [-143.55068359375002, -16.62109374999997], + [-143.44057617187497, -16.619726562499963] + ] + ], + [ + [ + [-139.02431640624997, -9.695214843750037], + [-138.82734375, -9.74160156249998], + [-139.13408203124996, -9.829492187500037], + [-139.02431640624997, -9.695214843750037] + ] + ], + [ + [ + [-140.075634765625, -9.425976562499983], + [-140.14438476562498, -9.359375], + [-140.07094726562497, -9.328125], + [-140.075634765625, -9.425976562499983] + ] + ], + [ + [ + [-140.07260742187503, -8.910449218750031], + [-140.21743164062497, -8.929687499999957], + [-140.24003906249993, -8.79755859375004], + [-140.057666015625, -8.801464843750026], + [-140.07260742187503, -8.910449218750031] + ] + ] + ] + }, + "properties": { "name": "Fr. Polynesia", "childNum": 5 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [51.26796875000002, 24.607226562500003], + [51.17802734375002, 24.58671875], + [51.093359375, 24.564648437499997], + [51.02275390625002, 24.565234375], + [50.96601562500001, 24.573925781249997], + [50.928320312500006, 24.595117187499994], + [50.85566406250001, 24.679638671874997], + [50.80439453125001, 24.789257812499997], + [50.8359375, 24.850390625], + [50.846777343750006, 24.888574218749994], + [50.75458984375001, 25.39926757812499], + [51.003125, 25.9814453125], + [51.262304687500006, 26.153271484374997], + [51.543066406250006, 25.902392578125003], + [51.4853515625, 25.524707031250003], + [51.60888671875, 25.052880859374994], + [51.42792968750001, 24.668261718750003], + [51.26796875000002, 24.607226562500003] + ] + ] + }, + "properties": { "name": "Qatar", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [28.2125, 45.450439453125], + [28.317675781250017, 45.347119140625], + [28.451269531250006, 45.2921875], + [28.78828125000001, 45.240966796875], + [28.78173828125, 45.309863281249996], + [28.894335937500017, 45.289941406249994], + [29.223535156250023, 45.4029296875], + [29.403710937500023, 45.419677734375], + [29.567675781250017, 45.37080078125], + [29.705859375000017, 45.259912109374994], + [29.557519531250023, 44.843408203124994], + [29.048242187500023, 44.757568359375], + [29.0953125, 44.975048828125], + [28.891503906250023, 44.91865234375], + [28.585351562500023, 43.742236328124996], + [28.221972656250017, 43.772851562499994], + [27.88427734375, 43.987353515624996], + [27.425390625, 44.0205078125], + [27.0869140625, 44.167382812499994], + [26.2158203125, 44.007275390625], + [25.4970703125, 43.670800781249994], + [22.919042968750006, 43.83447265625], + [22.868261718750006, 43.947900390624994], + [23.02851562500001, 44.077978515625], + [22.705078125, 44.23779296875], + [22.687890625000023, 44.248291015625], + [22.494531250000023, 44.435449218749994], + [22.554003906250017, 44.540332031249996], + [22.6201171875, 44.562353515625], + [22.70078125, 44.555517578125], + [22.734375, 44.569921875], + [22.72089843750001, 44.605517578124996], + [22.64208984375, 44.6509765625], + [22.49765625, 44.70625], + [22.350683593750006, 44.676123046875], + [22.200976562500017, 44.560693359374994], + [22.093066406250017, 44.541943359375], + [21.909277343750006, 44.66611328125], + [21.636132812500023, 44.71044921875], + [21.52314453125001, 44.790087890624996], + [21.36005859375001, 44.82666015625], + [21.35791015625, 44.86181640625], + [21.384375, 44.870068359375], + [21.442187500000017, 44.873388671875], + [21.519921875000023, 44.880810546875], + [21.532324218750006, 44.900683593749996], + [21.35703125, 44.990771484374996], + [21.465429687500006, 45.171875], + [21.431445312500017, 45.192529296874994], + [20.794042968750006, 45.46787109375], + [20.775, 45.749804687499996], + [20.760156250000023, 45.758105468749996], + [20.746875, 45.748974609375], + [20.727832031250017, 45.73740234375], + [20.709277343750017, 45.735253906249994], + [20.652734375000023, 45.77939453125], + [20.581152343750006, 45.869482421875], + [20.35859375000001, 45.975488281249994], + [20.241796875, 46.10859375], + [20.28095703125001, 46.1330078125], + [20.508105468750017, 46.166943359375], + [20.613671875000023, 46.13349609375], + [20.76025390625, 46.246240234374994], + [21.121679687500006, 46.282421875], + [21.99970703125001, 47.505029296874994], + [22.87666015625001, 47.947265625], + [23.054785156250006, 48.00654296875], + [23.139453125000017, 48.08740234375], + [23.20263671875, 48.084521484374996], + [23.408203125, 47.989990234375], + [23.628710937500017, 47.995849609375], + [24.578906250000017, 47.931054687499994], + [24.979101562500006, 47.72412109375], + [25.464257812500023, 47.910791015624994], + [25.689257812500017, 47.932470703125], + [25.90869140625, 47.967578125], + [26.162695312500006, 47.992529296875], + [26.236230468750023, 48.064355468749994], + [26.276953125, 48.113232421875], + [26.3056640625, 48.203759765624994], + [26.4423828125, 48.22998046875], + [26.618945312500017, 48.25986328125], + [26.980761718750017, 48.155029296875], + [27.614062500000017, 47.34052734375], + [28.07177734375, 46.978417968749994], + [28.23945312500001, 46.6408203125], + [28.07470703125, 45.598974609375], + [28.2125, 45.450439453125] + ] + ] + }, + "properties": { "name": "Romania", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [146.71396484375012, 43.743798828124994], + [146.62197265625, 43.81298828125006], + [146.88408203125002, 43.82915039062496], + [146.71396484375012, 43.743798828124994] + ] + ], + [ + [ + [146.20761718750006, 44.49765625], + [146.5677734375, 44.44042968749997], + [145.91406249999997, 44.10371093750004], + [145.58681640625, 43.84511718750002], + [145.5558593750001, 43.66459960937502], + [145.46171875000007, 43.870898437500045], + [146.20761718750006, 44.49765625] + ] + ], + [ + [ + [148.59951171875, 45.317626953125], + [147.91376953125004, 44.99038085937502], + [147.65781250000012, 44.97714843749998], + [146.89746093750003, 44.404296875], + [147.24658203124997, 44.856054687500006], + [147.88554687500007, 45.22563476562499], + [147.9240234375001, 45.38330078125006], + [148.05605468750005, 45.26210937500005], + [148.32421874999997, 45.28242187500001], + [148.8122070312501, 45.510009765625], + [148.83710937500004, 45.36269531250002], + [148.59951171875, 45.317626953125] + ] + ], + [ + [ + [149.68769531250004, 45.64204101562501], + [149.44707031250002, 45.593359375000034], + [149.9623046875, 46.02192382812504], + [150.553125, 46.208544921875045], + [149.68769531250004, 45.64204101562501] + ] + ], + [ + [ + [152.00205078125006, 46.89716796874998], + [151.72343750000007, 46.82880859375001], + [152.28886718750007, 47.1421875], + [152.00205078125006, 46.89716796874998] + ] + ], + [ + [ + [154.81044921875005, 49.31201171875], + [154.61093750000006, 49.29404296874998], + [154.82490234375004, 49.64692382812501], + [154.81044921875005, 49.31201171875] + ] + ], + [ + [ + [155.9210937500001, 50.30219726562501], + [155.39716796875004, 50.04125976562497], + [155.24306640625, 50.09462890625002], + [155.21835937500012, 50.29785156250003], + [155.68017578124997, 50.400732421875034], + [156.096875, 50.771875], + [155.9210937500001, 50.30219726562501] + ] + ], + [ + [ + [156.40507812500002, 50.65761718750005], + [156.16796874999997, 50.73188476562498], + [156.37646484374997, 50.86210937499996], + [156.4875, 50.84296874999998], + [156.40507812500002, 50.65761718750005] + ] + ], + [ + [ + [142.76103515625002, 54.393945312499966], + [143.32470703125003, 52.96308593749998], + [143.15556640625002, 52.08374023437497], + [143.29951171875004, 51.632373046875045], + [143.81601562500006, 50.28261718750002], + [144.71376953125, 48.64028320312502], + [144.04873046875, 49.249169921874994], + [143.73232421875, 49.31201171875], + [143.10498046875003, 49.198828125000034], + [142.57421874999997, 48.07216796875002], + [142.55693359375002, 47.737890625000034], + [143.21767578125005, 46.79487304687504], + [143.48564453125002, 46.752050781250006], + [143.58066406250012, 46.360693359375034], + [143.43164062500003, 46.02866210937498], + [143.28232421875006, 46.55898437500002], + [142.57802734375005, 46.700781250000034], + [142.07714843749997, 45.91704101562499], + [141.83037109375002, 46.451074218749966], + [142.03867187500012, 47.140283203124966], + [141.9640625000001, 47.58745117187502], + [142.18173828125012, 48.01337890625001], + [141.86630859375006, 48.750097656250006], + [142.1422851562501, 49.56914062499999], + [142.06601562500006, 50.630468750000034], + [142.20673828125004, 51.22255859375002], + [141.72236328125004, 51.73632812499997], + [141.66083984375004, 52.27294921874997], + [141.85556640625012, 52.79350585937499], + [141.82353515625007, 53.33950195312502], + [142.1419921875, 53.49560546875003], + [142.52617187500002, 53.44746093749998], + [142.70595703125, 53.89570312499998], + [142.33496093749997, 54.28071289062501], + [142.76103515625002, 54.393945312499966] + ] + ], + [ + [ + [137.17861328125005, 55.100439453125034], + [137.05527343750006, 54.9267578125], + [136.71464843750002, 54.956152343750034], + [137.17861328125005, 55.100439453125034] + ] + ], + [ + [ + [137.94052734375012, 55.092626953125034], + [138.20615234375012, 55.03354492187498], + [137.72148437500007, 54.66323242187505], + [137.46269531250002, 54.873388671875034], + [137.23291015624997, 54.79057617187496], + [137.5773437500001, 55.19702148437497], + [137.94052734375012, 55.092626953125034] + ] + ], + [ + [ + [21.235742187500023, 55.26411132812498], + [22.072363281250034, 55.06367187499998], + [22.56728515625005, 55.05913085937496], + [22.82470703125, 54.87128906249998], + [22.684472656250023, 54.56293945312504], + [22.679882812500068, 54.493017578125006], + [22.766210937499977, 54.356787109375034], + [22.168457031250057, 54.35986328125006], + [21.14052734375008, 54.39179687499998], + [19.604394531250023, 54.45917968750004], + [19.974511718750023, 54.92119140625002], + [20.520312500000017, 54.994873046875], + [20.89980468750008, 55.286669921875045], + [20.957812500000074, 55.27890625000006], + [20.594824218750006, 54.982373046874955], + [20.995898437500017, 54.90268554687506], + [21.18886718750008, 54.93520507812502], + [21.235742187500023, 55.26411132812498] + ] + ], + [ + [ + [166.65029296875005, 54.83906249999998], + [166.64511718750006, 54.69409179687503], + [165.75107421875006, 55.294531250000034], + [166.27578125000005, 55.311962890624955], + [166.24804687499997, 55.16542968750002], + [166.65029296875005, 54.83906249999998] + ] + ], + [ + [ + [150.58994140625006, 59.01875], + [150.47021484375003, 59.05405273437498], + [150.66621093750004, 59.16015625000003], + [150.58994140625006, 59.01875] + ] + ], + [ + [ + [163.63515625000005, 58.603369140625006], + [163.47138671875004, 58.509375], + [163.7609375000001, 59.01503906250002], + [164.57265625, 59.22114257812501], + [164.61572265624997, 58.885595703125034], + [163.63515625000005, 58.603369140625006] + ] + ], + [ + [ + [35.8161132812501, 65.18208007812501], + [35.77871093750005, 64.97666015625], + [35.52890625000006, 65.15107421875001], + [35.8161132812501, 65.18208007812501] + ] + ], + [ + [ + [70.02070312500004, 66.502197265625], + [69.65136718750003, 66.56533203125], + [69.50273437500002, 66.75107421875], + [70.07666015624997, 66.69589843750003], + [70.02070312500004, 66.502197265625] + ] + ], + [ + [ + [-179.79853515625, 68.9404296875], + [-178.873876953125, 68.75410156249995], + [-178.69262695312503, 68.54599609375], + [-178.09746093750002, 68.4248046875], + [-178.05581054687497, 68.26489257812503], + [-177.79677734374997, 68.33798828125], + [-178.37304687500003, 68.56567382812503], + [-177.52724609375002, 68.29438476562501], + [-177.58920898437503, 68.22421875], + [-175.34521484375, 67.67807617187503], + [-175.37470703124998, 67.35737304687498], + [-175.00268554687494, 67.4375], + [-174.849853515625, 67.34887695312503], + [-174.92490234375, 66.62314453125006], + [-174.503759765625, 66.537939453125], + [-174.39409179687496, 66.34423828124997], + [-174.084765625, 66.47309570312504], + [-174.06503906249998, 66.22958984374998], + [-173.77397460937502, 66.43466796875003], + [-174.23159179687497, 66.63188476562505], + [-174.08642578125, 66.94287109375], + [-174.55009765624993, 67.090625], + [-173.6796875, 67.144775390625], + [-173.15781249999998, 67.06909179687503], + [-173.32353515625, 66.95483398437503], + [-173.25893554687497, 66.84008789062503], + [-173.19301757812497, 66.99360351562504], + [-172.5201171875, 66.952490234375], + [-173.00751953125, 67.06489257812498], + [-171.79555664062502, 66.93173828125003], + [-170.50952148437503, 66.34365234375005], + [-170.604443359375, 66.24892578125002], + [-170.30122070312504, 66.29404296874998], + [-170.24394531250002, 66.16928710937503], + [-169.777880859375, 66.14311523437505], + [-169.83168945312497, 65.99892578124997], + [-170.54067382812497, 65.86542968749995], + [-170.66630859375, 65.62153320312501], + [-171.42153320312502, 65.81035156250002], + [-171.10585937500002, 65.51103515625005], + [-171.90712890625, 65.495947265625], + [-172.78330078124998, 65.68105468749997], + [-172.23281250000002, 65.45571289062497], + [-172.30927734375004, 65.27563476562497], + [-172.66191406249993, 65.24853515625006], + [-172.28603515625002, 65.20571289062502], + [-172.21318359375, 65.04814453124999], + [-173.08579101562498, 64.81733398437495], + [-172.80107421874996, 64.79052734375], + [-172.90087890624994, 64.62885742187501], + [-172.40146484374998, 64.413916015625], + [-172.73916015624997, 64.41225585937502], + [-172.90317382812498, 64.52607421875004], + [-172.96005859375003, 64.32768554687502], + [-173.27548828124998, 64.2896484375], + [-173.327490234375, 64.53955078125003], + [-173.72973632812497, 64.36450195312497], + [-174.57055664062503, 64.7177734375], + [-175.39511718749998, 64.80239257812502], + [-175.85385742187498, 65.01083984375003], + [-176.09326171875, 65.471044921875], + [-177.05625, 65.613623046875], + [-177.48876953125, 65.50371093749999], + [-178.4125, 65.49555664062501], + [-178.93906249999998, 66.03276367187505], + [-178.74672851562497, 66.01367187500006], + [-178.52656250000004, 66.40156250000004], + [-178.86811523437498, 66.18706054687502], + [-179.14340820312503, 66.37504882812505], + [-179.327197265625, 66.16259765625003], + [-179.68330078124998, 66.18413085937505], + [-179.78969726562497, 65.90087890625], + [-179.352099609375, 65.51674804687497], + [-180, 65.06723632812498], + [-180, 65.31196289062501], + [-180, 65.55678710937497], + [-180, 65.80156250000002], + [-180, 66.04628906250002], + [-180, 66.29106445312499], + [-180, 66.53583984375004], + [-180, 66.78056640625005], + [-180, 67.02534179687501], + [-180, 67.27011718750006], + [-180, 67.51484374999998], + [-180, 67.75961914062503], + [-180, 68.00439453124997], + [-180, 68.24912109375], + [-180, 68.49389648437497], + [-180, 68.738671875], + [-179.999951171875, 68.98344726562505], + [-179.79853515625, 68.9404296875] + ] + ], + [ + [ + [50.265234375, 69.18559570312502], + [49.62626953125002, 68.85971679687498], + [48.91035156250004, 68.74306640625002], + [48.4390625, 68.80488281249998], + [48.319921875, 69.26923828125001], + [48.8449218750001, 69.49472656250003], + [49.22519531250006, 69.51123046875], + [50.265234375, 69.18559570312502] + ] + ], + [ + [ + [161.46708984375002, 68.90097656250003], + [161.08281250000007, 69.4056640625], + [161.50517578125007, 69.63945312500002], + [161.46708984375002, 68.90097656250003] + ] + ], + [ + [ + [169.20078125000006, 69.58046875], + [168.34804687500005, 69.66435546875005], + [167.86474609375003, 69.90107421875004], + [168.35791015625003, 70.01567382812502], + [169.37480468750007, 69.88261718749999], + [169.20078125000006, 69.58046875] + ] + ], + [ + [ + [60.450488281250074, 69.93486328124999], + [60.44023437500002, 69.72592773437506], + [59.637011718750074, 69.72104492187503], + [59.50263671875004, 69.86621093750003], + [58.952734375, 69.89277343750004], + [58.51992187500005, 70.31831054687504], + [59.04804687500004, 70.46049804687505], + [60.450488281250074, 69.93486328124999] + ] + ], + [ + [ + [52.90332031250003, 71.36499023437503], + [53.19257812500004, 71.21528320312498], + [53.0226562500001, 70.96870117187501], + [52.24960937500006, 71.28491210937506], + [52.90332031250003, 71.36499023437503] + ] + ], + [ + [ + [178.8615234375001, 70.826416015625], + [178.68388671875013, 71.10566406250004], + [180, 71.53774414062505], + [180, 70.993017578125], + [178.8615234375001, 70.826416015625] + ] + ], + [ + [ + [137.95986328125005, 71.50766601562503], + [137.71181640625005, 71.4232421875], + [137.06406250000006, 71.52988281250003], + [137.816796875, 71.58789062500006], + [137.95986328125005, 71.50766601562503] + ] + ], + [ + [ + [-178.87646484375, 71.57705078124997], + [-178.13388671874998, 71.46547851562497], + [-177.523583984375, 71.16689453125], + [-179.415673828125, 70.91899414062502], + [-179.999951171875, 70.993017578125], + [-179.999951171875, 71.53774414062505], + [-178.87646484375, 71.57705078124997] + ] + ], + [ + [ + [77.6325195312501, 72.291259765625], + [76.87109374999997, 72.317041015625], + [77.74853515625003, 72.63120117187506], + [78.36513671875005, 72.48242187500003], + [77.6325195312501, 72.291259765625] + ] + ], + [ + [ + [79.50146484374997, 72.72192382812497], + [78.63320312500005, 72.85073242187502], + [79.16425781250004, 73.0943359375], + [79.50146484374997, 72.72192382812497] + ] + ], + [ + [ + [74.660546875, 72.87343750000002], + [74.18066406250003, 72.975341796875], + [74.19853515625002, 73.10908203124998], + [74.9615234375, 73.0625], + [74.660546875, 72.87343750000002] + ] + ], + [ + [ + [120.26132812500012, 73.08984374999997], + [119.79208984375006, 73.04541015624997], + [119.64042968750002, 73.12431640625007], + [120.26132812500012, 73.08984374999997] + ] + ], + [ + [ + [55.31982421875003, 73.30830078124998], + [56.42958984375005, 73.201171875], + [56.121679687500006, 72.80659179687498], + [55.40332031249997, 72.54907226562503], + [55.29785156249997, 71.93535156250005], + [56.45439453125002, 71.10737304687504], + [57.62539062500005, 70.72880859374999], + [57.14589843750005, 70.58911132812506], + [56.38574218749997, 70.73413085937503], + [56.49970703125004, 70.56640625000003], + [55.687304687500074, 70.69218749999999], + [54.60117187500006, 70.68007812500002], + [53.383593750000074, 70.87353515625], + [53.670507812500006, 71.08691406250003], + [54.155664062499994, 71.12548828125], + [53.40996093750002, 71.34013671875002], + [53.41162109375003, 71.530126953125], + [51.93789062500005, 71.47470703124998], + [51.511328125, 71.64809570312497], + [51.58251953124997, 72.07119140625], + [52.252050781250006, 72.12973632812503], + [52.66191406250002, 72.33686523437495], + [52.91660156250006, 72.66889648437501], + [52.5792968750001, 72.791357421875], + [53.3698242187501, 72.91674804687506], + [53.2511718750001, 73.182958984375], + [54.80390625000004, 73.38764648437498], + [55.31982421875003, 73.30830078124998] + ] + ], + [ + [ + [70.67392578125006, 73.09501953125005], + [70.04072265625004, 73.03715820312507], + [69.99589843750002, 73.359375], + [70.94023437500002, 73.51440429687503], + [71.6261718750001, 73.17397460937497], + [70.67392578125006, 73.09501953125005] + ] + ], + [ + [ + [142.18486328125007, 73.89589843750005], + [143.34375, 73.56875], + [143.45146484375007, 73.231298828125], + [141.59667968750003, 73.31083984375005], + [140.66279296875004, 73.45200195312503], + [139.785546875, 73.35522460937503], + [141.08476562500002, 73.86586914062497], + [142.18486328125007, 73.89589843750005] + ] + ], + [ + [ + [83.5490234375001, 74.07177734375], + [82.8177734375, 74.09160156250005], + [83.14980468750005, 74.151611328125], + [83.5490234375001, 74.07177734375] + ] + ], + [ + [ + [141.01025390625003, 73.99946289062501], + [140.40947265625002, 73.92167968750005], + [140.1935546875001, 74.23671875000002], + [141.03857421875003, 74.24272460937502], + [141.01025390625003, 73.99946289062501] + ] + ], + [ + [ + [113.38720703124997, 74.40043945312499], + [112.78242187500004, 74.09506835937503], + [111.50341796874997, 74.35307617187502], + [111.87978515625, 74.36381835937499], + [112.08447265624997, 74.54897460937505], + [113.38720703124997, 74.40043945312499] + ] + ], + [ + [ + [86.653125, 74.981298828125], + [87.05214843750005, 74.982568359375], + [86.92714843750005, 74.83076171874998], + [86.25859375000002, 74.89350585937498], + [86.653125, 74.981298828125] + ] + ], + [ + [ + [82.17236328125003, 75.41938476562501], + [81.97851562499997, 75.24711914062499], + [81.65478515625003, 75.28891601562498], + [81.71210937500004, 75.45141601562506], + [82.165625, 75.515625], + [82.17236328125003, 75.41938476562501] + ] + ], + [ + [ + [146.79521484375007, 75.37075195312505], + [148.43242187500002, 75.41352539062495], + [148.59013671875007, 75.23637695312502], + [150.82236328125006, 75.15654296875002], + [150.64628906250002, 74.944580078125], + [149.596875, 74.77260742187505], + [148.296875, 74.80043945312502], + [146.14853515625012, 75.19829101562499], + [146.5375, 75.58178710937506], + [146.79521484375007, 75.37075195312505] + ] + ], + [ + [ + [135.9486328125, 75.40957031250005], + [135.45195312500007, 75.38955078124997], + [135.6986328125, 75.84526367187499], + [136.16894531249997, 75.60556640625], + [135.9486328125, 75.40957031250005] + ] + ], + [ + [ + [140.04873046875, 75.82895507812503], + [140.81591796874997, 75.63071289062498], + [141.48544921875012, 76.13715820312495], + [142.66953125000012, 75.86342773437497], + [143.68583984375002, 75.86367187500002], + [145.35996093750006, 75.53046874999998], + [144.01972656250004, 75.04467773437506], + [143.1703125, 75.11689453125001], + [142.72949218749997, 75.33764648437506], + [142.941796875, 75.71328125000002], + [142.30791015625007, 75.69169921875005], + [142.19882812500006, 75.39267578124998], + [143.12792968749997, 74.9703125], + [142.47275390625006, 74.82041015625], + [141.98730468750003, 74.99125976562499], + [140.26787109375002, 74.846923828125], + [139.68125, 74.96406249999995], + [139.09912109374997, 74.65654296875002], + [138.09228515625003, 74.79746093750003], + [136.94765625000005, 75.32553710937498], + [137.28974609375004, 75.34863281249997], + [137.26884765625002, 75.7494140625], + [137.70654296875003, 75.75957031250002], + [137.56054687499997, 75.95522460937502], + [138.20761718750006, 76.11494140624995], + [138.91953125000006, 76.19672851562501], + [140.04873046875, 75.82895507812503] + ] + ], + [ + [ + [96.5324218750001, 76.278125], + [96.30058593750002, 76.121728515625], + [95.31113281250006, 76.21474609375002], + [95.37988281250003, 76.2890625], + [96.5324218750001, 76.278125] + ] + ], + [ + [ + [112.47802734375003, 76.62089843749999], + [112.531640625, 76.450048828125], + [111.96894531250004, 76.62617187500001], + [112.47802734375003, 76.62089843749999] + ] + ], + [ + [ + [149.15019531250002, 76.65991210937506], + [148.39863281250004, 76.64824218750007], + [149.4064453125001, 76.78208007812498], + [149.15019531250002, 76.65991210937506] + ] + ], + [ + [ + [67.7653320312501, 76.23759765624999], + [61.35595703124997, 75.31484375000002], + [60.27685546875003, 75.00756835937503], + [60.501367187499994, 74.90463867187503], + [59.67402343750004, 74.61015624999999], + [59.24013671875005, 74.69296874999998], + [59.040429687499994, 74.48554687500001], + [58.53466796875003, 74.49892578124997], + [58.6178710937501, 74.22739257812498], + [57.76738281250002, 74.013818359375], + [57.755957031250006, 73.769189453125], + [57.313085937500006, 73.838037109375], + [57.54257812500006, 73.65820312500003], + [56.96386718750003, 73.36655273437503], + [56.43037109375004, 73.29721679687503], + [55.00683593750003, 73.45385742187506], + [54.29990234375006, 73.35097656249997], + [53.7628906250001, 73.76616210937499], + [54.64267578125006, 73.95957031250006], + [55.34091796875006, 74.41962890624998], + [56.13710937500005, 74.49609375000003], + [55.5822265625001, 74.627685546875], + [56.4987304687501, 74.95708007812505], + [55.81005859374997, 75.12490234374997], + [56.03554687499999, 75.19423828124997], + [56.57031250000003, 75.09775390625003], + [56.8444335937501, 75.351416015625], + [57.606835937499994, 75.34125976562498], + [58.05830078125004, 75.6630859375], + [58.88125, 75.85478515625007], + [60.27929687499997, 76.09624023437505], + [60.94218750000002, 76.07128906250003], + [61.20166015624997, 76.28203125000007], + [62.97148437500002, 76.23666992187498], + [64.4634765625, 76.37817382812503], + [67.65185546874997, 77.011572265625], + [68.48574218750005, 76.93369140625003], + [68.94169921875002, 76.707666015625], + [67.7653320312501, 76.23759765624999] + ] + ], + [ + [ + [96.28544921875002, 77.02666015625007], + [95.27031250000007, 77.01884765624999], + [96.52841796875006, 77.20551757812501], + [96.28544921875002, 77.02666015625007] + ] + ], + [ + [ + [89.51425781250006, 77.18881835937498], + [89.14169921875012, 77.22680664062497], + [89.61621093749997, 77.31103515625], + [89.51425781250006, 77.18881835937498] + ] + ], + [ + [ + [130.68730468750007, 42.30253906249999], + [130.52695312500012, 42.535400390625], + [130.42480468749997, 42.72705078124997], + [131.06855468750004, 42.90224609375005], + [131.25732421875003, 43.378076171874994], + [131.2552734375, 44.07158203124999], + [130.9816406250001, 44.844335937500034], + [131.44687500000012, 44.984033203124966], + [131.85185546875002, 45.32685546875001], + [132.93603515624997, 45.029931640624994], + [133.1134765625001, 45.130712890625006], + [133.18603515625003, 45.49482421875004], + [133.43642578125, 45.60468750000004], + [133.86132812500003, 46.24775390625004], + [134.1676757812501, 47.30219726562501], + [134.75234375, 47.71542968749998], + [134.56601562500006, 48.02250976562502], + [134.66523437500004, 48.25390625], + [134.29335937500005, 48.37343750000002], + [133.46835937500006, 48.09716796875003], + [133.14404296875003, 48.10566406249998], + [132.7072265625001, 47.94726562500006], + [132.47626953125004, 47.714990234374994], + [130.96191406249997, 47.70932617187498], + [130.7326171875001, 48.01923828124998], + [130.80429687500012, 48.34150390624998], + [130.5521484375, 48.602490234374955], + [130.553125, 48.861181640625006], + [130.1959960937501, 48.89165039062499], + [129.49814453125012, 49.38881835937502], + [129.0651367187501, 49.374658203124966], + [128.70400390625, 49.60014648437499], + [127.99960937500006, 49.56860351562506], + [127.55078124999997, 49.801806640625045], + [127.590234375, 50.20898437500003], + [127.33720703125007, 50.35014648437502], + [127.30703125000005, 50.70795898437501], + [126.92480468749997, 51.10014648437496], + [126.34169921875, 52.36201171875001], + [125.64902343750012, 53.042285156250045], + [125.075, 53.20366210937496], + [124.81230468750002, 53.133837890625045], + [123.6078125, 53.546533203124994], + [120.98544921875012, 53.28457031250002], + [120.09453125000007, 52.787207031250034], + [120.0675781250001, 52.632910156250034], + [120.65615234375, 52.56665039062503], + [120.74980468750007, 52.096533203125006], + [120.06689453125003, 51.60068359375006], + [119.16367187500006, 50.40600585937503], + [119.34628906250012, 50.278955078124994], + [119.25986328125012, 50.06640625000003], + [118.4515625, 49.84448242187503], + [117.8734375, 49.51347656250002], + [116.6833007812501, 49.82377929687499], + [116.551171875, 49.92031250000002], + [116.35117187500012, 49.97807617187499], + [116.21679687500003, 50.00927734375003], + [116.13457031250002, 50.01079101562499], + [115.9259765625001, 49.95214843750003], + [115.79521484375002, 49.90590820312502], + [115.71777343750003, 49.88061523437503], + [115.58798828125006, 49.88603515624996], + [115.42919921874997, 49.89648437499997], + [115.36503906250002, 49.911767578124966], + [115.27451171875006, 49.948876953124994], + [115.00332031250005, 50.138574218749994], + [114.74316406249997, 50.23369140625002], + [114.29707031250004, 50.27441406250006], + [113.57421874999997, 50.00703125000001], + [113.44550781250004, 49.94160156250001], + [113.31904296875004, 49.87431640624999], + [113.16416015625012, 49.79716796874999], + [113.09208984375007, 49.692529296874994], + [113.05556640625, 49.61625976562499], + [112.91484375000002, 49.569238281249994], + [112.80644531250007, 49.52358398437502], + [112.69736328125012, 49.50727539062498], + [112.49492187500002, 49.532324218750034], + [112.07968750000006, 49.42421875000002], + [111.42929687500006, 49.342626953125034], + [111.3366210937501, 49.355859374999966], + [111.20419921875012, 49.304296875000034], + [110.82792968750002, 49.16616210937505], + [110.70976562500002, 49.14296875000002], + [110.42783203125006, 49.219970703125], + [110.32138671875012, 49.215869140625045], + [110.19990234375004, 49.17041015625003], + [109.5287109375, 49.269873046875034], + [109.45371093750012, 49.29633789062501], + [109.23671875000005, 49.334912109374955], + [108.61367187500005, 49.32280273437499], + [108.52246093750003, 49.34150390624998], + [108.4069335937501, 49.39638671875005], + [107.96542968750012, 49.65351562500004], + [107.91660156250012, 49.947802734375045], + [107.63095703125012, 49.98310546875004], + [107.3470703125, 49.986669921875034], + [107.23330078125, 49.989404296874994], + [107.14306640625003, 50.03300781249999], + [107.04023437500004, 50.086474609375045], + [106.94130859375005, 50.19667968750002], + [106.71113281250004, 50.312597656250006], + [106.57441406250004, 50.32880859375004], + [106.36845703125002, 50.317578124999955], + [106.21787109375006, 50.304589843749966], + [105.38359375000002, 50.47373046874998], + [104.07871093750012, 50.15424804687498], + [103.63291015625006, 50.138574218749994], + [103.49628906250004, 50.16494140625005], + [103.42119140625002, 50.18706054687502], + [103.3043945312501, 50.200292968750034], + [102.28837890625007, 50.58510742187502], + [102.31660156250004, 50.71845703125001], + [102.21503906250004, 50.82944335937506], + [102.19453125000004, 51.05068359375002], + [102.15195312500006, 51.107519531250034], + [102.14238281250007, 51.21606445312503], + [102.16005859375005, 51.260839843750006], + [102.1556640625, 51.31376953124996], + [102.1115234375001, 51.353466796874955], + [101.97919921875004, 51.382226562499966], + [101.82119140625, 51.421044921874966], + [101.57089843750006, 51.46718750000005], + [101.38125, 51.45263671875], + [100.53623046875006, 51.713476562500034], + [100.46894531250004, 51.72607421875003], + [100.23037109375, 51.729833984375006], + [100.0345703125, 51.73710937499996], + [99.92167968750002, 51.755517578124994], + [99.71923828124997, 51.87163085937502], + [98.89316406250006, 52.11728515625006], + [98.64052734375005, 51.80117187500005], + [98.103125, 51.483544921874994], + [97.82529296875012, 50.985253906249994], + [97.953125, 50.85517578124998], + [98.02978515625003, 50.64462890624998], + [98.07890625000002, 50.60380859375002], + [98.14501953124997, 50.56855468750001], + [98.22050781250007, 50.55717773437502], + [98.2794921875001, 50.53325195312502], + [98.25029296875002, 50.30244140624998], + [98.00390625000003, 50.01425781249998], + [97.35976562500005, 49.741455078125], + [97.20859375000006, 49.73081054687506], + [96.98574218750005, 49.88281250000003], + [96.31503906250012, 49.90112304687503], + [96.06552734375006, 49.99873046875001], + [95.52265625000004, 49.911230468750034], + [95.11142578125012, 49.935449218749994], + [94.93027343750006, 50.04375], + [94.8112304687501, 50.04819335937506], + [94.71806640625002, 50.04326171875002], + [94.67548828125004, 50.02807617187506], + [94.61474609375003, 50.02373046874996], + [94.56464843750004, 50.08793945312499], + [94.35468750000004, 50.221826171874994], + [94.25107421875006, 50.55639648437503], + [93.103125, 50.60390625000002], + [92.94130859375005, 50.77822265625002], + [92.85644531250003, 50.78911132812502], + [92.77929687500003, 50.778662109375006], + [92.738671875, 50.71093749999997], + [92.68134765625004, 50.683203125], + [92.6266601562501, 50.68828124999999], + [92.57890625000002, 50.725439453125006], + [92.42636718750006, 50.803076171875006], + [92.35478515625002, 50.86416015625002], + [92.29580078125, 50.84980468750004], + [92.19238281249997, 50.700585937499994], + [91.80429687500006, 50.693603515625], + [91.4464843750001, 50.52216796874998], + [91.41503906249997, 50.46801757812506], + [91.34082031249997, 50.470068359375034], + [91.30058593750002, 50.46337890625], + [91.2337890625, 50.45239257812497], + [91.02158203125012, 50.41547851562501], + [90.83808593750004, 50.32373046874997], + [90.76074218749997, 50.30595703124999], + [90.71435546874997, 50.25942382812502], + [90.65507812500007, 50.22236328125001], + [90.05371093750003, 50.09375], + [89.64384765625002, 49.90302734374998], + [89.65410156250007, 49.71748046875001], + [89.57919921875006, 49.69970703125003], + [89.475, 49.66054687500005], + [89.39560546875006, 49.61152343750001], + [89.24394531250007, 49.62705078125006], + [89.20292968750007, 49.59570312499997], + [89.17998046875002, 49.5322265625], + [89.10947265625012, 49.50136718750002], + [89.00839843750006, 49.472802734374994], + [88.97060546875, 49.483740234375006], + [88.94541015625012, 49.50766601562498], + [88.90019531250002, 49.53969726562502], + [88.86386718750006, 49.52763671874996], + [88.83164062500012, 49.44843749999998], + [88.633203125, 49.486132812500045], + [88.19257812500004, 49.451708984375045], + [88.13554687500002, 49.38149414062502], + [88.11572265624997, 49.25629882812501], + [88.0285156250001, 49.219775390625045], + [87.98808593750002, 49.186914062499994], + [87.9347656250001, 49.16455078124997], + [87.81425781250002, 49.162304687499955], + [87.7625, 49.16582031249996], + [87.5158203125001, 49.122412109375006], + [87.41669921875004, 49.07661132812501], + [87.32285156250012, 49.085791015625006], + [86.62646484374997, 49.56269531250001], + [86.67548828125004, 49.77729492187501], + [86.1808593750001, 49.49931640624996], + [85.2326171875001, 49.61582031249998], + [84.9894531250001, 50.061425781249994], + [84.32324218749997, 50.239160156249966], + [83.94511718750007, 50.774658203125], + [83.35732421875005, 50.99458007812504], + [82.76083984375012, 50.89335937500002], + [82.49394531250007, 50.72758789062499], + [81.46591796875006, 50.73984375], + [81.38828125000006, 50.95649414062501], + [81.0714843750001, 50.96875], + [81.12724609375002, 51.19106445312502], + [80.73525390625, 51.29340820312498], + [80.44804687500002, 51.18334960937503], + [80.42363281250002, 50.94628906249997], + [79.98623046875, 50.774560546874966], + [77.85996093750006, 53.269189453124994], + [76.48476562500005, 54.02255859374998], + [76.42167968750007, 54.151513671874966], + [76.65458984375007, 54.14526367187503], + [76.8373046875, 54.4423828125], + [75.43720703125004, 54.08964843749999], + [75.22021484374997, 53.89379882812506], + [74.45195312500007, 53.64726562500002], + [74.35156250000003, 53.487646484375006], + [73.85898437500006, 53.61972656249998], + [73.40693359375004, 53.44755859374999], + [73.30566406250003, 53.707226562499955], + [73.71240234375003, 54.04238281250002], + [73.22988281250005, 53.957812500000045], + [72.62226562500004, 54.13432617187502], + [72.44677734375003, 53.94184570312498], + [72.18603515625003, 54.32563476562501], + [72.00449218750006, 54.20566406249998], + [71.09316406250005, 54.21220703124999], + [71.18554687500003, 54.59931640624998], + [70.73808593750007, 55.30517578125], + [70.18242187500002, 55.162451171875034], + [68.9772460937501, 55.389599609374955], + [68.20625, 55.16093750000002], + [68.15585937500006, 54.97670898437505], + [65.476953125, 54.62329101562497], + [65.08837890624997, 54.340185546875034], + [64.46123046875002, 54.38417968750002], + [61.92871093750003, 53.94648437500004], + [61.231054687500006, 54.01948242187498], + [60.97949218749997, 53.62172851562505], + [61.53496093750002, 53.52329101562506], + [61.22890625, 53.445898437500006], + [61.19921874999997, 53.28715820312502], + [61.65986328125004, 53.22846679687504], + [62.08271484375004, 53.00541992187499], + [61.047460937500006, 52.97246093750002], + [60.77441406249997, 52.67578124999997], + [60.99453125000005, 52.33686523437504], + [60.03027343749997, 51.93325195312505], + [60.464746093749994, 51.651171875000045], + [61.55468750000003, 51.32460937500005], + [61.38945312500002, 50.86103515625001], + [60.94228515625005, 50.69550781250004], + [60.42480468749997, 50.67915039062498], + [60.05859374999997, 50.850292968749955], + [59.812402343749994, 50.58203125], + [59.523046875, 50.492871093749955], + [59.4523437500001, 50.62041015625002], + [58.88369140625005, 50.694433593750006], + [58.359179687500074, 51.063818359375034], + [57.83886718750003, 51.091650390625006], + [57.44218750000002, 50.88886718749998], + [57.01171874999997, 51.06518554687503], + [56.49140625000004, 51.01953124999997], + [55.68623046875004, 50.582861328125006], + [54.64160156250003, 51.011572265625034], + [54.555273437500006, 50.535791015624994], + [54.139746093750006, 51.04077148437503], + [53.33808593750004, 51.48237304687504], + [52.57119140625005, 51.481640624999955], + [52.21914062499999, 51.709375], + [51.344531250000074, 51.47534179687503], + [51.16347656250005, 51.6474609375], + [50.79394531249997, 51.729199218749955], + [50.246875, 51.28950195312498], + [49.49804687500003, 51.08359375000006], + [49.32343750000004, 50.851708984374966], + [48.625097656250006, 50.61269531250005], + [48.7589843750001, 49.92832031250006], + [48.33496093750003, 49.858251953125006], + [47.7057617187501, 50.37797851562502], + [47.42919921874997, 50.35795898437502], + [46.889550781249994, 49.69697265625001], + [46.80205078125002, 49.36708984375002], + [47.031347656250006, 49.150292968749994], + [46.70263671875003, 48.80556640625002], + [46.660937500000074, 48.41225585937502], + [47.06464843750004, 48.23247070312499], + [47.292382812499994, 47.74091796875004], + [47.48193359374997, 47.80390624999998], + [48.16699218750003, 47.70878906249996], + [48.959375, 46.77460937499998], + [48.558398437500074, 46.75712890624999], + [48.54121093750004, 46.60561523437502], + [49.232226562500074, 46.33715820312503], + [48.683691406250006, 46.08618164062497], + [48.72958984375006, 45.896826171875034], + [48.4870117187501, 45.93486328124996], + [47.63330078124997, 45.58403320312499], + [47.46328125, 45.67968750000003], + [47.5294921875001, 45.530224609374955], + [47.3512695312501, 45.21772460937498], + [46.7072265625001, 44.503320312499994], + [47.30703125000005, 44.103125], + [47.462792968749994, 43.55502929687498], + [47.64648437500003, 43.88461914062498], + [47.463183593750074, 43.03505859375002], + [48.572851562500006, 41.84448242187503], + [47.79101562499997, 41.19926757812502], + [47.31767578125002, 41.28242187500001], + [46.74931640625002, 41.812597656250006], + [46.42988281250004, 41.890966796875006], + [46.21269531250002, 41.989892578124994], + [45.63857421875005, 42.20507812500003], + [45.63427734374997, 42.234716796875034], + [45.72753906249997, 42.47504882812498], + [45.70527343750004, 42.49809570312496], + [45.56289062499999, 42.53574218749998], + [45.34375, 42.52978515625003], + [45.16025390625006, 42.675], + [45.07158203125002, 42.69414062500002], + [44.94335937499997, 42.73027343750002], + [44.870996093749994, 42.75639648437499], + [44.850488281249994, 42.746826171875], + [44.77109375000006, 42.61679687499998], + [44.69179687499999, 42.709619140624966], + [44.64433593750002, 42.734716796875034], + [44.50585937500003, 42.748632812500006], + [44.329492187499994, 42.703515624999966], + [44.10273437500004, 42.616357421874994], + [44.004687500000074, 42.59560546875002], + [43.95742187500005, 42.56655273437505], + [43.825976562500074, 42.571533203125], + [43.759863281250006, 42.593847656250006], + [43.738378906250006, 42.61699218750002], + [43.74990234375005, 42.65751953125002], + [43.79541015624997, 42.702978515625034], + [43.78261718750005, 42.747021484374955], + [43.62304687500003, 42.80771484374998], + [43.5578125000001, 42.844482421875], + [43.089160156250074, 42.9890625], + [43.00019531250004, 43.04965820312506], + [42.991601562499994, 43.09150390624998], + [42.76064453125005, 43.169580078124966], + [41.58056640624997, 43.21923828124997], + [41.460742187500074, 43.276318359374955], + [41.35820312500002, 43.33339843750005], + [41.08310546875006, 43.37446289062498], + [40.94199218750006, 43.41806640624998], + [40.801660156249994, 43.479931640624955], + [40.64804687500006, 43.53388671875004], + [40.084570312500006, 43.553125], + [40.02373046875002, 43.48486328125], + [39.873632812500006, 43.47280273437502], + [38.71728515624997, 44.28808593750003], + [38.18125, 44.41967773437503], + [37.851464843749994, 44.698828125000034], + [37.49511718750003, 44.69526367187504], + [37.20478515625004, 44.97197265624999], + [36.62763671875004, 45.15131835937504], + [36.941210937500074, 45.289697265624994], + [36.72041015625004, 45.371875], + [36.8659179687501, 45.42705078124999], + [37.21357421875004, 45.272314453125006], + [37.6471679687501, 45.37719726562506], + [37.61240234375006, 45.56469726562506], + [37.93310546875003, 46.001708984375], + [38.014257812500006, 46.047753906249966], + [38.07958984375003, 45.93481445312506], + [38.18359374999997, 46.09482421875006], + [38.49228515625006, 46.09052734374998], + [37.913867187500074, 46.40649414062503], + [37.766503906249994, 46.63613281250002], + [38.50097656249997, 46.663671875000034], + [38.43867187500004, 46.813085937500006], + [39.29345703125003, 47.105761718750045], + [39.19570312499999, 47.268847656250045], + [39.023730468750074, 47.27221679687503], + [38.928320312500006, 47.175683593749994], + [38.55244140625004, 47.15034179687498], + [38.7619140625001, 47.261621093749994], + [38.21435546875003, 47.091455078124966], + [38.36884765625004, 47.609960937500006], + [38.90029296875005, 47.85512695312502], + [39.77871093750005, 47.88754882812506], + [39.95791015625005, 48.268896484375034], + [39.8356445312501, 48.54277343749996], + [39.6447265625001, 48.591210937499966], + [39.792871093749994, 48.807714843750034], + [40.00361328125004, 48.82207031250002], + [39.68652343749997, 49.007910156250034], + [40.10878906250005, 49.251562500000034], + [40.080664062500006, 49.576855468749955], + [39.780566406250074, 49.57202148437503], + [39.17480468750003, 49.85595703124997], + [38.91835937499999, 49.82470703125], + [38.258593750000074, 50.05234375], + [38.046875, 49.92001953125006], + [37.42285156249997, 50.411474609375006], + [36.619433593750074, 50.209228515625], + [36.1164062500001, 50.408544921875006], + [35.59111328125002, 50.36875], + [35.31191406250005, 51.043896484374955], + [35.0640625, 51.203417968750045], + [34.21386718750003, 51.25537109375006], + [34.12109375000003, 51.67915039062498], + [34.397851562499994, 51.780419921874994], + [33.735253906249994, 52.344775390625045], + [32.435449218749994, 52.307226562500034], + [32.12226562500004, 52.05058593749996], + [31.763378906250097, 52.10107421875003], + [31.758593750000017, 52.125830078125034], + [31.690625, 52.22065429687498], + [31.64990234374997, 52.26220703125], + [31.60156250000003, 52.284814453124994], + [31.57734375000004, 52.31230468749999], + [31.585546875, 52.532470703125], + [31.56484375, 52.75922851562501], + [31.53515624999997, 52.798242187499966], + [31.442773437499994, 52.86181640625003], + [31.35302734374997, 52.93344726562498], + [31.295117187500097, 52.98979492187499], + [31.25878906249997, 53.01669921875006], + [31.364550781250017, 53.13896484375002], + [31.388378906250097, 53.18481445312503], + [31.41787109375005, 53.196044921875], + [31.849707031250006, 53.106201171875], + [32.14199218750005, 53.091162109375034], + [32.46933593750006, 53.270312500000045], + [32.578027343749994, 53.312402343749994], + [32.644433593749994, 53.32890624999999], + [32.70429687500004, 53.33632812499999], + [32.45097656250002, 53.6533203125], + [32.20039062500004, 53.78125], + [31.99218750000003, 53.796875], + [31.82080078124997, 53.79194335937498], + [31.754199218750017, 53.81044921875002], + [31.825292968750006, 53.93500976562501], + [31.837792968749994, 54.00078124999999], + [31.825976562500074, 54.030712890624955], + [31.79199218749997, 54.05590820312503], + [31.62841796874997, 54.111181640625006], + [31.403613281250017, 54.195947265624966], + [31.299121093750017, 54.29169921875001], + [31.184765625000097, 54.452978515625006], + [31.074804687500063, 54.491796875], + [31.154882812500063, 54.610937500000034], + [31.152148437500017, 54.625341796875034], + [31.12128906250004, 54.64848632812496], + [30.984179687500074, 54.695898437500034], + [30.79882812499997, 54.78325195312499], + [30.79101562499997, 54.806005859375006], + [30.804492187500074, 54.8609375], + [30.829882812500017, 54.91499023437498], + [30.977734375000097, 55.05048828124998], + [30.977734375000097, 55.08779296875002], + [30.958886718749994, 55.13759765625005], + [30.87744140625003, 55.223437500000045], + [30.81445312499997, 55.27871093750002], + [30.81054687499997, 55.306982421875006], + [30.82099609375004, 55.330273437499955], + [30.900585937500097, 55.397412109374955], + [30.906835937500063, 55.57001953125004], + [30.625585937500006, 55.666259765625], + [30.23359375000004, 55.84521484375006], + [30.04267578125004, 55.83642578125003], + [29.93701171874997, 55.84526367187499], + [29.881640625000074, 55.83232421875002], + [29.82392578125004, 55.79511718749998], + [29.74414062499997, 55.770410156249994], + [29.630078125000097, 55.75117187499998], + [29.482226562500074, 55.6845703125], + [29.412988281249994, 55.72485351562506], + [29.35341796875005, 55.784375], + [29.375, 55.938720703125], + [28.284277343750006, 56.055908203125], + [28.14794921875003, 56.142919921875034], + [28.202050781250023, 56.260400390624994], + [28.191699218750045, 56.31557617187505], + [28.169238281250017, 56.386865234374994], + [28.11083984375, 56.51069335937501], + [28.103125, 56.545703125000045], + [27.89208984375003, 56.741064453125034], + [27.88154296875001, 56.82416992187501], + [27.848632812500057, 56.85341796875002], + [27.806054687499994, 56.86708984375005], + [27.639453125000074, 56.84565429687504], + [27.83027343750004, 57.19448242187505], + [27.83828125000008, 57.247705078124966], + [27.82861328124997, 57.293310546875006], + [27.796875, 57.316943359375045], + [27.538671875000063, 57.429785156250034], + [27.51113281250005, 57.508154296875006], + [27.469726562500057, 57.524023437500034], + [27.35195312500005, 57.528125], + [27.4, 57.66679687499999], + [27.542089843750063, 57.799414062500006], + [27.778515625000068, 57.87070312500006], + [27.502441406250057, 58.221337890624994], + [27.434179687500006, 58.787255859374994], + [28.15107421875004, 59.374414062499966], + [28.0125, 59.484277343749966], + [28.05800781250008, 59.781542968750045], + [28.334570312500034, 59.69252929687502], + [28.518164062500034, 59.849560546874955], + [28.947265625000057, 59.828759765624994], + [29.147265625000045, 59.999755859375], + [30.12255859374997, 59.873583984375074], + [30.172656250000017, 59.957128906250034], + [29.72119140624997, 60.19531249999997], + [29.069140625000017, 60.19145507812499], + [28.643164062500006, 60.375292968750045], + [28.512792968750006, 60.67729492187502], + [27.797656250000074, 60.53613281250003], + [29.69013671875004, 61.54609375000001], + [31.18671875000004, 62.48139648437504], + [31.533984375000017, 62.885400390624994], + [31.180859375000097, 63.208300781250074], + [29.991503906250074, 63.73515625000002], + [30.50390625000003, 64.02060546875], + [30.513769531250006, 64.2], + [30.04189453125005, 64.44335937499997], + [30.072851562500063, 64.76503906250005], + [29.60419921875004, 64.968408203125], + [29.826953125000017, 65.14506835937502], + [29.608007812500006, 65.248681640625], + [29.715917968750063, 65.62456054687502], + [30.102734375000097, 65.72626953125004], + [29.066210937500045, 66.89174804687497], + [29.988085937500017, 67.66826171874999], + [29.343847656250006, 68.06186523437506], + [28.685156250000034, 68.189794921875], + [28.470703125000057, 68.48837890625], + [28.77285156250005, 68.84003906249995], + [28.414062500000057, 68.90415039062506], + [28.96582031250003, 69.02197265625], + [29.38828125, 69.29814453125005], + [30.08730468750005, 69.43286132812503], + [30.18017578124997, 69.63583984375], + [30.860742187499994, 69.53842773437503], + [30.869726562500006, 69.78344726562506], + [31.546972656250063, 69.696923828125], + [31.997949218749994, 69.80991210937503], + [31.98457031250004, 69.95366210937499], + [33.00781249999997, 69.72211914062498], + [32.91503906249997, 69.60170898437497], + [32.17675781250003, 69.67402343749995], + [32.37773437500002, 69.47910156250003], + [32.99980468750002, 69.4701171875], + [32.97890625000005, 69.367333984375], + [33.45429687500004, 69.42817382812495], + [33.14121093750006, 69.068701171875], + [33.684375, 69.31025390625001], + [35.85791015625003, 69.19174804687503], + [37.73056640625006, 68.69213867187503], + [38.43017578125003, 68.35561523437505], + [39.568945312500006, 68.07172851562501], + [39.82333984375006, 68.05859375], + [39.80927734375004, 68.15083007812498], + [40.38066406250002, 67.831884765625], + [40.96640625000006, 67.71347656250003], + [41.358789062499994, 67.20966796874998], + [41.18896484375003, 66.82617187500003], + [40.10332031250002, 66.29995117187502], + [38.65390625000006, 66.06904296874995], + [35.51347656250002, 66.39580078125002], + [34.82460937499999, 66.61113281249999], + [34.48261718750004, 66.55034179687505], + [34.4515625, 66.651220703125], + [33.15019531250002, 66.84394531250001], + [32.93046875000002, 67.08681640625002], + [31.895312500000074, 67.16142578125002], + [33.65595703125004, 66.44262695312506], + [33.36054687500004, 66.32954101562501], + [34.112695312499994, 66.225244140625], + [34.69179687500005, 65.95185546874998], + [34.77695312500006, 65.76826171874998], + [34.40644531250004, 65.39575195312503], + [35.03535156250004, 64.44023437500005], + [35.802050781250074, 64.3353515625], + [36.3649414062501, 64.00283203125002], + [37.44218750000002, 63.813378906249966], + [37.9679687500001, 63.949121093749994], + [38.0622070312501, 64.09101562499995], + [37.953710937500006, 64.32011718749999], + [37.183691406250006, 64.40849609375007], + [36.6242187500001, 64.75053710937502], + [36.534570312499994, 64.93862304687497], + [36.88281249999997, 65.17236328124997], + [39.7580078125001, 64.57705078125002], + [40.05781250000004, 64.77075195312497], + [40.44492187500006, 64.7787109375], + [39.7980468750001, 65.349853515625], + [39.816503906250006, 65.59794921874999], + [41.4757812500001, 66.12343750000002], + [42.21054687500006, 66.51967773437502], + [43.23320312500002, 66.41552734375003], + [43.653125, 66.2509765625], + [43.54189453125005, 66.12338867187503], + [43.84375, 66.14238281249999], + [44.10439453125005, 66.00859374999999], + [44.42929687500006, 66.93774414062503], + [43.7824218750001, 67.25449218749998], + [44.20468750000006, 68.25375976562498], + [43.33320312500004, 68.67338867187502], + [44.04804687500004, 68.54882812499997], + [45.891992187499994, 68.47968750000001], + [46.69042968750003, 67.84882812500001], + [45.52871093750005, 67.75756835937497], + [44.90214843750002, 67.41313476562505], + [45.56220703125004, 67.18559570312507], + [45.88535156250006, 66.89106445312501], + [46.4923828125001, 66.80019531249997], + [47.65585937500006, 66.97592773437498], + [47.87470703125004, 67.58417968749998], + [48.83320312500004, 67.681494140625], + [48.75429687500005, 67.89594726562501], + [49.15527343750003, 67.87041015625005], + [51.994726562500006, 68.53876953124995], + [52.3966796875001, 68.35170898437505], + [52.72265624999997, 68.484033203125], + [52.34404296875002, 68.60815429687497], + [53.80195312500004, 68.99589843750002], + [54.49121093750003, 68.992333984375], + [53.797656250000074, 68.90747070312503], + [53.9308593750001, 68.43554687499997], + [53.260546875000074, 68.26748046875002], + [54.476171875, 68.29414062499995], + [54.86132812500003, 68.20185546874998], + [55.418066406250006, 68.56782226562501], + [56.04365234375004, 68.64887695312501], + [57.126855468749994, 68.55400390625005], + [58.17304687500004, 68.88974609375006], + [59.0573242187501, 69.00605468750004], + [59.37050781250005, 68.73837890625003], + [59.09902343750005, 68.4443359375], + [59.725683593750006, 68.35161132812502], + [59.89599609374997, 68.70634765624999], + [60.489160156249994, 68.72895507812498], + [60.93359374999997, 68.98676757812501], + [60.17060546875004, 69.59091796875], + [60.90908203125005, 69.84711914062495], + [64.19042968750003, 69.53466796875], + [64.89628906250002, 69.247802734375], + [67.00244140625003, 68.87358398437505], + [68.37119140625006, 68.31425781250005], + [69.14052734375005, 68.95063476562501], + [68.54277343750002, 68.96708984374999], + [68.00585937499997, 69.48002929687505], + [67.62412109375, 69.58442382812501], + [67.06445312500003, 69.69370117187498], + [66.89667968750004, 69.55380859374998], + [67.28476562500006, 70.73872070312498], + [67.14335937500002, 70.83754882812502], + [66.70224609375006, 70.81850585937497], + [66.63964843749997, 71.08139648437498], + [68.2692382812501, 71.68281250000001], + [69.61181640625003, 72.98193359375], + [69.73828124999997, 72.88496093749998], + [71.5001953125001, 72.91367187500003], + [72.812109375, 72.69140624999997], + [72.57412109375, 72.01254882812506], + [71.86728515625, 71.457373046875], + [72.70449218750005, 70.96323242187498], + [72.5767578125, 68.96870117187498], + [73.59169921875005, 68.48188476562501], + [73.13945312500002, 68.18134765624998], + [73.06679687500005, 67.766943359375], + [71.84746093750002, 67.00761718750005], + [71.36523437500003, 66.96152343749998], + [71.53955078125003, 66.68310546875], + [70.72490234375007, 66.51943359374997], + [70.38281249999997, 66.60249023437501], + [70.69072265625002, 66.74531249999998], + [70.2833984375001, 66.68579101562503], + [69.8771484375001, 66.84545898437506], + [69.21777343749997, 66.82861328125], + [69.01347656250002, 66.78833007812503], + [69.19433593749997, 66.57866210937505], + [70.33945312500006, 66.34238281250006], + [71.35800781250006, 66.35942382812505], + [71.91699218749997, 66.24672851562502], + [72.32158203125002, 66.33212890625], + [72.4173828125, 66.56079101562506], + [73.79208984375, 66.99531250000001], + [74.07451171875007, 67.41411132812499], + [74.76953124999997, 67.76635742187497], + [74.39140625000007, 68.42060546874995], + [74.57958984375003, 68.751220703125], + [76.10751953125006, 68.975732421875], + [76.45917968750004, 68.97827148437497], + [77.2384765625001, 68.46958007812498], + [77.17441406250012, 67.77851562499998], + [77.77158203125006, 67.57026367187501], + [78.92246093750006, 67.58911132812503], + [77.58828125000005, 67.75190429687498], + [77.66484375000002, 68.19038085937495], + [77.99511718749997, 68.25947265624998], + [77.65068359375007, 68.90302734375001], + [76.00097656249997, 69.23505859374998], + [75.42001953125, 69.23862304687498], + [74.81484375, 69.09057617187503], + [73.83603515625006, 69.143212890625], + [73.578125, 69.80297851562503], + [74.34335937500006, 70.57871093749998], + [73.08623046875007, 71.44492187500006], + [73.67177734375, 71.84506835937503], + [74.99218749999997, 72.14482421874999], + [74.78681640625004, 72.811865234375], + [75.15244140625, 72.85273437499998], + [75.74140625000004, 72.29624023437503], + [75.273828125, 71.95893554687495], + [75.33203125000003, 71.34174804687498], + [76.92900390625002, 71.12788085937504], + [77.58964843750007, 71.16791992187501], + [78.32060546875002, 70.93041992187503], + [78.94218750000002, 70.93378906250001], + [79.08388671875, 71.00200195312505], + [78.58769531250007, 70.993896484375], + [78.21259765625004, 71.26630859374998], + [76.43339843750002, 71.55249023437503], + [76.03242187500004, 71.91040039062503], + [76.87138671875002, 72.03300781250005], + [77.77753906250004, 71.83642578125006], + [78.23242187500003, 71.95229492187502], + [78.01640625000007, 72.092041015625], + [77.49287109375004, 72.07172851562504], + [77.47158203125, 72.19213867187506], + [78.22539062500007, 72.37744140625006], + [79.4220703125001, 72.38076171875002], + [80.7625, 72.08916015625002], + [81.66162109374997, 71.71596679687502], + [82.75781250000003, 71.76411132812498], + [83.23359375000004, 71.66816406249995], + [82.32285156250006, 71.26000976562503], + [82.16318359375012, 70.59814453125003], + [82.22119140625003, 70.39570312499998], + [82.86914062499997, 70.95483398437503], + [83.03017578125, 70.58051757812498], + [82.6823242187501, 70.21772460937498], + [83.0807617187501, 70.09301757812497], + [83.07382812500012, 70.276708984375], + [83.73593750000006, 70.54648437499998], + [83.15126953125005, 71.10361328124998], + [83.534375, 71.68393554687498], + [83.20029296875012, 71.87470703125004], + [82.64541015625005, 71.92524414062504], + [82.09365234375, 72.26542968750005], + [80.82705078125005, 72.48828124999997], + [80.84160156250007, 72.94916992187498], + [80.4245117187501, 73.23115234374998], + [80.5832031250001, 73.56845703125003], + [85.20058593750005, 73.72153320312506], + [86.89296875, 73.88710937500002], + [85.79257812500012, 73.438330078125], + [86.67705078125002, 73.10678710937503], + [85.93896484374997, 73.45649414062495], + [87.12011718750003, 73.61503906250002], + [87.57119140625, 73.81074218750001], + [86.57109375000007, 74.24375], + [86.0013671875, 74.316015625], + [86.39580078125007, 74.45009765624997], + [86.89794921874997, 74.32534179687497], + [87.22968750000004, 74.3638671875], + [85.79101562499997, 74.6451171875], + [86.20126953125006, 74.81621093750005], + [86.65146484375012, 74.68242187500005], + [87.04179687500007, 74.77885742187499], + [87.46757812500002, 75.01323242187505], + [86.93906250000006, 75.06811523437503], + [87.00595703125012, 75.16982421874997], + [87.67138671874997, 75.12958984375004], + [90.18496093750005, 75.59106445312497], + [94.07519531249997, 75.91289062499999], + [92.89042968750002, 75.90996093750002], + [93.25927734375003, 76.09877929687502], + [95.57871093750012, 76.13730468749998], + [96.07548828125007, 76.08198242187498], + [95.65332031250003, 75.89218750000003], + [96.50859375000002, 76.00556640624995], + [96.49707031249997, 75.89121093750003], + [98.66201171875005, 76.24267578125003], + [99.77041015625, 76.02875976562498], + [99.5407226562501, 75.79858398437497], + [99.85136718750007, 75.93027343749998], + [99.8253906250001, 76.13593749999995], + [98.80566406250003, 76.48066406250004], + [100.84375, 76.52519531250005], + [101.59775390625006, 76.43920898437503], + [100.92802734375002, 76.55673828124998], + [100.98994140625004, 76.99047851562497], + [102.61015625000007, 77.508544921875], + [104.01455078125, 77.73041992187501], + [106.05957031249997, 77.39052734375002], + [104.20244140625002, 77.101806640625], + [106.9416015625001, 77.034375], + [107.42978515625006, 76.92656250000002], + [106.41357421874997, 76.51225585937499], + [107.72216796875003, 76.52231445312498], + [108.18164062500003, 76.73784179687502], + [111.39248046875, 76.686669921875], + [112.09394531250004, 76.48032226562506], + [111.94267578125002, 76.38046875000003], + [112.61953125, 76.38354492187506], + [112.65625, 76.05356445312498], + [113.2726562500001, 76.25166015625001], + [113.5638671875, 75.89165039062502], + [113.85722656250007, 75.92128906250002], + [113.56757812500004, 75.56840820312499], + [112.45302734375, 75.83017578125003], + [112.95566406250006, 75.571923828125], + [113.24296875000007, 75.61142578125003], + [113.72617187500012, 75.45063476562498], + [112.92490234375012, 75.01503906249997], + [109.84033203124997, 74.32197265624998], + [109.8102539062501, 74.16918945312503], + [108.19951171875002, 73.69409179687497], + [107.27109375000006, 73.62104492187501], + [106.67939453125004, 73.3306640625], + [106.1886718750001, 73.3080078125], + [105.14394531250005, 72.77705078125001], + [105.7082031250001, 72.836669921875], + [106.47792968750005, 73.13940429687503], + [107.750390625, 73.17314453125007], + [109.33105468749997, 73.48745117187497], + [109.85527343750002, 73.47246093750002], + [110.86816406249997, 73.73071289062497], + [109.70673828125004, 73.74375], + [110.2614257812501, 74.01743164062503], + [111.05625, 73.93935546875002], + [111.13085937500003, 74.05283203125003], + [111.55058593750007, 74.02851562499998], + [111.22812500000012, 73.96855468750002], + [111.40039062500003, 73.827734375], + [112.14726562500007, 73.70893554687498], + [112.79541015625003, 73.74609375], + [112.83593750000003, 73.96206054687502], + [113.03281250000006, 73.91386718750007], + [113.4162109375001, 73.647607421875], + [113.15693359375004, 73.45957031249998], + [113.49091796875004, 73.34609375000002], + [113.12783203125, 72.8306640625], + [113.66455078124997, 72.63452148437503], + [113.2155273437501, 72.80585937500001], + [113.88623046875003, 73.34580078124998], + [113.51035156250012, 73.50498046874998], + [115.33769531250007, 73.70258789062501], + [118.87089843750007, 73.53789062500002], + [118.45703124999997, 73.46440429687507], + [118.43027343750012, 73.24653320312501], + [119.750390625, 72.97910156250006], + [122.26015625, 72.88056640624995], + [122.75195312500003, 72.906494140625], + [122.61523437499997, 73.02792968750006], + [123.1603515625001, 72.95488281250002], + [123.62226562500004, 73.19326171875], + [123.49111328125005, 73.666357421875], + [124.54121093750004, 73.75126953125007], + [125.59853515625005, 73.447412109375], + [126.25449218750012, 73.548193359375], + [126.55253906250007, 73.33491210937498], + [127.03134765625006, 73.54746093750003], + [127.74033203125012, 73.48154296875], + [129.10058593750003, 73.11235351562502], + [128.5990234375, 72.895166015625], + [129.01728515625004, 72.8724609375], + [129.250390625, 72.70517578125003], + [128.41826171875002, 72.53515625000003], + [129.28134765625006, 72.43769531249998], + [129.41064453124997, 72.16630859375002], + [128.93496093750005, 72.07949218750002], + [127.8034179687501, 72.43403320312504], + [127.84140625000012, 72.308251953125], + [128.91142578125002, 71.75532226562495], + [129.21025390625007, 71.91694335937501], + [129.46083984375, 71.73930664062499], + [128.84326171875003, 71.6634765625], + [129.76191406250004, 71.11953125000002], + [130.53710937500003, 70.89252929687495], + [130.75712890625002, 70.96235351562498], + [131.02158203125006, 70.74609374999997], + [132.0353515625001, 71.24404296875], + [132.65390625000006, 71.92597656250001], + [133.6888671875, 71.434228515625], + [134.70273437500012, 71.38681640625003], + [135.55917968750006, 71.6103515625], + [136.09033203125003, 71.61958007812501], + [137.9396484375001, 71.1333984375], + [137.84404296875007, 71.22680664062503], + [138.31406250000006, 71.32553710937498], + [137.918359375, 71.38408203124999], + [138.23417968750007, 71.596337890625], + [138.78017578125, 71.62900390624998], + [139.209375, 71.44477539062501], + [139.98417968750007, 71.49150390625005], + [139.72294921875002, 71.88496093749998], + [139.35927734375005, 71.95136718750001], + [140.18769531250004, 72.19130859374997], + [139.17636718750006, 72.16347656249997], + [139.14082031250004, 72.32973632812502], + [139.60117187500012, 72.49609374999997], + [141.07929687500004, 72.5869140625], + [140.80820312500006, 72.89096679687503], + [142.06142578125005, 72.72080078125], + [146.25292968749997, 72.442236328125], + [146.234765625, 72.34970703125], + [144.77636718749997, 72.38227539062495], + [144.16923828125002, 72.25878906250003], + [144.29492187499997, 72.19262695312497], + [146.83183593750007, 72.29541015625003], + [146.11328125000003, 71.94497070312497], + [146.23027343750007, 72.1375], + [145.75859375000007, 72.22587890624999], + [145.75673828125005, 71.94130859375002], + [145.06396484374997, 71.92607421875002], + [145.18857421875012, 71.69580078125], + [146.07324218749997, 71.80834960937503], + [147.26181640625006, 72.327880859375], + [149.50156250000012, 72.16430664062497], + [150.01689453125002, 71.89565429687505], + [149.04873046875005, 71.79575195312503], + [148.9681640625, 71.69047851562499], + [150.59980468750004, 71.5201171875], + [150.09765624999997, 71.22656249999997], + [150.96777343749997, 71.38046874999998], + [151.58242187500005, 71.28696289062503], + [152.09277343749997, 71.02329101562503], + [151.76201171875002, 70.98247070312499], + [152.50878906250003, 70.83447265625003], + [156.68457031250003, 71.09375], + [158.03701171875005, 71.03925781250001], + [159.35068359375006, 70.79072265625001], + [160.00644531250006, 70.30966796875006], + [159.72939453125005, 69.87021484375006], + [160.91074218750012, 69.60634765625002], + [161.03554687500005, 69.09819335937507], + [161.30986328125007, 68.98227539062498], + [160.85605468750006, 68.53833007812506], + [161.565625, 68.90517578125], + [161.53691406250002, 69.379541015625], + [162.16601562499997, 69.61157226562503], + [163.20136718750004, 69.71474609375], + [166.82031250000003, 69.49956054687505], + [167.8568359375, 69.72822265624998], + [168.30302734375002, 69.27148437500003], + [169.31064453125006, 69.07954101562498], + [169.60986328124997, 68.78603515624997], + [170.53759765624997, 68.82539062500001], + [170.99541015625002, 69.04531250000005], + [170.58222656250004, 69.58334960937506], + [170.16093750000007, 69.62656249999998], + [170.48681640625003, 70.107568359375], + [173.27744140625006, 69.823828125], + [173.43867187500004, 69.94682617187502], + [175.92148437500012, 69.89531250000002], + [179.27265624999998, 69.25966796875002], + [180, 68.98344726562505], + [180, 65.06723632812498], + [178.51953125000003, 64.60297851562498], + [177.7486328125, 64.71704101562503], + [176.88085937499997, 65.08193359375002], + [176.34101562500015, 65.04731445312501], + [177.03730468750004, 64.99965820312497], + [177.22285156250004, 64.861669921875], + [177.06875, 64.78666992187502], + [176.06113281250012, 64.96088867187498], + [174.54882812500009, 64.68388671875005], + [176.0565429687501, 64.90473632812498], + [176.35097656250005, 64.70512695312502], + [176.14091796875007, 64.58583984375005], + [177.42744140625015, 64.76337890624998], + [177.43291015625002, 64.44448242187502], + [177.6875, 64.30473632812507], + [178.04472656250013, 64.21958007812503], + [178.22949218749991, 64.36440429687497], + [178.38144531250018, 64.26088867187502], + [178.73144531250003, 63.667089843750006], + [178.44042968750009, 63.605566406250006], + [178.74404296874994, 63.39477539062503], + [178.79296874999997, 63.54033203125002], + [179.38857421875, 63.14721679687497], + [179.25957031250002, 63.00830078125], + [179.5705078125001, 62.6875], + [179.12070312500012, 62.32036132812499], + [177.292578125, 62.59902343750002], + [177.33896484375006, 62.781347656250034], + [177.02353515625012, 62.777246093749994], + [177.15947265625007, 62.56098632812498], + [174.51435546875015, 61.823632812499966], + [173.6234375, 61.716064453125], + [173.13183593749997, 61.40664062500002], + [172.85654296875006, 61.469189453124955], + [172.90800781250002, 61.311621093750006], + [172.39609375000006, 61.16738281250002], + [172.39277343750004, 61.061767578125], + [170.60820312500007, 60.434912109375034], + [170.3509765625, 59.965527343749955], + [169.9826171875001, 60.067089843749955], + [169.2267578125001, 60.59594726562497], + [168.1375, 60.57392578125001], + [167.22675781250004, 60.406298828125045], + [166.27304687500012, 59.85625], + [166.13603515625007, 59.979345703125034], + [166.35214843750006, 60.48481445312498], + [165.08457031250006, 60.09858398437498], + [164.95371093750006, 59.843603515625006], + [164.52529296875, 60.06127929687503], + [164.11328125000003, 59.89755859374998], + [164.13505859375002, 59.984375], + [163.74384765625004, 60.02802734374998], + [163.36484375000012, 59.78144531250004], + [163.27285156250005, 59.302587890625006], + [162.14160156249997, 58.44741210937502], + [161.96005859375012, 58.07690429687506], + [162.39140625000002, 57.717236328124955], + [162.65429687499997, 57.94824218750003], + [163.22578125000004, 57.790380859375034], + [162.77929687500003, 57.35761718749998], + [162.79111328125012, 56.875390624999966], + [162.92207031250004, 56.72265625000003], + [163.2565429687501, 56.68803710937499], + [163.33554687500012, 56.232519531250006], + [163.04736328125003, 56.044677734375], + [162.84033203125003, 56.065625], + [162.628125, 56.232275390625034], + [163.03837890625002, 56.521875], + [162.67148437500006, 56.49008789062498], + [162.52822265625005, 56.260693359374955], + [162.08496093749997, 56.08964843750002], + [161.72392578125002, 55.49614257812499], + [162.10556640625006, 54.75214843750004], + [161.62480468750002, 54.51625976562502], + [160.77265625000004, 54.54135742187498], + [160.0744140625001, 54.18916015625001], + [159.84375, 53.78364257812498], + [160.02509765625004, 53.129589843749955], + [159.58593750000003, 53.237695312499966], + [158.74541015625002, 52.90893554687506], + [158.47207031250005, 53.032373046874966], + [158.6087890625, 52.873632812500034], + [158.49316406249997, 52.383154296875034], + [158.10351562500003, 51.80961914062499], + [156.84746093750002, 51.006591796875], + [156.74775390625004, 50.969287109375045], + [156.52119140625004, 51.38027343750002], + [156.36474609374997, 52.509375], + [156.11035156250003, 52.86616210937504], + [155.62031250000004, 54.86455078125002], + [155.5548828125001, 55.348486328125034], + [155.98251953125012, 56.69521484375002], + [156.8488281250001, 57.290185546874994], + [156.97675781250004, 57.46630859375], + [156.82988281250007, 57.77963867187498], + [157.4503906250001, 57.79926757812498], + [157.66640625000005, 58.01977539062506], + [158.27519531250007, 58.00898437499998], + [159.21064453125004, 58.519433593749966], + [159.8473632812501, 59.127148437499955], + [161.75351562500012, 60.15229492187501], + [162.06816406250002, 60.466406250000034], + [163.70996093749997, 60.916796875000045], + [163.55351562500002, 61.02563476562503], + [164.00546875000006, 61.34379882812499], + [163.80439453125004, 61.46137695312498], + [164.20722656250004, 62.29223632812506], + [164.59833984375004, 62.470556640625034], + [165.20810546875012, 62.37397460937501], + [165.41738281250005, 62.447070312500045], + [164.418359375, 62.704638671875045], + [163.33173828125004, 62.550927734374994], + [163.01767578125006, 61.89106445312504], + [163.25781249999997, 61.69946289062497], + [163.08525390625002, 61.570556640625], + [162.85595703125003, 61.705029296874955], + [162.39257812500003, 61.662109375], + [160.76660156249997, 60.753320312499966], + [160.17363281250002, 60.638427734375], + [160.37890625000003, 61.02548828124998], + [159.79042968750005, 60.956640625], + [160.309375, 61.894384765625006], + [159.55234375000012, 61.71948242187497], + [159.18925781250007, 61.92939453125001], + [158.07011718750002, 61.75361328125001], + [157.46933593750012, 61.798925781250006], + [157.0841796875001, 61.67568359375002], + [155.71611328125002, 60.682373046875], + [154.97080078125012, 60.376660156249955], + [154.29306640625006, 59.833349609375034], + [154.1498046875, 59.52851562500001], + [154.97128906250006, 59.44960937500002], + [155.16044921875002, 59.19013671875001], + [154.45800781250003, 59.21655273437497], + [154.01093750000004, 59.075537109375006], + [153.69521484375005, 59.22475585937505], + [153.36113281250002, 59.214794921874955], + [152.81787109375003, 58.92626953124997], + [152.31962890625002, 59.03076171875003], + [152.08789062499997, 58.910449218750045], + [151.32675781250006, 58.875097656250034], + [151.12109375000003, 59.08251953125003], + [152.26064453125, 59.22358398437498], + [151.34824218750012, 59.561132812500006], + [150.4835937500001, 59.494384765625], + [150.66728515625002, 59.55634765625001], + [149.64257812499997, 59.770410156249994], + [149.06523437500002, 59.63051757812502], + [149.20498046875, 59.488183593749966], + [148.79707031250004, 59.532324218750006], + [148.74414062499997, 59.37353515624997], + [148.96464843750007, 59.36914062499997], + [148.72666015625006, 59.257910156250034], + [148.25742187500006, 59.414208984374994], + [147.51445312500002, 59.2685546875], + [146.53720703125006, 59.45698242187501], + [146.0495117187501, 59.17055664062502], + [145.55458984375, 59.413525390624955], + [143.19218750000002, 59.3701171875], + [142.58027343750004, 59.240136718749966], + [140.79023437500004, 58.30346679687503], + [140.446875, 57.81367187499998], + [138.66210937500003, 56.96552734375004], + [137.69150390625006, 56.13935546875004], + [135.2625, 54.94331054687498], + [135.25771484375005, 54.73149414062499], + [135.85156249999997, 54.583935546874955], + [136.797265625, 54.62099609375005], + [136.71884765625006, 53.804101562499994], + [137.15537109375012, 53.82167968750002], + [137.14160156249997, 54.182226562500006], + [137.66601562500003, 54.283300781250006], + [137.3392578125, 54.10053710937498], + [137.83476562500002, 53.94672851562498], + [137.25371093750007, 53.546142578125], + [137.95048828125007, 53.60356445312499], + [138.52792968750012, 53.959863281249994], + [138.56914062500002, 53.818798828124955], + [138.24970703125004, 53.524023437500034], + [138.45068359375003, 53.53701171875002], + [138.69941406250004, 53.869726562500034], + [138.65722656249997, 54.29833984375003], + [139.31972656250005, 54.19296874999998], + [139.707421875, 54.27714843749999], + [140.68759765625012, 53.59643554687503], + [141.3737304687501, 53.29277343749999], + [141.18125, 53.01528320312505], + [140.83964843750002, 53.087890625], + [141.25585937499997, 52.84013671874996], + [141.13242187500006, 52.435693359374994], + [141.48525390625, 52.17851562500002], + [141.36689453125004, 51.92065429687506], + [140.93261718750003, 51.61992187499999], + [140.5208984375, 50.80019531250005], + [140.62451171874997, 50.08242187500002], + [140.46269531250002, 49.911474609375006], + [140.51718750000012, 49.59614257812498], + [140.17060546875004, 48.52368164062497], + [138.58681640625005, 47.057226562500006], + [138.33691406250003, 46.543408203124955], + [137.68544921875, 45.81835937500003], + [136.14228515625004, 44.489111328125034], + [135.87460937500012, 44.37353515625003], + [135.1310546875001, 43.52573242187506], + [134.01044921875004, 42.94746093750001], + [133.15996093750007, 42.69697265624998], + [132.70898437500003, 42.875830078125006], + [132.30380859375006, 42.88330078125], + [132.30957031249997, 43.31352539062499], + [131.8666015625, 43.09516601562501], + [131.93896484374997, 43.30195312500004], + [131.15830078125012, 42.62602539062499], + [130.709375, 42.656396484374966], + [130.8341796875001, 42.52294921875006], + [130.68730468750007, 42.30253906249999] + ] + ], + [ + [ + [107.69550781250004, 78.13090820312505], + [107.48164062500004, 78.057763671875], + [106.41552734375003, 78.13984375000001], + [107.69550781250004, 78.13090820312505] + ] + ], + [ + [ + [102.88476562499997, 79.25395507812505], + [102.4123046875001, 78.83544921874997], + [103.80078124999997, 79.14926757812503], + [104.45205078125005, 78.880029296875], + [105.14599609375003, 78.81884765625006], + [105.31259765625012, 78.49990234375], + [104.74179687500012, 78.33974609374997], + [102.79667968750007, 78.18789062500002], + [101.20410156249997, 78.19194335937505], + [99.50029296875002, 77.97607421875003], + [101.590625, 79.350439453125], + [102.25126953125002, 79.25605468749995], + [102.40488281250006, 79.43320312499998], + [102.88476562499997, 79.25395507812505] + ] + ], + [ + [ + [76.24892578125005, 79.65107421874995], + [77.58896484375012, 79.50190429687504], + [76.64951171875012, 79.493408203125], + [76.24892578125005, 79.65107421874995] + ] + ], + [ + [ + [92.68349609375005, 79.685205078125], + [91.37626953125007, 79.83549804687505], + [91.22929687500007, 80.03071289062504], + [93.803125, 79.904541015625], + [92.68349609375005, 79.685205078125] + ] + ], + [ + [ + [51.409277343750006, 79.94423828125], + [50.09140625, 79.98056640625003], + [50.93632812500002, 80.09423828125], + [51.409277343750006, 79.94423828125] + ] + ], + [ + [ + [59.68886718750005, 79.95581054687506], + [58.91923828125002, 79.98461914062506], + [59.54453125000006, 80.11884765624995], + [59.68886718750005, 79.95581054687506] + ] + ], + [ + [ + [97.67451171875004, 80.15825195312499], + [97.65166015625002, 79.76064453125], + [98.59648437500002, 80.05219726562495], + [100.0612304687501, 79.77709960937506], + [99.68066406250003, 79.32333984374998], + [99.04179687500007, 79.29301757812502], + [99.92929687500012, 78.96142578124997], + [98.41113281250003, 78.78779296875004], + [95.53105468750007, 79.09809570312501], + [95.02041015625005, 79.05268554687498], + [94.21875, 79.40234375], + [93.07080078124997, 79.49531250000001], + [94.98730468749997, 80.096826171875], + [95.28134765625012, 80.030517578125], + [97.67451171875004, 80.15825195312499] + ] + ], + [ + [ + [50.05175781250003, 80.07431640625003], + [49.55605468750005, 80.15893554687503], + [49.883691406249994, 80.230224609375], + [50.05175781250003, 80.07431640625003] + ] + ], + [ + [ + [57.07871093750006, 80.35092773437498], + [56.986914062500006, 80.07148437499998], + [55.811621093750006, 80.08715820312497], + [56.02441406250003, 80.34130859374997], + [57.07871093750006, 80.35092773437498] + ] + ], + [ + [ + [53.521386718749994, 80.18520507812497], + [52.34355468750002, 80.213232421875], + [52.85390625, 80.40239257812499], + [53.85166015625006, 80.26835937500005], + [53.521386718749994, 80.18520507812497] + ] + ], + [ + [ + [57.95625, 80.12324218749995], + [57.33232421875002, 80.15810546875005], + [57.075, 80.49394531249999], + [59.25546875000006, 80.34321289062501], + [58.39794921874997, 80.31875], + [57.95625, 80.12324218749995] + ] + ], + [ + [ + [54.41533203125002, 80.47280273437502], + [53.811914062499994, 80.47622070312502], + [53.87724609375002, 80.60527343750002], + [54.41533203125002, 80.47280273437502] + ] + ], + [ + [ + [47.441992187500006, 80.853662109375], + [48.44570312500005, 80.80600585937506], + [48.68359375000003, 80.63325195312504], + [47.7052734375001, 80.76518554687499], + [46.141406250000074, 80.44672851562495], + [45.969042968750074, 80.56948242187502], + [44.9049804687501, 80.61127929687501], + [47.441992187500006, 80.853662109375] + ] + ], + [ + [ + [62.167773437500074, 80.83476562500005], + [62.07578125000006, 80.616943359375], + [61.05126953124997, 80.418603515625], + [60.27832031249997, 80.49443359374999], + [59.649804687499994, 80.43125], + [59.59228515625003, 80.81650390624998], + [62.167773437500074, 80.83476562500005] + ] + ], + [ + [ + [50.278125, 80.92724609374997], + [51.70361328125003, 80.68764648437502], + [48.81103515625003, 80.35371093750001], + [48.97753906250003, 80.16259765624997], + [47.73730468749997, 80.08168945312502], + [47.89296875000005, 80.23925781249997], + [46.991015625000074, 80.182763671875], + [46.644433593749994, 80.30034179687507], + [47.89580078125002, 80.52905273437503], + [49.087792968749994, 80.515771484375], + [49.24433593750004, 80.82138671875], + [50.278125, 80.92724609374997] + ] + ], + [ + [ + [80.02666015625007, 80.84814453125003], + [79.09853515625005, 80.81206054687505], + [79.21738281250012, 80.96035156249997], + [80.27958984375007, 80.94980468750003], + [80.02666015625007, 80.84814453125003] + ] + ], + [ + [ + [61.1408203125001, 80.95034179687497], + [60.0783203125001, 80.99916992187497], + [61.45742187499999, 81.10395507812501], + [61.1408203125001, 80.95034179687497] + ] + ], + [ + [ + [54.71894531250004, 81.11596679687497], + [56.47226562500006, 80.99824218749995], + [57.58037109375002, 80.75546874999998], + [55.88339843750006, 80.62841796875003], + [54.66816406250004, 80.73867187500002], + [54.04541015624997, 80.87197265625], + [54.71894531250004, 81.11596679687497] + ] + ], + [ + [ + [58.62236328125002, 81.04165039062502], + [58.930566406249994, 80.83168945312497], + [58.28564453124997, 80.76489257812503], + [57.21093749999997, 81.01708984374997], + [58.04951171875004, 81.11845703125002], + [58.62236328125002, 81.04165039062502] + ] + ], + [ + [ + [63.37382812500002, 80.70009765624997], + [62.59257812500002, 80.85302734375006], + [64.80205078125002, 81.197265625], + [65.43740234375005, 80.93071289062507], + [63.37382812500002, 80.70009765624997] + ] + ], + [ + [ + [91.56718750000007, 81.14121093750003], + [91.2228515625001, 81.063818359375], + [89.90117187500002, 81.17070312500002], + [91.56718750000007, 81.14121093750003] + ] + ], + [ + [ + [96.52656250000004, 81.0755859375], + [97.86992187500007, 80.76328125000006], + [97.02539062499997, 80.53554687500002], + [97.29843750000006, 80.27275390625005], + [93.6546875, 80.009619140625], + [91.52382812500005, 80.35854492187502], + [93.2625, 80.79125976562497], + [92.59257812500007, 80.780859375], + [92.7103515625, 80.87216796875003], + [95.1595703125, 81.27099609375003], + [95.80068359375005, 81.28046874999998], + [96.52656250000004, 81.0755859375] + ] + ], + [ + [ + [57.81025390625004, 81.54604492187502], + [58.563867187499994, 81.41840820312504], + [57.858691406250074, 81.36806640625], + [57.76972656250004, 81.16972656249999], + [55.71669921875005, 81.1884765625], + [55.46601562500004, 81.31118164062502], + [57.81025390625004, 81.54604492187502] + ] + ], + [ + [ + [63.65097656250006, 81.60932617187501], + [62.10644531249997, 81.679345703125], + [63.709570312500006, 81.68730468750002], + [63.65097656250006, 81.60932617187501] + ] + ], + [ + [ + [58.29541015625003, 81.715185546875], + [58.13457031250002, 81.82797851562498], + [59.261816406250006, 81.85419921874998], + [59.35644531250003, 81.75898437499995], + [58.29541015625003, 81.715185546875] + ] + ] + ] + }, + "properties": { "name": "Russia", "childNum": 73 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [30.50996093750001, -1.067285156250009], + [30.47705078125, -1.0830078125], + [30.47021484375, -1.131152343750003], + [30.508105468750017, -1.208203125000011], + [30.631933593750006, -1.367480468750003], + [30.710742187500017, -1.396777343750003], + [30.76220703125, -1.458691406250011], + [30.812597656250006, -1.563085937500006], + [30.8765625, -2.143359375], + [30.85498046875, -2.265429687500003], + [30.828710937500006, -2.338476562500006], + [30.7625, -2.371679687500006], + [30.71484375, -2.363476562500011], + [30.656640625000023, -2.373828125], + [30.593359375, -2.396777343750003], + [30.553613281250023, -2.400097656250011], + [30.408496093750017, -2.31298828125], + [30.117285156250006, -2.416601562500006], + [29.93017578125, -2.339550781250011], + [29.8681640625, -2.71640625], + [29.698046875000017, -2.794726562500003], + [29.390234375, -2.80859375], + [29.10205078125, -2.595703125], + [29.01435546875001, -2.72021484375], + [28.893945312500023, -2.635058593750003], + [28.876367187500023, -2.400292968750009], + [29.13154296875001, -2.195117187500003], + [29.196582031250017, -1.719921875000011], + [29.576953125000017, -1.387890625000011], + [29.82539062500001, -1.335546875], + [29.930078125000023, -1.469921875000011], + [30.360253906250023, -1.074609375], + [30.41230468750001, -1.063085937500006], + [30.46992187500001, -1.066015625], + [30.50996093750001, -1.067285156250009] + ] + ] + }, + "properties": { "name": "Rwanda", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [41.98769531250005, 16.715625], + [42.059960937499994, 16.803515625000017], + [42.15781250000006, 16.570703125000023], + [41.80156250000002, 16.778759765624955], + [41.86044921875006, 17.002539062499977], + [41.98769531250005, 16.715625] + ] + ], + [ + [ + [46.53144531250004, 29.09624023437499], + [47.433203125, 28.989550781250017], + [47.671289062499994, 28.53315429687504], + [48.442480468750006, 28.542919921874983], + [48.80898437499999, 27.895898437499966], + [48.797167968750074, 27.72431640625001], + [49.2375, 27.49272460937499], + [49.17509765625002, 27.43764648437505], + [49.40527343749997, 27.18095703124996], + [50.149804687499994, 26.66264648437499], + [50.00810546875002, 26.678515625000017], + [50.21386718750003, 26.30849609375005], + [50.15546875000004, 26.100537109374955], + [50.03164062499999, 26.11098632812505], + [50.55791015625002, 25.086669921875], + [50.66689453125005, 24.96381835937501], + [50.72558593749997, 24.869384765625057], + [50.80439453125004, 24.789257812499983], + [50.928320312500006, 24.595117187500023], + [50.96601562500004, 24.573925781249983], + [51.022753906250074, 24.56523437499999], + [51.09335937500006, 24.564648437499955], + [51.178027343750074, 24.586718750000017], + [51.26796875, 24.607226562500017], + [51.33847656250006, 24.564355468749994], + [51.41123046875006, 24.570800781250057], + [51.30986328125002, 24.340380859375017], + [51.56835937500003, 24.286181640625074], + [51.592578125000074, 24.07885742187503], + [52.55507812500005, 22.932812499999955], + [55.104296875000074, 22.621484375000023], + [55.185839843750074, 22.7041015625], + [55.64101562499999, 22.001855468749994], + [54.97734375000002, 19.995947265625006], + [51.977636718750006, 18.996142578125074], + [49.04199218750003, 18.58178710937503], + [48.17216796875002, 18.156933593749983], + [47.57958984374997, 17.448339843750034], + [47.44179687499999, 17.111865234375045], + [47.14355468749997, 16.946679687499966], + [46.97568359375006, 16.953466796875034], + [46.72763671875006, 17.26557617187501], + [45.5353515625001, 17.30205078124999], + [45.14804687500006, 17.427441406249955], + [43.91699218749997, 17.32470703124997], + [43.41796875000003, 17.516259765625023], + [43.19091796875003, 17.359375], + [43.16503906249997, 16.689404296874955], + [42.79931640624997, 16.37177734375001], + [42.29394531249997, 17.434960937499966], + [41.75, 17.88574218749997], + [41.22949218750003, 18.678417968749983], + [40.75917968750005, 19.755468750000034], + [40.080664062500006, 20.265917968750017], + [39.728320312500074, 20.390332031249955], + [39.27607421875004, 20.973974609375034], + [39.093554687500074, 21.31035156249999], + [39.14707031250006, 21.518994140624955], + [38.98789062500006, 21.88173828125005], + [39.06201171874997, 22.592187500000023], + [38.46416015625002, 23.71186523437504], + [37.91972656250002, 24.185400390625063], + [37.54306640625006, 24.291650390625023], + [37.18085937500004, 24.82001953125001], + [37.26630859375004, 24.960058593750034], + [37.14882812499999, 25.291113281249977], + [35.18046875000002, 28.03486328125004], + [34.722070312499994, 28.130664062500017], + [34.625, 28.064501953125017], + [34.95078125, 29.353515625], + [36.068457031250006, 29.200537109375006], + [36.28281250000006, 29.355371093750023], + [36.47607421874997, 29.49511718749997], + [36.59179687500003, 29.666113281250006], + [36.703906250000074, 29.831640624999977], + [36.75527343750005, 29.86601562499996], + [37.46923828125003, 29.995068359374955], + [37.49072265625003, 30.01171874999997], + [37.55361328125005, 30.14458007812496], + [37.63359375000002, 30.313281250000045], + [37.64990234374997, 30.330957031249994], + [37.669726562500074, 30.34814453125003], + [37.862890625, 30.44262695312503], + [37.98007812500006, 30.5], + [37.47900390624997, 31.007763671874955], + [37.10527343750002, 31.35517578125004], + [36.95859375000006, 31.491503906250017], + [37.215625, 31.55610351562501], + [37.49335937500004, 31.625878906250023], + [38.111425781250006, 31.78115234375005], + [38.37548828124997, 31.84746093749996], + [38.962304687499994, 31.99492187499999], + [38.99707031249997, 32.00747070312505], + [39.145410156249994, 32.12451171875], + [39.36865234374997, 32.09174804687498], + [39.70410156250003, 32.04252929687499], + [40.02783203124997, 31.995019531249994], + [40.3693359375001, 31.93896484375003], + [40.47890625000005, 31.89335937499999], + [42.07441406250004, 31.08037109374999], + [43.77373046875002, 29.84921875], + [44.71650390625004, 29.19360351562503], + [46.35644531250003, 29.06367187500001], + [46.53144531250004, 29.09624023437499] + ] + ] + ] + }, + "properties": { "name": "Saudi Arabia", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [36.87138671875002, 21.996728515624994], + [36.92695312500001, 21.58652343749999], + [37.25859375000002, 21.108544921874994], + [37.25722656250002, 21.03940429687499], + [37.15058593750001, 21.103759765625], + [37.14111328125, 20.98178710937499], + [37.19316406250002, 20.12070312499999], + [37.471289062500006, 18.820117187500003], + [38.609472656250006, 18.005078125], + [38.422460937500006, 17.823925781249997], + [38.39716796875001, 17.778369140625003], + [38.38554687500002, 17.751269531250003], + [38.37373046875001, 17.717333984375003], + [38.34736328125001, 17.68359375], + [38.28984375000002, 17.637011718750003], + [38.26728515625001, 17.61669921875], + [38.253515625, 17.584765625], + [37.78242187500001, 17.4580078125], + [37.547460937500006, 17.324121093749994], + [37.51015625000002, 17.288134765625003], + [37.45292968750002, 17.108691406250003], + [37.41103515625002, 17.06171875], + [37.24882812500002, 17.056884765625], + [37.16953125, 17.04140625], + [37.0615234375, 17.061279296875], + [37.00898437500001, 17.058886718750003], + [36.995214843750006, 17.020556640625003], + [36.97578125000001, 16.86655273437499], + [36.97871093750001, 16.800585937500003], + [36.887792968750006, 16.624658203124994], + [36.91376953125001, 16.296191406250003], + [36.566015625, 15.362109375], + [36.4267578125, 15.132080078125], + [36.44814453125002, 14.940087890624994], + [36.470800781250006, 14.736474609374994], + [36.52431640625002, 14.2568359375], + [36.12519531250001, 12.75703125], + [35.67021484375002, 12.623730468749997], + [35.1123046875, 11.816552734374994], + [34.93144531250002, 10.864794921874989], + [34.77128906250002, 10.746191406249991], + [34.571875, 10.880175781249989], + [34.34394531250001, 10.658642578124997], + [34.31123046875001, 10.190869140624997], + [34.078125, 9.461523437499991], + [33.87148437500002, 9.506152343749989], + [33.96328125000002, 9.861767578124997], + [33.90703125000002, 10.181445312499989], + [33.13007812500001, 10.745947265624991], + [33.073339843750006, 11.606103515624994], + [33.199316406250006, 12.21728515625], + [32.721875, 12.223095703124997], + [32.73671875000002, 12.009667968749994], + [32.072265625, 12.006738281249994], + [32.338476562500006, 11.710107421874994], + [32.42080078125002, 11.089111328125], + [31.224902343750017, 9.799267578124997], + [30.75537109375, 9.731201171875], + [30.003027343750006, 10.277392578124989], + [29.60546875, 10.065087890624994], + [29.47314453125, 9.768603515624989], + [28.979589843750006, 9.594189453124997], + [28.844531250000017, 9.326074218749994], + [28.048925781250006, 9.32861328125], + [27.880859375, 9.601611328124989], + [27.07421875, 9.613818359374989], + [26.65869140625, 9.484130859375], + [25.91914062500001, 10.169335937499994], + [25.858203125000017, 10.406494140625], + [25.211718750000017, 10.329931640624991], + [25.066992187500006, 10.293798828124991], + [24.785253906250006, 9.774658203125], + [24.53193359375001, 8.886914062499997], + [24.147363281250023, 8.665625], + [23.53730468750001, 8.815820312499994], + [23.46826171875, 9.11474609375], + [23.62265625, 9.340625], + [23.646289062500017, 9.822900390624994], + [22.86005859375001, 10.919677734375], + [22.922656250000017, 11.344873046874994], + [22.591113281250017, 11.579882812499989], + [22.580957031250023, 11.990136718749994], + [22.472460937500017, 12.067773437499994], + [22.352343750000017, 12.660449218749989], + [21.928125, 12.678125], + [21.825292968750006, 12.79052734375], + [22.228125, 13.32958984375], + [22.1064453125, 13.7998046875], + [22.53857421875, 14.161865234375], + [22.38154296875001, 14.550488281249997], + [22.6708984375, 14.722460937500003], + [22.93232421875001, 15.162109375], + [22.933886718750017, 15.533105468749994], + [23.10517578125001, 15.702539062499994], + [23.970800781250006, 15.721533203124991], + [23.980273437500017, 19.496630859375003], + [23.980273437500017, 19.99594726562499], + [24.9794921875, 20.002587890624994], + [24.980273437500017, 21.995849609375], + [28.036425781250017, 21.995361328125], + [31.092675781250023, 21.994873046875], + [31.260644531250023, 22.00229492187499], + [31.400292968750023, 22.202441406250003], + [31.486132812500017, 22.14780273437499], + [31.434472656250023, 21.995849609375], + [36.87138671875002, 21.996728515624994] + ] + ] + }, + "properties": { "name": "Sudan", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [34.078125, 9.461523437499991], + [34.07275390625, 8.545263671874991], + [33.95332031250001, 8.443505859374994], + [33.28105468750002, 8.437255859375], + [32.99892578125002, 7.899511718749991], + [33.902441406250006, 7.509521484375], + [34.06425781250002, 7.225732421874994], + [34.71064453125001, 6.660302734374994], + [34.98359375000001, 5.858300781249994], + [35.26835937500002, 5.492285156249991], + [35.08447265625, 5.311865234374991], + [34.87832031250002, 5.109570312499997], + [34.63984375000001, 4.87548828125], + [34.38017578125002, 4.620654296874989], + [34.176855468750006, 4.419091796874994], + [33.97607421875, 4.22021484375], + [33.74160156250002, 3.985253906249994], + [33.568457031250006, 3.81171875], + [33.489355468750006, 3.755078125], + [32.99726562500001, 3.880175781249989], + [32.33574218750002, 3.706201171874994], + [32.13593750000001, 3.519726562499997], + [31.79804687500001, 3.802636718749994], + [31.547167968750017, 3.677587890624991], + [31.15234375, 3.785595703124997], + [30.83857421875001, 3.49072265625], + [30.757226562500023, 3.62421875], + [30.586718750000017, 3.62421875], + [30.50830078125, 3.835693359375], + [30.194921875, 3.98193359375], + [29.676855468750006, 4.5869140625], + [29.469628906250023, 4.61181640625], + [29.224902343750017, 4.391894531249989], + [28.72705078125, 4.504980468749991], + [28.427539062500017, 4.324169921874997], + [28.19208984375001, 4.350244140624994], + [27.7880859375, 4.644677734374994], + [27.4033203125, 5.109179687499989], + [27.143945312500023, 5.722949218749989], + [26.514257812500006, 6.069238281249994], + [26.30859375, 6.455322265625], + [26.36181640625, 6.635302734374989], + [25.27890625, 7.427490234375], + [25.18134765625001, 7.557226562499991], + [25.20039062500001, 7.807910156249989], + [24.853320312500017, 8.137548828124991], + [24.291406250000023, 8.29140625], + [24.147363281250023, 8.665625], + [24.53193359375001, 8.886914062499997], + [24.785253906250006, 9.774658203125], + [25.066992187500006, 10.293798828124991], + [25.211718750000017, 10.329931640624991], + [25.858203125000017, 10.406494140625], + [25.91914062500001, 10.169335937499994], + [26.65869140625, 9.484130859375], + [27.07421875, 9.613818359374989], + [27.880859375, 9.601611328124989], + [28.048925781250006, 9.32861328125], + [28.844531250000017, 9.326074218749994], + [28.979589843750006, 9.594189453124997], + [29.47314453125, 9.768603515624989], + [29.60546875, 10.065087890624994], + [30.003027343750006, 10.277392578124989], + [30.75537109375, 9.731201171875], + [31.224902343750017, 9.799267578124997], + [32.42080078125002, 11.089111328125], + [32.338476562500006, 11.710107421874994], + [32.072265625, 12.006738281249994], + [32.73671875000002, 12.009667968749994], + [32.721875, 12.223095703124997], + [33.199316406250006, 12.21728515625], + [33.073339843750006, 11.606103515624994], + [33.13007812500001, 10.745947265624991], + [33.90703125000002, 10.181445312499989], + [33.96328125000002, 9.861767578124997], + [33.87148437500002, 9.506152343749989], + [34.078125, 9.461523437499991] + ] + ] + }, + "properties": { "name": "S. Sudan", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-12.280615234374977, 14.809033203124997], + [-12.228417968749994, 14.45859375], + [-12.019189453124994, 14.206494140624997], + [-11.960888671874983, 13.875292968750003], + [-12.05419921875, 13.633056640625], + [-11.831689453124994, 13.315820312499994], + [-11.634960937499983, 13.369873046875], + [-11.390380859375, 12.941992187499991], + [-11.389404296875, 12.404394531249991], + [-12.399072265624994, 12.340087890625], + [-12.930712890624989, 12.532275390624989], + [-13.061279296875, 12.489990234375], + [-13.082910156249994, 12.633544921875], + [-13.729248046875, 12.673925781249991], + [-14.06484375, 12.67529296875], + [-14.349218749999977, 12.676416015624994], + [-15.196093749999989, 12.679931640625], + [-15.3779296875, 12.588964843749991], + [-15.574804687499977, 12.490380859374994], + [-15.839550781249983, 12.43789062499999], + [-16.144189453124994, 12.45742187499999], + [-16.24150390624999, 12.443310546874997], + [-16.41630859374999, 12.36767578125], + [-16.521337890624977, 12.3486328125], + [-16.656933593749983, 12.364355468749991], + [-16.711816406249994, 12.354833984374991], + [-16.76030273437499, 12.52578125], + [-16.44287109375, 12.609472656249991], + [-16.59765625, 12.715283203124997], + [-16.743896484375, 12.58544921875], + [-16.763330078124994, 13.064160156249997], + [-16.648779296874977, 13.154150390624991], + [-15.834277343749989, 13.156445312499997], + [-15.814404296874983, 13.325146484374997], + [-15.286230468749977, 13.39599609375], + [-15.151123046875, 13.556494140624991], + [-14.246777343749983, 13.23583984375], + [-13.826708984374989, 13.4078125], + [-13.977392578124977, 13.54345703125], + [-14.405468749999983, 13.503710937500003], + [-15.108349609374983, 13.81210937499999], + [-15.426855468749977, 13.727001953124997], + [-15.509667968749994, 13.586230468750003], + [-16.56230468749999, 13.587304687499994], + [-16.766943359374977, 13.904931640624994], + [-16.618115234374983, 14.04052734375], + [-16.791748046875, 14.004150390625], + [-17.168066406249977, 14.640625], + [-17.345800781249977, 14.729296875], + [-17.445019531249983, 14.651611328125], + [-17.53564453125, 14.755126953125], + [-17.147167968749983, 14.922021484374994], + [-16.843408203124994, 15.293994140625003], + [-16.570751953124983, 15.734423828125003], + [-16.535253906249977, 15.83837890625], + [-16.502050781249977, 15.917333984374991], + [-16.480078124999977, 16.097216796875003], + [-16.441015624999977, 16.204541015624997], + [-16.239013671875, 16.531298828125003], + [-15.768212890624994, 16.485107421875], + [-14.990625, 16.676904296874994], + [-14.300097656249989, 16.580273437499997], + [-13.868457031249989, 16.148144531249997], + [-13.756640624999989, 16.172509765624994], + [-13.40966796875, 16.05917968749999], + [-13.105273437499989, 15.57177734375], + [-12.735253906249994, 15.13125], + [-12.40869140625, 14.889013671874991], + [-12.280615234374977, 14.809033203124997] + ] + ] + }, + "properties": { "name": "Senegal", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [103.9697265625, 1.331445312499994], + [103.65019531249999, 1.325537109374991], + [103.81796875000003, 1.447070312499989], + [103.9697265625, 1.331445312499994] + ] + ] + }, + "properties": { "name": "Singapore", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-37.10332031249996, -54.065625], + [-36.70380859375001, -54.10810546874999], + [-36.64741210937498, -54.26230468749996], + [-36.32646484374996, -54.251171875], + [-35.79858398437497, -54.76347656250002], + [-36.08549804687499, -54.86679687500001], + [-36.885986328125, -54.33945312499996], + [-37.63090820312496, -54.16748046875001], + [-37.61884765625001, -54.04208984375004], + [-38.017431640625034, -54.008007812500026], + [-37.10332031249996, -54.065625] + ] + ] + }, + "properties": { "name": "S. Geo. and S. Sandw. Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-5.692138671874972, -15.997753906249997], + [-5.782519531250017, -16.00400390625002], + [-5.707861328124977, -15.90615234374998], + [-5.692138671874972, -15.997753906249997] + ] + ] + }, + "properties": { "name": "Saint Helena", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [160.57626953125006, -11.797851562500028], + [160.44306640625004, -11.814941406249957], + [159.98632812499997, -11.494726562500006], + [160.57626953125006, -11.797851562500028] + ] + ], + [ + [ + [166.13320312500005, -10.757812499999972], + [165.90400390625004, -10.851464843749966], + [165.79101562500003, -10.784765624999963], + [166.02382812500005, -10.6611328125], + [166.13320312500005, -10.757812499999972] + ] + ], + [ + [ + [161.71533203124997, -10.387304687499991], + [162.10537109375005, -10.45380859375004], + [162.37333984375002, -10.823242187499986], + [161.78681640625004, -10.716894531249991], + [161.53789062500007, -10.566406249999972], + [161.4870117187501, -10.361425781249963], + [161.29394531250003, -10.326464843750031], + [161.30478515625012, -10.204394531250031], + [161.71533203124997, -10.387304687499991] + ] + ], + [ + [ + [161.54785156249997, -9.625683593749997], + [161.55380859375012, -9.769726562500026], + [161.40976562500006, -9.681640625000028], + [161.36416015625, -9.353417968750037], + [161.54785156249997, -9.625683593749997] + ] + ], + [ + [ + [159.75039062500005, -9.272656250000011], + [159.97060546875, -9.433300781249969], + [160.35458984375006, -9.421582031249983], + [160.81894531250006, -9.862792968749986], + [160.64921875000002, -9.92861328124998], + [159.80273437499997, -9.763476562500003], + [159.61230468749997, -9.470703124999943], + [159.62558593750012, -9.311230468749969], + [159.75039062500005, -9.272656250000011] + ] + ], + [ + [ + [160.1681640625001, -8.995507812500037], + [160.40751953125007, -9.140332031249969], + [160.10537109375, -9.080761718749997], + [160.1681640625001, -8.995507812500037] + ] + ], + [ + [ + [159.18857421875006, -9.123535156250014], + [159.03632812500004, -9.075], + [159.12978515625, -8.99306640624998], + [159.22841796875005, -9.029980468749955], + [159.18857421875006, -9.123535156250014] + ] + ], + [ + [ + [158.10791015625003, -8.684179687500034], + [157.93759765625006, -8.73642578125002], + [157.90927734375006, -8.565625], + [158.10546874999997, -8.536816406250026], + [158.10791015625003, -8.684179687500034] + ] + ], + [ + [ + [157.38896484375002, -8.713476562499963], + [157.2123046875, -8.565039062500006], + [157.37949218750012, -8.420898437499943], + [157.38896484375002, -8.713476562499963] + ] + ], + [ + [ + [160.7494140625, -8.313964843750014], + [160.99765625000006, -8.612011718749983], + [160.94433593750003, -8.799023437499983], + [161.15869140624997, -8.961816406250009], + [161.36738281250004, -9.61123046874998], + [160.77207031250012, -8.963867187499986], + [160.7140625000001, -8.539257812499997], + [160.59042968750006, -8.372753906249997], + [160.7494140625, -8.313964843750014] + ] + ], + [ + [ + [157.76347656250002, -8.242187499999957], + [157.89843749999997, -8.506347656249943], + [157.81933593750003, -8.612011718749983], + [157.58789062500003, -8.445410156249963], + [157.5580078125, -8.269921875], + [157.30244140625004, -8.33330078124996], + [157.21757812500002, -8.262792968749977], + [157.490625, -7.965722656250037], + [157.76347656250002, -8.242187499999957] + ] + ], + [ + [ + [157.171875, -8.108105468749997], + [156.95830078125002, -8.014355468749997], + [157.02412109375004, -7.867871093749997], + [157.18613281250006, -7.941210937500017], + [157.171875, -8.108105468749997] + ] + ], + [ + [ + [156.687890625, -7.92304687500004], + [156.5109375000001, -7.707812499999974], + [156.5609375, -7.574023437499989], + [156.80908203124997, -7.722851562500026], + [156.687890625, -7.92304687500004] + ] + ], + [ + [ + [159.8791015625001, -8.534277343749949], + [158.9440429687501, -8.04072265625004], + [158.457421875, -7.544726562499974], + [158.734375, -7.604296875000031], + [159.43144531250002, -8.029003906249955], + [159.84306640625002, -8.326953124999989], + [159.8791015625001, -8.534277343749949] + ] + ], + [ + [ + [155.83984374999997, -7.097167968750014], + [155.67753906250002, -7.08896484375002], + [155.73896484375004, -6.972949218750017], + [155.83984374999997, -7.097167968750014] + ] + ], + [ + [ + [157.48671875000005, -7.330371093750003], + [157.44130859375, -7.425683593749966], + [157.10156249999997, -7.323632812499966], + [156.4525390625, -6.638281249999963], + [157.03027343750003, -6.891992187499952], + [157.19335937499997, -7.160351562499997], + [157.48671875000005, -7.330371093750003] + ] + ] + ] + }, + "properties": { "name": "Solomon Is.", "childNum": 16 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-12.526074218749926, 7.436328125000017], + [-12.951611328124926, 7.570849609374989], + [-12.615234374999972, 7.63720703125], + [-12.5125, 7.582421875000037], + [-12.526074218749926, 7.436328125000017] + ] + ], + [ + [ + [-10.758593749999989, 9.385351562499991], + [-10.682714843750006, 9.289355468749974], + [-10.687646484374937, 9.261132812499994], + [-10.749951171874926, 9.12236328124996], + [-10.747021484374955, 9.095263671875045], + [-10.726855468749932, 9.081689453125023], + [-10.615966796875, 9.059179687499977], + [-10.500537109375017, 8.687548828125017], + [-10.677343749999977, 8.400585937499997], + [-10.712109374999955, 8.335253906250017], + [-10.686962890624983, 8.321679687500009], + [-10.652636718749989, 8.330273437499983], + [-10.604003906249943, 8.319482421874994], + [-10.55771484374992, 8.315673828125028], + [-10.496435546874977, 8.362109374999974], + [-10.394433593749966, 8.480957031250028], + [-10.360058593749983, 8.49550781249998], + [-10.283203124999972, 8.48515625], + [-10.285742187499949, 8.454101562499986], + [-10.314648437499983, 8.310839843750017], + [-10.359814453124926, 8.187939453125026], + [-10.570849609374932, 8.071142578125034], + [-10.6474609375, 7.759375], + [-10.878076171874994, 7.538232421874994], + [-11.267675781249977, 7.232617187499997], + [-11.507519531249983, 6.906542968750003], + [-12.48564453124996, 7.386279296875045], + [-12.480273437499932, 7.75327148437502], + [-12.697607421874977, 7.715869140625045], + [-12.850878906249932, 7.818701171875034], + [-12.956933593749966, 8.145312500000045], + [-13.148974609374989, 8.214599609375043], + [-13.272753906249989, 8.429736328124989], + [-13.085009765624932, 8.42475585937504], + [-12.894091796874932, 8.62978515624998], + [-13.181835937499955, 8.576904296875043], + [-13.206933593749994, 8.843115234375006], + [-13.059472656249966, 8.881152343750031], + [-13.292675781249955, 9.04921875], + [-13.077294921874966, 9.069628906249974], + [-12.958789062499989, 9.263330078124994], + [-12.755859374999943, 9.373583984374989], + [-12.557861328125, 9.704980468749994], + [-12.427978515625028, 9.898144531250011], + [-12.142333984375, 9.87539062499999], + [-11.911083984374955, 9.993017578124977], + [-11.273632812499955, 9.996533203124983], + [-11.205664062499949, 9.977734374999969], + [-11.180859374999955, 9.925341796875045], + [-11.047460937499977, 9.786328125000054], + [-10.758593749999989, 9.385351562499991] + ] + ] + ] + }, + "properties": { "name": "Sierra Leone", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-89.36259765624999, 14.416015625], + [-89.1205078125, 14.370214843749991], + [-88.51254882812499, 13.978955078124997], + [-88.504345703125, 13.964208984374991], + [-88.49765625, 13.904541015625], + [-88.482666015625, 13.854248046875], + [-88.44912109375, 13.850976562499994], + [-88.40849609374999, 13.87539062499999], + [-88.27622070312499, 13.942675781250003], + [-88.151025390625, 13.987353515625003], + [-87.99101562499999, 13.879638671875], + [-87.8919921875, 13.894970703124997], + [-87.80224609375, 13.889990234374991], + [-87.7314453125, 13.841064453125], + [-87.71533203125, 13.812695312499997], + [-87.781884765625, 13.521386718749994], + [-87.930859375, 13.1806640625], + [-88.68564453124999, 13.281494140625], + [-88.51201171874999, 13.183935546874991], + [-89.80419921875, 13.560107421875003], + [-90.09521484375, 13.736523437499997], + [-90.04814453124999, 13.904052734375], + [-89.54716796874999, 14.241259765625003], + [-89.5736328125, 14.390087890624997], + [-89.36259765624999, 14.416015625] + ] + ] + }, + "properties": { "name": "El Salvador", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-56.26708984374997, 46.838476562500034], + [-56.38476562499994, 46.81943359375006], + [-56.36464843749994, 47.09897460937498], + [-56.26708984374997, 46.838476562500034] + ] + ] + }, + "properties": { "name": "St. Pierre and Miquelon", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [6.659960937499989, 0.120654296874989], + [6.51972656250004, 0.066308593750023], + [6.468164062499994, 0.22734375], + [6.68691406250008, 0.404394531249977], + [6.75, 0.24345703124996], + [6.659960937499989, 0.120654296874989] + ] + ], + [ + [ + [7.423828125, 1.567724609375006], + [7.330664062500034, 1.603369140624991], + [7.414453125000051, 1.699121093750037], + [7.423828125, 1.567724609375006] + ] + ] + ] + }, + "properties": { "name": "São Tomé and Principe", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-54.03422851562499, 3.62939453125], + [-54.00957031249999, 3.448535156249989], + [-54.06318359375, 3.353320312499989], + [-54.18803710937499, 3.178759765624989], + [-54.203125, 3.13818359375], + [-54.17070312499999, 2.993603515624997], + [-54.18808593749999, 2.874853515624991], + [-54.1955078125, 2.81787109375], + [-54.256738281249994, 2.713720703124991], + [-54.402001953124994, 2.461523437499991], + [-54.53593749999999, 2.343310546874989], + [-54.56840820312499, 2.342578124999989], + [-54.604736328125, 2.335791015624991], + [-54.61625976562499, 2.326757812499991], + [-54.661865234375, 2.327539062499994], + [-54.697412109374994, 2.359814453124997], + [-54.72221679687499, 2.441650390625], + [-54.87607421874999, 2.450390625], + [-54.92656249999999, 2.497363281249989], + [-54.968408203124994, 2.54833984375], + [-54.978662109374994, 2.59765625], + [-55.005810546875, 2.59296875], + [-55.0703125, 2.54833984375], + [-55.11411132812499, 2.539208984374994], + [-55.1876953125, 2.547509765624994], + [-55.286035156249994, 2.499658203124994], + [-55.343994140625, 2.48876953125], + [-55.38535156249999, 2.440625], + [-55.73056640624999, 2.406152343749994], + [-55.957470703125, 2.520458984374997], + [-55.99350585937499, 2.497509765624997], + [-56.02036132812499, 2.392773437499997], + [-56.0451171875, 2.364404296874994], + [-56.087792968749994, 2.34130859375], + [-56.12939453125, 2.299511718749997], + [-56.1376953125, 2.259033203125], + [-56.073632812499994, 2.236767578124997], + [-56.02006835937499, 2.158154296874997], + [-55.96196289062499, 2.095117187499994], + [-55.91533203124999, 2.03955078125], + [-55.921630859375, 1.976660156249991], + [-55.929638671875, 1.8875], + [-56.01992187499999, 1.842236328124997], + [-56.4828125, 1.942138671875], + [-56.704345703125, 2.036474609374991], + [-57.19736328124999, 2.853271484375], + [-57.303662109375, 3.377099609374994], + [-57.646728515625, 3.39453125], + [-58.05429687499999, 4.101660156249991], + [-57.84599609374999, 4.668164062499997], + [-57.91704101562499, 4.820410156249991], + [-57.711083984374994, 4.991064453124991], + [-57.331005859375, 5.020166015624994], + [-57.20981445312499, 5.195410156249991], + [-57.3185546875, 5.335351562499994], + [-57.194775390625, 5.5484375], + [-56.96982421874999, 5.992871093749997], + [-56.235595703125, 5.885351562499991], + [-55.897607421874994, 5.699316406249991], + [-55.909912109375, 5.892626953124989], + [-55.648339843749994, 5.985888671874989], + [-54.83369140625, 5.988330078124989], + [-54.05419921875, 5.807910156249989], + [-54.08046875, 5.502246093749989], + [-54.4796875, 4.836523437499991], + [-54.350732421874994, 4.054101562499994], + [-54.03422851562499, 3.62939453125] + ] + ] + }, + "properties": { "name": "Suriname", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [22.538671875, 49.072705078125], + [22.52412109375001, 49.031396484374994], + [22.389453125000017, 48.873486328125], + [22.295214843750017, 48.685839843749996], + [22.142871093750017, 48.568505859374994], + [22.1318359375, 48.405322265624996], + [21.766992187500023, 48.3380859375], + [21.45136718750001, 48.55224609375], + [20.490039062500017, 48.526904296874996], + [20.333789062500017, 48.295556640624994], + [19.95039062500001, 48.146630859374994], + [19.625390625000023, 48.223095703125], + [18.791894531250023, 48.000292968749996], + [18.72421875, 47.787158203124996], + [17.76191406250001, 47.770166015624994], + [17.147363281250023, 48.00595703125], + [16.86542968750001, 48.3869140625], + [16.953125, 48.598828125], + [17.135644531250023, 48.841064453125], + [17.75849609375001, 48.888134765625], + [18.0859375, 49.06513671875], + [18.160937500000017, 49.257373046874996], + [18.83222656250001, 49.510791015624996], + [19.1494140625, 49.4], + [19.44160156250001, 49.597705078124996], + [19.77392578125, 49.37216796875], + [19.756640625000017, 49.204394531249996], + [20.0576171875, 49.181298828124994], + [20.36298828125001, 49.38525390625], + [20.868457031250017, 49.314697265625], + [21.079394531250017, 49.418261718749996], + [21.6396484375, 49.411962890625], + [22.020117187500006, 49.209521484374996], + [22.473046875000023, 49.081298828125], + [22.538671875, 49.072705078125] + ] + ] + }, + "properties": { "name": "Slovakia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [16.516210937500006, 46.499902343749994], + [16.427636718750023, 46.5244140625], + [16.321191406250023, 46.534619140625], + [16.1064453125, 46.382226562499994], + [15.608984375, 46.171923828124996], + [15.592578125000017, 46.139990234375], + [15.596875, 46.109228515625], + [15.675585937500017, 45.983691406249996], + [15.652148437500017, 45.862158203125], + [15.277050781250011, 45.7326171875], + [15.353710937500011, 45.659912109375], + [15.283593750000023, 45.5796875], + [15.291210937500011, 45.541552734374996], + [15.32666015625, 45.502294921875], + [15.339453125, 45.467041015625], + [15.242089843750023, 45.44140625], + [15.110449218750006, 45.45078125], + [14.95458984375, 45.499902343749994], + [14.793066406250006, 45.47822265625], + [14.649511718750006, 45.571484375], + [14.591796875, 45.651269531249994], + [14.56884765625, 45.6572265625], + [14.548448660714302, 45.628388671875], + [14.507586495535731, 45.59039341517857], + [14.42734375, 45.505761718749994], + [14.369921875000017, 45.4814453125], + [13.878710937500017, 45.428369140624994], + [13.577929687500017, 45.516894531249996], + [13.8447265625, 45.59287109375], + [13.831152343750006, 45.680419921875], + [13.663476562500023, 45.7919921875], + [13.6005859375, 45.979785156249996], + [13.509179687500023, 45.973779296874994], + [13.487695312500023, 45.987109375], + [13.480273437500017, 46.009228515625], + [13.486425781250006, 46.03955078125], + [13.548046875000011, 46.089111328125], + [13.616601562500023, 46.133105468749996], + [13.634960937500011, 46.157763671874996], + [13.632519531250011, 46.177050781249996], + [13.420996093750006, 46.212304687499994], + [13.399511718750006, 46.317529296874994], + [13.563281250000017, 46.415087890624996], + [13.637109375000023, 46.448535156249996], + [13.6796875, 46.462890625], + [13.7, 46.520263671875], + [14.5498046875, 46.399707031249996], + [14.893261718750011, 46.605908203125], + [15.957617187500006, 46.677636718749994], + [16.093066406250017, 46.86328125], + [16.283593750000023, 46.857275390625], + [16.516210937500006, 46.499902343749994] + ] + ] + }, + "properties": { "name": "Slovenia", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [16.52851562500001, 56.29052734375], + [16.431640625, 56.24375], + [16.41230468750004, 56.568994140624994], + [17.02539062499997, 57.345068359375006], + [16.52851562500001, 56.29052734375] + ] + ], + [ + [ + [19.076464843750045, 57.8359375], + [18.813867187500023, 57.70620117187502], + [18.907910156250068, 57.39833984375002], + [18.146386718749994, 56.920507812500006], + [18.285351562500068, 57.08320312500001], + [18.136523437500045, 57.55664062500003], + [18.53740234374999, 57.83056640625006], + [18.90058593750001, 57.91547851562504], + [19.076464843750045, 57.8359375] + ] + ], + [ + [ + [19.156347656250063, 57.92260742187497], + [19.086523437500034, 57.86499023437506], + [19.134863281250034, 57.98134765625002], + [19.331445312500023, 57.962890625], + [19.156347656250063, 57.92260742187497] + ] + ], + [ + [ + [24.15546875000004, 65.80527343750006], + [23.102343750000074, 65.73535156250003], + [22.400976562500006, 65.86210937499999], + [22.254003906250006, 65.59755859375002], + [21.565527343750063, 65.40810546874997], + [21.609179687500074, 65.261376953125], + [21.410351562500068, 65.31743164062505], + [21.57392578125001, 65.12578124999999], + [21.138183593750057, 64.80869140625006], + [21.519628906250034, 64.46308593749998], + [20.76269531250003, 63.86782226562505], + [18.60644531250003, 63.17827148437499], + [18.31289062500008, 62.996386718750045], + [18.46308593750004, 62.895849609375006], + [18.170019531250034, 62.789355468750074], + [17.906640625000023, 62.88676757812502], + [18.037304687500068, 62.60053710937498], + [17.834472656250057, 62.50273437500002], + [17.410253906250063, 62.508398437500034], + [17.633691406249994, 62.23300781250006], + [17.374511718750057, 61.866308593750034], + [17.465429687500006, 61.68447265625005], + [17.196386718750006, 61.72456054687504], + [17.13076171875005, 61.57573242187499], + [17.25097656250003, 60.70078125], + [17.6611328125, 60.53515625000003], + [17.955761718750068, 60.589794921874955], + [18.85273437500001, 60.02587890625], + [18.970507812500045, 59.757226562499994], + [17.964257812500023, 59.359375], + [18.56025390625004, 59.39448242187498], + [18.285351562500068, 59.109375], + [16.978125, 58.65415039062506], + [16.214257812500023, 58.636669921874955], + [16.92382812499997, 58.49257812499999], + [16.651953125, 58.43432617187503], + [16.65224609375008, 57.50068359374998], + [16.348730468750063, 56.70927734374996], + [15.826660156250028, 56.12495117187501], + [14.782031250000017, 56.16191406250002], + [14.754785156250051, 56.03315429687498], + [14.401953125000034, 55.97675781250004], + [14.21503906250004, 55.83261718749998], + [14.341699218749994, 55.52773437500002], + [14.17373046875008, 55.396630859374966], + [12.885839843750063, 55.41137695312506], + [12.973925781250074, 55.748144531250006], + [12.471191406250057, 56.29052734375], + [12.801660156250051, 56.263916015625], + [12.65644531250004, 56.44057617187502], + [12.857421875000028, 56.45239257812503], + [12.883691406250051, 56.61772460937496], + [12.421484375000034, 56.906396484374966], + [11.449316406250063, 58.118359374999955], + [11.43154296875008, 58.339990234374994], + [11.24824218750004, 58.369140625], + [11.14716796875004, 58.98862304687498], + [11.19580078125, 59.07827148437505], + [11.388281250000063, 59.036523437499966], + [11.470703125000057, 58.909521484375034], + [11.64277343750004, 58.92607421875002], + [11.798144531250074, 59.28989257812498], + [11.680761718750034, 59.59228515625003], + [12.486132812500074, 60.10678710937506], + [12.588671874999989, 60.450732421875045], + [12.29414062500004, 61.00268554687506], + [12.706054687500028, 61.059863281250074], + [12.88076171875008, 61.35229492187506], + [12.155371093750006, 61.720751953125045], + [12.303515625000074, 62.28559570312501], + [11.999902343750051, 63.29169921875001], + [12.175195312500051, 63.595947265625], + [12.792773437500017, 64], + [13.203515625000023, 64.07509765625], + [13.960546875000063, 64.01401367187498], + [14.141210937500006, 64.17353515624998], + [14.077636718750028, 64.464013671875], + [13.650292968750023, 64.58154296874997], + [14.47968750000004, 65.30146484374998], + [14.543261718750045, 66.12934570312498], + [15.483789062500051, 66.30595703124999], + [15.422949218750006, 66.48984374999998], + [16.40351562500004, 67.05498046875002], + [16.12744140625, 67.42583007812507], + [16.783593750000023, 67.89501953125], + [17.324609375000023, 68.10380859374999], + [17.91669921875001, 67.96489257812502], + [18.303027343750045, 68.55541992187497], + [19.969824218750063, 68.35639648437501], + [20.348046875000023, 68.84873046875003], + [20.116699218750057, 69.02089843750005], + [20.622167968750006, 69.036865234375], + [21.99746093750005, 68.52060546874998], + [22.854101562500034, 68.36733398437502], + [23.63886718750004, 67.95439453125002], + [23.454882812500045, 67.46025390625007], + [23.733593750000068, 67.42290039062499], + [23.64150390625005, 67.12939453124997], + [23.988574218750045, 66.81054687500003], + [23.700292968750034, 66.25263671874998], + [24.15546875000004, 65.80527343750006] + ] + ] + ] + }, + "properties": { "name": "Sweden", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [31.9482421875, -25.957617187500006], + [32.060546875, -26.018359375], + [32.04140625000002, -26.28125], + [32.10595703125, -26.52001953125], + [32.112890625, -26.839453125], + [32.02480468750002, -26.811132812500006], + [31.994726562500006, -26.817480468750006], + [31.967187500000023, -26.96064453125001], + [31.946093750000017, -27.173632812500003], + [31.958398437500023, -27.30585937500001], + [31.742578125000023, -27.30996093750001], + [31.469531250000017, -27.295507812500006], + [31.274023437500006, -27.238378906250006], + [31.063378906250023, -27.1123046875], + [30.938085937500006, -26.915820312500003], + [30.88330078125, -26.79238281250001], + [30.806738281250006, -26.785253906250006], + [30.794335937500023, -26.764257812500006], + [30.803320312500006, -26.41347656250001], + [31.08808593750001, -25.98066406250001], + [31.207324218750017, -25.843359375], + [31.33515625000001, -25.75556640625001], + [31.382617187500017, -25.74296875], + [31.415136718750006, -25.74658203125], + [31.921679687500017, -25.96875], + [31.9482421875, -25.957617187500006] + ] + ] + }, + "properties": { "name": "Swaziland", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [55.54033203125002, -4.693066406250011], + [55.54296875, -4.785546875], + [55.383398437500006, -4.609277343750009], + [55.45576171875001, -4.558789062500011], + [55.54033203125002, -4.693066406250011] + ] + ] + }, + "properties": { "name": "Seychelles", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [42.358984375, 37.10859375], + [41.78857421875, 36.59716796875], + [41.41679687500002, 36.5146484375], + [41.295996093750006, 36.383349609374996], + [41.354101562500006, 35.640429687499996], + [41.19472656250002, 34.768994140625], + [40.98701171875001, 34.429052734375], + [38.773535156250006, 33.372216796874994], + [36.818359375, 32.317285156249994], + [36.3720703125, 32.3869140625], + [35.78730468750001, 32.734912109374996], + [35.91347656250002, 32.94960937499999], + [35.869140625, 33.43173828125], + [36.03447265625002, 33.58505859375], + [35.98613281250002, 33.75263671875], + [36.36503906250002, 33.83935546875], + [36.27783203125, 33.92529296875], + [36.5849609375, 34.221240234374996], + [36.50439453125, 34.432373046875], + [36.32988281250002, 34.499609375], + [36.383886718750006, 34.65791015625], + [35.97626953125001, 34.629199218749996], + [35.902441406250006, 35.420703125], + [35.76445312500002, 35.571582031249996], + [35.83964843750002, 35.84921875], + [35.892675781250006, 35.916552734374996], + [35.96757812500002, 35.910058593749994], + [36.12734375000002, 35.831445312499994], + [36.15361328125002, 35.833886718749994], + [36.34755859375002, 36.003515625], + [36.37539062500002, 36.171240234375], + [36.63671875, 36.233984375], + [36.64140625000002, 36.263525390625], + [36.5375, 36.45742187499999], + [36.54667968750002, 36.50634765625], + [36.596875, 36.7013671875], + [36.62841796875, 36.777685546875], + [36.65859375000002, 36.802539062499996], + [36.77656250000001, 36.79267578125], + [36.94179687500002, 36.7583984375], + [36.9853515625, 36.702392578125], + [37.06621093750002, 36.652636718749996], + [37.43632812500002, 36.643310546875], + [37.523535156250006, 36.6783203125], + [37.7203125, 36.743701171874996], + [37.90664062500002, 36.79462890625], + [38.19169921875002, 36.9015625], + [38.7666015625, 36.693115234375], + [38.90644531250001, 36.694677734375], + [39.1083984375, 36.680566406249994], + [39.35664062500001, 36.681591796875], + [39.50146484375, 36.70224609375], + [39.6865234375, 36.738623046875], + [40.01640625000002, 36.826074218749994], + [40.705664062500006, 37.097705078124996], + [41.886816406250006, 37.156396484374994], + [42.05986328125002, 37.2060546875], + [42.16787109375002, 37.288623046874996], + [42.202734375, 37.29726562499999], + [42.24755859375, 37.2822265625], + [42.2685546875, 37.2765625], + [42.31289062500002, 37.22958984375], + [42.358984375, 37.10859375] + ] + ] + }, + "properties": { "name": "Syria", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-72.3328125, 21.85136718749999], + [-72.14433593750002, 21.79272460937503], + [-72.33544921874994, 21.758007812499983], + [-72.3328125, 21.85136718749999] + ] + ] + }, + "properties": { "name": "Turks and Caicos Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [23.980273437500017, 19.496630859375003], + [23.970800781250006, 15.721533203124991], + [23.10517578125001, 15.702539062499994], + [22.933886718750017, 15.533105468749994], + [22.93232421875001, 15.162109375], + [22.6708984375, 14.722460937500003], + [22.38154296875001, 14.550488281249997], + [22.53857421875, 14.161865234375], + [22.1064453125, 13.7998046875], + [22.228125, 13.32958984375], + [21.825292968750006, 12.79052734375], + [21.928125, 12.678125], + [22.352343750000017, 12.660449218749989], + [22.472460937500017, 12.067773437499994], + [22.580957031250023, 11.990136718749994], + [22.591113281250017, 11.579882812499989], + [22.922656250000017, 11.344873046874994], + [22.86005859375001, 10.919677734375], + [22.49384765625001, 10.996240234374994], + [21.771484375, 10.642822265625], + [21.682714843750006, 10.289843749999989], + [20.773242187500017, 9.405664062499994], + [20.342089843750017, 9.127099609374994], + [18.95625, 8.938867187499994], + [18.886035156250017, 8.836035156249991], + [19.108691406250017, 8.656152343749994], + [18.56416015625001, 8.0458984375], + [17.6494140625, 7.98359375], + [16.784765625, 7.550976562499997], + [16.545312500000023, 7.865478515625], + [16.37890625, 7.683544921874997], + [15.957617187500006, 7.507568359375], + [15.480078125, 7.523779296874991], + [15.5498046875, 7.787890624999989], + [15.1162109375, 8.557324218749997], + [14.332324218750017, 9.20351562499999], + [13.977246093750011, 9.691552734374994], + [14.243261718750006, 9.979736328125], + [15.654882812500006, 10.0078125], + [15.276074218750011, 10.357373046874997], + [15.132226562500023, 10.648486328124989], + [15.029882812500006, 11.11367187499999], + [15.08125, 11.845507812499989], + [14.847070312500023, 12.502099609374994], + [14.461718750000017, 13.021777343749989], + [14.244824218750011, 13.07734375], + [14.06396484375, 13.07851562499999], + [13.932324218750011, 13.258496093749997], + [13.606347656250023, 13.70458984375], + [13.505761718750023, 14.134423828124994], + [13.4482421875, 14.380664062500003], + [14.367968750000017, 15.750146484374994], + [15.474316406250011, 16.908398437499997], + [15.735058593750011, 19.904052734375], + [15.963183593750017, 20.34619140625], + [15.587109375000011, 20.733300781249994], + [15.607324218750023, 20.954394531250003], + [15.181835937500011, 21.523388671874997], + [14.97900390625, 22.99619140624999], + [15.984082031250011, 23.445214843749994], + [20.14765625000001, 21.38925781249999], + [23.980273437500017, 19.496630859375003] + ] + ] + }, + "properties": { "name": "Chad", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [0.900488281250006, 10.993261718749991], + [0.763378906250011, 10.386669921874997], + [1.330078125, 9.996972656249994], + [1.3857421875, 9.361669921874991], + [1.600195312500006, 9.050048828125], + [1.624707031250011, 6.997314453125], + [1.530957031250011, 6.992431640625], + [1.777929687500006, 6.294628906249997], + [1.62265625, 6.216796875], + [1.187207031250011, 6.089404296874989], + [0.736914062500006, 6.452587890624997], + [0.525585937500011, 6.850927734374991], + [0.634765625, 7.353662109374994], + [0.5, 7.546875], + [0.686328125000017, 8.354882812499994], + [0.37255859375, 8.75927734375], + [0.48876953125, 8.851464843749994], + [0.525683593750017, 9.398486328124989], + [0.2333984375, 9.463525390624994], + [0.342578125000017, 9.604150390624994], + [0.264550781250023, 9.644726562499997], + [0.380859375, 10.291845703124991], + [-0.08632812499999, 10.673046875], + [0.009423828125023, 11.02099609375], + [-0.068603515625, 11.115625], + [0.49267578125, 10.954980468749994], + [0.900488281250006, 10.993261718749991] + ] + ] + }, + "properties": { "name": "Togo", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [98.40908203125005, 7.90205078125004], + [98.2962890625, 7.776074218750054], + [98.32207031250007, 8.166308593749974], + [98.4349609375, 8.085644531249969], + [98.40908203125005, 7.90205078125004] + ] + ], + [ + [ + [100.070703125, 9.58603515625002], + [99.96240234375003, 9.421630859375], + [99.93955078125006, 9.559960937500037], + [100.070703125, 9.58603515625002] + ] + ], + [ + [ + [102.42675781250003, 11.988720703125026], + [102.30195312500004, 11.98081054687502], + [102.27744140625006, 12.151855468750043], + [102.42675781250003, 11.988720703125026] + ] + ], + [ + [ + [100.12246093750005, 20.316650390625057], + [100.11494140625004, 20.257666015625034], + [100.13974609375012, 20.245410156250017], + [100.31796875000006, 20.38588867187505], + [100.51953125000003, 20.17792968750004], + [100.39765625000004, 19.756103515625], + [100.51357421875005, 19.553466796875], + [101.21191406249997, 19.54833984375003], + [101.22080078125006, 19.486621093750074], + [101.19755859375007, 19.327929687500074], + [101.2863281250001, 18.977148437500006], + [101.04697265625012, 18.441992187500063], + [101.05058593750002, 18.407031250000045], + [101.1375, 18.28686523437497], + [101.14394531250005, 18.14262695312499], + [100.90849609375002, 17.583886718750023], + [100.95585937500002, 17.541113281250006], + [101.10517578125004, 17.47954101562499], + [101.16748046874997, 17.49902343749997], + [101.41367187500012, 17.71875], + [101.55507812500005, 17.812353515625034], + [101.56367187500004, 17.82050781250001], + [101.6875, 17.889404296875], + [101.77480468750005, 18.03339843750004], + [101.81865234375002, 18.06464843750001], + [101.87548828124997, 18.046435546875017], + [101.94746093750004, 18.081494140624983], + [102.03457031250005, 18.169824218750023], + [102.10146484375, 18.210644531249983], + [102.14824218750002, 18.20385742187503], + [102.35185546875002, 18.045947265625017], + [102.45878906250002, 17.984619140625057], + [102.55253906250007, 17.96508789062497], + [102.61679687500006, 17.833349609375034], + [102.66064453124997, 17.817968750000034], + [102.680078125, 17.824121093750023], + [103.05136718750006, 18.02851562500001], + [103.0912109375, 18.13823242187499], + [103.14853515625006, 18.221728515625045], + [103.19970703124997, 18.259472656249983], + [103.26318359374997, 18.27846679687505], + [103.27958984375002, 18.304980468750017], + [103.24892578125, 18.338964843750034], + [103.25175781250002, 18.373486328124955], + [103.2882812500001, 18.408398437499955], + [103.36699218750007, 18.42333984374997], + [103.48798828125004, 18.418164062499983], + [103.62968750000002, 18.38256835937503], + [103.79228515625002, 18.316503906249977], + [103.89882812500005, 18.295312500000023], + [103.949609375, 18.31899414062505], + [104.04873046875005, 18.216699218749994], + [104.19619140625005, 17.988378906250006], + [104.32265625, 17.815820312500023], + [104.428125, 17.69897460937503], + [104.7396484375, 17.461669921875], + [104.81601562500012, 17.30029296874997], + [104.75898437500004, 17.0771484375], + [104.7435546875, 16.884375], + [104.75058593750012, 16.647558593750063], + [104.81933593750003, 16.46606445312503], + [105.04716796875007, 16.160253906249977], + [105.14873046875007, 16.09355468749999], + [105.33066406250006, 16.037890625000017], + [105.40625, 15.987451171875051], + [105.39892578124997, 15.829882812500017], + [105.62207031250003, 15.699951171875], + [105.641015625, 15.656542968750045], + [105.6388671875001, 15.585937500000057], + [105.615625, 15.488281250000057], + [105.49042968750004, 15.256591796875], + [105.49042968750004, 15.127587890625009], + [105.5333984375001, 15.041601562499991], + [105.54667968750002, 14.932470703124963], + [105.52304687500012, 14.843310546875003], + [105.49736328125002, 14.590673828124963], + [105.47558593750003, 14.530126953124977], + [105.42265625000007, 14.471630859375054], + [105.34218750000005, 14.416699218750054], + [105.24365234375003, 14.367871093750054], + [105.1833007812501, 14.346240234374989], + [105.16914062500004, 14.336083984374966], + [105.12597656250003, 14.280957031250011], + [105.07412109375005, 14.227441406250037], + [104.77900390625004, 14.427832031250006], + [103.19941406250004, 14.332617187499977], + [102.90927734375006, 14.136718750000028], + [102.546875, 13.585693359375043], + [102.33632812500005, 13.560302734375014], + [102.49960937500012, 12.669970703125003], + [102.75566406250002, 12.42626953125], + [102.73662109375007, 12.089794921875011], + [102.93388671875002, 11.706689453125037], + [102.594140625, 12.203027343749994], + [102.54023437500004, 12.109228515624977], + [101.83574218750002, 12.640380859375014], + [100.89775390625007, 12.653808593749986], + [100.96269531250007, 13.431982421874991], + [100.60292968750005, 13.568164062500017], + [100.23564453125002, 13.48447265625002], + [99.99052734375007, 13.243457031250031], + [100.08994140625006, 13.045654296874972], + [99.96396484375006, 12.690039062500006], + [99.98906250000007, 12.170800781249994], + [99.16503906250003, 10.319824218750028], + [99.25390625000003, 9.265234375000034], + [99.83554687500012, 9.288378906250031], + [99.98955078125007, 8.589208984374977], + [100.129296875, 8.428076171875006], + [100.16347656250005, 8.508398437500034], + [100.27939453125006, 8.268505859375011], + [100.54521484375002, 7.226904296874991], + [100.43935546875005, 7.280761718750043], + [100.38037109375003, 7.541503906250043], + [100.28378906250006, 7.551513671875043], + [100.25664062500002, 7.774902343749986], + [100.16074218750012, 7.599267578124994], + [100.4235351562501, 7.18784179687502], + [101.01787109375002, 6.860937500000034], + [101.49794921875005, 6.865283203125031], + [102.10107421874997, 6.242236328125031], + [101.87363281250012, 5.825292968749991], + [101.67841796875004, 5.778808593750028], + [101.5560546875, 5.907763671875003], + [101.1139648437501, 5.636767578125045], + [100.98164062500004, 5.771044921875045], + [101.05351562500002, 6.242578125], + [100.87392578125, 6.24541015624996], + [100.75449218750012, 6.460058593749991], + [100.3454101562501, 6.549902343750006], + [100.26142578125004, 6.682714843749963], + [100.11914062499997, 6.441992187500048], + [99.69599609375004, 6.87666015625004], + [99.72031250000012, 7.106201171875], + [99.55302734375002, 7.218798828125031], + [99.59697265625002, 7.355615234375009], + [99.35859375000004, 7.372216796875023], + [99.26367187499997, 7.619042968750037], + [99.07763671874997, 7.718066406250045], + [99.05107421875002, 7.887841796874994], + [98.78867187500012, 8.059814453125028], + [98.703515625, 8.256738281250009], + [98.57919921875006, 8.344287109374989], + [98.42099609375006, 8.17822265625], + [98.30546875000007, 8.226220703125009], + [98.24179687500006, 8.767871093750045], + [98.70253906250005, 10.19038085937504], + [98.7572265625, 10.660937499999974], + [99.1901367187501, 11.105273437499989], + [99.61474609374997, 11.781201171875026], + [99.40507812500002, 12.547900390625003], + [99.12392578125, 13.030761718750043], + [99.13681640625006, 13.716699218749994], + [98.57001953125004, 14.359912109375031], + [98.20214843749997, 14.97592773437502], + [98.19101562500012, 15.204101562499972], + [98.55693359375007, 15.367675781249986], + [98.59238281250006, 16.05068359375005], + [98.81796875000012, 16.180810546874994], + [98.88828125000006, 16.351904296875034], + [98.83544921875003, 16.417578125], + [98.66074218750006, 16.330419921875006], + [98.4388671875, 16.975683593750034], + [97.7064453125, 17.79711914062503], + [97.63222656250005, 18.290332031250074], + [97.37392578125, 18.51796875], + [97.74589843750002, 18.58818359374999], + [97.816796875, 19.459960937500057], + [98.01503906250005, 19.74951171874997], + [98.37128906250004, 19.68916015625004], + [98.9166992187501, 19.77290039062504], + [99.07421875000003, 20.09936523437503], + [99.48593750000006, 20.14985351562501], + [99.45888671875005, 20.363037109375], + [99.72011718750005, 20.32543945312497], + [99.8903320312501, 20.424414062499977], + [99.9542968750001, 20.415429687500023], + [100.0036132812501, 20.37958984375001], + [100.12246093750005, 20.316650390625057] + ] + ] + ] + }, + "properties": { "name": "Thailand", "childNum": 4 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [70.66416015625, 39.85546875], + [70.55957031250003, 39.790917968749994], + [70.48925781250003, 39.86303710937503], + [70.48281250000005, 39.88271484375005], + [70.49775390625004, 39.88242187499998], + [70.56708984375004, 39.86660156250005], + [70.66416015625, 39.85546875] + ] + ], + [ + [ + [70.95800781250003, 40.238867187500034], + [70.59921875, 39.974511718749994], + [69.96679687499997, 40.202246093750034], + [69.46875, 40.020751953125], + [69.47099609375002, 39.990625], + [69.43193359375007, 39.909765625000034], + [69.36542968750004, 39.94707031250002], + [69.30722656250006, 39.968554687500045], + [69.27880859374997, 39.91777343749999], + [69.24472656250006, 39.82709960937498], + [69.29765625000007, 39.52480468750005], + [70.50117187500004, 39.58735351562501], + [70.79931640625003, 39.39472656250001], + [71.4703125, 39.60366210937502], + [71.50302734375006, 39.58217773437502], + [71.51738281250002, 39.55385742187502], + [71.50585937499997, 39.51708984374997], + [71.5033203125, 39.47880859374999], + [71.73222656250002, 39.422998046874994], + [71.77861328125007, 39.27797851562502], + [72.04277343750002, 39.352148437500034], + [72.08417968750004, 39.310644531250034], + [72.14736328125005, 39.26074218749997], + [72.22998046874997, 39.20751953124997], + [72.63994140625002, 39.385986328125], + [73.10927734375, 39.36191406249998], + [73.2349609375, 39.37456054687499], + [73.3361328125001, 39.41235351562506], + [73.38740234375004, 39.442724609375034], + [73.4704101562501, 39.46059570312502], + [73.63164062500007, 39.44887695312502], + [73.63632812500006, 39.396679687499955], + [73.60732421875, 39.229199218749955], + [73.8052734375, 38.968652343749994], + [73.69609375000007, 38.85429687499996], + [73.80166015625, 38.60688476562501], + [74.02558593750004, 38.53984375000002], + [74.27744140625, 38.659765625000034], + [74.81230468750002, 38.46030273437498], + [74.8942382812501, 37.60141601562498], + [75.11875, 37.38569335937498], + [74.89130859375004, 37.231640624999955], + [74.875390625, 37.24199218750002], + [74.83046875, 37.28593750000002], + [74.73056640625006, 37.35703125], + [74.659375, 37.39448242187501], + [74.34902343750005, 37.41875], + [74.25966796875005, 37.41542968750002], + [74.20351562500005, 37.37246093750005], + [74.16708984375, 37.32944335937498], + [73.74960937500006, 37.23178710937498], + [73.6535156250001, 37.239355468750034], + [73.62753906250006, 37.261572265625006], + [73.71728515625003, 37.32944335937498], + [73.7337890625, 37.37578125000002], + [73.72060546875, 37.41875], + [73.65712890625005, 37.43046875], + [73.6046875000001, 37.44604492187503], + [73.48134765625, 37.4716796875], + [73.38291015625006, 37.462255859375034], + [73.21113281250004, 37.40849609375002], + [72.89550781250003, 37.26752929687498], + [72.65742187500004, 37.029052734375], + [71.665625, 36.696923828124994], + [71.530859375, 36.845117187499994], + [71.43291015625007, 37.12753906249998], + [71.5822265625001, 37.91010742187498], + [71.55195312500004, 37.93315429687496], + [71.48779296874997, 37.93188476562497], + [71.38964843750003, 37.90629882812502], + [71.31992187500006, 37.90185546875], + [71.27851562500004, 37.91840820312498], + [71.33271484375004, 38.170263671875034], + [71.25585937499997, 38.306982421875006], + [70.7359375, 38.42255859375001], + [70.41777343750002, 38.075439453125], + [70.21464843750002, 37.92441406250006], + [70.19941406250004, 37.88603515624996], + [70.25498046875006, 37.76538085937497], + [70.25146484374997, 37.66416015625006], + [70.18867187500004, 37.58247070312501], + [70.11982421875004, 37.54350585937499], + [69.9849609375, 37.566162109375], + [69.8208984375, 37.60957031250004], + [69.62578125000002, 37.59404296874999], + [69.49208984375, 37.55307617187498], + [69.42011718750004, 37.486718749999966], + [69.39921875000007, 37.39931640625002], + [69.42968749999997, 37.290869140625034], + [69.414453125, 37.20776367187497], + [69.35380859375007, 37.15004882812502], + [69.3039062500001, 37.11694335937503], + [69.26484375000004, 37.1083984375], + [69.18017578125003, 37.158300781250034], + [68.96044921875003, 37.32504882812498], + [68.9118164062501, 37.33393554687501], + [68.88525390624997, 37.32807617187498], + [68.85537109375005, 37.31684570312501], + [68.83847656250006, 37.30283203124998], + [68.82373046874997, 37.27070312500001], + [68.78203125000002, 37.25800781250001], + [68.7232421875, 37.26801757812501], + [68.6691406250001, 37.258398437500006], + [68.3869140625001, 37.1375], + [68.29951171875004, 37.08842773437502], + [68.28476562500006, 37.036328124999955], + [68.2609375000001, 37.01308593750002], + [68.2121093750001, 37.02153320312496], + [68.0677734375, 36.949804687500006], + [67.95800781249997, 36.972021484375006], + [67.83447265624997, 37.06420898437506], + [67.75898437500004, 37.172216796875034], + [67.7980468750001, 37.244970703125006], + [67.81435546875005, 37.48701171875004], + [68.3502929687501, 38.211035156250006], + [68.08720703125002, 38.47353515625002], + [68.13251953125004, 38.927636718749966], + [67.69443359375006, 38.99462890625003], + [67.64833984375005, 39.13105468750004], + [67.3576171875001, 39.216699218749994], + [67.426171875, 39.46557617187497], + [67.71904296875007, 39.62138671875002], + [68.46328125, 39.53671874999998], + [68.63896484375007, 39.8388671875], + [68.86875, 39.90747070312503], + [68.80468750000003, 40.05034179687499], + [68.9720703125, 40.08994140624998], + [68.63066406250007, 40.16708984374998], + [69.27490234374997, 40.19809570312498], + [69.20625, 40.566552734374994], + [69.35722656250002, 40.76738281249996], + [69.71289062500003, 40.65698242187503], + [70.40195312500006, 41.03510742187498], + [70.75107421875006, 40.721777343750006], + [70.37158203125003, 40.38413085937506], + [70.653125, 40.201171875], + [70.95800781250003, 40.238867187500034] + ] + ] + ] + }, + "properties": { "name": "Tajikistan", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [66.52226562500007, 37.34848632812506], + [66.471875, 37.3447265625], + [65.7650390625, 37.56914062499996], + [65.55498046875002, 37.25117187500004], + [65.30361328125005, 37.24677734375001], + [65.08964843750007, 37.237939453124994], + [64.9515625, 37.19355468750001], + [64.81630859375005, 37.13208007812503], + [64.7824218750001, 37.05927734375001], + [64.60253906250003, 36.554541015625034], + [64.5658203125, 36.427587890625034], + [64.51103515625002, 36.34067382812498], + [64.184375, 36.14892578125], + [63.8625, 36.012353515624994], + [63.12998046875006, 35.84619140624997], + [63.169726562500074, 35.678125], + [63.05664062500003, 35.44580078125003], + [62.98027343750002, 35.40917968750003], + [62.85800781250006, 35.34965820312499], + [62.688085937500006, 35.25532226562504], + [62.3078125000001, 35.17080078125005], + [62.08964843750002, 35.3796875], + [61.62099609375005, 35.43232421875004], + [61.34472656249997, 35.62949218750006], + [61.26201171875002, 35.61958007812498], + [61.25214843750004, 35.86762695312498], + [61.15292968750006, 35.97675781250001], + [61.212011718750006, 36.190527343750034], + [61.11962890625003, 36.64257812500003], + [60.34130859375003, 36.63764648437501], + [60.06279296875002, 36.962890625], + [59.454980468749994, 37.25283203125002], + [59.30175781249997, 37.51064453125005], + [58.81542968750003, 37.683496093749994], + [58.261621093749994, 37.665820312500045], + [57.35371093750004, 37.97333984374998], + [57.1935546875001, 38.216406250000034], + [56.440625, 38.249414062499994], + [56.272070312500006, 38.080419921875034], + [55.38085937500003, 38.051123046875034], + [54.90009765625004, 37.77792968750006], + [54.6994140625001, 37.47016601562498], + [53.91416015625006, 37.34355468750002], + [53.86865234375003, 38.949267578125045], + [53.70458984375003, 39.209570312500034], + [53.33632812500005, 39.34082031250006], + [53.15664062499999, 39.26499023437506], + [53.23564453125002, 39.608544921874966], + [53.603125, 39.546972656250034], + [53.472265625, 39.66879882812498], + [53.48730468749997, 39.909375], + [52.9875, 39.98759765625002], + [53.03554687500005, 39.7744140625], + [52.80468749999997, 40.054003906250045], + [52.73369140625002, 40.39873046875002], + [52.943457031250006, 41.03808593750006], + [53.1452148437501, 40.82495117187497], + [53.61523437500003, 40.818505859374994], + [53.87001953125005, 40.64868164062503], + [54.37734375, 40.693261718749966], + [54.319433593750006, 40.83457031249998], + [54.68505859375003, 40.873046875], + [54.70371093750006, 41.071142578125034], + [54.094824218750006, 41.51938476562506], + [53.80468749999997, 42.11762695312498], + [53.16416015625006, 42.09379882812502], + [52.97001953125002, 41.97622070312505], + [52.81484375, 41.711816406249994], + [52.850390625000074, 41.20029296875006], + [52.4938476562501, 41.780371093750034], + [53.0558593750001, 42.14775390624999], + [54.120996093749994, 42.335205078125], + [54.85380859375002, 41.965185546875006], + [55.434375, 41.296289062499994], + [55.97744140625005, 41.32221679687504], + [57.01796875, 41.26347656249996], + [57.11884765625004, 41.35029296874998], + [56.96406250000004, 41.856542968750006], + [57.290625, 42.123779296875], + [57.814257812500074, 42.18984375000005], + [58.02890625, 42.48764648437506], + [58.474414062500074, 42.29936523437496], + [58.15156250000004, 42.628076171874966], + [58.477148437500006, 42.66284179687503], + [58.5890625000001, 42.778466796874966], + [59.35429687500002, 42.32329101562496], + [59.98515625000002, 42.21171875], + [59.94179687499999, 41.97353515625002], + [60.20078125000006, 41.803125], + [60.07558593750005, 41.759667968749966], + [60.089648437500074, 41.39941406250003], + [60.454980468749994, 41.221630859374955], + [61.2423828125001, 41.18920898437503], + [61.496972656249994, 41.276074218749955], + [61.90283203124997, 41.09370117187501], + [62.48320312500002, 39.97563476562496], + [63.76367187500003, 39.16054687499999], + [64.3099609375, 38.97729492187497], + [65.612890625, 38.23857421875002], + [65.97119140624997, 38.244238281250006], + [66.60625, 37.98671875000005], + [66.52558593750004, 37.785742187500034], + [66.51132812500006, 37.59916992187496], + [66.51064453125, 37.45869140625004], + [66.52226562500007, 37.34848632812506] + ] + ] + }, + "properties": { "name": "Turkmenistan", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [124.0363281250001, -9.341601562500031], + [124.44443359375012, -9.190332031250023], + [124.28232421875012, -9.427929687500026], + [124.0363281250001, -9.341601562500031] + ] + ], + [ + [ + [125.06816406250002, -9.511914062499997], + [124.96015625000004, -9.213769531250009], + [125.10048828125, -9.189843750000023], + [125.14902343750012, -9.042578125000034], + [124.93681640625007, -9.053417968750026], + [124.92226562500005, -8.942480468749977], + [125.17802734375002, -8.647851562499994], + [125.38183593749997, -8.575390624999983], + [126.61972656250006, -8.459472656249986], + [126.96640625000012, -8.315722656250017], + [127.29609375000004, -8.424511718749969], + [126.91523437500004, -8.715234374999966], + [125.40800781250002, -9.275781250000023], + [125.06816406250002, -9.511914062499997] + ] + ], + [ + [ + [125.64609375000006, -8.139941406250003], + [125.5794921875, -8.311816406250017], + [125.50712890625007, -8.275097656249997], + [125.64609375000006, -8.139941406250003] + ] + ] + ] + }, + "properties": { "name": "Timor-Leste", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-175.1619140625, -21.169335937500023], + [-175.07817382812496, -21.129003906249977], + [-175.15659179687495, -21.26367187499997], + [-175.36235351562496, -21.106835937499994], + [-175.1619140625, -21.169335937500023] + ] + ], + [ + [ + [-173.953515625, -18.63935546875001], + [-174.06914062500002, -18.640234375], + [-173.96806640624993, -18.565332031250023], + [-173.953515625, -18.63935546875001] + ] + ] + ] + }, + "properties": { "name": "Tonga", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.012109374999966, 10.134326171874989], + [-61.906103515625006, 10.069140625000031], + [-61.49931640624999, 10.268554687499972], + [-61.47827148437497, 10.603369140624977], + [-61.65117187499993, 10.718066406249974], + [-60.917626953124966, 10.84023437499999], + [-61.03374023437502, 10.669873046875026], + [-61.012109374999966, 10.134326171874989] + ] + ] + }, + "properties": { "name": "Trinidad and Tobago", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [10.957617187500063, 33.72207031250005], + [10.722070312500051, 33.738916015624994], + [10.745214843750063, 33.88867187500006], + [11.017871093749989, 33.82333984374998], + [10.957617187500063, 33.72207031250005] + ] + ], + [ + [ + [11.278027343750068, 34.753808593749994], + [11.123632812500063, 34.68168945312496], + [11.254882812500057, 34.82031250000006], + [11.278027343750068, 34.753808593749994] + ] + ], + [ + [ + [10.274609375000011, 31.684960937499994], + [10.114941406250068, 31.46376953125005], + [10.216406250000063, 30.78320312500003], + [10.05976562500004, 30.58007812500003], + [9.932519531250051, 30.42534179687496], + [9.895019531250028, 30.387304687500034], + [9.51875, 30.229394531249994], + [9.224023437500023, 31.373681640624994], + [9.160253906250006, 31.621337890625], + [9.044042968750034, 32.072363281250034], + [8.333398437500051, 32.54360351562502], + [8.1125, 33.055322265624994], + [7.877246093750017, 33.172119140625], + [7.534375, 33.717919921874994], + [7.513867187500068, 34.080517578124955], + [8.24560546875, 34.73408203124998], + [8.276855468750057, 34.97949218749997], + [8.312109375000063, 35.084619140624994], + [8.394238281250011, 35.20385742187503], + [8.318066406250011, 35.654931640624994], + [8.348730468750063, 36.367968750000045], + [8.207617187500006, 36.518945312499994], + [8.601269531250068, 36.83393554687504], + [8.576562500000023, 36.93720703125001], + [9.687988281250057, 37.34038085937499], + [9.838476562500063, 37.30898437499999], + [9.830273437499983, 37.13535156250006], + [9.875585937499977, 37.25415039062503], + [10.196386718750063, 37.205859375000045], + [10.293261718750074, 36.781494140625], + [10.412304687499983, 36.73183593750002], + [11.053906250000068, 37.07250976562506], + [11.12666015625004, 36.874072265625045], + [10.476562500000028, 36.175146484375006], + [10.590820312500028, 35.88725585937499], + [11.00429687500008, 35.63383789062496], + [11.120117187500057, 35.24028320312499], + [10.69091796875, 34.67846679687503], + [10.118359375000068, 34.280078125000045], + [10.049023437500068, 34.056298828124994], + [10.305273437500034, 33.72827148437497], + [10.713183593750017, 33.68901367187496], + [10.722753906250006, 33.514404296875], + [10.958007812500057, 33.62631835937498], + [11.257421875000034, 33.30883789062506], + [11.202636718749972, 33.24921874999998], + [11.50458984375004, 33.181933593750045], + [11.502441406250028, 33.15556640624999], + [11.467187500000051, 32.96572265625005], + [11.459179687500011, 32.897363281249966], + [11.453906250000017, 32.64257812500003], + [11.533789062500034, 32.52495117187496], + [11.535937500000017, 32.47333984375001], + [11.504980468750034, 32.413671875000034], + [11.358007812500006, 32.34521484375003], + [11.168261718750074, 32.25673828125002], + [11.005175781250074, 32.17270507812506], + [10.826367187500068, 32.080664062500034], + [10.771582031250006, 32.02119140625001], + [10.60888671875, 31.929541015624977], + [10.47578125000004, 31.736035156249983], + [10.274609375000011, 31.684960937499994] + ] + ] + ] + }, + "properties": { "name": "Tunisia", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [25.970019531250045, 40.136328125], + [25.6689453125, 40.13588867187502], + [25.918359375000023, 40.23798828125004], + [25.970019531250045, 40.136328125] + ] + ], + [ + [ + [43.43339843750002, 41.155517578125], + [43.43945312500003, 41.10712890625001], + [43.72265624999997, 40.71953124999999], + [43.56933593750003, 40.48237304687498], + [43.66621093750004, 40.12636718750002], + [44.28925781250004, 40.040380859375006], + [44.76826171875004, 39.70351562500005], + [44.81718750000002, 39.65043945312496], + [44.58710937500004, 39.76855468750006], + [44.3893554687501, 39.422119140625], + [44.02324218750002, 39.37744140625006], + [44.27167968750004, 38.83603515625006], + [44.2985351562501, 38.38627929687499], + [44.4499023437501, 38.33422851562506], + [44.21132812499999, 37.908056640625006], + [44.589941406250006, 37.710351562499966], + [44.574023437500074, 37.435400390625006], + [44.79414062500004, 37.290380859375034], + [44.76513671875003, 37.142431640625006], + [44.73095703124997, 37.16528320312503], + [44.66933593750005, 37.17358398437503], + [44.60595703124997, 37.176025390625], + [44.401953125, 37.05849609375002], + [44.325585937499994, 37.0107421875], + [44.28183593750006, 36.97802734374997], + [44.24570312500006, 36.983300781249994], + [44.20166015624997, 37.05180664062502], + [44.208398437499994, 37.20263671875], + [44.19179687499999, 37.249853515625034], + [44.15625, 37.28295898437503], + [44.11445312500004, 37.30185546875006], + [44.01318359375003, 37.313525390625045], + [43.83642578124997, 37.223535156249994], + [43.67578125000003, 37.227246093749955], + [43.09248046875004, 37.36738281249998], + [42.936621093750006, 37.32475585937502], + [42.77460937500004, 37.371875], + [42.74111328125005, 37.361914062500034], + [42.6354492187501, 37.249267578125], + [42.45585937500002, 37.128710937500045], + [42.358984375, 37.10859375000004], + [42.31289062499999, 37.22958984374998], + [42.26855468749997, 37.276562499999955], + [42.24755859375003, 37.28222656250006], + [42.20273437500006, 37.29726562499999], + [42.16787109375005, 37.28862304687502], + [42.059863281250074, 37.2060546875], + [41.886816406250006, 37.156396484374994], + [40.70566406250006, 37.09770507812502], + [40.4503906250001, 37.00888671875006], + [40.016406250000074, 36.82607421875002], + [39.68652343749997, 36.73862304687506], + [39.50146484374997, 36.702246093750034], + [39.35664062500004, 36.68159179687498], + [39.10839843749997, 36.68056640625005], + [38.90644531250004, 36.69467773437498], + [38.76660156249997, 36.69311523437503], + [38.19169921875002, 36.90156250000004], + [37.90664062500005, 36.79462890625001], + [37.7203125, 36.74370117187502], + [37.52353515625006, 36.678320312500034], + [37.436328125000074, 36.643310546875], + [37.327050781249994, 36.64658203125006], + [37.18740234375005, 36.655908203124994], + [37.066210937500074, 36.652636718750045], + [36.98535156250003, 36.70239257812506], + [36.94179687499999, 36.758398437500006], + [36.77656250000004, 36.79267578124998], + [36.65859375000005, 36.80253906250002], + [36.62841796875003, 36.777685546875034], + [36.596875, 36.70136718750001], + [36.546679687500074, 36.50634765625], + [36.5375, 36.457421874999966], + [36.63671874999997, 36.233984375], + [36.37539062499999, 36.171240234375034], + [36.347558593749994, 36.003515625000034], + [36.20195312500002, 35.93754882812502], + [36.15361328125002, 35.83388671875005], + [36.12734375, 35.831445312499994], + [35.967578125000074, 35.91005859375002], + [35.89267578125006, 35.91655273437502], + [35.81093750000005, 36.30986328125002], + [36.18847656250003, 36.65898437499999], + [36.048925781250006, 36.91059570312501], + [35.393164062500006, 36.57519531249997], + [34.70361328125003, 36.81679687499999], + [33.694726562499994, 36.18198242187498], + [32.794824218749994, 36.03588867187497], + [32.37773437500002, 36.18364257812496], + [32.02197265625003, 36.53530273437502], + [31.35253906249997, 36.80107421874999], + [30.64404296874997, 36.86567382812501], + [30.446093750000074, 36.269873046875034], + [29.6890625, 36.15668945312498], + [29.22363281249997, 36.32446289062497], + [28.96962890625008, 36.71533203125003], + [28.303710937500057, 36.81196289062498], + [28.01943359375005, 36.63447265624998], + [28.083984375000057, 36.75146484375], + [27.453906250000017, 36.712158203125], + [28.00537109375003, 36.83198242187498], + [28.242382812500068, 37.029052734375], + [27.262988281250045, 36.97656250000003], + [27.30019531250005, 37.12685546875002], + [27.53505859375005, 37.16386718750002], + [27.06796875, 37.65791015625004], + [27.224414062500074, 37.725439453125006], + [27.23242187500003, 37.978662109374994], + [26.29072265625001, 38.27719726562498], + [26.44130859375005, 38.64121093749998], + [26.67421875000008, 38.33574218750002], + [27.14423828125001, 38.45195312499996], + [26.906835937500034, 38.48173828124999], + [26.763671875, 38.709619140624966], + [27.013671875000057, 38.88686523437502], + [26.814941406250057, 38.96098632812502], + [26.853613281250034, 39.115625], + [26.68183593750004, 39.292236328125], + [26.89921874999999, 39.549658203125034], + [26.113085937500074, 39.46738281249998], + [26.101367187500074, 39.56894531249998], + [26.18134765625004, 39.99008789062498], + [26.738085937500045, 40.40024414062506], + [27.28457031250008, 40.45561523437496], + [27.4755859375, 40.319921875000034], + [27.72802734375, 40.32880859374998], + [27.84853515625005, 40.38173828125002], + [27.73183593750008, 40.48149414062499], + [27.87490234375008, 40.512939453125], + [27.989550781250074, 40.48945312500001], + [27.96259765625001, 40.369873046875], + [29.00712890624999, 40.389746093750034], + [28.787890625000017, 40.534033203125034], + [28.95800781250003, 40.63056640624998], + [29.849218750000063, 40.760107421875006], + [29.113867187499977, 40.93784179687506], + [29.14814453125004, 41.221044921875034], + [31.25488281249997, 41.10761718750001], + [31.45800781249997, 41.32001953125004], + [32.306445312500074, 41.72958984374998], + [33.38134765625003, 42.01757812500003], + [34.75048828124997, 41.95683593749999], + [35.006445312500006, 42.06328125000002], + [35.15488281250006, 42.02753906250001], + [35.12207031250003, 41.89111328125003], + [35.297753906249994, 41.72851562500003], + [35.558007812499994, 41.63403320312506], + [36.05175781249997, 41.68256835937498], + [36.40537109375006, 41.27460937500001], + [36.77773437499999, 41.36347656250001], + [37.066210937500074, 41.184423828125034], + [38.38105468750004, 40.92451171875001], + [39.426367187500006, 41.10644531250003], + [40.26523437500006, 40.96132812500005], + [41.08359375000006, 41.26118164062504], + [41.41435546875002, 41.42363281249999], + [41.510058593750074, 41.51748046875002], + [41.70175781250006, 41.471582031249994], + [41.77939453125006, 41.44052734374998], + [41.823535156250074, 41.432373046875], + [41.92578125000003, 41.49565429687502], + [42.46640625, 41.43984375000002], + [42.56738281249997, 41.55927734375001], + [42.590429687500006, 41.57070312500002], + [42.60683593750005, 41.57880859374998], + [42.682421875000074, 41.58574218749999], + [42.75410156250004, 41.57890625000002], + [42.787890625000074, 41.56372070312503], + [42.82167968750005, 41.49238281249998], + [42.90673828125003, 41.46684570312502], + [43.05712890625003, 41.35283203124996], + [43.149023437500006, 41.30712890624997], + [43.171289062499994, 41.28793945312498], + [43.14101562499999, 41.26484374999998], + [43.15283203124997, 41.23642578125006], + [43.20546875000005, 41.19916992187501], + [43.43339843750002, 41.155517578125] + ] + ], + [ + [ + [27.47480468750001, 41.946875], + [28.014453125000017, 41.96904296874999], + [28.197851562500063, 41.55449218750002], + [29.057226562500006, 41.22973632812503], + [28.95625, 41.00820312499999], + [28.172167968750074, 41.08071289062502], + [27.49941406250005, 40.97314453124997], + [27.258007812499983, 40.687353515625006], + [26.772070312500034, 40.498046875], + [26.202734375000034, 40.07539062500004], + [26.25380859375005, 40.31469726562503], + [26.792089843750034, 40.626611328124994], + [26.10546875000003, 40.61132812499997], + [26.03896484375008, 40.726757812499955], + [26.331054687500057, 40.954492187499994], + [26.330664062499977, 41.23876953125], + [26.62490234375008, 41.401757812499994], + [26.581347656250074, 41.60126953125004], + [26.320898437500034, 41.716552734375], + [26.3603515625, 41.80156249999999], + [26.51142578125004, 41.82636718749998], + [26.549707031250023, 41.896728515625], + [26.5796875, 41.947949218749955], + [26.615332031250063, 41.964892578125045], + [26.884863281250006, 41.99184570312502], + [26.96875, 42.02685546875006], + [27.01171875, 42.05864257812496], + [27.193359375000057, 42.07709960937498], + [27.24433593750004, 42.09326171875], + [27.294921875000057, 42.079541015624955], + [27.47480468750001, 41.946875] + ] + ] + ] + }, + "properties": { "name": "Turkey", "childNum": 3 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [39.71132812499999, -7.977441406250023], + [39.602929687499994, -7.936132812499949], + [39.907128906249994, -7.649218750000031], + [39.71132812499999, -7.977441406250023] + ] + ], + [ + [ + [39.49648437499999, -6.174609375], + [39.573046875000074, -6.387402343750011], + [39.48095703124997, -6.45371093750002], + [39.18232421875004, -6.172558593750026], + [39.30898437499999, -5.721972656249974], + [39.49648437499999, -6.174609375] + ] + ], + [ + [ + [39.86503906250002, -4.906152343750037], + [39.74931640625002, -5.443847656249986], + [39.646777343750074, -5.368554687500009], + [39.6734375, -4.927050781250031], + [39.86503906250002, -4.906152343750037] + ] + ], + [ + [ + [33.90322265625005, -1.002050781250034], + [37.643847656250074, -3.045410156250028], + [37.608203125000074, -3.497070312500028], + [39.221777343750006, -4.692382812500014], + [38.80468750000003, -6.070117187500031], + [38.87402343750003, -6.33125], + [39.5460937500001, -7.024023437500034], + [39.288476562499994, -7.517871093750003], + [39.28701171875005, -7.787695312500006], + [39.4284179687501, -7.81279296874996], + [39.441015625, -8.011523437499946], + [39.304003906250074, -8.44384765625], + [39.451269531250006, -8.94296875], + [39.64130859375004, -9.19248046875002], + [39.72519531250006, -10.000488281249972], + [40.46357421875004, -10.464355468749972], + [39.98867187499999, -10.820800781250014], + [39.81708984375004, -10.912402343750031], + [38.9875, -11.167285156250003], + [38.49179687500006, -11.413281250000026], + [37.92021484375002, -11.294726562500031], + [37.72480468750004, -11.58066406250002], + [37.54169921875004, -11.675097656249974], + [37.37285156250002, -11.710449218749986], + [36.97890625000005, -11.566992187499977], + [36.30566406250003, -11.706347656249946], + [36.191308593749994, -11.670703124999974], + [36.17548828125004, -11.60927734374998], + [36.08222656250004, -11.537304687499969], + [35.91132812500004, -11.45468750000002], + [35.785449218750074, -11.452929687500017], + [35.63095703125006, -11.582031250000028], + [35.564355468749994, -11.602343749999989], + [35.418261718750074, -11.583203125], + [35.18261718750003, -11.574804687499977], + [34.95947265625003, -11.578125], + [34.93701171874997, -11.463476562500034], + [34.890625, -11.3935546875], + [34.77382812500005, -11.341699218750009], + [34.60791015624997, -11.08046875], + [34.66708984375006, -10.792480468750028], + [34.56992187500006, -10.241113281249966], + [34.32089843750006, -9.731542968749977], + [33.99560546875003, -9.495410156250003], + [33.88886718750004, -9.670117187499983], + [32.91992187500003, -9.407421875000026], + [32.75664062500002, -9.322265625], + [31.94257812500004, -9.05400390624996], + [31.91865234375004, -8.942187500000017], + [31.886132812499994, -8.921972656249977], + [31.81806640625004, -8.902246093749952], + [31.673632812500017, -8.908789062499963], + [31.55625, -8.80546875], + [31.44921874999997, -8.65390625], + [31.35058593750003, -8.607031250000034], + [31.07636718750004, -8.611914062499963], + [30.968359375000063, -8.550976562499983], + [30.89199218750005, -8.473730468749963], + [30.830664062500063, -8.385546875000031], + [30.720898437500097, -8.104394531250037], + [30.40673828125003, -7.460644531249983], + [30.313183593750097, -7.203710937499949], + [30.212695312500017, -7.037890625000017], + [30.10625, -6.915039062500028], + [29.961816406249994, -6.803125], + [29.798144531250017, -6.691894531249957], + [29.70966796875004, -6.61689453125004], + [29.590625, -6.394433593750023], + [29.540820312500017, -6.313867187500037], + [29.50625, -6.172070312500011], + [29.480078125, -6.025], + [29.490820312500063, -5.96542968750002], + [29.59638671875004, -5.775976562499963], + [29.60703125, -5.722656250000028], + [29.59414062500005, -5.650781250000037], + [29.542382812499994, -5.499804687500017], + [29.34277343749997, -4.983105468749997], + [29.32343750000004, -4.898828124999966], + [29.32568359374997, -4.835644531249969], + [29.404199218749994, -4.49667968750002], + [29.40322265625005, -4.449316406249963], + [29.71777343750003, -4.45585937499996], + [29.94726562499997, -4.307324218749983], + [30.4, -3.65390625], + [30.790234375000097, -3.274609375000011], + [30.811132812500006, -3.116406250000011], + [30.78027343750003, -2.984863281249957], + [30.70947265624997, -2.977246093749997], + [30.604296875000074, -2.935253906249969], + [30.515039062499994, -2.917578125], + [30.45556640625003, -2.893164062500006], + [30.433496093749994, -2.874511718750028], + [30.424023437500097, -2.82402343749996], + [30.473339843750097, -2.6943359375], + [30.42421875000005, -2.641601562500014], + [30.441992187500006, -2.613476562499969], + [30.53369140624997, -2.426269531250014], + [30.55361328125005, -2.400097656250011], + [30.593359375000063, -2.39677734374996], + [30.65664062500005, -2.373828124999989], + [30.71484375000003, -2.363476562500011], + [30.7625, -2.371679687499991], + [30.828710937500006, -2.338476562499977], + [30.85498046874997, -2.265429687500017], + [30.8765625, -2.143359375000017], + [30.864648437499994, -2.044042968749949], + [30.819140625000017, -1.967480468749983], + [30.812597656250006, -1.56308593750002], + [30.76220703124997, -1.458691406249983], + [30.710742187500074, -1.396777343749974], + [30.631933593750006, -1.36748046874996], + [30.508105468750074, -1.208203125000026], + [30.47021484374997, -1.13115234374996], + [30.47705078124997, -1.0830078125], + [30.509960937500097, -1.067285156249994], + [30.51992187499999, -1.0625], + [30.67275390625005, -1.051367187499949], + [30.741992187500017, -1.007519531249997], + [30.809179687500063, -0.994921875], + [30.82363281250005, -0.999023437499943], + [30.84472656250003, -1.002050781250034], + [32.371875, -1.002050781250034], + [33.90322265625005, -1.002050781250034] + ] + ] + ] + }, + "properties": { "name": "Tanzania", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [30.50996093750001, -1.067285156250009], + [30.46992187500001, -1.066015625], + [30.41230468750001, -1.063085937500006], + [30.360253906250023, -1.074609375], + [29.930078125000023, -1.469921875000011], + [29.82539062500001, -1.335546875], + [29.576953125000017, -1.387890625000011], + [29.717675781250023, 0.098339843749997], + [29.934472656250023, 0.4990234375], + [29.94287109375, 0.819238281249994], + [31.252734375000017, 2.044580078124994], + [31.176367187500006, 2.270068359374989], + [30.728613281250006, 2.455371093749989], + [30.8466796875, 2.847021484374991], + [30.754003906250006, 3.041796874999989], + [30.90644531250001, 3.408935546875], + [30.83857421875001, 3.49072265625], + [31.15234375, 3.785595703124997], + [31.547167968750017, 3.677587890624991], + [31.79804687500001, 3.802636718749994], + [32.13593750000001, 3.519726562499997], + [32.33574218750002, 3.706201171874994], + [32.99726562500001, 3.880175781249989], + [33.489355468750006, 3.755078125], + [33.568457031250006, 3.81171875], + [33.74160156250002, 3.985253906249994], + [33.97607421875, 4.22021484375], + [34.13203125000001, 3.88916015625], + [34.18574218750001, 3.869775390624994], + [34.1650390625, 3.81298828125], + [34.26708984375, 3.733154296875], + [34.39287109375002, 3.691503906249991], + [34.43769531250001, 3.650585937499997], + [34.44179687500002, 3.60625], + [34.3994140625, 3.412695312499991], + [34.4072265625, 3.357519531249991], + [34.447851562500006, 3.163476562499994], + [34.90576171875, 2.4796875], + [34.88300781250001, 2.417919921874997], + [34.96406250000001, 2.062402343749994], + [34.9775390625, 1.861914062499991], + [34.97646484375002, 1.719628906249994], + [34.79863281250002, 1.24453125], + [34.48173828125002, 1.042138671874994], + [34.41083984375001, 0.867285156249991], + [34.16093750000002, 0.605175781249997], + [33.94316406250002, 0.173779296874997], + [33.90322265625002, -1.002050781250006], + [32.371875, -1.002050781250006], + [30.8447265625, -1.002050781250006], + [30.823632812500023, -0.9990234375], + [30.809179687500006, -0.994921875], + [30.741992187500017, -1.007519531250011], + [30.672753906250023, -1.051367187500006], + [30.598730468750006, -1.069726562500009], + [30.519921875000023, -1.0625], + [30.50996093750001, -1.067285156250009] + ] + ] + }, + "properties": { "name": "Uganda", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [32.01220703124997, 46.20390624999999], + [32.15009765625004, 46.1546875], + [31.56386718750005, 46.25776367187504], + [31.50878906250003, 46.373144531250006], + [32.01220703124997, 46.20390624999999] + ] + ], + [ + [ + [38.21435546875003, 47.091455078124966], + [37.54335937499999, 47.07456054687498], + [36.794824218749994, 46.71440429687499], + [36.55878906250004, 46.76269531250006], + [35.82714843749997, 46.62431640625002], + [35.01455078125005, 46.10600585937502], + [35.280175781249994, 46.27949218750001], + [35.23037109375005, 46.440625], + [34.84960937500003, 46.189892578124955], + [35.02285156250005, 45.70097656250002], + [35.45751953124997, 45.316308593749994], + [36.170507812500006, 45.453076171874955], + [36.575, 45.3935546875], + [36.39335937500002, 45.06538085937501], + [35.87011718750003, 45.005322265624955], + [35.472558593749994, 45.098486328125006], + [35.08769531250002, 44.802636718749966], + [34.46992187500004, 44.7216796875], + [33.909960937500074, 44.387597656249966], + [33.45068359374997, 44.553662109374955], + [33.55517578125003, 45.09765625000003], + [32.5080078125001, 45.40380859375006], + [33.664843750000074, 45.94707031249996], + [33.59414062500005, 46.09624023437499], + [33.42988281250004, 46.05761718750003], + [33.20224609375006, 46.17573242187501], + [32.47675781250004, 46.08369140625001], + [31.83125, 46.28168945312501], + [32.00849609375004, 46.42998046875002], + [31.554882812500097, 46.554296875000034], + [32.36132812499997, 46.474951171875034], + [32.578027343749994, 46.615625], + [32.04433593750005, 46.642480468749966], + [31.75917968750005, 47.21284179687501], + [31.872851562500017, 46.649755859375034], + [31.532128906249994, 46.66474609374998], + [31.56337890625005, 46.77729492187501], + [31.402929687500063, 46.62880859375002], + [30.796289062499994, 46.55200195312503], + [30.219042968750074, 45.866748046875045], + [29.62841796875003, 45.722460937500045], + [29.705859375000074, 45.25991210937505], + [29.567675781250074, 45.37080078124998], + [29.40371093750005, 45.419677734375], + [29.22353515625005, 45.402929687500034], + [28.894335937500017, 45.28994140625002], + [28.78173828125, 45.30986328125002], + [28.76660156250003, 45.28623046874998], + [28.78828125000001, 45.240966796875], + [28.451269531250006, 45.292187499999955], + [28.317675781250045, 45.347119140624955], + [28.2125, 45.45043945312506], + [28.26484375000004, 45.48388671875003], + [28.310351562500074, 45.49858398437499], + [28.499023437500057, 45.517724609374994], + [28.513769531250034, 45.57241210937502], + [28.49160156250005, 45.66577148437503], + [28.562304687500074, 45.73579101562501], + [28.667578125, 45.79384765625002], + [28.729296875000074, 45.852001953124955], + [28.73876953125003, 45.937158203124994], + [28.84951171875005, 45.97866210937502], + [28.94775390624997, 46.049951171874966], + [28.971875, 46.12763671874998], + [29.00625, 46.17646484374998], + [28.94375, 46.28842773437506], + [28.930566406250023, 46.36225585937501], + [28.92744140625001, 46.42412109374999], + [28.958398437500023, 46.45849609374997], + [29.146289062500017, 46.52690429687496], + [29.186230468750068, 46.52397460937499], + [29.20078125, 46.504980468750034], + [29.20458984374997, 46.37934570312501], + [29.223828125000097, 46.37695312499997], + [29.458789062500017, 46.453759765624994], + [29.83789062499997, 46.35053710937501], + [29.878027343750063, 46.360205078125034], + [30.07568359375003, 46.377832031249966], + [30.131054687500097, 46.42309570312506], + [29.92431640624997, 46.53886718750002], + [29.934765625000097, 46.625], + [29.942480468750063, 46.72377929687502], + [29.918066406250063, 46.78242187499998], + [29.877832031249994, 46.828906250000045], + [29.57197265625004, 46.96401367187502], + [29.455664062500006, 47.292626953124994], + [29.134863281250006, 47.48969726562501], + [29.125390625000023, 47.96455078125001], + [28.42304687500001, 48.146875], + [28.34052734375001, 48.144433593749994], + [27.54921875000008, 48.47773437500004], + [27.22851562500003, 48.37143554687506], + [26.90058593750001, 48.37192382812506], + [26.847070312500023, 48.387158203124955], + [26.640429687500045, 48.29414062500001], + [26.618945312500017, 48.25986328125006], + [26.4423828125, 48.22998046875], + [26.162695312500063, 47.992529296875034], + [25.90869140625, 47.96757812500002], + [25.689257812500045, 47.93247070312506], + [25.46425781250005, 47.910791015624994], + [24.979101562500063, 47.72412109374997], + [24.578906250000074, 47.93105468750005], + [23.628710937500017, 47.995849609375], + [23.40820312500003, 47.98999023437506], + [23.20263671875, 48.084521484375045], + [23.13945312499999, 48.08740234375], + [22.87666015625001, 47.94726562500006], + [22.769140625000063, 48.109619140625], + [22.582421875000023, 48.134033203125], + [22.253710937500017, 48.407373046874994], + [22.131835937500057, 48.40532226562502], + [22.142871093750017, 48.568505859374966], + [22.295214843750045, 48.68583984374999], + [22.389453125000045, 48.87348632812501], + [22.52412109375004, 49.03139648437502], + [22.538671875, 49.07270507812501], + [22.847070312500023, 49.08125], + [22.705664062500006, 49.17119140624999], + [22.6494140625, 49.53901367187498], + [22.706152343750006, 49.60620117187497], + [23.03632812500004, 49.899072265624966], + [23.711718750000045, 50.377343749999966], + [23.97265625, 50.410058593749966], + [24.089941406250006, 50.53046874999998], + [24.0947265625, 50.617041015625034], + [23.9970703125, 50.809375], + [24.095800781250063, 50.87275390625001], + [23.664453125000023, 51.31005859375], + [23.61376953125, 51.525390625], + [23.706835937500045, 51.64130859374998], + [23.79169921875001, 51.63710937500002], + [23.864257812500057, 51.62397460937501], + [23.951171875, 51.58505859374998], + [23.978320312500017, 51.59130859375003], + [24.12685546875008, 51.664648437500034], + [24.280078125000017, 51.77470703124999], + [24.361914062500006, 51.86752929687498], + [25.785742187500006, 51.923828125], + [26.77343750000003, 51.77070312499998], + [26.952832031249983, 51.754003906250034], + [27.074121093750023, 51.760839843750006], + [27.14199218750008, 51.75205078124998], + [27.29628906250008, 51.59741210937503], + [27.689746093750017, 51.572412109374994], + [27.7, 51.47797851562501], + [27.85859375000004, 51.59238281250006], + [28.532031250000017, 51.56245117187501], + [28.59902343750008, 51.54262695312505], + [28.647753906250074, 51.45654296875], + [28.690234375000017, 51.43886718750005], + [28.73125, 51.43339843749999], + [28.84951171875005, 51.540185546874994], + [28.927539062500045, 51.56215820312502], + [28.97773437500004, 51.57177734375003], + [29.01308593750005, 51.59892578124996], + [29.06074218750001, 51.625439453124955], + [29.102050781250057, 51.627539062500034], + [29.346484375000017, 51.38256835937503], + [30.160742187500006, 51.477880859375006], + [30.449511718750017, 51.274316406249994], + [30.63251953125004, 51.35541992187501], + [30.61171875000005, 51.406347656250006], + [30.602343750000017, 51.47124023437499], + [30.56074218750004, 51.531494140625], + [30.533007812500017, 51.596337890624966], + [30.583886718749994, 51.68896484375003], + [30.667285156250017, 51.81411132812502], + [30.755273437499994, 51.89516601562502], + [30.84570312500003, 51.95307617187501], + [30.980664062500097, 52.04619140624996], + [31.217968750000097, 52.05024414062498], + [31.345996093750074, 52.10537109375002], + [31.57373046875003, 52.108105468749955], + [31.763378906250097, 52.10107421875003], + [32.12226562500004, 52.05058593749996], + [32.435449218749994, 52.307226562500034], + [33.735253906249994, 52.344775390625045], + [34.397851562499994, 51.780419921874994], + [34.12109375000003, 51.67915039062498], + [34.21386718750003, 51.25537109375006], + [35.0640625, 51.203417968750045], + [35.31191406250005, 51.043896484374955], + [35.59111328125002, 50.36875], + [36.1164062500001, 50.408544921875006], + [36.619433593750074, 50.209228515625], + [37.42285156249997, 50.411474609375006], + [38.046875, 49.92001953125006], + [38.258593750000074, 50.05234375], + [38.91835937499999, 49.82470703125], + [39.17480468750003, 49.85595703124997], + [39.780566406250074, 49.57202148437503], + [40.080664062500006, 49.576855468749955], + [40.10878906250005, 49.251562500000034], + [39.68652343749997, 49.007910156250034], + [40.00361328125004, 48.82207031250002], + [39.792871093749994, 48.807714843750034], + [39.6447265625001, 48.591210937499966], + [39.8356445312501, 48.54277343749996], + [39.95791015625005, 48.268896484375034], + [39.77871093750005, 47.88754882812506], + [38.90029296875005, 47.85512695312502], + [38.36884765625004, 47.609960937500006], + [38.21435546875003, 47.091455078124966] + ] + ] + ] + }, + "properties": { "name": "Ukraine", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-57.81059570312499, -30.85859375000001], + [-57.872509765625, -30.59101562500001], + [-57.831201171874994, -30.495214843750006], + [-57.71269531249999, -30.38447265625001], + [-57.65087890625, -30.295019531250006], + [-57.645751953125, -30.226953125], + [-57.60888671875, -30.187792968750003], + [-57.55229492187499, -30.26123046875], + [-57.21445312499999, -30.28339843750001], + [-57.186914062499994, -30.26484375000001], + [-57.120507812499994, -30.14443359375001], + [-56.83271484375, -30.107226562500003], + [-56.4072265625, -30.44746093750001], + [-55.998974609375, -30.837207031250003], + [-56.018457031249994, -30.99189453125001], + [-56.00468749999999, -31.079199218750006], + [-55.873681640624994, -31.069628906250003], + [-55.6271484375, -30.85810546875001], + [-55.60302734375, -30.85078125000001], + [-55.55732421875, -30.8759765625], + [-55.17353515625, -31.279589843750003], + [-55.09116210937499, -31.31396484375], + [-55.036035156249994, -31.27900390625001], + [-54.587646484375, -31.48515625], + [-54.22055664062499, -31.85517578125001], + [-53.76171875, -32.05683593750001], + [-53.601708984374994, -32.40302734375001], + [-53.12558593749999, -32.73671875], + [-53.2140625, -32.82109375], + [-53.31010742187499, -32.92705078125], + [-53.39521484375, -33.010351562500006], + [-53.482861328125, -33.06855468750001], + [-53.511865234374994, -33.10869140625], + [-53.53134765624999, -33.1708984375], + [-53.53134765624999, -33.65546875000001], + [-53.37060546875, -33.7421875], + [-53.419580078124994, -33.77919921875001], + [-53.47246093749999, -33.84931640625001], + [-53.53452148437499, -34.01748046875001], + [-53.742919921875, -34.24951171875], + [-53.785302734374994, -34.38037109375], + [-54.16855468749999, -34.670703125], + [-54.902294921875, -34.93281250000001], + [-55.67314453124999, -34.77568359375], + [-56.249951171875, -34.90126953125001], + [-57.17070312499999, -34.45234375000001], + [-57.8291015625, -34.47734375], + [-58.40019531249999, -33.91240234375], + [-58.363525390625, -33.18232421875001], + [-58.08232421874999, -32.893652343750006], + [-58.12958984375, -32.75722656250001], + [-58.16220703124999, -32.566503906250006], + [-58.201171875, -32.4716796875], + [-58.123046875, -32.321875], + [-58.11972656249999, -32.24892578125001], + [-58.164794921875, -32.18486328125], + [-58.177001953125, -32.11904296875001], + [-58.15634765624999, -32.0515625], + [-58.160400390625, -31.98652343750001], + [-58.18901367187499, -31.92421875], + [-58.16748046875, -31.87265625], + [-58.04233398437499, -31.76923828125001], + [-58.006982421874994, -31.68496093750001], + [-58.053857421874994, -31.494921875], + [-58.0333984375, -31.416601562500006], + [-57.89335937499999, -31.1953125], + [-57.868408203125, -31.10439453125001], + [-57.88632812499999, -30.93740234375001], + [-57.81059570312499, -30.85859375000001] + ] + ] + }, + "properties": { "name": "Uruguay", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-155.58134765624996, 19.012011718750017], + [-155.88129882812495, 19.07050781250001], + [-156.04868164062498, 19.749951171874983], + [-155.82031249999997, 20.01416015624997], + [-155.83164062499998, 20.27583007812501], + [-155.198779296875, 19.99438476562503], + [-154.80419921875, 19.524462890625045], + [-155.58134765624996, 19.012011718750017] + ] + ], + [ + [ + [-156.84960937499997, 20.772656249999955], + [-156.97338867187497, 20.757519531249983], + [-157.0505859375, 20.912451171875034], + [-156.88056640624995, 20.904833984375074], + [-156.84960937499997, 20.772656249999955] + ] + ], + [ + [ + [-156.48681640624994, 20.93256835937504], + [-156.27753906250004, 20.951269531250034], + [-155.98984374999998, 20.75712890624999], + [-156.40878906249998, 20.60517578125004], + [-156.480078125, 20.80122070312501], + [-156.69775390625003, 20.949072265625034], + [-156.58540039062495, 21.034326171874994], + [-156.48681640624994, 20.93256835937504] + ] + ], + [ + [ + [-157.21362304687497, 21.215380859375017], + [-156.71215820312506, 21.155078125000074], + [-156.85986328125, 21.05634765625004], + [-157.29033203124996, 21.112597656250017], + [-157.21362304687497, 21.215380859375017] + ] + ], + [ + [ + [-157.79936523437502, 21.456640625000034], + [-157.63540039062502, 21.30761718749997], + [-158.11035156249994, 21.318603515625], + [-158.27314453125, 21.585253906250045], + [-157.9625, 21.701367187499983], + [-157.79936523437502, 21.456640625000034] + ] + ], + [ + [ + [-159.37275390625, 21.93237304687497], + [-159.60883789062495, 21.909521484375034], + [-159.78916015625003, 22.041796875000074], + [-159.57919921874998, 22.22314453124997], + [-159.35205078124997, 22.219580078125034], + [-159.37275390625, 21.93237304687497] + ] + ], + [ + [ + [-81.04418945312503, 24.716796875000057], + [-81.137353515625, 24.710498046875017], + [-80.93046875, 24.75947265625004], + [-81.04418945312503, 24.716796875000057] + ] + ], + [ + [ + [-80.3818359375, 25.142285156249955], + [-80.58056640624997, 24.954248046875023], + [-80.25708007812497, 25.34760742187504], + [-80.3818359375, 25.142285156249955] + ] + ], + [ + [ + [-97.17070312499996, 26.159375], + [-97.40209960937494, 26.820507812499983], + [-97.38598632812494, 27.19648437500004], + [-97.17070312499996, 26.159375] + ] + ], + [ + [ + [-80.18676757812497, 27.278417968750034], + [-80.17050781250003, 27.20478515625004], + [-80.43691406249994, 27.850537109374955], + [-80.18676757812497, 27.278417968750034] + ] + ], + [ + [ + [-91.793701171875, 29.50073242187497], + [-92.00664062499996, 29.61030273437501], + [-91.875244140625, 29.640966796875034], + [-91.793701171875, 29.50073242187497] + ] + ], + [ + [ + [-84.90791015624998, 29.642626953125017], + [-85.11674804687499, 29.63281249999997], + [-84.737158203125, 29.732421875], + [-84.90791015624998, 29.642626953125017] + ] + ], + [ + [ + [-89.22397460937498, 30.084082031249977], + [-89.34199218749995, 30.062841796875006], + [-89.18466796874995, 30.168652343749983], + [-89.22397460937498, 30.084082031249977] + ] + ], + [ + [ + [-118.34794921875002, 33.3857421875], + [-118.29746093750003, 33.312109375], + [-118.44628906249997, 33.317089843749955], + [-118.56943359375002, 33.46416015624999], + [-118.34794921875002, 33.3857421875] + ] + ], + [ + [ + [-120.04355468749995, 33.918847656249994], + [-120.25190429687494, 34.01386718749998], + [-120.07182617187493, 34.026513671874966], + [-120.04355468749995, 33.918847656249994] + ] + ], + [ + [ + [-119.88237304687497, 34.07968749999998], + [-119.54926757812497, 34.02817382812506], + [-119.80957031249997, 33.9677734375], + [-119.88237304687497, 34.07968749999998] + ] + ], + [ + [ + [-75.54414062499995, 35.240087890625034], + [-75.69008789062502, 35.221582031249994], + [-75.53637695312497, 35.27861328124999], + [-75.50351562500003, 35.769140625], + [-75.46474609374994, 35.448632812499966], + [-75.54414062499995, 35.240087890625034] + ] + ], + [ + [ + [-74.13320312500002, 39.680761718750034], + [-74.25048828125, 39.529394531250006], + [-74.10673828124996, 39.74643554687498], + [-74.13320312500002, 39.680761718750034] + ] + ], + [ + [ + [-72.50976562500003, 40.98603515625001], + [-72.58085937499996, 40.92133789062498], + [-71.90322265625, 41.06069335937505], + [-73.19428710937495, 40.654199218749994], + [-74.01489257812497, 40.581201171874966], + [-73.87924804687498, 40.79165039062502], + [-73.573828125, 40.91962890624998], + [-72.62509765624998, 40.99184570312505], + [-72.27412109374998, 41.15302734375001], + [-72.50976562500003, 40.98603515625001] + ] + ], + [ + [ + [-69.9779296875, 41.26557617187504], + [-70.23305664062502, 41.28632812500001], + [-70.04121093750001, 41.3974609375], + [-69.9779296875, 41.26557617187504] + ] + ], + [ + [ + [-70.50991210937502, 41.376318359375034], + [-70.82919921874995, 41.35898437500006], + [-70.61601562499996, 41.45722656250001], + [-70.50991210937502, 41.376318359375034] + ] + ], + [ + [ + [-71.24140625000001, 41.49194335937497], + [-71.34624023437496, 41.469384765624994], + [-71.23203124999995, 41.654296875], + [-71.24140625000001, 41.49194335937497] + ] + ], + [ + [ + [-68.18725585937497, 44.33247070312501], + [-68.41171875000003, 44.294335937499966], + [-68.29941406249998, 44.456494140624955], + [-68.18725585937497, 44.33247070312501] + ] + ], + [ + [ + [-122.394140625, 47.39526367187503], + [-122.50991210937497, 47.358007812500006], + [-122.486474609375, 47.48876953125], + [-122.394140625, 47.39526367187503] + ] + ], + [ + [ + [-122.57275390624999, 48.15664062499999], + [-122.38315429687499, 47.923193359375034], + [-122.74150390624999, 48.22529296875004], + [-122.62861328125, 48.38422851562498], + [-122.54243164062503, 48.29399414062499], + [-122.69702148437499, 48.228662109374994], + [-122.57275390624999, 48.15664062499999] + ] + ], + [ + [ + [-94.80346679687497, 49.0029296875], + [-94.71279296874997, 48.863427734374994], + [-94.62089843749999, 48.74262695312501], + [-93.85161132812496, 48.607275390625034], + [-93.70771484374995, 48.52543945312499], + [-93.37788085937498, 48.61655273437498], + [-93.25795898437497, 48.62885742187501], + [-92.83671875, 48.567773437499994], + [-92.50058593749995, 48.43535156250002], + [-92.41459960937493, 48.276611328125], + [-92.3484375, 48.276611328125], + [-92.00517578125002, 48.301855468750006], + [-91.38720703124997, 48.05854492187498], + [-91.04345703125003, 48.19370117187498], + [-90.84033203125003, 48.20053710937506], + [-90.79731445312495, 48.13105468750001], + [-89.4556640625, 47.996240234374994], + [-88.37817382812497, 48.30307617187498], + [-87.74389648437497, 48.06054687500003], + [-87.20800781249997, 47.848486328125006], + [-86.67216796874996, 47.636425781249955], + [-85.65224609375, 47.21997070312503], + [-85.07006835937497, 46.97993164062498], + [-84.87597656249994, 46.89990234375003], + [-84.66577148437503, 46.54326171875002], + [-84.44047851562496, 46.49814453125006], + [-84.12319335937497, 46.50292968749997], + [-83.97778320312503, 46.08491210937498], + [-83.61596679687503, 46.116845703124994], + [-83.46948242187503, 45.99467773437499], + [-83.59267578125, 45.81713867187506], + [-82.91933593749994, 45.51796875000002], + [-82.55107421874996, 45.34736328125001], + [-82.48505859374993, 45.08374023437503], + [-82.137841796875, 43.570898437500034], + [-82.19038085937495, 43.47407226562501], + [-82.54531249999997, 42.62470703124998], + [-83.10952148437497, 42.25068359375001], + [-83.141943359375, 41.97587890624996], + [-82.69003906249995, 41.675195312499994], + [-82.43906249999998, 41.67485351562502], + [-81.97416992187496, 41.88872070312499], + [-81.50732421874997, 42.10346679687504], + [-81.02822265624997, 42.247167968750006], + [-80.24755859375, 42.36601562499996], + [-79.17373046875, 42.74853515625], + [-78.91508789062496, 42.90913085937504], + [-78.98076171874993, 42.98061523437502], + [-79.02617187499996, 43.01733398437506], + [-79.066064453125, 43.10610351562502], + [-79.171875, 43.466552734375], + [-79.00249023437502, 43.52714843749999], + [-78.845556640625, 43.58334960937498], + [-78.72041015625001, 43.62495117187501], + [-78.45825195312497, 43.63149414062502], + [-77.596533203125, 43.62861328124998], + [-76.819970703125, 43.62880859375002], + [-76.18579101562503, 44.24223632812502], + [-75.81933593749997, 44.468017578125], + [-75.40126953124997, 44.77226562499999], + [-74.99614257812496, 44.970117187499966], + [-74.76245117187494, 44.99907226562502], + [-74.663232421875, 45.00390625000003], + [-71.51752929687495, 45.00756835937497], + [-71.327294921875, 45.29008789062496], + [-70.86503906249999, 45.27070312500001], + [-70.296240234375, 45.90610351562506], + [-70.00771484375002, 46.70893554687501], + [-69.24287109374998, 47.46298828124998], + [-69.0501953125, 47.426611328125034], + [-68.93720703124998, 47.21123046875002], + [-68.23549804687502, 47.34594726562503], + [-67.806787109375, 47.08281249999999], + [-67.80224609374994, 45.7275390625], + [-67.43266601562496, 45.603125], + [-67.366943359375, 45.17377929687498], + [-67.12485351562498, 45.16943359375], + [-66.98701171874995, 44.82768554687502], + [-67.191259765625, 44.67558593750002], + [-67.83906249999998, 44.576269531250034], + [-68.056640625, 44.38432617187502], + [-68.15205078124998, 44.50200195312499], + [-68.45058593749997, 44.50761718749999], + [-68.53251953124996, 44.25864257812498], + [-68.81191406249994, 44.33935546875], + [-68.76269531249994, 44.57075195312498], + [-69.22607421875003, 43.98647460937505], + [-69.52075195312503, 43.89736328125002], + [-69.55668945312496, 43.982763671875006], + [-69.62392578125, 43.88061523437497], + [-69.65288085937493, 43.99389648437506], + [-69.808349609375, 43.772314453125034], + [-69.965234375, 43.855078125], + [-70.17880859374998, 43.76635742187506], + [-70.73310546875001, 43.07001953125004], + [-70.82905273437493, 42.82534179687502], + [-70.61293945312497, 42.623242187499955], + [-71.04619140624993, 42.331103515625045], + [-70.73828125, 42.228857421875006], + [-70.42666015625002, 41.75727539062501], + [-70.00141601562498, 41.82617187500003], + [-70.24106445312495, 42.09121093750002], + [-70.10893554687496, 42.07832031249998], + [-69.97788085937498, 41.961279296875006], + [-69.94863281249997, 41.67714843750005], + [-70.65712890625, 41.53422851562496], + [-70.70112304687498, 41.71484375], + [-71.1685546875, 41.489404296874994], + [-71.14873046874996, 41.74570312499998], + [-71.27109375, 41.68125], + [-71.39013671875003, 41.79531250000005], + [-71.52285156249997, 41.378955078125045], + [-72.92470703125002, 41.28515625000003], + [-73.98710937499999, 40.751367187499994], + [-73.87197265625, 41.05517578124997], + [-73.96992187499995, 41.24970703125001], + [-73.92719726562495, 40.914257812499955], + [-74.26420898437496, 40.52861328124999], + [-73.972265625, 40.40034179687498], + [-74.079931640625, 39.78813476562496], + [-74.06459960937497, 39.99311523437498], + [-74.79448242187499, 39.00190429687501], + [-74.95429687499995, 38.949951171875], + [-74.89702148437502, 39.14545898437504], + [-75.52421874999999, 39.49018554687501], + [-75.421875, 39.78969726562502], + [-75.07416992187495, 39.98349609375006], + [-75.40063476562503, 39.83159179687502], + [-75.58759765625001, 39.64077148437505], + [-75.3921875, 39.09277343750006], + [-75.08867187499999, 38.777539062499955], + [-75.18710937499995, 38.59111328124999], + [-75.03876953124993, 38.426367187500006], + [-75.934375, 37.15190429687496], + [-75.97504882812498, 37.3984375], + [-75.65927734374995, 37.953955078125034], + [-75.850830078125, 37.971582031249994], + [-75.85869140624999, 38.36206054687503], + [-76.05122070312495, 38.27954101562503], + [-76.2646484375, 38.436425781249994], + [-76.26416015625, 38.599951171875006], + [-76.016943359375, 38.62509765624998], + [-76.21298828124998, 38.75830078125003], + [-76.34116210937498, 38.70966796874998], + [-76.16816406249998, 38.85273437499998], + [-76.32958984375, 38.95278320312505], + [-76.13520507812493, 39.082128906250006], + [-76.23569335937498, 39.19160156250001], + [-76.153125, 39.315039062500034], + [-75.87597656249997, 39.3759765625], + [-76.003125, 39.41083984375001], + [-75.87294921874997, 39.510888671874966], + [-75.95893554687498, 39.58505859374998], + [-76.2763671875, 39.32275390625], + [-76.330810546875, 39.40390625], + [-76.42089843749997, 39.225], + [-76.57041015624995, 39.26933593749996], + [-76.42758789062498, 39.12602539062499], + [-76.55854492187493, 39.065234375000045], + [-76.39409179687502, 38.368994140625034], + [-76.67734374999998, 38.611962890624966], + [-76.66855468749998, 38.5375], + [-76.34116210937498, 38.08701171875006], + [-76.86811523437495, 38.39028320312502], + [-76.88974609375, 38.292089843750006], + [-77.00117187499995, 38.44526367187504], + [-77.23251953125, 38.40771484375003], + [-77.03037109374995, 38.88925781249998], + [-77.26040039062502, 38.6], + [-77.27324218749996, 38.35175781249998], + [-77.04677734375002, 38.356689453125], + [-76.26425781250003, 37.89355468749997], + [-76.34414062499997, 37.675683593749994], + [-76.49248046874999, 37.682226562500006], + [-77.11108398437497, 38.165673828124994], + [-76.54946289062494, 37.66914062500001], + [-76.30556640625, 37.57148437500001], + [-76.26347656249996, 37.35703125], + [-76.40097656249998, 37.386132812499994], + [-76.45390624999993, 37.27353515625006], + [-76.75771484375002, 37.50541992187496], + [-76.28330078125, 37.05268554687501], + [-76.40087890624997, 36.991308593750034], + [-76.63090820312493, 37.22172851562499], + [-77.25087890624994, 37.329199218750034], + [-76.671875, 37.172949218750006], + [-76.48784179687502, 36.89702148437499], + [-75.99941406249997, 36.91264648437499], + [-75.53417968749997, 35.81909179687506], + [-75.94648437499995, 36.65908203125002], + [-75.99277343749995, 36.47377929687502], + [-75.82006835937494, 36.11284179687502], + [-76.14785156250002, 36.279296875], + [-76.15, 36.14575195312497], + [-76.27060546874998, 36.18989257812501], + [-76.22739257812498, 36.11601562499996], + [-76.559375, 36.015332031249955], + [-76.733642578125, 36.229150390624994], + [-76.726220703125, 35.957617187500034], + [-76.06977539062501, 35.970312500000034], + [-76.08359374999998, 35.69052734375006], + [-75.85390625, 35.96015625000001], + [-75.75883789062499, 35.84326171875], + [-75.77392578124997, 35.64697265624997], + [-76.17382812499997, 35.354150390624994], + [-76.489501953125, 35.397021484375045], + [-76.57719726562502, 35.53232421874998], + [-76.74140624999998, 35.431494140625034], + [-77.03999023437495, 35.527392578125045], + [-76.51293945312497, 35.270410156249994], + [-76.77915039062503, 34.990332031250034], + [-77.07026367187501, 35.154638671875034], + [-76.97495117187503, 35.025195312500045], + [-76.74497070312498, 34.94096679687502], + [-76.45673828124998, 34.989355468750034], + [-76.36220703125, 34.9365234375], + [-76.43979492187498, 34.84291992187502], + [-77.29624023437503, 34.602929687499994], + [-77.41225585937497, 34.730810546875034], + [-77.37978515625, 34.526611328125], + [-77.750732421875, 34.28496093749996], + [-77.92783203125, 33.93974609374999], + [-77.95327148437494, 34.16899414062496], + [-78.01333007812502, 33.91181640624998], + [-78.40585937499995, 33.91757812499998], + [-78.84145507812497, 33.72407226562501], + [-79.19379882812498, 33.24414062500003], + [-79.22646484375, 33.40488281249998], + [-79.27602539062497, 33.135400390624966], + [-79.80498046874999, 32.78740234374996], + [-79.93310546874997, 32.81005859375006], + [-79.94072265625002, 32.667138671874966], + [-80.36284179687496, 32.500732421875], + [-80.6341796875, 32.51171875000003], + [-80.474267578125, 32.42275390625002], + [-80.579345703125, 32.28730468750004], + [-80.80253906249999, 32.44804687500002], + [-80.69423828124997, 32.21572265625002], + [-81.11328124999997, 31.87861328125001], + [-81.06611328124995, 31.787988281250023], + [-81.259375, 31.538916015624977], + [-81.17543945312494, 31.531298828125017], + [-81.38095703124998, 31.353271484375], + [-81.28847656249997, 31.263916015625], + [-81.441748046875, 31.19970703124997], + [-81.5162109375, 30.801806640625017], + [-81.24951171875003, 29.793798828125006], + [-80.52412109374995, 28.48608398437503], + [-80.5849609375, 28.271582031250034], + [-80.456884765625, 27.90068359374996], + [-80.61000976562494, 28.177587890624977], + [-80.60693359375003, 28.522900390624983], + [-80.693505859375, 28.34497070312497], + [-80.68847656250003, 28.578515625000023], + [-80.83818359374999, 28.757666015625034], + [-80.74863281250003, 28.381005859375023], + [-80.050048828125, 26.807714843750063], + [-80.1263671875, 25.83349609375], + [-80.48466796874999, 25.229833984375034], + [-81.11049804687494, 25.138037109374977], + [-81.13603515624999, 25.309667968750034], + [-80.94042968750003, 25.264208984375017], + [-81.11333007812499, 25.367236328125045], + [-81.36494140625001, 25.83105468750003], + [-81.715478515625, 25.98315429687503], + [-81.95893554687495, 26.489941406249983], + [-81.82866210937496, 26.68706054687499], + [-82.03959960937496, 26.552050781250017], + [-82.01328125, 26.96157226562505], + [-82.24287109374998, 26.848876953125], + [-82.44135742187501, 27.059667968750034], + [-82.71459960937497, 27.499609375000063], + [-82.40576171874994, 27.862890624999977], + [-82.67519531249994, 27.963769531250023], + [-82.61098632812502, 27.77724609375005], + [-82.74287109374995, 27.709375], + [-82.84350585937494, 27.845996093750017], + [-82.65146484375, 28.8875], + [-83.69438476562502, 29.92597656250001], + [-84.04423828124996, 30.10380859374999], + [-84.30966796874995, 30.064746093750045], + [-84.38281250000003, 29.90737304687505], + [-85.31894531249995, 29.680224609375045], + [-85.413818359375, 29.76757812499997], + [-85.413818359375, 29.842480468749955], + [-85.31489257812493, 29.758105468750017], + [-85.35361328125, 29.875732421875], + [-85.67578125, 30.121923828125063], + [-85.60351562500003, 30.286767578124966], + [-85.75581054687495, 30.1669921875], + [-86.454443359375, 30.39912109375004], + [-86.12382812499999, 30.40581054687499], + [-86.25737304687502, 30.493017578124977], + [-87.201171875, 30.339257812499994], + [-86.98579101562498, 30.43085937500001], + [-86.99755859375, 30.5703125], + [-87.17060546874998, 30.538769531249983], + [-87.28105468750002, 30.339257812499994], + [-87.47578124999998, 30.294287109375006], + [-87.44829101562499, 30.394140625], + [-87.62226562499998, 30.264746093750006], + [-88.00595703124998, 30.230908203124955], + [-87.79028320312503, 30.291796875000017], + [-88.011328125, 30.694189453125006], + [-88.13544921874998, 30.366601562499994], + [-88.90522460937495, 30.415136718750006], + [-89.32055664062503, 30.3453125], + [-89.58847656249998, 30.165966796874955], + [-90.12597656249997, 30.369091796874955], + [-90.33198242187493, 30.277587890625057], + [-90.41303710937501, 30.140332031249983], + [-90.17534179687499, 30.02910156249996], + [-89.73745117187497, 30.171972656250034], + [-89.66503906249994, 30.117041015625034], + [-89.81518554687497, 30.007275390624955], + [-89.631689453125, 29.90380859375003], + [-89.400732421875, 30.04604492187505], + [-89.35444335937501, 29.82021484375005], + [-89.72089843749995, 29.619287109374966], + [-89.01572265625, 29.202880859375057], + [-89.15551757812497, 29.01660156250003], + [-89.23608398437494, 29.081103515625017], + [-89.37612304687497, 28.981347656250023], + [-89.44316406249996, 29.194140625000045], + [-90.15908203124997, 29.537158203125017], + [-90.05278320312499, 29.336816406249966], + [-90.21279296875, 29.104931640624983], + [-90.37919921874996, 29.29511718750001], + [-90.75102539062496, 29.13085937500003], + [-91.29013671875, 29.288964843749994], + [-91.15078124999994, 29.317919921875045], + [-91.24882812499993, 29.56420898437503], + [-91.51420898437499, 29.55537109375001], + [-91.8931640625, 29.836035156249977], + [-92.135498046875, 29.699462890625057], + [-92.08403320312499, 29.59282226562499], + [-92.26083984374995, 29.55683593750004], + [-93.17568359375, 29.778955078124994], + [-93.82646484374999, 29.725146484375045], + [-93.84145507812502, 29.97973632812503], + [-93.89047851562495, 29.689355468750023], + [-94.759619140625, 29.384277343750057], + [-94.52626953125, 29.547949218750006], + [-94.77827148437498, 29.54785156249997], + [-94.74194335937497, 29.75], + [-95.0228515625, 29.70234375000001], + [-94.88828125000003, 29.37055664062501], + [-95.27348632812499, 28.96386718750003], + [-96.23452148437502, 28.488964843749983], + [-96.01103515624996, 28.631933593749977], + [-96.44873046874997, 28.594482421875], + [-96.64003906249994, 28.708789062500017], + [-96.42109374999993, 28.457324218750045], + [-96.67636718749998, 28.34130859375003], + [-96.77353515624998, 28.421630859375057], + [-96.839501953125, 28.194384765625017], + [-97.156494140625, 28.144335937500045], + [-97.141259765625, 28.060742187499983], + [-97.034326171875, 28.093847656250063], + [-97.07309570312498, 27.98608398437503], + [-97.43149414062498, 27.83720703124999], + [-97.28872070312494, 27.670605468749983], + [-97.43911132812502, 27.328271484374966], + [-97.76845703124997, 27.45751953125], + [-97.69238281250003, 27.287158203125017], + [-97.48510742187497, 27.237402343750006], + [-97.55468749999994, 26.96733398437496], + [-97.43505859375, 26.48583984375003], + [-97.14624023437494, 25.961474609375045], + [-97.37563476562497, 25.871826171875], + [-99.10776367187498, 26.446923828124994], + [-99.45654296874999, 27.05668945312496], + [-99.50532226562497, 27.54833984375003], + [-100.29604492187495, 28.32768554687499], + [-100.75458984375001, 29.182519531249994], + [-101.44038085937503, 29.77685546875], + [-102.26894531249998, 29.871191406250034], + [-102.61494140624994, 29.75234375], + [-102.8919921875, 29.216406250000034], + [-103.16831054687498, 28.998193359374994], + [-104.110595703125, 29.386132812499994], + [-104.50400390624995, 29.677685546874955], + [-104.97880859374996, 30.645947265624955], + [-106.14804687499995, 31.450927734375], + [-106.44541015624996, 31.768408203125006], + [-108.21181640625002, 31.779345703125017], + [-108.21445312499993, 31.329443359375034], + [-111.0419921875, 31.32421875000003], + [-114.83593749999994, 32.50830078125003], + [-114.72475585937495, 32.71533203125003], + [-117.12827148437495, 32.533349609374994], + [-117.46743164062495, 33.295507812500006], + [-118.08051757812497, 33.72216796874997], + [-118.41044921874996, 33.74394531249996], + [-118.506201171875, 34.01738281249999], + [-119.14375, 34.11201171874998], + [-119.60605468749999, 34.41801757812499], + [-120.48120117187503, 34.47163085937498], + [-120.64467773437502, 34.57998046875002], + [-120.65908203124994, 35.122412109375034], + [-120.85737304687501, 35.209667968749955], + [-120.899609375, 35.42509765624999], + [-121.28383789062494, 35.67631835937499], + [-121.87739257812498, 36.33105468749997], + [-121.80742187499995, 36.851220703124994], + [-122.394921875, 37.20751953125003], + [-122.49921875000001, 37.542626953124994], + [-122.44560546875002, 37.797998046874966], + [-122.07050781249998, 37.47827148437503], + [-122.38544921875001, 37.960595703124966], + [-122.31425781249999, 38.00732421874997], + [-121.52534179687503, 38.05590820312503], + [-122.39335937499995, 38.14482421875002], + [-122.52133789062499, 37.82641601562497], + [-122.93198242187498, 38.05546875000002], + [-122.998779296875, 37.98862304687498], + [-122.90815429687501, 38.19658203124999], + [-123.701123046875, 38.90727539062502], + [-123.83291015624994, 39.775488281250034], + [-124.35654296875003, 40.37109374999997], + [-124.07192382812497, 41.45952148437502], + [-124.53964843750003, 42.812890624999966], + [-124.14873046874997, 43.691748046875034], + [-123.92934570312495, 45.57695312499996], + [-123.989306640625, 46.21938476562502], + [-123.22060546874998, 46.153613281250045], + [-123.46484375, 46.27109374999998], + [-124.07275390624996, 46.279443359374994], + [-124.04433593750002, 46.605078125], + [-123.946142578125, 46.43256835937501], + [-123.88916015625003, 46.660009765625006], + [-124.11254882812497, 46.862695312499994], + [-123.84287109375002, 46.963183593750045], + [-124.11171875, 47.03520507812496], + [-124.1392578125, 46.95468749999998], + [-124.376025390625, 47.658642578124955], + [-124.66308593749996, 47.97412109375003], + [-124.7099609375, 48.38037109375], + [-123.97578125, 48.16845703125], + [-122.97387695312499, 48.07329101562496], + [-122.77861328125, 48.13759765625002], + [-122.65664062500002, 47.88115234374999], + [-122.77841796874996, 47.738427734374966], + [-122.82138671875, 47.79316406250001], + [-123.1390625, 47.386083984375034], + [-122.92216796874993, 47.40766601562498], + [-123.066796875, 47.39965820312506], + [-123.04863281249995, 47.479345703125034], + [-122.53281250000002, 47.919726562500045], + [-122.67548828124995, 47.612353515625045], + [-122.57788085937496, 47.29316406250001], + [-122.76777343750001, 47.21835937500006], + [-122.82846679687503, 47.336572265624994], + [-123.02758789062501, 47.13891601562503], + [-122.70195312500002, 47.11088867187502], + [-122.35380859374996, 47.37158203125], + [-122.40180664062497, 47.78427734374998], + [-122.24199218750002, 48.01074218750003], + [-122.5169921875, 48.15966796874997], + [-122.40854492187502, 48.29389648437498], + [-122.66899414062496, 48.465234374999966], + [-122.49677734374995, 48.50556640625001], + [-122.51274414062502, 48.66943359375], + [-122.56201171875001, 48.777978515624994], + [-122.68593749999995, 48.794287109375034], + [-122.72246093750002, 48.85302734375003], + [-122.78876953125003, 48.993017578125034], + [-121.40722656249994, 48.993017578125034], + [-119.70170898437495, 48.993017578125034], + [-119.27534179687494, 48.99306640625005], + [-118.84892578124993, 48.99306640625005], + [-117.99619140625002, 48.99306640625005], + [-116.71704101562501, 48.99306640625005], + [-110.74765625, 48.99306640625005], + [-104.77832031249997, 48.993115234374955], + [-98.80898437499995, 48.99316406249997], + [-97.52983398437493, 48.99316406249997], + [-96.67705078124993, 48.99316406249997], + [-96.25068359374993, 48.99316406249997], + [-95.39790039062493, 48.99316406249997], + [-95.16206054687493, 48.991748046875045], + [-95.15527343749997, 49.36967773437502], + [-94.85434570312495, 49.304589843749994], + [-94.86040039062493, 49.258593750000045], + [-94.80346679687497, 49.0029296875] + ] + ], + [ + [ + [-176.28671874999998, 51.79199218750006], + [-176.34965820312502, 51.733300781249994], + [-176.41372070312502, 51.840576171875], + [-176.28671874999998, 51.79199218750006] + ] + ], + [ + [ + [-177.87905273437502, 51.64970703125002], + [-178.05888671875, 51.67260742187497], + [-177.98637695312493, 51.76425781249998], + [-178.16826171874996, 51.90302734375001], + [-177.644482421875, 51.826269531250006], + [-177.87905273437502, 51.64970703125002] + ] + ], + [ + [ + [-177.14819335937497, 51.71674804687498], + [-177.67021484375002, 51.701074218749994], + [-177.11005859375, 51.92875976562502], + [-177.14819335937497, 51.71674804687498] + ] + ], + [ + [ + [-176.593310546875, 51.86669921875], + [-176.45234374999995, 51.735693359375034], + [-176.96162109374998, 51.60366210937505], + [-176.69833984374998, 51.986035156249955], + [-176.593310546875, 51.86669921875] + ] + ], + [ + [ + [179.72773437500015, 51.905419921874966], + [179.50390625000003, 51.97958984374998], + [179.6271484375001, 52.03041992187502], + [179.72773437500015, 51.905419921874966] + ] + ], + [ + [ + [177.4154296875, 51.88281249999997], + [177.25029296875013, 51.902929687500006], + [177.6696289062501, 52.10302734375], + [177.4154296875, 51.88281249999997] + ] + ], + [ + [ + [-173.5533203125, 52.13627929687502], + [-173.02290039062504, 52.07915039062502], + [-173.83579101562498, 52.048193359375006], + [-173.99248046874993, 52.12333984374996], + [-173.5533203125, 52.13627929687502] + ] + ], + [ + [ + [-172.464794921875, 52.27226562500002], + [-172.61982421874998, 52.27285156250005], + [-172.47041015625, 52.38803710937506], + [-172.31362304687497, 52.32958984375006], + [-172.464794921875, 52.27226562500002] + ] + ], + [ + [ + [-174.67739257812502, 52.035009765625006], + [-175.29555664062502, 52.022167968749955], + [-174.30615234375, 52.216162109375034], + [-174.43554687499997, 52.317236328125034], + [-174.168896484375, 52.42016601562503], + [-174.04560546875, 52.36723632812499], + [-174.12065429687493, 52.13520507812498], + [-174.67739257812502, 52.035009765625006] + ] + ], + [ + [ + [173.72275390625018, 52.35957031250004], + [173.40234375000009, 52.40478515625], + [173.77607421875004, 52.49511718750003], + [173.72275390625018, 52.35957031250004] + ] + ], + [ + [ + [172.81181640625002, 53.01298828125002], + [173.43603515625003, 52.85205078125], + [172.93515625000012, 52.752099609374966], + [172.49482421875004, 52.93789062499999], + [172.81181640625002, 53.01298828125002] + ] + ], + [ + [ + [-167.96435546875003, 53.345117187499994], + [-169.088916015625, 52.83203125], + [-168.68984375000002, 53.227246093749955], + [-168.38041992187496, 53.28344726562506], + [-168.28769531249998, 53.500146484374966], + [-167.82807617187495, 53.50795898437505], + [-167.96435546875003, 53.345117187499994] + ] + ], + [ + [ + [-166.61533203124998, 53.90092773437499], + [-166.37231445312494, 53.99897460937498], + [-166.230859375, 53.93261718750006], + [-166.54560546875, 53.726464843749966], + [-166.354541015625, 53.67353515625004], + [-166.85097656249997, 53.45288085937503], + [-167.78085937500003, 53.30024414062501], + [-167.13608398437503, 53.526464843750006], + [-167.01572265625003, 53.69838867187502], + [-166.80898437500002, 53.64614257812505], + [-166.741259765625, 53.71293945312496], + [-167.10561523437497, 53.813378906249994], + [-167.03808593749997, 53.9421875], + [-166.67329101562498, 54.00595703124998], + [-166.61533203124998, 53.90092773437499] + ] + ], + [ + [ + [-165.841552734375, 54.070654296875006], + [-166.05664062500003, 54.054345703124994], + [-166.08774414062498, 54.16914062500001], + [-165.89287109375, 54.20698242187498], + [-165.69287109375, 54.09990234375002], + [-165.841552734375, 54.070654296875006] + ] + ], + [ + [ + [-165.56113281249998, 54.13671874999997], + [-165.55063476562498, 54.28452148437498], + [-165.40786132812502, 54.19682617187496], + [-165.56113281249998, 54.13671874999997] + ] + ], + [ + [ + [-162.29814453124993, 54.847021484375006], + [-162.43388671875, 54.931542968749994], + [-162.26459960937504, 54.983496093750006], + [-162.29814453124993, 54.847021484375006] + ] + ], + [ + [ + [-163.476025390625, 54.98071289062497], + [-163.37895507812496, 54.81552734374998], + [-163.083251953125, 54.66899414062496], + [-163.35810546874995, 54.73569335937506], + [-164.82343749999998, 54.41909179687505], + [-164.887646484375, 54.60781250000002], + [-164.47861328124998, 54.906835937500006], + [-163.80712890624997, 55.04907226562503], + [-163.476025390625, 54.98071289062497] + ] + ], + [ + [ + [-159.51513671875, 55.15185546875003], + [-159.617724609375, 55.05732421875004], + [-159.54506835937497, 55.22597656250002], + [-159.51513671875, 55.15185546875003] + ] + ], + [ + [ + [-131.33974609375002, 55.079833984375], + [-131.32954101562498, 54.887744140625045], + [-131.592236328125, 55.02568359374999], + [-131.5654296875, 55.26411132812498], + [-131.33974609375002, 55.079833984375] + ] + ], + [ + [ + [-159.87299804687495, 55.128759765625034], + [-160.22705078124997, 54.92270507812506], + [-160.17207031249995, 55.123046875], + [-159.88735351562497, 55.27299804687502], + [-159.87299804687495, 55.128759765625034] + ] + ], + [ + [ + [-132.86225585937504, 54.894433593749966], + [-132.61723632812493, 54.892431640625006], + [-132.70581054687497, 54.684179687500034], + [-133.42905273437498, 55.30380859374998], + [-133.097412109375, 55.213720703125006], + [-132.86225585937504, 54.894433593749966] + ] + ], + [ + [ + [-160.329296875, 55.337695312500045], + [-160.34331054687493, 55.25878906250006], + [-160.51748046875, 55.33383789062506], + [-160.329296875, 55.337695312500045] + ] + ], + [ + [ + [-160.68491210937498, 55.314794921875006], + [-160.552783203125, 55.38076171875002], + [-160.48754882812503, 55.18486328124999], + [-160.79506835937497, 55.14521484375001], + [-160.72392578124993, 55.404638671875006], + [-160.68491210937498, 55.314794921875006] + ] + ], + [ + [ + [-133.30507812500002, 55.54375], + [-133.6501953125, 55.26928710937506], + [-133.73710937500002, 55.49692382812498], + [-133.30507812500002, 55.54375] + ] + ], + [ + [ + [-155.56601562500003, 55.82119140625005], + [-155.73735351562493, 55.82978515625001], + [-155.59394531250004, 55.92431640625], + [-155.56601562500003, 55.82119140625005] + ] + ], + [ + [ + [-130.97915039062502, 55.489160156249994], + [-131.187890625, 55.206298828125], + [-131.44755859374996, 55.40878906250006], + [-131.7625, 55.16582031250002], + [-131.84609374999997, 55.41625976562497], + [-131.62495117187504, 55.831689453124966], + [-131.26923828125004, 55.95537109375002], + [-130.997802734375, 55.727636718750006], + [-130.97915039062502, 55.489160156249994] + ] + ], + [ + [ + [-133.56611328125, 56.33920898437498], + [-133.202978515625, 56.31982421875003], + [-133.096630859375, 56.09003906250001], + [-132.59760742187504, 55.89501953125], + [-132.17270507812498, 55.48061523437502], + [-132.51127929687493, 55.59394531250001], + [-132.63129882812495, 55.47319335937502], + [-132.41787109375002, 55.48291015625006], + [-132.20668945312497, 55.22441406249996], + [-131.97641601562498, 55.208593750000034], + [-132.06474609375002, 54.713134765625], + [-133.11855468750002, 55.32763671875003], + [-132.95888671875002, 55.39555664062502], + [-133.0333984375, 55.589697265625034], + [-133.68017578124994, 55.78515625], + [-133.24150390624993, 55.920800781249994], + [-133.371240234375, 56.035888671875], + [-133.74252929687498, 55.96484375], + [-133.530859375, 56.145654296874966], + [-133.56611328125, 56.33920898437498] + ] + ], + [ + [ + [-132.77988281249998, 56.24726562499998], + [-133.03500976562498, 56.34091796875006], + [-132.90205078124998, 56.45375976562505], + [-132.62910156249995, 56.411914062500045], + [-132.77988281249998, 56.24726562499998] + ] + ], + [ + [ + [-132.11235351562493, 56.109375], + [-132.13295898437497, 55.94326171875005], + [-132.28730468749995, 55.92939453124998], + [-132.65991210937503, 56.07817382812499], + [-132.379833984375, 56.49877929687497], + [-132.06689453125, 56.24423828124998], + [-132.11235351562493, 56.109375] + ] + ], + [ + [ + [-154.208642578125, 56.51489257812497], + [-154.32221679687504, 56.570605468750045], + [-154.11040039062496, 56.602929687499966], + [-154.208642578125, 56.51489257812497] + ] + ], + [ + [ + [-169.755224609375, 56.63505859375002], + [-169.47431640624998, 56.59404296875002], + [-169.6326171875, 56.545703125000045], + [-169.755224609375, 56.63505859375002] + ] + ], + [ + [ + [-132.746875, 56.525683593750045], + [-132.94804687500002, 56.56723632812498], + [-132.842529296875, 56.79477539062506], + [-132.56796875000003, 56.57583007812505], + [-132.746875, 56.525683593750045] + ] + ], + [ + [ + [-133.98959960937503, 56.84497070312497], + [-133.73837890625, 56.65043945312496], + [-133.94970703125, 56.12773437499996], + [-134.18959960937502, 56.07695312500002], + [-134.084375, 56.456347656250045], + [-134.37368164062502, 56.838671875000045], + [-134.14326171874998, 56.93232421875001], + [-133.98959960937503, 56.84497070312497] + ] + ], + [ + [ + [-133.36621093750006, 57.003515625000034], + [-132.99624023437497, 56.93041992187497], + [-132.95917968749998, 56.67705078124996], + [-133.03491210937494, 56.62075195312505], + [-133.32895507812498, 56.83007812499997], + [-133.158154296875, 56.495166015625045], + [-133.4841796875, 56.45175781249998], + [-133.979443359375, 57.009570312500045], + [-133.36621093750006, 57.003515625000034] + ] + ], + [ + [ + [-153.007080078125, 57.12485351562498], + [-153.37460937499998, 57.05190429687505], + [-153.285205078125, 57.18505859375], + [-152.90839843750004, 57.152441406250006], + [-153.007080078125, 57.12485351562498] + ] + ], + [ + [ + [-134.96977539062496, 57.351416015625034], + [-134.62070312499998, 56.71831054687502], + [-134.68188476562503, 56.216162109375034], + [-134.98056640625003, 56.518945312499994], + [-134.88344726562497, 56.679052734375034], + [-135.33061523437505, 56.821875], + [-135.19960937499997, 57.02734375], + [-135.45493164062503, 57.24941406250005], + [-135.81230468750002, 57.00952148437503], + [-135.82275390625, 57.280419921874966], + [-135.448681640625, 57.534375], + [-134.96977539062496, 57.351416015625034] + ] + ], + [ + [ + [-152.89804687499998, 57.82392578125004], + [-152.42875976562493, 57.82568359375003], + [-152.48261718749998, 57.70332031249998], + [-152.21621093749997, 57.577001953125006], + [-152.41220703125003, 57.454785156249955], + [-152.94077148437498, 57.49809570312499], + [-152.67905273437503, 57.345117187499994], + [-153.274365234375, 57.22636718749996], + [-153.732568359375, 57.052343750000034], + [-153.643310546875, 56.960742187500045], + [-154.02734375, 56.77797851562502], + [-153.793212890625, 56.98950195312503], + [-154.24375, 57.143017578124955], + [-154.33896484374998, 56.9208984375], + [-154.67319335937498, 57.44609375], + [-154.11616210937498, 57.651220703125006], + [-153.6876953125, 57.30512695312504], + [-153.841552734375, 57.86284179687496], + [-153.48793945312497, 57.73095703125], + [-153.21748046875004, 57.79575195312506], + [-153.16044921875, 57.97197265624999], + [-152.85039062499993, 57.896777343750045], + [-152.89804687499998, 57.82392578125004] + ] + ], + [ + [ + [-135.73037109375002, 58.244238281250034], + [-135.61323242187507, 57.99184570312505], + [-135.346630859375, 58.12412109374998], + [-134.9546875, 58.01533203125004], + [-134.97065429687495, 57.817236328125006], + [-135.33847656250003, 57.768652343750034], + [-134.97885742187503, 57.724365234375], + [-134.93149414062498, 57.48115234375001], + [-135.564208984375, 57.66640625], + [-135.691943359375, 57.41992187500006], + [-135.91079101562502, 57.44658203124999], + [-136.568603515625, 57.97216796875003], + [-136.32197265625, 58.21889648437502], + [-136.14375, 58.098486328125006], + [-136.09438476562502, 58.198144531249966], + [-135.73037109375002, 58.244238281250034] + ] + ], + [ + [ + [-134.68027343749998, 58.16166992187499], + [-134.24008789062498, 58.143994140624955], + [-133.82275390624997, 57.62866210937503], + [-134.29233398437498, 58.044726562500074], + [-133.91113281250003, 57.3525390625], + [-134.51601562499997, 57.042578125], + [-134.48676757812495, 57.48203125], + [-134.92348632812497, 58.354638671874966], + [-134.68027343749998, 58.16166992187499] + ] + ], + [ + [ + [-152.416943359375, 58.360205078125034], + [-151.974365234375, 58.30986328124999], + [-152.068896484375, 58.17792968750001], + [-152.26835937499993, 58.25170898437506], + [-152.30922851562502, 58.133886718750034], + [-152.5982421875, 58.16259765625], + [-152.92841796875004, 57.99370117187499], + [-153.38134765625003, 58.08720703125002], + [-152.976123046875, 58.29702148437505], + [-152.771875, 58.278564453125], + [-152.84111328125002, 58.41640625000002], + [-152.416943359375, 58.360205078125034] + ] + ], + [ + [ + [-152.486083984375, 58.485009765624966], + [-152.63662109375002, 58.54169921874998], + [-152.3955078125, 58.619384765625], + [-152.486083984375, 58.485009765624966] + ] + ], + [ + [ + [-160.918994140625, 58.57709960937498], + [-161.13149414062502, 58.668212890625], + [-160.71513671875005, 58.79521484375002], + [-160.918994140625, 58.57709960937498] + ] + ], + [ + [ + [-148.02177734375, 60.06533203125005], + [-148.271875, 60.05327148437499], + [-148.07958984375003, 60.151660156250045], + [-148.02177734375, 60.06533203125005] + ] + ], + [ + [ + [-147.735888671875, 59.81323242187503], + [-147.76806640625, 59.94375], + [-147.180859375, 60.358251953125034], + [-147.01987304687498, 60.33222656249998], + [-147.735888671875, 59.81323242187503] + ] + ], + [ + [ + [-166.13544921875, 60.38354492187503], + [-165.72968750000004, 60.31420898437503], + [-165.591796875, 59.913134765625045], + [-166.14873046874996, 59.764111328124955], + [-167.13886718749998, 60.00854492187503], + [-167.43642578125002, 60.20664062500006], + [-166.836328125, 60.21699218750004], + [-166.47568359374998, 60.382763671874955], + [-166.13544921875, 60.38354492187503] + ] + ], + [ + [ + [-146.3939453125, 60.44965820312501], + [-146.10224609374998, 60.41118164062499], + [-146.61831054687497, 60.27368164062503], + [-146.70253906249997, 60.40854492187498], + [-146.3939453125, 60.44965820312501] + ] + ], + [ + [ + [-147.658251953125, 60.45048828124999], + [-147.787841796875, 60.17792968749998], + [-147.89145507812498, 60.299414062500034], + [-147.658251953125, 60.45048828124999] + ] + ], + [ + [ + [-172.74223632812496, 60.45737304687498], + [-172.23208007812494, 60.299121093750074], + [-172.63574218750003, 60.328857421875], + [-173.04765625000002, 60.56831054687501], + [-172.74223632812496, 60.45737304687498] + ] + ], + [ + [ + [-171.46303710937494, 63.640039062499994], + [-171.03486328125, 63.58549804687499], + [-170.29936523437502, 63.68061523437501], + [-169.55454101562498, 63.373486328124955], + [-168.71601562500004, 63.310595703125045], + [-168.76132812500003, 63.21376953125002], + [-169.364697265625, 63.17114257812506], + [-169.67636718750003, 62.95610351562502], + [-169.81860351562494, 63.122363281250045], + [-170.84838867187494, 63.44438476562502], + [-171.63183593749997, 63.351220703124966], + [-171.74638671874993, 63.703076171874955], + [-171.46303710937494, 63.640039062499994] + ] + ], + [ + [ + [-141.00214843750004, 68.77416992187506], + [-141.00214843750004, 67.89755859374998], + [-141.00214843750004, 66.43652343750006], + [-141.00214843750004, 65.55991210937498], + [-141.00214843750004, 64.09887695312506], + [-141.00214843750004, 63.22226562499998], + [-141.00214843750004, 61.761279296875045], + [-141.00214843750004, 60.884667968749994], + [-141.00214843750004, 60.30024414062504], + [-140.76274414062505, 60.259130859375006], + [-140.525439453125, 60.218359375000034], + [-140.45283203125004, 60.29970703125002], + [-139.97329101562497, 60.183154296875074], + [-139.67631835937505, 60.32832031249998], + [-139.23476562499997, 60.339746093749994], + [-139.07924804687497, 60.34370117187501], + [-139.07924804687497, 60.279443359374966], + [-139.136962890625, 60.17270507812498], + [-139.18515624999998, 60.083593750000034], + [-138.86875, 59.94575195312501], + [-138.317626953125, 59.611132812500074], + [-137.59331054687493, 59.22626953124998], + [-137.52089843750002, 58.91538085937498], + [-137.43857421875003, 58.903125], + [-137.2775390625, 58.988183593749994], + [-137.126220703125, 59.04096679687498], + [-136.81328125000002, 59.150048828124994], + [-136.57875976562502, 59.15224609375002], + [-136.46635742187493, 59.459082031250006], + [-136.27797851562502, 59.48032226562506], + [-136.321826171875, 59.604833984375034], + [-135.70258789062504, 59.72875976562506], + [-135.36787109374998, 59.743310546874994], + [-135.051025390625, 59.57866210937502], + [-134.94375, 59.28828125000001], + [-134.67724609374997, 59.19926757812499], + [-134.39306640625, 59.009179687499994], + [-134.32963867187505, 58.93969726562506], + [-134.21850585937503, 58.849902343750045], + [-133.54638671874997, 58.50346679687499], + [-133.27529296875, 58.22285156250004], + [-133.00141601562495, 57.948974609375], + [-132.55048828125, 57.499902343749994], + [-132.44248046874998, 57.40673828125003], + [-132.30166015624997, 57.27631835937501], + [-132.232177734375, 57.19853515624999], + [-132.27939453124998, 57.14536132812498], + [-132.33798828124998, 57.07944335937506], + [-132.15703125, 57.048193359375006], + [-132.03154296875, 57.02656250000004], + [-132.062890625, 56.95336914062503], + [-132.104296875, 56.856787109375006], + [-131.86616210937495, 56.792822265625006], + [-131.82426757812496, 56.589990234374994], + [-131.471875, 56.55673828125006], + [-130.649072265625, 56.26367187500003], + [-130.47709960937496, 56.230566406250034], + [-130.413134765625, 56.12250976562498], + [-130.09785156249995, 56.10927734375002], + [-130.01406249999997, 55.950537109375006], + [-130.2140625, 55.02587890625003], + [-130.57534179687497, 54.769677734374966], + [-130.849609375, 54.80761718750006], + [-131.04785156249997, 55.157666015624955], + [-130.74819335937502, 55.31801757812502], + [-131.127685546875, 55.96015625000001], + [-131.032763671875, 56.08808593749998], + [-131.78417968749997, 55.876562500000034], + [-131.98339843749994, 55.535009765625006], + [-132.15541992187502, 55.59956054687501], + [-132.20751953124997, 55.75341796875], + [-131.84384765625003, 56.16010742187498], + [-131.55136718749998, 56.206787109375], + [-131.88789062500004, 56.24165039062498], + [-132.18203125000002, 56.42065429687506], + [-132.82460937500002, 57.05581054687505], + [-133.465869140625, 57.17216796875002], + [-133.64873046874993, 57.64228515624998], + [-133.11704101562498, 57.56621093750002], + [-133.535205078125, 57.83295898437501], + [-133.1943359375, 57.87768554687506], + [-133.559375, 57.924462890624994], + [-133.72231445312502, 57.84423828125], + [-134.03110351562498, 58.072167968749966], + [-133.87675781249996, 58.51816406249998], + [-134.20883789062503, 58.232958984375045], + [-134.77612304687506, 58.45385742187503], + [-135.36367187500002, 59.41943359375], + [-135.50234375000002, 59.202294921874994], + [-135.090234375, 58.245849609375], + [-135.57177734374994, 58.41206054687504], + [-135.89755859374998, 58.40019531250002], + [-136.04311523437497, 58.82163085937498], + [-135.82636718750004, 58.89794921874997], + [-136.0166015625, 58.87397460937498], + [-136.150048828125, 59.04809570312503], + [-136.22583007812497, 58.765478515625006], + [-136.98901367187503, 59.03447265624999], + [-137.05903320312498, 58.87373046875001], + [-136.613916015625, 58.809277343749955], + [-136.48374023437503, 58.61767578125], + [-136.224609375, 58.602246093749955], + [-136.06147460937495, 58.45273437500006], + [-136.607421875, 58.24399414062498], + [-137.54399414062502, 58.58120117187502], + [-138.51489257812503, 59.16591796875005], + [-139.77329101562498, 59.52729492187504], + [-139.51303710937498, 59.698095703125006], + [-139.5123046875, 59.95356445312501], + [-139.28671874999998, 59.610937500000034], + [-139.22080078125003, 59.819873046875045], + [-138.9880859375, 59.83500976562502], + [-139.43144531249996, 60.012255859375074], + [-140.41982421874997, 59.71074218750002], + [-141.40830078125, 59.90278320312498], + [-141.408740234375, 60.11767578125006], + [-141.67016601562497, 59.969873046874966], + [-142.94565429687503, 60.09697265625002], + [-144.14721679687494, 60.01640625000002], + [-144.185498046875, 60.150732421875034], + [-144.901318359375, 60.335156249999955], + [-144.69111328125, 60.66909179687502], + [-145.248291015625, 60.38012695312506], + [-145.898876953125, 60.47817382812505], + [-145.67490234374998, 60.65112304687503], + [-146.57045898437497, 60.72915039062502], + [-146.39199218749997, 60.810839843750045], + [-146.63842773437497, 60.89731445312498], + [-146.59912109374994, 61.05351562500002], + [-146.284912109375, 61.11264648437498], + [-147.89111328125, 60.889892578125], + [-148.00512695312494, 60.96855468750002], + [-147.75185546874997, 61.218945312499955], + [-148.34189453125, 61.060400390625006], + [-148.34443359374998, 60.853564453125045], + [-148.55615234374994, 60.82700195312506], + [-148.25673828124997, 60.67529296874997], + [-148.64013671875, 60.48945312500004], + [-148.11918945312502, 60.57514648437498], + [-147.96411132812494, 60.48486328124997], + [-148.430712890625, 59.98911132812498], + [-149.2666015625, 59.99829101562497], + [-149.395263671875, 60.10576171875002], + [-149.59804687500002, 59.77045898437501], + [-149.7138671875, 59.91958007812502], + [-149.80126953124994, 59.737939453124966], + [-150.00532226562507, 59.78442382812503], + [-150.19804687499996, 59.56655273437505], + [-150.60737304687504, 59.56337890625002], + [-150.934521484375, 59.249121093750034], + [-151.18276367187502, 59.30078124999997], + [-151.73818359375002, 59.18852539062502], + [-151.94951171875, 59.26508789062498], + [-151.88461914062503, 59.386328125], + [-151.39960937499995, 59.51630859375001], + [-151.04648437499998, 59.771826171875034], + [-151.45009765624997, 59.65039062499997], + [-151.85322265625, 59.78208007812498], + [-151.39599609375006, 60.27446289062502], + [-151.35644531249997, 60.72294921874999], + [-150.44125976562503, 61.02358398437505], + [-149.07509765624997, 60.87641601562498], + [-150.05327148437496, 61.17109374999998], + [-149.433544921875, 61.50078125000002], + [-149.97568359374998, 61.27934570312502], + [-150.61225585937495, 61.301123046875006], + [-151.59350585937494, 60.979638671874966], + [-152.54091796874997, 60.265429687500045], + [-153.025, 60.29565429687497], + [-152.660107421875, 59.99721679687502], + [-153.21123046875002, 59.84272460937498], + [-153.09360351562503, 59.70913085937505], + [-153.65253906250004, 59.64702148437499], + [-154.17832031250003, 59.155566406250074], + [-153.41826171875, 58.9599609375], + [-153.43759765625003, 58.754833984374955], + [-154.289013671875, 58.30434570312502], + [-154.247021484375, 58.15942382812497], + [-155.006884765625, 58.01606445312501], + [-155.77797851562497, 57.56821289062498], + [-156.43588867187498, 57.359960937500006], + [-156.62900390624998, 57.00996093750001], + [-158.41440429687498, 56.435839843750045], + [-158.5521484375, 56.31269531249998], + [-158.27563476562497, 56.19624023437498], + [-158.5046875, 56.062109375], + [-158.59116210937503, 56.18452148437498], + [-158.78984375000002, 55.98691406250006], + [-159.52324218749993, 55.81000976562498], + [-159.65966796875003, 55.625927734374955], + [-159.77138671874997, 55.84111328125002], + [-160.49931640625002, 55.53730468750004], + [-161.38193359374998, 55.371289062499955], + [-161.44379882812495, 55.513281250000034], + [-161.202099609375, 55.54355468750006], + [-161.51694335937503, 55.61840820312503], + [-162.073974609375, 55.13930664062505], + [-162.38637695312497, 55.05234375], + [-162.63037109375003, 55.24667968749998], + [-162.67436523437505, 54.99658203125], + [-162.86503906249996, 54.954541015624955], + [-163.11962890624997, 55.06469726562503], + [-163.131103515625, 54.916552734375045], + [-163.33530273437503, 54.83916015624999], + [-163.27880859374997, 55.12182617187503], + [-162.906591796875, 55.19555664062503], + [-161.69731445312502, 55.9072265625], + [-161.215625, 56.02143554687498], + [-160.8986328125, 55.99365234375], + [-161.00537109375, 55.88715820312498], + [-160.80283203125003, 55.754443359375045], + [-160.70634765625002, 55.870458984375034], + [-160.29169921875, 55.80507812500005], + [-160.53906250000006, 56.00629882812501], + [-160.30205078125, 56.31411132812502], + [-158.91801757812502, 56.882177734375006], + [-158.675146484375, 56.79487304687498], + [-158.66079101562502, 57.03940429687498], + [-158.32094726562497, 57.29790039062499], + [-157.84575195312496, 57.52807617187497], + [-157.4619140625, 57.506201171875034], + [-157.697216796875, 57.679443359375], + [-157.610888671875, 58.050830078125074], + [-157.19370117187498, 58.19418945312506], + [-157.48837890624998, 58.25371093750002], + [-157.52363281249998, 58.421337890624955], + [-156.97465820312496, 58.736328125], + [-156.80888671875005, 59.13427734375], + [-157.14204101562504, 58.87763671875001], + [-158.19091796875003, 58.6142578125], + [-158.50317382812494, 58.85034179687497], + [-158.42563476562498, 58.99931640625002], + [-158.080517578125, 58.97744140625002], + [-158.422802734375, 59.08984375], + [-158.67827148437502, 58.92939453124998], + [-158.80947265625002, 58.973876953125], + [-158.78862304687493, 58.440966796875045], + [-158.95068359375, 58.404541015625], + [-159.67026367187498, 58.9111328125], + [-159.92021484375, 58.819873046875074], + [-160.36313476562498, 59.05117187500002], + [-161.246826171875, 58.799462890624994], + [-161.36132812499994, 58.66953124999998], + [-162.144921875, 58.64423828124998], + [-161.724365234375, 58.794287109375006], + [-161.64438476562498, 59.109667968750045], + [-161.9810546875, 59.14614257812502], + [-161.82871093749998, 59.588623046875], + [-162.421337890625, 60.28398437500002], + [-161.96201171875003, 60.695361328125045], + [-162.68496093749997, 60.268945312499966], + [-162.57075195312495, 59.98974609375], + [-163.68037109374998, 59.80151367187503], + [-164.14282226562497, 59.89677734374999], + [-165.02651367187497, 60.500634765624994], + [-165.35380859375002, 60.54121093750001], + [-164.80517578125, 60.89204101562498], + [-164.31850585937497, 60.77128906249999], + [-164.37236328125002, 60.59184570312502], + [-163.999560546875, 60.76606445312498], + [-163.72998046874997, 60.589990234374994], + [-163.420947265625, 60.757421875], + [-163.90654296874996, 60.85380859375002], + [-163.58691406249994, 60.902978515624994], + [-163.74902343750003, 60.9697265625], + [-163.99462890624997, 60.86469726562501], + [-165.11484375, 60.93281250000004], + [-164.86899414062503, 61.11176757812498], + [-165.27978515624994, 61.169628906249955], + [-165.27363281250004, 61.27485351562498], + [-165.56586914062498, 61.10234375000002], + [-165.86396484375004, 61.33569335937503], + [-165.84531249999998, 61.536230468750034], + [-166.152734375, 61.545947265625074], + [-166.16811523437502, 61.65083007812501], + [-165.80893554687503, 61.69609375000002], + [-166.07880859375, 61.803125], + [-165.61279296875003, 61.86928710937502], + [-165.707275390625, 62.10043945312506], + [-165.19453125, 62.47353515625002], + [-164.75786132812493, 62.496728515624966], + [-164.589453125, 62.709375], + [-164.79267578125, 62.623193359374966], + [-164.79965820312503, 62.918066406250006], + [-164.384228515625, 63.03046874999998], + [-164.40903320312503, 63.21503906250001], + [-163.94287109375, 63.247216796874994], + [-163.61630859374998, 63.125146484374994], + [-163.73784179687496, 63.016406250000045], + [-163.504345703125, 63.105859374999966], + [-163.28784179687494, 63.046435546875045], + [-162.621484375, 63.26582031249998], + [-162.28281250000003, 63.529199218749994], + [-161.97397460937498, 63.45292968749999], + [-161.09970703125003, 63.557910156250045], + [-160.778564453125, 63.818945312500034], + [-160.987548828125, 64.25126953125002], + [-161.49072265625003, 64.43378906249998], + [-160.93193359374996, 64.5791015625], + [-160.855908203125, 64.755615234375], + [-161.13017578125005, 64.92543945312505], + [-161.759375, 64.816259765625], + [-162.80703124999997, 64.37421875000001], + [-163.20390625, 64.65200195312502], + [-163.14433593750002, 64.423828125], + [-163.71308593749998, 64.588232421875], + [-164.978759765625, 64.45366210937502], + [-166.1427734375, 64.58276367187503], + [-166.48139648437498, 64.72807617187507], + [-166.415234375, 64.926513671875], + [-166.92841796875, 65.15708007812498], + [-166.15703125, 65.28583984375001], + [-167.40400390625, 65.42211914062497], + [-168.08837890624997, 65.65776367187502], + [-166.39873046875002, 66.14443359375005], + [-165.62993164062496, 66.131201171875], + [-165.77617187500002, 66.31904296875001], + [-164.46049804687502, 66.58842773437499], + [-163.63823242187502, 66.57465820312504], + [-163.89394531249997, 66.57587890625001], + [-164.03374023437493, 66.21552734374995], + [-163.69536132812502, 66.08383789062503], + [-161.93369140625003, 66.04287109374997], + [-161.45541992187503, 66.28139648437497], + [-161.03427734375003, 66.18881835937503], + [-161.12031249999995, 66.334326171875], + [-161.91689453124997, 66.41181640624998], + [-162.54365234375004, 66.80512695312501], + [-162.36162109375, 66.94731445312502], + [-161.591015625, 66.45952148437502], + [-160.23168945312503, 66.420263671875], + [-160.360888671875, 66.6125], + [-160.864013671875, 66.67084960937501], + [-161.39804687499998, 66.55185546875], + [-161.85668945312497, 66.70034179687497], + [-161.719921875, 67.02055664062502], + [-163.5318359375, 67.10258789062502], + [-164.1251953125, 67.60673828125007], + [-166.786279296875, 68.35961914062497], + [-166.38051757812502, 68.425146484375], + [-166.20908203125, 68.88535156250003], + [-165.04394531249994, 68.882470703125], + [-163.867919921875, 69.03666992187505], + [-161.88095703125003, 70.33173828125001], + [-162.073876953125, 70.16196289062498], + [-160.9962890625, 70.30458984375], + [-160.11713867187495, 70.59121093750002], + [-159.86567382812498, 70.27885742187499], + [-159.81499023437496, 70.49707031250003], + [-159.38676757812493, 70.52451171875003], + [-160.081591796875, 70.63486328125003], + [-159.680908203125, 70.786767578125], + [-159.31450195312496, 70.87851562500003], + [-159.251171875, 70.7484375], + [-157.909375, 70.860107421875], + [-156.47021484374994, 71.40766601562501], + [-156.469970703125, 71.29155273437507], + [-155.57944335937503, 71.12109374999997], + [-156.14658203125, 70.92783203125003], + [-155.97353515625002, 70.84199218749995], + [-155.16684570312498, 71.09921875000006], + [-154.19521484375002, 70.80112304687498], + [-153.23291015625, 70.93256835937504], + [-152.49121093749994, 70.88095703125], + [-152.23291015625, 70.81035156249999], + [-152.39921875, 70.62045898437503], + [-151.76904296875, 70.56015625], + [-151.94467773437498, 70.45209960937501], + [-149.26943359374997, 70.50078124999999], + [-147.70537109375, 70.21723632812495], + [-145.82314453124997, 70.16005859375002], + [-145.19736328125003, 70.00869140625002], + [-143.218310546875, 70.11625976562499], + [-142.70786132812498, 70.03378906249998], + [-141.40791015625, 69.65336914062502], + [-141.00214843750004, 69.65078125000002], + [-141.00214843750004, 68.77416992187506] + ] + ] + ] + }, + "properties": { "name": "United States", "childNum": 76 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [71.20615234375006, 39.892578125], + [71.15625, 39.88344726562502], + [71.06425781250002, 39.88491210937505], + [71.01171874999997, 39.895117187500006], + [71.04365234375004, 39.97631835937503], + [71.04482421875005, 39.992529296875034], + [70.96064453125004, 40.087988281250034], + [71.00546875, 40.15229492187498], + [71.0241210937501, 40.14916992187497], + [71.08037109375007, 40.07988281249999], + [71.2287109375001, 40.04814453124999], + [71.20615234375006, 39.892578125] + ] + ], + [ + [ + [70.94677734374997, 42.24868164062505], + [70.97900390625003, 42.26655273437504], + [71.03603515625, 42.28466796875], + [71.12998046875006, 42.25], + [71.21269531250002, 42.20644531250005], + [71.23232421875005, 42.18627929687503], + [71.22851562499997, 42.16289062499996], + [70.18095703125007, 41.571435546874994], + [70.734375, 41.400537109374994], + [70.86044921875006, 41.22490234375002], + [71.11074218750005, 41.152636718750045], + [71.29882812500003, 41.152490234374994], + [71.39306640625003, 41.123388671875034], + [71.40839843750004, 41.13603515625002], + [71.42089843750003, 41.341894531250034], + [71.60625, 41.367431640625], + [71.66494140625, 41.54121093749998], + [71.70068359374997, 41.454003906249966], + [71.75771484375005, 41.42802734375002], + [71.79248046875003, 41.41313476562499], + [71.85800781250006, 41.311376953125034], + [71.8786132812501, 41.195019531249955], + [71.95849609375003, 41.18706054687502], + [72.05244140625004, 41.16474609375001], + [72.1154296875001, 41.18657226562502], + [72.1642578125001, 41.173730468749966], + [72.18095703125002, 41.11845703124999], + [72.18730468750002, 41.02592773437499], + [72.2130859375001, 41.014257812500006], + [72.36406250000002, 41.04345703125], + [72.65830078125, 40.86992187499999], + [73.13212890625002, 40.82851562499999], + [72.6041015625, 40.52543945312499], + [72.40205078125004, 40.578076171874955], + [72.3892578125, 40.427392578124994], + [72.13125, 40.438623046874966], + [71.69248046875, 40.15234375], + [71.30468749999997, 40.28691406249996], + [70.990625, 40.2548828125], + [70.95800781250003, 40.238867187500034], + [70.653125, 40.201171875], + [70.37158203125003, 40.38413085937506], + [70.75107421875006, 40.721777343750006], + [70.40195312500006, 41.03510742187498], + [69.71289062500003, 40.65698242187503], + [69.35722656250002, 40.76738281249996], + [69.20625, 40.566552734374994], + [69.27490234374997, 40.19809570312498], + [68.63066406250007, 40.16708984374998], + [68.9720703125, 40.08994140624998], + [68.80468750000003, 40.05034179687499], + [68.86875, 39.90747070312503], + [68.63896484375007, 39.8388671875], + [68.46328125, 39.53671874999998], + [67.71904296875007, 39.62138671875002], + [67.426171875, 39.46557617187497], + [67.3576171875001, 39.216699218749994], + [67.64833984375005, 39.13105468750004], + [67.69443359375006, 38.99462890625003], + [68.13251953125004, 38.927636718749966], + [68.08720703125002, 38.47353515625002], + [68.3502929687501, 38.211035156250006], + [67.81435546875005, 37.48701171875004], + [67.7980468750001, 37.244970703125006], + [67.75898437500004, 37.172216796875034], + [67.75292968749997, 37.199804687500034], + [67.7, 37.227246093749955], + [67.60742187499997, 37.22250976562506], + [67.5172851562501, 37.26665039062499], + [67.44169921875007, 37.25800781250001], + [67.3197265625, 37.209570312500006], + [67.1955078125001, 37.23520507812498], + [67.06884765624997, 37.334814453125006], + [66.82773437500006, 37.37128906249998], + [66.52226562500007, 37.34848632812506], + [66.51064453125, 37.45869140625004], + [66.51132812500006, 37.59916992187496], + [66.52558593750004, 37.785742187500034], + [66.60625, 37.98671875000005], + [65.97119140624997, 38.244238281250006], + [65.612890625, 38.23857421875002], + [64.3099609375, 38.97729492187497], + [63.76367187500003, 39.16054687499999], + [62.48320312500002, 39.97563476562496], + [61.90283203124997, 41.09370117187501], + [61.496972656249994, 41.276074218749955], + [61.2423828125001, 41.18920898437503], + [60.454980468749994, 41.221630859374955], + [60.089648437500074, 41.39941406250003], + [60.07558593750005, 41.759667968749966], + [60.20078125000006, 41.803125], + [59.94179687499999, 41.97353515625002], + [59.98515625000002, 42.21171875], + [59.35429687500002, 42.32329101562496], + [58.5890625000001, 42.778466796874966], + [58.477148437500006, 42.66284179687503], + [58.15156250000004, 42.628076171874966], + [58.474414062500074, 42.29936523437496], + [58.02890625, 42.48764648437506], + [57.814257812500074, 42.18984375000005], + [57.290625, 42.123779296875], + [56.96406250000004, 41.856542968750006], + [57.11884765625004, 41.35029296874998], + [57.01796875, 41.26347656249996], + [55.97744140625005, 41.32221679687504], + [55.97568359375006, 44.99492187499996], + [58.555273437500006, 45.55537109375001], + [61.007910156250006, 44.39379882812497], + [61.99023437500003, 43.492138671874955], + [63.20703125000003, 43.62797851562502], + [64.44316406250007, 43.55117187499999], + [64.9054687500001, 43.714697265625006], + [65.49619140625, 43.310546875], + [65.80302734375002, 42.87695312500006], + [66.1002929687501, 42.99082031249998], + [66.00957031250007, 42.00488281250003], + [66.49863281250006, 41.99487304687503], + [66.70966796875004, 41.17915039062501], + [67.9357421875001, 41.19658203125002], + [68.11308593750007, 41.02861328124999], + [68.04765625000007, 40.80927734374998], + [68.29189453125, 40.656103515625034], + [68.57265625, 40.62265624999998], + [68.58408203125, 40.876269531250045], + [69.15361328125002, 41.42524414062498], + [70.94677734374997, 42.24868164062505] + ] + ] + ] + }, + "properties": { "name": "Uzbekistan", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-61.17451171875001, 13.158105468749966], + [-61.268457031249966, 13.287695312499991], + [-61.13896484374996, 13.358740234374991], + [-61.17451171875001, 13.158105468749966] + ] + ] + }, + "properties": { "name": "St. Vin. and Gren.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-60.99790039062498, 8.867333984374966], + [-61.069189453125034, 8.947314453125003], + [-60.91582031249996, 9.070312500000014], + [-60.86142578124998, 8.949609375000037], + [-60.99790039062498, 8.867333984374966] + ] + ], + [ + [ + [-60.821191406249966, 9.138378906250026], + [-60.94140625000003, 9.105566406250006], + [-60.73583984374997, 9.203320312500026], + [-60.821191406249966, 9.138378906250026] + ] + ], + [ + [ + [-63.84936523437494, 11.131005859374994], + [-63.917626953124994, 10.887548828125048], + [-64.40234375, 10.981591796875023], + [-64.21367187500002, 11.086132812499997], + [-64.0283203125, 11.00185546874998], + [-63.84936523437494, 11.131005859374994] + ] + ], + [ + [ + [-60.742138671874926, 5.202050781250037], + [-60.71196289062499, 5.191552734375023], + [-60.671972656250034, 5.164355468749989], + [-60.603857421875006, 4.94936523437498], + [-61.00283203125002, 4.535253906249991], + [-61.28007812500002, 4.516894531249974], + [-61.82084960937496, 4.197021484375], + [-62.153125, 4.098388671874986], + [-62.41064453124994, 4.156738281249972], + [-62.71210937499998, 4.01791992187502], + [-62.85698242187502, 3.593457031249969], + [-63.33867187500002, 3.943896484375045], + [-64.02148437500003, 3.929101562500051], + [-64.19248046874995, 4.126855468750009], + [-64.57636718750001, 4.139892578125], + [-64.788671875, 4.276025390625023], + [-64.66899414062496, 4.01181640625002], + [-64.22109375000002, 3.587402343749972], + [-64.04658203124998, 2.502392578124997], + [-63.389257812500006, 2.411914062500045], + [-63.43251953124994, 2.155566406250045], + [-64.00849609374995, 1.931591796874969], + [-64.20502929687493, 1.52949218750004], + [-65.10375976562497, 1.108105468749983], + [-65.47338867187497, 0.691259765624977], + [-65.55605468750002, 0.687988281250014], + [-65.52299804687493, 0.843408203124966], + [-65.68144531249999, 0.983447265624989], + [-66.06005859375003, 0.78535156250004], + [-66.34711914062498, 0.7671875], + [-66.87602539062499, 1.223046875000037], + [-67.21083984375, 2.390136718750043], + [-67.61870117187496, 2.793603515624994], + [-67.85908203124998, 2.793603515624994], + [-67.3111328125, 3.41586914062502], + [-67.66162109375, 3.864257812499986], + [-67.85527343750002, 4.506884765624989], + [-67.82490234374995, 5.270458984375026], + [-67.47387695312503, 5.929980468750003], + [-67.48198242187499, 6.18027343750002], + [-67.85917968749999, 6.289892578124963], + [-68.47177734375, 6.156542968749974], + [-69.42714843749997, 6.123974609374997], + [-70.12919921874999, 6.95361328125], + [-70.73715820312503, 7.090039062499997], + [-71.12861328124993, 6.98671875], + [-72.00664062499993, 7.032617187500023], + [-72.20771484374995, 7.37026367187498], + [-72.47197265624996, 7.524267578124991], + [-72.39033203124995, 8.287060546874969], + [-72.66542968749994, 8.62758789062498], + [-72.79638671874997, 9.10898437499999], + [-73.05839843749999, 9.259570312500031], + [-73.36621093749997, 9.194140625000017], + [-73.00654296874998, 9.789160156250006], + [-72.86933593750001, 10.49125976562496], + [-72.690087890625, 10.835839843749994], + [-72.24848632812501, 11.196435546875009], + [-71.95810546875, 11.66640625], + [-71.31972656249997, 11.861914062500048], + [-71.95693359375002, 11.569921874999977], + [-71.835107421875, 11.190332031250009], + [-71.6416015625, 11.013525390625048], + [-71.73090820312498, 10.994677734375017], + [-71.59433593749995, 10.657373046875051], + [-72.11284179687499, 9.815576171874966], + [-71.61953124999994, 9.047949218749991], + [-71.24140625000001, 9.160449218750003], + [-71.08583984375002, 9.348242187499977], + [-71.05268554687501, 9.705810546874986], + [-71.49423828125, 10.533203124999972], + [-71.46953124999993, 10.964160156250017], + [-70.23251953124998, 11.372998046874997], + [-70.09711914062493, 11.519775390624972], + [-69.80478515624998, 11.47421875000002], + [-69.81733398437495, 11.672070312499997], + [-70.19257812499993, 11.62460937500002], + [-70.28652343749997, 11.886035156249989], + [-70.20278320312497, 12.098388671874986], + [-70.00395507812496, 12.177880859375023], + [-69.63159179687494, 11.479931640625026], + [-68.827978515625, 11.431738281249977], + [-68.39863281249995, 11.160986328124977], + [-68.29628906249997, 10.689355468749994], + [-68.13994140624999, 10.492724609374989], + [-66.24721679687497, 10.632226562499994], + [-65.85175781249995, 10.257763671874997], + [-65.12910156249998, 10.070068359375043], + [-64.85048828125, 10.098095703124969], + [-64.188330078125, 10.457812499999989], + [-63.73188476562501, 10.503417968750043], + [-64.24750976562498, 10.54257812500002], + [-64.298193359375, 10.635156249999966], + [-61.879492187500006, 10.741015625000031], + [-62.379980468750006, 10.546875], + [-62.91357421875, 10.531494140624986], + [-62.68583984374996, 10.289794921875043], + [-62.740576171875006, 10.056152343750043], + [-62.55034179687499, 10.200439453125043], + [-62.320410156250034, 9.783056640625006], + [-62.22114257812498, 9.882568359375028], + [-62.15336914062493, 9.821777343749986], + [-62.15532226562499, 9.979248046875014], + [-62.077099609374926, 9.97504882812504], + [-61.73593749999998, 9.631201171874977], + [-61.76591796874996, 9.813818359374963], + [-61.58886718749994, 9.894531249999986], + [-60.79248046874997, 9.360742187500037], + [-61.02314453124998, 9.15458984374996], + [-61.24726562499998, 8.600341796875014], + [-61.61870117187499, 8.59746093749996], + [-61.30400390624999, 8.410400390625043], + [-60.800976562499926, 8.592138671875034], + [-60.16748046875, 8.616992187500031], + [-60.01752929687501, 8.549316406250014], + [-59.83164062499998, 8.305957031250003], + [-59.84907226562498, 8.248681640624966], + [-59.96484375000003, 8.191601562499969], + [-59.99072265624997, 8.16201171874998], + [-60.032421874999926, 8.053564453125006], + [-60.51362304687501, 7.813183593749969], + [-60.71865234374994, 7.535937499999974], + [-60.606542968750006, 7.320849609375031], + [-60.63330078124997, 7.211083984374966], + [-60.58320312499998, 7.156201171874969], + [-60.523193359375, 7.143701171875009], + [-60.464941406250034, 7.166552734375045], + [-60.39238281249999, 7.164550781249986], + [-60.34506835937495, 7.15], + [-60.32548828124996, 7.133984374999983], + [-60.32207031249996, 7.092041015625043], + [-60.35209960937496, 7.002880859374997], + [-60.39501953125, 6.945361328125003], + [-60.717919921874966, 6.768310546875], + [-61.14560546874998, 6.694531249999983], + [-61.20361328124997, 6.588378906250028], + [-61.181591796874926, 6.513378906250026], + [-61.15102539062502, 6.446533203124986], + [-61.15229492187501, 6.385107421875006], + [-61.12871093749999, 6.214306640625026], + [-61.15947265624996, 6.174414062499977], + [-61.22495117187498, 6.129199218750003], + [-61.303125, 6.049511718750026], + [-61.39082031250001, 5.938769531250017], + [-61.376806640625006, 5.906982421875028], + [-61.167187499999926, 5.674218750000037], + [-60.95400390625002, 5.437402343750023], + [-60.742138671874926, 5.202050781250037] + ] + ] + ] + }, + "properties": { "name": "Venezuela", "childNum": 4 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [-64.765625, 17.794335937499994], + [-64.58046874999994, 17.750195312499983], + [-64.88911132812495, 17.701708984375045], + [-64.765625, 17.794335937499994] + ] + ] + }, + "properties": { "name": "U.S. Virgin Is.", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [104.06396484375003, 10.390820312500011], + [104.01845703125, 10.029199218749966], + [103.84951171875005, 10.371093749999986], + [104.06396484375003, 10.390820312500011] + ] + ], + [ + [ + [107.52128906250007, 20.926611328124977], + [107.39921875000007, 20.903466796874966], + [107.55126953125003, 21.034033203125006], + [107.52128906250007, 20.926611328124977] + ] + ], + [ + [ + [107.60273437500004, 21.21679687500003], + [107.40351562500004, 21.093652343749994], + [107.47626953125004, 21.268945312499994], + [107.60273437500004, 21.21679687500003] + ] + ], + [ + [ + [107.97265624999997, 21.507958984375023], + [107.40996093750002, 21.284814453125023], + [107.35429687500007, 21.055175781250057], + [107.1647460937501, 20.94873046875003], + [106.68339843750007, 21.000292968750074], + [106.75341796875003, 20.73505859375004], + [106.55078124999997, 20.52656250000001], + [106.57285156250012, 20.392187499999977], + [105.98408203125004, 19.939062500000034], + [105.62177734375004, 18.96630859375003], + [105.88828125000006, 18.502490234375045], + [106.49902343749997, 17.946435546874994], + [106.47890625000005, 17.719580078125063], + [106.3705078125, 17.746875], + [107.83378906250002, 16.322460937499983], + [108.02939453125012, 16.331103515625074], + [108.82128906249997, 15.377929687500028], + [109.30332031250012, 13.856445312500043], + [109.271875, 13.279345703124974], + [109.42392578125006, 12.955957031249994], + [109.44492187500006, 12.599609375000057], + [109.33554687500012, 12.751904296874997], + [109.21894531250004, 12.64580078124996], + [109.30468750000003, 12.391162109375045], + [109.20683593750007, 12.415380859375006], + [109.1986328125, 11.724853515625014], + [109.03964843750012, 11.592675781249994], + [108.98671875, 11.336376953124997], + [108.09492187500004, 10.897265624999989], + [108.0013671875, 10.720361328125009], + [107.26152343750007, 10.39838867187504], + [107.00664062500002, 10.66054687499998], + [106.94746093750004, 10.400341796874997], + [106.72734375000007, 10.535644531250028], + [106.605859375, 10.46494140625002], + [106.74121093750003, 10.444384765625003], + [106.75742187500006, 10.295800781250023], + [106.46406250000004, 10.298291015624997], + [106.78525390625012, 10.116455078124986], + [106.59560546875005, 9.859863281250028], + [106.1364257812501, 10.221679687500014], + [106.56435546875005, 9.715625], + [106.48408203125004, 9.559423828125006], + [105.83095703125005, 10.000732421875028], + [106.15859375, 9.59414062499998], + [106.16835937500005, 9.396728515625], + [105.50097656249997, 9.093212890624983], + [105.11435546875006, 8.629199218750031], + [104.77041015625, 8.59765625], + [104.89628906250007, 8.746630859374974], + [104.81855468750004, 8.801855468750034], + [104.84521484375003, 9.606152343750026], + [105.08447265625003, 9.99570312499999], + [104.8019531250001, 10.202734374999977], + [104.66347656250005, 10.169921875000043], + [104.42636718750006, 10.411230468749991], + [104.85058593749997, 10.534472656249974], + [105.04638671874997, 10.701660156250014], + [105.04570312500002, 10.911376953125014], + [105.3146484375001, 10.845166015625026], + [105.40576171875003, 10.95161132812504], + [105.75507812500004, 10.989990234375043], + [105.85332031250007, 10.86357421874996], + [106.16396484375005, 10.794921875], + [106.16093750000002, 11.037109375000057], + [105.85605468750006, 11.294287109375048], + [105.85146484375005, 11.635009765625], + [106.0060546875001, 11.758007812500011], + [106.39921875000007, 11.687011718750028], + [106.41386718750002, 11.9484375], + [106.70009765625, 11.979296874999974], + [107.21210937500004, 12.30400390624996], + [107.39335937500002, 12.260498046874972], + [107.50644531250006, 12.364550781250031], + [107.47539062500002, 13.030371093749963], + [107.60546874999997, 13.437792968750017], + [107.3314453125, 14.126611328125009], + [107.51943359375005, 14.705078125], + [107.51376953125012, 14.817382812500057], + [107.52451171875012, 14.871826171875043], + [107.50468750000007, 14.915917968749966], + [107.48037109375, 14.979882812500037], + [107.55527343750006, 15.057031250000023], + [107.58964843750002, 15.118457031250017], + [107.63369140625005, 15.18984375], + [107.653125, 15.255224609374991], + [107.62167968750006, 15.309863281250017], + [107.56425781250002, 15.391601562499972], + [107.45957031250012, 15.4658203125], + [107.33876953125, 15.560498046875011], + [107.27939453125006, 15.618701171875045], + [107.16591796875005, 15.802490234375028], + [107.1888671875, 15.838623046875], + [107.36064453125002, 15.921728515624977], + [107.3919921875, 15.951660156250028], + [107.39638671875, 16.04301757812499], + [106.93066406249997, 16.353125], + [106.8927734375001, 16.396533203125074], + [106.85107421875003, 16.515625], + [106.83242187500005, 16.526269531250023], + [106.79160156250006, 16.490332031250006], + [106.73955078125007, 16.452539062500023], + [106.6564453125001, 16.49262695312501], + [106.54619140625002, 16.650732421874977], + [106.53369140625003, 16.821044921875057], + [106.52597656250006, 16.876611328124994], + [106.50224609375002, 16.95410156249997], + [106.26953125000003, 17.21679687500003], + [106.00625, 17.415283203125057], + [105.69140625000003, 17.737841796875045], + [105.58847656250012, 17.983691406250045], + [105.51855468749997, 18.077441406250045], + [105.45820312500004, 18.15429687499997], + [105.11455078125002, 18.40527343750003], + [105.08701171875006, 18.496240234374994], + [105.14541015625, 18.616796875000063], + [105.14648437500003, 18.650976562500006], + [103.89160156250003, 19.304980468750017], + [103.89638671875, 19.339990234375023], + [103.93203125, 19.366064453125006], + [104.0275390625001, 19.42045898437499], + [104.062890625, 19.48256835937505], + [104.05156250000007, 19.564160156249955], + [104.01347656250007, 19.64648437500003], + [104.03203125000002, 19.675146484375006], + [104.06279296875007, 19.678417968749983], + [104.25986328125006, 19.685498046874983], + [104.5462890625, 19.61054687500001], + [104.58789062500003, 19.61875], + [104.74316406250003, 19.754736328124977], + [104.80175781249997, 19.83613281250004], + [104.81513671875004, 19.90400390625001], + [104.9279296875001, 20.01811523437499], + [104.92919921875003, 20.082812500000017], + [104.88867187500003, 20.169091796875023], + [104.84785156250004, 20.202441406250045], + [104.69873046875003, 20.20532226562503], + [104.67695312500004, 20.224707031249977], + [104.66191406250007, 20.28901367187501], + [104.65644531250004, 20.32851562499999], + [104.6188476562501, 20.37451171875003], + [104.49619140625006, 20.413671875], + [104.39218750000012, 20.424755859374955], + [104.36777343750012, 20.44140624999997], + [104.40781250000012, 20.48574218750005], + [104.47861328125006, 20.529589843750017], + [104.53271484374997, 20.55488281250001], + [104.58320312500004, 20.646679687499955], + [104.34960937499997, 20.821093750000074], + [104.19531249999997, 20.913964843749966], + [104.10136718750002, 20.94550781250001], + [103.63505859375007, 20.697070312500017], + [103.46357421875004, 20.779833984375017], + [103.21074218750002, 20.840625], + [103.10449218749997, 20.891650390625045], + [102.88378906250003, 21.202587890624983], + [102.85117187500006, 21.26591796874999], + [102.94960937500005, 21.681347656249983], + [102.84521484374997, 21.73476562500005], + [102.81591796874997, 21.807373046875], + [102.7982421875, 21.797949218750034], + [102.77109375000006, 21.709667968749983], + [102.73857421875002, 21.67792968750001], + [102.66201171875005, 21.67602539062497], + [102.58251953125003, 21.90429687500003], + [102.12744140624997, 22.379199218750045], + [102.1759765625001, 22.414648437500006], + [102.2370117187501, 22.466015624999983], + [102.40644531250004, 22.70800781249997], + [102.47089843750004, 22.75092773437501], + [102.98193359374997, 22.4482421875], + [103.32666015625003, 22.769775390625057], + [103.49296875000007, 22.587988281250034], + [103.62021484375006, 22.782031250000045], + [103.94150390625006, 22.540087890625045], + [104.14306640624997, 22.800146484375006], + [104.37177734375004, 22.704052734374983], + [104.68730468750002, 22.822216796874983], + [104.86474609375003, 23.136376953125023], + [105.27539062500003, 23.34521484375003], + [105.8429687500001, 22.922802734374955], + [106.14843749999997, 22.970068359375006], + [106.2790039062501, 22.857470703125045], + [106.54179687500007, 22.908349609375023], + [106.78027343749997, 22.778906250000034], + [106.55039062500006, 22.501367187499994], + [106.66357421875003, 21.97890625000005], + [106.97099609375002, 21.923925781250034], + [107.35117187500012, 21.60888671874997], + [107.75927734374997, 21.655029296875057], + [107.97265624999997, 21.507958984375023] + ] + ] + ] + }, + "properties": { "name": "Vietnam", "childNum": 4 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [169.49130859375006, -19.54013671875002], + [169.34726562500006, -19.623535156249957], + [169.2174804687501, -19.476367187500003], + [169.24746093750005, -19.3447265625], + [169.49130859375006, -19.54013671875002] + ] + ], + [ + [ + [169.334375, -18.940234375000017], + [168.98691406250006, -18.87128906250001], + [169.01582031250004, -18.64375], + [169.14384765625002, -18.63105468750001], + [169.334375, -18.940234375000017] + ] + ], + [ + [ + [168.44580078124997, -17.54218750000004], + [168.58496093750003, -17.695898437500006], + [168.52460937500004, -17.798046875000026], + [168.15820312500003, -17.710546874999963], + [168.2731445312501, -17.552246093749957], + [168.44580078124997, -17.54218750000004] + ] + ], + [ + [ + [168.44677734375003, -16.778808593749957], + [168.18144531250002, -16.804003906250017], + [168.13535156250006, -16.636914062499997], + [168.44677734375003, -16.778808593749957] + ] + ], + [ + [ + [168.29667968750007, -16.33652343749999], + [167.92900390625002, -16.22871093749997], + [168.16386718750002, -16.081640625000034], + [168.29667968750007, -16.33652343749999] + ] + ], + [ + [ + [167.4125, -16.095898437499997], + [167.83662109375004, -16.449707031249957], + [167.44931640625012, -16.554980468750003], + [167.34921875000006, -16.15449218750004], + [167.15146484375006, -16.080468749999966], + [167.19951171875002, -15.885058593750031], + [167.33574218750007, -15.916699218749997], + [167.4125, -16.095898437499997] + ] + ], + [ + [ + [167.9113281250001, -15.435937500000023], + [167.67421875, -15.4515625], + [168.00253906250012, -15.283203124999986], + [167.9113281250001, -15.435937500000023] + ] + ], + [ + [ + [166.74580078125004, -14.826855468750011], + [166.81015625000012, -15.15742187500004], + [167.0755859375, -14.935644531249977], + [167.20078125000012, -15.443066406249969], + [167.0939453125001, -15.580859374999974], + [166.75830078125003, -15.631152343750003], + [166.63105468750004, -15.406054687499974], + [166.56738281250003, -14.641796874999969], + [166.74580078125004, -14.826855468750011] + ] + ], + [ + [ + [167.58486328125, -14.260937500000011], + [167.43027343750012, -14.294921875], + [167.41074218750006, -14.19746093750004], + [167.50644531250012, -14.142187499999977], + [167.58486328125, -14.260937500000011] + ] + ], + [ + [ + [167.48886718750006, -13.907226562499972], + [167.3917968750001, -13.788378906250017], + [167.48105468750006, -13.709472656250014], + [167.48886718750006, -13.907226562499972] + ] + ] + ] + }, + "properties": { "name": "Vanuatu", "childNum": 10 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [-171.4541015625, -14.04648437500002], + [-171.9119140625, -14.001660156250026], + [-172.04589843750003, -13.857128906249983], + [-171.60390624999997, -13.879199218750045], + [-171.4541015625, -14.04648437500002] + ] + ], + [ + [ + [-172.33349609375, -13.46523437499999], + [-172.17685546874998, -13.68466796875002], + [-172.224951171875, -13.804296874999963], + [-172.535693359375, -13.791699218749983], + [-172.77851562499998, -13.516796875000011], + [-172.33349609375, -13.46523437499999] + ] + ] + ] + }, + "properties": { "name": "Samoa", "childNum": 2 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [53.76318359374997, 12.636816406249991], + [54.18740234375005, 12.664013671875026], + [54.511132812499994, 12.552783203125017], + [54.12949218750006, 12.360644531250045], + [53.71884765625006, 12.318994140624994], + [53.31582031250005, 12.533154296875011], + [53.53496093750002, 12.715771484374997], + [53.76318359374997, 12.636816406249991] + ] + ], + [ + [ + [42.75585937500003, 13.70429687500004], + [42.689746093750074, 13.673632812500017], + [42.7941406250001, 13.766113281250028], + [42.75585937500003, 13.70429687500004] + ] + ], + [ + [ + [42.787402343750074, 13.971484375000031], + [42.69404296875004, 14.007910156249991], + [42.76210937500005, 14.067480468750048], + [42.787402343750074, 13.971484375000031] + ] + ], + [ + [ + [53.08564453125004, 16.648388671874955], + [52.327734375, 16.293554687500063], + [52.17402343750004, 15.956835937500017], + [52.2174804687501, 15.655517578125], + [51.3224609375001, 15.22626953125004], + [49.34990234375002, 14.637792968749977], + [48.66835937499999, 14.050146484374977], + [47.9899414062501, 14.048095703125], + [47.40771484374997, 13.661621093750057], + [46.78886718750002, 13.465576171874986], + [45.65732421875006, 13.338720703124991], + [45.03867187500006, 12.815869140624969], + [44.617773437500006, 12.817236328124977], + [44.00585937499997, 12.607666015625], + [43.634375, 12.744482421874991], + [43.487597656250074, 12.69882812500002], + [43.23193359375003, 13.267089843750057], + [43.2824218750001, 13.692529296875037], + [43.08906250000004, 14.010986328125], + [42.93642578125005, 14.938574218749963], + [42.85566406250004, 15.132958984375037], + [42.65781250000006, 15.232812500000051], + [42.79902343750004, 15.326269531249991], + [42.71718750000005, 15.654638671875006], + [42.83964843750002, 16.032031250000074], + [42.79931640624997, 16.37177734375001], + [43.16503906249997, 16.689404296874955], + [43.19091796875003, 17.359375], + [43.41796875000003, 17.516259765625023], + [43.91699218749997, 17.32470703124997], + [45.14804687500006, 17.427441406249955], + [45.5353515625001, 17.30205078124999], + [46.72763671875006, 17.26557617187501], + [46.97568359375006, 16.953466796875034], + [47.14355468749997, 16.946679687499966], + [47.44179687499999, 17.111865234375045], + [47.57958984374997, 17.448339843750034], + [48.17216796875002, 18.156933593749983], + [49.04199218750003, 18.58178710937503], + [51.977636718750006, 18.996142578125074], + [53.08564453125004, 16.648388671874955] + ] + ] + ] + }, + "properties": { "name": "Yemen", "childNum": 4 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [37.85693359375003, -46.94423828124998], + [37.5900390625001, -46.90800781250006], + [37.78955078124997, -46.8375], + [37.85693359375003, -46.94423828124998] + ] + ], + [ + [ + [31.799609375000017, -23.8921875], + [31.98583984374997, -24.460644531249983], + [31.921679687500017, -25.96875], + [31.335156250000097, -25.755566406249997], + [31.207324218750074, -25.843359375000034], + [31.08808593750004, -25.980664062500026], + [30.803320312500006, -26.41347656250001], + [30.806738281250006, -26.78525390624999], + [30.88330078124997, -26.792382812500023], + [30.938085937500006, -26.91582031250003], + [31.06337890625005, -27.1123046875], + [31.274023437500063, -27.23837890625002], + [31.469531250000017, -27.29550781250002], + [31.74257812500005, -27.309960937500037], + [31.95839843750005, -27.305859375], + [31.946093750000017, -27.173632812499974], + [31.96718750000005, -26.96064453125001], + [31.994726562500006, -26.817480468749977], + [32.024804687499994, -26.81113281250002], + [32.112890625, -26.83945312500002], + [32.19960937499999, -26.833496093749957], + [32.35351562499997, -26.861621093750003], + [32.7765625000001, -26.850976562499966], + [32.88613281250005, -26.849316406249983], + [32.53476562500006, -28.19970703125003], + [32.285742187500006, -28.62148437499998], + [31.335156250000097, -29.378125], + [29.97119140625003, -31.322070312500017], + [28.449414062500068, -32.62460937499999], + [27.077441406250074, -33.52119140625004], + [26.429492187500045, -33.75957031250002], + [25.80585937500001, -33.737109374999974], + [25.574218750000057, -34.03535156249998], + [25.00292968750003, -33.97363281250003], + [24.8271484375, -34.16894531250003], + [24.595507812500074, -34.17451171875], + [23.697851562500063, -33.99277343750002], + [23.268164062500006, -34.08115234374999], + [22.553808593750063, -34.01005859374999], + [22.24550781250005, -34.06914062500003], + [21.788964843750023, -34.37265624999996], + [20.529882812500034, -34.4630859375], + [20.020605468750006, -34.785742187500006], + [19.298242187500023, -34.61503906249996], + [19.330761718750068, -34.49238281250001], + [19.098339843750068, -34.350097656249986], + [18.831347656250017, -34.36406249999999], + [18.75214843750004, -34.08261718750002], + [18.50039062499999, -34.10927734375004], + [18.46162109375001, -34.346875], + [18.35205078124997, -34.1884765625], + [18.43300781250005, -33.71728515625003], + [17.851074218750057, -32.82744140625002], + [17.96523437500005, -32.70859374999996], + [18.125, -32.74912109374996], + [18.325292968750034, -32.50498046874996], + [18.21083984375008, -31.74248046874996], + [17.34707031250005, -30.44482421875], + [16.95, -29.40341796875002], + [16.739453124999983, -29.009375], + [16.447558593750045, -28.61757812499998], + [16.755761718750023, -28.45214843750003], + [16.7875, -28.39472656249997], + [16.81015625, -28.264550781249994], + [16.841210937500023, -28.21894531250004], + [16.875292968750045, -28.12792968749997], + [16.93330078125004, -28.06962890624999], + [17.05625, -28.03105468750003], + [17.1884765625, -28.13251953125001], + [17.358691406250017, -28.269433593750023], + [17.44794921875001, -28.698144531249966], + [18.310839843750017, -28.88623046875], + [19.16171875, -28.93876953124996], + [19.245800781250068, -28.90166015625003], + [19.31269531250004, -28.733300781250023], + [19.539843750000017, -28.574609375000023], + [19.98046875, -28.45126953125002], + [19.98046875, -28.310351562500003], + [19.98046875, -24.77675781249998], + [20.430664062500057, -25.14707031250002], + [20.79316406250001, -25.915625], + [20.641406250000017, -26.7421875], + [20.739843749999977, -26.84882812499997], + [21.694726562500023, -26.840917968749963], + [21.738085937500045, -26.806835937500026], + [21.788281250000068, -26.710058593750034], + [22.01093750000004, -26.635839843750006], + [22.090917968749977, -26.580175781250034], + [22.217578125000045, -26.38886718749997], + [22.47089843750004, -26.219042968750003], + [22.548632812500074, -26.178417968749997], + [22.59765625000003, -26.13271484375001], + [22.878808593750023, -25.457910156250023], + [23.148730468750017, -25.288671875], + [23.389257812500006, -25.291406250000023], + [23.89375, -25.600878906250017], + [23.96953124999999, -25.62607421874999], + [24.192968750000034, -25.632910156249963], + [24.33056640625, -25.742871093749983], + [25.21337890625, -25.75625], + [25.518164062500006, -25.66279296875001], + [25.91210937499997, -24.747460937499966], + [26.031835937500034, -24.70244140625003], + [26.130859375000057, -24.671484375000034], + [26.39716796875004, -24.61357421874996], + [26.451757812500063, -24.582714843749983], + [26.835058593750063, -24.240820312499963], + [27.085546875000034, -23.577929687500003], + [27.7685546875, -23.14892578125], + [27.812597656250006, -23.108007812500006], + [28.210156249999983, -22.693652343749974], + [28.83984375000003, -22.480859374999966], + [28.94580078125003, -22.39511718749999], + [29.013476562500045, -22.27841796875002], + [29.129882812500057, -22.21328125], + [29.364843750000063, -22.19394531250005], + [29.37744140625003, -22.19277343749998], + [29.66308593749997, -22.146289062500017], + [29.90234375000003, -22.184179687500006], + [30.19042968750003, -22.291113281250034], + [30.460156250000097, -22.329003906250023], + [30.71162109375004, -22.297851562499986], + [31.07343750000004, -22.30781249999997], + [31.19726562499997, -22.344921874999983], + [31.287890625000074, -22.402050781249983], + [31.54560546875004, -23.48232421874998], + [31.799609375000017, -23.8921875] + ], + [ + [27.19355468750001, -29.94130859375001], + [27.364062500000017, -30.27919921875001], + [27.753125, -30.6], + [28.05683593750001, -30.63105468750001], + [28.128710937500017, -30.52509765625001], + [28.39208984375003, -30.14755859375002], + [28.646875, -30.1265625], + [29.09804687500005, -29.919042968750006], + [29.142187500000063, -29.70097656249999], + [29.293554687500006, -29.56689453125003], + [29.348828125000097, -29.441992187499977], + [29.38671874999997, -29.319726562500023], + [29.301367187500006, -29.08984375], + [28.625781250000017, -28.581738281250054], + [28.583398437499994, -28.59414062499999], + [28.471875, -28.615820312499977], + [28.23261718750004, -28.701269531249977], + [28.084375, -28.779980468750026], + [27.95986328125008, -28.87333984375003], + [27.73554687500004, -28.940039062500034], + [27.294531250000063, -29.519335937500017], + [27.056933593750074, -29.62558593749999], + [27.19355468750001, -29.94130859375001] + ] + ] + ] + }, + "properties": { "name": "South Africa", "childNum": 2 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [33.148046875, -9.603515625], + [33.25, -9.759570312500003], + [33.35097656250002, -9.862207031250009], + [33.33710937500001, -9.954003906250009], + [33.3115234375, -10.037988281250009], + [33.52890625, -10.234667968750003], + [33.53759765625, -10.3515625], + [33.5537109375, -10.391308593750011], + [33.66152343750002, -10.553125], + [33.29277343750002, -10.85234375], + [33.37978515625002, -11.157910156250011], + [33.26835937500002, -11.40390625], + [33.23271484375002, -11.417675781250011], + [33.22636718750002, -11.534863281250011], + [33.30390625000001, -11.690820312500009], + [33.25234375000002, -12.112597656250003], + [33.34013671875002, -12.308300781250011], + [33.512304687500006, -12.347753906250006], + [32.975195312500006, -12.701367187500011], + [32.96757812500002, -13.225], + [32.67041015625, -13.590429687500006], + [32.797460937500006, -13.6884765625], + [32.98125, -14.009375], + [33.148046875, -13.94091796875], + [33.201757812500006, -14.013378906250011], + [30.231835937500023, -14.990332031250006], + [30.39609375, -15.64306640625], + [29.4873046875, -15.69677734375], + [28.9130859375, -15.98779296875], + [28.760546875000017, -16.53212890625001], + [27.932226562500006, -16.89619140625001], + [27.020800781250017, -17.95839843750001], + [26.779882812500006, -18.04150390625], + [26.333398437500023, -17.929296875], + [25.995898437500017, -17.969824218750006], + [25.2587890625, -17.793554687500006], + [25.001757812500017, -17.56855468750001], + [24.73291015625, -17.51777343750001], + [24.27490234375, -17.481054687500006], + [23.380664062500017, -17.640625], + [22.193945312500006, -16.628125], + [21.979785156250017, -15.95556640625], + [21.979394531250023, -14.440527343750006], + [21.979296875000017, -14.11962890625], + [21.979101562500006, -13.798730468750009], + [21.978906250000023, -13.0009765625], + [22.209570312500006, -13.0009765625], + [23.843164062500023, -13.0009765625], + [23.962988281250006, -12.988476562500011], + [23.882421875, -12.799023437500011], + [23.886523437500017, -12.743261718750006], + [23.909375, -12.636132812500009], + [23.98388671875, -11.725], + [23.96650390625001, -10.871777343750011], + [24.36572265625, -11.1298828125], + [24.3779296875, -11.417089843750006], + [25.28876953125001, -11.21240234375], + [25.349414062500017, -11.623046875], + [26.025976562500006, -11.89013671875], + [26.824023437500017, -11.965234375], + [27.1591796875, -11.579199218750006], + [27.573828125, -12.22705078125], + [28.412890625000017, -12.51806640625], + [28.550878906250006, -12.836132812500011], + [28.730078125, -12.925488281250011], + [29.014257812500006, -13.368847656250011], + [29.20185546875001, -13.398339843750009], + [29.55419921875, -13.248925781250009], + [29.775195312500017, -13.438085937500006], + [29.79511718750001, -12.155468750000011], + [29.508203125000023, -12.228222656250011], + [29.48554687500001, -12.41845703125], + [29.064355468750023, -12.348828125000011], + [28.482519531250006, -11.812109375], + [28.383398437500006, -11.566699218750003], + [28.6455078125, -10.550195312500009], + [28.60419921875001, -9.678808593750006], + [28.400683593750017, -9.224804687500011], + [28.869531250000023, -8.785839843750011], + [28.89814453125001, -8.485449218750006], + [30.75117187500001, -8.193652343750003], + [30.830664062500006, -8.385546875], + [30.891992187500023, -8.473730468750006], + [30.968359375, -8.550976562500011], + [31.07636718750001, -8.611914062500006], + [31.3505859375, -8.60703125], + [31.44921875, -8.65390625], + [31.53486328125001, -8.71328125], + [31.55625, -8.80546875], + [31.673632812500017, -8.908789062500006], + [31.91865234375001, -8.9421875], + [31.921875, -9.019433593750009], + [31.94257812500001, -9.054003906250003], + [32.75664062500002, -9.322265625], + [32.919921875, -9.407421875000011], + [32.99599609375002, -9.622851562500003], + [33.148046875, -9.603515625] + ] + ] + }, + "properties": { "name": "Zambia", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [31.287890625000017, -22.40205078125001], + [31.07343750000001, -22.30781250000001], + [30.71162109375001, -22.2978515625], + [30.46015625000001, -22.32900390625001], + [30.1904296875, -22.291113281250006], + [29.90234375, -22.184179687500006], + [29.6630859375, -22.146289062500003], + [29.37744140625, -22.19277343750001], + [29.36484375, -22.193945312500006], + [29.315234375000017, -22.15771484375], + [29.237207031250023, -22.07949218750001], + [29.042382812500023, -22.018359375], + [29.02558593750001, -21.796875], + [28.014062500000023, -21.55419921875], + [27.66943359375, -21.064257812500003], + [27.679296875, -20.503027343750006], + [27.28076171875, -20.47871093750001], + [27.17822265625, -20.10097656250001], + [26.168066406250006, -19.53828125000001], + [25.939355468750023, -18.93867187500001], + [25.242285156250006, -17.969042968750003], + [25.2587890625, -17.793554687500006], + [25.995898437500017, -17.969824218750006], + [26.333398437500023, -17.929296875], + [26.779882812500006, -18.04150390625], + [27.020800781250017, -17.95839843750001], + [27.932226562500006, -16.89619140625001], + [28.760546875000017, -16.53212890625001], + [28.9130859375, -15.98779296875], + [29.4873046875, -15.69677734375], + [30.39609375, -15.64306640625], + [30.437792968750017, -15.995312500000011], + [31.236230468750023, -16.02363281250001], + [31.939843750000023, -16.428808593750006], + [32.94804687500002, -16.71230468750001], + [32.87626953125002, -16.88359375], + [32.99306640625002, -18.35957031250001], + [32.69970703125, -18.94091796875], + [32.84980468750001, -19.10439453125001], + [32.77763671875002, -19.388769531250006], + [32.992773437500006, -19.98486328125], + [32.49238281250001, -20.659765625], + [32.353613281250006, -21.136523437500003], + [32.429785156250006, -21.29707031250001], + [31.429492187500017, -22.298828125], + [31.287890625000017, -22.40205078125001] + ] + ] + }, + "properties": { "name": "Zimbabwe", "childNum": 1 } + }, + { + "geometry": { + "type": "Polygon", + "coordinates": [ + [ + [74.00809389139292, 33.25375789331485], + [73.19660141888893, 33.898124784580936], + [73.13410859949555, 34.82510160558277], + [72.31128647748268, 35.77290936638241], + [73.08203125000107, 36.43949943991182], + [73.08961802927895, 36.86435907947333], + [73.116796875, 36.868554687499994], + [74.03886718750002, 36.825732421874996], + [74.54140625000002, 37.02216796875], + [74.69218750000002, 37.0357421875], + [74.8892578125, 36.952441406249996], + [74.94912109375002, 36.968359375], + [75.05390625000001, 36.987158203125], + [75.14521484375001, 36.9732421875], + [75.3466796875, 36.913476562499994], + [75.37685546875002, 36.883691406249994], + [75.42421875000002, 36.738232421875], + [75.46025390625002, 36.725048828125], + [75.57373046875, 36.759326171874996], + [75.66718750000001, 36.741992187499996], + [75.77216796875001, 36.694921875], + [75.84023437500002, 36.649707031249996], + [75.88496093750001, 36.600732421874994], + [75.93300781250002, 36.52158203125], + [75.95185546875001, 36.45810546875], + [75.97441406250002, 36.382421875], + [75.91230468750001, 36.048974609374994], + [76.07089843750003, 35.9830078125], + [76.14785156250002, 35.829003906249994], + [76.17783203125003, 35.810546875], + [76.25166015625001, 35.8109375], + [76.3857421875, 35.837158203125], + [76.50205078125003, 35.878222656249996], + [76.55126953125, 35.887060546875], + [76.5634765625, 35.772998046874996], + [76.6318359375, 35.729394531249994], + [76.7275390625, 35.678662109375], + [76.76689453124999, 35.66171875], + [76.81279296874999, 35.571826171874996], + [76.88222656250002, 35.4357421875], + [76.927734375, 35.346630859375], + [77.04863281249999, 35.109912109374996], + [77.00087890625002, 34.991992187499996], + [76.78291015625001, 34.900195312499996], + [76.75751953125001, 34.877832031249994], + [76.7490234375, 34.847558593749994], + [76.6962890625, 34.786914062499996], + [76.59443359375001, 34.73583984375], + [76.45673828125001, 34.756103515625], + [76.17246093750003, 34.667724609375], + [76.041015625, 34.669921875], + [75.93828125000002, 34.612548828125], + [75.86210937500002, 34.56025390625], + [75.70917968750001, 34.503076171874994], + [74.300390625, 34.765380859375], + [74.17197265625, 34.7208984375], + [74.05585937500001, 34.6806640625], + [73.96123046875002, 34.653466796874994], + [73.79453125, 34.378222656249996], + [73.80996093750002, 34.325341796874994], + [73.92460937500002, 34.287841796875], + [73.97236328125001, 34.236621093749996], + [73.9794921875, 34.191308593749994], + [73.90390625, 34.1080078125], + [73.94990234375001, 34.018798828125], + [74.24648437500002, 33.990185546875], + [73.97646484375002, 33.7212890625], + [74.15, 33.506982421874994], + [74.00809389139292, 33.25375789331485] + ] + ] + }, + "properties": { "name": "", "childNum": 1 } + }, + { + "geometry": { + "type": "MultiPolygon", + "coordinates": [ + [ + [ + [78.49194250885338, 32.53122786149202], + [78.10154031239509, 32.87658365066666], + [77.71342088235082, 32.6917648744551], + [77.06655516561037, 33.301666835953235], + [76.62299010270264, 33.32014871357439], + [76.32728006076415, 32.87658365066666], + [75.73585997688717, 32.78417426256088], + [75.62496871116024, 32.28516356678968], + [75.32221348233018, 32.28516356678968], + [74.98730468749997, 32.46220703124996], + [74.78886718750013, 32.4578125], + [74.6857421875001, 32.493798828124994], + [74.66328125000004, 32.75766601562495], + [74.63242187499995, 32.770898437500136], + [74.58828125000011, 32.7532226562501], + [74.35458984375012, 32.76870117187505], + [74.30546875000007, 32.81044921875002], + [74.30361328125005, 32.991796875000034], + [73.98984375000006, 33.22119140625006], + [74.15, 33.506982421874994], + [73.97646484375016, 33.72128906249998], + [74.24648437500011, 33.99018554687504], + [73.9499023437501, 34.018798828125], + [73.90390625000012, 34.10800781250006], + [73.97949218750009, 34.191308593749966], + [73.97236328125004, 34.23662109374996], + [73.92460937500007, 34.287841796875114], + [73.80996093750016, 34.32534179687511], + [73.79453125000006, 34.378222656250045], + [73.96123046875007, 34.653466796874994], + [74.05585937500015, 34.68066406250003], + [74.17197265624995, 34.72089843750004], + [74.30039062500006, 34.76538085937506], + [75.70917968750004, 34.50307617187508], + [75.86210937500002, 34.56025390625001], + [75.93828125000019, 34.612548828125], + [76.04101562500014, 34.66992187499997], + [76.17246093750006, 34.66772460937506], + [76.4567382812501, 34.756103515625114], + [76.5944335937501, 34.73583984375006], + [76.69628906249997, 34.78691406249999], + [76.74902343750014, 34.84755859375008], + [76.7575195312501, 34.87783203125005], + [76.7829101562501, 34.90019531249999], + [77.00087890625011, 34.99199218750002], + [77.03066406250011, 35.06235351562498], + [77.04863281250007, 35.109912109375074], + [77.42343749999995, 35.30258789062506], + [77.57158203125002, 35.37875976562495], + [77.69697265625015, 35.443261718750136], + [77.79941406250006, 35.49589843750002], + [78.0426757812501, 35.4797851562501], + [78.07578125000006, 35.13491210937502], + [78.15849609375002, 34.94648437499998], + [78.32695312500007, 34.60639648437498], + [78.86484375000006, 34.39033203125001], + [78.93642578125, 34.35195312500002], + [78.97060546875011, 34.22822265625004], + [78.72666015625006, 34.013378906249955], + [78.78378906250006, 33.80878906250004], + [78.86503906250002, 33.43110351562501], + [78.94843750000004, 33.346533203125006], + [79.1125, 33.22626953125001], + [79.13515625000005, 33.17192382812496], + [79.10283203125007, 33.05253906249996], + [79.14550781250003, 33.00146484375006], + [79.16992187500003, 32.497216796874994], + [78.91894531249997, 32.3582031250001], + [78.75351562500012, 32.49926757812506], + [78.73671875, 32.55839843750002], + [78.49194250885338, 32.53122786149202] + ] + ] + ] + }, + "properties": { "name": "", "childNum": 1 } + } + ] +} diff --git a/core/cmd/server/web/web.go b/core/cmd/server/web/web.go new file mode 100644 index 0000000..d8bed44 --- /dev/null +++ b/core/cmd/server/web/web.go @@ -0,0 +1,15 @@ +package web + +import "embed" + +//go:embed index.html +var IndexHtml embed.FS + +//go:embed assets/* +var Assets embed.FS + +//go:embed favicon.png +var Favicon embed.FS + +//go:embed static/* +var Static embed.FS diff --git a/core/constant/alert.go b/core/constant/alert.go new file mode 100644 index 0000000..1ca1d9e --- /dev/null +++ b/core/constant/alert.go @@ -0,0 +1,10 @@ +package constant + +const ( + WeChat = "wechat" + SMS = "sms" + Email = "mail" + WeCom = "weCom" + DingTalk = "dingTalk" + FeiShu = "feiShu" +) diff --git a/core/constant/common.go b/core/constant/common.go new file mode 100644 index 0000000..a44fdb0 --- /dev/null +++ b/core/constant/common.go @@ -0,0 +1,196 @@ +package constant + +import "sync/atomic" + +type DBContext string + +const ( + TimeOut5s = 5 + TimeOut20s = 20 + TimeOut5m = 300 + + DateLayout = "2006-01-02" // or use time.DateOnly while go version >= 1.20 + DefaultDate = "1970-01-01" + DateTimeLayout = "2006-01-02 15:04:05" // or use time.DateTime while go version >= 1.20 + DateTimeSlimLayout = "20060102150405" + + OrderDesc = "descending" + OrderAsc = "ascending" + + // backup + S3 = "S3" + OSS = "OSS" + Sftp = "SFTP" + OneDrive = "OneDrive" + MinIo = "MINIO" + Cos = "COS" + Kodo = "KODO" + WebDAV = "WebDAV" + Local = "LOCAL" + UPYUN = "UPYUN" + ALIYUN = "ALIYUN" + GoogleDrive = "GoogleDrive" + + OneDriveRedirectURI = "http://localhost/login/authorized" +) + +const ( + DirPerm = 0755 + FilePerm = 0644 +) + +const ( + SyncSystemProxy = "SyncSystemProxy" + SyncScripts = "SyncScripts" + SyncBackupAccounts = "SyncBackupAccounts" + SyncAlertSetting = "SyncAlertSetting" + SyncCustomApp = "SyncCustomApp" + SyncLanguage = "SyncLanguage" + SyncSystemProxyWithRestartDocker = "SyncSystemProxyWithRestartDocker" +) + +var WebUrlMap = map[string]struct{}{ + "/apps": {}, + "/apps/all": {}, + "/apps/installed": {}, + "/apps/upgrade": {}, + "/apps/setting": {}, + + "/ai": {}, + "/ai/model": {}, + "/ai/gpu": {}, + "/ai/gpu/current": {}, + "/ai/gpu/history": {}, + "/ai/mcp": {}, + "/ai/model/tensorrt": {}, + "/ai/model/ollama": {}, + + "/containers": {}, + "/containers/container/operate": {}, + "/containers/container": {}, + "/containers/image": {}, + "/containers/network": {}, + "/containers/volume": {}, + "/containers/repo": {}, + "/containers/compose": {}, + "/containers/template": {}, + "/containers/setting": {}, + "/containers/dashboard": {}, + + "/cronjobs": {}, + "/cronjobs/cronjob": {}, + "/cronjobs/library": {}, + "/cronjobs/cronjob/operate": {}, + + "/databases": {}, + "/databases/mysql": {}, + "/databases/mysql/remote": {}, + "/databases/postgresql": {}, + "/databases/postgresql/remote": {}, + "/databases/redis": {}, + "/databases/redis/remote": {}, + + "/hosts": {}, + "/hosts/files": {}, + "/hosts/monitor/monitor": {}, + "/hosts/monitor/setting": {}, + "/hosts/firewall/port": {}, + "/hosts/firewall/forward": {}, + "/hosts/firewall/ip": {}, + "/hosts/firewall/advance": {}, + "/hosts/process/process": {}, + "/hosts/process/network": {}, + "/hosts/ssh/ssh": {}, + "/hosts/ssh/log": {}, + "/hosts/ssh/session": {}, + "/hosts/disk": {}, + + "/terminal": {}, + + "/logs": {}, + "/logs/operation": {}, + "/logs/login": {}, + "/logs/website": {}, + "/logs/system": {}, + "/logs/ssh": {}, + "/logs/task": {}, + + "/settings": {}, + "/settings/panel": {}, + "/settings/backupaccount": {}, + "/settings/license": {}, + "/settings/about": {}, + "/settings/safe": {}, + "/settings/alert": {}, + "/settings/snapshot": {}, + "/settings/expired": {}, + + "/toolbox": {}, + "/toolbox/device": {}, + "/toolbox/supervisor": {}, + "/toolbox/clam": {}, + "/toolbox/clam/setting": {}, + "/toolbox/ftp": {}, + "/toolbox/fail2ban": {}, + "/toolbox/clean": {}, + + "/websites": {}, + "/websites/ssl": {}, + "/websites/runtimes/php": {}, + "/websites/runtimes/node": {}, + "/websites/runtimes/java": {}, + "/websites/runtimes/go": {}, + "/websites/runtimes/python": {}, + "/websites/runtimes/dotnet": {}, + + "/login": {}, + + "/xpack": {}, + "/xpack/waf/dashboard": {}, + "/xpack/waf/global": {}, + "/xpack/waf/websites": {}, + "/xpack/waf/log": {}, + "/xpack/waf/block": {}, + "/xpack/waf/blackwhite": {}, + "/xpack/waf/stat": {}, + + "/xpack/monitor/dashboard": {}, + "/xpack/monitor/setting": {}, + "/xpack/monitor/rank": {}, + "/xpack/monitor/log": {}, + "/xpack/monitor/trend": {}, + "/xpack/monitor/websites": {}, + + "/xpack/tamper": {}, + "/xpack/gpu": {}, + "/xpack/alert/dashboard": {}, + "/xpack/alert/log": {}, + "/xpack/alert/setting": {}, + "/xpack/setting": {}, + "/xpack/node/dashboard": {}, + "/xpack/node": {}, + "/xpack/simple-node": {}, + "/xpack/exchange/file": {}, + "/xpack/app": {}, + "/xpack/app-upgrade": {}, + + "/xpack/cluster/mysql": {}, + "/xpack/cluster/postgres": {}, + "/xpack/cluster/redis": {}, +} + +var DynamicRoutes = []string{ + `^/containers/composeDetail/[^/]+$`, + `^/databases/mysql/setting/[^/]+/[^/]+$`, + `^/databases/postgresql/setting/[^/]+/[^/]+$`, + `^/websites/[^/]+/config/[^/]+$`, +} + +var CertStore atomic.Value + +var DaemonJsonPath = "/etc/docker/daemon.json" + +const ( + RoleMaster = "master" + RoleSlave = "slave" +) diff --git a/core/constant/session.go b/core/constant/session.go new file mode 100644 index 0000000..36de02f --- /dev/null +++ b/core/constant/session.go @@ -0,0 +1,8 @@ +package constant + +const ( + AuthMethodSession = "session" + SessionName = "psession" + + PasswordExpiredName = "expired" +) diff --git a/core/constant/status.go b/core/constant/status.go new file mode 100644 index 0000000..b5ccc21 --- /dev/null +++ b/core/constant/status.go @@ -0,0 +1,30 @@ +package constant + +const ( + StatusSuccess = "Success" + StatusFailed = "Failed" + + // node + StatusWaiting = "Waiting" + StatusHealthy = "Healthy" + StatusStarting = "Starting" + StatusUnhealthy = "Unhealthy" + StatusWaitForUpgrade = "WaitForUpgrade" + StatusUpgrading = "Upgrading" + StatusRunning = "Running" + StatusFree = "Free" + StatusBound = "Bound" + StatusExceptional = "Exceptional" + StatusRetrying = "Retrying" + StatusLost = "Lost" + StatusExecuting = "Executing" + + StatusEnable = "Enable" + StatusDisable = "Disable" + StatusMux = "Mux" + + StatusInstalling = "Installing" + StatusNormal = "Normal" + StatusDeleted = "Deleted" + StatusLoading = "Loading" +) diff --git a/core/global/config.go b/core/global/config.go new file mode 100644 index 0000000..6ed4564 --- /dev/null +++ b/core/global/config.go @@ -0,0 +1,51 @@ +package global + +type ServerConfig struct { + Base Base `mapstructure:"base"` + Conn Conn `mapstructure:"conn"` + RemoteURL RemoteURL `mapstructure:"remote_url"` + LogConfig LogConfig `mapstructure:"log"` +} + +type Base struct { + Mode string `mapstructure:"mode"` + Username string `mapstructure:"username"` + Password string `mapstructure:"password"` + Language string `mapstructure:"language"` + IsDemo bool `mapstructure:"is_demo"` + IsIntl bool `mapstructure:"is_intl"` + IsOffLine bool `mapstructure:"is_offline"` + IsFxplay bool `mapstructure:"is_fxplay"` + Version string `mapstructure:"version"` + InstallDir string `mapstructure:"install_dir"` + ChangeUserInfo string `mapstructure:"change_user_info"` + EncryptKey string `mapstructure:"encrypt_key"` +} + +type Conn struct { + Port string `mapstructure:"port"` + BindAddress string `mapstructure:"bindAddress"` + Ipv6 string `mapstructure:"ipv6"` + SSL string `mapstructure:"ssl"` + Entrance string `mapstructure:"entrance"` +} + +type ApiInterface struct { + ApiKey string `mapstructure:"api_key"` + ApiInterfaceStatus string `mapstructure:"api_interface_status"` + IpWhiteList string `mapstructure:"ip_white_list"` + ApiKeyValidityTime string `mapstructure:"api_key_validity_time"` +} + +type RemoteURL struct { + RepoUrl string `mapstructure:"repo_url"` + ResourceURL string `mapstructure:"resource_url"` +} + +type LogConfig struct { + Level string `mapstructure:"level"` + TimeZone string `mapstructure:"timeZone"` + LogName string `mapstructure:"log_name"` + LogSuffix string `mapstructure:"log_suffix"` + MaxBackup int `mapstructure:"max_backup"` +} diff --git a/core/global/global.go b/core/global/global.go new file mode 100644 index 0000000..afb5e97 --- /dev/null +++ b/core/global/global.go @@ -0,0 +1,36 @@ +package global + +import ( + "github.com/1Panel-dev/1Panel/core/init/auth" + "github.com/1Panel-dev/1Panel/core/init/session/psession" + "github.com/go-playground/validator/v10" + "github.com/nicksnyder/go-i18n/v2/i18n" + "github.com/robfig/cron/v3" + "github.com/sirupsen/logrus" + "github.com/spf13/viper" + "gorm.io/gorm" +) + +var ( + DB *gorm.DB + AlertDB *gorm.DB + TaskDB *gorm.DB + AgentDB *gorm.DB + LOG *logrus.Logger + CONF ServerConfig + Api ApiInterface + VALID *validator.Validate + SESSION *psession.PSession + Viper *viper.Viper + + I18n *i18n.Localizer + I18nForCmd *i18n.Localizer + + Cron *cron.Cron + + ScriptSyncJobID cron.EntryID + + IPTracker *auth.IPTracker +) + +type DBOption func(*gorm.DB) *gorm.DB diff --git a/core/go.mod b/core/go.mod new file mode 100644 index 0000000..27b8271 --- /dev/null +++ b/core/go.mod @@ -0,0 +1,113 @@ +module github.com/1Panel-dev/1Panel/core + +go 1.24.9 + +require ( + github.com/1panel-dev/base64Captcha v1.3.8 + github.com/creack/pty v1.1.21 + github.com/fsnotify/fsnotify v1.7.0 + github.com/gin-contrib/gzip v1.0.1 + github.com/gin-gonic/gin v1.10.0 + github.com/glebarez/sqlite v1.11.0 + github.com/go-gormigrate/gormigrate/v2 v2.1.2 + github.com/go-playground/validator/v10 v10.22.0 + github.com/go-resty/resty/v2 v2.15.3 + github.com/go-webauthn/webauthn v0.15.0 + github.com/google/uuid v1.6.0 + github.com/gorilla/securecookie v1.1.2 + github.com/gorilla/sessions v1.4.0 + github.com/gorilla/websocket v1.5.3 + github.com/jinzhu/copier v0.4.0 + github.com/nicksnyder/go-i18n/v2 v2.4.0 + github.com/oschwald/maxminddb-golang v1.13.1 + github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/pkg/errors v0.9.1 + github.com/pkg/sftp v1.13.6 + github.com/robfig/cron/v3 v3.0.1 + github.com/sirupsen/logrus v1.9.3 + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e + github.com/soheilhy/cmux v0.1.5 + github.com/spf13/cobra v1.8.1 + github.com/spf13/viper v1.19.0 + github.com/swaggo/files/v2 v2.0.2 + github.com/swaggo/swag v1.16.3 + github.com/wader/gormstore/v2 v2.0.3 + github.com/xlzd/gotp v0.1.0 + golang.org/x/crypto v0.45.0 + golang.org/x/net v0.47.0 + golang.org/x/sys v0.38.0 + golang.org/x/term v0.37.0 + golang.org/x/text v0.31.0 + gopkg.in/yaml.v2 v2.4.0 + gopkg.in/yaml.v3 v3.0.1 + gorm.io/gorm v1.30.0 +) + +require ( + github.com/KyleBanks/depth v1.2.1 // indirect + github.com/PuerkitoBio/purell v1.1.1 // indirect + github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 // indirect + github.com/bytedance/sonic v1.11.6 // indirect + github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/cloudwego/base64x v0.1.4 // indirect + github.com/cloudwego/iasm v0.2.0 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.3 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/glebarez/go-sqlite v1.22.0 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.19.6 // indirect + github.com/go-openapi/spec v0.20.4 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect + github.com/go-webauthn/x v0.1.26 // indirect + github.com/goccy/go-json v0.10.3 // indirect + github.com/golang-jwt/jwt/v5 v5.3.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/google/go-tpm v0.9.6 // indirect + github.com/hashicorp/hcl v1.0.0 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.5 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/cpuid/v2 v2.2.8 // indirect + github.com/kr/fs v0.1.0 // indirect + github.com/leodido/go-urn v1.4.0 // indirect + github.com/magiconair/properties v1.8.7 // indirect + github.com/mailru/easyjson v0.7.6 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/pelletier/go-toml/v2 v2.2.2 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + github.com/sagikazarmark/locafero v0.4.0 // indirect + github.com/sagikazarmark/slog-shim v0.1.0 // indirect + github.com/sourcegraph/conc v0.3.0 // indirect + github.com/spf13/afero v1.11.0 // indirect + github.com/spf13/cast v1.6.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/subosito/gotenv v1.6.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.12 // indirect + github.com/x448/float16 v0.8.4 // indirect + go.uber.org/atomic v1.9.0 // indirect + go.uber.org/multierr v1.9.0 // indirect + golang.org/x/arch v0.8.0 // indirect + golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect + golang.org/x/image v0.28.0 // indirect + golang.org/x/tools v0.38.0 // indirect + google.golang.org/protobuf v1.34.1 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/ini.v1 v1.67.0 // indirect + gorm.io/driver/sqlite v1.4.4 // indirect + modernc.org/libc v1.66.1 // indirect + modernc.org/mathutil v1.7.1 // indirect + modernc.org/memory v1.11.0 // indirect + modernc.org/sqlite v1.38.0 // indirect +) diff --git a/core/go.sum b/core/go.sum new file mode 100644 index 0000000..9b5a9a1 --- /dev/null +++ b/core/go.sum @@ -0,0 +1,492 @@ +github.com/1panel-dev/base64Captcha v1.3.8 h1:GbQ2IuGMp4ai4erpwf4BMjm5eLC8Efb+dATVwgpPIII= +github.com/1panel-dev/base64Captcha v1.3.8/go.mod h1:gVpwyGm9+g4rg3pXdYnsFAouP73qMOSBfnT3bxCFzco= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/toml v1.3.2 h1:o7IhLm0Msx3BaB+n3Ag7L8EVlByGnpq14C4YWiu/gL8= +github.com/BurntSushi/toml v1.3.2/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/KyleBanks/depth v1.2.1 h1:5h8fQADFrWtarTdtDudMmGsC7GPbOAu6RVB3ffsVFHc= +github.com/KyleBanks/depth v1.2.1/go.mod h1:jzSb9d0L43HxTQfT+oSA1EEp2q+ne2uh6XgeJcm8brE= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/PuerkitoBio/purell v1.1.1 h1:WEQqlqaGbrPkxLJWfBwQmfEAE1Z7ONdDLqrN38tNFfI= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578 h1:d+Bc7a5rLufV/sSk/8dngufqelfh6jnri85riMAaF/M= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/bytedance/sonic v1.11.6 h1:oUp34TzMlL+OY1OUWxHqsdkgC/Zfc85zGqw9siXjrc0= +github.com/bytedance/sonic v1.11.6/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= +github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= +github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= +github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= +github.com/cloudwego/iasm v0.2.0/go.mod h1:8rXZaNYT2n95jn+zTI1sDr+IgcD2GVs0nlbbQPiEFhY= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA= +github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= +github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gin-contrib/gzip v1.0.1 h1:HQ8ENHODeLY7a4g1Au/46Z92bdGFl74OhxcZble9WJE= +github.com/gin-contrib/gzip v1.0.1/go.mod h1:njt428fdUNRvjuJf16tZMYZ2Yl+WQB53X5wmhDwXvC4= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.10.0 h1:nTuyha1TYqgedzytsKYqna+DfLos46nTv2ygFy86HFU= +github.com/gin-gonic/gin v1.10.0/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= +github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ= +github.com/glebarez/go-sqlite v1.22.0/go.mod h1:PlBIdHe0+aUEFn+r2/uthrWq4FxbzugL0L8Li6yQJbc= +github.com/glebarez/sqlite v1.11.0 h1:wSG0irqzP6VurnMEpFGer5Li19RpIRi2qvQz++w0GMw= +github.com/glebarez/sqlite v1.11.0/go.mod h1:h8/o8j5wiAsqSPoWELDUdJXhjAhsVliSn7bWZjOhrgQ= +github.com/go-gormigrate/gormigrate/v2 v2.1.2 h1:F/d1hpHbRAvKezziV2CC5KUE82cVe9zTgHSBoOOZ4CY= +github.com/go-gormigrate/gormigrate/v2 v2.1.2/go.mod h1:9nHVX6z3FCMCQPA7PThGcA55t22yKQfK/Dnsf5i7hUo= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6 h1:UBIxjkht+AWIgYzCDSv2GN+E/togfwXUJFRTWhl2Jjs= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/spec v0.20.4 h1:O8hJrt0UMnhHcluhIdUgCLRWyM2x7QkBXRvOs7m+O1M= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.22.0 h1:k6HsTZ0sTnROkhS//R0O+55JgM8C4Bx7ia+JlgcnOao= +github.com/go-playground/validator/v10 v10.22.0/go.mod h1:dbuPbCMFw/DrkbEynArYaCwl3amGuJotoKCe95atGMM= +github.com/go-resty/resty/v2 v2.15.3 h1:bqff+hcqAflpiF591hhJzNdkRsFhlB96CYfBwSFvql8= +github.com/go-resty/resty/v2 v2.15.3/go.mod h1:0fHAoK7JoBy/Ch36N8VFeMsK7xQOHhvWaC3iOktwmIU= +github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-webauthn/webauthn v0.15.0 h1:LR1vPv62E0/6+sTenX35QrCmpMCzLeVAcnXeH4MrbJY= +github.com/go-webauthn/webauthn v0.15.0/go.mod h1:hcAOhVChPRG7oqG7Xj6XKN1mb+8eXTGP/B7zBLzkX5A= +github.com/go-webauthn/x v0.1.26 h1:eNzreFKnwNLDFoywGh9FA8YOMebBWTUNlNSdolQRebs= +github.com/go-webauthn/x v0.1.26/go.mod h1:jmf/phPV6oIsF6hmdVre+ovHkxjDOmNH0t6fekWUxvg= +github.com/goccy/go-json v0.10.3 h1:KZ5WoDbxAIgm2HNbYckL0se1fHD6rz5j4ywS6ebzDqA= +github.com/goccy/go-json v0.10.3/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +github.com/google/go-tpm v0.9.6 h1:Ku42PT4LmjDu1H5C5ISWLlpI1mj+Zq7sPGKoRw2XROA= +github.com/google/go-tpm v0.9.6/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs= +github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA= +github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo= +github.com/gorilla/sessions v1.2.1/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ= +github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jackc/chunkreader v1.0.0 h1:4s39bBR8ByfqH+DKm8rQA3E1LHZWB9XWcrz8fqaZbe0= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.13.0 h1:3L1XMNV2Zvca/8BYhzcRFS70Lr0WlDg16Di6SFGAbys= +github.com/jackc/pgconn v1.13.0/go.mod h1:AnowpAqO4CMIIJNZl2VJp+KrkAZciAkhEl0W0JIobpI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0 h1:FYYE4yRw+AgI8wXIinMlNjBbp/UitDJwfj5LqqewP1A= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.3.1 h1:nwj7qwf0S+Q7ISFfBndqeLwSwxs+4DPsbRFjECT1Y4Y= +github.com/jackc/pgproto3/v2 v2.3.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.12.0 h1:Dlq8Qvcch7kiehm8wPGIW0W3KsCCHJnRacKW0UM8n5w= +github.com/jackc/pgtype v1.12.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.17.2 h1:0Ut0rpeKwvIVbMQ1KbMBU4h6wxehBI535LK6Flheh8E= +github.com/jackc/pgx/v4 v4.17.2/go.mod h1:lcxIZN44yMIrWI78a5CpucdD14hX0SBDbNRvjDBItsw= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.3.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/copier v0.4.0 h1:w3ciUoD19shMCRargcpm0cm91ytaBhDvuRpz1ODO/U8= +github.com/jinzhu/copier v0.4.0/go.mod h1:DfbEm0FYsaqBcKcFuvmOZb218JkPGtvSHsKg8S8hyyg= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.4/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= +github.com/jinzhu/now v1.1.5/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.8 h1:+StwCXwm9PdpiEkPyzBXIy+M9KUb4ODm0Zarf1kS5BM= +github.com/klauspost/cpuid/v2 v2.2.8/go.mod h1:Lcz8mBdAVJIBVzewtcLocK12l3Y+JytZYpaMropDUws= +github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/fs v0.1.0 h1:Jskdu9ieNAYnjxsi0LbQp1ulIKZV1LAFgK1tWhpZgl8= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ= +github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= +github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.15 h1:vfoHhTN1af61xCRSWzFIWzx2YskyMTwHLrExkBOjvxI= +github.com/mattn/go-sqlite3 v1.14.15/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= +github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/nicksnyder/go-i18n/v2 v2.4.0 h1:3IcvPOAvnCKwNm0TB0dLDTuawWEj+ax/RERNC+diLMM= +github.com/nicksnyder/go-i18n/v2 v2.4.0/go.mod h1:nxYSZE9M0bf3Y70gPQjN9ha7XNHX7gMc814+6wVyEI4= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/oschwald/maxminddb-golang v1.13.1 h1:G3wwjdN9JmIK2o/ermkHM+98oX5fS+k5MbwsmL4MRQE= +github.com/oschwald/maxminddb-golang v1.13.1/go.mod h1:K4pgV9N/GcK694KSTmVSDTODk4IsCNThNdTmnaBZ/F8= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM= +github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.6 h1:JFZT4XbOU7l77xGSpOdW+pwIMqP044IyjXX6FGyEKFo= +github.com/pkg/sftp v1.13.6/go.mod h1:tz1ryNURKu77RL+GuCzmoJYxQczL3wLNNpPWagdg4Qk= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= +github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sagikazarmark/locafero v0.4.0 h1:HApY1R9zGo4DBgr7dqsTH/JJxLTTsOt7u6keLGt6kNQ= +github.com/sagikazarmark/locafero v0.4.0/go.mod h1:Pe1W6UlPYUk/+wc/6KFhbORCfqzgYEpgQ3O5fPuL3H4= +github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE= +github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e h1:MRM5ITcdelLK2j1vwZ3Je0FKVCfqOLp5zO6trqMLYs0= +github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDqSt+GTGFMVlhk3ULuV0y9ZmzeVGR4mloJI3M= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= +github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= +github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8= +github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY= +github.com/spf13/cast v1.6.0 h1:GEiTHELF+vaR5dhz3VqZfFSzZjYbgeKDpBxQVS4GYJ0= +github.com/spf13/cast v1.6.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM= +github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI= +github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= +github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= +github.com/swaggo/files/v2 v2.0.2 h1:Bq4tgS/yxLB/3nwOMcul5oLEUKa877Ykgz3CJMVbQKU= +github.com/swaggo/files/v2 v2.0.2/go.mod h1:TVqetIzZsO9OhHX1Am9sRf9LdrFZqoK49N37KON/jr0= +github.com/swaggo/swag v1.16.3 h1:PnCYjPCah8FK4I26l2F/KQ4yz3sILcVUN3cTlBFA9Pg= +github.com/swaggo/swag v1.16.3/go.mod h1:DImHIuOFXKpMFAQjcC7FG4m3Dg4+QuUgUzJmKjI/gRk= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= +github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/wader/gormstore/v2 v2.0.3 h1:/29GWPauY8xZkpLnB8hsp+dZfP3ivA9fiDw1YVNTp6U= +github.com/wader/gormstore/v2 v2.0.3/go.mod h1:sr3N3a8F1+PBc3fHoKaphFqDXLRJ9Oe6Yow0HxKFbbg= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xlzd/gotp v0.1.0 h1:37blvlKCh38s+fkem+fFh7sMnceltoIEBYTVXyoa5Po= +github.com/xlzd/gotp v0.1.0/go.mod h1:ndLJ3JKzi3xLmUProq4LLxCuECL93dG9WASNLpHz8qg= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= +go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= +go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= +go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= +golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= +golang.org/x/crypto v0.45.0 h1:jMBrvKuj23MTlT0bQEOBcAE0mjg8mK9RXFhRH6nyF3Q= +golang.org/x/crypto v0.45.0/go.mod h1:XTGrrkGJve7CYK7J8PEww4aY7gM3qMCElcJQ8n8JdX4= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o= +golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8= +golang.org/x/image v0.28.0 h1:gdem5JW1OLS4FbkWgLO+7ZeFzYtL3xClb97GaUzYMFE= +golang.org/x/image v0.28.0/go.mod h1:GUJYXtnGKEUgggyzh+Vxt+AviiCcyiwpsl8iQ8MvwGY= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.29.0 h1:HV8lRxZC4l2cr3Zq1LvtOsi/ThTgWnUk/y64QSs8GwA= +golang.org/x/mod v0.29.0/go.mod h1:NyhrlYXJ2H4eJiRy/WDBO6HMqZQ6q9nk4JzS3NuCK+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= +golang.org/x/net v0.47.0 h1:Mx+4dIFzqraBXUugkia1OOvlD6LemFo1ALMHjrXDOhY= +golang.org/x/net v0.47.0/go.mod h1:/jNxtkgq5yWUGYkaZGqo27cfGZ1c5Nen03aYrrKpVRU= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.18.0 h1:kr88TuHDroi+UVf+0hZnirlk8o8T+4MrK6mr60WkH/I= +golang.org/x/sync v0.18.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc= +golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU= +golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM= +golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM= +golang.org/x/time v0.6.0 h1:eTDhh4ZXt5Qf0augr54TN6suAUudPcawVZeIAPU7D4U= +golang.org/x/time v0.6.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.38.0 h1:Hx2Xv8hISq8Lm16jvBZ2VQf+RLmbd7wVUsALibYI/IQ= +golang.org/x/tools v0.38.0/go.mod h1:yEsQ/d/YK8cjh0L6rZlY8tgtlKiBNTL14pGDJPJpYQs= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.34.1 h1:9ddQBjfCyZPOHPUiPxpYESBLc+T8P3E+Vo4IbKZgFWg= +google.golang.org/protobuf v1.34.1/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= +gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.4.0 h1:P+gpa0QGyNma39khn1vZMS/eXEJxTwHz4Q26NR4C8fw= +gorm.io/driver/mysql v1.4.0/go.mod h1:sSIebwZAVPiT+27jK9HIwvsqOGKx3YMPmrA3mBJR10c= +gorm.io/driver/postgres v1.4.1 h1:DutsKq2LK2Ag65q/+VygWth0/L4GAVOp+sCtg6WzZjs= +gorm.io/driver/postgres v1.4.1/go.mod h1:whNfh5WhhHs96honoLjBAMwJGYEuA3m1hvgUbNXhPCw= +gorm.io/driver/sqlite v1.4.1/go.mod h1:AKZZCAoFfOWHF7Nd685Iq8Uywc0i9sWJlzpoE/INzsw= +gorm.io/driver/sqlite v1.4.4 h1:gIufGoR0dQzjkyqDyYSCvsYR6fba1Gw5YKDqKeChxFc= +gorm.io/driver/sqlite v1.4.4/go.mod h1:0Aq3iPO+v9ZKbcdiz8gLWRw5VOPcBOPUQJFLq5e2ecI= +gorm.io/gorm v1.23.7/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.8/go.mod h1:l2lP/RyAtc1ynaTjFksBde/O8v9oOGIApu2/xRitmZk= +gorm.io/gorm v1.23.10/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.24.0/go.mod h1:DVrVomtaYTbqs7gB/x2uVvqnXzv0nqjB396B8cG4dBA= +gorm.io/gorm v1.30.0 h1:qbT5aPv1UH8gI99OsRlvDToLxW5zR7FzS9acZDOZcgs= +gorm.io/gorm v1.30.0/go.mod h1:8Z33v652h4//uMA76KjeDH8mJXPm1QNCYrMeatR0DOE= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +modernc.org/cc/v4 v4.26.1 h1:+X5NtzVBn0KgsBCBe+xkDC7twLb/jNVj9FPgiwSQO3s= +modernc.org/cc/v4 v4.26.1/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= +modernc.org/ccgo/v4 v4.28.0 h1:rjznn6WWehKq7dG4JtLRKxb52Ecv8OUGah8+Z/SfpNU= +modernc.org/ccgo/v4 v4.28.0/go.mod h1:JygV3+9AV6SmPhDasu4JgquwU81XAKLd3OKTUDNOiKE= +modernc.org/fileutil v1.3.3 h1:3qaU+7f7xxTUmvU1pJTZiDLAIoJVdUSSauJNHg9yXoA= +modernc.org/fileutil v1.3.3/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc= +modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI= +modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= +modernc.org/goabi0 v0.0.3 h1:y81b9r3asCh6Xtse6Nz85aYGB0cG3M3U6222yap1KWI= +modernc.org/goabi0 v0.0.3/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI= +modernc.org/libc v1.66.1 h1:4uQsntXbVyAgrV+j6NhKvDiUypoJL48BWQx6sy9y8ok= +modernc.org/libc v1.66.1/go.mod h1:AiZxInURfEJx516LqEaFcrC+X38rt9G7+8ojIXQKHbo= +modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= +modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= +modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI= +modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw= +modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= +modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= +modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= +modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE= +modernc.org/sqlite v1.38.0 h1:+4OrfPQ8pxHKuWG4md1JpR/EYAh3Md7TdejuuzE7EUI= +modernc.org/sqlite v1.38.0/go.mod h1:1Bj+yES4SVvBZ4cBOpVZ6QgesMCKpJZDq0nxYzOpmNE= +modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0= +modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A= +modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/core/i18n/i18n.go b/core/i18n/i18n.go new file mode 100644 index 0000000..b80f58e --- /dev/null +++ b/core/i18n/i18n.go @@ -0,0 +1,255 @@ +package i18n + +import ( + "embed" + "fmt" + "os/exec" + "strings" + "sync/atomic" + + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/gin-gonic/gin" + "github.com/nicksnyder/go-i18n/v2/i18n" + "golang.org/x/text/language" + "gopkg.in/yaml.v3" +) + +const defaultLang = "en" + +var langFiles = map[string]string{ + "zh": "lang/zh.yaml", + "en": "lang/en.yaml", + "zh-Hant": "lang/zh-Hant.yaml", + "pt-BR": "lang/pt-BR.yaml", + "ja": "lang/ja.yaml", + "ru": "lang/ru.yaml", + "ms": "lang/ms.yaml", + "ko": "lang/ko.yaml", + "tr": "lang/tr.yaml", + "es-ES": "lang/es-ES.yaml", +} + +func GetMsgWithMap(key string, maps map[string]interface{}) string { + var content string + if maps == nil { + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + } else { + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: maps, + }) + } + content = strings.ReplaceAll(content, ": ", "") + if content == "" { + return key + } else { + return content + } +} + +func GetMsgWithDetail(key string, detail string) string { + var ( + content string + dataMap = make(map[string]interface{}) + ) + dataMap["detail"] = detail + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: dataMap, + }) + if content != "" { + return content + } + return key +} + +func GetErrMsg(key string, maps map[string]interface{}) string { + var content string + if maps == nil { + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + } else { + content, _ = global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: maps, + }) + } + return content +} + +func GetMsgByKey(key string) string { + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + return content +} + +func Get(key string) string { + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + if content != "" { + return content + } + return key +} + +func GetWithName(key string, name string) string { + var ( + dataMap = make(map[string]interface{}) + ) + dataMap["name"] = name + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: dataMap, + }) + return content +} + +func GetWithNameAndErr(key string, name string, err error) string { + var ( + dataMap = make(map[string]interface{}) + ) + dataMap["name"] = name + dataMap["err"] = err.Error() + content, _ := global.I18n.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: dataMap, + }) + return content +} + +//go:embed lang/* +var fs embed.FS +var bundle *i18n.Bundle + +func UseI18n() gin.HandlerFunc { + return func(context *gin.Context) { + lang := context.GetHeader("Accept-Language") + if lang == "" { + lang = GetLanguage() + } + global.I18n = i18n.NewLocalizer(bundle, lang) + } +} + +func Init() { + if bundle == nil { + initBundle() + } + dbLang := getLanguageFromDBInternal() + if dbLang == "" { + dbLang = defaultLang + } + SetCachedDBLanguage(dbLang) + + global.I18n = i18n.NewLocalizer(bundle, dbLang) +} + +func UseI18nForCmd(lang string) { + if bundle == nil { + initBundle() + } + if lang == "" { + langFrom1pctl := getLanguageFrom1pctl() + if langFrom1pctl == "" { + lang = defaultLang + } else { + lang = langFrom1pctl + } + } + global.I18nForCmd = i18n.NewLocalizer(bundle, lang) +} + +func GetMsgByKeyForCmd(key string) string { + if global.I18nForCmd == nil { + UseI18nForCmd("") + } + content, _ := global.I18nForCmd.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + return content +} + +func GetMsgWithMapForCmd(key string, maps map[string]interface{}) string { + if global.I18nForCmd == nil { + UseI18nForCmd("") + } + var content string + if maps == nil { + content, _ = global.I18nForCmd.Localize(&i18n.LocalizeConfig{ + MessageID: key, + }) + } else { + content, _ = global.I18nForCmd.Localize(&i18n.LocalizeConfig{ + MessageID: key, + TemplateData: maps, + }) + } + content = strings.ReplaceAll(content, ": ", "") + if content == "" { + return key + } else { + return content + } +} + +func getLanguageFromDBInternal() string { + if global.DB == nil { + return defaultLang + } + lang, _ := repo.NewISettingRepo().GetValueByKey("Language") + if lang == "" { + return defaultLang + } + return lang +} +func getLanguageFrom1pctl() string { + cmd := exec.Command("bash", "-c", "grep '^LANGUAGE=' /usr/local/bin/1pctl | cut -d'=' -f2") + stdout, err := cmd.CombinedOutput() + if err != nil { + panic(err) + } + info := strings.ReplaceAll(string(stdout), "\n", "") + if len(info) == 0 || info == `""` { + panic("error `LANGUAGE` find in /usr/local/bin/1pctl") + } + return info +} + +var cachedDBLang atomic.Value + +func GetLanguage() string { + if v := cachedDBLang.Load(); v != nil { + return v.(string) + } + return defaultLang +} + +func SetCachedDBLanguage(lang string) { + if lang == "" { + lang = defaultLang + } + cachedDBLang.Store(lang) +} + +func initBundle() { + bundle = i18n.NewBundle(language.Chinese) + bundle.RegisterUnmarshalFunc("yaml", yaml.Unmarshal) + + isSuccess := true + for _, file := range langFiles { + if _, err := bundle.LoadMessageFileFS(fs, file); err != nil { + fmt.Printf("[i18n] load language file %s failed: %v\n", file, err) + isSuccess = false + } + } + + if !isSuccess { + panic("[i18n] failed to init language files, See log above for details") + } +} diff --git a/core/i18n/lang/en.yaml b/core/i18n/lang/en.yaml new file mode 100644 index 0000000..8a0ff0a --- /dev/null +++ b/core/i18n/lang/en.yaml @@ -0,0 +1,259 @@ +ErrInvalidParams: "Invalid request parameters: {{ .detail }}" +ErrTokenParse: "Token generation error: {{ .detail }}" +ErrInitialPassword: "Incorrect initial password" +ErrInternalServer: "Internal server error: {{ .detail }}" +ErrRecordExist: "Record already exists" +ErrRecordNotFound: "Record not found" +ErrStructTransform: "Type conversion failed: {{ .detail }}" +ErrNotLogin: "User not logged in: {{ .detail }}" +ErrPasswordExpired: "Current password has expired: {{ .detail }}" +ErrNotSupportType: "The current type is not supported: {{ .detail }}" +ErrProxy: "Request error, please check the status of this node: {{ .detail }}" +ErrApiConfigStatusInvalid: "API access is prohibited: {{ .detail }}" +ErrApiConfigKeyInvalid: "API key error: {{ .detail }}" +ErrApiConfigIPInvalid: "The API request IP is not on the whitelist: {{ .detail }}" +ErrApiConfigDisable: "This interface prohibits API calls: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "API timestamp error: {{ .detail }}" +ErrPasskeyDisabled: "Passkey requires HTTPS to be enabled" +ErrPasskeyNotConfigured: "No passkey configured" +ErrPasskeyLimit: "Passkey limit reached (max 5)" +ErrPasskeySession: "Passkey session expired or invalid" +ErrPasskeyDuplicate: "Passkey already exists" +ErrPasskeyVerify: "Passkey verification failed" + +# request +ErrNoSuchHost: "Unable to find the requested server {{ .err }}" +ErrHttpReqNotFound: "Unable to find the requested resource {{ .err }}" +ErrHttpReqFailed: "Request failed {{ .err }}" +ErrHttpReqTimeOut: "Request timed out {{ .err }}" +ErrCreateHttpClient: "Failed to create request {{ .err }}" +ErrProxySetting: "Proxy server information unavailable {{ .err }}, please check and try again!" +ErrEntranceFormat: "Security entrance {{ .name }} is not currently supported. Please check and try again!" + +# common +ErrDemoEnvironment: "Demo server, this operation is prohibited!" +ErrCmdTimeout: "Command execution timeout!" +ErrEntrance: "Security entrance information error, please check and try again!" +ErrGroupIsDefault: "Default group, unable to delete" +ErrGroupIsInUse: "The group is in use and cannot be deleted." +ErrLocalDelete: "Cannot delete the local node!" +ErrPortInUsed: "The {{ .name }} port is already in use!" +ErrInternalServerKey: "Internal server error:" +MasterNode: "Master Node" + +# app +CustomAppStoreFileValid: "Application store package requires .tar.gz format" +ErrFileNotFound: "{{ .name }} file does not exist" +AppBackup: 'Application backup' +AppBackupPush: 'Transfer application backup file {{.file}} to node {{ .name }}' +ErrSourceTargetSame: 'Source node and target node cannot be the same!' +AppInstall: 'Install application {{ .name }} on node {{ .targetNode }}' +AppInstallCheck: 'Check application installation environment' + +# backup +ErrBackupInUsed: "This backup account is used in scheduled tasks and cannot be deleted" +ErrBackupCheck: "Backup account connection test failed {{ .err }}" +ErrBackupLocal: "Local server backup account does not support this operation!" +ErrBackupPublic: "Detected that this backup account is not public, please check and try again!" +ErrOSSConn: "Unable to get the latest version, please check the server's external network connectivity." + +#license +LicenseCheck: 'Check if the license is available' +ErrLicenseInUsed: 'The license is already bound. Please check and try again!' +ErrLicenseExpired: 'The license has expired. Please check and try again!' +ErrLicense: "License format error, please check and retry!" +ErrLicenseCheck: "License validation failed, please check and retry!" +ErrXpackVersion: "License validation failed, this license is version-limited, cannot import, please check and retry!" +ErrLicenseSave: "Failed to save license information, error {{ .err }}, please retry!" +ErrLicenseSync: "License synchronization failed, no license info detected in database!" +ErrLicenseExist: "This license record already exists. You can directly go to the license page for node binding." +ErrXpackNotFound: "This section is for professional edition, please import the license in Panel Settings - License" +ErrXpackExceptional: "This section is for professional edition, please sync license status in Panel Settings - License" +ErrXpackLost: "License has reached the maximum retry count, please go to the [Panel Settings] [License] page and click the sync button manually to ensure the professional features work correctly" +ErrDeviceLost: "Required files for license verification are missing, please check and try again!" +ErrDeviceErr: "The current environment does not match the license import environment. Please edit the license and re-import!" +ErrXpackTimeout: "Request timeout, network connection might be unstable, please try again later!" +ErrUnbindMaster: "Detected nodes in node management, unable to unbind the current license, please remove nodes first and retry!" +ErrFreeNodeLimit: "Community version node limit reached, please go to www.lxware.cn/1panel to purchase and retry!" +ErrNodeBound: "This license is bound to another node, please check and retry!" +ErrNodeBoundDelete: "This license is bound and does not support delete operations. Please check and try again!" +ErrNodeBoundLimit: "The current free node has reached its limit, please check and try again!" +ErrLicenseFree: "Free nodes can only be used when the license is properly bound to a node. Please verify and try again!" +ErrLicenseUnbind: "Community Edition nodes detected for this license. Please unbind in [Panel Settings - License] and try again!" +ErrNoSuchNode: "Node information not found, please check and retry!" +ErrNodeUnbind: "This node is not within the license binding range, please check and retry!" +ErrNodeBind: "This node is already bound to a license, please check and retry!" +ErrNodeLocalRollback: "The primary node does not support direct rollback. Please manually execute the '1pctl restore' command to rollback!" + +InvalidRequestBodyType: "Invalid request body format, please check and ensure the content meets the required format before retrying!" +InvalidLicenseCodeType: "Invalid license code format provided, please check and try again!" +LicenseNotFoundType: "License not found, no matching record exists in the system for the provided license. Please check and try again!" +LicenseRevokedType: "The requested license has been revoked and is no longer usable. Please check and try again!" +LicenseExpiredType: "The license has expired. Please renew it or re-import the license in the Panel Settings - License section before retrying!" +LicenseProductMismatchType: "The license does not match the requested product or service!" +InvalidAssigneeType: "Invalid target user or device information for license assignment. Please check and try again!" +LicenseUsageNotFoundType: "No usage records found. This license has not been activated or used yet. Please check and try again!" +LicenseUsageLimitExceededType: "This license is already bound to another node. Please check and try again!" + +# alert +ErrAlertSync: "Alert information sync error, please check and retry!" + +# task +TaskStart: "{{ .name }} task started [START]" +TaskEnd: "{{ .name }} task ended [COMPLETED]" +TaskFailed: "{{ .name }} task failed" +TaskTimeout: "{{ .name }} timed out" +TaskSuccess: "{{ .name }} task succeeded" +TaskRetry: "Starting retry {{ .name }}" +SubTaskSuccess: "{{ .name }} succeeded" +SubTaskFailed: "{{ .name }} failed: {{ .err }}" +TaskInstall: "Install" +TaskUpgrade: "Upgrade" +TaskSync: 'Synchronize' +TaskSyncForNode: "Sync Node Data" +TaskBackup: "Backup" +SuccessStatus: "{{ .name }} succeeded" +FailedStatus: "{{ .name }} failed {{ .err }}" +Start: "Start" +SubTask: "Subtask" +Skip: "Skip errors and continue..." +PushAppInstallTaskToNode: "Push app installation task to node [{{ .name }}]" +TaskPush: "Push" +AppInstallTask: "App installation task" +PushAppFailed: "Failed to push app installation task" +Success: "Success" + +#script +ScriptLibrary: "Script Library" +RemoteScriptLibrary: "Remote Script Library" +ScriptSyncSkip: "Current script library is already the latest version!" +DownloadData: "Downloading script library file data.yaml" +DownloadPackage: "Downloading script library package" +AnalyticCompletion: "Analysis completed, now syncing to database..." + +Node: "Node" +SyncNode: "Sync data to node {{ .name }}" +LocalName: "'local' name is only used for system local identification" +SyncPackageData: "Package sync data" +SyncPackageEncrypt: "Data package encryption" +SyncRequest: "Request node sync API" +SyncFailedRetry: "Node data sync timeout (attempt {{ .index }}), retrying..." +SyncFailed: "Sync failed, please manually sync in the node list!" +SyncSystemProxy: "System Proxy Settings" +SyncScripts: "Script Library" +SyncBackupAccounts: "Backup Accounts" +SyncAlertSetting: "Alert Settings" +SyncCustomApp: "Custom App" +SyncLanguage: "System Language" + +#upgrade node +NodeUpgrade: "Upgrade node {{ .name }}" +UpgradeCheck: "Check for node updates" +UpgradeCheckLocal: "Local nodes do not support batch upgrades, skipping..." +UpgradeCheckLatest: "The node is already the latest version, skipping..." +NewSSHClient: "Initializing SSH connection" +BackupBeforeUpgrade: "Backup data before upgrade" +UploadUpgradeFile: "Distribute upgrade files" +RestartAfterUpgrade: "Start service after upgrade" + +#add node +MasterData: "Master Node Data" +LoadSftpClient: "Load SFTP Client" +PackageMasterData: "Generate master node backup package" +UploadBackup: "Upload backup data" +MvBackup: "Move data to backup directory" +TaskAddNode: "Add node" +LoadNodeArch: "Get node architecture info" +LoadNodeArchDetail: "Detected master node architecture: {{ .local }}, child node architecture: {{ .node }}" +LoadNodeUpgradeDetail: "Using the v1 version historical installation directory: {{ .baseDir }}, service listening port: {{ .port }}" +SyncAgentBaseInfo: "Sync basic node info" +GenerateSSLInfo: "Generate node SSL info" +ConnInfoNotMatch: "Connection info mismatch" +MakeAgentPackage: "Generate node installation package" +SendAgent: "Distribute node installation package" +StartService: "Start service" +NoBackupNode: "The backup node is currently empty. Please select a backup node to save and try again!" + +#cmd +AppVersion: "App version" +AppCommands: "App related commands" +AppInit: "Initialize app" +AppKeyVal: "App key (only supports English)" +AppCreateFileErr: "File {{ .name }} creation failed {{ .err }}" +AppCreateDirErr: "Folder {{ .name }} creation failed {{ .err }}" +AppMissKey: "App key missing, use -k to specify" +AppMissVersion: "App version missing, use -v to specify" +AppVersionExist: "Version already exists!" +AppCreateSuccessful: "Creation successful!" +AppWriteErr: "File {{ .name }} write failed {{ .err }}" +SudoHelper: "Please use {{ .cmd }} or switch to root user" +ListenIPCommands: "Switch listening ip" +ListenIPv4: "Listen on IPv4" +ListenIPv6: "Listen on IPv6" +ListenChangeSuccessful: "Switch successful! Now listening on {{ .value }}" +ResetCommands: "Reset system info" +ResetMFA: "Cancel 1Panel two-factor authentication" +ResetHttps: "Cancel 1Panel https login" +ResetEntrance: "Cancel 1Panel secure entrance" +ResetIPs: "Cancel 1Panel authorized ip restrictions" +ResetDomain: "Cancel 1Panel domain binding" +ResetPasskey: "Clear 1Panel passkeys" +RestoreCommands: "Rollback 1Panel service and data" +RestoreNoSuchFile: "No files available for rollback" +RestoreStep1: "(1/5) Starting rollback of 1Panel service and data from {{ .name }} directory..." +RestoreStep2: "(2/5) 1Panel binary rollback successful" +RestoreStep3: "(3/5) 1Panel script rollback successful" +RestoreStep4: "(4/5) 1Panel service rollback successful" +RestoreStep5: "(5/5) 1Panel data rollback successful" +RestoreSuccessful: "Rollback successful! Rolled back to {{ .version }}, restarting service, please wait..." +UpdateCommands: "Update panel info" +UpdateUser: "Update panel user" +UpdatePassword: "Update panel password" +UpdatePort: "Update panel port" +UpdateUserNull: "Error: panel user is empty!" +UpdateUserBlank: "Error: panel user contains spaces!" +UpdateUserFormat: "Error: Invalid panel user format! Only supports English, Chinese, numbers, and _, length 3-30" +UpdateUserErr: "Error: Failed to update panel user, {{ .err }}" +UpdateSuccessful: "Update successful!" +UpdateUserResult: "Panel user: {{ .name }}" +UpdatePasswordRead: "Error: Failed to read panel password information, {{ .err }}" +UpdatePasswordNull: "Error: Panel password is empty!" +UpdateUPasswordBlank: "Error: Panel password contains spaces!" +UpdatePasswordFormat: "Error: Panel password only supports letters, numbers, special characters !@#$%*_,.?, length 8-30!" +UpdatePasswordLen: "Error: Please enter a password longer than 6 characters!" +UpdatePasswordRe: "Confirm password:" +UpdatePasswordErr: "Error: Failed to update panel password, {{ .err }}" +UpdatePasswordSame: "Error: The two passwords do not match, please check and try again!" +UpdatePasswordResult: "Panel password: {{ .name }}" +UpdatePortFormat: "Error: The input port number must be between 1 and 65535!" +UpdatePortUsed: "Error: The port number is already in use, please check and try again!" +UpdatePortErr: "Error: Failed to update panel port, {{ .err }}" +UpdatePortResult: "Panel Port: {{ .name }}" +UpdatePortFirewallAdd: "Failed to add firewall port rule, {{ .err }}, please manually add the {{ .name }} port to the firewall rules." +UpdatePortFirewallDel: "Error: Failed to delete firewall port, {{ .err }}" +UpdatePortFirewallReload: "Failed to reload the firewall, {{ .err }}, please manually reload the firewall." +UserInfo: "Get panel information" +UserInfoAddr: "Panel address: " +UserInfoPassHelp: "Tip: To change the password, you can execute the command: " +DBConnErr: "Error: Failed to initialize database connection, {{ .err }}" +SystemVersion: "version: " +SystemMode: "mode: " + +#mobile app +ErrVerifyToken: 'Token verification error, please reset and scan again.' +ErrInvalidToken: 'Invalid token, please reset and scan again.' +ErrExpiredToken: 'Token has expired, please reset and scan again.' + +#cluster +ErrMasterDelete: "Unable to delete the master node, please delete the slave nodes first." +ClusterNameIsExist: "Cluster name already exists." +AppStatusUnHealthy: "Application status acquisition is abnormal, please check the installation node status in the node list." +MasterNodePortNotAvailable: "Node {{ .name }} port {{ .port }} connectivity verification failed, please check firewall/security group settings and master node status." +ClusterMasterNotExist: "The master node of the cluster is disconnected, please delete the child nodes." + +#ssl +ErrReqFailed: "{{.name}} request failed: {{ .err }}" + +#command +Name: "Name" +Command: "Command" diff --git a/core/i18n/lang/es-ES.yaml b/core/i18n/lang/es-ES.yaml new file mode 100644 index 0000000..767ce4b --- /dev/null +++ b/core/i18n/lang/es-ES.yaml @@ -0,0 +1,253 @@ +ErrInvalidParams: "Parámetros de solicitud no son válidos: {{ .detail }}" +ErrTokenParse: "Error al generar el token: {{ .detail }}" +ErrInitialPassword: "Contraseña inicial incorrecta" +ErrInternalServer: "Error interno del servidor: {{ .detail }}" +ErrRecordExist: "El registro ya existe" +ErrRecordNotFound: "Registro no encontrado" +ErrStructTransform: "Error en la conversión de tipo: {{ .detail }}" +ErrNotLogin: "Usuario no ha iniciado sesión: {{ .detail }}" +ErrPasswordExpired: "La contraseña actual ha expirado: {{ .detail }}" +ErrNotSupportType: "El tipo actual no es compatible: {{ .detail }}" +ErrProxy: "Error en la solicitud, por favor revise el estado de este nodo: {{ .detail }}" +ErrApiConfigStatusInvalid: "Acceso a la API prohibido: {{ .detail }}" +ErrApiConfigKeyInvalid: "Error de clave API: {{ .detail }}" +ErrApiConfigIPInvalid: "La IP de la solicitud API no está en la lista blanca: {{ .detail }}" +ErrApiConfigDisable: "Esta interfaz prohíbe llamadas a la API: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "Error de marca de tiempo de API: {{ .detail }}" +PushAppInstallTaskToNode: "Enviar tarea de instalación de aplicación al nodo [{{ .name }}]" +TaskPush: "Enviar" +AppInstallTask: "Tarea de instalación de aplicación" +PushAppFailed: "Error al enviar tarea de instalación de aplicación" +Success: "Éxito" + +# request +ErrNoSuchHost: "No se pudo encontrar el servidor solicitado {{ .err }}" +ErrHttpReqNotFound: "No se pudo encontrar el recurso solicitado {{ .err }}" +ErrHttpReqFailed: "Solicitud fallida {{ .err }}" +ErrHttpReqTimeOut: "La solicitud ha expirado {{ .err }}" +ErrCreateHttpClient: "Error al crear la solicitud {{ .err }}" +ErrProxySetting: "Información del servidor proxy no disponible {{ .err }}, ¡compruebe e inténtelo de nuevo!" +ErrEntranceFormat: "La entrada de seguridad {{ .name }} no está actualmente soportada. ¡Por favor verifique e inténtelo de nuevo!" + +# common +ErrDemoEnvironment: "Servidor de demostración, ¡esta operación está prohibida!" +ErrCmdTimeout: "¡Tiempo de espera al ejecutar el comando!" +ErrEntrance: "Error en la información de entrada de seguridad, ¡por favor revise e intente de nuevo!" +ErrGroupIsDefault: "Grupo predeterminado, no se puede eliminar" +ErrGroupIsInUse: "El grupo está en uso y no se puede eliminar." +ErrLocalDelete: "¡No se puede eliminar el nodo local!" +ErrPortInUsed: "¡El puerto {{ .name }} ya está en uso!" +ErrInternalServerKey: "Error interno del servidor:" +MasterNode: "Nodo Maestro" + +# app +CustomAppStoreFileValid: "El paquete de la tienda de aplicaciones debe tener formato .tar.gz" +ErrFileNotFound: "El archivo {{ .name }} no existe" +AppBackup: 'Copia de seguridad de aplicación' +AppBackupPush: 'Transferir archivo de copia de seguridad de aplicación {{.file}} al nodo {{ .name }}' +ErrSourceTargetSame: '¡El nodo de origen y el nodo de destino no pueden ser el mismo!' +AppInstall: 'Instalar aplicación {{ .name }} en nodo {{ .targetNode }}' +AppInstallCheck: 'Verificar entorno de instalación de aplicación' + +# backup +ErrBackupInUsed: "Esta cuenta de respaldo se utiliza en tareas programadas y no se puede eliminar" +ErrBackupCheck: "Fallo en la prueba de conexión de la cuenta de respaldo {{ .err }}" +ErrBackupLocal: "¡La cuenta de respaldo del servidor local no admite esta operación!" +ErrBackupPublic: "Se detectó que esta cuenta de respaldo no es pública, ¡verifique e intente de nuevo!" +ErrOSSConn: "No se pudo obtener la última versión, por favor revise la conectividad de red externa del servidor." + +#license +LicenseCheck: 'Comprobar si la licencia está disponible' +ErrLicenseInUsed: 'La licencia ya está vinculada. ¡Verifique e intente de nuevo!' +ErrLicenseExpired: 'La licencia ha expirado. ¡Verifique e intente de nuevo!' +ErrLicense: "Error en el formato de la licencia, ¡verifique y reintente!" +ErrLicenseCheck: "Validación de licencia fallida, ¡verifique y reintente!" +ErrXpackVersion: "Validación de licencia fallida, esta licencia está limitada por versión, no se puede importar, ¡verifique y reintente!" +ErrLicenseSave: "Error al guardar la información de la licencia, error {{ .err }}, ¡reintente!" +ErrLicenseSync: "Sincronización de licencia fallida, ¡no se detectó información de licencia en la base de datos!" +ErrLicenseExist: "Este registro de licencia ya existe. Puede ir directamente a la página de licencia para la vinculación del nodo." +ErrXpackNotFound: "Esta sección es para la edición profesional, importe la licencia en Panel > Ajustes > Licencia" +ErrXpackExceptional: "Esta sección es para la edición profesional, sincronice el estado de la licencia en Panel > Ajustes > Licencia" +ErrXpackOutOfDate: "La licencia actual ha expirado, importe nuevamente la licencia en Panel > Ajustes > Licencia" +ErrXpackLost: "La licencia ha alcanzado el número máximo de reintentos, vaya a [Ajustes del Panel] [Licencia] y haga clic en el botón de sincronización manualmente para asegurar el funcionamiento correcto de las funciones profesionales" +ErrDeviceLost: "Faltan archivos necesarios para la verificación de licencia, ¡verifique e intente de nuevo!" +ErrDeviceErr: "El entorno actual no coincide con el entorno de importación de la licencia. ¡Edite la licencia e impórtela de nuevo!" +ErrXpackTimeout: "Tiempo de espera de la solicitud, puede que la conexión de red sea inestable, ¡intente más tarde!" +ErrUnbindMaster: "Se detectaron nodos en la gestión de nodos, no se puede desvincular la licencia actual, elimine los nodos primero e intente de nuevo!" +ErrFreeNodeLimit: "Se alcanzó el límite de nodos en la versión comunitaria, ¡vaya a www.lxware.cn/1panel para comprar y reintentar!" +ErrNodeBound: "Esta licencia está vinculada a otro nodo, ¡verifique e intente de nuevo!" +ErrNodeBoundDelete: "Esta licencia está vinculada y no admite operaciones de eliminación. ¡Verifique e intente de nuevo!" +ErrNodeBoundLimit: "El nodo gratuito actual ha alcanzado su límite, ¡verifique e intente de nuevo!" +ErrLicenseFree: "Los nodos gratuitos solo pueden usarse si la licencia está correctamente vinculada a un nodo. ¡Verifique e intente de nuevo!" +ErrLicenseUnbind: "Se detectaron nodos de la Edición Comunitaria para esta licencia. ¡Desvincule en [Panel > Ajustes > Licencia] y vuelva a intentarlo!" +ErrNoSuchNode: "Información del nodo no encontrada, ¡verifique e intente de nuevo!" +ErrNodeUnbind: "Este nodo no está dentro del rango de vinculación de la licencia, ¡verifique e intente de nuevo!" +ErrNodeBind: "Este nodo ya está vinculado a una licencia, ¡verifique e intente de nuevo!" +ErrNodeLocalRollback: "El nodo principal no admite la reversión directa. ¡Ejecute manualmente el comando '1pctl restore' para revertir!" +InvalidRequestBodyType: "Formato del cuerpo de la solicitud no válido, por favor revisa y asegúrate de que el contenido cumpla con el formato requerido antes de reintentar." +InvalidLicenseCodeType: "Formato de código de licencia no válido, por favor revisa e inténtalo de nuevo." +LicenseNotFoundType: "Licencia no encontrada, no existe ningún registro coincidente en el sistema para la licencia proporcionada. Por favor revisa e inténtalo de nuevo." +LicenseRevokedType: "La licencia solicitada ha sido revocada y ya no se puede utilizar. Por favor revisa e inténtalo de nuevo." +LicenseExpiredType: "La licencia ha expirado. Renuévala o vuelve a importarla en Configuración del Panel - Sección de Licencia antes de reintentar." +LicenseProductMismatchType: "La licencia no corresponde con el producto o servicio solicitado." +InvalidAssigneeType: "Información de usuario o dispositivo de destino no válida para la asignación de licencia. Por favor revisa e inténtalo de nuevo." +LicenseUsageNotFoundType: "No se encontraron registros de uso. Esta licencia no ha sido activada o utilizada aún. Por favor revisa e inténtalo de nuevo." +LicenseUsageLimitExceededType: "Esta licencia ya está vinculada a otro nodo. Por favor revisa e inténtalo de nuevo." + +# alert +ErrAlertSync: "Error al sincronizar la información de alertas, ¡verifique e intente de nuevo!" + +# task +TaskStart: "Tarea {{ .name }} iniciada [INICIO]" +TaskEnd: "Tarea {{ .name }} finalizada [COMPLETADA]" +TaskFailed: "Tarea {{ .name }} fallida" +TaskTimeout: "{{ .name }} ha expirado" +TaskSuccess: "Tarea {{ .name }} terminada con éxito" +TaskRetry: "Iniciando reintento {{ .name }}" +SubTaskSuccess: "{{ .name }} completada correctamente" +SubTaskFailed: "{{ .name }} falló: {{ .err }}" +TaskInstall: "Instalar" +TaskUpgrade: "Actualizar" +TaskSync: 'Sincronizar' +TaskSyncForNode: "Sincronizar Datos del Nodo" +TaskBackup: "Respaldar" +SuccessStatus: "{{ .name }} correcta" +FailedStatus: "{{ .name }} fallida {{ .err }}" +Start: "Iniciar" +SubTask: "Subtarea" +Skip: "Omitir errores y continuar..." + +#script +ScriptLibrary: "Biblioteca de scripts" +RemoteScriptLibrary: "Biblioteca de Scripts Remota" +ScriptSyncSkip: "¡La biblioteca de scripts ya está en la última versión!" +DownloadData: "Descargando archivo de datos de la biblioteca de scripts data.yaml" +DownloadPackage: "Descargando paquete de la biblioteca de scripts" +AnalyticCompletion: "Análisis completado, sincronizando con la base de datos..." + +Node: "Nodo" +SyncNode: "Sincronizar datos del nodo" +LocalName: "El nombre 'local' solo se utiliza para la identificación local del sistema" +SyncPackageData: "Sincronizar datos del paquete [{{ .detail }}]" +SyncPackageEncrypt: "Encriptación del paquete de datos" +SyncRequest: "Solicitar API de sincronización de nodos" +SyncFailedRetry: "Tiempo de espera en la sincronización de datos del nodo (intento {{ .index }}), reintentando..." +SyncFailed: "¡La sincronización falló, sincronice manualmente en la lista de nodos!" +SyncSystemProxy: "Configuración del proxy del sistema" +SyncScripts: "Biblioteca de scripts" +SyncBackupAccounts: "Cuentas de copia de seguridad" +SyncAlertSetting: "Configuración de alertas" +SyncCustomApp: "Aplicación personalizada" +SyncLanguage: "Idioma del sistema" + +#upgrade node +NodeUpgrade: "Actualizar nodo {{ .name }}" +UpgradeCheck: "Comprobar actualizaciones de nodos" +UpgradeCheckLocal: "Los nodos locales no admiten actualizaciones en lote, omitiendo..." +UpgradeCheckLatest: "El nodo ya está en la última versión, omitiendo..." +NewSSHClient: "Inicializando conexión SSH" +BackupBeforeUpgrade: "Respaldar datos antes de actualizar" +UploadUpgradeFile: "Distribuir archivos de actualización" +RestartAfterUpgrade: "Iniciar servicio después de actualizar" + +#add node +MasterData: "Datos del nodo principal" +LoadSftpClient: "Cargar cliente SFTP" +PackageMasterData: "Generar paquete de respaldo del nodo principal" +UploadBackup: "Subir datos de respaldo" +MvBackup: "Mover datos al directorio de respaldos" +TaskAddNode: "Agregar nodo" +LoadNodeArch: "Obtener información de arquitectura del nodo" +LoadNodeArchDetail: "Arquitectura detectada - principal: {{ .local }}, nodo secundario: {{ .node }}" +LoadNodeUpgradeDetail: "Usando el directorio de instalación histórico versión v1: {{ .baseDir }}, puerto de escucha del servicio: {{ .port }}" +SyncAgentBaseInfo: "Sincronizar información básica del nodo" +GenerateSSLInfo: "Generar información SSL del nodo" +ConnInfoNotMatch: "La información de conexión no coincide" +MakeAgentPackage: "Generar paquete de instalación del nodo" +SendAgent: "Distribuir paquete de instalación del nodo" +StartService: "Iniciar servicio" +NoBackupNode: "Actualmente no hay nodo de respaldo. Seleccione uno para guardar e intente de nuevo." + +#cmd +AppVersion: "Versión de la aplicación" +AppCommands: "Comandos relacionados con la app" +AppInit: "Inicializar aplicación" +AppKeyVal: "Clave de la app (solo admite inglés)" +AppCreateFileErr: "Error al crear el archivo {{ .name }} {{ .err }}" +AppCreateDirErr: "Error al crear la carpeta {{ .name }} {{ .err }}" +AppMissKey: "Falta la clave de la app, use -k para especificar" +AppMissVersion: "Falta la versión de la app, use -v para especificar" +AppVersionExist: "¡La versión ya existe!" +AppCreateSuccessful: "¡Creación finalizada con éxito!" +AppWriteErr: "Error al escribir el archivo {{ .name }} {{ .err }}" +SudoHelper: "Por favor use {{ .cmd }} o cambie al usuario root" +ListenIPCommands: "Cambiar ip de escucha" +ListenIPv4: "Escuchar en IPv4" +ListenIPv6: "Escuchar en IPv6" +ListenChangeSuccessful: "¡Cambio exitoso! Ahora escuchando en {{ .value }}" +ResetCommands: "Restablecer información del sistema" +ResetMFA: "Cancelar autenticación de dos factores de 1Panel" +ResetHttps: "Cancelar inicio de sesión https de 1Panel" +ResetEntrance: "Cancelar entrada segura de 1Panel" +ResetIPs: "Cancelar restricciones de IP autorizadas de 1Panel" +ResetDomain: "Cancelar vinculación de dominio de 1Panel" +ResetPasskey: "Eliminar passkeys de 1Panel" +RestoreCommands: "Revertir servicio y datos de 1Panel" +RestoreNoSuchFile: "No hay archivos disponibles para revertir" +RestoreStep1: "(1/5) Iniciando reversión del servicio y datos de 1Panel desde el directorio {{ .name }}..." +RestoreStep2: "(2/5) Reversión de binarios de 1Panel finalizada con éxito" +RestoreStep3: "(3/5) Reversión de scripts de 1Panel finalizada con éxito" +RestoreStep4: "(4/5) Reversión de servicio de 1Panel finalizada con éxito" +RestoreStep5: "(5/5) Reversión de datos de 1Panel finalizada con éxito" +RestoreSuccessful: "¡Reversión exitosa! Revertido a {{ .version }}, reiniciando servicio, espere por favor..." +UpdateCommands: "Actualizar información del panel" +UpdateUser: "Actualizar usuario del panel" +UpdatePassword: "Actualizar contraseña del panel" +UpdatePort: "Actualizar puerto del panel" +UpdateUserNull: "Error: ¡el usuario del panel está vacío!" +UpdateUserBlank: "Error: ¡el usuario del panel contiene espacios!" +UpdateUserFormat: "Error: ¡formato de usuario del panel inválido! Solo admite inglés, chino, números y _, longitud 3-30" +UpdateUserErr: "Error: Fallo al actualizar usuario del panel, {{ .err }}" +UpdateSuccessful: "¡Actualización finalizada con éxito!" +UpdateUserResult: "Usuario del panel: {{ .name }}" +UpdatePasswordRead: "Error: Fallo al leer información de la contraseña del panel, {{ .err }}" +UpdatePasswordNull: "Error: ¡la contraseña del panel está vacía!" +UpdateUPasswordBlank: "Error: ¡la contraseña del panel contiene espacios!" +UpdatePasswordFormat: "Error: ¡La contraseña solo admite letras, números y los caracteres especiales !@#$%*_,.?, longitud 8-30!" +UpdatePasswordLen: "Error: ¡Ingrese una contraseña mayor a 6 caracteres!" +UpdatePasswordRe: "Confirmar contraseña:" +UpdatePasswordErr: "Error: Fallo al actualizar la contraseña del panel, {{ .err }}" +UpdatePasswordSame: "Error: Las dos contraseñas no coinciden, ¡verifique e intente de nuevo!" +UpdatePasswordResult: "Contraseña del panel: {{ .name }}" +UpdatePortFormat: "Error: ¡El puerto debe estar entre 1 y 65535!" +UpdatePortUsed: "Error: ¡El puerto ya está en uso, verifique e intente de nuevo!" +UpdatePortErr: "Error: Fallo al actualizar el puerto del panel, {{ .err }}" +UpdatePortResult: "Puerto del panel: {{ .name }}" +UpdatePortFirewallAdd: "Fallo al agregar la regla de puerto al firewall, {{ .err }}, agregue manualmente el puerto {{ .name }} al firewall." +UpdatePortFirewallDel: "Error: Fallo al eliminar el puerto del firewall, {{ .err }}" +UpdatePortFirewallReload: "Fallo al recargar el firewall, {{ .err }}, recargue el firewall manualmente." +UserInfo: "Obtener información del panel" +UserInfoAddr: "Dirección del panel: " +UserInfoPassHelp: "Consejo: para cambiar la contraseña, ejecute el comando: " +DBConnErr: "Error: Fallo al inicializar la conexión a la base de datos, {{ .err }}" +SystemVersion: "versión: " +SystemMode: "modo: " + +#mobile app +ErrVerifyToken: 'Error al verificar el token, por favor restablezca y escanee de nuevo.' +ErrInvalidToken: 'Token inválido, por favor restablezca y escanee de nuevo.' +ErrExpiredToken: 'El token ha expirado, por favor restablezca y escanee de nuevo.' + +#cluster +ErrMasterDelete: "No se puede eliminar el nodo principal, elimine primero los nodos secundarios." +ClusterNameIsExist: "El nombre del clúster ya existe." +AppStatusUnHealthy: "Adquisición anormal del estado de la aplicación, revise el estado del nodo en la lista de nodos." +MasterNodePortNotAvailable: "Verificación de conectividad del puerto {{ .port }} del nodo {{ .name }} fallida, verifique la configuración del firewall/grupo de seguridad y el estado del nodo principal." +ClusterMasterNotExist: "El nodo principal del clúster está desconectado, elimine los nodos secundarios." + +#ssl +ErrReqFailed: "{{.name}} petición fallida: {{ .err }}" + +#command +Name: "Nombre" +Command: "Comando" diff --git a/core/i18n/lang/ja.yaml b/core/i18n/lang/ja.yaml new file mode 100644 index 0000000..7711628 --- /dev/null +++ b/core/i18n/lang/ja.yaml @@ -0,0 +1,254 @@ +ErrInvalidParams: "リクエストパラメータが無効です: {{ .detail }}" +ErrTokenParse: "トークン生成エラー: {{ .detail }}" +ErrInitialPassword: "初期パスワードが間違っています" +ErrInternalServer: "サーバー内部エラー: {{ .detail }}" +ErrRecordExist: "レコードはすでに存在します" +ErrRecordNotFound: "レコードが見つかりません" +ErrStructTransform: "型変換に失敗しました: {{ .detail }}" +ErrNotLogin: "ユーザーがログインしていません: {{ .detail }}" +ErrPasswordExpired: "現在のパスワードの有効期限が切れています: {{ .detail }}" +ErrNotSupportType: "現在のタイプはサポートされていません: {{ .detail }}" +ErrProxy: "リクエストエラー、このノードの状態を確認してください: {{ .detail }}" +ErrApiConfigStatusInvalid: "APIアクセスは禁止されています: {{ .detail }}" +ErrApiConfigKeyInvalid: "APIキーが無効です: {{ .detail }}" +ErrApiConfigIPInvalid: "APIリクエストIPがホワイトリストにありません: {{ .detail }}" +ErrApiConfigDisable: "このインターフェースはAPI呼び出しを禁止しています: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "APIタイムスタンプエラー: {{ .detail }}" + +# request +ErrNoSuchHost: "要求されたサーバーが見つかりません {{ .err }}" +ErrHttpReqNotFound: "要求されたリソースが見つかりません {{ .err }}" +ErrHttpReqFailed: "リクエスト失敗 {{ .err }}" +ErrHttpReqTimeOut: "リクエストタイムアウト {{ .err }}" +ErrCreateHttpClient: "リクエストの作成に失敗しました {{ .err }}" +ErrProxySetting: "プロキシサーバー情報が利用できません {{ .err }}、確認して再試行してください!" +ErrEntranceFormat: "セキュリティエントランス {{ .name }} は現在サポートされていません。確認して再試行してください!" + +# common +ErrDemoEnvironment: "デモサーバーではこの操作は許可されていません!" +ErrCmdTimeout: "コマンドの実行がタイムアウトしました!" +ErrEntrance: "セキュリティ情報エラー、再確認してください!" +ErrGroupIsDefault: "デフォルトグループの削除はできません" +ErrGroupIsInUse: "グループは使用中のため、削除できません。" +ErrLocalDelete: "ローカルノードは削除できません!" +ErrPortInUsed: "{{ .name }} ポートはすでに使用されています!" +ErrInternalServerKey: "サーバー内部エラー: " +MasterNode: "マスターノード" + +# app +CustomAppStoreFileValid: "アプリストアパッケージは .tar.gz 形式である必要があります" +ErrFileNotFound: "{{ .name }} ファイルが存在しません" +AppBackup: 'アプリケーションバックアップ' +AppBackupPush: 'アプリケーションバックアップファイル {{.file}} をノード {{ .name }} に転送' +ErrSourceTargetSame: 'ソースノードとターゲットノードは同じにできません!' +AppInstall: 'ノード {{ .targetNode }} にアプリケーション {{ .name }} をインストール' +AppInstallCheck: 'アプリケーションインストール環境を確認' + +# backup +ErrBackupInUsed: "このバックアップアカウントはスケジュールタスクで使用されており、削除できません" +ErrBackupCheck: "バックアップアカウントの接続テストが失敗しました {{ .err }}" +ErrBackupLocal: "ローカルサーバーバックアップアカウントはこの操作をサポートしていません!" +ErrBackupPublic: "このバックアップアカウントが公開されていないと検出されました。再確認してください!" +ErrOSSConn: "最新バージョンを取得できません。外部ネットワーク接続を確認してください。" + + +#license +LicenseCheck: 'ライセンスが利用可能か確認する' +ErrLicenseInUsed: 'このライセンスは既に紐付けられています。確認して再試行してください!' +ErrLicenseExpired: 'このライセンスは期限切れです。確認して再試行してください!' +ErrLicense: "ライセンスフォーマットエラー、確認して再試行してください!" +ErrLicenseCheck: "ライセンスの確認に失敗しました、確認して再試行してください!" +ErrXpackVersion: "ライセンスの確認に失敗しました、このライセンスはバージョン制限があり、正常にインポートできません、確認して再試行してください!" +ErrLicenseSave: "ライセンス情報の保存に失敗しました、エラー {{ .err }}、再試行してください!" +ErrLicenseSync: "ライセンス情報の同期に失敗しました、データベースにライセンス情報が検出されませんでした!" +ErrLicenseExist: "このライセンス記録は既に存在します。ライセンスページで直接ノードの紐付けが可能です。" +ErrXpackNotFound: "この部分はプロフェッショナル版機能です、まず「パネル設定-ライセンス」画面でライセンスをインポートしてください" +ErrXpackExceptional: "この部分はプロフェッショナル版機能です、まず「パネル設定-ライセンス」画面でライセンス状態を同期してください" +ErrXpackLost: "ライセンスの最大試行回数に達しました、「パネル設定」「ライセンス」ページに移動し、手動で同期ボタンをクリックして、プロフェッショナル版機能が正常に使用できることを確認してください" +ErrDeviceLost: "ライセンス検証に必要なファイルが失われました。確認して再試行してください!" +ErrDeviceErr: "現在の環境とライセンスのインポート環境が一致しません。ライセンスを編集して再度インポートしてください!" +ErrXpackTimeout: "リクエストタイムアウト、ネットワーク接続が不安定な可能性があります。後で再試行してください!" +ErrUnbindMaster: "ノード管理にノードが存在するため、現在のライセンスを解除できません。ノードを削除してから再試行してください!" +ErrFreeNodeLimit: "コミュニティ版のノード数が無料上限に達しました、www.lxware.cn/1panel で購入して再試行してください!" +ErrNodeBound: "このライセンスは他のノードにバインドされています、確認して再試行してください!" +ErrNodeBoundDelete: "このライセンスはバインドされており、削除操作はサポートされていません。確認して再試行してください!" +ErrNodeBoundLimit: "現在の無料ノードは上限に達しました。確認して再試行してください!" +ErrLicenseFree: "ライセンスがノードに正常にバインドされている場合のみ、その無料ノードを使用できます。確認して再試行してください!" +ErrLicenseUnbind: "このライセンスにコミュニティ版ノードが存在します。[パネル設定 - ライセンス]でバインド解除してから再試行してください!" +ErrNoSuchNode: "そのノード情報が見つかりませんでした、確認して再試行してください!" +ErrNodeUnbind: "そのノードはライセンスのバインド範囲内ではありません、確認して再試行してください!" +ErrNodeBind: "そのノードはライセンスにバインドされています、確認して再試行してください!" +ErrNodeLocalRollback: "マスターノードは直接ロールバックをサポートしていません。手動で「1pctl restore」コマンドを実行してロールバックしてください!" + +InvalidRequestBodyType: "リクエストボディの形式が無効です。内容が要求された形式に準拠しているか確認してから再試行してください!" +InvalidLicenseCodeType: "提供されたライセンスコードの形式が無効です。確認してから再試行してください!" +LicenseNotFoundType: "ライセンスが見つかりません。提供されたライセンスに一致する記録がシステム内に存在しません。確認してから再試行してください!" +LicenseRevokedType: "リクエストされたライセンスは廃止されており、使用できません。確認してから再試行してください!" +LicenseExpiredType: "ライセンスの有効期限が切れています。更新するか、パネル設定 - ライセンス 画面で再インポートしてから再試行してください!" +LicenseProductMismatchType: "ライセンスがリクエストされた製品またはサービスと一致しません!" +InvalidAssigneeType: "ライセンス割り当ての対象ユーザーまたはデバイス情報が無効です。確認してから再試行してください!" +LicenseUsageNotFoundType: "使用記録がありません。このライセンスはまだ有効化または使用されていません。確認してから再試行してください!" +LicenseUsageLimitExceededType: "このライセンスは既に他のノードにバインドされています。確認してから再試行してください!" + +# alert +ErrAlertSync: "アラート情報の同期エラーです。後で再試行してください!" + +#task +TaskStart: "{{ .name }} タスク開始 [START]" +TaskEnd: "{{ .name }} タスク終了 [COMPLETED]" +TaskFailed: "{{ .name }} タスク失敗" +TaskTimeout: "{{ .name }} タイムアウト" +TaskSuccess: "{{ .name }} タスク成功" +TaskRetry: "第 {{ .name }} 回リトライ開始" +SubTaskSuccess: "{{ .name }} 成功" +SubTaskFailed: "{{ .name }} 失敗: {{ .err }}" +TaskInstall: "インストール" +TaskUpgrade: "アップグレード" +TaskSync: '同期' +TaskSyncForNode: "ノードデータを同期" +TaskBackup: "バックアップ" +SuccessStatus: "{{ .name }} 成功" +FailedStatus: "{{ .name }} 失敗 {{ .err }}" +Start: "開始" +SubTask: "サブタスク" +Skip: "エラーを無視して続行..." +PushAppInstallTaskToNode: "アプリインストールタスクをノード [{{ .name }}] にプッシュ" +TaskPush: "プッシュ" +AppInstallTask: "アプリインストールタスク" +PushAppFailed: "アプリインストールタスクのプッシュに失敗しました" +Success: "成功" + +#script +ScriptLibrary: "スクリプトライブラリ" +RemoteScriptLibrary: "リモートスクリプトライブラリ" +ScriptSyncSkip: "現在のスクリプトライブラリは最新バージョンです!" +DownloadData: "スクリプトライブラリファイル data.yaml をダウンロード中" +DownloadPackage: "スクリプトライブラリパッケージをダウンロード中" +AnalyticCompletion: "解析が完了しました、データベースに同期しています..." + +Node: "ノード" +SyncNode: "データをノード {{ .name }} に同期" +LocalName: "'local'名称はシステムのローカル識別のみに使用されます" +SyncPackageData: "同期データのパッケージ化" +SyncPackageEncrypt: "データパッケージの暗号化" +SyncRequest: "ノード同期APIをリクエスト" +SyncFailedRetry: "ノードデータ同期タイムアウト ({{ .index }}回目)、再試行中..." +SyncFailed: "同期に失敗しました、ノードリストで手動同期してください!" +SyncSystemProxy: "システムプロキシ設定" +SyncScripts: "スクリプトライブラリ" +SyncBackupAccounts: "バックアップアカウント" +SyncAlertSetting: "アラート設定" +SyncCustomApp: "カスタムアプリ" +SyncLanguage: "システム言語" + +#upgrade node +NodeUpgrade: "{{ .name }} ノードのアップグレード" +UpgradeCheck: "ノードの更新を確認" +UpgradeCheckLocal: "ローカルノードは一括アップグレードをサポートしていないため、スキップします..." +UpgradeCheckLatest: "ノードは既に最新バージョンです、スキップします..." +NewSSHClient: "SSH接続の初期化" +BackupBeforeUpgrade: "アップグレード前にデータをバックアップ" +UploadUpgradeFile: "アップグレードに必要なファイルを配布" +RestartAfterUpgrade: "アップグレード後にサービスを起動" + +#add node +MasterData: "マスターノードデータ" +LoadSftpClient: "SFTPクライアントの取得" +PackageMasterData: "マスターノードのバックアップパッケージを生成" +UploadBackup: "バックアップデータをアップロード" +MvBackup: "データをバックアップディレクトリに移動" +TaskAddNode: "ノードを追加" +LoadNodeArch: "ノードアーキテクチャ情報を取得" +LoadNodeArchDetail: "検出されたマスターノードアーキテクチャ: {{ .local }}, 子ノードアーキテクチャ: {{ .node }}" +LoadNodeUpgradeDetail: "v1 バージョンの履歴インストールディレクトリを使用しています: {{ .baseDir }}、サービスリスニングポート: {{ .port }}" +SyncAgentBaseInfo: "ノードの基本情報を同期" +GenerateSSLInfo: "ノードのSSL情報を生成" +ConnInfoNotMatch: "接続情報が一致しません" +MakeAgentPackage: "ノードインストールパッケージを生成" +SendAgent: "ノードインストールパッケージを送信" +StartService: "サービスを開始" +NoBackupNode: "現在バックアップノードが空です。保存するバックアップノードを選択して再試行してください!" + +#cmd +AppVersion: "アプリバージョン" +AppCommands: "アプリ関連のコマンド" +AppInit: "アプリを初期化します" +AppKeyVal: "アプリキー(英語のみをサポートする)" +AppCreateFileErr: "file {{ .name }}作成に失敗した{{ .err }}" +AppCreateDirErr: "Folder {{ .name }}作成に失敗した{{ .err }}" +AppMissKey: "アプリキーがありません、-kを使用して指定します" +AppMissVersion: "アプリバージョンがありません、-vを使用して指定します" +AppVersionExist: "バージョンはすでに存在します!" +AppCreateSuccessful: "創造は成功しました!" +AppWriteErr: "file {{ .name }} write failed {{ .err }}" +SudoHelper: "{{.cmd}}を使用するか、ルートユーザーに切り替えてください" +ListenIPCommands: "リスニングIPを切り替えます" +ListenIPv4: "IPv4で聞いてください" +ListenIPv6: "IPv6で聞く" +ListenChangeSuccessful: "切り替え成功!{{.Value}}で聞いています" +ResetCommands: "システム情報をリセットします" +ResetMFA: "1パネルの2要素認証をキャンセルします" +ResetHttps: "1Panel HTTPSログインをキャンセルします" +ResetEntrance: "1パネルの安全な入り口をキャンセルします" +ResetIPs: "1パネル認定IP制限をキャンセルします" +ResetDomain: "1パネルドメインバインディングをキャンセルします" +ResetPasskey: "1Panel のパスキーをクリアします" +RestoreCommands: "ロールバック1パネルサービスとデータ" +RestoreNoSuchFile: "ロールバックに使用できるファイルはありません" +RestoreStep1: "(1/5){{ .name }}ディレクトリからの1パネルサービスとデータの開始ロールバック..." +RestoreStep2: "(2/5)1パネルバイナリロールバックが成功しました" +RestoreStep3: "(3/5)1パネルスクリプトロールバック成功" +RestoreStep4: "(4/5)1パネルサービスロールバックが成功しました" +RestoreStep5: "(5/5)1パネルデータロールバックが成功しました" +RestoreSuccessful: "ロールバック成功!{{ .version }} にロールバックしました。サービスを再起動中です。しばらくお待ちください..." +UpdateCommands: "パネル情報を更新します" +UpdateUser: "パネルユーザーを更新します" +UpdatePassword: "パスワードを更新します" +UpdatePort: "パネルポートを更新します" +UpdateUserNull: "エラー: パネルユーザーは空です!" +UpdateUserBlank: "エラー: パネルユーザーにはスペースが含まれています!" +UpdateUserFormat: "エラー: 無効なパネルユーザー形式!英語、中国語、数字、_、長さ3〜30のみをサポートしています" +UpdateUserErr: "エラー: パネルユーザーの更新に失敗しました{{ .err }}" +UpdateSuccessful: "更新成功!" +UpdateUserResult: "パネルユーザー: {{ .name }}" +UpdatePasswordRead: "エラー: パネルパスワード情報の読み取りに失敗しました、{{ .err }}" +UpdatePasswordNull: "エラー: パネルパスワードは空です!" +UpdateUPasswordBlank: "エラー: パスワードにはスペースが含まれています!" +UpdatePasswordFormat: "エラー: パネルパスワードは、文字、数字、特殊文字のみをサポートします!@#$%*_、。、、長さ8-30!" +UpdatePasswordLen: "エラー: 6文字より長くパスワードを入力してください!" +UpdatePasswordRe: "パスワードを認証する: " +UpdatePasswordErr: "エラー: {{ .err }}のパスワードの更新に失敗しました" +UpdatePasswordSame: "エラー: 2つのパスワードが一致しません。チェックして再試行してください!" +UpdatePasswordResult: "パネルパスワード: {{ .name }}" +UpdatePortFormat: "エラー: 入力ポート番号は1〜65535でなければなりません!" +UpdatePortUsed: "エラー: ポート番号はすでに使用されています。チェックして再試行してください!" +UpdatePortErr: "エラー: パネルポートの更新に失敗しました{{ .err }}" +UpdatePortResult: "パネルポート: {{ .name }}" +UpdatePortFirewallAdd: "ファイアウォールポートルールの追加に失敗した{{ .err }}、ファイアウォールルールに{{ .name }}ポートを手動で追加してください。" +UpdatePortFirewallDel: "エラー: ファイアウォールポートの削除に失敗しました、{{ .err }}" +UpdatePortFirewallReload: "ファイアウォールのリロードに失敗しました{{ .err }}、ファイアウォールを手動でリロードしてください。" +UserInfo: "パネル情報を取得します" +UserInfoAddr: "パネルアドレス: " +UserInfoPassHelp: "ヒント: パスワードを変更するには、コマンドを実行できます。" +DBConnErr: "エラー: データベース接続の初期化に失敗しました、{{ .err }}" +SystemVersion: "バージョン: " +SystemMode: "モード: " + +#mobile app +ErrVerifyToken: 'トークンの検証エラーです。リセット後、再度QRコードをスキャンしてください。' +ErrInvalidToken: '無効なトークンです。リセット後、再度QRコードをスキャンしてください。' +ErrExpiredToken: 'トークンの有効期限が切れました。リセット後、再度QRコードをスキャンしてください。' + +#cluster +ErrMasterDelete: "マスターノードを削除できません。スレーブノードを先に削除してください。" +ClusterNameIsExist: "クラスタ名は既に存在します。" +AppStatusUnHealthy: "アプリケーションのステータス取得が異常です。ノードリストでインストールノードのステータスを確認してください。" +MasterNodePortNotAvailable: "ノード {{ .name }} のポート {{ .port }} の接続性検証が失敗しました。ファイアウォール/セキュリティグループの設定とマスターノードのステータスを確認してください。" +ClusterMasterNotExist: "クラスタのマスターノードが切断されています。子ノードを削除してください。" + +#ssl +ErrReqFailed: "{{.name}} リクエスト失敗: {{ .err }}" + +#command +Name: "名前" +Command: "コマンド" diff --git a/core/i18n/lang/ko.yaml b/core/i18n/lang/ko.yaml new file mode 100644 index 0000000..2ef9b36 --- /dev/null +++ b/core/i18n/lang/ko.yaml @@ -0,0 +1,253 @@ +ErrInvalidParams: "요청 매개변수가 잘못되었습니다: {{ .detail }}" +ErrTokenParse: "토큰 생성 오류: {{ .detail }}" +ErrInitialPassword: "초기 비밀번호가 잘못되었습니다" +ErrInternalServer: "서버 내부 오류: {{ .detail }}" +ErrRecordExist: "레코드가 이미 존재합니다" +ErrRecordNotFound: "레코드를 찾을 수 없습니다" +ErrStructTransform: "형 변환 실패: {{ .detail }}" +ErrNotLogin: "사용자가 로그인되지 않았습니다: {{ .detail }}" +ErrPasswordExpired: "현재 비밀번호가 만료되었습니다: {{ .detail }}" +ErrNotSupportType: "현재 유형은 지원되지 않습니다: {{ .detail }}" +ErrProxy: "요청 오류, 이 노드 상태를 확인하세요: {{ .detail }}" +ErrApiConfigStatusInvalid: "API 액세스가 금지되었습니다: {{ .detail }}" +ErrApiConfigKeyInvalid: "API 키 오류: {{ .detail }}" +ErrApiConfigIPInvalid: "API 요청 IP가 화이트리스트에 없습니다: {{ .detail }}" +ErrApiConfigDisable: "이 인터페이스는 API 호출을 금지합니다: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "API 타임스탬프 오류: {{ .detail }}" + +# request +ErrNoSuchHost: "요청한 서버를 찾을 수 없습니다 {{ .err }}" +ErrHttpReqNotFound: "요청한 리소스를 찾을 수 없습니다 {{ .err }}" +ErrHttpReqFailed: "요청 실패 {{ .err }}" +ErrHttpReqTimeOut: "요청 시간이 초과되었습니다 {{ .err }}" +ErrCreateHttpClient: "요청 생성 실패 {{ .err }}" +ErrProxySetting: "프록시 서버 정보를 사용할 수 없음 {{ .err }}, 확인 후 다시 시도하세요!" +ErrEntranceFormat: "보안 입구 {{ .name }}은(는) 현재 지원되지 않습니다. 확인 후 다시 시도해 주세요!" + +# common +ErrDemoEnvironment: "데모 서버에서는 이 작업이 금지되어 있습니다!" +ErrCmdTimeout: "명령 실행 시간 초과!" +ErrEntrance: "보안 정보 오류입니다. 확인 후 다시 시도하십시오!" +ErrGroupIsDefault: "기본 그룹은 삭제할 수 없습니다" +ErrGroupIsInUse: "그룹이 사용 중이므로 삭제할 수 없습니다." +ErrLocalDelete: "로컬 노드는 삭제할 수 없습니다!" +ErrPortInUsed: "{{ .name }} 포트가 이미 사용 중입니다!" +ErrInternalServerKey: "서버 내부 오류:" +MasterNode: "마스터 노드" + +# app +CustomAppStoreFileValid: "앱 스토어 패키지는 .tar.gz 형식이어야 합니다" +ErrFileNotFound: "{{ .name }} 파일이 존재하지 않습니다" +AppBackup: '애플리케이션 백업' +AppBackupPush: '애플리케이션 백업 파일 {{.file}}을(를) 노드 {{ .name }}(으)로 전송' +ErrSourceTargetSame: '소스 노드와 대상 노드는 동일할 수 없습니다!' +AppInstall: '노드 {{ .targetNode }}에 애플리케이션 {{ .name }} 설치' +AppInstallCheck: '애플리케이션 설치 환경 확인' + +# backup +ErrBackupInUsed: "이 백업 계정은 예약된 작업에 사용 중이며 삭제할 수 없습니다" +ErrBackupCheck: "백업 계정 연결 테스트 실패 {{ .err }}" +ErrBackupLocal: "로컬 서버 백업 계정은 이 작업을 지원하지 않습니다!" +ErrBackupPublic: "이 백업 계정이 공개된 것으로 감지되지 않았습니다. 다시 확인하십시오!" +ErrOSSConn: "최신 버전을 가져올 수 없습니다. 외부 네트워크 연결을 확인하십시오." + +#license +LicenseCheck: '라이선스 사용 가능 여부 확인' +ErrLicenseInUsed: '해당 라이선스가 이미 연결되었습니다. 확인 후 다시 시도하세요!' +ErrLicenseExpired: '해당 라이선스가 만료되었습니다. 확인 후 다시 시도하세요!' +ErrLicense: "라이선스 형식이 잘못되었습니다. 다시 확인하고 시도해 주세요!" +ErrLicenseCheck: "라이선스 검증 실패. 다시 확인하고 시도해 주세요!" +ErrXpackVersion: "라이선스 검증 실패, 이 라이선스는 버전 제한이 있습니다. 성공적으로 가져올 수 없습니다. 다시 확인하고 시도해 주세요!" +ErrLicenseSave: "라이선스 정보 저장 실패, 오류 {{ .err }}, 다시 시도해 주세요!" +ErrLicenseSync: "라이선스 정보 동기화 실패, 데이터베이스에서 라이선스 정보를 찾을 수 없습니다!" +ErrLicenseExist: "해당 라이선스 기록이 이미 존재합니다. 라이선스 페이지에서 직접 노드 바인딩이 가능합니다." +ErrXpackNotFound: "이 섹션은 프로페셔널 에디션 기능입니다. 먼저 [패널 설정]-[라이선스] 인터페이스에서 라이선스를 가져오세요." +ErrXpackExceptional: "이 섹션은 프로페셔널 에디션 기능입니다. 먼저 [패널 설정]-[라이선스] 인터페이스에서 라이선스 상태를 동기화하세요." +ErrXpackLost: "라이선스가 최대 재시도 횟수에 도달했습니다. [패널 설정][라이선스] 페이지로 이동하여 수동으로 동기화 버튼을 클릭하여 프로페셔널 기능을 정상적으로 사용할 수 있도록 하세요." +ErrDeviceLost: "라이센스 검증에 필요한 파일이 누락되었습니다. 확인 후 다시 시도해 주세요!" +ErrDeviceErr: "현재 환경이 라이선스 가져오기 환경과 일치하지 않습니다. 라이선스를 편집하고 다시 가져오십시오!" +ErrXpackTimeout: "요청 시간 초과, 네트워크 연결이 불안정할 수 있습니다. 나중에 다시 시도해 주세요!" +ErrUnbindMaster: "노드 관리 내에 노드가 존재함을 감지하였습니다. 현재 라이선스를 해제할 수 없습니다. 먼저 제거 후 다시 시도해 주세요!" +ErrFreeNodeLimit: "커뮤니티 에디션 노드 수가 무료 한도에 도달하였습니다. www.lxware.cn/1panel 에서 구매 후 다시 시도해 주세요!" +ErrNodeBound: "이 라이선스는 다른 노드에 바인딩되어 있습니다. 다시 확인하고 시도해 주세요!" +ErrNodeBoundDelete: "이 라이센스는 바인딩되어 있으며 삭제 작업을 지원하지 않습니다. 확인 후 다시 시도해 주세요!" +ErrNodeBoundLimit: "현재 무료 노드가 한도에 도달했습니다. 확인 후 다시 시도해 주세요!" +ErrLicenseFree: "라이선스가 노드에 정상적으로 바인딩된 경우에만 무료 노드를 사용할 수 있습니다. 확인 후 다시 시도하세요!" +ErrLicenseUnbind: "이 라이선스에 커뮤니티 에디션 노드가 존재합니다. [패널 설정 - 라이선스]에서 바인딩 해제 후 다시 시도하세요!" +ErrNoSuchNode: "노드 정보를 찾을 수 없습니다. 다시 확인하고 시도해 주세요!" +ErrNodeUnbind: "이 노드가 라이선스 바인딩 범위에 있지 않음을 감지하였습니다. 다시 확인하고 시도해 주세요!" +ErrNodeBind: "이 노드가 이미 라이선스에 바인딩되어 있음을 감지하였습니다. 다시 확인하고 시도해 주세요!" +ErrNodeLocalRollback: "마스터 노드는 직접 롤백을 지원하지 않습니다. 수동으로 '1pctl restore' 명령어를 실행하여 롤백하세요!" + +InvalidRequestBodyType: "요청 본문 형식이 잘못되었습니다. 내용이 형식 요구 사항을 충족하는지 확인한 후 다시 시도하세요!" +InvalidLicenseCodeType: "제공된 라이선스 코드 형식이 잘못되었습니다. 확인 후 다시 시도하세요!" +LicenseNotFoundType: "라이선스가 존재하지 않습니다. 시스템에서 제공된 라이선스와 일치하는 기록을 찾을 수 없습니다. 확인 후 다시 시도하세요!" +LicenseRevokedType: "요청한 라이선스는 폐기되어 사용할 수 없습니다. 확인 후 다시 시도하세요!" +LicenseExpiredType: "라이선스가 만료되었습니다. 갱신하거나 패널 설정 - 라이선스에서 다시 가져온 후 시도하세요!" +LicenseProductMismatchType: "라이선스가 요청한 제품 또는 서비스와 일치하지 않습니다!" +InvalidAssigneeType: "라이선스 할당 대상 사용자 또는 장치 정보가 유효하지 않습니다. 확인 후 다시 시도하세요!" +LicenseUsageNotFoundType: "사용 기록이 없습니다. 이 라이선스는 아직 활성화되거나 사용되지 않았습니다. 확인 후 다시 시도하세요!" +LicenseUsageLimitExceededType: "이 라이선스는 이미 다른 노드에 연결되었습니다. 확인 후 다시 시도하세요!" + +# alert +ErrAlertSync: "경고 정보 동기화 오류, 다시 확인하고 시도해 주세요!" + +#task +TaskStart: "{{ .name }} 작업 시작 [START]" +TaskEnd: "{{ .name }} 작업 종료 [COMPLETED]" +TaskFailed: "{{ .name }} 작업 실패" +TaskTimeout: "{{ .name }} 시간 초과" +TaskSuccess: "{{ .name }} 작업 성공" +TaskRetry: "제 {{ .name }} 차 재시도 시작" +SubTaskSuccess: "{{ .name }} 성공" +SubTaskFailed: "{{ .name }} 실패: {{ .err }}" +TaskInstall: "설치" +TaskUpgrade: "업그레이드" +TaskSync: '동기화' +TaskSyncForNode: "노드 데이터 동기화" +TaskBackup: "백업" +SuccessStatus: "{{ .name }} 성공" +FailedStatus: "{{ .name }} 실패 {{ .err }}" +Start: "시작" +SubTask: "서브 작업" +Skip: "오류 무시하고 계속..." +PushAppInstallTaskToNode: "노드 [{{ .name }}]에 앱 설치 작업 푸시" +TaskPush: "푸시" +AppInstallTask: "앱 설치 작업" +PushAppFailed: "앱 설치 작업 푸시 실패" +Success: "성공" + +#script +ScriptLibrary: "스크립트 라이브러리" +RemoteScriptLibrary: "원격 스크립트 라이브러리" +ScriptSyncSkip: "현재 스크립트 라이브러리가 이미 최신 버전입니다!" +DownloadData: "스크립트 라이브러리 파일 data.yaml 다운로드 중" +DownloadPackage: "스크립트 라이브러리 패키지 다운로드 중" +AnalyticCompletion: "분석 완료, 데이터베이스에 동기화 중..." + +Node: "노드" +SyncNode: "데이터를 노드 {{ .name }}(으)로 동기화" +LocalName: "'local' 이름은 시스템 로컬 식별 전용으로 사용됩니다" +SyncPackageData: "동기화 데이터 패키징" +SyncPackageEncrypt: "데이터 패키지 암호화" +SyncRequest: "노드 동기화 API 요청" +SyncFailedRetry: "노드 데이터 동기화 시간 초과 ({{ .index }}번째 시도), 재시도 중..." +SyncFailed: "동기화 실패, 노드 목록에서 수동 동기화를 실행하세요!" +SyncSystemProxy: "시스템 프록시 설정" +SyncScripts: "스크립트 라이브러리" +SyncBackupAccounts: "백업 계정" +SyncAlertSetting: "알림 설정" +SyncCustomApp: "사용자 정의 앱" +SyncLanguage: "시스템 언어" + +#upgrade node +NodeUpgrade: "{{ .name }} 노드 업그레이드" +UpgradeCheck: "노드 업데이트 확인" +UpgradeCheckLocal: "로컬 노드는 일괄 업그레이드를 지원하지 않으므로 건너뜁니다..." +UpgradeCheckLatest: "이 노드는 이미 최신 버전입니다. 건너뜁니다..." +NewSSHClient: "SSH 연결 초기화" +BackupBeforeUpgrade: "업그레이드 전 데이터 백업" +UploadUpgradeFile: "업그레이드에 필요한 파일 전송" +RestartAfterUpgrade: "업그레이드 후 서비스 시작" + +#add node +MasterData: "마스터 노드 데이터" +LoadSftpClient: "SFTP 클라이언트 가져오기" +PackageMasterData: "마스터 노드 백업 패키지 생성" +UploadBackup: "백업 데이터 업로드" +MvBackup: "데이터를 백업 디렉터리로 이동" +TaskAddNode: "노드 추가" +LoadNodeArch: "노드 아키텍처 정보 가져오기" +LoadNodeArchDetail: "감지된 마스터 노드 아키텍처: {{ .local }}, 자식 노드 아키텍처: {{ .node }}" +LoadNodeUpgradeDetail: "v1 버전 기록 설치 디렉토리 사용: {{ .baseDir }}, 서비스 수신 포트: {{ .port }}" +SyncAgentBaseInfo: "노드 기본 데이터 동기화" +GenerateSSLInfo: "노드 SSL 정보 생성" +ConnInfoNotMatch: "연결 정보가 일치하지 않음" +MakeAgentPackage: "노드 설치 패키지 생성" +SendAgent: "노드 설치 패키지 배포" +StartService: "서비스 시작" +NoBackupNode: "현재 백업 노드가 비어 있습니다. 저장할 백업 노드를 선택한 후 다시 시도하십시오!" + +# CMD +AppVersion: "앱 버전" +AppCommands: "앱 관련 명령어" +AppInit: "앱 초기화" +AppKeyVal: "앱 키 (영어만 지원)" +AppCreateFileErr: "파일 {{ .name }} 생성 실패 {{ .err }}" +AppCreateDirErr: "폴더 {{ .name }} 생성 실패 {{ .err }}" +AppMissKey: "앱 키가 없습니다. -k 옵션으로 지정하세요." +AppMissVersion: "앱 버전이 없습니다. -v 옵션으로 지정하세요." +AppVersionExist: "버전이 이미 존재합니다!" +AppCreateSuccessful: "생성 성공!" +AppWriteErr: "파일 {{ .name }} 쓰기 실패 {{ .err }}" +SudoHelper: "{{ .cmd }} 명령을 사용하거나 root 사용자로 전환하세요." +ListenIPCommands: "IP 변경" +ListenIPv4: "IPv4 에서 수신 대기" +ListenIPv6: "IPv6 에서 수신 대기" +ListenChangeSuccessful: "변경 성공! 현재 {{ .value }}에서 수신 대기 중입니다." +ResetCommands: "시스템 정보 초기화" +ResetMFA: "1Panel 이중 인증 취소" +ResetHttps: "1Panel HTTPS 로그인 취소" +ResetEntrance: "1Panel 보안 입구 취소" +ResetIPs: "1Panel 허용 IP 제한 취소" +ResetDomain: "1Panel 도메인 바인딩 취소" +ResetPasskey: "1Panel 패스키 초기화" +RestoreCommands: "1Panel 서비스 및 데이터 복구" +RestoreNoSuchFile: "복구 가능한 파일이 없습니다." +RestoreStep1: "(1/5) {{ .name }} 디렉토리에서 1Panel 서비스 및 데이터 복구 시작..." +RestoreStep2: "(2/5) 1Panel 바이너리 복구 성공" +RestoreStep3: "(3/5) 1Panel 스크립트 복구 성공" +RestoreStep4: "(4/5) 1Panel 서비스 복구 성공" +RestoreStep5: "(5/5) 1Panel 데이터 복구 성공" +RestoreSuccessful: "롤백 성공! {{ .version }}(으)로 롤백되었습니다. 서비스를 재시작 중입니다. 잠시만 기다려 주세요..." +UpdateCommands: "패널 정보 업데이트" +UpdateUser: "패널 사용자 업데이트" +UpdatePassword: "패널 비밀번호 업데이트" +UpdatePort: "패널 포트 업데이트" +UpdateUserNull: "오류: 패널 사용자가 비어 있습니다!" +UpdateUserBlank: "오류: 패널 사용자에 공백이 포함되어 있습니다!" +UpdateUserFormat: "오류: 잘못된 패널 사용자 형식! 영어, 중국어, 숫자, _만 지원하며 길이는 3~30자입니다." +UpdateUserErr: "오류: 패널 사용자 업데이트 실패 {{ .err }}" +UpdateSuccessful: "업데이트 성공!" +UpdateUserResult: "패널 사용자: {{ .name }}" +UpdatePasswordRead: "오류: 패널 비밀번호 정보 읽기 실패 {{ .err }}" +UpdatePasswordNull: "오류: 패널 비밀번호가 비어 있습니다!" +UpdateUPasswordBlank: "오류: 패널 비밀번호에 공백이 포함되어 있습니다!" +UpdatePasswordFormat: "오류: 패널 비밀번호는 문자, 숫자, 특수 문자 !@#$%*_,.?만 지원하며 길이는 8~30자입니다!" +UpdatePasswordLen: "오류: 6자 이상의 비밀번호를 입력하세요!" +UpdatePasswordRe: "비밀번호 확인:" +UpdatePasswordErr: "오류: 패널 비밀번호 업데이트 실패 {{ .err }}" +UpdatePasswordSame: "오류: 두 비밀번호가 일치하지 않습니다. 확인 후 다시 시도하세요!" +UpdatePasswordResult: "패널 비밀번호: {{ .name }}" +UpdatePortFormat: "오류: 입력한 포트 번호는 1~65535 사이여야 합니다!" +UpdatePortUsed: "오류: 포트 번호가 이미 사용 중입니다. 확인 후 다시 시도하세요!" +UpdatePortErr: "오류: 패널 포트 업데이트 실패 {{ .err }}" +UpdatePortResult: "패널 포트: {{ .name }}" +UpdatePortFirewallAdd: "방화벽 포트 규칙 추가 실패 {{ .err }}, 방화벽 규칙에 {{ .name }} 포트를 수동으로 추가하세요." +UpdatePortFirewallDel: "오류: 방화벽 포트 삭제 실패 {{ .err }}" +UpdatePortFirewallReload: "방화벽 다시 로드 실패 {{ .err }}, 방화벽을 수동으로 다시 로드하세요." +UserInfo: "패널 정보 가져오기" +UserInfoAddr: "패널 주소: " +UserInfoPassHelp: "팁: 비밀번호를 변경하려면 다음 명령어를 실행할 수 있습니다: " +DBConnErr: "오류: 데이터베이스 연결 초기화 실패 {{ .err }}" +SystemVersion: "버전: " +SystemMode: "모드: " + +#mobile app +ErrVerifyToken: '토큰 검증 오류가 발생했습니다. 재설정 후 다시 스캔해 주세요.' +ErrInvalidToken: '유효하지 않은 토큰입니다. 재설정 후 다시 스캔해 주세요.' +ErrExpiredToken: '토큰이 만료되었습니다. 재설정 후 다시 스캔해 주세요.' + +#cluster +ErrMasterDelete: "마스터 노드를 삭제할 수 없습니다. 슬레이브 노드를 먼저 삭제하세요." +ClusterNameIsExist: "클러스터 이름이 이미 존재합니다." +AppStatusUnHealthy: "애플리케이션 상태 획득이 비정상입니다. 노드 목록에서 설치 노드 상태를 확인하세요." +MasterNodePortNotAvailable: "노드 {{ .name }} 포트 {{ .port }} 연결성 검증에 실패했습니다. 방화벽/보안 그룹 설정 및 마스터 노드 상태를 확인하세요." +ClusterMasterNotExist: "클러스터의 마스터 노드가 연결이 끊어졌습니다. 자식 노드를 삭제하세요." + +#ssl +ErrReqFailed: "{{.name}} 요청 실패: {{ .err }}" + +#command +Name: "이름" +Command: "명령어" diff --git a/core/i18n/lang/ms.yaml b/core/i18n/lang/ms.yaml new file mode 100644 index 0000000..dc545fb --- /dev/null +++ b/core/i18n/lang/ms.yaml @@ -0,0 +1,243 @@ +ErrInvalidParams: "Parameter permintaan tidak sah: {{ .detail }}" +ErrTokenParse: "Ralat penjanaan token: {{ .detail }}" +ErrInitialPassword: "Kata laluan asal salah" +ErrInternalServer: "Ralat dalaman pelayan: {{ .detail }}" +ErrRecordExist: "Rekod sudah wujud" +ErrRecordNotFound: "Rekod tidak ditemui" +ErrStructTransform: "Penukaran jenis gagal: {{ .detail }}" +ErrNotLogin: "Pengguna belum log masuk: {{ .detail }}" +ErrPasswordExpired: "Kata laluan semasa telah tamat tempoh: {{ .detail }}" +ErrNotSupportType: "Jenis ini tidak disokong: {{ .detail }}" +ErrProxy: "Ralat permintaan, sila semak status nod ini: {{ .detail }}" +ErrApiConfigStatusInvalid: "Akses API dilarang: {{ .detail }}" +ErrApiConfigKeyInvalid: "Kunci API tidak sah: {{ .detail }}" +ErrApiConfigIPInvalid: "IP permintaan API tidak dalam senarai putih: {{ .detail }}" +ErrApiConfigDisable: "Antara muka ini melarang panggilan API: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "Ralat cap masa API: {{ .detail }}" + +# request +ErrNoSuchHost: "Pelayan yang diminta tidak dapat ditemui {{ .err }}" +ErrHttpReqNotFound: "Sumber yang diminta tidak dapat ditemui {{ .err }}" +ErrHttpReqFailed: "Permintaan gagal {{ .err }}" +ErrHttpReqTimeOut: "Permintaan telah tamat masa {{ .err }}" +ErrCreateHttpClient: "Gagal mencipta permintaan {{ .err }}" +ErrProxySetting: "Maklumat pelayan proksi tidak tersedia {{ .err }}, sila periksa dan cuba lagi!" +ErrEntranceFormat: "Pintu masuk keselamatan {{ .name }} tidak disokong buat masa ini. Sila periksa dan cuba lagi!" + +# common +ErrDemoEnvironment: "Pelayan demo, operasi ini dilarang!" +ErrCmdTimeout: "Perintah telah tamat masa!" +ErrEntrance: "Maklumat pintu masuk keselamatan salah, sila periksa dan cuba lagi!" +ErrGroupIsDefault: "Kumpulan lalai tidak boleh dihapuskan" +ErrGroupIsInUse: "Kumpulan sedang digunakan dan tidak boleh dipadam." +ErrLocalDelete: "Nod tempatan tidak boleh dihapuskan!" +ErrPortInUsed: "Port {{ .name }} telah digunakan!" +ErrInternalServerKey: "Ralat dalaman pelayan:" +MasterNode: "Nod Induk" + +# app +CustomAppStoreFileValid: "Pakej stor aplikasi perlu dalam format .tar.gz" +ErrFileNotFound: "Fail {{ .name }} tidak wujud" + +# backup +ErrBackupInUsed: "Akaun sandaran ini telah digunakan dalam tugas berjadual dan tidak boleh dihapuskan" +ErrBackupCheck: "Ujian sambungan akaun sandaran gagal {{ .err }}" +ErrBackupLocal: "Akaun sandaran pelayan tempatan tidak menyokong operasi ini!" +ErrBackupPublic: "Akaun sandaran ini dikesan tidak awam, sila semak semula dan cuba lagi!" +ErrOSSConn: "Tidak dapat mendapatkan versi terkini, sila semak sambungan rangkaian luar pelayan." + +#license +LicenseCheck: 'Periksa sama ada lesen tersedia' +ErrLicenseInUsed: 'Lesen ini telah terikat. Sila periksa dan cuba lagi!' +ErrLicenseExpired: 'Lesen ini telah tamat tempoh. Sila periksa dan cuba lagi!' +ErrLicense: "Format lesen tidak sah, sila semak dan cuba lagi!" +ErrLicenseCheck: "Pengesahan lesen gagal, sila semak dan cuba lagi!" +ErrXpackVersion: "Pengesahan lesen gagal, lesen ini terhad kepada versi tertentu, sila semak dan cuba lagi!" +ErrLicenseSave: "Gagal menyimpan maklumat lesen {{ .err }}, sila cuba lagi!" +ErrLicenseSync: "Penyelarasan maklumat lesen gagal, maklumat lesen tidak ditemui dalam pangkalan data!" +ErrLicenseExist: "Rekod lesen ini sudah wujud. Anda boleh terus ke halaman lesen untuk mengikat nod." +ErrXpackNotFound: "Bahagian ini adalah fungsi profesional, sila muatkan lesen di Tetapan Papan Pemuka terlebih dahulu." +ErrXpackExceptional: "Bahagian ini adalah fungsi profesional, sila selaraskan lesen di Tetapan Papan Pemuka terlebih dahulu." +ErrXpackLost: "Lesen telah mencapai bilangan percubaan maksimum, sila ke [Tetapan Papan Pemuka][Lesen] dan klik butang penyelarasan secara manual." +ErrDeviceLost: "Fail yang diperlukan untuk pengesahan lesen hilang, sila semak dan cuba lagi!" +ErrDeviceErr: "Persekitaran semasa tidak sepadan dengan persekitaran import lesen. Sila edit lesen dan import semula!" +ErrXpackTimeout: "Permintaan tamat masa, sambungan rangkaian mungkin tidak stabil, sila cuba lagi kemudian!" +ErrUnbindMaster: "Terdapat nod dalam pengurusan nod, sila keluarkan dahulu dan cuba lagi!" +ErrFreeNodeLimit: "Jumlah nod versi komuniti telah mencapai had percuma, sila lawati www.lxware.cn/1panel untuk pembelian!" +ErrNodeBound: "Lesen ini telah diikat dengan nod lain, sila semak dan cuba lagi!" +ErrNodeBoundDelete: "Lisensi ini telah diikat dan tidak menyokong operasi penghapusan. Sila semak dan cuba lagi!" +ErrNodeBoundLimit: "Nod percuma semasa telah mencapai had, sila semak dan cuba lagi!" +ErrLicenseFree: "Nod percuma hanya boleh digunakan apabila lesen terikat dengan betul pada nod. Sila semak dan cuba lagi!" +ErrLicenseUnbind: "Nod Edisi Komuniti dikesan untuk lesen ini. Sila lepaskan ikatan dalam [Tetapan Panel - Lesen] dan cuba lagi!" +ErrNoSuchNode: "Maklumat nod tidak ditemui, sila semak dan cuba lagi!" +ErrNodeUnbind: "Nod di luar skop lesen dikesan, sila semak dan cuba lagi!" +ErrNodeBind: "Nod ini telah diikat dengan lesen, sila semak dan cuba lagi!" +ErrNodeLocalRollback: "Nod utama tidak menyokong rollback secara langsung. Sila laksanakan arahan '1pctl restore' secara manual untuk rollback!" + +InvalidRequestBodyType: "Format badan permintaan tidak sah, sila periksa dan pastikan kandungan memenuhi format yang diperlukan sebelum mencuba semula!" +InvalidLicenseCodeType: "Format kod lesen yang diberikan tidak sah, sila periksa dan cuba lagi!" +LicenseNotFoundType: "Lesen tidak dijumpai, tiada rekod yang sepadan dengan lesen yang diberikan dalam sistem. Sila periksa dan cuba lagi!" +LicenseRevokedType: "Lesen yang diminta telah dibatalkan dan tidak boleh digunakan lagi. Sila periksa dan cuba lagi!" +LicenseExpiredType: "Lesen telah tamat tempoh. Sila perbaharui atau import semula lesen dalam Tetapan Panel - Bahagian Lesen sebelum mencuba semula!" +LicenseProductMismatchType: "Lesen tidak sepadan dengan produk atau perkhidmatan yang diminta!" +InvalidAssigneeType: "Maklumat pengguna atau peranti sasaran untuk penugasan lesen tidak sah. Sila periksa dan cuba lagi!" +LicenseUsageNotFoundType: "Tiada rekod penggunaan dijumpai. Lesen ini belum diaktifkan atau digunakan. Sila periksa dan cuba lagi!" +LicenseUsageLimitExceededType: "Lesen ini sudah terikat pada nod lain. Sila periksa dan cuba lagi!" + +# alert +ErrAlertSync: "Ralat penyinkronan maklumat amaran, sila semak dan cuba lagi!" + +#task +TaskStart: "{{ .name }} tugas bermula [START]" +TaskEnd: "{{ .name }} tugas tamat [COMPLETED]" +TaskFailed: "{{ .name }} tugas gagal" +TaskTimeout: "{{ .name }} tamat masa" +TaskSuccess: "{{ .name }} tugas berjaya" +TaskRetry: "Mula percubaan semula {{ .name }}" +SubTaskSuccess: "{{ .name }} berjaya" +SubTaskFailed: "{{ .name }} gagal: {{ .err }}" +TaskInstall: "Pasang" +TaskUpgrade: "Kemas kini" +TaskSync: 'Selaraskan' +TaskSyncForNode: "Segerakan Data Nod" +TaskBackup: "Sandaran" +SuccessStatus: "{{ .name }} berjaya" +FailedStatus: "{{ .name }} gagal {{ .err }}" +Start: "Mula" +SubTask: "Tugas Sub" +Skip: "Abaikan ralat dan teruskan..." +PushAppInstallTaskToNode: "Tolak tugas pemasangan aplikasi ke nod [{{ .name }}]" +TaskPush: "Tolak" +AppInstallTask: "Tugas pemasangan aplikasi" +PushAppFailed: "Gagal menolak tugas pemasangan aplikasi" +Success: "Berjaya" + +#script +ScriptLibrary: "Pustaka Skrip" +RemoteScriptLibrary: "Pustaka Skrip Jauh" +ScriptSyncSkip: "Pustaka skrip semasa sudah versi terkini!" +DownloadData: "Muat turun fail pustaka skrip data.yaml" +DownloadPackage: "Muat turun pakej pustaka skrip" +AnalyticCompletion: "Analisis selesai, sedang menyegerakkan ke pangkalan data..." + +Node: "Nod" +SyncNode: "Selaraskan data ke nod {{ .name }}" +LocalName: "Nama 'local' hanya digunakan untuk pengenalpastian tempatan sistem" +SyncPackageData: "Pakej data segerak" +SyncPackageEncrypt: "Enkripsi pakej data" +SyncRequest: "Permintaan API segerak nod" +SyncFailedRetry: "Segerakan data nod tamat masa (percubaan ke-{{ .index }}), mencuba semula..." +SyncFailed: "Segerakan gagal, sila segerakkan secara manual dalam senarai nod!" +SyncSystemProxy: "Tetapan Proksi Sistem" +SyncScripts: "Pustaka Skrip" +SyncBackupAccounts: "Akaun Sandaran" +SyncAlertSetting: "Tetapan Amaran" +SyncCustomApp: "Aplikasi Tersuai" +SyncLanguage: "Bahasa Sistem" + +#upgrade node +NodeUpgrade: "Naik taraf node {{ .name }}" +UpgradeCheck: "Periksa kemas kini nod" +UpgradeCheckLocal: "Nod tempatan tidak menyokong peningkatan kelompok, dilangkau..." +UpgradeCheckLatest: "Nod sudah versi terkini, dilangkau..." +NewSSHClient: "Memulakan sambungan SSH" +BackupBeforeUpgrade: "Menyandarkan data sebelum naik taraf" +UploadUpgradeFile: "Sebarkan fail naik taraf" +RestartAfterUpgrade: "Mulakan perkhidmatan selepas naik taraf" + +#add node +MasterData: "Data Nod Master" +LoadSftpClient: "Dapatkan Klien SFTP" +PackageMasterData: "Hasilkan pakej sandaran nod master" +UploadBackup: "Muat naik data sandaran" +MvBackup: "Alihkan data ke direktori sandaran" +TaskAddNode: "Tambah nod" +LoadNodeArch: "Dapatkan maklumat seni bina nod" +LoadNodeArchDetail: "Mengesan seni bina nod induk: {{ .local }}, seni bina nod anak: {{ .node }}" +LoadNodeUpgradeDetail: "Menggunakan direktori pemasangan sejarah versi v1: {{ .baseDir }}, port mendengar perkhidmatan: {{ .port }}" +SyncAgentBaseInfo: "Sinkronkan maklumat asas nod" +GenerateSSLInfo: "Cipta maklumat SSL nod" +ConnInfoNotMatch: "Maklumat sambungan tidak sepadan" +MakeAgentPackage: "Cipta pakej pemasangan nod" +SendAgent: "Muat naik pakej pemasangan nod" +StartService: "Mulakan perkhidmatan" +NoBackupNode: "Nod sandaran kosong. Sila pilih nod sandaran untuk disimpan dan cuba lagi!" + +#cmd +AppVersion: "Versi aplikasi" +AppCommands: "Arahan berkaitan aplikasi" +AppInit: "Inisialisasi aplikasi" +AppKeyVal: "Kunci aplikasi (hanya menyokong bahasa Inggeris)" +AppCreateFileErr: "Gagal mencipta fail {{ .name }} {{ .err }}" +AppCreateDirErr: "Gagal mencipta folder {{ .name }} {{ .err }}" +AppMissKey: "Kunci aplikasi hilang, gunakan -k untuk menetapkan" +AppMissVersion: "Versi aplikasi hilang, gunakan -v untuk menetapkan" +AppVersionExist: "Versi sudah wujud!" +AppCreateSuccessful: "Ciptaan berjaya!" +AppWriteErr: "Penulisan fail {{ .name }} gagal {{ .err }}" +SudoHelper: "Sila gunakan {{ .cmd }} atau tukar ke pengguna root" +ListenIPCommands: "Tukar IP mendengar" +ListenIPv4: "Mendengar pada IPv4" +ListenIPv6: "Mendengar pada IPv6" +ListenChangeSuccessful: "Tukar berjaya! Kini mendengar pada {{ .value }}" +ResetCommands: "Tetapkan semula maklumat sistem" +ResetMFA: "Batal pengesahan dua faktor 1Panel" +ResetHttps: "Batal log masuk HTTPS 1Panel" +ResetEntrance: "Batal pintu masuk keselamatan 1Panel" +ResetIPs: "Batal sekatan IP yang dibenarkan 1Panel" +ResetDomain: "Batal pengikatan domain 1Panel" +ResetPasskey: "Kosongkan passkey 1Panel" +RestoreCommands: "Pulihkan perkhidmatan dan data 1Panel" +RestoreNoSuchFile: "Tiada fail tersedia untuk pemulihan" +RestoreStep1: "(1/5) Memulakan pemulihan perkhidmatan dan data 1Panel daripada direktori {{ .name }}..." +RestoreStep2: "(2/5) Pemulihan binari 1Panel berjaya" +RestoreStep3: "(3/5) Pemulihan skrip 1Panel berjaya" +RestoreStep4: "(4/5) Pemulihan perkhidmatan 1Panel berjaya" +RestoreStep5: "(5/5) Pemulihan data 1Panel berjaya" +RestoreSuccessful: "Pulangan semula berjaya! Dikembalikan ke {{ .version }}, memulakan semula perkhidmatan, sila tunggu..." +UpdateCommands: "Kemas kini maklumat panel" +UpdateUser: "Kemas kini pengguna panel" +UpdatePassword: "Kemas kini kata laluan panel" +UpdatePort: "Kemas kini port panel" +UpdateUserNull: "Ralat: Pengguna panel kosong!" +UpdateUserBlank: "Ralat: Pengguna panel mengandungi ruang kosong!" +UpdateUserFormat: "Ralat: Format pengguna panel tidak sah! Hanya menyokong huruf Inggeris, Cina, nombor, dan , dengan panjang 3-30 aksara" +UpdateUserErr: "Ralat: Gagal mengemas kini pengguna panel, {{ .err }}" +UpdateSuccessful: "Kemas kini berjaya!" +UpdateUserResult: "Pengguna panel: {{ .name }}" +UpdatePasswordRead: "Ralat: Gagal membaca maklumat kata laluan panel, {{ .err }}" +UpdatePasswordNull: "Ralat: Kata laluan panel kosong!" +UpdateUPasswordBlank: "Ralat: Kata laluan panel mengandungi ruang kosong!" +UpdatePasswordFormat: "Ralat: Kata laluan panel hanya menyokong huruf, nombor, dan aksara khas !@#$%*,.?, dengan panjang 8-30 aksara!" +UpdatePasswordLen: "Ralat: Sila masukkan kata laluan lebih panjang daripada 6 aksara!" +UpdatePasswordRe: "Sahkan kata laluan:" +UpdatePasswordErr: "Ralat: Gagal mengemas kini kata laluan panel, {{ .err }}" +UpdatePasswordSame: "Ralat: Kedua-dua kata laluan tidak sepadan, sila semak dan cuba lagi!" +UpdatePasswordResult: "Kata laluan panel: {{ .name }}" +UpdatePortFormat: "Ralat: Nombor port yang dimasukkan mesti antara 1 hingga 65535!" +UpdatePortUsed: "Ralat: Nombor port sudah digunakan, sila semak dan cuba lagi!" +UpdatePortErr: "Ralat: Gagal mengemas kini port panel, {{ .err }}" +UpdatePortResult: "Port Panel: {{ .name }}" +UpdatePortFirewallAdd: "Gagal menambah peraturan port firewall, {{ .err }}, sila tambah port {{ .name }} secara manual ke dalam peraturan firewall." +UpdatePortFirewallDel: "Ralat: Gagal memadam port firewall, {{ .err }}" +UpdatePortFirewallReload: "Gagal memuat semula firewall, {{ .err }}, sila muat semula firewall secara manual." +UserInfo: "Dapatkan maklumat panel" +UserInfoAddr: "Alamat panel: " +UserInfoPassHelp: "Petua: Untuk menukar kata laluan, anda boleh menjalankan arahan: " +DBConnErr: "Ralat: Gagal memulakan sambungan pangkalan data, {{ .err }}" +SystemVersion: "Versi: " +SystemMode: "Mod: " + +#cluster +ErrMasterDelete: "Tidak dapat menghapus nod utama, sila hapuskan nod perantara dahulu." +ClusterNameIsExist: "Nama kluster sudah wujud." +AppStatusUnHealthy: "Pengambilan status aplikasi tidak normal, sila periksa status nod pemasangan dalam senarai nod." +MasterNodePortNotAvailable: "Pengesahan kesambungan pelabuhan {{ .name }} nod {{ .port }} gagal, sila periksa tetapan firewall/kumpulan keselamatan dan status nod utama." +ClusterMasterNotExist: "Node utama kluster terputus, sila padamkan nod anak." + +#ssl +ErrReqFailed: "{{.name}} permintaan gagal: {{ .err }}" + +#command +Name: "Nama" +Command: "Arahan" diff --git a/core/i18n/lang/pt-BR.yaml b/core/i18n/lang/pt-BR.yaml new file mode 100644 index 0000000..a54a065 --- /dev/null +++ b/core/i18n/lang/pt-BR.yaml @@ -0,0 +1,253 @@ +ErrInvalidParams: "Parâmetros de solicitação inválidos: {{ .detail }}" +ErrTokenParse: "Erro ao gerar o token: {{ .detail }}" +ErrInitialPassword: "Senha inicial incorreta" +ErrInternalServer: "Erro interno do servidor: {{ .detail }}" +ErrRecordExist: "Registro já existe" +ErrRecordNotFound: "Registro não encontrado" +ErrStructTransform: "Falha na conversão de tipo: {{ .detail }}" +ErrNotLogin: "Usuário não está logado: {{ .detail }}" +ErrPasswordExpired: "A senha atual expirou: {{ .detail }}" +ErrNotSupportType: "Tipo atual não suportado: {{ .detail }}" +ErrProxy: "Erro de solicitação, verifique o status deste nó: {{ .detail }}" +ErrApiConfigStatusInvalid: "Acesso à API proibido: {{ .detail }}" +ErrApiConfigKeyInvalid: "Erro de chave API: {{ .detail }}" +ErrApiConfigIPInvalid: "O IP da solicitação de API não está na lista de permissões: {{ .detail }}" +ErrApiConfigDisable: "Esta interface proíbe chamadas de API: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "Erro de timestamp da API: {{ .detail }}" + +# request +ErrNoSuchHost: "Servidor solicitado não encontrado {{ .err }}" +ErrHttpReqNotFound: "Recurso solicitado não encontrado {{ .err }}" +ErrHttpReqFailed: "Falha na requisição {{ .err }}" +ErrHttpReqTimeOut: "Tempo de requisição esgotado {{ .err }}" +ErrCreateHttpClient: "Falha ao criar a requisição {{ .err }}" +ErrProxySetting: "Informação do servidor proxy indisponível {{ .err }}, verifique e tente novamente!" +ErrEntranceFormat: "A entrada de segurança {{ .name }} não é suportada atualmente. Por favor verifique e tente novamente!" + +# common +ErrDemoEnvironment: "Servidor de demonstração, essa operação é proibida!" +ErrCmdTimeout: "Tempo de execução do comando esgotado!" +ErrEntrance: "Erro nas informações de entrada de segurança, por favor, verifique e tente novamente!" +ErrGroupIsDefault: "Grupo padrão não pode ser excluído" +ErrGroupIsInUse: "O grupo está em uso e não pode ser excluído." +ErrLocalDelete: "O nó local não pode ser excluído!" +ErrPortInUsed: "A porta {{ .name }} já está em uso!" +ErrInternalServerKey: "Erro interno do servidor:" +MasterNode: "Nó Mestre" + +# app +CustomAppStoreFileValid: "O pacote da loja de aplicativos deve estar no formato .tar.gz" +ErrFileNotFound: "Arquivo {{ .name }} não encontrado" +AppBackup: 'Backup de aplicação' +AppBackupPush: 'Transferir arquivo de backup de aplicação {{.file}} para o nó {{ .name }}' +ErrSourceTargetSame: 'O nó de origem e o nó de destino não podem ser os mesmos!' +AppInstall: 'Instalar aplicação {{ .name }} no nó {{ .targetNode }}' +AppInstallCheck: 'Verificar ambiente de instalação da aplicação' + +# backup +ErrBackupInUsed: "Esta conta de backup está em uso em tarefas agendadas e não pode ser excluída" +ErrBackupCheck: "Teste de conexão da conta de backup falhou {{ .err }}" +ErrBackupLocal: "A conta de backup do servidor local não suporta essa operação!" +ErrBackupPublic: "A conta de backup detectada não é pública, por favor verifique e tente novamente!" +ErrOSSConn: "Não foi possível obter a versão mais recente, verifique a conectividade externa do servidor." + +#license +LicenseCheck: 'Verificar se a licença está disponível' +ErrLicenseInUsed: 'Esta licença já está vinculada. Por favor, verifique e tente novamente!' +ErrLicenseExpired: 'Esta licença expirou. Por favor, verifique e tente novamente!' +ErrLicense: "Formato de licença inválido, por favor verifique e tente novamente!" +ErrLicenseCheck: "Falha na verificação da licença, por favor verifique e tente novamente!" +ErrXpackVersion: "Falha na verificação da licença, esta licença é restrita a uma versão específica, por favor verifique e tente novamente!" +ErrLicenseSave: "Falha ao salvar as informações da licença {{ .err }}, por favor tente novamente!" +ErrLicenseSync: "Falha ao sincronizar as informações da licença, nenhuma informação foi encontrada no banco de dados!" +ErrLicenseExist: "Este registro de licença já existe. Você pode ir diretamente para a página de licenças para vincular nós." +ErrXpackNotFound: "Esta parte é uma funcionalidade profissional, por favor importe a licença em Configurações do Painel - Licença." +ErrXpackExceptional: "Esta parte é uma funcionalidade profissional, por favor sincronize a licença em Configurações do Painel - Licença." +ErrXpackLost: "A licença atingiu o número máximo de tentativas, acesse [Configurações do Painel][Licença] e clique no botão de sincronização manual." +ErrDeviceLost: "Arquivos necessários para a verificação da licença estão faltando, por favor verifique e tente novamente!" +ErrDeviceErr: "O ambiente atual não corresponde ao ambiente de importação da licença. Por favor, edite a licença e reimporte!" +ErrXpackTimeout: "Requisição expirou, a conexão de rede pode estar instável, por favor tente novamente mais tarde!" +ErrUnbindMaster: "Foram encontrados nós na gestão de nós, remova-os primeiro e tente novamente!" +ErrFreeNodeLimit: "O número de nós da versão comunitária atingiu o limite gratuito, por favor acesse www.lxware.cn/1panel para comprar!" +ErrNodeBound: "Esta licença já está vinculada a outro nó, por favor verifique e tente novamente!" +ErrNodeBoundDelete: "Esta licença está vinculada e não suporta operações de exclusão. Por favor, verifique e tente novamente!" +ErrNodeBoundLimit: "O nó gratuito atual atingiu seu limite, por favor verifique e tente novamente!" +ErrLicenseFree: "Nós gratuitos só podem ser usados quando a licença está devidamente vinculada a um nó. Por favor verifique e tente novamente!" +ErrLicenseUnbind: "Nós da Edição Comunitária detectados para esta licença. Por favor desvincule em [Configurações do Painel - Licença] e tente novamente!" +ErrNoSuchNode: "As informações deste nó não foram encontradas, por favor verifique e tente novamente!" +ErrNodeUnbind: "Nó fora do escopo da licença detectado, por favor verifique e tente novamente!" +ErrNodeBind: "Este nó já está vinculado a uma licença, por favor verifique e tente novamente!" +ErrNodeLocalRollback: "O nó principal não suporta rollback direto. Por favor, execute manualmente o comando '1pctl restore' para fazer o rollback!" + +InvalidRequestBodyType: "Formato do corpo da requisição inválido. Verifique se o conteúdo está no formato correto e tente novamente!" +InvalidLicenseCodeType: "Formato do código de licença inválido. Verifique e tente novamente!" +LicenseNotFoundType: "Licença não encontrada. Não há registros correspondentes no sistema para a licença fornecida. Verifique e tente novamente!" +LicenseRevokedType: "A licença solicitada foi revogada e não pode mais ser usada. Verifique e tente novamente!" +LicenseExpiredType: "A licença expirou. Renove-a ou reimporte-a na seção Configurações do Painel - Licença antes de tentar novamente!" +LicenseProductMismatchType: "A licença não corresponde ao produto ou serviço solicitado!" +InvalidAssigneeType: "Informações do usuário ou dispositivo alvo para atribuição de licença inválidas. Verifique e tente novamente!" +LicenseUsageNotFoundType: "Nenhum registro de uso encontrado. Esta licença ainda não foi ativada ou usada. Verifique e tente novamente!" +LicenseUsageLimitExceededType: "Esta licença já está vinculada a outro nó. Verifique e tente novamente!" + +# alert +ErrAlertSync: "Erro na sincronização das informações de alerta, verifique e tente novamente!" + +#task +TaskStart: "{{ .name }} tarefa iniciada [START]" +TaskEnd: "{{ .name }} tarefa finalizada [COMPLETED]" +TaskFailed: "{{ .name }} tarefa falhou" +TaskTimeout: "{{ .name }} tempo esgotado" +TaskSuccess: "{{ .name }} tarefa bem-sucedida" +TaskRetry: "Iniciando tentativa {{ .name }}" +SubTaskSuccess: "{{ .name }} bem-sucedido" +SubTaskFailed: "{{ .name }} falhou: {{ .err }}" +TaskInstall: "Instalar" +TaskUpgrade: "Atualizar" +TaskSync: 'Sincronizar' +TaskSyncForNode: "Sincronizar Dados do Nó" +TaskBackup: "Backup" +SuccessStatus: "{{ .name }} bem-sucedido" +FailedStatus: "{{ .name }} falhou {{ .err }}" +Start: "Iniciar" +SubTask: "Subtarefa" +Skip: "Ignorar erros e continuar..." +PushAppInstallTaskToNode: "Enviar tarefa de instalação de aplicativo para o nó [{{ .name }}]" +TaskPush: "Enviar" +AppInstallTask: "Tarefa de instalação de aplicativo" +PushAppFailed: "Falha ao enviar tarefa de instalação de aplicativo" +Success: "Sucesso" + +#script +ScriptLibrary: "Biblioteca de Scripts" +RemoteScriptLibrary: "Biblioteca de Scripts Remota" +ScriptSyncSkip: "A biblioteca de scripts atual já é a versão mais recente!" +DownloadData: "Baixando arquivo data.yaml da biblioteca de scripts" +DownloadPackage: "Baixando pacote da biblioteca de scripts" +AnalyticCompletion: "Análise concluída, agora sincronizando com o banco de dados..." + +Node: "Nó" +SyncNode: "Sincronizar dados para o nó {{ .name }}" +LocalName: "O nome 'local' é usado apenas para identificação local do sistema" +SyncPackageData: "Empacotar dados de sincronização" +SyncPackageEncrypt: "Criptografia de pacote de dados" +SyncRequest: "Solicitar API de sincronização de nó" +SyncFailedRetry: "Tempo esgotado na sincronização de dados do nó (tentativa {{ .index }}), tentando novamente..." +SyncFailed: "Falha na sincronização, por favor sincronize manualmente na lista de nós!" +SyncSystemProxy: "Configurações de Proxy do Sistema" +SyncScripts: "Biblioteca de Scripts" +SyncBackupAccounts: "Contas de Backup" +SyncAlertSetting: "Configurações de Alerta" +SyncCustomApp: "Aplicativo Personalizado" +SyncLanguage: "Idioma do Sistema" + +#upgrade node +NodeUpgrade: "Atualizar nó {{ .name }}" +UpgradeCheck: "Verificar atualizações do nó" +UpgradeCheckLocal: "Nós locais não suportam atualização em lote, pulando..." +UpgradeCheckLatest: "O nó já está na versão mais recente, pulando..." +NewSSHClient: "Inicializando a conexão SSH" +BackupBeforeUpgrade: "Fazer backup dos dados antes da atualização" +UploadUpgradeFile: "Distribuir arquivos necessários para a atualização" +RestartAfterUpgrade: "Iniciar serviço após a atualização" + +#add node +MasterData: "Dados do Nó Mestre" +LoadSftpClient: "Carregar Cliente SFTP" +PackageMasterData: "Gerar pacote de backup do nó mestre" +UploadBackup: "Carregar dados de backup" +MvBackup: "Mover dados para diretório de backup" +TaskAddNode: "Adicionar nó" +LoadNodeArch: "Obter informações de arquitetura do nó" +LoadNodeArchDetail: "Arquitetura do nó mestre detectada: {{ .local }}, arquitetura do nó filho: {{ .node }}" +LoadNodeUpgradeDetail: "Usando o diretório de instalação histórico da versão v1: {{ .baseDir }}, porta de escuta do serviço: {{ .port }}" +SyncAgentBaseInfo: "Sincronizando dados básicos do nó" +GenerateSSLInfo: "Gerando informações SSL do nó" +ConnInfoNotMatch: "As informações de conexão não coincidem" +MakeAgentPackage: "Gerando pacote de instalação do nó" +SendAgent: "Enviando pacote de instalação do nó" +StartService: "Iniciando serviço" +NoBackupNode: "O nó de backup está vazio atualmente. Selecione um nó de backup para salvar e tente novamente!" + +#cmd +AppVersion: "Versão do aplicativo" +AppCommands: "Comandos relacionados ao aplicativo" +AppInit: "Inicializar aplicativo" +AppKeyVal: "Chave do aplicativo (somente suporta inglês)" +AppCreateFileErr: "Falha ao criar o arquivo {{ .name }} {{ .err }}" +AppCreateDirErr: "Falha ao criar a pasta {{ .name }} {{ .err }}" +AppMissKey: "Chave do aplicativo ausente, use -k para especificar" +AppMissVersion: "Versão do aplicativo ausente, use -v para especificar" +AppVersionExist: "A versão já existe!" +AppCreateSuccessful: "Criação bem-sucedida!" +AppWriteErr: "Falha ao escrever no arquivo {{ .name }} {{ .err }}" +SudoHelper: "Por favor, use {{ .cmd }} ou altere para o usuário root" +ListenIPCommands: "Trocar IP de escuta" +ListenIPv4: "Escutando em IPv4" +ListenIPv6: "Escutando em IPv6" +ListenChangeSuccessful: "Troca bem-sucedida! Agora escutando em {{ .value }}" +ResetCommands: "Redefinir informações do sistema" +ResetMFA: "Cancelar autenticação de dois fatores do 1Panel" +ResetHttps: "Cancelar login https do 1Panel" +ResetEntrance: "Cancelar entrada segura do 1Panel" +ResetIPs: "Cancelar restrições de IP autorizado do 1Panel" +ResetDomain: "Cancelar vinculação de domínio do 1Panel" +ResetPasskey: "Redefinir passkeys do 1Panel" +RestoreCommands: "Restaurar serviço e dados do 1Panel" +RestoreNoSuchFile: "Nenhum arquivo disponível para restauração" +RestoreStep1: "(1/5) Iniciando a restauração do serviço e dados do 1Panel a partir do diretório {{ .name }}..." +RestoreStep2: "(2/5) Restauração do binário do 1Panel bem-sucedida" +RestoreStep3: "(3/5) Restauração dos scripts do 1Panel bem-sucedida" +RestoreStep4: "(4/5) Restauração do serviço do 1Panel bem-sucedida" +RestoreStep5: "(5/5) Restauração dos dados do 1Panel bem-sucedida" +RestoreSuccessful: "Reversão bem-sucedida! Revertido para {{ .version }}, reiniciando serviço, por favor aguarde..." +UpdateCommands: "Atualizar informações do painel" +UpdateUser: "Atualizar usuário do painel" +UpdatePassword: "Atualizar senha do painel" +UpdatePort: "Atualizar porta do painel" +UpdateUserNull: "Erro: usuário do painel está vazio!" +UpdateUserBlank: "Erro: o usuário do painel contém espaços!" +UpdateUserFormat: "Erro: Formato de usuário do painel inválido! Apenas suporta inglês, chinês, números e _, comprimento de 3-30." +UpdateUserErr: "Erro: Falha ao atualizar o usuário do painel, {{ .err }}" +UpdateSuccessful: "Atualização bem-sucedida!" +UpdateUserResult: "Usuário do painel: {{ .name }}" +UpdatePasswordRead: "Erro: Falha ao ler as informações da senha do painel, {{ .err }}" +UpdatePasswordNull: "Erro: A senha do painel está vazia!" +UpdateUPasswordBlank: "Erro: A senha do painel contém espaços!" +UpdatePasswordFormat: "Erro: A senha do painel suporta apenas letras, números, caracteres especiais !@#$%*_,.?, comprimento de 8-30!" +UpdatePasswordLen: "Erro: Por favor, insira uma senha com mais de 6 caracteres!" +UpdatePasswordRe: "Confirmar senha:" +UpdatePasswordErr: "Erro: Falha ao atualizar a senha do painel, {{ .err }}" +UpdatePasswordSame: "Erro: As duas senhas não correspondem, por favor, verifique e tente novamente!" +UpdatePasswordResult: "Senha do painel: {{ .name }}" +UpdatePortFormat: "Erro: O número da porta deve estar entre 1 e 65535!" +UpdatePortUsed: "Erro: O número da porta já está em uso, por favor, verifique e tente novamente!" +UpdatePortErr: "Erro: Falha ao atualizar a porta do painel, {{ .err }}" +UpdatePortResult: "Porta do painel: {{ .name }}" +UpdatePortFirewallAdd: "Falha ao adicionar regra de porta ao firewall, {{ .err }}, por favor, adicione manualmente a porta {{ .name }} às regras do firewall." +UpdatePortFirewallDel: "Erro: Falha ao excluir a porta do firewall, {{ .err }}" +UpdatePortFirewallReload: "Falha ao recarregar o firewall, {{ .err }}, por favor, recarregue o firewall manualmente." +UserInfo: "Obter informações do painel" +UserInfoAddr: "Endereço do painel: " +UserInfoPassHelp: "Dica: Para alterar a senha, você pode executar o comando: " +DBConnErr: "Erro: Falha ao inicializar a conexão com o banco de dados, {{ .err }}" +SystemVersion: "versão: " +SystemMode: "modo: " + +#mobile app +ErrVerifyToken: 'Erro de verificação do token, por favor, reinicie e escaneie novamente.' +ErrInvalidToken: 'Token inválido, por favor, reinicie e escaneie novamente.' +ErrExpiredToken: 'O token expirou, por favor, reinicie e escaneie novamente.' + +#cluster +ErrMasterDelete: "Não é possível excluir o nó mestre, exclua os nós escravos primeiro." +ClusterNameIsExist: "O nome do cluster já existe." +AppStatusUnHealthy: "A aquisição do status do aplicativo está anormal, verifique o status dos nós de instalação na lista de nós." +MasterNodePortNotAvailable: "A verificação de conectividade da porta {{ .port }} do nó {{ .name }} falhou, verifique as configurações de firewall/grupo de segurança e o status do nó mestre." +ClusterMasterNotExist: "O nó mestre do cluster está desconectado, por favor, exclua os nós filhos." + +#ssl +ErrReqFailed: "{{.name}} solicitação falhou: {{ .err }}" + +#command +Name: "Название" +Command: "Команда" diff --git a/core/i18n/lang/ru.yaml b/core/i18n/lang/ru.yaml new file mode 100644 index 0000000..4db57e0 --- /dev/null +++ b/core/i18n/lang/ru.yaml @@ -0,0 +1,252 @@ +ErrInvalidParams: "Недопустимые параметры запроса: {{ .detail }}" +ErrTokenParse: "Ошибка генерации токена: {{ .detail }}" +ErrInitialPassword: "Неверный начальный пароль" +ErrInternalServer: "Внутренняя ошибка сервера: {{ .detail }}" +ErrRecordExist: "Запись уже существует" +ErrRecordNotFound: "Запись не найдена" +ErrStructTransform: "Ошибка преобразования типа: {{ .detail }}" +ErrNotLogin: "Пользователь не авторизован: {{ .detail }}" +ErrPasswordExpired: "Текущий пароль истёк: {{ .detail }}" +ErrNotSupportType: "Текущий тип не поддерживается: {{ .detail }}" +ErrProxy: "Ошибка запроса, проверьте состояние узла: {{ .detail }}" +ErrApiConfigStatusInvalid: "Доступ к API запрещён: {{ .detail }}" +ErrApiConfigKeyInvalid: "Ошибка ключа API: {{ .detail }}" +ErrApiConfigIPInvalid: "IP запроса API отсутствует в списке разрешённых: {{ .detail }}" +ErrApiConfigDisable: "Этот интерфейс запрещает вызовы API: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "Ошибка временной метки API: {{ .detail }}" + +# request +ErrNoSuchHost: "Не удалось найти запрашиваемый сервер {{ .err }}" +ErrHttpReqNotFound: "Не удалось найти запрашиваемый ресурс {{ .err }}" +ErrHttpReqFailed: "Ошибка запроса {{ .err }}" +ErrHttpReqTimeOut: "Время ожидания запроса истекло {{ .err }}" +ErrCreateHttpClient: "Ошибка создания запроса {{ .err }}" +ErrProxySetting: "Информация о прокси-сервере недоступна {{ .err }}, проверьте и повторите попытку!" +ErrEntranceFormat: "Защищенный вход {{ .name }} в настоящее время не поддерживается. Пожалуйста, проверьте и повторите попытку!" + +# common +ErrDemoEnvironment: "Демонстрационный сервер, эта операция запрещена!" +ErrCmdTimeout: "Время выполнения команды истекло!" +ErrEntrance: "Ошибка информации о безопасном входе, проверьте и повторите попытку!" +ErrGroupIsDefault: "Группу по умолчанию нельзя удалить" +ErrGroupIsInUse: "Группа используется и не может быть удалена." +ErrLocalDelete: "Локальный узел нельзя удалить!" +ErrPortInUsed: "Порт {{ .name }} уже используется!" +ErrInternalServerKey: "Внутренняя ошибка сервера:" +MasterNode: "Главный узел" + +# app +CustomAppStoreFileValid: "Пакет магазина приложений должен быть в формате .tar.gz" +ErrFileNotFound: "Файл {{ .name }} не найден" +AppBackup: 'Резервная копия приложения' +AppBackupPush: 'Передать файл резервной копии приложения {{.file}} на узел {{ .name }}' +ErrSourceTargetSame: 'Исходный узел и целевой узел не могут быть одинаковыми!' +AppInstall: 'Установить приложение {{ .name }} на узел {{ .targetNode }}' +AppInstallCheck: 'Проверить среду установки приложения' + +# backup +ErrBackupInUsed: "Эта учетная запись резервного копирования используется в запланированных задачах и не может быть удалена" +ErrBackupCheck: "Ошибка тестирования соединения учетной записи резервного копирования {{ .err }}" +ErrBackupLocal: "Локальная учетная запись резервного копирования не поддерживает эту операцию!" +ErrBackupPublic: "Обнаружено, что эта учетная запись резервного копирования не является публичной, проверьте и повторите попытку!" +ErrOSSConn: "Не удалось получить последнюю версию, проверьте подключение сервера к внешней сети." + +#license +LicenseCheck: 'Проверить доступность лицензии' +ErrLicenseInUsed: 'Лицензия уже привязана. Пожалуйста, проверьте и повторите попытку!' +ErrLicenseExpired: 'Срок действия лицензии истек. Пожалуйста, проверьте и повторите попытку!' +ErrLicense: "Неверный формат лицензии, проверьте и повторите попытку!" +ErrLicenseCheck: "Ошибка проверки лицензии, проверьте и повторите попытку!" +ErrXpackVersion: "Ошибка проверки лицензии, лицензия ограничена по версии, проверьте и повторите попытку!" +ErrLicenseSave: "Ошибка сохранения информации о лицензии {{ .err }}, повторите попытку!" +ErrLicenseSync: "Ошибка синхронизации информации о лицензии, информация не найдена в базе данных!" +ErrLicenseExist: "Данная лицензия уже существует. Вы можете перейти на страницу лицензий для привязки узлов." +ErrXpackNotFound: "Эта часть доступна только в профессиональной версии, сначала импортируйте лицензию в разделе Настройки панели - Лицензия." +ErrXpackExceptional: "Эта часть доступна только в профессиональной версии, сначала синхронизируйте лицензию в разделе Настройки панели - Лицензия." +ErrXpackLost: "Лицензия достигла максимального количества попыток, перейдите в [Настройки панели][Лицензия] и нажмите кнопку синхронизации вручную." +ErrDeviceLost: "Необходимые файлы для проверки лицензии отсутствуют, пожалуйста, проверьте и попробуйте снова!" +ErrDeviceErr: "Текущая среда не соответствует среде импорта лицензии. Отредактируйте лицензию и повторите импорт!" +ErrXpackTimeout: "Время ожидания запроса истекло, возможно нестабильное сетевое соединение, повторите попытку позже!" +ErrUnbindMaster: "В управлении узлами обнаружены узлы, сначала удалите их и повторите попытку!" +ErrFreeNodeLimit: "Количество узлов в версии сообщества достигло бесплатного лимита, посетите www.lxware.cn/1panel для покупки!" +ErrNodeBound: "Эта лицензия уже связана с другим узлом, проверьте и повторите попытку!" +ErrNodeBoundDelete: "Эта лицензия привязана и не поддерживает операции удаления. Пожалуйста, проверьте и попробуйте снова!" +ErrNodeBoundLimit: "Текущий бесплатный узел достиг предела, пожалуйста, проверьте и попробуйте снова!" +ErrLicenseFree: "Бесплатные узлы доступны только при правильной привязке лицензии к узлу. Проверьте и повторите попытку!" +ErrLicenseUnbind: "Обнаружены узлы Community Edition для этой лицензии. Отвяжите в [Настройки панели - Лицензия] и повторите попытку!" +ErrNoSuchNode: "Информация об узле не найдена, проверьте и повторите попытку!" +ErrNodeUnbind: "Обнаружен узел вне области действия лицензии, проверьте и повторите попытку!" +ErrNodeBind: "Этот узел уже связан с лицензией, проверьте и повторите попытку!" +ErrNodeLocalRollback: "Основной узел не поддерживает прямой откат. Пожалуйста, вручную выполните команду '1pctl restore' для отката!" + +InvalidRequestBodyType: "Неверный формат тела запроса. Проверьте, соответствует ли содержимое требуемому формату, и повторите попытку!" +InvalidLicenseCodeType: "Указан неверный формат лицензионного кода. Проверьте и повторите попытку!" +LicenseNotFoundType: "Лицензия не найдена. В системе нет записей, соответствующих указанной лицензии. Проверьте и повторите попытку!" +LicenseRevokedType: "Запрошенная лицензия аннулирована и больше не может быть использована. Проверьте и повторите попытку!" +LicenseExpiredType: "Срок действия лицензии истек. Продлите ее или повторно импортируйте в разделе Настройки панели - Лицензия перед повторной попыткой!" +LicenseProductMismatchType: "Лицензия не соответствует запрошенному продукту или услуге!" +InvalidAssigneeType: "Некорректная информация о целевом пользователе или устройстве для назначения лицензии. Проверьте и повторите попытку!" +LicenseUsageNotFoundType: "Записи об использовании отсутствуют. Эта лицензия еще не активирована или не использовалась. Проверьте и повторите попытку!" +LicenseUsageLimitExceededType: "Эта лицензия уже привязана к другому узлу. Проверьте и повторите попытку!" + +# alert +ErrAlertSync: "Ошибка синхронизации информации об оповещениях, пожалуйста, проверьте и повторите попытку!" + +# task +TaskStart: "{{ .name }} задача начата [START]" +TaskEnd: "{{ .name }} задача завершена [COMPLETED]" +TaskFailed: "{{ .name }} задача не выполнена" +TaskTimeout: "{{ .name }} истекло время" +TaskSuccess: "{{ .name }} задача выполнена успешно" +TaskRetry: "Начало повторной попытки {{ .name }}" +SubTaskSuccess: "{{ .name }} успешно" +SubTaskFailed: "{{ .name }} не удалось: {{ .err }}" +TaskInstall: "Установить" +TaskUpgrade: "Обновить" +TaskSync: 'Синхронизация' +TaskSyncForNode: "Синхронизировать Данные Узла" +TaskBackup: "Резервная копия" +SuccessStatus: "{{ .name }} успешно" +FailedStatus: "{{ .name }} не удалось {{ .err }}" +Start: "Начать" +SubTask: "Подзадача" +Skip: "Пропустить ошибки и продолжить..." +TaskPush: "Отправить" +AppInstallTask: "Задача установки приложения" +PushAppFailed: "Не удалось отправить задачу установки приложения" +Success: "Успешно" + +#script +ScriptLibrary: "Библиотека скриптов" +RemoteScriptLibrary: "Удаленная Библиотека Скриптов" +ScriptSyncSkip: "Текущая библиотека скриптов уже является последней версией!" +DownloadData: "Загрузка файла библиотеки скриптов data.yaml" +DownloadPackage: "Загрузка архива библиотеки скриптов" +AnalyticCompletion: "Анализ завершён, начинается синхронизация с базой данных..." + +Node: "Узел" +SyncNode: "Синхронизировать данные с узлом {{ .name }}" +LocalName: "Имя 'local' используется только для локальной идентификации в системе" +SyncPackageData: "Упаковка данных синхронизации" +SyncPackageEncrypt: "Шифрование пакета данных" +SyncRequest: "Запрос API синхронизации узла" +SyncFailedRetry: "Таймаут синхронизации данных узла (попытка {{ .index }}), повторная попытка..." +SyncFailed: "Ошибка синхронизации, выполните ручную синхронизацию в списке узлов!" +SyncSystemProxy: "Настройки системного прокси" +SyncScripts: "Библиотека скриптов" +SyncBackupAccounts: "Резервные аккаунты" +SyncAlertSetting: "Настройки оповещений" +SyncCustomApp: "Пользовательское приложение" +SyncLanguage: "Язык системы" + +#upgrade node +NodeUpgrade: "Обновление узла {{ .name }}" +UpgradeCheck: "Проверить обновления узла" +UpgradeCheckLocal: "Локальные узлы не поддерживают массовое обновление, пропуск..." +UpgradeCheckLatest: "Узел уже имеет последнюю версию, пропуск..." +NewSSHClient: "Инициализация SSH-соединения" +BackupBeforeUpgrade: "Резервное копирование данных перед обновлением" +UploadUpgradeFile: "Распределение файлов обновления" +RestartAfterUpgrade: "Запуск службы после обновления" + +#add node +MasterData: "Данные главного узла" +LoadSftpClient: "Загрузить SFTP-клиент" +PackageMasterData: "Создать архив резервной копии главного узла" +UploadBackup: "Загрузить резервные данные" +MvBackup: "Переместить данные в резервный каталог" +TaskAddNode: "Добавить узел" +LoadNodeArch: "Получить информацию об архитектуре узла" +LoadNodeArchDetail: "Обнаружена архитектура главного узла: {{ .local }}, архитектура дочернего узла: {{ .node }}" +LoadNodeUpgradeDetail: "Используется каталог установки исторической версии v1: {{ .baseDir }}, порт прослушивания службы: {{ .port }}" +SyncAgentBaseInfo: "Синхронизировать базовую информацию узла" +GenerateSSLInfo: "Сгенерировать SSL информацию узла" +ConnInfoNotMatch: "Информация о соединении не совпадает" +MakeAgentPackage: "Сгенерировать установочный пакет узла" +SendAgent: "Отправить установочный пакет узла" +StartService: "Запустить сервис" +NoBackupNode: "Резервный узел в настоящее время пуст. Выберите резервный узел для сохранения и повторите попытку!" + +#cmd +AppVersion: "Версия приложения" +AppCommands: "Команды приложения" +AppInit: "Инициализация приложения" +AppKeyVal: "Ключ приложения (поддерживаются только английские символы)" +AppCreateFileErr: "Не удалось создать файл {{ .name }} {{ .err }}" +AppCreateDirErr: "Не удалось создать папку {{ .name }} {{ .err }}" +AppMissKey: "Отсутствует ключ приложения, используйте -k для указания" +AppMissVersion: "Отсутствует версия приложения, используйте -v для указания" +AppVersionExist: "Версия уже существует!" +AppCreateSuccessful: "Успешно создано!" +AppWriteErr: "Не удалось записать файл {{ .name }} {{ .err }}" +SudoHelper: "Пожалуйста, используйте {{ .cmd }} или переключитесь на пользователя root" +ListenIPCommands: "Переключить IP для прослушивания" +ListenIPv4: "Прослушивание IPv4" +ListenIPv6: "Прослушивание IPv6" +ListenChangeSuccessful: "Переключено успешно! Теперь прослушиваем {{ .value }}" +ResetCommands: "Сбросить информацию о системе" +ResetMFA: "Отменить двухфакторную аутентификацию 1Panel" +ResetHttps: "Отменить вход в 1Panel через https" +ResetEntrance: "Отменить безопасный вход 1Panel" +ResetIPs: "Отменить ограничения авторизованных IP для 1Panel" +ResetDomain: "Отменить привязку домена доступа 1Panel" +ResetPasskey: "Сбросить passkey 1Panel" +RestoreCommands: "Откатить сервисы и данные 1Panel" +RestoreNoSuchFile: "Нет доступных для отката файлов" +RestoreStep1: "(1/5)Начинаем откат сервисов и данных 1Panel из каталога {{ .name }}..." +RestoreStep2: "(2/5)Откат бинарного файла 1Panel завершён" +RestoreStep3: "(3/5)Откат скриптов 1Panel завершён" +RestoreStep4: "(4/5)Откат сервисов 1Panel завершён" +RestoreStep5: "(5/5)Откат данных 1Panel завершён" +RestoreSuccessful: "Откат успешен! Откатился к {{ .version }}, перезапускаю службу, пожалуйста, подождите..." +UpdateCommands: "Изменить информацию панели" +UpdateUser: "Изменить пользователя панели" +UpdatePassword: "Изменить пароль панели" +UpdatePort: "Изменить порт панели" +UpdateUserNull: "Ошибка: введён пользователь панели пуст!" +UpdateUserBlank: "Ошибка: введён пользователь панели с пробелами!" +UpdateUserFormat: "Ошибка: неверный формат имени пользователя панели! Поддерживаются только английские, китайские символы, цифры и _ , длина 3-30 символов" +UpdateUserErr: "Ошибка: не удалось изменить пользователя панели, {{ .err }}" +UpdateSuccessful: "Успешно изменено!" +UpdateUserResult: "Пользователь панели: {{ .name }}" +UpdatePasswordRead: "Ошибка: не удалось прочитать информацию о пароле панели, {{ .err }}" +UpdatePasswordNull: "Ошибка: введён пароль пуст!" +UpdateUPasswordBlank: "Ошибка: введён пароль с пробелами!" +UpdatePasswordFormat: "Ошибка: пароль должен содержать только буквы, цифры, специальные символы (!@#$%*_,.?), длина 8-30 символов!" +UpdatePasswordLen: "Ошибка: пароль должен быть длиной не менее 6 символов!" +UpdatePasswordRe: "Подтверждение пароля:" +UpdatePasswordErr: "Ошибка: не удалось изменить пароль панели, {{ .err }}" +UpdatePasswordSame: "Ошибка: пароли не совпадают, пожалуйста, проверьте и попробуйте снова!" +UpdatePasswordResult: "Пароль панели: {{ .name }}" +UpdatePortFormat: "Ошибка: введённый порт должен быть в пределах от 1 до 65535!" +UpdatePortUsed: "Ошибка: этот порт уже занят, пожалуйста, проверьте и попробуйте снова!" +UpdatePortErr: "Ошибка: не удалось изменить порт панели, {{ .err }}" +UpdatePortResult: "Порт панели: {{ .name }}" +UpdatePortFirewallAdd: "Не удалось добавить правило фаервола для порта, {{ .err }}, пожалуйста, вручную добавьте порт {{ .name }} в правила фаервола." +UpdatePortFirewallDel: "Ошибка: не удалось удалить правило фаервола для порта, {{ .err }}" +UpdatePortFirewallReload: "Не удалось перезагрузить фаервол, {{ .err }}, пожалуйста, вручную перезагрузите фаервол." +UserInfo: "Получить информацию о панели" +UserInfoAddr: "Адрес панели:" +UserInfoPassHelp: "Подсказка: для изменения пароля можно выполнить команду:" +DBConnErr: "Ошибка: не удалось инициализировать соединение с базой данных, {{ .err }}" +SystemVersion: "Версия:" +SystemMode: "Режим:" + +#mobile app +ErrVerifyToken: 'шибка проверки токена, пожалуйста, сбросьте и отсканируйте снова.' +ErrInvalidToken: 'Неверный токен, пожалуйста, сбросьте и отсканируйте снова.' +ErrExpiredToken: 'Токен истек, пожалуйста, сбросьте и отсканируйте снова.' + +#cluster +ErrMasterDelete: "Невозможно удалить основной узел, сначала удалите подчиненные узлы." +ClusterNameIsExist: "Имя кластера уже существует." +AppStatusUnHealthy: "Получение статуса приложения аномально, пожалуйста, проверьте статус узлов установки в списке узлов." +MasterNodePortNotAvailable: "Проверка подключения порта {{ .port }} узла {{ .name }} не удалась, пожалуйста, проверьте настройки брандмауэра/группы безопасности и статус главного узла." +ClusterMasterNotExist: "Основной узел кластера отключен, пожалуйста, удалите дочерние узлы." + +#ssl +ErrReqFailed: "{{.name}} запрос не удался: {{ .err }}" + +#command +Name: "Название" +Command: "Команда" diff --git a/core/i18n/lang/tr.yaml b/core/i18n/lang/tr.yaml new file mode 100644 index 0000000..7d3361c --- /dev/null +++ b/core/i18n/lang/tr.yaml @@ -0,0 +1,252 @@ +ErrInvalidParams: "Geçersiz istek parametreleri: {{ .detail }}" +ErrTokenParse: "Token oluşturma hatası: {{ .detail }}" +ErrInitialPassword: "Yanlış başlangıç şifresi" +ErrInternalServer: "İç sunucu hatası: {{ .detail }}" +ErrRecordExist: "Kayıt zaten mevcut" +ErrRecordNotFound: "Kayıt bulunamadı" +ErrStructTransform: "Tip dönüştürme başarısız: {{ .detail }}" +ErrNotLogin: "Kullanıcı giriş yapmamış: {{ .detail }}" +ErrPasswordExpired: "Mevcut şifrenin süresi dolmuş: {{ .detail }}" +ErrNotSupportType: "Mevcut tip desteklenmiyor: {{ .detail }}" +ErrProxy: "İstek hatası, lütfen bu düğümün durumunu kontrol edin: {{ .detail }}" +ErrApiConfigStatusInvalid: "API erişimi yasaklandı: {{ .detail }}" +ErrApiConfigKeyInvalid: "API anahtarı hatası: {{ .detail }}" +ErrApiConfigIPInvalid: "API istek IP'si beyaz listede değil: {{ .detail }}" +ErrApiConfigDisable: "Bu arayüz API çağrılarını yasaklıyor: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "API zaman damgası hatası: {{ .detail }}" + +# request +ErrNoSuchHost: "İstenen sunucu bulunamıyor {{ .err }}" +ErrHttpReqNotFound: "İstenen kaynak bulunamıyor {{ .err }}" +ErrHttpReqFailed: "İstek başarısız {{ .err }}" +ErrHttpReqTimeOut: "İstek zaman aşımına uğradı {{ .err }}" +ErrCreateHttpClient: "İstek oluşturma başarısız {{ .err }}" +ErrProxySetting: "Proxy sunucu bilgisi kullanılamıyor {{ .err }}, lütfen kontrol edip tekrar deneyin!" +ErrEntranceFormat: "{{ .name }} güvenlik girişi şu anda desteklenmiyor. Lütfen kontrol edip tekrar deneyin!" + +# common +ErrDemoEnvironment: "Demo sunucu, bu işlem yasak!" +ErrCmdTimeout: "Komut çalıştırma zaman aşımı!" +ErrEntrance: "Güvenlik girişi bilgi hatası, lütfen kontrol edip tekrar deneyin!" +ErrGroupIsDefault: "Varsayılan grup, silinemez" +ErrGroupIsInUse: "Grup kullanımda ve silinemez." +ErrLocalDelete: "Yerel düğüm silinemez!" +ErrPortInUsed: "{{ .name }} portu zaten kullanımda!" +ErrInternalServerKey: "İç sunucu hatası:" +MasterNode: "Ana Düğüm" + +# app +CustomAppStoreFileValid: "Uygulama mağazası paketi .tar.gz formatında olmalıdır" +ErrFileNotFound: "{{ .name }} dosyası mevcut değil" +AppBackup: 'Uygulama yedekleme' +AppBackupPush: 'Uygulama yedek dosyası {{.file}} düğüm {{ .name }} a aktar' +ErrSourceTargetSame: 'Kaynak düğüm ve hedef düğüm aynı olamaz!' +AppInstall: 'Düğüm {{ .targetNode }} üzerine {{ .name }} uygulamasını yükle' +AppInstallCheck: 'Uygulama kurulum ortamını kontrol et' + +# backup +ErrBackupInUsed: "Bu yedekleme hesabı zamanlanmış görevlerde kullanılıyor ve silinemez" +ErrBackupCheck: "Yedekleme hesabı bağlantı testi başarısız {{ .err }}" +ErrBackupLocal: "Yerel sunucu yedekleme hesabı bu işlemi desteklemiyor!" +ErrBackupPublic: "Bu yedekleme hesabının herkese açık olmadığı tespit edildi, lütfen kontrol edip tekrar deneyin!" +ErrOSSConn: "En son sürüm alınamıyor, lütfen sunucunun dış ağ bağlantısını kontrol edin." + +#license +LicenseCheck: 'Lisansın mevcut olup olmadığını kontrol et' +ErrLicenseInUsed: 'Lisans zaten bağlı. Lütfen kontrol edip tekrar deneyin!' +ErrLicenseExpired: 'Lisansın süresi dolmuş. Lütfen kontrol edip tekrar deneyin!' +ErrLicense: "Lisans format hatası, lütfen kontrol edip tekrar deneyin!" +ErrLicenseCheck: "Lisans doğrulama başarısız, lütfen kontrol edip tekrar deneyin!" +ErrXpackVersion: "Lisans doğrulama başarısız, bu lisans sürüm sınırlıdır, içe aktarılamıyor, lütfen kontrol edip tekrar deneyin!" +ErrLicenseSave: "Lisans bilgilerini kaydetme başarısız, hata {{ .err }}, lütfen tekrar deneyin!" +ErrLicenseSync: "Lisans senkronizasyonu başarısız, veritabanında lisans bilgisi tespit edilmedi!" +ErrLicenseExist: "Bu lisans kaydı zaten mevcut. Düğüm bağlama için doğrudan lisans sayfasına gidebilirsiniz." +ErrXpackNotFound: "Bu bölüm profesyonel sürüm içindir, lütfen Panel Ayarları - Lisans'ta lisansı içe aktarın" +ErrXpackExceptional: "Bu bölüm profesyonel sürüm içindir, lütfen Panel Ayarları - Lisans'ta lisans durumunu senkronize edin" +ErrXpackLost: "Lisans maksimum yeniden deneme sayısına ulaştı, lütfen [Panel Ayarları] [Lisans] sayfasına gidin ve profesyonel özelliklerin doğru çalışması için manuel olarak senkronize butonuna tıklayın" +ErrDeviceLost: "Lisans doğrulama için gerekli dosyalar eksik, lütfen kontrol edip tekrar deneyin!" +ErrXpackTimeout: "İstek zaman aşımı, ağ bağlantısı kararsız olabilir, lütfen daha sonra tekrar deneyin!" +ErrUnbindMaster: "Düğüm yönetiminde düğümler tespit edildi, mevcut lisansın bağı çözülemiyor, lütfen önce düğümleri kaldırın ve tekrar deneyin!" +ErrFreeNodeLimit: "Topluluk sürümü düğüm limiti ulaşıldı, lütfen www.lxware.cn/1panel adresinden satın alın ve tekrar deneyin!" +ErrNodeBound: "Bu lisans başka bir düğüme bağlı, lütfen kontrol edip tekrar deneyin!" +ErrNodeBoundDelete: "Bu lisans bağlı ve silme işlemlerini desteklemiyor. Lütfen kontrol edip tekrar deneyin!" +ErrNodeBoundLimit: "Mevcut ücretsiz düğüm limitine ulaştı, lütfen kontrol edip tekrar deneyin!" +ErrLicenseFree: "Ücretsiz düğümler yalnızca lisans bir düğüme düzgün şekilde bağlandığında kullanılabilir. Lütfen doğrulayın ve tekrar deneyin!" +ErrLicenseUnbind: "Bu lisans için Topluluk Sürümü düğümleri tespit edildi. Lütfen [Panel Ayarları - Lisans]'ta bağı çözün ve tekrar deneyin!" +ErrNoSuchNode: "Düğüm bilgisi bulunamadı, lütfen kontrol edip tekrar deneyin!" +ErrNodeUnbind: "Bu düğüm lisans bağlama aralığında değil, lütfen kontrol edip tekrar deneyin!" +ErrNodeBind: "Bu düğüm zaten bir lisansa bağlı, lütfen kontrol edip tekrar deneyin!" +ErrNodeLocalRollback: "Ana düğüm doğrudan geri alma desteklemiyor. Lütfen geri almak için '1pctl restore' komutunu manuel olarak çalıştırın!" + +InvalidRequestBodyType: "Geçersiz istek gövdesi formatı, lütfen içeriğin format gereksinimlerine uygun olduğunu kontrol edip tekrar deneyin!" +InvalidLicenseCodeType: "Sağlanan lisans kodu formatı geçersiz, lütfen kontrol edip tekrar deneyin!" +LicenseNotFoundType: "Lisans bulunamadı, sistemde sağlanan lisansla eşleşen bir kayıt yok. Lütfen kontrol edip tekrar deneyin!" +LicenseRevokedType: "İstenen lisans iptal edilmiş ve artık kullanılamaz. Lütfen kontrol edip tekrar deneyin!" +LicenseExpiredType: "Lisansın süresi doldu. Lütfen yenileyin veya Panel Ayarları - Lisans bölümünden yeniden içe aktarın!" +LicenseProductMismatchType: "Lisans, istenen ürün veya hizmetle eşleşmiyor!" +InvalidAssigneeType: "Lisans ataması için hedef kullanıcı veya cihaz bilgisi geçersiz. Lütfen kontrol edip tekrar deneyin!" +LicenseUsageNotFoundType: "Kullanım kaydı bulunamadı. Bu lisans henüz etkinleştirilmemiş veya kullanılmamış. Lütfen kontrol edip tekrar deneyin!" +LicenseUsageLimitExceededType: "Bu lisans zaten başka bir düğüme bağlı. Lütfen kontrol edip tekrar deneyin!" + +# alert +ErrAlertSync: "Uyarı bilgisi senkronizasyon hatası, lütfen kontrol edip tekrar deneyin!" + +# task +TaskStart: "{{ .name }} görevi başladı [BAŞLANGIÇ]" +TaskEnd: "{{ .name }} görevi bitti [TAMAMLANDI]" +TaskFailed: "{{ .name }} görevi başarısız" +TaskTimeout: "{{ .name }} zaman aşımına uğradı" +TaskSuccess: "{{ .name }} görevi başarılı" +TaskRetry: "{{ .name }} yeniden denenmeye başlanıyor" +SubTaskSuccess: "{{ .name }} başarılı" +SubTaskFailed: "{{ .name }} başarısız: {{ .err }}" +TaskInstall: "Kurulum" +TaskUpgrade: "Yükseltme" +TaskSync: 'Senkronize' +TaskSyncForNode: "Düğüm Verilerini Senkronize Et" +TaskBackup: "Yedekleme" +SuccessStatus: "{{ .name }} başarılı" +FailedStatus: "{{ .name }} başarısız {{ .err }}" +Start: "Başla" +SubTask: "Alt görev" +Skip: "Hataları atla ve devam et..." +PushAppInstallTaskToNode: "Uygulama kurulum görevini düğüme [{{ .name }}] gönder" +TaskPush: "Gönder" +AppInstallTask: "Uygulama kurulum görevi" +PushAppFailed: "Uygulama kurulum görevi gönderilemedi" +Success: "Başarılı" + +#script +ScriptLibrary: "Betik Kütüphanesi" +RemoteScriptLibrary: "Uzak Komut Dosyası Kütüphanesi" +ScriptSyncSkip: "Mevcut betik kütüphanesi zaten en son sürüm!" +DownloadData: "Betik kütüphanesi dosyası data.yaml indiriliyor" +DownloadPackage: "Betik kütüphanesi paketi indiriliyor" +AnalyticCompletion: "Analiz tamamlandı, şimdi veritabanına senkronize ediliyor..." + +Node: "Düğüm" +SyncNode: "Verileri {{ .name }} düğümüne senkronize et" +LocalName: "'local' adı yalnızca sistem yerel tanımlaması için kullanılır" +SyncPackageData: "Paket senkronizasyon verisi" +SyncPackageEncrypt: "Veri paketi şifreleme" +SyncRequest: "Düğüm senkronizasyon API'si isteği" +SyncFailedRetry: "Düğüm veri senkronizasyonu zaman aşımı (deneme {{ .index }}), yeniden deneniyor..." +SyncFailed: "Senkronizasyon başarısız, lütfen düğüm listesinde manuel senkronize edin!" +SyncSystemProxy: "Sistem Vekil Sunucu Ayarları" +SyncScripts: "Komut Dosyası Kütüphanesi" +SyncBackupAccounts: "Yedek Hesaplar" +SyncAlertSetting: "Uyarı Ayarları" +SyncCustomApp: "Özel Uygulama" +SyncLanguage: "Sistem Dili" + +#upgrade node +NodeUpgrade: "Düğümü yükselt {{ .name }}" +UpgradeCheck: "Düğüm güncellemelerini kontrol et" +UpgradeCheckLocal: "Yerel düğümler toplu yükseltmeleri desteklemiyor, atlanıyor..." +UpgradeCheckLatest: "Düğüm zaten en son sürüm, atlanıyor..." +NewSSHClient: "SSH bağlantısı başlatılıyor" +BackupBeforeUpgrade: "Yükseltme öncesi veri yedekleme" +UploadUpgradeFile: "Yükseltme dosyalarını dağıt" +RestartAfterUpgrade: "Yükseltme sonrası servisi başlat" + +#add node +MasterData: "Ana Düğüm Verisi" +LoadSftpClient: "SFTP İstemcisini Yükle" +PackageMasterData: "Ana düğüm yedek paketi oluştur" +UploadBackup: "Yedek verileri yükle" +MvBackup: "Verileri yedek dizinine taşı" +TaskAddNode: "Düğüm ekle" +LoadNodeArch: "Düğüm mimari bilgilerini al" +LoadNodeArchDetail: "Ana düğüm mimarisi tespit edildi: {{ .local }}, alt düğüm mimarisi: {{ .node }}" +LoadNodeUpgradeDetail: "v1 sürüm geçmiş kurulum dizini kullanılıyor: {{ .baseDir }}, servis dinleme portu: {{ .port }}" +SyncAgentBaseInfo: "Temel düğüm bilgilerini senkronize et" +GenerateSSLInfo: "Düğüm SSL bilgilerini oluştur" +ConnInfoNotMatch: "Bağlantı bilgisi uyuşmuyor" +MakeAgentPackage: "Düğüm kurulum paketini oluştur" +SendAgent: "Düğüm kurulum paketini dağıt" +StartService: "Servisi başlat" +NoBackupNode: "Yedek düğüm şu anda boş. Lütfen kaydetmek için bir yedek düğüm seçin ve tekrar deneyin!" + +#cmd +AppVersion: "Uygulama sürümü" +AppCommands: "Uygulama ile ilgili komutlar" +AppInit: "Uygulamayı başlat" +AppKeyVal: "Uygulama anahtarı (sadece İngilizce destekler)" +AppCreateFileErr: "Dosya {{ .name }} oluşturma başarısız {{ .err }}" +AppCreateDirErr: "Klasör {{ .name }} oluşturma başarısız {{ .err }}" +AppMissKey: "Uygulama anahtarı eksik, belirtmek için -k kullanın" +AppMissVersion: "Uygulama sürümü eksik, belirtmek için -v kullanın" +AppVersionExist: "Sürüm zaten mevcut!" +AppCreateSuccessful: "Oluşturma başarılı!" +AppWriteErr: "Dosya {{ .name }} yazma başarısız {{ .err }}" +SudoHelper: "Lütfen {{ .cmd }} kullanın veya root kullanıcısına geçin" +ListenIPCommands: "Dinleme IP'sini değiştir" +ListenIPv4: "IPv4'te dinle" +ListenIPv6: "IPv6'da dinle" +ListenChangeSuccessful: "Değişiklik başarılı! Şimdi {{ .value }} üzerinde dinleniyor" +ResetCommands: "Sistem bilgilerini sıfırla" +ResetMFA: "1Panel iki faktörlü kimlik doğrulamayı iptal et" +ResetHttps: "1Panel https girişini iptal et" +ResetEntrance: "1Panel güvenli girişi iptal et" +ResetIPs: "1Panel yetkili IP kısıtlamalarını iptal et" +ResetDomain: "1Panel alan adı bağlamasını iptal et" +ResetPasskey: "1Panel passkey'lerini sıfırla" +RestoreCommands: "1Panel servisini ve verilerini geri al" +RestoreNoSuchFile: "Geri alma için mevcut dosya yok" +RestoreStep1: "(1/5) {{ .name }} dizininden 1Panel servisi ve verilerin geri alınması başlatılıyor..." +RestoreStep2: "(2/5) 1Panel binary geri alımı başarılı" +RestoreStep3: "(3/5) 1Panel betik geri alımı başarılı" +RestoreStep4: "(4/5) 1Panel servis geri alımı başarılı" +RestoreStep5: "(5/5) 1Panel veri geri alımı başarılı" +RestoreSuccessful: "Geri alma başarılı! {{ .version }} sürümüne geri alındı, hizmet yeniden başlatılıyor, lütfen bekleyin..." +UpdateCommands: "Panel bilgilerini güncelle" +UpdateUser: "Panel kullanıcısını güncelle" +UpdatePassword: "Panel şifresini güncelle" +UpdatePort: "Panel portunu güncelle" +UpdateUserNull: "Hata: panel kullanıcısı boş!" +UpdateUserBlank: "Hata: panel kullanıcısı boşluk içeriyor!" +UpdateUserFormat: "Hata: Geçersiz panel kullanıcı formatı! Sadece İngilizce, Çince, sayılar ve _ destekler, uzunluk 3-30" +UpdateUserErr: "Hata: Panel kullanıcısı güncelleme başarısız, {{ .err }}" +UpdateSuccessful: "Güncelleme başarılı!" +UpdateUserResult: "Panel kullanıcısı: {{ .name }}" +UpdatePasswordRead: "Hata: Panel şifre bilgilerini okuma başarısız, {{ .err }}" +UpdatePasswordNull: "Hata: Panel şifresi boş!" +UpdateUPasswordBlank: "Hata: Panel şifresi boşluk içeriyor!" +UpdatePasswordFormat: "Hata: Panel şifresi sadece harfler, sayılar, özel karakterler !@#$%*_,.? destekler, uzunluk 8-30!" +UpdatePasswordLen: "Hata: Lütfen 6 karakterden uzun bir şifre girin!" +UpdatePasswordRe: "Şifreyi onayla:" +UpdatePasswordErr: "Hata: Panel şifresi güncelleme başarısız, {{ .err }}" +UpdatePasswordSame: "Hata: İki şifre eşleşmiyor, lütfen kontrol edip tekrar deneyin!" +UpdatePasswordResult: "Panel şifresi: {{ .name }}" +UpdatePortFormat: "Hata: Girilen port numarası 1 ile 65535 arasında olmalıdır!" +UpdatePortUsed: "Hata: Port numarası zaten kullanımda, lütfen kontrol edip tekrar deneyin!" +UpdatePortErr: "Hata: Panel port güncelleme başarısız, {{ .err }}" +UpdatePortResult: "Panel Portu: {{ .name }}" +UpdatePortFirewallAdd: "Güvenlik duvarı port kuralı ekleme başarısız, {{ .err }}, lütfen {{ .name }} portunu güvenlik duvarı kurallarına manuel olarak ekleyin." +UpdatePortFirewallDel: "Hata: Güvenlik duvarı portu silme başarısız, {{ .err }}" +UpdatePortFirewallReload: "Güvenlik duvarını yeniden yükleme başarısız, {{ .err }}, lütfen güvenlik duvarını manuel olarak yeniden yükleyin." +UserInfo: "Panel bilgilerini al" +UserInfoAddr: "Panel adresi: " +UserInfoPassHelp: "İpucu: Şifreyi değiştirmek için komutu çalıştırabilirsiniz: " +DBConnErr: "Hata: Veritabanı bağlantısını başlatma başarısız, {{ .err }}" +SystemVersion: "sürüm: " +SystemMode: "mod: " + +#mobile app +ErrVerifyToken: 'Token doğrulama hatası, lütfen sıfırlayın ve tekrar tarayın.' +ErrInvalidToken: 'Geçersiz token, lütfen sıfırlayın ve tekrar tarayın.' +ErrExpiredToken: 'Token süresi dolmuş, lütfen sıfırlayın ve tekrar tarayın.' + +#cluster +ErrMasterDelete: "Ana düğümü silinemiyor, lütfen önce alt düğümleri silin." +ClusterNameIsExist: "Küme adı zaten var." +AppStatusUnHealthy: "Uygulama durumu alımı anormal, lütfen düğüm listesindeki yükleme düğümü durumunu kontrol edin." +MasterNodePortNotAvailable: "Düğüm {{ .name }} portu {{ .port }} bağlantı doğrulaması başarısız oldu, lütfen güvenlik duvarı/güvenlik grubu ayarlarını ve ana düğüm durumunu kontrol edin." +ClusterMasterNotExist: "Küme ana düğümü bağlantısı kesildi, lütfen alt düğümleri silin." + +#ssl +ErrReqFailed: "{{.name}} istek başarısız: {{ .err }}" + +#command +Name: "Ad" +Command: "Komut" diff --git a/core/i18n/lang/zh-Hant.yaml b/core/i18n/lang/zh-Hant.yaml new file mode 100644 index 0000000..4d1f6c2 --- /dev/null +++ b/core/i18n/lang/zh-Hant.yaml @@ -0,0 +1,263 @@ +ErrInvalidParams: "請求參數錯誤: {{ .detail }}" +ErrTokenParse: "Token 生成錯誤: {{ .detail }}" +ErrInitialPassword: "原密碼錯誤" +ErrInternalServer: "服務內部錯誤: {{ .detail }}" +ErrRecordExist: "記錄已存在" +ErrRecordNotFound: "記錄未找到" +ErrStructTransform: "類型轉換失敗: {{ .detail }}" +ErrNotLogin: "使用者未登入: {{ .detail }}" +ErrPasswordExpired: "目前密碼已過期: {{ .detail }}" +ErrNotSupportType: "系統暫不支援目前類型: {{ .detail }}" +ErrProxy: "請求錯誤,請檢查該節點狀態: {{ .detail }}" +ErrApiConfigStatusInvalid: "API 介面禁止存取: {{ .detail }}" +ErrApiConfigKeyInvalid: "API 金鑰錯誤: {{ .detail }}" +ErrApiConfigIPInvalid: "API 請求 IP 不在白名單: {{ .detail }}" +ErrApiConfigDisable: "此介面禁止使用 API 呼叫: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "API 時間戳錯誤: {{ .detail }}" + +#request +ErrNoSuchHost: "無法找到請求的伺服器 {{ .err }}" +ErrHttpReqNotFound: "無法找到請求的資源 {{ .err }}" +ErrHttpReqFailed: "請求失敗 {{ .err }}" +ErrHttpReqTimeOut: "請求逾時 {{ .err }}" +ErrCreateHttpClient: "建立請求失敗 {{ .err }}" +ErrProxySetting: "代理伺服器資訊不可用 {{ .err }},請檢查後重試!" +ErrEntranceFormat: "暫不支援安全入口 {{ .name }} ,請檢查後重試!" + +#common +ErrDemoEnvironment: "示範伺服器,禁止此操作!" +ErrCmdTimeout: "指令執行逾時!" +ErrEntrance: "安全入口資訊錯誤,請檢查後再試!" +ErrGroupIsDefault: "預設分組無法刪除" +ErrGroupIsInUse: "分組正被使用,無法刪除。" +ErrLocalDelete: "無法刪除本機節點!" +ErrPortInUsed: "{{ .name }} 埠已被佔用!" +ErrInternalServerKey: "服務內部錯誤: " +MasterNode: "主節點" + +#app +CustomAppStoreFileValid: "應用商店包需要 .tar.gz 格式" +ErrFileNotFound: "{{ .name }} 檔案不存在" +AppBackup: '應用備份' +AppBackupPush: '傳輸應用備份文件 {{.file}} 到節點 {{ .name }}' +ErrSourceTargetSame: '源節點和目標節點不能相同!' +AppInstall: '在 {{ .targetNode }} 節點安裝應用 {{ .name }}' +AppInstallCheck: '檢查應用安裝環境' + +#backup +ErrBackupInUsed: "該備份帳號已在排程任務中使用,無法刪除" +ErrBackupCheck: "備份帳號測試連線失敗 {{ .err }}" +ErrBackupLocal: "本機伺服器備份帳號暫不支援該操作!" +ErrBackupPublic: "檢測到該備份帳號為非公用,請檢查後再試!" +ErrOSSConn: "無法取得最新版本,請確認伺服器是否能夠連接外部網路。" + +#license +LicenseCheck: '檢查許可證是否可用' +ErrLicenseInUsed: '檢測到該許可證已被綁定,請檢查後重試!' +ErrLicenseExpired: '檢測到該許可證已過期,請檢查後重試!' +ErrLicense: "許可證格式錯誤,請檢查後重試!" +ErrLicenseCheck: "許可證校驗失敗,請檢查後重試!" +ErrXpackVersion: "許可證校驗失敗,該許可證受版本限制,無法成功匯入,請檢查後重試!" +ErrLicenseSave: "許可證資訊儲存失敗,錯誤 {{ .err }},請重試!" +ErrLicenseSync: "許可證資訊同步失敗,資料庫中未檢測到許可證資訊!" +ErrLicenseExist: "該許可證記錄已存在,您可直接前往許可證頁面進行節點綁定。" +ErrXpackNotFound: "該部分為專業版功能,請先在 面板設定-許可證 介面匯入許可證" +ErrXpackExceptional: "該部分為專業版功能,請先在 面板設定-許可證 介面同步許可證狀態" +ErrXpackLost: "許可證已達到最大重試次數,請進入【面板設定】【許可證】頁面手動點擊同步按鈕,以確保專業版功能正常使用" +ErrDeviceLost: "許可證校驗必要檔案遺失,請檢查後重試!" +ErrDeviceErr: "目前環境與許可證匯入環境不一致,請編輯許可證重新匯入!" +ErrXpackTimeout: "請求超時,網路連接可能不穩定,請稍後再試!" +ErrUnbindMaster: "檢測到節點管理內存在節點,無法解綁目前許可證,請先移除後重試!" +ErrFreeNodeLimit: "社區版節點數量已達免費上限,請前往 www.lxware.cn/1panel 購買後重試!" +ErrNodeBound: "該許可證已綁定到其他節點,請檢查後重試!" +ErrNodeBoundDelete: "該許可證已被綁定,不支援刪除操作,請檢查後重試!" +ErrNodeBoundLimit: "目前免費節點已達上限,請檢查後重試!" +ErrLicenseFree: "僅當許可證正常綁定到節點後,才能使用其免費節點,請檢查後重試!" +ErrLicenseUnbind: "檢測到該許可證存在社區版節點,請在 [面板設定 - 許可證] 中解除綁定後重試!" +ErrNoSuchNode: "未能找到該節點資訊,請檢查後重試!" +ErrNodeUnbind: "檢測到該節點未在許可證綁定範圍內,請檢查後重試!" +ErrNodeBind: "檢測到該節點已綁定許可證,請檢查後重試!" +ErrNodeLocalRollback: "主節點暫不支援直接回滾,請手動執行「1pctl restore」指令回滾!" + +InvalidRequestBodyType: "請求體格式錯誤,請檢查請求內容是否符合格式要求後重試!" +InvalidLicenseCodeType: "提供的許可證格式錯誤,請檢查後重試!" +LicenseNotFoundType: "許可證不存在,系統中未找到與提供許可證匹配的紀錄,請檢查後重試!" +LicenseRevokedType: "請求的許可證已被廢棄,無法使用,請檢查後重試!" +LicenseExpiredType: "許可證已超過有效期,請續費或在 面板設定-許可證 介面重新匯入許可證後重試!" +LicenseProductMismatchType: "許可證與目前請求的產品或服務不匹配!" +InvalidAssigneeType: "許可證分配的目標使用者或裝置資訊無效,請檢查後重試!" +LicenseUsageNotFoundType: "無使用記錄,目前許可證尚未被啟動或使用,請檢查後重試!" +LicenseUsageLimitExceededType: "該許可證已綁定到其他節點,請檢查後重試!" + +# alert +ErrAlertSync: "告警資訊同步錯誤,請檢查後重試!" + +#task +TaskStart: "{{ .name }} 任務開始 [START]" +TaskEnd: "{{ .name }} 任務結束 [COMPLETED]" +TaskFailed: "{{ .name }} 任務失敗" +TaskTimeout: "{{ .name }} 超時" +TaskSuccess: "{{ .name }} 任務成功" +TaskRetry: "開始第 {{ .name }} 次重試" +SubTaskSuccess: "{{ .name }} 成功" +SubTaskFailed: "{{ .name }} 失敗: {{ .err }}" +TaskInstall: "安裝" +TaskUpgrade: "升級" +TaskSync: "同步" +TaskSyncForNode: "同步節點數據" +TaskBackup: "備份" +SuccessStatus: "{{ .name }} 成功" +FailedStatus: "{{ .name }} 失敗 {{ .err }}" +Start: "開始" +SubTask: "子任務" +Skip: "忽略錯誤並繼續..." +PushAppInstallTaskToNode: "推送應用安裝任務到節點 [{{ .name }}]" +TaskPush: "推送" +AppInstallTask: "應用安裝任務" +PushAppFailed: "推送應用安裝任務失敗" +Success: "成功" + +#script +ScriptLibrary: "腳本庫" +RemoteScriptLibrary: "遠端腳本庫" +ScriptSyncSkip: "目前腳本庫已是最新版本!" +DownloadData: "下載腳本庫文件 data.yaml" +DownloadPackage: "下載腳本庫壓縮檔" +AnalyticCompletion: "解析成功,現在開始同步到資料庫..." + +#sync node +Node: "節點" +SyncNode: "同步資料到節點 {{ .name }}" +LocalName: "'local' 名稱僅用於系統本機標識" +SyncPackageData: "打包同步資料" +SyncPackageEncrypt: "封包加密" +SyncRequest: "請求節點同步介面" +SyncFailedRetry: "第 {{ .index }} 次同步節點資料失敗逾時,正在重試..." +SyncFailed: "同步失敗,請在節點列表中手動同步!" +SyncSystemProxy: "系統代理配置" +SyncScripts: "腳本庫" +SyncBackupAccounts: "備份帳號" +SyncAlertSetting: "告警設定" +SyncCustomApp: "自訂應用" +SyncLanguage: "系統語言" + + +#upgrade node +NodeUpgrade: "升級節點 {{ .name }}" +UpgradeCheck: "檢查節點更新" +UpgradeCheckLocal: "本機節點不支援批次升級,跳過..." +UpgradeCheckLatest: "檢測到該節點已是最新版本,跳過..." +NewSSHClient: "初始化 SSH 連接" +BackupBeforeUpgrade: "升級前備份資料" +UploadUpgradeFile: "發送升級所需文件" +RestartAfterUpgrade: "升級後啟動服務" + +#add node +MasterData: "主節點資料" +LoadSftpClient: "取得上傳用戶端" +PackageMasterData: "生成主節點備份壓縮檔" +UploadBackup: "上傳備份資料" +MvBackup: "行動數據到備份目錄" +TaskAddNode: "新增節點" +LoadNodeArch: "取得節點架構資訊" +LoadNodeArchDetail: "檢測到主節點架構: {{ .local }},子節點架構: {{ .node }}" +LoadNodeUpgradeDetail: "使用 v1 版本歷史安裝目錄: {{ .baseDir }},服務監聽埠: {{ .port }}" +SyncAgentBaseInfo: "同步節點基礎資料" +GenerateSSLInfo: "生成節點 SSL 資訊" +ConnInfoNotMatch: "連接資訊不匹配" +MakeAgentPackage: "生成節點安裝包" +SendAgent: "下發節點安裝包" +StartService: "啟動服務" +NoBackupNode: "目前備份節點為空,請選擇備份節點儲存後重試!" + +#cmd +AppVersion: "應用版本" +AppCommands: "應用相關指令" +AppInit: "初始化應用" +AppKeyVal: "應用的 key(僅支援英文)" +AppCreateFileErr: "文件 {{ .name }} 建立失敗 {{ .err }}" +AppCreateDirErr: "資料夾 {{ .name }} 建立失敗 {{ .err }}" +AppMissKey: "應用的 key 缺失,使用 -k 指定" +AppMissVersion: "應用版本缺失,使用 -v 指定" +AppVersionExist: "版本已存在!" +AppCreateSuccessful: "建立成功!" +AppWriteErr: "文件 {{ .name }} 寫入失敗 {{ .err }}" +SudoHelper: "請使用 {{ .cmd }} 或者切換到 root 使用者" +ListenIPCommands: "切換監聽 IP" +ListenIPv4: "監聽 IPv4" +ListenIPv6: "監聽 IPv6" +ListenChangeSuccessful: "切換成功!已切換至監聽 {{ .value }}" +ResetCommands: "重設系統資訊" +ResetMFA: "取消 1Panel 兩步驗證" +ResetHttps: "取消 1Panel https 方式登入" +ResetEntrance: "取消 1Panel 安全入口" +ResetIPs: "取消 1Panel 授權 IP 限制" +ResetDomain: "取消 1Panel 存取域名綁定" +ResetPasskey: "清空 1Panel 通行密鑰" +RestoreCommands: "回滾 1Panel 服務及資料" +RestoreNoSuchFile: "暫無可回滾文件" +RestoreStep1: "(1/5)開始從 {{ .name }} 目錄回滾 1Panel 服務及資料... " +RestoreStep2: "(2/5)1Panel 二進制回滾成功" +RestoreStep3: "(3/5)1Panel 腳本回滾成功" +RestoreStep4: "(4/5)1Panel 服務回滾成功" +RestoreStep5: "(5/5)1Panel 資料回滾成功" +RestoreSuccessful: "回滾成功!已回滾到 {{ .version },正在重啟服務,請稍候..." +UpdateCommands: "修改面板資訊" +UpdateUser: "修改面板使用者" +UpdatePassword: "修改面板密碼" +UpdatePort: "修改面板埠" +UpdateUserNull: "錯誤: 輸入面板使用者為空!" +UpdateUserBlank: "錯誤: 輸入面板使用者中包含空格字元!" +UpdateUserFormat: "錯誤: 輸入面板使用者錯誤!僅支援英文、中文、數字和_,長度3-30" +UpdateUserErr: "錯誤: 面板使用者修改失敗,{{ .err }}" +UpdateSuccessful: "修改成功!" +UpdateUserResult: "面板使用者: {{ .name }}" +UpdatePasswordRead: "錯誤: 面板密碼資訊讀取錯誤,{{ .err }}" +UpdatePasswordNull: "錯誤: 輸入面板密碼為空!" +UpdateUPasswordBlank: "錯誤: 輸入面板密碼中包含空格字元!" +UpdatePasswordFormat: "錯誤: 面板密碼僅支援字母、數字、特殊字元(!@#$%*_,.?),長度 8-30 位!" +UpdatePasswordLen: "錯誤: 請輸入 6 位以上密碼!" +UpdatePasswordRe: "確認密碼: " +UpdatePasswordErr: "錯誤: 面板密碼修改失敗,{{ .err }}" +UpdatePasswordSame: "錯誤: 兩次密碼不匹配,請檢查後重試!" +UpdatePasswordResult: "面板密碼: {{ .name }}" +UpdatePortFormat: "錯誤: 輸入的埠號必須在 1 到 65535 之間!" +UpdatePortUsed: "錯誤: 該埠號正被佔用,請檢查後重試!" +UpdatePortErr: "錯誤: 面板埠修改失敗,{{ .err }}" +UpdatePortResult: "面板埠: {{ .name }}" +UpdatePortFirewallAdd: "新增防火牆埠規則失敗,{{ .err }},請您手動將 {{ .name }} 埠新增至防火牆規則中。" +UpdatePortFirewallDel: "錯誤: 防火牆埠刪除失敗,{{ .err }}" +UpdatePortFirewallReload: "防火牆重載失敗,{{ .err }},請您手動重載防火牆。" +UserInfo: "取得面板資訊" +UserInfoAddr: "面板地址: " +UserInfoPassHelp: "提示: 修改密碼可執行指令: " +DBConnErr: "錯誤: 初始化資料庫連線失敗,{{ .err }}" +SystemVersion: "版本: " +SystemMode: "模式: " + +#exchange +LocalNodeIpFailed: "無法取得主節點 IP ,請編輯主節點增加 IP 位址和 SSH 認證資訊" +HandlePrivateKey: "處理節點私鑰" +HandlePublicKey: "處理節點公鑰" +ExchangeFile: "開始從 {{ .source }} 節點同步 {{ .sourcePath }} 到 {{ .dest }} 節點 {{ .destPath }}" +TaskRsync: "同步" +NodeFile: "節點文件" + +#mobile app +ErrVerifyToken: '令牌驗證錯誤,請重設後再次掃碼' +ErrInvalidToken: '無效的令牌,請重設後再次掃碼' +ErrExpiredToken: '令牌過期,請重設後再次掃碼' + +#cluster +ErrMasterDelete: "無法刪除主節點,請先刪除從節點" +ClusterNameIsExist: "叢集名稱已存在" +AppStatusUnHealthy: "應用取得狀態異常,請在節點列表檢查安裝節點狀態" +MasterNodePortNotAvailable: "節點 {{ .name }} 埠 {{ .port }} 連通性校驗失敗,請檢查防火牆/安全組設定和主節點狀態" +ClusterMasterNotExist: "叢集主節點失聯,請刪除子節點" + +#ssl +ErrReqFailed: "{{.name}} 請求失敗: {{ .err }}" + +#command +Name: "名稱" +Command: "指令" diff --git a/core/i18n/lang/zh.yaml b/core/i18n/lang/zh.yaml new file mode 100644 index 0000000..aca646e --- /dev/null +++ b/core/i18n/lang/zh.yaml @@ -0,0 +1,268 @@ +ErrInvalidParams: "请求参数错误: {{ .detail }}" +ErrTokenParse: "Token 生成错误: {{ .detail }}" +ErrInitialPassword: "原密码错误" +ErrInternalServer: "服务内部错误: {{ .detail }}" +ErrRecordExist: "记录已存在" +ErrRecordNotFound: "记录未能找到" +ErrStructTransform: "类型转换失败: {{ .detail }}" +ErrNotLogin: "用户未登录: {{ .detail }}" +ErrPasswordExpired: "当前密码已过期: {{ .detail }}" +ErrNotSupportType: "系统暂不支持当前类型: {{ .detail }}" +ErrProxy: "请求错误,请检查该节点状态: {{ .detail }}" +ErrApiConfigStatusInvalid: "API 接口禁止访问: {{ .detail }}" +ErrApiConfigKeyInvalid: "API 接口密钥错误: {{ .detail }}" +ErrApiConfigIPInvalid: "调用 API 接口 IP 不在白名单: {{ .detail }}" +ErrApiConfigDisable: "此接口禁止使用 API 接口调用: {{ .detail }}" +ErrApiConfigKeyTimeInvalid: "API 接口时间戳错误: {{ .detail }}" +ErrPasskeyDisabled: "需开启 HTTPS 才能使用 Passkey" +ErrPasskeyNotConfigured: "尚未配置 Passkey" +ErrPasskeyLimit: "Passkey 数量已达上限(最多 5 个)" +ErrPasskeySession: "Passkey 会话已过期或无效" +ErrPasskeyDuplicate: "Passkey 已存在" +ErrPasskeyVerify: "Passkey 验证失败" + +#request +ErrNoSuchHost: "无法找到请求的服务器 {{ .err }}" +ErrHttpReqNotFound: "无法找到请求的资源 {{ .err }}" +ErrHttpReqFailed: "请求失败 {{ .err }}" +ErrHttpReqTimeOut: "请求超时 {{ .err }}" +ErrCreateHttpClient: "创建请求失败 {{ .err }}" +ErrProxySetting: "代理服务器信息不可用 {{ .err }},请检查后重试!" +ErrEntranceFormat: "暂不支持安全入口 {{ .name }} ,请检查后重试!" + +#common +ErrDemoEnvironment: "演示服务器,禁止此操作!" +ErrCmdTimeout: "命令执行超时!" +ErrEntrance: "安全入口信息错误,请检查后重试!" +ErrGroupIsDefault: "默认分组,无法删除" +ErrGroupIsInUse: "分组正被使用,无法删除" +ErrLocalDelete: "无法删除本地节点!" +ErrPortInUsed: "{{ .name }} 端口已被占用!" +ErrInternalServerKey: "服务内部错误:" +MasterNode: "主节点" + +#app +CustomAppStoreFileValid: "应用商店包需要 .tar.gz 格式" +ErrFileNotFound: '{{ .name }} 文件不存在' +AppBackup: "应用备份" +AppBackupPush: "传输应用备份文件 {{.file}} 到节点 {{ .name }}" +ErrSourceTargetSame: "源节点和目标节点不能相同!" +AppInstall: "在 {{ .targetNode }} 节点安装应用 {{ .name }}" +AppInstallCheck: "检查应用安装环境" + +#backup +ErrBackupInUsed: "该备份账号已在计划任务中使用,无法删除" +ErrBackupCheck: "备份账号测试连接失败 {{ .err }}" +ErrBackupLocal: "本地服务器备份账号暂不支持该操作!" +ErrBackupPublic: "检测到该备份账号为非公用,请检查后重试!" +ErrOSSConn: "无法获取最新版本,请确认服务器是否能够连接外部网络。" + +#license +LicenseCheck: '检查许可证是否可用' +ErrLicenseInUsed: '检查到该许可证已被绑定,请检查后重试!' +ErrLicenseExpired: '检查到该许可证已过期,请检查后重试!' +ErrLicense: "许可证格式错误,请检查后重试!" +ErrLicenseCheck: "许可证校验失败,请检查后重试!" +ErrXpackVersion: "许可证校验失败,该许可证受版本限制,无法成功导入,请检查后重试!" +ErrLicenseSave: "许可证信息保存失败,错误 {{ .err }},请重试!" +ErrLicenseSync: "许可证信息同步失败,数据库中未检测到许可证信息!" +ErrLicenseExist: "该许可证记录已存在,您可直接前往许可证页面进行节点绑定。" +ErrXpackNotFound: "该部分为专业版功能,请先在 面板设置-许可证 界面导入许可证" +ErrXpackExceptional: "该部分为专业版功能,请先在 面板设置-许可证 界面同步许可证状态" +ErrXpackLost: "许可证已达到最大重试次数,请进入【面板设置】【许可证】页面手动点击同步按钮,以确保专业版功能正常使用" +ErrDeviceLost: "许可证校验必要文件丢失,请检查后重试!" +ErrDeviceErr: "当前环境与许可证导入环境不一致,请编辑许可证重新导入!" +ErrXpackTimeout: "请求超时,网络连接可能不稳定,请稍后再试!" +ErrUnbindMaster: "检测到节点管理内存在专业版节点,无法解绑当前许可证,请先移除或解绑后重试!" +ErrFreeNodeLimit: "社区版节点数量已达到免费上限,请前往 www.lxware.cn/1panel 购买后重试!" +ErrNodeBound: "该许可证已绑定到其他节点,请检查后重试!" +ErrNodeBoundDelete: "该许可证已被绑定,不支持删除操作,请检查后重试!" +ErrNodeBoundLimit: "当前免费节点已经达到上限,请检查后重试!" +ErrLicenseFree: "仅当许可证正常绑定到节点后,才能使用其免费节点,请检查后重试!" +ErrLicenseUnbind: "检测到该许可证存在社区版节点,请在 [ 面板设置 - 许可证 ] 中解绑后重试!" +ErrNoSuchNode: "未能找到该节点信息,请检查后重试!" +ErrNodeUnbind: "检测到该节点未在许可证绑定范围内,请检查后重试!" +ErrNodeBind: "检测到该节点已绑定许可证,请检查后重试!" +ErrNodeLocalRollback: "主节点暂不支持直接回滚,请手动执行 1pctl restore 命令回滚!" + +InvalidRequestBodyType: "请求体格式错误,请检查请求内容是否符合格式要求后重试!" +InvalidLicenseCodeType: "提供的许可证格式错误,请检查后重试!" +LicenseNotFoundType: "许可证不存在,系统中未找到与提供许可证匹配的记录,请检查后重试!" +LicenseRevokedType: "请求的许可证已被废弃,无法使用,请检查后重试!" +LicenseExpiredType: "许可证已超过有效期,请续费或在 面板设置-许可证 界面重新导入许可证后重试!" +LicenseProductMismatchType: "许可证与当前请求的产品或服务不匹配!" +InvalidAssigneeType: "许可证分配的目标用户或设备信息无效,请检查后重试!" +LicenseUsageNotFoundType: "无使用记录,当前许可证尚未被激活或使用,请检查后重试!" +LicenseUsageLimitExceededType: "该许可证已绑定到其他节点,请检查后重试!" + +# alert +ErrAlertSync: "告警信息同步错误,请检查后重试!" + +#task +TaskStart: "{{ .name }} 任务开始 [START]" +TaskEnd: "{{ .name }} 任务结束 [COMPLETED]" +TaskFailed: "{{ .name }} 任务失败" +TaskTimeout: "{{ .name }} 超时" +TaskSuccess: "{{ .name }} 任务成功" +TaskRetry: "开始第 {{ .name }} 次重试" +SubTaskSuccess: "{{ .name }} 成功" +SubTaskFailed: "{{ .name }} 失败: {{ .err }}" +TaskInstall: "安装" +TaskUpgrade: "升级" +TaskSync: "同步" +TaskSyncForNode: "同步节点数据" +TaskBackup: "备份" +SuccessStatus: "{{ .name }} 成功" +FailedStatus: "{{ .name }} 失败 {{ .err }}" +Start: "开始" +SubTask: "子任务" +Skip: "忽略错误并继续..." +PushAppInstallTaskToNode: "推送应用安装任务到节点 [{{ .name }}]" +TaskPush: "推送" +AppInstallTask: "应用安装任务" +PushAppFailed: "推送应用安装任务失败" +Success: "成功" + +#script +ScriptLibrary: "脚本库" +RemoteScriptLibrary: "远程脚本库" +ScriptSyncSkip: "检查到当前脚本库已是最新版本!" +DownloadData: "下载脚本库文件 data.yaml" +DownloadPackage: "下载脚本库压缩包" +AnalyticCompletion: "解析成功,现在开始同步到数据库..." + +#sync node +Node: "节点" +SyncNode: "同步数据到节点 {{ .name }}" +LocalName: "local 名称仅用于系统本机标识" +SyncPackageData: "打包同步数据" +SyncPackageEncrypt: "数据包加密" +SyncRequest: "请求节点同步接口" +SyncFailedRetry: "第 {{ .index }} 次同步节点数据失败超时,正在重试..." +SyncFailed: "同步失败,请在节点列表中手动同步!" +SyncSystemProxy: "系统代理配置" +SyncScripts: "脚本库" +SyncBackupAccounts: "备份账号" +SyncAlertSetting: "告警设置" +SyncCustomApp: "自定义应用" +SyncLanguage: "系统语言" + + +#upgrade node +NodeUpgrade: "升级节点 {{ .name }}" +UpgradeCheck: "检查节点更新" +UpgradeCheckLocal: "本地节点不支持批量升级,跳过..." +UpgradeCheckLatest: "检测到该节点已是最新版本,跳过..." +NewSSHClient: "初始化 SSH 连接" +BackupBeforeUpgrade: "升级前备份数据" +UploadUpgradeFile: "下发升级所需文件" +RestartAfterUpgrade: "升级后启动服务" + +#add node +MasterData: "主节点数据" +LoadSftpClient: "获取上传客户端" +PackageMasterData: "生成主节点备份压缩包" +UploadBackup: "上传备份数据" +TaskAddNode: "添加节点" +LoadNodeArch: "获取节点架构信息" +LoadNodeArchDetail: "检测到主节点架构: {{ .local }},子节点架构: {{ .node }}" +LoadNodeUpgradeDetail: "使用 v1 版本历史安装目录: {{ .baseDir }},服务监听端口: {{ .port }}" +SyncAgentBaseInfo: "同步节点基础数据" +GenerateSSLInfo: "生成节点 SSL 信息" +ConnInfoNotMatch: "连接信息不匹配" +MakeAgentPackage: "生成节点安装包" +SendAgent: "下发节点安装包" +StartService: "启动服务" +NoBackupNode: "当前备份节点为空,请选择备份节点保存后重试!" + +#cmd +AppVersion: "应用版本" +AppCommands: "应用相关命令" +AppInit: "初始化应用" +AppKeyVal: "应用的 key(仅支持英文)" +AppCreateFileErr: "文件 {{ .name }} 创建失败 {{ .err }}" +AppCreateDirErr: "文件夹 {{ .name }} 创建失败 {{ .err }}" +AppMissKey: "应用的 key 缺失,使用 -k 指定" +AppMissVersion: "应用版本缺失,使用 -v 指定" +AppVersionExist: "版本已存在!" +AppCreateSuccessful: "创建成功!" +AppWriteErr: "文件 {{ .name }} 写入失败 {{ .err }}" +SudoHelper: "请使用 {{ .cmd }} 或者切换到 root 用户" +ListenIPCommands: "切换监听 IP" +ListenIPv4: "监听 IPv4" +ListenIPv6: "监听 IPv6" +ListenChangeSuccessful: "切换成功!已切换至监听 {{ .value }}" +ResetCommands: "重置系统信息" +ResetMFA: "取消 1Panel 两步验证" +ResetHttps: "取消 1Panel https 方式登录" +ResetEntrance: "取消 1Panel 安全入口" +ResetIPs: "取消 1Panel 授权 IP 限制" +ResetDomain: "取消 1Panel 访问域名绑定" +ResetPasskey: "清空 1Panel 通行密钥" +RestoreCommands: "回滚 1Panel 服务及数据" +RestoreNoSuchFile: "暂无可回滚文件" +RestoreStep1: "(1/5)开始从 {{ .name }} 目录回滚 1Panel 服务及数据... " +RestoreStep2: "(2/5)1Panel 二进制回滚成功" +RestoreStep3: "(3/5)1Panel 脚本回滚成功" +RestoreStep4: "(4/5)1Panel 服务回滚成功" +RestoreStep5: "(5/5)1Panel 数据回滚成功" +RestoreSuccessful: "回滚成功!已回滚到 {{ .version }},正在重启服务,请稍候..." +UpdateCommands: "修改面板信息" +UpdateUser: "修改面板用户" +UpdatePassword: "修改面板密码" +UpdatePort: "修改面板端口" +UpdateUserNull: "错误: 输入面板用户为空!" +UpdateUserBlank: "错误: 输入面板用户中包含空格字符!" +UpdateUserFormat: "错误: 输入面板用户错误!仅支持英文、中文、数字和_,长度3-30" +UpdateUserErr: "错误: 面板用户修改失败,{{ .err }}" +UpdateSuccessful: "修改成功!" +UpdateUserResult: "面板用户: {{ .name }}" +UpdatePasswordRead: "错误: 面板密码信息读取错误,{{ .err }}" +UpdatePasswordNull: "错误: 输入面板密码为空!" +UpdateUPasswordBlank: "错误: 输入面板密码中包含空格字符!" +UpdatePasswordFormat: "错误: 面板密码仅支持字母、数字、特殊字符(!@#$%*_,.?),长度 8-30 位!" +UpdatePasswordLen: "错误: 请输入 6 位以上密码!" +UpdatePasswordRe: "确认密码: " +UpdatePasswordErr: "错误: 面板密码修改失败,{{ .err }}" +UpdatePasswordSame: "错误: 两次密码不匹配,请检查后重试!" +UpdatePasswordResult: "面板密码: {{ .name }}" +UpdatePortFormat: "错误: 输入的端口号必须在 1 到 65535 之间!" +UpdatePortUsed: "错误: 该端口号正被占用,请检查后重试!" +UpdatePortErr: "错误: 面板端口修改失败,{{ .err }}" +UpdatePortResult: "面板端口: {{ .name }}" +UpdatePortFirewallAdd: "添加防火墙端口规则失败,{{ .err }},请您手动将 {{ .name }} 端口添加至防火墙规则中。" +UpdatePortFirewallDel: "错误: 防火墙端口删除失败,{{ .err }}" +UpdatePortFirewallReload: "防火墙重载失败,{{ .err }},请您手动重载防火墙。" +UserInfo: "获取面板信息" +UserInfoAddr: "面板地址: " +UserInfoPassHelp: "提示: 修改密码可执行命令: " +DBConnErr: "错误: 初始化数据库连接失败,{{ .err }}" +SystemVersion: "版本: " +SystemMode: "模式: " + +#exchange +LocalNodeIpFailed: "无法获取主节点 IP ,请编辑主节点增加 IP 地址和 SSH 认证信息" +HandlePrivateKey: "处理节点私钥" +HandlePublicKey: "处理节点公钥" +ExchangeFile: "开始从 {{ .source }} 节点同步 {{ .sourcePath }} 到 {{ .dest }} 节点 {{ .destPath }}" +TaskRsync: "同步" +NodeFile: "节点文件" + +#mobile app +ErrVerifyToken: '令牌验证错误,请重置后再次扫码' +ErrInvalidToken: '无效的令牌,请重置后再次扫码' +ErrExpiredToken: '令牌过期,请重置后再次扫码' + +#cluster +ErrMasterDelete: "无法删除主节点,请先删除从节点" +ClusterNameIsExist: "集群名称已存在" +AppStatusUnHealthy: "应用获取状态异常,请在节点列表检查安装节点状态" +MasterNodePortNotAvailable: "节点 {{ .name }} 端口 {{ .port }} 连通性校验失败,请检查防火墙/安全组设置和主节点状态" +ClusterMasterNotExist: "集群主节点失联,请删除子节点" + +#ssl +ErrReqFailed: "{{.name}} 请求失败: {{ .err }}" + +#command +Name: "名称" +Command: "命令" diff --git a/core/init/auth/ip_tracker.go b/core/init/auth/ip_tracker.go new file mode 100644 index 0000000..031431d --- /dev/null +++ b/core/init/auth/ip_tracker.go @@ -0,0 +1,99 @@ +package auth + +import ( + "sync" + "time" +) + +const ( + MaxIPCount = 100 + ExpireDuration = 30 * time.Minute +) + +type IPRecord struct { + NeedCaptcha bool + LastUpdate time.Time +} + +type IPTracker struct { + records map[string]*IPRecord + ipOrder []string + mu sync.RWMutex +} + +func NewIPTracker() *IPTracker { + return &IPTracker{ + records: make(map[string]*IPRecord), + ipOrder: make([]string, 0), + } +} + +func (t *IPTracker) NeedCaptcha(ip string) bool { + t.mu.Lock() + defer t.mu.Unlock() + + record, exists := t.records[ip] + if !exists { + return false + } + + if time.Since(record.LastUpdate) > ExpireDuration { + t.removeIPUnsafe(ip) + return false + } + + return record.NeedCaptcha +} + +func (t *IPTracker) SetNeedCaptcha(ip string) { + t.mu.Lock() + defer t.mu.Unlock() + + if record, exists := t.records[ip]; exists { + if time.Since(record.LastUpdate) > ExpireDuration { + t.removeIPUnsafe(ip) + } else { + record.NeedCaptcha = true + record.LastUpdate = time.Now() + return + } + } + + if len(t.records) >= MaxIPCount { + t.removeOldestUnsafe() + } + + t.records[ip] = &IPRecord{ + NeedCaptcha: true, + LastUpdate: time.Now(), + } + t.ipOrder = append(t.ipOrder, ip) +} + +func (t *IPTracker) Clear(ip string) { + t.mu.Lock() + defer t.mu.Unlock() + + t.removeIPUnsafe(ip) +} + +func (t *IPTracker) removeIPUnsafe(ip string) { + delete(t.records, ip) + + for i, storedIP := range t.ipOrder { + if storedIP == ip { + t.ipOrder = append(t.ipOrder[:i], t.ipOrder[i+1:]...) + break + } + } +} + +func (t *IPTracker) removeOldestUnsafe() { + if len(t.ipOrder) == 0 { + return + } + + oldestIP := t.ipOrder[0] + delete(t.records, oldestIP) + t.ipOrder = t.ipOrder[1:] +} diff --git a/core/init/cron/cron.go b/core/init/cron/cron.go new file mode 100644 index 0000000..db6eb6c --- /dev/null +++ b/core/init/cron/cron.go @@ -0,0 +1,23 @@ +package cron + +import ( + "time" + + "github.com/1Panel-dev/1Panel/core/app/service" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/init/cron/job" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/robfig/cron/v3" +) + +func Init() { + nyc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + global.Cron = cron.New(cron.WithLocation(nyc), cron.WithChain(cron.Recover(cron.DefaultLogger)), cron.WithChain(cron.DelayIfStillRunning(cron.DefaultLogger))) + + if _, err := global.Cron.AddJob("0 3 */31 * *", job.NewBackupJob()); err != nil { + global.LOG.Errorf("[core] can not add backup token refresh corn job: %s", err.Error()) + } + + service.StartSync() + global.Cron.Start() +} diff --git a/core/init/cron/job/backup.go b/core/init/cron/job/backup.go new file mode 100644 index 0000000..edb9daf --- /dev/null +++ b/core/init/cron/job/backup.go @@ -0,0 +1,63 @@ +package job + +import ( + "encoding/json" + "time" + + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/cloud_storage" + "github.com/1Panel-dev/1Panel/core/utils/xpack" +) + +type backup struct{} + +func NewBackupJob() *backup { + return &backup{} +} + +func (b *backup) Run() { + var backups []model.BackupAccount + _ = global.DB.Where("`type` in (?) AND is_public = 0", []string{constant.OneDrive, constant.ALIYUN, constant.GoogleDrive}).Find(&backups) + if len(backups) == 0 { + return + } + for _, backupItem := range backups { + if backupItem.ID == 0 { + continue + } + global.LOG.Infof("Start to refresh %s-%s access_token ...", backupItem.Type, backupItem.Name) + varMap := make(map[string]interface{}) + if err := json.Unmarshal([]byte(backupItem.Vars), &varMap); err != nil { + global.LOG.Errorf("failed to refresh %s - %s token, please retry, err: %v", backupItem.Type, backupItem.Name, err) + continue + } + var ( + refreshToken string + err error + ) + switch backupItem.Type { + case constant.OneDrive: + refreshToken, err = cloud_storage.RefreshToken("refresh_token", "refreshToken", varMap) + case constant.ALIYUN: + refreshToken, err = cloud_storage.RefreshALIToken(varMap) + } + if err != nil { + varMap["refresh_status"] = constant.StatusFailed + varMap["refresh_msg"] = err.Error() + global.LOG.Errorf("failed to refresh %s-%s token, please retry, err: %v", backupItem.Type, backupItem.Name, err) + continue + } + varMap["refresh_status"] = constant.StatusSuccess + varMap["refresh_time"] = time.Now().Format(constant.DateTimeLayout) + varMap["refresh_token"] = refreshToken + + varsItem, _ := json.Marshal(varMap) + _ = global.DB.Model(&model.BackupAccount{}).Where("id = ?", backupItem.ID).Updates(map[string]interface{}{"vars": string(varsItem)}).Error + global.LOG.Infof("Refresh %s-%s access_token successful!", backupItem.Type, backupItem.Name) + if err := xpack.Sync(constant.SyncBackupAccounts); err != nil { + global.LOG.Errorf("sync backup account to node failed, err: %v", err) + } + } +} diff --git a/core/init/db/db.go b/core/init/db/db.go new file mode 100644 index 0000000..0d4b614 --- /dev/null +++ b/core/init/db/db.go @@ -0,0 +1,15 @@ +package db + +import ( + "path" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/common" +) + +func Init() { + global.DB = common.LoadDBConnByPath(path.Join(global.CONF.Base.InstallDir, "1panel/db/core.db"), "core") + global.TaskDB = common.LoadDBConnByPath(path.Join(global.CONF.Base.InstallDir, "1panel/db/task.db"), "task") + global.AgentDB = common.LoadDBConnByPath(path.Join(global.CONF.Base.InstallDir, "1panel/db/agent.db"), "agent") + global.AlertDB = common.LoadDBConnByPath(path.Join(global.CONF.Base.InstallDir, "1panel/db/alert.db"), "alert") +} diff --git a/core/init/geo/lang.go b/core/init/geo/lang.go new file mode 100644 index 0000000..44c9bd6 --- /dev/null +++ b/core/init/geo/lang.go @@ -0,0 +1,131 @@ +package geo + +import ( + "fmt" + "os" + "path" + "sort" + "strings" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/cmd" + fileUtils "github.com/1Panel-dev/1Panel/core/utils/files" +) + +func Init() { + go initLang() +} + +func initLang() { + geoPath := path.Join(global.CONF.Base.InstallDir, "1panel/geo/GeoIP.mmdb") + isLangExist := fileUtils.Stat("/usr/local/bin/lang/zh.sh") + isGeoExist := fileUtils.Stat(geoPath) + if isLangExist && isGeoExist { + return + } + upgradePath := path.Join(global.CONF.Base.InstallDir, "1panel/tmp/upgrade") + tmpPath, err := loadRestorePath(upgradePath) + upgradeDir := path.Join(upgradePath, tmpPath, "downloads") + if err != nil || len(tmpPath) == 0 || !fileUtils.Stat(upgradeDir) { + if !isLangExist { + downloadLangFromRemote() + } + if !isGeoExist { + downloadGeoFromRemote(geoPath) + } + return + } + + files, _ := os.ReadDir(upgradeDir) + if len(files) == 0 { + tmpPath = "no such file" + } else { + for _, item := range files { + if item.IsDir() && strings.HasPrefix(item.Name(), "1panel-") { + tmpPath = path.Join(upgradePath, tmpPath, "downloads", item.Name()) + break + } + } + } + if tmpPath == "no such file" || !fileUtils.Stat(tmpPath) { + if !isLangExist { + downloadLangFromRemote() + } + if !isGeoExist { + downloadGeoFromRemote(geoPath) + } + return + } + if !isLangExist { + if !fileUtils.Stat(path.Join(tmpPath, "lang")) { + downloadLangFromRemote() + return + } + if err := cmd.RunDefaultBashCf("cp -r %s %s", path.Join(tmpPath, "lang"), "/usr/local/bin/"); err != nil { + global.LOG.Errorf("load lang from package failed, %v", err) + return + } + global.LOG.Info("init lang successful") + } + if !isGeoExist { + if !fileUtils.Stat(path.Join(tmpPath, "GeoIP.mmdb")) { + downloadGeoFromRemote(geoPath) + return + } + if err := cmd.RunDefaultBashCf("mkdir %s && cp %s %s/", path.Dir(geoPath), path.Join(tmpPath, "GeoIP.mmdb"), path.Dir(geoPath)); err != nil { + global.LOG.Errorf("load geo ip from package failed, %v", err) + return + } + global.LOG.Info("init geo ip successful") + } +} + +func loadRestorePath(upgradeDir string) (string, error) { + if _, err := os.Stat(upgradeDir); err != nil && os.IsNotExist(err) { + return "no such file", nil + } + files, err := os.ReadDir(upgradeDir) + if err != nil { + return "", err + } + var folders []string + for _, file := range files { + if file.IsDir() { + folders = append(folders, file.Name()) + } + } + if len(folders) == 0 { + return "no such file", nil + } + sort.Slice(folders, func(i, j int) bool { + return folders[i] > folders[j] + }) + return folders[0], nil +} + +func downloadLangFromRemote() { + path := fmt.Sprintf("%s/language/lang.tar.gz", global.CONF.RemoteURL.RepoUrl) + if err := fileUtils.DownloadFile(path, "/usr/local/bin/lang.tar.gz"); err != nil { + global.LOG.Errorf("download lang.tar.gz failed, err: %v", err) + return + } + if !fileUtils.Stat("/usr/local/bin/lang.tar.gz") { + global.LOG.Error("download lang.tar.gz failed, no such file") + return + } + if err := cmd.RunDefaultBashCf("tar zxvfC %s %s", "/usr/local/bin/lang.tar.gz", "/usr/local/bin/"); err != nil { + global.LOG.Errorf("decompress lang.tar.gz failed, %v", err) + return + } + _ = os.Remove("/usr/local/bin/lang.tar.gz") + global.LOG.Info("download lang successful") +} +func downloadGeoFromRemote(targetPath string) { + _ = os.MkdirAll(path.Dir(targetPath), os.ModePerm) + pathItem := fmt.Sprintf("%s/geo/GeoIP.mmdb", global.CONF.RemoteURL.RepoUrl) + if err := fileUtils.DownloadFile(pathItem, targetPath); err != nil { + global.LOG.Errorf("download geo ip failed, err: %v", err) + return + } + global.LOG.Info("download geo ip successful") +} diff --git a/core/init/hook/hook.go b/core/init/hook/hook.go new file mode 100644 index 0000000..5991dab --- /dev/null +++ b/core/init/hook/hook.go @@ -0,0 +1,98 @@ +package hook + +import ( + "strings" + + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/app/service" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" +) + +func Init() { + settingRepo := repo.NewISettingRepo() + global.CONF.Conn.Port, _ = settingRepo.GetValueByKey("ServerPort") + global.CONF.Conn.Ipv6, _ = settingRepo.GetValueByKey("Ipv6") + global.Api.ApiInterfaceStatus, _ = settingRepo.GetValueByKey("ApiInterfaceStatus") + if global.Api.ApiInterfaceStatus == constant.StatusEnable { + global.Api.ApiKey, _ = settingRepo.GetValueByKey("ApiKey") + global.Api.IpWhiteList, _ = settingRepo.GetValueByKey("IpWhiteList") + global.Api.ApiKeyValidityTime, _ = settingRepo.GetValueByKey("ApiKeyValidityTime") + } + global.CONF.Conn.BindAddress, _ = settingRepo.GetValueByKey("BindAddress") + global.CONF.Conn.SSL, _ = settingRepo.GetValueByKey("SSL") + global.CONF.Base.Version, _ = settingRepo.GetValueByKey("SystemVersion") + if err := settingRepo.Update("SystemStatus", "Free"); err != nil { + global.LOG.Fatalf("init service before start failed, err: %v", err) + } + + handleUserInfo(global.CONF.Base.ChangeUserInfo, settingRepo) + + generateKey() + initDockerConf() +} + +func handleUserInfo(tags string, settingRepo repo.ISettingRepo) { + if len(tags) == 0 { + return + } + settingMap := make(map[string]string) + if tags == "use_existing" { + settingMap["ServerPort"] = common.LoadParams("ORIGINAL_PORT") + global.CONF.Conn.Port = settingMap["ServerPort"] + settingMap["UserName"] = global.CONF.Base.Username + settingMap["Password"] = global.CONF.Base.Password + settingMap["SecurityEntrance"] = global.CONF.Conn.Entrance + settingMap["SystemVersion"] = common.LoadParams("ORIGINAL_VERSION") + global.CONF.Base.Version = settingMap["SystemVersion"] + settingMap["Language"] = global.CONF.Base.Language + } + if tags == "all" { + settingMap["UserName"] = common.RandStrAndNum(10) + settingMap["Password"] = common.RandStrAndNum(10) + settingMap["SecurityEntrance"] = common.RandStrAndNum(10) + } + if strings.Contains(global.CONF.Base.ChangeUserInfo, "username") { + settingMap["UserName"] = common.RandStrAndNum(10) + } + if strings.Contains(global.CONF.Base.ChangeUserInfo, "password") { + settingMap["Password"] = common.RandStrAndNum(10) + } + if strings.Contains(global.CONF.Base.ChangeUserInfo, "entrance") { + settingMap["SecurityEntrance"] = common.RandStrAndNum(10) + } + for key, val := range settingMap { + if len(val) == 0 { + continue + } + if key == "Password" { + val, _ = encrypt.StringEncrypt(val) + } + if err := settingRepo.Update(key, val); err != nil { + global.LOG.Errorf("update %s before start failed, err: %v", key, err) + } + } + + _, _ = cmd.RunDefaultWithStdoutBashCf("%s sed -i '/CHANGE_USER_INFO=%v/d' /usr/local/bin/1pctl", cmd.SudoHandleCmd(), global.CONF.Base.ChangeUserInfo) + _, _ = cmd.RunDefaultWithStdoutBashCf("%s sed -i -e 's#ORIGINAL_PASSWORD=.*#ORIGINAL_PASSWORD=**********#g' /usr/local/bin/1pctl", cmd.SudoHandleCmd()) +} + +func generateKey() { + if err := service.NewISettingService().GenerateRSAKey(); err != nil { + global.LOG.Errorf("generate rsa key error : %s", err.Error()) + } +} + +func initDockerConf() { + stdout, err := cmd.RunDefaultWithStdoutBashC("which docker") + if err != nil { + return + } + dockerPath := stdout + if strings.Contains(dockerPath, "snap") { + constant.DaemonJsonPath = "/var/snap/docker/current/config/daemon.json" + } +} diff --git a/core/init/log/log.go b/core/init/log/log.go new file mode 100644 index 0000000..5640e46 --- /dev/null +++ b/core/init/log/log.go @@ -0,0 +1,67 @@ +package log + +import ( + "fmt" + "io" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/log" + + "github.com/sirupsen/logrus" +) + +const ( + TimeFormat = "2006-01-02 15:04:05" + FileTImeFormat = "2006-01-02" + RollingTimePattern = "0 0 * * *" +) + +func Init() { + l := logrus.New() + setOutput(l, global.CONF.LogConfig) + global.LOG = l + global.LOG.Info("init logger successfully") +} + +func setOutput(logger *logrus.Logger, config global.LogConfig) { + writer, err := log.NewWriterFromConfig(&log.Config{ + LogPath: path.Join(global.CONF.Base.InstallDir, "1panel/log"), + FileName: config.LogName, + TimeTagFormat: FileTImeFormat, + MaxRemain: config.MaxBackup, + RollingTimePattern: RollingTimePattern, + LogSuffix: config.LogSuffix, + }) + if err != nil { + panic(err) + } + level, err := logrus.ParseLevel(config.Level) + if err != nil { + panic(err) + } + fileAndStdoutWriter := io.MultiWriter(writer, os.Stdout) + + logger.SetOutput(fileAndStdoutWriter) + logger.SetLevel(level) + logger.SetFormatter(new(MineFormatter)) +} + +type MineFormatter struct{} + +func (s *MineFormatter) Format(entry *logrus.Entry) ([]byte, error) { + detailInfo := "" + if entry.Caller != nil { + function := strings.ReplaceAll(entry.Caller.Function, "github.com/1Panel-dev/1Panel/core/", "") + detailInfo = fmt.Sprintf("(%s: %d)", function, entry.Caller.Line) + } + if len(entry.Data) == 0 { + msg := fmt.Sprintf("[%s] [%s] %s %s \n", time.Now().Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo) + return []byte(msg), nil + } + msg := fmt.Sprintf("[%s] [%s] %s %s {%v} \n", time.Now().Format(TimeFormat), strings.ToUpper(entry.Level.String()), entry.Message, detailInfo, entry.Data) + return []byte(msg), nil +} diff --git a/core/init/migration/helper/menu.go b/core/init/migration/helper/menu.go new file mode 100644 index 0000000..1a6cee3 --- /dev/null +++ b/core/init/migration/helper/menu.go @@ -0,0 +1,154 @@ +package helper + +import ( + "encoding/json" + "fmt" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/app/model" + "gorm.io/gorm" +) + +func LoadMenus() string { + item := []dto.ShowMenu{ + {ID: "1", Disabled: true, Title: "menu.home", IsShow: true, Label: "Home-Menu", Path: "/", Sort: 100}, + {ID: "2", Disabled: true, Title: "menu.apps", IsShow: true, Label: "App-Menu", Path: "/apps/all", Sort: 200}, + {ID: "3", Disabled: false, Title: "menu.website", IsShow: true, Label: "Website-Menu", Path: "/websites", Sort: 300, + Children: []dto.ShowMenu{ + {ID: "31", Disabled: false, Title: "menu.website", IsShow: true, Label: "Website", Path: "/websites", Sort: 100}, + {ID: "32", Disabled: false, Title: "menu.ssl", IsShow: true, Label: "SSL", Path: "/websites/ssl", Sort: 200}, + {ID: "33", Disabled: false, Title: "menu.runtime", IsShow: true, Label: "PHP", Path: "/websites/runtimes/php", Sort: 300}, + }}, + {ID: "4", Disabled: false, Title: "menu.aiTools", IsShow: true, Label: "AI-Menu", Path: "/ai/model", Sort: 400, + Children: []dto.ShowMenu{ + {ID: "41", Disabled: false, Title: "aiTools.model.model", IsShow: true, Label: "OllamaModel", Path: "/ai/model", Sort: 100}, + {ID: "42", Disabled: false, Title: "menu.mcp", IsShow: true, Label: "MCPServer", Path: "/ai/mcp", Sort: 200}, + {ID: "43", Disabled: false, Title: "aiTools.gpu.gpu", IsShow: true, Label: "GPU", Path: "/ai/gpu", Sort: 300}, + }}, + {ID: "5", Disabled: false, Title: "menu.database", IsShow: true, Label: "Database-Menu", Path: "/databases", Sort: 500}, + {ID: "6", Disabled: false, Title: "menu.container", IsShow: true, Label: "Container-Menu", Path: "/containers", Sort: 600}, + {ID: "7", Disabled: false, Title: "menu.system", IsShow: true, Label: "System-Menu", Path: "/hosts/files", Sort: 700, + Children: []dto.ShowMenu{ + {ID: "71", Disabled: false, Title: "menu.files", IsShow: true, Label: "File", Path: "/hosts/files", Sort: 100}, + {ID: "72", Disabled: false, Title: "menu.monitor", IsShow: true, Label: "Monitorx", Path: "/hosts/monitor/monitor", Sort: 200}, + {ID: "74", Disabled: false, Title: "menu.firewall", IsShow: true, Label: "FirewallPort", Path: "/hosts/firewall/port", Sort: 300}, + {ID: "75", Disabled: false, Title: "menu.processManage", IsShow: true, Label: "Process", Path: "/hosts/process/process", Sort: 400}, + {ID: "76", Disabled: false, Title: "menu.ssh", IsShow: true, Label: "SSH", Path: "/hosts/ssh/ssh", Sort: 500}, + {ID: "77", Disabled: false, Title: "menu.disk", IsShow: true, Label: "Disk", Path: "/hosts/disk", Sort: 600}, + }}, + {ID: "8", Disabled: false, Title: "menu.terminal", IsShow: true, Label: "Terminal-Menu", Path: "/hosts/terminal", Sort: 800}, + {ID: "10", Disabled: false, Title: "menu.cronjob", IsShow: true, Label: "Cronjob-Menu", Path: "/cronjobs", Sort: 900}, + {ID: "9", Disabled: false, Title: "menu.toolbox", IsShow: true, Label: "Toolbox-Menu", Path: "/toolbox", Sort: 1000}, + {ID: "11", Disabled: false, Title: "xpack.menu", IsShow: true, Label: "Xpack-Menu", Sort: 1100, + Children: []dto.ShowMenu{ + {ID: "118", Disabled: false, Title: "xpack.app.app", IsShow: true, Label: "XApp", Path: "/xpack/app", Sort: 100}, + {ID: "112", Disabled: false, Title: "xpack.waf.name", IsShow: true, Label: "Dashboard", Path: "/xpack/waf/dashboard", Sort: 200}, + {ID: "111", Disabled: false, Title: "xpack.node.nodeManagement", IsShow: true, Label: "NodeDashboard", Path: "/xpack/node/dashboard", Sort: 300}, + {ID: "119", Disabled: false, Title: "xpack.upage", IsShow: true, Label: "Upage", Path: "/xpack/upage", Sort: 400}, + {ID: "113", Disabled: false, Title: "xpack.monitor.name", IsShow: true, Label: "MonitorDashboard", Path: "/xpack/monitor/dashboard", Sort: 500}, + {ID: "114", Disabled: false, Title: "xpack.tamper.tamper", IsShow: true, Label: "Tamper", Path: "/xpack/tamper", Sort: 600}, + {ID: "120", Disabled: false, Title: "xpack.cluster.cluster", IsShow: true, Label: "Cluster", Path: "/xpack/cluster", Sort: 700}, + {ID: "115", Disabled: false, Title: "xpack.exchange.exchange", IsShow: true, Label: "FileExange", Path: "/xpack/exchange/file", Sort: 800}, + {ID: "117", Disabled: false, Title: "xpack.setting.setting", IsShow: true, Label: "XSetting", Path: "/xpack/setting", Sort: 900}, + }}, + {ID: "12", Disabled: false, Title: "menu.logs", IsShow: true, Label: "Log-Menu", Path: "/logs", Sort: 1200}, + {ID: "13", Disabled: true, Title: "menu.settings", IsShow: true, Label: "Setting-Menu", Path: "/settings", Sort: 1300}, + } + menu, _ := json.Marshal(item) + return string(menu) +} + +func MenuSort() []dto.MenuLabelSort { + var MenuLabelsWithSort = []dto.MenuLabelSort{ + {Label: "Home-Menu", Sort: 100}, + {Label: "App-Menu", Sort: 200}, + {Label: "Website-Menu", Sort: 300}, + {Label: "Website", Sort: 100}, + {Label: "SSL", Sort: 200}, + {Label: "PHP", Sort: 300}, + {Label: "AI-Menu", Sort: 400}, + {Label: "OllamaModel", Sort: 100}, + {Label: "MCPServer", Sort: 200}, + {Label: "GPU", Sort: 300}, + {Label: "Database-Menu", Sort: 500}, + {Label: "Container-Menu", Sort: 600}, + {Label: "System-Menu", Sort: 700}, + {Label: "File", Sort: 100}, + {Label: "Monitorx", Sort: 200}, + {Label: "FirewallPort", Sort: 300}, + {Label: "Process", Sort: 400}, + {Label: "SSH", Sort: 500}, + {Label: "Disk", Sort: 600}, + {Label: "Terminal-Menu", Sort: 800}, + {Label: "Cronjob-Menu", Sort: 900}, + {Label: "Toolbox-Menu", Sort: 1000}, + {Label: "Xpack-Menu", Sort: 1100}, + {Label: "XApp", Sort: 100}, + {Label: "Dashboard", Sort: 200}, + {Label: "Node", Sort: 300}, + {Label: "Upage", Sort: 400}, + {Label: "MonitorDashboard", Sort: 500}, + {Label: "Tamper", Sort: 600}, + {Label: "Cluster", Sort: 700}, + {Label: "FileExange", Sort: 800}, + {Label: "XSetting", Sort: 900}, + {Label: "Log-Menu", Sort: 1200}, + {Label: "Setting-Menu", Sort: 1300}, + } + return MenuLabelsWithSort +} + +func AddMenu(newMenu dto.ShowMenu, parentMenuID string, tx *gorm.DB) error { + var menuJSON string + if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil { + return err + } + if strings.Contains(menuJSON, fmt.Sprintf(`"%s"`, newMenu.Label)) && strings.Contains(menuJSON, fmt.Sprintf(`"%s"`, newMenu.Path)) { + return nil + } + var menus []dto.ShowMenu + if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", LoadMenus()).Error + } + for i, menu := range menus { + if menu.ID == parentMenuID { + exists := false + for _, child := range menu.Children { + if child.ID == newMenu.ID { + exists = true + break + } + } + if !exists { + menus[i].Children = append([]dto.ShowMenu{newMenu}, menus[i].Children...) + } + break + } + } + updatedJSON, err := json.Marshal(menus) + if err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", LoadMenus()).Error + } + return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error +} + +func RemoveMenuByID(menus []dto.ShowMenu, id string) []dto.ShowMenu { + var result []dto.ShowMenu + for _, menu := range menus { + if menu.ID == id { + continue + } + + if len(menu.Children) > 0 { + menu.Children = RemoveMenuByID(menu.Children, id) + } + + result = append(result, menu) + } + return result +} diff --git a/core/init/migration/migrate.go b/core/init/migration/migrate.go new file mode 100644 index 0000000..50be926 --- /dev/null +++ b/core/init/migration/migrate.go @@ -0,0 +1,37 @@ +package migration + +import ( + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/init/migration/migrations" + + "github.com/go-gormigrate/gormigrate/v2" +) + +func Init() { + m := gormigrate.New(global.DB, gormigrate.DefaultOptions, []*gormigrate.Migration{ + migrations.AddTable, + migrations.InitSetting, + migrations.InitOneDrive, + migrations.InitHost, + migrations.InitTerminalSetting, + migrations.AddTaskDB, + migrations.AddPasskeySetting, + migrations.AddXpackHideMenu, + migrations.UpdateXpackHideMenu, + migrations.UpdateOnedrive, + migrations.AddClusterMenu, + migrations.DeleteXpackHideMenu, + migrations.AddCronjobGroup, + migrations.AddDiskMenu, + migrations.AddSimpleNodeGroup, + migrations.AddUpgradeBackupCopies, + migrations.AddScriptSync, + migrations.UpdateXpackHideMenuSort, + migrations.AdjustXpackNode, + }) + if err := m.Migrate(); err != nil { + global.LOG.Error(err) + panic(err) + } + global.LOG.Info("Migration run successfully") +} diff --git a/core/init/migration/migrations/init.go b/core/init/migration/migrations/init.go new file mode 100644 index 0000000..d2d6c23 --- /dev/null +++ b/core/init/migration/migrations/init.go @@ -0,0 +1,675 @@ +package migrations + +import ( + "encoding/json" + "errors" + "fmt" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/app/dto" + + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/init/migration/helper" + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + "github.com/go-gormigrate/gormigrate/v2" + "gorm.io/gorm" +) + +var AddTable = &gormigrate.Migration{ + ID: "20240506-add-table", + Migrate: func(tx *gorm.DB) error { + return tx.AutoMigrate( + &model.OperationLog{}, + &model.LoginLog{}, + &model.Setting{}, + &model.BackupAccount{}, + &model.Group{}, + &model.Host{}, + &model.Command{}, + &model.UpgradeLog{}, + &model.ScriptLibrary{}, + ) + }, +} + +var InitSetting = &gormigrate.Migration{ + ID: "20200908-add-table-setting", + Migrate: func(tx *gorm.DB) error { + encryptKey := common.RandStr(16) + if err := tx.Create(&model.Setting{Key: "UserName", Value: global.CONF.Base.Username}).Error; err != nil { + return err + } + global.CONF.Base.EncryptKey = encryptKey + pass, _ := encrypt.StringEncrypt(global.CONF.Base.Password) + language := "en" + if global.CONF.Base.Language == "zh" { + language = "zh" + } + if err := tx.Create(&model.Setting{Key: "Password", Value: pass}).Error; err != nil { + return err + } + _, _ = cmd.RunDefaultWithStdoutBashCf("%s sed -i -e 's#ORIGINAL_PASSWORD=.*#ORIGINAL_PASSWORD=**********#g' /usr/local/bin/1pctl", cmd.SudoHandleCmd()) + if err := tx.Create(&model.Setting{Key: "Theme", Value: "light"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "MenuTabs", Value: constant.StatusDisable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "PanelName", Value: "1Panel"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "Language", Value: language}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "SessionTimeout", Value: "86400"}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "SSLType", Value: "self"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "SSLID", Value: "0"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "SSL", Value: constant.StatusDisable}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "DeveloperMode", Value: constant.StatusDisable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyType", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyUrl", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyPort", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyUser", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyPasswd", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ProxyPasswdKeep", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "HideMenu", Value: helper.LoadMenus()}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "ServerPort", Value: global.CONF.Conn.Port}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "SecurityEntrance", Value: global.CONF.Conn.Entrance}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "EncryptKey", Value: encryptKey}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ExpirationTime", Value: time.Now().AddDate(0, 0, 10).Format(constant.DateTimeLayout)}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ExpirationDays", Value: "0"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ComplexityVerification", Value: constant.StatusEnable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "MFAStatus", Value: constant.StatusDisable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "MFASecret", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "MFAInterval", Value: "30"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "PasskeyUserID", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "PasskeyCredentials", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "SystemVersion", Value: global.CONF.Base.Version}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "SystemStatus", Value: "Free"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "BindAddress", Value: "0.0.0.0"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "Ipv6", Value: constant.StatusDisable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "BindDomain", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "AllowIPs", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "NoAuthSetting", Value: "200"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ApiInterfaceStatus", Value: constant.StatusDisable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ApiKey", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "IpWhiteList", Value: ""}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ApiKeyValidityTime", Value: "120"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ScriptVersion", Value: ""}).Error; err != nil { + return err + } + + if err := tx.Create(&model.Setting{Key: "UninstallDeleteImage", Value: constant.StatusDisable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "UpgradeBackup", Value: "Enable"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "UninstallDeleteBackup", Value: constant.StatusDisable}).Error; err != nil { + return err + } + return nil + }, +} + +var AddPasskeySetting = &gormigrate.Migration{ + ID: "20250910-add-passkey-setting", + Migrate: func(tx *gorm.DB) error { + var addSettingsIfMissing = func(tx *gorm.DB, key, value string) error { + var setting model.Setting + if err := tx.Where("key = ?", key).First(&setting).Error; err != nil { + if errors.Is(err, gorm.ErrRecordNotFound) { + return tx.Create(&model.Setting{Key: key, Value: value}).Error + } + return err + } + return nil + } + if err := addSettingsIfMissing(tx, "PasskeyUserID", ""); err != nil { + return err + } + if err := addSettingsIfMissing(tx, "PasskeyCredentials", ""); err != nil { + return err + } + return nil + }, +} + +var InitTerminalSetting = &gormigrate.Migration{ + ID: "20240814-init-terminal-setting", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "LineHeight", Value: "1.2"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "LetterSpacing", Value: "0"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "FontSize", Value: "12"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "CursorBlink", Value: constant.StatusEnable}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "CursorStyle", Value: "block"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "Scrollback", Value: "1000"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "ScrollSensitivity", Value: "6"}).Error; err != nil { + return err + } + return nil + }, +} + +var InitHost = &gormigrate.Migration{ + ID: "20240816-init-host", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Group{Name: "Default", Type: "host", IsDefault: true}).Error; err != nil { + return err + } + if err := tx.Create(&model.Group{Name: "Default", Type: "node", IsDefault: true}).Error; err != nil { + return err + } + if err := tx.Create(&model.Group{Name: "Default", Type: "command", IsDefault: true}).Error; err != nil { + return err + } + if err := tx.Create(&model.Group{Name: "Default", Type: "website", IsDefault: true}).Error; err != nil { + return err + } + if err := tx.Create(&model.Group{Name: "Default", Type: "redis", IsDefault: true}).Error; err != nil { + return err + } + if err := tx.Create(&model.Group{Name: "Default", Type: "script", IsDefault: true}).Error; err != nil { + return err + } + return nil + }, +} + +var InitOneDrive = &gormigrate.Migration{ + ID: "20240808-init-one-drive", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "OneDriveID", Value: "MDEwOTM1YTktMWFhOS00ODU0LWExZGMtNmU0NWZlNjI4YzZi"}).Error; err != nil { + return err + } + if err := tx.Create(&model.Setting{Key: "OneDriveSc", Value: "akpuOFF+YkNXOU1OLWRzS1ZSRDdOcG1LT2ZRM0RLNmdvS1RkVWNGRA=="}).Error; err != nil { + return err + } + if err := tx.Create(&model.BackupAccount{ + Name: "localhost", + Type: "LOCAL", + Vars: fmt.Sprintf("{\"dir\":\"%s\"}", path.Join(global.CONF.Base.InstallDir, "1panel/backup")), + }).Error; err != nil { + return err + } + return nil + }, +} + +var AddTaskDB = &gormigrate.Migration{ + ID: "20241125-add-task-table", + Migrate: func(tx *gorm.DB) error { + return global.TaskDB.AutoMigrate( + &model.Task{}, + ) + }, +} + +var AddXpackHideMenu = &gormigrate.Migration{ + ID: "20250529-add-xpack-hide-menu", + Migrate: func(tx *gorm.DB) error { + var menuJSON string + if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil { + return err + } + if strings.Contains(menuJSON, `"XApp"`) && strings.Contains(menuJSON, `"/xpack/app"`) { + return nil + } + + var menus []dto.ShowMenu + if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + newItem := dto.ShowMenu{ + ID: "118", + Disabled: false, + Title: "xpack.app.app", + IsShow: true, + Label: "XApp", + Path: "/xpack/app", + } + + for i, menu := range menus { + if menu.ID == "11" { + exists := false + for _, child := range menu.Children { + if child.ID == newItem.ID { + exists = true + break + } + } + if !exists { + menus[i].Children = append([]dto.ShowMenu{newItem}, menus[i].Children...) + } + break + } + } + + updatedJSON, err := json.Marshal(menus) + if err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error + }, +} + +var UpdateXpackHideMenu = &gormigrate.Migration{ + ID: "20250617-update-xpack-hide-menu", + Migrate: func(tx *gorm.DB) error { + var menuJSON string + if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil { + return err + } + var menus []dto.ShowMenu + if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + newItem := dto.ShowMenu{ + ID: "119", + Disabled: false, + Title: "xpack.upage", + IsShow: true, + Label: "Upage", + Path: "/xpack/upage", + } + + for i, menu := range menus { + if menu.ID == "11" { + exists := false + for _, child := range menu.Children { + if child.ID == newItem.ID { + exists = true + break + } + } + if exists { + break + } + + insertIndex := -1 + for j, child := range menu.Children { + if child.ID == "111" { + insertIndex = j + break + } + } + + if insertIndex != -1 { + children := menu.Children + menus[i].Children = append(children[:insertIndex+1], append([]dto.ShowMenu{newItem}, children[insertIndex+1:]...)...) + } else { + menus[i].Children = append([]dto.ShowMenu{newItem}, menus[i].Children...) + } + break + } + } + + for i, menu := range menus { + if menu.ID == "11" { + existsIndex := -1 + for j, child := range menu.Children { + if child.ID == "118" { + existsIndex = j + break + } + } + + if existsIndex == 0 { + break + } + + var item118 dto.ShowMenu + if existsIndex != -1 { + item118 = menu.Children[existsIndex] + menus[i].Children = append(menu.Children[:existsIndex], menu.Children[existsIndex+1:]...) + } else { + item118 = dto.ShowMenu{ + ID: "118", + Disabled: false, + Title: "xpack.app.app", + IsShow: true, + Label: "XApp", + Path: "/xpack/app", + } + } + + menus[i].Children = append([]dto.ShowMenu{item118}, menus[i].Children...) + break + } + } + + var idx9, idx10 = -1, -1 + for i, menu := range menus { + if menu.ID == "9" && menu.Path == "/toolbox" { + idx9 = i + } + if menu.ID == "10" && menu.Path == "/cronjobs" { + idx10 = i + } + } + if idx9 != -1 && idx10 != -1 && idx10 > idx9 { + menus[idx9], menus[idx10] = menus[idx10], menus[idx9] + } + + for i, menu := range menus { + if menu.ID == "7" { + for j, child := range menu.Children { + if child.ID == "75" { + if child.Title != "menu.processManage" { + menus[i].Children[j].Title = "menu.processManage" + } + break + } + } + break + } + } + + updatedJSON, err := json.Marshal(menus) + if err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error + }, +} + +var UpdateOnedrive = &gormigrate.Migration{ + ID: "20250704-update-onedrive", + Migrate: func(tx *gorm.DB) error { + if err := tx.Model(&model.Setting{}). + Where("key = ?", "OneDriveID"). + Update("value", "NTQ0NmNmZTMtNGM3OS00N2EwLWFlMjUtZmM2NDU0NzhlMmQ5").Error; err != nil { + return err + } + if err := tx.Model(&model.Setting{}). + Where("key = ?", "OneDriveSc"). + Update("value", "bGRlOFF+WEVrR1M0b25Vb1VsRWpMYzE2MW9rTXZEM25KdnZ1MGN6MA==").Error; err != nil { + return err + } + return nil + }, +} + +var AddClusterMenu = &gormigrate.Migration{ + ID: "20250707-add-cluster-menu", + Migrate: func(tx *gorm.DB) error { + return helper.AddMenu(dto.ShowMenu{ + ID: "120", + Disabled: false, + Title: "xpack.cluster.cluster", + IsShow: true, + Label: "Cluster", + Path: "/xpack/cluster", + }, "11", tx) + }, +} + +var DeleteXpackHideMenu = &gormigrate.Migration{ + ID: "20250718-delete-xpack-hide-menu", + Migrate: func(tx *gorm.DB) error { + var menuJSON string + if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil { + return err + } + if menuJSON == "" { + menuJSON = helper.LoadMenus() + } + var menus []dto.ShowMenu + if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + menus = helper.RemoveMenuByID(menus, "116") + updatedJSON, err := json.Marshal(menus) + if err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error + }, +} + +var AddCronjobGroup = &gormigrate.Migration{ + ID: "20250729-add-cronjob-group", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Group{Name: "Default", Type: "cronjob", IsDefault: true}).Error; err != nil { + return err + } + return nil + }, +} + +var AddDiskMenu = &gormigrate.Migration{ + ID: "20250811-add-disk-menu", + Migrate: func(tx *gorm.DB) error { + return helper.AddMenu(dto.ShowMenu{ + ID: "77", + Disabled: false, + Title: "menu.disk", + IsShow: true, + Label: "Disk", + Path: "/hosts/disk", + }, "7", tx) + }, +} + +var AddSimpleNodeGroup = &gormigrate.Migration{ + ID: "20250916-add-simple-node-group", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Group{Name: "Default", Type: "SimpleNode", IsDefault: true}).Error; err != nil { + return err + } + return nil + }, +} + +var AddUpgradeBackupCopies = &gormigrate.Migration{ + ID: "20250925-add-upgrade-backup-copies", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "UpgradeBackupCopies", Value: "0"}).Error; err != nil { + return err + } + return nil + }, +} + +var AddScriptSync = &gormigrate.Migration{ + ID: "20250916-add-script-sync", + Migrate: func(tx *gorm.DB) error { + if err := tx.Create(&model.Setting{Key: "ScriptSync", Value: constant.StatusEnable}).Error; err != nil { + return err + } + return nil + }, +} + +var UpdateXpackHideMenuSort = &gormigrate.Migration{ + ID: "20251009-update-xpack-hide-menu-sort", + Migrate: func(tx *gorm.DB) error { + var menuJSON string + if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil { + return err + } + + var menus []dto.ShowMenu + if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + labelSortMap := make(map[string]int) + for _, item := range helper.MenuSort() { + labelSortMap[item.Label] = item.Sort + } + + for i := range menus { + if sortVal, exists := labelSortMap[menus[i].Label]; exists { + menus[i].Sort = sortVal + } else if menus[i].Sort <= 0 { + menus[i].Sort = (i + 1) * 100 + } + + for j := range menus[i].Children { + if childSort, exists := labelSortMap[menus[i].Children[j].Label]; exists { + menus[i].Children[j].Sort = childSort + } else if menus[i].Children[j].Sort <= 0 { + menus[i].Children[j].Sort = menus[i].Sort + (j+1)*10 + } + } + } + + updatedJSON, err := json.Marshal(menus) + if err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error + }, +} + +var AdjustXpackNode = &gormigrate.Migration{ + ID: "20260108-adjust-xpack-node", + Migrate: func(tx *gorm.DB) error { + var menuJSON string + if err := tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Pluck("value", &menuJSON).Error; err != nil { + return err + } + if menuJSON == "" { + menuJSON = helper.LoadMenus() + } + var menus []dto.ShowMenu + if err := json.Unmarshal([]byte(menuJSON), &menus); err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + for i := 0; i < len(menus); i++ { + if menus[i].Label != "Xpack-Menu" { + continue + } + for j := 0; j < len(menus[i].Children); j++ { + if menus[i].Children[j].Label == "Node" { + menus[i].Children[j].Label = "NodeDashboard" + menus[i].Children[j].Path = "/xpack/node/dashboard" + break + } + } + } + + updatedJSON, err := json.Marshal(menus) + if err != nil { + return tx.Model(&model.Setting{}). + Where("key = ?", "HideMenu"). + Update("value", helper.LoadMenus()).Error + } + + return tx.Model(&model.Setting{}).Where("key = ?", "HideMenu").Update("value", string(updatedJSON)).Error + }, +} diff --git a/core/init/proxy/proxy.go b/core/init/proxy/proxy.go new file mode 100644 index 0000000..7cbb952 --- /dev/null +++ b/core/init/proxy/proxy.go @@ -0,0 +1,42 @@ +package proxy + +import ( + "context" + "net" + "net/http" + "net/http/httputil" + "time" +) + +const SockPath = "/etc/1panel/agent.sock" + +var ( + LocalAgentProxy *httputil.ReverseProxy +) + +func Init() { + dialer := &net.Dialer{ + Timeout: 5 * time.Second, + } + dialUnix := func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialer.DialContext(ctx, "unix", SockPath) + } + transport := &http.Transport{ + DialContext: dialUnix, + ForceAttemptHTTP2: false, + MaxIdleConns: 50, + MaxIdleConnsPerHost: 50, + IdleConnTimeout: 30 * time.Second, + } + LocalAgentProxy = &httputil.ReverseProxy{ + Director: func(req *http.Request) { + req.URL.Scheme = "http" + req.URL.Host = "unix" + }, + Transport: transport, + ErrorHandler: func(rw http.ResponseWriter, req *http.Request, err error) { + rw.WriteHeader(http.StatusBadGateway) + _, _ = rw.Write([]byte("Bad Gateway: " + err.Error())) + }, + } +} diff --git a/core/init/router/proxy.go b/core/init/router/proxy.go new file mode 100644 index 0000000..783c191 --- /dev/null +++ b/core/init/router/proxy.go @@ -0,0 +1,89 @@ +package router + +import ( + "net/http" + "net/url" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/core/init/proxy" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/cmd/server/res" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/xpack" + "github.com/gin-gonic/gin" +) + +func Proxy() gin.HandlerFunc { + return func(c *gin.Context) { + reqPath := c.Request.URL.Path + if strings.HasPrefix(reqPath, "/1panel/swagger") || !strings.HasPrefix(c.Request.URL.Path, "/api/v2") { + c.Next() + return + } + if strings.HasPrefix(reqPath, "/api/v2/core") && !strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/xpack") { + c.Next() + return + } + var nodeItem string + queryNode := c.Query("operateNode") + if queryNode != "" && queryNode != "undefined" { + nodeItem = queryNode + } else { + nodeItem = c.Request.Header.Get("CurrentNode") + } + currentNode, err := url.QueryUnescape(nodeItem) + if err != nil { + helper.ErrorWithDetail(c, http.StatusBadRequest, "ErrProxy", err) + return + } + + apiReq := c.GetBool("API_AUTH") + + if !apiReq && strings.HasPrefix(c.Request.URL.Path, "/api/v2/") && !isLocalAPI(c.Request.URL.Path) && !checkSession(c) { + data, _ := res.ErrorMsg.ReadFile("html/401.html") + c.Data(401, "text/html; charset=utf-8", data) + c.Abort() + return + } + + if !strings.HasPrefix(c.Request.URL.Path, "/api/v2/core") && (currentNode == "local" || len(currentNode) == 0) { + defer func() { + if err := recover(); err != nil && err != http.ErrAbortHandler { + global.LOG.Debug(err) + } + }() + proxy.LocalAgentProxy.ServeHTTP(c.Writer, c.Request) + c.Abort() + return + } + xpack.Proxy(c, currentNode) + c.Abort() + } +} + +func checkSession(c *gin.Context) bool { + psession, err := global.SESSION.Get(c) + if err != nil { + return false + } + settingRepo := repo.NewISettingRepo() + setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout")) + if err != nil { + return false + } + lifeTime, _ := strconv.Atoi(setting.Value) + httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL")) + if err != nil { + return false + } + _ = global.SESSION.Set(c, psession, httpsSetting.Value == constant.StatusEnable, lifeTime) + return true +} + +func isLocalAPI(urlPath string) bool { + return urlPath == "/api/v2/core/xpack/sync/ssl" +} diff --git a/core/init/router/router.go b/core/init/router/router.go new file mode 100644 index 0000000..d863bc2 --- /dev/null +++ b/core/init/router/router.go @@ -0,0 +1,150 @@ +package router + +import ( + "encoding/base64" + "io" + "net/http" + "os" + "path/filepath" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/service" + "github.com/1Panel-dev/1Panel/core/cmd/server/docs" + "github.com/1Panel-dev/1Panel/core/cmd/server/web" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/1Panel-dev/1Panel/core/init/swagger" + "github.com/1Panel-dev/1Panel/core/middleware" + rou "github.com/1Panel-dev/1Panel/core/router" + "github.com/1Panel-dev/1Panel/core/utils/security" + "github.com/gin-contrib/gzip" + "github.com/gin-gonic/gin" +) + +var ( + Router *gin.Engine +) + +func setWebStatic(rootRouter *gin.RouterGroup) { + rootRouter.StaticFS("/public", http.FS(web.Favicon)) + rootRouter.StaticFS("/favicon.ico", http.FS(web.Favicon)) + RegisterImages(rootRouter) + setStaticResource(rootRouter) + rootRouter.GET("/assets/*filepath", func(c *gin.Context) { + c.Writer.Header().Set("Cache-Control", "private, max-age=2628000, immutable") + if c.Request.URL.Path[len(c.Request.URL.Path)-1] == '/' { + c.AbortWithStatus(http.StatusForbidden) + return + } + staticServer := http.FileServer(http.FS(web.Assets)) + staticServer.ServeHTTP(c.Writer, c.Request) + }) + authService := service.NewIAuthService() + entrance := authService.GetSecurityEntrance() + if entrance != "" { + rootRouter.GET("/"+entrance, func(c *gin.Context) { + currentEntrance := authService.GetSecurityEntrance() + if currentEntrance != entrance { + security.HandleNotSecurity(c, "") + return + } + security.ToIndexHtml(c) + }) + } + rootRouter.GET("/", func(c *gin.Context) { + if !security.CheckSecurity(c) { + return + } + entrance = authService.GetSecurityEntrance() + if entrance != "" { + entranceValue := base64.StdEncoding.EncodeToString([]byte(entrance)) + c.SetCookie("SecurityEntrance", entranceValue, 0, "", "", false, true) + } + staticServer := http.FileServer(http.FS(web.IndexHtml)) + staticServer.ServeHTTP(c.Writer, c.Request) + }) +} + +func Routers() *gin.Engine { + Router = gin.New() + Router.Use(i18n.UseI18n()) + Router.Use(middleware.WhiteAllow()) + Router.Use(middleware.BindDomain()) + + swaggerRouter := Router.Group("1panel") + docs.SwaggerInfo.BasePath = "/api/v2" + swaggerRouter.Use(middleware.SessionAuth()).GET("/swagger/*any", swagger.SwaggerHandler()) + + PublicGroup := Router.Group("") + { + PublicGroup.Use(gzip.Gzip(gzip.DefaultCompression)) + setWebStatic(PublicGroup) + } + if global.CONF.Base.IsDemo { + Router.Use(middleware.DemoHandle()) + } + + Router.Use(middleware.OperationLog()) + Router.Use(middleware.GlobalLoading()) + Router.Use(middleware.PasswordExpired()) + Router.Use(middleware.ApiAuth()) + + PrivateGroup := Router.Group("/api/v2/core") + PrivateGroup.Use(middleware.SetPasswordPublicKey()) + for _, router := range rou.RouterGroupApp { + router.InitRouter(PrivateGroup) + } + + Router.Use(Proxy()) + Router.NoRoute(func(c *gin.Context) { + if !security.HandleNotRoute(c) { + return + } + security.HandleNotSecurity(c, "") + }) + + return Router +} + +func RegisterImages(rootRouter *gin.RouterGroup) { + staticDir := filepath.Join(global.CONF.Base.InstallDir, "1panel/uploads/theme") + rootRouter.GET("/api/v2/images/*filename", func(c *gin.Context) { + fileName := filepath.Base(c.Param("filename")) + filePath := filepath.Join(staticDir, fileName) + if !strings.HasPrefix(filePath, staticDir) { + c.AbortWithStatus(http.StatusForbidden) + return + } + f, err := os.Open(filePath) + if err != nil { + c.Status(http.StatusNotFound) + return + } + defer f.Close() + buf := make([]byte, 512) + n, _ := f.Read(buf) + content := buf[:n] + mimeType := http.DetectContentType(buf[:n]) + if strings.Contains(string(content), " + window.ui = SwaggerUIBundle({ + url: "{{.URL}}", + dom_id: '#swagger-ui', + deepLinking: true, + presets: [ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ], + plugins: [ + SwaggerUIBundle.plugins.DownloadUrl + ], + layout: "StandaloneLayout" + }); + + // +}; +` + +func SwaggerHandler() gin.HandlerFunc { + fileServer := http.StripPrefix("/1panel/swagger/", http.FileServer(http.FS(swaggerfiles.FS))) + + return func(c *gin.Context) { + path := c.Request.URL.Path + path = strings.TrimPrefix(path, "/1panel/swagger") + if !matcher.MatchString(path) && path != "/" { + // 404 + c.AbortWithStatus(http.StatusNotFound) + return + } + switch path { + case "/doc.json": + c.Header("Content-Type", "application/json; charset=utf-8") + c.Header("Cache-Control", "private, max-age=600") + c.String(http.StatusOK, docs.SwaggerInfo.ReadDoc()) + case "/swagger-initializer.js": + c.Header("Content-Type", "application/javascript; charset=utf-8") + c.Header("Cache-Control", "private, max-age=600") + c.String(http.StatusOK, strings.ReplaceAll(CustomSwaggerInitializerJS, "{{.URL}}", swaggerDocFile)) + default: + + c.Header("Cache-Control", "private, max-age=600") + fileServer.ServeHTTP(c.Writer, c.Request) + } + } +} diff --git a/core/init/validator/validator.go b/core/init/validator/validator.go new file mode 100644 index 0000000..06cb694 --- /dev/null +++ b/core/init/validator/validator.go @@ -0,0 +1,65 @@ +package validator + +import ( + "regexp" + "unicode" + + "github.com/1Panel-dev/1Panel/core/global" + + "github.com/go-playground/validator/v10" +) + +func Init() { + validator := validator.New() + if err := validator.RegisterValidation("name", checkNamePattern); err != nil { + panic(err) + } + if err := validator.RegisterValidation("ip", checkIpPattern); err != nil { + panic(err) + } + if err := validator.RegisterValidation("password", checkPasswordPattern); err != nil { + panic(err) + } + global.VALID = validator +} + +func checkNamePattern(fl validator.FieldLevel) bool { + value := fl.Field().String() + result, err := regexp.MatchString("^[a-zA-Z\u4e00-\u9fa5]{1}[a-zA-Z0-9_\u4e00-\u9fa5]{0,30}$", value) + if err != nil { + global.LOG.Errorf("regexp matchString failed, %v", err) + } + return result +} + +func checkIpPattern(fl validator.FieldLevel) bool { + value := fl.Field().String() + result, err := regexp.MatchString(`^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$`, value) + if err != nil { + global.LOG.Errorf("regexp check ip matchString failed, %v", err) + } + return result +} + +func checkPasswordPattern(fl validator.FieldLevel) bool { + value := fl.Field().String() + if len(value) < 8 || len(value) > 30 { + return false + } + + hasNum := false + hasLetter := false + for _, r := range value { + if unicode.IsLetter(r) && !hasLetter { + hasLetter = true + } + if unicode.IsNumber(r) && !hasNum { + hasNum = true + } + if hasLetter && hasNum { + return true + } + } + + return false +} diff --git a/core/init/viper/viper.go b/core/init/viper/viper.go new file mode 100644 index 0000000..73a0190 --- /dev/null +++ b/core/init/viper/viper.go @@ -0,0 +1,112 @@ +package viper + +import ( + "bytes" + "fmt" + "os" + "path" + "strings" + + "github.com/1Panel-dev/1Panel/core/cmd/server/conf" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/fsnotify/fsnotify" + "github.com/spf13/viper" + "gopkg.in/yaml.v3" +) + +func Init() { + baseDir := "/opt" + port := "9999" + mode := "" + version := "v2.0.0" + username, password, entrance, language := "", "", "", "zh" + v := viper.NewWithOptions() + v.SetConfigType("yaml") + + config := global.ServerConfig{} + if err := yaml.Unmarshal(conf.AppYaml, &config); err != nil { + panic(err) + } + if config.Base.Mode != "" { + mode = config.Base.Mode + } + _, err := os.Stat("/opt/1panel/conf/app.yaml") + if mode == "dev" && err == nil { + v.SetConfigName("app") + v.AddConfigPath(path.Join("/opt/1panel/conf")) + if err := v.ReadInConfig(); err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + } else { + baseDir = common.LoadParams("BASE_DIR") + port = common.LoadParams("ORIGINAL_PORT") + version = common.LoadParams("ORIGINAL_VERSION") + username = common.LoadParams("ORIGINAL_USERNAME") + password = common.LoadParams("ORIGINAL_PASSWORD") + entrance = common.LoadParams("ORIGINAL_ENTRANCE") + language = common.LoadParams("LANGUAGE") + + reader := bytes.NewReader(conf.AppYaml) + if err := v.ReadConfig(reader); err != nil { + panic(fmt.Errorf("Fatal error config file: %s \n", err)) + } + } + v.OnConfigChange(func(e fsnotify.Event) { + if err := v.Unmarshal(&global.CONF); err != nil { + panic(err) + } + }) + serverConfig := global.ServerConfig{} + if err := v.Unmarshal(&serverConfig); err != nil { + panic(err) + } + _, err = os.Stat("/opt/1panel/conf/app.yaml") + if mode == "dev" && err == nil { + if serverConfig.Base.InstallDir != "" { + baseDir = serverConfig.Base.InstallDir + } + if serverConfig.Conn.Port != "" { + port = serverConfig.Conn.Port + } + if serverConfig.Base.Version != "" { + version = serverConfig.Base.Version + } + if serverConfig.Base.Username != "" { + username = serverConfig.Base.Username + } + if serverConfig.Base.Password != "" { + password = serverConfig.Base.Password + } + if serverConfig.Conn.Entrance != "" { + entrance = serverConfig.Conn.Entrance + } + if serverConfig.Base.IsIntl { + language = "en" + } + } + + global.CONF = serverConfig + global.CONF.Base.InstallDir = baseDir + global.CONF.Base.IsDemo = v.GetBool("base.is_demo") + global.CONF.Base.IsIntl = v.GetBool("base.is_intl") + global.CONF.Base.IsFxplay = v.GetBool("base.is_fxplay") + global.CONF.Base.IsOffLine = v.GetBool("base.is_offline") + global.CONF.Base.Version = version + global.CONF.Base.Username = username + global.CONF.Base.Password = password + global.CONF.Base.Language = language + global.CONF.Base.ChangeUserInfo = loadChangeInfo() + global.CONF.Conn.Entrance = entrance + global.CONF.Conn.Port = port + global.Viper = v +} + +func loadChangeInfo() string { + stdout, err := cmd.RunDefaultWithStdoutBashC("grep '^CHANGE_USER_INFO=' /usr/local/bin/1pctl | cut -d'=' -f2") + if err != nil { + return "" + } + return strings.ReplaceAll(stdout, "\n", "") +} diff --git a/core/log/config.go b/core/log/config.go new file mode 100644 index 0000000..8a02a54 --- /dev/null +++ b/core/log/config.go @@ -0,0 +1,44 @@ +package log + +import ( + "errors" + "io" + "os" + "path" + + "github.com/1Panel-dev/1Panel/core/constant" +) + +var ( + BufferSize = 0x100000 + DefaultFileMode = os.FileMode(constant.DirPerm) + DefaultFileFlag = os.O_RDWR | os.O_CREATE | os.O_APPEND + ErrInvalidArgument = errors.New("error argument invalid") + QueueSize = 1024 + LogQueueSize = 1024 + ErrClosed = errors.New("error write on close") +) + +type Config struct { + TimeTagFormat string + LogPath string + FileName string + LogSuffix string + MaxRemain int + RollingTimePattern string +} + +type Manager interface { + Fire() chan string + Close() +} + +type RollingWriter interface { + io.Writer + Close() error +} + +func FilePath(c *Config) (filepath string) { + filepath = path.Join(c.LogPath, c.FileName) + c.LogSuffix + return +} diff --git a/core/log/dup_write_darwin.go b/core/log/dup_write_darwin.go new file mode 100644 index 0000000..822d24e --- /dev/null +++ b/core/log/dup_write_darwin.go @@ -0,0 +1,20 @@ +package log + +import ( + "golang.org/x/sys/unix" + "os" + "runtime" +) + +var stdErrFileHandler *os.File + +func dupWrite(file *os.File) error { + stdErrFileHandler = file + if err := unix.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil { + return err + } + runtime.SetFinalizer(stdErrFileHandler, func(fd *os.File) { + fd.Close() + }) + return nil +} diff --git a/core/log/dup_write_linux.go b/core/log/dup_write_linux.go new file mode 100644 index 0000000..822d24e --- /dev/null +++ b/core/log/dup_write_linux.go @@ -0,0 +1,20 @@ +package log + +import ( + "golang.org/x/sys/unix" + "os" + "runtime" +) + +var stdErrFileHandler *os.File + +func dupWrite(file *os.File) error { + stdErrFileHandler = file + if err := unix.Dup2(int(file.Fd()), int(os.Stderr.Fd())); err != nil { + return err + } + runtime.SetFinalizer(stdErrFileHandler, func(fd *os.File) { + fd.Close() + }) + return nil +} diff --git a/core/log/dup_write_windows.go b/core/log/dup_write_windows.go new file mode 100644 index 0000000..4996cc1 --- /dev/null +++ b/core/log/dup_write_windows.go @@ -0,0 +1,9 @@ +package log + +import ( + "os" +) + +func dupWrite(file *os.File) error { + return nil +} diff --git a/core/log/manager.go b/core/log/manager.go new file mode 100644 index 0000000..1f4f945 --- /dev/null +++ b/core/log/manager.go @@ -0,0 +1,53 @@ +package log + +import ( + "github.com/robfig/cron/v3" + "path" + "sync" + "time" +) + +type manager struct { + startAt time.Time + fire chan string + cr *cron.Cron + context chan int + wg sync.WaitGroup + lock sync.Mutex +} + +func (m *manager) Fire() chan string { + return m.fire +} + +func (m *manager) Close() { + close(m.context) + m.cr.Stop() +} + +func NewManager(c *Config) (Manager, error) { + m := &manager{ + startAt: time.Now(), + cr: cron.New(), + fire: make(chan string), + context: make(chan int), + wg: sync.WaitGroup{}, + } + + if _, err := m.cr.AddFunc(c.RollingTimePattern, func() { + m.fire <- m.GenLogFileName(c) + }); err != nil { + return nil, err + } + m.cr.Start() + + return m, nil +} + +func (m *manager) GenLogFileName(c *Config) (filename string) { + m.lock.Lock() + filename = path.Join(c.LogPath, c.FileName+"-"+m.startAt.Format(c.TimeTagFormat)) + c.LogSuffix + m.startAt = time.Now() + m.lock.Unlock() + return +} diff --git a/core/log/writer.go b/core/log/writer.go new file mode 100644 index 0000000..17f5380 --- /dev/null +++ b/core/log/writer.go @@ -0,0 +1,251 @@ +package log + +import ( + "log" + "os" + "path" + "sort" + "strings" + "sync" + "sync/atomic" + "time" + + "github.com/1Panel-dev/1Panel/core/constant" + + "github.com/1Panel-dev/1Panel/core/global" +) + +type Writer struct { + m Manager + file *os.File + absPath string + fire chan string + cf *Config + rollingfilech chan string + queue chan []byte +} + +type AsynchronousWriter struct { + Writer + ctx chan int + errChan chan error + closed int32 + wg sync.WaitGroup +} + +func (w *AsynchronousWriter) Close() error { + if atomic.CompareAndSwapInt32(&w.closed, 0, 1) { + close(w.ctx) + w.onClose() + + func() { + defer func() { + if r := recover(); r != nil { + global.LOG.Error(r) + } + }() + w.m.Close() + }() + return w.file.Close() + } + return ErrClosed +} + +func (w *AsynchronousWriter) onClose() { + var err error + for { + select { + case b := <-w.queue: + if _, err = w.file.Write(b); err != nil { + select { + case w.errChan <- err: + default: + _asyncBufferPool.Put(&b) + return + } + } + _asyncBufferPool.Put(&b) + default: + return + } + } +} + +var _asyncBufferPool = sync.Pool{ + New: func() interface{} { + return make([]byte, BufferSize) + }, +} + +func NewWriterFromConfig(c *Config) (RollingWriter, error) { + if c.LogPath == "" || c.FileName == "" { + return nil, ErrInvalidArgument + } + if err := os.MkdirAll(c.LogPath, 0700); err != nil { + return nil, err + } + filepath := FilePath(c) + file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE|os.O_APPEND, constant.FilePerm) + if err != nil { + return nil, err + } + if err := dupWrite(file); err != nil { + return nil, err + } + mng, err := NewManager(c) + if err != nil { + return nil, err + } + + var rollingWriter RollingWriter + writer := Writer{ + queue: make(chan []byte, LogQueueSize), + m: mng, + file: file, + absPath: filepath, + fire: mng.Fire(), + cf: c, + } + if c.MaxRemain > 0 { + writer.rollingfilech = make(chan string, c.MaxRemain) + dir, err := os.ReadDir(c.LogPath) + if err != nil { + mng.Close() + return nil, err + } + + files := make([]string, 0, 10) + for _, fi := range dir { + if fi.IsDir() { + continue + } + + fileName := c.FileName + if strings.Contains(fi.Name(), fileName) && strings.Contains(fi.Name(), c.LogSuffix) { + start := strings.Index(fi.Name(), "-") + end := strings.Index(fi.Name(), c.LogSuffix) + name := fi.Name() + if start > 0 && end > 0 { + _, err := time.Parse(c.TimeTagFormat, name[start+1:end]) + if err == nil { + files = append(files, fi.Name()) + } + } + } + } + sort.Slice(files, func(i, j int) bool { + t1Start := strings.Index(files[i], "-") + t1End := strings.Index(files[i], c.LogSuffix) + t2Start := strings.Index(files[i], "-") + t2End := strings.Index(files[i], c.LogSuffix) + t1, _ := time.Parse(c.TimeTagFormat, files[i][t1Start+1:t1End]) + t2, _ := time.Parse(c.TimeTagFormat, files[j][t2Start+1:t2End]) + return t1.Before(t2) + }) + + for _, file := range files { + retry: + select { + case writer.rollingfilech <- path.Join(c.LogPath, file): + default: + writer.DoRemove() + goto retry + } + } + } + + wr := &AsynchronousWriter{ + ctx: make(chan int), + errChan: make(chan error, QueueSize), + wg: sync.WaitGroup{}, + closed: 0, + Writer: writer, + } + rollingWriter = wr + wr.wg.Add(1) + go wr.writer() + wr.wg.Wait() + + return rollingWriter, nil +} + +func (w *AsynchronousWriter) writer() { + var err error + w.wg.Done() + for { + select { + case filename := <-w.fire: + if err = w.Reopen(filename); err != nil && len(w.errChan) < cap(w.errChan) { + w.errChan <- err + } + case b := <-w.queue: + if _, err = w.file.Write(b); err != nil && len(w.errChan) < cap(w.errChan) { + w.errChan <- err + } + _asyncBufferPool.Put(&b) + case <-w.ctx: + return + } + } +} + +func (w *Writer) DoRemove() { + file := <-w.rollingfilech + if err := os.Remove(file); err != nil { + log.Println("error in remove log file", file, err) + } +} + +func (w *Writer) Write(b []byte) (int, error) { + var ok = false + for !ok { + select { + case filename := <-w.fire: + if err := w.Reopen(filename); err != nil { + return 0, err + } + default: + ok = true + } + } + + select { + case w.queue <- b: + return len(b), nil + } +} + +func (w *Writer) Reopen(file string) error { + fileInfo, err := w.file.Stat() + if err != nil { + return err + } + + if fileInfo.Size() == 0 { + return nil + } + + w.file.Close() + if err := os.Rename(w.absPath, file); err != nil { + return err + } + newFile, err := os.OpenFile(w.absPath, DefaultFileFlag, DefaultFileMode) + if err != nil { + return err + } + + w.file = newFile + + go func() { + if w.cf.MaxRemain > 0 { + retry: + select { + case w.rollingfilech <- file: + default: + w.DoRemove() + goto retry + } + } + }() + return nil +} diff --git a/core/middleware/api_auth.go b/core/middleware/api_auth.go new file mode 100644 index 0000000..1a1b92c --- /dev/null +++ b/core/middleware/api_auth.go @@ -0,0 +1,118 @@ +package middleware + +import ( + "crypto/md5" + "encoding/hex" + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/gin-gonic/gin" + "net" + "strconv" + "strings" + "time" +) + +func ApiAuth() gin.HandlerFunc { + return func(c *gin.Context) { + if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") { + c.Next() + return + } + + panelToken := c.GetHeader("1Panel-Token") + panelTimestamp := c.GetHeader("1Panel-Timestamp") + if panelToken != "" || panelTimestamp != "" { + if global.Api.ApiInterfaceStatus == constant.StatusEnable { + clientIP := c.ClientIP() + if !isValid1PanelTimestamp(panelTimestamp) { + helper.BadAuth(c, "ErrApiConfigKeyTimeInvalid", nil) + return + } + if !isValid1PanelToken(panelToken, panelTimestamp) { + helper.BadAuth(c, "ErrApiConfigKeyInvalid", nil) + return + } + + if !isIPInWhiteList(clientIP) { + helper.BadAuth(c, "ErrApiConfigIPInvalid", nil) + return + } + c.Set("API_AUTH", true) + c.Next() + return + } else { + helper.BadAuth(c, "ErrApiConfigStatusInvalid", nil) + return + } + } + } +} + +func isValid1PanelTimestamp(panelTimestamp string) bool { + apiKeyValidityTime := global.Api.ApiKeyValidityTime + apiTime, err := strconv.Atoi(apiKeyValidityTime) + if err != nil || apiTime < 0 { + global.LOG.Errorf("apiTime %d, err: %v", apiTime, err) + return false + } + if apiTime == 0 { + return true + } + panelTime, err := strconv.ParseInt(panelTimestamp, 10, 64) + if err != nil { + global.LOG.Errorf("panelTimestamp %s, panelTime %d, apiTime %d, err: %v", panelTimestamp, apiTime, panelTime, err) + return false + } + nowTime := time.Now().Unix() + tolerance := int64(60) + if panelTime > nowTime+tolerance { + global.LOG.Errorf("Valid Panel Timestamp, apiTime %d, panelTime %d, nowTime %d, err: %v", apiTime, panelTime, nowTime, err) + return false + } + return nowTime-panelTime <= int64(apiTime)*60+tolerance +} + +func isValid1PanelToken(panelToken string, panelTimestamp string) bool { + system1PanelToken := global.Api.ApiKey + return panelToken == GenerateMD5("1panel"+system1PanelToken+panelTimestamp) +} + +func isIPInWhiteList(clientIP string) bool { + ipWhiteString := global.Api.IpWhiteList + if len(ipWhiteString) == 0 { + global.LOG.Error("IP whitelist is empty") + return false + } + ipWhiteList, ipErr := common.HandleIPList(ipWhiteString) + if ipErr != nil { + global.LOG.Errorf("Failed to handle IP list: %v", ipErr) + return false + } + clientParsedIP := net.ParseIP(clientIP) + if clientParsedIP == nil { + return false + } + iPv4 := clientParsedIP.To4() + iPv6 := clientParsedIP.To16() + for _, cidr := range ipWhiteList { + if (iPv4 != nil && (cidr == "0.0.0.0" || cidr == "0.0.0.0/0" || iPv4.String() == cidr)) || (iPv6 != nil && (cidr == "::/0" || iPv6.String() == cidr)) { + return true + } + _, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + continue + } + if (iPv4 != nil && ipNet.Contains(iPv4)) || (iPv6 != nil && ipNet.Contains(iPv6)) { + return true + } + } + return false +} + +func GenerateMD5(param string) string { + hash := md5.New() + hash.Write([]byte(param)) + return hex.EncodeToString(hash.Sum(nil)) +} diff --git a/core/middleware/bind_domain.go b/core/middleware/bind_domain.go new file mode 100644 index 0000000..ad85708 --- /dev/null +++ b/core/middleware/bind_domain.go @@ -0,0 +1,41 @@ +package middleware + +import ( + "strings" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/gin-gonic/gin" +) + +func BindDomain() gin.HandlerFunc { + return func(c *gin.Context) { + localRequest := c.GetBool("LOCAL_REQUEST") + if localRequest { + c.Next() + return + } + settingRepo := repo.NewISettingRepo() + status, err := settingRepo.Get(repo.WithByKey("BindDomain")) + if err != nil { + helper.InternalServer(c, err) + return + } + if len(status.Value) == 0 { + c.Next() + return + } + domains := c.Request.Host + parts := strings.Split(c.Request.Host, ":") + if len(parts) > 0 { + domains = parts[0] + } + + if domains != status.Value { + code := LoadErrCode() + helper.ErrWithHtml(c, code, "err_domain") + return + } + c.Next() + } +} diff --git a/core/middleware/demo_handle.go b/core/middleware/demo_handle.go new file mode 100644 index 0000000..532d75f --- /dev/null +++ b/core/middleware/demo_handle.go @@ -0,0 +1,74 @@ +package middleware + +import ( + "net/http" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/gin-gonic/gin" +) + +var whiteUrlList = map[string]struct{}{ + "/api/v2/dashboard/app/launcher/option": {}, + "/api/v2/websites/config": {}, + "/api/v2/websites/waf/config": {}, + "/api/v2/files/loadfile": {}, + "/api/v2/files/size": {}, + "/api/v2/runtimes/sync": {}, + "/api/v2/toolbox/device/base": {}, + "/api/v2/files/user/group": {}, + "/api/v2/files/mount": {}, + "/api/v2/hosts/ssh/log": {}, + "/api/v2/toolbox/clam/base": {}, + "/api/v2/hosts/too": {}, + "/api/v2/backups/record/size": {}, + + "/api/v2/core/auth/login": {}, + "/api/v2/core/logs/login": {}, + "/api/v2/core/logs/operation": {}, + "/api/v2/core/auth/logout": {}, + + "/api/v2/apps/installed/loadport": {}, + "/api/v2/apps/installed/check": {}, + "/api/v2/apps/installed/conninfo": {}, + "/api/v2/databases/load/file": {}, + "/api/v2/databases/variables": {}, + "/api/v2/databases/status": {}, + "/api/v2/databases/baseinfo": {}, + + "/api/v2/xpack/waf/attack/stat": {}, + "/api/v2/xpack/waf/config/website": {}, + "/api/v2/xpack/waf/relation/stat": {}, + + "/api/v2/xpack/monitor/stat": {}, + "/api/v2/xpack/monitor/visitors": {}, + "/api/v2/xpack/monitor/visitors/loc": {}, + "/api/v2/xpack/monitor/qps": {}, + "/api/v2/xpack/monitor/logs/stat": {}, + "/api/v2/xpack/monitor/websites": {}, + "/api/v2/xpack/monitor/trend": {}, + "/api/v2/xpack/monitor/rank": {}, + "/api/v2/xpack/waf/cdn": {}, + + "/api/v2/core/nodes/list": {}, +} + +func DemoHandle() gin.HandlerFunc { + return func(c *gin.Context) { + if strings.Contains(c.Request.URL.Path, "search") || (c.Request.Method == http.MethodGet && c.Request.URL.Path != "/api/v2/containers/exec") { + c.Next() + return + } + if _, ok := whiteUrlList[c.Request.URL.Path]; ok { + c.Next() + return + } + + c.JSON(http.StatusInternalServerError, dto.Response{ + Code: http.StatusInternalServerError, + Message: buserr.New("ErrDemoEnvironment").Error(), + }) + c.Abort() + } +} diff --git a/core/middleware/helper.go b/core/middleware/helper.go new file mode 100644 index 0000000..b115b34 --- /dev/null +++ b/core/middleware/helper.go @@ -0,0 +1,36 @@ +package middleware + +import ( + "net/http" + + "github.com/1Panel-dev/1Panel/core/app/repo" +) + +func LoadErrCode() int { + settingRepo := repo.NewISettingRepo() + codeVal, err := settingRepo.Get(repo.WithByKey("NoAuthSetting")) + if err != nil { + return 500 + } + + switch codeVal.Value { + case "400": + return http.StatusBadRequest + case "401": + return http.StatusUnauthorized + case "403": + return http.StatusForbidden + case "404": + return http.StatusNotFound + case "408": + return http.StatusRequestTimeout + case "416": + return http.StatusRequestedRangeNotSatisfiable + case "500": + return http.StatusInternalServerError + case "444": + return 444 + default: + return http.StatusOK + } +} diff --git a/core/middleware/ip_limit.go b/core/middleware/ip_limit.go new file mode 100644 index 0000000..d2f900a --- /dev/null +++ b/core/middleware/ip_limit.go @@ -0,0 +1,49 @@ +package middleware + +import ( + "github.com/1Panel-dev/1Panel/core/utils/common" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/gin-gonic/gin" +) + +func WhiteAllow() gin.HandlerFunc { + return func(c *gin.Context) { + tokenString := c.GetHeader("X-Panel-Local-Token") + clientIP := common.GetRealClientIP(c) + if clientIP == "127.0.0.1" && tokenString != "" && c.Request.URL.Path == "/api/v2/core/xpack/sync/ssl" { + c.Set("LOCAL_REQUEST", true) + c.Next() + return + } + if common.IsPrivateIP(clientIP) { + c.Next() + return + } + + settingRepo := repo.NewISettingRepo() + status, err := settingRepo.Get(repo.WithByKey("AllowIPs")) + if err != nil { + helper.InternalServer(c, err) + return + } + + if len(status.Value) == 0 { + c.Next() + return + } + for _, ip := range strings.Split(status.Value, ",") { + if len(ip) == 0 { + continue + } + if ip == clientIP || (strings.Contains(ip, "/") && common.CheckIpInCidr(ip, clientIP)) { + c.Next() + return + } + } + code := LoadErrCode() + helper.ErrWithHtml(c, code, "err_ip_limit") + } +} diff --git a/core/middleware/loading.go b/core/middleware/loading.go new file mode 100644 index 0000000..afa14a6 --- /dev/null +++ b/core/middleware/loading.go @@ -0,0 +1,23 @@ +package middleware + +import ( + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/gin-gonic/gin" +) + +func GlobalLoading() gin.HandlerFunc { + return func(c *gin.Context) { + settingRepo := repo.NewISettingRepo() + status, err := settingRepo.Get(repo.WithByKey("SystemStatus")) + if err != nil { + helper.InternalServer(c, err) + return + } + if status.Value != "Free" { + helper.ErrorWithDetail(c, 407, status.Value, err) + return + } + c.Next() + } +} diff --git a/core/middleware/operation.go b/core/middleware/operation.go new file mode 100644 index 0000000..58495fd --- /dev/null +++ b/core/middleware/operation.go @@ -0,0 +1,286 @@ +package middleware + +import ( + "bytes" + "compress/gzip" + "encoding/json" + "fmt" + "io" + "mime" + "mime/multipart" + "net/http" + "net/url" + "path" + "reflect" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/cmd/server/docs" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/gin-gonic/gin" + "github.com/glebarez/sqlite" + "gorm.io/gorm" +) + +func OperationLog() gin.HandlerFunc { + return func(c *gin.Context) { + if strings.Contains(c.Request.URL.Path, "search") || c.Request.Method == http.MethodGet { + c.Next() + return + } + + source := loadLogInfo(c.Request.URL.Path) + pathItem := strings.TrimPrefix(c.Request.URL.Path, "/api/v2") + pathItem = strings.TrimPrefix(pathItem, "/api/v2/core") + currentNodeItem := c.Request.Header.Get("CurrentNode") + currentNode, _ := url.QueryUnescape(currentNodeItem) + record := &model.OperationLog{ + Source: source, + Node: currentNode, + IP: c.ClientIP(), + Method: strings.ToLower(c.Request.Method), + Path: pathItem, + UserAgent: c.Request.UserAgent(), + } + swagger := make(map[string]operationJson) + if err := json.Unmarshal(docs.XLogJson, &swagger); err != nil { + c.Next() + return + } + operationDic, hasPath := swagger[record.Path] + if !hasPath { + c.Next() + return + } + if len(operationDic.FormatZH) == 0 { + c.Next() + return + } + + formatMap := make(map[string]interface{}) + if len(operationDic.BodyKeys) != 0 { + body, err := io.ReadAll(c.Request.Body) + if err == nil { + c.Request.Body = io.NopCloser(bytes.NewBuffer(body)) + } + bodyMap := make(map[string]interface{}) + if strings.Contains(c.Request.Header.Get("Content-Type"), "multipart/form-data") { + bodyMap, _ = parseMultipart(body, c.Request.Header.Get("Content-Type")) + } else { + _ = json.Unmarshal(body, &bodyMap) + } + for _, key := range operationDic.BodyKeys { + if _, ok := bodyMap[key]; ok { + formatMap[key] = bodyMap[key] + } + } + } + if len(operationDic.BeforeFunctions) != 0 { + dbItem, err := newDB(record.Path) + if err != nil { + c.Next() + return + } + for _, funcs := range operationDic.BeforeFunctions { + for key, value := range formatMap { + if funcs.InputValue == key { + var names []string + if funcs.IsList { + sql := fmt.Sprintf("SELECT %s FROM %s where %s in (?);", funcs.OutputColumn, funcs.DB, funcs.InputColumn) + _ = dbItem.Raw(sql, value).Scan(&names) + } else { + _ = dbItem.Raw(fmt.Sprintf("select %s from %s where %s = ?;", funcs.OutputColumn, funcs.DB, funcs.InputColumn), value).Scan(&names) + } + formatMap[funcs.OutputValue] = strings.Join(names, ",") + break + } + } + } + closeDB(dbItem) + } + for key, value := range formatMap { + if strings.Contains(operationDic.FormatEN, "["+key+"]") { + t := reflect.TypeOf(value) + if t.Kind() != reflect.Array && t.Kind() != reflect.Slice { + operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", value)) + operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", value)) + } else { + val := reflect.ValueOf(value) + length := val.Len() + + var elements []string + for i := 0; i < length; i++ { + element := val.Index(i).Interface().(string) + elements = append(elements, element) + } + operationDic.FormatZH = strings.ReplaceAll(operationDic.FormatZH, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(elements, ","))) + operationDic.FormatEN = strings.ReplaceAll(operationDic.FormatEN, "["+key+"]", fmt.Sprintf("[%v]", strings.Join(elements, ","))) + } + } + } + record.DetailEN = strings.ReplaceAll(operationDic.FormatEN, "[]", "") + record.DetailZH = strings.ReplaceAll(operationDic.FormatZH, "[]", "") + + writer := responseBodyWriter{ + ResponseWriter: c.Writer, + body: &bytes.Buffer{}, + } + c.Writer = writer + now := time.Now() + + c.Next() + + datas := writer.body.Bytes() + logRepo := repo.NewILogRepo() + if c.Request.Header.Get("Content-Encoding") == "gzip" { + buf := bytes.NewReader(writer.body.Bytes()) + reader, err := gzip.NewReader(buf) + if err != nil { + record.Status = constant.StatusFailed + record.Message = fmt.Sprintf("gzip new reader failed, err: %v", err) + latency := time.Since(now) + record.Latency = latency + + if err := logRepo.CreateOperationLog(record); err != nil { + global.LOG.Errorf("create operation record failed, err: %v", err) + } + return + } + defer reader.Close() + datas, _ = io.ReadAll(reader) + } + var res response + _ = json.Unmarshal(datas, &res) + if res.Code == 200 { + record.Status = constant.StatusSuccess + } else { + record.Status = constant.StatusFailed + record.Message = res.Message + } + + latency := time.Since(now) + record.Latency = latency + + if err := logRepo.CreateOperationLog(record); err != nil { + global.LOG.Errorf("create operation record failed, err: %v", err) + } + } +} + +type operationJson struct { + API string `json:"api"` + Method string `json:"method"` + BodyKeys []string `json:"bodyKeys"` + ParamKeys []string `json:"paramKeys"` + BeforeFunctions []functionInfo `json:"beforeFunctions"` + FormatZH string `json:"formatZH"` + FormatEN string `json:"formatEN"` +} +type functionInfo struct { + InputColumn string `json:"input_column"` + InputValue string `json:"input_value"` + IsList bool `json:"isList"` + DB string `json:"db"` + OutputColumn string `json:"output_column"` + OutputValue string `json:"output_value"` +} + +type response struct { + Code int `json:"code"` + Message string `json:"message"` +} + +type responseBodyWriter struct { + gin.ResponseWriter + body *bytes.Buffer +} + +func (r responseBodyWriter) Write(b []byte) (int, error) { + r.body.Write(b) + return r.ResponseWriter.Write(b) +} + +func loadLogInfo(path string) string { + path = replaceStr(path, "/api/v2", "/core", "/xpack") + if !strings.Contains(path, "/") { + return "" + } + pathArrays := strings.Split(path, "/") + if len(pathArrays) < 2 { + return "" + } + return pathArrays[1] +} + +func newDB(pathItem string) (*gorm.DB, error) { + dbFile := "" + switch { + case strings.HasPrefix(pathItem, "/core"): + dbFile = path.Join(global.CONF.Base.InstallDir, "1panel/db/core.db") + case strings.HasPrefix(pathItem, "/xpack"): + dbFile = path.Join(global.CONF.Base.InstallDir, "1panel/db/xpack.db") + default: + dbFile = path.Join(global.CONF.Base.InstallDir, "1panel/db/agent.db") + } + + db, _ := gorm.Open(sqlite.Open(dbFile), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + }) + sqlDB, err := db.DB() + if err != nil { + return nil, err + } + sqlDB.SetMaxOpenConns(4) + sqlDB.SetMaxIdleConns(1) + sqlDB.SetConnMaxIdleTime(15 * time.Minute) + sqlDB.SetConnMaxLifetime(time.Hour) + return db, nil +} + +func closeDB(db *gorm.DB) { + sqlDB, err := db.DB() + if err != nil { + return + } + _ = sqlDB.Close() +} + +func replaceStr(val string, rep ...string) string { + for _, item := range rep { + val = strings.ReplaceAll(val, item, "") + } + return val +} + +func parseMultipart(formData []byte, contentType string) (map[string]interface{}, error) { + d, params, err := mime.ParseMediaType(contentType) + if err != nil || d != "multipart/form-data" { + return nil, http.ErrNotMultipart + } + boundary, ok := params["boundary"] + if !ok { + return nil, http.ErrMissingBoundary + } + reader := multipart.NewReader(bytes.NewReader(formData), boundary) + ret := make(map[string]interface{}) + + f, err := reader.ReadForm(32 << 20) + if err != nil { + return nil, err + } + + for k, v := range f.Value { + if len(v) > 0 { + ret[k] = v[0] + } + } + for k, v := range f.File { + if len(v) > 0 { + ret[k] = v[0].Filename + } + } + return ret, nil +} diff --git a/core/middleware/password_expired.go b/core/middleware/password_expired.go new file mode 100644 index 0000000..ebd8e0b --- /dev/null +++ b/core/middleware/password_expired.go @@ -0,0 +1,53 @@ +package middleware + +import ( + "net/http" + "strconv" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/gin-gonic/gin" +) + +func PasswordExpired() gin.HandlerFunc { + return func(c *gin.Context) { + if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") || + c.Request.URL.Path == "/api/v2/core/settings/expired/handle" || + c.Request.URL.Path == "/api/v2/core/settings/search" { + c.Next() + return + } + settingRepo := repo.NewISettingRepo() + setting, err := settingRepo.Get(repo.WithByKey("ExpirationDays")) + if err != nil { + helper.ErrorWithDetail(c, http.StatusInternalServerError, "ErrPasswordExpired", err) + return + } + expiredDays, _ := strconv.Atoi(setting.Value) + if expiredDays == 0 { + c.Next() + return + } + + extime, err := settingRepo.Get(repo.WithByKey("ExpirationTime")) + if err != nil { + helper.ErrorWithDetail(c, http.StatusInternalServerError, "ErrPasswordExpired", err) + return + } + loc, _ := time.LoadLocation(common.LoadTimeZoneByCmd()) + expiredTime, err := time.ParseInLocation(constant.DateTimeLayout, extime.Value, loc) + if err != nil { + helper.ErrorWithDetail(c, 313, "ErrPasswordExpired", err) + return + } + if time.Now().After(expiredTime) { + helper.ErrorWithDetail(c, 313, "ErrPasswordExpired", err) + return + } + c.Next() + } +} diff --git a/core/middleware/password_rsa.go b/core/middleware/password_rsa.go new file mode 100644 index 0000000..6548562 --- /dev/null +++ b/core/middleware/password_rsa.go @@ -0,0 +1,22 @@ +package middleware + +import ( + "encoding/base64" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/gin-gonic/gin" +) + +func SetPasswordPublicKey() gin.HandlerFunc { + return func(c *gin.Context) { + cookieKey, _ := c.Cookie("panel_public_key") + settingRepo := repo.NewISettingRepo() + key, _ := settingRepo.Get(repo.WithByKey("PASSWORD_PUBLIC_KEY")) + base64Key := base64.StdEncoding.EncodeToString([]byte(key.Value)) + if base64Key == cookieKey { + c.Next() + return + } + c.SetCookie("panel_public_key", base64Key, 7*24*60*60, "/", "", false, false) + c.Next() + } +} diff --git a/core/middleware/session.go b/core/middleware/session.go new file mode 100644 index 0000000..979993f --- /dev/null +++ b/core/middleware/session.go @@ -0,0 +1,41 @@ +package middleware + +import ( + "github.com/1Panel-dev/1Panel/core/app/api/v2/helper" + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/gin-gonic/gin" + "strconv" + "strings" +) + +func SessionAuth() gin.HandlerFunc { + return func(c *gin.Context) { + apiReq := c.GetBool("API_AUTH") + if strings.HasPrefix(c.Request.URL.Path, "/api/v2/core/auth") || apiReq { + c.Next() + return + } + + psession, err := global.SESSION.Get(c) + if err != nil { + helper.BadAuth(c, "ErrNotLogin", err) + return + } + settingRepo := repo.NewISettingRepo() + setting, err := settingRepo.Get(repo.WithByKey("SessionTimeout")) + if err != nil { + global.LOG.Errorf("create operation record failed, err: %v", err) + return + } + lifeTime, _ := strconv.Atoi(setting.Value) + httpsSetting, err := settingRepo.Get(repo.WithByKey("SSL")) + if err != nil { + global.LOG.Errorf("create operation record failed, err: %v", err) + return + } + _ = global.SESSION.Set(c, psession, httpsSetting.Value == constant.StatusEnable, lifeTime) + c.Next() + } +} diff --git a/core/router/command.go b/core/router/command.go new file mode 100644 index 0000000..2c66607 --- /dev/null +++ b/core/router/command.go @@ -0,0 +1,27 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/1Panel-dev/1Panel/core/middleware" + "github.com/gin-gonic/gin" +) + +type CommandRouter struct{} + +func (s *CommandRouter) InitRouter(Router *gin.RouterGroup) { + commandRouter := Router.Group("commands"). + Use(middleware.SessionAuth()). + Use(middleware.PasswordExpired()) + baseApi := v2.ApiGroupApp.BaseApi + { + commandRouter.POST("/list", baseApi.ListCommand) + commandRouter.POST("", baseApi.CreateCommand) + commandRouter.POST("/del", baseApi.DeleteCommand) + commandRouter.POST("/search", baseApi.SearchCommand) + commandRouter.POST("/tree", baseApi.SearchCommandTree) + commandRouter.POST("/update", baseApi.UpdateCommand) + commandRouter.POST("/export", baseApi.ExportCommands) + commandRouter.POST("/upload", baseApi.UploadCommandCsv) + commandRouter.POST("/import", baseApi.ImportCommands) + } +} diff --git a/core/router/common.go b/core/router/common.go new file mode 100644 index 0000000..a22a1a2 --- /dev/null +++ b/core/router/common.go @@ -0,0 +1,14 @@ +package router + +func commonGroups() []CommonRouter { + return []CommonRouter{ + &BaseRouter{}, + &BackupRouter{}, + &LogRouter{}, + &SettingRouter{}, + &CommandRouter{}, + &HostRouter{}, + &GroupRouter{}, + &ScriptRouter{}, + } +} diff --git a/core/router/entry.go b/core/router/entry.go new file mode 100644 index 0000000..49ed25f --- /dev/null +++ b/core/router/entry.go @@ -0,0 +1,9 @@ +//go:build !xpack + +package router + +func RouterGroups() []CommonRouter { + return commonGroups() +} + +var RouterGroupApp = RouterGroups() diff --git a/core/router/ro_backup.go b/core/router/ro_backup.go new file mode 100644 index 0000000..a85beba --- /dev/null +++ b/core/router/ro_backup.go @@ -0,0 +1,23 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/1Panel-dev/1Panel/core/middleware" + "github.com/gin-gonic/gin" +) + +type BackupRouter struct{} + +func (s *BackupRouter) InitRouter(Router *gin.RouterGroup) { + backupRouter := Router.Group("backups"). + Use(middleware.SessionAuth()). + Use(middleware.PasswordExpired()) + baseApi := v2.ApiGroupApp.BaseApi + { + backupRouter.GET("/client/:clientType", baseApi.LoadBackupClientInfo) + backupRouter.POST("/refresh/token", baseApi.RefreshToken) + backupRouter.POST("", baseApi.CreateBackup) + backupRouter.POST("/del", baseApi.DeleteBackup) + backupRouter.POST("/update", baseApi.UpdateBackup) + } +} diff --git a/core/router/ro_base.go b/core/router/ro_base.go new file mode 100644 index 0000000..662c8cb --- /dev/null +++ b/core/router/ro_base.go @@ -0,0 +1,23 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/gin-gonic/gin" +) + +type BaseRouter struct{} + +func (s *BaseRouter) InitRouter(Router *gin.RouterGroup) { + baseRouter := Router.Group("auth") + baseApi := v2.ApiGroupApp.BaseApi + { + baseRouter.GET("/captcha", baseApi.Captcha) + baseRouter.POST("/passkey/begin", baseApi.PasskeyBeginLogin) + baseRouter.POST("/passkey/finish", baseApi.PasskeyFinishLogin) + baseRouter.POST("/mfalogin", baseApi.MFALogin) + baseRouter.POST("/login", baseApi.Login) + baseRouter.POST("/logout", baseApi.LogOut) + baseRouter.GET("/setting", baseApi.GetLoginSetting) + baseRouter.GET("/welcome", baseApi.GetWelcomePage) + } +} diff --git a/core/router/ro_group.go b/core/router/ro_group.go new file mode 100644 index 0000000..c585a4b --- /dev/null +++ b/core/router/ro_group.go @@ -0,0 +1,24 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/1Panel-dev/1Panel/core/middleware" + "github.com/gin-gonic/gin" +) + +type GroupRouter struct { +} + +func (a *GroupRouter) InitRouter(Router *gin.RouterGroup) { + groupRouter := Router.Group("groups"). + Use(middleware.SessionAuth()). + Use(middleware.PasswordExpired()) + + baseApi := v2.ApiGroupApp.BaseApi + { + groupRouter.POST("", baseApi.CreateGroup) + groupRouter.POST("/del", baseApi.DeleteGroup) + groupRouter.POST("/update", baseApi.UpdateGroup) + groupRouter.POST("/search", baseApi.ListGroup) + } +} diff --git a/core/router/ro_host.go b/core/router/ro_host.go new file mode 100644 index 0000000..05a6d6b --- /dev/null +++ b/core/router/ro_host.go @@ -0,0 +1,29 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/1Panel-dev/1Panel/core/middleware" + "github.com/gin-gonic/gin" +) + +type HostRouter struct{} + +func (s *HostRouter) InitRouter(Router *gin.RouterGroup) { + hostRouter := Router.Group("hosts"). + Use(middleware.SessionAuth()). + Use(middleware.PasswordExpired()) + baseApi := v2.ApiGroupApp.BaseApi + { + hostRouter.POST("", baseApi.CreateHost) + hostRouter.POST("/info", baseApi.GetHostByID) + hostRouter.POST("/del", baseApi.DeleteHost) + hostRouter.POST("/update", baseApi.UpdateHost) + hostRouter.POST("/update/group", baseApi.UpdateHostGroup) + hostRouter.POST("/search", baseApi.SearchHost) + hostRouter.POST("/tree", baseApi.HostTree) + hostRouter.POST("/test/byinfo", baseApi.TestByInfo) + hostRouter.POST("/test/byid/:id", baseApi.TestByID) + + hostRouter.GET("/terminal", baseApi.WsSsh) + } +} diff --git a/core/router/ro_log.go b/core/router/ro_log.go new file mode 100644 index 0000000..46cb798 --- /dev/null +++ b/core/router/ro_log.go @@ -0,0 +1,22 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/1Panel-dev/1Panel/core/middleware" + + "github.com/gin-gonic/gin" +) + +type LogRouter struct{} + +func (s *LogRouter) InitRouter(Router *gin.RouterGroup) { + operationRouter := Router.Group("logs"). + Use(middleware.SessionAuth()). + Use(middleware.PasswordExpired()) + baseApi := v2.ApiGroupApp.BaseApi + { + operationRouter.POST("/login", baseApi.GetLoginLogs) + operationRouter.POST("/operation", baseApi.GetOperationLogs) + operationRouter.POST("/clean", baseApi.CleanLogs) + } +} diff --git a/core/router/ro_router.go b/core/router/ro_router.go new file mode 100644 index 0000000..58a52dc --- /dev/null +++ b/core/router/ro_router.go @@ -0,0 +1,7 @@ +package router + +import "github.com/gin-gonic/gin" + +type CommonRouter interface { + InitRouter(Router *gin.RouterGroup) +} diff --git a/core/router/ro_script_library.go b/core/router/ro_script_library.go new file mode 100644 index 0000000..fae1764 --- /dev/null +++ b/core/router/ro_script_library.go @@ -0,0 +1,24 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/1Panel-dev/1Panel/core/middleware" + "github.com/gin-gonic/gin" +) + +type ScriptRouter struct{} + +func (s *ScriptRouter) InitRouter(Router *gin.RouterGroup) { + scriptRouter := Router.Group("script"). + Use(middleware.SessionAuth()). + Use(middleware.PasswordExpired()) + baseApi := v2.ApiGroupApp.BaseApi + { + scriptRouter.POST("", baseApi.CreateScript) + scriptRouter.POST("/search", baseApi.SearchScript) + scriptRouter.POST("/del", baseApi.DeleteScript) + scriptRouter.POST("/update", baseApi.UpdateScript) + scriptRouter.POST("/sync", baseApi.SyncScript) + scriptRouter.GET("/run", baseApi.RunScript) + } +} diff --git a/core/router/ro_setting.go b/core/router/ro_setting.go new file mode 100644 index 0000000..e4c62c5 --- /dev/null +++ b/core/router/ro_setting.go @@ -0,0 +1,57 @@ +package router + +import ( + v2 "github.com/1Panel-dev/1Panel/core/app/api/v2" + "github.com/1Panel-dev/1Panel/core/middleware" + "github.com/gin-gonic/gin" +) + +type SettingRouter struct{} + +func (s *SettingRouter) InitRouter(Router *gin.RouterGroup) { + router := Router.Group("settings"). + Use(middleware.SessionAuth()) + settingRouter := Router.Group("settings"). + Use(middleware.SessionAuth()). + Use(middleware.PasswordExpired()) + + noAuthRouter := Router.Group("settings") + baseApi := v2.ApiGroupApp.BaseApi + { + router.POST("/search", baseApi.GetSettingInfo) + router.POST("/expired/handle", baseApi.HandlePasswordExpired) + settingRouter.POST("/by", baseApi.GetSettingByKey) + settingRouter.POST("/terminal/search", baseApi.GetTerminalSettingInfo) + settingRouter.GET("/search/available", baseApi.GetSystemAvailable) + settingRouter.POST("/update", baseApi.UpdateSetting) + settingRouter.POST("/terminal/update", baseApi.UpdateTerminalSetting) + settingRouter.GET("/interface", baseApi.LoadInterfaceAddr) + settingRouter.POST("/menu/update", baseApi.UpdateMenu) + settingRouter.POST("/menu/default", baseApi.DefaultMenu) + settingRouter.POST("/proxy/update", baseApi.UpdateProxy) + settingRouter.POST("/bind/update", baseApi.UpdateBindInfo) + settingRouter.POST("/port/update", baseApi.UpdatePort) + settingRouter.POST("/ssl/update", baseApi.UpdateSSL) + settingRouter.GET("/ssl/info", baseApi.LoadFromCert) + settingRouter.POST("/ssl/download", baseApi.DownloadSSL) + settingRouter.POST("/password/update", baseApi.UpdatePassword) + settingRouter.POST("/mfa", baseApi.LoadMFA) + settingRouter.POST("/mfa/bind", baseApi.MFABind) + settingRouter.POST("/passkey/register/begin", baseApi.PasskeyRegisterBegin) + settingRouter.POST("/passkey/register/finish", baseApi.PasskeyRegisterFinish) + settingRouter.GET("/passkey/list", baseApi.PasskeyList) + settingRouter.DELETE("/passkey/:id", baseApi.PasskeyDelete) + + settingRouter.POST("/upgrade", baseApi.Upgrade) + settingRouter.POST("/upgrade/notes", baseApi.GetNotesByVersion) + settingRouter.GET("/upgrade/releases", baseApi.LoadRelease) + settingRouter.GET("/upgrade", baseApi.GetUpgradeInfo) + settingRouter.POST("/api/config/generate/key", baseApi.GenerateApiKey) + settingRouter.POST("/api/config/update", baseApi.UpdateApiConfig) + + noAuthRouter.POST("/ssl/reload", baseApi.ReloadSSL) + + settingRouter.POST("/apps/store/update", baseApi.UpdateAppstoreConfig) + settingRouter.GET("/apps/store/config", baseApi.GetAppstoreConfig) + } +} diff --git a/core/server/init.go b/core/server/init.go new file mode 100644 index 0000000..b8ed30b --- /dev/null +++ b/core/server/init.go @@ -0,0 +1,6 @@ +//go:build !xpack + +package server + +func InitOthers() { +} diff --git a/core/server/server.go b/core/server/server.go new file mode 100644 index 0000000..a4ffd12 --- /dev/null +++ b/core/server/server.go @@ -0,0 +1,218 @@ +package server + +import ( + "bufio" + "crypto/tls" + "encoding/gob" + "fmt" + "net" + "net/http" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/init/auth" + "github.com/1Panel-dev/1Panel/core/init/db" + "github.com/1Panel-dev/1Panel/core/init/geo" + "github.com/1Panel-dev/1Panel/core/init/log" + "github.com/1Panel-dev/1Panel/core/init/migration" + "github.com/1Panel-dev/1Panel/core/init/proxy" + "github.com/1Panel-dev/1Panel/core/init/run" + "github.com/gin-gonic/gin" + "github.com/soheilhy/cmux" + + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/i18n" + "github.com/1Panel-dev/1Panel/core/init/cron" + "github.com/1Panel-dev/1Panel/core/init/hook" + "github.com/1Panel-dev/1Panel/core/init/router" + "github.com/1Panel-dev/1Panel/core/init/session" + "github.com/1Panel-dev/1Panel/core/init/session/psession" + "github.com/1Panel-dev/1Panel/core/init/validator" + "github.com/1Panel-dev/1Panel/core/init/viper" +) + +func Start() { + viper.Init() + log.Init() + db.Init() + migration.Init() + i18n.Init() + validator.Init() + geo.Init() + gob.Register(psession.SessionUser{}) + cron.Init() + session.Init() + hook.Init() + InitOthers() + + run.Init() + proxy.Init() + + rootRouter := router.Routers() + + if global.CONF.Base.Mode != "stable" { + gin.SetMode(gin.DebugMode) + } else { + gin.SetMode(gin.ReleaseMode) + } + + global.IPTracker = auth.NewIPTracker() + + tcpItem := "tcp4" + if global.CONF.Conn.Ipv6 == constant.StatusEnable { + tcpItem = "tcp" + global.CONF.Conn.BindAddress = fmt.Sprintf("[%s]", global.CONF.Conn.BindAddress) + } + server := &http.Server{ + Addr: global.CONF.Conn.BindAddress + ":" + global.CONF.Conn.Port, + Handler: rootRouter, + ReadHeaderTimeout: 5 * time.Second, + ReadTimeout: 600 * time.Second, + WriteTimeout: 600 * time.Second, + IdleTimeout: 240 * time.Second, + } + ln, err := net.Listen(tcpItem, server.Addr) + if err != nil { + panic(err) + } + type tcpKeepAliveListener struct { + *net.TCPListener + } + if global.CONF.Conn.SSL == constant.StatusEnable { + constant.CertStore.Store(loadCert()) + + server.TLSConfig = &tls.Config{ + GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { + return constant.CertStore.Load().(*tls.Certificate), nil + }, + } + global.LOG.Infof("listen at https://%s:%s [%s]", global.CONF.Conn.BindAddress, global.CONF.Conn.Port, tcpItem) + + if err := server.ServeTLS(tcpKeepAliveListener{ln.(*net.TCPListener)}, "", ""); err != nil { + panic(err) + } + return + } else if global.CONF.Conn.SSL == constant.StatusMux { + constant.CertStore.Store(loadCert()) + + server.TLSConfig = &tls.Config{ + NextProtos: []string{"h2", "http/1.1"}, + GetCertificate: func(info *tls.ClientHelloInfo) (*tls.Certificate, error) { + return constant.CertStore.Load().(*tls.Certificate), nil + }, + } + + m := cmux.New(ln) + + httpsL := m.Match(cmux.TLS()) + httpL := m.Match(cmux.HTTP1Fast()) + anyL := m.Match(cmux.Any()) + + go func() { + if err := server.Serve(tls.NewListener(httpsL, server.TLSConfig)); err != nil { + global.LOG.Errorf("HTTPS Serve Error: %v", err) + } + }() + + go func() { + for { + conn, err := httpL.Accept() + if err != nil { + return + } + go handleMuxHttpConn(conn) + } + }() + + go func() { + for { + conn, err := anyL.Accept() + if err != nil { + return + } + conn.Close() + } + }() + + global.LOG.Infof("listen at mux (http/https)://%s:%s [%s]", global.CONF.Conn.BindAddress, global.CONF.Conn.Port, tcpItem) + + if err := m.Serve(); err != nil { + panic(err) + } + return + } else { + global.LOG.Infof("listen at http://%s:%s [%s]", global.CONF.Conn.BindAddress, global.CONF.Conn.Port, tcpItem) + if err := server.Serve(tcpKeepAliveListener{ln.(*net.TCPListener)}); err != nil { + panic(err) + } + return + } +} + +func loadCert() *tls.Certificate { + certPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.crt") + keyPath := path.Join(global.CONF.Base.InstallDir, "1panel/secret/server.key") + certificate, err := os.ReadFile(certPath) + if err != nil { + panic(err) + } + key, err := os.ReadFile(keyPath) + if err != nil { + panic(err) + } + cert, err := tls.X509KeyPair(certificate, key) + if err != nil { + panic(err) + } + return &cert +} + +func handleMuxHttpConn(conn net.Conn) { + defer conn.Close() + + req, err := http.ReadRequest(bufio.NewReader(conn)) + if err != nil { + return + } + + if req.Host == "" { + return + } + + ua := req.Header.Get("User-Agent") + if ua == "" { + return + } + + switch req.Method { + case http.MethodGet, http.MethodHead, http.MethodPost: + default: + return + } + + if len(req.RequestURI) > 4096 { + return + } + + if !strings.HasPrefix(req.URL.Path, "/") { + return + } + + target := "https://" + req.Host + req.URL.RequestURI() + + resp := &http.Response{ + StatusCode: http.StatusTemporaryRedirect, + Proto: "HTTP/1.1", + ProtoMajor: 1, + ProtoMinor: 1, + Header: make(http.Header), + } + resp.Header.Set("Location", target) + resp.Header.Set("Connection", "close") + + _ = resp.Write(conn) + return +} diff --git a/core/utils/captcha/captcha.go b/core/utils/captcha/captcha.go new file mode 100644 index 0000000..9d43108 --- /dev/null +++ b/core/utils/captcha/captcha.go @@ -0,0 +1,43 @@ +package captcha + +import ( + "strings" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1panel-dev/base64Captcha" +) + +var store = base64Captcha.DefaultMemStore + +func VerifyCode(codeID string, code string) string { + vv := store.Get(codeID, true) + vv = strings.TrimSpace(vv) + code = strings.TrimSpace(code) + if codeID == "" || code == "" { + return "ErrCaptchaCode" + } + if strings.EqualFold(vv, code) { + return "" + } + return "ErrCaptchaCode" +} + +func CreateCaptcha() (*dto.CaptchaResponse, error) { + var driverString base64Captcha.DriverString + driverString.Source = "1234567890QWERTYUPLKJHGFDSAZXCVBNMqwertyupkjhgfdsazxcvbnm" + driverString.Width = 120 + driverString.Height = 50 + driverString.NoiseCount = 0 + driverString.Length = 4 + driverString.Fonts = []string{"RitaSmith.ttf", "actionj.ttf", "chromohv.ttf"} + driver := driverString.ConvertFonts() + c := base64Captcha.NewCaptcha(driver, store) + id, b64s, _, err := c.Generate() + if err != nil { + return nil, err + } + return &dto.CaptchaResponse{ + CaptchaID: id, + ImagePath: b64s, + }, nil +} diff --git a/core/utils/cloud_storage/refresh_token.go b/core/utils/cloud_storage/refresh_token.go new file mode 100644 index 0000000..2d1293e --- /dev/null +++ b/core/utils/cloud_storage/refresh_token.go @@ -0,0 +1,117 @@ +package cloud_storage + +import ( + "crypto/tls" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "net/url" + "strings" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/go-resty/resty/v2" +) + +func loadParamFromVars(key string, vars map[string]interface{}) string { + if _, ok := vars[key]; !ok { + if key != "bucket" && key != "port" && key != "authMode" && key != "passPhrase" { + global.LOG.Errorf("load param %s from vars failed, err: not exist!", key) + } + return "" + } + + return fmt.Sprintf("%v", vars[key]) +} + +type aliTokenResp struct { + RefreshToken string `json:"refresh_token"` + AccessToken string `json:"access_token"` +} + +func RefreshALIToken(varMap map[string]interface{}) (string, error) { + refresh_token := loadParamFromVars("refresh_token", varMap) + if len(refresh_token) == 0 { + return "", errors.New("no such refresh token find in db") + } + client := resty.New() + client.SetTLSClientConfig(&tls.Config{ + InsecureSkipVerify: true, + }) + data := map[string]interface{}{ + "grant_type": "refresh_token", + "refresh_token": refresh_token, + } + + url := "https://api.aliyundrive.com/token/refresh" + resp, err := client.R(). + SetBody(data). + Post(url) + + if err != nil { + return "", fmt.Errorf("load account token failed, err: %v", err) + } + if resp.StatusCode() != 200 { + return "", fmt.Errorf("load account token failed, code: %v", resp.StatusCode()) + } + var respItem aliTokenResp + if err := json.Unmarshal(resp.Body(), &respItem); err != nil { + return "", err + } + return respItem.RefreshToken, nil +} + +func RefreshToken(grantType string, tokenType string, varMap map[string]interface{}) (string, error) { + data := url.Values{} + isCN := loadParamFromVars("isCN", varMap) + data.Set("client_id", loadParamFromVars("client_id", varMap)) + data.Set("client_secret", loadParamFromVars("client_secret", varMap)) + if grantType == "refresh_token" { + data.Set("grant_type", "refresh_token") + data.Set("refresh_token", loadParamFromVars("refresh_token", varMap)) + } else { + data.Set("grant_type", "authorization_code") + data.Set("code", loadParamFromVars("code", varMap)) + } + data.Set("redirect_uri", loadParamFromVars("redirect_uri", varMap)) + client := &http.Client{} + defer client.CloseIdleConnections() + url := "https://login.microsoftonline.com/common/oauth2/v2.0/token" + if isCN == "true" { + url = "https://login.chinacloudapi.cn/common/oauth2/v2.0/token" + } + req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode())) + if err != nil { + return "", fmt.Errorf("new http post client for access token failed, err: %v", err) + } + req.Header.Add("Content-Type", "application/x-www-form-urlencoded") + resp, err := client.Do(req) + if err != nil { + return "", fmt.Errorf("request for access token failed, err: %v", err) + } + defer resp.Body.Close() + respBody, err := io.ReadAll(resp.Body) + if err != nil { + return "", fmt.Errorf("read data from response body failed, err: %v", err) + } + + tokenMap := map[string]interface{}{} + if err := json.Unmarshal(respBody, &tokenMap); err != nil { + return "", fmt.Errorf("unmarshal data from response body failed, err: %v", err) + } + if tokenType == "accessToken" { + accessToken, ok := tokenMap["access_token"].(string) + if !ok { + return "", errors.New("no such access token in response") + } + tokenMap = nil + return accessToken, nil + } + refreshToken, ok := tokenMap["refresh_token"].(string) + if !ok { + return "", errors.New("no such access token in response") + } + tokenMap = nil + return refreshToken, nil +} diff --git a/core/utils/cmd/cmd.go b/core/utils/cmd/cmd.go new file mode 100644 index 0000000..49a7b32 --- /dev/null +++ b/core/utils/cmd/cmd.go @@ -0,0 +1,58 @@ +package cmd + +import ( + "bufio" + "fmt" + "io" + "os/exec" + "strings" +) + +func SudoHandleCmd() string { + cmd := exec.Command("sudo", "-n", "ls") + if err := cmd.Run(); err == nil { + return "sudo " + } + return "" +} + +func Which(name string) bool { + stdout, err := RunDefaultWithStdoutBashCf("which %s", name) + if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) { + return false + } + return true +} + +func ExecWithStreamOutput(command string, outputCallback func(string)) error { + cmd := exec.Command("bash", "-c", command) + + stdout, err := cmd.StdoutPipe() + if err != nil { + return fmt.Errorf("failed to get stdout: %w", err) + } + + stderr, err := cmd.StderrPipe() + if err != nil { + return fmt.Errorf("failed to get stderr: %w", err) + } + + if err := cmd.Start(); err != nil { + return fmt.Errorf("failed to start command: %w", err) + } + + go streamReader(stdout, outputCallback) + go streamReader(stderr, outputCallback) + + if err := cmd.Wait(); err != nil { + return fmt.Errorf("command finished with error: %w", err) + } + return nil +} + +func streamReader(reader io.ReadCloser, callback func(string)) { + scanner := bufio.NewScanner(reader) + for scanner.Scan() { + callback(scanner.Text()) + } +} diff --git a/core/utils/cmd/cmdx.go b/core/utils/cmd/cmdx.go new file mode 100644 index 0000000..71be68a --- /dev/null +++ b/core/utils/cmd/cmdx.go @@ -0,0 +1,230 @@ +package cmd + +import ( + "bytes" + "context" + "errors" + "fmt" + "log" + "os" + "os/exec" + "strings" + "syscall" + "time" + + "github.com/1Panel-dev/1Panel/core/app/task" + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/constant" +) + +type CommandHelper struct { + workDir string + outputFile string + scriptPath string + timeout time.Duration + taskItem *task.Task + logger *log.Logger + IgnoreExist1 bool +} + +type Option func(*CommandHelper) + +func NewCommandMgr(opts ...Option) *CommandHelper { + s := &CommandHelper{} + for _, opt := range opts { + opt(s) + } + return s +} + +func RunDefaultBashC(command string) error { + mgr := NewCommandMgr() + return mgr.RunBashC(command) +} +func RunDefaultBashCf(command string, arg ...interface{}) error { + mgr := NewCommandMgr() + return mgr.RunBashCf(command, arg...) +} +func RunDefaultWithStdoutBashC(command string) (string, error) { + mgr := NewCommandMgr(WithTimeout(20 * time.Second)) + return mgr.RunWithStdoutBashC(command) +} +func RunDefaultWithStdoutBashCf(command string, arg ...interface{}) (string, error) { + mgr := NewCommandMgr(WithTimeout(20 * time.Second)) + return mgr.RunWithStdoutBashCf(command, arg...) +} + +func (c *CommandHelper) Run(name string, arg ...string) error { + _, err := c.run(name, arg...) + return err +} +func (c *CommandHelper) RunBashCWithArgs(arg ...string) error { + arg = append([]string{"-c"}, arg...) + _, err := c.run("bash", arg...) + return err +} +func (c *CommandHelper) RunBashC(command string) error { + _, err := c.run("bash", "-c", command) + return err +} +func (c *CommandHelper) RunBashCf(command string, arg ...interface{}) error { + _, err := c.run("bash", "-c", fmt.Sprintf(command, arg...)) + return err +} + +func (c *CommandHelper) RunWithStdout(name string, arg ...string) (string, error) { + return c.run(name, arg...) +} +func (c *CommandHelper) RunWithStdoutBashC(command string) (string, error) { + return c.run("bash", "-c", command) +} +func (c *CommandHelper) RunWithStdoutBashCf(command string, arg ...interface{}) (string, error) { + return c.run("bash", "-c", fmt.Sprintf(command, arg...)) +} + +func (c *CommandHelper) run(name string, arg ...string) (string, error) { + var cmd *exec.Cmd + var ctx context.Context + var cancel context.CancelFunc + + if c.timeout != 0 { + ctx, cancel = context.WithTimeout(context.Background(), c.timeout) + defer cancel() + cmd = exec.CommandContext(ctx, name, arg...) + } else { + cmd = exec.Command(name, arg...) + } + + customWriter := &CustomWriter{taskItem: c.taskItem} + var stdout, stderr bytes.Buffer + if c.taskItem != nil { + cmd.Stdout = customWriter + cmd.Stderr = customWriter + } else if c.logger != nil { + cmd.Stdout = c.logger.Writer() + cmd.Stderr = c.logger.Writer() + } else if len(c.outputFile) != 0 { + file, err := os.OpenFile(c.outputFile, os.O_WRONLY|os.O_CREATE, constant.FilePerm) + if err != nil { + return "", err + } + defer file.Close() + cmd.Stdout = file + cmd.Stderr = file + } else if len(c.scriptPath) != 0 { + cmd.Stdout = &stdout + cmd.Stderr = &stderr + cmd = exec.Command("bash", c.scriptPath) + } else { + cmd.Stdout = &stdout + cmd.Stderr = &stderr + } + env := os.Environ() + cmd.Env = env + if len(c.workDir) != 0 { + cmd.Dir = c.workDir + } + + if c.timeout != 0 { + err := cmd.Run() + if c.taskItem != nil { + customWriter.Flush() + } + if ctx != nil && errors.Is(ctx.Err(), context.DeadlineExceeded) { + return "", buserr.New("ErrCmdTimeout") + } + if err != nil { + return handleErr(stdout, stderr, c.IgnoreExist1, err) + } + return stdout.String(), nil + } + + err := cmd.Run() + if err != nil { + return handleErr(stdout, stderr, c.IgnoreExist1, err) + } + return stdout.String(), nil +} + +func WithOutputFile(outputFile string) Option { + return func(s *CommandHelper) { + s.outputFile = outputFile + } +} +func WithTimeout(timeout time.Duration) Option { + return func(s *CommandHelper) { + s.timeout = timeout + } +} +func WithLogger(logger *log.Logger) Option { + return func(s *CommandHelper) { + s.logger = logger + } +} +func WithTask(taskItem task.Task) Option { + return func(s *CommandHelper) { + s.taskItem = &taskItem + } +} +func WithWorkDir(workDir string) Option { + return func(s *CommandHelper) { + s.workDir = workDir + } +} +func WithScriptPath(scriptPath string) Option { + return func(s *CommandHelper) { + s.scriptPath = scriptPath + } +} +func WithIgnoreExist1() Option { + return func(s *CommandHelper) { + s.IgnoreExist1 = true + } +} + +type CustomWriter struct { + taskItem *task.Task + buffer bytes.Buffer +} + +func (cw *CustomWriter) Write(p []byte) (n int, err error) { + cw.buffer.Write(p) + lines := strings.Split(cw.buffer.String(), "\n") + + for i := 0; i < len(lines)-1; i++ { + cw.taskItem.Log(lines[i]) + } + cw.buffer.Reset() + cw.buffer.WriteString(lines[len(lines)-1]) + + return len(p), nil +} +func (cw *CustomWriter) Flush() { + if cw.buffer.Len() > 0 { + cw.taskItem.Log(cw.buffer.String()) + cw.buffer.Reset() + } +} + +func handleErr(stdout, stderr bytes.Buffer, ignoreExist1 bool, err error) (string, error) { + var exitError *exec.ExitError + if ignoreExist1 && errors.As(err, &exitError) { + if status, ok := exitError.Sys().(syscall.WaitStatus); ok { + if status.ExitStatus() == 1 { + return "", nil + } + } + } + errMsg := "" + if len(stderr.String()) != 0 { + errMsg = fmt.Sprintf("stderr: %s", stderr.String()) + } + if len(stdout.String()) != 0 { + if len(errMsg) != 0 { + errMsg = fmt.Sprintf("%s; stdout: %s", errMsg, stdout.String()) + } else { + errMsg = fmt.Sprintf("stdout: %s", stdout.String()) + } + } + return errMsg, err +} diff --git a/core/utils/common/common.go b/core/utils/common/common.go new file mode 100644 index 0000000..1d2bb5d --- /dev/null +++ b/core/utils/common/common.go @@ -0,0 +1,263 @@ +package common + +import ( + "crypto/md5" + "encoding/hex" + "fmt" + mathRand "math/rand" + "net" + "os" + "path" + "strconv" + "strings" + "time" + + "github.com/gin-gonic/gin" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/cmd" +) + +var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890") + +func RandStr(n int) string { + b := make([]rune, n) + for i := range b { + b[i] = letters[mathRand.Intn(len(letters))] + } + return string(b) +} +func RandStrAndNum(n int) string { + source := mathRand.NewSource(time.Now().UnixNano()) + randGen := mathRand.New(source) + const charset = "abcdefghijklmnopqrstuvwxyz0123456789" + b := make([]byte, n) + for i := range b { + b[i] = charset[randGen.Intn(len(charset)-1)] + } + return (string(b)) +} + +func Md5(val string) string { + hash := md5.New() + hash.Write([]byte(val)) + return hex.EncodeToString(hash.Sum(nil)) +} + +func LoadTimeZoneByCmd() string { + loc := time.Now().Location().String() + if _, err := time.LoadLocation(loc); err != nil { + loc = "Asia/Shanghai" + } + std, err := cmd.RunDefaultWithStdoutBashC("timedatectl | grep 'Time zone'") + if err != nil { + return loc + } + fields := strings.Fields(string(std)) + if len(fields) != 5 { + return loc + } + if _, err := time.LoadLocation(fields[2]); err != nil { + return loc + } + return fields[2] +} + +func ScanPort(port int) bool { + ln, err := net.Listen("tcp", ":"+strconv.Itoa(port)) + if err != nil { + return true + } + defer ln.Close() + return false +} + +func CheckPort(host string, port string, timeout time.Duration) bool { + target := net.JoinHostPort(host, port) + conn, err := net.DialTimeout("tcp", target, timeout) + if err != nil { + if netErr, ok := err.(net.Error); ok && netErr.Timeout() { + return false + } + return strings.Contains(err.Error(), "connection refused") + } + defer conn.Close() + return true +} + +func ComparePanelVersion(version1, version2 string) bool { + if version1 == version2 { + return false + } + version1s := SplitStr(version1, ".", "-") + version2s := SplitStr(version2, ".", "-") + + if len(version2s) > len(version1s) { + for i := 0; i < len(version2s)-len(version1s); i++ { + version1s = append(version1s, "0") + } + } + if len(version1s) > len(version2s) { + for i := 0; i < len(version1s)-len(version2s); i++ { + version2s = append(version2s, "0") + } + } + + n := min(len(version1s), len(version2s)) + for i := 0; i < n; i++ { + if version1s[i] == version2s[i] { + continue + } else { + v1, err1 := strconv.Atoi(version1s[i]) + if err1 != nil { + return version1s[i] > version2s[i] + } + v2, err2 := strconv.Atoi(version2s[i]) + if err2 != nil { + return version1s[i] > version2s[i] + } + return v1 > v2 + } + } + return true +} + +func SplitStr(str string, spi ...string) []string { + lists := []string{str} + var results []string + for _, s := range spi { + results = []string{} + for _, list := range lists { + results = append(results, strings.Split(list, s)...) + } + lists = results + } + return results +} + +func LoadArch() (string, error) { + std, err := cmd.RunDefaultWithStdoutBashC("uname -a") + if err != nil { + return "", fmt.Errorf("std: %s, err: %s", std, err.Error()) + } + return LoadArchWithStdout(std) +} +func LoadArchWithStdout(std string) (string, error) { + if strings.Contains(std, "x86_64") { + return "amd64", nil + } + if strings.Contains(std, "arm64") || strings.Contains(std, "aarch64") { + return "arm64", nil + } + if strings.Contains(std, "armv7l") { + return "armv7", nil + } + if strings.Contains(std, "ppc64le") { + return "ppc64le", nil + } + if strings.Contains(std, "s390x") { + return "s390x", nil + } + if strings.Contains(std, "riscv64") { + return "riscv64", nil + } + return "", fmt.Errorf("unsupported such arch: %s", std) +} + +func Clean(str []byte) { + for i := 0; i < len(str); i++ { + str[i] = 0 + } +} + +func CreateDirWhenNotExist(isDir bool, pathItem string) (string, error) { + checkPath := pathItem + if !isDir { + checkPath = path.Dir(pathItem) + } + if _, err := os.Stat(checkPath); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(checkPath, os.ModePerm); err != nil { + global.LOG.Errorf("mkdir %s failed, err: %v", checkPath, err) + return pathItem, err + } + } + return pathItem, nil +} + +func GetLang(c *gin.Context) string { + lang := c.GetHeader("Accept-Language") + if lang == "" { + lang = "en" + } + return lang +} + +func CheckIpInCidr(cidr, checkIP string) bool { + ip, ipNet, err := net.ParseCIDR(cidr) + if err != nil { + global.LOG.Errorf("parse CIDR %s failed, err: %v", cidr, err) + return false + } + for ip := ip.Mask(ipNet.Mask); ipNet.Contains(ip); incIP(ip) { + if ip.String() == checkIP { + return true + } + } + return false +} + +func incIP(ip net.IP) { + for j := len(ip) - 1; j >= 0; j-- { + ip[j]++ + if ip[j] > 0 { + break + } + } +} + +func HandleIPList(content string) ([]string, error) { + ipList := strings.Split(content, "\n") + var res []string + for _, ip := range ipList { + if ip == "" { + continue + } + if net.ParseIP(ip) != nil { + res = append(res, ip) + continue + } + if _, _, err := net.ParseCIDR(ip); err != nil { + return nil, err + } + res = append(res, ip) + } + return res, nil +} + +func LoadParams(param string) string { + stdout, err := cmd.RunDefaultWithStdoutBashCf("grep '^%s=' /usr/local/bin/1pctl | cut -d'=' -f2", param) + if err != nil { + panic(err) + } + info := strings.ReplaceAll(stdout, "\n", "") + if len(info) == 0 || info == `""` { + panic(fmt.Sprintf("error `%s` find in /usr/local/bin/1pctl", param)) + } + return info +} + +func GetRealClientIP(c *gin.Context) string { + addr := c.Request.RemoteAddr + if ip, _, err := net.SplitHostPort(addr); err == nil { + return ip + } + return addr +} + +func IsPrivateIP(ipStr string) bool { + ip := net.ParseIP(ipStr) + if ip == nil { + return false + } + return ip.IsPrivate() || ip.IsLoopback() +} diff --git a/core/utils/common/sqlite.go b/core/utils/common/sqlite.go new file mode 100644 index 0000000..acb8238 --- /dev/null +++ b/core/utils/common/sqlite.go @@ -0,0 +1,68 @@ +package common + +import ( + "fmt" + "log" + "os" + "path" + "time" + + "github.com/glebarez/sqlite" + "gorm.io/gorm" + "gorm.io/gorm/logger" +) + +func LoadDBConnByPath(fullPath, dbName string) *gorm.DB { + if _, err := CreateDirWhenNotExist(true, path.Dir(fullPath)); err != nil { + panic(fmt.Errorf("init db dir failed, err: %v", err)) + } + if _, err := os.Stat(fullPath); err != nil { + f, err := os.Create(fullPath) + if err != nil { + panic(fmt.Errorf("init %s db file failed, err: %v", dbName, err)) + } + _ = f.Close() + } + + db, err := GetDBWithPath(fullPath) + if err != nil { + panic(err) + } + return db +} + +func CloseDB(db *gorm.DB) { + sqlDB, err := db.DB() + if err != nil { + return + } + _ = sqlDB.Close() +} + +func GetDBWithPath(dbPath string) (*gorm.DB, error) { + db, _ := gorm.Open(sqlite.Open(dbPath), &gorm.Config{ + DisableForeignKeyConstraintWhenMigrating: true, + Logger: newLogger(), + }) + sqlDB, dbError := db.DB() + if dbError != nil { + return nil, dbError + } + sqlDB.SetMaxOpenConns(4) + sqlDB.SetMaxIdleConns(1) + sqlDB.SetConnMaxIdleTime(15 * time.Minute) + sqlDB.SetConnMaxLifetime(time.Hour) + return db, nil +} + +func newLogger() logger.Interface { + return logger.New( + log.New(os.Stdout, "\r\n", log.LstdFlags), + logger.Config{ + SlowThreshold: time.Second, + LogLevel: logger.Silent, + IgnoreRecordNotFoundError: true, + Colorful: false, + }, + ) +} diff --git a/core/utils/controller/controller.go b/core/utils/controller/controller.go new file mode 100644 index 0000000..cfce0c4 --- /dev/null +++ b/core/utils/controller/controller.go @@ -0,0 +1,315 @@ +package controller + +import ( + "errors" + "fmt" + "os" + "os/exec" + "strings" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/controller/manager" + "github.com/1Panel-dev/1Panel/core/utils/ssh" +) + +type Controller interface { + Name() string + IsActive(serviceName string) (bool, error) + IsEnable(serviceName string) (bool, error) + IsExist(serviceName string) (bool, error) + Status(serviceName string) (string, error) + + Operate(operate, serviceName string) error + + Reload() error +} + +func New() (Controller, error) { + managerOptions := []string{"systemctl", "rc-service", "service"} + for _, item := range managerOptions { + if _, err := exec.LookPath(item); err != nil { + continue + } + switch item { + case "systemctl": + return manager.NewSystemd(), nil + case "rc-service": + return manager.NewOpenrc(), nil + case "service": + return manager.NewSysvinit(), nil + } + } + return nil, errors.New("not support such manager initializatio") +} + +func NewWithClient(client *ssh.SSHClient) (Controller, error) { + managerOptions := []string{"systemctl", "rc-service", "service"} + for _, item := range managerOptions { + stdout, err := client.Runf("which %s", item) + if err != nil || (len(strings.ReplaceAll(stdout, "\n", "")) == 0) { + continue + } + switch item { + case "systemctl": + mgr := manager.NewSystemd() + mgr.Client = client + return mgr, nil + case "rc-service": + mgr := manager.NewOpenrc() + mgr.Client = client + return mgr, nil + case "service": + mgr := manager.NewSysvinit() + mgr.Client = client + return mgr, nil + } + } + return nil, errors.New("not support such manager initializatio") +} + +func Handle(operate, serviceName string) error { + service, err := LoadServiceName(serviceName) + if err != nil { + return err + } + client, err := New() + if err != nil { + return err + } + return client.Operate(operate, service) +} +func HandleStart(serviceName string) error { + service, err := LoadServiceName(serviceName) + if err != nil { + return err + } + return Handle("start", service) +} +func HandleStop(serviceName string) error { + service, err := LoadServiceName(serviceName) + if err != nil { + return err + } + return Handle("stop", service) +} +func HandleRestart(serviceName string) error { + service, err := LoadServiceName(serviceName) + if err != nil { + return err + } + return Handle("restart", service) +} + +func CheckExist(serviceName string) (bool, error) { + service, err := LoadServiceName(serviceName) + if err != nil { + return false, err + } + client, err := New() + if err != nil { + return false, err + } + b, er := client.IsExist(service) + return b, er +} +func CheckActive(serviceName string) (bool, error) { + service, err := LoadServiceName(serviceName) + if err != nil { + return false, err + } + client, err := New() + if err != nil { + return false, err + } + return client.IsActive(service) +} +func CheckEnable(serviceName string) (bool, error) { + service, err := LoadServiceName(serviceName) + if err != nil { + return false, err + } + client, err := New() + if err != nil { + return false, err + } + return client.IsEnable(service) +} + +func Reload() error { + client, err := New() + if err != nil { + return err + } + return client.Reload() +} + +func RestartPanel(core, agent, reload bool) { + client, err := New() + if err != nil { + global.LOG.Errorf("load client for controller failed, err: %v", err) + return + } + if reload { + if err := client.Reload(); err != nil { + global.LOG.Errorf("restart 1panel service failed, err: %v", err) + return + } + } + if agent { + if err := client.Operate("restart", "1panel-agent"); err != nil { + global.LOG.Errorf("restart 1panel agent service failed, err: %v", err) + return + } + } + if core { + if err := client.Operate("restart", "1panel-core"); err != nil { + global.LOG.Errorf("restart 1panel core service failed, err: %v", err) + return + } + } +} + +func LoadServiceName(keyword string) (string, error) { + client, err := New() + if err != nil { + return "", err + } + + processedName := loadProcessedName(client.Name(), keyword) + exist, _ := client.IsExist(processedName) + if exist { + return processedName, nil + } + alistName := loadFromPredefined(client, keyword) + if len(alistName) != 0 { + return alistName, nil + } + return "", fmt.Errorf("find such service for %s failed", keyword) +} + +func loadProcessedName(mgr, keyword string) string { + keyword = strings.ToLower(keyword) + if strings.HasSuffix(keyword, ".service.socket") { + keyword = strings.TrimSuffix(keyword, ".service.socket") + ".socket" + } + if mgr != "systemd" { + keyword = strings.TrimSuffix(keyword, ".service") + return keyword + } + if !strings.HasSuffix(keyword, ".service") && !strings.HasSuffix(keyword, ".socket") { + keyword += ".service" + } + return keyword +} + +func loadFromPredefined(mgr Controller, keyword string) string { + predefinedMap := map[string][]string{ + "clam": {"clamav-daemon.service", "clamd@scan.service", "clamd"}, + "freshclam": {"clamav-freshclam.service", "freshclam.service"}, + "fail2ban": {"fail2ban.service", "fail2ban"}, + "supervisor": {"supervisord.service", "supervisor.service", "supervisord", "supervisor"}, + "ssh": {"sshd.service", "ssh.service", "sshd", "ssh"}, + "1panel-core": {"1panel-core.service"}, + "1panel-agent": {"1panel-agent.service"}, + "docker": {"docker.service", "dockerd"}, + } + if val, ok := predefinedMap[keyword]; ok { + for _, item := range val { + if exist, _ := mgr.IsExist(item); exist { + return item + } + } + } + return "" +} + +// GetServicePath returns the configuration file path for the specified service. +// If serviceName is empty, it returns the default directory path based on the system's service manager. +// For non-empty serviceName, it retrieves the exact service file path. +// Parameters: +// - serviceName: Name of the service. If empty, returns the default directory. +// +// Returns: +// - string: The service configuration file path. +// - error: Error if the service manager is unsupported or command execution fails. +func GetServicePath(serviceName string) (string, error) { + if serviceName == "" { + client, err := New() + if err != nil { + return "", err + } + switch client.Name() { + case "systemd": + return "/etc/systemd/system/", nil + case "openrc", "sysvinit": + return "/etc/init.d/", nil + default: + return "", fmt.Errorf("unsupported manager: %s", client.Name()) + } + } + service, err := LoadServiceName(serviceName) + if err != nil { + return "", err + } + client, err := New() + if err != nil { + return "", err + } + switch client.Name() { + case "systemd": + stdout, err := exec.Command("systemctl", "show", "-p", "FragmentPath", service).Output() + if err != nil { + return "", err + } + parts := strings.SplitN(string(stdout), "=", 2) + if len(parts) != 2 { + return "", fmt.Errorf("unexpected output: %s", string(stdout)) + } + return strings.TrimSpace(parts[1]), nil + case "openrc", "sysvinit": + return fmt.Sprintf("/etc/init.d/%s", service), nil + default: + return "", fmt.Errorf("unsupported manager: %s", client.Name()) + } +} + +func SelectInitScript(keyword string) (string, error) { + client, err := New() + if err != nil { + return "", err + } + switch client.Name() { + case "systemd": + keyword = strings.TrimSuffix(keyword, ".service") + ".service" + case "openrc": + keyword = strings.TrimSuffix(keyword, ".service") + ".openrc" + case "sysvinit": + if _, err := os.Stat("/etc/rc.common"); err == nil { + keyword = strings.TrimSuffix(keyword, ".service") + ".prod" + } else { + keyword = strings.TrimSuffix(keyword, ".service") + ".init" + } + default: + return "", fmt.Errorf("unsupported manager: %s", client.Name()) + } + return keyword, nil +} + +func GetScriptName(keyword string) (string, error) { + client, err := New() + if err != nil { + return "", err + } + switch client.Name() { + case "systemd": + keyword = strings.TrimSuffix(keyword, ".service") + ".service" + case "openrc", "sysvinit": + lastDotIdx := strings.LastIndex(keyword, ".") + if lastDotIdx != -1 { + keyword = keyword[:lastDotIdx] + } + default: + return "", fmt.Errorf("unsupported manager: %s", client.Name()) + } + return keyword, nil +} diff --git a/core/utils/controller/manager/common.go b/core/utils/controller/manager/common.go new file mode 100644 index 0000000..89f7620 --- /dev/null +++ b/core/utils/controller/manager/common.go @@ -0,0 +1,27 @@ +package manager + +import ( + "errors" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/ssh" +) + +func handlerErr(out string, err error) error { + if err != nil { + if out != "" { + return errors.New(out) + } + return err + } + return nil +} + +func run(client *ssh.SSHClient, name string, args ...string) (string, error) { + if client == nil { + return cmd.NewCommandMgr(cmd.WithTimeout(10*time.Second)).RunWithStdoutBashCf("LANGUAGE=en_US:en %s %s", name, strings.Join(args, " ")) + } + return client.Runf("LANGUAGE=en_US:en %s %s", name, strings.Join(args, " ")) +} diff --git a/core/utils/controller/manager/openrc.go b/core/utils/controller/manager/openrc.go new file mode 100644 index 0000000..ceb5b1f --- /dev/null +++ b/core/utils/controller/manager/openrc.go @@ -0,0 +1,64 @@ +package manager + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/ssh" +) + +type Openrc struct { + toolCmd string + Client *ssh.SSHClient +} + +func NewOpenrc() *Openrc { + return &Openrc{toolCmd: "rc-service"} +} + +func (s *Openrc) Name() string { + return "openrc" +} +func (s *Openrc) IsActive(serviceName string) (bool, error) { + out, err := cmd.RunDefaultWithStdoutBashCf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", serviceName) + if err != nil { + return false, err + } + return out == "active\n", nil +} +func (s *Openrc) IsEnable(serviceName string) (bool, error) { + out, err := cmd.RunDefaultWithStdoutBashCf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", serviceName) + if err != nil { + return false, err + } + return out == "enabled\n", nil +} +func (s *Openrc) IsExist(serviceName string) (bool, error) { + if _, err := os.Stat(filepath.Join("/etc/init.d", serviceName)); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", serviceName, err) + } + return true, nil +} +func (s *Openrc) Status(serviceName string) (string, error) { + return run(s.Client, s.toolCmd, serviceName, "status") +} + +func (s *Openrc) Operate(operate, serviceName string) error { + switch operate { + case "enable": + return handlerErr(run(s.Client, "rc-update", "add", serviceName, "default")) + case "disable": + return handlerErr(run(s.Client, "rc-update", "del", serviceName, "default")) + default: + return handlerErr(run(s.Client, s.toolCmd, serviceName, operate)) + } +} + +func (s *Openrc) Reload() error { + return nil +} diff --git a/core/utils/controller/manager/snap.go b/core/utils/controller/manager/snap.go new file mode 100644 index 0000000..435345e --- /dev/null +++ b/core/utils/controller/manager/snap.go @@ -0,0 +1,54 @@ +package manager + +import ( + "strings" +) + +type Snap struct{ toolCmd string } + +func NewSnap() *Snap { + return &Snap{toolCmd: "snap"} +} + +func (s *Snap) IsExist(serviceName string) bool { + out, err := run(nil, s.toolCmd, "services") + if err != nil { + return false + } + return strings.Contains(out, serviceName) +} + +func (s *Snap) IsActive(serviceName string) bool { + out, err := run(nil, s.toolCmd, "services") + if err != nil { + return false + } + lines := strings.Split(out, "\n") + for _, line := range lines { + if strings.Contains(line, serviceName) && strings.Contains(line, "active") { + return true + } + } + return false +} + +func (s *Snap) IsEnable(serviceName string) bool { + out, err := run(nil, s.toolCmd, "services") + if err != nil { + return false + } + lines := strings.Split(out, "\n") + for _, line := range lines { + if strings.Contains(line, serviceName) && strings.Contains(line, "enabled") { + return true + } + } + return false +} + +func (s *Snap) Operate(operate, serviceName string) error { + if s.IsExist(serviceName) { + return handlerErr(run(nil, s.toolCmd, operate, serviceName)) + } + return nil +} diff --git a/core/utils/controller/manager/systemd.go b/core/utils/controller/manager/systemd.go new file mode 100644 index 0000000..d8d8fe3 --- /dev/null +++ b/core/utils/controller/manager/systemd.go @@ -0,0 +1,80 @@ +package manager + +import ( + "strings" + + "github.com/1Panel-dev/1Panel/core/utils/ssh" +) + +type Systemd struct { + toolCmd string + Client *ssh.SSHClient +} + +func NewSystemd() *Systemd { + return &Systemd{toolCmd: "systemctl"} +} + +func (s *Systemd) Name() string { + return "systemd" +} +func (s *Systemd) IsActive(serviceName string) (bool, error) { + out, err := run(s.Client, s.toolCmd, "is-active", serviceName) + if err != nil && out != "inactive\n" { + if NewSnap().IsActive(serviceName) { + return true, nil + } + return false, err + } + return out == "active\n", nil +} + +func (s *Systemd) IsEnable(serviceName string) (bool, error) { + out, err := run(s.Client, s.toolCmd, "is-enabled", serviceName) + if err != nil && out != "disabled\n" { + if serviceName == "sshd" && out == "alias\n" { + return s.IsEnable("ssh") + } + if NewSnap().IsEnable(serviceName) { + return true, nil + } + return false, err + } + return out == "enabled\n", nil +} + +func (s *Systemd) IsExist(serviceName string) (bool, error) { + out, err := run(s.Client, s.toolCmd, "is-enabled", serviceName) + if err != nil && out != "enabled\n" { + if strings.Contains(out, "disabled") { + return true, err + } + if NewSnap().IsExist(serviceName) { + return true, nil + } + return false, err + } + return true, err +} + +func (s *Systemd) Status(serviceName string) (string, error) { + return run(s.Client, s.toolCmd, "status", serviceName) +} +func (s *Systemd) Operate(operate, serviceName string) error { + out, err := run(s.Client, s.toolCmd, operate, serviceName) + if err != nil { + if serviceName == "sshd" && strings.Contains(out, "alias name or linked unit file") { + return s.Operate(operate, "ssh") + } + if err := NewSnap().Operate(operate, serviceName); err == nil { + return nil + } + return handlerErr(run(s.Client, s.toolCmd, operate, serviceName)) + } + return nil +} + +func (s *Systemd) Reload() error { + out, err := run(s.Client, s.toolCmd, "daemon-reload") + return handlerErr(out, err) +} diff --git a/core/utils/controller/manager/sysvinit.go b/core/utils/controller/manager/sysvinit.go new file mode 100644 index 0000000..74274a5 --- /dev/null +++ b/core/utils/controller/manager/sysvinit.go @@ -0,0 +1,57 @@ +package manager + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/ssh" +) + +type Sysvinit struct { + toolCmd string + Client *ssh.SSHClient +} + +func NewSysvinit() *Sysvinit { + return &Sysvinit{toolCmd: "service"} +} + +func (s *Sysvinit) Name() string { + return "sysvinit" +} +func (s *Sysvinit) IsActive(serviceName string) (bool, error) { + out, err := cmd.RunDefaultWithStdoutBashCf("if service %s status >/dev/null 2>&1; then echo 'active'; else echo 'inactive'; fi", serviceName) + if err != nil { + return false, err + } + return out == "active\n", nil +} +func (s *Sysvinit) IsEnable(serviceName string) (bool, error) { + out, err := cmd.RunDefaultWithStdoutBashCf("if ls /etc/rc*.d/S*%s >/dev/null 2>&1; then echo 'enabled'; else echo 'disabled'; fi", serviceName) + if err != nil { + return false, err + } + return out == "enabled\n", nil +} +func (s *Sysvinit) IsExist(serviceName string) (bool, error) { + if _, err := os.Stat(filepath.Join("/etc/init.d", serviceName)); err != nil { + if os.IsNotExist(err) { + return false, nil + } + return false, fmt.Errorf("stat /etc/init.d/%s failed: %w", serviceName, err) + } + return true, nil +} +func (s *Sysvinit) Status(serviceName string) (string, error) { + return run(s.Client, s.toolCmd, serviceName, "status") +} + +func (s *Sysvinit) Operate(operate, serviceName string) error { + return handlerErr(run(s.Client, s.toolCmd, serviceName, operate)) +} + +func (s *Sysvinit) Reload() error { + return nil +} diff --git a/core/utils/copier/copier.go b/core/utils/copier/copier.go new file mode 100644 index 0000000..bfc684c --- /dev/null +++ b/core/utils/copier/copier.go @@ -0,0 +1,18 @@ +package copier + +import ( + "encoding/json" + + "github.com/pkg/errors" +) + +func Copy(to, from interface{}) error { + b, err := json.Marshal(from) + if err != nil { + return errors.Wrap(err, "marshal from data err") + } + if err = json.Unmarshal(b, to); err != nil { + return errors.Wrap(err, "unmarshal to data err") + } + return nil +} diff --git a/core/utils/csv/command.go b/core/utils/csv/command.go new file mode 100644 index 0000000..863d0f7 --- /dev/null +++ b/core/utils/csv/command.go @@ -0,0 +1,43 @@ +package csv + +import ( + "encoding/csv" + "os" + + "github.com/1Panel-dev/1Panel/core/i18n" +) + +type CommandTemplate struct { + Name string `json:"name"` + Command string `json:"command"` +} + +func ExportCommands(filename string, commands []CommandTemplate) error { + file, err := os.Create(filename) + if err != nil { + return err + } + defer file.Close() + + writer := csv.NewWriter(file) + defer writer.Flush() + + if err := writer.Write([]string{ + i18n.GetMsgByKey("Name"), + i18n.GetMsgByKey("Command"), + }); err != nil { + return err + } + + for _, log := range commands { + record := []string{ + log.Name, + log.Command, + } + if err := writer.Write(record); err != nil { + return err + } + } + + return nil +} diff --git a/core/utils/encrypt/encrypt.go b/core/utils/encrypt/encrypt.go new file mode 100644 index 0000000..753fdad --- /dev/null +++ b/core/utils/encrypt/encrypt.go @@ -0,0 +1,230 @@ +package encrypt + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "crypto/rsa" + "crypto/x509" + "encoding/base64" + "encoding/pem" + "errors" + "fmt" + "io" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/model" + "github.com/1Panel-dev/1Panel/core/global" +) + +func StringDecryptWithBase64(text string) (string, error) { + decryptItem, err := StringDecrypt(text) + if err != nil { + return "", err + } + return base64.StdEncoding.EncodeToString([]byte(decryptItem)), nil +} + +func StringEncrypt(text string) (string, error) { + if len(text) == 0 { + return "", nil + } + if len(global.CONF.Base.EncryptKey) == 0 { + var encryptSetting model.Setting + if err := global.DB.Where("key = ?", "EncryptKey").First(&encryptSetting).Error; err != nil { + return "", err + } + global.CONF.Base.EncryptKey = encryptSetting.Value + } + key := global.CONF.Base.EncryptKey + return StringEncryptWithKey(text, key) +} + +func StringEncryptWithKey(text, key string) (string, error) { + if len(text) == 0 || len(key) == 0 { + return "", nil + } + pass := []byte(text) + xpass, err := aesEncryptWithSalt([]byte(key), pass) + if err == nil { + pass64 := base64.StdEncoding.EncodeToString(xpass) + return pass64, err + } + return "", err +} + +func StringDecrypt(text string) (string, error) { + if len(text) == 0 { + return "", nil + } + if len(global.CONF.Base.EncryptKey) == 0 { + var encryptSetting model.Setting + if err := global.DB.Where("key = ?", "EncryptKey").First(&encryptSetting).Error; err != nil { + return "", err + } + global.CONF.Base.EncryptKey = encryptSetting.Value + } + key := global.CONF.Base.EncryptKey + return StringDecryptWithKey(text, key) +} + +func StringDecryptWithKey(text, key string) (string, error) { + defer func() { + if r := recover(); r != nil { + if global.LOG != nil { + global.LOG.Errorf("A panic occurred during string decrypt with key, error message: %v", r) + } + } + }() + if len(text) == 0 { + return "", nil + } + bytesPass, err := base64.StdEncoding.DecodeString(text) + if err != nil { + return "", err + } + var tpass []byte + tpass, err = aesDecryptWithSalt([]byte(key), bytesPass) + if err == nil { + result := string(tpass[:]) + return result, err + } + return "", err +} + +func padding(plaintext []byte, blockSize int) []byte { + padding := blockSize - len(plaintext)%blockSize + padtext := bytes.Repeat([]byte{byte(padding)}, padding) + return append(plaintext, padtext...) +} + +func unPadding(origData []byte) []byte { + length := len(origData) + unpadding := int(origData[length-1]) + return origData[:(length - unpadding)] +} + +func aesEncryptWithSalt(key, plaintext []byte) ([]byte, error) { + plaintext = padding(plaintext, aes.BlockSize) + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + ciphertext := make([]byte, aes.BlockSize+len(plaintext)) + iv := ciphertext[0:aes.BlockSize] + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + cbc := cipher.NewCBCEncrypter(block, iv) + cbc.CryptBlocks(ciphertext[aes.BlockSize:], plaintext) + return ciphertext, nil +} +func aesDecryptWithSalt(key, ciphertext []byte) ([]byte, error) { + var block cipher.Block + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + if len(ciphertext) < aes.BlockSize { + return nil, fmt.Errorf("iciphertext too short") + } + iv := ciphertext[:aes.BlockSize] + ciphertext = ciphertext[aes.BlockSize:] + cbc := cipher.NewCBCDecrypter(block, iv) + cbc.CryptBlocks(ciphertext, ciphertext) + ciphertext = unPadding(ciphertext) + return ciphertext, nil +} + +func ParseRSAPrivateKey(privateKeyPEM string) (*rsa.PrivateKey, error) { + block, _ := pem.Decode([]byte(privateKeyPEM)) + if block == nil || block.Type != "RSA PRIVATE KEY" { + return nil, errors.New("failed to decode PEM block containing the private key") + } + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return nil, err + } + return privateKey, nil +} + +func aesDecrypt(ciphertext, key, iv []byte) ([]byte, error) { + if len(key) != 16 && len(key) != 24 && len(key) != 32 { + return nil, errors.New("invalid AES key length: must be 16, 24, or 32 bytes") + } + if len(iv) != aes.BlockSize { + return nil, errors.New("invalid IV length: must be 16 bytes") + } + + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(ciphertext, ciphertext) + ciphertext = pkcs7Unpad(ciphertext) + return ciphertext, nil +} + +func pkcs7Unpad(data []byte) []byte { + length := len(data) + padLength := int(data[length-1]) + return data[:length-padLength] +} + +func DecryptPassword(encryptedData string, privateKey *rsa.PrivateKey) (string, error) { + parts := strings.Split(encryptedData, ":") + if len(parts) != 3 { + return "", errors.New("encrypted data format error") + } + keyCipher := parts[0] + ivBase64 := parts[1] + ciphertextBase64 := parts[2] + + encryptedAESKey, err := base64.StdEncoding.DecodeString(keyCipher) + if err != nil { + return "", errors.New("failed to decode keyCipher") + } + + aesKey, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, encryptedAESKey) + if err != nil { + return "", errors.New("failed to decode AES Key") + } + + ciphertext, err := base64.StdEncoding.DecodeString(ciphertextBase64) + if err != nil { + return "", errors.New("failed to decrypt the encrypted data") + } + iv, err := base64.StdEncoding.DecodeString(ivBase64) + if err != nil { + return "", errors.New("failed to decode the IV") + } + + password, err := aesDecrypt(ciphertext, aesKey, iv) + if err != nil { + return "", err + } + return string(password), nil +} + +func ExportPrivateKeyToPEM(privateKey *rsa.PrivateKey) string { + privateKeyBytes := x509.MarshalPKCS1PrivateKey(privateKey) + privateKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: privateKeyBytes, + }) + return string(privateKeyPEM) +} + +func ExportPublicKeyToPEM(publicKey *rsa.PublicKey) (string, error) { + publicKeyBytes, err := x509.MarshalPKIXPublicKey(publicKey) + if err != nil { + return "", err + } + publicKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "PUBLIC KEY", + Bytes: publicKeyBytes, + }) + return string(publicKeyPEM), nil +} diff --git a/core/utils/files/files.go b/core/utils/files/files.go new file mode 100644 index 0000000..eb3058c --- /dev/null +++ b/core/utils/files/files.go @@ -0,0 +1,231 @@ +package files + +import ( + "bytes" + "crypto/md5" + "encoding/hex" + "errors" + "fmt" + "io" + "net/http" + "os" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/cmd" + "github.com/1Panel-dev/1Panel/core/utils/req_helper" +) + +func CopyFile(src, dst string, withName bool) error { + source, err := os.Open(src) + if err != nil { + return err + } + defer source.Close() + + if path.Base(src) != path.Base(dst) && !withName { + dst = path.Join(dst, path.Base(src)) + } + if _, err := os.Stat(path.Dir(dst)); err != nil { + if os.IsNotExist(err) { + _ = os.MkdirAll(path.Dir(dst), os.ModePerm) + } + } + target, err := os.OpenFile(dst+"_temp", os.O_RDWR|os.O_CREATE|os.O_TRUNC, constant.FilePerm) + if err != nil { + return err + } + defer target.Close() + + if _, err = io.Copy(target, source); err != nil { + return err + } + if err = os.Rename(dst+"_temp", dst); err != nil { + return err + } + return nil +} + +func CopyItem(isDir, withName bool, src, dst string) error { + if path.Base(src) != path.Base(dst) && !withName { + dst = path.Join(dst, path.Base(src)) + } + srcInfo, err := os.Stat(path.Dir(src)) + if err != nil { + return err + } + if _, err := os.Stat(dst); err != nil { + if os.IsNotExist(err) { + _ = os.MkdirAll(dst, srcInfo.Mode()) + } + } + cmdStr := fmt.Sprintf(`cp -rf %s %s`, src, dst+"/") + if !isDir { + cmdStr = fmt.Sprintf(`cp -f %s %s`, src, dst+"/") + } + stdout, err := cmd.NewCommandMgr(cmd.WithTimeout(60 * time.Second)).RunWithStdoutBashC(cmdStr) + if err != nil { + return fmt.Errorf("handle %s failed, stdout: %s, err: %v", cmdStr, stdout, err) + } + return nil +} + +func CopyFileWithRename(src, dst string) error { + srcInfo, err := os.Stat(path.Dir(src)) + if err != nil { + return err + } + if _, err := os.Stat(path.Dir(dst)); err != nil { + if os.IsNotExist(err) { + _ = os.MkdirAll(path.Dir(dst), srcInfo.Mode()) + } + } + if err := cmd.RunDefaultBashCf("cp -f %s %s.tmp", src, dst); err != nil { + return fmt.Errorf("handle cp file failed, err: %v", err) + } + if err = cmd.RunDefaultBashCf("mv %s.tmp %s", dst, dst); err != nil { + return err + } + return nil +} + +func HandleTar(sourceDir, targetDir, name, exclusionRules string, secret string) error { + if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(targetDir, os.ModePerm); err != nil { + return err + } + } + + exMap := make(map[string]struct{}) + excludes := strings.Split(exclusionRules, ",") + excludeRules := "" + for _, exclude := range excludes { + if len(exclude) == 0 { + continue + } + if _, ok := exMap[exclude]; ok { + continue + } + excludeRules += fmt.Sprintf(" --exclude '%s'", exclude) + exMap[exclude] = struct{}{} + } + path := "" + if strings.Contains(sourceDir, "/") { + itemDir := strings.ReplaceAll(sourceDir[strings.LastIndex(sourceDir, "/"):], "/", "") + aheadDir := sourceDir[:strings.LastIndex(sourceDir, "/")] + if len(aheadDir) == 0 { + aheadDir = "/" + } + path += fmt.Sprintf("-C %s %s", aheadDir, itemDir) + } else { + path = sourceDir + } + + commands := "" + + if len(secret) != 0 { + extraCmd := "| openssl enc -aes-256-cbc -salt -k '" + secret + "' -out" + commands = fmt.Sprintf("tar -zcf %s %s %s %s", " -"+excludeRules, path, extraCmd, targetDir+"/"+name) + global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + } else { + commands = fmt.Sprintf("tar -zcf %s %s %s", targetDir+"/"+name, excludeRules, path) + global.LOG.Debug(commands) + } + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(24*time.Hour), cmd.WithIgnoreExist1()) + stdout, err := cmdMgr.RunWithStdoutBashC(commands) + if err != nil { + if len(stdout) != 0 { + global.LOG.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err) + return fmt.Errorf("do handle tar failed, stdout: %s, err: %v", stdout, err) + } + } + return nil +} + +func HandleUnTar(sourceFile, targetDir string, secret string) error { + if _, err := os.Stat(targetDir); err != nil && os.IsNotExist(err) { + if err = os.MkdirAll(targetDir, os.ModePerm); err != nil { + return err + } + } + commands := "" + if len(secret) != 0 { + extraCmd := "openssl enc -d -aes-256-cbc -k '" + secret + "' -in " + sourceFile + " | " + commands = fmt.Sprintf("%s tar -zxvf - -C %s", extraCmd, targetDir+" > /dev/null 2>&1") + global.LOG.Debug(strings.ReplaceAll(commands, fmt.Sprintf(" '%s' ", secret), " ****** ")) + } else { + commands = fmt.Sprintf("tar zxvf '%s' -C '%s'", sourceFile, targetDir) + global.LOG.Debug(commands) + } + + cmdMgr := cmd.NewCommandMgr(cmd.WithTimeout(24 * time.Hour)) + stdout, err := cmdMgr.RunWithStdoutBashC(commands) + if err != nil { + global.LOG.Errorf("do handle untar failed, stdout: %s, err: %v", stdout, err) + return errors.New(stdout) + } + return nil +} + +func DownloadFile(url, dst string) error { + resp, err := req_helper.HandleGet(url) + if err != nil { + return err + } + defer resp.Body.Close() + + out, err := os.Create(dst) + if err != nil { + return fmt.Errorf("create download file [%s] error, err %s", dst, err.Error()) + } + defer out.Close() + + if _, err = io.Copy(out, resp.Body); err != nil { + return fmt.Errorf("save download file [%s] error, err %s", dst, err.Error()) + } + return nil +} + +func DownloadFileWithProxy(url, dst string) error { + _, resp, err := req_helper.HandleRequestWithProxy(url, http.MethodGet, constant.TimeOut5m) + if err != nil { + return err + } + + out, err := os.Create(dst) + if err != nil { + return fmt.Errorf("create download file [%s] error, err %s", dst, err.Error()) + } + defer out.Close() + + reader := bytes.NewReader(resp) + if _, err = io.Copy(out, reader); err != nil { + return fmt.Errorf("save download file [%s] error, err %s", dst, err.Error()) + } + return nil +} + +func Stat(path string) bool { + _, err := os.Stat(path) + if err != nil && os.IsNotExist(err) { + return false + } + return true +} + +func GetFileMD5(filePath string) (string, error) { + file, err := os.Open(filePath) + if err != nil { + return "", err + } + defer file.Close() + hash := md5.New() + + if _, err = io.Copy(hash, file); err != nil { + return "", err + } + return hex.EncodeToString(hash.Sum(nil)), nil +} diff --git a/core/utils/firewall/firewall.go b/core/utils/firewall/firewall.go new file mode 100644 index 0000000..d87cb38 --- /dev/null +++ b/core/utils/firewall/firewall.go @@ -0,0 +1,50 @@ +package firewall + +import ( + "fmt" + + "github.com/1Panel-dev/1Panel/core/utils/cmd" +) + +func UpdatePort(oldPort, newPort string) error { + firewalld := cmd.Which("firewalld") + if firewalld { + status, _ := cmd.RunDefaultWithStdoutBashC("LANGUAGE=en_US:en firewall-cmd --state") + isRunning := status == "running\n" + if isRunning { + return firewallUpdatePort(oldPort, newPort) + } + } + + ufw := cmd.Which("ufw") + if !ufw { + return nil + } + status, _ := cmd.RunDefaultWithStdoutBashC("LANGUAGE=en_US:en ufw status | grep Status") + isRuning := status == "Status: active\n" + if isRuning { + return ufwUpdatePort(oldPort, newPort) + } + return nil +} + +func firewallUpdatePort(oldPort, newPort string) error { + stdout, err := cmd.RunDefaultWithStdoutBashCf("firewall-cmd --zone=public --add-port=%s/tcp --permanent", newPort) + if err != nil { + return fmt.Errorf("add (port: %s/tcp) failed, err: %s", newPort, stdout) + } + + _, _ = cmd.RunDefaultWithStdoutBashCf("firewall-cmd --zone=public --remove-port=%s/tcp --permanent", oldPort) + _, _ = cmd.RunDefaultWithStdoutBashC("firewall-cmd --reload") + return nil +} + +func ufwUpdatePort(oldPort, newPort string) error { + stdout, err := cmd.RunDefaultWithStdoutBashCf("ufw allow %s", newPort) + if err != nil { + return fmt.Errorf("add (port: %s/tcp) failed, err: %s", newPort, stdout) + } + + _, _ = cmd.RunDefaultWithStdoutBashCf("ufw delete allow %s", oldPort) + return nil +} diff --git a/core/utils/geo/geo.go b/core/utils/geo/geo.go new file mode 100644 index 0000000..c390a61 --- /dev/null +++ b/core/utils/geo/geo.go @@ -0,0 +1,48 @@ +package geo + +import ( + "net" + "path" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/oschwald/maxminddb-golang" +) + +type Location struct { + En string `maxminddb:"en"` + Zh string `maxminddb:"zh"` +} + +type LocationRes struct { + Iso string `maxminddb:"iso"` + Country Location `maxminddb:"country"` + Latitude float64 `maxminddb:"latitude"` + Longitude float64 `maxminddb:"longitude"` + Province Location `maxminddb:"province"` +} + +func NewGeo() (*maxminddb.Reader, error) { + geoPath := path.Join(global.CONF.Base.InstallDir, "1panel", "geo", "GeoIP.mmdb") + return maxminddb.Open(geoPath) +} + +func GetIPLocation(reader *maxminddb.Reader, ip, lang string) (string, error) { + var err error + var geoLocation LocationRes + if reader == nil { + geoPath := path.Join(global.CONF.Base.InstallDir, "1panel", "geo", "GeoIP.mmdb") + reader, err = maxminddb.Open(geoPath) + if err != nil { + return "", err + } + } + ipNet := net.ParseIP(ip) + err = reader.Lookup(ipNet, &geoLocation) + if err != nil { + return "", err + } + if lang == "zh" { + return geoLocation.Country.Zh + " " + geoLocation.Province.Zh, nil + } + return geoLocation.Country.En + " " + geoLocation.Province.En, nil +} diff --git a/core/utils/mfa/mfa.go b/core/utils/mfa/mfa.go new file mode 100644 index 0000000..cdd09cb --- /dev/null +++ b/core/utils/mfa/mfa.go @@ -0,0 +1,45 @@ +package mfa + +import ( + "bytes" + "encoding/base64" + "strconv" + "time" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/skip2/go-qrcode" + "github.com/xlzd/gotp" +) + +const secretLength = 16 + +type Otp struct { + Secret string `json:"secret"` + QrImage string `json:"qrImage"` +} + +func GetOtp(username, title string, interval int) (otp Otp, err error) { + secret := gotp.RandomSecret(secretLength) + otp.Secret = secret + totp := gotp.NewTOTP(secret, 6, interval, nil) + uri := totp.ProvisioningUri(username, title) + subImg, err := qrcode.Encode(uri, qrcode.Medium, 256) + dist := make([]byte, 3000) + base64.StdEncoding.Encode(dist, subImg) + index := bytes.IndexByte(dist, 0) + baseImage := dist[0:index] + otp.QrImage = "data:image/png;base64," + string(baseImage) + return +} + +func ValidCode(code, intervalStr, secret string) bool { + interval, err := strconv.Atoi(intervalStr) + if err != nil { + global.LOG.Errorf("type conversion failed, err: %v", err) + return false + } + totp := gotp.NewTOTP(secret, 6, interval, nil) + now := time.Now().Unix() + prevTime := now - int64(interval) + return totp.Verify(code, now) || totp.Verify(code, prevTime) +} diff --git a/core/utils/passkey/passkey_record.go b/core/utils/passkey/passkey_record.go new file mode 100644 index 0000000..5a668d4 --- /dev/null +++ b/core/utils/passkey/passkey_record.go @@ -0,0 +1,12 @@ +package passkey + +import "github.com/go-webauthn/webauthn/webauthn" + +type PasskeyCredentialRecord struct { + ID string `json:"id"` + Name string `json:"name"` + CreatedAt string `json:"createdAt"` + LastUsedAt string `json:"lastUsedAt"` + FlagsValue uint8 `json:"flagsValue"` + Credential webauthn.Credential `json:"credential"` +} diff --git a/core/utils/passkey/passkey_store.go b/core/utils/passkey/passkey_store.go new file mode 100644 index 0000000..0e97485 --- /dev/null +++ b/core/utils/passkey/passkey_store.go @@ -0,0 +1,86 @@ +package passkey + +import ( + "crypto/rand" + "encoding/base64" + "sync" + "time" + + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/go-webauthn/webauthn/webauthn" +) + +const ( + PasskeyUserIDSettingKey = "PasskeyUserID" + PasskeyCredentialSettingKey = "PasskeyCredentials" + PasskeyMaxCredentials = 5 + PasskeySessionTTL = 5 * time.Minute + PasskeySessionKindLogin = "login" + PasskeySessionKindRegister = "register" + PasskeyCredentialNameDefault = "Passkey" +) + +var passkeySessions = newPasskeySessionStore() + +func GetPasskeySessionStore() *passkeySessionStore { + return passkeySessions +} + +type passkeySession struct { + Kind string + Name string + Session webauthn.SessionData + ExpiresAt time.Time +} + +type passkeySessionStore struct { + mu sync.Mutex + items map[string]passkeySession +} + +func newPasskeySessionStore() *passkeySessionStore { + return &passkeySessionStore{items: make(map[string]passkeySession)} +} + +func (s *passkeySessionStore) Set(kind, name string, session webauthn.SessionData) string { + s.mu.Lock() + defer s.mu.Unlock() + + sessionID := generatePasskeySessionID() + s.items[sessionID] = passkeySession{ + Kind: kind, + Name: name, + Session: session, + ExpiresAt: time.Now().Add(PasskeySessionTTL), + } + return sessionID +} + +func (s *passkeySessionStore) Get(sessionID string) (passkeySession, bool) { + s.mu.Lock() + defer s.mu.Unlock() + + item, ok := s.items[sessionID] + if !ok { + return passkeySession{}, false + } + if time.Now().After(item.ExpiresAt) { + delete(s.items, sessionID) + return passkeySession{}, false + } + return item, true +} + +func (s *passkeySessionStore) Delete(sessionID string) { + s.mu.Lock() + defer s.mu.Unlock() + delete(s.items, sessionID) +} + +func generatePasskeySessionID() string { + raw := make([]byte, 32) + if _, err := rand.Read(raw); err != nil { + return common.RandStr(32) + } + return base64.RawURLEncoding.EncodeToString(raw) +} diff --git a/core/utils/passkey/passkey_user.go b/core/utils/passkey/passkey_user.go new file mode 100644 index 0000000..e98bc7f --- /dev/null +++ b/core/utils/passkey/passkey_user.go @@ -0,0 +1,26 @@ +package passkey + +import "github.com/go-webauthn/webauthn/webauthn" + +type PasskeyUser struct { + ID []byte + Name string + DisplayName string + Credentials []webauthn.Credential +} + +func (u PasskeyUser) WebAuthnID() []byte { + return u.ID +} + +func (u PasskeyUser) WebAuthnName() string { + return u.Name +} + +func (u PasskeyUser) WebAuthnDisplayName() string { + return u.DisplayName +} + +func (u PasskeyUser) WebAuthnCredentials() []webauthn.Credential { + return u.Credentials +} diff --git a/core/utils/req_helper/proxy_local/req_to_local.go b/core/utils/req_helper/proxy_local/req_to_local.go new file mode 100644 index 0000000..606428e --- /dev/null +++ b/core/utils/req_helper/proxy_local/req_to_local.go @@ -0,0 +1,81 @@ +package proxy_local + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "github.com/gin-gonic/gin" + "io" + "net" + "net/http" + "net/url" + "os" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/dto" + "github.com/1Panel-dev/1Panel/core/i18n" +) + +func NewLocalClient(reqUrl, reqMethod string, body io.Reader, ctx *gin.Context) (interface{}, error) { + sockPath := "/etc/1panel/agent.sock" + if _, err := os.Stat(sockPath); err != nil { + return nil, fmt.Errorf("no such agent.sock find in localhost, err: %v", err) + } + dialUnix := func() (conn net.Conn, err error) { + return net.Dial("unix", sockPath) + } + transport := &http.Transport{ + DialContext: func(ctx context.Context, network, addr string) (net.Conn, error) { + return dialUnix() + }, + } + client := &http.Client{ + Transport: transport, + } + defer client.CloseIdleConnections() + parsedURL, err := url.Parse("http://unix") + if err != nil { + return nil, fmt.Errorf("handle url Parse failed, err: %v \n", err) + } + rURL := &url.URL{ + Scheme: "http", + Path: reqUrl, + Host: parsedURL.Host, + } + + req, err := http.NewRequest(reqMethod, rURL.String(), body) + if err != nil { + return nil, fmt.Errorf("creating request failed, err: %v", err) + } + if ctx != nil { + for key, values := range ctx.Request.Header { + for _, value := range values { + req.Header.Add(key, value) + } + } + } + + resp, err := client.Do(req) + if err != nil { + return nil, fmt.Errorf("client do request failed, err: %v", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, fmt.Errorf("do request failed, err: %v", resp.Status) + } + bodyByte, err := io.ReadAll(resp.Body) + if err != nil { + return nil, fmt.Errorf("read resp body from request failed, err: %v", err) + } + var respJson dto.Response + if err := json.Unmarshal(bodyByte, &respJson); err != nil { + return nil, fmt.Errorf("json umarshal resp data failed, err: %v", err) + } + if respJson.Code != http.StatusOK { + return nil, errors.New(strings.ReplaceAll(respJson.Message, i18n.Get("ErrInternalServerKey"), "")) + } + + return respJson.Data, nil +} diff --git a/core/utils/req_helper/requset.go b/core/utils/req_helper/requset.go new file mode 100644 index 0000000..776093d --- /dev/null +++ b/core/utils/req_helper/requset.go @@ -0,0 +1,113 @@ +package req_helper + +import ( + "context" + "crypto/tls" + "errors" + "io" + "net" + "net/http" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/buserr" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/xpack" +) + +func HandleRequest(url, method string, timeout int) (int, []byte, error) { + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DialContext: (&net.Dialer{ + Timeout: 60 * time.Second, + KeepAlive: 60 * time.Second, + }).DialContext, + TLSHandshakeTimeout: 5 * time.Second, + ResponseHeaderTimeout: 10 * time.Second, + IdleConnTimeout: 15 * time.Second, + } + return handleRequestWithTransport(url, method, transport, timeout) +} + +func HandleRequestWithProxy(url, method string, timeout int) (int, []byte, error) { + transport := xpack.LoadRequestTransport() + return handleRequestWithTransport(url, method, transport, timeout) +} + +func handleRequestWithTransport(url, method string, transport *http.Transport, timeout int) (int, []byte, error) { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("handle request failed, error message: %v", r) + return + } + }() + + client := http.Client{Timeout: time.Duration(timeout) * time.Second, Transport: transport} + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second) + defer cancel() + request, err := http.NewRequestWithContext(ctx, method, url, nil) + if err != nil { + return 0, nil, err + } + request.Header.Set("Content-Type", "application/json") + resp, err := client.Do(request) + if err != nil { + return 0, nil, err + } + body, err := io.ReadAll(resp.Body) + if err != nil { + return 0, nil, err + } + defer resp.Body.Close() + + return resp.StatusCode, body, nil +} + +func HandleGet(url string) (*http.Response, error) { + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + DialContext: (&net.Dialer{ + Timeout: 60 * time.Second, + KeepAlive: 60 * time.Second, + }).DialContext, + TLSHandshakeTimeout: 5 * time.Second, + ResponseHeaderTimeout: 10 * time.Second, + IdleConnTimeout: 15 * time.Second, + } + return handleGetWithTransport(url, transport) +} + +func HandleGetWithProxy(url string) (*http.Response, error) { + transport := xpack.LoadRequestTransport() + return handleGetWithTransport(url, transport) +} + +func handleGetWithTransport(url string, transport *http.Transport) (*http.Response, error) { + client := &http.Client{ + Timeout: time.Second * 300, + } + client.Transport = transport + defer client.CloseIdleConnections() + + req, err := http.NewRequestWithContext(context.Background(), "GET", url, nil) + if err != nil { + return nil, buserr.WithMap("ErrCreateHttpClient", map[string]interface{}{"err": err.Error()}, err) + } + + resp, err := client.Do(req) + if err != nil { + if errors.Is(err, context.DeadlineExceeded) { + return nil, buserr.WithMap("ErrHttpReqTimeOut", map[string]interface{}{"err": err.Error()}, err) + } else { + if strings.Contains(err.Error(), "no such host") { + return nil, buserr.WithErr("ErrNoSuchHost", errors.New("no such host")) + } + return nil, buserr.WithMap("ErrHttpReqFailed", map[string]interface{}{"err": err.Error()}, err) + } + } + if resp.StatusCode == 404 { + return nil, buserr.WithErr("ErrHttpReqNotFound", errors.New("no such resource")) + } + + return resp, nil +} diff --git a/core/utils/security/security.go b/core/utils/security/security.go new file mode 100644 index 0000000..c5530ac --- /dev/null +++ b/core/utils/security/security.go @@ -0,0 +1,202 @@ +package security + +import ( + "encoding/base64" + "fmt" + "net/http" + "regexp" + "strconv" + "strings" + + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/app/service" + "github.com/1Panel-dev/1Panel/core/cmd/server/res" + "github.com/1Panel-dev/1Panel/core/cmd/server/web" + "github.com/1Panel-dev/1Panel/core/constant" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/common" + "github.com/gin-gonic/gin" +) + +func HandleNotRoute(c *gin.Context) bool { + if !checkBindDomain(c) { + HandleNotSecurity(c, "err_domain") + return false + } + if !checkIPLimit(c) { + HandleNotSecurity(c, "err_ip_limit") + return false + } + if checkFrontendPath(c) { + ToIndexHtml(c) + return false + } + if isEntrancePath(c) { + ToIndexHtml(c) + return false + } + return true +} + +func CheckSecurity(c *gin.Context) bool { + if !checkEntrance(c) && !checkSession(c) { + HandleNotSecurity(c, "") + return false + } + if !checkBindDomain(c) { + HandleNotSecurity(c, "err_domain") + return false + } + if !checkIPLimit(c) { + HandleNotSecurity(c, "err_ip_limit") + return false + } + return true +} + +func ToIndexHtml(c *gin.Context) { + c.Writer.Header().Set("Content-Type", "text/html; charset=utf-8") + c.Writer.WriteHeader(http.StatusOK) + data, err := web.IndexHtml.ReadFile("index.html") + if err != nil { + c.String(http.StatusInternalServerError, "index.html not found") + return + } + _, _ = c.Writer.Write(data) + c.Writer.Flush() +} + +func isEntrancePath(c *gin.Context) bool { + entrance := service.NewIAuthService().GetSecurityEntrance() + if entrance != "" && strings.TrimSuffix(c.Request.URL.Path, "/") == "/"+entrance { + return true + } + return false +} + +func checkEntrance(c *gin.Context) bool { + authService := service.NewIAuthService() + entrance := authService.GetSecurityEntrance() + if entrance == "" { + return true + } + + cookieValue, err := c.Cookie("SecurityEntrance") + if err != nil { + return false + } + entranceValue, err := base64.StdEncoding.DecodeString(cookieValue) + if err != nil { + return false + } + return string(entranceValue) == entrance +} + +func HandleNotSecurity(c *gin.Context, resType string) { + resPage, err := service.NewIAuthService().GetResponsePage() + if err != nil { + c.String(http.StatusInternalServerError, "Internal Server Error") + return + } + if resPage == "444" { + CloseDirectly(c) + return + } + + file := fmt.Sprintf("html/%s.html", resPage) + if resPage == "200" && resType != "" { + file = fmt.Sprintf("html/200_%s.html", resType) + } + data, err := res.ErrorMsg.ReadFile(file) + if err != nil { + c.String(http.StatusInternalServerError, "Internal Server Error") + return + } + statusCode, err := strconv.Atoi(resPage) + if err != nil { + c.String(http.StatusInternalServerError, "Internal Server Error") + return + } + c.Data(statusCode, "text/html; charset=utf-8", data) +} + +func isFrontendPath(c *gin.Context) bool { + reqUri := strings.TrimSuffix(c.Request.URL.Path, "/") + if _, ok := constant.WebUrlMap[reqUri]; ok { + return true + } + for _, route := range constant.DynamicRoutes { + if match, _ := regexp.MatchString(route, reqUri); match { + return true + } + } + return false +} + +func checkFrontendPath(c *gin.Context) bool { + if !isFrontendPath(c) { + return false + } + authService := service.NewIAuthService() + if authService.GetSecurityEntrance() != "" { + return authService.IsLogin(c) + } + return true +} + +func checkBindDomain(c *gin.Context) bool { + settingRepo := repo.NewISettingRepo() + status, _ := settingRepo.Get(repo.WithByKey("BindDomain")) + if len(status.Value) == 0 { + return true + } + domains := c.Request.Host + parts := strings.Split(c.Request.Host, ":") + if len(parts) > 0 { + domains = parts[0] + } + return domains == status.Value +} + +func checkIPLimit(c *gin.Context) bool { + settingRepo := repo.NewISettingRepo() + status, _ := settingRepo.Get(repo.WithByKey("AllowIPs")) + if len(status.Value) == 0 { + return true + } + clientIP := common.GetRealClientIP(c) + if common.IsPrivateIP(clientIP) { + return true + } + + for _, ip := range strings.Split(status.Value, ",") { + if len(ip) == 0 { + continue + } + if ip == clientIP || (strings.Contains(ip, "/") && common.CheckIpInCidr(ip, clientIP)) { + return true + } + } + return false +} + +func checkSession(c *gin.Context) bool { + _, err := global.SESSION.Get(c) + return err == nil +} + +func CloseDirectly(c *gin.Context) { + hijacker, ok := c.Writer.(http.Hijacker) + if !ok { + c.AbortWithStatus(http.StatusForbidden) + return + } + conn, _, err := hijacker.Hijack() + if err != nil { + c.AbortWithStatus(http.StatusForbidden) + return + } + + conn.Close() + c.Abort() +} diff --git a/core/utils/ssh/http.go b/core/utils/ssh/http.go new file mode 100644 index 0000000..b64b66b --- /dev/null +++ b/core/utils/ssh/http.go @@ -0,0 +1,81 @@ +package ssh + +import ( + "bufio" + "crypto/tls" + "encoding/base64" + "fmt" + "net" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/global" +) + +type HTTPProxyDialer struct { + Type string + URL string + User string + Password string +} + +func HTTPDial(dialer HTTPProxyDialer, network, addr string) (net.Conn, error) { + var conn net.Conn + var err error + + global.LOG.Debugf("Dialing HTTP proxy %s for %s", dialer.URL, addr) + dialer.URL = strings.TrimPrefix(dialer.URL, dialer.Type+"://") + if dialer.Type == "https" { + conn, err = tls.DialWithDialer( + &net.Dialer{Timeout: 30 * time.Second}, + network, + dialer.URL, + &tls.Config{InsecureSkipVerify: true}, + ) + } else { + conn, err = net.DialTimeout(network, dialer.URL, 30*time.Second) + } + if err != nil { + return nil, err + } + conn.SetDeadline(time.Now().Add(30 * time.Second)) + connectReq := fmt.Sprintf("CONNECT %s HTTP/1.1\r\n", addr) + connectReq += fmt.Sprintf("Host: %s\r\n", addr) + connectReq += "User-Agent: Go-ssh-client/1.0\r\n" + + if dialer.User != "" { + auth := base64.StdEncoding.EncodeToString( + []byte(dialer.User + ":" + dialer.Password), + ) + connectReq += fmt.Sprintf("Proxy-Authorization: Basic %s\r\n", auth) + } + connectReq += "Connection: keep-alive\r\n\r\n" + if _, err := conn.Write([]byte(connectReq)); err != nil { + conn.Close() + return nil, err + } + reader := bufio.NewReader(conn) + response, err := reader.ReadString('\n') + if err != nil { + conn.Close() + return nil, err + } + if !strings.HasPrefix(response, "HTTP/1.1 200") && + !strings.HasPrefix(response, "HTTP/1.0 200") { + conn.Close() + return nil, fmt.Errorf("proxy connection failed: %s", strings.TrimSpace(response)) + } + for { + line, err := reader.ReadString('\n') + if err != nil { + conn.Close() + return nil, err + } + if line == "\r\n" || line == "\n" { + break + } + } + conn.SetDeadline(time.Time{}) + + return conn, nil +} diff --git a/core/utils/ssh/ssh.go b/core/utils/ssh/ssh.go new file mode 100644 index 0000000..197b974 --- /dev/null +++ b/core/utils/ssh/ssh.go @@ -0,0 +1,321 @@ +package ssh + +import ( + "errors" + "fmt" + "net" + "path" + "strings" + "time" + + "github.com/1Panel-dev/1Panel/core/app/repo" + "github.com/1Panel-dev/1Panel/core/global" + "github.com/1Panel-dev/1Panel/core/utils/encrypt" + gossh "golang.org/x/crypto/ssh" + "golang.org/x/net/proxy" +) + +type ConnInfo struct { + User string `json:"user"` + Addr string `json:"addr"` + Port int `json:"port"` + AuthMode string `json:"authMode"` + Password string `json:"password"` + PrivateKey []byte `json:"privateKey"` + PassPhrase []byte `json:"passPhrase"` + + UseProxy bool `json:"useProxy"` + DialTimeOut time.Duration `json:"dialTimeOut"` +} + +type SSHClient struct { + Client *gossh.Client `json:"client"` + SudoItem string `json:"sudoItem"` +} + +func NewClient(c ConnInfo) (*SSHClient, error) { + config := &gossh.ClientConfig{} + config.SetDefaults() + addr := net.JoinHostPort(c.Addr, fmt.Sprintf("%d", c.Port)) + config.User = c.User + if c.AuthMode == "password" { + config.Auth = []gossh.AuthMethod{gossh.Password(c.Password)} + } else { + signer, err := makePrivateKeySigner(c.PrivateKey, c.PassPhrase) + if err != nil { + return nil, err + } + config.Auth = []gossh.AuthMethod{gossh.PublicKeys(signer)} + } + if c.DialTimeOut == 0 { + c.DialTimeOut = 5 * time.Second + } + config.Timeout = c.DialTimeOut + + config.HostKeyCallback = gossh.InsecureIgnoreHostKey() + proto := "tcp" + if strings.Contains(c.Addr, ":") { + proto = "tcp6" + } + client, err := DialWithTimeout(proto, addr, c.UseProxy, config) + if nil != err { + return nil, err + } + sshClient := &SSHClient{Client: client} + if c.User == "root" { + return sshClient, nil + } + if _, err := sshClient.Run("sudo -n ls"); err == nil { + sshClient.SudoItem = "sudo" + } + return sshClient, nil +} + +func (c *SSHClient) Run(shell string) (string, error) { + shell = c.SudoItem + " " + shell + shell = strings.ReplaceAll(shell, " && ", fmt.Sprintf(" && %s ", c.SudoItem)) + session, err := c.Client.NewSession() + if err != nil { + return "", err + } + defer session.Close() + buf, err := session.CombinedOutput(shell) + + return string(buf), err +} + +func (c *SSHClient) CpFileWithCheck(src, dst string) error { + localMd5, err := c.Runf("md5sum %s | awk '{print $1}'", src) + if err != nil { + global.LOG.Debugf("load md5sum with src for %s failed, std: %s, err: %v", path.Base(src), localMd5, err) + localMd5 = "" + } + for i := 0; i < 3; i++ { + std, cpErr := c.Runf("cp %s %s", src, dst) + if err != nil { + err = fmt.Errorf("cp file %s failed, std: %s, err: %v", src, std, cpErr) + continue + } + if len(strings.TrimSpace(localMd5)) == 0 { + return nil + } + remoteMd5, errDst := c.Runf("md5sum %s | awk '{print $1}'", dst) + if errDst != nil { + global.LOG.Debugf("load md5sum with dst for %s failed, std: %s, err: %v", path.Base(src), remoteMd5, errDst) + return nil + } + if strings.TrimSpace(localMd5) == strings.TrimSpace(remoteMd5) { + return nil + } + err = errors.New("cp file failed, file is not match!") + } + + return err +} + +func (c *SSHClient) SudoHandleCmd() string { + if _, err := c.Run("sudo -n ls"); err == nil { + return "sudo " + } + return "" +} + +func (c *SSHClient) IsRoot(user string) bool { + if user == "root" { + return true + } + _, err := c.Run("sudo -n true") + return err == nil +} + +func (c *SSHClient) Runf(shell string, args ...interface{}) (string, error) { + shell = c.SudoItem + " " + shell + shell = strings.ReplaceAll(shell, " && ", fmt.Sprintf(" && %s ", c.SudoItem)) + session, err := c.Client.NewSession() + if err != nil { + return "", err + } + defer session.Close() + buf, err := session.CombinedOutput(fmt.Sprintf(shell, args...)) + + return string(buf), err +} + +func (c *SSHClient) Close() { + if c.Client != nil { + _ = c.Client.Close() + } +} + +func makePrivateKeySigner(privateKey []byte, passPhrase []byte) (gossh.Signer, error) { + if len(passPhrase) != 0 { + return gossh.ParsePrivateKeyWithPassphrase(privateKey, passPhrase) + } + return gossh.ParsePrivateKey(privateKey) +} + +func (c *SSHClient) RunWithStreamOutput(command string, outputCallback func(string)) error { + session, err := c.Client.NewSession() + if err != nil { + return fmt.Errorf("failed to create SSH session: %w", err) + } + defer session.Close() + + stdout, err := session.StdoutPipe() + if err != nil { + return fmt.Errorf("failed to set up stdout pipe: %w", err) + } + + stderr, err := session.StderrPipe() + if err != nil { + return fmt.Errorf("failed to set up stderr pipe: %w", err) + } + + if err := session.Start(command); err != nil { + return fmt.Errorf("failed to start command: %w", err) + } + + stdoutCh := make(chan string, 100) + stderrCh := make(chan string, 100) + doneCh := make(chan struct{}) + + go func() { + buffer := make([]byte, 1024) + for { + n, err := stdout.Read(buffer) + if err != nil { + close(stdoutCh) + return + } + if n > 0 { + stdoutCh <- string(buffer[:n]) + } + } + }() + + go func() { + buffer := make([]byte, 1024) + for { + n, err := stderr.Read(buffer) + if err != nil { + close(stderrCh) + return + } + if n > 0 { + stderrCh <- string(buffer[:n]) + } + } + }() + + go func() { + for { + select { + case stdoutOutput, ok := <-stdoutCh: + if !ok { + stdoutCh = nil + if stderrCh == nil { + close(doneCh) + return + } + continue + } + if outputCallback != nil { + outputCallback(stdoutOutput) + } + + case stderrOutput, ok := <-stderrCh: + if !ok { + stderrCh = nil + if stdoutCh == nil { + close(doneCh) + return + } + continue + } + if outputCallback != nil { + outputCallback(stderrOutput) + } + } + } + }() + + err = session.Wait() + <-doneCh + + return err +} + +func DialWithTimeout(network, addr string, useProxy bool, config *gossh.ClientConfig) (*gossh.Client, error) { + var conn net.Conn + var err error + if useProxy { + conn, err = loadSSHConnByProxy(network, addr, config.Timeout) + } else { + conn, err = net.DialTimeout(network, addr, config.Timeout) + } + if err != nil { + return nil, err + } + _ = conn.SetDeadline(time.Now().Add(config.Timeout)) + c, chans, reqs, err := gossh.NewClientConn(conn, addr, config) + if err != nil { + return nil, err + } + if err := conn.SetDeadline(time.Time{}); err != nil { + conn.Close() + return nil, fmt.Errorf("clear deadline failed: %v", err) + } + return gossh.NewClient(c, chans, reqs), nil +} + +func loadSSHConnByProxy(network, addr string, timeout time.Duration) (net.Conn, error) { + settingRepo := repo.NewISettingRepo() + proxyType, err := settingRepo.Get(repo.WithByKey("ProxyType")) + if err != nil { + return nil, fmt.Errorf("get proxy type from db failed, err: %v", err) + } + if len(proxyType.Value) == 0 { + return nil, fmt.Errorf("get proxy type from db failed, err: %v", err) + } + proxyUrl, _ := settingRepo.Get(repo.WithByKey("ProxyUrl")) + port, _ := settingRepo.Get(repo.WithByKey("ProxyPort")) + user, _ := settingRepo.Get(repo.WithByKey("ProxyUser")) + passwd, _ := settingRepo.Get(repo.WithByKey("ProxyPasswd")) + + pass, _ := encrypt.StringDecrypt(passwd.Value) + proxyItem := fmt.Sprintf("%s:%s", proxyUrl.Value, port.Value) + switch proxyType.Value { + case "http", "https": + item := HTTPProxyDialer{ + Type: proxyType.Value, + URL: proxyItem, + User: user.Value, + Password: pass, + } + return HTTPDial(item, network, addr) + case "socks5": + var auth *proxy.Auth + if len(user.Value) == 0 { + auth = nil + } else { + auth = &proxy.Auth{ + User: user.Value, + Password: pass, + } + } + dialer, err := proxy.SOCKS5("tcp", proxyItem, auth, &net.Dialer{ + Timeout: 30 * time.Second, + KeepAlive: 30 * time.Second, + }) + if err != nil { + return nil, fmt.Errorf("new socks5 proxy failed, err: %v", err) + } + return dialer.Dial(network, addr) + default: + conn, err := net.DialTimeout(network, addr, timeout) + if err != nil { + return nil, err + } + return conn, nil + } +} diff --git a/core/utils/terminal/local_cmd.go b/core/utils/terminal/local_cmd.go new file mode 100644 index 0000000..0ef1ca3 --- /dev/null +++ b/core/utils/terminal/local_cmd.go @@ -0,0 +1,103 @@ +package terminal + +import ( + "os" + "os/exec" + "syscall" + "time" + "unsafe" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/creack/pty" + "github.com/pkg/errors" +) + +const ( + DefaultCloseSignal = syscall.SIGINT + DefaultCloseTimeout = 10 * time.Second +) + +type LocalCommand struct { + closeSignal syscall.Signal + closeTimeout time.Duration + + cmd *exec.Cmd + pty *os.File +} + +func NewCommand(script string) (*LocalCommand, error) { + cmd := exec.Command("bash") + if term := os.Getenv("TERM"); term != "" { + cmd.Env = append(os.Environ(), "TERM="+term) + } else { + cmd.Env = append(os.Environ(), "TERM=xterm") + } + cmd.Env = append(cmd.Env, "INIT_SCRIPT="+script) + pty, err := pty.Start(cmd) + if err != nil { + return nil, errors.Wrapf(err, "failed to start command") + } + if len(script) != 0 { + time.Sleep(100 * time.Millisecond) + _, _ = pty.Write([]byte("bash -c \"$INIT_SCRIPT\"\n")) + } + + lcmd := &LocalCommand{ + closeSignal: DefaultCloseSignal, + closeTimeout: DefaultCloseTimeout, + + cmd: cmd, + pty: pty, + } + + return lcmd, nil +} + +func (lcmd *LocalCommand) Read(p []byte) (n int, err error) { + return lcmd.pty.Read(p) +} + +func (lcmd *LocalCommand) Write(p []byte) (n int, err error) { + return lcmd.pty.Write(p) +} + +func (lcmd *LocalCommand) Close() error { + if lcmd.cmd != nil && lcmd.cmd.Process != nil { + _ = lcmd.cmd.Process.Kill() + } + _ = lcmd.pty.Close() + return nil +} + +func (lcmd *LocalCommand) ResizeTerminal(width int, height int) error { + window := struct { + row uint16 + col uint16 + x uint16 + y uint16 + }{ + uint16(height), + uint16(width), + 0, + 0, + } + _, _, errno := syscall.Syscall( + syscall.SYS_IOCTL, + lcmd.pty.Fd(), + syscall.TIOCSWINSZ, + uintptr(unsafe.Pointer(&window)), + ) + if errno != 0 { + return errno + } else { + return nil + } +} + +func (lcmd *LocalCommand) Wait(quitChan chan bool) { + if err := lcmd.cmd.Wait(); err != nil { + global.LOG.Errorf("ssh session wait failed, err: %v", err) + setQuit(quitChan) + } + setQuit(quitChan) +} diff --git a/core/utils/terminal/ws_local_session.go b/core/utils/terminal/ws_local_session.go new file mode 100644 index 0000000..3135375 --- /dev/null +++ b/core/utils/terminal/ws_local_session.go @@ -0,0 +1,128 @@ +package terminal + +import ( + "encoding/base64" + "encoding/json" + "sync" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/gorilla/websocket" + "github.com/pkg/errors" +) + +type LocalWsSession struct { + slave *LocalCommand + wsConn *websocket.Conn + + allowCtrlC bool + writeMutex sync.Mutex +} + +func NewLocalWsSession(cols, rows int, wsConn *websocket.Conn, slave *LocalCommand, allowCtrlC bool) (*LocalWsSession, error) { + if err := slave.ResizeTerminal(cols, rows); err != nil { + global.LOG.Errorf("ssh pty change windows size failed, err: %v", err) + } + + return &LocalWsSession{ + slave: slave, + wsConn: wsConn, + + allowCtrlC: allowCtrlC, + }, nil +} + +func (sws *LocalWsSession) Start(quitChan chan bool) { + go sws.handleSlaveEvent(quitChan) + go sws.receiveWsMsg(quitChan) +} + +func (sws *LocalWsSession) handleSlaveEvent(exitCh chan bool) { + defer setQuit(exitCh) + defer global.LOG.Debug("thread of handle slave event has exited now") + + buffer := make([]byte, 1024) + for { + select { + case <-exitCh: + return + default: + n, _ := sws.slave.Read(buffer) + _ = sws.masterWrite(buffer[:n]) + } + } +} + +func (sws *LocalWsSession) masterWrite(data []byte) error { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("A panic occurred during write ws message to master, error message: %v", r) + } + }() + sws.writeMutex.Lock() + defer sws.writeMutex.Unlock() + wsData, err := json.Marshal(WsMsg{ + Type: WsMsgCmd, + Data: base64.StdEncoding.EncodeToString(data), + }) + if err != nil { + return errors.Wrapf(err, "failed to encoding to json") + } + err = sws.wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + return errors.Wrapf(err, "failed to write to master") + } + return nil +} + +func (sws *LocalWsSession) receiveWsMsg(exitCh chan bool) { + defer func() { + if r := recover(); r != nil { + setQuit(exitCh) + global.LOG.Errorf("A panic occurred during receive ws message, error message: %v", r) + } + }() + wsConn := sws.wsConn + defer setQuit(exitCh) + defer global.LOG.Debug("thread of receive ws msg has exited now") + for { + select { + case <-exitCh: + return + default: + _, wsData, err := wsConn.ReadMessage() + if err != nil { + global.LOG.Errorf("reading webSocket message failed, err: %v", err) + return + } + msgObj := WsMsg{} + _ = json.Unmarshal(wsData, &msgObj) + switch msgObj.Type { + case WsMsgResize: + if msgObj.Cols > 0 && msgObj.Rows > 0 { + if err := sws.slave.ResizeTerminal(msgObj.Cols, msgObj.Rows); err != nil { + global.LOG.Errorf("ssh pty change windows size failed, err: %v", err) + } + } + case WsMsgCmd: + decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data) + if err != nil { + global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err) + } + if string(decodeBytes) != "\x03" || sws.allowCtrlC { + sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes) + } + case WsMsgHeartbeat: + err = wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err) + } + } + } + } +} + +func (sws *LocalWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) { + if _, err := sws.slave.Write(cmdBytes); err != nil { + global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err) + } +} diff --git a/core/utils/terminal/ws_session.go b/core/utils/terminal/ws_session.go new file mode 100644 index 0000000..03030d8 --- /dev/null +++ b/core/utils/terminal/ws_session.go @@ -0,0 +1,223 @@ +package terminal + +import ( + "bytes" + "encoding/base64" + "encoding/json" + "io" + "sync" + "time" + + "github.com/1Panel-dev/1Panel/core/global" + "github.com/gorilla/websocket" + "golang.org/x/crypto/ssh" +) + +type safeBuffer struct { + buffer bytes.Buffer + mu sync.Mutex +} + +func (w *safeBuffer) Write(p []byte) (int, error) { + w.mu.Lock() + defer w.mu.Unlock() + return w.buffer.Write(p) +} +func (w *safeBuffer) Bytes() []byte { + w.mu.Lock() + defer w.mu.Unlock() + return w.buffer.Bytes() +} +func (w *safeBuffer) Reset() { + w.mu.Lock() + defer w.mu.Unlock() + w.buffer.Reset() +} + +const ( + WsMsgCmd = "cmd" + WsMsgResize = "resize" + WsMsgHeartbeat = "heartbeat" +) + +type WsMsg struct { + Type string `json:"type"` + Data string `json:"data,omitempty"` // WsMsgCmd + Cols int `json:"cols,omitempty"` // WsMsgResize + Rows int `json:"rows,omitempty"` // WsMsgResize + Timestamp int `json:"timestamp,omitempty"` // WsMsgHeartbeat +} + +type LogicSshWsSession struct { + stdinPipe io.WriteCloser + comboOutput *safeBuffer + logBuff *safeBuffer + inputFilterBuff *safeBuffer + session *ssh.Session + wsConn *websocket.Conn + isAdmin bool + IsFlagged bool +} + +func NewLogicSshWsSession(cols, rows int, sshClient *ssh.Client, wsConn *websocket.Conn, initCmd string) (*LogicSshWsSession, error) { + sshSession, err := sshClient.NewSession() + if err != nil { + return nil, err + } + + stdinP, err := sshSession.StdinPipe() + if err != nil { + return nil, err + } + + comboWriter := new(safeBuffer) + logBuf := new(safeBuffer) + inputBuf := new(safeBuffer) + sshSession.Stdout = comboWriter + sshSession.Stderr = comboWriter + + modes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + if err := sshSession.RequestPty("xterm", rows, cols, modes); err != nil { + return nil, err + } + if err := sshSession.Shell(); err != nil { + return nil, err + } + if len(initCmd) != 0 { + time.Sleep(100 * time.Millisecond) + _, _ = stdinP.Write([]byte(" clear &&" + initCmd + "\n")) + } + return &LogicSshWsSession{ + stdinPipe: stdinP, + comboOutput: comboWriter, + logBuff: logBuf, + inputFilterBuff: inputBuf, + session: sshSession, + wsConn: wsConn, + isAdmin: true, + IsFlagged: false, + }, nil +} + +func (sws *LogicSshWsSession) Close() { + if sws.session != nil { + sws.session.Close() + } + if sws.logBuff != nil { + sws.logBuff = nil + } + if sws.comboOutput != nil { + sws.comboOutput = nil + } +} + +func (sws *LogicSshWsSession) Start(quitChan chan bool) { + go sws.receiveWsMsg(quitChan) + go sws.sendComboOutput(quitChan) +} + +func (sws *LogicSshWsSession) receiveWsMsg(exitCh chan bool) { + defer func() { + if r := recover(); r != nil { + global.LOG.Errorf("[A panic occurred during receive ws message, error message: %v", r) + } + }() + wsConn := sws.wsConn + defer setQuit(exitCh) + for { + select { + case <-exitCh: + return + default: + _, wsData, err := wsConn.ReadMessage() + if err != nil { + return + } + msgObj := WsMsg{} + _ = json.Unmarshal(wsData, &msgObj) + switch msgObj.Type { + case WsMsgResize: + if msgObj.Cols > 0 && msgObj.Rows > 0 { + if err := sws.session.WindowChange(msgObj.Rows, msgObj.Cols); err != nil { + global.LOG.Errorf("ssh pty change windows size failed, err: %v", err) + } + } + case WsMsgCmd: + decodeBytes, err := base64.StdEncoding.DecodeString(msgObj.Data) + if err != nil { + global.LOG.Errorf("websock cmd string base64 decoding failed, err: %v", err) + } + sws.sendWebsocketInputCommandToSshSessionStdinPipe(decodeBytes) + case WsMsgHeartbeat: + // 接收到心跳包后将心跳包原样返回,可以用于网络延迟检测等情况 + err = wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + global.LOG.Errorf("ssh sending heartbeat to webSocket failed, err: %v", err) + } + } + } + } +} + +func (sws *LogicSshWsSession) sendWebsocketInputCommandToSshSessionStdinPipe(cmdBytes []byte) { + if _, err := sws.stdinPipe.Write(cmdBytes); err != nil { + global.LOG.Errorf("ws cmd bytes write to ssh.stdin pipe failed, err: %v", err) + } +} + +func (sws *LogicSshWsSession) sendComboOutput(exitCh chan bool) { + wsConn := sws.wsConn + defer setQuit(exitCh) + + tick := time.NewTicker(time.Millisecond * time.Duration(60)) + defer tick.Stop() + for { + select { + case <-tick.C: + if sws.comboOutput == nil { + return + } + bs := sws.comboOutput.Bytes() + if len(bs) > 0 { + wsData, err := json.Marshal(WsMsg{ + Type: WsMsgCmd, + Data: base64.StdEncoding.EncodeToString(bs), + }) + if err != nil { + global.LOG.Errorf("encoding combo output to json failed, err: %v", err) + continue + } + err = wsConn.WriteMessage(websocket.TextMessage, wsData) + if err != nil { + global.LOG.Errorf("ssh sending combo output to webSocket failed, err: %v", err) + } + _, err = sws.logBuff.Write(bs) + if err != nil { + global.LOG.Errorf("combo output to log buffer failed, err: %v", err) + } + sws.comboOutput.buffer.Reset() + } + if string(bs) == string([]byte{13, 10, 108, 111, 103, 111, 117, 116, 13, 10}) { + sws.Close() + return + } + + case <-exitCh: + return + } + } +} + +func (sws *LogicSshWsSession) Wait(quitChan chan bool) { + if err := sws.session.Wait(); err != nil { + setQuit(quitChan) + } +} + +func setQuit(ch chan bool) { + ch <- true +} diff --git a/docker-compose-middleare.yml b/docker-compose-middleare.yml new file mode 100644 index 0000000..43da8fc --- /dev/null +++ b/docker-compose-middleare.yml @@ -0,0 +1,453 @@ +#version: '3' + +networks: + local: + external: true + name: local + +services: + portainer: + restart: always + image: portainer/portainer-ce:2.19.1 + container_name: framework_portainer + privileged: true + networks: + - local + ports: + - 19000:9000 + environment: + - MINIO_ROOT_USER=minioadmin + - MINIO_ROOT_PASSWORD=Admin@hd2019 + volumes: + - /data/portainer/data:/data + - /var/run/docker.sock:/var/run/docker.sock + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + nacos: + image: nacos/nacos-server:v2.4.3 + container_name: middleare_nacos + restart: always + privileged: true + networks: + - local + ports: + - 8848:8848 + - 9848:9848 + - 9849:9849 + environment: + - JVM_XMS=256m + - JVM_XMX=1G + - MODE=standalone + - PREFER_HOST_MODE=hostname + - SPRING_DATASOURCE_PLATFORM=mysql + - MYSQL_SERVICE_HOST=mysql + - MYSQL_SERVICE_PORT=3306 + - MYSQL_SERVICE_USER=root + - MYSQL_SERVICE_PASSWORD=ngskcloud0809 + - MYSQL_SERVICE_DB_NAME=nacos_config + - TIME_ZONE=Asia/Shanghai + - MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true + volumes: + - /data/nacos/logs:/home/nacos/logs + - /etc/timezone:/etc/timezone:ro + depends_on: + - mysql + emqx1.io: + image: emqx/emqx:5.2.1 + container_name: middleare_emqx + restart: always + #privileged: true + networks: + - local + ports: + - 18083:18083 + - 1883:1883 + volumes: + - /data/emqx/log:/opt/emqx/log + - /data/emqx/data:/opt/emqx/data + - /data/emqx/etc:/opt/emqx/etc + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + environment: + - "EMQX_NODE_NAME=emqx@emqx1.io" + nginx: + image: nginx:1.22 + container_name: middleare_nginx + restart: always + privileged: true + networks: + - local + ports: + - 80:80 + - 443:443 + volumes: + - /data/nginx/nginx.conf:/etc/nginx/nginx.conf + - /data/nginx/conf.d:/etc/nginx/conf.d + - /data/nginx/log:/var/log/nginx + - /data/nginx/html:/usr/share/nginx/html + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + redis: + restart: always + image: bitnami/redis:8.0.3 + container_name: middleare_redis + privileged: true + networks: + - local + ports: + - 6379:6379 + environment: + - REDIS_PASSWORD=ngsk0809 + volumes: + - /data/redis/data:/bitnami/redis/data + - /data/redis/redis.conf:/etc/redis/redis.conf + - /etc/localtime:/etc/localtime:ro + - /data/redis/run:/var/run + logging: + driver: "json-file" + options: + max-size: "100m" + max-file: "2" + sysctls: + - net.core.somaxconn=551 + command: redis-server /etc/redis/redis.conf + redis-img: + restart: always + image: redis:6.2.6 + container_name: middleare_redis_img + privileged: true + networks: + - local + ports: + - 6380:6379 + volumes: + - /data/redis-img/data:/data + - /data/redis-img/redis.conf:/etc/redis/redis.conf + - /etc/localtime:/etc/localtime:ro + command: redis-server /etc/redis/redis.conf + minio: + restart: always + image: minio/minio:RELEASE.2022-10-08T20-11-00Z + container_name: middleare_minio + privileged: true + networks: + - local + ports: + - 9000:9000 + - 9001:9001 + environment: + - MINIO_ROOT_USER=minioadmin + - MINIO_ROOT_PASSWORD=Admin@hd2019 + volumes: + - /data/minio/data1:/data1 + - /data/minio/data2:/data2 + - /data/minio/data3:/data3 + - /data/minio/data4:/data4 + - /data/minio/conf:/root/.minio + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + command: server /data{1...4} --console-address ":9001" + mysql: + restart: always + image: mysql:8.0.27 + container_name: middleare_mysql + privileged: true + hostname: mysql + networks: + - local + ports: + - 3306:3306 + environment: + MYSQL_ROOT_PASSWORD: ngskcloud0809 + volumes: + - /data/mysql/conf/my.cnf:/etc/mysql/my.cnf + - /data/mysql/datadir:/var/lib/mysql + - /data/mysql/log/mysqld_slow.log:/var/log/mysqld_slow.log + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + nexus: + image: sonatype/nexus3:3.69.0-java11 + container_name: middleare_nexus + restart: always + #privileged: true + networks: + - local + ports: + - 8081:8081 + - 8082:8082 + - 8083:8083 + environment: + - JAVA_TOOL_OPTIONS=-Xms1g -Xmx2g -XX:+UseContainerSupport -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=512m -Duser.timezone=Asia/Shanghai + volumes: + - /data/nexus/nexus-data:/nexus-data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + code-server: + image: linuxserver/code-server:4.90.1 + container_name: middleare_code-server + restart: always + privileged: true + networks: + - local + ports: + - 8443:8443 + environment: + - PUID=0 + - PGID=0 + - TZ=Asia/Shanghai + - SUDO_PASSWORD=admin@Code2023 + - DEFAULT_WORKSPACE=/app/data + volumes: + - /data/code-server/config:/config + - /data/code-server/data:/app/data + - /data/code-server/profile:/etc/profile + - /data/maven/apache-maven-3.8.6:/usr/local/maven + - /data/jdk:/usr/local/jdk + - /data/maven/.m2/repository:/data/maven/.m2/repository + - /data/maven/.m2/settings.xml:/root/.m2/settings.xml + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + gitea: + image: gitea/gitea:1.19.3 + container_name: middleare_gitea + restart: always + privileged: true + networks: + - local + environment: + - USER_UID=1000 + - USER_GID=1000 + - GITEA__database__DB_TYPE=mysql + - GITEA__database__HOST=mysql:3306 + - GITEA__database__NAME=gitea + - GITEA__database__USER=root + - GITEA__database__PASSWD=ngskcloud0809 + volumes: + - /data/gitea:/data + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + ports: + - "30000:3000" + - "22222:22" + iotdb-service: + image: apache/iotdb:1.3.1-standalone + hostname: iotdb-service + container_name: iotdb-service + restart: always + ports: + - "6667:6667" + - "8181:8181" + - "5555:5555" + environment: + - cn_internal_address=iotdb-service + - cn_internal_port=10710 + - cn_consensus_port=10720 + - cn_seed_config_node=iotdb-service:10710 + - dn_rpc_address=iotdb-service + - dn_internal_address=iotdb-service + - dn_rpc_port=6667 + - dn_mpp_data_exchange_port=10740 + - dn_schema_region_consensus_port=10750 + - dn_data_region_consensus_port=10760 + - dn_seed_config_node=iotdb-service:10710 + - MAX_HEAP_SIZE=512M + - HEAP_NEWSIZE=256M + volumes: + - /data/iotdb/data:/iotdb/data + - /data/iotdb/logs:/iotdb/logs + networks: + - local + 1panel: + build: + context: . + dockerfile: Dockerfile + image: 1panel-dev-v2:local + container_name: middleare_1panel + restart: always + privileged: true + networks: + - local + ports: + - "9999:9999" + environment: + - TZ=Asia/Shanghai + volumes: + - /data/1panel:/opt/1panel + - /var/run/docker.sock:/var/run/docker.sock + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + depends_on: + - mysql + jenkins: + image: jenkins/jenkins:lts-jdk21 + container_name: jenkins + restart: always + privileged: true + networks: + - local + ports: + - "9090:8080" + - "50000:50000" + user: root + environment: + - JAVA_TOOL_OPTIONS=-Xms1g -Xmx2g -XX:MaxDirectMemorySize=512m -Duser.timezone=Asia/Shanghai + - JENKINS_UC=https://mirrors.tuna.tsinghua.edu.cn/jenkins/updates/current + volumes: + - /data/jenkins:/var/jenkins_home + - /data/docker-compose-server.yml:/var/jenkins_home/home/workspace/docker-compose.yml:ro + - /data/nginx/html:/data/frontend + - /data/maven:/data/maven + - /usr/bin/docker:/usr/bin/docker + - /var/run/docker.sock:/var/run/docker.sock + - /usr/bin/docker-compose:/usr/bin/docker-compose + - /etc/timezone:/etc/timezone:ro + - /etc/localtime:/etc/localtime:ro + - /usr/libexec/docker/cli-plugins:/usr/libexec/docker/cli-plugins + + graylog: + image: graylog/graylog:6.3 + container_name: graylog + volumes: + - /data/graylog-new/graylog/data:/usr/share/graylog/data/data + - /data/graylog-new/graylog/journal:/usr/share/graylog/data/journal + - /etc/localtime:/etc/localtime:ro + - /usr/share/zoneinfo/Asia/Shanghai:/etc/timezone:ro + - /data/graylog-new/graylog/config/graylog.conf:/usr/share/graylog/data/config/graylog.conf + environment: + - GRAYLOG_PASSWORD_SECRET=${GRAYLOG_PASSWORD_SECRET} + - GRAYLOG_ROOT_PASSWORD_SHA2=${GRAYLOG_ROOT_PASSWORD_SHA2} + - GRAYLOG_HTTP_BIND_ADDRESS=0.0.0.0:9000 + - GRAYLOG_HTTP_EXTERNAL_URI=http://192.168.5.119:9009/ + - GRAYLOG_MONGODB_URI=mongodb://mongodb:27017/graylog + - TZ=Asia/Shanghai + - GRAYLOG_TIMEZONE=Asia/Shanghai + - GRAYLOG_SERVER_JAVA_OPTS=-Duser.timezone=Asia/Shanghai + networks: + - local + restart: always + depends_on: + - mongodb + - datanode + ports: + - "9009:9000" + - "12201:12201/udp" + + + mongodb: + image: mongo:6.0 + container_name: mongodb + restart: always + environment: + - TZ=Asia/Shanghai + healthcheck: + test: ["CMD", "mongosh", "--eval", "db.adminCommand('ping')"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - local + volumes: + - /data/graylog-new/mongodb/db:/data/db + - /data/graylog-new/mongodb/config:/data/configdb + +# elasticsearch: +# image: docker.elastic.co/elasticsearch/elasticsearch-oss:7.10.2 +# container_name: elasticsearch +# restart: always +# volumes: +# - /data/garylog/elasticsearch/data:/usr/share/elasticsearch/data # 挂载数据目录 +# - /data/garylog/elasticsearch/config:/usr/share/elasticsearch/config # 挂载配置文件目录 +# - /data/garylog/elasticsearch/plugins:/usr/share/elasticsearch/plugins # 挂载插件目录 +# environment: +# - http.host=0.0.0.0 +# - transport.host=localhost +# - network.host=0.0.0.0 +# - "ES_JAVA_OPTS=-Dlog4j2.formatMsgNoLookups=true -Xms512m -Xmx512m" +# ports: +# - "9200:9200" +# ulimits: +# memlock: +# soft: -1 +# hard: -1 +# deploy: +# resources: +# limits: +# memory: 1g +# networks: +# - local + + datanode: + image: graylog/graylog-datanode:6.3 + container_name: datanode + restart: always + hostname: datanode + networks: + - local + environment: + - GRAYLOG_DATANODE_NODE_ID_FILE=/var/lib/graylog-datanode/node-id + - GRAYLOG_DATANODE_PASSWORD_SECRET=${GRAYLOG_PASSWORD_SECRET} + - GRAYLOG_DATANODE_MONGODB_URI=mongodb://mongodb:27017/graylog + - TZ=Asia/Shanghai + - GRAYLOG_DATANODE_JAVA_OPTS=-Xms4g -Xmx4g -Duser.timezone=Asia/Shanghai + - GRAYLOG_DATANODE_ELASTICSEARCH_COMPATIBILITY_MODE=true + - GRAYLOG_DATANODE_OPENSEARCH_SECURITY_DISABLED=true + - GRAYLOG_DATANODE_HTTP_BIND_ADDRESS=0.0.0.0:9200 + - GRAYLOG_DATANODE_OPENSEARCH_DISABLE_SSL=true + - GRAYLOG_DATANODE_TRANSPORT_SSL_ENABLED=false + ports: + - "8999:8999" + - "9200:9200" + - "9300:9300" + volumes: + - /data/graylog-new/datanode:/var/lib/graylog-datanode + - /usr/share/zoneinfo/Asia/Shanghai:/etc/localtime:ro + +# +# apm-server: +# image: elastic/apm-server:7.16.3 +# container_name: apm-server +# restart: always +# ports: +# - "8200:8200" # APM Server默认端口 +# environment: +# - output.elasticsearch.hosts=["http://datanode:9200"] +# - output.elasticsearch.username=admin +# - output.elasticsearch.password=admin +# - output.elasticsearch.bulk_max_size=5120 +# - output.elasticsearch.retry.max_retries=3 +# - output.elasticsearch.retry.initial_interval=10s +# - queue.mem.events=20480 +# - output.elasticsearch.workers=4 +# depends_on: +# - datanode +# networks: +# - local + +# sky-oap: +# image: docker.io/apache/skywalking-oap-server:latest +# container_name: sky-oap +# restart: always +# ports: +# - "12800:12800" +# - "11800:11800" +# environment: +# - TZ=Asia/Shanghai +# - SW_STORAGE=elasticsearch +# - SW_STORAGE_ES_CLUSTER_NODES=192.168.5.119:9200 +# - SW_STORAGE_ES_HTTP_SSL_ENABLED=false +# networks: +# - local +# +# sky-ui: +# image: docker.io/apache/skywalking-ui:latest +# container_name: sky-ui +# restart: always +# ports: +# - "8868:8080" +# environment: +# - TZ=Asia/Shanghai +# - SW_OAP_ADDRESS=http://sky-oap:12800 +# depends_on: +# - sky-oap +# networks: +# - local diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 0000000..1a3e8c4 --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +mkdir -p /opt/1panel/conf /opt/1panel/db /opt/1panel/log /opt/1panel/tmp /opt/1panel/geo + +if [ ! -f /opt/1panel/conf/app.yaml ]; then + cp /usr/local/share/1panel/app.yaml /opt/1panel/conf/app.yaml +fi + +/usr/local/bin/1panel-agent & +agent_pid=$! + +/usr/local/bin/1panel-core & +core_pid=$! + +term_handler() { + kill "${core_pid}" "${agent_pid}" 2>/dev/null || true +} + +trap term_handler SIGTERM SIGINT + +wait "${core_pid}" +kill "${agent_pid}" 2>/dev/null || true +wait "${agent_pid}" 2>/dev/null || true diff --git a/docs/README.ar.md b/docs/README.ar.md new file mode 100644 index 0000000..b44e2a7 --- /dev/null +++ b/docs/README.ar.md @@ -0,0 +1,72 @@ +

    1Panel

    +

    أفضل أداة إدارة خوادم لينكس عبر الويب بتقييم عالٍ

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +
    +1Panel هي لوحة تحكم وتشغيل وتصيان خادمات Linux من الجيل الجديد. + +- **الإدارة الفعالة**: من خلال واجهة رسومية ويب مستخدمة بسهولة، يتيح 1Panel للمستخدمين إدارة خادمات Linux الخاصة بهم بلا عناء. وتشمل الميزات الرئيسية مراقبة المضيفات، وإدارة الملفات، وإدارة قواعد البيانات، وإدارة الحاويات. +- **استعراض المواقع على الإنترنت بسرعة**: بفضل التكامل العميق مع برنامج WordPress الشهير لبناء المواقع عبر الإنترنت المفتوح المصدر، يعمل 1Panel على تبسيط عملية ربطドومين (المجال) وتكوين شهادات SSL، وكل ذلك يمكن تحقيقه بضغطة زر واحدة. +- **مخزن التطبيقات**: يقوم 1Panel بترتيب مجموعة واسعة من الأدوات والتطبيقات المفتوحة المصدر عالية الجودة، مما يسهل عملية التثبيت والتحديثات لاستخدميه. +- **الأمن والاستقرار**: من خلال الاستفادة من الحاويات وممارسات استقرارية لتوزيع التطبيقات الآمنة، يعمل 1Panel على تقليل تغيرات الحساسية. كما يعزز الأمن بشكل أكبر من خلال إمكانيات إدارة الحماية الحائط والتدقيق في السجلات. +- **نسخ واحتياطي و استعادة بضغطة زر واحدة**: حماية البيانات أصبحت بسيطة بفضل وظيفة النسخ واحتياطي واستعادة بضغطة زر واحدة في 1Panel، والتي تدعم حلول تخزين سحابية مختلفة لضمان سلامة البيانات وسهولة الوصول إليها. + +## بداية سريعة + +أعد تشغيل البرنامج النصي أدناه واتبع التعليمات التي تظهر لتثبيت 1Panel: + +```bash +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +يرجى الرجوع إلى مستنداتنا [هنا](https://docs.1panel.pro/quick_start/) لمزيد من التفاصيل. + +## مقاطع شاشة + +![UI Display](https://resource.1panel.pro/img/1panel.png) + +## تاريخ النجوم + +[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## معلومات أمنية + +إذا اكتشفت أي مشاكل أمنية، يرجى الاتصال بنا عبر: wanghe@fit2cloud.com. + +## رخصة + +مخول بموجب الرخصة العامة العامة لغنو GPLv3 (الرخصة العامة العامة لغنو GPLv3) (هذه "الرخصة")؛ لن تتمكن من استخدام هذا الملف إلا إذا اتبعت الرخصة. يمكنك الحصول على نسخة من الرخصة من + + +ما لم يكن ذلك مطلوبًا بموجب القانون المعمول به أو لم يتم الاتفاق عليه كتابيًا، يتم توزيع البرنامج الموزع بموجب الرخصة على أساس "كما هو"، بلا أي نوع من الضمانات أو الشروط، سواء كانت صريحة أو ضمنية. راجع الرخصة للحصول على اللغة المحددة التي تحكم الصلاحيات والقيود بموجب الرخصة. +
    diff --git a/docs/README.de.md b/docs/README.de.md new file mode 100644 index 0000000..cfb37b6 --- /dev/null +++ b/docs/README.de.md @@ -0,0 +1,71 @@ +

    1Panel

    +

    Hochbewertetes Web-basiertes Linux-Server-Management-Tool

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel ist ein modernes und quelloffenes Web-basiertes Steuerungsfeld zur Verwaltung von Linux-Servern. + +- **Effiziente Verwaltung**: Über eine benutzerfreundliche grafische Web-Schnittstelle ermöglicht 1Panel es Benutzern, ihre Linux-Server mühelos zu verwalten. Zu seinen Hauptfunktionen gehören die Überwachung des Hosts, die Dateiverwaltung, die Datenbankadministration und die Containerverwaltung. +- **Schnelle Website-Bereitstellung**: Durch die tiefe Integration des beliebten quelloffenen Website-Erstellungstools WordPress vereinfacht 1Panel den Prozess der Domainbindung und der SSL-Zertifikatskonfiguration, und alles kann mit nur einem Klick erreicht werden. +- **App-Store**: 1Panel bietet eine Vielzahl hochwertiger quelloffener Tools und Anwendungen an und erleichtert die Installation und Aktualisierung für seine Benutzer. +- **Sicherheit und Zuverlässigkeit**: Durch die Nutzung von Containerisierungstechnologie und sicheren Anwendungsbereitstellungspraktiken minimiert 1Panel die Angriffsfläche für Schwachstellen. Die Sicherheit wird durch integriertes Firewall-Management und Audit-Log-Funktionen weiter erhöht. +- **Ein-Klick-Sicherung und -Wiederherstellung**: Der Datenschutz wird durch die Ein-Klick-Sicherungs- und -Wiederherstellungsfunktionen von 1Panel vereinfacht und unterstützt verschiedene Cloud-Speicherlösungen, um die Integrität und Verfügbarkeit der Daten zu gewährleisten. + +## Schneller Startleitfaden + +Führen Sie das folgende Skript aus und befolgen Sie die Anweisungen, um 1Panel zu installieren: + +```bash +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +Bitte verweisen Sie auf unsere [Dokumentation](https://docs.1panel.pro/quick_start/) für weitere Informationen. + +## Bildschirmfoto + +![Benutzeroberflächenanzeige](https://resource.1panel.pro/img/1panel.png) + +## Sternverlauf + +[![Sternverlaufsdiagramm](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## Sicherheitsinformationen + +Wenn Sie irgendwelche Sicherheitsprobleme entdecken, kontaktieren Sie uns bitte über: wanghe@fit2cloud.com. + +## Lizenz + +Lizenziert unter der GNU General Public License Version 3 (GPLv3) (die „Lizenz“); Sie dürfen diese Datei nur in Übereinstimmung mit der Lizenz verwenden. Sie können eine Kopie der Lizenz unter + +https://www.gnu.org/licenses/gpl-3.0.html erhalten. + +Sofern nicht durch anwendbares Recht vorgeschrieben oder schriftlich vereinbart, wird die unter der Lizenz verbreitete Software „AS IS“ ohne jegliche Arten von Garantien oder Bedingungen verteilt, sei es ausdrücklich oder stillschweigend. Siehe die Lizenz für die spezifische Sprache, die die Rechte und Einschränkungen unter der Lizenz regelt. diff --git a/docs/README.es-es.md b/docs/README.es-es.md new file mode 100644 index 0000000..d365ebf --- /dev/null +++ b/docs/README.es-es.md @@ -0,0 +1,71 @@ +

    1Panel

    +

    La herramienta de gestión más valorada para administrar servidores Linux.

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel es un panel de control moderno y de código abierto basado en web para la gestión de servidores Linux. + +- **Gestión eficiente**: los usuarios pueden gestionar fácilmente servidores Linux a través de una interfaz web, como monitorización de hosts, gestión de archivos, gestión de bases de datos, gestión de contenedores, entre otros; +- **Creación rápida de sitios web**: integración profunda con el software de creación de sitios web de código abierto WordPress y Halo, operaciones como vinculación de dominios y configuración de certificados SSL se realizan con un solo clic; +- **Tienda de aplicaciones**: selección de diversas herramientas y software de código abierto de alta calidad, ayudando a los usuarios a instalar y actualizar fácilmente; +- **Seguro y confiable**: basado en la gestión de contenedores y el despliegue de aplicaciones, se logra la mínima exposición a vulnerabilidades, al tiempo que se ofrecen funciones como protección contra virus, cortafuegos y auditoría de registros; +- **Copia de seguridad con un clic**: soporta copias de seguridad y restauraciones con un clic, los usuarios pueden respaldar datos en diversos medios de almacenamiento en la nube, asegurando que nunca se pierdan. + +## Inicio Rápido + +Ejecute el siguiente script y siga las indicaciones para instalar 1Panel: + +```bash +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +Por favor, consulte nuestra [documentación](https://docs.1panel.pro/quick_start/) para más detalles. + +## Captura de Pantalla + +![UI Display](https://resource.1panel.pro/img/1panel.png) + +## Historial de Estrellas + +[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## Información de Seguridad + +Si descubre algún problema de seguridad, contáctenos a través de: wanghe@fit2cloud.com. + +## Licencia + +Licenciado bajo la Licencia Pública General GNU versión 3 (GPLv3) (la "Licencia"); no puede usar este archivo excepto en cumplimiento con la Licencia. Puede obtener una copia de la Licencia en + + + +A menos que lo exija la ley aplicable o se acuerde por escrito, el software distribuido bajo la Licencia se distribuye en una BASE "TAL CUAL", SIN GARANTÍAS O CONDICIONES DE NINGÚN TIPO, ya sean expresas o implícitas. Consulte la Licencia para el lenguaje específico que rige los permisos y limitaciones bajo la Licencia. diff --git a/docs/README.fr.md b/docs/README.fr.md new file mode 100644 index 0000000..32f7b39 --- /dev/null +++ b/docs/README.fr.md @@ -0,0 +1,71 @@ +

    1Panel

    +

    Outil de gestion de serveurs Linux basé sur le web le mieux noté

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel est un panneau de contrôle basé sur le web, moderne et open source, destiné à la gestion de serveurs Linux. + +- **Gestion efficace**: Grâce à une interface graphique web conviviale, 1Panel permet aux utilisateurs de gérer facilement leurs serveurs Linux. Ses principales fonctionnalités comprennent la surveillance des hôtes, la gestion des fichiers, l'administration des bases de données et la gestion des conteneurs. +- **Déploiement rapide de sites web**: Avec une intégration approfondie du logiciel de création de sites Web open-source populaire WordPress, 1Panel simplifie le processus de liaison de domaine et de configuration du certificat SSL, tout cela pouvant être réalisé en un seul clic. +- **Boutique d'applications**: 1Panel propose une variété d'outils et d'applications open-source de haute qualité, facilitant l'installation et la mise à jour pour les utilisateurs. +- **Sécurité et fiabilité**: En tirant parti de la technologie de conteneurisation et des pratiques de déploiement d'applications sécurisées, 1Panel minimise l'exposition aux vulnérabilités. La sécurité est davantage renforcée via la gestion du pare-feu intégré et la capacité d'audit des journaux. +- **Sauvegarde et restauration en un clic**: La protection des données est simplifiée grâce à la fonction de sauvegarde et de restauration en un clic de 1Panel, qui prend en charge diverses solutions de stockage cloud pour garantir l'intégrité et la disponibilité des données. + +## Guide de démarrage rapide + +Exécutez le script ci-dessous et suivez les instructions pour installer 1Panel : + +```bash +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +Veuillez vous référer à notre [documentation](https://docs.1panel.pro/quick_start/) pour plus d'informations. + +## Capture d'écran + +![UI Display](https://resource.1panel.pro/img/1panel.png) + +## Histoire des étoiles + +[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## Informations de sécurité + +Si vous découvrez un quelconque problème de sécurité, veuillez nous contacter via : wanghe@fit2cloud.com. + +## Licence + +Ce logiciel est sous licence GNU General Public License version 3 (GPLv3) (la « Licence ») ; vous ne pouvez utiliser ce fichier que conformément à la Licence. Vous pouvez obtenir une copie de la Licence à l'adresse suivante : + + + +Sauf si exigé par la loi en vigueur ou convenu par écrit, le logiciel distribué sous la Licence est distribué « tel quel », SANS GARANTIE D'AUCUNE SORTE, expresse ou implicite, et sans aucune condition. Consultez la Licence pour les termes spécifiques régissant les autorisations et les limitations prévues par la Licence. diff --git a/docs/README.id.md b/docs/README.id.md new file mode 100644 index 0000000..131c14e --- /dev/null +++ b/docs/README.id.md @@ -0,0 +1,71 @@ +

    1Panel

    +

    Alat Manajemen Server Linux Berbasis Web dengan Peringkat Teratas

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel adalah panel kontrol berbasis web yang modern dan open-source untuk manajemen server Linux. + +- **Manajemen Efisien**: Melalui antarmuka grafis web yang ramah pengguna, 1Panel memungkinkan pengguna mengelola server Linux mereka dengan mudah. Fitur utamanya mencakup pemantauan host, manajemen file, administrasi database, dan manajemen kontainer. +- **Penyebaran Situs Web Cepat**: Dengan integrasi mendalam perangkat lunak pembuatan situs web open-source populer WordPress, 1Panel menyederhanakan proses pengikatan domain dan konfigurasi sertifikat SSL, semuanya dapat dicapai hanya dengan satu klik. +- **Toko Aplikasi**: 1Panel menyediakan berbagai macam alat dan aplikasi open-source berkualitas tinggi, memudahkan instalasi dan pembaruan bagi penggunanya. +- **Keamanan dan Keandalan**: Dengan memanfaatkan teknologi kontainerisasi dan praktik penyebaran aplikasi aman, 1Panel meminimalkan paparan kerentanan. Keamanan lebih ditingkatkan melalui manajemen firewall terintegrasi dan kemampuan audit log. +- **Cadangan & Pulih Satu Klik**: Perlindungan data menjadi sederhana dengan fungsi cadangan dan pulih satu klik dari 1Panel, mendukung berbagai solusi penyimpanan cloud untuk memastikan integritas dan ketersediaan data. + +## Panduan Cepat Memulai + +Jalankan skrip di bawah ini dan ikuti petunjuk untuk menginstal 1Panel: + +```bash +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +Silakan merujuk ke [dokumentasi](https://docs.1panel.pro/quick_start/) kami untuk informasi lebih lanjut. + +## Tangkapan Layar + +![UI Display](https://resource.1panel.pro/img/1panel.png) + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## Informasi Keamanan + +Jika Anda menemukan masalah keamanan apa pun, silakan hubungi kami melalui: wanghe@fit2cloud.com. + +## License + +Dilindungi di bawah Lisensi GNU General Public License versi 3 (GPLv3) (selanjutnya disebut “Lisensi”); Anda tidak diperbolehkan menggunakan file ini kecuali sesuai dengan ketentuan Lisensi. Anda dapat memperoleh salinan Lisensi di + +https://www.gnu.org/licenses/gpl-3.0.html + +Kecuali diwajibkan oleh hukum yang berlaku atau disetujui secara tertulis, perangkat lunak yang didistribusikan berdasarkan Lisensi ini disediakan “APA ADANYA” TANPA JAMINAN APAPUN, baik tersurat maupun tersirat. Lihat Lisensi untuk informasi lebih lanjut mengenai hak dan batasan dalam Lisensi ini. diff --git a/docs/README.ja.md b/docs/README.ja.md new file mode 100644 index 0000000..83ad30f --- /dev/null +++ b/docs/README.ja.md @@ -0,0 +1,69 @@ +

    1Panel

    +

    最高評価のWebベースのLinuxサーバー管理ツール

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel はモダンでオープンソースの Linux サーバー操作と管理パネルです: + +- **効率的管理**:ユーザーは、Web グラフィカルインターフェースを使用して、Linux サーバーを簡単に管理し、ホストの監視、ファイル管理、データベース管理、コンテナ管理などの機能を実現できます。 +- **迅速なウェブサイト構築**:オープンソースのウェブサイト構築ソフトウェアであるWordPressと[Halo](https://github.com/halo-dev/halo/)を深く統合し、ドメインのバインディング、SSL 証明書の設定などをワンクリックで完了します。 +- **アプリストア**:さまざまな高品質のオープンソースツールやアプリケーションソフトウェアを厳選して提供し、ユーザーが簡単にインストールおよびアップグレードできるよう支援します。 +- **安全性と信頼性**:コンテナの管理とアプリケーションのデプロイに基づいて、最小限の脆弱性露出面を実現し、同時にファイアウォールやログ監査などの機能を提供します。 +- **ワンクリックバックアップ**:ワンクリックでバックアップと復元をサポートし、ユーザーはデータをさまざまなクラウドストレージメディアにバックアップし、永久に失うことはありません。 + +## クイックスタート + +ワンクリックで 1Panel をインストールするには、次のコマンドを実行します: + +```sh +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +## UI ディスプレイ + +![UI Display](https://resource.fit2cloud.com/1panel/img/overview_en.png) + +## Star History + +[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## セキュリティ情報 + +セキュリティの問題を発見した場合は、以下の方法でご連絡ください:wanghe@fit2cloud.com。 + +## License + +このファイルは、GNU General Public License バージョン 3 (GPLv3)(以下「ライセンス」)の下でライセンスされています。ライセンスに従わない限り、このファイルを使用することはできません。ライセンスのコピーは以下で取得できます。 + + + +適用法により要求される場合、または書面で同意される場合を除き、ライセンスの下で配布されるソフトウェアは「現状のまま(AS IS)」で提供され、いかなる種類の保証もありません。ライセンスにおける特定の権利および制限については、ライセンスを参照してください。 diff --git a/docs/README.ko.md b/docs/README.ko.md new file mode 100644 index 0000000..c411ca5 --- /dev/null +++ b/docs/README.ko.md @@ -0,0 +1,71 @@ +

    1Panel

    +

    최고 평점의 웹 기반 리눅스 서버 관리 도구

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel은 리눅스 서버 관리를 위한 오픈 소스 현대 웹 기반 제어판입니다. + +- **효율적인 관리**: 사용자 친화적인 웹 그래픽 인터페이스를 통해 1Panel은 사용자가 리눅스 서버를 손쉽게 관리할 수 있도록 합니다. 주요 기능으로는 호스트 모니터링, 파일 관리, 데이터베이스 관리 및 컨테이너 관리가 포함됩니다. +- **신속한 웹사이트 배포**: 인기 있는 오픈 소스 웹사이트 빌딩 소프트웨어인 WordPress와의 깊은 통합을 통해 1Panel은 도메인 바인딩 및 SSL 인증서 구성을 단 한 번의 클릭으로 간소화합니다. +- **애플리케이션 스토어**: 1Panel은 다양한 고품질 오픈 소스 도구 및 애플리케이션을 선별하여 사용자가 쉽게 설치하고 업데이트할 수 있도록 합니다. +- **보안 및 신뢰성**: 컨테이너화 및 안전한 애플리케이션 배포 관행을 활용하여 1Panel은 취약점 노출을 최소화합니다. 또한 통합 방화벽 관리 및 로그 감사 기능을 통해 보안을 강화합니다. +- **원클릭 백업 및 복원**: 1Panel의 원클릭 백업 및 복원 기능을 통해 데이터 보호가 간편해지며, 다양한 클라우드 스토리지 솔루션을 지원하여 데이터 무결성과 가용성을 보장합니다. + +## 빠른 시작 + +아래 스크립트를 실행하고 프롬프트에 따라 1Panel을 설치하세요: + +```bash +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +자세한 내용은 [문서](https://docs.1panel.pro/quick_start/)를 참조하세요. + +## 스크린샷 + +![UI 디스플레이](https://resource.1panel.pro/img/1panel.png) + +## 스타 히스토리 + +[![스타 히스토리 차트](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## 보안 정보 + +보안 문제를 발견하신 경우, 다음을 통해 저희에게 연락해 주시기 바랍니다: wanghe@fit2cloud.com. + +## 라이센스 + +GNU 일반 공공 라이센스 버전 3(GPLv3) (이하 "라이센스")에 따라 이 파일을 사용할 수 있으며, 라이센스를 준수하지 않는 한 이 파일을 사용할 수 없습니다. 라이센스의 사본은 다음에서 얻을 수 있습니다. + + + +적용 가능한 법률에 따라 요구되거나 서면으로 동의하지 않는 한, 라이센스에 따라 배포된 소프트웨어는 "있는 그대로" 제공되며, 명시적이거나 묵시적인 보증이나 조건이 없습니다. 라이센스에 따라 권한 및 제한 사항을 규정하는 특정 언어를 참조하십시오. \ No newline at end of file diff --git a/docs/README.ms.md b/docs/README.ms.md new file mode 100644 index 0000000..0bd88dd --- /dev/null +++ b/docs/README.ms.md @@ -0,0 +1,71 @@ +

    1Panel

    +

    Alat Pengurusan Pelayan Linux Berasaskan Web Terbaik

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel ialah panel kawalan moden sumber terbuka berasaskan web untuk pengurusan pelayan Linux. + +- **Pengurusan yang Cekap**: Melalui antara muka grafik web yang mesra pengguna, 1Panel membolehkan pengguna menguruskan pelayan Linux mereka dengan mudah. Ciri utama termasuk pemantauan hos, pengurusan fail, pentadbiran pangkalan data, dan pengurusan kontena. +- **Penggunaan Laman Web yang Pantas**: Dengan integrasi mendalam perisian pembinaan laman web sumber terbuka popular seperti WordPress, 1Panel mempermudah proses pengikatan domain dan konfigurasi sijil SSL, semuanya boleh dicapai dengan hanya satu klik. +- **Gedung Aplikasi**: 1Panel menyediakan pelbagai alat dan aplikasi sumber terbuka berkualiti tinggi, memudahkan pemasangan dan kemas kini untuk penggunanya. +- **Keselamatan dan Kebolehpercayaan**: Dengan memanfaatkan penggunaan kontena dan amalan penyebaran aplikasi yang selamat, 1Panel meminimumkan pendedahan terhadap kerentanan. Ia turut meningkatkan keselamatan melalui pengurusan firewall yang terintegrasi dan keupayaan pengauditan log. +- **Sandaran & Pemulihan Satu Klik**: Perlindungan data menjadi mudah dengan fungsi sandaran dan pemulihan satu klik 1Panel, yang menyokong pelbagai penyelesaian storan awan untuk memastikan integriti dan ketersediaan data. + +## Permulaan Pantas + +Jalankan skrip di bawah dan ikuti arahan untuk memasang 1Panel: + +```bash +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +Sila rujuk [dokumentasi](https://docs.1panel.pro/quick_start/) untuk maklumat lanjut. + +## Tangkap Layar + +![UI Display](https://resource.1panel.pro/img/1panel.png) + +## Sejarah Bintang + +[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## Maklumat Keselamatan + +Jika anda menemui sebarang isu keselamatan, sila hubungi kami melalui: wanghe@fit2cloud.com. + +## Lesen + +Dilesenkan di bawah Lesen Awam Am GNU versi 3 (GPLv3) (selepas ini disebut sebagai "Lesen"); anda tidak boleh menggunakan fail ini kecuali mematuhi terma-terma Lesen. Anda boleh mendapatkan salinan Lesen di: + + + +Kecuali dikehendaki oleh undang-undang yang berkuat kuasa atau dipersetujui secara bertulis, perisian yang diedarkan di bawah Lesen ini diedarkan "SEBAGAIMANA ADANYA", TANPA SEBARANG JAMINAN ATAU SYARAT, sama ada tersurat atau tersirat. Sila rujuk Lesen untuk bahasa khusus yang mengawal hak dan batasan di bawah Lesen ini. diff --git a/docs/README.pt-br.md b/docs/README.pt-br.md new file mode 100644 index 0000000..7d3098a --- /dev/null +++ b/docs/README.pt-br.md @@ -0,0 +1,71 @@ +

    1Panel

    +

    Ferramenta de Gerenciamento de Servidores Linux Baseada na Web Mais Avaliada

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel é um painel de controle open-source, moderno e baseado na web para gerenciamento de servidores Linux. + +- **Gerenciamento Eficiente**: Por meio de uma interface gráfica web amigável, o 1Panel permite que os usuários gerenciem seus servidores Linux com facilidade. As principais funcionalidades incluem monitoramento de hosts, gerenciamento de arquivos, administração de bancos de dados e gerenciamento de containers. +- **Implantação Rápida de Sites**: Com integração profunda com o popular software open-source WordPress, o 1Panel simplifica o processo de vinculação de domínios e configuração de certificados SSL, tudo realizável com um único clique. +- **Loja de Aplicativos**: O 1Panel seleciona uma ampla gama de ferramentas e aplicativos open-source de alta qualidade, facilitando a instalação e atualização para seus usuários. +- **Segurança e Confiabilidade**: Ao utilizar práticas de containerização e implantação segura de aplicativos, o 1Panel minimiza a exposição a vulnerabilidades. A segurança é ainda aprimorada por meio da gestão integrada de firewall e auditoria de logs. +- **Backup e Restauração com um Clique**: A proteção de dados é simplificada com a funcionalidade de backup e restauração com um clique do 1Panel, que suporta várias soluções de armazenamento em nuvem para garantir a integridade e disponibilidade dos dados. + +## Início Rápido + +Execute o script abaixo e siga as instruções para instalar o 1Panel: + +```bash +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +Por favor, consulte nossa [documentação](https://docs.1panel.pro/quick_start/) para mais detalhes. + +## Captura de Tela + +![Exibição da UI](https://resource.1panel.pro/img/1panel.png) + +## Histórico de Estrelas + +[![Gráfico de Histórico de Estrelas](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## Informações de Segurança + +Se você descobrir algum problema de segurança, por favor, entre em contato conosco através do e-mail: wanghe@fit2cloud.com. + +## Licença + +Licenciado sob a Licença Pública Geral GNU versão 3 (GPLv3) (a "Licença"); você não pode usar este arquivo exceto em conformidade com a Licença. Você pode obter uma cópia da Licença em + + + +Exceto quando exigido por lei aplicável ou acordado por escrito, o software distribuído sob a Licença é distribuído "COMO ESTÁ", SEM GARANTIAS OU CONDIÇÕES DE QUALQUER TIPO, expressas ou implícitas. Consulte a Licença para ver as permissões e limitações específicas sob a Licença. diff --git a/docs/README.ru.md b/docs/README.ru.md new file mode 100644 index 0000000..204921d --- /dev/null +++ b/docs/README.ru.md @@ -0,0 +1,71 @@ +

    1Panel

    +

    Высококлассная веб-панель управления Linux-серверами

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel - это современная веб-панель управления Linux-серверами с открытым исходным кодом. + +- **Эффективное управление**: Благодаря удобному веб-интерфейсу, 1Panel позволяет пользователям легко управлять своими Linux-серверами. Основные функции включают мониторинг хоста, управление файлами, администрирование баз данных и управление контейнерами. +- **Быстрое развертывание сайтов**: Благодаря глубокой интеграции с популярной системой создания сайтов WordPress, 1Panel упрощает процесс привязки домена и настройки SSL-сертификатов, все это достигается одним кликом. +- **Магазин приложений**: 1Panel предлагает широкий выбор качественных инструментов и приложений с открытым исходным кодом, обеспечивая простую установку и обновление для пользователей. +- **Безопасность и надежность**: Используя контейнеризацию и безопасные практики развертывания приложений, 1Panel минимизирует уязвимости. Безопасность дополнительно усиливается благодаря встроенному управлению межсетевым экраном и возможностям аудита логов. +- **Резервное копирование и восстановление в один клик**: Защита данных упрощается благодаря функции резервного копирования и восстановления в один клик, с поддержкой различных облачных хранилищ для обеспечения целостности и доступности данных. + +## Быстрый старт + +Выполните скрипт ниже и следуйте инструкциям для установки 1Panel: + +```bash +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +Для получения дополнительной информации обратитесь к нашей [документации](https://docs.1panel.pro/quick_start/). + +## Скриншот + +![Интерфейс](https://resource.1panel.pro/img/1panel.png) + +## История звезд + +[![График истории звезд](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## Информация о безопасности + +Если вы обнаружите проблемы безопасности, пожалуйста, свяжитесь с нами по адресу: wanghe@fit2cloud.com. + +## Лицензия + +Лицензировано под GNU General Public License версии 3 (GPLv3) ("Лицензия"); вы не можете использовать этот файл, кроме как в соответствии с Лицензией. Вы можете получить копию Лицензии по адресу + + + +Если иное не предусмотрено применимым законодательством или не согласовано в письменной форме, программное обеспечение, распространяемое по Лицензии, распространяется на условиях "КАК ЕСТЬ", БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ ИЛИ УСЛОВИЙ, явных или подразумеваемых. См. текст Лицензии для получения информации о разрешениях и ограничениях по Лицензии. diff --git a/docs/README.tr.md b/docs/README.tr.md new file mode 100644 index 0000000..42be68e --- /dev/null +++ b/docs/README.tr.md @@ -0,0 +1,73 @@ +

    1Panel

    +

    En Yüksek Puanlı Web Tabanlı Linux Sunucu Yönetim Aracı

    +

    + 1Panel-dev%2F1Panel | Trendshift + 1Panel - Top-Rated web-based Linux server management tool | Product Hunt +

    +

    + License: GPL v3 + Codacy + + chat on Discord + GitHub release + Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +------------------------------ + +1Panel, Linux sunucu yönetimi için modern, açık kaynaklı bir web tabanlı kontrol panelidir. + +- **Verimli Yönetim**: Kullanıcı dostu bir web grafik arayüzü aracılığıyla, 1Panel kullanıcıların Linux sunucularını zahmetsizce yönetmelerini sağlar. Ana özellikler arasında ana bilgisayar izleme, dosya yönetimi, veritabanı yönetimi ve konteyner yönetimi yer alır. +- **Hızlı Web Sitesi Dağıtımı**: Popüler açık kaynaklı web sitesi oluşturma yazılımı WordPress ile derin entegrasyon sayesinde, 1Panel alan adı bağlama ve SSL sertifikası yapılandırma işlemlerini basitleştirir, hepsi sadece bir tıkla yapılabilir. +- **Uygulama Mağazası**: 1Panel, yüksek kaliteli açık kaynaklı araçlar ve uygulamalar sunar, kullanıcıların kolayca kurulum yapmalarını ve güncellemelerini sağlar. +- **Güvenlik ve Güvenilirlik**: Konteynerleştirme ve güvenli uygulama dağıtımı yöntemlerini kullanarak 1Panel, güvenlik açıklarını en aza indirir. Ayrıca, entegre güvenlik duvarı yönetimi ve günlük denetleme özellikleri ile güvenliği artırır. +- **Tek Tıkla Yedekleme ve Geri Yükleme**: 1Panel’in tek tıkla yedekleme ve geri yükleme işlevi, veri korumasını basitleştirir ve çeşitli bulut depolama çözümleriyle veri bütünlüğü ve erişilebilirliğini sağlar. + +## Hızlı Başlangıç + +**Tek Tuşlu Yükleme** + +Aşağıdaki komutu çalıştırın ve 1Panel’i kurmak için yönergeleri takip edin: + +```sh +curl -sSL https://resource.1panel.pro/quick_start.sh -o quick_start.sh && bash quick_start.sh +``` + +Daha fazla detay için [dokümantasyonumuza](https://docs.1panel.pro/quick_start/) göz atın. + +## Ekran Görüntüsü + +![UI Display](https://resource.1panel.pro/img/1panel.png) + +## Yıldız Geçmişi + +[![Star History Chart](https://api.star-history.com/svg?repos=1Panel-dev/1Panel&type=Date)](https://star-history.com/#1Panel-dev/1Panel&Date) + +## Güvenlik Bilgileri + +Herhangi bir güvenlik sorunu keşfettiğinizde, lütfen bizimle şu adres üzerinden iletişime geçin: wanghe@fit2cloud.com. + +## Lisans + +Bu yazılım, GNU Genel Kamu Lisansı sürüm 3 (GPLv3) ile lisanslanmıştır (bu "Lisans" olarak anılacaktır); bu dosyayı yalnızca Lisans ile uyumlu olarak kullanabilirsiniz. Lisansı şu adresten edinebilirsiniz: + + + +Uygulanan yasalar gereği veya yazılı olarak kabul edilmedikçe, Lisans altında dağıtılan yazılım, HERHANGİ BİR GARANTİ VEYA ŞART olmaksızın "OLDUĞU GİBİ" sunulmaktadır. Lisans, izinler ve sınırlamalar ile ilgili özel dil için Lisans’a bakın. \ No newline at end of file diff --git a/docs/README.zh-Hans.md b/docs/README.zh-Hans.md new file mode 100644 index 0000000..8bd8120 --- /dev/null +++ b/docs/README.zh-Hans.md @@ -0,0 +1,85 @@ +

    1Panel

    +

    Top-Rated Web-based Linux Server Management Tool

    +

    现代化、开源的 Linux 服务器运维管理面板

    +

    + 1Panel-dev%2F1Panel | Trendshift + Featured|HelloGitHub +

    +

    + License: GPL v3 + Codacy + GitHub release + GitHub Stars + Gitee Stars + GitCode Stars
    +

    +

    + English + 中文(简体) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +[![Watch the video](https://resource.fit2cloud.com/1panel/img/overview_video.png)](https://www.bilibili.com/video/BV1Mt421n7LZ/) + +------------------------------ +## 什么是 1Panel? + +1Panel 是新一代的 Linux 服务器运维管理面板。 + +- **高效管理**:用户可以通过 Web 界面轻松管理 Linux 服务器,如主机监控、文件管理、数据库管理、容器管理等; +- **快速建站**:深度集成开源建站软件 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名绑定、SSL 证书配置等操作一键搞定; +- **应用商店**:精选上架各类高质量的开源工具和应用软件,协助用户轻松安装并升级; +- **安全可靠**:基于容器管理并部署应用,实现最小的漏洞暴露面,同时提供病毒防护、防火墙和日志审计等功能; +- **一键备份**:支持一键备份和恢复,用户可以将数据备份到各类云端存储介质,永不丢失。 + +## 快速开始 + +**一键安装** + +执行如下命令一键安装 1Panel: + +```sh +curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh +``` + +如果是用于离线环境,推荐使用 [安装包方式](https://1panel.cn/docs/installation/package_installation/) 进行安装部署。 + +**学习资料** + +- [在线文档](https://1panel.cn/docs/) +- [社区论坛](https://bbs.fit2cloud.com/c/1p/7) +- [如何加入微信交流群?](https://bbs.fit2cloud.com/t/topic/2147) + +## 专业版 + +相比于社区版,1Panel 专业版为用户提供了大量增强功能及技术支持服务,增强功能包括 WAF 增强、网站防篡改、网站监控、GPU 监控、黑金主题及自定义界面等。 [点击查看专业版详细介绍](https://www.lxware.cn/1panel)。 + +## 飞致云的其他明星项目 + +- [MaxKB](https://github.com/1Panel-dev/MaxKB/) - 基于 LLM 大语言模型的开源知识库问答系统 +- [JumpServer](https://github.com/jumpserver/jumpserver/) - 广受欢迎的开源堡垒机 +- [Halo](https://github.com/halo-dev/halo/) - 强大易用的开源建站工具 +- [DataEase](https://github.com/dataease/dataease/) - 人人可用的开源数据可视化分析工具 +- [MeterSphere](https://github.com/metersphere/metersphere/) - 开源持续测试工具 +- [KubePi](https://github.com/1Panel-dev/KubePi/) - 现代化、开源的 K8s 面板 + +## License + +Copyright (c) 2014-2026 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved. + +Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + + + +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/docs/README.zh-Hant.md b/docs/README.zh-Hant.md new file mode 100644 index 0000000..9b86a89 --- /dev/null +++ b/docs/README.zh-Hant.md @@ -0,0 +1,85 @@ +

    1Panel

    +

    Top-Rated Web-based Linux Server Management Tool

    +

    現代化、開源的 Linux 伺服器運維管理面板

    +

    + 1Panel-dev%2F1Panel | Trendshift + Featured|HelloGitHub +

    +

    + License: GPL v3 + Codacy + GitHub release + GitHub Stars + Gitee Stars + GitCode Stars
    +

    +

    + English + 中文(簡體) + 日本語 + Português (Brasil) + العربية
    + Deutsch + Español + français + 한국어 + Bahasa Indonesia + 中文(繁體) + Türkçe + Русский + Bahasa Melayu +

    + +[![Watch the video](https://resource.fit2cloud.com/1panel/img/overview_video.png)](https://www.bilibili.com/video/BV1Mt421n7LZ/) + +------------------------------ +## 什麼是 1Panel? + +1Panel 是新一代的 Linux 伺服器運維管理面板。 + +- **高效管理**:使用者可以透過 Web 介面輕鬆管理 Linux 伺服器,如主機監控、文件管理、資料庫管理、容器管理等; +- **快速建站**:深度整合開源建站軟體 WordPress 和 [Halo](https://github.com/halo-dev/halo/),域名綁定、SSL 證書配置等操作一鍵搞定; +- **應用商店**:精選上架各類高品質的開源工具和應用軟體,協助使用者輕鬆安裝並升級; +- **安全可靠**:基於容器管理並部署應用,實現最小的漏洞暴露面,同時提供病毒防護、防火牆和日誌審計等功能; +- **一鍵備份**:支援一鍵備份和復原,使用者可以將資料備份到各類雲端儲存介質,永不遺失。 + +## 快速開始 + +**一鍵安裝** + +執行如下指令一鍵安裝 1Panel: + +```sh +curl -sSL https://resource.fit2cloud.com/1panel/package/quick_start.sh -o quick_start.sh && sudo bash quick_start.sh +``` + +如果是用於離線環境,推薦使用 [安裝包方式](https://1panel.cn/docs/installation/package_installation/) 進行安裝部署。 + +**學習資料** + +- [線上文件](https://1panel.cn/docs/) +- [社區論壇](https://bbs.fit2cloud.com/c/1p/7) +- [如何加入微信交流群?](https://bbs.fit2cloud.com/t/topic/2147) + +## 專業版 + +相比於社區版,1Panel 專業版為使用者提供了大量增強功能及技術支援服務,增強功能包括 WAF 增強、網站防篡改、網站監控、GPU 監控、黑金主題及自訂介面等。 [點擊查看專業版詳細介紹](https://www.lxware.cn/1panel)。 + +## 飛致雲的其他明星項目 + +- [MaxKB](https://github.com/1Panel-dev/MaxKB/) - 基於 LLM 大語言模型的開源知識庫問答系統 +- [JumpServer](https://github.com/jumpserver/jumpserver/) - 廣受歡迎的開源堡壘機 +- [Halo](https://github.com/halo-dev/halo/) - 強大易用的開源建站工具 +- [DataEase](https://github.com/dataease/dataease/) - 人人可用的開源資料可視化分析工具 +- [MeterSphere](https://github.com/metersphere/metersphere/) - 開源持續測試工具 +- [KubePi](https://github.com/1Panel-dev/KubePi/) - 現代化、開源的 K8s 面板 + +## License + +Copyright (c) 2014-2026 [FIT2CLOUD 飞致云](https://fit2cloud.com/), All rights reserved. + +Licensed under The GNU General Public License version 3 (GPLv3) (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at + + + +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/frontend/.editorconfig b/frontend/.editorconfig new file mode 100644 index 0000000..53408a9 --- /dev/null +++ b/frontend/.editorconfig @@ -0,0 +1,25 @@ +# @see: http://editorconfig.org + +root = true + +# 表示所有文件适用 +[*] +# 设置文件字符集为 utf-8 +charset = utf-8 +# 控制换行类型(lf | cr | crlf) +end_of_line = lf +# 始终在文件末尾插入一个新行 +insert_final_newline = true +# 缩进风格(tab | space) +indent_style = space +# 缩进大小 +indent_size = 4 +# 最大行长度 +max_line_length = 120 + +# 表示仅 md 文件适用以下规则 +[*.md] +# 关闭最大行长度限制 +max_line_length = off +# 关闭末尾空格修剪 +trim_trailing_whitespace = false diff --git a/frontend/.env b/frontend/.env new file mode 100644 index 0000000..8e79839 --- /dev/null +++ b/frontend/.env @@ -0,0 +1,7 @@ +VITE_GLOB_APP_TITLE = '1Panel' +VITE_PORT = 4004 +VITE_OPEN = false +VITE_REPORT = false +VITE_BUILD_GZIP = false +VITE_DROP_CONSOLE = true +PANEL_XPACK = false \ No newline at end of file diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 0000000..fe459a3 --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,6 @@ +NODE_ENV = 'development' +VITE_API_URL = '/api/v2' +VITE_REPORT = false +VITE_BUILD_GZIP = false +VITE_DROP_CONSOLE = true +PANEL_XPACK = true \ No newline at end of file diff --git a/frontend/.env.production b/frontend/.env.production new file mode 100644 index 0000000..2bfdfd7 --- /dev/null +++ b/frontend/.env.production @@ -0,0 +1,6 @@ +NODE_ENV = "production" +VITE_API_URL = '/api/v2' +VITE_REPORT = true +VITE_BUILD_GZIP = false +VITE_DROP_CONSOLE = true +PANEL_XPACK = true diff --git a/frontend/.eslintignore b/frontend/.eslintignore new file mode 100644 index 0000000..160d0f1 --- /dev/null +++ b/frontend/.eslintignore @@ -0,0 +1,17 @@ +*.sh +node_modules +*.md +*.woff +*.ttf +.vscode +.idea +dist +/public +/docs +.husky +.local +/bin +.eslintrc.js +.prettierrc.js +/src/mock/* + diff --git a/frontend/.eslintrc.js b/frontend/.eslintrc.js new file mode 100644 index 0000000..350a4f9 --- /dev/null +++ b/frontend/.eslintrc.js @@ -0,0 +1,73 @@ +// @see: http://eslint.cn + +module.exports = { + root: true, + env: { + browser: true, + node: true, + es6: true, + }, + /* 指定如何解析语法 */ + parser: 'vue-eslint-parser', + /* 优先级低于 parse 的语法解析配置 */ + parserOptions: { + parser: '@typescript-eslint/parser', + ecmaVersion: 2020, + sourceType: 'module', + jsxPragma: 'React', + ecmaFeatures: { + jsx: true, + }, + }, + /* 继承某些已有的规则 */ + extends: [ + 'plugin:vue/vue3-recommended', + 'plugin:@typescript-eslint/recommended', + 'prettier', + 'plugin:prettier/recommended', + ], + /* + * "off" 或 0 ==> 关闭规则 + * "warn" 或 1 ==> 打开的规则作为警告(不影响代码执行) + * "error" 或 2 ==> 规则作为一个错误(代码不能执行,界面报错) + */ + rules: { + // eslint (http://eslint.cn/docs/rules) + 'no-var': 'error', // 要求使用 let 或 const 而不是 var + 'no-multiple-empty-lines': ['error', { max: 1 }], // 不允许多个空行 + 'no-use-before-define': 'off', // 禁止在 函数/类/变量 定义之前使用它们 + 'prefer-const': 'off', // 此规则旨在标记使用 let 关键字声明但在初始分配后从未重新分配的变量,要求使用 const + 'no-irregular-whitespace': 'off', // 禁止不规则的空白 + + // typeScript (https://typescript-eslint.io/rules) + '@typescript-eslint/no-unused-vars': 'error', // 禁止定义未使用的变量 + '@typescript-eslint/no-inferrable-types': 'off', // 可以轻松推断的显式类型可能会增加不必要的冗长 + '@typescript-eslint/no-namespace': 'off', // 禁止使用自定义 TypeScript 模块和命名空间。 + '@typescript-eslint/no-explicit-any': 'off', // 禁止使用 any 类型 + '@typescript-eslint/ban-ts-ignore': 'off', // 禁止使用 @ts-ignore + '@typescript-eslint/ban-types': 'off', // 禁止使用特定类型 + '@typescript-eslint/explicit-function-return-type': 'off', // 不允许对初始化为数字、字符串或布尔值的变量或参数进行显式类型声明 + '@typescript-eslint/no-var-requires': 'off', // 不允许在 import 语句中使用 require 语句 + '@typescript-eslint/no-empty-function': 'off', // 禁止空函数 + '@typescript-eslint/no-use-before-define': 'off', // 禁止在变量定义之前使用它们 + '@typescript-eslint/ban-ts-comment': 'off', // 禁止 @ts- 使用注释或要求在指令后进行描述 + '@typescript-eslint/no-non-null-assertion': 'off', // 不允许使用后缀运算符的非空断言(!) + '@typescript-eslint/explicit-module-boundary-types': 'off', // 要求导出函数和类的公共类方法的显式返回和参数类型 + + // vue (https://eslint.vuejs.org/rules) + 'vue/no-v-html': 'off', // 禁止使用 v-html + 'vue/script-setup-uses-vars': 'error', // 防止 + + diff --git a/frontend/lint-staged.config.js b/frontend/lint-staged.config.js new file mode 100644 index 0000000..2f579f0 --- /dev/null +++ b/frontend/lint-staged.config.js @@ -0,0 +1,8 @@ +module.exports = { + '*.{js,jsx,ts,tsx}': ['eslint --fix', 'prettier --write'], + '{!(package)*.json,*.code-snippets,.!(browserslist)*rc}': ['prettier --write--parser json'], + 'package.json': ['prettier --write'], + '*.vue': ['eslint --fix', 'prettier --write', 'stylelint --fix'], + '*.{scss,less,styl,html}': ['stylelint --fix', 'prettier --write'], + '*.md': ['prettier --write'], +}; diff --git a/frontend/package.json b/frontend/package.json new file mode 100644 index 0000000..2a01112 --- /dev/null +++ b/frontend/package.json @@ -0,0 +1,109 @@ +{ + "name": "1panel-frontend", + "private": true, + "version": "2.0", + "description": "1Panel 前端", + "scripts": { + "dev": "node --max-old-space-size=8192 ./node_modules/.bin/vite --host", + "serve": "vite", + "build:dev": "vite build --mode development", + "build:test": "vue-tsc --noEmit && vite build --mode test", + "build:pro": "vite build --mode production", + "preview": "vite preview", + "lint:eslint": "eslint --fix --ext .js,.ts,.vue ./src", + "lint:prettier": "prettier --write --loglevel warn \"src/**/*.{js,ts,json,tsx,css,less,scss,vue,html,md}\"", + "lint:stylelint": "stylelint --cache --fix \"**/*.{vue,less,postcss,css,scss}\" --cache --cache-location node_modules/.cache/stylelint/", + "lint:lint-staged": "lint-staged", + "release": "standard-version", + "commit": "git pull && git add -A && git-cz && git push", + "prettier:comment": "自动格式化当前目录下的所有文件", + "prettier": "prettier --write ." + }, + "dependencies": { + "@codemirror/lang-html": "^6.4.10", + "@codemirror/lang-json": "^6.0.2", + "@codemirror/lang-php": "^6.0.2", + "@codemirror/language": "^6.11.3", + "@codemirror/legacy-modes": "^6.5.1", + "@codemirror/theme-one-dark": "^6.1.3", + "@element-plus/icons-vue": "^1.1.4", + "@vue-office/docx": "^1.6.2", + "@vue-office/excel": "^1.7.8", + "@vueuse/core": "^8.9.4", + "@xterm/addon-fit": "^0.10.0", + "@xterm/xterm": "^5.5.0", + "anser": "^2.3.2", + "axios": "^1.7.2", + "codemirror": "^6.0.2", + "crypto-js": "^4.2.0", + "dompurify": "^3.3.1", + "echarts": "^5.5.0", + "element-plus": "2.11.9", + "fit2cloud-ui-plus": "^1.2.4", + "highlight.js": "^11.9.0", + "js-base64": "^3.7.7", + "jsencrypt": "^3.3.2", + "md-editor-v3": "^2.11.3", + "monaco-editor": "^0.53.0", + "nprogress": "^0.2.0", + "pinia": "^2.1.7", + "pinia-plugin-persistedstate": "^1.6.1", + "punycode": "^2.3.1", + "qs": "^6.12.1", + "screenfull": "^6.0.2", + "uuid": "^10.0.0", + "vue": "^3.4.27", + "vue-clipboard3": "^2.0.0", + "vue-codemirror": "^6.1.1", + "vue-demi": "^0.14.6", + "vue-i18n": "^10.0.5", + "vue-router": "^4.5.1" + }, + "devDependencies": { + "@types/node": "^20.14.8", + "@types/uuid": "^10.0.0", + "@typescript-eslint/eslint-plugin": "^5.62.0", + "@typescript-eslint/parser": "^5.22.0", + "@vitejs/plugin-vue": "^6.0.1", + "@vitejs/plugin-vue-jsx": "^5.1.1", + "autoprefixer": "^10.4.7", + "commitizen": "^4.2.4", + "eslint": "^8.57.0", + "eslint-config-prettier": "^8.10.0", + "eslint-plugin-prettier": "^4.2.1", + "eslint-plugin-vue": "^8.7.1", + "lint-staged": "^12.4.2", + "postcss": "^8.4.31", + "postcss-html": "^1.4.1", + "prettier": "^2.6.2", + "rollup-plugin-visualizer": "^5.5.4", + "sass": "^1.83.0", + "standard-version": "^9.5.0", + "stylelint": "^15.10.1", + "tailwindcss": "^3.4.1", + "typescript": "^4.5.4", + "unplugin-auto-import": "^0.16.4", + "unplugin-vue-components": "^0.25.0", + "unplugin-vue-define-options": "^3.0.0-beta.23", + "vite": "^7.1.5", + "vite-plugin-compression": "^0.5.1", + "vite-plugin-eslint": "^1.8.1", + "vite-plugin-html": "^3.2.2", + "vite-plugin-monaco-editor": "^1.1.0", + "vite-plugin-vue-setup-extend": "^0.4.0", + "vite-svg-loader": "^5.1.0", + "vue-tsc": "^0.29.8" + }, + "trustedDependencies": [ + "@vue-office/docx", + "@vue-office/excel" + ], + "overrides": { + "esbuild": "npm:esbuild-wasm@latest" + }, + "config": { + "commitizen": { + "path": "node_modules/cz-git" + } + } +} diff --git a/frontend/postcss.config.cjs b/frontend/postcss.config.cjs new file mode 100644 index 0000000..67cdf1a --- /dev/null +++ b/frontend/postcss.config.cjs @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/frontend/public/favicon.png b/frontend/public/favicon.png new file mode 100644 index 0000000..c80e8f0 Binary files /dev/null and b/frontend/public/favicon.png differ diff --git a/frontend/src/App.vue b/frontend/src/App.vue new file mode 100644 index 0000000..6876dc8 --- /dev/null +++ b/frontend/src/App.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/frontend/src/api/config/service-port.ts b/frontend/src/api/config/service-port.ts new file mode 100644 index 0000000..4ddd84b --- /dev/null +++ b/frontend/src/api/config/service-port.ts @@ -0,0 +1,3 @@ +// * 后端微服务端口名 +export const PORT1 = '9999'; +export const PORT2 = '/hooks'; diff --git a/frontend/src/api/helper/axios-cancel.ts b/frontend/src/api/helper/axios-cancel.ts new file mode 100644 index 0000000..0d32041 --- /dev/null +++ b/frontend/src/api/helper/axios-cancel.ts @@ -0,0 +1,63 @@ +import axios, { AxiosRequestConfig, Canceler } from 'axios'; +import { isFunction } from '@vueuse/core'; +import qs from 'qs'; + +// * 声明一个 Map 用于存储每个请求的标识 和 取消函数 +let pendingMap = new Map(); + +// * 序列化参数 +export const getPendingUrl = (config: AxiosRequestConfig) => + [config.method, config.url, qs.stringify(config.data), qs.stringify(config.params)].join('&'); + +export class AxiosCanceler { + /** + * @description: 添加请求 + * @param {Object} config + * @return void + */ + addPending(config: AxiosRequestConfig) { + // * 在请求开始前,对之前的请求做检查取消操作 + this.removePending(config); + const url = getPendingUrl(config); + config.cancelToken = + config.cancelToken || + new axios.CancelToken((cancel) => { + if (!pendingMap.has(url)) { + // 如果 pending 中不存在当前请求,则添加进去 + pendingMap.set(url, cancel); + } + }); + } + + /** + * @description: 移除请求 + * @param {Object} config + */ + removePending(config: AxiosRequestConfig) { + const url = getPendingUrl(config); + + if (pendingMap.has(url)) { + // 如果在 pending 中存在当前请求标识,需要取消当前请求,并且移除 + const cancel = pendingMap.get(url); + isFunction(cancel) && cancel(); + pendingMap.delete(url); + } + } + + /** + * @description: 清空所有pending + */ + removeAllPending() { + pendingMap.forEach((cancel) => { + isFunction(cancel) && cancel(); + }); + pendingMap.clear(); + } + + /** + * @description: 重置 + */ + reset(): void { + pendingMap = new Map(); + } +} diff --git a/frontend/src/api/helper/check-status.ts b/frontend/src/api/helper/check-status.ts new file mode 100644 index 0000000..1c18cc2 --- /dev/null +++ b/frontend/src/api/helper/check-status.ts @@ -0,0 +1,26 @@ +import i18n from '@/lang'; +import router from '@/routers'; +import { MsgError } from '@/utils/message'; +import { GlobalStore } from '@/store'; +const globalStore = GlobalStore(); + +export const checkStatus = (status: number, msg: string): void => { + switch (status) { + case 400: + MsgError(msg ? msg : i18n.global.t('commons.res.paramError')); + break; + case 404: + MsgError(msg ? msg : i18n.global.t('commons.res.notFound')); + break; + case 403: + globalStore.setLogStatus(false); + router.replace({ name: 'entrance', params: { code: globalStore.entrance } }); + MsgError(msg ? msg : i18n.global.t('commons.res.forbidden')); + break; + case 500: + MsgError(msg ? msg : i18n.global.t('commons.res.serverError')); + break; + default: + MsgError(msg ? msg : i18n.global.t('commons.res.commonError')); + } +}; diff --git a/frontend/src/api/index.ts b/frontend/src/api/index.ts new file mode 100644 index 0000000..13ab634 --- /dev/null +++ b/frontend/src/api/index.ts @@ -0,0 +1,168 @@ +import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse, InternalAxiosRequestConfig } from 'axios'; +import { ResultData } from '@/api/interface'; +import { ResultEnum } from '@/enums/http-enum'; +import { checkStatus } from './helper/check-status'; +import router from '@/routers'; +import { GlobalStore } from '@/store'; +import { MsgError } from '@/utils/message'; +import { Base64 } from 'js-base64'; +import i18n from '@/lang'; +import { changeToLocal } from '@/utils/node'; + +const globalStore = GlobalStore(); + +const config = { + baseURL: import.meta.env.VITE_API_URL as string, + timeout: ResultEnum.TIMEOUT as number, + withCredentials: true, +}; + +class RequestHttp { + service: AxiosInstance; + public constructor(config: AxiosRequestConfig) { + this.service = axios.create(config); + this.service.interceptors.request.use( + (config: AxiosRequestConfig) => { + let language = globalStore.language; + config.headers = { + 'Accept-Language': language, + ...config.headers, + }; + if (config.headers.CurrentNode == undefined) { + config.headers.CurrentNode = encodeURIComponent(globalStore.currentNode); + } else { + config.headers.CurrentNode = encodeURIComponent(String(config.headers.CurrentNode)); + } + if ( + config.url === '/core/auth/login' || + config.url === '/core/auth/mfalogin' || + config.url === '/core/auth/passkey/begin' || + config.url === '/core/auth/passkey/finish' + ) { + let entrance = Base64.encode(globalStore.entrance); + config.headers.EntranceCode = entrance; + } + return { + ...config, + } as InternalAxiosRequestConfig; + }, + (error: AxiosError) => { + return Promise.reject(error); + }, + ); + + this.service.interceptors.response.use( + (response: AxiosResponse) => { + const { data } = response; + if (data.code == ResultEnum.OVERDUE || data.code == ResultEnum.FORBIDDEN) { + globalStore.setLogStatus(false); + router.push({ + name: 'entrance', + params: { code: globalStore.entrance }, + }); + return Promise.reject(data); + } + if (data.code == ResultEnum.EXPIRED) { + router.push({ name: 'Expired' }); + return; + } + if (data.code == ResultEnum.ERRXPACK) { + globalStore.isProductPro = false; + window.location.reload(); + return Promise.reject(data); + } + if (data.code == ResultEnum.NodeUnBind) { + changeToLocal(); + window.location.reload(); + return; + } + if (data.code == ResultEnum.ERRGLOBALLOADDING) { + globalStore.setGlobalLoading(true); + globalStore.setLoadingText(data.message); + return; + } else { + if (globalStore.isLoading) { + globalStore.setGlobalLoading(false); + } + } + if (data.code == ResultEnum.ERRAUTH) { + return data; + } + if (data.code && data.code !== ResultEnum.SUCCESS) { + if (data.message.toLowerCase().indexOf('operation not permitted') !== -1) { + MsgError(i18n.global.t('license.tamperHelper')); + return Promise.reject(data); + } + MsgError(data.message); + return Promise.reject(data); + } + return data; + }, + async (error: AxiosError) => { + const { response } = error; + + if (error.message.indexOf('timeout') !== -1) MsgError(i18n.global.t('commons.msg.requestTimeout')); + if (response) { + switch (response.status) { + case 313: + router.push({ name: 'Expired' }); + return; + case 500: + case 502: + case 524: + case 407: + checkStatus( + response.status, + response.data && response.data['message'] ? response.data['message'] : '', + ); + return Promise.reject(error); + default: + return; + } + } + if (!window.navigator.onLine) router.replace({ path: '/500' }); + return Promise.reject(error); + }, + ); + } + + get(url: string, params?: object, _object = {}): Promise> { + return this.service.get(url, { params, ..._object }); + } + post(url: string, params?: object, timeout?: number, headers?: object): Promise> { + let config = { + baseURL: import.meta.env.VITE_API_URL as string, + timeout: timeout ? timeout : (ResultEnum.TIMEOUT as number), + withCredentials: true, + headers: headers, + }; + if (headers) { + config.headers = headers; + } + return this.service.post(url, params, config); + } + postLocalNode(url: string, params?: object, timeout?: number): Promise> { + return this.service.post(url, params, { + baseURL: import.meta.env.VITE_API_URL as string, + timeout: timeout ? timeout : (ResultEnum.TIMEOUT as number), + withCredentials: true, + headers: { + CurrentNode: 'local', + }, + }); + } + put(url: string, params?: object, _object = {}): Promise> { + return this.service.put(url, params, _object); + } + delete(url: string, params?: any, _object = {}): Promise> { + return this.service.delete(url, { params, ..._object }); + } + download(url: string, params?: object, _object = {}): Promise { + return this.service.post(url, params, _object); + } + upload(url: string, params: object = {}, config?: AxiosRequestConfig): Promise> { + return this.service.post(url, params, config); + } +} + +export default new RequestHttp(config); diff --git a/frontend/src/api/interface/ai.ts b/frontend/src/api/interface/ai.ts new file mode 100644 index 0000000..3c10cdf --- /dev/null +++ b/frontend/src/api/interface/ai.ts @@ -0,0 +1,238 @@ +import { ReqPage } from '.'; + +export namespace AI { + export interface OllamaModelInfo { + id: number; + name: string; + size: string; + from: string; + logFileExist: boolean; + status: string; + message: string; + createdAt: Date; + } + export interface OllamaModelDropInfo { + id: number; + name: string; + } + export interface OllamaModelSearch extends ReqPage { + info: string; + } + + export interface Info { + cudaVersion: string; + driverVersion: string; + type: string; + gpu: GPU[]; + } + export interface GPU { + index: number; + productName: string; + persistenceMode: string; + busID: string; + displayActive: string; + ecc: string; + fanSpeed: string; + + temperature: string; + performanceState: string; + powerDraw: string; + maxPowerLimit: string; + memUsed: string; + memTotal: string; + gpuUtil: string; + computeMode: string; + migMode: string; + processes: Process[]; + } + export interface Process { + pid: string; + type: string; + processName: string; + usedMemory: string; + } + + export interface XpuInfo { + type: string; + driverVersion: string; + xpu: Xpu[]; + } + + interface Xpu { + basic: Basic; + stats: Stats; + processes: XpuProcess[]; + } + + interface Basic { + deviceID: number; + deviceName: string; + vendorName: string; + driverVersion: string; + memory: string; + freeMemory: string; + pciBdfAddress: string; + } + + interface Stats { + power: string; + frequency: string; + temperature: string; + memoryUsed: string; + memoryUtil: string; + } + + interface XpuProcess { + pid: number; + command: string; + shr: string; + memory: string; + } + + export interface BindDomain { + domain: string; + sslID: number; + ipList: string; + appInstallID: number; + websiteID?: number; + } + + export interface BindDomainReq { + appInstallID: number; + } + + export interface BindDomainRes { + domain: string; + sslID: number; + allowIPs: string[]; + websiteID?: number; + connUrl: string; + acmeAccountID: number; + } + + export interface Environment { + key: string; + value: string; + } + + export interface Volume { + source: string; + target: string; + } + + export interface McpServer { + id: number; + name: string; + status: string; + baseUrl: string; + ssePath: string; + command: string; + port: number; + message: string; + createdAt?: string; + containerName: string; + environments: Environment[]; + volumes: Volume[]; + dir?: string; + hostIP: string; + protocol: string; + url: string; + outputTransport: string; + streamableHttpPath: string; + type: string; + } + + export interface McpServerSearch extends ReqPage { + name: string; + } + + export interface McpServerDelete { + id: number; + } + + export interface McpServerOperate { + id: number; + operate: string; + } + + export interface McpBindDomain { + domain: string; + sslID: number; + ipList: string; + } + + export interface McpDomainRes { + domain: string; + sslID: number; + acmeAccountID: number; + allowIPs: string[]; + websiteID?: number; + connUrl: string; + } + + export interface McpBindDomainUpdate { + websiteID: number; + sslID: number; + ipList: string; + } + + export interface ImportMcpServer { + name: string; + command: string; + ssePath: string; + containerName: string; + environments: Environment[]; + } + + export interface ExposedPort { + hostPort: number; + containerPort: number; + hostIP: string; + } + + export interface Environment { + key: string; + value: string; + } + export interface Volume { + source: string; + target: string; + } + + export interface ExtraHosts { + hostname: string; + ip: string; + } + + export interface TensorRTLLM { + id?: number; + name: string; + containerName: string; + version: string; + modelDir: string; + status?: string; + message?: string; + createdAt?: string; + exposedPorts?: ExposedPort[]; + environments?: Environment[]; + volumes?: Volume[]; + extraHosts?: ExtraHosts[]; + } + + export interface TensorRTLLMDTO extends TensorRTLLM { + dir?: string; + } + + export interface TensorRTLLMSearch extends ReqPage { + name: string; + } + + export interface TensorRTLLMDelete { + id: number; + } + + export interface TensorRTLLMOperate { + id: number; + operate: string; + } +} diff --git a/frontend/src/api/interface/alert.ts b/frontend/src/api/interface/alert.ts new file mode 100644 index 0000000..ad168f2 --- /dev/null +++ b/frontend/src/api/interface/alert.ts @@ -0,0 +1,167 @@ +import { CommonModel, ReqPage } from '@/api/interface'; + +export namespace Alert { + export interface AlertInfo extends CommonModel { + type: string; + subType: string; + cycle: number; + count: number; + interval: number; + method: string; + title: string; + project: string; + status: string; + sendCount: number; + sendMethod: string[]; + advancedParams: string; + } + + export interface AlertDetail { + type: string; + licenseId: string; + title: string; + project: string; + method: string; + params: string; + } + + export interface AlertUpdateStatusReq { + id: number; + status: string; + } + + export interface AlertLog extends CommonModel { + alertId: number; + alertDetail: AlertDetail; + alertRule: AlertInfo; + count: number; + message: string; + status: string; + method: string; + } + + export interface DisksDTO { + path: string; + type: string; + device: string; + total: number; + free: number; + used: number; + usedPercent: number; + inodesTotal: number; + inodesUsed: number; + inodesFree: number; + inodesUsedPercent: number; + } + + export interface AlertSearch extends ReqPage { + type: string; + status: string; + method: string; + orderBy: string; + order: string; + } + + export interface AlertCreateReq { + type: string; + cycle: number; + count: number; + interval: number; + method: string; + title: string; + project: string; + status: string; + sendCount: number; + } + + export interface AlertUpdateReq extends CommonModel { + type: string; + cycle: number; + count: number; + interval: number; + method: string; + title: string; + project: string; + status: string; + sendCount: number; + } + + export interface DelReq { + id: number; + } + + export interface AlertLogSearch extends ReqPage { + status: string; + count: number; + } + + export interface AlertLogId { + id: number; + } + + export interface ClamsDTO { + id: number; + name: string; + status: string; + path: string; + createdAt: string; + updatedAt: string; + } + + export interface CronJobDTO { + id: number; + name: string; + type: string; + status: string; + createdAt: string; + updatedAt: string; + } + + export interface CronJobReq { + name: string; + type: string; + status: string; + } + + export interface AlertSmsReq { + licenseId: string; + } + + export interface AlertSmsDTO { + name: string; + total: number; + used: number; + } + + export interface AlertConfigInfo extends CommonModel { + type: string; + title: string; + config: string; + status: string; + } + + export interface AlertConfigUpdateReq { + id: number; + type: string; + title: string; + config: string; + status: string; + } + + export interface AlertConfigTest { + port: number; + host: string; + sender: string; + userName: string; + password: string; + displayName: string; + encryption: string; + recipient: string; + } + + export interface CommonConfig { + isOffline?: string; + alertDailyNum?: number; + alertSendTimeRange?: string; + } +} diff --git a/frontend/src/api/interface/app.ts b/frontend/src/api/interface/app.ts new file mode 100644 index 0000000..5ca77de --- /dev/null +++ b/frontend/src/api/interface/app.ts @@ -0,0 +1,363 @@ +import { ReqPage, CommonModel } from '.'; + +export namespace App { + export interface App extends CommonModel { + name: string; + icon?: string; + key: string; + tags: Tag[]; + shortDescZh: string; + shortDescEn: string; + description: string; + author: string; + source: string; + type: string; + status: string; + limit: number; + website?: string; + github?: string; + readme: string; + batchInstallSupport: boolean; + } + + interface Locale { + zh: string; + en: string; + 'zh-Hant': string; + ja: string; + ms: string; + 'pt-br': string; + ru: string; + ko: string; + tr: string; + 'es-es': string; + } + + export interface AppDTO extends App { + versions: string[]; + installed: boolean; + architectures: string; + } + + export interface Tag { + key: string; + name: string; + sort: number; + } + + export interface AppItem { + name: string; + key: string; + id: number; + description: string; + status: string; + installed: boolean; + limit: number; + tags: string[]; + gpuSupport: boolean; + recommend: number; + batchInstallSupport: boolean; + } + + export interface AppResPage { + total: number; + items: AppItem[]; + } + + export interface AppUpdateRes { + version: string; + canUpdate: boolean; + } + + export interface AppDetail extends CommonModel { + appId: number; + icon: string; + version: string; + readme: string; + params: AppParams; + dockerCompose: string; + image: string; + hostMode?: boolean; + memoryRequired: number; + architectures: string; + gpuSupport: boolean; + } + + export interface AppReq extends ReqPage { + name?: string; + tags?: string[]; + type?: string; + recommend?: boolean; + resource?: string; + showCurrentArch?: boolean; + } + + export interface AppParams { + formFields: FromField[]; + } + + export interface FromField { + type: string; + labelZh: string; + labelEn: string; + required: boolean; + default: any; + envKey: string; + key?: string; + values?: ServiceParam[]; + child?: FromFieldChild; + params?: FromParam[]; + multiple?: boolean; + allowCreate?: boolean; + label: Locale; + description: Locale; + } + + export interface FromFieldChild extends FromField { + services: App.AppService[]; + } + + export interface FromParam { + type: string; + key: string; + value: string; + envKey: string; + } + + export interface ServiceParam { + label: ''; + value: ''; + from?: ''; + } + + export interface AppInstall { + appDetailId: number; + params: any; + taskID: string; + name: string; + } + + export interface AppInstallSearch extends ReqPage { + name?: string; + tags?: string[]; + update?: boolean; + unused?: boolean; + sync?: boolean; + } + export interface ChangePort { + key: string; + name: string; + port: number; + } + + export interface AppInstalled extends CommonModel { + name: string; + appID: number; + appDetailId: string; + env: string; + status: string; + description: string; + message: string; + icon: string; + canUpdate: boolean; + path: string; + httpPort?: number; + httpsPort?: number; + favorite: boolean; + app: App; + webUI: string; + appKey?: string; + serviceName: string; + appStatus?: string; + appType?: string; + version?: string; + } + + export interface AppInstalledInfo { + id: number; + name: string; + version: string; + status: string; + message: string; + httpPort: number; + container: string; + env: { [key: string]: string }; + appKey: string; + } + + export interface AppInstallDto { + id: number; + name: string; + appID: number; + appDetailID: number; + version: string; + status: string; + message: string; + httpPort: number; + httpsPort: number; + path: string; + canUpdate: boolean; + icon: string; + appName: string; + ready: number; + total: number; + appKey: string; + appType: string; + appStatus: string; + isEdit: boolean; + dockerCompose: string; + app: App.AppDetail; + linkDB: boolean; + } + + export interface AppInstalledInfo { + id: number; + key: string; + name: string; + } + + export interface CheckInstalled { + name: string; + version: string; + isExist: boolean; + app: string; + status: string; + createdAt: string; + lastBackupAt: string; + appInstallId: number; + containerName: string; + installPath: string; + httpPort: number; + httpsPort: number; + websiteDir: string; + } + + export interface DatabaseConnInfo { + status: string; + username: string; + password: string; + privilege: boolean; + containerName: string; + serviceName: string; + systemIP: string; + port: number; + } + export interface AppInstallResource { + type: string; + name: string; + } + + export interface AppInstalledOp { + installId: number; + operate: string; + backupId?: number; + detailId?: number; + forceDelete?: boolean; + deleteBackup?: boolean; + deleteImage?: boolean; + taskID?: string; + } + + export interface AppInstalledSearch extends ReqPage { + type: string; + unused?: boolean; + all?: boolean; + } + + export interface AppService { + label: string; + value: string; + config?: Object; + from?: string; + status: string; + } + + export interface VersionDetail { + version: string; + detailId: number; + } + + export interface InstallParams { + labelZh: string; + labelEn: string; + value: any; + edit: boolean; + key: string; + rule: string; + type: string; + values?: any; + showValue?: string; + required?: boolean; + multiple?: boolean; + label: Locale; + } + + export interface AppConfig { + params: InstallParams[]; + rawCompose?: string; + cpuQuota: number; + memoryLimit: number; + memoryUnit: string; + containerName: string; + allowPort: boolean; + dockerCompose: string; + hostMode?: boolean; + type: string; + webUI: string; + specifyIP: string; + restartPolicy: string; + } + + export interface IgnoredApp { + name: string; + detailID: number; + version: string; + scope: string; + } + + export interface AppUpdateVersionReq { + appInstallID: number; + updateVersion?: string; + } + + export interface AppIgnoreReq { + appID: number; + appDetailID: number; + scope: string; + } + + export interface CancelAppIgnore { + id: number; + } + + export interface AppStoreSync { + taskID: string; + } + + export interface AppConfigUpdate { + installID: number; + webUI: string; + } + + export interface AppStoreConfig { + uninstallDeleteImage: string; + uninstallDeleteBackup: string; + upgradeBackup: string; + } + + export interface AppStoreConfigUpdate { + scope: string; + status: string; + } + + export interface CustomAppStoreConfig { + status: string; + imagePrefix: string; + } + + export interface InstallAppToNodes extends AppInstall { + nodes: string[]; + appKey: string; + version: string; + } +} diff --git a/frontend/src/api/interface/auth.ts b/frontend/src/api/interface/auth.ts new file mode 100644 index 0000000..5f19dd4 --- /dev/null +++ b/frontend/src/api/interface/auth.ts @@ -0,0 +1,45 @@ +export namespace Login { + export interface ReqLoginForm { + name: string; + password: string; + captcha: string; + captchaID: string; + authMethod: string; + } + export interface MFALoginForm { + name: string; + password: string; + code: string; + authMethod: string; + } + export interface ResLogin { + name: string; + token: string; + mfaStatus: string; + } + export interface PasskeyBeginResponse { + sessionId: string; + publicKey: Record; + } + export interface ResCaptcha { + imagePath: string; + captchaID: string; + captchaLength: number; + } + export interface ResAuthButtons { + [propName: string]: any; + } + + export interface LoginSetting { + isDemo: boolean; + isIntl: boolean; + isFxplay: boolean; + language: string; + menuTabs: string; + panelName: string; + theme: string; + isOffLine: boolean; + needCaptcha: boolean; + passkeySetting: boolean; + } +} diff --git a/frontend/src/api/interface/backup.ts b/frontend/src/api/interface/backup.ts new file mode 100644 index 0000000..2532e3e --- /dev/null +++ b/frontend/src/api/interface/backup.ts @@ -0,0 +1,106 @@ +import { ReqPage } from '.'; + +export namespace Backup { + export interface SearchWithType extends ReqPage { + type: string; + name: string; + } + export interface BackupOption { + id: number; + name: string; + type: string; + } + export interface BackupInfo { + id: number; + name: string; + type: string; + isPublic: boolean; + accessKey: string; + bucket: string; + credential: string; + rememberAuth: boolean; + backupPath: string; + bucketInput: boolean; + vars: string; + varsJson: object; + createdAt: Date; + } + export interface CheckResult { + isOk: boolean; + msg: string; + token: string; + } + export interface ClientInfo { + client_id: string; + client_secret: string; + redirect_uri: string; + } + export interface BackupOperate { + id: number; + type: string; + name: string; + isPublic: boolean; + accessKey: string; + bucket: string; + credential: string; + backupPath: string; + vars: string; + } + export interface RecordDownload { + downloadAccountID: number; + fileDir: string; + fileName: string; + } + export interface RecordInfo { + id: number; + createdAt: Date; + accountType: string; + accountName: string; + downloadAccountID: number; + fileDir: string; + fileName: string; + size: number; + } + export interface ForBucket { + type: string; + isPublic: boolean; + accessKey: string; + credential: string; + vars: string; + } + export interface SearchBackupRecord extends ReqPage { + type: string; + name: string; + detailName: string; + } + export interface SearchForSize extends ReqPage { + type: string; + name: string; + detailName: string; + info: string; + cronjobID: number; + } + export interface RecordFileSize extends ReqPage { + id: number; + size: number; + } + export interface SearchBackupRecordByCronjob extends ReqPage { + cronjobID: number; + } + export interface Backup { + type: string; + name: string; + detailName: string; + secret: string; + taskID: string; + } + export interface Recover { + downloadAccountID: number; + type: string; + name: string; + detailName: string; + file: string; + secret: string; + taskID: string; + } +} diff --git a/frontend/src/api/interface/command.ts b/frontend/src/api/interface/command.ts new file mode 100644 index 0000000..baa1449 --- /dev/null +++ b/frontend/src/api/interface/command.ts @@ -0,0 +1,16 @@ +export namespace Command { + export interface CommandInfo { + id: number; + type: string; + name: string; + groupID: number; + command: string; + } + export interface CommandOperate { + id: number; + type: string; + name: string; + groupID: number; + command: string; + } +} diff --git a/frontend/src/api/interface/container.ts b/frontend/src/api/interface/container.ts new file mode 100644 index 0000000..def8dd3 --- /dev/null +++ b/frontend/src/api/interface/container.ts @@ -0,0 +1,404 @@ +import { ReqPage } from '.'; + +export namespace Container { + export interface ContainerOperate { + taskID: string; + names: Array; + operation: string; + } + export interface ContainerRename { + name: string; + newName: string; + } + export interface ContainerCommit { + containerID: string; + containerName: string; + newImageName: string; + comment: string; + author: string; + pause: boolean; + taskID: string; + } + export interface ContainerSearch extends ReqPage { + name: string; + state: string; + filters: string; + orderBy: string; + order: string; + } + export interface ContainerStatus { + created: number; + running: number; + paused: number; + restarting: number; + removing: number; + exited: number; + dead: number; + + containerCount: number; + composeCount: number; + composeTemplateCount: number; + imageCount: number; + networkCount: number; + volumeCount: number; + repoCount: number; + + containerUsage: number; + containerReclaimable: number; + imageUsage: number; + imageReclaimable: number; + volumeUsage: number; + volumeReclaimable: number; + buildCacheUsage: number; + buildCacheReclaimable: number; + } + export interface ContainerOption { + name: string; + state: string; + } + export interface ResourceLimit { + cpu: number; + memory: number; + } + export interface ContainerHelper { + taskID: string; + name: string; + image: string; + imageInput: boolean; + forcePull: boolean; + networks: Array; + hostname: string; + domainName: string; + dns: Array; + cmdStr: string; + entrypointStr: string; + memoryItem: number; + cmd: Array; + workingDir: string; + user: string; + openStdin: boolean; + tty: boolean; + entrypoint: Array; + publishAllPorts: boolean; + exposedPorts: Array; + nanoCPUs: number; + cpuShares: number; + memory: number; + volumes: Array; + privileged: boolean; + autoRemove: boolean; + labels: Array; + env: Array; + restartPolicy: string; + } + export interface ContainerUpgrade { + taskID: string; + names: Array; + image: string; + forcePull: boolean; + } + export interface Port { + host: string; + hostIP: string; + containerPort: string; + hostPort: string; + protocol: string; + } + export interface Volume { + type: string; + sourceDir: string; + containerDir: string; + mode: string; + } + export interface ContainerInfo { + containerID: string; + name: string; + imageName: string; + createTime: string; + state: string; + runTime: string; + network: Array; + ports: Array; + isFromApp: boolean; + isFromCompose: boolean; + + hasLoad: boolean; + cpuPercent: number; + memoryPercent: number; + } + export interface ContainerNetwork { + network: string; + ipv4: string; + ipv6: string; + macAddr: string; + } + export interface ContainerInfo { + name: string; + state: string; + } + export interface ContainerItemStats { + sizeRw: number; + sizeRootFs: number; + + containerUsage: number; + containerReclaimable: number; + imageUsage: number; + imageReclaimable: number; + volumeUsage: number; + volumeReclaimable: number; + buildCacheUsage: number; + buildCacheReclaimable: number; + } + export interface ContainerListStats { + containerID: string; + cpuTotalUsage: number; + systemUsage: number; + cpuPercent: number; + percpuUsage: number; + memoryCache: number; + memoryUsage: number; + memoryLimit: number; + memoryPercent: number; + } + export interface ContainerStats { + cpuPercent: number; + memory: number; + cache: number; + ioRead: number; + ioWrite: number; + networkRX: number; + networkTX: number; + shotTime: Date; + } + export interface ContainerInspect { + id: string; + type: string; + detail: string; + } + export interface ContainerPrune { + pruneType: string; + withTagAll: boolean; + } + export interface ContainerPruneReport { + deletedNumber: number; + spaceReclaimed: number; + } + export interface Options { + option: string; + } + + export interface ImageSearch extends ReqPage { + name: string; + orderBy: string; + order: string; + } + export interface ImageInfo { + id: string; + createdAt: Date; + name: string; + tags: Array; + size: string; + isUsed: boolean; + } + export interface ImageBuild { + taskID: string; + from: string; + name: string; + dockerfile: string; + tags: Array; + args: Array; + } + export interface ImagePull { + taskID: string; + repoID: number; + imageName: Array; + } + export interface ImageTag { + sourceID: string; + tags: Array; + } + export interface ImagePush { + taskID: string; + repoID: number; + tagName: string; + } + export interface ImageLoad { + taskID: string; + paths: Array; + } + export interface ImageSave { + taskID: string; + tagName: string; + path: string; + name: string; + } + + export interface NetworkInfo { + id: string; + name: string; + isSystem: boolean; + labels: Array; + driver: string; + ipamDriver: string; + subnet: string; + gateway: string; + createdAt: string; + attachable: string; + expand: boolean; + } + export interface NetworkCreate { + name: string; + labels: Array; + options: Array; + driver: string; + subnet: string; + gateway: string; + ipRange: string; + } + + export interface VolumeInfo { + name: string; + labels: Array; + driver: string; + mountpoint: string; + createdAt: string; + } + export interface VolumeCreate { + name: string; + driver: string; + options: Array; + labels: Array; + } + + export interface RepoCreate { + name: string; + downloadUrl: string; + protocol: string; + username: string; + password: string; + auth: boolean; + } + export interface RepoUpdate { + id: number; + downloadUrl: string; + protocol: string; + username: string; + password: string; + auth: boolean; + } + export interface RepoInfo { + id: number; + createdAt: Date; + name: string; + downloadUrl: string; + protocol: string; + username: string; + password: string; + auth: boolean; + } + export interface RepoOptions { + id: number; + name: string; + downloadUrl: string; + } + + export interface ComposeInfo { + name: string; + createdAt: string; + createdBy: string; + containerCount: number; + runningCount: number; + configFile: string; + workdir: string; + path: string; + containers: Array; + expand: boolean; + env: string; + } + export interface ComposeContainer { + name: string; + createTime: string; + containerID: string; + state: string; + } + export interface ComposeCreate { + taskID: string; + name: string; + from: string; + file: string; + path: string; + template: number; + env: string; + pullImage?: boolean; + } + export interface ComposeOperation { + name: string; + operation: string; + path: string; + withFile: boolean; + force: boolean; + } + export interface ComposeUpdate { + name: string; + path: string; + content: string; + env: string; + createdBy: string; + } + + export interface TemplateCreate { + name: string; + description: string; + content: string; + } + export interface TemplateUpdate { + id: number; + description: string; + content: string; + } + export interface TemplateInfo { + id: number; + createdAt: Date; + name: string; + description: string; + content: string; + } + + export interface BatchDelete { + names: Array; + } + + export interface DaemonJsonUpdateByFile { + file: string; + } + export interface DockerStatus { + isActive: boolean; + isExist: boolean; + } + export interface DaemonJsonConf { + isSwarm: boolean; + isExist: boolean; + isActive: boolean; + version: string; + registryMirrors: Array; + insecureRegistries: Array; + liveRestore: boolean; + iptables: boolean; + cgroupDriver: string; + + ipv6: boolean; + fixedCidrV6: string; + ip6Tables: boolean; + experimental: boolean; + + logMaxSize: string; + logMaxFile: string; + } + + export interface ContainerLogInfo { + container: string; + since: string; + tail: number; + containerType: string; + } +} diff --git a/frontend/src/api/interface/cronjob.ts b/frontend/src/api/interface/cronjob.ts new file mode 100644 index 0000000..a21e442 --- /dev/null +++ b/frontend/src/api/interface/cronjob.ts @@ -0,0 +1,232 @@ +import { ReqPage } from '.'; + +export namespace Cronjob { + export interface Search extends ReqPage { + info: string; + groupIDs: Array; + orderBy?: string; + order?: string; + } + export interface CronjobInfo { + id: number; + name: string; + type: string; + groupID: number; + specCustom: boolean; + spec: string; + specs: Array; + specObjs: Array; + + executor: string; + isExecutorCustom: boolean; + script: string; + scriptMode: string; + isCustom: boolean; + command: string; + inContainer: boolean; + containerName: string; + user: string; + scriptID: number; + appID: string; + website: string; + exclusionRules: string; + ignoreFiles: Array; + dbType: string; + dbName: string; + url: string; + urlItems: Array; + isDir: boolean; + files: Array; + sourceDir: string; + snapshotRule: snapshotRule; + ignoreAppIDs: Array; + withImage: boolean; + + websiteList: Array; + appIdList: Array; + dbNameList: Array; + + sourceAccounts: Array; + downloadAccount: string; + sourceAccountIDs: string; + downloadAccountID: number; + sourceAccountItems: Array; + + retainCopies: number; + ignoreErr: boolean; + retryTimes: number; + timeout: number; + timeoutItem: number; + timeoutUnit: string; + status: string; + secret: string; + hasAlert: boolean; + alertCount: number; + alertTitle: string; + alertMethod: string; + alertMethodItems: Array; + + scopes: string[]; + args: string; + argItems: Array; + } + export interface Item { + val: string; + } + export interface CronjobOperate { + id: number; + name: string; + groupID: number; + type: string; + specCustom: boolean; + spec: string; + specs: Array; + specObjs: Array; + + scriptID: number; + appID: string; + website: string; + exclusionRules: string; + dbType: string; + dbName: string; + url: string; + isDir: boolean; + sourceDir: string; + snapshotRule: snapshotRule; + + //shell + executor: string; + scriptMode: string; + script: string; + command: string; + containerName: string; + user: string; + + sourceAccountIDs: string; + downloadAccountID: number; + retainCopies: number; + retryTimes: number; + timeout: number; + ignoreErr: boolean; + secret: string; + + alertCount: number; + alertTitle: string; + alertMethod: string; + + scopes?: string[]; + args: string; + } + export interface CronjobTrans { + name: string; + type: string; + specCustom: boolean; + spec: string; + group: string; + + executor: string; + scriptMode: string; + script: string; + command: string; + containerName: string; + user: string; + url: string; + + scriptName: string; + apps: Array; + websites: Array; + dbType: string; + dbNames: Array; + + exclusionRules: string; + + isDir: boolean; + sourceDir: string; + + retainCopies: number; + retryTimes: number; + timeout: number; + ignoreErr: boolean; + snapshotRule: string; + secret: string; + + sourceAccounts: Array; + downloadAccount: string; + + alertCount: number; + } + export interface TransHelper { + name: string; + detailName: string; + } + export interface snapshotTransHelper { + withImage: boolean; + ignoreApps: Array; + } + export interface snapshotRule { + withImage: boolean; + ignoreAppIDs: Array; + } + export interface SpecObj { + specType: string; + week: number; + day: number; + hour: number; + minute: number; + second: number; + } + export interface CronjobDelete { + ids: Array; + cleanData: boolean; + cleanRemoteData: boolean; + } + export interface UpdateStatus { + id: number; + status: string; + } + export interface Download { + recordID: number; + backupAccountID: number; + } + export interface ScriptOptions { + id: number; + name: string; + script: string; + } + export interface SearchRecord extends ReqPage { + cronjobID: number; + startTime: Date; + endTime: Date; + status: string; + } + export interface Record { + id: number; + taskID: string; + file: string; + startTime: string; + records: string; + status: string; + message: string; + targetPath: string; + interval: number; + } + + export interface ScriptInfo { + id: number; + name: string; + script: string; + groups: string; + isInteractive: boolean; + groupList: Array; + groupBelong: Array; + description: string; + createdAt: Date; + } + export interface ScriptOperate { + id: number; + name: string; + script: string; + groups: string; + description: string; + } +} diff --git a/frontend/src/api/interface/dashboard.ts b/frontend/src/api/interface/dashboard.ts new file mode 100644 index 0000000..ea733e8 --- /dev/null +++ b/frontend/src/api/interface/dashboard.ts @@ -0,0 +1,159 @@ +export namespace Dashboard { + export interface OsInfo { + os: string; + platform: string; + platformFamily: string; + kernelArch: string; + kernelVersion: string; + + diskSize: number; + } + export interface QuickJump { + id: number; + name: string; + alias: string; + title: string; + detail: string; + recommend: number; + isShow: boolean; + router: string; + } + export interface AppLauncher { + key: string; + icon: string; + limit: number; + shortDescEn: string; + shortDescZh: string; + currentRow: InstallDetail; + + isInstall: boolean; + isRecommend: boolean; + detail: Array; + } + export interface AppLauncherOption { + key: string; + isShow: boolean; + } + export interface InstallDetail { + installID: number; + detailID: string; + name: string; + version: string; + path: string; + status: string; + appType: string; + webUI: string; + httpPort: string; + httpsPort: string; + } + export interface BaseInfo { + hostname: string; + os: string; + platform: string; + platformFamily: string; + platformVersion: string; + prettyDistro: string; + kernelArch: string; + kernelVersion: string; + virtualizationSystem: string; + ipV4Addr: string; + httpProxy: string; + + cpuCores: number; + cpuLogicalCores: number; + cpuModelName: string; + cpuMhz: number; + + currentInfo: CurrentInfo; + quickJump: Array; + } + export interface CurrentInfo { + uptime: number; + timeSinceUptime: string; + procs: number; + + load1: number; + load5: number; + load15: number; + loadUsagePercent: number; + + cpuPercent: Array; + cpuUsedPercent: number; + cpuUsed: number; + cpuTotal: number; + cpuDetailedPercent: Array; // [user, system, nice, idle, iowait, irq, softirq, steal] + + memoryTotal: number; + memoryAvailable: number; + memoryUsed: number; + memoryFree: number; + memoryShard: number; + memoryCache: number; + memoryUsedPercent: number; + swapMemoryTotal: number; + swapMemoryAvailable: number; + swapMemoryUsed: number; + swapMemoryUsedPercent: number; + + ioReadBytes: number; + ioWriteBytes: number; + ioCount: number; + ioReadTime: number; + ioWriteTime: number; + + diskData: Array; + + gpuData: Array; + xpuData: Array; + + topCPUItems?: Array; + topMemItems?: Array; + + netBytesSent: number; + netBytesRecv: number; + + shotTime: Date; + } + export interface Process { + name: string; + pid: number; + percent: number; + memory: number; + cmd: string; + user: string; + } + export interface DiskInfo { + path: string; + type: string; + device: string; + total: number; + free: number; + used: number; + usedPercent: number; + + inodesTotal: number; + inodesUsed: number; + inodesFree: number; + inodesUsedPercent: number; + } + export interface GPUInfo { + index: number; + productName: string; + gpuUtil: string; + temperature: string; + performanceState: string; + powerUsage: string; + memoryUsage: string; + fanSpeed: string; + } + + export interface XPUInfo { + deviceID: number; + deviceName: string; + memory: string; + temperature: string; + memoryUsed: string; + power: string; + memoryUtil: string; + } +} diff --git a/frontend/src/api/interface/database.ts b/frontend/src/api/interface/database.ts new file mode 100644 index 0000000..332d7c9 --- /dev/null +++ b/frontend/src/api/interface/database.ts @@ -0,0 +1,362 @@ +import { ReqPage } from '.'; + +export namespace Database { + export interface SearchDBWithPage { + info: string; + database: string; + page: number; + pageSize: number; + orderBy?: string; + order?: string; + } + export interface SearchBackupRecord extends ReqPage { + mysqlName: string; + dbName: string; + } + export interface MysqlDBInfo { + id: number; + createdAt: Date; + name: string; + mysqlName: string; + from: string; + format: string; + username: string; + password: string; + permission: string; + isDelete: string; + description: string; + } + export interface BaseInfo { + name: string; + port: number; + password: string; + remoteConn: boolean; + mysqlKey: string; + containerName: string; + } + export interface DBConfUpdate { + type: string; + database: string; + file: string; + } + export interface MysqlDBCreate { + name: string; + from: string; + database: string; + format: string; + username: string; + password: string; + permission: string; + description: string; + } + + export interface BindUser { + database: string; + db: string; + username: string; + password: string; + permission: string; + } + + export interface MysqlLoadDB { + from: string; + type: string; + database: string; + } + export interface MysqlDBDeleteCheck { + id: number; + type: string; + database: string; + } + + export interface MysqlDBDelete { + id: number; + type: string; + database: string; + forceDelete: boolean; + deleteBackup: boolean; + } + export interface MysqlVariables { + mysqlName: string; + binlog_cache_size: number; + innodb_buffer_pool_size: number; + innodb_log_buffer_size: number; + join_buffer_size: number; + key_buffer_size: number; + max_connections: number; + query_cache_size: number; + read_buffer_size: number; + read_rnd_buffer_size: number; + sort_buffer_size: number; + table_open_cache: number; + thread_cache_size: number; + thread_stack: number; + tmp_table_size: number; + + slow_query_log: string; + long_query_time: number; + } + export interface VariablesUpdate { + type: string; + database: string; + variables: Array; + } + export interface VariablesUpdateHelper { + param: string; + value: any; + } + export interface MysqlStatus { + Aborted_clients: number; + Aborted_connects: number; + Bytes_received: number; + Bytes_sent: number; + Com_commit: number; + Com_rollback: number; + Connections: number; + Created_tmp_disk_tables: number; + Created_tmp_tables: number; + Innodb_buffer_pool_pages_dirty: number; + Innodb_buffer_pool_read_requests: number; + Innodb_buffer_pool_reads: number; + Key_read_requests: number; + Key_reads: number; + Key_write_requests: number; + Key_writes: number; + Max_used_connections: number; + Open_tables: number; + Opened_files: number; + Opened_tables: number; + Qcache_hits: number; + Qcache_inserts: number; + Questions: number; + Select_full_join: number; + Select_range_check: number; + Sort_merge_passes: number; + Table_locks_waited: number; + Threads_cached: number; + Threads_connected: number; + Threads_created: number; + Threads_running: number; + Uptime: number; + Run: number; + File: string; + Position: number; + } + export interface FormatCollationOption { + format: string; + collations: Array; + } + export interface PgLoadDB { + from: string; + type: string; + database: string; + } + export interface PgBind { + name: string; + database: string; + username: string; + password: string; + superUser: boolean; + } + export interface PgChangePrivileges { + name: string; + database: string; + username: string; + superUser: boolean; + } + export interface PostgresqlDBDelete { + id: number; + type: string; + database: string; + forceDelete: boolean; + deleteBackup: boolean; + } + export interface PostgresqlDBDeleteCheck { + id: number; + type: string; + database: string; + } + export interface PostgresqlDBInfo { + id: number; + createdAt: Date; + name: string; + postgresqlName: string; + from: string; + format: string; + username: string; + password: string; + description: string; + } + export interface PostgresqlConfUpdateByFile { + type: string; + database: string; + file: string; + } + export interface PostgresqlDBCreate { + name: string; + from: string; + database: string; + format: string; + username: string; + password: string; + superUser: boolean; + description: string; + } + export interface PostgresqlDBInfo { + id: number; + createdAt: Date; + name: string; + mysqlName: string; + from: string; + format: string; + username: string; + password: string; + superUser: boolean; + isDelete: string; + description: string; + } + export interface ChangeInfo { + id: number; + from: string; + type: string; + database: string; + value: string; + } + + // redis + export interface RedisConfUpdate { + dbType: string; + database: string; + timeout: string; + maxclients: string; + maxmemory: string; + } + export interface RedisConfPersistenceUpdate { + database: string; + type: string; + appendonly: string; + appendfsync: string; + save: string; + dbType: string; + } + export interface RedisStatus { + tcp_port: string; + uptime_in_days: string; + connected_clients: string; + used_memory: string; + used_memory_rss: string; + used_memory_peak: string; + mem_fragmentation_ratio: string; + total_connections_received: string; + total_commands_processed: string; + instantaneous_ops_per_sec: string; + keyspace_hits: string; + keyspace_misses: string; + latest_fork_usec: string; + } + export interface RedisConf { + name: string; + port: number; + timeout: number; + maxclients: number; + requirepass: string; + maxmemory: string; + } + export interface RedisPersistenceConf { + appendonly: string; + appendfsync: string; + save: string; + } + + // remote + export interface DatabaseInfo { + id: number; + createdAt: Date; + name: string; + type: string; + version: string; + from: string; + address: string; + port: number; + initialDB: string; + username: string; + password: string; + + ssl: boolean; + hasCA: boolean; + rootCert: string; + clientKey: string; + clientCert: string; + skipVerify: boolean; + + timeout: number; + description: string; + } + export interface SearchDatabasePage { + info: string; + type: string; + page: number; + pageSize: number; + orderBy?: string; + order?: string; + } + export interface DatabaseOption { + id: number; + from: string; + type: string; + database: string; + version: string; + address: string; + } + export interface DbItem { + id: number; + from: string; + database: string; + name: string; + } + export interface DatabaseCreate { + name: string; + version: string; + from: string; + address: string; + port: number; + username: string; + password: string; + + ssl: boolean; + rootCert: string; + clientKey: string; + clientCert: string; + skipVerify: boolean; + + timeout: number; + description: string; + } + export interface DatabaseUpdate { + id: number; + version: string; + address: string; + port: number; + username: string; + password: string; + + ssl: boolean; + rootCert: string; + clientKey: string; + clientCert: string; + skipVerify: boolean; + + timeout: number; + description: string; + } + export interface DatabaseDelete { + id: number; + forceDelete: boolean; + deleteBackup: boolean; + } + + export interface DBResource { + type: string; + name: string; + } +} diff --git a/frontend/src/api/interface/file.ts b/frontend/src/api/interface/file.ts new file mode 100644 index 0000000..1892a95 --- /dev/null +++ b/frontend/src/api/interface/file.ts @@ -0,0 +1,263 @@ +import { CommonModel, ReqPage } from '.'; +export namespace File { + export interface File extends CommonModel { + path: string; + name: string; + user: string; + group: string; + uid: number; + gid: number; + content: string; + size: number; + isDir: boolean; + isSymlink: boolean; + isHidden: boolean; + linkPath: string; + type: string; + updateTime: string; + modTime: string; + mode: number; + mimeType: string; + dirSize: number; + items: File[]; + extension: string; + itemTotal: number; + favoriteID: number; + remark?: string; + } + + export interface ReqFile extends ReqPage { + path: string; + search?: string; + expand: boolean; + dir?: boolean; + showHidden?: boolean; + containSub?: boolean; + sortBy?: string; + sortOrder?: string; + isDetail?: boolean; + } + + export interface ReqNodeFile extends ReqFile { + node: string; + } + + export interface PreviewContentReq { + path: string; + isDetail?: boolean; + } + + export interface SearchUploadInfo extends ReqPage { + path: string; + } + export interface UploadInfo { + name: string; + size: number; + createdAt: string; + } + + export interface FileTree { + id: string; + name: string; + isDir: boolean; + path: string; + children?: FileTree[]; + } + + export interface FileCreate { + path: string; + isDir: boolean; + mode?: number; + isLink?: boolean; + isSymlink?: boolean; + linkPath?: boolean; + sub?: boolean; + name?: string; + } + + export interface FileDelete { + path: string; + isDir: boolean; + forceDelete: boolean; + } + + export interface FileBatchDelete { + isDir: boolean; + paths: Array; + } + + export interface FileCompress { + files: string[]; + type: string; + dst: string; + name: string; + replace: boolean; + secret: string; + } + + export interface FileDeCompress { + path: string; + dst: string; + type: string; + secret: string; + } + + export interface FileEdit { + path: string; + content: string; + } + + export interface FileRename { + oldName: string; + newName: string; + } + + export interface FileOwner { + path: string; + user: string; + group: string; + sub: boolean; + } + + export interface FileWget { + path: string; + name: string; + url: string; + ignoreCertificate?: boolean; + } + + export interface FileWgetRes { + key: string; + } + + export interface FileKeys { + keys: string[]; + } + + export interface FileMove { + oldPaths: string[]; + newPath: string; + type: string; + } + + export interface FileDownload { + paths: string[]; + name: string; + url: string; + } + + export interface FileChunkDownload { + name: string; + path: string; + } + + export interface DirSizeReq { + path: string; + } + + export interface DirSizeRes { + size: number; + } + + export interface DepthDirSizeRes { + size: number; + path: string; + } + + export interface FilePath { + path: string; + } + + export interface ExistFileInfo { + name: string; + path: string; + size: number; + uploadSize: number; + modTime: string; + isDir: boolean; + } + + export interface RecycleBin { + sourcePath: string; + name: string; + isDir: boolean; + size: number; + deleteTime: string; + rName: string; + from: string; + } + + export interface RecycleBinReduce { + rName: string; + from: string; + name: string; + } + + export interface FileReadByLine { + id?: number; + type: string; + name?: string; + page: number; + pageSize: number; + taskID?: string; + taskType?: string; + taskOperate?: string; + resourceID?: number; + } + + export interface Favorite extends CommonModel { + path: string; + isDir: boolean; + isTxt: boolean; + name: string; + } + + export interface FileRole { + paths: string[]; + mode: number; + user: string; + group: string; + sub: boolean; + } + + export interface FileRemarkUpdate { + path: string; + remark: string; + } + + export interface FileRemarksRes { + remarks: Record; + } + + export interface UserGroupResponse { + users: UserInfo[]; + groups: string[]; + } + export interface UserInfo { + username: string; + group: string; + } + + export interface ConvertFile { + type: string; + path: string; + extension: string; + inputFile: string; + outputFormat: string; + } + + export interface ConvertFileRequest { + files: ConvertFile[]; + outputPath: string; + deleteSource: boolean; + taskID: string; + } + + export interface ConvertLogResponse { + date: string; + type: string; + log: string; + status: string; + message: string; + } +} diff --git a/frontend/src/api/interface/group.ts b/frontend/src/api/interface/group.ts new file mode 100644 index 0000000..728ba94 --- /dev/null +++ b/frontend/src/api/interface/group.ts @@ -0,0 +1,19 @@ +export namespace Group { + export interface GroupInfo { + id: number; + name: string; + type: string; + isDefault: boolean; + isDelete: boolean; + } + export interface GroupCreate { + id: number; + name: string; + type: string; + } + export interface GroupUpdate { + id: number; + name: string; + isDefault: boolean; + } +} diff --git a/frontend/src/api/interface/host-tool.ts b/frontend/src/api/interface/host-tool.ts new file mode 100644 index 0000000..ba7b1b9 --- /dev/null +++ b/frontend/src/api/interface/host-tool.ts @@ -0,0 +1,68 @@ +export namespace HostTool { + export interface HostTool { + type: string; + config: {}; + } + + export interface Supersivor extends HostTool { + configPath: string; + includeDir: string; + logPath: string; + isExist: boolean; + init: boolean; + msg: string; + version: string; + status: string; + ctlExist: boolean; + serviceName: string; + } + + export interface SupersivorConfig { + type: string; + operate: string; + content?: string; + } + + export interface SupersivorConfigRes { + type: string; + content: string; + } + + export interface SupersivorInit { + type: string; + configPath: string; + serviceName: string; + } + + export interface SupersivorProcess { + operate: string; + name: string; + command: string; + user: string; + dir: string; + numprocs: string; + status?: ProcessStatus[]; + autoRestart: string; + autoStart: string; + environment: string; + } + + export interface ProcessStatus { + PID: string; + status: string; + uptime: string; + name: string; + } + + export interface ProcessReq { + operate: string; + name: string; + } + + export interface ProcessFileReq { + operate: string; + name: string; + content?: string; + file: string; + } +} diff --git a/frontend/src/api/interface/host.ts b/frontend/src/api/interface/host.ts new file mode 100644 index 0000000..be8fb25 --- /dev/null +++ b/frontend/src/api/interface/host.ts @@ -0,0 +1,350 @@ +import { CommonModel, ReqPage } from '.'; + +export namespace Host { + export interface HostTree { + id: number; + label: string; + children: Array; + } + export interface TreeNode { + id: number; + label: string; + } + export interface Host extends CommonModel { + name: string; + groupID: number; + groupBelong: string; + addr: string; + port: number; + user: string; + authMode: string; + password: string; + privateKey: string; + passPhrase: string; + rememberPassword: boolean; + description: string; + } + export interface HostOperate { + isLocal: boolean; + id: number; + name: string; + groupID: number; + addr: string; + port: number; + user: string; + authMode: string; + password: string; + privateKey: string; + passPhrase: string; + rememberPassword: boolean; + + description: string; + } + export interface HostConnTest { + isLocal: boolean; + addr: string; + port: number; + user: string; + authMode: string; + privateKey: string; + passPhrase: string; + password: string; + + localSSHConnShow: string; + } + export interface GroupChange { + id: number; + groupID: number; + } + export interface ReqSearch { + info?: string; + } + export interface SearchWithPage extends ReqPage { + groupID: number; + info?: string; + } + + export interface FirewallBase { + name: string; + isExist: boolean; + isActive: boolean; + isInit: boolean; + isBind: boolean; + version: string; + pingStatus: string; + } + export interface RuleSearch extends ReqPage { + status: string; + strategy: string; + info: string; + type: string; + } + export interface RuleInfo extends ReqPage { + family: string; + address: string; + destination: string; + port: string; + srcPort: string; + destPort: string; + protocol: string; + strategy: string; + + usedStatus: string; + description: string; + + [key: string]: any; + } + export interface UpdateDescription { + type: string; + chain: string; + srcIP: string; + dstIP: string; + srcPort: string; + dstPort: string; + protocol: string; + strategy: string; + description: string; + } + export interface RulePort { + operation: string; + address: string; + port: string; + source: string; + protocol: string; + strategy: string; + description: string; + } + export interface RuleForward { + operation: string; + protocol: string; + port: string; + targetIP: string; + targetPort: string; + interface: string; + } + export interface RuleIP { + operation: string; + address: string; + strategy: string; + description: string; + } + export interface UpdatePortRule { + oldRule: RulePort; + newRule: RulePort; + } + export interface UpdateAddrRule { + oldRule: RuleIP; + newRule: RuleIP; + } + export interface BatchRule { + type: string; + rules: Array; + } + + export interface MonitorSetting { + defaultNetwork: string; + defaultIO: string; + monitorStatus: string; + monitorStoreDays: string; + monitorInterval: string; + } + export interface MonitorData { + param: string; + date: Array; + value: Array; + } + export interface MonitorSearch { + param: string; + io: string; + network: string; + startTime: Date; + endTime: Date; + } + + export interface MonitorGPUSearch { + productName: string; + startTime: Date; + endTime: Date; + } + export interface MonitorGPUOptions { + gpuType: string; + options: Array; + chartHide: Array; + } + export interface ChartHide { + productName: string; + process: boolean; + gpu: boolean; + memory: boolean; + power: boolean; + temperature: boolean; + speed: boolean; + } + export interface MonitorGPUData { + date: Array; + gpuValue: Array; + temperatureValue: Array; + powerTotal: Array; + powerUsed: Array; + powerPercent: Array; + memoryTotal: Array; + memoryUsed: Array; + memoryPercent: Array; + speedValue: Array; + gpuProcesses: Array>; + } + export interface GPUProcess { + pid: string; + type: string; + processName: string; + usedMemory: string; + } + + export interface SSHInfo { + autoStart: boolean; + isActive: boolean; + message: string; + port: string; + listenAddress: string; + passwordAuthentication: string; + pubkeyAuthentication: string; + encryptionMode: string; + primaryKey: string; + permitRootLogin: string; + useDNS: string; + currentUser: string; + } + export interface SSHUpdate { + key: string; + oldValue: string; + newValue: string; + } + export interface RootCert { + name: string; + mode: string; + encryptionMode: string; + passPhrase: string; + privateKey: string; + publicKey: string; + description: string; + } + export interface RootCertInfo { + id: number; + createAt: Date; + name: string; + mode: string; + encryptionMode: string; + passPhrase: string; + description: string; + publicKey: string; + privateKey: string; + } + export interface searchSSHLog extends ReqPage { + info: string; + status: string; + } + export interface analysisSSHLog extends ReqPage { + orderBy: string; + } + export interface sshHistory { + date: Date; + area: string; + user: string; + authMode: string; + address: string; + port: string; + status: string; + message: string; + } + + export interface DiskBasicInfo { + device: string; + size: string; + model: string; + diskType: string; + isRemovable: boolean; + isSystem: boolean; + filesystem: string; + used: string; + avail: string; + usePercent: number; + mountPoint: string; + isMounted: boolean; + serial: string; + } + + export interface DiskInfo extends DiskBasicInfo { + partitions?: DiskBasicInfo[]; + } + + export interface CompleteDiskInfo { + disks: DiskInfo[]; + unpartitionedDisks: DiskBasicInfo[]; + systemDisks?: DiskInfo[]; + totalDisks: number; + totalCapacity: number; + } + + export interface DiskPartition { + device: string; + filesystem: string; + label: string; + autoMount: boolean; + mountPoint: string; + noFail: boolean; + } + + export interface DiskMount { + device: string; + mountPoint: string; + filesystem?: string; + } + + export interface DiskUmount { + mountPoint: string; + } + + export interface ComponentInfo { + exists: boolean; + version: string; + path: string; + error: string; + } + + // Iptables Filter + export interface IptablesFilterRuleSearch extends ReqPage { + info: string; + type: string; + } + export interface IptablesData { + items: IptablesRules[]; + total: number; + defaultStrategy: string; + } + export interface IptablesRules { + id: number; + protocol: string; + srcPort: string; + dstPort: string; + srcIP: string; + dstIP: string; + strategy: string; + description: string; + } + export interface ChainStatus { + isBind: boolean; + defaultStrategy: string; + } + export interface IptablesFilterRuleOp { + operation: string; + id?: number; + chain: string; + protocol: string; + srcIP?: string; + srcPort?: number; + dstIP?: string; + dstPort?: number; + strategy: string; + description?: string; + } +} diff --git a/frontend/src/api/interface/index.ts b/frontend/src/api/interface/index.ts new file mode 100644 index 0000000..26edf4d --- /dev/null +++ b/frontend/src/api/interface/index.ts @@ -0,0 +1,40 @@ +export interface Result { + code: number; + message: string; +} + +export interface ResultData { + code: number; + message: string; + data: T; +} + +export interface ResPage { + items: T[]; + total: number; +} + +export interface ReqPage { + page: number; + pageSize: number; +} +export interface SearchWithPage { + info: string; + page: number; + pageSize: number; + orderBy?: string; + order?: string; + name?: string; +} +export interface CommonModel { + id: number; + createdAt?: string; + updatedAt?: string; +} +export interface DescriptionUpdate { + id: number; + description: string; +} +export interface UpdateByFile { + file: string; +} diff --git a/frontend/src/api/interface/log.ts b/frontend/src/api/interface/log.ts new file mode 100644 index 0000000..2084fa7 --- /dev/null +++ b/frontend/src/api/interface/log.ts @@ -0,0 +1,62 @@ +import { DateTimeFormats } from '@intlify/core-base'; +import { ReqPage } from '.'; + +export namespace Log { + export interface OperationLog { + id: number; + source: string; + action: string; + ip: string; + path: string; + method: string; + userAgent: string; + body: string; + resp: string; + + status: number; + latency: number; + errorMessage: string; + + detail: string; + createdAt: DateTimeFormats; + } + export interface SearchOpLog extends ReqPage { + source: string; + status: string; + operation: string; + } + export interface SearchLgLog extends ReqPage { + ip: string; + status: string; + } + export interface LoginLogs { + ip: string; + address: string; + agent: string; + status: string; + message: string; + createdAt: DateTimeFormats; + } + export interface CleanLog { + logType: string; + } + + export interface SearchTaskReq extends ReqPage { + type: string; + status: string; + } + + export interface Task { + id: string; + name: string; + type: string; + logFile: string; + status: string; + errorMsg: string; + operationLogID: number; + resourceID: number; + currentStep: string; + endAt: Date; + createdAt: Date; + } +} diff --git a/frontend/src/api/interface/nginx.ts b/frontend/src/api/interface/nginx.ts new file mode 100644 index 0000000..0a1b6d4 --- /dev/null +++ b/frontend/src/api/interface/nginx.ts @@ -0,0 +1,66 @@ +export namespace Nginx { + export interface NginxScopeReq { + scope: string; + } + export interface NginxParam { + name: string; + params: string[]; + } + + export interface NginxConfigReq { + operate: string; + websiteId?: number; + scope: string; + params?: any; + } + + export interface NginxStatus { + accepts: number; + handled: number; + active: number; + requests: number; + reading: number; + writing: number; + waiting: number; + } + + export interface NginxFileUpdate { + content: string; + backup: boolean; + } + + export interface NginxBuildReq { + taskID: string; + mirror: string; + } + + export interface NginxModule { + name: string; + script?: string; + packages?: string; + enable: boolean; + params: string; + } + + export interface NginxBuildConfig { + mirror: string; + modules: NginxModule[]; + } + + export interface NginxModuleUpdate extends NginxModule { + operate: string; + } + + export interface NginxHttpsStatus { + https: boolean; + sslRejectHandshake: boolean; + } + + export interface NginxOperateReq { + operate: string; + } + + export interface NginxHttpsOperateReq extends NginxOperateReq { + sslRejectHandshake: boolean; + } +} diff --git a/frontend/src/api/interface/process.ts b/frontend/src/api/interface/process.ts new file mode 100644 index 0000000..cc1a22c --- /dev/null +++ b/frontend/src/api/interface/process.ts @@ -0,0 +1,65 @@ +export namespace Process { + export interface StopReq { + PID: number; + } + + export interface PsProcessData { + PID: number; + name: string; + PPID: number; + username: string; + status: string; + startTime: string; + numThreads: number; + numConnections: number; + cpuPercent: string; + + diskRead: string; + diskWrite: string; + cmdLine: string; + + rss: string; + vms: string; + hwm: string; + data: string; + stack: string; + locked: string; + swap: string; + dirty: string; + pss: string; + uss: string; + shared: string; + text: string; + + cpuValue: number; + rssValue: number; + + envs: string[]; + + openFiles: OpenFilesStat[]; + connects: ProcessConnect[]; + } + + export interface ProcessConnect { + type: string; + status: string; + localaddr: string; + remoteaddr: string; + PID: number; + name: string; + } + + export type ProcessConnects = ProcessConnect[]; + + export interface OpenFilesStat { + path: string; + fd: number; + } + + export interface ListeningProcess { + PID: number; + Port: { [key: string]: {} }; + Protocol: number; + Name: string; + } +} diff --git a/frontend/src/api/interface/runtime.ts b/frontend/src/api/interface/runtime.ts new file mode 100644 index 0000000..c67771f --- /dev/null +++ b/frontend/src/api/interface/runtime.ts @@ -0,0 +1,243 @@ +import { CommonModel, ReqPage } from '.'; +import { App } from './app'; +export namespace Runtime { + export interface Runtime extends CommonModel { + name: string; + appDetailID: number; + image: string; + workDir: string; + dockerCompose: string; + env: string; + params: string; + type: string; + resource: string; + version: string; + status: string; + codeDir: string; + port: string; + appID: number; + remark: string; + } + + export interface RuntimeReq extends ReqPage { + name?: string; + status?: string; + type?: string; + } + + export interface NodeReq { + codeDir: string; + } + + export interface NodeScripts { + name: string; + script: string; + } + + export interface RuntimeDTO extends Runtime { + appParams: App.InstallParams[]; + appID: number; + source?: string; + path?: string; + exposedPorts?: ExposedPort[]; + environments?: Environment[]; + volumes?: Volume[]; + extraHosts?: ExtraHost[]; + container: string; + } + + export interface RuntimeCreate { + id?: number; + name: string; + appDetailID: number; + image: string; + params: Object; + type: string; + resource: string; + appID?: number; + version?: string; + rebuild?: boolean; + source?: string; + codeDir?: string; + port?: number; + exposedPorts?: ExposedPort[]; + environments?: Environment[]; + volumes?: Volume[]; + extraHosts?: ExtraHost[]; + remark?: string; + } + + export interface ExposedPort { + hostPort: number; + containerPort: number; + hostIP: string; + } + export interface Environment { + key: string; + value: string; + } + export interface Volume { + source: string; + target: string; + } + + export interface ExtraHost { + hostname: string; + ip: string; + } + + export interface RuntimeUpdate { + name: string; + appDetailID: number; + image: string; + params: object; + type: string; + resource: string; + appID?: number; + version?: string; + rebuild?: boolean; + } + + export interface RuntimeDelete { + id: number; + forceDelete: boolean; + } + + export interface RuntimeOperate { + ID: number; + operate: string; + } + + export interface NodeModule { + name: string; + version: string; + description: string; + } + + export interface NodeModuleReq { + ID: number; + Operate?: string; + Module?: string; + PkgManager?: string; + } + + export interface PHPExtensions extends CommonModel { + id: number; + name: string; + extensions: string; + } + + export interface PHPExtensionsList extends ReqPage { + all: boolean; + } + + export interface PHPExtensionsCreate { + name: string; + extensions: string; + } + + export interface PHPExtensionsUpdate { + id: number; + name: string; + extensions: string; + } + + export interface PHPExtensionsDelete { + id: number; + } + + export interface PHPExtensionsRes { + extensions: string[]; + supportExtensions: SupportExtension[]; + } + + export interface SupportExtension { + name: string; + description: string; + installed: boolean; + check: string; + versions: string[]; + } + + export interface PHPExtensionInstall { + name: string; + id: number; + taskID?: string; + } + + export interface PHPConfig { + params: any; + disableFunctions: string[]; + uploadMaxSize: string; + maxExecutionTime: string; + } + + export interface PHPConfigUpdate { + id: number; + params?: any; + disableFunctions?: string[]; + scope: string; + uploadMaxSize?: string; + maxExecutionTime?: string; + } + + export interface PHPUpdate { + id: number; + content: string; + type: string; + } + + export interface PHPFileReq { + id: number; + type: string; + } + + export interface FPMConfig { + id: number; + params: any; + } + + export interface ProcessReq { + operate: string; + name: string; + id: number; + } + + export interface ProcessFileReq { + operate: string; + name: string; + content?: string; + file: string; + id: number; + } + + export interface SupersivorProcess { + operate: string; + name: string; + command: string; + user: string; + dir: string; + numprocs: string; + id: number; + environment: string; + } + + export interface PHPContainerConfig { + id: number; + containerName: string; + exposedPorts: ExposedPort[]; + environments: Environment[]; + volumes: Volume[]; + extraHosts: ExtraHost[]; + } + + export interface RemarkUpdate { + id: number; + remark: string; + } + + export interface FpmStatus { + key: string; + value: any; + } +} diff --git a/frontend/src/api/interface/setting.ts b/frontend/src/api/interface/setting.ts new file mode 100644 index 0000000..0f92147 --- /dev/null +++ b/frontend/src/api/interface/setting.ts @@ -0,0 +1,308 @@ +import { DateTimeFormats } from '@intlify/core-base'; + +export namespace Setting { + export interface SettingInfo { + userName: string; + password: string; + email: string; + systemIP: string; + systemVersion: string; + upgradeBackupCopies: string; + dockerSockPath: string; + developerMode: string; + + sessionTimeout: number; + localTime: string; + timeZone: string; + ntpSite: string; + + panelName: string; + theme: string; + menuTabs: string; + language: string; + defaultNetwork: string; + lastCleanTime: string; + lastCleanSize: string; + lastCleanData: string; + + serverPort: number; + ipv6: string; + bindAddress: string; + ssl: string; + sslType: string; + allowIPs: string; + bindDomain: string; + securityEntrance: string; + expirationDays: number; + expirationTime: string; + complexityVerification: string; + mfaStatus: string; + mfaSecret: string; + mfaInterval: string; + + monitorStatus: string; + monitorInterval: number; + monitorStoreDays: number; + + messageType: string; + emailVars: string; + weChatVars: string; + dingVars: string; + snapshotIgnore: string; + hideMenu: string; + noAuthSetting: string; + + proxyUrl: string; + proxyType: string; + proxyPort: string; + proxyUser: string; + proxyPasswd: string; + proxyPasswdKeep: string; + + apiInterfaceStatus: string; + apiKey: string; + ipWhiteList: string; + apiKeyValidityTime: number; + } + export interface TerminalInfo { + lineHeight: string; + letterSpacing: string; + fontSize: string; + cursorBlink: string; + cursorStyle: string; + scrollback: string; + scrollSensitivity: string; + } + export interface SettingUpdate { + key: string; + value: string; + } + export interface ProxyUpdate { + proxyUrl: string; + proxyType: string; + proxyPort: string; + proxyUser: string; + proxyPasswd: string; + proxyPasswdKeep: string; + withDockerRestart: boolean; + } + export interface ApiConfig { + apiInterfaceStatus: string; + apiKey: string; + ipWhiteList: string; + apiKeyValidityTime: number; + } + export interface SSLUpdate { + ssl: string; + domain: string; + sslType: string; + cert: string; + key: string; + sslID: number; + } + export interface SSLInfo { + domain: string; + timeout: string; + rootPath: string; + cert: string; + key: string; + sslID: number; + } + export interface PasswordUpdate { + oldPassword: string; + newPassword: string; + } + export interface PortUpdate { + serverPort: number; + } + export interface MFARequest { + title: string; + interval: number; + } + export interface MFAInfo { + secret: string; + qrImage: string; + } + export interface MFABind { + secret: string; + code: string; + interval: string; + } + export interface PasskeyRegisterRequest { + name: string; + } + export interface PasskeyBeginResponse { + sessionId: string; + publicKey: Record; + } + export interface PasskeyInfo { + id: string; + name: string; + createdAt: string; + lastUsedAt: string; + } + export interface CommonDescription { + id: string; + type: string; + detailType: string; + isPinned: boolean; + description: string; + } + + export interface SnapshotCreate { + id: number; + sourceAccountIDs: string; + downloadAccountID: string; + description: string; + secret: string; + timeout: number; + + appData: Array; + panelData: Array; + backupData: Array; + + withMonitorData: boolean; + withLoginLog: boolean; + withOperationLog: boolean; + } + export interface SnapshotImport { + backupAccountID: number; + names: Array; + description: string; + } + export interface SnapshotRecover { + id: number; + taskID: string; + isNew: boolean; + reDownload: boolean; + secret: string; + } + export interface SnapshotInfo { + id: number; + name: string; + sourceAccounts: Array; + downloadAccount: string; + description: string; + status: string; + message: string; + createdAt: DateTimeFormats; + version: string; + secret: string; + timeout: number; + + taskID: string; + taskRecoverID: string; + taskRollbackID: string; + + interruptStep: string; + recoverStatus: string; + recoverMessage: string; + rollbackStatus: string; + rollbackMessage: string; + } + export interface SnapshotData { + appData: Array; + panelData: Array; + backupData: Array; + + withMonitorData: boolean; + withLoginLog: boolean; + withOperationLog: boolean; + } + export interface DataTree { + id: string; + label: string; + key: string; + name: string; + size: number; + isShow: boolean; + isDisable: boolean; + + path: string; + + Children: Array; + } + export interface UpgradeInfo { + testVersion: string; + newVersion: string; + latestVersion: string; + releaseNote: string; + } + + export interface License { + licenseName: string; + assigneeName: string; + productPro: string; + versionConstraint: string; + trial: boolean; + status: string; + message: string; + smsUsed: number; + smsTotal: number; + } + export interface LicenseOptions { + id: number; + licenseName: string; + totalFreeCount: number; + availableXpackCount: number; + availableFreeCount: number; + } + export interface LicenseStatus { + productPro: string; + status: string; + smsTotal: number; + smsUsed: number; + } + export interface NodeItem { + id: number; + addr: string; + status: string; + version: string; + isXpack: boolean; + isBound: boolean; + name: string; + } + export interface SimpleNodeItem { + id: number; + name: string; + addr: string; + description: string; + systemVersion: string; + securityEntrance: string; + cpuUsedPercent: number; + cpuTotal: number; + memoryTotal: number; + memoryUsedPercent: number; + } + export interface ReleasesNotes { + Version: string; + CreatedAt: string; + Content: string; + NewCount: number; + OptimizationCount: number; + FixCount: number; + } + + export interface LicenseBind { + nodeID: number; + licenseID: number; + syncList: string; + withDockerRestart: boolean; + } + export interface LicenseUnbind { + id: number; + force: boolean; + withDockerRestart: boolean; + } + + export interface SmsInfo { + licenseName: string; + smsUsed: number; + smsTotal: number; + } + + export interface NodeAppItem { + name: string; + updateCount: number; + } +} diff --git a/frontend/src/api/interface/terminal.ts b/frontend/src/api/interface/terminal.ts new file mode 100644 index 0000000..94a6eb3 --- /dev/null +++ b/frontend/src/api/interface/terminal.ts @@ -0,0 +1,9 @@ +export interface ReqTerminal { + name: string; + ip: string; + port: number; + user: string; + authType: string; + password: string; + key: string; +} diff --git a/frontend/src/api/interface/toolbox.ts b/frontend/src/api/interface/toolbox.ts new file mode 100644 index 0000000..b1bbe98 --- /dev/null +++ b/frontend/src/api/interface/toolbox.ts @@ -0,0 +1,194 @@ +import { ReqPage } from '.'; +import { Cronjob } from './cronjob'; + +export namespace Toolbox { + export interface DeviceBaseInfo { + dns: Array; + hosts: Array; + hostname: string; + ntp: string; + user: string; + timeZone: string; + localTime: string; + + swapMemoryTotal: number; + swapMemoryAvailable: number; + swapMemoryUsed: number; + maxSize: number; + + swapDetails: Array; + } + export interface SwapHelper { + path: string; + size: number; + used: string; + + isNew: boolean; + } + export interface HostHelper { + ip: string; + host: string; + } + export interface TimeZoneOptions { + from: string; + zones: Array; + } + + export interface CleanData { + systemClean: Array; + backupClean: Array; + uploadClean: Array; + downloadClean: Array; + systemLogClean: Array; + containerClean: Array; + } + export interface CleanTree { + id: string; + label: string; + children: Array; + type: string; + name: string; + size: number; + isCheck: boolean; + isRecommend: boolean; + } + + export interface Fail2banBaseInfo { + isEnable: boolean; + isActive: boolean; + isExist: boolean; + version: string; + + port: number; + maxRetry: number; + banTime: string; + findTime: string; + banAction: string; + logPath: string; + } + + export interface Fail2banSearch { + status: string; + } + + export interface Fail2banUpdate { + key: string; + value: string; + } + + export interface Fail2banSet { + ips: Array; + operate: string; + } + + export interface FtpBaseInfo { + isActive: boolean; + isExist: boolean; + } + export interface FtpInfo { + id: number; + user: string; + password: string; + status: string; + path: string; + description: string; + } + export interface FtpCreate { + user: string; + password: string; + path: string; + description: string; + } + export interface FtpUpdate { + id: number; + password: string; + status: string; + path: string; + description: string; + } + export interface FtpSearchLog extends ReqPage { + user: string; + operation: string; + } + export interface FtpLog { + ip: string; + user: string; + time: string; + operation: string; + status: string; + size: string; + } + + export interface ClamBaseInfo { + version: string; + isActive: boolean; + isExist: boolean; + + freshVersion: string; + freshIsExist: boolean; + freshIsActive: boolean; + } + export interface ClamInfo { + id: number; + name: string; + status: string; + path: string; + infectedStrategy: string; + infectedDir: string; + lastHandleDate: string; + hasSpec: boolean; + spec: string; + specObj: Cronjob.SpecObj; + timeout: number; + timeoutItem: number; + timeoutUnit: string; + description: string; + hasAlert: boolean; + alertCount: number; + alertTitle: string; + alertMethod: string; + alertMethodItems: Array; + } + export interface ClamCreate { + name: string; + path: string; + infectedStrategy: string; + infectedDir: string; + spec: string; + timeout: number; + specObj: Cronjob.SpecObj; + description: string; + } + export interface ClamUpdate { + id: number; + name: string; + path: string; + infectedStrategy: string; + infectedDir: string; + spec: string; + timeout: number; + specObj: Cronjob.SpecObj; + description: string; + } + export interface ClamSearchLog extends ReqPage { + clamID: number; + startTime: Date; + endTime: Date; + } + export interface ClamRecordReq { + tail: string; + clamName: string; + recordName: string; + } + export interface ClamRecord { + id: number; + taskID: string; + scanDate: string; + scanTime: string; + infectedFiles: string; + totalError: string; + startTime: Date; + status: string; + message: string; + } +} diff --git a/frontend/src/api/interface/website.ts b/frontend/src/api/interface/website.ts new file mode 100644 index 0000000..ce6af12 --- /dev/null +++ b/frontend/src/api/interface/website.ts @@ -0,0 +1,737 @@ +import { CommonModel, ReqPage } from '.'; + +export namespace Website { + export interface Website extends CommonModel { + primaryDomain: string; + type: string; + alias: string; + remark: string; + domains: string[]; + appType: string; + appInstallId?: number; + webSiteGroupId: number; + otherDomains: string; + defaultServer: boolean; + protocol: string; + autoRenew: boolean; + appinstall?: NewAppInstall; + webSiteSSL: SSL; + runtimeID: number; + rewrite: string; + user: string; + group: string; + IPV6: boolean; + accessLog?: boolean; + errorLog?: boolean; + childSites?: string[]; + dbID: number; + dbType: string; + favorite: boolean; + streamPorts: string; + udp: boolean; + } + + export interface WebsiteDTO extends Website { + errorLogPath: string; + accessLogPath: string; + sitePath: string; + appName: string; + runtimeName: string; + runtimeType: string; + openBaseDir: boolean; + algorithm: string; + servers: NginxUpstreamServer[]; + } + + export interface WebsiteStreamUpdate { + websiteID: number; + algorithm: string; + streamPorts?: string; + servers: NginxUpstreamServer[]; + } + + export interface WebsiteRes extends CommonModel { + protocol: string; + primaryDomain: string; + type: string; + alias: string; + remark: string; + status: string; + expireDate: string; + sitePath: string; + appName: string; + runtimeName: string; + sslExpireDate: Date; + } + + export interface NewAppInstall { + name: string; + appDetailId: number; + params: any; + } + + export interface WebSiteSearch extends ReqPage { + name: string; + orderBy: string; + order: string; + websiteGroupId: number; + } + + export interface WebSiteDel { + id: number; + deleteApp: boolean; + deleteBackup: boolean; + forceDelete: boolean; + } + + export interface WebSiteCreateReq { + type: string; + alias: string; + remark: string; + appType: string; + appInstallId: number; + webSiteGroupId: number; + proxy: string; + proxyType: string; + ftpUser: string; + ftpPassword: string; + taskID: string; + SSLID?: number; + enableSSL: boolean; + createDB?: boolean; + dbName?: string; + dbPassword?: string; + dbFormat?: string; + dbUser?: string; + dbHost?: string; + domains: SubDomain[]; + streamPorts?: string; + name: string; + algorithm: string; + servers: NginxUpstreamServer[]; + } + + export interface WebSiteUpdateReq { + id: number; + primaryDomain: string; + remark: string; + webSiteGroupId: number; + expireDate?: string; + IPV6: boolean; + favorite: boolean; + } + + export interface WebSiteOp { + id: number; + operate: string; + } + + export interface WebSiteOpLog { + id: number; + operate: string; + logType: string; + page?: number; + pageSize?: number; + } + + export interface OptionReq { + types?: string[]; + } + + export interface WebSiteLog { + enable: boolean; + content: string; + end: boolean; + path: string; + } + + export interface Domain { + websiteId: number; + port: number; + id: number; + domain: string; + ssl: boolean; + } + + export interface DomainCreate { + websiteID: number; + domains: SubDomain[]; + } + + export interface DomainUpdate { + id: number; + ssl: boolean; + } + + interface SubDomain { + domain: string; + port: number; + ssl: boolean; + } + + export interface DomainDelete { + id: number; + } + + export interface NginxConfigReq { + operate: string; + websiteId: number; + scope: string; + params?: any; + } + + export interface NginxScopeReq { + websiteId: number; + scope: string; + } + + export interface NginxParam { + name: string; + params: string[]; + } + + export interface NginxScopeConfig { + enable: boolean; + params: NginxParam[]; + } + + export interface DnsAccount extends CommonModel { + name: string; + type: string; + authorization: Object; + } + + export interface DnsAccountCreate { + name: string; + type: string; + authorization: Object; + } + + export interface DnsAccountUpdate { + id: number; + name: string; + type: string; + authorization: Object; + } + + export interface SSL extends CommonModel { + primaryDomain: string; + privateKey: string; + pem: string; + otherDomains: string; + certURL: string; + type: string; + issuerName: string; + expireDate: string; + startDate: string; + provider: string; + websites?: Website.Website[]; + autoRenew: boolean; + acmeAccountId: number; + status: string; + domains: string; + description: string; + dnsAccountId?: number; + pushDir: boolean; + dir: string; + keyType: string; + nameserver1: string; + nameserver2: string; + disableCNAME: boolean; + skipDNS: boolean; + execShell: boolean; + shell: string; + pushNode: boolean; + nodes: string; + privateKeyPath: string; + certPath: string; + isIP: boolean; + } + + export interface SSLDTO extends SSL { + logPath: string; + } + + export interface SSLCreate { + primaryDomain: string; + otherDomains: string; + provider: string; + acmeAccountId: number; + dnsAccountId: number; + id?: number; + description: string; + isIP: boolean; + } + + export interface SSLApply { + websiteId: number; + SSLId: number; + } + + export interface SSLRenew { + SSLId: number; + } + + export interface SSLUpdate { + id: number; + autoRenew: boolean; + description: string; + primaryDomain: string; + otherDomains: string; + acmeAccountId: number; + provider: string; + dnsAccountId?: number; + keyType: string; + pushDir: boolean; + dir: string; + } + + export interface AcmeAccount extends CommonModel { + email: string; + url: string; + type: string; + useProxy: boolean; + } + + export interface AcmeAccountCreate { + email: string; + useProxy: boolean; + } + + export interface AcmeAccountUpdate { + id: number; + useProxy: boolean; + } + + export interface DNSResolveReq { + acmeAccountId: number; + websiteSSLId: number; + } + + export interface DNSResolve { + resolve: string; + value: string; + domain: string; + err: string; + } + + export interface SSLReq { + name?: string; + acmeAccountID?: string; + } + + export interface HTTPSReq { + websiteId: number; + enable: boolean; + websiteSSLId?: number; + type: string; + certificate?: string; + privateKey?: string; + httpConfig: string; + SSLProtocol: string[]; + algorithm: string; + http3: boolean; + } + + export interface HTTPSConfig { + enable: boolean; + SSL: SSL; + httpConfig: string; + SSLProtocol: string[]; + algorithm: string; + hsts: boolean; + hstsIncludeSubDomains: boolean; + httpsPort?: string; + http3: boolean; + } + + export interface BatchSetHttps { + ids: number[]; + taskID: string; + enable: boolean; + websiteSSLId?: number; + type: string; + certificate?: string; + privateKey?: string; + httpConfig: string; + SSLProtocol: string[]; + algorithm: string; + http3: boolean; + } + + export interface CheckReq { + installIds?: number[]; + } + + export interface CheckRes { + name: string; + status: string; + version: string; + appName: string; + } + + export interface DelReq { + id: number; + } + + export interface NginxUpdate { + id: number; + content: string; + } + + export interface DefaultServerUpdate { + id: number; + } + export interface RewriteReq { + websiteID: number; + name: string; + } + + export interface RewriteRes { + content: string; + } + + export interface RewriteUpdate { + websiteID: number; + name: string; + content: string; + } + + export interface CustomRewirte { + operate: string; + name: string; + content: string; + } + + export interface DirUpdate { + id: number; + siteDir: string; + } + + export interface DirPermissionUpdate { + id: number; + user: string; + group: string; + } + + export interface ProxyReq { + id: number; + } + + export interface ProxyConfig { + id: number; + operate: string; + enable: boolean; + cache: boolean; + cacheTime: number; + cacheUnit: string; + serverCacheTime: number; + serverCacheUnit: string; + name: string; + modifier: string; + match: string; + proxyPass: string; + proxyHost: string; + filePath?: string; + replaces?: ProxReplace; + content?: string; + proxyAddress?: string; + proxyProtocol?: string; + sni?: boolean; + proxySSLName: string; + cors: boolean; + allowOrigins: string; + allowMethods: string; + allowHeaders: string; + allowCredentials: boolean; + preflight: boolean; + browserCache?: boolean; + } + + export interface ProxReplace { + [key: string]: string; + } + + export interface ProxyFileUpdate { + websiteID: number; + name: string; + content: string; + } + + export interface AuthReq { + websiteID: number; + } + + export interface NginxAuth { + username: string; + remark: string; + } + + export interface AuthConfig { + enable: boolean; + items: NginxAuth[]; + } + + export interface NginxAuthConfig { + websiteID: number; + operate: string; + username: string; + password: string; + remark: string; + scope: string; + path?: ''; + name?: ''; + } + + export interface NginxPathAuthConfig { + websiteID: number; + operate: string; + path: string; + username: string; + password: string; + name: string; + } + + export interface LeechConfig { + enable: boolean; + cache: boolean; + cacheTime: number; + cacheUint: string; + extends: string; + return: string; + serverNames: string[]; + noneRef: boolean; + logEnable: boolean; + blocked: boolean; + websiteID?: number; + } + + export interface LeechReq { + websiteID: number; + } + + export interface WebsiteReq { + websiteID: number; + } + + export interface RedirectConfig { + operate: string; + websiteID: number; + domains?: string[]; + enable: boolean; + name: string; + keepPath: boolean; + type: string; + redirect: string; + path?: string; + target: string; + redirectRoot?: boolean; + filePath?: string; + content?: string; + } + + export interface RedirectFileUpdate { + websiteID: number; + name: string; + content: string; + } + + export interface PHPVersionChange { + websiteID: number; + runtimeID: number; + } + + export interface DirConfig { + dirs: string[]; + user: string; + userGroup: string; + msg: string; + } + + export interface SSLUpload { + privateKey: string; + certificate: string; + privateKeyPath: string; + certificatePath: string; + type: string; + sslID: number; + } + + export interface SSLObtain { + ID: number; + } + + export interface CA extends CommonModel { + name: string; + csr: string; + privateKey: string; + keyType: string; + } + + export interface CACreate { + name: string; + commonName: string; + country: string; + organization: string; + organizationUint: string; + keyType: string; + province: string; + city: string; + } + + export interface CADTO extends CA { + commonName: string; + country: string; + organization: string; + organizationUint: string; + province: string; + city: string; + } + + export interface SSLObtainByCA { + id: number; + domains: string; + keyType: string; + time: number; + unit: string; + pushDir: boolean; + dir: string; + description: string; + } + + export interface RenewSSLByCA { + SSLID: number; + } + + export interface SSLDownload { + id: number; + } + + export interface WebsiteHtml { + content: string; + } + export interface WebsiteHtmlUpdate { + type: string; + content: string; + sync: boolean; + } + + export interface NginxUpstream { + name: string; + algorithm: string; + servers: NginxUpstreamServer[]; + content?: string; + websiteID?: number; + } + + export interface NginxUpstreamFile { + name: string; + content: string; + websiteID: number; + } + + export interface LoadBalanceReq { + websiteID: number; + name: string; + algorithm: string; + servers: NginxUpstreamServer[]; + } + + interface NginxUpstreamServer { + server: string; + weight: number; + failTimeout: number; + failTimeoutUnit: string; + maxFails: number; + maxConns: number; + flag: string; + } + + export interface LoadBalanceDel { + websiteID: number; + name: string; + } + + export interface WebsiteLBUpdateFile { + websiteID: number; + name: string; + content: string; + } + + export interface WebsiteCacheConfig { + open: boolean; + cacheLimit: number; + cacheLimitUnit: string; + shareCache: number; + shareCacheUnit: string; + cacheExpire: number; + cacheExpireUnit: string; + } + + export interface WebsiteRealIPConfig { + open: boolean; + ipFrom: string; + ipHeader: string; + ipOther: string; + } + + export interface WebsiteResource { + name: string; + type: string; + resourceID: number; + detail: any; + } + + export interface WebsiteDatabase { + type: string; + databaseID: number; + websiteID: number; + from: string; + databaseName: number; + } + + export interface ChangeDatabase { + websiteID: number; + databaseID: number; + databaseType: string; + } + + export interface CrossSiteAccessOp { + websiteID: number; + operation: string; + } + + export interface ExecComposer { + websiteID: number; + command: string; + extCommand?: string; + mirror: string; + dir: string; + user: string; + taskID: string; + } + + export interface BatchOperate { + ids: number[]; + operate: string; + taskID: string; + } + + export interface CorsConfig { + cors: boolean; + allowOrigins: string; + allowMethods: string; + allowHeaders: string; + allowCredentials: boolean; + preflight: boolean; + } + + export interface CorsConfigReq extends CorsConfig { + websiteID: number; + } + + export interface BatchSetGroup { + ids: number[]; + groupID: number; + } +} diff --git a/frontend/src/api/modules/ai.ts b/frontend/src/api/modules/ai.ts new file mode 100644 index 0000000..fd2bfa4 --- /dev/null +++ b/frontend/src/api/modules/ai.ts @@ -0,0 +1,93 @@ +import { AI } from '@/api/interface/ai'; +import http from '@/api'; +import { ResPage } from '../interface'; + +export const createOllamaModel = (name: string, taskID: string) => { + return http.post(`/ai/ollama/model`, { name: name, taskID: taskID }); +}; +export const recreateOllamaModel = (name: string, taskID: string) => { + return http.post(`/ai/ollama/model/recreate`, { name: name, taskID: taskID }); +}; +export const deleteOllamaModel = (ids: Array, force: boolean) => { + return http.post(`/ai/ollama/model/del`, { ids: ids, forceDelete: force }); +}; +export const searchOllamaModel = (params: AI.OllamaModelSearch) => { + return http.post>(`/ai/ollama/model/search`, params); +}; +export const loadOllamaModel = (name: string) => { + return http.post(`/ai/ollama/model/load`, { name: name }); +}; +export const syncOllamaModel = () => { + return http.post>(`/ai/ollama/model/sync`); +}; +export const closeOllamaModel = (name: string) => { + return http.post(`/ai/ollama/close`, { name: name }); +}; + +export const loadGPUInfo = () => { + return http.get(`/ai/gpu/load`); +}; + +export const bindDomain = (req: AI.BindDomain) => { + return http.post(`/ai/domain/bind`, req); +}; + +export const getBindDomain = (req: AI.BindDomainReq) => { + return http.post(`/ai/domain/get`, req); +}; + +export const updateBindDomain = (req: AI.BindDomain) => { + return http.post(`/ai/domain/update`, req); +}; + +export const pageMcpServer = (req: AI.McpServerSearch) => { + return http.post>(`/ai/mcp/search`, req); +}; + +export const createMcpServer = (req: AI.McpServer) => { + return http.post(`/ai/mcp/server`, req); +}; + +export const updateMcpServer = (req: AI.McpServer) => { + return http.post(`/ai/mcp/server/update`, req); +}; + +export const deleteMcpServer = (req: AI.McpServerDelete) => { + return http.post(`/ai/mcp/server/del`, req); +}; + +export const operateMcpServer = (req: AI.McpServerOperate) => { + return http.post(`/ai/mcp/server/op`, req); +}; + +export const bindMcpDomain = (req: AI.McpBindDomain) => { + return http.post(`/ai/mcp/domain/bind`, req); +}; + +export const getMcpDomain = () => { + return http.get(`/ai/mcp/domain/get`); +}; + +export const updateMcpDomain = (req: AI.McpBindDomainUpdate) => { + return http.post(`/ai/mcp/domain/update`, req); +}; + +export const pageTensorRTLLM = (req: AI.TensorRTLLMSearch) => { + return http.post>(`/ai/tensorrt/search`, req); +}; + +export const createTensorRTLLM = (req: AI.TensorRTLLM) => { + return http.post(`/ai/tensorrt/create`, req); +}; + +export const updateTensorRTLLM = (req: AI.TensorRTLLM) => { + return http.post(`/ai/tensorrt/update`, req); +}; + +export const deleteTensorRTLLM = (req: AI.TensorRTLLMDelete) => { + return http.post(`/ai/tensorrt/delete`, req); +}; + +export const operateTensorRTLLM = (req: AI.TensorRTLLMOperate) => { + return http.post(`/ai/tensorrt/operate`, req); +}; diff --git a/frontend/src/api/modules/alert.ts b/frontend/src/api/modules/alert.ts new file mode 100644 index 0000000..29c2b76 --- /dev/null +++ b/frontend/src/api/modules/alert.ts @@ -0,0 +1,73 @@ +import http from '@/api'; +import { ResPage } from '@/api/interface'; +import { Alert } from '../interface/alert'; +import { deepCopy } from '@/utils/util'; + +export const SearchAlerts = (req: Alert.AlertSearch) => { + return http.post>(`/alert/search`, req); +}; + +export const CreateAlert = (req: Alert.AlertCreateReq) => { + let request = deepCopy(req) as Alert.AlertCreateReq; + return http.post(`/alert`, request); +}; + +export const UpdateAlert = (req: Alert.AlertUpdateReq) => { + return http.post(`/alert/update`, req); +}; + +export const DeleteAlert = (req: Alert.DelReq) => { + return http.post(`/alert/del`, req); +}; + +export const UpdateAlertStatus = (req: Alert.AlertUpdateStatusReq) => { + return http.post(`/alert/status`, req); +}; + +export const ListDisks = () => { + return http.get(`/alert/disks/list`); +}; + +export const SearchAlertLogs = (req: Alert.AlertLogSearch) => { + return http.post>(`/alert/logs/search`, req); +}; + +export const CleanAlertLogs = () => { + return http.post(`/alert/logs/clean`); +}; + +export const ListClams = () => { + return http.get(`/alert/clams/list`); +}; + +export const ListCronJob = (req: Alert.CronJobReq) => { + return http.post(`/alert/cronjob/list`, req); +}; + +export const ListAlertConfigs = () => { + return http.post(`/alert/config/info`); +}; + +export const DeleteAlertConfig = (req: Alert.DelReq) => { + return http.post(`/alert/config/del`, req); +}; + +export const UpdateAlertConfig = (req: Alert.AlertConfigUpdateReq) => { + return http.post(`/alert/config/update`, req); +}; + +export const TestAlertConfig = (req: Alert.AlertConfigTest) => { + return http.post(`/alert/config/test`, req); +}; + +export const SyncAlertInfo = (req: Alert.AlertLogId) => { + return http.post(`/xpack/alert/logs/sync`, req); +}; + +export const SyncAlertAll = () => { + return http.post(`/xpack/alert/logs/sync/all`); +}; + +export const SyncOfflineAlert = () => { + return http.post(`/core/xpack/alert/offline/sync`); +}; diff --git a/frontend/src/api/modules/app.ts b/frontend/src/api/modules/app.ts new file mode 100644 index 0000000..85c9781 --- /dev/null +++ b/frontend/src/api/modules/app.ts @@ -0,0 +1,142 @@ +import http from '@/api'; +import { ResPage } from '../interface'; +import { App } from '../interface/app'; +import { TimeoutEnum } from '@/enums/http-enum'; + +export const syncApp = (req: App.AppStoreSync) => { + return http.post('apps/sync/remote', req); +}; + +export const syncLocalApp = (req: App.AppStoreSync) => { + return http.post('apps/sync/local', req); +}; + +export const searchApp = (req: App.AppReq) => { + return http.post('apps/search', req); +}; + +export const getAppByKey = (key: string, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.get('apps/' + key + `${params}`); +}; + +export const getAppTags = () => { + return http.get('apps/tags'); +}; + +export const getAppDetail = (appID: number, version: string, type: string, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.get(`apps/detail/${appID}/${version}/${type}${params}`); +}; + +export const getAppDetailByID = (id: number) => { + return http.get(`apps/details/${id}`); +}; + +export const installApp = (install: App.AppInstall) => { + return http.post('apps/install', install); +}; + +export const changePort = (params: App.ChangePort) => { + return http.post('apps/installed/port/change', params); +}; + +export const searchAppInstalled = (search: App.AppInstallSearch, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.post>(`apps/installed/search${params}`, search); +}; + +export const listAppInstalled = () => { + return http.get>('apps/installed/list'); +}; + +export const getAppPort = (type: string, name: string) => { + return http.post(`apps/installed/loadport`, { type: type, name: name }); +}; + +export const getAppConnInfo = (type: string, name: string) => { + return http.post(`apps/installed/conninfo`, { type: type, name: name }); +}; + +export const checkAppInstalled = (key: string, name: string) => { + return http.post(`apps/installed/check`, { key: key, name: name }); +}; + +export const appInstalledDeleteCheck = (appInstallId: number, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.get(`apps/installed/delete/check/${appInstallId}${params}`); +}; + +export const getAppInstalled = (search: App.AppInstalledSearch) => { + return http.post>('apps/installed/search', search); +}; + +export const getAppInstalledByID = (installID: number, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.get(`apps/installed/info/${installID}${params}`); +}; + +export const installedOp = (op: App.AppInstalledOp, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.post(`apps/installed/op${params}`, op, TimeoutEnum.T_40S); +}; + +export const syncInstalledApp = () => { + return http.post('apps/installed/sync', {}); +}; + +export const getAppService = (key: string | undefined, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.get(`apps/services/${key}${params}`); +}; + +export const getAppUpdateVersions = (req: App.AppUpdateVersionReq, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.post(`apps/installed/update/versions${params}`, req); +}; + +export const getAppDefaultConfig = (key: string, name: string) => { + return http.post(`apps/installed/conf`, { type: key, name: name }); +}; + +export const getAppInstallParams = (id: number) => { + return http.get(`apps/installed/params/${id}`); +}; + +export const updateAppInstallParams = (req: any) => { + return http.post(`apps/installed/params/update`, req); +}; + +export const ignoreUpgrade = (req: App.AppIgnoreReq) => { + return http.post(`apps/installed/ignore`, req); +}; + +export const getIgnoredApp = () => { + return http.get(`apps/ignored/detail`); +}; + +export const cancelAppIgnore = (req: App.CancelAppIgnore) => { + return http.post(`apps/ignored/cancel`, req); +}; + +export const updateInstallConfig = (req: App.AppConfigUpdate) => { + return http.post(`apps/installed/config/update`, req); +}; + +export const syncCutomAppStore = (req: App.AppStoreSync) => { + return http.post(`/custom/app/sync`, req); +}; + +export const getCurrentNodeCustomAppConfig = () => { + return http.get(`/custom/app/config`); +}; + +export function getAppIconUrl(appKey: string, node?: string): string { + const baseURL = import.meta.env.VITE_API_URL as string; + const params = node ? `?operateNode=${node}` : ''; + return `${baseURL}/apps/icon/${appKey}${params}`; +} + +export const installAppToNodes = (param: App.InstallAppToNodes) => { + return http.post(`/core/xpack/sync/app/install`, param); +}; diff --git a/frontend/src/api/modules/auth.ts b/frontend/src/api/modules/auth.ts new file mode 100644 index 0000000..3f69d13 --- /dev/null +++ b/frontend/src/api/modules/auth.ts @@ -0,0 +1,34 @@ +import { Login } from '@/api/interface/auth'; +import http from '@/api'; + +export const loginApi = (params: Login.ReqLoginForm) => { + return http.post(`/core/auth/login`, params); +}; + +export const mfaLoginApi = (params: Login.MFALoginForm) => { + return http.post(`/core/auth/mfalogin`, params); +}; + +export const passkeyBeginApi = () => { + return http.post(`/core/auth/passkey/begin`); +}; + +export const passkeyFinishApi = (params: Record, sessionId: string) => { + return http.post(`/core/auth/passkey/finish`, params, undefined, { 'Passkey-Session': sessionId }); +}; + +export const getCaptcha = () => { + return http.get(`/core/auth/captcha`); +}; + +export const logOutApi = () => { + return http.post(`/core/auth/logout`); +}; + +export const getLoginSetting = () => { + return http.get('/core/auth/setting'); +}; + +export const getWelcomePage = () => { + return http.get('/core/auth/welcome'); +}; diff --git a/frontend/src/api/modules/backup.ts b/frontend/src/api/modules/backup.ts new file mode 100644 index 0000000..db8aa5b --- /dev/null +++ b/frontend/src/api/modules/backup.ts @@ -0,0 +1,131 @@ +import http from '@/api'; +import { deepCopy } from '@/utils/util'; +import { Base64 } from 'js-base64'; +import { ResPage } from '../interface'; +import { Backup } from '../interface/backup'; +import { TimeoutEnum } from '@/enums/http-enum'; +import { GlobalStore } from '@/store'; +const globalStore = GlobalStore(); + +// backup-agent +export const getLocalBackupDir = (node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.get(`/backups/local${params}`); +}; +export const searchBackup = (params: Backup.SearchWithType) => { + return http.post>(`/backups/search`, params); +}; +export const checkBackup = (params: Backup.BackupOperate) => { + let request = deepCopy(params) as Backup.BackupOperate; + if (request.accessKey) { + request.accessKey = Base64.encode(request.accessKey); + } + if (request.credential) { + request.credential = Base64.encode(request.credential); + } + if (!params.isPublic || !globalStore.isProductPro) { + return http.postLocalNode(`/backups/conn/check`, request); + } + return http.post(`/backups/conn/check`, request); +}; +export const listBucket = (params: Backup.ForBucket) => { + let request = deepCopy(params) as Backup.BackupOperate; + if (request.accessKey) { + request.accessKey = Base64.encode(request.accessKey); + } + if (request.credential) { + request.credential = Base64.encode(request.credential); + } + if (!params.isPublic || !globalStore.isProductPro) { + return http.postLocalNode('/backups/buckets', request, TimeoutEnum.T_40S); + } + return http.post('/backups/buckets', request, TimeoutEnum.T_40S); +}; +export const handleBackup = (params: Backup.Backup, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/backup${query}`, params, TimeoutEnum.T_10M); +}; +export const listBackupOptions = () => { + return http.get>(`/backups/options`); +}; +export const handleRecover = (params: Backup.Recover, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/recover${query}`, params, TimeoutEnum.T_10M); +}; +export const handleRecoverByUpload = (params: Backup.Recover) => { + return http.post(`/backups/recover/byupload`, params, TimeoutEnum.T_10M); +}; +export const downloadBackupRecord = (params: Backup.RecordDownload, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/record/download${query}`, params, TimeoutEnum.T_10M); +}; +export const deleteBackupRecord = (params: { ids: number[] }, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/record/del${query}`, params); +}; +export const updateRecordDescription = (id: Number, description: String, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/backups/record/description/update${query}`, { id: id, description: description }); +}; +export const uploadByRecover = (filePath: string, targetDir: String) => { + return http.post(`/backups/upload`, { filePath: filePath, targetDir: targetDir }); +}; +export const searchBackupRecords = (params: Backup.SearchBackupRecord, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/backups/record/search${query}`, params, TimeoutEnum.T_5M); +}; +export const loadRecordSize = (param: Backup.SearchForSize, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/backups/record/size${query}`, param); +}; +export const searchBackupRecordsByCronjob = (params: Backup.SearchBackupRecordByCronjob) => { + return http.post>(`/backups/record/search/bycronjob`, params, TimeoutEnum.T_5M); +}; +export const getFilesFromBackup = (id: number) => { + return http.post>(`/backups/search/files`, { id: id }); +}; + +// backup-core +export const refreshToken = (params: { id: number; name: string; isPublic: boolean }) => { + if (!params.isPublic) { + return http.post('/backups/refresh/token', { id: params.id }); + } + return http.post('/core/backups/refresh/token', { name: params.name }); +}; +export const getClientInfo = (clientType: string) => { + return http.get(`/core/backups/client/${clientType}`); +}; +export const addBackup = (params: Backup.BackupOperate) => { + let request = deepCopy(params) as Backup.BackupOperate; + if (request.accessKey) { + request.accessKey = Base64.encode(request.accessKey); + } + if (request.credential) { + request.credential = Base64.encode(request.credential); + } + let urlItem = '/core/backups'; + if (!params.isPublic) { + urlItem = '/backups'; + } + return http.post(urlItem, request, TimeoutEnum.T_60S); +}; +export const editBackup = (params: Backup.BackupOperate) => { + let request = deepCopy(params) as Backup.BackupOperate; + if (request.accessKey) { + request.accessKey = Base64.encode(request.accessKey); + } + if (request.credential) { + request.credential = Base64.encode(request.credential); + } + let urlItem = '/core/backups/update'; + if (!params.isPublic) { + urlItem = '/backups/update'; + } + return http.post(urlItem, request, TimeoutEnum.T_60S); +}; +export const deleteBackup = (params: { id: number; name: string; isPublic: boolean }) => { + if (!params.isPublic) { + return http.post('/backups/del', { id: params.id }); + } + return http.post('/core/backups/del', { name: params.name }); +}; diff --git a/frontend/src/api/modules/command.ts b/frontend/src/api/modules/command.ts new file mode 100644 index 0000000..9c72c7b --- /dev/null +++ b/frontend/src/api/modules/command.ts @@ -0,0 +1,31 @@ +import http from '@/api'; +import { ResPage, SearchWithPage } from '../interface'; +import { Command } from '../interface/command'; + +export const getCommandList = (type: string) => { + return http.post>(`/core/commands/list`, { type: type }); +}; +export const exportCommands = () => { + return http.post(`/core/commands/export`); +}; +export const uploadCommands = (params: FormData) => { + return http.upload>(`/core/commands/upload`, params); +}; +export const importCommands = (list: Array) => { + return http.post(`/core/commands/import`, { items: list }); +}; +export const getCommandPage = (params: SearchWithPage) => { + return http.post>(`/core/commands/search`, params); +}; +export const getCommandTree = (type: string) => { + return http.post(`/core/commands/tree`, { type: type }); +}; +export const addCommand = (params: Command.CommandOperate) => { + return http.post(`/core/commands`, params); +}; +export const editCommand = (params: Command.CommandOperate) => { + return http.post(`/core/commands/update`, params); +}; +export const deleteCommand = (params: { ids: number[] }) => { + return http.post(`/core/commands/del`, params); +}; diff --git a/frontend/src/api/modules/container.ts b/frontend/src/api/modules/container.ts new file mode 100644 index 0000000..d8ebe93 --- /dev/null +++ b/frontend/src/api/modules/container.ts @@ -0,0 +1,228 @@ +import http from '@/api'; +import { ResPage, SearchWithPage } from '../interface'; +import { Container } from '../interface/container'; +import { TimeoutEnum } from '@/enums/http-enum'; + +export const searchContainer = (params: Container.ContainerSearch) => { + return http.post>(`/containers/search`, params, TimeoutEnum.T_40S); +}; +export const listContainer = () => { + return http.post>(`/containers/list`, {}); +}; +export const listContainerByImage = (image: string) => { + return http.post>(`/containers/list/byimage`, { name: image }); +}; +export const loadContainerUsers = (name: string) => { + return http.post>(`/containers/users`, { name: name }); +}; +export const loadContainerStatus = () => { + return http.get(`/containers/status`); +}; +export const loadResourceLimit = () => { + return http.get(`/containers/limit`); +}; +export const createContainer = (params: Container.ContainerHelper) => { + return http.post(`/containers`, params, TimeoutEnum.T_10M); +}; +export const createContainerByCommand = (command: string, taskID: string) => { + return http.post(`/containers/command`, { command: command, taskID: taskID }); +}; +export const updateContainer = (params: Container.ContainerHelper) => { + return http.post(`/containers/update`, params, TimeoutEnum.T_10M); +}; +export const upgradeContainer = (params: Container.ContainerUpgrade) => { + return http.post(`/containers/upgrade`, params, TimeoutEnum.T_10M); +}; +export const commitContainer = (params: Container.ContainerCommit) => { + return http.post(`/containers/commit`, params); +}; +export const loadContainerInfo = (name: string) => { + return http.post(`/containers/info`, { name: name }); +}; +export const cleanComposeLog = (composeName: string, composePath: string, operateNode?: string) => { + const params = operateNode ? `?operateNode=${operateNode}` : ''; + return http.post( + `/containers/compose/clean/log${params}`, + { name: composeName, path: composePath }, + TimeoutEnum.T_60S, + ); +}; +export const cleanContainerLog = (containerName: string, operateNode?: string) => { + const params = operateNode ? `?operateNode=${operateNode}` : ''; + return http.post(`/containers/clean/log${params}`, { name: containerName }, TimeoutEnum.T_60S); +}; +export const containerItemStats = (containerID: string) => { + return http.post(`/containers/item/stats`, { name: containerID }, TimeoutEnum.T_60S); +}; +export const containerListStats = () => { + return http.get>(`/containers/list/stats`); +}; +export const containerStats = (id: string) => { + return http.get(`/containers/stats/${id}`); +}; +export const containerRename = (params: Container.ContainerRename) => { + return http.post(`/containers/rename`, params); +}; +export const containerOperator = (params: Container.ContainerOperate) => { + return http.post(`/containers/operate`, params); +}; +export const containerPrune = (params: Container.ContainerPrune) => { + return http.post(`/containers/prune`, params); +}; +export const inspect = (params: Container.ContainerInspect) => { + return http.post(`/containers/inspect`, params); +}; + +export const DownloadFile = (params: Container.ContainerLogInfo) => { + return http.download('/containers/download/log', params, { + responseType: 'blob', + timeout: TimeoutEnum.T_40S, + }); +}; + +// image +export const searchImage = (params: Container.ImageSearch) => { + return http.post>(`/containers/image/search`, params); +}; +export const listAllImage = () => { + return http.get>(`/containers/image/all`); +}; +export const listImage = () => { + return http.get>(`/containers/image`); +}; +export const imageBuild = (params: Container.ImageBuild) => { + return http.post(`/containers/image/build`, params); +}; +export const imagePull = (params: Container.ImagePull) => { + return http.post(`/containers/image/pull`, params); +}; +export const imagePush = (params: Container.ImagePush) => { + return http.post(`/containers/image/push`, params); +}; +export const imageLoad = (params: Container.ImageLoad) => { + return http.post(`/containers/image/load`, params, TimeoutEnum.T_10M); +}; +export const imageSave = (params: Container.ImageSave) => { + return http.post(`/containers/image/save`, params, TimeoutEnum.T_10M); +}; +export const imageTag = (params: Container.ImageTag) => { + return http.post(`/containers/image/tag`, params); +}; +export const imageRemove = (params: Container.BatchDelete) => { + return http.post(`/containers/image/remove`, params); +}; + +// network +export const searchNetwork = (params: SearchWithPage) => { + return http.post>(`/containers/network/search`, params); +}; +export const listNetwork = () => { + return http.get>(`/containers/network`); +}; +export const deleteNetwork = (params: Container.BatchDelete) => { + return http.post(`/containers/network/del`, params); +}; +export const createNetwork = (params: Container.NetworkCreate) => { + return http.post(`/containers/network`, params); +}; + +// volume +export const searchVolume = (params: SearchWithPage) => { + return http.post>(`/containers/volume/search`, params); +}; +export const listVolume = () => { + return http.get>(`/containers/volume`); +}; +export const deleteVolume = (params: Container.BatchDelete) => { + return http.post(`/containers/volume/del`, params); +}; +export const createVolume = (params: Container.VolumeCreate) => { + return http.post(`/containers/volume`, params); +}; + +// repo +export const checkRepoStatus = (id: number) => { + return http.post(`/containers/repo/status`, { id: id }, TimeoutEnum.T_40S); +}; +export const searchImageRepo = (params: SearchWithPage) => { + return http.post>(`/containers/repo/search`, params); +}; +export const listImageRepo = () => { + return http.get(`/containers/repo`); +}; +export const createImageRepo = (params: Container.RepoCreate) => { + return http.post(`/containers/repo`, params, TimeoutEnum.T_40S); +}; +export const updateImageRepo = (params: Container.RepoUpdate) => { + return http.post(`/containers/repo/update`, params, TimeoutEnum.T_40S); +}; +export const deleteImageRepo = (id: Number) => { + return http.post(`/containers/repo/del`, { id: id }, TimeoutEnum.T_40S); +}; + +// composeTemplate +export const searchComposeTemplate = (params: SearchWithPage) => { + return http.post>(`/containers/template/search`, params); +}; +export const listComposeTemplate = () => { + return http.get(`/containers/template`); +}; +export const deleteComposeTemplate = (params: { ids: number[] }) => { + return http.post(`/containers/template/del`, params); +}; +export const createComposeTemplate = (params: Container.TemplateCreate) => { + return http.post(`/containers/template`, params); +}; +export const batchComposeTemplate = (templates: Array) => { + return http.post(`/containers/template/batch`, { templates: templates }); +}; +export const updateComposeTemplate = (params: Container.TemplateUpdate) => { + return http.post(`/containers/template/update`, params); +}; + +// compose +export const searchCompose = (params: SearchWithPage) => { + return http.post>(`/containers/compose/search`, params); +}; +export const upCompose = (params: Container.ComposeCreate) => { + return http.post(`/containers/compose`, params); +}; +export const testCompose = (params: Container.ComposeCreate) => { + return http.post(`/containers/compose/test`, params); +}; +export const composeOperate = (params: Container.ComposeOperation) => { + return http.post(`/containers/compose/operate`, params); +}; +export const composeUpdate = (params: Container.ComposeUpdate) => { + return http.post(`/containers/compose/update`, params, TimeoutEnum.T_10M); +}; + +// docker +export const dockerOperate = (operation: string) => { + return http.post(`/containers/docker/operate`, { operation: operation }, TimeoutEnum.T_3M); +}; +export const loadDaemonJson = () => { + return http.get(`/containers/daemonjson`); +}; +export const loadDaemonJsonFile = () => { + return http.get(`/containers/daemonjson/file`); +}; +export const loadDockerStatus = () => { + return http.get(`/containers/docker/status`); +}; +export const updateDaemonJson = (key: string, value: string) => { + return http.post(`/containers/daemonjson/update`, { key: key, value: value }, TimeoutEnum.T_3M); +}; +export const updateLogOption = (maxSize: string, maxFile: string) => { + return http.post(`/containers/logoption/update`, { logMaxSize: maxSize, logMaxFile: maxFile }, TimeoutEnum.T_3M); +}; +export const updateIpv6Option = (fixedCidrV6: string, ip6Tables: boolean, experimental: boolean) => { + return http.post( + `/containers/ipv6option/update`, + { fixedCidrV6: fixedCidrV6, ip6Tables: ip6Tables, experimental: experimental }, + TimeoutEnum.T_3M, + ); +}; +export const updateDaemonJsonByfile = (params: Container.DaemonJsonUpdateByFile) => { + return http.post(`/containers/daemonjson/update/byfile`, params, TimeoutEnum.T_3M); +}; diff --git a/frontend/src/api/modules/cronjob.ts b/frontend/src/api/modules/cronjob.ts new file mode 100644 index 0000000..3847268 --- /dev/null +++ b/frontend/src/api/modules/cronjob.ts @@ -0,0 +1,86 @@ +import http from '@/api'; +import { ResPage, SearchWithPage } from '../interface'; +import { Cronjob } from '../interface/cronjob'; +import { TimeoutEnum } from '@/enums/http-enum'; + +export const searchCronjobPage = (params: Cronjob.Search, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/cronjobs/search${query}`, params); +}; + +export const loadNextHandle = (spec: string) => { + return http.post>(`/cronjobs/next`, { spec: spec }); +}; + +export const editCronjobGroup = (id: number, groupID: number) => { + return http.post(`/cronjobs/group/update`, { id: id, groupID: groupID }); +}; + +export const importCronjob = (trans: Array) => { + return http.post('cronjobs/import', { cronjobs: trans }, TimeoutEnum.T_60S); +}; +export const exportCronjob = (params: { ids: Array }) => { + return http.download('cronjobs/export', params, { responseType: 'blob', timeout: TimeoutEnum.T_40S }); +}; + +export const loadCronjobInfo = (id: number) => { + return http.post(`/cronjobs/load/info`, { id: id }); +}; + +export const loadScriptOptions = () => { + return http.get>(`/cronjobs/script/options`); +}; + +export const getRecordLog = (id: number) => { + return http.post(`/cronjobs/records/log`, { id: id }); +}; + +export const addCronjob = (params: Cronjob.CronjobOperate) => { + return http.post(`/cronjobs`, params); +}; + +export const editCronjob = (params: Cronjob.CronjobOperate) => { + return http.post(`/cronjobs/update`, params); +}; + +export const deleteCronjob = (params: Cronjob.CronjobDelete) => { + return http.post(`/cronjobs/del`, params); +}; + +export const searchRecords = (params: Cronjob.SearchRecord) => { + return http.post>(`cronjobs/search/records`, params); +}; + +export const stopCronjob = (id: number) => { + return http.post(`cronjobs/stop`, { id: id }); +}; + +export const cleanRecords = (id: number, cleanData: boolean, cleanRemoteData: boolean) => { + return http.post(`cronjobs/records/clean`, { cronjobID: id, cleanData: cleanData, cleanRemoteData }); +}; + +export const updateStatus = (params: Cronjob.UpdateStatus, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`cronjobs/status${query}`, params); +}; + +export const handleOnce = (id: number, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`cronjobs/handle${query}`, { id: id }); +}; + +export const searchScript = (params: SearchWithPage) => { + return http.post>(`/core/script/search`, params); +}; +export const addScript = (params: Cronjob.ScriptOperate) => { + return http.post(`/core/script`, params); +}; +export const syncScript = (taskID: string) => { + return http.post(`/core/script/sync`, { taskID: taskID }, TimeoutEnum.T_60S); +}; +export const editScript = (params: Cronjob.ScriptOperate) => { + return http.post(`/core/script/update`, params); +}; +export const deleteScript = (ids: Array) => { + return http.post(`/core/script/del`, { ids: ids }); +}; diff --git a/frontend/src/api/modules/dashboard.ts b/frontend/src/api/modules/dashboard.ts new file mode 100644 index 0000000..077140d --- /dev/null +++ b/frontend/src/api/modules/dashboard.ts @@ -0,0 +1,42 @@ +import http from '@/api'; +import { Dashboard } from '../interface/dashboard'; + +export const loadOsInfo = () => { + return http.get(`/dashboard/base/os`); +}; + +export const loadQuickOption = () => { + return http.get>(`/dashboard/quick/option`); +}; +export const changeQuick = (quicks: Array) => { + return http.post(`/dashboard/quick/change`, { quicks: quicks }); +}; +export const loadAppLauncher = () => { + return http.get>(`/dashboard/app/launcher`); +}; +export const loadAppLauncherOption = (filter: string) => { + return http.post>(`/dashboard/app/launcher/option`, { filter: filter }); +}; +export const changeLauncherStatus = (key: string, val: string) => { + return http.post(`/dashboard/app/launcher/show`, { key: key, value: val }); +}; + +export const loadBaseInfo = (ioOption: string, netOption: string) => { + return http.get(`/dashboard/base/${ioOption}/${netOption}`); +}; + +export const loadCurrentInfo = (ioOption: string, netOption: string) => { + return http.get(`/dashboard/current/${ioOption}/${netOption}`); +}; + +export const loadTopCPU = () => { + return http.get>(`/dashboard/current/top/cpu`); +}; + +export const loadTopMem = () => { + return http.get>(`/dashboard/current/top/mem`); +}; + +export const systemRestart = (operation: string) => { + return http.post(`/dashboard/system/restart/${operation}`); +}; diff --git a/frontend/src/api/modules/database.ts b/frontend/src/api/modules/database.ts new file mode 100644 index 0000000..3dbd356 --- /dev/null +++ b/frontend/src/api/modules/database.ts @@ -0,0 +1,199 @@ +import http from '@/api'; +import { deepCopy } from '@/utils/util'; +import { Base64 } from 'js-base64'; +import { ResPage, DescriptionUpdate } from '../interface'; +import { Database } from '../interface/database'; +import { TimeoutEnum } from '@/enums/http-enum'; + +// common +export const loadDBBaseInfo = (type: string, database: string) => { + return http.post(`/databases/common/info`, { type: type, name: database }); +}; +export const loadDBFile = (type: string, database: string) => { + return http.post(`/databases/common/load/file`, { type: type, name: database }); +}; +export const updateDBFile = (params: Database.DBConfUpdate) => { + return http.post(`/databases/common/update/conf`, params); +}; + +// pg +export const addPostgresqlDB = (params: Database.PostgresqlDBCreate) => { + let request = deepCopy(params) as Database.PostgresqlDBCreate; + if (request.password) { + request.password = Base64.encode(request.password); + } + return http.post(`/databases/pg`, request, TimeoutEnum.T_40S); +}; +export const bindPostgresqlUser = (params: Database.PgBind) => { + return http.post(`/databases/pg/bind`, params, TimeoutEnum.T_40S); +}; +export const changePrivileges = (params: Database.PgChangePrivileges) => { + return http.post(`/databases/pg/privileges`, params, TimeoutEnum.T_40S); +}; +export const searchPostgresqlDBs = (params: Database.SearchDBWithPage, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/databases/pg/search${query}`, params); +}; +export const updatePostgresqlDescription = (params: DescriptionUpdate) => { + return http.post(`/databases/pg/description`, params); +}; +export const loadPgFromRemote = (database: string) => { + return http.post(`/databases/pg/${database}/load`); +}; +export const deleteCheckPostgresqlDB = (params: Database.PostgresqlDBDeleteCheck) => { + return http.post(`/databases/pg/del/check`, params, TimeoutEnum.T_40S); +}; +export const updatePostgresqlPassword = (params: Database.ChangeInfo) => { + let request = deepCopy(params) as Database.ChangeInfo; + if (request.value) { + request.value = Base64.encode(request.value); + } + return http.post(`/databases/pg/password`, request, TimeoutEnum.T_40S); +}; +export const deletePostgresqlDB = (params: Database.PostgresqlDBDelete) => { + return http.post(`/databases/pg/del`, params, TimeoutEnum.T_40S); +}; + +// mysql +export const searchMysqlDBs = (params: Database.SearchDBWithPage, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post>(`/databases/search${query}`, params); +}; +export const addMysqlDB = (params: Database.MysqlDBCreate) => { + let request = deepCopy(params) as Database.MysqlDBCreate; + if (request.password) { + request.password = Base64.encode(request.password); + } + return http.post(`/databases`, request); +}; +export const bindUser = (params: Database.BindUser) => { + let request = deepCopy(params) as Database.BindUser; + if (request.password) { + request.password = Base64.encode(request.password); + } + return http.post(`/databases/bind`, request); +}; +export const loadDBFromRemote = (params: Database.MysqlLoadDB) => { + return http.post(`/databases/load`, params); +}; +export const updateMysqlAccess = (params: Database.ChangeInfo) => { + return http.post(`/databases/change/access`, params); +}; +export const updateMysqlPassword = (params: Database.ChangeInfo) => { + let request = deepCopy(params) as Database.ChangeInfo; + if (request.value) { + request.value = Base64.encode(request.value); + } + return http.post(`/databases/change/password`, request); +}; +export const updateMysqlDescription = (params: DescriptionUpdate) => { + return http.post(`/databases/description/update`, params); +}; +export const updateMysqlVariables = (params: Database.VariablesUpdate) => { + return http.post(`/databases/variables/update`, params); +}; +export const deleteCheckMysqlDB = (params: Database.MysqlDBDeleteCheck) => { + return http.post>(`/databases/del/check`, params); +}; +export const deleteMysqlDB = (params: Database.MysqlDBDelete) => { + return http.post(`/databases/del`, params); +}; + +export const loadMysqlVariables = (type: string, database: string) => { + return http.post(`/databases/variables`, { type: type, name: database }); +}; +export const loadMysqlStatus = (type: string, database: string) => { + return http.post(`/databases/status`, { type: type, name: database }); +}; +export const loadRemoteAccess = (type: string, database: string) => { + return http.post(`/databases/remote`, { type: type, name: database }); +}; +export const loadFormatCollations = (database: string) => { + return http.post>(`/databases/format/options`, { name: database }); +}; + +// redis +export const loadRedisStatus = (type: string, database: string) => { + return http.post(`/databases/redis/status`, { type: type, name: database }); +}; +export const loadRedisConf = (type: string, database: string) => { + return http.post(`/databases/redis/conf`, { type: type, name: database }); +}; +export const redisPersistenceConf = (type: string, database: string) => { + return http.post(`/databases/redis/persistence/conf`, { + type: type, + name: database, + }); +}; +export const checkRedisCli = () => { + return http.get(`/databases/redis/check`); +}; +export const installRedisCli = () => { + return http.post(`/databases/redis/install/cli`, {}, TimeoutEnum.T_5M); +}; +export const changeRedisPassword = (database: string, password: string) => { + if (password) { + password = Base64.encode(password); + } + return http.post(`/databases/redis/password`, { database: database, value: password }); +}; +export const updateRedisPersistenceConf = (params: Database.RedisConfPersistenceUpdate) => { + return http.post(`/databases/redis/persistence/update`, params); +}; +export const updateRedisConf = (params: Database.RedisConfUpdate) => { + return http.post(`/databases/redis/conf/update`, params); +}; +export const updateRedisConfByFile = (params: Database.DBConfUpdate) => { + return http.post(`/databases/redis/conffile/update`, params); +}; + +// database +export const getDatabase = (name: string) => { + return http.get(`/databases/db/${name}`); +}; +export const searchDatabases = (params: Database.SearchDatabasePage) => { + return http.post>(`/databases/db/search`, params); +}; +export const listDatabases = (type: string, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.get>(`/databases/db/list/${type}${query}`); +}; +export const listDbItems = (type: string) => { + return http.get>(`/databases/db/item/${type}`); +}; +export const checkDatabase = (params: Database.DatabaseCreate) => { + let request = deepCopy(params) as Database.DatabaseCreate; + if (request.ssl) { + request.clientKey = Base64.encode(request.clientKey); + request.clientCert = Base64.encode(request.clientCert); + request.rootCert = Base64.encode(request.rootCert); + } + + return http.post(`/databases/db/check`, request, TimeoutEnum.T_60S); +}; +export const addDatabase = (params: Database.DatabaseCreate) => { + let request = deepCopy(params) as Database.DatabaseCreate; + if (request.ssl) { + request.clientKey = Base64.encode(request.clientKey); + request.clientCert = Base64.encode(request.clientCert); + request.rootCert = Base64.encode(request.rootCert); + } + + return http.post(`/databases/db`, request, TimeoutEnum.T_60S); +}; +export const editDatabase = (params: Database.DatabaseUpdate) => { + let request = deepCopy(params) as Database.DatabaseCreate; + if (request.ssl) { + request.clientKey = Base64.encode(request.clientKey); + request.clientCert = Base64.encode(request.clientCert); + request.rootCert = Base64.encode(request.rootCert); + } + + return http.post(`/databases/db/update`, request, TimeoutEnum.T_60S); +}; +export const deleteCheckDatabase = (id: number) => { + return http.post(`/databases/db/del/check`, { id: id }); +}; +export const deleteDatabase = (params: Database.DatabaseDelete) => { + return http.post(`/databases/db/del`, params); +}; diff --git a/frontend/src/api/modules/files.ts b/frontend/src/api/modules/files.ts new file mode 100644 index 0000000..9b372ba --- /dev/null +++ b/frontend/src/api/modules/files.ts @@ -0,0 +1,180 @@ +import { File } from '@/api/interface/file'; +import http from '@/api'; +import { AxiosRequestConfig } from 'axios'; +import { ResPage } from '../interface'; +import { TimeoutEnum } from '@/enums/http-enum'; +import { ReqPage } from '@/api/interface'; +import { Dashboard } from '@/api/interface/dashboard'; + +export const getFilesList = (params: File.ReqFile) => { + return http.post('files/search', params, TimeoutEnum.T_5M); +}; + +export const getFilesListByNode = (params: File.ReqNodeFile) => { + return http.post('files/search?operateNode=' + params.node, params, TimeoutEnum.T_5M); +}; + +export const getUploadList = (params: File.SearchUploadInfo) => { + return http.post>('files/upload/search', params); +}; + +export const getFilesTree = (params: File.ReqFile) => { + return http.post('files/tree', params); +}; + +export const createFile = (form: File.FileCreate) => { + return http.post('files', form); +}; + +export const deleteFile = (form: File.FileDelete) => { + return http.post('files/del', form); +}; + +export const deleteFileByNode = (form: File.FileDelete, node: string) => { + return http.post('files/del?operateNode=' + node, form); +}; + +export const batchDeleteFile = (form: File.FileBatchDelete) => { + return http.post('files/batch/del', form); +}; + +export const changeFileMode = (form: File.FileCreate) => { + return http.post('files/mode', form); +}; + +export const compressFile = (form: File.FileCompress) => { + return http.post('files/compress', form, TimeoutEnum.T_10M); +}; + +export const deCompressFile = (form: File.FileDeCompress) => { + return http.post('files/decompress', form, TimeoutEnum.T_10M); +}; + +export const getFileContent = (params: File.ReqFile) => { + return http.post('files/content', params); +}; + +export const getPreviewContent = (params: File.PreviewContentReq) => { + return http.post('files/preview', params, TimeoutEnum.T_5M); +}; + +export const saveFileContent = (params: File.FileEdit) => { + return http.post('files/save', params); +}; + +export const checkFile = (path: string, withInit: boolean) => { + return http.post('files/check', { path: path, withInit: withInit }); +}; + +export const uploadFileData = (params: FormData, config: AxiosRequestConfig) => { + return http.upload('files/upload', params, config); +}; + +export const batchCheckFiles = (paths: string[]) => { + return http.post('files/batch/check', { paths: paths }, TimeoutEnum.T_5M); +}; + +export const batchGetFileRemarks = (paths: string[]) => { + return http.post('files/remarks', { paths: paths }, TimeoutEnum.T_5M); +}; + +export const setFileRemark = (params: File.FileRemarkUpdate) => { + return http.post('files/remark', params); +}; + +export const chunkUploadFileData = (params: FormData, config: AxiosRequestConfig) => { + return http.upload('files/chunkupload', params, config); +}; + +export const renameRile = (params: File.FileRename) => { + return http.post('files/rename', params); +}; + +export const changeOwner = (params: File.FileOwner) => { + return http.post('files/owner', params); +}; + +export const wgetFile = (params: File.FileWget) => { + return http.post('files/wget', params); +}; + +export const moveFile = (params: File.FileMove) => { + return http.post('files/move', params, TimeoutEnum.T_5M); +}; + +export const downloadFile = (params: File.FileDownload) => { + return http.download('files/download', params, { responseType: 'blob', timeout: TimeoutEnum.T_40S }); +}; + +export const computeDirSize = (params: File.DirSizeReq) => { + return http.post('files/size', params, TimeoutEnum.T_5M); +}; + +export const computeDepthDirSize = (params: File.DirSizeReq) => { + return http.post('files/depth/size', params, TimeoutEnum.T_5M); +}; + +export const fileWgetKeys = () => { + return http.get('files/wget/process/keys'); +}; + +export const getRecycleList = (params: ReqPage) => { + return http.post>('files/recycle/search', params); +}; + +export const reduceFile = (params: File.RecycleBinReduce) => { + return http.post('files/recycle/reduce', params); +}; + +export const clearRecycle = () => { + return http.post('files/recycle/clear'); +}; + +export const searchFavorite = (params: ReqPage) => { + return http.post>('files/favorite/search', params); +}; + +export const addFavorite = (path: string) => { + return http.post('files/favorite', { path: path }); +}; + +export const readByLine = (req: File.FileReadByLine, operateNode?: string) => { + const params = operateNode ? `?operateNode=${operateNode}` : ''; + return http.post(`files/read${params}`, req, TimeoutEnum.T_40S); +}; + +export const removeFavorite = (id: number) => { + return http.post('files/favorite/del', { id: id }); +}; + +export const batchChangeRole = (params: File.FileRole) => { + return http.post('files/batch/role', params); +}; + +export const getRecycleStatus = () => { + return http.get('files/recycle/status'); +}; + +export const getRecycleStatusByNode = (node: string) => { + return http.get('files/recycle/status?operateNode=' + node); +}; + +export const getPathByType = (pathType: string) => { + return http.get(`files/path/${pathType}`); +}; + +export const searchHostMount = () => { + return http.post(`/files/mount`); +}; + +export const searchUserGroup = () => { + return http.post(`/files/user/group`); +}; + +export const convertFiles = (params: File.ConvertFileRequest) => { + return http.post('files/convert', params, TimeoutEnum.T_5M); +}; + +export const convertLogs = (params: ReqPage) => { + return http.post>('files/convert/log', params, TimeoutEnum.T_5M); +}; diff --git a/frontend/src/api/modules/group.ts b/frontend/src/api/modules/group.ts new file mode 100644 index 0000000..9514f7f --- /dev/null +++ b/frontend/src/api/modules/group.ts @@ -0,0 +1,28 @@ +import { Group } from '../interface/group'; +import http from '@/api'; + +export const getGroupList = (type: string) => { + return http.post>(`/core/groups/search`, { type: type }); +}; +export const createGroup = (params: Group.GroupCreate) => { + return http.post(`/core/groups`, params); +}; +export const updateGroup = (params: Group.GroupUpdate) => { + return http.post(`/core/groups/update`, params); +}; +export const deleteGroup = (id: number) => { + return http.post(`/core/groups/del`, { id: id }); +}; + +export const getAgentGroupList = (type: string) => { + return http.post>(`/groups/search`, { type: type }); +}; +export const createAgentGroup = (params: Group.GroupCreate) => { + return http.post(`/groups`, params); +}; +export const updateAgentGroup = (params: Group.GroupUpdate) => { + return http.post(`/groups/update`, params); +}; +export const deleteAgentGroup = (id: number) => { + return http.post(`/groups/del`, { id: id }); +}; diff --git a/frontend/src/api/modules/host-tool.ts b/frontend/src/api/modules/host-tool.ts new file mode 100644 index 0000000..5e23a4b --- /dev/null +++ b/frontend/src/api/modules/host-tool.ts @@ -0,0 +1,39 @@ +import http from '@/api'; +import { HostTool } from '../interface/host-tool'; +import { TimeoutEnum } from '@/enums/http-enum'; + +export const getSupervisorStatus = () => { + return http.post(`/hosts/tool`, { type: 'supervisord', operate: 'status' }); +}; + +export const operateSupervisor = (operate: string) => { + return http.post(`/hosts/tool/operate`, { type: 'supervisord', operate: operate }); +}; + +export const operateSupervisorConfig = (req: HostTool.SupersivorConfig) => { + return http.post(`/hosts/tool/config`, req); +}; + +export const getSupervisorLog = () => { + return http.post(`/hosts/tool/log`, { type: 'supervisord' }); +}; + +export const initSupervisor = (req: HostTool.SupersivorInit) => { + return http.post(`/hosts/tool/init`, req); +}; + +export const createSupervisorProcess = (req: HostTool.SupersivorProcess) => { + return http.post(`/hosts/tool/supervisor/process`, req); +}; + +export const operateSupervisorProcess = (req: HostTool.ProcessReq) => { + return http.post(`/hosts/tool/supervisor/process`, req, TimeoutEnum.T_60S); +}; + +export const getSupervisorProcess = () => { + return http.get(`/hosts/tool/supervisor/process`, {}, { timeout: TimeoutEnum.T_3M }); +}; + +export const operateSupervisorProcessFile = (req: HostTool.ProcessFileReq) => { + return http.post(`/hosts/tool/supervisor/process/file`, req, TimeoutEnum.T_60S); +}; diff --git a/frontend/src/api/modules/host.ts b/frontend/src/api/modules/host.ts new file mode 100644 index 0000000..977a46c --- /dev/null +++ b/frontend/src/api/modules/host.ts @@ -0,0 +1,167 @@ +import http from '@/api'; +import { ResPage, ReqPage } from '../interface'; +import { Host } from '../interface/host'; +import { TimeoutEnum } from '@/enums/http-enum'; +import { deepCopy } from '@/utils/util'; +import { Base64 } from 'js-base64'; + +// firewall +export const loadFireBaseInfo = (tab: string) => { + return http.post(`/hosts/firewall/base`, { name: tab }, TimeoutEnum.T_40S); +}; +export const searchFireRule = (params: Host.RuleSearch) => { + return http.post>(`/hosts/firewall/search`, params, TimeoutEnum.T_40S); +}; +export const operateFire = (operation: string, withDockerRestart: boolean) => { + return http.post( + `/hosts/firewall/operate`, + { + operation: operation, + withDockerRestart: withDockerRestart, + }, + TimeoutEnum.T_60S, + ); +}; +export const operatePortRule = (params: Host.RulePort) => { + return http.post(`/hosts/firewall/port`, params, TimeoutEnum.T_40S); +}; +export const operateForwardRule = (params: { rules: Host.RuleForward[]; forceDelete?: boolean }) => { + return http.post(`/hosts/firewall/forward`, params, TimeoutEnum.T_40S); +}; +export const operateIPRule = (params: Host.RuleIP) => { + return http.post(`/hosts/firewall/ip`, params, TimeoutEnum.T_40S); +}; +export const updatePortRule = (params: Host.UpdatePortRule) => { + return http.post(`/hosts/firewall/update/port`, params, TimeoutEnum.T_40S); +}; +export const updateAddrRule = (params: Host.UpdateAddrRule) => { + return http.post(`/hosts/firewall/update/addr`, params, TimeoutEnum.T_40S); +}; +export const updateFirewallDescription = (params: Host.UpdateDescription) => { + return http.post(`/hosts/firewall/update/description`, params); +}; +export const batchOperateRule = (params: Host.BatchRule) => { + return http.post(`/hosts/firewall/batch`, params, TimeoutEnum.T_60S); +}; + +// Iptables Filter +export const searchFilterRules = (params: Host.IptablesFilterRuleSearch) => { + return http.post(`/hosts/firewall/filter/rule/search`, params); +}; +export const loadChainStatus = (name: string) => { + return http.post(`/hosts/firewall/filter/chain/status`, { name: name }, TimeoutEnum.T_60S); +}; +export const operateFilterRule = (params: Host.IptablesFilterRuleOp) => { + return http.post(`/hosts/firewall/filter/rule/operate`, params, TimeoutEnum.T_40S); +}; +export const batchOperateFilterRule = (params: { rules: Host.IptablesFilterRuleOp[] }) => { + return http.post(`/hosts/firewall/filter/rule/batch`, params, TimeoutEnum.T_40S); +}; +export const operateFilterChain = (name: string, op: string) => { + return http.post(`/hosts/firewall/filter/operate`, { name: name, operate: op }, TimeoutEnum.T_60S); +}; + +// monitors +export const loadMonitor = (param: Host.MonitorSearch) => { + return http.post>(`/hosts/monitor/search`, param); +}; +export const getGPUOptions = () => { + return http.get(`/hosts/monitor/gpuoptions`); +}; +export const loadGPUMonitor = (param: Host.MonitorGPUSearch) => { + return http.post(`/hosts/monitor/gpu/search`, param); +}; +export const getNetworkOptions = () => { + return http.get>(`/hosts/monitor/netoptions`); +}; +export const getIOOptions = () => { + return http.get>(`/hosts/monitor/iooptions`); +}; +export const cleanMonitors = () => { + return http.post(`/hosts/monitor/clean`, {}); +}; +export const loadMonitorSetting = () => { + return http.get(`/hosts/monitor/setting`, {}); +}; +export const updateMonitorSetting = (key: string, value: string) => { + return http.post(`/hosts/monitor/setting/update`, { key: key, value: value }); +}; + +// ssh +export const getSSHInfo = () => { + return http.post(`/hosts/ssh/search`); +}; +export const operateSSH = (operation: string) => { + return http.post(`/hosts/ssh/operate`, { operation: operation }, TimeoutEnum.T_40S); +}; +export const updateSSH = (params: Host.SSHUpdate) => { + return http.post(`/hosts/ssh/update`, params, TimeoutEnum.T_40S); +}; +export const loadSSHFile = (name: string) => { + return http.post(`/hosts/ssh/file`, { name: name }); +}; +export const updateSSHByFile = (key: string, file: string) => { + return http.post(`/hosts/ssh/file/update`, { key: key, value: file }, TimeoutEnum.T_60S); +}; +export const createCert = (params: Host.RootCert) => { + let request = deepCopy(params) as Host.RootCert; + if (request.passPhrase) { + request.passPhrase = Base64.encode(request.passPhrase); + } + if (request.privateKey) { + request.privateKey = Base64.encode(request.privateKey); + } + if (request.publicKey) { + request.publicKey = Base64.encode(request.publicKey); + } + return http.post(`/hosts/ssh/cert`, request); +}; +export const editCert = (params: Host.RootCert) => { + let request = deepCopy(params) as Host.RootCert; + if (request.passPhrase) { + request.passPhrase = Base64.encode(request.passPhrase); + } + if (request.privateKey) { + request.privateKey = Base64.encode(request.privateKey); + } + if (request.publicKey) { + request.publicKey = Base64.encode(request.publicKey); + } + return http.post(`/hosts/ssh/cert/update`, request); +}; +export const searchCert = (params: ReqPage) => { + return http.post>(`/hosts/ssh/cert/search`, params); +}; +export const deleteCert = (ids: Array, forceDelete: boolean) => { + return http.post(`/hosts/ssh/cert/delete`, { ids: ids, forceDelete: forceDelete }); +}; +export const syncCert = () => { + return http.post(`/hosts/ssh/cert/sync`); +}; +export const loadSSHLogs = (params: Host.searchSSHLog) => { + return http.post>(`/hosts/ssh/log`, params); +}; +export const exportSSHLogs = (params: Host.searchSSHLog) => { + return http.post(`/hosts/ssh/log/export`, params, TimeoutEnum.T_40S); +}; + +export const listDisks = () => { + return http.get(`/hosts/disks`); +}; + +export const partitionDisk = (params: Host.DiskPartition) => { + return http.post(`/hosts/disks/partition`, params, TimeoutEnum.T_60S); +}; + +export const mountDisk = (params: Host.DiskMount) => { + return http.post(`/hosts/disks/mount`, params, TimeoutEnum.T_60S); +}; + +export const unmountDisk = (params: Host.DiskUmount) => { + return http.post(`/hosts/disks/unmount`, params, TimeoutEnum.T_60S); +}; + +export const getComponentInfo = (name: string, operateNode?: string) => { + const params = operateNode ? `?operateNode=${operateNode}` : ''; + return http.get(`/hosts/components/${name}${params}`); +}; diff --git a/frontend/src/api/modules/log.ts b/frontend/src/api/modules/log.ts new file mode 100644 index 0000000..311e14f --- /dev/null +++ b/frontend/src/api/modules/log.ts @@ -0,0 +1,29 @@ +import http from '@/api'; +import { ResPage } from '../interface'; +import { Log } from '../interface/log'; + +export const getOperationLogs = (info: Log.SearchOpLog) => { + return http.post>(`/core/logs/operation`, info); +}; + +export const getLoginLogs = (info: Log.SearchLgLog) => { + return http.post>(`/core/logs/login`, info); +}; + +export const getSystemFiles = (node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.get>(`/logs/system/files${params}`); +}; + +export const cleanLogs = (param: Log.CleanLog) => { + return http.post(`/core/logs/clean`, param); +}; + +export const searchTasks = (req: Log.SearchTaskReq, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.post>(`/logs/tasks/search${params}`, req); +}; + +export const countExecutingTask = () => { + return http.get(`/logs/tasks/executing/count`); +}; diff --git a/frontend/src/api/modules/nginx.ts b/frontend/src/api/modules/nginx.ts new file mode 100644 index 0000000..b4120a1 --- /dev/null +++ b/frontend/src/api/modules/nginx.ts @@ -0,0 +1,43 @@ +import http from '@/api'; +import { File } from '../interface/file'; +import { Nginx } from '../interface/nginx'; + +export const getNginx = () => { + return http.get(`/openresty`); +}; + +export const getNginxConfigByScope = (req: Nginx.NginxScopeReq) => { + return http.post(`/openresty/scope`, req); +}; + +export const updateNginxConfigByScope = (req: Nginx.NginxConfigReq) => { + return http.post(`/openresty/update`, req); +}; + +export const getNginxStatus = () => { + return http.get(`/openresty/status`); +}; + +export const updateNginxConfigFile = (req: Nginx.NginxFileUpdate) => { + return http.post(`/openresty/file`, req); +}; + +export const buildNginx = (req: Nginx.NginxBuildReq) => { + return http.post(`/openresty/build`, req); +}; + +export const getNginxModules = () => { + return http.get(`/openresty/modules`); +}; + +export const updateNginxModule = (req: Nginx.NginxModuleUpdate) => { + return http.post(`/openresty/modules/update`, req); +}; + +export const getHttpsStatus = () => { + return http.get(`/openresty/https`); +}; + +export const operateHttps = (req: Nginx.NginxOperateReq) => { + return http.post(`/openresty/https`, req); +}; diff --git a/frontend/src/api/modules/process.ts b/frontend/src/api/modules/process.ts new file mode 100644 index 0000000..16ac1fb --- /dev/null +++ b/frontend/src/api/modules/process.ts @@ -0,0 +1,14 @@ +import http from '@/api'; +import { Process } from '../interface/process'; + +export const stopProcess = (req: Process.StopReq) => { + return http.post(`/process/stop`, req); +}; + +export const getProcessByID = (pid: number) => { + return http.get(`/process/${pid}`); +}; + +export const getListeningProcess = () => { + return http.post(`/process/listening`); +}; diff --git a/frontend/src/api/modules/runtime.ts b/frontend/src/api/modules/runtime.ts new file mode 100644 index 0000000..8c706b7 --- /dev/null +++ b/frontend/src/api/modules/runtime.ts @@ -0,0 +1,139 @@ +import http from '@/api'; +import { ResPage, ReqPage } from '../interface'; +import { Runtime } from '../interface/runtime'; +import { TimeoutEnum } from '@/enums/http-enum'; +import { App } from '@/api/interface/app'; +import { File } from '../interface/file'; +import { HostTool } from '../interface/host-tool'; + +export const SearchRuntimes = (req: Runtime.RuntimeReq) => { + return http.post>(`/runtimes/search`, req); +}; + +export const CreateRuntime = (req: Runtime.RuntimeCreate) => { + return http.post(`/runtimes`, req); +}; + +export const DeleteRuntime = (req: Runtime.RuntimeDelete) => { + return http.post(`/runtimes/del`, req); +}; + +export const RuntimeDeleteCheck = (runTimeId: number) => { + return http.get(`runtimes/installed/delete/check/${runTimeId}`); +}; + +export const GetRuntime = (id: number) => { + return http.get(`/runtimes/${id}`); +}; + +export const UpdateRuntime = (req: Runtime.RuntimeUpdate) => { + return http.post(`/runtimes/update`, req); +}; + +export const GetNodeScripts = (req: Runtime.NodeReq) => { + return http.post(`/runtimes/node/package`, req); +}; + +export const OperateRuntime = (req: Runtime.RuntimeOperate) => { + return http.post(`/runtimes/operate`, req); +}; + +export const GetNodeModules = (req: Runtime.NodeModuleReq) => { + return http.post(`/runtimes/node/modules`, req); +}; + +export const OperateNodeModule = (req: Runtime.NodeModuleReq) => { + return http.post(`/runtimes/node/modules/operate`, req, TimeoutEnum.T_10M); +}; + +export const SearchPHPExtensions = (req: ReqPage) => { + return http.post>(`/runtimes/php/extensions/search`, req); +}; + +export const ListPHPExtensions = (req: Runtime.PHPExtensionsList) => { + return http.post>(`/runtimes/php/extensions/search`, req); +}; + +export const CreatePHPExtensions = (req: Runtime.PHPExtensionsCreate) => { + return http.post(`/runtimes/php/extensions`, req); +}; + +export const UpdatePHPExtensions = (req: Runtime.PHPExtensionsUpdate) => { + return http.post(`/runtimes/php/extensions/update`, req); +}; + +export const DeletePHPExtensions = (req: Runtime.PHPExtensionsDelete) => { + return http.post(`/runtimes/php/extensions/del`, req); +}; + +export const SyncRuntime = () => { + return http.post(`/runtimes/sync`, {}); +}; + +export const GetPHPExtensions = (id: number) => { + return http.get(`/runtimes/php/${id}/extensions`); +}; + +export const InstallPHPExtension = (req: Runtime.PHPExtensionInstall) => { + return http.post(`/runtimes/php/extensions/install`, req); +}; + +export const UnInstallPHPExtension = (req: Runtime.PHPExtensionInstall) => { + return http.post(`/runtimes/php/extensions/uninstall`, req); +}; + +export const GetPHPConfig = (id: number) => { + return http.get(`/runtimes/php/config/${id}`); +}; + +export const UpdatePHPConfig = (req: Runtime.PHPConfigUpdate) => { + return http.post(`/runtimes/php/config`, req); +}; + +export const UpdatePHPFile = (req: Runtime.PHPUpdate) => { + return http.post(`/runtimes/php/update`, req); +}; + +export const GetPHPConfigFile = (req: Runtime.PHPFileReq) => { + return http.post(`/runtimes/php/file`, req); +}; + +export const UpdateFPMConfig = (req: Runtime.FPMConfig) => { + return http.post(`/runtimes/php/fpm/config`, req); +}; + +export const GetFPMConfig = (id: number) => { + return http.get(`/runtimes/php/fpm/config/${id}`); +}; + +export const GetSupervisorProcess = (id: number) => { + return http.get(`/runtimes/supervisor/process/${id}`); +}; + +export const operateSupervisorProcess = (req: Runtime.ProcessReq) => { + return http.post(`/runtimes/supervisor/process`, req, TimeoutEnum.T_60S); +}; + +export const operateSupervisorProcessFile = (req: Runtime.ProcessFileReq) => { + return http.post(`/runtimes/supervisor/process/file`, req, TimeoutEnum.T_60S); +}; + +export const createSupervisorProcess = (req: Runtime.SupersivorProcess) => { + return http.post(`/runtimes/supervisor/process`, req); +}; + +export const getPHPContainerConfig = (id: number) => { + return http.get(`/runtimes/php/container/${id}`); +}; + +export const updatePHPContainerConfig = (req: Runtime.PHPContainerConfig) => { + return http.post(`/runtimes/php/container/update`, req); +}; + +export const updateRemark = (req: Runtime.RemarkUpdate) => { + return http.post(`/runtimes/remark`, req); +}; + +export const getFPMStatus = (id: number) => { + return http.get(`/runtimes/php/fpm/status/${id}`); +}; diff --git a/frontend/src/api/modules/setting.ts b/frontend/src/api/modules/setting.ts new file mode 100644 index 0000000..3fd346f --- /dev/null +++ b/frontend/src/api/modules/setting.ts @@ -0,0 +1,214 @@ +import http from '@/api'; +import { deepCopy } from '@/utils/util'; +import { Base64 } from 'js-base64'; +import { ResPage, SearchWithPage, DescriptionUpdate, ReqPage } from '../interface'; +import { Setting } from '../interface/setting'; +import { TimeoutEnum } from '@/enums/http-enum'; +import { App } from '../interface/app'; + +// license +export const uploadLicense = (oldLicense: string, params: FormData) => { + if (oldLicense === '') { + return http.upload('/core/licenses/upload', params); + } + return http.upload('/core/licenses/update', params); +}; +export const searchLicense = (params: ReqPage) => { + return http.post>('/core/licenses/search', params); +}; +export const deleteLicense = (params: { ids: number }) => { + return http.post('/core/licenses/del', params); +}; +export const getLicenseStatus = () => { + return http.get(`/core/licenses/status`); +}; +export const getMasterLicenseStatus = () => { + return http.get(`/core/licenses/master/status`); +}; +export const syncLicense = (id: number) => { + return http.post(`/core/licenses/sync`, { id: id }); +}; +export const bindLicense = (params: Setting.LicenseBind) => { + return http.post(`/core/licenses/bind`, params, TimeoutEnum.T_60S); +}; +export const unbindLicense = (params: Setting.LicenseUnbind) => { + return http.post(`/core/licenses/unbind`, params, TimeoutEnum.T_60S); +}; +export const changeBind = (id: number, nodeIDs: Array) => { + return http.post(`/core/licenses/bind/free`, { licenseID: id, nodeIDs: nodeIDs }, TimeoutEnum.T_60S); +}; +export const loadLicenseOptions = () => { + return http.get>(`/core/licenses/options`); +}; +export const listNodeOptions = (type: string) => { + return http.post>(`/core/nodes/list`, { type: type }); +}; + +export const listAllNodes = () => { + return http.get>(`/core/nodes/all`); +}; +export const listAllSimpleNodes = () => { + return http.get>(`/core/nodes/simple/all`); +}; + +export const getLicenseSmsInfo = () => { + return http.get(`/core/licenses/sms/info`); +}; + +export const listAppNodes = () => { + return http.get>(`/core/xpack/nodes/apps/update`, {}, { timeout: TimeoutEnum.T_60S }); +}; + +// agent +export const loadBaseDir = () => { + return http.get(`/settings/basedir`); +}; +export const loadDaemonJsonPath = () => { + return http.get(`/settings/daemonjson`, {}); +}; +export const updateAgentSetting = (param: Setting.SettingUpdate) => { + return http.post(`/settings/update`, param); +}; +export const getAgentSettingInfo = () => { + return http.post(`/settings/search`); +}; +export const getAgentSettingByKey = (key: string) => { + return http.get(`/settings/get/${key}`); +}; +export const updateCommonDescription = (param: Setting.CommonDescription) => { + return http.post(`/settings/description/save`, param); +}; + +// core +export const getSettingInfo = () => { + return http.post(`/core/settings/search`); +}; +export const getSettingBy = (key: string) => { + return http.post(`/core/settings/by`, { key: key }); +}; +export const getTerminalInfo = () => { + return http.post(`/core/settings/terminal/search`); +}; +export const UpdateTerminalInfo = (param: Setting.TerminalInfo) => { + return http.post(`/core/settings/terminal/update`, param); +}; +export const getSystemAvailable = () => { + return http.get(`/core/settings/search/available`); +}; +export const updateSetting = (param: Setting.SettingUpdate) => { + return http.post(`/core/settings/update`, param); +}; +export const updateMenu = (param: Setting.SettingUpdate) => { + return http.post(`/core/settings/menu/update`, param); +}; +export const defaultMenu = () => { + return http.post(`/core/settings/menu/default`); +}; +export const updateProxy = (params: Setting.ProxyUpdate) => { + let request = deepCopy(params) as Setting.ProxyUpdate; + if (request.proxyPasswd) { + request.proxyPasswd = Base64.encode(request.proxyPasswd); + } + request.proxyType = request.proxyType === 'close' ? '' : request.proxyType; + return http.post(`/core/settings/proxy/update`, request); +}; +export const updatePassword = (param: Setting.PasswordUpdate) => { + return http.post(`/core/settings/password/update`, param); +}; +export const loadInterfaceAddr = () => { + return http.get(`/core/settings/interface`); +}; +export const updateBindInfo = (ipv6: string, bindAddress: string) => { + return http.post(`/core/settings/bind/update`, { ipv6: ipv6, bindAddress: bindAddress }); +}; +export const updatePort = (param: Setting.PortUpdate) => { + return http.post(`/core/settings/port/update`, param); +}; +export const updateSSL = (param: Setting.SSLUpdate) => { + return http.post(`/core/settings/ssl/update`, param); +}; +export const loadSSLInfo = () => { + return http.get(`/core/settings/ssl/info`); +}; +export const downloadSSL = () => { + return http.download(`/core/settings/ssl/download`); +}; +export const handleExpired = (param: Setting.PasswordUpdate) => { + return http.post(`/core/settings/expired/handle`, param); +}; +export const loadMFA = (param: Setting.MFARequest) => { + return http.post(`/core/settings/mfa`, param); +}; +export const bindMFA = (param: Setting.MFABind) => { + return http.post(`/core/settings/mfa/bind`, param); +}; +export const passkeyRegisterBegin = (param: Setting.PasskeyRegisterRequest) => { + return http.post(`/core/settings/passkey/register/begin`, param); +}; +export const passkeyRegisterFinish = (param: Record, sessionId: string) => { + return http.post(`/core/settings/passkey/register/finish`, param, undefined, { 'Passkey-Session': sessionId }); +}; +export const passkeyList = () => { + return http.get>(`/core/settings/passkey/list`); +}; +export const passkeyDelete = (id: string) => { + return http.delete(`/core/settings/passkey/${id}`); +}; +export const getAppStoreConfig = (node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.get(`/core/settings/apps/store/config${params}`); +}; +export const updateAppStoreConfig = (req: App.AppStoreConfigUpdate) => { + return http.post(`/core/settings/apps/store/update`, req); +}; + +// snapshot +export const loadSnapshotInfo = () => { + return http.get(`/settings/snapshot/load`, {}, { timeout: TimeoutEnum.T_60S }); +}; +export const snapshotCreate = (param: Setting.SnapshotCreate) => { + return http.post(`/settings/snapshot`, param); +}; +export const snapshotRecreate = (id: number) => { + return http.post(`/settings/snapshot/recreate`, { id: id }); +}; +export const snapshotImport = (param: Setting.SnapshotImport) => { + return http.post(`/settings/snapshot/import`, param); +}; +export const updateSnapshotDescription = (param: DescriptionUpdate) => { + return http.post(`/settings/snapshot/description/update`, param); +}; +export const snapshotDelete = (param: { ids: number[]; deleteWithFile: boolean }) => { + return http.post(`/settings/snapshot/del`, param); +}; +export const snapshotRecover = (param: Setting.SnapshotRecover) => { + return http.post(`/settings/snapshot/recover`, param); +}; +export const snapshotRollback = (param: Setting.SnapshotRecover) => { + return http.post(`/settings/snapshot/rollback`, param); +}; +export const searchSnapshotPage = (param: SearchWithPage) => { + return http.post>(`/settings/snapshot/search`, param); +}; + +// upgrade +export const loadUpgradeInfo = () => { + return http.get(`/core/settings/upgrade`); +}; +export const loadReleaseNotes = (version: string) => { + return http.post(`/core/settings/upgrade/notes`, { version: version }); +}; +export const listReleases = () => { + return http.get>(`/core/settings/upgrade/releases`); +}; +export const upgrade = (version: string) => { + return http.post(`/core/settings/upgrade`, { version: version }); +}; + +// api config +export const generateApiKey = () => { + return http.post(`/core/settings/api/config/generate/key`); +}; +export const updateApiConfig = (param: Setting.ApiConfig) => { + return http.post(`/core/settings/api/config/update`, param); +}; diff --git a/frontend/src/api/modules/terminal.ts b/frontend/src/api/modules/terminal.ts new file mode 100644 index 0000000..3066028 --- /dev/null +++ b/frontend/src/api/modules/terminal.ts @@ -0,0 +1,71 @@ +import http from '@/api'; +import { ResPage } from '../interface'; +import { Host } from '../interface/host'; +import { Base64 } from 'js-base64'; +import { deepCopy } from '@/utils/util'; + +export const searchHosts = (params: Host.SearchWithPage) => { + return http.post>(`/core/hosts/search`, params); +}; +export const getHostByID = (id: number) => { + return http.post(`/core/hosts/info`, { id: id }); +}; +export const getHostTree = (params: Host.ReqSearch) => { + return http.post>(`/core/hosts/tree`, params); +}; +export const updateLocalConn = (param: { withReset: boolean; defaultConn: string }) => { + return http.post(`/settings/ssh/default`, param); +}; +export const addHost = (params: Host.HostOperate) => { + let request = deepCopy(params) as Host.HostOperate; + if (request.password) { + request.password = Base64.encode(request.password); + } + if (request.privateKey) { + request.privateKey = Base64.encode(request.privateKey); + } + if (params.isLocal) { + return http.post(`/settings/ssh`, request); + } + return http.post(`/core/hosts`, request); +}; +export const testByInfo = (params: Host.HostConnTest) => { + let request = deepCopy(params) as Host.HostOperate; + if (request.password) { + request.password = Base64.encode(request.password); + } + if (request.privateKey) { + request.privateKey = Base64.encode(request.privateKey); + } + if (params.isLocal) { + return http.post(`/settings/ssh/check/info`, request); + } + return http.post(`/core/hosts/test/byinfo`, request); +}; +export const testByID = (id: number) => { + return http.post(`/core/hosts/test/byid/${id}`); +}; +export const editHost = (params: Host.HostOperate) => { + let request = deepCopy(params) as Host.HostOperate; + if (request.password) { + request.password = Base64.encode(request.password); + } + if (request.privateKey) { + request.privateKey = Base64.encode(request.privateKey); + } + return http.post(`/core/hosts/update`, request); +}; +export const editHostGroup = (params: Host.GroupChange) => { + return http.post(`/core/hosts/update/group`, params); +}; +export const deleteHost = (params: { ids: number[] }) => { + return http.post(`/core/hosts/del`, params); +}; + +// agent +export const loadLocalConn = () => { + return http.get(`/settings/ssh/conn`); +}; +export const testLocalConn = () => { + return http.post(`/settings/ssh/check`); +}; diff --git a/frontend/src/api/modules/toolbox.ts b/frontend/src/api/modules/toolbox.ts new file mode 100644 index 0000000..3eb1744 --- /dev/null +++ b/frontend/src/api/modules/toolbox.ts @@ -0,0 +1,149 @@ +import http from '@/api'; +import { ReqPage, ResPage, UpdateByFile } from '../interface'; +import { Toolbox } from '../interface/toolbox'; +import { Base64 } from 'js-base64'; +import { TimeoutEnum } from '@/enums/http-enum'; +import { deepCopy } from '@/utils/util'; + +// device +export const getDeviceBase = () => { + return http.post(`/toolbox/device/base`, {}, TimeoutEnum.T_60S); +}; +export const loadTimeZoneOptions = () => { + return http.get>(`/toolbox/device/zone/options`); +}; +export const loadUsers = () => { + return http.get>(`/toolbox/device/users`); +}; +export const updateDevice = (key: string, value: string) => { + return http.post(`/toolbox/device/update/conf`, { key: key, value: value }, TimeoutEnum.T_60S); +}; +export const updateDeviceHost = (param: Array) => { + return http.post(`/toolbox/device/update/host`, param, TimeoutEnum.T_60S); +}; +export const updateDevicePasswd = (user: string, passwd: string) => { + return http.post(`/toolbox/device/update/passwd`, { user: user, passwd: Base64.encode(passwd) }, TimeoutEnum.T_60S); +}; +export const updateDeviceSwap = (params: Toolbox.SwapHelper) => { + return http.post(`/toolbox/device/update/swap`, params, TimeoutEnum.T_10M); +}; +export const updateDeviceByConf = (name: string, file: string) => { + return http.post(`/toolbox/device/update/byconf`, { name: name, file: file }, TimeoutEnum.T_5M); +}; +export const checkDNS = (key: string, value: string) => { + return http.post(`/toolbox/device/check/dns`, { key: key, value: value }); +}; +export const loadDeviceConf = (name: string) => { + return http.post(`/toolbox/device/conf`, { name: name }); +}; + +// clean +export const scan = () => { + return http.post(`/toolbox/scan`, {}); +}; +export const clean = (param: any) => { + return http.post(`/toolbox/clean`, param, TimeoutEnum.T_5M); +}; + +// fail2ban +export const getFail2banBase = () => { + return http.get(`/toolbox/fail2ban/base`); +}; +export const getFail2banConf = () => { + return http.get(`/toolbox/fail2ban/load/conf`); +}; + +export const searchFail2ban = (param: Toolbox.Fail2banSearch) => { + return http.post>(`/toolbox/fail2ban/search`, param); +}; + +export const operateFail2ban = (operate: string) => { + return http.post(`/toolbox/fail2ban/operate`, { operation: operate }, TimeoutEnum.T_5M); +}; + +export const operatorFail2banSSHD = (param: Toolbox.Fail2banSet) => { + return http.post(`/toolbox/fail2ban/operate/sshd`, param, TimeoutEnum.T_5M); +}; + +export const updateFail2ban = (param: Toolbox.Fail2banUpdate) => { + return http.post(`/toolbox/fail2ban/update`, param, TimeoutEnum.T_5M); +}; + +export const updateFail2banByFile = (param: UpdateByFile) => { + return http.post(`/toolbox/fail2ban/update/byconf`, param, TimeoutEnum.T_5M); +}; + +// ftp +export const getFtpBase = () => { + return http.get(`/toolbox/ftp/base`); +}; +export const searchFtpLog = (param: Toolbox.FtpSearchLog) => { + return http.post>(`/toolbox/ftp/log/search`, param); +}; +export const searchFtp = (param: ReqPage) => { + return http.post>(`/toolbox/ftp/search`, param); +}; +export const operateFtp = (operate: string) => { + return http.post(`/toolbox/ftp/operate`, { operation: operate }, TimeoutEnum.T_5M); +}; +export const syncFtp = () => { + return http.post(`/toolbox/ftp/sync`); +}; + +export const createFtp = (params: Toolbox.FtpCreate) => { + let request = deepCopy(params) as Toolbox.FtpCreate; + if (request.password) { + request.password = Base64.encode(request.password); + } + return http.post(`/toolbox/ftp`, request); +}; + +export const updateFtp = (params: Toolbox.FtpUpdate) => { + let request = deepCopy(params) as Toolbox.FtpUpdate; + if (request.password) { + request.password = Base64.encode(request.password); + } + return http.post(`/toolbox/ftp/update`, request); +}; + +export const deleteFtp = (params: { ids: number[] }) => { + return http.post(`/toolbox/ftp/del`, params); +}; + +// clam +export const cleanClamRecord = (id: number) => { + return http.post(`/toolbox/clam/record/clean`, { id: id }); +}; +export const searchClamRecord = (param: Toolbox.ClamSearchLog) => { + return http.post>(`/toolbox/clam/record/search`, param); +}; +export const searchClamFile = (name: string, tail: string) => { + return http.post(`/toolbox/clam/file/search`, { name: name, tail: tail }); +}; +export const updateClamFile = (name: string, file: string) => { + return http.post(`/toolbox/clam/file/update`, { name: name, file: file }, TimeoutEnum.T_60S); +}; +export const searchClamBaseInfo = () => { + return http.post(`/toolbox/clam/base`); +}; +export const updateClamBaseInfo = (operate: string) => { + return http.post(`/toolbox/clam/operate`, { Operation: operate }, TimeoutEnum.T_60S); +}; +export const searchClam = (param: ReqPage) => { + return http.post>(`/toolbox/clam/search`, param); +}; +export const createClam = (params: Toolbox.ClamCreate) => { + return http.post(`/toolbox/clam`, params); +}; +export const updateClam = (params: Toolbox.ClamUpdate) => { + return http.post(`/toolbox/clam/update`, params); +}; +export const updateClamStatus = (id: number, status: string) => { + return http.post(`/toolbox/clam/status/update`, { id: id, status: status }); +}; +export const deleteClam = (params: { ids: number[]; removeInfected: boolean }) => { + return http.post(`/toolbox/clam/del`, params); +}; +export const handleClamScan = (id: number) => { + return http.post(`/toolbox/clam/handle`, { id: id }); +}; diff --git a/frontend/src/api/modules/website.ts b/frontend/src/api/modules/website.ts new file mode 100644 index 0000000..cd95d80 --- /dev/null +++ b/frontend/src/api/modules/website.ts @@ -0,0 +1,383 @@ +import http from '@/api'; +import { ReqPage, ResPage } from '../interface'; +import { Website } from '../interface/website'; +import { File } from '../interface/file'; +import { TimeoutEnum } from '@/enums/http-enum'; +import { deepCopy } from '@/utils/util'; +import { Base64 } from 'js-base64'; + +export const searchWebsites = (req: Website.WebSiteSearch, node?: string) => { + const params = node ? `?operateNode=${node}` : ''; + return http.post>(`/websites/search${params}`, req); +}; + +export const listWebsites = () => { + return http.get(`/websites/list`); +}; + +export const createWebsite = (req: Website.WebSiteCreateReq) => { + let request = deepCopy(req) as Website.WebSiteCreateReq; + if (request.ftpPassword) { + request.ftpPassword = Base64.encode(request.ftpPassword); + } + return http.post(`/websites`, request, TimeoutEnum.T_10M); +}; + +export const opWebsite = (req: Website.WebSiteOp, node?: string) => { + const query = node ? `?operateNode=${node}` : ''; + return http.post(`/websites/operate${query}`, req); +}; + +export const opWebsiteLog = (req: Website.WebSiteOpLog) => { + return http.post(`/websites/log`, req); +}; + +export const updateWebsite = (req: Website.WebSiteUpdateReq) => { + return http.post(`/websites/update`, req); +}; + +export const getWebsite = (id: number) => { + return http.get(`/websites/${id}`); +}; + +export const getWebsiteOptions = (req: Website.OptionReq) => { + return http.post(`/websites/options`, req); +}; + +export const getWebsiteConfig = (id: number, type: string) => { + return http.get(`/websites/${id}/config/${type}`); +}; + +export const deleteWebsite = (req: Website.WebSiteDel) => { + return http.post(`/websites/del`, req); +}; + +export const listDomains = (id: number) => { + return http.get(`/websites/domains/${id}`); +}; + +export const deleteDomain = (req: Website.DomainDelete) => { + return http.post(`/websites/domains/del/`, req); +}; + +export const createDomain = (req: Website.DomainCreate) => { + return http.post(`/websites/domains`, req); +}; + +export const updateDomain = (req: Website.DomainUpdate) => { + return http.post(`/websites/domains/update`, req); +}; + +export const getNginxConfig = (req: Website.NginxScopeReq) => { + return http.post(`/websites/config`, req); +}; + +export const updateNginxConfig = (req: Website.NginxConfigReq) => { + return http.post(`/websites/config/update`, req); +}; + +export const searchDnsAccount = (req: ReqPage) => { + return http.post>(`/websites/dns/search`, req); +}; + +export const createDnsAccount = (req: Website.DnsAccountCreate) => { + return http.post(`/websites/dns`, req); +}; + +export const updateDnsAccount = (req: Website.DnsAccountUpdate) => { + return http.post(`/websites/dns/update`, req); +}; + +export const deleteDnsAccount = (req: Website.DelReq) => { + return http.post(`/websites/dns/del`, req); +}; + +export const searchAcmeAccount = (req: ReqPage) => { + return http.post>(`/websites/acme/search`, req); +}; + +export const createAcmeAccount = (req: Website.AcmeAccountCreate) => { + return http.post(`/websites/acme`, req, TimeoutEnum.T_10M); +}; + +export const deleteAcmeAccount = (req: Website.DelReq) => { + return http.post(`/websites/acme/del`, req); +}; + +export const updateAcmeAccount = (req: Website.AcmeAccountUpdate) => { + return http.post(`/websites/acme/update`, req, TimeoutEnum.T_10M); +}; + +export const searchSSL = (req: ReqPage) => { + return http.post>(`/websites/ssl/search`, req); +}; + +export const listSSL = (req: Website.SSLReq) => { + return http.post(`/websites/ssl/search`, req); +}; + +export const listLocalNodeSSL = (req: Website.SSLReq) => { + return http.postLocalNode(`/websites/ssl/search`, req); +}; + +export const createSSL = (req: Website.SSLCreate) => { + return http.post(`/websites/ssl`, req, TimeoutEnum.T_10M); +}; + +export const deleteSSL = (req: Website.DelReq) => { + return http.post(`/websites/ssl/del`, req); +}; + +export const getSSL = (id: number) => { + return http.get(`/websites/ssl/${id}`); +}; + +export const obtainSSL = (req: Website.SSLObtain) => { + return http.post(`/websites/ssl/obtain`, req); +}; + +export const updateSSL = (req: Website.SSLUpdate) => { + return http.post(`/websites/ssl/update`, req); +}; + +export const getDnsResolve = (req: Website.DNSResolveReq) => { + return http.post(`/websites/ssl/resolve`, req, TimeoutEnum.T_5M); +}; + +export const getHTTPSConfig = (id: number) => { + return http.get(`/websites/${id}/https`); +}; + +export const updateHTTPSConfig = (req: Website.HTTPSReq) => { + return http.post(`/websites/${req.websiteId}/https`, req); +}; + +export const preCheck = (req: Website.CheckReq) => { + return http.post(`/websites/check`, req); +}; + +export const updateNginxFile = (req: Website.NginxUpdate) => { + return http.post(`/websites/nginx/update`, req, TimeoutEnum.T_3M); +}; + +export const changeDefaultServer = (req: Website.DefaultServerUpdate) => { + return http.post(`/websites/default/server`, req); +}; + +export const getRewriteConfig = (req: Website.RewriteReq) => { + return http.post(`/websites/rewrite`, req); +}; + +export const updateRewriteConfig = (req: Website.RewriteUpdate) => { + return http.post(`/websites/rewrite/update`, req); +}; + +export const updateWebsiteDir = (req: Website.DirUpdate) => { + return http.post(`/websites/dir/update`, req); +}; + +export const updateWebsiteDirPermission = (req: Website.DirPermissionUpdate) => { + return http.post(`/websites/dir/permission`, req); +}; + +export const getProxyConfig = (req: Website.ProxyReq) => { + return http.post(`/websites/proxies`, req); +}; + +export const operateProxyConfig = (req: Website.ProxyReq) => { + return http.post(`/websites/proxies/update`, req); +}; + +export const updateProxyConfigFile = (req: Website.ProxyFileUpdate) => { + return http.post(`/websites/proxies/file`, req); +}; + +export const clearProxyCache = (req: Website.WebsiteReq) => { + return http.post(`/websites/proxy/clear`, req); +}; + +export const getAuthConfig = (req: Website.AuthReq) => { + return http.post(`/websites/auths`, req); +}; + +export const operateAuthConfig = (req: Website.NginxAuthConfig) => { + return http.post(`/websites/auths/update`, req); +}; + +export const getPathAuthConfig = (req: Website.AuthReq) => { + return http.post(`/websites/auths/path`, req); +}; + +export const operatePathAuthConfig = (req: Website.NginxPathAuthConfig) => { + return http.post(`/websites/auths/path/update`, req); +}; + +export const getAntiLeech = (req: Website.LeechReq) => { + return http.post(`/websites/leech`, req); +}; + +export const updateAntiLeech = (req: Website.LeechConfig) => { + return http.post(`/websites/leech/update`, req); +}; + +export const getRedirectConfig = (req: Website.WebsiteReq) => { + return http.post(`/websites/redirect`, req); +}; + +export const operateRedirectConfig = (req: Website.WebsiteReq) => { + return http.post(`/websites/redirect/update`, req); +}; + +export const updateRedirectConfigFile = (req: Website.RedirectFileUpdate) => { + return http.post(`/websites/redirect/file`, req); +}; + +export const changePHPVersion = (req: Website.PHPVersionChange) => { + return http.post(`/websites/php/version`, req); +}; + +export const getDirConfig = (req: Website.ProxyReq) => { + return http.post(`/websites/dir`, req); +}; + +export const uploadSSL = (req: Website.SSLUpload) => { + return http.post(`/websites/ssl/upload`, req); +}; + +export const uploadSSLFile = (params: FormData) => { + return http.upload(`/websites/ssl/upload/file`, params, {}); +}; + +export const searchCAs = (req: ReqPage) => { + return http.post>(`/websites/ca/search`, req); +}; + +export const createCA = (req: Website.CACreate) => { + return http.post(`/websites/ca`, req); +}; + +export const obtainSSLByCA = (req: Website.SSLObtainByCA) => { + return http.post(`/websites/ca/obtain`, req); +}; + +export const deleteCA = (req: Website.DelReq) => { + return http.post(`/websites/ca/del`, req); +}; + +export const renewSSLByCA = (req: Website.RenewSSLByCA) => { + return http.post(`/websites/ca/renew`, req); +}; + +export const downloadFile = (params: Website.SSLDownload) => { + return http.download(`/websites/ssl/download`, params, { + responseType: 'blob', + timeout: TimeoutEnum.T_40S, + }); +}; + +export const getCA = (id: number) => { + return http.get(`/websites/ca/${id}`); +}; + +export const getDefaultHtml = (type: string) => { + return http.get(`/websites/default/html/${type}`); +}; + +export const updateDefaultHtml = (req: Website.WebsiteHtmlUpdate) => { + return http.post(`/websites/default/html/update`, req); +}; + +export const downloadCAFile = (params: Website.SSLDownload) => { + return http.download(`/websites/ca/download`, params, { + responseType: 'blob', + timeout: TimeoutEnum.T_40S, + }); +}; + +export const getLoadBalances = (id: number) => { + return http.get(`/websites/${id}/lbs`); +}; + +export const createLoadBalance = (req: Website.LoadBalanceReq) => { + return http.post(`/websites/lbs/create`, req); +}; + +export const deleteLoadBalance = (req: Website.LoadBalanceDel) => { + return http.post(`/websites/lbs/del`, req); +}; + +export const updateLoadBalance = (req: Website.LoadBalanceReq) => { + return http.post(`/websites/lbs/update`, req); +}; + +export const updateLoadBalanceFile = (req: Website.WebsiteLBUpdateFile) => { + return http.post(`/websites/lbs/file`, req); +}; + +export const updateCacheConfig = (req: Website.WebsiteCacheConfig) => { + return http.post(`/websites/proxy/config`, req); +}; + +export const getCacheConfig = (id: number) => { + return http.get(`/websites/proxy/config/${id}`); +}; + +export const updateRealIPConfig = (req: Website.WebsiteRealIPConfig) => { + return http.post(`/websites/realip/config`, req); +}; + +export const getRealIPConfig = (id: number) => { + return http.get(`/websites/realip/config/${id}`); +}; + +export const getWebsiteResource = (id: number) => { + return http.get(`/websites/resource/${id}`); +}; + +export const getWebsiteDatabase = () => { + return http.get(`/websites/databases`); +}; + +export const changeDatabase = (req: Website.ChangeDatabase) => { + return http.post(`/websites/databases`, req); +}; + +export const operateCustomRewrite = (req: Website.CustomRewirte) => { + return http.post(`/websites/rewrite/custom`, req); +}; + +export const listCustomRewrite = () => { + return http.get(`/websites/rewrite/custom`); +}; + +export const operateCrossSiteAccess = (req: Website.CrossSiteAccessOp) => { + return http.post(`/websites/crosssite`, req); +}; + +export const execComposer = (req: Website.ExecComposer) => { + return http.post(`/websites/exec/composer`, req); +}; + +export const batchOpreate = (req: Website.BatchOperate) => { + return http.post(`/websites/batch/operate`, req); +}; + +export const getCorsConfig = (id: number) => { + return http.get(`/websites/cors/${id}`); +}; + +export const updateCorsConfig = (req: Website.CorsConfigReq) => { + return http.post(`/websites/cors/update`, req); +}; + +export const batchSetGroup = (req: Website.BatchSetGroup) => { + return http.post(`/websites/batch/group`, req); +}; + +export const updateWebsiteStream = (req: Website.WebsiteStreamUpdate) => { + return http.post(`/websites/stream/update`, req); +}; + +export const batchSetHttps = (req: Website.BatchSetHttps) => { + return http.post(`/websites/batch/ssl`, req); +}; diff --git a/frontend/src/assets/iconfont/iconfont.css b/frontend/src/assets/iconfont/iconfont.css new file mode 100644 index 0000000..7bcac26 --- /dev/null +++ b/frontend/src/assets/iconfont/iconfont.css @@ -0,0 +1,516 @@ +@font-face { + font-family: "iconfont"; /* Project id 4776196 */ + src: url('iconfont.woff2?t=1760516880031') format('woff2'), + url('iconfont.woff?t=1760516880031') format('woff'), + url('iconfont.ttf?t=1760516880031') format('truetype'), + url('iconfont.svg?t=1760516880031#iconfont') format('svg'); +} + +.iconfont { + font-family: "iconfont" !important; + font-size: 16px; + font-style: normal; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +.p-run-menu:before { + content: "\e636"; +} + +.p-ssh-menu:before { + content: "\e668"; +} + +.p-mcp-menu:before { + content: "\e62b"; +} + +.p-ai-menu:before { + content: "\e7bf"; +} + +.p-moxing-menu:before { + content: "\e62c"; +} + +.p-ah-menu:before { + content: "\e6ba"; +} + +.p-tamper-menu:before { + content: "\e61c"; +} + +.p-ssl-menu:before { + content: "\e7b0"; +} + +.p-file-menu:before { + content: "\e609"; +} + +.p-system-monitor-menu:before { + content: "\e621"; +} + +.p-gpu-menu:before { + content: "\e631"; +} + +.p-waf-menu:before { + content: "\e623"; +} + +.p-xsetting-menu:before { + content: "\e624"; +} + +.p-disk-menu:before { + content: "\e6e5"; +} + +.p-exchange-menu:before { + content: "\e625"; +} + +.p-mobile-menu:before { + content: "\e628"; +} + +.p-process-menu:before { + content: "\ec35"; +} + +.p-firewalld-menu:before { + content: "\e798"; +} + +.p-monitor-menu:before { + content: "\e64a"; +} + +.p-node-menu:before { + content: "\e813"; +} + +.p-featureshitu:before { + content: "\e63e"; +} + +.p-youhuawendang:before { + content: "\e7c4"; +} + +.p-cluster-3:before { + content: "\e706"; +} + +.p-cluster-2:before { + content: "\e61f"; +} + +.p-cluster-1:before { + content: "\e7d8"; +} + +.p-start:before { + content: "\e688"; +} + +.p-stop:before { + content: "\e750"; +} + +.p-node-4:before { + content: "\e73c"; +} + +.p-node-3:before { + content: "\e7d7"; +} + +.p-file-2:before { + content: "\e6bf"; +} + +.p-file-1:before { + content: "\e742"; +} + +.p-file-3:before { + content: "\e607"; +} + +.p-huobao1:before { + content: "\e604"; +} + +.p-gerenzhongxin1:before { + content: "\e61e"; +} + +.p-jiqiren2:before { + content: "\e61b"; +} + +.p-terminal2:before { + content: "\e82f"; +} + +.p-tuijian:before { + content: "\e627"; +} + +.p-node-1:before { + content: "\e61a"; +} + +.p-node-2:before { + content: "\e647"; +} + +.p-tongyijiancha:before { + content: "\e619"; +} + +.p-alert-3:before { + content: "\e728"; +} + +.p-file-zip:before { + content: "\e606"; +} + +.p-file-normal:before { + content: "\e7ac"; +} + +.p-txt:before { + content: "\e6e3"; +} + +.p-file-folder:before { + content: "\e600"; +} + +.p-file-unknown:before { + content: "\e601"; +} + +.p-file-txt:before { + content: "\e602"; +} + +.p-language:before { + content: "\e605"; +} + +.p-theme:before { + content: "\e638"; +} + +.p-arrow-right:before { + content: "\e665"; +} + +.p-docker:before { + content: "\e659"; +} + +.p-appstore:before { + content: "\eb65"; +} + +.p-website:before { + content: "\e781"; +} + +.p-config:before { + content: "\e78e"; +} + +.p-appstore1:before { + content: "\e792"; +} + +.p-log:before { + content: "\e793"; +} + +.p-host:before { + content: "\e7b1"; +} + +.p-home:before { + content: "\e7c6"; +} + +.p-plan:before { + content: "\e746"; +} + +.p-database:before { + content: "\e754"; +} + +.p-rejected-order:before { + content: "\e75e"; +} + +.p-toolbox:before { + content: "\e769"; +} + +.p-yingwen:before { + content: "\e6c3"; +} + +.p-zhongwen:before { + content: "\e6c8"; +} + +.p-logout:before { + content: "\e8fe"; +} + +.p-taolun:before { + content: "\e603"; +} + +.p-bug:before { + content: "\e616"; +} + +.p-huaban88:before { + content: "\e67c"; +} + +.p-star:before { + content: "\e60f"; +} + +.p-file-ppt:before { + content: "\e6e2"; +} + +.p-file-html:before { + content: "\e608"; +} + +.p-file-word:before { + content: "\e6e4"; +} + +.p-file-excel:before { + content: "\e6e6"; +} + +.p-file-pdf:before { + content: "\e6e7"; +} + +.p-file-mp3:before { + content: "\e6e8"; +} + +.p-file-svg:before { + content: "\e6e9"; +} + +.p-file-jpg:before { + content: "\e6ea"; +} + +.p-file-video:before { + content: "\e6eb"; +} + +.p-file-png:before { + content: "\e7ae"; +} + +.p-yanzhengma1:before { + content: "\e744"; +} + +.p-caidan:before { + content: "\e61d"; +} + +.p-xiangqing:before { + content: "\e677"; +} + +.p-webdav:before { + content: "\e622"; +} + +.p-tongji:before { + content: "\e856"; +} + +.p-tamper-4:before { + content: "\e7c3"; +} + +.p-tamper-2:before { + content: "\e610"; +} + +.p-tamper-3:before { + content: "\ec4d"; +} + +.p-tamper-1:before { + content: "\e687"; +} + +.p-setting-1:before { + content: "\e626"; +} + +.p-setting-2:before { + content: "\e630"; +} + +.p-setting-3:before { + content: "\e617"; +} + +.p-waf-4:before { + content: "\e60a"; +} + +.p-waf-1:before { + content: "\e62a"; +} + +.p-waf-2:before { + content: "\e682"; +} + +.p-waf-3:before { + content: "\e666"; +} + +.p-xpack:before { + content: "\e60b"; +} + +.p-monitor-4:before { + content: "\ec4e"; +} + +.p-monitor-2:before { + content: "\ec4f"; +} + +.p-monitor-1:before { + content: "\e60d"; +} + +.p-monitor-3:before { + content: "\ec50"; +} + +.p-m-ios:before { + content: "\e60e"; +} + +.p-m-pc:before { + content: "\e771"; +} + +.p-m-theworld:before { + content: "\e947"; +} + +.p-m-android:before { + content: "\e9e0"; +} + +.p-m-tencent:before { + content: "\e60c"; +} + +.p-m-windows:before { + content: "\e6a0"; +} + +.p-m-machine:before { + content: "\e862"; +} + +.p-m-mobile:before { + content: "\e620"; +} + +.p-m-ucweb:before { + content: "\e611"; +} + +.p-m-edge:before { + content: "\e8e2"; +} + +.p-m-2345explorer:before { + content: "\e612"; +} + +.p-m-chrome:before { + content: "\ea09"; +} + +.p-m-opera:before { + content: "\ea0e"; +} + +.p-m-linux:before { + content: "\e80b"; +} + +.p-m-maxthon:before { + content: "\e676"; +} + +.p-m-mac:before { + content: "\ef2d"; +} + +.p-m-ie:before { + content: "\eaab"; +} + +.p-Chrome-OS:before { + content: "\e613"; +} + +.p-m-safari:before { + content: "\e614"; +} + +.p-m-360se:before { + content: "\e678"; +} + +.p-Firefox:before { + content: "\e87c"; +} + +.p-docker1:before { + content: "\e76a"; +} + +.p-alert-1:before { + content: "\e615"; +} + +.p-alert-2:before { + content: "\e701"; +} + +.p-17:before { + content: "\e618"; +} + diff --git a/frontend/src/assets/iconfont/iconfont.js b/frontend/src/assets/iconfont/iconfont.js new file mode 100644 index 0000000..49172c7 --- /dev/null +++ b/frontend/src/assets/iconfont/iconfont.js @@ -0,0 +1 @@ +window._iconfont_svg_string_4776196='',(c=>{var l=(a=(a=document.getElementsByTagName("script"))[a.length-1]).getAttribute("data-injectcss"),a=a.getAttribute("data-disable-injectsvg");if(!a){var h,t,z,p,v,m=function(l,a){a.parentNode.insertBefore(l,a)};if(l&&!c.__iconfont__svg__cssinject__){c.__iconfont__svg__cssinject__=!0;try{document.write("")}catch(l){console&&console.log(l)}}h=function(){var l,a=document.createElement("div");a.innerHTML=c._iconfont_svg_string_4776196,(a=a.getElementsByTagName("svg")[0])&&(a.setAttribute("aria-hidden","true"),a.style.position="absolute",a.style.width=0,a.style.height=0,a.style.overflow="hidden",a=a,(l=document.body).firstChild?m(a,l.firstChild):l.appendChild(a))},document.addEventListener?~["complete","loaded","interactive"].indexOf(document.readyState)?setTimeout(h,0):(t=function(){document.removeEventListener("DOMContentLoaded",t,!1),h()},document.addEventListener("DOMContentLoaded",t,!1)):document.attachEvent&&(z=h,p=c.document,v=!1,d(),p.onreadystatechange=function(){"complete"==p.readyState&&(p.onreadystatechange=null,M())})}function M(){v||(v=!0,z())}function d(){try{p.documentElement.doScroll("left")}catch(l){return void setTimeout(d,50)}M()}})(window); \ No newline at end of file diff --git a/frontend/src/assets/iconfont/iconfont.json b/frontend/src/assets/iconfont/iconfont.json new file mode 100644 index 0000000..48ce445 --- /dev/null +++ b/frontend/src/assets/iconfont/iconfont.json @@ -0,0 +1,884 @@ +{ + "id": "4776196", + "name": "1Panel-V2", + "font_family": "iconfont", + "css_prefix_text": "p-", + "description": "", + "glyphs": [ + { + "icon_id": "5041257", + "name": "运行环境", + "font_class": "run-menu", + "unicode": "e636", + "unicode_decimal": 58934 + }, + { + "icon_id": "36768791", + "name": "SSH密钥", + "font_class": "ssh-menu", + "unicode": "e668", + "unicode_decimal": 58984 + }, + { + "icon_id": "44603268", + "name": "MCP服务", + "font_class": "mcp-menu", + "unicode": "e62b", + "unicode_decimal": 58923 + }, + { + "icon_id": "44724034", + "name": "线性- AI建站", + "font_class": "ai-menu", + "unicode": "e7bf", + "unicode_decimal": 59327 + }, + { + "icon_id": "45110983", + "name": "大模型-大", + "font_class": "moxing-menu", + "unicode": "e62c", + "unicode_decimal": 58924 + }, + { + "icon_id": "15170483", + "name": "ahas 应用高可用", + "font_class": "ah-menu", + "unicode": "e6ba", + "unicode_decimal": 59066 + }, + { + "icon_id": "1598478", + "name": "网站防篡改", + "font_class": "tamper-menu", + "unicode": "e61c", + "unicode_decimal": 58908 + }, + { + "icon_id": "3141923", + "name": "SSL数字证书", + "font_class": "ssl-menu", + "unicode": "e7b0", + "unicode_decimal": 59312 + }, + { + "icon_id": "4608954", + "name": "文件夹,文件管理", + "font_class": "file-menu", + "unicode": "e609", + "unicode_decimal": 58889 + }, + { + "icon_id": "5653301", + "name": "监控", + "font_class": "system-monitor-menu", + "unicode": "e621", + "unicode_decimal": 58913 + }, + { + "icon_id": "7082471", + "name": "gpu", + "font_class": "gpu-menu", + "unicode": "e631", + "unicode_decimal": 58929 + }, + { + "icon_id": "7948556", + "name": "WAF", + "font_class": "waf-menu", + "unicode": "e623", + "unicode_decimal": 58915 + }, + { + "icon_id": "18270117", + "name": "界面设置", + "font_class": "xsetting-menu", + "unicode": "e624", + "unicode_decimal": 58916 + }, + { + "icon_id": "35113302", + "name": "磁盘管理", + "font_class": "disk-menu", + "unicode": "e6e5", + "unicode_decimal": 59109 + }, + { + "icon_id": "43864633", + "name": "文件互传", + "font_class": "exchange-menu", + "unicode": "e625", + "unicode_decimal": 58917 + }, + { + "icon_id": "397898", + "name": "移动端,手机端", + "font_class": "mobile-menu", + "unicode": "e628", + "unicode_decimal": 58920 + }, + { + "icon_id": "5767874", + "name": "任务进程", + "font_class": "process-menu", + "unicode": "ec35", + "unicode_decimal": 60469 + }, + { + "icon_id": "14095351", + "name": "防火墙", + "font_class": "firewalld-menu", + "unicode": "e798", + "unicode_decimal": 59288 + }, + { + "icon_id": "15009371", + "name": "监测数据", + "font_class": "monitor-menu", + "unicode": "e64a", + "unicode_decimal": 58954 + }, + { + "icon_id": "22231530", + "name": "Data server", + "font_class": "node-menu", + "unicode": "e813", + "unicode_decimal": 59411 + }, + { + "icon_id": "18536446", + "name": "feature视图", + "font_class": "featureshitu", + "unicode": "e63e", + "unicode_decimal": 58942 + }, + { + "icon_id": "18133027", + "name": "优化文档", + "font_class": "youhuawendang", + "unicode": "e7c4", + "unicode_decimal": 59332 + }, + { + "icon_id": "88609", + "name": "cluster-3", + "font_class": "cluster-3", + "unicode": "e706", + "unicode_decimal": 59142 + }, + { + "icon_id": "663330", + "name": "cluster-2", + "font_class": "cluster-2", + "unicode": "e61f", + "unicode_decimal": 58911 + }, + { + "icon_id": "4766884", + "name": "cluster-1", + "font_class": "cluster-1", + "unicode": "e7d8", + "unicode_decimal": 59352 + }, + { + "icon_id": "15838584", + "name": "arrow-right-filling", + "font_class": "start", + "unicode": "e688", + "unicode_decimal": 59016 + }, + { + "icon_id": "212329", + "name": "stop", + "font_class": "stop", + "unicode": "e750", + "unicode_decimal": 59216 + }, + { + "icon_id": "7712749", + "name": "node-4", + "font_class": "node-4", + "unicode": "e73c", + "unicode_decimal": 59196 + }, + { + "icon_id": "10055631", + "name": "node-3", + "font_class": "node-3", + "unicode": "e7d7", + "unicode_decimal": 59351 + }, + { + "icon_id": "22553639", + "name": "file-2", + "font_class": "file-2", + "unicode": "e6bf", + "unicode_decimal": 59071 + }, + { + "icon_id": "25604138", + "name": "file-1", + "font_class": "file-1", + "unicode": "e742", + "unicode_decimal": 59202 + }, + { + "icon_id": "43808299", + "name": "file-3", + "font_class": "file-3", + "unicode": "e607", + "unicode_decimal": 58887 + }, + { + "icon_id": "29971586", + "name": "火爆", + "font_class": "huobao1", + "unicode": "e604", + "unicode_decimal": 58884 + }, + { + "icon_id": "10199157", + "name": "个人中心", + "font_class": "gerenzhongxin1", + "unicode": "e61e", + "unicode_decimal": 58910 + }, + { + "icon_id": "10505865", + "name": "机器人", + "font_class": "jiqiren2", + "unicode": "e61b", + "unicode_decimal": 58907 + }, + { + "icon_id": "5127551", + "name": "terminal", + "font_class": "terminal2", + "unicode": "e82f", + "unicode_decimal": 59439 + }, + { + "icon_id": "34213658", + "name": "推荐", + "font_class": "tuijian", + "unicode": "e627", + "unicode_decimal": 58919 + }, + { + "icon_id": "17895547", + "name": "node-1", + "font_class": "node-1", + "unicode": "e61a", + "unicode_decimal": 58906 + }, + { + "icon_id": "40498212", + "name": "node-2", + "font_class": "node-2", + "unicode": "e647", + "unicode_decimal": 58951 + }, + { + "icon_id": "3977832", + "name": "统一检查", + "font_class": "tongyijiancha", + "unicode": "e619", + "unicode_decimal": 58905 + }, + { + "icon_id": "4472516", + "name": "setting-2", + "font_class": "alert-3", + "unicode": "e728", + "unicode_decimal": 59176 + }, + { + "icon_id": "22761832", + "name": "文件类型-压缩包", + "font_class": "file-zip", + "unicode": "e606", + "unicode_decimal": 58886 + }, + { + "icon_id": "19671156", + "name": "txt-1", + "font_class": "file-normal", + "unicode": "e7ac", + "unicode_decimal": 59308 + }, + { + "icon_id": "26815646", + "name": "txt", + "font_class": "txt", + "unicode": "e6e3", + "unicode_decimal": 59107 + }, + { + "icon_id": "22735864", + "name": "文件类型-文件夹", + "font_class": "file-folder", + "unicode": "e600", + "unicode_decimal": 58880 + }, + { + "icon_id": "22761833", + "name": "文件类型-未知文件", + "font_class": "file-unknown", + "unicode": "e601", + "unicode_decimal": 58881 + }, + { + "icon_id": "22761837", + "name": "文件类型-Txt", + "font_class": "file-txt", + "unicode": "e602", + "unicode_decimal": 58882 + }, + { + "icon_id": "7533292", + "name": "中英文", + "font_class": "language", + "unicode": "e605", + "unicode_decimal": 58885 + }, + { + "icon_id": "22551111", + "name": "主题", + "font_class": "theme", + "unicode": "e638", + "unicode_decimal": 58936 + }, + { + "icon_id": "15838431", + "name": "arrow-right", + "font_class": "arrow-right", + "unicode": "e665", + "unicode_decimal": 58981 + }, + { + "icon_id": "3876424", + "name": "docker", + "font_class": "docker", + "unicode": "e659", + "unicode_decimal": 58969 + }, + { + "icon_id": "19688849", + "name": "应用商店", + "font_class": "appstore", + "unicode": "eb65", + "unicode_decimal": 60261 + }, + { + "icon_id": "4765743", + "name": "earth", + "font_class": "website", + "unicode": "e781", + "unicode_decimal": 59265 + }, + { + "icon_id": "4765891", + "name": "setting", + "font_class": "config", + "unicode": "e78e", + "unicode_decimal": 59278 + }, + { + "icon_id": "4765962", + "name": "app store", + "font_class": "appstore1", + "unicode": "e792", + "unicode_decimal": 59282 + }, + { + "icon_id": "4765971", + "name": "detail", + "font_class": "log", + "unicode": "e793", + "unicode_decimal": 59283 + }, + { + "icon_id": "4766440", + "name": "sever", + "font_class": "host", + "unicode": "e7b1", + "unicode_decimal": 59313 + }, + { + "icon_id": "4766685", + "name": "home", + "font_class": "home", + "unicode": "e7c6", + "unicode_decimal": 59334 + }, + { + "icon_id": "11487994", + "name": "calendar", + "font_class": "plan", + "unicode": "e746", + "unicode_decimal": 59206 + }, + { + "icon_id": "11488064", + "name": "integral", + "font_class": "database", + "unicode": "e754", + "unicode_decimal": 59220 + }, + { + "icon_id": "11488108", + "name": "rejected-order", + "font_class": "rejected-order", + "unicode": "e75e", + "unicode_decimal": 59230 + }, + { + "icon_id": "11488148", + "name": "tool", + "font_class": "toolbox", + "unicode": "e769", + "unicode_decimal": 59241 + }, + { + "icon_id": "8358944", + "name": "英文5", + "font_class": "yingwen", + "unicode": "e6c3", + "unicode_decimal": 59075 + }, + { + "icon_id": "8358949", + "name": "中文5", + "font_class": "zhongwen", + "unicode": "e6c8", + "unicode_decimal": 59080 + }, + { + "icon_id": "924436", + "name": "logout", + "font_class": "logout", + "unicode": "e8fe", + "unicode_decimal": 59646 + }, + { + "icon_id": "1760690", + "name": "讨论", + "font_class": "taolun", + "unicode": "e603", + "unicode_decimal": 58883 + }, + { + "icon_id": "6642940", + "name": "bug", + "font_class": "bug", + "unicode": "e616", + "unicode_decimal": 58902 + }, + { + "icon_id": "15337722", + "name": "Logo GitHub", + "font_class": "huaban88", + "unicode": "e67c", + "unicode_decimal": 59004 + }, + { + "icon_id": "974125", + "name": "star", + "font_class": "star", + "unicode": "e60f", + "unicode_decimal": 58895 + }, + { + "icon_id": "26815641", + "name": "PPT", + "font_class": "file-ppt", + "unicode": "e6e2", + "unicode_decimal": 59106 + }, + { + "icon_id": "26815643", + "name": "HTML", + "font_class": "file-html", + "unicode": "e608", + "unicode_decimal": 58888 + }, + { + "icon_id": "26815644", + "name": "word", + "font_class": "file-word", + "unicode": "e6e4", + "unicode_decimal": 59108 + }, + { + "icon_id": "26815647", + "name": "excel", + "font_class": "file-excel", + "unicode": "e6e6", + "unicode_decimal": 59110 + }, + { + "icon_id": "26815651", + "name": "PDF", + "font_class": "file-pdf", + "unicode": "e6e7", + "unicode_decimal": 59111 + }, + { + "icon_id": "26815652", + "name": "音频", + "font_class": "file-mp3", + "unicode": "e6e8", + "unicode_decimal": 59112 + }, + { + "icon_id": "26815654", + "name": "SVG", + "font_class": "file-svg", + "unicode": "e6e9", + "unicode_decimal": 59113 + }, + { + "icon_id": "26815655", + "name": "JPG", + "font_class": "file-jpg", + "unicode": "e6ea", + "unicode_decimal": 59114 + }, + { + "icon_id": "26815656", + "name": "视频", + "font_class": "file-video", + "unicode": "e6eb", + "unicode_decimal": 59115 + }, + { + "icon_id": "19671162", + "name": "png-1", + "font_class": "file-png", + "unicode": "e7ae", + "unicode_decimal": 59310 + }, + { + "icon_id": "7131916", + "name": "验证码", + "font_class": "yanzhengma1", + "unicode": "e744", + "unicode_decimal": 59204 + }, + { + "icon_id": "7708032", + "name": "菜单", + "font_class": "caidan", + "unicode": "e61d", + "unicode_decimal": 58909 + }, + { + "icon_id": "10293150", + "name": "详情", + "font_class": "xiangqing", + "unicode": "e677", + "unicode_decimal": 58999 + }, + { + "icon_id": "23044673", + "name": "功率", + "font_class": "webdav", + "unicode": "e622", + "unicode_decimal": 58914 + }, + { + "icon_id": "5838475", + "name": "统计", + "font_class": "tongji", + "unicode": "e856", + "unicode_decimal": 59478 + }, + { + "icon_id": "435944", + "name": "日志", + "font_class": "tamper-4", + "unicode": "e7c3", + "unicode_decimal": 59331 + }, + { + "icon_id": "5799978", + "name": "扫描", + "font_class": "tamper-2", + "unicode": "e610", + "unicode_decimal": 58896 + }, + { + "icon_id": "5961299", + "name": "安全", + "font_class": "tamper-3", + "unicode": "ec4d", + "unicode_decimal": 60493 + }, + { + "icon_id": "28131805", + "name": "完整性检查", + "font_class": "tamper-1", + "unicode": "e687", + "unicode_decimal": 59015 + }, + { + "icon_id": "899647", + "name": "欢迎页", + "font_class": "setting-1", + "unicode": "e626", + "unicode_decimal": 58918 + }, + { + "icon_id": "1114667", + "name": "图标", + "font_class": "setting-2", + "unicode": "e630", + "unicode_decimal": 58928 + }, + { + "icon_id": "12010559", + "name": "注册网站", + "font_class": "setting-3", + "unicode": "e617", + "unicode_decimal": 58903 + }, + { + "icon_id": "8229531", + "name": "ACL策略", + "font_class": "waf-4", + "unicode": "e60a", + "unicode_decimal": 58890 + }, + { + "icon_id": "8765146", + "name": "地图", + "font_class": "waf-1", + "unicode": "e62a", + "unicode_decimal": 58922 + }, + { + "icon_id": "16563651", + "name": "8-4限制区域", + "font_class": "waf-2", + "unicode": "e682", + "unicode_decimal": 59010 + }, + { + "icon_id": "17064153", + "name": "拦截", + "font_class": "waf-3", + "unicode": "e666", + "unicode_decimal": 58982 + }, + { + "icon_id": "3722144", + "name": "钻石", + "font_class": "xpack", + "unicode": "e60b", + "unicode_decimal": 58891 + }, + { + "icon_id": "40398413", + "name": "监控-copy", + "font_class": "monitor-4", + "unicode": "ec4e", + "unicode_decimal": 60494 + }, + { + "icon_id": "40398423", + "name": "地图-copy", + "font_class": "monitor-2", + "unicode": "ec4f", + "unicode_decimal": 60495 + }, + { + "icon_id": "8156501", + "name": "趋势", + "font_class": "monitor-1", + "unicode": "e60d", + "unicode_decimal": 58893 + }, + { + "icon_id": "40398652", + "name": "日志-copy", + "font_class": "monitor-3", + "unicode": "ec50", + "unicode_decimal": 60496 + }, + { + "icon_id": "1120947", + "name": "mac", + "font_class": "m-ios", + "unicode": "e60e", + "unicode_decimal": 58894 + }, + { + "icon_id": "1354850", + "name": "PC", + "font_class": "m-pc", + "unicode": "e771", + "unicode_decimal": 59249 + }, + { + "icon_id": "1948990", + "name": "浏览器-世界之窗", + "font_class": "m-theworld", + "unicode": "e947", + "unicode_decimal": 59719 + }, + { + "icon_id": "3176588", + "name": "Android", + "font_class": "m-android", + "unicode": "e9e0", + "unicode_decimal": 59872 + }, + { + "icon_id": "3547761", + "name": "tencent", + "font_class": "m-tencent", + "unicode": "e60c", + "unicode_decimal": 58892 + }, + { + "icon_id": "3846904", + "name": "Windows", + "font_class": "m-windows", + "unicode": "e6a0", + "unicode_decimal": 59040 + }, + { + "icon_id": "6172784", + "name": "cloud-machine", + "font_class": "m-machine", + "unicode": "e862", + "unicode_decimal": 59490 + }, + { + "icon_id": "6189402", + "name": "mobile", + "font_class": "m-mobile", + "unicode": "e620", + "unicode_decimal": 58912 + }, + { + "icon_id": "7172218", + "name": "UC浏览器", + "font_class": "m-ucweb", + "unicode": "e611", + "unicode_decimal": 58897 + }, + { + "icon_id": "7736005", + "name": "edge", + "font_class": "m-edge", + "unicode": "e8e2", + "unicode_decimal": 59618 + }, + { + "icon_id": "8714496", + "name": "2345浏览器", + "font_class": "m-2345explorer", + "unicode": "e612", + "unicode_decimal": 58898 + }, + { + "icon_id": "11983530", + "name": "chrome", + "font_class": "m-chrome", + "unicode": "ea09", + "unicode_decimal": 59913 + }, + { + "icon_id": "11983549", + "name": "opera", + "font_class": "m-opera", + "unicode": "ea0e", + "unicode_decimal": 59918 + }, + { + "icon_id": "12463174", + "name": "linux", + "font_class": "m-linux", + "unicode": "e80b", + "unicode_decimal": 59403 + }, + { + "icon_id": "13354966", + "name": "maxthonlogo", + "font_class": "m-maxthon", + "unicode": "e676", + "unicode_decimal": 58998 + }, + { + "icon_id": "18583105", + "name": "macbook", + "font_class": "m-mac", + "unicode": "ef2d", + "unicode_decimal": 61229 + }, + { + "icon_id": "19448086", + "name": "IE", + "font_class": "m-ie", + "unicode": "eaab", + "unicode_decimal": 60075 + }, + { + "icon_id": "32828855", + "name": "Chrome-OS", + "font_class": "Chrome-OS", + "unicode": "e613", + "unicode_decimal": 58899 + }, + { + "icon_id": "34065043", + "name": "Safari浏览器", + "font_class": "m-safari", + "unicode": "e614", + "unicode_decimal": 58900 + }, + { + "icon_id": "37095081", + "name": "bro360se", + "font_class": "m-360se", + "unicode": "e678", + "unicode_decimal": 59000 + }, + { + "icon_id": "37759945", + "name": "Firefox", + "font_class": "Firefox", + "unicode": "e87c", + "unicode_decimal": 59516 + }, + { + "icon_id": "1064806", + "name": "docker", + "font_class": "docker1", + "unicode": "e76a", + "unicode_decimal": 59242 + }, + { + "icon_id": "29851742", + "name": "短信告警", + "font_class": "alert-1", + "unicode": "e615", + "unicode_decimal": 58901 + }, + { + "icon_id": "33427786", + "name": "告警日志", + "font_class": "alert-2", + "unicode": "e701", + "unicode_decimal": 59137 + }, + { + "icon_id": "11124973", + "name": "表单", + "font_class": "17", + "unicode": "e618", + "unicode_decimal": 58904 + } + ] +} diff --git a/frontend/src/assets/iconfont/iconfont.svg b/frontend/src/assets/iconfont/iconfont.svg new file mode 100644 index 0000000..326961f --- /dev/null +++ b/frontend/src/assets/iconfont/iconfont.svg @@ -0,0 +1,269 @@ + + + + Created by iconfont + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/iconfont/iconfont.ttf b/frontend/src/assets/iconfont/iconfont.ttf new file mode 100644 index 0000000..cf49cab Binary files /dev/null and b/frontend/src/assets/iconfont/iconfont.ttf differ diff --git a/frontend/src/assets/iconfont/iconfont.woff b/frontend/src/assets/iconfont/iconfont.woff new file mode 100644 index 0000000..d428deb Binary files /dev/null and b/frontend/src/assets/iconfont/iconfont.woff differ diff --git a/frontend/src/assets/iconfont/iconfont.woff2 b/frontend/src/assets/iconfont/iconfont.woff2 new file mode 100644 index 0000000..de8fd55 Binary files /dev/null and b/frontend/src/assets/iconfont/iconfont.woff2 differ diff --git a/frontend/src/assets/images/1panel-login-bg.jpg b/frontend/src/assets/images/1panel-login-bg.jpg new file mode 100644 index 0000000..71c2096 Binary files /dev/null and b/frontend/src/assets/images/1panel-login-bg.jpg differ diff --git a/frontend/src/assets/images/1panel-login.jpg b/frontend/src/assets/images/1panel-login.jpg new file mode 100644 index 0000000..41054c9 Binary files /dev/null and b/frontend/src/assets/images/1panel-login.jpg differ diff --git a/frontend/src/assets/images/1panel-logo.svg b/frontend/src/assets/images/1panel-logo.svg new file mode 100644 index 0000000..97d2a71 --- /dev/null +++ b/frontend/src/assets/images/1panel-logo.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/images/1panel-menu-logo.svg b/frontend/src/assets/images/1panel-menu-logo.svg new file mode 100644 index 0000000..1257149 --- /dev/null +++ b/frontend/src/assets/images/1panel-menu-logo.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/src/assets/images/2345explorer.png b/frontend/src/assets/images/2345explorer.png new file mode 100644 index 0000000..5751dd3 Binary files /dev/null and b/frontend/src/assets/images/2345explorer.png differ diff --git a/frontend/src/assets/images/360se.png b/frontend/src/assets/images/360se.png new file mode 100644 index 0000000..b687f5d Binary files /dev/null and b/frontend/src/assets/images/360se.png differ diff --git a/frontend/src/assets/images/404.svg b/frontend/src/assets/images/404.svg new file mode 100644 index 0000000..1fb16d8 --- /dev/null +++ b/frontend/src/assets/images/404.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/android.png b/frontend/src/assets/images/android.png new file mode 100644 index 0000000..393001a Binary files /dev/null and b/frontend/src/assets/images/android.png differ diff --git a/frontend/src/assets/images/chrome.png b/frontend/src/assets/images/chrome.png new file mode 100644 index 0000000..e4e2773 Binary files /dev/null and b/frontend/src/assets/images/chrome.png differ diff --git a/frontend/src/assets/images/cros.png b/frontend/src/assets/images/cros.png new file mode 100644 index 0000000..cf9d7e6 Binary files /dev/null and b/frontend/src/assets/images/cros.png differ diff --git a/frontend/src/assets/images/edge.png b/frontend/src/assets/images/edge.png new file mode 100644 index 0000000..2da3aaf Binary files /dev/null and b/frontend/src/assets/images/edge.png differ diff --git a/frontend/src/assets/images/enlarge_black.svg b/frontend/src/assets/images/enlarge_black.svg new file mode 100644 index 0000000..2a7352e --- /dev/null +++ b/frontend/src/assets/images/enlarge_black.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/frontend/src/assets/images/enlarge_white.svg b/frontend/src/assets/images/enlarge_white.svg new file mode 100644 index 0000000..384cd82 --- /dev/null +++ b/frontend/src/assets/images/enlarge_white.svg @@ -0,0 +1,3 @@ + \ No newline at end of file diff --git a/frontend/src/assets/images/error.svg b/frontend/src/assets/images/error.svg new file mode 100644 index 0000000..da80d0b --- /dev/null +++ b/frontend/src/assets/images/error.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/images/favicon-gold.png b/frontend/src/assets/images/favicon-gold.png new file mode 100644 index 0000000..2574ecb Binary files /dev/null and b/frontend/src/assets/images/favicon-gold.png differ diff --git a/frontend/src/assets/images/favicon.png b/frontend/src/assets/images/favicon.png new file mode 100644 index 0000000..6f82a12 Binary files /dev/null and b/frontend/src/assets/images/favicon.png differ diff --git a/frontend/src/assets/images/favicon.svg b/frontend/src/assets/images/favicon.svg new file mode 100644 index 0000000..4f248bf --- /dev/null +++ b/frontend/src/assets/images/favicon.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/frontend/src/assets/images/firefox.png b/frontend/src/assets/images/firefox.png new file mode 100644 index 0000000..c118f9c Binary files /dev/null and b/frontend/src/assets/images/firefox.png differ diff --git a/frontend/src/assets/images/harmonyos.png b/frontend/src/assets/images/harmonyos.png new file mode 100644 index 0000000..ff0d8d2 Binary files /dev/null and b/frontend/src/assets/images/harmonyos.png differ diff --git a/frontend/src/assets/images/huaweibrowser.png b/frontend/src/assets/images/huaweibrowser.png new file mode 100644 index 0000000..0ffa8ad Binary files /dev/null and b/frontend/src/assets/images/huaweibrowser.png differ diff --git a/frontend/src/assets/images/ie.png b/frontend/src/assets/images/ie.png new file mode 100644 index 0000000..1d3bbe8 Binary files /dev/null and b/frontend/src/assets/images/ie.png differ diff --git a/frontend/src/assets/images/ios.png b/frontend/src/assets/images/ios.png new file mode 100644 index 0000000..7e803a6 Binary files /dev/null and b/frontend/src/assets/images/ios.png differ diff --git a/frontend/src/assets/images/linux.png b/frontend/src/assets/images/linux.png new file mode 100644 index 0000000..d5c1bd8 Binary files /dev/null and b/frontend/src/assets/images/linux.png differ diff --git a/frontend/src/assets/images/logo.svg b/frontend/src/assets/images/logo.svg new file mode 100644 index 0000000..7565660 --- /dev/null +++ b/frontend/src/assets/images/logo.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/images/mac.png b/frontend/src/assets/images/mac.png new file mode 100644 index 0000000..e57d01c Binary files /dev/null and b/frontend/src/assets/images/mac.png differ diff --git a/frontend/src/assets/images/machine.png b/frontend/src/assets/images/machine.png new file mode 100644 index 0000000..df587a3 Binary files /dev/null and b/frontend/src/assets/images/machine.png differ diff --git a/frontend/src/assets/images/menu-bg.png b/frontend/src/assets/images/menu-bg.png new file mode 100644 index 0000000..45b863b Binary files /dev/null and b/frontend/src/assets/images/menu-bg.png differ diff --git a/frontend/src/assets/images/menu-bg.svg b/frontend/src/assets/images/menu-bg.svg new file mode 100644 index 0000000..cf67907 --- /dev/null +++ b/frontend/src/assets/images/menu-bg.svg @@ -0,0 +1 @@ + diff --git a/frontend/src/assets/images/mobile.png b/frontend/src/assets/images/mobile.png new file mode 100644 index 0000000..d2190f4 Binary files /dev/null and b/frontend/src/assets/images/mobile.png differ diff --git a/frontend/src/assets/images/no_app.svg b/frontend/src/assets/images/no_app.svg new file mode 100644 index 0000000..74c0fdd --- /dev/null +++ b/frontend/src/assets/images/no_app.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/images/no_update_app.svg b/frontend/src/assets/images/no_update_app.svg new file mode 100644 index 0000000..2947474 --- /dev/null +++ b/frontend/src/assets/images/no_update_app.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/frontend/src/assets/images/opera.png b/frontend/src/assets/images/opera.png new file mode 100644 index 0000000..84e6d0f Binary files /dev/null and b/frontend/src/assets/images/opera.png differ diff --git a/frontend/src/assets/images/pc.png b/frontend/src/assets/images/pc.png new file mode 100644 index 0000000..d5ede41 Binary files /dev/null and b/frontend/src/assets/images/pc.png differ diff --git a/frontend/src/assets/images/safari.png b/frontend/src/assets/images/safari.png new file mode 100644 index 0000000..b06369a Binary files /dev/null and b/frontend/src/assets/images/safari.png differ diff --git a/frontend/src/assets/images/spider.png b/frontend/src/assets/images/spider.png new file mode 100644 index 0000000..63b0faa Binary files /dev/null and b/frontend/src/assets/images/spider.png differ diff --git a/frontend/src/assets/images/tencent.png b/frontend/src/assets/images/tencent.png new file mode 100644 index 0000000..c89cb39 Binary files /dev/null and b/frontend/src/assets/images/tencent.png differ diff --git a/frontend/src/assets/images/theworld.png b/frontend/src/assets/images/theworld.png new file mode 100644 index 0000000..d0a6396 Binary files /dev/null and b/frontend/src/assets/images/theworld.png differ diff --git a/frontend/src/assets/images/ucweb.png b/frontend/src/assets/images/ucweb.png new file mode 100644 index 0000000..26ea364 Binary files /dev/null and b/frontend/src/assets/images/ucweb.png differ diff --git a/frontend/src/assets/images/unknown.png b/frontend/src/assets/images/unknown.png new file mode 100644 index 0000000..5205802 Binary files /dev/null and b/frontend/src/assets/images/unknown.png differ diff --git a/frontend/src/assets/images/unsafe.svg b/frontend/src/assets/images/unsafe.svg new file mode 100644 index 0000000..33916c4 --- /dev/null +++ b/frontend/src/assets/images/unsafe.svg @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/frontend/src/assets/images/wechat.png b/frontend/src/assets/images/wechat.png new file mode 100644 index 0000000..fc950d0 Binary files /dev/null and b/frontend/src/assets/images/wechat.png differ diff --git a/frontend/src/assets/images/windows.png b/frontend/src/assets/images/windows.png new file mode 100644 index 0000000..4effcd2 Binary files /dev/null and b/frontend/src/assets/images/windows.png differ diff --git a/frontend/src/assets/json/iso.json b/frontend/src/assets/json/iso.json new file mode 100644 index 0000000..09800db --- /dev/null +++ b/frontend/src/assets/json/iso.json @@ -0,0 +1,1156 @@ +[ + { + "iso": "CN", + "zh": "中国", + "tw": "中國", + "en": "China" + }, + { + "iso": "HK", + "zh": "中国香港", + "tw": "中國香港", + "en": "Hong Kong" + }, + { + "iso": "MO", + "zh": "中国澳门", + "tw": "中國澳門", + "en": "Macau" + }, + { + "iso": "TW", + "zh": "中国台湾", + "tw": "中國台灣", + "en": "Taiwan" + }, + { + "iso": "AF", + "zh": "阿富汗", + "tw": "阿富汗", + "en": "Afghanistan" + }, + { + "iso": "AL", + "zh": "阿尔巴尼亚", + "tw": "阿爾巴尼亞", + "en": "Albania" + }, + { + "iso": "DZ", + "zh": "阿尔及利亚", + "tw": "阿爾及利亞", + "en": "Algeria" + }, + { + "iso": "AO", + "zh": "安哥拉", + "tw": "安哥拉", + "en": "Angola" + }, + { + "iso": "AR", + "zh": "阿根廷", + "tw": "阿根廷", + "en": "Argentina" + }, + { + "iso": "AM", + "zh": "亚美尼亚", + "tw": "亞美尼亞", + "en": "Armenia" + }, + { + "iso": "AU", + "zh": "澳大利亚", + "tw": "澳大利亞", + "en": "Australia" + }, + { + "iso": "AT", + "zh": "奥地利", + "tw": "奧地利", + "en": "Austria" + }, + { + "iso": "AZ", + "zh": "阿塞拜疆", + "tw": "阿塞拜疆", + "en": "Azerbaijan" + }, + { + "iso": "BS", + "zh": "巴哈马", + "tw": "巴哈馬", + "en": "Bahamas" + }, + { + "iso": "BH", + "zh": "巴林", + "tw": "巴林", + "en": "Bahrain" + }, + { + "iso": "BD", + "zh": "孟加拉国", + "tw": "孟加拉國", + "en": "Bangladesh" + }, + { + "iso": "BY", + "zh": "白俄罗斯", + "tw": "白俄羅斯", + "en": "Belarus" + }, + { + "iso": "BE", + "zh": "比利时", + "tw": "比利時", + "en": "Belgium" + }, + { + "iso": "BZ", + "zh": "伯利兹", + "tw": "貝里斯", + "en": "Belize" + }, + { + "iso": "BJ", + "zh": "贝宁", + "tw": "貝南", + "en": "Benin" + }, + { + "iso": "BT", + "zh": "不丹", + "tw": "不丹", + "en": "Bhutan" + }, + { + "iso": "BO", + "zh": "玻利维亚", + "tw": "玻利維亞", + "en": "Bolivia" + }, + { + "iso": "BA", + "zh": "波斯尼亚和黑塞哥维那", + "tw": "波斯尼亞和黑塞哥維那", + "en": "Bosnia and Herz." + }, + { + "iso": "BW", + "zh": "博茨瓦纳", + "tw": "博茨瓦納", + "en": "Botswana" + }, + { + "iso": "BR", + "zh": "巴西", + "tw": "巴西", + "en": "Brazil" + }, + { + "iso": "VG", + "zh": "英属维京群岛", + "tw": "英屬維京群島", + "en": "British Virgin Islands" + }, + { + "iso": "BN", + "zh": "文莱", + "tw": "文萊", + "en": "Brunei" + }, + { + "iso": "BG", + "zh": "保加利亚", + "tw": "保加利亞", + "en": "Bulgaria" + }, + { + "iso": "BF", + "zh": "布基纳法索", + "tw": "布基納法索", + "en": "Burkina Faso" + }, + { + "iso": "BI", + "zh": "布隆迪", + "tw": "布隆迪", + "en": "Burundi" + }, + { + "iso": "KH", + "zh": "柬埔寨", + "tw": "柬埔寨", + "en": "Cambodia" + }, + { + "iso": "KN", + "zh": "圣基茨和尼维斯", + "tw": "聖基茨和尼維斯", + "en": "St Kitts and Nevis" + }, + { + "iso": "CM", + "zh": "喀麦隆", + "tw": "喀麥隆", + "en": "Cameroon" + }, + { + "iso": "CA", + "zh": "加拿大", + "tw": "加拿大", + "en": "Canada" + }, + { + "iso": "CV", + "zh": "佛得角", + "tw": "佛得角", + "en": "Cape Verde" + }, + { + "iso": "KY", + "zh": "开曼群岛", + "tw": "開曼群島", + "en": "Cayman Islands" + }, + { + "iso": "CF", + "zh": "中非共和国", + "tw": "中非共和國", + "en": "Central African Rep." + }, + { + "iso": "TD", + "zh": "乍得", + "tw": "乍得", + "en": "Chad" + }, + { + "iso": "CL", + "zh": "智利", + "tw": "智利", + "en": "Chile" + }, + + { + "iso": "CO", + "zh": "哥伦比亚", + "tw": "哥倫比亞", + "en": "Colombia" + }, + { + "iso": "KM", + "zh": "科摩罗", + "tw": "科摩羅", + "en": "Comoros" + }, + { + "iso": "CG", + "zh": "刚果", + "tw": "剛果", + "en": "Congo" + }, + { + "iso": "CR", + "zh": "哥斯达黎加", + "tw": "哥斯達黎加", + "en": "Costa Rica" + }, + { + "iso": "HR", + "zh": "克罗地亚", + "tw": "克羅地亞", + "en": "Croatia" + }, + { + "iso": "CU", + "zh": "古巴", + "tw": "古巴", + "en": "Cuba" + }, + { + "iso": "CY", + "zh": "塞浦路斯", + "tw": "塞普勒斯", + "en": "Cyprus" + }, + { + "iso": "CZ", + "zh": "捷克共和国", + "tw": "捷克共和國", + "en": "Czech Rep." + }, + { + "iso": "CI", + "zh": "科特迪瓦", + "tw": "象牙海岸", + "en": "Côte d'Ivoire" + }, + { + "iso": "CD", + "zh": "刚果民主共和国", + "tw": "剛果民主共和國", + "en": "Dem. Rep. Congo" + }, + { + "iso": "KP", + "zh": "朝鲜", + "tw": "朝鮮", + "en": "Dem. Rep. Korea" + }, + { + "iso": "DK", + "zh": "丹麦", + "tw": "丹麥", + "en": "Denmark" + }, + { + "iso": "DJ", + "zh": "吉布提", + "tw": "吉布提", + "en": "Djibouti" + }, + { + "iso": "DO", + "zh": "多米尼加共和国", + "tw": "多明尼加共和國", + "en": "Dominican Rep." + }, + { + "iso": "EC", + "zh": "厄瓜多尔", + "tw": "厄瓜多爾", + "en": "Ecuador" + }, + { + "iso": "EG", + "zh": "埃及", + "tw": "埃及", + "en": "Egypt" + }, + { + "iso": "SV", + "zh": "萨尔瓦多", + "tw": "薩爾瓦多", + "en": "El Salvador" + }, + { + "iso": "GQ", + "zh": "赤道几内亚", + "tw": "赤道幾內亞", + "en": "Equatorial Guinea" + }, + { + "iso": "ER", + "zh": "厄立特里亚", + "tw": "厄立特里亞", + "en": "Eritrea" + }, + { + "iso": "EE", + "zh": "爱沙尼亚", + "tw": "愛沙尼亞", + "en": "Estonia" + }, + { + "iso": "ET", + "zh": "埃塞俄比亚", + "tw": "埃塞俄比亞", + "en": "Ethiopia" + }, + { + "iso": "FK", + "zh": "福克兰群岛", + "tw": "福克蘭群島", + "en": "Falkland Is." + }, + { + "iso": "FJ", + "zh": "斐济", + "tw": "斐濟", + "en": "Fiji" + }, + { + "iso": "FI", + "zh": "芬兰", + "tw": "芬蘭", + "en": "Finland" + }, + { + "iso": "FR", + "zh": "法国", + "tw": "法國", + "en": "France" + }, + { + "iso": "GA", + "zh": "加蓬", + "tw": "加蓬", + "en": "Gabon" + }, + { + "iso": "GM", + "zh": "冈比亚", + "tw": "岡比亞", + "en": "Gambia" + }, + { + "iso": "GE", + "zh": "格鲁吉亚", + "tw": "喬治亞", + "en": "Georgia" + }, + { + "iso": "DE", + "zh": "德国", + "tw": "德國", + "en": "Germany" + }, + { + "iso": "GH", + "zh": "加纳", + "tw": "加納", + "en": "Ghana" + }, + { + "iso": "GR", + "zh": "希腊", + "tw": "希臘", + "en": "Greece" + }, + { + "iso": "GL", + "zh": "格陵兰", + "tw": "格陵蘭", + "en": "Greenland" + }, + { + "iso": "GT", + "zh": "危地马拉", + "tw": "危地馬拉", + "en": "Guatemala" + }, + { + "iso": "GN", + "zh": "几内亚", + "tw": "幾內亞", + "en": "Guinea" + }, + { + "iso": "GW", + "zh": "几内亚比绍", + "tw": "幾內亞比索", + "en": "Guinea-Bissau" + }, + { + "iso": "GY", + "zh": "圭亚那", + "tw": "圭亞那", + "en": "Guyana" + }, + { + "iso": "HT", + "zh": "海地", + "tw": "海地", + "en": "Haiti" + }, + { + "iso": "HN", + "zh": "洪都拉斯", + "tw": "洪都拉斯", + "en": "Honduras" + }, + { + "iso": "HU", + "zh": "匈牙利", + "tw": "匈牙利", + "en": "Hungary" + }, + { + "iso": "IS", + "zh": "冰岛", + "tw": "冰島", + "en": "Iceland" + }, + { + "iso": "IN", + "zh": "印度", + "tw": "印度", + "en": "India" + }, + { + "iso": "ID", + "zh": "印度尼西亚", + "tw": "印尼", + "en": "Indonesia" + }, + { + "iso": "IR", + "zh": "伊朗", + "tw": "伊朗", + "en": "Iran" + }, + { + "iso": "IQ", + "zh": "伊拉克", + "tw": "伊拉克", + "en": "Iraq" + }, + { + "iso": "IE", + "zh": "爱尔兰", + "tw": "愛爾蘭", + "en": "Ireland" + }, + { + "iso": "IM", + "zh": "马恩岛", + "tw": "曼岛", + "en": "Isle of Man" + }, + { + "iso": "IL", + "zh": "以色列", + "tw": "以色列", + "en": "Israel" + }, + { + "iso": "IT", + "zh": "意大利", + "tw": "義大利", + "en": "Italy" + }, + { + "iso": "JM", + "zh": "牙买加", + "tw": "牙買加", + "en": "Jamaica" + }, + { + "iso": "JP", + "zh": "日本", + "tw": "日本", + "en": "Japan" + }, + { + "iso": "JO", + "zh": "约旦", + "tw": "約旦", + "en": "Jordan" + }, + { + "iso": "KZ", + "zh": "哈萨克斯坦", + "tw": "哈薩克斯坦", + "en": "Kazakhstan" + }, + { + "iso": "KE", + "zh": "肯尼亚", + "tw": "肯亞", + "en": "Kenya" + }, + { + "iso": "KR", + "zh": "韩国", + "tw": "韓國", + "en": "Korea" + }, + { + "iso": "KW", + "zh": "科威特", + "tw": "科威特", + "en": "Kuwait" + }, + { + "iso": "KG", + "zh": "吉尔吉斯斯坦", + "tw": "吉爾吉斯斯坦", + "en": "Kyrgyzstan" + }, + { + "iso": "LA", + "zh": "老挝", + "tw": "寮國", + "en": "Lao PDR" + }, + { + "iso": "LV", + "zh": "拉脱维亚", + "tw": "拉脫維亞", + "en": "Latvia" + }, + { + "iso": "LB", + "zh": "黎巴嫩", + "tw": "黎巴嫩", + "en": "Lebanon" + }, + { + "iso": "LS", + "zh": "莱索托", + "tw": "賴索托", + "en": "Lesotho" + }, + { + "iso": "LR", + "zh": "利比里亚", + "tw": "賴比瑞亞", + "en": "Liberia" + }, + { + "iso": "LY", + "zh": "利比亚", + "tw": "利比亞", + "en": "Libya" + }, + { + "iso": "LT", + "zh": "立陶宛", + "tw": "立陶宛", + "en": "Lithuania" + }, + { + "iso": "LU", + "zh": "卢森堡", + "tw": "盧森堡", + "en": "Luxembourg" + }, + { + "iso": "MK", + "zh": "马其顿", + "tw": "馬其頓", + "en": "Macedonia" + }, + { + "iso": "MG", + "zh": "马达加斯加", + "tw": "馬達加斯加", + "en": "Madagascar" + }, + { + "iso": "MW", + "zh": "马拉维", + "tw": "馬拉威", + "en": "Malawi" + }, + { + "iso": "MY", + "zh": "马来西亚", + "tw": "馬來西亞", + "en": "Malaysia" + }, + { + "iso": "MV", + "zh": "马尔代夫", + "tw": "馬爾代夫", + "en": "Maldives" + }, + { + "iso": "ML", + "zh": "马里", + "tw": "馬里", + "en": "Mali" + }, + { + "iso": "MT", + "zh": "马耳他", + "tw": "馬爾他", + "en": "Malta" + }, + { + "iso": "MR", + "zh": "毛里塔尼亚", + "tw": "毛里塔尼亞", + "en": "Mauritania" + }, + { + "iso": "MU", + "zh": "毛里求斯", + "tw": "毛里求斯", + "en": "Mauritius" + }, + { + "iso": "MX", + "zh": "墨西哥", + "tw": "墨西哥", + "en": "Mexico" + }, + { + "iso": "MD", + "zh": "摩尔多瓦", + "tw": "摩爾多瓦", + "en": "Moldova" + }, + { + "iso": "MC", + "zh": "摩纳哥", + "tw": "摩納哥", + "en": "Monaco" + }, + { + "iso": "MN", + "zh": "蒙古", + "tw": "蒙古", + "en": "Mongolia" + }, + { + "iso": "ME", + "zh": "黑山共和国", + "tw": "黑山共和國", + "en": "Montenegro" + }, + { + "iso": "MA", + "zh": "摩洛哥", + "tw": "摩洛哥", + "en": "Morocco" + }, + { + "iso": "MZ", + "zh": "莫桑比克", + "tw": "莫桑比克", + "en": "Mozambique" + }, + { + "iso": "MM", + "zh": "缅甸", + "tw": "緬甸", + "en": "Myanmar" + }, + { + "iso": "NA", + "zh": "纳米比亚", + "tw": "納米比亞", + "en": "Namibia" + }, + { + "iso": "NP", + "zh": "尼泊尔", + "tw": "尼泊爾", + "en": "Nepal" + }, + { + "iso": "NL", + "zh": "荷兰", + "tw": "荷蘭", + "en": "Netherlands" + }, + { + "iso": "NC", + "zh": "新喀里多尼亚", + "tw": "新喀里多尼亞", + "en": "New Caledonia" + }, + { + "iso": "NZ", + "zh": "新西兰", + "tw": "新西蘭", + "en": "New Zealand" + }, + { + "iso": "NI", + "zh": "尼加拉瓜", + "tw": "尼加拉瓜", + "en": "Nicaragua" + }, + { + "iso": "NE", + "zh": "尼日尔", + "tw": "尼日爾", + "en": "Niger" + }, + { + "iso": "NG", + "zh": "尼日利亚", + "tw": "尼日利亞", + "en": "Nigeria" + }, + { + "iso": "NO", + "zh": "挪威", + "tw": "挪威", + "en": "Norway" + }, + { + "iso": "OM", + "zh": "阿曼", + "tw": "阿曼", + "en": "Oman" + }, + { + "iso": "PK", + "zh": "巴基斯坦", + "tw": "巴基斯坦", + "en": "Pakistan" + }, + { + "iso": "PA", + "zh": "巴拿马", + "tw": "巴拿馬", + "en": "Panama" + }, + { + "iso": "PG", + "zh": "巴布亚新几内亚", + "tw": "巴布亞新幾內亞", + "en": "Papua New Guinea" + }, + { + "iso": "PY", + "zh": "巴拉圭", + "tw": "巴拉圭", + "en": "Paraguay" + }, + { + "iso": "PE", + "zh": "秘鲁", + "tw": "秘魯", + "en": "Peru" + }, + { + "iso": "PH", + "zh": "菲律宾", + "tw": "菲律賓", + "en": "Philippines" + }, + { + "iso": "PL", + "zh": "波兰", + "tw": "波蘭", + "en": "Poland" + }, + { + "iso": "PT", + "zh": "葡萄牙", + "tw": "葡萄牙", + "en": "Portugal" + }, + { + "iso": "PR", + "zh": "波多黎各", + "tw": "波多黎各", + "en": "Puerto Rico" + }, + { + "iso": "QA", + "zh": "卡塔尔", + "tw": "卡塔爾", + "en": "Qatar" + }, + { + "iso": "RE", + "zh": "留尼旺", + "tw": "留尼旺", + "en": "Reunion" + }, + { + "iso": "RO", + "zh": "罗马尼亚", + "tw": "羅馬尼亞", + "en": "Romania" + }, + { + "iso": "RU", + "zh": "俄罗斯", + "tw": "俄羅斯", + "en": "Russia" + }, + { + "iso": "RW", + "zh": "卢旺达", + "tw": "盧旺達", + "en": "Rwanda" + }, + { + "iso": "GS", + "zh": "南乔治亚和南桑威奇群岛", + "tw": "南喬治亞和南三明治群島", + "en": "S. Geo. and S. Sandw. Is." + }, + { + "iso": "SS", + "zh": "南苏丹", + "tw": "南蘇丹", + "en": "S. Sudan" + }, + { + "iso": "SM", + "zh": "圣马力诺", + "tw": "聖馬力諾", + "en": "San Marino" + }, + { + "iso": "SA", + "zh": "沙特阿拉伯", + "tw": "沙地阿拉伯", + "en": "Saudi Arabia" + }, + { + "iso": "SN", + "zh": "塞内加尔", + "tw": "塞內加爾", + "en": "Senegal" + }, + { + "iso": "RS", + "zh": "塞尔维亚", + "tw": "塞爾維亞", + "en": "Serbia" + }, + { + "iso": "SL", + "zh": "塞拉利昂", + "tw": "塞拉利昂", + "en": "Sierra Leone" + }, + { + "iso": "SG", + "zh": "新加坡", + "tw": "新加坡", + "en": "Singapore" + }, + { + "iso": "SK", + "zh": "斯洛伐克", + "tw": "斯洛伐克", + "en": "Slovakia" + }, + { + "iso": "SI", + "zh": "斯洛文尼亚", + "tw": "斯洛文尼亞", + "en": "Slovenia" + }, + { + "iso": "SB", + "zh": "所罗门群岛", + "tw": "所羅門群島", + "en": "Solomon Is." + }, + { + "iso": "SO", + "zh": "索马里", + "tw": "索馬里", + "en": "Somalia" + }, + { + "iso": "ZA", + "zh": "南非", + "tw": "南非", + "en": "South Africa" + }, + { + "iso": "ES", + "zh": "西班牙", + "tw": "西班牙", + "en": "Spain" + }, + { + "iso": "LK", + "zh": "斯里兰卡", + "tw": "斯里蘭卡", + "en": "Sri Lanka" + }, + { + "iso": "SD", + "zh": "苏丹", + "tw": "蘇丹", + "en": "Sudan" + }, + { + "iso": "SR", + "zh": "苏里南", + "tw": "蘇利南", + "en": "Suriname" + }, + { + "iso": "SZ", + "zh": "斯威士兰", + "tw": "斯威士蘭", + "en": "Swaziland" + }, + { + "iso": "SE", + "zh": "瑞典", + "tw": "瑞典", + "en": "Sweden" + }, + { + "iso": "CH", + "zh": "瑞士", + "tw": "瑞士", + "en": "Switzerland" + }, + { + "iso": "SY", + "zh": "叙利亚", + "tw": "敘利亞", + "en": "Syria" + }, + + { + "iso": "TJ", + "zh": "塔吉克斯坦", + "tw": "塔吉克斯坦", + "en": "Tajikistan" + }, + { + "iso": "TZ", + "zh": "坦桑尼亚", + "tw": "坦桑尼亞", + "en": "Tanzania" + }, + { + "iso": "TH", + "zh": "泰国", + "tw": "泰國", + "en": "Thailand" + }, + { + "iso": "TG", + "zh": "多哥", + "tw": "多哥", + "en": "Togo" + }, + { + "iso": "TO", + "zh": "汤加", + "tw": "湯加", + "en": "Tonga" + }, + { + "iso": "TT", + "zh": "特立尼达和多巴哥", + "tw": "特立尼達和多巴哥", + "en": "Trinidad and Tobago" + }, + { + "iso": "TN", + "zh": "突尼斯", + "tw": "突尼斯", + "en": "Tunisia" + }, + { + "iso": "TR", + "zh": "土耳其", + "tw": "土耳其", + "en": "Turkey" + }, + { + "iso": "TM", + "zh": "土库曼斯坦", + "tw": "土庫曼斯坦", + "en": "Turkmenistan" + }, + { + "iso": "VI", + "zh": "美属维尔京群岛", + "tw": "美屬維爾京群島", + "en": "U.S. Virgin Islands" + }, + { + "iso": "UG", + "zh": "乌干达", + "tw": "烏干達", + "en": "Uganda" + }, + { + "iso": "UA", + "zh": "乌克兰", + "tw": "烏克蘭", + "en": "Ukraine" + }, + { + "iso": "AE", + "zh": "阿拉伯联合酋长国", + "tw": "阿拉伯聯合大公國", + "en": "United Arab Emirates" + }, + { + "iso": "GB", + "zh": "英国", + "tw": "英國", + "en": "United Kingdom" + }, + { + "iso": "US", + "zh": "美国", + "tw": "美國", + "en": "United States" + }, + { + "iso": "UY", + "zh": "乌拉圭", + "tw": "烏拉圭", + "en": "Uruguay" + }, + { + "iso": "UZ", + "zh": "乌兹别克斯坦", + "tw": "烏茲別克斯坦", + "en": "Uzbekistan" + }, + { + "iso": "VU", + "zh": "瓦努阿图", + "tw": "瓦努阿圖", + "en": "Vanuatu" + }, + { + "iso": "VA", + "zh": "梵蒂冈城", + "tw": "梵蒂岡城", + "en": "Vatican City" + }, + { + "iso": "VE", + "zh": "委内瑞拉", + "tw": "委內瑞拉", + "en": "Venezuela" + }, + { + "iso": "VN", + "zh": "越南", + "tw": "越南", + "en": "Vietnam" + }, + { + "iso": "EH", + "zh": "西撒哈拉", + "tw": "西撒哈拉", + "en": "W. Sahara" + }, + { + "iso": "YE", + "zh": "也门", + "tw": "也門", + "en": "Yemen" + }, + { + "iso": "YU", + "zh": "南斯拉夫", + "tw": "南斯拉夫", + "en": "Yugoslavia" + }, + { + "iso": "ZR", + "zh": "扎伊尔", + "tw": "扎伊爾", + "en": "Zaire" + }, + { + "iso": "ZM", + "zh": "赞比亚", + "tw": "贊比亞", + "en": "Zambia" + }, + { + "iso": "ZW", + "zh": "津巴布韦", + "tw": "津巴布韋", + "en": "Zimbabwe" + } +] diff --git a/frontend/src/components/agent-group/change.vue b/frontend/src/components/agent-group/change.vue new file mode 100644 index 0000000..49a0de3 --- /dev/null +++ b/frontend/src/components/agent-group/change.vue @@ -0,0 +1,83 @@ + + + diff --git a/frontend/src/components/agent-group/index.vue b/frontend/src/components/agent-group/index.vue new file mode 100644 index 0000000..ba6c49d --- /dev/null +++ b/frontend/src/components/agent-group/index.vue @@ -0,0 +1,179 @@ + + diff --git a/frontend/src/components/app-status/index.vue b/frontend/src/components/app-status/index.vue new file mode 100644 index 0000000..500c007 --- /dev/null +++ b/frontend/src/components/app-status/index.vue @@ -0,0 +1,193 @@ + + + diff --git a/frontend/src/components/back-button/index.vue b/frontend/src/components/back-button/index.vue new file mode 100644 index 0000000..e6b2aef --- /dev/null +++ b/frontend/src/components/back-button/index.vue @@ -0,0 +1,40 @@ + + + diff --git a/frontend/src/components/backup/index.vue b/frontend/src/components/backup/index.vue new file mode 100644 index 0000000..3dcc7c5 --- /dev/null +++ b/frontend/src/components/backup/index.vue @@ -0,0 +1,481 @@ + + + + + diff --git a/frontend/src/components/card-with-header/index.vue b/frontend/src/components/card-with-header/index.vue new file mode 100644 index 0000000..5d1bf4b --- /dev/null +++ b/frontend/src/components/card-with-header/index.vue @@ -0,0 +1,72 @@ + + + + + diff --git a/frontend/src/components/codemirror-pro/drawer.vue b/frontend/src/components/codemirror-pro/drawer.vue new file mode 100644 index 0000000..419758e --- /dev/null +++ b/frontend/src/components/codemirror-pro/drawer.vue @@ -0,0 +1,39 @@ + + + diff --git a/frontend/src/components/codemirror-pro/index.vue b/frontend/src/components/codemirror-pro/index.vue new file mode 100644 index 0000000..35e7d9f --- /dev/null +++ b/frontend/src/components/codemirror-pro/index.vue @@ -0,0 +1,212 @@ + + + diff --git a/frontend/src/components/codemirror-pro/nginx.ts b/frontend/src/components/codemirror-pro/nginx.ts new file mode 100644 index 0000000..c3586eb --- /dev/null +++ b/frontend/src/components/codemirror-pro/nginx.ts @@ -0,0 +1,143 @@ +import { StreamParser } from '@codemirror/language'; + +function words(str) { + const obj = {}, + words = str.split(' '); + for (let i = 0; i < words.length; ++i) obj[words[i]] = true; + return obj; +} + +const keywords = words( + /* ngxDirectiveControl */ 'break return rewrite set' + + /* ngxDirective */ ' accept_mutex accept_mutex_delay access_log add_after_body add_before_body add_header addition_types aio alias allow ancient_browser ancient_browser_value auth_basic auth_basic_user_file auth_http auth_http_header auth_http_timeout autoindex autoindex_exact_size autoindex_localtime charset charset_types client_body_buffer_size client_body_in_file_only client_body_in_single_buffer client_body_temp_path client_body_timeout client_header_buffer_size client_header_timeout client_max_body_size connection_pool_size create_full_put_path daemon dav_access dav_methods debug_connection debug_points default_type degradation degrade deny devpoll_changes devpoll_events directio directio_alignment empty_gif env epoll_events error_log eventport_events expires fastcgi_bind fastcgi_buffer_size fastcgi_buffers fastcgi_busy_buffers_size fastcgi_cache fastcgi_cache_key fastcgi_cache_methods fastcgi_cache_min_uses fastcgi_cache_path fastcgi_cache_use_stale fastcgi_cache_valid fastcgi_catch_stderr fastcgi_connect_timeout fastcgi_hide_header fastcgi_ignore_client_abort fastcgi_ignore_headers fastcgi_index fastcgi_intercept_errors fastcgi_max_temp_file_size fastcgi_next_upstream fastcgi_param fastcgi_pass_header fastcgi_pass_request_body fastcgi_pass_request_headers fastcgi_read_timeout fastcgi_send_lowat fastcgi_send_timeout fastcgi_split_path_info fastcgi_store fastcgi_store_access fastcgi_temp_file_write_size fastcgi_temp_path fastcgi_upstream_fail_timeout fastcgi_upstream_max_fails flv geoip_city geoip_country google_perftools_profiles gzip gzip_buffers gzip_comp_level gzip_disable gzip_hash gzip_http_version gzip_min_length gzip_no_buffer gzip_proxied gzip_static gzip_types gzip_vary gzip_window if_modified_since ignore_invalid_headers image_filter image_filter_buffer image_filter_jpeg_quality image_filter_transparency imap_auth imap_capabilities imap_client_buffer index ip_hash keepalive_requests keepalive_timeout kqueue_changes kqueue_events large_client_header_buffers limit_conn limit_conn_log_level limit_rate limit_rate_after limit_req limit_req_log_level limit_req_zone limit_zone lingering_time lingering_timeout lock_file log_format log_not_found log_subrequest map_hash_bucket_size map_hash_max_size master_process memcached_bind memcached_buffer_size memcached_connect_timeout memcached_next_upstream memcached_read_timeout memcached_send_timeout memcached_upstream_fail_timeout memcached_upstream_max_fails merge_slashes min_delete_depth modern_browser modern_browser_value msie_padding msie_refresh multi_accept open_file_cache open_file_cache_errors open_file_cache_events open_file_cache_min_uses open_file_cache_valid open_log_file_cache output_buffers override_charset perl perl_modules perl_require perl_set pid pop3_auth pop3_capabilities port_in_redirect postpone_gzipping postpone_output protocol proxy proxy_bind proxy_buffer proxy_buffer_size proxy_buffering proxy_buffers proxy_busy_buffers_size proxy_cache proxy_cache_key proxy_cache_methods proxy_cache_min_uses proxy_cache_path proxy_cache_use_stale proxy_cache_valid proxy_connect_timeout proxy_headers_hash_bucket_size proxy_headers_hash_max_size proxy_hide_header proxy_ignore_client_abort proxy_ignore_headers proxy_intercept_errors proxy_max_temp_file_size proxy_method proxy_next_upstream proxy_pass_error_message proxy_pass_header proxy_pass_request_body proxy_pass_request_headers proxy_read_timeout proxy_redirect proxy_send_lowat proxy_send_timeout proxy_set_body proxy_set_header proxy_ssl_session_reuse proxy_store proxy_store_access proxy_temp_file_write_size proxy_temp_path proxy_timeout proxy_upstream_fail_timeout proxy_upstream_max_fails random_index read_ahead real_ip_header recursive_error_pages request_pool_size reset_timedout_connection resolver resolver_timeout rewrite_log rtsig_overflow_events rtsig_overflow_test rtsig_overflow_threshold rtsig_signo satisfy secure_link_secret send_lowat send_timeout sendfile sendfile_max_chunk server_name_in_redirect server_names_hash_bucket_size server_names_hash_max_size server_tokens set_real_ip_from smtp_auth smtp_capabilities smtp_client_buffer smtp_greeting_delay so_keepalive source_charset ssi ssi_ignore_recycled_buffers ssi_min_file_chunk ssi_silent_errors ssi_types ssi_value_length ssl ssl_certificate ssl_certificate_key ssl_ciphers ssl_client_certificate ssl_crl ssl_dhparam ssl_engine ssl_prefer_server_ciphers ssl_protocols ssl_session_cache ssl_session_timeout ssl_verify_client ssl_verify_depth starttls stub_status sub_filter sub_filter_once sub_filter_types tcp_nodelay tcp_nopush thread_stack_size timeout timer_resolution types_hash_bucket_size types_hash_max_size underscores_in_headers uninitialized_variable_warn use user userid userid_domain userid_expires userid_mark userid_name userid_p3p userid_path userid_service valid_referers variables_hash_bucket_size variables_hash_max_size worker_connections worker_cpu_affinity worker_priority worker_processes worker_rlimit_core worker_rlimit_nofile worker_rlimit_sigpending worker_threads working_directory xclient xml_entities xslt_stylesheet xslt_typesdrew@li229-23', +); + +const keywords_block = words( + /* ngxDirectiveBlock */ 'http mail events server types location upstream charset_map limit_except if geo map', +); + +const keywords_important = words( + /* ngxDirectiveImportant */ 'include root server server_name listen internal proxy_pass memcached_pass fastcgi_pass try_files', +); + +let type; +function ret(style, tp) { + type = tp; + return style; +} + +function tokenBase(stream, state) { + stream.eatWhile(/[\w\$_]/); + + const cur = stream.current(); + + if (keywords.propertyIsEnumerable(cur)) { + return 'keyword'; + } else if (keywords_block.propertyIsEnumerable(cur)) { + return 'controlKeyword'; + } else if (keywords_important.propertyIsEnumerable(cur)) { + return 'controlKeyword'; + } + + const ch = stream.next(); + if (ch == '@') { + stream.eatWhile(/[\w\\\-]/); + return ret('meta', stream.current()); + } else if (ch == '/' && stream.eat('*')) { + return 'compare'; + } else if (ch == '<' && stream.eat('!')) { + state.tokenize = tokenSGMLComment; + return tokenSGMLComment(stream, state); + } else if (ch == '=') ret(null, 'compare'); + else if ((ch == '~' || ch == '|') && stream.eat('=')) return ret(null, 'compare'); + else if (ch == '"' || ch == "'") { + state.tokenize = tokenString(ch); + return state.tokenize(stream, state); + } else if (ch == '#') { + stream.skipToEnd(); + return ret('comment', 'comment'); + } else if (ch == '!') { + stream.match(/^\s*\w*/); + return ret('keyword', 'important'); + } else if (/\d/.test(ch)) { + stream.eatWhile(/[\w.%]/); + return ret('number', 'unit'); + } else if (/[,.+>*\/]/.test(ch)) { + return ret(null, 'select-op'); + } else if (/[;{}:\[\]]/.test(ch)) { + return ret(null, ch); + } else { + stream.eatWhile(/[\w\\\-]/); + return ret('variable', 'variable'); + } +} + +function tokenSGMLComment(stream, state) { + let dashes = 0, + ch; + while ((ch = stream.next()) != null) { + if (dashes >= 2 && ch == '>') { + state.tokenize = tokenBase; + break; + } + dashes = ch == '-' ? dashes + 1 : 0; + } + return ret('comment', 'comment'); +} + +function tokenString(quote) { + return function (stream, state) { + let escaped = false, + ch; + while ((ch = stream.next()) != null) { + if (ch == quote && !escaped) break; + escaped = !escaped && ch == '\\'; + } + if (!escaped) state.tokenize = tokenBase; + return ret('string', 'string'); + }; +} + +interface NgxState { + tokenize: (stream: any, state: NgxState) => string | null; + baseIndent: number; + stack: string[]; +} + +export const nginx: StreamParser = { + name: 'nginx', + startState: function (): NgxState { + return { + tokenize: tokenBase, + baseIndent: 0, + stack: [], + }; + }, + token: function (stream, state) { + if (stream.eatSpace()) return null; + type = null; + const style = state.tokenize(stream, state); + const context = state.stack[state.stack.length - 1]; + if (type === 'hash' && context === 'rule') return 'atom'; + else if (style === 'variable') { + if (context === 'rule') return 'number'; + else if (!context || context === '@media{') return 'tag'; + } + if (context === 'rule' && /^[\{\};]$/.test(type!)) state.stack.pop(); + if (type === '{') { + if (context === '@media') state.stack[state.stack.length - 1] = '@media{'; + else state.stack.push('{'); + } else if (type === '}') state.stack.pop(); + else if (type === '@media') state.stack.push('@media'); + else if (context === '{' && type !== 'comment') state.stack.push('rule'); + return style; + }, + indent: function (state, textAfter, cx) { + let n = state.stack.length; + if (/^\}/.test(textAfter)) n -= state.stack[state.stack.length - 1] === 'rule' ? 2 : 1; + return state.baseIndent + n * cx.unit; + }, + languageData: { + indentOnInput: /^\s*\}$/, + }, +}; diff --git a/frontend/src/components/complex-table/index.vue b/frontend/src/components/complex-table/index.vue new file mode 100644 index 0000000..a23df97 --- /dev/null +++ b/frontend/src/components/complex-table/index.vue @@ -0,0 +1,352 @@ + + + + diff --git a/frontend/src/components/config-card/index.vue b/frontend/src/components/config-card/index.vue new file mode 100644 index 0000000..baa1814 --- /dev/null +++ b/frontend/src/components/config-card/index.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/frontend/src/components/confirm-dialog/index.vue b/frontend/src/components/confirm-dialog/index.vue new file mode 100644 index 0000000..210801f --- /dev/null +++ b/frontend/src/components/confirm-dialog/index.vue @@ -0,0 +1,62 @@ + + + diff --git a/frontend/src/components/copy-button/index.vue b/frontend/src/components/copy-button/index.vue new file mode 100644 index 0000000..f7e9e0c --- /dev/null +++ b/frontend/src/components/copy-button/index.vue @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/components/del-dialog/index.vue b/frontend/src/components/del-dialog/index.vue new file mode 100644 index 0000000..e19618e --- /dev/null +++ b/frontend/src/components/del-dialog/index.vue @@ -0,0 +1,111 @@ + + + diff --git a/frontend/src/components/detail-show/index.vue b/frontend/src/components/detail-show/index.vue new file mode 100644 index 0000000..1ea7abd --- /dev/null +++ b/frontend/src/components/detail-show/index.vue @@ -0,0 +1,39 @@ + + + diff --git a/frontend/src/components/dialog-pro/index.vue b/frontend/src/components/dialog-pro/index.vue new file mode 100644 index 0000000..3481253 --- /dev/null +++ b/frontend/src/components/dialog-pro/index.vue @@ -0,0 +1,97 @@ + + + diff --git a/frontend/src/components/docker-proxy/dialog.vue b/frontend/src/components/docker-proxy/dialog.vue new file mode 100644 index 0000000..115f7ef --- /dev/null +++ b/frontend/src/components/docker-proxy/dialog.vue @@ -0,0 +1,87 @@ + + + diff --git a/frontend/src/components/docker-proxy/docker-restart.vue b/frontend/src/components/docker-proxy/docker-restart.vue new file mode 100644 index 0000000..d03be53 --- /dev/null +++ b/frontend/src/components/docker-proxy/docker-restart.vue @@ -0,0 +1,50 @@ + + + diff --git a/frontend/src/components/docker-proxy/index.vue b/frontend/src/components/docker-proxy/index.vue new file mode 100644 index 0000000..7de9bd8 --- /dev/null +++ b/frontend/src/components/docker-proxy/index.vue @@ -0,0 +1,64 @@ + + + diff --git a/frontend/src/components/drawer-pro/index.vue b/frontend/src/components/drawer-pro/index.vue new file mode 100644 index 0000000..362ef8b --- /dev/null +++ b/frontend/src/components/drawer-pro/index.vue @@ -0,0 +1,171 @@ + + + diff --git a/frontend/src/components/error-message/404.vue b/frontend/src/components/error-message/404.vue new file mode 100644 index 0000000..92c75d1 --- /dev/null +++ b/frontend/src/components/error-message/404.vue @@ -0,0 +1,44 @@ + + + + + diff --git a/frontend/src/components/error-message/err_domain.vue b/frontend/src/components/error-message/err_domain.vue new file mode 100644 index 0000000..bf66f0c --- /dev/null +++ b/frontend/src/components/error-message/err_domain.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/frontend/src/components/error-message/err_ip.vue b/frontend/src/components/error-message/err_ip.vue new file mode 100644 index 0000000..31908f1 --- /dev/null +++ b/frontend/src/components/error-message/err_ip.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/frontend/src/components/error-message/error_code.vue b/frontend/src/components/error-message/error_code.vue new file mode 100644 index 0000000..2bd25d9 --- /dev/null +++ b/frontend/src/components/error-message/error_code.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/frontend/src/components/error-message/unsafe.vue b/frontend/src/components/error-message/unsafe.vue new file mode 100644 index 0000000..4eaea37 --- /dev/null +++ b/frontend/src/components/error-message/unsafe.vue @@ -0,0 +1,55 @@ + + + + + diff --git a/frontend/src/components/error-prompt/index.vue b/frontend/src/components/error-prompt/index.vue new file mode 100644 index 0000000..05f6398 --- /dev/null +++ b/frontend/src/components/error-prompt/index.vue @@ -0,0 +1,22 @@ + + + + + diff --git a/frontend/src/components/exist-file/index.vue b/frontend/src/components/exist-file/index.vue new file mode 100644 index 0000000..987a76e --- /dev/null +++ b/frontend/src/components/exist-file/index.vue @@ -0,0 +1,78 @@ + + diff --git a/frontend/src/components/file-list/index.vue b/frontend/src/components/file-list/index.vue new file mode 100644 index 0000000..e80355e --- /dev/null +++ b/frontend/src/components/file-list/index.vue @@ -0,0 +1,499 @@ + + + + + diff --git a/frontend/src/components/file-role/index.vue b/frontend/src/components/file-role/index.vue new file mode 100644 index 0000000..7ab67f8 --- /dev/null +++ b/frontend/src/components/file-role/index.vue @@ -0,0 +1,171 @@ + + diff --git a/frontend/src/components/group/index.vue b/frontend/src/components/group/index.vue new file mode 100644 index 0000000..f1fde91 --- /dev/null +++ b/frontend/src/components/group/index.vue @@ -0,0 +1,182 @@ + + diff --git a/frontend/src/components/index.ts b/frontend/src/components/index.ts new file mode 100644 index 0000000..5be84de --- /dev/null +++ b/frontend/src/components/index.ts @@ -0,0 +1,27 @@ +import { type App } from 'vue'; +import LayoutContent from './layout-content/index.vue'; +import RouterButton from './router-button/index.vue'; +import ComplexTable from './complex-table/index.vue'; +import OpDialog from './del-dialog/index.vue'; +import TableSearch from './table-search/index.vue'; +import TableSetting from './table-setting/index.vue'; +import TableRefresh from './table-refresh/index.vue'; +import CopyButton from '@/components/copy-button/index.vue'; +import MsgInfo from '@/components/msg-info/index.vue'; +import DrawerPro from '@/components/drawer-pro/index.vue'; +import DialogPro from '@/components/dialog-pro/index.vue'; +export default { + install(app: App) { + app.component(LayoutContent.name, LayoutContent); + app.component(RouterButton.name, RouterButton); + app.component(ComplexTable.name, ComplexTable); + app.component(OpDialog.name, OpDialog); + app.component(CopyButton.name, CopyButton); + app.component(TableSearch.name, TableSearch); + app.component(TableSetting.name, TableSetting); + app.component(TableRefresh.name, TableRefresh); + app.component(MsgInfo.name, MsgInfo); + app.component(DrawerPro.name, DrawerPro); + app.component(DialogPro.name, DialogPro); + }, +}; diff --git a/frontend/src/components/input-tag/index.vue b/frontend/src/components/input-tag/index.vue new file mode 100644 index 0000000..b06e26f --- /dev/null +++ b/frontend/src/components/input-tag/index.vue @@ -0,0 +1,85 @@ + + + diff --git a/frontend/src/components/layout-col/form.vue b/frontend/src/components/layout-col/form.vue new file mode 100644 index 0000000..d5e1f02 --- /dev/null +++ b/frontend/src/components/layout-col/form.vue @@ -0,0 +1,16 @@ + + + diff --git a/frontend/src/components/layout-content/form-button.vue b/frontend/src/components/layout-content/form-button.vue new file mode 100644 index 0000000..bba22c5 --- /dev/null +++ b/frontend/src/components/layout-content/form-button.vue @@ -0,0 +1,5 @@ + diff --git a/frontend/src/components/layout-content/index.vue b/frontend/src/components/layout-content/index.vue new file mode 100644 index 0000000..1bbc21c --- /dev/null +++ b/frontend/src/components/layout-content/index.vue @@ -0,0 +1,149 @@ + + + + + diff --git a/frontend/src/components/layout-content/no-such-service.vue b/frontend/src/components/layout-content/no-such-service.vue new file mode 100644 index 0000000..080ca5c --- /dev/null +++ b/frontend/src/components/layout-content/no-such-service.vue @@ -0,0 +1,32 @@ + + + diff --git a/frontend/src/components/license-import/index.vue b/frontend/src/components/license-import/index.vue new file mode 100644 index 0000000..9358530 --- /dev/null +++ b/frontend/src/components/license-import/index.vue @@ -0,0 +1,168 @@ + + + diff --git a/frontend/src/components/log/compose/index.vue b/frontend/src/components/log/compose/index.vue new file mode 100644 index 0000000..8c1ae1f --- /dev/null +++ b/frontend/src/components/log/compose/index.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/frontend/src/components/log/container-drawer/index.vue b/frontend/src/components/log/container-drawer/index.vue new file mode 100644 index 0000000..e74b11d --- /dev/null +++ b/frontend/src/components/log/container-drawer/index.vue @@ -0,0 +1,105 @@ + + + + + diff --git a/frontend/src/components/log/container/index.vue b/frontend/src/components/log/container/index.vue new file mode 100644 index 0000000..e6995a7 --- /dev/null +++ b/frontend/src/components/log/container/index.vue @@ -0,0 +1,315 @@ + + + + + diff --git a/frontend/src/components/log/custom-hightlight/index.vue b/frontend/src/components/log/custom-hightlight/index.vue new file mode 100644 index 0000000..2518ea3 --- /dev/null +++ b/frontend/src/components/log/custom-hightlight/index.vue @@ -0,0 +1,425 @@ + + + + diff --git a/frontend/src/components/log/file-drawer/index.vue b/frontend/src/components/log/file-drawer/index.vue new file mode 100644 index 0000000..8d2bcbb --- /dev/null +++ b/frontend/src/components/log/file-drawer/index.vue @@ -0,0 +1,85 @@ + + + + diff --git a/frontend/src/components/log/file/index.vue b/frontend/src/components/log/file/index.vue new file mode 100644 index 0000000..2fa44b7 --- /dev/null +++ b/frontend/src/components/log/file/index.vue @@ -0,0 +1,504 @@ + + + diff --git a/frontend/src/components/log/task/index.vue b/frontend/src/components/log/task/index.vue new file mode 100644 index 0000000..1d66118 --- /dev/null +++ b/frontend/src/components/log/task/index.vue @@ -0,0 +1,76 @@ + + diff --git a/frontend/src/components/log/task/log-without-dialog.vue b/frontend/src/components/log/task/log-without-dialog.vue new file mode 100644 index 0000000..a71cdd4 --- /dev/null +++ b/frontend/src/components/log/task/log-without-dialog.vue @@ -0,0 +1,22 @@ + + diff --git a/frontend/src/components/main-div/index.vue b/frontend/src/components/main-div/index.vue new file mode 100644 index 0000000..5c6960f --- /dev/null +++ b/frontend/src/components/main-div/index.vue @@ -0,0 +1,36 @@ + + + diff --git a/frontend/src/components/mkdown-editor/index.vue b/frontend/src/components/mkdown-editor/index.vue new file mode 100644 index 0000000..0945dac --- /dev/null +++ b/frontend/src/components/mkdown-editor/index.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/components/msg-info/index.vue b/frontend/src/components/msg-info/index.vue new file mode 100644 index 0000000..6815e89 --- /dev/null +++ b/frontend/src/components/msg-info/index.vue @@ -0,0 +1,35 @@ + + + + diff --git a/frontend/src/components/node-select/index.vue b/frontend/src/components/node-select/index.vue new file mode 100644 index 0000000..da37b4d --- /dev/null +++ b/frontend/src/components/node-select/index.vue @@ -0,0 +1,49 @@ + + + diff --git a/frontend/src/components/port-jump/index.vue b/frontend/src/components/port-jump/index.vue new file mode 100644 index 0000000..50ec9d4 --- /dev/null +++ b/frontend/src/components/port-jump/index.vue @@ -0,0 +1,67 @@ + + diff --git a/frontend/src/components/router-button/index.vue b/frontend/src/components/router-button/index.vue new file mode 100644 index 0000000..f991ee1 --- /dev/null +++ b/frontend/src/components/router-button/index.vue @@ -0,0 +1,98 @@ + + + + + diff --git a/frontend/src/components/status/index.vue b/frontend/src/components/status/index.vue new file mode 100644 index 0000000..c1cab24 --- /dev/null +++ b/frontend/src/components/status/index.vue @@ -0,0 +1,134 @@ + + + + + diff --git a/frontend/src/components/svg-icon/svg-icon.vue b/frontend/src/components/svg-icon/svg-icon.vue new file mode 100644 index 0000000..d9d46c1 --- /dev/null +++ b/frontend/src/components/svg-icon/svg-icon.vue @@ -0,0 +1,51 @@ + + + diff --git a/frontend/src/components/system-upgrade/index.vue b/frontend/src/components/system-upgrade/index.vue new file mode 100644 index 0000000..017373e --- /dev/null +++ b/frontend/src/components/system-upgrade/index.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/frontend/src/components/system-upgrade/releases/index.vue b/frontend/src/components/system-upgrade/releases/index.vue new file mode 100644 index 0000000..2054dfa --- /dev/null +++ b/frontend/src/components/system-upgrade/releases/index.vue @@ -0,0 +1,186 @@ + + + + + diff --git a/frontend/src/components/system-upgrade/upgrade/index.vue b/frontend/src/components/system-upgrade/upgrade/index.vue new file mode 100644 index 0000000..602c412 --- /dev/null +++ b/frontend/src/components/system-upgrade/upgrade/index.vue @@ -0,0 +1,118 @@ + + + + + diff --git a/frontend/src/components/table-refresh/index.vue b/frontend/src/components/table-refresh/index.vue new file mode 100644 index 0000000..3174621 --- /dev/null +++ b/frontend/src/components/table-refresh/index.vue @@ -0,0 +1,19 @@ + + + + + diff --git a/frontend/src/components/table-search/index.vue b/frontend/src/components/table-search/index.vue new file mode 100644 index 0000000..f965fb3 --- /dev/null +++ b/frontend/src/components/table-search/index.vue @@ -0,0 +1,49 @@ + + + diff --git a/frontend/src/components/table-setting/index.vue b/frontend/src/components/table-setting/index.vue new file mode 100644 index 0000000..f45a35b --- /dev/null +++ b/frontend/src/components/table-setting/index.vue @@ -0,0 +1,80 @@ + + + + + diff --git a/frontend/src/components/task-list/index.vue b/frontend/src/components/task-list/index.vue new file mode 100644 index 0000000..1292947 --- /dev/null +++ b/frontend/src/components/task-list/index.vue @@ -0,0 +1,112 @@ + + + diff --git a/frontend/src/components/terminal/database.vue b/frontend/src/components/terminal/database.vue new file mode 100644 index 0000000..77c6724 --- /dev/null +++ b/frontend/src/components/terminal/database.vue @@ -0,0 +1,56 @@ + + + diff --git a/frontend/src/components/terminal/index.vue b/frontend/src/components/terminal/index.vue new file mode 100644 index 0000000..12bf0b8 --- /dev/null +++ b/frontend/src/components/terminal/index.vue @@ -0,0 +1,295 @@ + + + + + diff --git a/frontend/src/components/tooltip/index.vue b/frontend/src/components/tooltip/index.vue new file mode 100644 index 0000000..3c6c240 --- /dev/null +++ b/frontend/src/components/tooltip/index.vue @@ -0,0 +1,54 @@ + + + + diff --git a/frontend/src/components/upload/index.vue b/frontend/src/components/upload/index.vue new file mode 100644 index 0000000..93d0dd2 --- /dev/null +++ b/frontend/src/components/upload/index.vue @@ -0,0 +1,460 @@ + + + diff --git a/frontend/src/components/v-charts/components/Line.vue b/frontend/src/components/v-charts/components/Line.vue new file mode 100644 index 0000000..35c9957 --- /dev/null +++ b/frontend/src/components/v-charts/components/Line.vue @@ -0,0 +1,271 @@ + + + diff --git a/frontend/src/components/v-charts/components/Pie.vue b/frontend/src/components/v-charts/components/Pie.vue new file mode 100644 index 0000000..770b532 --- /dev/null +++ b/frontend/src/components/v-charts/components/Pie.vue @@ -0,0 +1,192 @@ + + + diff --git a/frontend/src/components/v-charts/index.vue b/frontend/src/components/v-charts/index.vue new file mode 100644 index 0000000..666dcc8 --- /dev/null +++ b/frontend/src/components/v-charts/index.vue @@ -0,0 +1,25 @@ + + diff --git a/frontend/src/components/vscode-open/index.vue b/frontend/src/components/vscode-open/index.vue new file mode 100644 index 0000000..1bc12ba --- /dev/null +++ b/frontend/src/components/vscode-open/index.vue @@ -0,0 +1,92 @@ + + diff --git a/frontend/src/composables/useGlobalStore.ts b/frontend/src/composables/useGlobalStore.ts new file mode 100644 index 0000000..5109707 --- /dev/null +++ b/frontend/src/composables/useGlobalStore.ts @@ -0,0 +1,12 @@ +import { GlobalStore } from '@/store'; +import { storeToRefs } from 'pinia'; + +export const useGlobalStore = () => { + const globalStore = GlobalStore(); + const storeRefs = storeToRefs(globalStore); + + return { + globalStore, + ...storeRefs, + }; +}; diff --git a/frontend/src/config/nprogress.ts b/frontend/src/config/nprogress.ts new file mode 100644 index 0000000..a280ea1 --- /dev/null +++ b/frontend/src/config/nprogress.ts @@ -0,0 +1,11 @@ +import NProgress from 'nprogress'; +import 'nprogress/nprogress.css'; + +NProgress.configure({ + easing: 'ease', + speed: 300, + showSpinner: true, + trickleSpeed: 200, + minimum: 0.3, +}); +export default NProgress; diff --git a/frontend/src/config/pinia-persist.ts b/frontend/src/config/pinia-persist.ts new file mode 100644 index 0000000..d51a76e --- /dev/null +++ b/frontend/src/config/pinia-persist.ts @@ -0,0 +1,11 @@ +import { PersistedStateOptions } from 'pinia-plugin-persistedstate'; + +const piniaPersistConfig = (key: string) => { + const persist: PersistedStateOptions = { + key, + storage: window.localStorage, + }; + return persist; +}; + +export default piniaPersistConfig; diff --git a/frontend/src/directives/index.ts b/frontend/src/directives/index.ts new file mode 100644 index 0000000..3720e7e --- /dev/null +++ b/frontend/src/directives/index.ts @@ -0,0 +1,16 @@ +import { App, Directive } from 'vue'; +import integerInput from './modules/integer'; + +const directivesList: { [key: string]: Directive } = { + 'integer-input': integerInput, +}; + +const directives = { + install: function (app: App) { + Object.keys(directivesList).forEach((key) => { + app.directive(key, directivesList[key]); + }); + }, +}; + +export default directives; diff --git a/frontend/src/directives/modules/integer.ts b/frontend/src/directives/modules/integer.ts new file mode 100644 index 0000000..d90b077 --- /dev/null +++ b/frontend/src/directives/modules/integer.ts @@ -0,0 +1,23 @@ +import type { Directive, DirectiveBinding } from 'vue'; + +const integerInput: Directive = { + mounted(el: HTMLElement, binding: DirectiveBinding) { + const { value } = binding; + el.addEventListener('input', (event: Event) => { + const inputElement = event.target as HTMLInputElement; + let inputValue = inputElement.value; + inputValue = inputValue.replace(/\..*/, ''); + if (value?.min !== undefined && Number(inputValue) < value.min) { + inputValue = value.min.toString(); + } + if (value?.max !== undefined && Number(inputValue) > value.max) { + inputValue = value.max.toString(); + } + inputElement.value = inputValue; + const inputEvent = new Event('input', { bubbles: true }); + inputElement.dispatchEvent(inputEvent); + }); + }, +}; + +export default integerInput; diff --git a/frontend/src/enums/app.ts b/frontend/src/enums/app.ts new file mode 100644 index 0000000..9ec923d --- /dev/null +++ b/frontend/src/enums/app.ts @@ -0,0 +1,4 @@ +export enum DeviceType { + Mobile, + Desktop, +} diff --git a/frontend/src/enums/files.ts b/frontend/src/enums/files.ts new file mode 100644 index 0000000..2f87dcc --- /dev/null +++ b/frontend/src/enums/files.ts @@ -0,0 +1,40 @@ +export enum CompressType { + Zip = 'zip', + Gz = 'gz', + Bz2 = 'bz2', + TarBz2 = 'tar.bz2', + Tar = 'tar', + TGz = 'tgz', + TarGz = 'tar.gz', + Xz = 'xz', + TarXz = 'tar.xz', + Rar = 'rar', + '7z' = '7z', +} + +export enum CompressExtension { + zip = '.zip', + gz = '.gz', + 'tar.bz2' = '.tar.bz2', + bz2 = '.bz2', + tar = '.tar', + tgz = '.tgz', + 'tar.gz' = '.tar.gz', + 'tar.xz' = '.tar.xz', + xz = '.xz', + rar = '.rar', + '7z' = '.7z', +} + +export const MimetypeByExtensionObject: Record = { + '.zip': 'application/zip', + '.tar': 'application/x-tar', + '.tar.bz2': 'application/x-bzip2', + '.bz2': 'application/x-bzip2', + '.tar.gz': 'application/gzip', + '.tgz': 'application/x-gzip', + '.gz': 'application/gzip', + '.xz': 'application/x-xz', + '.rar': 'application/x-rar-compressed', + '.7z': 'application/x-7z-compressed', +}; diff --git a/frontend/src/enums/http-enum.ts b/frontend/src/enums/http-enum.ts new file mode 100644 index 0000000..7049997 --- /dev/null +++ b/frontend/src/enums/http-enum.ts @@ -0,0 +1,26 @@ +export enum ResultEnum { + SUCCESS = 200, + ERRIP = 310, + ERRDOMAIN = 311, + UNSAFETY = 312, + EXPIRED = 313, + + ERROR = 500, + OVERDUE = 401, + FORBIDDEN = 403, + NOTFOUND = 404, + ERRAUTH = 406, + ERRGLOBALLOADDING = 407, + ERRXPACK = 410, + NodeUnBind = 411, + TIMEOUT = 20000, + TYPE = 'success', +} + +export enum TimeoutEnum { + T_40S = 40000, + T_60S = 60000, + T_3M = 180000, + T_5M = 300000, + T_10M = 600000, +} diff --git a/frontend/src/env.d.ts b/frontend/src/env.d.ts new file mode 100644 index 0000000..a699b8a --- /dev/null +++ b/frontend/src/env.d.ts @@ -0,0 +1,7 @@ +/// + +declare module '*.vue' { + import type { DefineComponent } from 'vue'; + const component: DefineComponent<{}, {}, any>; + export default component; +} diff --git a/frontend/src/global/bus.ts b/frontend/src/global/bus.ts new file mode 100644 index 0000000..a8feefa --- /dev/null +++ b/frontend/src/global/bus.ts @@ -0,0 +1,3 @@ +import Bus from '@/utils/bus'; + +export default new Bus(); diff --git a/frontend/src/global/form-rules.ts b/frontend/src/global/form-rules.ts new file mode 100644 index 0000000..c71e9d9 --- /dev/null +++ b/frontend/src/global/form-rules.ts @@ -0,0 +1,944 @@ +import i18n from '@/lang'; +import { FormItemRule } from 'element-plus'; + +const checkNoSpace = (rule: any, value: any, callback: any) => { + if (value.indexOf(' ') !== -1) { + return callback(new Error(i18n.global.t('setting.noSpace'))); + } + callback(); +}; + +const checkIp = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.requiredInput'))); + } else { + const 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])$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.ip'))); + } else { + callback(); + } + } +}; + +const checkIpv4 = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(); + } else { + const 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])$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.ip'))); + } else { + callback(); + } + } +}; + +const checkIpV6 = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.requiredInput'))); + } else { + const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`; + const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})'; + const IPv6AddressRegExp = new RegExp( + '^(' + + `(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` + + `(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` + + `(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` + + `(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` + + `(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` + + `(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` + + ')(%[0-9a-zA-Z-.:]{1,})?$', + ); + if (!IPv6AddressRegExp.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.ip'))); + } else { + callback(); + } + } +}; + +const checkIpV4V6OrDomain = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.requiredInput'))); + } else { + const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`; + const IPv4AddressRegExp = new RegExp(`^${IPv4AddressFormat}$`); + const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})'; + const IPv6AddressRegExp = new RegExp( + '^(' + + `(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` + + `(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` + + `(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` + + `(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` + + `(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` + + `(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` + + ')(%[0-9a-zA-Z-.:]{1,})?$', + ); + const regHost = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/; + if (!regHost.test(value) && !IPv4AddressRegExp.test(value) && !IPv6AddressRegExp.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.ip'))); + } else { + callback(); + } + } +}; + +const checkDomainOrIP = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(); + } else { + const ipv4Regex = /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + const ipv6Regex = + /^(?:(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,7}:|(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|:(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|fe80:(?::[0-9a-fA-F]{0,4}){0,4}%[0-9a-zA-Z]{1,}|::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])|(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(?:25[0-5]|(?:2[0-4]|1{0,1}[0-9]){0,1}[0-9]))$/; + const domainRegex = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i; + + if (ipv4Regex.test(value) || ipv6Regex.test(value) || domainRegex.test(value)) { + callback(); + } else { + callback(new Error(i18n.global.t('commons.rule.domain'))); + } + } +}; + +const checkHost = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.requiredInput'))); + } else { + const regIP = + /^(\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])$/; + const regHost = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/; + if (!regIP.test(value) && !regHost.test(value)) { + callback(new Error(i18n.global.t('commons.rule.host'))); + } else { + callback(); + } + } +}; + +const checkIllegal = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.requiredInput'))); + return; + } + if ( + value.indexOf('&') !== -1 || + value.indexOf('|') !== -1 || + value.indexOf(';') !== -1 || + value.indexOf('$') !== -1 || + value.indexOf("'") !== -1 || + value.indexOf('`') !== -1 || + value.indexOf('(') !== -1 || + value.indexOf(')') !== -1 || + value.indexOf('>') !== -1 || + value.indexOf('<') !== -1 + ) { + callback(new Error(i18n.global.t('commons.rule.illegalInput'))); + } else { + callback(); + } +}; + +const complexityPassword = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.complexityPassword'))); + } else { + const reg = /^(?![\d]+$)(?![a-zA-Z]+$)(?![^\da-zA-Z]+$).{8,30}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.complexityPassword'))); + } else { + callback(); + } + } +}; + +const checkName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.commonName'))); + } else { + const reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-zA-Z0-9_.\u4e00-\u9fa5-]{0,127}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.commonName'))); + } else { + callback(); + } + } +}; + +const checkUserName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.userName'))); + } else { + const reg = /^[a-zA-Z0-9_\u4e00-\u9fa5]{3,30}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.userName'))); + } else { + callback(); + } + } +}; + +const checkSimpleName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.simpleName'))); + } else { + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9_]{2,29}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.simpleName'))); + } else { + callback(); + } + } +}; + +const checkSimplePassword = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.simplePassword'))); + } else { + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9_]{0,29}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.simplePassword'))); + } else { + callback(); + } + } +}; + +const checkAuthBasicPassword = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.authBasicPassword'))); + } else { + const reg = /^[a-zA-Z0-9_\-\.@$!%*?&]{1,72}$/; + if (!reg.test(value)) { + callback(new Error(i18n.global.t('commons.rule.authBasicPassword'))); + } else { + callback(); + } + } +}; + +const checkDBName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.dbName'))); + } else { + const reg = /^[a-zA-Z0-9\u4e00-\u9fa5]{1}[a-zA-Z0-9_.\u4e00-\u9fa5-]{0,63}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.dbName'))); + } else { + callback(); + } + } +}; + +const checkComposeName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.composeName'))); + } else { + const reg = /^[a-z0-9]{1}[a-z0-9_-]{0,256}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.composeName'))); + } else { + callback(); + } + } +}; + +const checkImageName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.imageName'))); + } else { + const reg = /^[a-zA-Z0-9]{1}[a-z:@A-Z0-9_/.-]{0,255}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.imageName'))); + } else { + callback(); + } + } +}; + +const checkVolumeName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.volumeName'))); + } else { + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9_.-]{1,30}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.volumeName'))); + } else { + callback(); + } + } +}; + +const checkLinuxName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.linuxName', ['/\\:*?\'"()<>|']))); + } else { + const reg = /^[^/\\\"'|<>()?*]{1,128}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.linuxName', ['/\\:*?\'"()<>|']))); + } else { + callback(); + } + } +}; + +const checkSupervisorName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.supervisorName'))); + } else { + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9_-]{0,127}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.supervisorName'))); + } else { + callback(); + } + } +}; + +const checkDatabaseName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.databaseName'))); + } else { + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9_]{0,30}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.databaseName'))); + } else { + callback(); + } + } +}; + +const checkAppName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.appName'))); + } else { + const reg = /^(?![_-])[a-zA-Z0-9_-]{1,29}[a-zA-Z0-9]$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.appName'))); + } else { + callback(); + } + } +}; + +const checkAlias = (rule: any, value: any, callback: any) => { + if (!value) { + callback(new Error(i18n.global.t('commons.rule.alias'))); + return; + } + const reg = /^(?![-_])[A-Za-z0-9._-]{1,128}(? { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.domain'))); + } else { + const reg = + /^([\w\u4e00-\u9fa5\-\*]{1,100}\.){1,10}([\w\u4e00-\u9fa5\-]{1,24}|[\w\u4e00-\u9fa5\-]{1,24}\.[\w\u4e00-\u9fa5\-]{1,24})$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.domain'))); + } else { + callback(); + } + } +}; + +const checkDomainWithPort = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.domain'))); + } else { + const reg = + /^([\w\u4e00-\u9fa5\-\*]{1,100}\.){1,10}([\w\u4e00-\u9fa5\-]{1,24}|[\w\u4e00-\u9fa5\-]{1,24}\.[\w\u4e00-\u9fa5\-]{1,24})(:\d{1,5})?$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.domain'))); + } else { + callback(); + } + } +}; + +const checkIntegerNumber = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.integer'))); + } else { + const reg = /^[1-9]\d*$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.integer'))); + } else { + callback(); + } + } +}; + +const checkIntegerNumberWith0 = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.integer'))); + } else { + const reg = /^[0-9]*$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.integer'))); + } else { + callback(); + } + } +}; + +const checkFloatNumber = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.integer'))); + } else { + const reg = /^\d+(\.\d+)?$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.number'))); + } else { + callback(); + } + } +}; + +const checkParamCommon = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.paramName'))); + } else { + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9._-]{1,63}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.paramName'))); + } else { + callback(); + } + } +}; + +const checkParamComplexity = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.paramComplexity', ['.%@!~_-']))); + } else { + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9.%@!~_-]{4,126}[a-zA-Z0-9]{1}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.paramComplexity', ['.%@!~_-']))); + } else { + callback(); + } + } +}; + +const checkParamUrlAndPort = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.paramUrlAndPort'))); + } else { + const reg = + /^(https?:\/\/)?((localhost)|([a-zA-Z0-9_-]+\.)*[a-zA-Z0-9_-]+)(:[1-9]\d{0,3}|:[1-5]\d{4}|:6[0-4]\d{3}|:65[0-4]\d{2}|:655[0-2]\d|:6553[0-5])?(\/\S*)?$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.paramUrlAndPort'))); + } else { + callback(); + } + } +}; + +const checkPort = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.port'))); + } else { + const reg = /^([1-9](\d{0,3}))$|^([1-5]\d{4})$|^(6[0-4]\d{3})$|^(65[0-4]\d{2})$|^(655[0-2]\d)$|^(6553[0-5])$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.port'))); + } else { + callback(); + } + } +}; + +const checkDoc = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.nginxDoc'))); + } else { + const reg = /^[A-Za-z0-9\n./]+$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.nginxDoc'))); + } else { + callback(); + } + } +}; + +export function checkNumberRange(min: number, max: number): FormItemRule { + return { + required: false, + trigger: 'blur', + min: min, + max: max, + type: 'number', + message: i18n.global.t('commons.rule.numberRange', [min, max]), + }; +} + +export function checkFloatNumberRange(min: number, max: number): FormItemRule { + let validatorFunc = function (rule: any, value: any, callback: any) { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.disableFunction'))); + } else { + if ((Number(value) < min || Number(value) > max) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.disableFunction'))); + } else { + callback(); + } + } + }; + return { + required: false, + trigger: 'blur', + validator: validatorFunc, + message: i18n.global.t('commons.rule.numberRange', [min, max]), + }; +} + +const checkContainerName = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(); + } else { + const reg = /^[a-zA-Z0-9]{1}[a-zA-Z0-9_.-]{1,127}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.containerName'))); + } else { + callback(); + } + } +}; + +const checkDisableFunctions = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.disableFunction'))); + } else { + const reg = /^[a-zA-Z_,]+$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.disableFunction'))); + } else { + callback(); + } + } +}; + +const checkLeechExts = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.leechExts'))); + } else { + const reg = /^[a-zA-Z0-9,]+$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.leechExts'))); + } else { + callback(); + } + } +}; + +const checkParamSimple = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(); + } else { + const reg = /^[a-z0-9][a-z0-9]{1,128}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.paramSimple'))); + } else { + callback(); + } + } +}; + +const checkFilePermission = (rule, value, callback) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.filePermission'))); + } else { + const regFilePermission = /^[0-7]{3,4}$/; + if (!regFilePermission.test(value)) { + callback(new Error(i18n.global.t('commons.rule.filePermission'))); + } else { + callback(); + } + } +}; + +const checkPHPExtensions = (rule, value, callback) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.phpExtension'))); + } else { + const reg = /^[a-z0-9,_]+$/; + if (!reg.test(value)) { + callback(new Error(i18n.global.t('commons.rule.phpExtension'))); + } else { + callback(); + } + } +}; + +const checkHttpOrHttps = (rule, value, callback) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(new Error(i18n.global.t('commons.rule.paramHttp'))); + } else { + const regHttpHttps = /^(http|https):\/\//; + if (!regHttpHttps.test(value)) { + callback(new Error(i18n.global.t('commons.rule.paramHttp'))); + } else { + callback(); + } + } +}; + +const checkPhone = (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(); + } else { + const reg = /^(?:(?:\+|00)86)?1[3-9]\d{9}$/; + if (!reg.test(value) && value !== '') { + callback(new Error(i18n.global.t('commons.rule.phone'))); + } else { + callback(); + } + } +}; + +export function checkMaxLength(maxLength: number): FormItemRule { + return { + required: false, + trigger: 'blur', + type: 'string', + validator: (rule: any, value: any, callback: any) => { + if (value === '' || typeof value === 'undefined' || value == null) { + callback(); + } else { + if (value.length > maxLength) { + callback(new Error(i18n.global.t('commons.rule.maxLength', [maxLength]))); + } else { + callback(); + } + } + }, + }; +} + +const checkIpv4orV6 = (rule: any, value: any, callback: any) => { + if (value === '' || value == null) { + return callback(); + } + + const ipv4 = /^(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(?:\.(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}$/; + const ipv6 = /^((?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}|(?:[A-Fa-f0-9]{1,4}:){1,7}:|:(?::[A-Fa-f0-9]{1,4}){1,7})$/; + + if (!ipv4.test(value) && !ipv6.test(value)) { + return callback(new Error(i18n.global.t('commons.rule.ip'))); + } + callback(); +}; + +interface CommonRule { + requiredInput: FormItemRule; + requiredSelect: FormItemRule; + requiredSelectBusiness: FormItemRule; + noSpace: FormItemRule; + name: FormItemRule; + userName: FormItemRule; + simpleName: FormItemRule; + simplePassword: FormItemRule; + dbName: FormItemRule; + imageName: FormItemRule; + composeName: FormItemRule; + volumeName: FormItemRule; + linuxName: FormItemRule; + password: FormItemRule; + email: FormItemRule; + number: FormItemRule; + integerNumber: FormItemRule; + integerNumberWith0: FormItemRule; + floatNumber: FormItemRule; + ip: FormItemRule; + ipV6: FormItemRule; + ipv4: FormItemRule; + ipV4V6OrDomain: FormItemRule; + host: FormItemRule; + illegal: FormItemRule; + port: FormItemRule; + domain: FormItemRule; + databaseName: FormItemRule; + nginxDoc: FormItemRule; + appName: FormItemRule; + containerName: FormItemRule; + disabledFunctions: FormItemRule; + leechExts: FormItemRule; + domainWithPort: FormItemRule; + filePermission: FormItemRule; + phpExtensions: FormItemRule; + supervisorName: FormItemRule; + domainOrIP: FormItemRule; + authBasicPassword: FormItemRule; + ipv4orV6: FormItemRule; + alias: FormItemRule; + + paramCommon: FormItemRule; + paramComplexity: FormItemRule; + paramPort: FormItemRule; + paramExtUrl: FormItemRule; + paramSimple: FormItemRule; + paramHttp: FormItemRule; + phone: FormItemRule; +} + +export const Rules: CommonRule = { + requiredInput: { + required: true, + message: i18n.global.t('commons.rule.requiredInput'), + trigger: 'blur', + }, + requiredSelect: { + required: true, + message: i18n.global.t('commons.rule.requiredSelect'), + trigger: 'change', + }, + requiredSelectBusiness: { + required: true, + min: 1, + max: 65535, + type: 'number', + message: i18n.global.t('commons.rule.requiredSelect'), + trigger: 'change', + }, + noSpace: { + required: true, + validator: checkNoSpace, + trigger: 'blur', + }, + simpleName: { + required: true, + validator: checkSimpleName, + trigger: 'blur', + }, + simplePassword: { + required: true, + validator: checkSimplePassword, + trigger: 'blur', + }, + dbName: { + required: true, + validator: checkDBName, + trigger: 'blur', + }, + composeName: { + required: true, + validator: checkComposeName, + trigger: 'blur', + }, + imageName: { + required: true, + validator: checkImageName, + trigger: 'blur', + }, + volumeName: { + required: true, + validator: checkVolumeName, + trigger: 'blur', + }, + name: { + required: true, + validator: checkName, + trigger: 'blur', + }, + userName: { + required: true, + validator: checkUserName, + trigger: 'blur', + }, + linuxName: { + required: true, + validator: checkLinuxName, + trigger: 'blur', + }, + databaseName: { + required: true, + validator: checkDatabaseName, + trigger: 'blur', + }, + password: { + validator: complexityPassword, + trigger: 'blur', + }, + email: { + required: true, + type: 'email', + message: i18n.global.t('commons.rule.email'), + trigger: 'blur', + }, + number: { + required: true, + trigger: 'blur', + min: 0, + type: 'number', + message: i18n.global.t('commons.rule.number'), + }, + integerNumber: { + required: true, + validator: checkIntegerNumber, + trigger: 'blur', + }, + integerNumberWith0: { + required: true, + validator: checkIntegerNumberWith0, + trigger: 'blur', + }, + floatNumber: { + required: true, + validator: checkFloatNumber, + trigger: 'blur', + min: 0, + message: i18n.global.t('commons.rule.number'), + }, + ip: { + validator: checkIp, + required: true, + trigger: 'blur', + }, + ipV6: { + validator: checkIpV6, + required: true, + trigger: 'blur', + }, + ipV4V6OrDomain: { + validator: checkIpV4V6OrDomain, + required: true, + trigger: 'blur', + }, + host: { + validator: checkHost, + required: true, + trigger: 'blur', + }, + illegal: { + validator: checkIllegal, + required: true, + trigger: 'blur', + }, + port: { + required: true, + trigger: 'blur', + min: 1, + max: 65535, + type: 'number', + message: i18n.global.t('commons.rule.port'), + }, + domain: { + required: true, + validator: checkDomain, + trigger: 'blur', + }, + paramCommon: { + required: true, + validator: checkParamCommon, + trigger: 'blur', + }, + paramComplexity: { + required: true, + validator: checkParamComplexity, + trigger: 'blur', + }, + paramPort: { + required: true, + trigger: 'blur', + validator: checkPort, + }, + paramExtUrl: { + required: true, + validator: checkParamUrlAndPort, + trigger: 'blur', + }, + nginxDoc: { + required: true, + validator: checkDoc, + trigger: 'blur', + }, + appName: { + required: true, + trigger: 'blur', + validator: checkAppName, + }, + containerName: { + required: false, + trigger: 'blur', + validator: checkContainerName, + }, + disabledFunctions: { + required: true, + trigger: 'blur', + validator: checkDisableFunctions, + }, + leechExts: { + required: true, + trigger: 'blur', + validator: checkLeechExts, + }, + supervisorName: { + required: true, + trigger: 'blur', + validator: checkSupervisorName, + }, + paramSimple: { + required: true, + trigger: 'blur', + validator: checkParamSimple, + }, + domainWithPort: { + required: true, + validator: checkDomainWithPort, + trigger: 'blur', + }, + filePermission: { + required: true, + validator: checkFilePermission, + trigger: 'blur', + }, + phpExtensions: { + required: true, + validator: checkPHPExtensions, + trigger: 'blur', + }, + paramHttp: { + required: true, + validator: checkHttpOrHttps, + trigger: 'blur', + }, + ipv4: { + validator: checkIpv4, + trigger: 'blur', + }, + domainOrIP: { + validator: checkDomainOrIP, + trigger: 'blur', + }, + phone: { + validator: checkPhone, + trigger: 'blur', + }, + authBasicPassword: { + validator: checkAuthBasicPassword, + trigger: 'blur', + }, + ipv4orV6: { + validator: checkIpv4orV6, + trigger: 'blur', + }, + alias: { + required: true, + validator: checkAlias, + trigger: 'blur', + }, +}; diff --git a/frontend/src/global/mimetype.ts b/frontend/src/global/mimetype.ts new file mode 100644 index 0000000..2aecfbb --- /dev/null +++ b/frontend/src/global/mimetype.ts @@ -0,0 +1,481 @@ +import { CompressType } from '@/enums/files'; +import i18n from '@/lang'; + +export const Mimetypes = new Map([ + ['application/zip', CompressType.Zip], + ['application/x-zip', CompressType.Zip], + ['application/x-zip-compressed', CompressType.Zip], + ['application/x-tar', CompressType.Tar], + ['application/x-bzip2', CompressType.Bz2], + ['application/gzip', CompressType.TarGz], + ['application/x-gzip', CompressType.TarGz], + ['application/x-gunzip', CompressType.TarGz], + ['application/gzipped', CompressType.TarGz], + ['application/gzip-compressed', CompressType.TarGz], + ['application/x-gzip-compressed', CompressType.TarGz], + ['gzip/document', CompressType.TarGz], + ['application/x-xz', CompressType.Xz], + ['application/octet-stream', CompressType.Tar], + ['application/x-rar-compressed', CompressType.Rar], + ['application/vnd.rar', CompressType.Rar], + ['application/rar', CompressType.Rar], + ['application/x-7z-compressed', CompressType['7z']], +]); + +export const Languages = [ + { + label: 'plaintext', + value: ['txt'], + }, + { + label: 'json', + value: ['json'], + }, + { + label: 'vue', + value: ['vue'], + }, + { + label: 'typescript', + value: ['ts'], + }, + { + label: 'lua', + value: ['lua'], + }, + { + label: 'markdown', + value: ['md'], + }, + { + label: 'yaml', + value: ['yml', 'yaml'], + }, + { + label: 'xml', + value: ['xml'], + }, + { + label: 'php', + value: ['php'], + }, + { + label: 'sql', + value: ['sql'], + }, + { + label: 'go', + value: ['go'], + }, + { + label: 'html', + value: ['html'], + }, + { + label: 'javascript', + value: ['js'], + }, + { + label: 'java', + value: ['java'], + }, + { + label: 'kotlin', + value: ['kt'], + }, + { + label: 'python', + value: ['py'], + }, + { + label: 'redis', + value: ['redis'], + }, + { + label: 'shell', + value: ['sh'], + }, + { + label: 'css', + value: ['css'], + }, + { + label: 'ini', + value: ['ini'], + }, +]; + +export const Rewrites = [ + 'default', + 'wordpress', + 'wp2', + 'typecho', + 'typecho2', + 'thinkphp', + 'yii2', + 'laravel5', + 'discuz', + 'discuzx', + 'discuzx2', + 'discuzx3', + 'EduSoho', + 'EmpireCMS', + 'ShopWind', + 'crmeb', + 'dabr', + 'dbshop', + 'dedecms', + 'drupal', + 'ecshop', + 'emlog', + 'maccms', + 'mvc', + 'niushop', + 'phpcms', + 'sablog', + 'seacms', + 'shopex', + 'zblog', +]; + +export const Units = [ + { label: i18n.global.t('commons.units.second'), value: 's' }, + { label: i18n.global.t('commons.units.minute'), value: 'm' }, + { label: i18n.global.t('commons.units.hour'), value: 'h' }, + { label: i18n.global.t('commons.units.day'), value: 'd' }, + { label: i18n.global.t('commons.units.week'), value: 'w' }, + { label: i18n.global.t('commons.units.month'), value: 'M' }, + { label: i18n.global.t('commons.units.year'), value: 'y' }, +]; + +export const sizeUnits = [ + { label: 'B', value: 'b' }, + { label: 'KB', value: 'k' }, + { label: 'MB', value: 'm' }, + { label: 'GB', value: 'g' }, +]; + +export const AcmeAccountTypes = [ + { label: "Let's Encrypt", value: 'letsencrypt' }, + { label: 'ZeroSSL', value: 'zerossl' }, + { label: 'Buypass', value: 'buypass' }, + { label: 'Google Cloud', value: 'google' }, + { label: i18n.global.t('ssl.customAcme'), value: 'custom' }, +]; + +export const KeyTypes = [ + { label: 'EC 256', value: 'P256' }, + { label: 'EC 384', value: 'P384' }, + { label: 'RSA 2048', value: '2048' }, + { label: 'RSA 3072', value: '3072' }, + { label: 'RSA 4096', value: '4096' }, +]; + +export const DNSTypes = [ + { + label: i18n.global.t('website.aliyun'), + value: 'AliYun', + }, + { + label: i18n.global.t('website.aliEsa'), + value: 'AliESA', + }, + { + label: i18n.global.t('website.awsRoute53'), + value: 'AWSRoute53', + }, + { + label: i18n.global.t('website.tencentCloud'), + value: 'TencentCloud', + }, + { + label: i18n.global.t('website.huaweicloud'), + value: 'HuaweiCloud', + }, + { + label: 'GoDaddy', + value: 'Godaddy', + }, + { + label: 'Cloudflare', + value: 'CloudFlare', + }, + { + label: 'Vercel', + value: 'Vercel', + }, + { + label: 'CloudDNS', + value: 'CloudDns', + }, + { + label: 'NameSilo', + value: 'NameSilo', + }, + { + label: 'NameCheap', + value: 'NameCheap', + }, + { + label: 'Name.com', + value: 'NameCom', + }, + { + label: 'Dynu', + value: 'Dynu', + }, + { + label: 'reg.ru', + value: 'RegRu', + }, + { + label: 'FreeMyIP', + value: 'FreeMyIP', + }, + { + label: i18n.global.t('ssl.baiduCloud'), + value: 'BaiduCloud', + }, + { + label: i18n.global.t('website.rainyun'), + value: 'RainYun', + }, + { + label: i18n.global.t('website.westCN'), + value: 'WestCN', + }, + { + label: 'ClouDNS', + value: 'ClouDNS', + }, + { + label: 'Spaceship', + value: 'Spaceship', + }, + { + label: 'OVH', + value: 'Ovh', + }, + { + label: 'Acme DNS', + value: 'AcmeDNS', + }, + { + label: i18n.global.t('website.volcengine'), + value: 'Volcengine', + }, + { + label: 'PorkBun', + value: 'PorkBun', + }, + { + label: 'DNSPod (' + i18n.global.t('ssl.deprecated') + ')', + value: 'DnsPod', + }, +]; + +export const Fields = [ + { + label: 'URL', + value: 'URL', + }, + { + label: 'IP', + value: 'IP', + }, + { + label: 'Header', + value: 'Header', + }, + { + label: 'Host', + value: 'Host', + }, + { + label: i18n.global.t('xpack.waf.method'), + value: 'Method', + }, +]; + +export const Patterns = [ + { + label: i18n.global.t('xpack.waf.contain'), + value: 'contain', + hidden: ['Method'], + }, + { + label: i18n.global.t('xpack.waf.notContain'), + value: 'notContain', + hidden: ['Method'], + }, + { + label: i18n.global.t('xpack.waf.equal'), + value: 'eq', + }, + { + label: i18n.global.t('xpack.waf.notEqual'), + value: 'notEq', + }, + { + label: i18n.global.t('xpack.waf.regex'), + value: 'regex', + hidden: ['Method'], + }, + { + label: i18n.global.t('xpack.waf.belongToIpGroup'), + value: 'belongToIpGroup', + }, + { + label: i18n.global.t('xpack.waf.notBelongToIpGroup'), + value: 'notBelongToIpGroup', + }, +]; + +export const HttpCodes = [ + { + label: i18n.global.t('xpack.waf.badReq'), + value: 400, + }, + { + label: i18n.global.t('xpack.waf.forbidden'), + value: 403, + }, + { + label: i18n.global.t('xpack.waf.notFound'), + value: 404, + }, + { + label: i18n.global.t('xpack.waf.noRes'), + value: 444, + }, + { + label: i18n.global.t('xpack.waf.serverErr'), + value: 500, + }, + { + label: i18n.global.t('xpack.waf.serviceUnavailable'), + value: 503, + }, + { + label: i18n.global.t('xpack.waf.gatewayTimeout'), + value: 504, + }, +]; + +export const HttpMethods = [ + { + label: 'GET', + value: 'GET', + }, + { + label: 'POST', + value: 'POST', + }, + { + label: 'PUT', + value: 'PUT', + }, + { + label: 'DELETE', + value: 'DELETE', + }, + { + label: 'HEAD', + value: 'HEAD', + }, + { + label: 'OPTIONS', + value: 'OPTIONS', + }, + { + label: 'PATCH', + value: 'PATCH', + }, + { + label: 'TRACE', + value: 'TRACE', + }, +]; + +export const Actions = [ + { + label: i18n.global.t('xpack.waf.actionAllow'), + value: 'allow', + }, + { + label: i18n.global.t('xpack.waf.deny'), + value: 'deny', + }, + { + label: i18n.global.t('xpack.waf.captcha'), + value: 'captcha', + }, + { + label: i18n.global.t('xpack.waf.fiveSeconds'), + value: 'five_seconds', + }, +]; + +export const getAlgorithms = (type: string) => { + const baseAlgorithms = [ + { + label: i18n.global.t('commons.table.default'), + value: 'default', + placeHolder: i18n.global.t('website.defaultHelper'), + }, + { + label: i18n.global.t('website.ipHash'), + value: 'ip_hash', + placeHolder: i18n.global.t('website.ipHashHelper'), + }, + { + label: i18n.global.t('website.leastConn'), + value: 'least_conn', + placeHolder: i18n.global.t('website.leastConnHelper'), + }, + ]; + + if (type === 'stream') { + return baseAlgorithms.filter((algo) => algo.value !== 'ip_hash'); + } + + return baseAlgorithms; +}; + +export const getStatusStrategy = () => [ + { + label: i18n.global.t('website.strategyDown'), + value: 'down', + }, + { + label: i18n.global.t('website.strategyBackup'), + value: 'backup', + }, +]; + +export const getWebsiteTypes = () => [ + { + label: i18n.global.t('website.deployment'), + value: 'deployment', + }, + { + label: i18n.global.t('runtime.runtime'), + value: 'runtime', + }, + { + label: i18n.global.t('website.static'), + value: 'static', + }, + { + label: i18n.global.t('website.proxy'), + value: 'proxy', + }, + { + label: i18n.global.t('website.subsite'), + value: 'subsite', + }, + { + label: i18n.global.t('website.stream'), + value: 'stream', + }, +]; diff --git a/frontend/src/global/use-logo.ts b/frontend/src/global/use-logo.ts new file mode 100644 index 0000000..5cf1a83 --- /dev/null +++ b/frontend/src/global/use-logo.ts @@ -0,0 +1,30 @@ +import { GlobalStore } from '@/store'; +import { getXpackSetting } from '@/utils/xpack'; + +export const useLogo = async () => { + const globalStore = GlobalStore(); + const res = await getXpackSetting(); + if (res) { + localStorage.setItem('1p-favicon', res.data.logo); + globalStore.themeConfig.title = res.data.title; + globalStore.themeConfig.logo = res.data.logo; + globalStore.themeConfig.logoWithText = res.data.logoWithText; + globalStore.themeConfig.loginImage = res.data?.loginImage; + globalStore.themeConfig.loginBgType = res.data?.loginBgType; + globalStore.themeConfig.loginBackground = res.data?.loginBackground; + globalStore.themeConfig.loginBtnLinkColor = res.data?.loginBtnLinkColor; + globalStore.themeConfig.favicon = res.data.favicon; + globalStore.watermarkShow = res.data.watermarkShow === 'Enable'; + try { + globalStore.watermark = JSON.parse(res.data.watermark); + } catch { + globalStore.watermark = null; + } + } + + const link = (document.querySelector("link[rel*='icon']") || document.createElement('link')) as HTMLLinkElement; + link.type = 'image/x-icon'; + link.rel = 'shortcut icon'; + link.href = globalStore.themeConfig.favicon ? `/api/v2/images/favicon?t=${Date.now()}` : '/public/favicon.png'; + document.getElementsByTagName('head')[0].appendChild(link); +}; diff --git a/frontend/src/global/use-theme.ts b/frontend/src/global/use-theme.ts new file mode 100644 index 0000000..b4e79ca --- /dev/null +++ b/frontend/src/global/use-theme.ts @@ -0,0 +1,32 @@ +import { GlobalStore } from '@/store'; +import { setPrimaryColor } from '@/utils/theme'; + +export const useTheme = () => { + const switchTheme = () => { + const globalStore = GlobalStore(); + const themeConfig = globalStore.themeConfig; + let itemTheme = themeConfig.theme; + if (itemTheme === 'auto') { + const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches; + itemTheme = prefersDark ? 'dark' : 'light'; + } + document.documentElement.className = itemTheme === 'dark' ? 'dark' : 'light'; + if (globalStore.isMasterProductPro && themeConfig.themeColor) { + try { + const themeColor = JSON.parse(themeConfig.themeColor); + const color = itemTheme === 'dark' ? themeColor.dark : themeColor.light; + + if (color) { + themeConfig.primary = color; + setPrimaryColor(color); + } + } catch (e) { + console.error('Failed to parse themeColor', e); + } + } + }; + + return { + switchTheme, + }; +}; diff --git a/frontend/src/lang/index.ts b/frontend/src/lang/index.ts new file mode 100644 index 0000000..9902a50 --- /dev/null +++ b/frontend/src/lang/index.ts @@ -0,0 +1,94 @@ +import { createI18n } from 'vue-i18n'; + +type LocaleMessage = Record; +type LocaleLoader = () => Promise<{ default: LocaleMessage }>; + +const DEFAULT_LOCALE = 'en'; +const STORAGE_KEY = 'lang'; + +const LOCALE_LOADERS: Record = { + zh: () => import('./modules/zh'), + 'zh-Hant': () => import('./modules/zh-Hant'), + en: () => import('./modules/en'), + 'pt-BR': () => import('./modules/pt-br'), + ja: () => import('./modules/ja'), + ru: () => import('./modules/ru'), + ms: () => import('./modules/ms'), + ko: () => import('./modules/ko'), + tr: () => import('./modules/tr'), + 'es-ES': () => import('./modules/es-es'), +}; + +const getStoredLocale = () => { + if (typeof window === 'undefined') return DEFAULT_LOCALE; + return localStorage.getItem(STORAGE_KEY) || DEFAULT_LOCALE; +}; + +const initialLocale = getStoredLocale(); + +const loadedLocales = new Set(); + +export const loadLocaleMessages = async (locale: string) => { + const targetLocale = LOCALE_LOADERS[locale] ? locale : DEFAULT_LOCALE; + if (loadedLocales.has(targetLocale)) { + return targetLocale; + } + const loader = LOCALE_LOADERS[targetLocale]; + if (!loader) { + return targetLocale; + } + const messagesModule = await loader(); + const messages = messagesModule.default || {}; + if (!i18n) { + return targetLocale; + } + i18n.global.setLocaleMessage(targetLocale, messages); + loadedLocales.add(targetLocale); + return targetLocale; +}; + +const getInitialMessages = async (): Promise> => { + const loader = LOCALE_LOADERS[initialLocale]; + if (!loader) { + return { [initialLocale]: {} }; + } + try { + const messagesModule = await loader(); + const messages = messagesModule.default || {}; + loadedLocales.add(initialLocale); + return { [initialLocale]: messages }; + } catch { + return { [initialLocale]: {} }; + } +}; + +const initialMessages = await getInitialMessages(); + +const i18n = createI18n({ + legacy: false, + missingWarn: false, + fallbackWarn: false, + locale: initialLocale, + fallbackLocale: DEFAULT_LOCALE, + globalInjection: true, + messages: initialMessages, + warnHtmlMessage: false, +}); + +export const ensureFallbackLocale = async () => { + const fallback = i18n.global.fallbackLocale.value || DEFAULT_LOCALE; + if (typeof fallback === 'string') { + await loadLocaleMessages(fallback); + } +}; + +export const setActiveLocale = async (locale: string) => { + const loaded = await loadLocaleMessages(locale); + i18n.global.locale.value = loaded; + if (typeof window !== 'undefined') { + localStorage.setItem(STORAGE_KEY, loaded); + } + return loaded; +}; + +export default i18n; diff --git a/frontend/src/lang/modules/en.ts b/frontend/src/lang/modules/en.ts new file mode 100644 index 0000000..a8f9509 --- /dev/null +++ b/frontend/src/lang/modules/en.ts @@ -0,0 +1,4173 @@ +import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/en'; + +const message = { + commons: { + true: 'true', + false: 'false', + colon: ': ', + example: 'For example, ', + fit2cloud: 'FIT2CLOUD', + lingxia: 'Lingxia', + button: { + run: 'Run', + prev: 'Previous', + next: 'Next', + create: 'Create ', + add: 'Add ', + save: 'Save ', + set: 'Set', + sync: 'Sync ', + delete: 'Delete', + edit: 'Edit ', + enable: 'Enable', + disable: 'Disable', + confirm: 'Confirm', + cancel: 'Cancel', + reset: 'Reset', + setDefault: 'Restore Default', + restart: 'Restart', + conn: 'Connect', + disconn: 'Disconnect', + clean: 'Clean', + login: 'Login', + close: 'Close', + stop: 'Stop', + start: 'Start', + view: 'View', + watch: 'Watch', + handle: 'Trigger', + clone: 'Clone', + expand: 'Expand', + collapse: 'Collapse', + log: 'View logs', + back: 'Back', + backup: 'Backup', + recover: 'Recover', + retry: 'Retry', + upload: 'Upload', + download: 'Download', + init: 'Initialize', + verify: 'Verify', + saveAndEnable: 'Save and enable', + import: 'Import', + export: 'Export', + power: 'Authorization', + search: 'Search', + refresh: 'Refresh', + get: 'Get', + upgrade: 'Upgrade', + update: 'Update', + updateNow: 'Update Now', + ignore: 'Ignore upgrade', + copy: 'Copy', + random: 'Random', + install: 'Install', + uninstall: 'Uninstall', + fullscreen: 'Enter fullscreen', + quitFullscreen: 'Exit fullscreen', + showAll: 'Show All', + hideSome: 'Hide Some', + agree: 'Agree', + notAgree: 'Not Agree', + preview: 'Preview', + open: 'Open', + notSave: 'Not Save', + createNewFolder: 'Create new folder', + createNewFile: 'Create new file', + helpDoc: 'Help Document', + bind: 'Bind', + unbind: 'Unbind', + cover: 'cover', + skip: 'skip', + fix: 'Fix', + down: 'Stop', + up: 'Start', + sure: 'Confirm', + show: 'Show', + hide: 'Hide', + visit: 'Visit', + migrate: 'Migrate', + }, + operate: { + start: 'Start', + stop: 'Stop', + restart: 'Restart', + reload: 'Reload', + rebuild: 'Rebuild', + sync: 'Sync', + up: 'Up', + down: 'Down', + delete: 'Delete', + }, + search: { + timeStart: 'Time start', + timeEnd: 'Time end', + timeRange: 'To', + dateStart: 'Date start', + dateEnd: 'Date end', + date: 'Date', + }, + table: { + all: 'All', + total: 'Total {0}', + name: 'Name', + type: 'Type', + status: 'Status', + statusSuccess: 'Success', + statusFailed: 'Failed', + statusWaiting: 'Waiting...', + records: 'Records', + group: 'Group', + default: 'Default', + createdAt: 'Creation time', + publishedAt: 'Publish time', + date: 'Date', + updatedAt: 'Update time', + operate: 'Operations', + message: 'Message', + description: 'Description', + interval: 'Interval', + user: 'Owner', + title: 'Title', + port: 'Port', + forward: 'Forward', + protocol: 'Protocol', + tableSetting: 'Table setting', + refreshRate: 'Refresh rate', + noRefresh: 'No refresh', + selectColumn: 'Select column', + local: 'local', + serialNumber: 'Serial number', + manageGroup: 'Manage Groups', + backToList: 'Back to List', + keepEdit: 'Continue Editing', + }, + loadingText: { + Upgrading: 'System upgrade, please wait...', + Restarting: 'System restart, please wait...', + Recovering: 'Recovering from snapshot, please wait...', + Rollbacking: 'Rollbacking from snapshot, please wait...', + }, + msg: { + noneData: 'No data available', + delete: `This operation delete can't be undone. Do you want to continue?`, + clean: `This operation clean can't be undone. Do you want to continue?`, + closeDrawerHelper: 'The system may not save the changes you made. Do you want to continue?', + deleteSuccess: 'Deleted successfully', + loginSuccess: 'Logged in successfully', + operationSuccess: 'Done successfully', + copySuccess: 'Copied successfully', + notSupportOperation: `This operation isn't supported`, + requestTimeout: 'The request timed out, please try again later', + infoTitle: 'Hint', + notRecords: 'No execution record is generated for the current task', + sureLogOut: 'Are you sure you want to log out?', + createSuccess: 'Created successfully', + updateSuccess: 'Updated successfully', + uploadSuccess: 'Uploaded successfully', + operateConfirm: 'If you are sure about the operation, please input it manually : ', + inputOrSelect: 'Please select or enter', + copyFailed: 'Failed to copy', + operatorHelper: `The operation "{1}" will be performed on "{0}" and can't be undone. Do you want to continue?`, + notFound: 'Sorry, the page you requested does not exist.', + unSupportType: `Current file type isn't supported.`, + unSupportSize: 'The uploaded file exceeds {0}M, please confirm!', + fileExist: `The file already exists in the current folder. Repeat uploading isn't supported.`, + fileNameErr: + 'You can upload only files whose name contains 1 to 256 characters, including English, Chinese, digits, or periods (.-_)', + confirmNoNull: `Make sure the value {0} isn't empty.`, + errPort: 'Incorrect port information, please confirm!', + remove: 'Remove', + backupHelper: 'The current operation will back up {0}. Do you want to proceed?', + recoverHelper: 'Restoring from {0} file. This operation is irreversible. Do you want to continue?', + refreshSuccess: 'Refresh successful', + rootInfoErr: "It's already the root directory", + resetSuccess: 'Reset successful', + creatingInfo: 'Creating, no need for this operation', + installSuccess: 'Install successful', + uninstallSuccess: 'Uninstall successful', + offlineTips: 'Offline version does not support this operation', + errImportFormat: 'Import data or format is abnormal, please check and try again!', + importHelper: + 'When importing conflicting or duplicate data, the imported content will be used as the standard to update the original database data.', + errImport: 'File content is abnormal:', + }, + login: { + username: 'Username', + password: 'Password', + passkey: 'Passkey Login', + welcome: 'Welcome back, please enter your username and password to log in!', + errorAuthInfo: 'The user name or password you entered is incorrect, please re-enter!', + errorMfaInfo: 'Incorrect authentication information, please try again!', + captchaHelper: 'Captcha', + errorCaptcha: 'Captcha code error!', + notSafe: 'Access Denied', + safeEntrance1: 'The secure login has been enabled in the current environment', + safeEntrance2: 'Enter the following command on the SSH terminal to view the panel entry: 1pctl user-info', + errIP1: 'Authorized IP address access is enabled in the current environment', + errDomain1: 'Access domain name binding is enabled in the current environment', + errHelper: 'To reset the binding information, run the following command on the SSH terminal: ', + codeInput: 'Please enter the 6-digit verification code of the MFA validator', + mfaTitle: 'MFA Certification', + mfaCode: 'MFA verification code', + title: 'Linux Server Management Panel', + licenseHelper: '', + errorAgree: 'Click to agree to the Community Software License', + logout: 'Logout', + agreeTitle: 'Agreement', + agreeContent: + 'In order to better protect your legitimate rights and interests, please read and agree to the following agreement « Community License Agreement »', + passkeyFailed: 'Passkey login failed, please try again', + passkeyNotSupported: + 'Current browser or environment does not support passkeys, please confirm you have bound the domain name and enabled the panel SSL, and used a trusted certificate when accessing', + passkeyToPassword: 'Have trouble using a passkey? Use password instead', + }, + rule: { + username: 'Enter a username', + password: 'Enter a password', + rePassword: 'Confirm password is inconsistent with the password.', + requiredInput: 'This field is required.', + requiredSelect: 'Select an item in the list', + illegalChar: 'Injection of characters & ; $ \' ` ( ) " > < | is currently not supported', + illegalInput: `This field mustn't contains illegal characters.`, + commonName: + 'This field must start with non-special characters and must consist of English, Chinese, numbers, ".", "-", and "_" characters with a length of 1-128.', + userName: 'This field must consist of English, Chinese, numbers and "_" characters with a length of 3-30.', + simpleName: `This field mustn't start with underscore character and must consist of English, numbers, and "_" characters with a length of 3-30.`, + simplePassword: `This field mustn't start with underscore character and must consist of English, numbers, and "_" characters with a length of 1-30.`, + dbName: `This field mustn't start with underscore character and must consist of English, numbers, and "_" characters with a length of 1-64.`, + imageName: + 'This field must consist of English, numbers, ":", "@", "/", ".", "-", and "_" characters with a length of 1-256.', + volumeName: + 'This field must consist of English, numbers, ".", "-", and "_" characters with a length of 2-30.', + supervisorName: + 'This field must start with non-special characters and must consist of English, numbers, "-", and "_" characters with a length of 1-128.', + composeName: + 'Supports non-special characters at the beginning, lowercase letters, numbers, - and _, length 1-256', + complexityPassword: + 'This field must consist of English, numbers with a length of 8-30 and contain at least two special characters.', + commonPassword: 'This field length must be more than 6.', + linuxName: `This field length must be between 1 and 128. The field mustn't contain these special characters: "{0}".`, + email: 'This field must be a valid email address.', + number: 'This field must be a number.', + integer: 'This field must be a positive integer.', + ip: 'This field must be a valid IP address.', + host: 'This field must be a valid IP address or domain name.', + hostHelper: 'Support input of IP address or domain name', + port: 'This field must be a valid port number.', + selectHelper: 'Please select the correct {0} file', + domain: 'This field must be like: example.com or example.com:8080.', + databaseName: 'This field must consist of English, numbers, and "_" characters with a length of 1-30.', + ipErr: 'This field must be a valid IP address.', + numberRange: 'This field must be a number between {0} and {1}.', + paramName: + 'This field must consist of English, numbers, ".", "-", and "_" characters with a length of 2-30.', + paramComplexity: `This field mustn't start and end with special characters and must consist of English, numbers, "{0}" characters with a length of 6-128.`, + paramUrlAndPort: 'This field must be in the format of "http(s)://(domain name/ip):(port)".', + nginxDoc: 'This field must consist of English, numbers and "." characters.', + appName: `This field musnt't start and end with "-" and "_" characters and must consist of English, numbers, "-", and "_" characters with a length of 2-30.`, + containerName: 'Supports letters, numbers, -, _ and .; cannot start with - _ or .; length: 2-128', + mirror: 'The mirror acceleration address should start with http(s)://, support English letters (both uppercase and lowercase), numbers, . / and -, and should not contain blank lines.', + disableFunction: 'Only support letters ,underscores,and,', + leechExts: 'Only support letters, numbers and,', + paramSimple: 'Support lowercase letters and numbers, length 1-128', + filePermission: 'File Permission Error', + formatErr: 'Format error, please check and retry', + phpExtension: 'Only supports , _ lowercase English and numbers', + paramHttp: 'Must start with http:// or https://', + phone: 'The format of the phone number is incorrect', + authBasicPassword: 'Supports letters, numbers, and common special characters, length 1-72', + length128Err: 'Length cannot exceed 128 characters', + maxLength: 'Length cannot exceed {0} characters', + alias: 'Supports English, numbers, - and _, length 1-128, and cannot start or end with -_.', + }, + res: { + paramError: 'The request failed, please try again later!', + forbidden: 'The current user has no permission', + serverError: 'Service exception', + notFound: 'The resource does not exist', + commonError: 'The request failed', + }, + service: { + serviceNotStarted: `The {0} service isn't started.`, + }, + status: { + running: 'Running', + done: 'Done', + scanFailed: 'Incomplete', + success: 'Success', + waiting: 'Waiting', + waitForUpgrade: 'Wait for Upgrade', + failed: 'Failed', + stopped: 'Stopped', + error: 'Error', + created: 'Created', + restarting: 'Restarting', + uploading: 'Uploading', + unhealthy: 'Unhealthy', + removing: 'Removing', + paused: 'Paused', + exited: 'Exited', + dead: 'Dead', + installing: 'Installing', + enabled: 'Enabled', + disabled: 'Disabled', + normal: 'Normal', + building: 'Building', + upgrading: 'Upgrading', + pending: 'Pending Edit', + rebuilding: 'Rebuilding', + deny: 'Denied', + accept: 'Accepted', + used: 'Used', + unused: 'Unused', + starting: 'Starting', + recreating: 'Recreating', + creating: 'Creating', + init: 'Waiting for application', + ready: 'normal', + applying: 'Applying', + uninstalling: 'Uninstalling', + lost: 'Lost Contact', + bound: 'Bound', + unbind: 'Unbound', + exceptional: 'Exceptional', + free: 'Free', + enable: 'Enabled', + disable: 'Disabled', + deleted: 'Deleted', + downloading: 'Downloading', + packing: 'Packing', + sending: 'Sending', + healthy: 'Normal', + executing: 'Executing', + installerr: 'Installation failed', + applyerror: 'Apply failed', + systemrestart: 'Interrupted', + starterr: 'Startup failed', + uperr: 'Startup failed', + new: 'New', + conflict: 'Conflict', + duplicate: 'Duplicate', + unexecuted: 'Unexecuted', + }, + units: { + second: ' second | second | seconds', + minute: 'minute | minute | minutes', + hour: 'hour | hour | hours', + day: 'day | day | days', + week: 'week | week | weeks', + month: 'month | month | months', + year: 'year | year | years', + time: 'rqm', + core: 'core | core | cores', + millisecond: 'millisecond | milliseconds', + secondUnit: 's', + minuteUnit: 'min', + hourUnit: 'h', + dayUnit: 'd', + }, + log: { + noLog: 'No logs available', + }, + }, + menu: { + home: 'Overview', + apps: 'App Store', + website: 'Website | Websites', + project: 'Project | Projects', + config: 'Configuration | Configurations', + ssh: 'SSH Settings', + firewall: 'Firewall', + filter: 'Filter', + ssl: 'Certificate | Certificates', + database: 'Database | Databases', + aiTools: 'AI', + mcp: 'MCP', + container: 'Container | Containers', + cronjob: 'Cron Job | Cron Jobs', + system: 'System', + security: 'Security', + files: 'File Browser', + monitor: 'Monitoring', + terminal: 'Terminal | Terminals', + settings: 'Setting | Settings', + toolbox: 'Toolbox', + logs: 'Log | Logs', + runtime: 'Runtime | Runtimes', + processManage: 'Process | Processes', + process: 'Process | Processes', + network: 'Network | Networks', + supervisor: 'Supervisor', + tamper: 'Tamper-proof', + app: 'Application', + msgCenter: 'Task Center', + disk: 'Disk', + }, + home: { + recommend: 'recommend', + dir: 'dir', + alias: 'Alias', + quickDir: 'Quick Dir', + minQuickJump: 'Please set at least one quick jump entry!', + maxQuickJump: 'You can set up to four quick jump entries!', + database: 'Database - All', + restart_1panel: 'Restart panel', + restart_system: 'Restart server', + operationSuccess: 'Operation succeeded, rebooting, please refresh the browser manually later!', + entranceHelper: `Security entrance isn't enabled. You can enable it in "Settings -> Security" to improve system security.`, + appInstalled: 'Applications', + systemInfo: 'System infomation', + hostname: 'Hostname', + platformVersion: 'Operating system', + kernelVersion: 'Kernel', + kernelArch: 'Architecture', + network: 'Network', + io: 'Disk I/O', + ip: 'Local IP', + proxy: 'System proxy', + baseInfo: 'Base info', + totalSend: 'Total sent', + totalRecv: 'Total received', + rwPerSecond: 'I/O operations', + ioDelay: 'I/O latency', + uptime: 'Uptime', + runningTime: 'Up since', + mem: 'System Memory', + swapMem: 'Swap Partition', + + runSmoothly: 'Low load', + runNormal: 'Moderate load', + runSlowly: 'High load', + runJam: 'Heavy load', + + core: 'Physical core', + logicCore: 'Logical core', + corePercent: 'Core Usage', + cpuFrequency: 'CPU Frequency', + cpuDetailedPercent: 'CPU Usage Detail', + cpuUser: 'User', + cpuSystem: 'System', + cpuIdle: 'Idle', + cpuIrq: 'IRQ', + cpuSoftirq: 'Soft IRQ', + cpuSteal: 'Steal', + cpuTop: 'Top 5 Processes by CPU Usage', + memTop: 'Top 5 Processes by Memory Usage', + loadAverage: 'Load average in the last 1 minute | Load average in the last {n} minutes', + load: 'Load', + mount: 'Mount point', + fileSystem: 'File system', + total: 'Total', + used: 'Used', + cache: 'Cache', + free: 'Free', + shard: 'Sharded', + available: 'Available', + percent: 'Utilization', + goInstall: 'Go install', + + networkCard: 'Network card', + disk: 'Disk', + }, + tabs: { + more: 'More', + hide: 'Hide', + closeLeft: 'Close left', + closeRight: 'Close right', + closeCurrent: 'Close current', + closeOther: 'Close other', + closeAll: 'Close All', + }, + header: { + logout: 'Logout', + }, + database: { + manage: 'Manage database', + deleteBackupHelper: 'Delete database backups simultaneously', + delete: 'Delete operation cannot be rolled back, please input "', + deleteHelper: '" to delete this database', + create: 'Create database', + noMysql: 'Database service (MySQL or MariaDB)', + noPostgresql: 'Database service PostgreSQL', + goUpgrade: 'Go to upgrade', + goInstall: 'Go to install', + isDelete: 'Deleted', + permission: 'Change permissions', + format: 'Character Set', + collation: 'Collation', + collationHelper: 'If empty, use the default collation of the {0} character set', + permissionForIP: 'IP', + permissionAll: 'All of them(%)', + localhostHelper: + 'Configuring database permissions as "localhost" for container deployment will prevent external access to the container. Please choose carefully!', + databaseConnInfo: 'View connection info', + rootPassword: 'Root password', + serviceName: 'Service Name', + serviceNameHelper: 'Access between containers in the same network.', + backupList: 'Backup', + loadBackup: 'Import', + localUpload: 'Local Upload', + hostSelect: 'Server Selection', + selectHelper: 'Are you sure you want to import backup file {0}?', + remoteAccess: 'Remote access', + remoteHelper: 'Multiple IP comma-delimited, example: 172.16.10.111, 172.16.10.112', + remoteConnHelper: + 'Remote connection to MySQL as user root may have security risks. Therefore, perform this operation with caution.', + changePassword: 'Change password', + changeConnHelper: 'This operation will modify the current database {0}. Do you want to continue?', + changePasswordHelper: + 'The database has been associated with an application. Changing the password will change the database password of the application at the same time. The change takes effect after the application restarts.', + recoverTimeoutHelper: '-1 means no timeout limit', + + confChange: 'Configuration', + confNotFound: + 'The configuration file could not be found. Please upgrade the application to the latest version in the app store and try again!', + + portHelper: + 'This port is the exposed port of the container. You need to save the modification separately and restart the container!', + + loadFromRemote: 'Sync from server', + userBind: 'Bind user', + pgBindHelper: `This operation is used to create a new user and bind it to the target database. Currently, selecting users already existing in the database isn't supported.`, + pgSuperUser: 'Super User', + loadFromRemoteHelper: + 'This will synchronize the database info on the server to 1Panel. Do you want to continue?', + passwordHelper: 'Cannot obtain, click to modify', + remote: 'Remote', + remoteDB: 'Remote server | Remote servers', + createRemoteDB: 'Bind @.lower:database.remoteDB', + unBindRemoteDB: 'Unbind @.lower:database.remoteDB', + unBindForce: 'Force unbind', + unBindForceHelper: 'Ignore all errors during the unbinding process to ensure the final operation is successful', + unBindRemoteHelper: + 'Unbinding the remote database will only remove the binding relationship and will not directly delete the remote database', + editRemoteDB: 'Edit remote server', + localDB: 'Local database', + address: 'Database address', + version: 'Database version', + userHelper: 'The root user or a database user with root privileges can access the remote database.', + pgUserHelper: 'Database user with superuser privileges.', + ssl: 'Use SSL', + clientKey: 'Client private key', + clientCert: 'Client certificate', + caCert: 'CA certificate', + hasCA: 'Has CA certificate', + skipVerify: 'Ignore certificate validity check', + initialDB: 'Initial Database', + + formatHelper: + 'The current database character set is {0}, the character set inconsistency may cause recovery failure', + dropHelper: 'You can drag and drop the uploaded file here or', + clickHelper: 'click to upload', + supportUpType: + 'Only supports sql, sql.gz, tar.gz, .zip file formats. The imported compressed file must contain only one .sql file or include test.sql', + + currentStatus: 'Current state', + baseParam: 'Basic parameter', + performanceParam: 'Performance parameter', + runTime: 'Startup time', + connections: 'Total connections', + bytesSent: 'Send bytes', + bytesReceived: 'Received bytes', + queryPerSecond: 'Query per second', + txPerSecond: 'Tx per second', + connInfo: 'active/peak connections', + connInfoHelper: 'If the value is too large, increase "max_connections".', + threadCacheHit: 'Thread cache hit', + threadCacheHitHelper: 'If it is too low, increase "thread_cache_size".', + indexHit: 'Index hit', + indexHitHelper: 'If it is too low, increase "key_buffer_size".', + innodbIndexHit: 'Innodb index hit rate', + innodbIndexHitHelper: 'If it is too low, increase "innodb_buffer_pool_size".', + cacheHit: 'Querying the cache hit', + cacheHitHelper: 'If it is too low, increase "query_cache_size".', + tmpTableToDB: 'Temporary table to disk', + tmpTableToDBHelper: 'If it is too large, try increasing "tmp_table_size".', + openTables: 'Open tables', + openTablesHelper: 'The configuration value of "table_open_cache" must be greater than or equal to this value.', + selectFullJoin: 'Select full join', + selectFullJoinHelper: `If the value isn't 0, check whether the index of the data table is correct.`, + selectRangeCheck: 'The number of joins with no index', + selectRangeCheckHelper: `If the value isn't 0, check whether the index of the data table is correct.`, + sortMergePasses: 'Number of sorted merges', + sortMergePassesHelper: 'If the value is too large, increase "sort_buffer_size".', + tableLocksWaited: 'Lock table number', + tableLocksWaitedHelper: 'If the value is too large, consider increasing your database performance.', + + performanceTuning: 'Performance tuning', + optimizationScheme: 'Optimization scheme', + keyBufferSizeHelper: 'Buffer size for index', + queryCacheSizeHelper: 'Query cache. If this function is disabled, set this parameter to 0.', + tmpTableSizeHelper: 'Temporary table cache size', + innodbBufferPoolSizeHelper: 'Innodb buffer size', + innodbLogBufferSizeHelper: 'Innodb log buffer size', + sortBufferSizeHelper: '* connections, buffer size per thread sort', + readBufferSizeHelper: '* connections, read buffer size', + readRndBufferSizeHelper: '* connections, random read buffer size', + joinBufferSizeHelper: '* connections, association table cache size', + threadStackelper: '* connections, stack size per thread', + binlogCacheSizeHelper: '* onnections, binary log cache size (multiples of 4096)', + threadCacheSizeHelper: 'Thread pool size', + tableOpenCacheHelper: 'Table cache', + maxConnectionsHelper: 'Max connections', + restart: 'Restart', + + slowLog: 'Slow logs', + noData: 'No slow logs yet.', + + isOn: 'On', + longQueryTime: 'threshold(s)', + thresholdRangeHelper: 'Please enter the correct threshold (1 - 600).', + + timeout: 'Timeout(s)', + timeoutHelper: 'Idle connection timeout period. 0 indicates that the connection is on continuously.', + maxclients: 'Max clients', + requirepassHelper: + 'Leave this blank to indicate that no password has been set. Changes need to be saved separately and the container restarted!', + databases: 'Number of databases', + maxmemory: 'Maximum memory usage', + maxmemoryHelper: '0 indicates no restriction.', + tcpPort: 'Current listening port.', + uptimeInDays: 'Days in operation.', + connectedClients: 'Number of connected clients.', + usedMemory: 'Current memory usage of Redis.', + usedMemoryRss: 'Memory size requested from the operating system.', + usedMemoryPeak: 'Peak memory consumption of Redis.', + memFragmentationRatio: 'Memory fragmentation ratio.', + totalConnectionsReceived: 'Total number of clients connected since run.', + totalCommandsProcessed: 'The total number of commands executed since the run.', + instantaneousOpsPerSec: 'Number of commands executed by the server per second.', + keyspaceHits: 'The number of times a database key was successfully found.', + keyspaceMisses: 'Number of failed attempts to find the database key.', + hit: 'Find the database key hit ratio.', + latestForkUsec: 'The number of microseconds spent on the last fork() operation.', + redisCliHelper: `"redis-cli" service isn't detected. Enable the service first.`, + redisQuickCmd: 'Redis quick commands', + recoverHelper: 'This will overwrite the data with [{0}]. Do you want to continue?', + submitIt: 'Overwrite the data', + + baseConf: 'Basic', + allConf: 'All', + restartNow: 'Restart now', + restartNowHelper1: + 'You need to restart the system after the configuration changes take effect. If your data needs to be persisted, perform the save operation first.', + restartNowHelper: 'This will take effect only after the system restarts.', + + persistence: 'Persistence', + rdbHelper1: 'second(s), insert', + rdbHelper2: 'pieces of data', + rdbHelper3: 'Meeting any of the conditions will trigger RDB persistence.', + rdbInfo: 'Ensure that the value in the rule list ranges from 1 to 100000', + + containerConn: 'Container connection', + connAddress: 'Address', + containerConnHelper: + 'This connection address is used by applications running on the PHP execution environment/container installation.', + remoteConn: 'External connection', + remoteConnHelper2: + 'This connnection address can be used by applications running on non-container or external applications.', + remoteConnHelper3: + 'The default access address is the host IP. To modify it, go to the "Default Access Address" configuration item in the panel settings page.', + localIP: 'Local IP', + }, + aiTools: { + model: { + model: 'Model', + create: 'Add Model', + create_helper: 'Pull "{0}"', + ollama_doc: 'You can visit the Ollama official website to search and find more models.', + container_conn_helper: 'Use this address for inter-container access or connection', + ollama_sync: 'Syncing Ollama model found the following models do not exist, do you want to delete them?', + from_remote: 'This model was not downloaded via 1Panel, no related pull logs.', + no_logs: 'The pull logs for this model have been deleted and cannot be viewed.', + }, + proxy: { + proxy: 'AI Proxy Enhancement', + proxyHelper1: 'Bind domain and enable HTTPS for enhanced transmission security', + proxyHelper2: 'Limit IP access to prevent exposure on the public internet', + proxyHelper3: 'Enable streaming', + proxyHelper4: 'Once created, you can view and manage it in the website list', + proxyHelper5: + 'After enabling, you can disable external access to the port in the App Store - Installed - Ollama - Parameters to improve security.', + proxyHelper6: 'To disable proxy configuration, you can delete it from the website list.', + whiteListHelper: 'Restrict access to only IPs in the whitelist', + }, + gpu: { + gpu: 'GPU Monitoring', + gpuHelper: 'The system did not detect NVIDIA-SMI or XPU-SMI commands. Please check and try again!', + process: 'Process Information', + type: 'Type', + typeG: 'Graphics', + typeC: 'Compute', + typeCG: 'Compute+Graphics', + processName: 'Process Name', + shr: 'Shared Memory', + temperatureHelper: 'High GPU temperature may cause GPU frequency reduction', + gpuUtil: 'GPU Utilization', + temperature: 'Temperature', + performanceState: 'Performance State', + powerUsage: 'Power Consumption', + memoryUsage: 'Memory Utilization', + fanSpeed: 'Fan Speed', + power: 'Power', + powerCurrent: 'Current Power', + powerLimit: 'Power Limit', + memory: 'Memory', + memoryUsed: 'Memory Used', + memoryTotal: 'Total Memory', + percent: 'Utilization', + + base: 'Basic Information', + driverVersion: 'Driver Version', + cudaVersion: 'CUDA Version', + processMemoryUsage: 'Memory Usage', + performanceStateHelper: 'From P0 (maximum performance) to P12 (minimum performance)', + busID: 'Bus Address', + persistenceMode: 'Persistence Mode', + enabled: 'Enabled', + disabled: 'Disabled', + persistenceModeHelper: + 'Persistence mode responds to tasks more quickly, but standby power consumption will increase accordingly', + displayActive: 'GPU Initialization', + displayActiveT: 'Yes', + displayActiveF: 'No', + ecc: 'Error Checking and Correcting Technology', + computeMode: 'Compute Mode', + default: 'Default', + exclusiveProcess: 'Exclusive Process', + exclusiveThread: 'Exclusive Thread', + prohibited: 'Prohibited', + defaultHelper: 'Default: Processes can execute concurrently', + exclusiveProcessHelper: + 'Exclusive Process: Only one CUDA context can use the GPU, but it can be shared by multiple threads', + exclusiveThreadHelper: 'Exclusive Thread: Only one thread in a CUDA context can use the GPU', + prohibitedHelper: 'Prohibited: Concurrent process execution is not allowed', + migModeHelper: 'Used to create MIG instances, implementing physical GPU isolation at the user layer.', + migModeNA: 'Not Supported', + current: 'Real-time Monitoring', + history: 'Historical Records', + notSupport: 'The current version or driver does not support displaying this parameter.', + }, + mcp: { + server: 'MCP Server', + create: 'Add MCP Server', + edit: 'Edit MCP Server', + baseUrl: 'External Access Path', + baseUrlHelper: 'For example: http://192.168.1.2:8000', + ssePath: 'SSE Path', + ssePathHelper: 'For example: /sse, note not to duplicate with other servers', + environment: 'Environment Variables', + envKey: 'Variable Name', + envValue: 'Variable Value', + externalUrl: 'External Connection Address', + operatorHelper: 'Will perform {1} operation on {0}, continue?', + domain: 'Default Access Address', + domainHelper: 'For example: 192.168.1.1 or example.com', + bindDomain: 'Bind Website', + commandPlaceHolder: 'Currently only supports npx and binary startup commands', + importMcpJson: 'Import MCP Server Configuration', + importMcpJsonError: 'mcpServers structure is incorrect', + bindDomainHelper: + 'After binding the website, it will modify the access address of all installed MCP Servers and close external access to the ports', + outputTransport: 'Output Type', + streamableHttpPath: 'Streaming Path', + streamableHttpPathHelper: 'For example: /mcp, note that it should not overlap with other Servers', + npxHelper: 'Suitable for mcp started with npx or binary', + uvxHelper: 'Suitable for mcp started with uvx', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: 'Model Directory', + commandHelper: + 'If external access is needed, set the port in the command to be the same as the application port', + imageAlert: + 'Due to the large image size, it is recommended to manually download the image to the server before installation', + modelSpeedup: 'Enable model acceleration', + modelType: 'Model type', + }, + }, + container: { + create: 'Create', + createByCommand: 'Create by command', + commandInput: 'Command input', + commandRule: 'Please enter the correct docker run container creation command!', + commandHelper: 'This command will be executed on the server to create the container. Do you want to continue?', + edit: 'Edit container', + updateHelper1: 'Detected that this container comes from the app store. Please note the following two points:', + updateHelper2: + '1. The current modifications will not be synchronized to the installed applications in the app store.', + updateHelper3: + '2. If you modify the application on the installed page, the currently edited content will become invalid.', + updateHelper4: + 'Editing the container requires rebuilding, and any non-persistent data will be lost. Do you want to continue?', + containerList: 'Container list', + operatorHelper: '{0} will be performed on the following container, Do you want to continue?', + operatorAppHelper: + 'The "{0}" operation will be performed on the following container(s) and may affect the running services. Do you want to continue?', + containerDeleteHelper: + "Detected that the container is from the App Store. Deleting the container will not completely remove it from 1Panel. To delete it completely, please go to the App Store -> 'Installed' or 'Runtime Environment' menus. Continue?", + start: 'Start', + stop: 'Stop', + restart: 'Restart', + kill: 'Kill', + pause: 'Pause', + unpause: 'Resume', + rename: 'Rename', + remove: 'Remove', + removeAll: 'Remove all', + containerPrune: 'Prune', + containerPruneHelper1: 'This will delete all containers that are in stopped state.', + containerPruneHelper2: + 'If the containers are from the app store, you need to go to "App Store -> Installed" and click the "Rebuild" button to reinstall them after performing the cleanup.', + containerPruneHelper3: `This operation can't be undone. Do you want to continue?`, + imagePrune: 'Prune', + imagePruneSome: 'Clean unlabeled', + imagePruneSomeEmpty: 'No images with the "none" tag can be cleaned.', + imagePruneSomeHelper: 'Clean the images with the tag "none" that are not used by any containers.', + imagePruneAll: 'Clean unused', + imagePruneAllEmpty: 'No unused images can be cleaned.', + imagePruneAllHelper: 'Clean the images that are not used by any containers.', + networkPrune: 'Prune', + networkPruneHelper: 'This will remove all unused networks. Do you want to continue?', + volumePrune: 'Prune', + volumePruneHelper: 'This will remove all unused local volumes. Do you want to continue?', + cleanSuccess: 'The operation is successful, the number of this cleanup: {0}!', + cleanSuccessWithSpace: + 'The operation is successful. The number of disks cleaned this time is {0}. The disk space freed is {1}!', + unExposedPort: 'The current port mapping address is 127.0.0.1, which cannot enable external access.', + upTime: 'Uptime', + fetch: 'Fetch', + lines: 'Lines', + linesHelper: 'Please enter the correct number of logs to retrieve!', + lastDay: 'Last day', + last4Hour: 'Last 4 hours', + lastHour: 'Last hour', + last10Min: 'Last 10 minutes', + cleanLog: 'Clean log', + downLogHelper1: 'This will download all logs from container {0}. Do you want to continue?', + downLogHelper2: 'This will download the recent {0} logs from container {0}. Do you want to continue?', + cleanLogHelper: `This will require restarting the container and can't be undone. Do you want to continue?`, + newName: 'New name', + workingDir: 'Working Dir', + source: 'Resource usage', + cpuUsage: 'CPU usage', + cpuTotal: 'CPU total', + core: 'Core', + memUsage: 'Memory usage', + memTotal: 'Memory limit', + memCache: 'Memory cache', + loadSize: 'Get Container Size', + ip: 'IP address', + cpuShare: 'CPU shares', + cpuShareHelper: + 'Container engine uses a base value of 1024 for CPU shares. You can increase it to give the container more CPU time.', + inputIpv4: 'Example: 192.168.1.1', + inputIpv6: 'Example: 2001:0db8:85a3:0000:0000:8a2e:0370:7334', + + diskUsage: 'Disk Usage', + localVolume: 'Local Storage Volume', + buildCache: 'Build Cache', + usage: 'Used: {0}, Releasable: {1}', + clean: 'Release', + imageClean: 'Clean up images will delete all unused images. This operation cannot be rolled back. Continue?', + containerClean: + 'Clean up containers will delete all stopped containers (including stopped apps from App Store). This operation cannot be rolled back. Continue?', + sizeRw: 'Layer Size', + sizeRwHelper: 'Size of the writable layer unique to the container', + sizeRootFs: 'Virtual Size', + sizeRootFsHelper: 'Total size of all image layers the container depends on + container layer', + + containerFromAppHelper: + 'Detected that this container originates from the app store. App operations may cause current edits to be invalidated.', + containerFromAppHelper1: + 'Click the [Param] button in the installed applications list to enter the editing page and modify the container name.', + command: 'Command', + console: 'Container interaction', + tty: 'Allocate a pseudo-TTY (-t)', + openStdin: 'Keep STDIN open even if not attached (-i)', + custom: 'Custom', + emptyUser: 'When empty, you will log in as default', + privileged: 'Privileged', + privilegedHelper: + 'Allow the container to perform certain privileged operations on the host, which may increase container risks. Use with caution!', + + upgradeHelper: 'Repository Name/Image Name: Image Version', + upgradeWarning2: + 'The upgrade operation requires rebuilding the container, any unpersisted data will be lost. Do you wish to continue?', + oldImage: 'Current image', + sameImageContainer: 'Same-image containers', + sameImageHelper: 'Containers using the same image can be batch upgraded after selection', + targetImage: 'Target image', + imageLoadErr: 'No image name detected for the container', + appHelper: 'The container comes from the app store, and upgrading may make the service unavailable.', + + resource: 'Resource', + input: 'Manually input', + forcePull: 'Always pull image ', + forcePullHelper: 'This will ignore existing images on the server and pull the latest image from the registry.', + server: 'Host', + serverExample: '80, 80-88, ip:80 or ip:80-88', + containerExample: '80 or 80-88', + exposePort: 'Expose port', + exposeAll: 'Expose all', + cmdHelper: 'Example: nginx -g "daemon off;"', + entrypointHelper: 'Example: docker-entrypoint.sh', + autoRemove: 'Auto remove', + cpuQuota: 'Number of CPU cores', + memoryLimit: 'Memory', + limitHelper: `If set to 0, it means that there is no limitation. The maximum value is {0}`, + macAddr: 'MAC Address', + mount: 'Mount', + volumeOption: 'Volume', + hostOption: 'Host', + serverPath: 'Server path', + containerDir: 'Container path', + volumeHelper: 'Ensure that the content of the storage volume is correct', + networkEmptyHelper: 'Please confirm the container network selection is correct', + modeRW: 'RW', + modeR: 'R', + sharedLabel: 'Propagation Mode', + private: 'Private', + privateHelper: 'Mount changes in the container and host do not affect each other', + rprivate: 'Recursive Private', + rprivateHelper: 'All mounts in the container are completely isolated from the host', + shared: 'Shared', + sharedHelper: 'Mount changes in the host and container are visible to each other', + rshared: 'Recursive Shared', + rsharedHelper: 'All mount changes in the host and container are visible to each other', + slave: 'Slave', + slaveHelper: 'The container can see host mount changes, but its own changes do not affect the host', + rslave: 'Recursive Slave', + rslaveHelper: 'All mounts in the container can see host changes, but do not affect the host', + mode: 'Mode', + env: 'Environments', + restartPolicy: 'Restart policy', + always: 'always', + unlessStopped: 'unless-stopped', + onFailure: 'on-failure (five times by default)', + no: 'never', + + refreshTime: 'Refresh interval', + cache: 'Cache', + + image: 'Image | Images', + imagePull: 'Pull', + imagePullHelper: + 'Supports selecting multiple images to pull, press Enter after entering each image to continue', + imagePush: 'Push', + imagePushHelper: + 'Detected that this image has multiple tags. Please confirm that the image name used for pushing is: {0}', + imageDelete: 'Image delete', + repoName: 'Container registry', + imageName: 'Image name', + pull: 'Pull', + path: 'Path', + importImage: 'Import', + buildArgs: 'Build Arguments', + imageBuild: 'Build', + pathSelect: 'Path', + label: 'Label', + imageTag: 'Image tag', + imageTagHelper: 'Supports setting multiple image tags, press Enter after entering each tag to continue', + push: 'Push', + fileName: 'Filename', + export: 'Export', + exportImage: 'Image export', + size: 'Size', + tag: 'Tags', + tagHelper: 'One per line. For example,\nkey1=value1\nkey2=value2', + imageNameHelper: 'Image name and Tag, for example: nginx:latest', + cleanBuildCache: 'Clean build cache', + delBuildCacheHelper: `This will delete all cached artifacts that are generated during builds and can't be undone. Do you want to continue?`, + urlWarning: 'The URL prefix does not need to include http:// or https://. Please modify.', + + network: 'Network | Networks', + networkHelper: + 'This may cause some applications and runtime environments to not work properly. Do you want to continue?', + createNetwork: 'Create', + networkName: 'Name', + driver: 'Driver', + option: 'Option', + attachable: 'Attachable', + parentNetworkCard: 'Parent Network Card', + subnet: 'Subnet', + scope: 'IP scope', + gateway: 'Gateway', + auxAddress: 'Exclude IP', + + volume: 'Volume | Volumes', + volumeDir: 'Volume directory', + nfsEnable: 'Enable NFS storage', + nfsAddress: 'Address', + mountpoint: 'Mountpoint', + mountpointNFSHelper: 'e.g. /nfs, /nfs-share', + options: 'Options', + createVolume: 'Create', + + repo: 'Container registry | Container registries', + createRepo: 'Add', + httpRepoHelper: 'Operating an HTTP-type repository requires restarting the Docker service.', + httpRepo: 'Choosing HTTP protocol requires restarting the Docker service to add it into insecure registries.', + delInsecure: 'Deletion of credit', + delInsecureHelper: + 'This will restart Docker service to remove it from insecure registries. Do you want to continue?', + downloadUrl: 'Server', + imageRepo: 'Image repo', + repoHelper: 'Does it include a mirror repository/organization/project?', + auth: 'Require authentication', + mirrorHelper: + 'If there are multiple mirrors, newlines must be displayed, for example:\nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com', + registrieHelper: + 'If multiple private repositories exist, newlines must be displayed, for example:\n172.16.10.111:8081 \n172.16.10.112:8081', + + compose: 'Compose | Composes', + composeFile: 'Compose File', + fromChangeHelper: 'Switching the source will clean the current edited content. Do you want to continue?', + composePathHelper: 'Configuration file save path: {0}', + composeHelper: + 'The composition created through 1Panel editor or template will be saved in the {0}/docker/compose directory.', + deleteFile: 'Delete file', + deleteComposeHelper: + 'Delete all files related to container compose, including configuration files and persistent files. Please proceed with caution!', + deleteCompose: '" Delete this composition.', + createCompose: 'Create', + composeDirectory: 'Compose directory', + template: 'Template', + composeTemplate: 'Compose template | Compose templates', + createComposeTemplate: 'Create', + content: 'Content', + contentEmpty: 'Compose content cannot be empty, please enter and try again!', + containerNumber: 'Container number', + containerStatus: 'Container status', + exited: 'Exited', + running: 'Running ( {0} / {1} )', + composeDetailHelper: + 'The compose is created external to 1Panel. The start and stop operations are not supported.', + composeOperatorHelper: '{1} operation will be performed on {0}. Do you want to continue?', + composeDownHelper: + 'This will stop and remove all containers and networks under the {0} compose. Do you want to continue?', + composeEnvHelper2: + 'This orchestration was created by the 1Panel App Store. Please modify environment variables in the installed applications.', + + setting: 'Setting | Settings', + goSetting: 'Go to edit', + operatorStatusHelper: 'This will "{0}" Docker service. Do you want to continue?', + dockerStatus: 'Docker Service', + daemonJsonPathHelper: 'Ensure that the configuration path is the same as that specified in docker.service.', + mirrors: 'Registry mirrors', + mirrorsHelper: '', + mirrorsHelper2: 'For details, see the official documents. ', + registries: 'Insecure registries', + ipv6Helper: + 'When enabling IPv6, you need to add an IPv6 container network. Refer to the official documentation for specific configuration steps.', + ipv6CidrHelper: 'IPv6 address pool range for containers', + ipv6TablesHelper: 'Automatic configuration of Docker IPv6 for iptables rules.', + experimentalHelper: + 'Enabling ip6tables requires this configuration to be turned on; otherwise, ip6tables will be ignored', + cutLog: 'Log option', + cutLogHelper1: 'The current configuration will only affect newly created containers.', + cutLogHelper2: 'Existing containers need to be recreated for the configuration to take effect.', + cutLogHelper3: + 'Please note that recreating containers may result in data loss. If your containers contain important data, make sure to backup before performing the rebuilding operation.', + maxSize: 'Max size', + maxFile: 'Max file', + liveHelper: + 'By default, when the Docker daemon terminates, it shuts down running containers. You can configure the daemon so that containers remain running if the daemon becomes unavailable. This functionality is called live restore. The live restore option helps reduce container downtime due to daemon crashes, planned outages, or upgrades.', + liveWithSwarmHelper: 'live-restore daemon configuration is incompatible with swarm mode.', + iptablesDisable: 'Close iptables', + iptablesHelper1: 'Automatic configuration of iptables rules for Docker.', + iptablesHelper2: + 'Disabling iptables will result in the containers being unable to communicate with external networks.', + daemonJsonPath: 'Conf Path', + serviceUnavailable: `Docker service isn't started at present.`, + startIn: ' to start', + sockPath: 'Unix domain socket', + sockPathHelper: 'Communication channel between Docker daemon and the client.', + sockPathHelper1: 'Default path: /var/run/docker-x.sock', + sockPathMsg: + 'Saving the Socket Path setting may result in Docker service being unavailable. Do you want to continue?', + sockPathErr: 'Please select or enter the correct Docker sock file path', + related: 'Related', + includeAppstore: 'Show containers from the app store', + excludeAppstore: 'Hide App Store Container', + + cleanDockerDiskZone: 'Clean up disk space used by Docker', + cleanImagesHelper: '( Clean up all images that are not used by any containers )', + cleanContainersHelper: '( Clean up all stopped containers )', + cleanVolumesHelper: '( Clean up all unused local volumes )', + + makeImage: 'Create image', + newImageName: 'New image name', + commitMessage: 'Commit message', + author: 'Author', + ifPause: 'Pause Container During Creation', + ifMakeImageWithContainer: 'Create New Image from This Container?', + finishTime: 'Last stop time', + }, + cronjob: { + create: 'Create cron job', + edit: 'Edit cron job', + importHelper: + 'Duplicate scheduled tasks will be automatically skipped during import. Tasks will be set to [Disabled] status by default, and set to [Pending Edit] status when data association is abnormal.', + changeStatus: 'Change status', + disableMsg: 'This will stop the scheduled task from automatically executing. Do you want to continue?', + enableMsg: 'This will allow the scheduled task to automatically execute. Do you want to continue?', + taskType: 'Type', + nextTime: 'Next 5 executions', + record: 'Records', + viewRecords: 'View records', + shell: 'Shell', + stop: 'Manual Stop', + stopHelper: 'This operation will force stop the current task execution. Continue?', + log: 'Backup logs', + logHelper: 'Backup system log', + ogHelper1: '1.1Panel System log ', + logHelper2: '2. SSH login log of the server ', + logHelper3: '3. All site logs ', + containerCheckBox: 'In container (no need to enter the container command)', + containerName: 'Container name', + ntp: 'Time synchronization', + ntp_helper: 'You can configure the NTP server on the Quick Setup page of the Toolbox.', + app: 'Backup app', + website: 'Backup website', + rulesHelper: + 'When there are multiple compression exclusion rules, they need to be displayed with line breaks. For example,\n*.log \n*.sql', + lastRecordTime: 'Last execution time', + all: 'All', + failedRecord: 'Failure records', + successRecord: 'Successful records', + database: 'Backup database', + backupArgs: 'Backup Arguments', + backupArgsHelper: + 'Unlisted backup arguments can be manually entered and selected. For example: Enter --no-data and select the first option from the dropdown list.', + singleTransaction: 'Backup InnoDB tables using a single transaction, suitable for large-volume data backups', + quick: 'Read data row by row instead of loading the entire table into memory, suitable for large-volume data and low-memory machine backups', + skipLockTables: 'Backup without locking all tables, suitable for highly concurrent databases', + missBackupAccount: 'The backup account could not be found', + syncDate: 'Synchronization time ', + clean: 'Cache clean', + curl: 'Access URL', + taskName: 'Name', + cronSpec: 'Trigger cycle', + cronSpecDoc: + 'Custom execution cycles only support the [minute hour day month week] format, e.g., 0 0 * * *. For details, please refer to the official documentation.', + cronSpecHelper: 'Enter the correct execution period', + cleanHelper: + 'This operation records all job execution records, backup files, and log files. Do you want to continue?', + backupContent: 'Backup content', + directory: 'Backup directory', + sourceDir: 'Backup directory', + snapshot: 'System snapshot', + allOptionHelper: `The current task plan is to back up all [{0}]. Direct download isn't supported at the moment. You can check the backup list of [{0}] menu.`, + exclusionRules: 'Exclusive rule', + exclusionRulesHelper: + 'Select or enter exclusion rules, press Enter after each set to continue. Exclusion rules will apply to all compression operations in this backup', + default_download_path: 'Default download link', + saveLocal: 'Retain local backups (the same as the number of cloud storage copies)', + url: 'URL Address', + urlHelper: 'Please enter a valid URL address', + targetHelper: 'Backup accounts are maintained in panel settings.', + withImageHelper: 'Backup app store images, but this will increase the snapshot file size.', + ignoreApp: 'Exclude apps', + withImage: 'Backup Application Image', + retainCopies: 'Retain records', + retryTimes: 'Retry Attempts', + timeout: 'Timeout', + ignoreErr: 'Ignore errors', + ignoreErrHelper: 'Ignore errors during backup to ensure all backup tasks complete', + retryTimesHelper: '0 means no retry after failure', + retainCopiesHelper: 'Number of copies to retain for execution records and logs', + retainCopiesHelper1: 'Number of copies to retain for backup files', + retainCopiesUnit: ' copies (View)', + cronSpecRule: 'The execution period format in line {0} is incorrect. Please check and try again!', + cronSpecRule2: 'Execution period format is incorrect, please check and try again!', + perMonthHelper: 'Execute on the {0} day of every month at {1}:{2}', + perWeekHelper: 'Execute every week on {0} at {1}:{2}', + perDayHelper: 'Execute every day at {0}:{1}', + perHourHelper: 'Execute every hour at {0} minutes', + perNDayHelper: 'Execute every {0} days at {1}:{2}', + perNHourHelper: 'Execute every {0} hours at {1}', + perNMinuteHelper: 'Execute every {0} minutes', + perNSecondHelper: 'Execute every {0} seconds', + perMonth: 'Every month', + perWeek: 'Every week', + perHour: 'Every hour', + perNDay: 'Every N day(s)', + perDay: 'Every day', + perNHour: 'Every N hour(s)', + perNMinute: 'Every N minute(s)', + perNSecond: 'Every N second(s)', + day: 'day(s)', + monday: 'Monday', + tuesday: 'Tuesday', + wednesday: 'Wednesday', + thursday: 'Thursday', + friday: 'Friday', + saturday: 'Saturday', + sunday: 'Sunday', + shellContent: 'Script', + executor: 'Executor', + errRecord: 'Incorrect logging', + errHandle: 'Cronjob execution failure', + noRecord: 'Trigger the Cron Job, and you will see the records here.', + cleanData: 'Clean data', + cleanRemoteData: 'Delete remote data', + cleanDataHelper: 'Delete the backup file generated during this task.', + noLogs: 'No task output yet...', + errPath: 'Backup path [{0}] error, cannot download!', + cutWebsiteLog: 'Website log rotation', + cutWebsiteLogHelper: 'The rotated log files will be backed up to the backup directory of 1Panel.', + syncIpGroup: 'Sync WAF IP groups', + + requestExpirationTime: 'Upload request expiration time(Hours)', + unitHours: 'Unit: Hours', + alertTitle: 'Planned Task - {0} 「{1}」 Task Failure Alert', + library: { + script: 'Script', + syncNow: 'Sync Now', + turnOnSync: 'Enable Auto Sync', + turnOnSyncHelper: + 'Enabling auto sync will perform automatic synchronization during early morning hours daily', + turnOffSync: 'Disable Auto Sync', + turnOffSyncHelper: 'Disabling auto sync may cause script synchronization delays, confirm?', + isInteractive: 'Interactive', + interactive: 'Interactive script', + interactiveHelper: 'Requires user input during execution and cannot be used in scheduled tasks.', + library: 'Script Library', + remoteLibrary: 'Remote Script Library', + create: 'Add Script', + edit: 'Edit Script', + groupHelper: + 'Set different groups based on script characteristics, which allows for faster script filtering operations.', + handleHelper: 'Execute script {1} on {0}, continue?', + noSuchApp: 'The {0} service was not detected. Please install it quickly using the script library first!', + syncHelper: 'About to sync system script library. This operation only affects system scripts. Continue?', + }, + }, + monitor: { + globalFilter: 'Global Filter', + enableMonitor: 'Monitoring Status', + storeDays: 'Retention Days', + defaultNetwork: 'Default Network Card', + defaultNetworkHelper: 'Default network card option displayed in monitoring and overview interfaces', + defaultIO: 'Default Disk', + defaultIOHelper: 'Default disk option displayed in monitoring and overview interfaces', + cleanMonitor: 'Clear Monitoring Records', + cleanHelper: 'This operation will clear all monitoring records including GPU. Continue?', + + avgLoad: 'Load average', + loadDetail: 'Load detail', + resourceUsage: 'Utilization', + networkCard: 'Network interface', + read: 'Read', + write: 'Write', + readWriteCount: 'I/O operations', + readWriteTime: 'I/O latency', + today: 'Today', + yesterday: 'Yesterday', + lastNDay: 'Last {0} days', + lastNMonth: 'Last {0} months', + lastHalfYear: 'Last half year', + memory: 'Memory', + percent: 'Percentage', + cache: 'Cache', + disk: 'Disk', + network: 'Network', + up: 'Up', + down: 'Down', + interval: 'Collection Interval', + intervalHelper: 'Please enter an appropriate monitoring collection interval (5 seconds - 12 hours)', + }, + terminal: { + local: 'Local', + defaultConn: 'Default Connection', + defaultConnHelper: + 'This operation will automatically connect to the node terminal after opening the terminal for 【{0}】. Continue?', + withReset: 'Reset Connection Information', + localConnJump: + 'Default connection information is maintained in [Terminal - Settings]. If connection fails, please edit there!', + localHelper: 'The `local` name is used only for system local identification', + connLocalErr: 'Unable to automatically authenticate, please fill in the local server login information.', + testConn: 'Test connection', + saveAndConn: 'Save and connect', + connTestOk: 'Connection information available', + connTestFailed: 'Connection unavailable, please check connection information.', + host: 'Host | Hosts', + createConn: 'New connection', + noHost: 'No host', + groupChange: 'Change group', + expand: 'Expand all', + fold: 'All contract', + batchInput: 'Batch processing', + quickCommand: 'Quick command | Quick commands', + noSuchCommand: 'No quick command data found in the imported CSV file, please check and try again!', + quickCommandHelper: 'You can use the quick commands at the bottom of the "Terminals -> Terminals".', + groupDeleteHelper: + 'After the group is removed, all connections in the group will be migrated to the default group. Do you want to continue?', + command: 'Command', + quickCmd: 'Quick command', + addHost: 'Add', + localhost: 'Localhost', + ip: 'Address', + authMode: 'Authentication', + passwordMode: 'Password', + rememberPassword: 'Remember authentication information', + keyMode: 'PrivateKey', + key: 'Private key', + keyPassword: 'Private key password', + emptyTerminal: 'No terminal is currently connected.', + lineHeight: 'Line Height', + letterSpacing: 'Letter Spacing', + fontSize: 'Font Size', + cursorBlink: 'Cursor Blink', + cursorStyle: 'Cursor Style', + cursorUnderline: 'Underline', + cursorBlock: 'Block', + cursorBar: 'Bar', + scrollback: 'Scrollback', + scrollSensitivity: 'Scroll Sensitivity', + saveHelper: 'Are you sure you want to save the current terminal configuration?', + }, + toolbox: { + common: { + toolboxHelper: 'For some installation and usage issues, please refer to', + }, + swap: { + swap: 'Swap Partition', + swapHelper1: + 'The size of the swap should be 1 to 2 times the physical memory, adjustable based on specific requirements;', + swapHelper2: + 'Before creating a swap file, ensure that the system disk has sufficient available space, as the swap file size will occupy the corresponding disk space;', + swapHelper3: + 'Swap can help alleviate memory pressure, but it is only an alternative. Excessive reliance on swap may lead to a decrease in system performance. It is recommended to prioritize increasing memory or optimizing application memory usage;', + swapHelper4: 'It is advisable to regularly monitor the usage of swap to ensure normal system operation.', + swapDeleteHelper: + 'This operation will remove the Swap partition {0}. For system security reasons, the corresponding file will not be automatically deleted. If deletion is required, please proceed manually!', + saveHelper: 'Please save the current settings first!', + saveSwap: + 'Saving the current configuration will adjust the Swap partition {0} size to {1}. Do you want to continue?', + swapMin: 'The minimum partition size is 40 KB. Please modify and try again!', + swapMax: 'The maximum value for partition size is {0}. Please modify and try again!', + swapOff: 'The minimum partition size is 40 KB. Setting it to 0 will disable the Swap partition.', + }, + device: { + dnsHelper: 'DNS server', + dnsAlert: + 'Attention! Modifying the configuration of /etc/resolv.conf file will restore the file to its default values after system restart.', + dnsHelper1: + 'When there are multiple DNS entries, they should be displayed on new lines. e.g.\n114.114.114.114\n8.8.8.8', + hostsHelper: 'Hostname resolution', + hosts: 'Domain', + hostAlert: 'Hidden commented records, please click the All configuration button to view or set', + toolbox: 'Quick settings', + hostname: 'Hostname', + passwd: 'System password', + passwdHelper: 'Input characters cannot include $ and &', + timeZone: 'System time zone', + localTime: 'Server time', + timeZoneChangeHelper: 'Modifying the system time zone requires restarting the service. Continue?', + timeZoneHelper: `If you don't install "timedatectl" command, you may not change the time zone. Because system uses that command to change time zone.`, + timeZoneCN: 'Beijing', + timeZoneAM: 'Los Angeles', + timeZoneNY: 'New York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'NTP server', + hostnameHelper: `Hostname modification depends on the "hostnamectl" command. If the command isn't installed, the modification may fail.`, + userHelper: `The username depends on the "whoami" command for retrieval. If the command isn't installed, retrieval may fail.`, + passwordHelper: `Password modification depends on the "chpasswd" command. If the command isn't installed, the modification may fail.`, + hostHelper: + 'There is an empty value in the provided content. Please check and try again after modification!', + dnsCheck: 'Test Availability', + dnsOK: 'DNS configuration information is available!', + dnsTestFailed: `DNS configuration information isn't available.`, + }, + fail2ban: { + sshPort: 'Listen to SSH port', + sshPortHelper: 'Current Fail2ban listens to the SSH connection port of the host', + unActive: `The Fail2ban service isn't enabled at present.`, + operation: 'You will perform operation "{0}" on Fail2ban service. Do you want to continue?', + fail2banChange: 'Fail2ban Configuration Modification', + ignoreHelper: 'The IP list in the allowlist will be ignored for blocking. Do you want to continue?', + bannedHelper: 'The IP list in the blocklist will be blocked by the server. Do you want to continue?', + maxRetry: 'Maximum retry attempts', + banTime: 'Ban time', + banTimeHelper: 'Default ban time is 10 minutes, -1 indicates permanent ban', + banTimeRule: 'Please enter a valid ban time or -1', + banAllTime: 'Permanent ban', + findTime: 'Discovery period', + banAction: 'Ban action', + banActionOption: 'Ban specified IP addresses using {0}', + allPorts: ' (All Ports)', + ignoreIP: 'IP allowlist', + bannedIP: 'IP blocklist', + logPath: 'Log path', + logPathHelper: 'Default is /var/log/secure or /var/log/auth.log', + }, + ftp: { + ftp: 'FTP account | FTP accounts', + notStart: 'FTP service is currently not running, please start it first!', + operation: 'This will perform "{0}" operation on FTP service. Do you want to continue?', + noPasswdMsg: 'Can not get the current FTP account password, please set the password and try again! ', + enableHelper: + 'Enabling the selected FTP account will restore its access permissions. Do you want to continue?', + disableHelper: + 'Disabling the selected FTP account will revoke its access permissions. Do you want to continue?', + syncHelper: 'Sync FTP account data between server and database. Do you want to continue?', + dirSystem: + 'This directory is system-reserved. Modification may cause system crash, please modify and try again!', + dirHelper: 'Enabling FTP requires directory permission changes - please choose carefully', + dirMsg: 'Enabling FTP will modify permissions for the entire {0} directory. Continue?', + }, + clam: { + clam: 'Virus scan', + cron: 'Scheduled scan', + cronHelper: 'Professional version supports scheduled scan feature', + specErr: 'Execution schedule format error, please check and retry!', + disableMsg: + 'Stopping scheduled execution will prevent this scan task from running automatically. Do you want to continue?', + enableMsg: + 'Enabling scheduled execution will allow this scan task to run automatically at regular intervals. Do you want to continue?', + showFresh: 'Show signature updater service', + hideFresh: 'Hide signature updater service', + clamHelper: + 'The minimum recommended configuration for ClamAV is: 3 GiB of RAM or more, single-core CPU with 2.0 GHz or higher, and at least 5 GiB of available hard disk space.', + notStart: 'ClamAV service is currently not running, please start it first!', + removeRecord: 'Delete peport files', + noRecords: 'Click the "Trigger" button to start the scan and you will see records here.', + removeInfected: 'Delete virus files', + removeInfectedHelper: + 'Delete virus files detected during the task to ensure server security and normal operation.', + clamCreate: 'Create scan rule', + infectedStrategy: 'Infected strategy', + removeHelper: 'Delete virus files, choose carefully!', + move: 'Move', + moveHelper: 'Move virus files to specified directory', + copyHelper: 'Copy virus files to specified directory', + none: 'Do nothing', + noneHelper: 'Take no action on virus files', + scanDir: 'Scan directory', + infectedDir: 'Infected directory', + scanDate: 'Scan Date', + scanResult: 'Scan logs tail', + tail: 'Lines', + infectedFiles: 'Infected files', + log: 'Details', + clamConf: 'Clam AV daemon', + clamLog: '@:toolbox.clam.clamConf logs', + freshClam: 'FreshClam', + freshClamLog: '@:toolbox.clam.freshClam logs', + alertHelper: 'Professional version supports scheduled scan and SMS alert', + alertTitle: 'Virus scan task 「{0}」 detected infected file alert', + }, + }, + logs: { + core: 'Panel Service', + agent: 'Node Monitoring', + panelLog: 'Panel logs', + operation: 'Operation logs', + login: 'Login logs', + loginIP: 'Login IP', + loginAddress: 'Login address', + loginAgent: 'Login agent', + loginStatus: 'Status', + system: 'System logs', + deleteLogs: 'Clean logs', + resource: 'Resource', + detail: { + dashboard: 'Overview', + ai: 'AI', + groups: 'Group', + hosts: 'Host', + apps: 'App', + websites: 'Website', + containers: 'Container', + files: 'File', + runtimes: 'Runtime', + process: 'Process', + toolbox: 'Toolbox', + backups: 'Backup / Restore', + tampers: 'Tamper', + xsetting: 'Interface Settings', + logs: 'Log', + settings: 'Setting', + cronjobs: 'Cronjob', + databases: 'Database', + waf: 'WAF', + licenses: 'License', + nodes: 'Node', + commands: 'Quick Commands', + }, + websiteLog: 'Website logs', + runLog: 'Run logs', + errLog: 'Error logs', + task: 'Task Log', + taskName: 'Task Name', + taskRunning: 'Running', + }, + file: { + fileDirNum: '{0} directories, {1} files,', + currentDir: 'Directory', + dir: 'Folder', + fileName: 'File name', + search: 'Search', + mode: 'Permissions', + editPermissions: 'Edit @.lower:file.mode', + owner: 'Owner', + file: 'File', + remoteFile: 'Download from remote', + share: 'Share', + sync: 'Data Synchronization', + size: 'Size', + updateTime: 'Modified', + rename: 'Rename', + role: 'Permissions', + info: 'View attributes', + linkFile: 'Soft link', + shareList: 'Share list', + zip: 'Compressed', + group: 'Group', + path: 'Path', + public: 'Others', + setRole: 'Set permissions', + link: 'File link', + rRole: 'Read', + wRole: 'Write', + xRole: 'Executable', + name: 'Name', + compress: 'Compress', + deCompress: 'Decompress', + compressType: 'Compress format', + compressDst: 'Compress path', + replace: 'Overwrite existing files', + compressSuccess: 'Compressed successfully', + deCompressSuccess: 'Decompress succeeded', + deCompressDst: 'Decompress path', + linkType: 'Link type', + softLink: 'Soft link', + hardLink: 'Hard link', + linkPath: 'Link path', + selectFile: 'Select file', + downloadUrl: 'Remote URL', + downloadStart: 'Download started', + moveSuccess: 'Successfully moved', + copySuccess: 'Successfully copied', + pasteMsg: 'Please click the [Paste] button at the top right of the target directory', + move: 'Move', + calculate: 'Calculate', + remark: 'Remark', + setRemark: 'Set remark', + remarkPrompt: 'Enter a remark', + remarkPlaceholder: 'Remark', + remarkToggle: 'Remarks', + remarkToggleTip: 'Load file remarks', + canNotDeCompress: 'Cannot decompress this file', + uploadSuccess: 'Successfully upload', + downloadProcess: 'Download progress', + downloading: 'Downloading...', + infoDetail: 'File properties', + root: 'Root directory', + list: 'File list', + sub: 'Include subdirectories', + downloadSuccess: 'Successfully downloaded', + theme: 'Theme', + language: 'Language', + eol: 'End of line', + copyDir: 'Copy', + paste: 'Paste', + changeOwner: 'Modify user and user group', + containSub: 'Apply the permission change recursively', + ownerHelper: + 'The default user of the PHP operating environment: the user group is 1000:1000, it is normal that the users inside and outside the container show inconsistencies', + searchHelper: 'Support wildcards such as *', + uploadFailed: '[{0}] File upload file', + fileUploadStart: 'Uploading [{0}]....', + currentSelect: 'Current select: ', + unsupportedType: 'Unsupported file type', + deleteHelper: + 'Are you sure you want to delete the following files? By default, it will enter the recycle bin after deletion', + fileHelper: `Note:\n1. Search results can't be sorted.\n2. Folders can't be sorted by size.`, + forceDeleteHelper: 'Permanently delete the file (without entering the recycle bin, delete it directly)', + recycleBin: 'Recycle bin', + sourcePath: 'Original path', + deleteTime: 'Delete time', + confirmReduce: 'Are you sure you want to restore the following files?', + reduceSuccess: 'Restore successful', + reduce: 'Reduction', + reduceHelper: + 'If a file or directory with the same name exists in the original path, it will be overwritten. Do you want to continue?', + clearRecycleBin: 'Clean', + clearRecycleBinHelper: 'Do you want to clean the recycle bin?', + favorite: 'Favorites', + removeFavorite: 'Remove from favorites?', + addFavorite: 'Add/Remove to Favorites', + clearList: 'Clean list', + deleteRecycleHelper: 'Are you sure you want to permanently delete the following files?', + typeErrOrEmpty: '[{0}] file type is wrong or empty folder', + dropHelper: 'Drag the files you want to upload here', + fileRecycleBin: 'Enable recycle bin', + fileRecycleBinMsg: '{0} recycle bin', + wordWrap: 'Automatically wrap', + deleteHelper2: + 'Are you sure you want to delete the selected file? The deletion operation cannot be rolled back', + ignoreCertificate: 'Allow insecure server connections', + ignoreCertificateHelper: + 'Allowing inscure server connections may lead to data leak or tampering. Use this option only when trusting the download source.', + uploadOverLimit: 'The number of files exceeds 1000! Please compress and upload', + clashDitNotSupport: 'File names are prohibited from containing .1panel_clash', + clashDeleteAlert: `The "Recycle Bin" folder can't be deleted`, + clashOpenAlert: 'Please click the "Recycle Bin" button to open the recycle bin directory', + right: 'Forward', + back: 'Back', + top: 'Go Back', + up: 'Go back', + openWithVscode: 'Open with VS Code', + vscodeHelper: 'Please make sure that VS Code is installed locally and the SSH Remote plugin is configured', + saveContentAndClose: 'The file has been modified, do you want to save and close it?', + saveAndOpenNewFile: 'The file has been modified, do you want to save and open the new file?', + noEdit: 'The file has not been modified, no need to do this!', + noNameFolder: 'Untitled folder', + noNameFile: 'Untitled file', + minimap: 'Code mini map', + fileCanNotRead: 'File can not read', + previewTruncated: 'File is too large, only showing the last part', + previewEmpty: 'File is empty or not a text file', + previewLargeFile: 'Preview', + panelInstallDir: `1Panel installation directory can't be deleted`, + wgetTask: 'Download Task', + existFileTitle: 'Same name file prompt', + existFileHelper: 'The uploaded file contains a file with the same name, do you want to overwrite it?', + existFileSize: 'File size (new -> old)', + existFileDirHelper: 'The selected file/folder has a duplicate name. Please proceed with caution! \n', + coverDirHelper: 'The selected folders to be replaced will be copied to the destination path!', + noSuchFile: 'The file or directory was not found. Please check and try again.', + setting: 'Setting', + showHide: 'Show hidden files', + noShowHide: 'Don’t show hidden files', + cancelUpload: 'Cancel Upload', + cancelUploadHelper: 'Whether to cancel the upload, after cancellation the upload list will be cleared.', + keepOneTab: 'Keep at least one tab', + notCanTab: 'Cannot add more tabs', + convert: 'Convert Format', + converting: 'Convert To', + fileCanNotConvert: 'This file does not support format conversion', + formatType: 'Format Type', + sourceFormat: 'Source Format', + sourceFile: 'Source File', + saveDir: 'Save Directory', + deleteSourceFile: 'Delete Source File', + convertHelper: 'Convert the selected files to another format', + convertHelper1: 'Please select the files to be converted', + execConvert: 'Start conversion. You can view the conversion logs in the Task Center', + convertLogs: 'Conversion Logs', + formatConvert: 'Format Conversion', + }, + ssh: { + autoStart: 'Auto start', + enable: 'Enable Autostart', + disable: 'Disable Autostart', + sshAlert: + 'The list data is sorted based on login date. Changing time zone or performing other operations may cause deviations in the date of login logs.', + sshAlert2: + 'You can use "Fail2ban" in the "Toolbox" to block IP addresses that attempt brute force attacks, and this will enhance the security of the host.', + sshOperate: 'Operation "{0}" on the SSH service will be performed. Do you want to continue?', + sshChange: 'SSH Setting', + sshChangeHelper: 'This action changed "{0}" to "{1}". Do you want to continue?', + sshFileChangeHelper: + 'Modifying the configuration file may cause service availability. Exercise caution when performing this operation. Do you want to continue?', + port: 'Port', + portHelper: 'Specific the port that SSH service listens on.', + listenAddress: 'Listen address', + allV4V6: '0.0.0.0:{0}(IPv4) and :::{0}(IPv6)', + listenHelper: + 'Leaving both of IPv4 and IPv6 settings blank will listen on "0.0.0.0:{0}(IPv4)" and ":::{0}(IPv6)".', + addressHelper: 'Specify the address that SSH service listens on.', + permitRootLogin: 'Permit root user login', + rootSettingHelper: 'The default login method for root user is "Allow SSH login".', + rootHelper1: 'Allow SSH login', + rootHelper2: 'Disable SSH login', + rootHelper3: 'Only key login is allowed', + rootHelper4: 'Only predefined commands can be executed. No other operations can be performed.', + passwordAuthentication: 'Password auth', + pwdAuthHelper: 'Whether to enable password authentication. This parameter is enabled by default.', + pubkeyAuthentication: 'Key auth', + privateKey: 'Private Key', + publicKey: 'Public Key', + password: 'Password', + createMode: 'Creation Method', + generate: 'Auto-generate', + unSyncPass: 'Key password cannot be synchronized', + syncHelper: 'The sync operation will clean invalid keys and sync new complete key pairs. Continue?', + input: 'Manual Input', + import: 'File Upload', + authKeys: 'Authorization Keys', + authKeysHelper: 'Save current public key information?', + pubkey: 'Key info', + pubKeyHelper: 'The current key information only takes effect for user {0}', + encryptionMode: 'Encryption mode', + passwordHelper: 'Can contain 6 to 10 digits and English cases', + reGenerate: 'Regenerate key', + keyAuthHelper: 'Whether to enable key authentication.', + useDNS: 'useDNS', + dnsHelper: + 'Control whether the DNS resolution function is enabled on the SSH server to verify the identity of the connection.', + analysis: 'Statistical information', + denyHelper: + "Performing a 'deny' operation on the following addresses. After setting, the IP will be prohibited from accessing the server. Do you want to continue?", + acceptHelper: + "Performing an 'accept' operation on the following addresses. After setting, the IP will regain normal access. Do you want to continue?", + noAddrWarning: 'No [{0}] addresses are currently selected. Please check and try again!', + loginLogs: 'Login logs', + loginMode: 'Mode', + authenticating: 'Key', + publickey: 'Key', + belong: 'Belong', + local: 'Local', + session: 'Session | Sessions', + loginTime: 'Login time', + loginIP: 'Login IP', + stopSSHWarn: 'Whether to disconnect this SSH connection', + }, + setting: { + panel: 'Panel', + user: 'Panel user', + userChange: 'Change panel user', + userChangeHelper: 'Changing the panel user will log you out. Continue?', + passwd: 'Panel password', + emailHelper: 'For password retrieval', + watermark: 'Watermark Settings', + watermarkContent: 'Watermark Content', + contentHelper: + '{0} represents node name, {1} represents node address. You can use variables or fill in custom names.', + watermarkColor: 'Watermark Color', + watermarkFont: 'Watermark Font Size', + watermarkHeight: 'Watermark Height', + watermarkWidth: 'Watermark Width', + watermarkRotate: 'Rotation Angle', + watermarkGap: 'Spacing', + watermarkCloseHelper: 'Are you sure you want to turn off the system watermark settings?', + watermarkOpenHelper: 'Are you sure you want to save the current system watermark settings?', + title: 'Panel alias', + panelPort: 'Panel port', + titleHelper: + 'Supports a length of 3 to 30 characters, including English letters, Chinese characters, numbers, spaces, and common special characters', + portHelper: + 'The recommended port range is 8888 to 65535. Note: If the server has a security group, permit the new port from the security group in advance', + portChange: 'Port change', + portChangeHelper: 'Modify the service port and restart the service. Do you want to continue?', + theme: 'Theme', + menuTabs: 'Menu tabs', + dark: 'Dark', + darkGold: 'Dark Gold', + light: 'Light', + auto: 'Follow System', + language: 'Language', + languageHelper: + 'By default, it follows the browser language. This parameter takes effect only on the current browser', + sessionTimeout: 'Session timeout', + sessionTimeoutError: 'The minimum session timeout is 300 seconds', + sessionTimeoutHelper: + 'The panel will automatically be logged out if there is no operation for more than {0} second(s).', + systemIP: 'System address', + systemIPHelper: + 'The address will be used for application redirection, container access, and other functions. Each node can be configured with a different address.', + proxy: 'Server proxy', + proxyHelper: 'It will be effective in the following scenarios after you set up the proxy server:', + proxyHelper1: + 'Installation package download and synchronization from the app store (Professional edition only)', + proxyHelper2: 'System update and update information retrieval (Professional edition only)', + proxyHelper3: 'System license verification and synchronization', + proxyHelper4: 'Docker network will be accessed through a proxy server (Professional edition only)', + proxyHelper5: 'Unified download and sync for system-type script libraries (Professional)', + proxyHelper6: 'Apply for certificate (Professional)', + proxyType: 'Proxy type', + proxyUrl: 'Proxy Address', + proxyPort: 'Proxy Port', + proxyPasswdKeep: 'Remember Password', + proxyDocker: 'Docker Proxy', + proxyDockerHelper: + 'Synchronize proxy server configuration to Docker, support offline server image pulling and other operations', + syncToNode: 'Sync to Node', + syncToNodeHelper: 'Sync settings to other nodes', + nodes: 'Node', + selectNode: 'Select Node', + selectNodeError: 'Please select a node', + apiInterface: 'Enable API', + apiInterfaceClose: 'Once closed, API interfaces cannot be accessed. Do you want to continue?', + apiInterfaceHelper: 'Allow third-party applications to access the API.', + apiInterfaceAlert1: `Don't enable it in production environments because it may increase server security risks.`, + apiInterfaceAlert2: `Don't use third-party applications to call the API to prevent potential security threats.`, + apiInterfaceAlert3: 'API document:', + apiInterfaceAlert4: 'Usage document:', + apiKey: 'API key', + apiKeyHelper: 'API key is used for third-party applications to access the API.', + ipWhiteList: 'IP allowlist', + ipWhiteListEgs: 'One per line. For example,\n172.161.10.111\n172.161.10.0/24', + ipWhiteListHelper: 'IPs within the allowlist can access the API,0.0.0.0/0 (all IPv4), ::/0 (all IPv6)', + apiKeyValidityTime: 'Validity period of interface key', + apiKeyValidityTimeEgs: 'Validity period of interface key (in minutes)', + apiKeyValidityTimeHelper: + 'The interface timestamp is valid if its difference from the current timestamp (in minutes) is within the allowed range. A value of 0 disables verification.', + apiKeyReset: 'Interface key reset', + apiKeyResetHelper: 'the associated key service will become invalid. Please add a new key to the service', + confDockerProxy: 'Configure docker proxy', + restartNowHelper: 'Configuring Docker proxy requires restarting the Docker service.', + restartNow: 'Restart immediately', + restartLater: 'Restart manually later', + systemIPWarning: `The server address isn't currently set. Set it in the control panel first.`, + systemIPWarning1: `The current server address is set to {0}, and quick redirection isn't possible!`, + syncTime: 'Server Time', + timeZone: 'Time Zone', + timeZoneChangeHelper: 'Changing the time zone requires restarting the service. Do you want to continue?', + timeZoneHelper: + 'Timezone modification depends on the system timedatectl service. take effect after restart the 1Panel service.', + timeZoneCN: 'Bei Jing', + timeZoneAM: 'Los Angeles', + timeZoneNY: 'New York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'Ntp Server', + syncSiteHelper: + 'This operation will use {0} as the source for system time synchronization. Do you want to continue?', + changePassword: 'Change Password', + oldPassword: 'Original password', + newPassword: 'New password', + retryPassword: 'Confirm password', + noSpace: 'Input information cannot include space characters', + duplicatePassword: 'The new password cannot be the same as the original password, please re-enter!', + diskClean: 'Cache clean', + developerMode: 'Preview Program', + developerModeHelper: `You will get to expirence new features and fixes before they're released broadly and give early feedback.`, + thirdParty: 'Third-party accounts', + scope: 'Scope', + public: 'Public', + publicHelper: + 'Public type backup accounts will be synchronized to each sub-node, and sub-nodes can use them together', + private: 'Private', + privateHelper: + 'Private type backup accounts are only created on the current node and are for the use of the current node only', + noTypeForCreate: 'No backup type is currently created', + LOCAL: 'Server disk', + OSS: 'Ali OSS', + S3: 'Amazon S3', + mode: 'Mode', + MINIO: 'MinIO', + SFTP: 'SFTP', + WebDAV: 'WebDAV', + WebDAVAlist: 'WebDAV connect Alist can refer to the official documentation', + UPYUN: 'UPYUN', + ALIYUN: 'Aliyun Drive', + ALIYUNHelper: + 'The current maximum limit for non-client downloads on Aliyun Drive is 100 MB. Exceeding this limit requires downloading through the client.', + ALIYUNRecover: + 'The current maximum limit for non-client downloads on Aliyun Drive is 100 MB. Exceeding this limit requires downloading through the client to the local device, then synchronizing the snapshot for recovery.', + GoogleDrive: 'Google Drive', + analysis: 'Analysis', + analysisHelper: + 'Paste the entire token content to automatically parse the required parts. For specific operations, please refer to the official documentation.', + serviceName: 'Service Name', + operator: 'Operator', + OneDrive: 'Microsoft OneDrive', + isCN: 'Century Internet', + isNotCN: 'International Version', + client_id: 'Client ID', + client_secret: 'Client secret', + redirect_uri: 'Redirect URL', + onedrive_helper: 'Custom configuration can be referred to in the official documentation', + clickToRefresh: 'Click to refresh', + refreshTime: 'Token Refresh Time', + refreshStatus: 'Token Refresh Status', + backupDir: 'Backup directory', + codeWarning: 'The current authorization code format is incorrect, please confirm again!', + code: 'Auth code', + codeHelper: + 'Please click on the "Acquire" button, then login to {0} and copy the content after "code" in the redirected link. Paste it into this input box. For specific instructions, please refer to the official documentation.', + googleHelper: + 'Please first create a Google application and obtain client information, fill in the form and click the get button. For specific operations, please refer to the official documentation.', + loadCode: 'Acquire', + COS: 'Tencent COS', + ap_beijing_1: 'Beijing Zone 1', + ap_beijing: 'Beijing', + ap_nanjing: 'Nanjing', + ap_shanghai: 'Shanghai', + ap_guangzhou: 'Guangzhou', + ap_chengdu: 'Chengdu', + ap_chongqing: 'Chongqing', + ap_shenzhen_fsi: 'Shenzhen Financial', + ap_shanghai_fsi: 'Shanghai Financial', + ap_beijing_fsi: 'Beijing Financial', + ap_hongkong: 'Hong Kong, China', + ap_singapore: 'Singapore', + ap_mumbai: 'Mumbai', + ap_jakarta: 'Jakarta', + ap_seoul: 'Seoul', + ap_bangkok: 'Bangkok', + ap_tokyo: 'Tokyo', + na_siliconvalley: 'Silicon Valley (US West)', + na_ashburn: 'Ashburn (US East)', + na_toronto: 'Toronto', + sa_saopaulo: 'Sao Paulo', + eu_frankfurt: 'Frankfurt', + KODO: 'Qiniu Kodo', + scType: ' Storage type', + typeStandard: 'Standard', + typeStandard_IA: 'Standard_IA', + typeArchive: 'Archive', + typeDeep_Archive: 'Deep_Archive', + scLighthouse: 'Default, Lightweight object storage only supports this storage type', + scStandard: + 'Standard storage, suitable for business scenarios with large amounts of frequently accessed hot files and frequent data interactions.', + scStandard_IA: + 'Infrequent access storage, suitable for business scenarios with lower access frequency (e.g., average monthly access frequency of 1-2 times), minimum storage duration of 30 days.', + scArchive: 'Archival storage is suitable for business scenarios with extremely low access frequency.', + scDeep_Archive: 'Durable cold storage is suitable for business scenarios with extremely low access frequency.', + archiveHelper: + 'Archival storage files cannot be downloaded directly and must first be restored through the corresponding cloud service provider`s website. Please use with caution!', + backupAlert: + 'If a cloud provider is compatible with the S3 protocol, you can directly use Amazon S3 for backup. ', + domain: 'Accelerate domain', + backupAccount: 'Backup account | Backup accounts', + loadBucket: 'Get bucket', + accountName: 'Account name', + accountKey: 'Account key', + address: 'Address', + path: 'Path', + + safe: 'Security', + passkey: 'Passkey', + passkeyManage: 'Manage', + passkeyHelper: 'For quick login, up to 5 passkeys can be bound', + passkeyRequireSSL: 'Enable HTTPS to use passkeys', + passkeyNotSupported: 'Current browser or environment does not support passkeys', + passkeyCount: 'Bound {0}/{1}', + passkeyName: 'Name', + passkeyNameHelper: 'Enter a name to distinguish devices', + passkeyAdd: 'Add Passkey', + passkeyCreatedAt: 'Created At', + passkeyLastUsedAt: 'Last Used', + passkeyDeleteConfirm: 'After deletion, this passkey cannot be used to log in. Continue?', + passkeyLimit: 'Up to 5 passkeys can be bound', + passkeyFailed: + 'Passkey registration failed, please confirm that the panel SSL certificate is a trusted certificate', + bindInfo: 'Bind info', + bindAll: 'Listen All', + bindInfoHelper: + 'Changing the service listening address or protocol may result in service unavailability. Do you want to continue?', + ipv6: 'Listen IPv6', + bindAddress: 'Listen address', + entrance: 'Entrance', + showEntrance: 'Show disabled alert in "Overview" page', + entranceHelper: + 'Enabling security entrance will only allow logging in to the panel through specified security entrance.', + entranceError: + 'Please enter a secure login entry point of 5-116 characters, only numbers or letters are supported.', + entranceInputHelper: 'Leave it blank to disable the security entrance.', + randomGenerate: 'Random', + expirationTime: 'Expiration Date', + unSetting: 'Not set', + noneSetting: + 'Set the expiration time for the panel password. After the expiration, you need to reset the password', + expirationHelper: 'If the password expiration time is [0] days, the password expiration function is disabled', + days: 'Expiration Days', + expiredHelper: 'The current password has expired. Please change the password again.', + timeoutHelper: + '[ {0} days ] The panel password is about to expire. After the expiration, you need to reset the password', + complexity: 'Complexity validation', + complexityHelper: `After you enable it, the password validation rule will be: 8-30 characters, including English, numbers, and at least two special characters.`, + bindDomain: 'Bind domain', + unBindDomain: 'Unbind domain', + panelSSL: 'Panel SSL', + panelSSLHelper: + 'After the automatic renewal of the panel SSL, you need to manually restart the 1Panel service for the changes to take effect.', + unBindDomainHelper: + 'The action of unbinding a domain name may cause system insecurity. Do you want to continue?', + bindDomainHelper: 'After you bind the domain, only that domain can access 1Panel service.', + bindDomainHelper1: 'Leave it blank to disable the domain name binding.', + bindDomainWarning: + 'After domain binding, you will be logged out and can only access 1Panel service through the domain name specified in the settings. Do you want to continue?', + allowIPs: 'Authorized IP', + unAllowIPs: 'Unauthorized IP', + unAllowIPsWarning: + 'Authorizing an empty IP will allow all IPs to access the system, which may cause system insecurity. Do you want to continue?', + allowIPsHelper: + 'After you set the authorized IP address list, only the IP address in the list can access the panel service.', + allowIPsWarning: + 'After you set the authorized IP address list, only the IP address in the list can access the panel service. Do you want to continue?', + allowIPsHelper1: `Leave it blank to disable the IP address restriction.`, + allowIPEgs: 'One per line. For example,\n172.16.10.111\n172.16.10.0/24', + mfa: 'Two-factor authentication (2FA)', + mfaClose: 'Disabling MFA will reduce the security of the service. Do you want to continue?', + secret: 'Secret', + mfaInterval: 'Refresh interval(s)', + mfaTitleHelper: + 'The title is used to distinguish different 1Panel hosts. Scan again or manually add the secret key after you modify the title.', + mfaIntervalHelper: 'Scan again or manually add the secret key after you modify the refresh time.', + mfaAlert: + 'One-time token is dynamically generated 6-digit number and based on the current time. Make sure that the server time is synchronized.', + mfaHelper: 'After you enabled it, the one-time token needs to be verified.', + mfaHelper1: 'Download an authenticator app, for example,', + mfaHelper2: + 'To obtain the one-time token, scan the following QR code using your authenticator app or copy the secret key into your authentication app.', + mfaHelper3: 'Enter six digits from the app', + mfaCode: 'One-time token', + sslChangeHelper: 'Modify the https setting and restart the service. Do you want to continue?', + sslDisable: 'Disable', + sslDisableHelper: + 'If the https service is disabled, you need to restart the panel for it to take effect. Do you want to continue?', + noAuthSetting: 'Unauthorized setting', + noAuthSettingHelper: `When users don't log in with specified security entrance, or don't access the panel from specified IP or domain name, this response can hid panel characteristic.`, + responseSetting: 'Response setting', + help200: 'Help Page', + error400: 'Bad Request', + error401: 'Unauthorized', + error403: 'Forbidden', + error404: 'Not Found', + error408: 'Request Timeout', + error416: 'Range Not Satisfiable', + error444: 'Connection Closed', + error500: 'Internal Server Error', + + https: 'Setting HTTPS for the panel improves access security', + strictHelper: 'Non-HTTPS traffic cannot connect to the panel', + muxHelper: + 'The panel will listen on both HTTP and HTTPS ports and redirect HTTP to HTTPS, but this may reduce security', + certType: 'Certificate type', + selfSigned: 'Self signed', + selfSignedHelper: `Browsers may not trust self-signed certificates and may display security warnings.`, + select: 'Select', + domainOrIP: 'Domain or IP:', + timeOut: 'Timeout', + rootCrtDownload: 'Root certificate download', + primaryKey: 'Primary key', + certificate: 'Certificate', + backupJump: + 'Backup files not in the current backup list, please try downloading from the file directory and importing for backup.', + + snapshot: 'Snapshot | Snaphshots', + noAppData: 'No system applications available for selection', + noBackupData: 'No backup data available for selection', + stepBaseData: 'Base Data', + stepAppData: 'System Application', + stepPanelData: 'System Data', + stepBackupData: 'Backup Data', + stepOtherData: 'Other Data', + operationLog: 'Retain Operation Log', + loginLog: 'Retain Access Log', + systemLog: 'Retain System Log', + taskLog: 'Retain Task Log', + monitorData: 'Retain Monitoring Data', + dockerConf: 'Retain Docker Configuration', + selectAllImage: 'Backup All Application Images', + logLabel: 'Log', + agentLabel: 'Node Configuration', + appDataLabel: 'Application Data', + appImage: 'Application Image', + appBackup: 'Application Backup', + backupLabel: 'Backup Directory', + confLabel: 'Configuration File', + dockerLabel: 'Container', + taskLabel: 'Scheduled Task', + resourceLabel: 'Application Resource Directory', + runtimeLabel: 'Runtime Environment', + appLabel: 'Application', + databaseLabel: 'Database', + snapshotLabel: 'Snapshot File', + websiteLabel: 'Website', + directoryLabel: 'Directory', + appStoreLabel: 'Application Store', + shellLabel: 'Script', + tmpLabel: 'Temporary Directory', + sslLabel: 'Certificate Directory', + reCreate: 'Failed to create snapshot', + reRollback: 'Rollback snapshot failed', + deleteHelper: + 'All snapshot files including those in the third-party backup account will be deleted. Do you want to continue?', + status: 'Snapshot status', + ignoreRule: 'Ignore rule', + editIgnoreRule: '@:commons.button.edit @.lower:setting.ignoreRule', + ignoreHelper: + 'This rule will be used to compress and backup the 1Panel data directory during creating snapshot. By default, socket files are ignored.', + ignoreHelper1: 'One per line. For example,\n*.log\n/opt/1panel/cache', + panelInfo: 'Write 1Panel basic information', + panelBin: 'Backup 1Panel system files', + daemonJson: 'Backup Docker configuration file', + appData: 'Backup installed apps from 1Panel', + panelData: 'Backup 1Panel data directory', + backupData: 'Backup local backup directory for 1Panel', + compress: 'Create Snapshot file', + upload: 'Upload snapshot file', + recoverDetail: 'Recover detail', + createSnapshot: 'Create snapshot', + importSnapshot: 'Sync snapshot', + importHelper: 'Snapshot directory: ', + lastRecoverAt: 'Last recovery time', + lastRollbackAt: 'Last rollback time', + reDownload: 'Download the backup file again', + recoverErrArch: `Snapshot recovery between different server architectures isn't supported!`, + recoverErrSize: 'Detected insufficient disk space, please check or clean up and try again!', + recoverHelper: + 'Starting recovery from snapshot {0}, please confirm the following information before proceeding:', + recoverHelper1: 'Recovery requires restarting Docker and 1Panel services', + recoverHelper2: + 'Please ensure there is sufficient disk space on the server (Snapshot file size: {0}, Available space: {1})', + recoverHelper3: + 'Please ensure the server architecture matches the architecture of the server where the snapshot was created (Current server architecture: {0})', + rollback: 'Rollback', + rollbackHelper: + 'Rolling back this recovery will replace all files from this recovery, and may require restarting Docker and 1Panel services. Do you want to continue?', + + upgradeRecord: 'Upgrade record', + upgrading: ' Upgrading, please wait...', + upgradeHelper: 'The upgrade requires restarting the 1Panel service. Do you want to continue?', + noUpgrade: 'It is currently the latest version', + versionHelper: + 'Name rules: [major version].[functional version].[Bug fix version], as shown in the following example:', + rollbackLocalHelper: + 'The primary node does not support direct rollback. Please manually execute the [1pctl restore] command to rollback!', + upgradeCheck: 'Check for updates', + upgradeNotes: 'Release note', + upgradeNow: 'Upgrade now', + source: 'Download source', + versionNotSame: 'Node version mismatch with the main node. Please upgrade in Node Management before retrying.', + versionCompare: + 'Detected that node {0} is already at the latest upgradable version. Please check the primary node version and try again!', + + about: 'About', + versionItem: 'Current Version', + backupCopies: 'Number of Copies to Keep', + backupCopiesHelper: 'Set the number of upgrade backup copies to keep for version rollback. 0 means keep all.', + backupCopiesRule: 'Please keep at least 3 upgrade backup records', + release: 'Release Notes', + releaseHelper: + 'Failed to fetch release notes for the current environment. You can manually check the official documentation.', + project: 'GitHub', + issue: 'Feedback', + doc: 'Official document', + star: 'Star', + description: 'Linux Server Panel', + forum: 'Discussions', + doc2: 'Docs', + currentVersion: 'Version', + + license: 'License', + bindNode: 'Bind Node', + menuSetting: 'Menu Settings', + menuSettingHelper: 'When only 1 submenu exists, the menu bar will display only that submenu', + showAll: 'Show All', + hideALL: 'Hide All', + ifShow: 'Whether to Show', + menu: 'Menu', + confirmMessage: 'The page will be refreshed to update the advanced menu list. Continue?', + recoverMessage: 'The page will be refreshed and restore the menu list to its initial state. Continue?', + compressPassword: 'Compression password', + backupRecoverMessage: 'Please enter the compression or decompression password (leave blank to not set)', + }, + license: { + offLine: 'Offline', + community: 'OSS', + oss: 'Open Source Software', + pro: 'Pro', + trial: 'Trial', + add: 'Add Community Edition', + licenseBindHelper: 'Free node quotas can only be used when the license is bound to a node', + licenseAlert: + 'Community Edition nodes can only be added when the license is properly bound to a node. Only nodes properly bound to the license support switching.', + licenseUnbindHelper: 'Community Edition nodes detected for this license. Please unbind and try again!', + subscription: 'Subscription', + perpetual: 'Perpetual', + versionConstraint: '{0} Version Buyout', + forceUnbind: 'Force Unbind', + forceUnbindHelper: + 'Forcing unbind will ignore any errors that occur during the unbinding process and ultimately release the license binding.', + updateForce: 'Force update (ignore all errors during unbinding to ensure final operation succeeds)', + trialInfo: 'Version', + authorizationId: 'Subscription authorization ID', + authorizedUser: 'Authorized user', + lostHelper: + 'The license has reached the maximum number of retry attempts. Please manually click the sync button to ensure the professional version features are functioning properly.', + exceptionalHelper: + 'License synchronization verification is abnormal. Please manually click the sync button to ensure the professional version functions properly. detail: ', + quickUpdate: 'Quick update', + import: 'Import', + power: 'Authorize', + unbindHelper: 'All Pro related Settings will be cleaned after unbinding. Do you want to continue? ', + importLicense: 'Import license', + importHelper: 'Please click or drag the license file here', + levelUpPro: 'Upgrade to Professional Edition', + licenseSync: 'License Sync', + knowMorePro: 'Learn More', + closeAlert: 'The current page can be closed in the panel settings', + introduce: 'Feature Introduction', + waf: 'Upgrading to the professional version can provide features such as interception map, logs, block records, geographical location blocking, custom rules, custom interception pages, etc.', + tamper: 'Upgrading to the professional version can protect websites from unauthorized modifications or tampering.', + tamperHelper: 'Operation failed, the file or folder has tamper protection enabled. Please check and try again!', + setting: + 'Upgrading to the professional version allows customization of panel logo, welcome message, and other information.', + monitor: + 'Upgrade to the professional version to view the real-time status of the website, visitor trends, visitor sources, request logs and other information. ', + alert: 'Upgrade to the professional version to receive alarm information via SMS and view alarm logs, fully control various key events, and ensure worry-free system operation', + node: 'Upgrading to the Professional Edition allows you to manage multiple Linux servers with 1Panel.', + nodeApp: + 'Upgrading to the Professional Edition allows unified upgrading of multi-node application versions without manually switching nodes.', + nodeDashboard: + 'Upgrading to the Professional Edition enables centralized management of multi-node applications, websites, databases, and scheduled tasks.', + fileExchange: 'Upgrade to the Professional Edition to quickly transfer files between multiple servers.', + app: 'Upgrade to the professional version to view service information, abnormal monitoring, etc. through the mobile APP. ', + cluster: + 'Upgrading to the Professional Edition allows you to manage MySQL/Postgres/Redis master-slave clusters.', + }, + clean: { + scan: 'Start scanning', + scanHelper: 'Easily clean up junk files produced during 1Panel runtime', + clean: 'Clean now', + reScan: 'Rescan', + cleanHelper: 'Selected files and directories cannot be rolled back after cleanup. Continue?', + statusSuggest: '(Recommended Cleaning)', + statusClean: '(Very clean)', + statusEmpty: 'Very clean, no cleaning needed!', + statusWarning: '(Proceed with Caution)', + lastCleanTime: 'Last Cleaned: {0}', + lastCleanHelper: 'Files and directories cleaned: {0}, total cleaned: {1}', + cleanSuccessful: 'Successfully cleaned', + currentCleanHelper: 'Files and directories cleaned in this session: {0}, Total cleaned: {1}', + suggest: '(Recommended)', + totalScan: 'Total junk files to be cleaned: ', + selectScan: 'Total selected junk files: ', + + system: 'System Junk Files', + systemHelper: 'Temporary files generated during snapshot, upgrade, and other processes', + panelOriginal: 'Backup files before system snapshot restore', + upgrade: 'System upgrade backup files', + agentPackages: 'Historical version child node upgrade/installation packages', + upgradeHelper: '(Recommend keeping the latest upgrade backup for system rollback)', + snapshot: 'System snapshot temporary files', + rollback: 'Backup files before recover', + + backup: 'System Backup', + backupHelper: 'Backup files not associated with local backup accounts', + unknownBackup: 'System Backup', + tmpBackup: 'Temporary Backup', + unknownApp: 'Unassociated App Backup', + unknownDatabase: 'Unassociated Database Backup', + unknownWebsite: 'Unassociated Website Backup', + unknownSnapshot: 'Unassociated Snapshot Backup', + + upload: 'Temporary Upload Files', + uploadHelper: 'Temporary files uploaded from the system backup list', + download: 'Temporary Download Files', + downloadHelper: 'Temporary files downloaded from third-party backup accounts by the system', + directory: 'Directory', + + systemLog: 'Log file', + systemLogHelper: 'System logs, task logs, website log files', + dockerLog: 'Container operation log files', + taskLog: 'Scheduled task execution log files', + shell: 'Shell script scheduled tasks', + containerShell: 'Shell script scheduled tasks executed inside containers', + curl: 'CURL scheduled tasks', + + docker: 'Container garbage', + dockerHelper: 'Files such as containers, images, volumes, build cache, etc.', + volumes: 'Volumes', + buildCache: 'Container Build Cache', + + appTmpDownload: 'App temporary download file', + unknownWebsiteLog: 'Unlinked website log backup file', + }, + app: { + app: 'Application | Applications', + installName: 'Name', + installed: 'Installed', + all: 'All', + version: 'Version', + detail: 'Details', + params: 'Edit parameters', + author: 'Author', + source: 'Source', + appName: 'Application Name', + deleteWarn: + 'The delete operation will delete all data and backups together. This operation cannot be rolled back. Do you want to continue? ', + syncSuccess: 'Synchronized successfully', + canUpgrade: 'Updates', + backupName: 'File Name', + backupPath: 'File Path', + backupdate: 'Backup time', + versionSelect: 'Please select a version', + operatorHelper: 'Operation {0} will be performed on the selected application. Do you want to continue?', + startOperatorHelper: 'The application will be started. Do you want to continue?', + stopOperatorHelper: 'The application will be stopped. Do you want to continue?', + restartOperatorHelper: 'The application will be restarted. Do you want to continue?', + reloadOperatorHelper: 'The application will be reloaded. Do you want to continue?', + checkInstalledWarn: `"{0}" isn't detected. Go to "App Store" to install.`, + limitHelper: 'The application has already been installed.', + deleteHelper: `"{0}" has been associated with the following resource(s) and can't be deleted`, + checkTitle: 'Hint', + defaultConfig: 'Default configuration', + defaultConfigHelper: 'It has been restored to the default configuration, it will take effect after saving', + forceDelete: 'Force delete', + forceDeleteHelper: + 'Force deletion will ignore errors during the deletion process and eventually delete metadata.', + deleteBackup: 'Delete backup', + deleteBackupHelper: 'Also delete the application backup', + deleteDB: 'Delete database', + deleteDBHelper: 'Also delete the database', + noService: 'No {0}', + toInstall: 'Go to install', + param: 'Parameters', + alreadyRun: 'Age', + syncAppList: 'Sync', + less1Minute: 'Less than 1 minute', + appOfficeWebsite: 'Website', + github: 'Github', + document: 'Document', + updatePrompt: 'No updates available', + installPrompt: 'No apps installed yet', + updateHelper: 'Editing parameters may cause the application to fail to start. Please proceed with caution.', + updateWarn: 'Update parameters need to rebuild the application, Do you want to continue? ', + busPort: 'Port', + syncStart: 'Start syncing! Please refresh the app store later', + advanced: 'Advanced settings', + cpuCore: 'core(s)', + containerName: 'Container name', + containerNameHelper: 'The container name will be automatically generated when not set', + allowPort: 'External access', + allowPortHelper: 'Allowing external port access will release the firewall port', + appInstallWarn: `The application dosn't expose the external access port by default. Click "Advanced settings" to expose it.`, + upgradeStart: 'Start upgrading! Please refresh the page later', + toFolder: 'Open the installation directory', + editCompose: 'Edit compose file', + editComposeHelper: 'Editing the compose file may cause the software installation to fail', + composeNullErr: 'compose cannot be empty', + takeDown: 'TakeDown', + allReadyInstalled: 'Installed', + installHelper: 'If you have image pull issues, configure image acceleration.', + installWarn: `The external access isn't checked, and it will make the application unable to access through external network. Do you want to continue?`, + showIgnore: 'View ignored applications', + cancelIgnore: 'Cancel ignore', + ignoreList: 'Ignored applications', + appHelper: 'Go to application details page to learn installation instruction for some special applications.', + backupApp: 'Backup application before upgrade', + backupAppHelper: + 'If the upgrade fails, the backup will be automatically rolled back. Please check the failure reason in the log audit-system log', + openrestyDeleteHelper: 'Forced delete of OpenResty will delete all websites. Do you want to continue?', + downloadLogHelper1: 'All logs of {0} application are about to be downloaded. Do you want to continue? ', + downloadLogHelper2: + 'The latest {1} logs of {0} application are about to be downloaded. Do you want to continue? ', + syncAllAppHelper: 'All applications will be synchronized. Do you want to continue? ', + hostModeHelper: + 'The current application network mode is host mode. If you need to open the port, please open it manually on the firewall page.', + showLocal: 'Show local applications', + reload: 'Reload', + upgradeWarn: + 'Upgrading the application will replace the docker-compose.yml file. If there are any changes, you can click to view the file comparison', + newVersion: 'New version', + oldVersion: 'Current version', + composeDiff: 'File comparison', + showDiff: 'View comparison', + useNew: 'Use custom version', + useDefault: 'Use default version', + useCustom: 'Customize docker-compose.yml', + useCustomHelper: `Using a custom docker-compose.yml file may cause the application upgrade to fail. If it isn't necessary, don't check it.`, + diffHelper: + 'The left side is the old version, the right side is the new version. After editing, click to save the custom version', + pullImage: 'Pull Image', + pullImageHelper: 'Execute docker pull to pull the image before the application starts', + deleteImage: 'Delete Image', + deleteImageHelper: 'Delete the image related to the application. The task will not stop if deletion fails', + requireMemory: 'Memory', + supportedArchitectures: 'Architectures', + link: 'Link', + showCurrentArch: 'Architecture', + syncLocalApp: 'Sync Local App', + memoryRequiredHelper: 'Current application memory requirement {0}', + gpuConfig: 'Enable GPU Support', + gpuConfigHelper: + 'Please ensure the machine has an NVIDIA GPU and that NVIDIA drivers and the NVIDIA Docker Container Toolkit are installed', + webUI: 'Web Access Address', + webUIPlaceholder: 'For example: example.com:8080/login', + defaultWebDomain: 'Default Access Address', + defaultWebDomainHepler: + 'If the application port is 8080, the redirect address will be http(s)://default access address:8080', + webUIConfig: + 'The current node has no default access address configured. Please set it in application parameters or go to panel settings to configure!', + toLink: 'Open', + customAppHelper: + 'Before installing a custom app store package, please ensure that there are no installed apps.', + forceUninstall: 'Force Uninstall', + syncCustomApp: 'Sync Custom App', + ignoreAll: 'Ignore all subsequent versions', + ignoreVersion: 'Ignore specified version', + specifyIP: 'Bind Host IP', + specifyIPHelper: + 'Set the host address/network interface to bind the port (if you are not sure about this, please do not fill it in)', + uninstallDeleteBackup: 'Uninstall App - Delete Backup', + uninstallDeleteImage: 'Uninstall App - Delete Image', + upgradeBackup: 'Backup App Before Upgrade', + noAppHelper: 'No application detected, please go to the task center to view the app store sync log', + isEdirWarn: 'Detected modification to docker-compose.yml file, please check the comparison', + }, + website: { + primaryDomain: 'Primary domain', + otherDomains: 'Other domains', + static: 'Static', + deployment: 'Deployment', + supportUpType: 'Only .tar.gz file format is supported, and the compressed package must contain {0}.json file', + proxy: 'Reverse proxy', + alias: 'Alias', + ftpUser: 'FTP account', + ftpPassword: 'FTP password', + ftpHelper: + 'After creating a website, a corresponding FTP account will be created and the FTP directory will link to the website directory.', + remark: 'Remark', + groupSetting: 'Group Management', + createGroup: 'Create group', + appNew: 'New Application', + appInstalled: 'Installed application', + create: 'Create', + delete: 'Delete Website', + deleteApp: 'Delete Application', + deleteBackup: 'Delete Backup', + domain: 'Domain', + domainHelper: 'One domain per line.\nSupport wildcard "*" and IP address.\nSupport adding port.', + addDomain: 'Add', + domainConfig: 'Domains', + defaultDoc: 'Document', + perserver: 'Concurrency', + perserverHelper: 'Limit the maximum concurrency of the current site', + perip: 'Single IP', + peripHelper: 'Limit the maximum number of concurrent access to a single IP', + rate: 'Traffic limits', + rateHelper: 'Limit the flow of each request (unit: KB)', + limitHelper: 'Enable flow control', + other: 'Other', + currentSSL: 'Current Certificate', + dnsAccount: 'DNS account', + applySSL: 'Certificate Application', + SSLList: 'Certificate List', + createDnsAccount: 'DNS account', + aliyun: 'Aliyun DNS', + aliEsa: 'Aliyun ESA', + awsRoute53: 'Amazon Route 53', + manual: 'Manual parsing', + key: 'Key', + check: 'View', + acmeAccountManage: 'Manage ACME accounts', + email: 'Email', + acmeAccount: 'ACME account', + provider: 'Verification method', + dnsManual: 'Manual Resolution', + expireDate: 'Expiration date', + brand: 'Organization', + deploySSL: 'Deployment', + deploySSLHelper: 'Are you sure to deploy the certificate? ', + ssl: 'Certificate | Certificates', + dnsAccountManage: 'Manage DNS providers', + renewSSL: 'Renew', + renewHelper: 'Are you sure to renew the certificate? ', + renewSuccess: 'Renew certificate', + enableHTTPS: 'Enable', + aliasHelper: 'Alias is the directory name of the website', + lastBackupAt: 'last backup time', + null: 'none', + nginxConfig: 'Nginx configuration', + websiteConfig: 'Website settings', + proxySettings: 'Proxy Settings', + advancedSettings: 'Advanced Settings', + cacheSettings: 'Cache Settings', + sniSettings: 'SNI Settings', + basic: 'Basic', + source: 'Configuration', + security: 'Security', + nginxPer: 'Performance tuning', + neverExpire: 'Never', + setDefault: 'Set as default', + deleteHelper: 'Related application status is abnormal, please check', + toApp: 'Go to the installed list', + cycle: 'Cycle', + frequency: 'Frequency', + ccHelper: + 'Accumulatively request the same URL more than {1} times within {0} seconds, trigger CC defense, block this IP', + mustSave: 'The modification needs to be saved to take effect', + fileExt: 'file extension', + fileExtBlock: 'file extension blocklist', + value: 'value', + enable: 'Enable', + proxyAddress: 'Proxy Address', + proxyHelper: 'Example: 127.0.0.1:8080', + forceDelete: 'Force Delete', + forceDeleteHelper: + 'Force deletion will ignore errors during the deletion process and eventually delete metadata.', + deleteAppHelper: 'Delete associated applications and application backups at the same time', + deleteBackupHelper: 'Also delete website backups.', + deleteDatabaseHelper: 'Also delete the database associated with the website', + deleteConfirmHelper: `The delete operation can't be undone. Enter "{0}" to confirm deletion.`, + staticPath: 'The corresponding main directory is ', + limit: 'Scheme', + blog: 'Forum/Blog', + imageSite: 'Picture Site', + downloadSite: 'Download Site', + shopSite: 'Mall', + doorSite: 'Portal', + qiteSite: 'Enterprise', + videoSite: 'Video', + errLog: 'Error log', + stopHelper: + 'After stopping the site, it will not be able to access normally, and the user will display the stop page of the current site when visiting. Do you want to continue? ', + startHelper: + 'After enabling the site, users can access the content of the site normally, do you want to continue? ', + sitePath: 'Directory', + siteAlias: 'Site alias', + primaryPath: 'Root directory', + folderTitle: 'The website mainly contains the following folders', + wafFolder: 'Firewall rules', + indexFolder: 'Website root directory', + sslFolder: 'Website Certificate', + enableOrNot: 'Enable', + oldSSL: 'Existing certificate', + manualSSL: 'Import certificate', + select: 'Select', + selectSSL: 'Select Certificate', + privateKey: 'Key (KEY)', + certificate: 'Certificate (PEM format)', + HTTPConfig: 'HTTP Options', + HTTPSOnly: 'Block HTTP requests', + HTTPToHTTPS: 'Redirect to HTTPS', + HTTPAlso: 'Allow direct HTTP requests', + sslConfig: 'SSL options', + disableHTTPS: 'Disable HTTPS', + disableHTTPSHelper: + 'Disabling HTTPS will delete the certificate related configuration, Do you want to continue?', + SSLHelper: + "Note: Do not use SSL certificates for illegal websites.\nIf HTTPS access can't be used after opening, check whether the security group has correctly released port 443.", + SSLConfig: 'Certificate settings', + SSLProConfig: 'Protocol settings', + supportProtocol: 'Protocol version', + encryptionAlgorithm: 'Encryption algorithm', + notSecurity: '(not safe)', + encryptHelper: + "Let's Encrypt has a frequency limit for issuing certificates, but it is sufficient to meet normal needs. Too frequent operations will cause issuance failure. For specific restrictions, please see official document ", + ipValue: 'Value', + ext: 'file extension', + wafInputHelper: 'Input data by line, one line', + data: 'data', + ever: 'permanent', + nextYear: 'One year later', + noLog: 'No logs found', + defaultServer: 'Set default site', + noDefaultServer: 'Not set', + defaultServerHelper: + 'After setting the default site, all unbinded domain names and IPs will be redirected to the default site\nThis can effectively prevent malicious resolution\nHowever, it will also cause the WAF unauthorized domain name interception to fail', + restoreHelper: 'Are you sure to restore using this backup?', + websiteDeploymentHelper: 'Use an installed application or create a new application to create a website.', + websiteStatictHelper: 'Create a website directory on the host.', + websiteProxyHelper: + 'Use reverse proxy to proxy existing service. For example, if a service is installed and running on port 8080, the proxy address will be "http://127.0.0.1:8080".', + runtimeProxyHelper: 'Use a website runtime to create a website.', + runtime: 'Runtime', + deleteRuntimeHelper: + 'The Runtime application needs to be deleted together with the website, please handle it with caution', + proxyType: 'Network Type', + unix: 'Unix Network', + tcp: 'TCP/IP Network', + phpFPM: 'FPM Config', + phpConfig: 'PHP Config', + updateConfig: 'Update Config', + isOn: 'On', + isOff: 'Off', + rewrite: 'Pseudo-static', + rewriteMode: 'Scheme', + current: 'Current', + rewriteHelper: + 'If setting pseudo-static causes the website to become inaccessible, try to revert to the default settings.', + runDir: 'Run Directory', + runUserHelper: + 'For websites deployed through the PHP container runtime environment, you need to set the owner and user group of all files and folders under index and subdirectories to 1000. For the local PHP environment, refer to the local PHP-FPM user and user group settings', + userGroup: 'User/Group', + uGroup: 'Group', + proxyPath: 'Proxy path', + proxyPass: 'Target URL', + cache: 'Cache', + cacheTime: 'Cache duration', + enableCache: 'Cache', + proxyHost: 'Proxy host', + disabled: 'Stopped', + startProxy: 'This will start reverse proxy. Do you want to continue?', + stopProxy: 'This will stop the reverse proxy. Do you want to continue?', + sourceFile: 'View source', + proxyHelper1: 'When accessing this directory, the content of the target URL will be returned and displayed.', + proxyPassHelper: 'The target URL must be valid and accessible.', + proxyHostHelper: 'Pass the domain name in the request header to the proxy server.', + modifier: 'Matching rules', + modifierHelper: + 'Example: "=" is exact match, "~" is regular match, "^~" matches the beginning of the path, etc.', + replace: 'Text replacements', + replaceHelper: + 'The nginx text replacement feature allows for string substitution in the response content during reverse proxying. It is commonly used to modify links, API addresses, etc., in HTML, CSS, JavaScript, and other files returned by the backend. It supports regular expression matching for complex content replacement needs.', + addReplace: 'Add', + replaced: 'Search String (cannot be empty)', + replaceText: 'Replace with string', + replacedErr: 'The Search String cannot be empty', + replacedErr2: 'The Search String cannot be repeated', + replacedListEmpty: 'No text replacement rules', + proxySslName: 'Proxy SNI Name', + basicAuth: 'Basic authentication', + editBasicAuthHelper: + 'The password is asymmetrically encrypted and cannot be echoed. Editing needs to reset the password', + antiLeech: 'Anti-leech', + extends: 'Extension', + browserCache: 'Browser Cache', + noModify: 'No Modify', + serverCache: 'Server Cache', + leechLog: 'Record anti-leech log', + accessDomain: 'Allowed domains', + leechReturn: 'Response resource', + noneRef: 'Allow empty referrer', + disable: 'not enabled', + disableLeechHelper: 'Whether to disable the anti-leech', + disableLeech: 'Disable anti-leech', + ipv6: 'Listen IPv6', + leechReturnError: 'Please fill in the HTTP status code', + blockedRef: 'Allow non-standard Referer', + accessControl: 'Anti-leech control', + leechcacheControl: 'Cache control', + logEnableControl: 'Log static asset requests', + leechSpecialValidHelper: + "When 'Allow empty Referer' is enabled, requests without a Referer (direct access, etc.) are not blocked; enabling 'Allow non-standard Referer' allows any Referer that does not start with http/https (client requests, etc.).", + leechInvalidReturnHelper: 'HTTP status code returned after blocking hotlinking requests', + leechlogControlHelper: + 'Logs static asset requests; usually disabled in production to avoid excessive, noisy logs', + selectAcme: 'Select Acme account', + imported: 'Created manually', + importType: 'Import type', + pasteSSL: 'Paste code', + localSSL: 'Select server file', + privateKeyPath: 'Private key file', + certificatePath: 'Certificate file', + ipWhiteListHelper: 'The role of IP allowlist: all rules are invalid for IP allowlist', + redirect: 'Redirect', + sourceDomain: 'Source domain', + targetURL: 'Target URL address', + keepPath: 'URI params', + path: 'path', + redirectType: 'redirection type', + redirectWay: 'Way', + keep: 'keep', + notKeep: 'Do not keep', + redirectRoot: 'Redirect to the homepage', + redirectHelper: '301 permanent redirection, 302 temporary redirection', + changePHPVersionWarn: 'This operation cannot be rolled back, do you want to continue?', + changeVersion: 'Switch version', + retainConfig: 'Whether to keep php-fpm.conf and php.ini files', + runDirHelper2: 'Please ensure that the secondary running directory is under the index directory', + openrestyHelper: + 'OpenResty default HTTP port: {0} HTTPS port: {1}, which may affect website domain name access and HTTPS forced redirect', + primaryDomainHelper: 'Exmaple: example.com or example.com:8080', + acmeAccountType: 'Account type', + keyType: 'Key algorithm', + tencentCloud: 'Tencent Cloud', + containWarn: 'The domain name contains the main domain, please re-enter', + rewriteHelper2: + 'Applications like WordPress installed from the app store typically come with pseudo-static configuration preset. Reconfiguring them may lead to errors.', + websiteBackupWarn: + 'Only supports importing local backups, importing backups from other machines may cause recovery failure', + ipWebsiteWarn: 'Websites with IP as domain names need to be set as default site to be accessed normally.', + hstsHelper: 'Enabling HSTS can increase website security', + includeSubDomains: 'SubDomains', + hstsIncludeSubDomainsHelper: + 'Once enabled, the HSTS policy will apply to all subdomains of the current domain.', + defaultHtml: 'Set default page', + website404: 'Website 404 error page', + domain404: 'Website page does not exist', + indexHtml: 'Static website default page', + stopHtml: 'Website stop page', + indexPHP: 'PHP website default page', + sslExpireDate: 'SSL Expiration Date', + website404Helper: 'Website 404 error page only supports PHP runtime environment websites and static websites', + sni: 'Origin SNI', + sniHelper: + "When the reverse proxy backend is HTTPS, you might need to set the origin SNI. Please refer to the CDN service provider's documentation for details.", + huaweicloud: 'Huawei Cloud', + createDb: 'Create Database', + enableSSLHelper: 'Failure to enable will not affect the creation of the website', + batchAdd: 'Batch Add Domains', + batchInput: 'Batch Input', + domainNotFQDN: 'This domain may not be accessible on the public network', + domainInvalid: 'Invalid domain format', + domainBatchHelper: 'One domain per line, format: domain:port@ssl\nExample: example.com:443@ssl or example.com', + generateDomain: 'Generate', + global: 'Global', + subsite: 'Subsite', + subsiteHelper: 'A subsite can select an existing PHP or static website directory as the main directory.', + parentWbeiste: 'Parent Website', + deleteSubsite: 'To delete the current website, you must first delete the subsite(s) {0}', + loadBalance: 'Load Balancing', + server: 'Server', + algorithm: 'Algorithm', + ipHash: 'IP Hash', + ipHashHelper: + 'Distributes requests to a specific server based on the client IP address, ensuring that a particular client is always routed to the same server.', + leastConn: 'Least Connections', + leastConnHelper: 'Sends requests to the server with the fewest active connections.', + leastTime: 'Least Time', + leastTimeHelper: 'Sends requests to the server with the shortest active connection time.', + defaultHelper: + 'Default method, requests are evenly distributed to each server. If servers have weights configured, requests are distributed based on the specified weights, with higher-weighted servers receiving more requests.', + weight: 'Weight', + maxFails: 'Max Fails', + maxConns: 'Max Connections', + strategy: 'Strategy', + strategyDown: 'Down', + strategyBackup: 'Backup', + ipHashBackupErr: 'IP hash does not support backup nodes', + failTimeout: 'Failure timeout', + failTimeoutHelper: + 'The time window length for server health checks. When the cumulative number of failures reaches the threshold within this period, the server will be temporarily removed and retried after the same duration. Default 10 seconds', + + staticChangePHPHelper: 'Currently a static website, you can switch to a PHP website', + proxyCache: 'Reverse Proxy Cache', + cacheLimit: 'Cache Space Limit', + shareCahe: 'Cache Count Memory Size', + cacheExpire: 'Cache Expiration Time', + shareCaheHelper: 'Approximately 8000 cache objects can be stored per 1M of memory', + cacheLimitHelper: 'Old cache will be automatically deleted when the limit is exceeded', + cacheExpireJHelper: 'Cache will be deleted if it misses after the expiration time', + realIP: 'Real IP', + ipFrom: 'IP Source', + ipFromHelper: + "By configuring trusted IP sources, OpenResty will analyze IP information in HTTP headers, accurately identify and record visitors' real IP addresses, including in access logs", + ipFromExample1: "If the frontend is a tool like Frp, you can enter Frp's IP address, such as 127.0.0.1", + ipFromExample2: "If the frontend is a CDN, you can enter the CDN's IP address range", + ipFromExample3: + 'If unsure, you can enter 0.0.0.0/0 (ipv4) ::/0 (ipv6) [Note: Allowing any source IP is not secure]', + http3Helper: + 'HTTP/3 is an upgrade to HTTP/2, offering faster connection speeds and better performance, but not all browsers support HTTP/3. Enabling it may cause some browsers to be unable to access the site.', + cors: 'Cross-Origin Resource Sharing (CORS)', + enableCors: 'Enable CORS', + allowOrigins: 'Allowed domains', + allowMethods: 'Allowed request methods', + allowHeaders: 'Allowed request headers', + allowCredentials: 'Allow cookies to be sent', + preflight: 'Preflight request fast response', + preflightHleper: + 'When enabled, when the browser sends a cross-origin preflight request (OPTIONS request), the system will automatically return a 204 status code and set the necessary cross-origin response headers', + + changeDatabase: 'Change Database', + changeDatabaseHelper1: 'Database association is used for backing up and restoring the website.', + changeDatabaseHelper2: 'Switching to another database will cause previous backups to be unrecoverable.', + saveCustom: 'Save as Template', + rainyun: 'Rain Yun', + volcengine: 'Volcengine', + runtimePortHelper: 'The current runtime environment has multiple ports. Please select a proxy port.', + runtimePortWarn: 'The current runtime environment has no ports, unable to proxy', + cacheWarn: 'Please turn off the cache switch in the reverse proxy first', + loadBalanceHelper: + 'After creating the load balancing, please go to "Reverse Proxy", add a proxy and set the backend address to: http://', + favorite: 'Favorite', + cancelFavorite: 'Cancel Favorite', + useProxy: 'Use Proxy', + useProxyHelper: 'Use the proxy server address in the panel settings', + westCN: 'West Digital', + openBaseDir: 'Prevent Cross-Site Attacks', + openBaseDirHelper: + 'open_basedir is used to restrict the PHP file access path, which helps prevent cross-site access and enhance security', + serverCacheTime: 'Server Cache Time', + serverCacheTimeHelper: + 'The time a request is cached on the server. During this period, identical requests will return the cached result directly without requesting the origin server.', + browserCacheTime: 'Browser Cache Time', + browserCacheTimeHelper: + 'The time static resources are cached locally in the browser, reducing redundant requests. Users will use the local cache directly before it expires when refreshing the page.', + donotLinkeDB: 'Do Not Link Database', + toWebsiteDir: 'Enter Website Directory', + execParameters: 'Execution Parameters', + extCommand: 'Supplementary Command', + mirror: 'Mirror Source', + execUser: 'Executing User', + execDir: 'Execution Directory', + packagist: 'China Full Mirror', + + batchOpreate: 'Batch Operation', + batchOpreateHelper: 'Batch {0} websites, continue operation?', + stream: 'TCP/UDP Proxy', + streamPorts: 'Listening Ports', + streamPortsHelper: + 'Set the external listening port number, clients will access the service through this port, separated by commas, e.g., 5222,5223', + streamHelper: 'TCP/UDP Port Forwarding and Load Balancing', + udp: 'Enable UDP', + + syncHtmlHelper: 'Sync to PHP and static websites', + }, + php: { + short_open_tag: 'Short tag support', + max_execution_time: 'Maximum script execution time', + max_input_time: 'Maximum input time', + memory_limit: 'Script memory limit', + post_max_size: 'POST data maximum size', + file_uploads: 'Whether to allow uploading files', + upload_max_filesize: 'The maximum size allowed to upload files', + max_file_uploads: 'The maximum number of files allowed to be uploaded at the same time', + default_socket_timeout: 'Socket timeout', + error_reporting: 'Error level', + display_errors: 'Whether to output detailed error information', + cgi_fix_pathinfo: 'Whether to open pathinfo', + date_timezone: 'Time zone', + disableFunction: 'Disable function', + disableFunctionHelper: 'Enter the function to be disabled, such as exec, please use multiple, split', + uploadMaxSize: 'Upload limit', + indexHelper: + 'In order to ensure the normal operation of the PHP website, please place the code in the index directory and avoid renaming', + extensions: 'Manage extension templates', + extension: 'Extension', + extensionHelper: 'Please use multiple extensions, split', + toExtensionsList: 'View extension list', + containerConfig: 'Container Configuration', + containerConfigHelper: + 'Environment variables and other information can be modified in Configuration - Container Configuration after creation', + dateTimezoneHelper: 'Example: TZ=Asia/Shanghai (Please add as needed)', + }, + nginx: { + serverNamesHashBucketSizeHelper: 'The hash table size of the server name', + clientHeaderBufferSizeHelper: 'The header buffer size requested by the client', + clientMaxBodySizeHelper: 'Maximum Upload File', + keepaliveTimeoutHelper: 'Connection Timeout', + gzipMinLengthHelper: 'Minimum Compressed File', + gzipCompLevelHelper: 'Compression Rate', + gzipHelper: 'Enable compression for transmission', + connections: 'Active connections', + accepts: 'Accepts', + handled: 'Handled', + requests: 'Requests', + reading: 'Reading', + writing: 'Writing', + waiting: 'Waiting', + status: 'Current Status', + configResource: 'Configuration', + saveAndReload: 'Save and reload', + clearProxyCache: 'Clean reverse proxy cache', + clearProxyCacheWarn: 'This action will delete all files in the cache directory. Do you want to continue?', + create: 'Add a new module', + update: 'Edit a module', + params: 'Parameters', + packages: 'Packages', + script: 'Scripts', + module: 'Modules', + build: 'Build', + buildWarn: + 'Building OpenResty requires reserving a certain amount of CPU and memory, which may take a long time, please be patient', + mirrorUrl: 'Software Source', + paramsHelper: 'For example: --add-module=/tmp/ngx_brotli', + packagesHelper: 'For example: git, curl (separated by commas)', + scriptHelper: + 'Scripts to execute before compilation, usually for downloading module source code, installing dependencies, etc.', + buildHelper: + 'Click build after adding/modifying a module. OpenResty will automatically restart upon successful build.', + defaultHttps: 'HTTPS Anti-tampering', + defaultHttpsHelper1: 'Enabling this can resolve HTTPS tampering issues.', + sslRejectHandshake: 'Reject default SSL handshake', + sslRejectHandshakeHelper: + 'Enabling this can avoid certificate leakage, setting a default website will invalidate this setting', + }, + ssl: { + create: 'Request', + provider: 'Type', + manualCreate: 'Created manually', + acmeAccount: 'ACME account', + resolveDomain: 'Resolve domain name', + err: 'Error', + value: 'record value', + dnsResolveHelper: 'Please go to the DNS resolution service provider to add the following resolution records:', + detail: 'View details', + msg: 'Information', + ssl: 'Certificate', + key: 'Private key', + startDate: 'Effective time', + organization: 'issuing organization', + renewConfirm: 'This will renew a new certificate for domain name {0}. Do you want to continue?', + autoRenew: 'Automatic renewal', + autoRenewHelper: 'Automatically renew 30 days before expiration', + renewSuccess: 'Renewal successful', + renewWebsite: + 'This certificate has been associated with the following websites, and the application will be applied to these websites simultaneously', + createAcme: 'Create Account', + acmeHelper: 'Acme Account is used to apply for free certificates', + upload: 'Import', + applyType: 'Application method', + apply: 'Renew', + applyStart: 'Certificate application starts', + getDnsResolve: 'Getting DNS resolution value, please wait...', + selfSigned: 'Manage Self-signed CA', + ca: 'Certificate authority', + commonName: 'Common name', + caName: 'Certificate authority name', + company: 'Organization name', + department: 'Organizational unit name', + city: 'Locality name', + province: 'State or province name', + country: 'Country name (2 letter code)', + commonNameHelper: 'For example, ', + selfSign: 'Issue certificate', + days: 'validity period', + domainHelper: 'One domain name per line, supports * and IP address', + pushDir: 'Push the certificate to the local directory', + dir: 'Directory', + pushDirHelper: + 'Certificate file "fullchain.pem" and key file "privkey.pem" will be generated in this directory.', + organizationDetail: 'Organization details', + fromWebsite: 'From website', + dnsMauanlHelper: + 'In manual resolution mode, you need to click the apply button after creation to obtain the DNS resolution value', + httpHelper: + 'Using HTTP mode requires installing OpenResty and does not support applying for wildcard domain certificates.', + buypassHelper: `Buypass isn't accessible in mainland China`, + googleHelper: 'How to get EAB HmacKey and EAB kid', + googleCloudHelper: `Google Cloud API isn't accessible in most parts of mainland China`, + skipDNSCheck: 'Skip DNS check', + skipDNSCheckHelper: 'Check here only if you encounter a timeout issue during certification request.', + cfHelper: 'Do not use Global API Key', + deprecated: 'will be deprecated', + deprecatedHelper: + 'Maintenance has been stopped and may be abandoned in a future version. Please use Tencent Cloud method for analysis', + disableCNAME: 'Disable CNAME', + disableCNAMEHelper: 'Check here if the domain name has a CNAME record and the request fails.', + nameserver: 'DNS server', + nameserverHelper: 'Use a custom DNS server to verify domain names.', + edit: 'Edit certificate', + execShell: 'Execute the script after certification request.', + shell: 'Script content', + shellHelper: + 'The default execution directory of the script is the 1Panel installation directory. If a certificate is pushed into local directory, the execution directory will be the certificate push directory. The default execution timeout is 30 minutes.', + customAcme: 'Custom ACME Service', + customAcmeURL: 'ACME Service URL', + baiduCloud: 'Baidu Cloud', + pushNode: 'Sync to Other Nodes', + pushNodeHelper: 'Push to selected nodes after application/renewal', + fromMaster: 'Master Node Push', + hostedZoneID: 'Hosted Zone ID', + isIP: 'IP Certificate', + useEAB: 'Use EAB authentication', + }, + firewall: { + create: 'Create rule', + edit: 'Edit rule', + advancedControl: 'Advanced Control', + advancedControlNotAvailable: 'Currently using {0} firewall, advanced rules only support iptables', + ccDeny: 'CC Protection', + ipWhiteList: 'IP allowlist', + ipBlockList: 'IP blocklist', + fileExtBlockList: 'File extension blocklist', + urlWhiteList: 'URL allowlist', + urlBlockList: 'URL blocklist', + argsCheck: 'GET parameter check', + postCheck: 'POST parameter verification', + cookieBlockList: 'Cookie blocklist', + + dockerHelper: + 'The current firewall cannot disable container port mapping. Installed applications can go to the [Installed] page to edit application parameters and configure port release rules.', + iptablesHelper: + 'Detected that the system is using {0} firewall. To switch to iptables, please uninstall it manually first!', + quickJump: 'Quick access', + used: 'Used', + unUsed: 'Unused', + dockerRestart: 'Firewall operations require restarting the Docker service', + firewallHelper: '{0} system firewall', + firewallNotStart: `The system firewall isn't enabled at present. Enable it first.`, + restartFirewallHelper: 'This operation will restart the current firewall. Do you want to continue?', + stopFirewallHelper: 'This will make the server lose security protection. Do you want to continue?', + startFirewallHelper: + 'After the firewall is enabled, the server security can be better protected. Do you want to continue?', + noPing: 'Disable ping', + enableBanPing: 'Block Ping', + disableBanPing: 'Unblock Ping', + noPingTitle: 'Disable ping', + noPingHelper: `This will disable ping, and the server won't echo ICMP response. Do you want to continue?`, + onPingHelper: 'This will enable ping, and hackers may discover your server. Do you want to continue?', + changeStrategy: 'Change the {0} strategy', + changeStrategyIPHelper1: + 'Change the IP address strategy to [deny]. After the IP address is set, access to the server is prohibited. Do you want to continue?', + changeStrategyIPHelper2: + 'Change the IP address strategy to [allow]. After the IP address is set, normal access is restored. Do you want to continue?', + changeStrategyPortHelper1: + 'Change the port policy to [drop]. After the port policy is set, external access is denied. Do you want to continue?', + changeStrategyPortHelper2: + 'Change the port policy to [accept]. After the port policy is set, normal port access will be restored. Do you want to continue?', + stop: 'Stop', + portFormatError: 'This field must be a valid port.', + portHelper1: 'Multiple ports, e.g. 8080 and 8081', + portHelper2: 'Range port, e.g. 8080-8089', + changeStrategyHelper: + 'Change [{1}] {0} strategy to [{2}]. After setting, {0} will access {2} externally. Do you want to continue?', + + strategy: 'Strategy', + accept: 'Accept', + drop: 'Drop', + anyWhere: 'Any', + address: 'Specified IPs', + addressHelper: 'Support IP address or IP segment', + allow: 'Allow', + deny: 'Deny', + addressFormatError: 'This field must be a valid IP address.', + addressHelper1: 'Support IP address or IP range. For example, "172.16.10.11" or "172.16.10.0/24".', + addressHelper2: 'For multiple IP addresses, separate with comma. For example, "172.16.10.11, 172.16.0.0/24".', + allIP: 'All IP', + portRule: 'Rule | Rules', + createPortRule: '@:commons.button.create @.lower:firewall.portRule', + forwardRule: 'Port-Forward rule | Port-Forward rules', + createForwardRule: '@:commons.button.create @:firewall.forwardRule', + ipRule: 'IP rule | IP rules', + createIpRule: '@:commons.button.create @:firewall.ipRule', + userAgent: 'User-Agent filter', + sourcePort: 'Source port', + targetIP: 'Destination IP', + targetPort: 'Destination port', + forwardHelper1: 'If you want to forward to the local port, the destination IP should be set to "127.0.0.1".', + forwardHelper2: 'Leave the destination IP blank to forward to the local port.', + forwardPortHelper: 'Supports port ranges, e.g. 8080-8089', + forwardInboundInterface: 'Forward Inbound Network Interface', + exportHelper: 'About to export {0} firewall rules. Continue?', + importSuccess: 'Successfully imported {0} rules', + importPartialSuccess: 'Import completed: {0} succeeded, {1} failed', + + ipv4Limit: 'The current operation only supports IPv4 addresses', + basicStatus: 'Current chain {0} is unbound, please bind first!', + baseIptables: 'iptables Service', + forwardIptables: 'iptables Port Forwarding Service', + advanceIptables: 'iptables Advanced Configuration Service', + initMsg: 'About to initialize {0}, continue?', + initHelper: + 'Detected that {0} is not initialized. Please click the initialization button in the top status bar to configure!', + bindHelper: 'Bind - Firewall rules will only take effect when the status is bound. Confirm?', + unbindHelper: + 'Unbind - When unbound, all added firewall rules will become invalid. Proceed with caution. Confirm?', + defaultStrategy: 'Default policy for current chain {0} is {1}', + defaultStrategy2: + 'Default policy for current chain {0} is {1}, current status is unbound. Added firewall rules will take effect after binding!', + filterRule: 'Filter Rule', + filterHelper: + 'Filter rules allow you to control network traffic at the INPUT/OUTPUT level. Configure carefully to avoid locking the system.', + chain: 'Chain', + targetChain: 'Target Chain', + sourceIP: 'Source IP', + destIP: 'Destination IP', + inboundDirection: 'Inbound Direction', + outboundDirection: 'Outbound Direction', + destPort: 'Destination Port', + action: 'Action', + reject: 'Reject', + sourceIPHelper: 'CIDR format, e.g., 192.168.1.0/24. Leave empty for all addresses', + destIPHelper: 'CIDR format, e.g., 10.0.0.0/8. Leave empty for all addresses', + portHelper: '0 means any port', + allPorts: 'All Ports', + deleteRuleConfirm: 'Will delete {0} rules. Continue?', + }, + runtime: { + runtime: 'Runtime', + workDir: 'Working directory', + create: 'Create', + localHelper: 'For local environment installation and offline environment usage issues, please refer to ', + versionHelper: 'PHP version, e.g. v8.0', + buildHelper: `If more extensions are selected, the CPU usage will be higher during the image creation process. Avoid selecting all extensions.`, + openrestyWarn: 'PHP needs to be upgraded to OpenResty to version 1.21.4.1 or later to use', + toupgrade: 'To Upgrade', + edit: 'Edit runtime', + extendHelper: `If the extensions you need are not in the list, you can manually input the extension name. For example, input "sockets", then select the first one.`, + rebuildHelper: 'After editing the extension, you need to rebuild the PHP application to take effect', + rebuild: 'Rebuild PHP App', + source: 'PHP extension source', + ustc: 'University of Science and Technology of China', + netease: 'Netease', + aliyun: 'Alibaba Cloud', + tsinghua: 'Tsinghua University', + xtomhk: 'XTOM Mirror Station (Hong Kong)', + xtom: 'XTOM Mirror Station (Global)', + phpsourceHelper: 'Choose a proper source according to your network environment.', + appPort: 'App port', + externalPort: 'External port', + packageManager: 'Package manager', + codeDir: 'Code directory', + appPortHelper: 'The port used by the application.', + externalPortHelper: 'The port exposed to the outside world.', + runScript: 'Run script', + runScriptHelper: 'The startup command list is parsed from the package.json file in the source directory.', + open: 'Open', + operatorHelper: + 'The {0} operation will be performed on the selected operating environment. Do you want to continue? ', + taobao: 'Taobao', + tencent: 'Tencent', + imageSource: 'Image source', + moduleManager: 'Module Management', + module: 'Module', + nodeOperatorHelper: + 'Is {0} {1} module? The operation may cause abnormality in the operating environment, please confirm before proceeding', + customScript: 'Custom startup command', + customScriptHelper: + 'Please enter the complete startup command, for example: npm run start. For PM2 startup commands, please replace with pm2-runtime, otherwise it will fail to start.', + portError: `Don't repeat the same port.`, + systemRestartHelper: 'Status description: Interruption - status acquisition failed due to system restart', + javaScriptHelper: 'Provide a full startup command. For example, "java -jar halo.jar -Xmx1024M -Xms256M".', + javaDirHelper: 'The directory must contain jar files, subdirectories are also acceptable', + goHelper: 'Provide a full startup command. For example, "go run main.go" or "./main".', + goDirHelper: 'The directory or subdirectory must contain Go or binary files.', + extension: 'Extension', + installExtension: 'Do you confirm to install the extension {0}', + loadedExtension: 'Loaded Extension', + popularExtension: 'Popular Extension', + uninstallExtension: 'Are you sure you want to uninstall the extension {0}', + phpConfigHelper: + 'Modifying the configuration requires restarting the operating environment, do you want to continue', + operateMode: 'operation mode', + dynamic: 'dynamic', + static: 'static', + ondemand: 'on-demand', + dynamicHelper: + 'dynamically adjust the number of processes, high flexibility, suitable for websites with large traffic fluctuations or low memory', + staticHelper: + 'fixed number of processes, suitable for websites with high concurrency and stable traffic, high resource consumption', + ondemandHelper: + 'processes are started and destroyed on demand, resource utilization is optimal, but the initial response may be slow', + max_children: 'maximum number of processes allowed to be created', + start_servers: 'number of processes created at startup', + min_spare_servers: 'minimum number of idle processes', + max_spare_servers: 'maximum number of idle processes', + envKey: 'Name', + envValue: 'Value', + environment: 'Environment Variable', + pythonHelper: + 'Provide a full startup command. For example, "pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000".', + dotnetHelper: 'Provide a full startup command. For example, "dotnet MyWebApp.dll".', + dirHelper: 'Note: Please fill in the directory path inside the container', + concurrency: 'Concurrency Scheme', + loadStatus: 'Load Status', + extraHosts: 'Host mapping', + }, + process: { + pid: 'Process ID', + ppid: 'Parent process ID', + numThreads: 'Threads', + memory: 'Memory', + diskRead: 'Disk read', + diskWrite: 'Disk write', + netSent: 'uplink', + netRecv: 'downstream', + numConnections: 'Connections', + startTime: 'Start time', + running: 'Running', + sleep: 'sleep', + stop: 'stop', + idle: 'idle', + zombie: 'zombie process', + wait: 'waiting', + lock: 'lock', + blocked: 'blocked', + cmdLine: 'Start command', + basic: 'Basic', + mem: 'Memory', + openFiles: 'Open files', + env: 'Environments', + noenv: 'None', + net: 'Network connections', + laddr: 'Local address/port', + raddr: 'Remote address/port', + stopProcess: 'End', + viewDetails: 'View details', + stopProcessWarn: 'Are you sure you want to end this process (PID:{0})?', + kill: 'Kill Process', + killNow: 'Kill Now', + killHelper: 'Killing process {0} may cause some programs to malfunction. Continue?', + processName: 'Process name', + }, + tool: { + supervisor: { + loadStatusErr: 'Failed to retrieve process status, please check the status of the supervisor service.', + notSupport: 'Supervisor service not detected, please go to the script library page to install it manually', + list: 'Daemon process', + config: 'Supervisor configuration', + primaryConfig: 'Main configuration file location', + notSupportCtl: `The supervisorctl isn't detected, please go to the script library page to install it manually`, + user: 'User', + command: 'Command', + dir: 'Directory', + numprocs: 'Number of process', + initWarn: + 'This will modify "files" value in "[include"] section in the main configuration file. The directory of other configuration file will be: "{1Panel installation directory}/1panel/tools/supervisord/supervisor.d/".', + operatorHelper: 'Operation {1} will be performed on {0}, continue? ', + uptime: 'Running time', + notStartWarn: `Supervisor isn't started. Start it first.`, + serviceName: 'Service name', + initHelper: + 'Supervisor service is detected but not initialized. Please click the initialization button in the top status bar to configure it.', + serviceNameHelper: 'Supervisor service name managed by systemctl, usually supervisor or supervisord', + restartHelper: + 'This will restart the service after initialization, which causes all the existing daemon processes to stop.', + RUNNING: 'Running', + STOPPED: 'Stopped', + STOPPING: 'Stopping', + STARTING: 'Starting', + FATAL: 'Failed to start', + BACKOFF: 'Start exception', + ERROR: 'Error', + statusCode: 'Status code', + manage: 'Management', + autoRestart: 'Auto Restart', + EXITED: 'Exited', + autoRestartHelper: 'Whether to automatically restart the program after it crashes', + autoStart: 'Auto Start', + autoStartHelper: 'Whether to automatically start the service after Supervisor starts', + }, + }, + disk: { + management: 'Disk Management', + partition: 'Partition', + unmount: 'Unmount', + unmountHelper: 'Do you want to unmount the partition {0}?', + mount: 'Mount', + partitionAlert: + 'Disk partitioning requires formatting the disk, and existing data will be deleted. Please save or take snapshots of your data in advance.', + mountPoint: 'Mount Directory', + systemDisk: 'System Disk', + unpartitionedDisk: 'Unpartitioned Disk', + handlePartition: 'Partition Now', + filesystem: 'Filesystem', + unmounted: 'Unmounted', + cannotOperate: 'Cannot Operate', + systemDiskHelper: 'Hint: The current disk is the system disk. It cannot be operated on.', + autoMount: 'Auto Mount', + model: 'Device Model', + diskType: 'Disk Type', + serial: 'Serial Number', + noFail: 'Mount failure does not affect system startup', + }, + xpack: { + expiresTrialAlert: + 'Friendly reminder: Your Pro trial will expire in {0} days, and all Pro features will no longer be accessible. Please renew or upgrade to the full version in a timely manner.', + expiresAlert: + 'Friendly reminder: Your Pro license will expire in {0} days, and all Pro features will no longer be accessible. Please renew promptly to ensure continued usage.', + menu: 'Pro', + upage: 'AI Website Builder', + proAlert: 'Upgrade to Pro to use this feature', + app: { + app: 'APP', + title: 'Panel Alias', + titleHelper: 'The panel alias is used for display in the APP (default panel alias)', + qrCode: 'QR Code', + apiStatusHelper: 'The Panel APP needs to enable the API interface feature', + apiInterfaceHelper: + 'Supports panel API interface access (this feature needs to be enabled for the panel app)', + apiInterfaceHelper1: + "Panel app access requires adding the visitor to the whitelist; for non-fixed IPs, it's recommended to add 0.0.0.0/0 (all IPv4), ::/0 (all IPv6)", + qrCodeExpired: 'Refresh time', + apiLeakageHelper: 'Do not disclose the QR code. Ensure it is used only in trusted environments.', + }, + waf: { + name: 'WAF', + blackWhite: 'Black and White List', + globalSetting: 'Global Settings', + websiteSetting: 'Website Settings', + blockRecords: 'Blocked Records', + world: 'World', + china: 'China', + intercept: 'Interception', + request: 'Requests', + count4xx: '4xx Quantity', + count5xx: '5xx Quantity', + todayStatus: "Today's Status", + reqMap: 'Attack Map (Last 30 days)', + resource: 'Source', + count: 'Quantity', + hight: 'High', + low: 'Low', + reqCount: 'Requests', + interceptCount: 'Interception Number', + requestTrends: 'Request Trends (Last 7 Days)', + interceptTrends: 'Interception Trends (Last 7 Days)', + whiteList: 'Whitelist', + blackList: 'Blacklist', + ipBlackListHelper: 'IP addresses in the blacklist are blocked from accessing the website', + ipWhiteListHelper: 'IP addresses in the whitelist bypass all restrictions', + uaBlackListHelper: 'Requests with User-Agent values in the blacklist will be blocked', + uaWhiteListHelper: 'Requests with User-Agent values in the whitelist bypass all restrictions', + urlBlackListHelper: 'Requests to URLs in the blacklist will be blocked', + urlWhiteListHelper: 'Requests to URLs in the whitelist bypass all restrictions', + ccHelper: + 'If a website receives more than {1} requests from the same IP within {0} seconds, the IP will be blocked for {2}', + blockTime: 'Blocking Duration', + attackHelper: 'If cumulative interceptions exceed {1} within {0} seconds, the IP will be blocked for {2}', + notFoundHelper: + 'If cumulative requests return 404 errors more than {1} times within {0} seconds, the IP will be blocked for {2}', + frequencyLimit: 'Frequency Limit', + regionLimit: 'Region Limit', + defaultRule: 'Default Rules', + accessFrequencyLimit: 'Access Frequency Limit', + attackLimit: 'Attack Frequency Limit', + notFoundLimit: '404 Frequency Limit', + urlLimit: 'URL Frequency Limit', + urlLimitHelper: 'Set access frequency for a single URL', + sqliDefense: 'SQL Injection Protection', + sqliHelper: 'Detect SQL injection in requests and block them', + xssHelper: 'Detect XSS in requests and block them', + xssDefense: 'XSS Protection', + uaDefense: 'Malicious User-Agent Rules', + uaHelper: 'Includes rules to identify common malicious bots', + argsDefense: 'Malicious Parameter Rules', + argsHelper: 'Blocks requests containing malicious parameters', + cookieDefense: 'Malicious Cookie Rules', + cookieHelper: 'Prohibit malicious cookies from being carried in requests', + headerDefense: 'Malicious Header Rules', + headerHelper: 'Prohibit requests from containing malicious headers', + httpRule: 'HTTP Request Method Rules', + httpHelper: + 'Set the method types that are allowed to access. If you want to restrict certain types of access, please turn off this type of button. For example: only GET type access is allowed, then you need to turn off other types of buttons except GET', + geoRule: 'Regional Access Restrictions', + geoHelper: + 'Restrict access to your website from certain regions, for example: if access is allowed from mainland China, then requests from outside mainland China will be blocked', + ipLocation: 'IP Location', + action: 'Action', + ruleType: 'Attack Type', + ipHelper: 'Enter the IP address', + attackLog: 'Attack Log', + rule: 'Rule', + ipArr: 'IPV4 Range', + ipStart: 'Start IP', + ipEnd: 'End IP', + ipv4: 'IPv4', + ipv6: 'IPv6', + urlDefense: 'URL Rules', + urlHelper: 'Forbidden URL', + dirFilter: 'Directory Filter', + sqlInject: 'SQL Injection', + xss: 'XSS', + phpExec: 'PHP Script Execution', + oneWordTrojan: 'One word Trojan', + appFilter: 'Dangerous Directory Filtering', + webshell: 'Webshell', + args: 'Malicious Parameters', + protocolFilter: 'Protocol Filter', + javaFilter: 'Java Dangerous File Filtering', + scannerFilter: 'Scanner Filter', + escapeFilter: 'Escape Filter', + customRule: 'Custom Rules', + httpMethod: 'HTTP Method Filter', + fileExt: 'File Upload Limit', + fileExtHelper: 'Prohibited file extensions for upload', + deny: 'Forbidden', + allow: 'Allow', + field: 'Object', + pattern: 'Condition', + ruleContent: 'Content', + contain: 'include', + equal: 'equal', + regex: 'regular expression', + notEqual: 'Not equal to', + customRuleHelper: 'Take actions based on specified conditions', + actionAllow: 'Allow', + blockIP: 'Block IP', + code: 'Return Status Code', + noRes: 'Disconnect (444)', + badReq: 'Invalid Parameters (400)', + forbidden: 'Access Forbidden (403)', + serverErr: 'Server Error (500)', + resHtml: 'Response Page', + allowHelper: 'Allowing access will skip subsequent WAF rules, please use with caution', + captcha: 'human-machine verification', + fiveSeconds: '5-Seconds verification', + location: 'Region', + redisConfig: 'Redis Configuration', + redisHelper: 'Enable Redis to persist temporarily blocked IPs', + wafHelper: 'All websites will lose protection after closing', + attackIP: 'Attacking IP', + attackParam: 'Attack Details', + execRule: 'Hit Rule', + acl: 'ACL', + sql: 'SQL Injection', + cc: 'Access Frequency Limit', + isBlocking: 'Blocked', + isFree: 'Unblocked', + unLock: 'Unlock', + unLockHelper: 'Do you want to unblock IP: {0}?', + saveDefault: 'Save Default', + saveToWebsite: 'Apply to Website', + saveToWebsiteHelper: 'Apply current settings to all websites? ', + websiteHelper: + 'Here are the default settings for creating a website. Modifications need to be applied to the website to take effect', + websiteHelper2: + 'Here are the default settings for creating a website. Please modify the specific configuration at the website', + ipGroup: 'IP Group', + ipGroupHelper: + 'One IP or IP segment per line, supports IPv4 and IPv6, for example: 192.168.1.1 or 192.168.1.0/24', + ipBlack: 'IP blacklist', + openRestyAlert: 'OpenResty version needs to be higher than {0}', + initAlert: + 'Initialization is required for first-time use, the website configuration file will be modified, and the original WAF configuration will be lost. Please be sure to back up OpenResty in advance', + initHelper: + 'The initialization operation will clear the existing WAF configuration. Are you sure you want to initialize? ', + mainSwitch: 'Main Switch', + websiteAlert: 'Please create a website first', + defaultUrlBlack: 'URL Rules', + htmlRes: 'Intercept Page', + urlSearchHelper: 'Please enter the URL to support fuzzy search', + toCreate: 'Create', + closeWaf: 'Close WAF', + closeWafHelper: 'Closing WAF will cause the website to lose protection, do you want to continue', + addblack: 'Black', + addwhite: 'Add white', + addblackHelper: 'Add IP:{0} to the default blacklist?', + addwhiteHelper: 'Add IP:{0} to the default whitelist?', + defaultUaBlack: 'User-Agent rule', + defaultIpBlack: 'Malicious IP Group', + cookie: 'Cookie Rules', + urlBlack: 'URL Blacklist', + uaBlack: 'User-Agent blacklist', + attackCount: 'Attack frequency limit', + fileExtCheck: 'File upload limit', + geoRestrict: 'Regional access restriction', + attacklog: 'Interception Record', + unknownWebsite: 'Unauthorized domain name access', + geoRuleEmpty: 'Region cannot be empty', + unknown: 'Website Not Exist', + geo: 'Region Restriction', + revertHtml: 'Do you want to restore {0} as the default page?', + five_seconds: '5-Second verification', + header: 'Header rules', + methodWhite: 'HTTP rules', + expiryDate: 'Expiration date', + expiryDateHelper: + 'After passing the verification, it will no longer be verified within the validity period', + defaultIpBlackHelper: 'Some malicious IPs collected from the Internet to prevent access', + notFoundCount: '404 Frequency Limit', + matchValue: 'Match value', + headerName: 'Supports non-special characters starting with English, numbers, -, length 3-30', + cdnHelper: 'Websites using CDN can open here to obtain the correct source IP', + clearLogWarn: 'Clearing the log will not be possible, do you want to continue?', + commonRuleHelper: 'Rule is fuzzy matching', + blockIPHelper: + 'Blocked IPs are temporarily stored in OpenResty and will be unblocked when you restart OpenResty. They can be permanently blocked through the blocking function', + addWhiteUrlHelper: 'Add URL {0} to the whitelist?', + dashHelper: 'The community version can also use the functions in global settings and website settings', + wafStatusHelper: 'WAF is not enabled, please enable it in global settings', + ccMode: 'Mode', + global: 'Global Mode', + uriMode: 'URL Mode', + globalHelper: + 'Global Mode: Triggered when the total number of requests to any URL within a unit of time exceeds the threshold', + uriModeHelper: + 'URL Mode: Triggered when the number of requests to a single URL within a unit of time exceeds the threshold', + + ip: 'IP BlackList', + globalSettingHelper: + 'Settings with the [Website] tag need to be enabled in [Website Settings], and global settings are only the default settings for newly created websites', + globalSettingHelper2: + 'Settings need to be enabled in both [Global Settings] and [Website Settings] at the same time', + urlCCHelper: 'More than {1} requests to this URL within {0} seconds, blocking this IP {2}', + urlCCHelper2: 'URL cannot contain parameters', + notContain: 'Not contain', + urlcc: 'URL frequency limit', + method: 'Request type', + addIpsToBlock: 'Batch block IP', + addUrlsToWhite: 'Batch add URL to white list', + noBlackIp: 'IP is already blocked, no need to block again', + noWhiteUrl: 'URL is already in the white list, no need to add again', + spiderIpHelper: + 'Includes Baidu, Bing, Google, 360, Shenma, Sogou, ByteDance, DuckDuckGo, Yandex. Closing this will block all spider access.', + spiderIp: 'Spider IP Pool', + geoIp: 'IP Address Library', + geoIpHelper: 'Used to confirm the geolocation of the IP', + stat: 'Attack Report', + statTitle: 'Report', + attackIp: 'IP', + attackCountNum: 'Counts', + percent: 'Percentage', + addblackUrlHelper: 'Whether to add URL: {0} to the default blacklist?', + rce: 'Remote Code Execution', + software: 'Software', + cveHelper: 'Contains vulnerabilities of common software and frameworks', + vulnCheck: 'Supplementary Rules', + ssrf: 'SSRF Vulnerability', + afr: 'Arbitrary File Read', + ua: 'Unauthorized Access', + id: 'Information Disclosure', + aa: 'Authentication Bypass', + dr: 'Directory Traversal', + xxe: 'XXE Vulnerability', + suid: 'Serialization Vulnerability', + dos: 'Denial of Service Vulnerability', + afd: 'Arbitrary File Download', + sqlInjection: 'SQL Injection', + afw: 'Arbitrary File Write', + il: 'Information Leak', + clearAllLog: 'Clear all logs', + exportLog: 'Export logs', + appRule: 'Application Rules', + appRuleHelper: + 'Common application rules, enabling can reduce false positives, one website can only use one rule', + logExternal: 'Exclude Record Types', + ipWhite: 'IP White List', + urlWhite: 'URL White List', + uaWhite: 'User-Agent White List', + logExternalHelper: + 'Excluded record types will not be recorded in logs, blacklist/whitelist, regional access restrictions, and custom rules will generate a lot of logs, it is recommended to exclude', + ssti: 'SSTI Attack', + crlf: 'CRLF Injection', + strict: 'Strict Mode', + strictHelper: 'Use stricter rules to validate requests', + saveLog: 'Save Log', + remoteURLHelper: 'The remote URL needs to ensure one IP per line and no other characters', + notFound: 'Not Found (404)', + serviceUnavailable: 'Service Unavailable (503)', + gatewayTimeout: 'Gateway Timeout (504)', + belongToIpGroup: 'Belongs to IP Group', + notBelongToIpGroup: 'Does not belong to IP Group', + unknownWebsiteKey: 'Unknown Domain', + special: 'Special', + fileToLarge: 'File exceeds 1MB and cannot be uploaded', + uploadOverLimit: 'Uploaded file exceeds the quantity limit, maximum 1 file', + importRuleHelper: 'One rule per line', + }, + monitor: { + name: 'Website Monitoring', + pv: 'Page Views', + uv: 'Unique Visitors', + flow: 'Traffic Flow', + ip: 'IP', + spider: 'Spider', + visitors: 'Visitor Trends', + today: 'Today', + last7days: 'Last 7 Days', + last30days: 'Last 30 Days', + uvMap: 'Visitor Map (30th)', + qps: 'Real-time Requests (per minute)', + flowSec: 'Real-time Traffic (per minute)', + excludeCode: 'Exclude Status Codes', + excludeUrl: 'Exclude URLs', + excludeExt: 'Exclude Extensions', + cdnHelper: 'Obtain the real IP from the CDN-provided Header', + reqRank: 'Visit Ranking', + refererDomain: 'Referrer Domain', + os: 'System', + browser: 'Browser/Client', + device: 'Device', + showMore: 'More', + unknown: 'Other', + pc: 'Computer', + mobile: 'Mobile Device', + wechat: 'WeChat', + machine: 'machine', + tencent: 'Tencent Browser', + ucweb: 'UC Browser', + '2345explorer': '2345 browser', + huaweibrowser: 'Huawei Browser', + log: 'Request Logs', + statusCode: 'Status Code', + requestTime: 'Response Time', + flowRes: 'Response Traffic', + method: 'Request Method', + statusCodeHelper: 'Enter the status code above', + statusCodeError: 'Invalid status code type', + methodHelper: 'Enter the request method above', + all: 'All', + baidu: 'Baidu', + google: 'Google', + bing: 'Bing', + bytes: 'Today headlines', + sogou: 'Sogou', + failed: 'Error', + ipCount: 'IP Count', + spiderCount: 'Spider Requests', + averageReqTime: 'Average Response Time', + totalFlow: 'Total Flow', + logSize: 'Log File Size', + realIPType: 'Real IP acquisition method', + fromHeader: 'Get from HTTP Header', + fromHeaders: 'Get from Header list', + header: 'HTTP Header', + cdnConfig: 'CDN Configuration', + xff1: 'First-level Proxy from X-Forwarded-For', + xff2: 'Second-level Proxy from X-Forwarded-For', + xff3: 'Third-level Proxy from X-Forwarded-For', + xffHelper: + 'For example: X-Forwarded-For: ,,, The upper level proxy will take the last IP ', + headersHelper: + 'Obtain the real IP from commonly used CDN HTTP headers, selecting the first available value', + monitorCDNHelper: + 'Modifying the CDN configuration for website monitoring will also update the WAF CDN settings', + wafCDNHelper: 'Modifying the WAF CDN configuration will also update the website monitoring CDN settings', + statusErr: 'Invalid status code format', + shenma: 'Shenma Search', + duckduckgo: 'DuckDuckGo', + '360': '360 Search', + excludeUri: 'Exclude URIs', + top100Helper: 'Show the top 100 data', + logSaveDay: 'Log Retention Period (days)', + cros: 'Chrome OS', + theworld: 'TheWorld Browser', + edge: 'Microsoft Edge', + maxthon: 'Maxthon Browser', + monitorStatusHelper: 'Monitoring is not enabled, please enable it in settings', + excludeIp: 'Exclude IP Addresses', + excludeUa: 'Exclude User-Agent', + remotePort: 'Remote Port', + unknown_browser: 'Unknown', + unknown_os: 'Unknown', + unknown_device: 'Unknown', + logSaveSize: 'Maximum Log Save Size', + logSaveSizeHelper: 'This is the log save size for a single website', + '360se': '360 Security Browser', + websites: 'Website List', + trend: 'Trend Statistics', + reqCount: 'Request Count', + uriHelper: 'You can use /test/* or /*/index.php to exclude Uri', + }, + tamper: { + tamper: 'Website Tamper Protection', + ignoreTemplate: 'Plantilla de Exclusión', + protectTemplate: 'Plantilla de Protección', + ignoreTemplateHelper: + 'Ingrese contenido de exclusión, separado por Enter o espacio. (Directorio específico ./log o nombre de directorio tmp, para excluir archivos necesita ingresar archivo específico ./data/test.html)', + protectTemplateHelper: + 'Ingrese contenido de protección, separado por Enter o espacio. (Archivo específico ./index.html, extensión de archivo .html, tipo de archivo js, para proteger directorios necesita ingresar directorio específico ./log)', + templateContent: 'Contenido de Plantilla', + template: 'Plantilla', + saveTemplate: 'Guardar como Plantilla', + tamperHelper1: + 'Para sitios web de implementación con un clic, se recomienda habilitar la protección contra manipulaciones del directorio de aplicaciones; si el sitio web no se puede usar normalmente o falla la copia de seguridad/restauración, desactive primero la protección contra manipulaciones;', + tamperHelper2: + 'Restringirá las operaciones de lectura/escritura, eliminación, permisos y modificación de propietario para archivos protegidos en directorios no excluidos', + tamperPath: 'Directorio de Protección', + tamperPathEdit: 'Modificar Ruta', + log: 'Registro de Bloqueo', + totalProtect: 'Protección Total', + todayProtect: 'Protección de Hoy', + templateRule: 'Longitud 1-512, el nombre no puede contener {0} y otros símbolos', + ignore: 'Excluir', + ignoreHelper: + 'Seleccione o ingrese contenido de exclusión, separado por Enter o espacio. (Directorio específico ./log o nombre de directorio tmp, para excluir archivos necesita ingresar o seleccionar archivo específico ./data/test.html)', + protect: 'Proteger', + protectHelper: + 'Seleccione o ingrese contenido de protección, separado por Enter o espacio. (Archivo específico ./index.html, extensión de archivo .html, tipo de archivo js, para proteger directorios necesita ingresar o seleccionar directorio específico ./log)', + tamperHelper00: 'La exclusión y protección solo admiten rutas relativas;', + tamperHelper01: + 'Después de habilitar la protección contra manipulaciones, el sistema restringirá las operaciones de creación, edición y eliminación de archivos protegidos en directorios no excluidos;', + tamperHelper02: + 'Prioridad: Protección de ruta específica > Exclusión de ruta específica > Protección > Exclusión', + tamperHelper03: + 'Monitoring operations only target non-excluded directories, monitoring the creation of non-protected files in these directories.', + disableHelper: 'About to disable tamper protection for the following websites, continue?', + appendOnly: 'Append Only', + appendOnlyHelper: + 'Restrict deletion operations for files in this directory, only allowing addition of excluded directories or non-protected files', + immutable: 'Immutable', + immutableHelper: 'Restrict editing, deletion, permission, and owner modification operations for this file', + onWatch: 'Watch', + onWatchHelper: + 'Monitor and intercept creation of protected files or non-excluded directories in this directory', + forceStop: 'Force Close', + forceStopHelper: 'About to force disable the anti-tamper function for this website directory. Continue?', + }, + setting: { + setting: 'Panel Settings', + title: 'Panel Description', + titleHelper: + 'Will be displayed on the user login page (e.g. Linux server operation and maintenance management panel, recommended 8-15 characters)', + logo: 'Logo (Without Text)', + logoHelper: + 'Will be displayed in the upper left corner of the management page when the menu is collapsed (recommended image size: 82px*82px)', + logoWithText: 'Logo (With Text)', + logoWithTextHelper: + 'Will be displayed in the upper left corner of the management page when the menu is expanded (recommended image size: 185px*55px)', + favicon: 'Website Icon', + faviconHelper: 'Website icon (recommended image size: 16px*16px)', + setDefault: 'Restore Default', + setHelper: 'The current settings will be saved. Do you want to continue?', + setDefaultHelper: 'All panel settings will be restored to default. Do you want to continue?', + logoGroup: 'Logo', + imageGroup: 'Image', + loginImage: 'Login Page Image', + loginImageHelper: 'Displayed on the login page (recommended image size: 500*416px)', + loginBgType: 'Login Page Background Type', + loginBgImage: 'Login Page Background Image', + loginBgImageHelper: 'Displayed as background image on the login page (recommended image size: 1920*1080px)', + loginBgColor: 'Login Page Background Color', + loginBgColorHelper: 'Displayed as background color on the login page', + image: 'Image', + bgColor: 'Background Color', + loginGroup: 'Login Page', + loginBtnLinkColor: 'Button/Link Color', + loginBtnLinkColorHelper: 'Will be displayed as the button/link color on the login page', + }, + helper: { + wafTitle1: 'Interception Map', + wafContent1: 'Displays the geographical distribution of interceptions over the past 30 days', + wafTitle2: 'Regional Access Restrictions', + wafContent2: 'Restrict website access sources according to geographical locations', + wafTitle3: 'Custom interception page', + wafContent3: 'Create a custom page to display after a request is intercepted', + wafTitle4: 'Custom Rules (ACL)', + wafContent4: 'Intercept requests according to custom rules', + + tamperTitle1: 'File Integrity Monitoring', + tamperContent1: + 'Monitor the integrity of website files, including core files, script files, and configuration files.', + tamperTitle2: 'Real-time Scanning and Detection', + tamperContent2: 'Detect abnormal or tampered files by real-time scanning the website file system.', + tamperTitle3: 'Security Permission Settings', + tamperContent3: + 'Restrict access to website files through proper permission settings and access control policies, reducing potential attack surface.', + tamperTitle4: 'Logging and Analysis', + tamperContent4: + 'Record file access and operation logs for subsequent auditing and analysis by administrators, as well as to identify potential security threats.', + + settingTitle1: 'Custom Welcome Message', + settingContent1: 'Set a custom welcome message on the 1Panel login page.', + settingTitle2: 'Custom Logo', + settingContent2: 'Allow uploading logo images containing brand names or other text.', + settingTitle3: 'Custom Website Icon', + settingContent3: + 'Allow uploading custom icons to replace the default browser icon, improving user experience.', + + monitorTitle1: 'Visitor Trend', + monitorContent1: 'Statistics and displays website visitor trends', + monitorTitle2: 'Visitor Map', + monitorContent2: 'Statistics and displays the geographical distribution of visitors to the website', + monitorTitle3: 'Access Statistics', + monitorContent3: + 'Statistics on website request information, including spiders, access devices, request status, etc.', + monitorTitle4: 'Real-time monitoring', + monitorContent4: + 'Real-time monitoring of website request information, including number of requests, traffic, etc.', + + alertTitle1: 'SMS Alerts', + alertContent1: + 'When abnormal server resource usage, website and certificate expiration, new version update, password expiration, etc. occur, users will be notified via SMS alarm to ensure timely processing.', + alertTitle2: 'Alert log', + alertContent2: + 'Provide users with the function of viewing alarm logs to facilitate tracking and analysis of historical alarm events.', + alertTitle3: 'Alert Settings', + alertContent3: + 'Provide users with custom phone numbers, daily push frequency, and daily push time configurations, making it easier for users to set up more reasonable push alerts.', + + nodeDashTitle1: 'Application Management', + nodeDashContent1: + 'Unified management of multi-node applications, supports status monitoring, quick start/stop, terminal connection, and backup', + nodeDashTitle2: 'Website Management', + nodeDashContent2: + 'Unified management of multi-node websites, real-time status monitoring, supports batch start/stop and quick backup', + nodeDashTitle3: 'Database Management', + nodeDashContent3: + 'Unified management of multi-node databases, key status at a glance, supports one-click backup', + nodeDashTitle4: 'Scheduled Task Management', + nodeDashContent4: + 'Unified management of multi-node scheduled tasks, supports status monitoring, quick start/stop, and manual trigger execution', + + nodeTitle1: 'One-Click Node Addition', + nodeContent1: 'Quickly integrate multiple server nodes', + nodeTitle2: 'Batch Upgrade', + nodeContent2: 'Synchronize and upgrade all nodes with one operation', + nodeTitle3: 'Node Status Monitoring', + nodeContent3: "Real-time monitoring of each node's operational status", + nodeTitle4: 'Quick Remote Connection', + nodeContent4: 'One-click direct connection to node remote terminals', + + fileExchangeTitle1: 'Key Authentication Transmission', + fileExchangeContent1: 'Authenticate via SSH keys to ensure transmission security.', + fileExchangeTitle2: 'Efficient File Synchronization', + fileExchangeContent2: + 'Only synchronize changed content to significantly improve transmission speed and stability.', + fileExchangeTitle3: 'Support Multi-Node Intercommunication', + fileExchangeContent3: + 'Easily transfer project files between different nodes, flexible management of multiple servers.', + + nodeAppTitle1: 'Application Upgrade Management', + nodeAppContent1: 'Unified monitoring of multi-node application updates, supports one-click upgrade', + + appTitle1: 'Flexible Panel Management', + appContent1: 'Easily manage your 1Panel server anytime, anywhere.', + appTitle2: 'Comprehensive Service Information', + appContent2: + 'Manage basic applications, websites, Docker, databases, etc., and quickly create applications and websites via the mobile app.', + appTitle3: 'Real-Time Abnormal Monitoring', + appContent3: + 'View real-time server status, WAF security monitoring, website traffic statistics, and process health status on the mobile app.', + + clusterTitle1: 'Master-Slave Deployment', + clusterContent1: + 'Supports creating MySQL/Postgres/Redis master-slave instances on different nodes, automatically completing master-slave association and initialization', + clusterTitle2: 'Master-Slave Management', + clusterContent2: + 'Unified page to centrally manage multiple master-slave nodes, view their roles, running status, etc.', + clusterTitle3: 'Replication Status', + clusterContent3: + 'Displays master-slave replication status and delay information, assisting in troubleshooting synchronization issues', + }, + node: { + master: 'Main Node', + masterBackup: 'Master Node Backup', + backupNode: 'Backup Node', + backupFrequency: 'Backup Frequency (hours)', + backupCopies: 'Backup Retention Copies', + noBackupNode: 'The backup node is currently empty. Please select a backup node to save and try again!', + masterBackupAlert: + 'Master node backup is not currently configured. To ensure data security, please set up a backup node as soon as possible to facilitate manual switching to a new master node in case of failure.', + node: 'Node', + addr: 'Address', + nodeUpgrade: 'Update Settings', + nodeUpgradeHelper: + 'Selected nodes will automatically start upgrading after the master node upgrade is completed, no manual operation required.', + nodeUnhealthy: 'Node status abnormal', + deletedNode: 'Deleted node {0} does not currently support upgrade operations!', + nodeUnhealthyHelper: 'Abnormal node status detected. Please check in [Node Management] and try again!', + nodeUnbind: 'Node not bound to license', + nodeUnbindHelper: + 'Detected that this node is not bound to a license. Please bind it in [Panel Settings - License] menu and try again!', + memTotal: 'Total Memory', + nodeManagement: 'Multi-Machine Management', + nodeItem: 'Node Management', + panelItem: 'Panel Management', + addPanel: 'Add Panel', + addPanelHelper: + 'After successfully adding the panel, you can quickly access the target panel in [Overview - Panels].', + panel: '1Panel Panel', + others: 'Other Panels', + addNode: 'Add Node', + connInfo: 'Connection Information', + nodeInfo: 'Node Information', + withProxy: 'Enable Proxy Access', + withoutProxy: 'Disable Proxy Access', + withProxyHelper: + 'Will use the system proxy {0} maintained in panel settings to access child nodes. Continue?', + withoutProxyHelper: + 'Will stop using the system proxy maintained in panel settings to access child nodes. Continue?', + syncInfo: 'Sync', + syncHelper: 'When master node data changes, it synchronizes to this child node in real-time', + syncBackupAccount: 'Backup account settings', + syncWithMaster: + 'After upgrading to Pro, all data will be synced by default. Sync policies can be manually adjusted in node management.', + syncProxy: 'System proxy settings', + syncProxyHelper: 'Syncing system proxy settings requires Docker restart', + syncProxyHelper1: 'Restarting Docker may affect currently running container services.', + syncProxyHelper2: 'You can manually restart in the Containers - Configuration page.', + syncProxyHelper3: + 'Syncing system proxy settings requires Docker restart, which may affect currently running container services', + syncProxyHelper4: + 'Syncing system proxy settings requires Docker restart. You can manually restart later in the Containers - Configuration page.', + syncCustomApp: 'Sync Custom App Repository', + syncAlertSetting: 'System alert settings', + syncNodeInfo: 'Node basic data,', + nodeSyncHelper: 'Node information synchronization will sync the following information:', + nodeSyncHelper1: '1. Public backup account information', + nodeSyncHelper2: '2. Connection information between the main node and sub-nodes', + + nodeCheck: 'Availability check', + checkSSH: 'Check node SSH connection', + checkUserPermission: 'Check node user permissions', + isNotRoot: 'Detected that password-less sudo is not supported on this node and current user is non-root', + checkLicense: 'Check node license status', + checkService: 'Check existing service information on node', + checkPort: 'Check node port reachability', + panelExist: + 'Detected that this node is running 1Panel V1 service. Please upgrade to V2 using the migration script before adding.', + coreExist: + 'The current node is already enabled as a master node and cannot be directly added as a slave node. Please downgrade it to a slave node first before adding, refer to the documentation for details.', + agentExist: + 'Detected that 1panel-agent is already installed on this node. Continuing will retain existing data and only replace the 1panel-agent service.', + agentNotExist: + 'It is detected that 1panel-agent is not installed on this node, so the node information cannot be edited directly. Please delete it and add it again.', + oldDataExist: + 'Detected historical 1Panel V2 data on this node. The following information will be used to overwrite current settings:', + errLicense: 'The license bound to this node is unavailable. Please check and try again!', + errNodePort: + 'Node port [ {0} ] is detected as inaccessible. Please check if the firewall or security group has allowed this port.', + + reinstallHelper: 'Reinstall node {0}, do you want to continue?', + unhealthyCheck: 'Abnormal Check', + fixOperation: 'Fix Operation', + checkName: 'Check Item', + checkSSHConn: 'Check SSH Connection Availability', + fixSSHConn: 'Manually edit the node to confirm connection information', + checkConnInfo: 'Check Agent Connection Information', + checkStatus: 'Check Node Service Availability', + fixStatus: 'Run "systemctl status 1panel-agent.service" to check if the service is running.', + checkAPI: 'Check Node API Availability', + fixAPI: 'Check the node logs and verify if the firewall ports are properly opened.', + forceDelete: 'Force Delete', + operateHelper: 'The following nodes will undergo {0} operation, do you want to continue?', + operatePanelHelper: 'The following panels will undergo {0} operation, do you want to continue?', + forceDeleteHelper: 'Force delete will ignore node deletion errors and delete database metadata', + uninstall: 'Delete node data', + uninstallHelper: 'This will delete all 1Panel related data of the node. Proceed with caution!', + baseDir: 'Installation Directory', + baseDirHelper: + 'When the installation directory is empty, it will be installed in the /opt directory by default', + nodePort: 'Node Port', + offline: 'Offline mode', + freeCount: 'Free quota [{0}]', + offlineHelper: 'Used when the node is in an offline environment', + + appUpgrade: 'App Upgrade', + appUpgradeHelper: 'There are {0} apps that need to be upgraded', + }, + customApp: { + name: 'Custom App Repository', + appStoreType: 'App Store Package Source', + appStoreUrl: 'Repository URL', + local: 'Local Path', + remote: 'Remote Link', + imagePrefix: 'Image Prefix', + imagePrefixHelper: + 'Function: Customize the image prefix and modify the image field in the compose file. For example, when the image prefix is set to 1panel/custom, the image field for MaxKB will change to 1panel/custom/maxkb:v1.10.0', + closeHelper: 'Cancel using custom app repository', + appStoreUrlHelper: 'Only .tar.gz format is supported', + postNode: 'Sync to sub-node', + postNodeHelper: + 'Sync the custom store package to tmp/customApp/apps.tar.gz in the installation directory of the sub-node', + nodes: 'Nodes', + selectNode: 'Select Node', + selectNodeError: 'Please select a node', + licenseHelper: 'The Pro version supports the custom application repository feature', + databaseHelper: 'Application associated database, please select target node database', + nodeHelper: 'Cannot select current node', + migrateHelper: + 'Currently only supports migrating monolithic applications and applications associated only with MySQL, MariaDB, PostgreSQL databases', + opensslHelper: + 'If using encrypted backup, the OpenSSL versions between the two nodes must be consistent, otherwise migration may fail.', + installApp: 'Batch install', + installAppHelper: 'Batch install apps to selected nodes', + }, + alert: { + isAlert: 'Alert', + alertCount: 'Alert Count', + clamHelper: 'Trigger alert when scanning infected files', + cronJobHelper: 'Trigger alert when task execution fails', + licenseHelper: 'Professional version supports SMS alert', + alertCountHelper: 'Maximum daily alarm frequency', + alert: 'SMS Alert', + logs: 'Alert Logs', + list: 'Alert List', + addTask: 'Create Alert', + editTask: 'Edit Alert', + alertMethod: 'Method', + alertMsg: 'Alert Message', + alertRule: 'Alert Rules', + titleSearchHelper: 'Enter alert title for fuzzy search', + taskType: 'Type', + ssl: 'Certificate Expiry', + siteEndTime: 'Website Expiry', + panelPwdEndTime: 'Panel Password Expiry', + panelUpdate: 'New Panel Version Available', + cpu: 'Server CPU Alert', + memory: 'Server Memory Alert', + load: 'Server Load Alert', + disk: 'Server Disk Alert', + website: 'Website', + certificate: 'SSL Certificate', + remainingDays: 'Remaining Days', + sendCount: 'Send Count', + sms: 'SMS', + wechat: 'WeChat', + dingTalk: 'DingTalk', + feiShu: 'FeiShu', + mail: 'Email', + weCom: 'WeCom', + sendCountRulesHelper: 'Total alerts sent before expiry (once daily)', + panelUpdateRulesHelper: 'Total alerts sent for new panel version (once daily)', + oneDaySendCountRulesHelper: 'Maximum alerts sent per day', + siteEndTimeRulesHelper: 'Websites that never expire will not trigger alerts', + autoRenewRulesHelper: + 'Certificates with auto-renew enabled and remaining days less than 31 will not trigger alerts', + panelPwdEndTimeRulesHelper: 'Panel password expiry alerts are unavailable if no expiration is set', + sslRulesHelper: 'All SSL Certificates', + diskInfo: 'Disk', + monitoringType: 'Monitoring Type', + autoRenew: 'Auto-Renew', + useDisk: 'Disk Usage', + usePercentage: 'Usage Percentage', + changeStatus: 'Change Status', + disableMsg: + 'Stopping the alert task will prevent this task from sending alert messages. Do you want to continue?', + enableMsg: 'Enabling the alert task will allow this task to send alert messages. Do you want to continue?', + useExceed: 'Usage Exceeds', + useExceedRulesHelper: 'Trigger alert when usage exceeds the set value', + cpuUseExceedAvg: 'The average cpu usage exceeds the specified value', + memoryUseExceedAvg: 'The average memory usage exceeds the specified value', + loadUseExceedAvg: 'The average load usage exceeds the specified value', + cpuUseExceedAvgHelper: 'The average cpu usage within the specified time exceeds the specified value', + memoryUseExceedAvgHelper: 'The average memory usage within the specified time exceeds the specified value', + loadUseExceedAvgHelper: 'The average load usage within the specified time exceeds the specified value', + resourceAlertRulesHelper: 'Note: Continuous alerts within 30 minutes will send only one', + specifiedTime: 'Specified Time', + deleteTitle: 'Delete Alert', + deleteMsg: 'Are you sure you want to delete the alert task?', + + allSslTitle: 'All Website SSL Certificate Expiry Alerts', + sslTitle: 'SSL Certificate Expiry Alert for Website {0}', + allSiteEndTimeTitle: 'All Website Expiry Alerts', + siteEndTimeTitle: 'Website {0} Expiry Alert', + panelPwdEndTimeTitle: 'Panel Password Expiry Alert', + panelUpdateTitle: 'New Panel Version Notification', + cpuTitle: 'High CPU Usage Alert', + memoryTitle: 'High Memory Usage Alert', + loadTitle: 'High Load Alert', + diskTitle: 'High Disk Usage Alert for Mount Directory {0}', + allDiskTitle: 'High Disk Usage Alert', + + timeRule: 'Remaining time less than {0} days (if not handled, will resend the next day)', + panelUpdateRule: + 'Send an alert once when a new panel version is detected (if not handled, will resend the next day)', + avgRule: 'Average {1} usage exceeds {2}% within {0} minutes, triggers alert, sends {3} times per day', + diskRule: 'Disk usage for mount directory {0} exceeds {1}{2}, triggers alert, sends {3} times per day', + allDiskRule: 'Disk usage exceeds {0}{1}, triggers alert, sends {2} times per day', + + cpuName: ' CPU ', + memoryName: 'Memory', + loadName: 'Load', + diskName: 'Disk', + + syncAlertInfo: 'Manual Push', + syncAlertInfoMsg: 'Do you want to manually push the alert task?', + pushError: 'Push Failed', + pushSuccess: 'Push Successful', + syncError: 'Sync Failed', + success: 'Alert Successful', + pushing: 'Pushing...', + error: 'Alert Failed', + cleanLog: 'Clean Logs', + cleanAlertLogs: 'Clean Alert Logs', + daily: 'Daily Alert Count: {0}', + cumulative: 'Cumulative Alert Count: {0}', + clams: 'Virus scan alert', + taskName: 'Task Name', + cronJobType: 'Task Type', + clamPath: 'Scan Directory', + cronjob: 'Cronjob execution {0} failed', + app: 'Backup App', + web: 'Backup Website', + database: 'Backup Database', + directory: 'Backup Directory', + log: 'Backup Logs', + snapshot: 'System Snapshot', + clamsRulesHelper: 'Virus scanning tasks that require alerts', + cronJobRulesHelper: 'This type of scheduled task needs to be configured', + clamsTitle: 'Virus scan task 「 {0} 」 detected infected file alert', + cronJobAppTitle: 'Cronjob - Backup App 「 {0} 」 Task Failure Alert', + cronJobWebsiteTitle: 'Cronjob - Backup Website「 {0} 」Task Failure Alert', + cronJobDatabaseTitle: 'Cronjob - Backup Database「 {0} 」Task Failure Alert', + cronJobDirectoryTitle: 'Cronjob - Backup Directory「 {0} 」Task Failure Alert', + cronJobLogTitle: 'Cronjob - Backup Logs「 {0} 」Task Failure Alert', + cronJobSnapshotTitle: 'Cronjob - Backup Snapshot「 {0} 」Task Failure Alert', + cronJobShellTitle: 'Cronjob - Shell script 「 {0} 」Task Failure Alert', + cronJobCurlTitle: 'Cronjob - URL access「 {0} 」Task Failure Alert', + cronJobCutWebsiteLogTitle: 'Cronjob - Cut website log「 {0} 」Task Failure Alert', + cronJobCleanTitle: 'Cronjob - Cache cleaning「 {0} 」Task Failure Alert', + cronJobNtpTitle: 'Cronjob - Synchronization server time「 {0} 」Task Failure Alert', + clamsRule: 'Virus scan detected infected file alert,sent {0} times per day', + cronJobAppRule: 'Backup app task failed alert,sent {0} times per day', + cronJobWebsiteRule: 'Backup website task failed alert,sent {0} times per day', + cronJobDatabaseRule: 'Backup database task failed alert,sent {0} times per day', + cronJobDirectoryRule: 'Backup directory task failed alert,sent {0} times per day', + cronJobLogRule: 'Backup logs task failed alert,sent {0} times per day', + cronJobSnapshotRule: 'Backup snapshot task failed alert,sent {0} times per day', + cronJobShellRule: 'Shell script task failed alert,sent {0} times per day', + cronJobCurlRule: 'URL access task failed alert,sent {0} times per day', + cronJobCutWebsiteLogRule: 'Cut website log task failed alert,sent {0} times per day', + cronJobCleanRule: 'Cache cleaning task failed alert,sent {0} times per day', + cronJobNtpRule: 'Synchronization server time task failed alert,sent {0} times per day', + alertSmsHelper: 'SMS limit: total of {0} messages, {1} already used', + goBuy: 'Purchase More', + phone: 'Phone', + phoneHelper: 'Provide real phone number for alert messages', + dailyAlertNum: 'Daily Alert Limit', + dailyAlertNumHelper: 'Maximum number of alerts per day (up to 100)', + timeRange: 'Time Range', + sendTimeRange: 'Send time range', + sendTimeRangeHelper: 'Can push {0} time range', + to: 'to', + startTime: 'Start Time', + endTime: 'End Time', + defaultPhone: 'Default to license-bound account phone number', + noticeAlert: 'Notice Alert', + resourceAlert: 'Resource Alert', + agentOfflineAlertHelper: + 'When offline alert is enabled for the node, the main node will scan every 30 minutes to execute alert tasks.', + offline: 'Offline Alert', + offlineHelper: + 'When set to offline alert, the main node will scan every 30 minutes to execute alert tasks.', + offlineOff: 'Enable Offline Alert', + offlineOffHelper: + 'Enabling offline alert will make the main node scan every 30 minutes to execute alert tasks.', + offlineClose: 'Disable Offline Alert', + offlineCloseHelper: + 'Disabling offline alert requires sub-nodes to handle alerts independently. Please ensure network connectivity to avoid alert failure.', + alertNotice: 'Alert Notification', + methodConfig: 'Notification Method Configuration', + commonConfig: 'Global Configuration', + smsConfig: 'SMS', + smsConfigHelper: 'Configure phone numbers for SMS notifications', + emailConfig: 'Email', + emailConfigHelper: 'Configure SMTP email sending service', + deleteConfigTitle: 'Delete Alert Configuration', + deleteConfigMsg: 'Are you sure you want to delete the alert configuration?', + test: 'Test', + alertTestOk: 'Test notification succeeded', + alertTestFailed: 'Test notification failed', + displayName: 'Display Name', + sender: 'Sender Address', + password: 'Password', + host: 'SMTP Server', + port: 'Port', + encryption: 'Encryption Method', + recipient: 'Recipient', + licenseTime: 'License Expiration Reminder', + licenseTimeTitle: 'License Expiration Reminder', + displayNameHelper: 'Sender display name for emails', + senderHelper: 'Email address used to send messages', + passwordHelper: 'Authorization code for the email service', + hostHelper: 'SMTP server address, e.g., smtp.qq.com', + portHelper: 'SSL usually uses 465, TLS usually uses 587', + sslHelper: 'If the SMTP port is 465, SSL is usually required', + tlsHelper: 'If the SMTP port is 587, TLS is usually required', + triggerCondition: 'Trigger Condition', + loginFail: ' login failures within', + nodeException: 'Node Exception Alert', + licenseException: 'License Exception Alert', + panelLogin: 'Panel Login Exception Alert', + sshLogin: 'SSH Login Exception Alert', + panelIpLogin: 'Panel Login IP Exception Alert', + sshIpLogin: 'SSH Login IP Exception Alert', + ipWhiteListHelper: + 'IPs in the whitelist are not restricted by rules, and there will be no alert upon successful login', + nodeExceptionRule: 'Node exception alert, sent {0} times per day', + licenseExceptionRule: 'License exception alert, sent {0} times per day', + panelLoginRule: 'Panel login alert, sent {0} times per day', + sshLoginRule: 'SSH login alert, sent {0} times per day', + userNameHelper: 'Username is empty, the sender address will be used by default', + }, + theme: { + lingXiaGold: 'Ling Xia Gold', + classicBlue: 'Classic Blue', + freshGreen: 'Fresh Green', + customColor: 'Custom Color', + setDefault: 'Default', + setDefaultHelper: + 'The theme color scheme is about to be restored to its initial state. Do you want to continue?', + setHelper: 'The currently selected theme color scheme is about to be saved. Do you want to continue?', + }, + exchange: { + exchange: 'File Exchange', + exchangeConfirm: "Do you want to transfer the file/folder {1} from {0} node to {2} node's {3} directory?", + }, + cluster: { + cluster: 'Application High Availability', + name: 'Cluster Name', + addCluster: 'Add Cluster', + installNode: 'Install Node', + master: 'Master Node', + slave: 'Slave Node', + replicaStatus: 'Master-Slave Status', + unhealthyDeleteError: 'The installation node status is abnormal, please check the node list and try again!', + replicaStatusError: 'Status acquisition is abnormal, please check the master node.', + masterHostError: 'The master node IP cannot be 127.0.0.1', + }, + }, +}; + +export default { + ...fit2cloudEnLocale, + ...message, +}; diff --git a/frontend/src/lang/modules/es-es.ts b/frontend/src/lang/modules/es-es.ts new file mode 100644 index 0000000..afaa8c8 --- /dev/null +++ b/frontend/src/lang/modules/es-es.ts @@ -0,0 +1,4130 @@ +import fit2cloudEsEsLocale from 'fit2cloud-ui-plus/src/locale/lang/en'; + +const message = { + commons: { + true: 'verdadero', + false: 'falso', + colon: ': ', + example: 'Por ejemplo, ', + fit2cloud: 'FIT2CLOUD', + lingxia: 'Lingxia', + button: { + run: 'Ejecutar', + prev: 'Anterior', + next: 'Siguiente', + create: 'Crear ', + add: 'Agregar ', + save: 'Guardar ', + set: 'Establecer', + sync: 'Sincronizar ', + delete: 'Eliminar', + edit: 'Editar ', + enable: 'Habilitar', + disable: 'Deshabilitar', + confirm: 'Confirmar', + cancel: 'Cancelar', + reset: 'Restablecer', + setDefault: 'Restaurar valores predeterminados', + restart: 'Reiniciar', + conn: 'Conectar', + disconn: 'Desconectar', + clean: 'Limpiar', + login: 'Iniciar sesión', + close: 'Cerrar', + stop: 'Detener', + start: 'Iniciar', + view: 'Ver', + watch: 'Observar', + handle: 'Disparar', + clone: 'Clonar', + expand: 'Expandir', + collapse: 'Colapsar', + log: 'Ver registros', + back: 'Atrás', + backup: 'Respaldar', + recover: 'Recuperar', + retry: 'Reintentar', + upload: 'Subir', + download: 'Descargar', + init: 'Inicializar', + verify: 'Verificar', + saveAndEnable: 'Guardar y habilitar', + import: 'Importar', + export: 'Exportar', + power: 'Autorización', + search: 'Buscar', + refresh: 'Actualizar', + get: 'Obtener', + upgrade: 'Actualizar', + update: 'Actualizar', + updateNow: 'Actualizar Ahora', + ignore: 'Ignorar actualización', + copy: 'Copiar', + random: 'Aleatorio', + install: 'Instalar', + uninstall: 'Desinstalar', + fullscreen: 'Pantalla completa', + quitFullscreen: 'Salir de pantalla completa', + showAll: 'Mostrar todo', + hideSome: 'Ocultar algunos', + agree: 'Aceptar', + notAgree: 'No aceptar', + preview: 'Vista previa', + open: 'Abrir', + notSave: 'No guardar', + createNewFolder: 'Crear nueva carpeta', + createNewFile: 'Crear nuevo archivo', + helpDoc: 'Documento de ayuda', + bind: 'Vincular', + unbind: 'Desvincular', + cover: 'cubrir', + skip: 'omitir', + fix: 'Arreglar', + down: 'Detener', + up: 'Iniciar', + sure: 'Confirmar', + show: 'Mostrar', + hide: 'Ocultar', + migrate: 'Migrar', + }, + operate: { + start: 'Iniciar', + stop: 'Detener', + restart: 'Reiniciar', + reload: 'Recargar', + rebuild: 'Reconstruir', + sync: 'Sincronizar', + up: 'Subir', + down: 'Bajar', + delete: 'Eliminar', + }, + search: { + timeStart: 'Hora de inicio', + timeEnd: 'Hora de fin', + timeRange: 'Hasta', + dateStart: 'Fecha de inicio', + dateEnd: 'Fecha de fin', + date: 'Fecha', + }, + table: { + all: 'Todo', + total: 'Total {0}', + name: 'Nombre', + type: 'Tipo', + status: 'Estado', + statusSuccess: 'Éxito', + statusFailed: 'Fallido', + statusWaiting: 'Esperando...', + records: 'Registros', + group: 'Grupo', + default: 'Predeterminado', + createdAt: 'Fecha de creación', + publishedAt: 'Fecha de publicación', + date: 'Fecha', + updatedAt: 'Fecha de actualización', + operate: 'Operaciones', + message: 'Mensaje', + description: 'Descripción', + interval: 'Intervalo', + user: 'Propietario', + title: 'Título', + port: 'Puerto', + forward: 'Reenviar', + protocol: 'Protocolo', + tableSetting: 'Configuración de tabla', + refreshRate: 'Frecuencia de actualización', + noRefresh: 'Sin actualización', + selectColumn: 'Seleccionar columna', + local: 'local', + serialNumber: 'Número de serie', + manageGroup: 'Gestionar grupos', + backToList: 'Volver a la lista', + keepEdit: 'Continuar Editando', + }, + loadingText: { + Upgrading: 'Actualizando el sistema, por favor espere...', + Restarting: 'Reiniciando el sistema, por favor espere...', + Recovering: 'Recuperando desde instantánea, por favor espere...', + Rollbacking: 'Revirtiendo desde instantánea, por favor espere...', + }, + msg: { + noneData: 'No hay datos disponibles', + delete: 'Esta operación de eliminación no se puede deshacer. ¿Desea continuar?', + clean: 'Esta operación de limpieza no se puede deshacer. ¿Desea continuar?', + closeDrawerHelper: 'Es posible que el sistema no guarde los cambios que realizó. ¿Desea continuar?', + deleteSuccess: 'Eliminación completada correctamente', + loginSuccess: 'Inicio de sesión completado correctamente', + operationSuccess: 'Operación completada correctamente', + copySuccess: 'Copiado completado correctamente', + notSupportOperation: 'Esta operación no es compatible', + requestTimeout: 'La solicitud excedió el tiempo de espera, por favor intente de nuevo más tarde', + infoTitle: 'Aviso', + notRecords: 'No se ha generado ningún registro de ejecución para la tarea actual', + sureLogOut: '¿Está seguro de que desea cerrar sesión?', + createSuccess: 'Creación completada correctamente', + updateSuccess: 'Actualización completada correctamente', + uploadSuccess: 'Carga completada correctamente', + operateConfirm: 'Si está seguro de la operación, por favor ingréselo manualmente: ', + inputOrSelect: 'Por favor seleccione o ingrese', + copyFailed: 'Error al copiar', + operatorHelper: 'Se realizará la operación "{1}" en "{0}" y no se puede deshacer. ¿Desea continuar?', + notFound: 'Lo sentimos, la página que solicitó no existe.', + unSupportType: 'El tipo de archivo actual no es compatible.', + unSupportSize: 'El archivo subido supera los {0}M, ¡por favor verifique!', + fileExist: 'El archivo ya existe en la carpeta actual. No se permite subirlo de nuevo.', + fileNameErr: + 'Solo puede subir archivos cuyo nombre contenga de 1 a 256 caracteres, incluyendo inglés, chino, dígitos o los caracteres .-_', + confirmNoNull: 'Asegúrese de que el valor {0} no esté vacío.', + errPort: 'Información de puerto incorrecta, ¡por favor verifique!', + remove: 'Eliminar', + backupHelper: 'La operación actual realizará una copia de seguridad de {0}. ¿Desea continuar?', + recoverHelper: 'Restaurando desde el archivo {0}. Esta operación es irreversible. ¿Desea continuar?', + refreshSuccess: 'Actualización completada con éxito', + rootInfoErr: 'Ya se encuentra en el directorio raíz', + resetSuccess: 'Restablecimiento completado correctamente', + creatingInfo: 'Creando, no es necesario realizar esta operación', + installSuccess: 'Instalación completada correctamente', + uninstallSuccess: 'Desinstalación completada correctamente', + offlineTips: 'La versión offline no admite esta operación', + errImportFormat: 'Los datos de importación o el formato son anormales, ¡compruebe e inténtelo de nuevo!', + importHelper: + 'Al importar datos conflictivos o duplicados, el contenido importado se utilizará como estándar para actualizar los datos originales de la base de datos.', + errImport: 'El contenido del archivo es anormal:', + }, + login: { + username: 'Usuario', + password: 'Contraseña', + passkey: 'Inicio de sesión con Passkey', + welcome: '¡Bienvenido de nuevo! Por favor, ingrese su nombre de usuario y contraseña para iniciar sesión.', + errorAuthInfo: 'El nombre de usuario o la contraseña que ingresó son incorrectos, ¡inténtelo de nuevo!', + errorMfaInfo: 'Información de autenticación incorrecta, ¡por favor intente nuevamente!', + captchaHelper: 'Captcha', + errorCaptcha: '¡Código captcha incorrecto!', + notSafe: 'Acceso denegado', + safeEntrance1: 'El inicio de sesión seguro está habilitado en el entorno actual', + safeEntrance2: + 'Ingrese el siguiente comando en la terminal SSH para ver la entrada al panel: 1pctl user-info', + errIP1: 'El acceso mediante dirección IP autorizada está habilitado en el entorno actual', + errDomain1: 'La vinculación del nombre de dominio de acceso está habilitada en el entorno actual', + errHelper: + 'Para restablecer la información de vinculación, ejecute el siguiente comando en la terminal SSH: ', + codeInput: 'Por favor, ingrese el código de verificación de 6 dígitos del validador MFA', + mfaTitle: 'Autenticación MFA', + mfaCode: 'Código de verificación MFA', + title: 'Panel de gestión de servidores Linux', + licenseHelper: '', + errorAgree: 'Haga clic para aceptar la Licencia de Software Comunitaria', + logout: 'Cerrar sesión', + agreeTitle: 'Acuerdo', + agreeContent: + 'Para proteger mejor sus derechos e intereses legítimos, por favor lea y acepte el siguiente acuerdo « Acuerdo de Licencia Comunitaria »', + passkeyFailed: 'El inicio de sesión con Passkey falló, inténtalo de nuevo', + passkeyNotSupported: 'El navegador o entorno actual no admite Passkey', + passkeyToPassword: '¿Tienes problemas para usar una passkey? Usa la contraseña en su lugar', + }, + rule: { + username: 'Introduzca un nombre de usuario', + password: 'Introduzca una contraseña', + rePassword: 'La confirmación de la contraseña no coincide con la contraseña.', + requiredInput: 'Este campo es obligatorio.', + requiredSelect: 'Seleccione un elemento de la lista', + illegalChar: 'Actualmente no se admite la inyección de caracteres & ; $ \' ` ( ) " > < |', + illegalInput: 'Este campo no debe contener caracteres no permitidos.', + commonName: + 'Este campo debe comenzar con un carácter no especial y debe estar compuesto por letras, caracteres chinos, números, ".", "-", y "_" con una longitud de 1 a 128.', + userName: + 'Este campo debe estar compuesto por letras, caracteres chinos, números y "_" con una longitud de 3 a 30.', + simpleName: + 'Este campo no debe comenzar con el carácter "_" y debe estar compuesto por letras, números y "_" con una longitud de 3 a 30.', + simplePassword: + 'Este campo no debe comenzar con el carácter "_" y debe estar compuesto por letras, números y "_" con una longitud de 1 a 30.', + dbName: 'Este campo no debe comenzar con el carácter "_" y debe estar compuesto por letras, números y "_" con una longitud de 1 a 64.', + imageName: + 'Este campo debe estar compuesto por letras, números, ":", "@", "/", ".", "-", y "_" con una longitud de 1 a 256.', + volumeName: + 'Este campo debe estar compuesto por letras, números, ".", "-", y "_" con una longitud de 2 a 30.', + supervisorName: + 'Este campo debe comenzar con un carácter no especial y debe estar compuesto por letras, números, "-", y "_" con una longitud de 1 a 128.', + composeName: + 'Debe comenzar con un carácter no especial, permite minúsculas, números, - y _, longitud de 1 a 256', + complexityPassword: + 'Este campo debe estar compuesto por letras, números, con una longitud de 8 a 30 y contener al menos dos caracteres especiales.', + commonPassword: 'La longitud de este campo debe ser mayor a 6.', + linuxName: + 'La longitud de este campo debe estar entre 1 y 128. El campo no debe contener los siguientes caracteres especiales: "{0}".', + email: 'Este campo debe ser una dirección de correo electrónico válida.', + number: 'Este campo debe ser un número.', + integer: 'Este campo debe ser un número entero positivo.', + ip: 'Este campo debe ser una dirección IP válida.', + host: 'Este campo debe ser una dirección IP válida o un nombre de dominio.', + hostHelper: 'Admite introducir dirección IP o nombre de dominio', + port: 'Este campo debe ser un número de puerto válido.', + selectHelper: 'Por favor seleccione el archivo {0} correcto', + domain: 'Este campo debe tener el formato: ejemplo.com o ejemplo.com:8080.', + databaseName: 'Este campo debe estar compuesto por letras, números y "_" con una longitud de 1 a 30.', + ipErr: 'Este campo debe ser una dirección IP válida.', + numberRange: 'Este campo debe ser un número entre {0} y {1}.', + paramName: + 'Este campo debe estar compuesto por letras, números, ".", "-", y "_" con una longitud de 2 a 30.', + paramComplexity: + 'Este campo no debe comenzar ni terminar con caracteres especiales y debe estar compuesto por letras, números y los caracteres "{0}", con una longitud de 6 a 128.', + paramUrlAndPort: 'Este campo debe tener el formato "http(s)://(nombre de dominio/ip):(puerto)".', + nginxDoc: 'Este campo debe estar compuesto por letras, números y ".".', + appName: + 'Este campo no debe comenzar ni terminar con "-" o "_" y debe estar compuesto por letras, números, "-", y "_" con una longitud de 2 a 30.', + containerName: 'Permite letras, números, -, _ y .; no puede comenzar con - _ o .; longitud: 2-128', + mirror: 'La dirección de aceleración del mirror debe comenzar con http(s)://, soporta letras (mayúsculas y minúsculas), números, . / y -, y no debe contener líneas en blanco.', + disableFunction: 'Solo admite letras, guiones bajos y comas', + leechExts: 'Solo admite letras, números y comas', + paramSimple: 'Admite letras minúsculas y números, longitud de 1 a 128', + filePermission: 'Error en el permiso del archivo', + formatErr: 'Error de formato, por favor verifique e intente nuevamente', + phpExtension: 'Solo admite _, letras minúsculas en inglés y números', + paramHttp: 'Debe comenzar con http:// o https://', + phone: 'El formato del número de teléfono es incorrecto', + authBasicPassword: 'Admite letras, números y caracteres especiales comunes, longitud de 1 a 72', + length128Err: 'La longitud no puede exceder los 128 caracteres', + maxLength: 'La longitud no puede exceder los {0} caracteres', + alias: 'Soporta A->Z, números, - and _, largo 1-128 carácteres, no puede empezar ni terminar con -_.', + }, + res: { + paramError: 'La solicitud falló, por favor intente de nuevo más tarde.', + forbidden: 'El usuario actual no tiene permisos', + serverError: 'Excepción del servicio', + notFound: 'El recurso no existe', + commonError: 'La solicitud falló', + }, + service: { + serviceNotStarted: `El servicio {0} no está iniciado.`, + }, + status: { + running: 'En ejecución', + done: 'Completado', + scanFailed: 'Incompleto', + success: 'Completado con éxito', + waiting: 'En espera', + waitForUpgrade: 'Esperar Actualización', + failed: 'Fallido', + stopped: 'Detenido', + error: 'Error', + created: 'Creado', + restarting: 'Reiniciando', + uploading: 'Cargando', + unhealthy: 'No saludable', + removing: 'Eliminando', + paused: 'Pausado', + exited: 'Finalizado', + dead: 'Muerto', + installing: 'Instalando', + enabled: 'Habilitado', + disabled: 'Deshabilitado', + normal: 'Normal', + building: 'Construyendo', + upgrading: 'Actualizando', + pending: 'Pendiente de editarse', + rebuilding: 'Reconstruyendo', + deny: 'Denegado', + accept: 'Aceptado', + used: 'En uso', + unused: 'Sin usar', + starting: 'Iniciando', + recreating: 'Recreando', + creating: 'Creando', + init: 'Esperando aplicación', + ready: 'normal', + applying: 'Aplicando', + uninstalling: 'Desinstalando', + lost: 'Contacto perdido', + bound: 'Vinculado', + unbind: 'Desvinculado', + exceptional: 'Excepcional', + free: 'Libre', + enable: 'Habilitado', + disable: 'Deshabilitado', + deleted: 'Eliminado', + downloading: 'Descargando', + packing: 'Empaquetando', + sending: 'Enviando', + healthy: 'Normal', + executing: 'Ejecutando', + installerr: 'Instalación fallida', + applyerror: 'Aplicación fallida', + systemrestart: 'Interrumpido', + starterr: 'Error al iniciar', + uperr: 'Error al iniciar', + new: 'Nuevo', + conflict: 'Conflicto', + duplicate: 'Duplicado', + unexecuted: 'No Ejecutado', + }, + units: { + second: ' segundo | segundo | segundos', + minute: 'minuto | minuto | minutos', + hour: 'hora | hora | horas', + day: 'día | día | días', + week: 'semana | semana | semanas', + month: 'mes | mes | meses', + year: 'año | año | años', + time: 'rqm', + core: 'núcleo | núcleo | núcleos', + millisecond: 'milisegundo | milisegundos', + secondUnit: 's', + minuteUnit: 'min', + hourUnit: 'h', + dayUnit: 'd', + }, + log: { + noLog: 'No logs available', + }, + }, + menu: { + home: 'Resumen', + apps: 'Tienda de aplicaciones', + website: 'Sitio web | Sitios web', + project: 'Proyecto | Proyectos', + config: 'Configuración | Configuraciones', + ssh: 'Configuración SSH', + firewall: 'Cortafuegos', + ssl: 'Certificado | Certificados', + database: 'Base de datos | Bases de datos', + aiTools: 'IA', + mcp: 'MCP', + container: 'Contenedor | Contenedores', + cronjob: 'Tarea programada | Tareas programadas', + system: 'Sistema', + security: 'Seguridad', + files: 'Explorador de archivos', + monitor: 'Monitoreo', + terminal: 'Terminal | Terminales', + settings: 'Configuración | Configuraciones', + toolbox: 'Caja de herramientas', + logs: 'Registro | Registros', + runtime: 'Ejecución | Entornos de ejecución', + processManage: 'Proceso | Procesos', + process: 'Proceso | Procesos', + network: 'Red | Redes', + supervisor: 'Supervisor', + tamper: 'Antimanipulación', + app: 'Aplicación', + msgCenter: 'Centro de tareas', + disk: 'Disco', + }, + home: { + recommend: 'recomendar', + dir: 'directorio', + quickDir: 'Directorio rápido', + minQuickJump: '¡Establezca al menos una entrada de salto rápido!', + maxQuickJump: '¡Puede establecer hasta cuatro entradas de salto rápido!', + database: 'Database - All', + restart_1panel: 'Reiniciar panel', + restart_system: 'Reiniciar servidor', + operationSuccess: + 'Operación completada con éxito, reiniciando, ¡por favor actualice el navegador manualmente más tarde!', + entranceHelper: + 'La entrada de seguridad no está habilitada. Puede habilitarla en "Configuraciones -> Seguridad" para mejorar la seguridad del sistema.', + appInstalled: 'Aplicaciones', + systemInfo: 'Información del sistema', + hostname: 'Nombre del host', + platformVersion: 'Sistema operativo', + kernelVersion: 'Kernel', + kernelArch: 'Arquitectura', + network: 'Red', + io: 'Disco I/O', + ip: 'IP local', + proxy: 'Proxy del sistema', + baseInfo: 'Información básica', + totalSend: 'Total enviado', + totalRecv: 'Total recibido', + rwPerSecond: 'Operaciones de E/S', + ioDelay: 'Latencia de E/S', + uptime: 'Tiempo en funcionamiento', + runningTime: 'Desde', + mem: 'Memoria del Sistema', + swapMem: 'Partición swap', + runSmoothly: 'Carga baja', + runNormal: 'Carga moderada', + runSlowly: 'Carga alta', + runJam: 'Carga pesada', + core: 'Núcleo físico', + logicCore: 'Núcleo lógico', + corePercent: 'Uso del Núcleo', + cpuFrequency: 'Frecuencia CPU', + cpuDetailedPercent: 'Distribución del Tiempo de CPU', + cpuUser: 'Usuario', + cpuSystem: 'Sistema', + cpuIdle: 'Inactivo', + cpuIrq: 'IRQ', + cpuSoftirq: 'Soft IRQ', + cpuSteal: 'Robado', + cpuTop: 'Top 5 de Procesos por Uso de CPU', + memTop: 'Top 5 de Procesos por Uso de Memoria', + loadAverage: 'Promedio de carga en el último minuto | Promedio de carga en los últimos {n} minutos', + load: 'Carga', + mount: 'Punto de montaje', + fileSystem: 'Sistema de archivos', + total: 'Total', + used: 'En uso', + cache: 'Cache', + free: 'Libre', + shard: 'Fragmentado', + available: 'Disponible', + percent: 'Utilización', + goInstall: 'Ir a instalar', + networkCard: 'Tarjeta de red', + disk: 'Disco', + }, + tabs: { + more: 'Más', + hide: 'Ocultar', + closeLeft: 'Cerrar izquierda', + closeRight: 'Cerrar derecha', + closeCurrent: 'Cerrar actual', + closeOther: 'Cerrar otras', + closeAll: 'Cerrar todo', + }, + header: { + logout: 'Cerrar sesión', + }, + database: { + manage: 'Administrar base de datos', + deleteBackupHelper: 'Eliminar copias de seguridad de la base de datos simultáneamente', + delete: 'La operación de eliminación no se puede revertir, por favor introduzca "', + deleteHelper: '" para eliminar esta base de datos', + create: 'Crear base de datos', + noMysql: 'Servicio de base de datos (MySQL o MariaDB)', + noPostgresql: 'Servicio de base de datos PostgreSQL', + goUpgrade: 'Ir a actualizar', + goInstall: 'Ir a instalar', + isDelete: 'Eliminada', + permission: 'Cambiar permisos', + format: 'Juego de Caracteres', + collation: 'Intercalación', + collationHelper: 'Si está vacío, use la intercalación predeterminada del juego de caracteres {0}', + permissionForIP: 'IP', + permissionAll: 'Todos (%)', + localhostHelper: + 'Configurar los permisos de la base de datos como "localhost" para el despliegue en contenedores impedirá el acceso externo al contenedor. ¡Por favor elija cuidadosamente!', + databaseConnInfo: 'Ver información de conexión', + rootPassword: 'Contraseña root', + serviceName: 'Nombre del servicio', + serviceNameHelper: 'Acceso entre contenedores en la misma red.', + backupList: 'Respaldo', + loadBackup: 'Importar', + localUpload: 'Subida local', + hostSelect: 'Server Selection', + selectHelper: 'Estas seguro de importar el archivo de backup {0}?', + remoteAccess: 'Acceso remoto', + remoteHelper: 'Múltiples IP separadas por comas, ejemplo: 172.16.10.111, 172.16.10.112', + remoteConnHelper: + 'Conectarse remotamente como usuario root en MySQL puede presentar riesgos de seguridad. Por lo tanto, realice esta operación con precaución.', + changePassword: 'Cambiar contraseña', + changeConnHelper: 'Esta operación modificará la base de datos actual {0}. ¿Desea continuar?', + changePasswordHelper: + 'La base de datos ha sido asociada a una aplicación. Cambiar la contraseña también cambiará la contraseña de la base de datos en la aplicación. El cambio se aplica después de reiniciar la aplicación.', + recoverTimeoutHelper: '-1 significa sin límite de tiempo de espera', + + confChange: 'Configuración', + confNotFound: + 'No se pudo encontrar el archivo de configuración. Por favor actualice la aplicación a la última versión en la tienda y vuelva a intentarlo.', + portHelper: + 'Este puerto es el puerto expuesto del contenedor. Debe guardar la modificación por separado y reiniciar el contenedor.', + loadFromRemote: 'Sincronizar desde el servidor', + userBind: 'Vincular usuario', + pgBindHelper: + 'Esta operación se utiliza para crear un nuevo usuario y vincularlo a la base de datos destino. Actualmente no se admite seleccionar usuarios ya existentes en la base de datos.', + pgSuperUser: 'Superusuario', + loadFromRemoteHelper: + 'Esto sincronizará la información de la base de datos del servidor a 1Panel. ¿Desea continuar?', + passwordHelper: 'No se puede obtener, haga clic para modificar', + remote: 'Remoto', + remoteDB: 'Servidor remoto | Servidores remotos', + manageRemoteDB: 'Administrar servidores remotos', + createRemoteDB: 'Vincular @.lower:database.remoteDB', + unBindRemoteDB: 'Desvincular @.lower:database.remoteDB', + unBindForce: 'Forzar desvinculación', + unBindForceHelper: + 'Ignorar todos los errores durante la desvinculación para asegurar que la operación final sea exitosa', + unBindRemoteHelper: + 'Desvincular la base de datos remota solo eliminará la relación de vinculación y no eliminará directamente la base de datos remota', + editRemoteDB: 'Editar servidor remoto', + localDB: 'Base de datos local', + address: 'Dirección de la base de datos', + version: 'Versión de la base de datos', + userHelper: 'El usuario root o un usuario con privilegios de root puede acceder a la base de datos remota.', + pgUserHelper: 'Usuario de base de datos con privilegios de superusuario.', + ssl: 'Usar SSL', + clientKey: 'Clave privada del cliente', + clientCert: 'Certificado del cliente', + caCert: 'Certificado CA', + hasCA: 'Posee certificado CA', + skipVerify: 'Omitir la verificación de validez del certificado', + initialDB: 'Base de Datos Inicial', + + formatHelper: + 'El conjunto de caracteres actual de la base de datos es {0}, la inconsistencia de conjuntos puede causar errores al recuperar', + dropHelper: 'Puede arrastrar y soltar el archivo aquí o', + clickHelper: 'hacer clic para subir', + supportUpType: + 'Solo se admiten los formatos de archivo sql, sql.gz, tar.gz y .zip. El archivo comprimido importado debe contener solo un archivo .sql o incluir test.sql.', + currentStatus: 'Estado actual', + baseParam: 'Parámetro básico', + performanceParam: 'Parámetro de rendimiento', + runTime: 'Tiempo de inicio', + connections: 'Total de conexiones', + bytesSent: 'Bytes enviados', + bytesReceived: 'Bytes recibidos', + queryPerSecond: 'Consultas por segundo', + txPerSecond: 'Tx por segundo', + connInfo: 'Conexiones activas/pico', + connInfoHelper: 'Si el valor es demasiado grande, aumente "max_connections".', + threadCacheHit: 'Cache de hilos exitoso', + threadCacheHitHelper: 'Si es muy bajo, aumente "thread_cache_size".', + indexHit: 'Índice exitoso', + indexHitHelper: 'Si es muy bajo, aumente "key_buffer_size".', + innodbIndexHit: 'Tasa de aciertos del índice Innodb', + innodbIndexHitHelper: 'Si es muy bajo, aumente "innodb_buffer_pool_size".', + cacheHit: 'Aciertos en caché de consulta', + cacheHitHelper: 'Si es muy bajo, aumente "query_cache_size".', + tmpTableToDB: 'Tabla temporal en disco', + tmpTableToDBHelper: 'Si es muy alto, intente aumentar "tmp_table_size".', + openTables: 'Tablas abiertas', + openTablesHelper: 'El valor de configuración de "table_open_cache" debe ser mayor o igual a este valor.', + selectFullJoin: 'Seleccionar join completo', + selectFullJoinHelper: 'Si el valor no es 0, verifique si el índice de la tabla es correcto.', + selectRangeCheck: 'Uniones sin índice', + selectRangeCheckHelper: 'Si el valor no es 0, verifique si el índice de la tabla es correcto.', + sortMergePasses: 'Cantidad de merges ordenados', + sortMergePassesHelper: 'Si es muy alto, aumente "sort_buffer_size".', + tableLocksWaited: 'Bloqueos de tabla', + tableLocksWaitedHelper: 'Si el valor es muy alto, considere mejorar el rendimiento de su base de datos.', + performanceTuning: 'Ajuste de rendimiento', + optimizationScheme: 'Esquema de optimización', + keyBufferSizeHelper: 'Tamaño del buffer para índices', + queryCacheSizeHelper: 'Caché de consulta. Si la función está deshabilitada, establezca este parámetro en 0.', + tmpTableSizeHelper: 'Tamaño del caché de tabla temporal', + innodbBufferPoolSizeHelper: 'Tamaño del buffer Innodb', + innodbLogBufferSizeHelper: 'Tamaño del buffer de logs de Innodb', + sortBufferSizeHelper: '* conexiones, tamaño del buffer de ordenamiento por hilo', + readBufferSizeHelper: '* conexiones, tamaño del buffer de lectura', + readRndBufferSizeHelper: '* conexiones, tamaño del buffer de lectura aleatoria', + joinBufferSizeHelper: '* conexiones, tamaño del caché de asociación de tablas', + threadStackelper: '* conexiones, tamaño de pila por hilo', + binlogCacheSizeHelper: '* conexiones, caché del log binario (múltiplos de 4096)', + threadCacheSizeHelper: 'Tamaño del pool de hilos', + tableOpenCacheHelper: 'Caché de tablas', + maxConnectionsHelper: 'Máximo de conexiones', + restart: 'Reiniciar', + slowLog: 'Registros lentos', + noData: 'Aún no hay registros lentos.', + isOn: 'Activado', + longQueryTime: 'umbral(es)', + thresholdRangeHelper: 'Por favor, introduzca el umbral correcto (1 - 600).', + timeout: 'Tiempo de espera', + timeoutHelper: 'Tiempo de espera de conexión inactiva. 0 indica que la conexión está siempre activa.', + maxclients: 'Máximo de clientes', + requirepassHelper: + 'Déjelo en blanco si no se ha establecido ninguna contraseña. Los cambios deben guardarse por separado y el contenedor debe reiniciarse.', + databases: 'Cantidad de bases de datos', + maxmemory: 'Uso máximo de memoria', + maxmemoryHelper: '0 indica sin restricción.', + tcpPort: 'Puerto de escucha actual.', + uptimeInDays: 'Días en operación.', + connectedClients: 'Cantidad de clientes conectados.', + usedMemory: 'Uso de memoria actual de Redis.', + usedMemoryRss: 'Tamaño de memoria solicitado al sistema operativo.', + usedMemoryPeak: 'Pico de uso de memoria de Redis.', + memFragmentationRatio: 'Ratio de fragmentación de memoria.', + totalConnectionsReceived: 'Total de clientes conectados desde el inicio.', + totalCommandsProcessed: 'Total de comandos ejecutados desde el inicio.', + instantaneousOpsPerSec: 'Comandos ejecutados por el servidor por segundo.', + keyspaceHits: 'Cantidad de veces que se encontró la clave de la base de datos.', + keyspaceMisses: 'Cantidad de intentos fallidos para encontrar la clave.', + hit: 'Proporción de aciertos de clave de la base de datos.', + latestForkUsec: 'Microsegundos consumidos en la última operación fork().', + redisCliHelper: 'No se detectó el servicio "redis-cli". Primero habilite el servicio.', + redisQuickCmd: 'Comandos rápidos de Redis', + recoverHelper: 'Esto sobrescribirá los datos con [{0}]. ¿Desea continuar?', + submitIt: 'Sobrescribir los datos', + baseConf: 'Básico', + allConf: 'Todos', + restartNow: 'Reiniciar ahora', + restartNowHelper1: + 'Necesita reiniciar el sistema después de que los cambios de configuración tengan efecto. Si necesita persistir los datos, guarde antes de reiniciar.', + restartNowHelper: 'Esto solo surtirá efecto después de reiniciar el sistema.', + persistence: 'Persistencia', + rdbHelper1: 'segundo(s), insertar', + rdbHelper2: 'piezas de datos', + rdbHelper3: 'Cumplir cualquiera de las condiciones activará la persistencia RDB.', + rdbInfo: 'Asegúrese de que el valor de la regla esté entre 1 y 100000', + + containerConn: 'Conexión del contenedor', + connAddress: 'Dirección', + containerConnHelper: + 'Esta dirección de conexión es usada por aplicaciones en entorno de ejecución PHP o instalaciones en contenedores.', + remoteConn: 'Conexión externa', + remoteConnHelper2: + 'Esta dirección de conexión puede ser usada por aplicaciones fuera del contenedor o externas.', + remoteConnHelper3: + 'La dirección de acceso predeterminada es la IP del host. Para modificarla, diríjase a la configuración de "Dirección de acceso predeterminada" en la página de ajustes del panel.', + localIP: 'IP local', + }, + aiTools: { + model: { + model: 'Modelo', + create: 'Agregar modelo', + create_helper: 'Descargar "{0}"', + ollama_doc: 'Puede visitar el sitio oficial de Ollama para buscar y encontrar más modelos.', + container_conn_helper: 'Utilice esta dirección para el acceso o conexión entre contenedores', + ollama_sync: + 'Al sincronizar modelos de Ollama, se detectaron los siguientes modelos que no existen. ¿Desea eliminarlos?', + from_remote: 'Este modelo no fue descargado vía 1Panel, no hay registros de descarga relacionados.', + no_logs: 'Los registros de descarga de este modelo han sido eliminados y no se pueden consultar.', + }, + proxy: { + proxy: 'Mejoras de proxy de IA', + proxyHelper1: 'Vincule un dominio y habilite HTTPS para mejorar la seguridad de la transmisión', + proxyHelper2: 'Limite el acceso por IP para evitar exposición pública', + proxyHelper3: 'Habilitar transmisión en tiempo real', + proxyHelper4: 'Una vez creado, puede verlo y gestionarlo en la lista de sitios web', + proxyHelper5: + 'Después de habilitarlo, puede deshabilitar el acceso externo al puerto en Tienda de Aplicaciones - Instaladas - Ollama - Parámetros para mejorar la seguridad.', + proxyHelper6: + 'Para deshabilitar la configuración del proxy, puede eliminarla desde la lista de sitios web.', + whiteListHelper: 'Restringir el acceso solo a las IP incluidas en la lista blanca', + }, + gpu: { + gpu: 'Monitoreo de GPU', + gpuHelper: 'El sistema no detectó comandos NVIDIA-SMI o XPU-SMI. ¡Compruebe e inténtelo de nuevo!', + process: 'Información del Proceso', + type: 'Tipo', + typeG: 'Gráficos', + typeC: 'Cómputo', + typeCG: 'Cómputo+Gráficos', + processName: 'Nombre del Proceso', + shr: 'Memoria Compartida', + temperatureHelper: 'La alta temperatura de la GPU puede causar una reducción de la frecuencia de la GPU', + gpuUtil: 'Utilización de GPU', + temperature: 'Temperatura', + performanceState: 'Estado de Rendimiento', + powerUsage: 'Consumo de Energía', + memoryUsage: 'Utilización de Memoria', + fanSpeed: 'Velocidad del Ventilador', + power: 'Energía', + powerCurrent: 'Energía Actual', + powerLimit: 'Límite de Energía', + memory: 'Memoria', + memoryUsed: 'Memoria Utilizada', + memoryTotal: 'Memoria Total', + percent: 'Utilización', + + base: 'Información Básica', + driverVersion: 'Versión del Controlador', + cudaVersion: 'Versión de CUDA', + processMemoryUsage: 'Uso de Memoria', + performanceStateHelper: 'Desde P0 (rendimiento máximo) hasta P12 (rendimiento mínimo)', + busID: 'Dirección del Bus', + persistenceMode: 'Modo de Persistencia', + enabled: 'Habilitado', + disabled: 'Deshabilitado', + persistenceModeHelper: + 'El modo de persistencia responde a las tareas más rápidamente, pero el consumo de energía en espera aumentará en consecuencia', + displayActive: 'Inicialización de GPU', + displayActiveT: 'Sí', + displayActiveF: 'No', + ecc: 'Tecnología de Corrección de Errores', + computeMode: 'Modo de Computación', + default: 'Predeterminado', + exclusiveProcess: 'Proceso Exclusivo', + exclusiveThread: 'Hilo Exclusivo', + prohibited: 'Prohibido', + defaultHelper: 'Predeterminado: Los procesos pueden ejecutarse concurrentemente', + exclusiveProcessHelper: + 'Proceso Exclusivo: Solo un contexto CUDA puede usar la GPU, pero puede ser compartido por múltiples hilos', + exclusiveThreadHelper: 'Hilo Exclusivo: Solo un hilo en un contexto CUDA puede usar la GPU', + prohibitedHelper: 'Prohibido: No se permite la ejecución concurrente de procesos', + migModeHelper: + 'Se utiliza para crear instancias MIG, implementando aislamiento físico de GPU en la capa de usuario.', + migModeNA: 'No Compatible', + current: 'Monitoreo en Tiempo Real', + history: 'Registros Históricos', + notSupport: 'La versión actual o el controlador no admiten mostrar este parámetro.', + }, + mcp: { + server: 'Servidor MCP', + create: 'Agregar servidor MCP', + edit: 'Editar servidor MCP', + baseUrl: 'Ruta de acceso externa', + baseUrlHelper: 'Por ejemplo: http://192.168.1.2:8000', + ssePath: 'Ruta SSE', + ssePathHelper: 'Por ejemplo: /sse, asegúrese de que no se repita con otros servidores', + environment: 'Variables de entorno', + envKey: 'Nombre de la variable', + envValue: 'Valor de la variable', + externalUrl: 'Dirección de conexión externa', + operatorHelper: 'Se realizará la operación {1} en {0}, ¿desea continuar?', + domain: 'Dirección de acceso predeterminada', + domainHelper: 'Por ejemplo: 192.168.1.1 o ejemplo.com', + bindDomain: 'Vincular sitio web', + commandPlaceHolder: 'Actualmente solo se admiten comandos de inicio npx y binarios', + importMcpJson: 'Importar configuración del servidor MCP', + importMcpJsonError: 'La estructura de mcpServers es incorrecta', + bindDomainHelper: + 'Al vincular el sitio web, se modificará la dirección de acceso de todos los servidores MCP instalados y se cerrará el acceso externo a los puertos', + outputTransport: 'Tipo de salida', + streamableHttpPath: 'Ruta de transmisión', + streamableHttpPathHelper: + 'Por ejemplo: /mcp, tenga en cuenta que no debe superponerse con otros servidores', + npxHelper: 'Adecuado para mcp iniciado con npx o binario', + uvxHelper: 'Adecuado para mcp iniciado con uvx', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: 'Directorio del Modelo', + commandHelper: + 'Si se necesita acceso externo, establezca el puerto en el comando para que sea el mismo que el puerto de la aplicación', + imageAlert: + 'Debido al gran tamaño de la imagen, se recomienda descargar manualmente la imagen al servidor antes de la instalación', + modelSpeedup: 'Habilitar aceleración de modelo', + modelType: 'Tipo de modelo', + }, + }, + container: { + create: 'Crear', + createByCommand: 'Crear por comando', + commandInput: 'Introducir comando', + commandRule: 'Por favor introduzca el comando correcto para crear el contenedor con docker run.', + commandHelper: 'Este comando se ejecutará en el servidor para crear el contenedor. ¿Desea continuar?', + edit: 'Editar contenedor', + updateHelper1: + 'Se detectó que este contenedor proviene de la tienda de aplicaciones. Tenga en cuenta lo siguiente:', + updateHelper2: + '1. Las modificaciones actuales no se sincronizarán con las aplicaciones instaladas en la tienda.', + updateHelper3: + '2. Si modifica la aplicación en la página de instalados, el contenido editado actualmente quedará inválido.', + updateHelper4: + 'Editar el contenedor requiere reconstruirlo y se perderán los datos no persistentes. ¿Desea continuar?', + containerList: 'Lista de contenedores', + operatorHelper: 'Se realizará {0} en el siguiente contenedor, ¿desea continuar?', + operatorAppHelper: + 'La operación "{0}" se realizará en los siguientes contenedores y puede afectar los servicios en ejecución. ¿Desea continuar?', + containerDeleteHelper: + "Se detectó que el contenedor proviene de la Tienda de Aplicaciones. Eliminar el contenedor no lo eliminará completamente de 1Panel. Para eliminarlo por completo, vaya a la Tienda de Aplicaciones -> menús 'Instalado' o 'Entorno de Ejecución'. ¿Continuar?", + start: 'Iniciar', + stop: 'Detener', + restart: 'Reiniciar', + kill: 'Finalizar', + pause: 'Pausar', + unpause: 'Reanudar', + rename: 'Renombrar', + remove: 'Eliminar', + removeAll: 'Eliminar todos', + containerPrune: 'Limpiar', + containerPruneHelper1: 'Esto eliminará todos los contenedores que estén detenidos.', + containerPruneHelper2: + 'Si los contenedores son de la tienda de aplicaciones, vaya a "Tienda de Aplicaciones -> Instaladas" y haga clic en "Reconstruir" para reinstalarlos después de la limpieza.', + containerPruneHelper3: 'Esta operación no se puede deshacer. ¿Desea continuar?', + imagePrune: 'Limpiar', + imagePruneSome: 'Limpiar sin etiqueta', + imagePruneSomeEmpty: 'No hay imágenes con la etiqueta "none" para limpiar.', + imagePruneSomeHelper: + 'Limpia las imágenes con la etiqueta "none" que no estén siendo usadas por ningún contenedor.', + imagePruneAll: 'Limpiar no usadas', + imagePruneAllEmpty: 'No hay imágenes sin uso para limpiar.', + imagePruneAllHelper: 'Limpia las imágenes que no estén siendo usadas por ningún contenedor.', + networkPrune: 'Limpiar', + networkPruneHelper: 'Esto eliminará todas las redes no utilizadas. ¿Desea continuar?', + volumePrune: 'Limpiar', + volumePruneHelper: 'Esto eliminará todos los volúmenes locales no utilizados. ¿Desea continuar?', + cleanSuccess: 'La operación se completó correctamente, cantidad limpiada: {0}!', + cleanSuccessWithSpace: + 'La operación se completó correctamente. El número de discos limpiados esta vez es {0}. El espacio liberado es {1}!', + unExposedPort: 'La dirección de mapeo del puerto actual es 127.0.0.1, lo que impide el acceso externo.', + upTime: 'Tiempo activo', + fetch: 'Obtener', + lines: 'Líneas', + linesHelper: 'Por favor introduzca el número correcto de líneas de logs a recuperar.', + lastDay: 'Último día', + last4Hour: 'Últimas 4 horas', + lastHour: 'Última hora', + last10Min: 'Últimos 10 minutos', + cleanLog: 'Limpiar logs', + downLogHelper1: 'Esto descargará todos los logs del contenedor {0}. ¿Desea continuar?', + downLogHelper2: 'Esto descargará los últimos {0} logs del contenedor {0}. ¿Desea continuar?', + cleanLogHelper: 'Esto requerirá reiniciar el contenedor y no se puede deshacer. ¿Desea continuar?', + newName: 'Nuevo nombre', + workingDir: 'Directorio de trabajo', + source: 'Uso de recursos', + cpuUsage: 'Uso de CPU', + cpuTotal: 'CPU total', + core: 'Núcleo', + memUsage: 'Uso de memoria', + memTotal: 'Límite de memoria', + memCache: 'Caché de memoria', + loadSize: 'Obtener Tamaño del Contenedor', + ip: 'Dirección IP', + cpuShare: 'Proporción de CPU', + cpuShareHelper: + 'El motor de contenedores usa un valor base de 1024 para la proporción de CPU. Puede aumentarlo para darle al contenedor más tiempo de CPU.', + inputIpv4: 'Ejemplo: 192.168.1.1', + inputIpv6: 'Ejemplo: 2001:0db8:85a3:0000:0000:8a2e:0370:7334', + + diskUsage: 'Uso del Disco', + localVolume: 'Volumen de Almacenamiento Local', + buildCache: 'Caché de Construcción', + usage: 'Usado: {0}, Liberable: {1}', + clean: 'Liberar', + imageClean: + 'Limpiar imágenes eliminará todas las imágenes no utilizadas. Esta operación no se puede deshacer. ¿Continuar?', + containerClean: + 'Limpiar contenedores eliminará todos los contenedores detenidos (incluidas las aplicaciones detenidas de la Tienda de Aplicaciones). Esta operación no se puede deshacer. ¿Continuar?', + sizeRw: 'Tamaño de Capa de Contenedor', + sizeRwHelper: 'Tamaño de la capa escribible exclusiva del contenedor', + sizeRootFs: 'Tamaño Virtual', + sizeRootFsHelper: + 'Tamaño total de todas las capas de imagen de las que depende el contenedor + capa del contenedor', + + containerFromAppHelper: + 'Se detectó que este contenedor proviene de la tienda de aplicaciones. Las operaciones sobre la app pueden invalidar los cambios actuales.', + containerFromAppHelper1: + 'Haga clic en el botón [Parámetros] en la lista de aplicaciones instaladas para acceder a la página de edición y modificar el nombre del contenedor.', + command: 'Comando', + console: 'Interacción con el contenedor', + tty: 'Asignar un pseudo-TTY (-t)', + openStdin: 'Mantener STDIN abierto (-i)', + custom: 'Personalizado', + emptyUser: 'Si está vacío, iniciará sesión como predeterminado', + privileged: 'Privilegiado', + privilegedHelper: + 'Permite que el contenedor realice ciertas operaciones privilegiadas en el host, lo que puede aumentar los riesgos. ¡Úselo con precaución!', + upgradeHelper: 'Nombre de repositorio/imagen: versión de la imagen', + upgradeWarning2: + 'La operación de actualización requiere reconstruir el contenedor, cualquier dato no persistente se perderá. ¿Desea continuar?', + oldImage: 'Imagen actual', + sameImageContainer: 'Contenedores de la misma imagen', + sameImageHelper: + 'Los contenedores que usan la misma imagen pueden actualizarse en lote después de seleccionarlos', + targetImage: 'Imagen objetivo', + imageLoadErr: 'No se detectó un nombre de imagen para el contenedor', + appHelper: + 'El contenedor proviene de la tienda de aplicaciones, y al actualizar podría hacer que el servicio no esté disponible.', + resource: 'Recurso', + input: 'Introducir manualmente', + forcePull: 'Siempre descargar imagen', + forcePullHelper: + 'Esto ignorará las imágenes existentes en el servidor y descargará la más reciente desde el repositorio.', + server: 'Servidor', + serverExample: '80, 80-88, ip:80 o ip:80-88', + containerExample: '80 o 80-88', + exposePort: 'Exponer puerto', + exposeAll: 'Exponer todos', + cmdHelper: 'Ejemplo: nginx -g "daemon off;"', + entrypointHelper: 'Ejemplo: docker-entrypoint.sh', + autoRemove: 'Eliminar automáticamente', + cpuQuota: 'Cantidad de núcleos de CPU', + memoryLimit: 'Memoria', + limitHelper: 'Si se establece en 0, no hay limitación. El valor máximo es {0}', + macAddr: 'Dirección MAC', + mount: 'Montaje', + volumeOption: 'Volumen', + hostOption: 'Host', + serverPath: 'Ruta en el servidor', + containerDir: 'Ruta en el contenedor', + volumeHelper: 'Asegúrese de que el contenido del volumen de almacenamiento sea correcto', + networkEmptyHelper: 'Por favor confirme que la selección de red del contenedor es correcta', + modeRW: 'RW', + modeR: 'R', + sharedLabel: 'Modo de Propagación', + private: 'Privado', + privateHelper: 'Los cambios de montaje en el contenedor y el host no se afectan mutuamente', + rprivate: 'Privado Recursivo', + rprivateHelper: 'Todos los montajes en el contenedor están completamente aislados del host', + shared: 'Compartido', + sharedHelper: 'Los cambios de montaje en el host y el contenedor son visibles entre sí', + rshared: 'Compartido Recursivo', + rsharedHelper: 'Todos los cambios de montaje en el host y el contenedor son visibles entre sí', + slave: 'Esclavo', + slaveHelper: + 'El contenedor puede ver los cambios de montaje del host, pero sus propios cambios no afectan al host', + rslave: 'Esclavo Recursivo', + rslaveHelper: 'Todos los montajes en el contenedor pueden ver los cambios del host, pero no afectan al host', + mode: 'Modo', + env: 'Entornos', + restartPolicy: 'Política de reinicio', + always: 'siempre', + unlessStopped: 'a menos que se detenga', + onFailure: 'al fallar (cinco veces por defecto)', + no: 'nunca', + refreshTime: 'Intervalo de actualización', + cache: 'Caché', + image: 'Imagen | Imágenes', + imagePull: 'Descargar', + imagePullHelper: + 'Admite seleccionar múltiples imágenes para descargar, presione Enter después de ingresar cada imagen para continuar', + imagePush: 'Subir', + imagePushHelper: + 'Detected that this image has multiple tags. Please confirm that the image name used for pushing is: {0}', + imageDelete: 'Eliminar imagen', + repoName: 'Repositorio de contenedores', + imageName: 'Nombre de la imagen', + pull: 'Descargar', + path: 'Ruta', + importImage: 'Importar', + buildArgs: 'Argumentos de Construcción', + imageBuild: 'Construir', + pathSelect: 'Ruta', + label: 'Etiqueta', + imageTag: 'Etiqueta de imagen', + imageTagHelper: + 'Admite configurar múltiples etiquetas de imagen, presione Enter después de ingresar cada etiqueta para continuar', + push: 'Subir', + fileName: 'Nombre de archivo', + export: 'Exportar', + exportImage: 'Exportar imagen', + size: 'Tamaño', + tag: 'Etiquetas', + tagHelper: 'Una por línea. Por ejemplo,\nkey1=value1\nkey2=value2', + imageNameHelper: 'Nombre de imagen y etiqueta, por ejemplo: nginx:latest', + cleanBuildCache: 'Limpiar caché de compilación', + delBuildCacheHelper: + 'Esto eliminará todos los artefactos almacenados en caché generados durante las compilaciones y no se puede deshacer. ¿Desea continuar?', + urlWarning: 'El prefijo de la URL no debe incluir http:// o https://. Por favor modifique.', + network: 'Red | Redes', + networkHelper: + 'Esto puede causar que algunas aplicaciones o entornos no funcionen correctamente. ¿Desea continuar?', + createNetwork: 'Crear', + networkName: 'Nombre', + driver: 'Driver', + option: 'Opción', + attachable: 'Adjuntable', + parentNetworkCard: 'Tarjeta de Red Principal', + subnet: 'Subred', + scope: 'Rango de IP', + gateway: 'Puerta de enlace', + auxAddress: 'Excluir IP', + volume: 'Volumen | Volúmenes', + volumeDir: 'Directorio de volumen', + nfsEnable: 'Habilitar almacenamiento NFS', + nfsAddress: 'Dirección', + mountpoint: 'Punto de montaje', + mountpointNFSHelper: 'ej: /nfs, /nfs-share', + options: 'Opciones', + createVolume: 'Crear', + repo: 'Repositorio de contenedores | Repositorios de contenedores', + createRepo: 'Agregar', + httpRepoHelper: 'Operar un repositorio tipo HTTP requiere reiniciar el servicio Docker.', + httpRepo: + 'Elegir el protocolo HTTP requiere reiniciar el servicio Docker para agregarlo a los registros inseguros.', + delInsecure: 'Eliminar registro inseguro', + delInsecureHelper: + 'Esto reiniciará el servicio Docker para quitarlo de los registros inseguros. ¿Desea continuar?', + downloadUrl: 'Servidor', + imageRepo: 'Repositorio de imágenes', + repoHelper: '¿Incluye un repositorio/organización/proyecto mirror?', + auth: 'Requiere autenticación', + mirrorHelper: + 'Si hay varios mirrors, deben estar en líneas separadas. Ejemplo:\nhttp://xxxxxx.m.daocloud.io\nhttps://xxxxxx.mirror.aliyuncs.com', + registrieHelper: + 'Si existen varios repositorios privados, deben estar en líneas separadas. Ejemplo:\n172.16.10.111:8081\n172.16.10.112:8081', + + compose: 'Compose | Composes', + composeFile: 'Archivo de Orquestación', + fromChangeHelper: 'Cambiar la fuente limpiará el contenido actualmente editado. ¿Desea continuar?', + composePathHelper: 'Ruta de guardado del archivo de configuración: {0}', + composeHelper: + 'La composición creada mediante el editor o plantilla de 1Panel se guardará en el directorio {0}/docker/compose.', + deleteFile: 'Eliminar archivo', + deleteComposeHelper: + 'Elimina todos los archivos relacionados con la composición de contenedores, incluidos los archivos de configuración y los persistentes. ¡Proceda con precaución!', + deleteCompose: '" Eliminar esta composición.', + createCompose: 'Crear', + composeDirectory: 'Directorio de compose', + template: 'Plantilla', + composeTemplate: 'Plantilla de compose | Plantillas de compose', + createComposeTemplate: 'Crear', + content: 'Contenido', + contentEmpty: 'El contenido de Compose no puede estar vacío, por favor introduzca algo y vuelva a intentarlo.', + containerNumber: 'Número de contenedores', + containerStatus: 'Estado del contenedor', + exited: 'Finalizado', + running: 'En ejecución', + composeDetailHelper: + 'La composición fue creada externamente a 1Panel. Las operaciones de inicio y detención no son compatibles.', + composeOperatorHelper: 'La operación {1} se realizará en {0}. ¿Desea continuar?', + composeDownHelper: + 'Esto detendrá y eliminará todos los contenedores y redes bajo la composición {0}. ¿Desea continuar?', + composeEnvHelper2: + 'Esta orquestación fue creada por la Tienda de Aplicaciones 1Panel. Modifique las variables de entorno en las aplicaciones instaladas.', + + setting: 'Configuración | Configuraciones', + goSetting: 'Ir a editar', + operatorStatusHelper: 'Esto "{0}" el servicio Docker. ¿Desea continuar?', + dockerStatus: 'Servicio Docker', + daemonJsonPathHelper: + 'Asegúrese de que la ruta de configuración sea la misma que la especificada en docker.service.', + mirrors: 'Mirrors del registro', + mirrorsHelper: '', + mirrorsHelper2: 'Para más detalles, consulte la documentación oficial.', + registries: 'Registros inseguros', + ipv6Helper: + 'Al habilitar IPv6, necesita agregar una red de contenedores IPv6. Consulte la documentación oficial para pasos específicos de configuración.', + ipv6CidrHelper: 'Rango de pool de direcciones IPv6 para contenedores', + ipv6TablesHelper: 'Configuración automática de reglas iptables para Docker IPv6.', + experimentalHelper: + 'Para habilitar ip6tables, debe activar esta configuración; de lo contrario, se ignorará ip6tables.', + cutLog: 'Opción de logs', + cutLogHelper1: 'La configuración actual solo afectará a los contenedores nuevos.', + cutLogHelper2: 'Los contenedores existentes deben ser recreados para que la configuración surta efecto.', + cutLogHelper3: + 'Tenga en cuenta que recrear los contenedores puede provocar la pérdida de datos. Si contienen información importante, realice una copia de seguridad antes de reconstruir.', + maxSize: 'Tamaño máximo', + maxFile: 'Archivo máximo', + liveHelper: + 'Por defecto, cuando el demonio Docker termina, detiene los contenedores en ejecución. Puede configurar el demonio para que los contenedores sigan en ejecución si el demonio no está disponible. Esta función se llama "live restore" y ayuda a reducir el tiempo fuera de servicio por caídas, mantenimientos o actualizaciones.', + liveWithSwarmHelper: 'La configuración live-restore es incompatible con el modo swarm.', + iptablesDisable: 'Desactivar iptables', + iptablesHelper1: 'Configuración automática de reglas iptables para Docker.', + iptablesHelper2: 'Deshabilitar iptables impedirá que los contenedores se comuniquen con redes externas.', + daemonJsonPath: 'Ruta de configuración', + serviceUnavailable: 'El servicio Docker no está iniciado actualmente.', + startIn: ' para iniciar', + sockPath: 'Socket Unix', + sockPathHelper: 'Canal de comunicación entre el demonio Docker y el cliente.', + sockPathHelper1: 'Ruta predeterminada: /var/run/docker-x.sock', + sockPathMsg: + 'Guardar la configuración del Socket puede dejar el servicio Docker fuera de servicio. ¿Desea continuar?', + sockPathErr: 'Por favor seleccione o introduzca la ruta correcta del archivo Docker sock', + related: 'Relacionado', + includeAppstore: 'Mostrar contenedores de la tienda', + excludeAppstore: 'Ocultar contenedores de la tienda', + cleanDockerDiskZone: 'Limpiar el espacio en disco usado por Docker', + cleanImagesHelper: '( Limpia todas las imágenes que no están siendo usadas por ningún contenedor )', + cleanContainersHelper: '( Limpia todos los contenedores detenidos )', + cleanVolumesHelper: '( Limpia todos los volúmenes locales no utilizados )', + makeImage: 'Crear imagen', + newImageName: 'Nuevo nombre de imagen', + commitMessage: 'Mensaje de commit', + author: 'Autor', + ifPause: '¿Pausar el contenedor durante la creación?', + ifMakeImageWithContainer: '¿Crear nueva imagen a partir de este contenedor?', + finishTime: 'Hora de la última detención', + }, + cronjob: { + create: 'Crear tarea programada', + edit: 'Editar tarea programada', + importHelper: + 'Las tareas programadas duplicadas se omitirán automáticamente durante la importación. Las tareas se establecerán en estado [Deshabilitado] por defecto, y en estado [Pendiente de edición] cuando la asociación de datos sea anormal.', + changeStatus: 'Cambiar estado', + disableMsg: 'Esto detendrá la ejecución automática de la tarea programada. ¿Desea continuar?', + enableMsg: 'Esto permitirá la ejecución automática de la tarea programada. ¿Desea continuar?', + taskType: 'Tipo', + nextTime: 'Próximas 5 ejecuciones', + record: 'Registros', + viewRecords: 'Ver registros', + shell: 'Shell', + stop: 'Detención Manual', + stopHelper: 'Esta operación forzará la detención de la ejecución de la tarea actual. ¿Continuar?', + log: 'Registros de respaldo', + logHelper: 'Registro del sistema de copias de seguridad', + ogHelper1: '1. Registro del sistema de 1Panel', + logHelper2: '2. Registro de inicio de sesión SSH del servidor', + logHelper3: '3. Todos los registros del sitio web', + containerCheckBox: 'En contenedor (no necesita introducir el comando del contenedor)', + containerName: 'Nombre del contenedor', + ntp: 'Sincronización horaria', + ntp_helper: 'Puede configurar el servidor NTP en la página de Configuración Rápida de la Caja de Herramientas.', + app: 'Respaldo de aplicación', + website: 'Respaldo de sitio web', + rulesHelper: + 'Si hay varias reglas de exclusión de compresión, deben estar en líneas separadas. Ejemplo:\n*.log\n*.sql', + lastRecordTime: 'Hora de la última ejecución', + all: 'Todos', + failedRecord: 'Registros fallidos', + successRecord: 'Registros completados correctamente', + database: 'Respaldo de base de datos', + backupArgs: 'Argumentos de Copia de Seguridad', + backupArgsHelper: + 'Los argumentos de copia de seguridad no listados pueden ser ingresados y seleccionados manualmente. Por ejemplo: Ingrese --no-data y seleccione la primera opción de la lista desplegable.', + singleTransaction: + 'Copia de seguridad de tablas InnoDB utilizando una única transacción, adecuada para copias de seguridad de datos de gran volumen', + quick: 'Lee datos fila por fila en lugar de cargar la tabla completa en memoria, adecuado para copias de seguridad de datos de gran volumen y máquinas con poca memoria', + skipLockTables: + 'Copia de seguridad sin bloquear todas las tablas, adecuada para bases de datos altamente concurrentes', + missBackupAccount: 'No se pudo encontrar la cuenta de respaldo', + syncDate: 'Hora de sincronización', + clean: 'Limpiar caché', + curl: 'Acceder a URL', + taskName: 'Nombre', + cronSpec: 'Ciclo de ejecución', + cronSpecDoc: + 'Custom execution cycles only support the [minute hour day month week] format, e.g., 0 0 * * *. For details, please refer to the official documentation.', + cronSpecHelper: 'Introduzca el periodo de ejecución correcto', + cleanHelper: + 'Esta operación registra todos los registros de ejecución de tareas, archivos de respaldo y archivos de registro. ¿Desea continuar?', + backupContent: 'Contenido de respaldo', + directory: 'Directorio de respaldo', + sourceDir: 'Directorio de respaldo', + snapshot: 'Instantánea del sistema', + allOptionHelper: `El plan actual es respaldar todos los [{0}]. La descarga directa no está soportada por ahora. Puede consultar la lista de respaldos en el menú [{0}].`, + exclusionRules: 'Reglas de exclusión', + exclusionRulesHelper: + 'Seleccione o ingrese reglas de exclusión, presione Enter después de cada conjunto para continuar. Las reglas de exclusión se aplicarán a todas las operaciones de compresión en esta copia de seguridad', + default_download_path: 'Enlace de descarga predeterminado', + saveLocal: 'Retener respaldos locales (igual al número de copias en la nube)', + url: 'Dirección URL', + urlHelper: 'Por favor ingrese una dirección URL válida', + targetHelper: 'Las cuentas de respaldo se gestionan en los ajustes del panel.', + withImageHelper: + 'Respalda imágenes de la tienda de aplicaciones, pero esto aumentará el tamaño del archivo de la instantánea.', + ignoreApp: 'Excluir aplicaciones', + withImage: 'Copia de Seguridad de la Imagen de la Aplicación', + retainCopies: 'Retener registros', + retryTimes: 'Intentos de reintento', + timeout: 'Tiempo de espera', + ignoreErr: 'Ignorar errores', + ignoreErrHelper: 'Ignora los errores durante el respaldo para asegurar que todas las tareas se completen', + retryTimesHelper: '0 significa que no habrá reintentos tras un fallo', + retainCopiesHelper: 'Número de copias a retener para registros y logs de ejecución', + retainCopiesHelper1: 'Número de copias a retener para los archivos de respaldo', + retainCopiesUnit: ' copias (Ver)', + cronSpecRule: + 'El formato del periodo de ejecución en la línea {0} es incorrecto. ¡Por favor verifique e intente nuevamente!', + cronSpecRule2: 'El formato del periodo de ejecución es incorrecto, por favor verifique e intente nuevamente.', + perMonthHelper: 'Ejecutar el día {0} de cada mes a las {1}:{2}', + perWeekHelper: 'Ejecutar cada semana el {0} a las {1}:{2}', + perDayHelper: 'Ejecutar todos los días a las {0}:{1}', + perHourHelper: 'Ejecutar cada hora en el minuto {0}', + perNDayHelper: 'Ejecutar cada {0} días a las {1}:{2}', + perNHourHelper: 'Ejecutar cada {0} horas en el minuto {1}', + perNMinuteHelper: 'Ejecutar cada {0} minutos', + perNSecondHelper: 'Ejecutar cada {0} segundos', + perMonth: 'Cada mes', + perWeek: 'Cada semana', + perHour: 'Cada hora', + perNDay: 'Cada N días', + perDay: 'Cada día', + perNHour: 'Cada N horas', + perNMinute: 'Cada N minutos', + perNSecond: 'Cada N segundos', + day: 'día(s)', + monday: 'Lunes', + tuesday: 'Martes', + wednesday: 'Miércoles', + thursday: 'Jueves', + friday: 'Viernes', + saturday: 'Sábado', + sunday: 'Domingo', + shellContent: 'Script', + executor: 'Ejecutor', + errRecord: 'Registro incorrecto', + errHandle: 'Fallo en la ejecución de la tarea programada', + noRecord: 'Al activar la tarea programada, verá aquí los registros.', + cleanData: 'Limpiar datos', + cleanRemoteData: 'Eliminar datos remotos', + cleanDataHelper: 'Elimina el archivo de respaldo generado por esta tarea.', + noLogs: 'Aún no hay salida de tareas...', + errPath: '¡Error en la ruta de respaldo [{0}], no se puede descargar!', + cutWebsiteLog: 'Rotación de logs del sitio', + cutWebsiteLogHelper: 'Los archivos de log rotados se respaldarán en el directorio de respaldos de 1Panel.', + syncIpGroup: 'Sincronizar grupos de IP de WAF', + + requestExpirationTime: 'Tiempo de expiración de solicitud de subida (Horas)', + unitHours: 'Unidad: Horas', + alertTitle: 'Tarea programada - {0} 「{1}」 Alerta de fallo', + library: { + script: 'Script', + syncNow: 'Sincronizar Ahora', + turnOnSync: 'Activar Sincronización Automática', + turnOnSyncHelper: + 'Activar la sincronización automática realizará sincronizaciones automáticas durante las primeras horas de la madrugada diariamente', + turnOffSync: 'Desactivar Sincronización Automática', + turnOffSyncHelper: + 'Desactivar la sincronización automática puede causar retrasos en la sincronización de scripts, ¿confirmar?', + isInteractive: 'Interactivo', + interactive: 'Script interactivo', + interactiveHelper: + 'Requiere introducir datos durante la ejecución y no puede ser usada en tareas programadas.', + remoteLibrary: 'Biblioteca de Scripts Remota', + library: 'Librería de scripts', + create: 'Agregar script', + edit: 'Editar script', + groupHelper: + 'Establezca diferentes grupos según las características de los scripts, esto facilita el filtrado.', + handleHelper: 'Ejecutar el script {1} en {0}, ¿desea continuar?', + noSuchApp: 'No se detectó el servicio {0}. ¡Por favor instálelo desde la librería de scripts!', + syncHelper: + 'Se va a sincronizar la librería de scripts del sistema. Solo afecta a los scripts del sistema. ¿Desea continuar?', + }, + }, + monitor: { + globalFilter: 'Filtro Global', + enableMonitor: 'Estado de Monitoreo', + storeDays: 'Días de Retención', + defaultNetwork: 'Tarjeta de Red Predeterminada', + defaultNetworkHelper: + 'Opción de tarjeta de red predeterminada mostrada en las interfaces de monitoreo y resumen', + defaultIO: 'Disco Predeterminado', + defaultIOHelper: 'Opción de disco predeterminada mostrada en las interfaces de monitoreo y resumen', + cleanMonitor: 'Limpiar Registros de Monitoreo', + cleanHelper: 'Esta operación borrará todos los registros de monitoreo, incluidos los de GPU. ¿Continuar?', + + avgLoad: 'Carga promedio', + loadDetail: 'Detalle de carga', + resourceUsage: 'Utilización', + networkCard: 'Interfaz de red', + read: 'Lectura', + write: 'Escritura', + readWriteCount: 'Operaciones de E/S', + readWriteTime: 'Latencia de E/S', + today: 'Hoy', + yesterday: 'Ayer', + lastNDay: 'Últimos {0} días', + lastNMonth: 'Últimos {0} meses', + lastHalfYear: 'Último semestre', + memory: 'Memoria', + percent: 'Porcentaje', + cache: 'Caché', + disk: 'Disco', + network: 'Red', + up: 'Subida', + down: 'Bajada', + interval: 'Intervalo de Recolección', + intervalHelper: 'Ingrese un intervalo de recolección de monitoreo apropiado (5 segundos - 12 horas)', + }, + terminal: { + local: 'Local', + defaultConn: 'Conexión predeterminada', + defaultConnHelper: + 'Esta operación conectará automáticamente al terminal del nodo después de abrir el terminal para 【{0}】. ¿Continuar?', + withReset: 'Restablecer Información de Conexión', + localConnJump: + 'La información de conexión predeterminada se mantiene en [Terminal - Configuración]. Si la conexión falla, ¡edite allí!', + localHelper: 'El nombre `local` se utiliza solo para identificación interna del sistema', + connLocalErr: + 'No se puede autenticar automáticamente, por favor introduzca la información de inicio de sesión del servidor local.', + testConn: 'Probar conexión', + saveAndConn: 'Guardar y conectar', + connTestOk: 'La información de conexión es válida', + connTestFailed: 'Conexión no disponible, por favor revise la información de conexión.', + host: 'Host | Hosts', + createConn: 'Nueva conexión', + noHost: 'Sin host', + groupChange: 'Cambiar grupo', + expand: 'Expandir todo', + fold: 'Contraer todo', + batchInput: 'Procesamiento por lotes', + quickCommand: 'Comando rápido | Comandos rápidos', + noSuchCommand: + 'No se encontraron datos de comandos rápidos en el archivo CSV importado, ¡compruebe e inténtelo de nuevo!', + quickCommandHelper: 'Puede usar comandos rápidos en la parte inferior de "Terminales -> Terminales".', + groupDeleteHelper: + 'Después de eliminar el grupo, todas las conexiones pasarán al grupo predeterminado. ¿Desea continuar?', + command: 'Comando', + quickCmd: 'Comando rápido', + addHost: 'Agregar', + localhost: 'Localhost', + ip: 'Dirección', + authMode: 'Autenticación', + passwordMode: 'Contraseña', + rememberPassword: 'Recordar autenticación', + keyMode: 'Clave privada', + key: 'Clave privada', + keyPassword: 'Contraseña de la clave privada', + emptyTerminal: 'No hay ninguna terminal conectada actualmente.', + lineHeight: 'Altura de línea', + letterSpacing: 'Espaciado de letras', + fontSize: 'Tamaño de fuente', + cursorBlink: 'Parpadeo del cursor', + cursorStyle: 'Estilo de cursor', + cursorUnderline: 'Subrayado', + cursorBlock: 'Bloque', + cursorBar: 'Barra', + scrollback: 'Scrollback', + scrollSensitivity: 'Sensibilidad de scroll', + saveHelper: '¿Está seguro de que desea guardar la configuración actual de la terminal?', + }, + toolbox: { + common: { + toolboxHelper: 'Para dudas de instalación y uso, consulte', + }, + swap: { + swap: 'Partición Swap', + swapHelper1: 'El tamaño de swap debe ser de 1 a 2 veces la memoria física, ajustable según necesidades.', + swapHelper2: + 'Antes de crear un archivo swap, asegúrese de que el disco tenga espacio suficiente, ya que el archivo ocupará ese espacio.', + swapHelper3: + 'El swap ayuda a aliviar la presión de memoria, pero es solo un suplemento. Un uso excesivo puede afectar el rendimiento. Se recomienda aumentar memoria o optimizar el uso.', + swapHelper4: 'Se recomienda monitorear regularmente el uso de swap para asegurar el funcionamiento normal.', + swapDeleteHelper: + 'Esta operación eliminará la partición Swap {0}. Por seguridad, el archivo no se borrará automáticamente. Si requiere borrarlo, hágalo manualmente.', + saveHelper: '¡Por favor guarde primero la configuración!', + saveSwap: 'Guardar la configuración ajustará la partición Swap {0} a {1}. ¿Desea continuar?', + swapMin: 'El tamaño mínimo es de 40 KB. ¡Por favor modifique y vuelva a intentar!', + swapMax: 'El tamaño máximo es {0}. ¡Por favor modifique y vuelva a intentar!', + swapOff: 'El tamaño mínimo es de 40 KB. Si lo pone en 0, desactivará la partición Swap.', + }, + device: { + dnsHelper: 'Servidor DNS', + dnsAlert: '¡Atención! Modificar el archivo /etc/resolv.conf lo restaurará tras reiniciar el sistema.', + dnsHelper1: 'Si hay varios DNS, deben ir en líneas nuevas. Ejemplo:\n114.114.114.114\n8.8.8.8', + hostsHelper: 'Resolución de host', + hosts: 'Dominio', + hostAlert: 'Registros comentados ocultos, haga clic en Configuración completa para verlos o modificarlos.', + toolbox: 'Configuraciones rápidas', + hostname: 'Nombre del host', + passwd: 'Contraseña del sistema', + passwdHelper: 'Los caracteres no pueden incluir $ y &', + timeZone: 'Zona horaria del sistema', + localTime: 'Hora del servidor', + timeZoneChangeHelper: 'Cambiar la zona horaria requiere reiniciar el servicio. ¿Continuar?', + timeZoneHelper: + 'Si no tiene instalado "timedatectl", no podrá cambiar la zona horaria porque el sistema lo usa.', + timeZoneCN: 'Beijing', + timeZoneAM: 'Los Ángeles', + timeZoneNY: 'Nueva York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'Servidor NTP', + hostnameHelper: 'El cambio de nombre depende de "hostnamectl". Si no está instalado, podría fallar.', + userHelper: 'El nombre de usuario depende de "whoami". Si no está instalado, podría fallar.', + passwordHelper: 'El cambio de contraseña depende de "chpasswd". Si no está instalado, podría fallar.', + hostHelper: 'Hay un valor vacío en el contenido. Por favor, revise y modifique.', + dnsCheck: 'Probar disponibilidad', + dnsOK: '¡La configuración DNS es válida!', + dnsTestFailed: 'La configuración DNS no es válida.', + }, + fail2ban: { + sshPort: 'Puerto SSH', + sshPortHelper: 'Fail2ban escucha el puerto SSH del host', + unActive: 'El servicio Fail2ban no está habilitado actualmente.', + operation: 'Va a realizar la operación "{0}" en Fail2ban. ¿Desea continuar?', + fail2banChange: 'Modificación de configuración de Fail2ban', + ignoreHelper: 'La lista de IPs permitidas será ignorada para el bloqueo. ¿Desea continuar?', + bannedHelper: 'La lista de IPs bloqueadas será bloqueada por el servidor. ¿Desea continuar?', + maxRetry: 'Intentos máximos', + banTime: 'Tiempo de bloqueo', + banTimeHelper: 'El tiempo por defecto es 10 minutos, -1 es bloqueo permanente', + banTimeRule: 'Por favor, introduzca un tiempo válido o -1', + banAllTime: 'Bloqueo permanente', + findTime: 'Periodo de detección', + banAction: 'Acción de bloqueo', + banActionOption: 'Bloquear las IP especificadas usando {0}', + allPorts: ' (Todos los puertos)', + ignoreIP: 'Lista blanca de IP', + bannedIP: 'Lista negra de IP', + logPath: 'Ruta del log', + logPathHelper: 'Por defecto /var/log/secure o /var/log/auth.log', + }, + ftp: { + ftp: 'Cuenta FTP | Cuentas FTP', + notStart: 'El servicio FTP no está iniciado, ¡inícielo primero!', + operation: 'Se realizará la operación "{0}" en el servicio FTP. ¿Desea continuar?', + noPasswdMsg: 'No se pudo obtener la contraseña actual de la cuenta FTP, ¡asigne una y vuelva a intentar!', + enableHelper: 'Habilitar la cuenta FTP restaurará los permisos de acceso. ¿Desea continuar?', + disableHelper: 'Deshabilitar la cuenta FTP revocará los permisos de acceso. ¿Desea continuar?', + syncHelper: 'Sincronizar datos de cuentas FTP entre servidor y base de datos. ¿Desea continuar?', + dirSystem: + 'Este directorio es reservado por el sistema. Modificarlo podría causar errores. ¡Modifíquelo con cuidado!', + dirHelper: 'Habilitar FTP requiere cambios en los permisos de directorio - elija cuidadosamente', + dirMsg: 'Habilitar FTP modificará los permisos de todo el directorio {0}. ¿Desea continuar?', + }, + clam: { + clam: 'Escaneo de virus', + cron: 'Escaneo programado', + cronHelper: 'La versión profesional soporta el escaneo programado', + specErr: 'Formato de horario incorrecto, ¡verifique e intente nuevamente!', + disableMsg: + 'Detener la ejecución programada impedirá que esta tarea se ejecute automáticamente. ¿Desea continuar?', + enableMsg: + 'Habilitar la ejecución programada permitirá que esta tarea se ejecute automáticamente. ¿Desea continuar?', + showFresh: 'Mostrar servicio de actualización de firmas', + hideFresh: 'Ocultar servicio de actualización de firmas', + clamHelper: + 'La configuración recomendada para ClamAV es: al menos 3 GiB de RAM, CPU de un núcleo a 2.0 GHz o más, y al menos 5 GiB de disco disponible.', + notStart: 'El servicio ClamAV no está iniciado, ¡inícielo primero!', + removeRecord: 'Eliminar archivos de informe', + noRecords: 'Haga clic en "Ejecutar" para iniciar el escaneo y aquí verá los registros.', + removeInfected: 'Eliminar archivos infectados', + removeInfectedHelper: + 'Elimina los archivos infectados detectados durante la tarea para asegurar el servidor.', + clamCreate: 'Crear regla de escaneo', + infectedStrategy: 'Estrategia para infectados', + removeHelper: 'Eliminar archivos infectados, ¡elija cuidadosamente!', + move: 'Mover', + moveHelper: 'Mover archivos infectados a un directorio específico', + copyHelper: 'Copiar archivos infectados a un directorio específico', + none: 'No hacer nada', + noneHelper: 'No realizar acciones sobre los archivos infectados', + scanDir: 'Directorio a escanear', + infectedDir: 'Directorio de infectados', + scanDate: 'Fecha de escaneo', + scanResult: 'Cola de logs de escaneo', + tail: 'Líneas', + infectedFiles: 'Archivos infectados', + log: 'Detalles', + clamConf: 'Clam AV daemon', + clamLog: '@:toolbox.clam.clamConf logs', + freshClam: 'FreshClam', + freshClamLog: '@:toolbox.clam.freshClam logs', + alertHelper: 'La versión profesional soporta escaneo programado y alertas por SMS', + alertTitle: 'La tarea de escaneo de virus 「{0}」 detectó archivos infectados', + }, + }, + logs: { + core: 'Servicio del panel', + agent: 'Monitoreo de nodo', + panelLog: 'Logs del panel', + operation: 'Logs de operación', + login: 'Logs de acceso', + loginIP: 'IP de acceso', + loginAddress: 'Dirección de acceso', + loginAgent: 'Agente de acceso', + loginStatus: 'Estado', + system: 'Logs del sistema', + deleteLogs: 'Limpiar logs', + resource: 'Recurso', + detail: { + dashboard: 'Resumen general', + ai: 'IA', + groups: 'Grupo', + hosts: 'Host', + apps: 'Aplicación', + websites: 'Sitio web', + containers: 'Contenedor', + files: 'Archivo', + runtimes: 'Runtime', + process: 'Proceso', + toolbox: 'Caja de herramientas', + backups: 'Respaldo / Restaurar', + tampers: 'Antimanipulación', + xsetting: 'Configuración de interfaz', + logs: 'Log', + settings: 'Configuración', + cronjobs: 'Tarea programada', + databases: 'Base de datos', + waf: 'WAF', + licenses: 'Licencia', + nodes: 'Nodo', + commands: 'Comandos rápidos', + }, + websiteLog: 'Logs de sitio web', + runLog: 'Logs de ejecución', + errLog: 'Logs de errores', + task: 'Log de tareas', + taskName: 'Nombre de la tarea', + taskRunning: 'En ejecución', + }, + file: { + fileDirNum: '{0} directorios, {1} archivos,', + currentDir: 'Directorio', + dir: 'Carpeta', + fileName: 'Nombre del archivo', + search: 'Buscar', + mode: 'Permisos', + editPermissions: 'Editar @.lower:file.mode', + owner: 'Propietario', + file: 'Archivo', + remoteFile: 'Descargar de remoto', + share: 'Compartir', + sync: 'Sincronización de datos', + size: 'Tamaño', + updateTime: 'Modificado', + rename: 'Renombrar', + role: 'Permisos', + info: 'Ver atributos', + linkFile: 'Enlace simbólico', + shareList: 'Lista de compartidos', + zip: 'Comprimido', + group: 'Grupo', + path: 'Ruta', + public: 'Otros', + setRole: 'Asignar permisos', + link: 'Enlace de archivo', + rRole: 'Lectura', + wRole: 'Escritura', + xRole: 'Ejecutable', + name: 'Nombre', + compress: 'Comprimir', + deCompress: 'Descomprimir', + compressType: 'Formato de compresión', + compressDst: 'Ruta de compresión', + replace: 'Sobrescribir archivos existentes', + compressSuccess: 'Compresión completada correctamente', + deCompressSuccess: 'Descompresión completada correctamente', + deCompressDst: 'Ruta de descompresión', + linkType: 'Tipo de enlace', + softLink: 'Enlace simbólico', + hardLink: 'Enlace físico', + linkPath: 'Ruta del enlace', + selectFile: 'Seleccionar archivo', + downloadUrl: 'URL remota', + downloadStart: 'Descarga iniciada', + moveSuccess: 'Movido correctamente', + copySuccess: 'Copiado correctamente', + pasteMsg: 'Por favor haz clic en el botón [Pegar] en la parte superior derecha del directorio de destino', + move: 'Mover', + calculate: 'Calcular', + remark: 'Observación', + setRemark: 'Establecer observación', + remarkPrompt: 'Ingrese una observación', + remarkPlaceholder: 'Observación', + remarkToggle: 'Notas', + remarkToggleTip: 'Cargar notas del archivo', + canNotDeCompress: 'No se puede descomprimir este archivo', + uploadSuccess: 'Carga completada correctamente', + downloadProcess: 'Progreso de descarga', + downloading: 'Descargando...', + infoDetail: 'Propiedades del archivo', + root: 'Directorio raíz', + list: 'Lista de archivos', + sub: 'Incluir subdirectorios', + downloadSuccess: 'Descarga completada correctamente', + theme: 'Tema', + language: 'Idioma', + eol: 'Fin de línea', + copyDir: 'Copiar', + paste: 'Pegar', + changeOwner: 'Modificar usuario y grupo', + containSub: 'Aplicar cambio de permisos recursivamente', + ownerHelper: + 'El usuario predeterminado del entorno PHP: el grupo es 1000:1000, es normal que haya diferencias entre usuario dentro y fuera del contenedor', + searchHelper: 'Soporta comodines como *', + uploadFailed: '[{0}] Fallo en la carga del archivo', + fileUploadStart: 'Cargando [{0}]....', + currentSelect: 'Seleccionado: ', + unsupportedType: 'Tipo de archivo no soportado', + deleteHelper: '¿Está seguro de eliminar los siguientes archivos? Por defecto irán a la papelera de reciclaje.', + fileHelper: + 'Nota:\n1. Los resultados de búsqueda no pueden ordenarse.\n2. Las carpetas no pueden ordenarse por tamaño.', + forceDeleteHelper: 'Eliminar permanentemente el archivo (sin pasar por la papelera, lo elimina directamente)', + recycleBin: 'Papelera', + sourcePath: 'Ruta original', + deleteTime: 'Hora de eliminación', + confirmReduce: '¿Desea restaurar los siguientes archivos?', + reduceSuccess: 'Restauración completada correctamente', + reduce: 'Restaurar', + reduceHelper: + 'Si hay un archivo o directorio con el mismo nombre en la ruta original, será sobrescrito. ¿Desea continuar?', + clearRecycleBin: 'Vaciar', + clearRecycleBinHelper: '¿Desea vaciar la papelera?', + favorite: 'Favoritos', + removeFavorite: '¿Eliminar de favoritos?', + addFavorite: 'Agregar/Quitar de favoritos', + clearList: 'Limpiar lista', + deleteRecycleHelper: '¿Está seguro de eliminar permanentemente los siguientes archivos?', + typeErrOrEmpty: 'El archivo [{0}] es de tipo incorrecto o carpeta vacía', + dropHelper: 'Arrastre aquí los archivos que desea cargar', + fileRecycleBin: 'Habilitar papelera de reciclaje', + fileRecycleBinMsg: '{0} papelera', + wordWrap: 'Ajuste de línea', + deleteHelper2: '¿Está seguro de eliminar el archivo seleccionado? No se puede deshacer.', + ignoreCertificate: 'Permitir conexiones inseguras', + ignoreCertificateHelper: + 'Permitir conexiones inseguras puede provocar fugas o alteraciones de datos. Úselo solo si confía en la fuente.', + uploadOverLimit: '¡El número de archivos supera los 1000! Por favor, comprima y cargue', + clashDitNotSupport: 'Los nombres de archivo no pueden contener .1panel_clash', + clashDeleteAlert: 'No se puede eliminar la carpeta "Papelera"', + clashOpenAlert: 'Haga clic en "Papelera" para abrir la carpeta de papelera', + right: 'Adelante', + back: 'Atrás', + top: 'Volver arriba', + up: 'Subir', + openWithVscode: 'Abrir con VS Code', + vscodeHelper: 'Asegúrese de tener instalado VS Code y configurado el plugin SSH Remote', + saveContentAndClose: 'El archivo se ha modificado, ¿desea guardar y cerrar?', + saveAndOpenNewFile: 'El archivo se ha modificado, ¿desea guardar y abrir el nuevo archivo?', + noEdit: 'El archivo no ha sido modificado, ¡no es necesario hacer esto!', + noNameFolder: 'Carpeta sin nombre', + noNameFile: 'Archivo sin nombre', + minimap: 'Mapa de código', + fileCanNotRead: 'No se puede leer el archivo', + previewTruncated: 'El archivo es demasiado grande, solo se muestra la última parte', + previewEmpty: 'El archivo está vacío o no es un archivo de texto', + previewLargeFile: 'Vista previa', + panelInstallDir: 'El directorio de instalación de 1Panel no puede eliminarse', + wgetTask: 'Tarea de descarga', + existFileTitle: 'Archivo con el mismo nombre', + existFileHelper: 'El archivo cargado contiene un archivo con el mismo nombre, ¿desea sobrescribirlo?', + existFileSize: 'Tamaño del archivo (nuevo -> viejo)', + existFileDirHelper: 'El archivo o carpeta seleccionado tiene nombre duplicado. ¡Proceda con precaución!\n', + coverDirHelper: '¡Las carpetas seleccionadas para reemplazo se copiarán en la ruta de destino!', + noSuchFile: 'No se encontró el archivo o directorio. Por favor verifique e intente de nuevo.', + setting: 'Configuración', + showHide: 'Mostrar archivos ocultos', + noShowHide: 'No mostrar archivos ocultos', + cancelUpload: 'Cancelar carga', + cancelUploadHelper: 'Indica si se cancela la carga; después de la cancelación, la lista de cargas se borrará.', + keepOneTab: 'Mantener al menos una pestaña', + notCanTab: 'No se pueden añadir más pestañas', + convert: 'Convertir Formato', + converting: 'Convirtiendo', + fileCanNotConvert: 'Este archivo no admite conversión de formato', + formatType: 'Tipo de Formato', + sourceFormat: 'Formato de Origen', + sourceFile: 'Archivo de Origen', + saveDir: 'Directorio de Guardado', + deleteSourceFile: 'Eliminar Archivo de Origen', + convertHelper: 'Convertir los archivos seleccionados a otro formato', + convertHelper1: 'Por favor, seleccione los archivos a convertir', + execConvert: 'Iniciar conversión. Puede ver los registros de conversión en el Centro de Tareas', + convertLogs: 'Registros de Conversión', + formatConvert: 'Conversión de Formato', + }, + ssh: { + autoStart: 'Inicio automático', + enable: 'Habilitar inicio automático', + disable: 'Deshabilitar inicio automático', + sshAlert: + 'La lista se ordena por fecha de inicio de sesión. Cambiar la zona horaria u otras operaciones pueden afectar las fechas.', + sshAlert2: + 'Puede usar "Fail2ban" en "Caja de Herramientas" para bloquear IPs que intentan ataques de fuerza bruta y así mejorar la seguridad.', + sshOperate: 'Se realizará la operación "{0}" en el servicio SSH. ¿Desea continuar?', + sshChange: 'Configuración SSH', + sshChangeHelper: 'Esta acción cambió "{0}" a "{1}". ¿Desea continuar?', + sshFileChangeHelper: + 'Modificar el archivo de configuración puede afectar la disponibilidad del servicio. Ejecute esta acción con precaución. ¿Desea continuar?', + port: 'Puerto', + portHelper: 'Especifique el puerto en el que escucha el servicio SSH.', + listenAddress: 'Dirección de escucha', + allV4V6: '0.0.0.0:{0}(IPv4) y :::{0}(IPv6)', + listenHelper: 'Si deja ambos campos en blanco, escuchará en "0.0.0.0:{0}(IPv4)" y ":::{0}(IPv6)".', + addressHelper: 'Especifique la dirección en la que escucha el servicio SSH.', + permitRootLogin: 'Permitir inicio de sesión root', + rootSettingHelper: 'El método predeterminado para root es "Permitir inicio de sesión SSH".', + rootHelper1: 'Permitir inicio SSH', + rootHelper2: 'Deshabilitar inicio SSH', + rootHelper3: 'Solo clave permitida', + rootHelper4: 'Solo se pueden ejecutar comandos predefinidos. No se permite ninguna otra operación.', + passwordAuthentication: 'Autenticación por contraseña', + pwdAuthHelper: 'Si habilitar o no la autenticación por contraseña. Por defecto está activada.', + pubkeyAuthentication: 'Autenticación por clave', + privateKey: 'Clave privada', + publicKey: 'Clave pública', + password: 'Contraseña', + createMode: 'Método de creación', + generate: 'Autogenerar', + unSyncPass: 'La contraseña de la clave no se puede sincronizar', + syncHelper: + 'La operación de sincronización limpiará las claves inválidas y sincronizará nuevos pares de claves completos. ¿Desea continuar?', + input: 'Entrada manual', + import: 'Subir archivo', + authKeys: 'Claves de Autorización', + authKeysHelper: '¿Guardar información actual de clave pública?', + pubkey: 'Información de clave', + pubKeyHelper: 'La información de la clave actual solo tiene efecto para el usuario {0}', + encryptionMode: 'Modo de cifrado', + passwordHelper: 'Puede contener entre 6 y 10 dígitos y letras en inglés', + reGenerate: 'Regenerar clave', + keyAuthHelper: 'Indica si se habilita la autenticación por clave.', + useDNS: 'useDNS', + dnsHelper: + 'Controla si la función de resolución DNS está habilitada en el servidor SSH para verificar la identidad de la conexión.', + analysis: 'Información estadística', + denyHelper: + "Se realizará una operación de 'denegar' sobre las siguientes direcciones. Tras la configuración, la IP quedará prohibida de acceder al servidor. ¿Desea continuar?", + acceptHelper: + "Se realizará una operación de 'aceptar' sobre las siguientes direcciones. Tras la configuración, la IP recuperará el acceso normal. ¿Desea continuar?", + noAddrWarning: + 'Actualmente no se han seleccionado direcciones [{0}]. ¡Por favor verifique e inténtelo de nuevo!', + loginLogs: 'Registros de inicio de sesión', + loginMode: 'Modo', + authenticating: 'Clave', + publickey: 'Clave', + belong: 'Pertenece', + local: 'Local', + session: 'Sesión | Sesiones', + loginTime: 'Hora de inicio de sesión', + loginIP: 'IP de inicio de sesión', + stopSSHWarn: 'Indica si se debe desconectar esta conexión SSH', + }, + setting: { + panel: 'Panel', + user: 'Usuario del panel', + userChange: 'Cambiar usuario del panel', + userChangeHelper: 'Cambiar el usuario del panel cerrará tu sesión. ¿Deseas continuar?', + passwd: 'Contraseña del panel', + emailHelper: 'Para recuperación de contraseña', + watermark: 'Configuración de marca de agua', + watermarkContent: 'Contenido de la marca de agua', + contentHelper: + '{0} representa el nombre del nodo, {1} representa la dirección del nodo. Puede usar variables o ingresar nombres personalizados.', + watermarkColor: 'Color de la marca de agua', + watermarkFont: 'Tamaño de fuente de la marca de agua', + watermarkHeight: 'Altura de la marca de agua', + watermarkWidth: 'Anchura de la marca de agua', + watermarkRotate: 'Ángulo de rotación', + watermarkGap: 'Espaciado', + watermarkCloseHelper: '¿Seguro que deseas desactivar la configuración de marca de agua del sistema?', + watermarkOpenHelper: '¿Seguro que deseas guardar la configuración actual de marca de agua del sistema?', + title: 'Alias del panel', + panelPort: 'Puerto del panel', + titleHelper: + 'Admite de 3 a 30 caracteres, incluyendo letras, números, espacios y caracteres especiales comunes', + portHelper: + 'El rango recomendado de puertos es de 8888 a 65535. Nota: si el servidor tiene un grupo de seguridad, permite el nuevo puerto en el grupo de seguridad previamente', + portChange: 'Cambio de puerto', + portChangeHelper: 'Modificará el puerto del servicio y reiniciará el servicio. ¿Deseas continuar?', + theme: 'Tema', + menuTabs: 'Pestañas de menú', + dark: 'Oscuro', + darkGold: 'Dorado oscuro', + light: 'Claro', + auto: 'Seguir sistema', + language: 'Idioma', + languageHelper: + 'Por defecto sigue el idioma del navegador. Este parámetro solo tiene efecto en el navegador actual', + sessionTimeout: 'Tiempo de espera de sesión', + sessionTimeoutError: 'El tiempo mínimo de espera de sesión es de 300 segundos', + sessionTimeoutHelper: + 'El panel cerrará la sesión automáticamente si no hay actividad durante más de {0} segundo(s).', + systemIP: 'Dirección del sistema', + systemIPHelper: + 'La dirección se utilizará para la redirección de aplicaciones, el acceso a contenedores y otras funciones. Cada nodo puede configurarse con una dirección diferente.', + proxy: 'Proxy del servidor', + proxyHelper: 'Tendrá efecto en los siguientes escenarios después de configurar el servidor proxy:', + proxyHelper1: + 'Descarga de paquetes de instalación y sincronización desde la tienda de aplicaciones (solo edición profesional)', + proxyHelper2: + 'Actualización del sistema y recuperación de información de actualización (solo edición profesional)', + proxyHelper3: 'Verificación y sincronización de licencias del sistema', + proxyHelper4: 'La red de Docker se accederá a través de un servidor proxy (solo edición profesional)', + proxyHelper5: 'Descarga y sincronización unificada para bibliotecas de scripts del sistema (Profesional)', + proxyHelper6: 'Solicitud de certificado (Profesional)', + proxyType: 'Tipo de proxy', + proxyUrl: 'Dirección del proxy', + proxyPort: 'Puerto del proxy', + proxyPasswdKeep: 'Recordar contraseña', + proxyDocker: 'Proxy de Docker', + proxyDockerHelper: + 'Sincroniza la configuración del servidor proxy con Docker, admite la extracción de imágenes en servidores offline y otras operaciones', + syncToNode: 'Sincronizar con nodo', + syncToNodeHelper: 'Sincroniza la configuración con otros nodos', + nodes: 'Nodo', + selectNode: 'Seleccionar nodo', + selectNodeError: 'Por favor selecciona un nodo', + apiInterface: 'Habilitar API', + apiInterfaceClose: 'Una vez cerrado, no se podrá acceder a las interfaces de API. ¿Deseas continuar?', + apiInterfaceHelper: 'Permite que aplicaciones de terceros accedan a la API.', + apiInterfaceAlert1: + 'No lo habilites en entornos de producción ya que puede aumentar riesgos de seguridad en el servidor.', + apiInterfaceAlert2: + 'No uses aplicaciones de terceros para llamar a la API y evitar posibles amenazas de seguridad.', + apiInterfaceAlert3: 'Documento de API:', + apiInterfaceAlert4: 'Documento de uso:', + apiKey: 'Clave de API', + apiKeyHelper: 'La clave API se utiliza para que aplicaciones de terceros accedan a la API.', + ipWhiteList: 'Lista blanca de IP', + ipWhiteListEgs: 'Una por línea. Por ejemplo,\n172.161.10.111\n172.161.10.0/24', + ipWhiteListHelper: + 'Las IP en la lista blanca pueden acceder a la API, 0.0.0.0/0 (todas IPv4), ::/0 (todas IPv6)', + apiKeyValidityTime: 'Periodo de validez de la clave de interfaz', + apiKeyValidityTimeEgs: 'Periodo de validez de la clave de interfaz (en minutos)', + apiKeyValidityTimeHelper: + 'La marca de tiempo de la interfaz es válida si la diferencia con la actual (en minutos) está dentro del rango permitido. Un valor 0 desactiva la verificación.', + apiKeyReset: 'Restablecer clave de interfaz', + apiKeyResetHelper: + 'El servicio asociado con la clave quedará inválido. Por favor añade una nueva clave al servicio', + confDockerProxy: 'Configurar proxy de Docker', + restartNowHelper: 'Configurar el proxy de Docker requiere reiniciar el servicio de Docker.', + restartNow: 'Reiniciar ahora', + restartLater: 'Reiniciar manualmente después', + systemIPWarning: 'La dirección del servidor no está configurada. Configúrala primero en el panel de control.', + systemIPWarning1: + 'La dirección del servidor actual está configurada en {0}, ¡y la redirección rápida no es posible!', + syncTime: 'Hora del servidor', + timeZone: 'Zona horaria', + timeZoneChangeHelper: 'Cambiar la zona horaria requiere reiniciar el servicio. ¿Deseas continuar?', + timeZoneHelper: + 'La modificación de zona horaria depende del servicio system timedatectl. Tomará efecto después de reiniciar el servicio 1Panel.', + timeZoneCN: 'Pekín', + timeZoneAM: 'Los Ángeles', + timeZoneNY: 'Nueva York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'Servidor NTP', + syncSiteHelper: + 'Esta operación usará {0} como fuente para la sincronización horaria del sistema. ¿Deseas continuar?', + changePassword: 'Cambiar contraseña', + oldPassword: 'Contraseña original', + newPassword: 'Nueva contraseña', + retryPassword: 'Confirmar contraseña', + noSpace: 'La información ingresada no puede incluir espacios', + duplicatePassword: 'La nueva contraseña no puede ser igual a la original, por favor vuelve a introducirla', + diskClean: 'Limpieza de caché', + developerMode: 'Programa de vista previa', + developerModeHelper: + 'Podrás experimentar nuevas funciones y correcciones antes de su lanzamiento general y dar retroalimentación anticipada.', + thirdParty: 'Cuentas de terceros', + scope: 'Ámbito', + public: 'Pública', + publicHelper: + 'Las cuentas de respaldo de tipo público se sincronizarán en cada subnodo, y estos podrán usarlas conjuntamente', + private: 'Privada', + privateHelper: + 'Las cuentas de respaldo de tipo privado solo se crean en el nodo actual y son para uso exclusivo de este nodo', + noTypeForCreate: 'Actualmente no se ha creado ningún tipo de respaldo', + LOCAL: 'Disco del servidor', + OSS: 'Ali OSS', + S3: 'Amazon S3', + mode: 'Modo', + MINIO: 'MinIO', + SFTP: 'SFTP', + WebDAV: 'WebDAV', + WebDAVAlist: 'La conexión WebDAV a Alist puede consultarse en la documentación oficial', + UPYUN: 'UPYUN', + ALIYUN: 'Aliyun Drive', + ALIYUNHelper: + 'El límite máximo actual para descargas sin cliente en Aliyun Drive es de 100 MB. Superarlo requiere descargar mediante el cliente.', + ALIYUNRecover: + 'El límite máximo actual para descargas sin cliente en Aliyun Drive es de 100 MB. Superarlo requiere descargar mediante el cliente al dispositivo local y luego sincronizar la instantánea para la recuperación.', + GoogleDrive: 'Google Drive', + analysis: 'Análisis', + analysisHelper: + 'Pega el contenido completo del token para analizar automáticamente las partes requeridas. Para operaciones específicas, consulta la documentación oficial.', + serviceName: 'Nombre del servicio', + operator: 'Operador', + OneDrive: 'Microsoft OneDrive', + isCN: 'Versión Century Internet', + isNotCN: 'Versión internacional', + client_id: 'ID de cliente', + client_secret: 'Secreto de cliente', + redirect_uri: 'URL de redirección', + onedrive_helper: 'La configuración personalizada puede consultarse en la documentación oficial', + clickToRefresh: 'Haz clic para refrescar', + refreshTime: 'Tiempo de actualización del token', + refreshStatus: 'Estado de actualización del token', + backupDir: 'Directorio de respaldo', + codeWarning: 'El formato del código de autorización es incorrecto, ¡por favor confírmalo de nuevo!', + code: 'Código de autorización', + codeHelper: + 'Haz clic en el botón "Obtener", luego inicia sesión en {0} y copia el contenido después de "code" en el enlace redirigido. Pégalo en este campo. Para instrucciones específicas, consulta la documentación oficial.', + googleHelper: + 'Primero crea una aplicación de Google y obtén la información del cliente, completa el formulario y haz clic en obtener. Para operaciones específicas, consulta la documentación oficial.', + loadCode: 'Obtener', + COS: 'Tencent COS', + ap_beijing_1: 'Zona 1 Pekín', + ap_beijing: 'Pekín', + ap_nanjing: 'Nankín', + ap_shanghai: 'Shanghái', + ap_guangzhou: 'Cantón', + ap_chengdu: 'Chengdú', + ap_chongqing: 'Chongqing', + ap_shenzhen_fsi: 'Shenzhen Financiero', + ap_shanghai_fsi: 'Shanghái Financiero', + ap_beijing_fsi: 'Pekín Financiero', + ap_hongkong: 'Hong Kong, China', + ap_singapore: 'Singapur', + ap_mumbai: 'Bombay', + ap_jakarta: 'Yakarta', + ap_seoul: 'Seúl', + ap_bangkok: 'Bangkok', + ap_tokyo: 'Tokio', + na_siliconvalley: 'Silicon Valley (EE. UU. Oeste)', + na_ashburn: 'Ashburn (EE. UU. Este)', + na_toronto: 'Toronto', + sa_saopaulo: 'São Paulo', + eu_frankfurt: 'Fráncfort', + KODO: 'Qiniu Kodo', + scType: 'Tipo de almacenamiento', + typeStandard: 'Estándar', + typeStandard_IA: 'Estándar_IA', + typeArchive: 'Archivo', + typeDeep_Archive: 'Archivo profundo', + scLighthouse: 'Por defecto, el almacenamiento ligero de objetos solo admite este tipo de almacenamiento', + scStandard: + 'Almacenamiento estándar, adecuado para escenarios con grandes volúmenes de archivos de acceso frecuente y alta interacción de datos.', + scStandard_IA: + 'Almacenamiento de acceso infrecuente, adecuado para escenarios con baja frecuencia de acceso (ej., 1-2 veces por mes), con duración mínima de 30 días.', + scArchive: 'Almacenamiento de archivo, adecuado para escenarios con frecuencia de acceso extremadamente baja.', + scDeep_Archive: + 'Almacenamiento en frío duradero, adecuado para escenarios con frecuencia de acceso extremadamente baja.', + archiveHelper: + 'Los archivos de almacenamiento de archivo no pueden descargarse directamente, deben restaurarse primero desde la web del proveedor de la nube correspondiente. ¡Úsalo con precaución!', + backupAlert: + 'Si un proveedor de nube es compatible con el protocolo S3, puedes usar Amazon S3 directamente para el respaldo. ', + domain: 'Dominio de aceleración', + backupAccount: 'Cuenta de respaldo | Cuentas de respaldo', + loadBucket: 'Obtener bucket', + accountName: 'Nombre de la cuenta', + accountKey: 'Clave de la cuenta', + address: 'Dirección', + path: 'Ruta', + safe: 'Seguridad', + passkey: 'Passkey', + passkeyManage: 'Administrar', + passkeyHelper: 'Para inicio rápido, se pueden vincular hasta 5 passkeys', + passkeyRequireSSL: 'Habilita HTTPS para usar passkeys', + passkeyNotSupported: 'El navegador o entorno actual no admite Passkey', + passkeyCount: 'Vinculadas {0}/{1}', + passkeyName: 'Nombre', + passkeyNameHelper: 'Introduce un nombre para distinguir dispositivos', + passkeyAdd: 'Agregar Passkey', + passkeyCreatedAt: 'Creado', + passkeyLastUsedAt: 'Último uso', + passkeyDeleteConfirm: 'Después de eliminar, no podrás iniciar sesión con esta passkey. ¿Continuar?', + passkeyLimit: 'Se pueden vincular hasta 5 passkeys', + passkeyFailed: + 'El registro de passkey falló, por favor, asegúrate de que el certificado SSL del panel es confiable', + bindInfo: 'Información de enlace', + bindAll: 'Escuchar en todas', + bindInfoHelper: + 'Cambiar la dirección o el protocolo de escucha del servicio puede causar indisponibilidad. ¿Deseas continuar?', + ipv6: 'Escuchar IPv6', + bindAddress: 'Dirección de escucha', + entrance: 'Entrada', + showEntrance: 'Mostrar alerta de deshabilitado en la página "Resumen"', + entranceHelper: + 'Habilitar entrada de seguridad solo permitirá iniciar sesión en el panel mediante la entrada especificada.', + entranceError: 'Introduce un punto de entrada seguro de 5 a 116 caracteres, solo se admiten números o letras.', + entranceInputHelper: 'Déjalo en blanco para desactivar la entrada de seguridad.', + randomGenerate: 'Aleatorio', + expirationTime: 'Fecha de expiración', + unSetting: 'No configurado', + noneSetting: + 'Establece el tiempo de expiración para la contraseña del panel. Tras expirar, deberás restablecer la contraseña', + expirationHelper: + 'Si el tiempo de expiración de la contraseña es [0] días, la función de expiración estará desactivada', + days: 'Días de expiración', + expiredHelper: 'La contraseña actual ha expirado. Por favor cámbiala de nuevo.', + timeoutHelper: + '[ {0} días ] La contraseña del panel está a punto de expirar. Tras expirar, deberás restablecerla', + complexity: 'Validación de complejidad', + complexityHelper: + 'Tras habilitarlo, la regla de validación de contraseña será: 8-30 caracteres, incluyendo letras, números y al menos dos caracteres especiales.', + bindDomain: 'Vincular dominio', + unBindDomain: 'Desvincular dominio', + panelSSL: 'SSL del panel', + panelSSLHelper: + 'Tras la renovación automática del SSL del panel, deberás reiniciar manualmente el servicio 1Panel para que los cambios surtan efecto.', + unBindDomainHelper: + 'La acción de desvincular un dominio puede provocar inseguridad en el sistema. ¿Deseas continuar?', + bindDomainHelper: 'Tras vincular el dominio, solo ese dominio podrá acceder al servicio de 1Panel.', + bindDomainHelper1: 'Déjalo en blanco para desactivar la vinculación de dominio.', + bindDomainWarning: + 'Tras la vinculación del dominio, se cerrará la sesión y solo se podrá acceder al servicio 1Panel mediante el dominio especificado. ¿Deseas continuar?', + allowIPs: 'IP autorizada', + unAllowIPs: 'IP no autorizada', + unAllowIPsWarning: + 'Autorizar una IP vacía permitirá que todas las IP accedan al sistema, lo cual puede provocar inseguridad. ¿Deseas continuar?', + allowIPsHelper: 'Tras configurar la lista de IP autorizadas, solo las IP en la lista podrán acceder al panel.', + allowIPsWarning: + 'Tras configurar la lista de IP autorizadas, solo las IP en la lista podrán acceder al panel. ¿Deseas continuar?', + allowIPsHelper1: 'Déjalo en blanco para desactivar la restricción por IP.', + allowIPEgs: 'Una por línea. Por ejemplo,\n172.16.10.111\n172.16.10.0/24', + mfa: 'Autenticación en dos pasos (2FA)', + mfaClose: 'Desactivar MFA reducirá la seguridad del servicio. ¿Deseas continuar?', + secret: 'Secreto', + mfaInterval: 'Intervalo de refresco', + mfaTitleHelper: + 'El título se usa para distinguir diferentes hosts de 1Panel. Tras modificarlo, vuelve a escanear o añade la clave secreta manualmente.', + mfaIntervalHelper: + 'Tras modificar el tiempo de refresco, vuelve a escanear o añade la clave secreta manualmente.', + mfaAlert: + 'El token de un solo uso es un número dinámico de 6 dígitos basado en la hora actual. Asegúrate de que la hora del servidor esté sincronizada.', + mfaHelper: 'Tras habilitarlo, será necesario verificar el token de un solo uso.', + mfaHelper1: 'Descarga una app de autenticación, por ejemplo,', + mfaHelper2: + 'Para obtener el token, escanea el siguiente código QR con tu app de autenticación o copia la clave secreta en la aplicación.', + mfaHelper3: 'Introduce los seis dígitos de la app', + mfaCode: 'Token de un solo uso', + sslChangeHelper: 'Modificará la configuración https y reiniciará el servicio. ¿Deseas continuar?', + sslDisable: 'Deshabilitar', + sslDisableHelper: + 'Si el servicio https está deshabilitado, deberás reiniciar el panel para que surta efecto. ¿Deseas continuar?', + noAuthSetting: 'Configuración no autorizada', + noAuthSettingHelper: + 'Cuando los usuarios no inician sesión desde la entrada de seguridad especificada, o no acceden al panel desde la IP o dominio especificados, esta respuesta puede ocultar características del panel.', + responseSetting: 'Configuración de respuesta', + help200: 'Página de ayuda', + error400: 'Solicitud incorrecta', + error401: 'No autorizado', + error403: 'Prohibido', + error404: 'No encontrado', + error408: 'Tiempo de espera de la solicitud', + error416: 'Rango no satisfactorio', + error444: 'Conexión cerrada', + error500: 'Error interno del servidor', + https: 'Configurar HTTPS para el panel mejora la seguridad de acceso', + strictHelper: 'El tráfico no HTTPS no puede conectarse al panel', + muxHelper: + 'El panel escuchará en los puertos HTTP y HTTPS y redirigirá HTTP a HTTPS, pero esto puede reducir la seguridad', + certType: 'Tipo de certificado', + selfSigned: 'Autofirmado', + selfSignedHelper: + 'Los navegadores pueden no confiar en certificados autofirmados y mostrar advertencias de seguridad.', + select: 'Seleccionar', + domainOrIP: 'Dominio o IP:', + timeOut: 'Tiempo de espera', + rootCrtDownload: 'Descargar certificado raíz', + primaryKey: 'Clave primaria', + certificate: 'Certificado', + backupJump: + 'Archivos de respaldo fuera de la lista actual, intenta descargarlos desde el directorio de archivos e importarlos para el respaldo.', + snapshot: 'Instantánea | Instantáneas', + noAppData: 'No hay aplicaciones del sistema disponibles para seleccionar', + noBackupData: 'No hay datos de respaldo disponibles para seleccionar', + stepBaseData: 'Datos base', + stepAppData: 'Aplicación del sistema', + stepPanelData: 'Datos del sistema', + stepBackupData: 'Datos de respaldo', + stepOtherData: 'Otros datos', + operationLog: 'Conservar registro de operaciones', + loginLog: 'Conservar registro de accesos', + systemLog: 'Conservar registro del sistema', + taskLog: 'Conservar registro de tareas', + monitorData: 'Conservar datos de monitoreo', + dockerConf: 'Conservar configuración de Docker', + selectAllImage: 'Respaldar todas las imágenes de aplicaciones', + logLabel: 'Registro', + agentLabel: 'Configuración de nodo', + appDataLabel: 'Datos de aplicación', + appImage: 'Imagen de aplicación', + appBackup: 'Respaldo de aplicación', + backupLabel: 'Directorio de respaldo', + confLabel: 'Archivo de configuración', + dockerLabel: 'Contenedor', + taskLabel: 'Tarea programada', + resourceLabel: 'Directorio de recursos de aplicación', + runtimeLabel: 'Entorno de ejecución', + appLabel: 'Aplicación', + databaseLabel: 'Base de datos', + snapshotLabel: 'Archivo de instantánea', + websiteLabel: 'Sitio web', + directoryLabel: 'Directorio', + appStoreLabel: 'Tienda de aplicaciones', + shellLabel: 'Script', + tmpLabel: 'Directorio temporal', + sslLabel: 'Directorio de certificados', + reCreate: 'No se pudo crear la instantánea', + reRollback: 'Error al revertir la instantánea', + deleteHelper: + 'Todos los archivos de instantánea, incluidos los de la cuenta de respaldo de terceros, serán eliminados. ¿Deseas continuar?', + status: 'Estado de la instantánea', + ignoreRule: 'Regla de exclusión', + editIgnoreRule: '@:commons.button.edit @.lower:setting.ignoreRule', + ignoreHelper: + 'Esta regla se usará para comprimir y respaldar el directorio de datos de 1Panel al crear la instantánea. Por defecto, se ignoran los archivos de socket.', + ignoreHelper1: 'Una por línea. Por ejemplo,\n*.log\n/opt/1panel/cache', + panelInfo: 'Escribir información básica de 1Panel', + panelBin: 'Respaldar archivos del sistema 1Panel', + daemonJson: 'Respaldar archivo de configuración de Docker', + appData: 'Respaldar aplicaciones instaladas de 1Panel', + panelData: 'Respaldar directorio de datos de 1Panel', + backupData: 'Respaldar directorio local de respaldo de 1Panel', + compress: 'Crear archivo de instantánea', + upload: 'Subir archivo de instantánea', + recoverDetail: 'Detalle de recuperación', + createSnapshot: 'Crear instantánea', + importSnapshot: 'Sincronizar instantánea', + importHelper: 'Directorio de instantánea: ', + lastRecoverAt: 'Última hora de recuperación', + lastRollbackAt: 'Última hora de reversión', + reDownload: 'Descargar el archivo de respaldo de nuevo', + recoverErrArch: '¡La recuperación de instantánea entre arquitecturas de servidor diferentes no es compatible!', + recoverErrSize: + 'Se detectó espacio insuficiente en disco, por favor verifica o libera espacio e inténtalo de nuevo.', + recoverHelper: + 'Iniciando recuperación desde la instantánea {0}, confirma la siguiente información antes de continuar:', + recoverHelper1: 'La recuperación requiere reiniciar Docker y los servicios de 1Panel', + recoverHelper2: + 'Asegúrate de que haya suficiente espacio en disco en el servidor (Tamaño del archivo de instantánea: {0}, Espacio disponible: {1})', + recoverHelper3: + 'Asegúrate de que la arquitectura del servidor coincida con la del servidor donde se creó la instantánea (Arquitectura actual: {0})', + rollback: 'Reversión', + rollbackHelper: + 'Revertir esta recuperación reemplazará todos los archivos de esta recuperación y puede requerir reiniciar Docker y los servicios de 1Panel. ¿Deseas continuar?', + upgradeRecord: 'Registro de actualización', + upgrading: ' Actualizando, por favor espera...', + upgradeHelper: 'La actualización requiere reiniciar el servicio 1Panel. ¿Deseas continuar?', + noUpgrade: 'Actualmente es la última versión', + versionHelper: + 'Reglas de nombre: [versión mayor].[versión funcional].[versión de corrección de errores], como se muestra en el siguiente ejemplo:', + rollbackLocalHelper: + 'El nodo principal no admite reversión directa. ¡Ejecuta manualmente el comando [1pctl restore] para revertir!', + upgradeCheck: 'Buscar actualizaciones', + upgradeNotes: 'Notas de la versión', + upgradeNow: 'Actualizar ahora', + source: 'Fuente de descarga', + versionNotSame: + 'La versión del nodo no coincide con la del nodo principal. Actualiza en la gestión de nodos antes de reintentar.', + versionCompare: + 'Se detectó que el nodo {0} ya está en la última versión actualizable. Verifica la versión del nodo principal e inténtalo de nuevo.', + + about: 'Acerca de', + versionItem: 'Versión Actual', + backupCopies: 'Número de Copias a Conservar', + backupCopiesHelper: + 'Establezca el número de copias de respaldo de actualización para conservar para la reversión de versión. 0 significa conservar todas.', + backupCopiesRule: 'Conserve al menos 3 registros de respaldo de actualización', + release: 'Notas de lanzamiento', + releaseHelper: + 'No se pudieron obtener las notas de lanzamiento para el entorno actual. Puedes consultarlas manualmente en la documentación oficial.', + project: 'GitHub', + issue: 'Comentarios', + doc: 'Documento oficial', + star: 'Estrella', + description: 'Panel de servidor Linux', + forum: 'Discusiones', + doc2: 'Documentación', + currentVersion: 'Versión', + license: 'Licencia', + bindNode: 'Vincular nodo', + menuSetting: 'Configuración de menú', + menuSettingHelper: 'Cuando solo exista 1 submenú, la barra de menú mostrará únicamente ese submenú', + showAll: 'Mostrar todo', + hideALL: 'Ocultar todo', + ifShow: 'Mostrar o no', + menu: 'Menú', + confirmMessage: 'La página se actualizará para refrescar la lista de menús avanzados. ¿Deseas continuar?', + recoverMessage: + 'La página se actualizará y la lista de menús se restaurará a su estado inicial. ¿Desea continuar?', + compressPassword: 'Contraseña de compresión', + backupRecoverMessage: + 'Introduce la contraseña de compresión o descompresión (déjalo en blanco para no establecerla)', + }, + license: { + community: 'OSS', + oss: 'Software Open Source', + pro: 'Pro', + trial: 'Prueba', + add: 'Agregar versión Community', + licenseBindHelper: + 'Las cuotas de nodo gratuitas solo se pueden utilizar cuando la licencia está vinculada a un nodo', + licenseAlert: + 'Solo se pueden agregar nodos Community cuando la licencia está correctamente vinculada. Solo los nodos vinculados admiten cambios.', + licenseUnbindHelper: 'Nodos Community detectados en esta licencia. Desvincule e intente nuevamente.', + subscription: 'Suscripción', + perpetual: 'Perpetua', + versionConstraint: '{0} Compra de versión', + forceUnbind: 'Desvinculación forzada', + forceUnbindHelper: 'La desvinculación forzada ignorará errores y liberará la licencia.', + updateForce: 'Forzar actualización (ignorar errores para liberar la licencia)', + trialInfo: 'Versión', + authorizationId: 'ID de autorización', + authorizedUser: 'Usuario autorizado', + lostHelper: 'La licencia alcanzó el máximo de reintentos. Sincronícela manualmente para activar funciones Pro.', + exceptionalHelper: + 'Verificación de licencia anómala. Sincronícela manualmente para activar funciones Pro. detalle: ', + quickUpdate: 'Actualización rápida', + import: 'Importar', + power: 'Autorizar', + unbindHelper: 'Se eliminarán todos los ajustes Pro tras desvincular. ¿Desea continuar?', + importLicense: 'Importar licencia', + importHelper: 'Haga clic o arrastre el archivo de licencia aquí', + levelUpPro: 'Actualizar a Pro', + licenseSync: 'Sincronizar licencia', + knowMorePro: 'Ver más', + closeAlert: 'Puede cerrar la página en la configuración del panel', + introduce: 'Introducción de funciones', + waf: 'Actualizar a Pro permite interceptación, logs, geobloqueo, reglas, páginas personalizadas, etc.', + tamper: 'Actualizar a Pro protege sitios ante modificaciones no autorizadas.', + tamperHelper: + 'La operación falló, el archivo/carpeta tiene protección antimanipulación. Revise e intente de nuevo.', + setting: 'Actualizar a Pro permite personalizar logo, mensaje de bienvenida y más.', + monitor: 'Pro permite ver estado web en tiempo real, tendencias, logs, etc.', + alert: 'Pro permite recibir alertas SMS y ver logs, control total de eventos clave.', + node: 'Actualizar a la Edición Profesional le permite administrar múltiples servidores Linux con 1Panel.', + nodeApp: + 'Actualizar a la Edición Profesional permite actualizar de forma unificada las versiones de aplicaciones multi-nodo sin cambiar manualmente entre nodos.', + nodeDashboard: + 'Actualizar a la Edición Profesional permite la gestión centralizada de aplicaciones, sitios web, bases de datos y tareas programadas multi-nodo.', + fileExchange: 'Pro permite transferir archivos entre varios servidores.', + app: 'Pro permite ver información de servicio, monitoreo, etc. vía app móvil.', + cluster: + 'Actualizar a la Edición Profesional te permite gestionar clústeres maestro-esclavo de MySQL/Postgres/Redis.', + }, + clean: { + scan: 'Iniciar escaneo', + scanHelper: 'Limpie fácilmente archivos basura generados por 1Panel', + clean: 'Limpiar ahora', + reScan: 'Volver a escanear', + cleanHelper: + 'Los archivos y directorios seleccionados no se pueden revertir después de la limpieza. ¿Continuar?', + statusSuggest: '(Limpieza recomendada)', + statusClean: '(Muy limpio)', + statusEmpty: 'Muy limpio, ¡no necesita limpieza!', + statusWarning: '(Precaución)', + lastCleanTime: 'Última limpieza: {0}', + lastCleanHelper: 'Archivos/carpetas limpiados: {0}, total limpiado: {1}', + cleanSuccessful: 'Limpieza completada correctamente', + currentCleanHelper: 'En esta sesión: {0} archivos/carpetas, Total: {1}', + suggest: '(Recomendado)', + totalScan: 'Total a limpiar: ', + selectScan: 'Total seleccionado: ', + system: 'Archivos basura del sistema', + systemHelper: 'Archivos temporales generados durante instantáneas, actualizaciones y otros procesos', + panelOriginal: 'Archivos de copia de seguridad antes de restaurar instantánea del sistema', + upgrade: 'Archivos de copia de seguridad de actualización del sistema', + agentPackages: 'Paquetes de actualización/instalación de nodos secundarios de versiones históricas', + upgradeHelper: '(Mantenga el último respaldo para restaurar el sistema)', + snapshot: 'Archivos temporales de instantáneas', + rollback: 'Archivos de respaldo antes de recuperación', + + backup: 'Copia de Seguridad del Sistema', + backupHelper: 'Archivos de copia de seguridad no asociados con cuentas de copia de seguridad locales', + unknownBackup: 'Copia de Seguridad del Sistema', + tmpBackup: 'Copia de Seguridad Temporal', + unknownApp: 'Copia de Seguridad de Aplicación no Asociada', + unknownDatabase: 'Copia de Seguridad de Base de Datos no Asociada', + unknownWebsite: 'Copia de Seguridad de Sitio Web no Asociada', + unknownSnapshot: 'Copia de Seguridad de Instantánea no Asociada', + + upload: 'Archivos temporales de carga', + uploadHelper: 'Archivos temporales subidos en la lista de respaldos', + download: 'Archivos temporales de descarga', + downloadHelper: 'Archivos temporales descargados desde respaldos externos', + directory: 'Directorio', + + systemLog: 'Archivo de registro', + systemLogHelper: 'Registros del sistema, registros de tareas, archivos de registro del sitio web', + dockerLog: 'Archivos de registro de operaciones de contenedores', + taskLog: 'Archivos de registro de ejecución de tareas programadas', + shell: 'Tareas programadas de script de shell', + containerShell: 'Tareas programadas de script de shell ejecutadas dentro de contenedores', + curl: 'Tareas programadas de CURL', + + docker: 'Basura de contenedores', + dockerHelper: 'Archivos como contenedores, imágenes, volúmenes, caché de construcción, etc.', + volumes: 'Volúmenes', + buildCache: 'Caché de build de contenedores', + + appTmpDownload: 'Archivo de descarga temporal de la aplicación', + unknownWebsiteLog: 'Archivo de respaldo de registro de sitio web no vinculado', + }, + app: { + app: 'Aplicación | Aplicaciones', + installName: 'Nombre', + installed: 'Instaladas', + all: 'Todas', + version: 'Versión', + detail: 'Detalles', + params: 'Editar parámetros', + author: 'Autor', + source: 'Origen', + appName: 'Nombre de la aplicación', + deleteWarn: + 'La eliminación eliminará todos los datos y respaldos. Esta acción no se puede deshacer. ¿Desea continuar?', + syncSuccess: 'Sincronizado correctamente', + canUpgrade: 'Actualizaciones', + backupName: 'Nombre de archivo', + backupPath: 'Ruta del archivo', + backupdate: 'Fecha de respaldo', + versionSelect: 'Seleccione una versión', + operatorHelper: 'Se realizará la operación {0} en la aplicación seleccionada. ¿Desea continuar?', + startOperatorHelper: 'La aplicación se iniciará. ¿Desea continuar?', + stopOperatorHelper: 'La aplicación se detendrá. ¿Desea continuar?', + restartOperatorHelper: 'La aplicación se reiniciará. ¿Desea continuar?', + reloadOperatorHelper: 'La aplicación se recargará. ¿Desea continuar?', + checkInstalledWarn: 'No se detecta "{0}". Vaya a "Tienda de aplicaciones" para instalar.', + limitHelper: 'La aplicación ya está instalada.', + deleteHelper: '"{0}" está asociada con los siguientes recursos y no puede eliminarse', + checkTitle: 'Aviso', + defaultConfig: 'Configuración predeterminada', + defaultConfigHelper: 'Se restauró a configuración predeterminada, surte efecto al guardar', + forceDelete: 'Forzar eliminación', + forceDeleteHelper: 'Forzar ignorará errores y eliminará la metadata.', + deleteBackup: 'Eliminar respaldo', + deleteBackupHelper: 'También eliminar respaldo de la aplicación', + deleteDB: 'Eliminar base de datos', + deleteDBHelper: 'También eliminar la base de datos', + noService: 'Sin {0}', + toInstall: 'Ir a instalar', + param: 'Parámetros', + alreadyRun: 'Edad', + syncAppList: 'Sincronizar', + less1Minute: 'Menos de 1 minuto', + appOfficeWebsite: 'Sitio web', + github: 'Github', + document: 'Documento', + updatePrompt: 'No hay actualizaciones disponibles', + installPrompt: 'Aún no hay apps instaladas', + updateHelper: 'Editar parámetros puede hacer que la app no inicie. Proceda con precaución.', + updateWarn: 'Actualizar parámetros requiere reconstruir la app. ¿Desea continuar?', + busPort: 'Puerto', + syncStart: '¡Sincronizando! Actualice la tienda después', + advanced: 'Configuración avanzada', + cpuCore: 'núcleo(s)', + containerName: 'Nombre del contenedor', + containerNameHelper: 'El nombre se generará automáticamente si no se establece', + allowPort: 'Acceso externo', + allowPortHelper: 'Permitir acceso externo abrirá el puerto en el firewall', + appInstallWarn: + 'La aplicación no expone el puerto externo por defecto. Use "Configuración avanzada" para habilitarlo.', + upgradeStart: '¡Iniciando actualización! Refresque la página después', + toFolder: 'Abrir directorio de instalación', + editCompose: 'Editar archivo compose', + editComposeHelper: 'Editar el compose puede hacer que la instalación falle', + composeNullErr: 'El compose no puede estar vacío', + takeDown: 'Desinstalar', + allReadyInstalled: 'Instaladas', + installHelper: 'Si tiene problemas con el pull de imagen, configure aceleración.', + installWarn: 'Si no habilita el acceso externo, la app no será accesible externamente. ¿Desea continuar?', + showIgnore: 'Ver aplicaciones ignoradas', + cancelIgnore: 'Cancelar ignoradas', + ignoreList: 'Aplicaciones ignoradas', + appHelper: 'Consulte los detalles de la app para instrucciones de instalación.', + backupApp: 'Respaldar aplicación antes de actualizar', + backupAppHelper: + 'Si la actualización falla, el respaldo se revertirá. Revise el motivo en los logs del sistema.', + openrestyDeleteHelper: 'Eliminar OpenResty forzadamente borrará todos los sitios. ¿Desea continuar?', + downloadLogHelper1: 'Se descargarán todos los logs de la app {0}. ¿Desea continuar?', + downloadLogHelper2: 'Se descargarán los últimos {1} logs de la app {0}. ¿Desea continuar?', + syncAllAppHelper: 'Se sincronizarán todas las aplicaciones. ¿Desea continuar?', + hostModeHelper: 'El modo de red es host. Si necesita abrir el puerto, hágalo manualmente en el firewall.', + showLocal: 'Mostrar apps locales', + reload: 'Recargar', + upgradeWarn: 'Actualizar reemplazará docker-compose.yml. Si hay cambios, puede ver la comparación.', + newVersion: 'Nueva versión', + oldVersion: 'Versión actual', + composeDiff: 'Comparación de archivos', + showDiff: 'Ver comparación', + useNew: 'Usar versión personalizada', + useDefault: 'Usar versión predeterminada', + useCustom: 'Personalizar docker-compose.yml', + useCustomHelper: + 'Personalizar docker-compose.yml puede causar errores de actualización. Si no es necesario, no marque.', + diffHelper: 'Izquierda: versión vieja, Derecha: nueva. Edite y guarde la versión personalizada.', + pullImage: 'Pull de imagen', + pullImageHelper: 'Ejecutar docker pull antes de iniciar la app', + deleteImage: 'Eliminar imagen', + deleteImageHelper: 'Eliminar imagen asociada a la app. Si falla, la tarea continuará.', + requireMemory: 'Memoria', + supportedArchitectures: 'Arquitecturas', + link: 'Enlace', + showCurrentArch: 'Arquitectura', + syncLocalApp: 'Sincronizar app local', + memoryRequiredHelper: 'La app requiere {0} de memoria', + gpuConfig: 'Habilitar soporte GPU', + gpuConfigHelper: 'Asegúrese de tener GPU NVIDIA, drivers y NVIDIA Docker Container Toolkit instalados', + webUI: 'Dirección web', + webUIPlaceholder: 'Ejemplo: ejemplo.com:8080/login', + defaultWebDomain: 'Dirección de acceso predeterminada', + defaultWebDomainHepler: 'Si el puerto es 8080, el acceso será http(s)://dirección:8080', + webUIConfig: + 'No hay dirección de acceso predeterminada. Configúrela en los parámetros de la app o en la configuración del panel.', + toLink: 'Abrir', + customAppHelper: 'Antes de instalar un paquete personalizado, asegúrese de no tener apps instaladas.', + forceUninstall: 'Desinstalar forzadamente', + syncCustomApp: 'Sincronizar app personalizada', + ignoreAll: 'Ignorar todas las versiones siguientes', + ignoreVersion: 'Ignorar versión especificada', + specifyIP: 'Vincular IP de host', + specifyIPHelper: 'Establece la dirección/red del host para el puerto (si no sabe, no rellene)', + uninstallDeleteBackup: 'Desinstalar - Eliminar respaldo', + uninstallDeleteImage: 'Desinstalar - Eliminar imagen', + upgradeBackup: 'Respaldar app antes de actualizar', + noAppHelper: + 'No se detectó ninguna aplicación, por favor vaya al centro de tareas para ver el registro de sincronización de la tienda de aplicaciones', + isEdirWarn: 'Se detectó modificación en el archivo docker-compose.yml, por favor revise la comparación', + }, + website: { + primaryDomain: 'Dominio principal', + otherDomains: 'Otros dominios', + static: 'Estático', + deployment: 'Despliegue', + supportUpType: 'Solo se admite el formato .tar.gz, y el paquete comprimido debe contener el archivo {0}.json', + proxy: 'Proxy inverso', + alias: 'Alias', + ftpUser: 'Cuenta FTP', + ftpPassword: 'Contraseña FTP', + ftpHelper: + 'Al crear un sitio web se generará una cuenta FTP correspondiente, cuyo directorio enlazará con el directorio del sitio.', + remark: 'Observación', + groupSetting: 'Gestión de grupos', + createGroup: 'Crear grupo', + appNew: 'Nueva aplicación', + appInstalled: 'Aplicación instalada', + create: 'Crear', + delete: 'Eliminar sitio', + deleteApp: 'Eliminar aplicación', + deleteBackup: 'Eliminar copia de seguridad', + domain: 'Dominio', + domainHelper: 'Un dominio por línea.\nSoporta comodín "*" y direcciones IP.\nTambién permite añadir puerto.', + addDomain: 'Añadir dominio', + domainConfig: 'Dominios', + defaultDoc: 'Documento predeterminado', + perserver: 'Concurrencia por servidor', + perserverHelper: 'Límite de concurrencia máxima para este sitio', + perip: 'Concurrencia por IP', + peripHelper: 'Límite de accesos concurrentes desde una misma IP', + rate: 'Límite de tráfico', + rateHelper: 'Limita el flujo de cada petición (unidad: KB)', + limitHelper: 'Activar control de flujo', + other: 'Otros', + currentSSL: 'Certificado actual', + dnsAccount: 'Cuenta DNS', + applySSL: 'Solicitar certificado', + SSLList: 'Lista de certificados', + createDnsAccount: 'Cuenta DNS', + aliyun: 'Aliyun DNS', + aliEsa: 'Aliyun ESA', + awsRoute53: 'Amazon Route 53', + manual: 'Resolución manual', + key: 'Clave', + check: 'Ver', + acmeAccountManage: 'Gestión de cuentas ACME', + email: 'Correo electrónico', + acmeAccount: 'Cuenta ACME', + provider: 'Método de verificación', + dnsManual: 'Resolución manual', + expireDate: 'Fecha de expiración', + brand: 'Organización', + deploySSL: 'Desplegar certificado', + deploySSLHelper: '¿Seguro que deseas desplegar este certificado?', + ssl: 'Certificado | Certificados', + dnsAccountManage: 'Gestión de proveedores DNS', + renewSSL: 'Renovar', + renewHelper: '¿Seguro que deseas renovar el certificado?', + renewSuccess: 'Certificado renovado', + enableHTTPS: 'Habilitar', + aliasHelper: 'El alias es el nombre de directorio del sitio web', + lastBackupAt: 'Última copia de seguridad', + null: 'Ninguno', + nginxConfig: 'Configuración Nginx', + websiteConfig: 'Configuración del sitio', + proxySettings: 'Configuración de proxy', + advancedSettings: 'Configuración avanzada', + cacheSettings: 'Configuración de caché', + sniSettings: 'Configuración SNI', + basic: 'Básico', + source: 'Configuración', + security: 'Seguridad', + nginxPer: 'Optimización de rendimiento', + neverExpire: 'Nunca expira', + setDefault: 'Establecer como predeterminado', + deleteHelper: 'El estado de la aplicación asociada es anómalo, revisa antes de continuar', + toApp: 'Ir a aplicaciones instaladas', + cycle: 'Ciclo', + frequency: 'Frecuencia', + ccHelper: + 'Si la misma URL recibe más de {1} peticiones en {0} segundos, se activa defensa CC y se bloquea la IP', + mustSave: 'Los cambios deben guardarse para que surtan efecto', + fileExt: 'Extensión de archivo', + fileExtBlock: 'Lista negra de extensiones', + value: 'Valor', + enable: 'Habilitar', + proxyAddress: 'Dirección de proxy', + proxyHelper: 'Ejemplo: 127.0.0.1:8080', + forceDelete: 'Forzar eliminación', + forceDeleteHelper: 'La eliminación forzada ignorará errores y eliminará metadatos igualmente.', + deleteAppHelper: 'Eliminar también aplicaciones y backups asociados', + deleteBackupHelper: 'Eliminar también copias de seguridad del sitio', + deleteDatabaseHelper: 'También eliminar la base de datos asociada al sitio web', + deleteConfirmHelper: `Esta acción es irreversible. Escribe "{0}" para confirmar.`, + staticPath: 'El directorio principal correspondiente es ', + limit: 'Esquema', + blog: 'Foro/Blog', + imageSite: 'Sitio de imágenes', + downloadSite: 'Sitio de descargas', + shopSite: 'Tienda', + doorSite: 'Portal', + qiteSite: 'Empresa', + videoSite: 'Video', + errLog: 'Log de errores', + stopHelper: 'Al detener el sitio, no será accesible y mostrará una página de parada. ¿Deseas continuar?', + startHelper: 'Al habilitar el sitio, los usuarios podrán acceder normalmente. ¿Deseas continuar?', + sitePath: 'Directorio', + siteAlias: 'Alias del sitio', + primaryPath: 'Directorio raíz', + folderTitle: 'El sitio contiene principalmente las siguientes carpetas', + wafFolder: 'Reglas del firewall', + indexFolder: 'Directorio raíz del sitio', + sslFolder: 'Certificados del sitio', + enableOrNot: 'Habilitar', + oldSSL: 'Certificado existente', + manualSSL: 'Importar certificado', + select: 'Seleccionar', + selectSSL: 'Seleccionar certificado', + privateKey: 'Clave privada (KEY)', + certificate: 'Certificado (formato PEM)', + HTTPConfig: 'Opciones HTTP', + HTTPSOnly: 'Bloquear peticiones HTTP', + HTTPToHTTPS: 'Redirigir a HTTPS', + HTTPAlso: 'Permitir peticiones HTTP directas', + sslConfig: 'Opciones SSL', + disableHTTPS: 'Desactivar HTTPS', + disableHTTPSHelper: 'Desactivar HTTPS eliminará la configuración del certificado. ¿Quieres continuar?', + SSLHelper: + 'Nota: No uses certificados SSL en sitios ilegales.\nSi HTTPS no funciona, comprueba que el puerto 443 esté abierto en el firewall.', + SSLConfig: 'Configuración del certificado', + SSLProConfig: 'Configuración del protocolo', + supportProtocol: 'Versión de protocolo', + encryptionAlgorithm: 'Algoritmo de cifrado', + notSecurity: '(inseguro)', + encryptHelper: + "Let's Encrypt tiene límites de frecuencia para emitir certificados. Consulta la documentación oficial.", + ipValue: 'Valor', + ext: 'Extensión de archivo', + wafInputHelper: 'Introduce datos línea por línea', + data: 'Datos', + ever: 'Permanente', + nextYear: 'Un año', + noLog: 'No se encontraron logs', + defaultServer: 'Configurar como sitio por defecto', + noDefaultServer: 'No configurado', + defaultServerHelper: + 'El sitio por defecto recibirá peticiones de dominios/IP no asociados.\nPreviene resoluciones maliciosas, pero anula la intercepción WAF de dominios no autorizados.', + restoreHelper: '¿Seguro que deseas restaurar este backup?', + websiteDeploymentHelper: 'Usa una aplicación instalada o crea una nueva aplicación para desplegar un sitio.', + websiteStatictHelper: 'Crea un directorio de sitio estático en el host.', + websiteProxyHelper: + 'Usa un proxy inverso para servicios existentes. Ejemplo: un servicio en el puerto 8080 → "http://127.0.0.1:8080".', + runtimeProxyHelper: 'Usa un runtime de sitio web para crear un sitio.', + runtime: 'Runtime', + deleteRuntimeHelper: 'El runtime debe eliminarse junto con el sitio. Hazlo con precaución.', + proxyType: 'Tipo de red', + unix: 'Red Unix', + tcp: 'Red TCP/IP', + phpFPM: 'Config FPM', + phpConfig: 'Config PHP', + updateConfig: 'Actualizar configuración', + isOn: 'Activado', + isOff: 'Desactivado', + rewrite: 'Reescritura (pseudoestático)', + rewriteMode: 'Esquema', + current: 'Actual', + rewriteHelper: 'Si el pseudoestático causa inaccesibilidad, restaura la configuración predeterminada.', + runDir: 'Directorio de ejecución', + runUserHelper: + 'En entornos PHP con contenedor, asigna propietario/grupo 1000 a index y subdirectorios. En PHP local, usa el usuario/grupo de PHP-FPM.', + userGroup: 'Usuario/Grupo', + uGroup: 'Grupo', + proxyPath: 'Ruta de proxy', + proxyPass: 'URL destino', + cache: 'Caché', + cacheTime: 'Duración de caché', + enableCache: 'Activar caché', + proxyHost: 'Host de proxy', + disabled: 'Detenido', + startProxy: 'Se iniciará el proxy inverso. ¿Deseas continuar?', + stopProxy: 'Se detendrá el proxy inverso. ¿Deseas continuar?', + sourceFile: 'Ver origen', + proxyHelper1: 'Al acceder a este directorio se devolverá el contenido del destino configurado.', + proxyPassHelper: 'La URL destino debe ser válida y accesible.', + proxyHostHelper: 'Pasar el dominio en la cabecera Host al servidor proxy.', + modifier: 'Reglas de coincidencia', + modifierHelper: 'Ejemplo: "=" coincidencia exacta, "~" regex, "^~" inicio de ruta.', + replace: 'Reemplazos de texto', + replaceHelper: + 'Permite sustitución de cadenas en contenido proxy (HTML, CSS, JS, etc.). Soporta regex para casos complejos.', + addReplace: 'Añadir', + replaced: 'Cadena de búsqueda (obligatoria)', + replaceText: 'Reemplazar por', + replacedErr: 'La cadena de búsqueda no puede estar vacía', + replacedErr2: 'La cadena de búsqueda no puede repetirse', + replacedListEmpty: 'Sin reglas de reemplazo', + proxySslName: 'Nombre SNI de proxy', + basicAuth: 'Autenticación básica', + editBasicAuthHelper: + 'La contraseña está cifrada de forma asimétrica y no puede mostrarse. Editar implica restablecerla.', + antiLeech: 'Anti-hotlink', + extends: 'Extensiones', + browserCache: 'Caché de navegador', + noModify: 'No Modificar', + serverCache: 'Caché del servidor', + leechLog: 'Registrar logs anti-hotlink', + accessDomain: 'Dominios permitidos', + leechReturn: 'Recurso de respuesta', + noneRef: 'Permitir Referer vacío', + disable: 'No habilitado', + disableLeechHelper: 'Deshabilitar protección anti-hotlink', + disableLeech: 'Desactivar anti-hotlink', + ipv6: 'Escuchar IPv6', + leechReturnError: 'Introduce un código HTTP válido', + blockedRef: 'Permitir Referer no estándar', + accessControl: 'Control anti-hotlink', + leechcacheControl: 'Control de caché', + logEnableControl: 'Registrar peticiones de estáticos', + leechSpecialValidHelper: + 'Permitir Referer vacío: peticiones sin Referer no se bloquean. Permitir Referer no estándar: se aceptan Referer que no empiecen con http/https.', + leechInvalidReturnHelper: 'Código HTTP devuelto tras bloquear hotlinking', + leechlogControlHelper: + 'Registrar peticiones de estáticos (deshabilitar en producción para evitar exceso de logs).', + selectAcme: 'Seleccionar cuenta Acme', + imported: 'Creado manualmente', + importType: 'Tipo de importación', + pasteSSL: 'Pegar código', + localSSL: 'Seleccionar archivo del servidor', + privateKeyPath: 'Archivo de clave privada', + certificatePath: 'Archivo de certificado', + ipWhiteListHelper: 'La lista blanca de IP ignora todas las reglas de bloqueo', + redirect: 'Redirección', + sourceDomain: 'Dominio origen', + targetURL: 'URL destino', + keepPath: 'Mantener parámetros URI', + path: 'Ruta', + redirectType: 'Tipo de redirección', + redirectWay: 'Modo', + keep: 'Mantener', + notKeep: 'No mantener', + redirectRoot: 'Redirigir a la home', + redirectHelper: '301 permanente o 302 temporal', + changePHPVersionWarn: 'Esta acción es irreversible, ¿continuar?', + changeVersion: 'Cambiar versión', + retainConfig: 'Conservar php-fpm.conf y php.ini', + runDirHelper2: 'El directorio secundario debe estar bajo index', + openrestyHelper: + 'Puertos por defecto de OpenResty → HTTP: {0}, HTTPS: {1}. Esto puede afectar redirecciones y accesos.', + primaryDomainHelper: 'Ejemplo: ejemplo.com o ejemplo.com:8080', + acmeAccountType: 'Tipo de cuenta', + keyType: 'Algoritmo de clave', + tencentCloud: 'Tencent Cloud', + containWarn: 'El dominio incluye al principal, corrige la entrada', + rewriteHelper2: + 'Aplicaciones como WordPress suelen tener configuración pseudoestática predefinida. Cambiarla puede causar errores.', + websiteBackupWarn: 'Solo se admiten backups locales. Los de otras máquinas pueden fallar en la restauración', + ipWebsiteWarn: 'Los sitios con IP como dominio deben configurarse como sitio por defecto para funcionar', + hstsHelper: 'Habilitar HSTS mejora la seguridad', + includeSubDomains: 'Incluir subdominios', + hstsIncludeSubDomainsHelper: 'Al habilitarlo, la política HSTS aplicará también a los subdominios.', + defaultHtml: 'Configurar página por defecto', + website404: 'Página de error 404', + domain404: 'Página no encontrada', + indexHtml: 'Página predeterminada estática', + stopHtml: 'Página de sitio detenido', + indexPHP: 'Página predeterminada PHP', + sslExpireDate: 'Fecha de expiración SSL', + website404Helper: 'La página 404 solo aplica a sitios PHP o estáticos', + sni: 'SNI de origen', + sniHelper: + 'Cuando el backend proxy es HTTPS, puede ser necesario configurar el SNI. Consulta la doc del proveedor CDN.', + huaweicloud: 'Huawei Cloud', + createDb: 'Crear base de datos', + enableSSLHelper: 'Si falla, no afectará la creación del sitio', + batchAdd: 'Añadir múltiples dominios', + batchInput: 'Entrada por Lotes', + domainNotFQDN: 'Este dominio puede no ser accesible en la red pública', + domainInvalid: 'Formato de dominio inválido', + domainBatchHelper: + 'Un dominio por línea, formato: dominio:puerto@ssl\nEjemplo: example.com:443@ssl o example.com', + generateDomain: 'Generar', + global: 'Global', + subsite: 'Subsitio', + subsiteHelper: 'Un subsite puede usar un directorio PHP o estático existente como raíz.', + parentWbeiste: 'Sitio padre', + deleteSubsite: 'Antes de borrar este sitio elimina los subsitios {0}', + loadBalance: 'Balanceo de carga', + server: 'Servidor', + algorithm: 'Algoritmo', + ipHash: 'IP Hash', + ipHashHelper: 'Asigna al mismo servidor las peticiones de un cliente según su IP.', + leastConn: 'Menos conexiones', + leastConnHelper: 'Envía peticiones al servidor con menos conexiones activas.', + leastTime: 'Menor tiempo', + leastTimeHelper: 'Envía peticiones al servidor con menor tiempo de conexión activa.', + defaultHelper: + 'Método por defecto: reparte las peticiones equitativamente. Si hay pesos configurados, reparte según ellos.', + weight: 'Peso', + maxFails: 'Fallos máximos', + maxConns: 'Máximo de conexiones', + strategy: 'Estrategia', + strategyDown: 'Baja', + strategyBackup: 'Backup', + ipHashBackupErr: 'IP hash does not support backup nodes', + failTimeout: 'Tiempo de espera de fallo', + failTimeoutHelper: + 'La duración de la ventana de tiempo para las comprobaciones de estado del servidor. Cuando el número acumulado de fallos alcanza el umbral dentro de este período, el servidor se eliminará temporalmente y se reintentará después de la misma duración. Por defecto 10 segundos', + + staticChangePHPHelper: 'Actualmente es un sitio estático, puedes cambiarlo a PHP', + proxyCache: 'Caché de proxy inverso', + cacheLimit: 'Límite de espacio de caché', + shareCahe: 'Tamaño de memoria para conteo de caché', + cacheExpire: 'Expiración de caché', + shareCaheHelper: '1M de memoria almacena aprox. 8000 objetos', + cacheLimitHelper: 'Se eliminará caché antigua al superar el límite', + cacheExpireJHelper: 'Caché eliminada al expirar si no se usa', + realIP: 'IP real', + ipFrom: 'Origen de IP', + ipFromHelper: 'Definiendo orígenes confiables, OpenResty analizará cabeceras HTTP para identificar la IP real.', + ipFromExample1: 'Si el frontend es Frp: 127.0.0.1', + ipFromExample2: 'Si el frontend es un CDN: rango IP del CDN', + ipFromExample3: 'En caso de duda: 0.0.0.0/0 (IPv4) ::/0 (IPv6) [Nota: poco seguro]', + http3Helper: 'HTTP/3 ofrece mayor velocidad y rendimiento, pero no todos los navegadores lo soportan.', + cors: 'Intercambio de Recursos de Origen Cruzado (CORS)', + enableCors: 'Habilitar CORS', + allowOrigins: 'Dominios permitidos', + allowMethods: 'Métodos de solicitud permitidos', + allowHeaders: 'Encabezados de solicitud permitidos', + allowCredentials: 'Permitir enviar cookies', + preflight: 'Respuesta rápida a solicitud de preflight', + preflightHleper: + 'Cuando está habilitado, cuando el navegador envía una solicitud de preflight de origen cruzado (solicitud OPTIONS), el sistema devolverá automáticamente un código de estado 204 y establecerá los encabezados de respuesta de origen cruzado necesarios', + + changeDatabase: 'Cambiar base de datos', + changeDatabaseHelper1: 'La asociación se usa en backups/restauraciones.', + changeDatabaseHelper2: 'Cambiar de DB invalida backups previos.', + saveCustom: 'Guardar como plantilla', + rainyun: 'Rain Yun', + volcengine: 'Volcengine', + runtimePortHelper: 'Este runtime tiene múltiples puertos, selecciona uno.', + runtimePortWarn: 'Este runtime no tiene puertos, no se puede usar proxy', + cacheWarn: 'Desactiva la caché en proxy inverso primero', + loadBalanceHelper: 'Tras crear balanceo, ve a "Proxy inverso" y añade un backend: http://', + favorite: 'Favorito', + cancelFavorite: 'Quitar favorito', + useProxy: 'Usar proxy', + useProxyHelper: 'Usa la dirección del proxy configurado en el panel', + westCN: 'West Digital', + openBaseDir: 'Prevenir ataques cross-site', + openBaseDirHelper: 'open_basedir restringe rutas accesibles por PHP, protegiendo contra accesos cruzados', + serverCacheTime: 'Tiempo de caché en servidor', + serverCacheTimeHelper: 'Durante este tiempo, peticiones idénticas devuelven caché sin ir al backend.', + browserCacheTime: 'Tiempo de caché en navegador', + browserCacheTimeHelper: + 'Recursos estáticos se guardan en caché en el navegador reduciendo solicitudes repetidas.', + donotLinkeDB: 'No vincular base de datos', + toWebsiteDir: 'Acceder a directorio del sitio', + execParameters: 'Parámetros de ejecución', + extCommand: 'Comando adicional', + mirror: 'Fuente mirror', + execUser: 'Usuario de ejecución', + execDir: 'Directorio de ejecución', + packagist: 'Mirror China completo', + + batchOpreate: 'Operación en Lote', + batchOpreateHelper: 'Lote {0} sitios web, ¿continuar operación?', + stream: 'Proxy TCP/UDP', + streamPorts: 'Puertos de escucha', + streamPortsHelper: + 'Configura el número de puerto de escucha externo, los clientes accederán al servicio a través de este puerto, separado por comas, p. ej., 5222,5223', + streamHelper: 'Reenvío de Puertos y Balanceo de Carga TCP/UDP', + udp: 'Habilitar UDP', + + syncHtmlHelper: 'Sincronizar con PHP y sitios web estáticos', + }, + php: { + short_open_tag: 'Soporte de etiquetas cortas', + max_execution_time: 'Tiempo máximo de ejecución del script', + max_input_time: 'Tiempo máximo de entrada', + memory_limit: 'Límite de memoria del script', + post_max_size: 'Tamaño máximo de datos POST', + file_uploads: 'Permitir subida de archivos', + upload_max_filesize: 'Tamaño máximo permitido para subir archivos', + max_file_uploads: 'Cantidad máxima de archivos a subir al mismo tiempo', + default_socket_timeout: 'Tiempo de espera del socket', + error_reporting: 'Nivel de errores', + display_errors: 'Mostrar información detallada de errores', + cgi_fix_pathinfo: 'Habilitar pathinfo', + date_timezone: 'Zona horaria', + disableFunction: 'Deshabilitar función', + disableFunctionHelper: 'Introduce la función a deshabilitar, como exec. Para varias, separa con comas', + uploadMaxSize: 'Límite de subida', + indexHelper: + 'Para garantizar el funcionamiento correcto del sitio PHP, coloca el código en el directorio index y evita renombrar', + extensions: 'Gestionar extensiones', + extension: 'Extensión', + extensionHelper: 'Para varias extensiones, sepáralas con comas', + toExtensionsList: 'Ver lista de extensiones', + containerConfig: 'Configuración del contenedor', + containerConfigHelper: + 'Las variables de entorno y demás se pueden modificar en Configuración - Configuración del Contenedor después de la creación', + dateTimezoneHelper: 'Ejemplo: TZ=America/Mexico_City (ajusta según necesites)', + }, + nginx: { + serverNamesHashBucketSizeHelper: 'Tamaño de la tabla hash para nombres de servidor', + clientHeaderBufferSizeHelper: 'Tamaño del buffer de cabecera solicitado por el cliente', + clientMaxBodySizeHelper: 'Tamaño máximo de archivo subido', + keepaliveTimeoutHelper: 'Tiempo de espera de la conexión', + gzipMinLengthHelper: 'Tamaño mínimo para comprimir', + gzipCompLevelHelper: 'Nivel de compresión', + gzipHelper: 'Habilitar compresión para transmisión', + connections: 'Conexiones activas', + accepts: 'Aceptadas', + handled: 'Gestionadas', + requests: 'Solicitudes', + reading: 'Leyendo', + writing: 'Escribiendo', + waiting: 'Esperando', + status: 'Estado actual', + configResource: 'Configuración', + saveAndReload: 'Guardar y recargar', + clearProxyCache: 'Limpiar caché de proxy inverso', + clearProxyCacheWarn: 'Esta acción eliminará todos los archivos en el directorio de caché. ¿Deseas continuar?', + create: 'Agregar nuevo módulo', + update: 'Editar módulo', + params: 'Parámetros', + packages: 'Paquetes', + script: 'Scripts', + module: 'Módulos', + build: 'Compilar', + buildWarn: 'Compilar OpenResty requiere reservar CPU y memoria, puede tomar tiempo, ten paciencia', + mirrorUrl: 'Fuente de software', + paramsHelper: 'Por ejemplo: --add-module=/tmp/ngx_brotli', + packagesHelper: 'Por ejemplo: git, curl (separados por coma)', + scriptHelper: + 'Scripts a ejecutar antes de compilar, usualmente para descargar código fuente de módulos, instalar dependencias, etc.', + buildHelper: + 'Haz clic en compilar después de agregar/modificar un módulo. OpenResty se reiniciará automáticamente tras una compilación exitosa.', + defaultHttps: 'HTTPS Anti-manipulación', + defaultHttpsHelper1: 'Habilitar esto puede resolver problemas de manipulación de HTTPS.', + sslRejectHandshake: 'Rechazar handshake SSL predeterminado', + sslRejectHandshakeHelper: + 'Habilitar esto puede evitar la fuga de certificados, establecer un sitio web predeterminado invalidará esta configuración', + }, + ssl: { + create: 'Solicitar', + provider: 'Tipo', + manualCreate: 'Creado manualmente', + acmeAccount: 'Cuenta ACME', + resolveDomain: 'Resolver dominio', + err: 'Error', + value: 'valor del registro', + dnsResolveHelper: 'Por favor, agrega los siguientes registros en tu proveedor de DNS:', + detail: 'Ver detalles', + msg: 'Información', + ssl: 'Certificado', + key: 'Clave privada', + startDate: 'Fecha de inicio', + organization: 'Organización emisora', + renewConfirm: 'Esto renovará un nuevo certificado para el dominio {0}. ¿Deseas continuar?', + autoRenew: 'Renovación automática', + autoRenewHelper: 'Renueva automáticamente 30 días antes de la expiración', + renewSuccess: 'Renovación exitosa', + renewWebsite: 'Este certificado está asociado a los siguientes sitios y se aplicará en ellos simultáneamente', + createAcme: 'Crear cuenta', + acmeHelper: 'La cuenta Acme se usa para solicitar certificados gratuitos', + upload: 'Importar', + applyType: 'Método de solicitud', + apply: 'Renovar', + applyStart: 'Inicia solicitud de certificado', + getDnsResolve: 'Obteniendo valor DNS, espera...', + selfSigned: 'Gestionar CA autofirmada', + ca: 'Autoridad certificadora', + commonName: 'Nombre común', + caName: 'Nombre de CA', + company: 'Nombre de la organización', + department: 'Unidad organizativa', + city: 'Ciudad', + province: 'Estado o provincia', + country: 'País (código de 2 letras)', + commonNameHelper: 'Por ejemplo, ', + selfSign: 'Emitir certificado', + days: 'Período de validez', + domainHelper: 'Un dominio por línea, admite * y dirección IP', + pushDir: 'Guardar certificado en el directorio local', + dir: 'Directorio', + pushDirHelper: 'Se generarán los archivos "fullchain.pem" y "privkey.pem" en este directorio.', + organizationDetail: 'Detalles de la organización', + fromWebsite: 'Desde el sitio web', + dnsMauanlHelper: 'En modo manual, haz clic en solicitar tras la creación para obtener el valor DNS', + httpHelper: 'El modo HTTP requiere OpenResty instalado y no soporta certificados comodín.', + buypassHelper: 'Buypass no es accesible desde China continental', + googleHelper: 'Cómo obtener EAB HmacKey y EAB kid', + googleCloudHelper: 'La API de Google Cloud no es accesible en la mayoría de China', + skipDNSCheck: 'Omitir comprobación DNS', + skipDNSCheckHelper: 'Solo marca si tienes problemas de timeout al solicitar el certificado.', + cfHelper: 'No uses la clave API Global', + deprecated: 'será obsoleto', + deprecatedHelper: 'El soporte se ha detenido y puede eliminarse en el futuro. Usa el método Tencent Cloud.', + disableCNAME: 'Deshabilitar CNAME', + disableCNAMEHelper: 'Marca si el dominio tiene un CNAME y falla la solicitud.', + nameserver: 'Servidor DNS', + nameserverHelper: 'Usa un DNS personalizado para verificar dominios.', + edit: 'Editar certificado', + execShell: 'Ejecutar script tras solicitud de certificado.', + shell: 'Contenido del script', + shellHelper: + 'Por defecto, el script se ejecuta en el directorio de instalación de 1Panel. Si se guarda el certificado, será en ese directorio. Timeout: 30 minutos.', + customAcme: 'Servicio ACME personalizado', + customAcmeURL: 'URL del servicio ACME', + baiduCloud: 'Baidu Cloud', + pushNode: 'Sincronizar con otros nodos', + pushNodeHelper: 'Enviar a los nodos seleccionados después de la aplicación/renovación', + fromMaster: 'Envío desde el nodo maestro', + hostedZoneID: 'Hosted Zone ID', + isIP: 'Certificado IP', + useEAB: 'Usar autenticación EAB', + }, + firewall: { + create: 'Crear regla', + edit: 'Editar regla', + ccDeny: 'Protección CC', + ipWhiteList: 'Lista blanca de IP', + ipBlockList: 'Lista negra de IP', + fileExtBlockList: 'Lista negra de extensiones de archivo', + urlWhiteList: 'Lista blanca de URL', + urlBlockList: 'Lista negra de URL', + argsCheck: 'Verificación de parámetros GET', + postCheck: 'Verificación de parámetros POST', + cookieBlockList: 'Lista negra de cookies', + dockerHelper: + 'El firewall actual no puede deshabilitar el mapeo de puertos de contenedores. Las aplicaciones instaladas pueden ir a la página [Instaladas] para editar los parámetros de la aplicación y configurar reglas de liberación de puertos.', + iptablesHelper: + 'Se detectó que el sistema está usando el firewall {0}. Para cambiar a iptables, ¡desinstálelo manualmente primero!', + quickJump: 'Acceso rápido', + used: 'En uso', + unUsed: 'No usado', + dockerRestart: 'Las operaciones del firewall requieren reiniciar el servicio de Docker', + firewallHelper: 'Firewall del sistema {0}', + firewallNotStart: `El firewall del sistema no está habilitado actualmente. Actívalo primero.`, + restartFirewallHelper: 'Esta operación reiniciará el firewall actual. ¿Deseas continuar?', + stopFirewallHelper: 'Esto hará que el servidor pierda protección de seguridad. ¿Deseas continuar?', + startFirewallHelper: + 'Tras habilitar el firewall, la seguridad del servidor podrá protegerse mejor. ¿Deseas continuar?', + noPing: 'Deshabilitar ping', + enableBanPing: 'Bloquear Ping', + disableBanPing: 'Desbloquear Ping', + noPingTitle: 'Deshabilitar ping', + noPingHelper: `Esto deshabilitará el ping, y el servidor no responderá con eco ICMP. ¿Deseas continuar?`, + onPingHelper: 'Esto habilitará el ping, y los atacantes podrían descubrir tu servidor. ¿Deseas continuar?', + changeStrategy: 'Cambiar la estrategia de {0}', + changeStrategyIPHelper1: + 'Cambiar la estrategia de dirección IP a [denegar]. Después de establecerla, se prohibirá el acceso al servidor. ¿Deseas continuar?', + changeStrategyIPHelper2: + 'Cambiar la estrategia de dirección IP a [permitir]. Después de establecerla, se restaurará el acceso normal. ¿Deseas continuar?', + changeStrategyPortHelper1: + 'Cambiar la política de puerto a [rechazar]. Después de establecerla, se denegará el acceso externo. ¿Deseas continuar?', + changeStrategyPortHelper2: + 'Cambiar la política de puerto a [aceptar]. Después de establecerla, se restaurará el acceso normal. ¿Deseas continuar?', + stop: 'Detener', + portFormatError: 'Este campo debe ser un puerto válido.', + portHelper1: 'Puertos múltiples, ej. 8080 y 8081', + portHelper2: 'Rango de puertos, ej. 8080-8089', + changeStrategyHelper: + 'Cambiar estrategia de {0} [{1}] a [{2}]. Después de configurarla, {0} tendrá acceso externo como {2}. ¿Deseas continuar?', + + strategy: 'Estrategia', + accept: 'Aceptar', + drop: 'Rechazar', + anyWhere: 'Cualquiera', + address: 'IPs especificadas', + addressHelper: 'Admite dirección IP o segmento de red', + allow: 'Permitir', + deny: 'Denegar', + addressFormatError: 'Este campo debe ser una dirección IP válida.', + addressHelper1: 'Admite dirección IP o rango de IP. Ejemplo: "172.16.10.11" o "172.16.10.0/24".', + addressHelper2: 'Para múltiples direcciones IP, sepáralas con comas. Ejemplo: "172.16.10.11, 172.16.0.0/24".', + allIP: 'Todas las IP', + portRule: 'Regla | Reglas', + createPortRule: '@:commons.button.create @.lower:firewall.portRule', + forwardRule: 'Regla de redirección de puertos | Reglas de redirección de puertos', + createForwardRule: '@:commons.button.create @:firewall.forwardRule', + ipRule: 'Regla IP | Reglas IP', + createIpRule: '@:commons.button.create @:firewall.ipRule', + userAgent: 'Filtro User-Agent', + sourcePort: 'Puerto de origen', + targetIP: 'IP de destino', + targetPort: 'Puerto de destino', + forwardHelper1: 'Si quieres reenviar al puerto local, la IP de destino debe ser "127.0.0.1".', + forwardHelper2: 'Deja en blanco la IP de destino para reenviar al puerto local.', + forwardPortHelper: 'Admite rangos de puertos, ej.: 8080-8089', + forwardInboundInterface: 'Interfaz de Red de Entrada para Reenvío', + exportHelper: 'A punto de exportar {0} reglas de firewall. ¿Continuar?', + importSuccess: 'Se importaron correctamente {0} reglas', + importPartialSuccess: 'Importación completada: {0} correctas, {1} fallidas', + + ipv4Limit: 'La operación actual solo admite direcciones IPv4', + basicStatus: 'La cadena actual {0} no está vinculada, ¡vincule primero!', + baseIptables: 'Servicio iptables', + forwardIptables: 'Servicio de Reenvío de Puertos iptables', + advanceIptables: 'Servicio de Configuración Avanzada de iptables', + initMsg: 'A punto de inicializar {0}, ¿continuar?', + initHelper: + 'Se detectó que {0} no está inicializado. ¡Haga clic en el botón de inicialización en la barra de estado superior para configurar!', + bindHelper: + 'Vincular: las reglas de firewall solo surtirán efecto cuando el estado esté vinculado. ¿Confirmar?', + unbindHelper: + 'Desvincular: al desvincular, todas las reglas de firewall agregadas se volverán inválidas. Proceda con precaución. ¿Confirmar?', + defaultStrategy: 'La política predeterminada para la cadena actual {0} es {1}', + defaultStrategy2: + 'La política predeterminada para la cadena actual {0} es {1}, el estado actual es no vinculado. ¡Las reglas de firewall agregadas surtirán efecto después de la vinculación!', + filterRule: 'Regla de Filtro', + filterHelper: + 'Las reglas de filtro le permiten controlar el tráfico de red a nivel INPUT/OUTPUT. Configure con cuidado para evitar bloquear el sistema.', + chain: 'Cadena', + targetChain: 'Cadena de Destino', + sourceIP: 'IP de Origen', + destIP: 'IP de Destino', + inboundDirection: 'Dirección de Entrada', + outboundDirection: 'Dirección de Salida', + destPort: 'Puerto de Destino', + action: 'Acción', + reject: 'Rechazar', + sourceIPHelper: 'Formato CIDR, ej. 192.168.1.0/24. Dejar vacío para todas las direcciones', + destIPHelper: 'Formato CIDR, ej. 10.0.0.0/8. Dejar vacío para todas las direcciones', + portHelper: '0 significa cualquier puerto', + allPorts: 'Todos los Puertos', + deleteRuleConfirm: 'Se eliminarán {0} reglas. ¿Continuar?', + }, + runtime: { + runtime: 'Runtime', + workDir: 'Directorio de trabajo', + create: 'Crear', + localHelper: 'Para problemas de instalación en entorno local y uso en entorno sin conexión, consulte ', + versionHelper: 'Versión de PHP, ej. v8.0', + buildHelper: `Si se seleccionan más extensiones, el uso de CPU será mayor durante el proceso de creación de la imagen. Evita seleccionar todas las extensiones.`, + openrestyWarn: 'PHP necesita actualizarse a OpenResty versión 1.21.4.1 o superior para poder usarse', + toupgrade: 'Actualizar', + edit: 'Editar runtime', + extendHelper: `Si las extensiones que necesitas no están en la lista, puedes introducir el nombre manualmente. Ejemplo: "sockets", luego selecciona la primera.`, + rebuildHelper: 'Tras editar la extensión, debes reconstruir la aplicación PHP para que surta efecto', + rebuild: 'Reconstruir aplicación PHP', + source: 'Origen de extensiones PHP', + ustc: 'Universidad de Ciencia y Tecnología de China', + netease: 'Netease', + aliyun: 'Alibaba Cloud', + tsinghua: 'Universidad de Tsinghua', + xtomhk: 'Espejo XTOM (Hong Kong)', + xtom: 'Espejo XTOM (Global)', + phpsourceHelper: 'Elige una fuente adecuada según tu entorno de red.', + appPort: 'Puerto de la aplicación', + externalPort: 'Puerto externo', + packageManager: 'Gestor de paquetes', + codeDir: 'Directorio del código', + appPortHelper: 'El puerto usado por la aplicación.', + externalPortHelper: 'El puerto expuesto al exterior.', + runScript: 'Script de ejecución', + runScriptHelper: + 'La lista de comandos de inicio se obtiene desde el archivo package.json en el directorio fuente.', + open: 'Abrir', + operatorHelper: 'La operación {0} se ejecutará sobre el entorno de ejecución seleccionado. ¿Deseas continuar?', + taobao: 'Taobao', + tencent: 'Tencent', + imageSource: 'Fuente de imagen', + moduleManager: 'Gestión de módulos', + module: 'Módulo', + nodeOperatorHelper: + '¿Quieres {0} el módulo {1}? La operación puede causar fallos en el entorno de ejecución, confirma antes de continuar', + customScript: 'Comando de inicio personalizado', + customScriptHelper: + 'Por favor, ingresa el comando de inicio completo, por ejemplo: npm run start. Para comandos de inicio de PM2, por favor reemplaza con pm2-runtime, de lo contrario fallará al iniciar.', + portError: 'No repitas el mismo puerto.', + systemRestartHelper: + 'Descripción de estado: Interrupción - fallo al obtener estado debido a reinicio del sistema', + javaScriptHelper: + 'Proporciona un comando de inicio completo. Ejemplo: "java -jar halo.jar -Xmx1024M -Xms256M".', + javaDirHelper: 'El directorio debe contener archivos .jar, se permiten subdirectorios', + goHelper: 'Proporciona un comando de inicio completo. Ejemplo: "go run main.go" o "./main".', + goDirHelper: 'El directorio o subdirectorio debe contener archivos Go o binarios.', + extension: 'Extensión', + installExtension: '¿Confirmas instalar la extensión {0}?', + loadedExtension: 'Extensión cargada', + popularExtension: 'Extensión popular', + uninstallExtension: '¿Seguro que quieres desinstalar la extensión {0}?', + phpConfigHelper: 'Modificar la configuración requiere reiniciar el entorno de ejecución, ¿quieres continuar?', + operateMode: 'Modo de operación', + dynamic: 'Dinámico', + static: 'Estático', + ondemand: 'Bajo demanda', + dynamicHelper: + 'Ajusta dinámicamente el número de procesos, alta flexibilidad, adecuado para sitios con grandes fluctuaciones de tráfico o poca memoria', + staticHelper: + 'Número fijo de procesos, adecuado para sitios con alta concurrencia y tráfico estable, alto consumo de recursos', + ondemandHelper: + 'Los procesos se inician y destruyen bajo demanda, la utilización de recursos es óptima, pero la respuesta inicial puede ser lenta', + max_children: 'Número máximo de procesos permitidos', + start_servers: 'Número de procesos creados al inicio', + min_spare_servers: 'Número mínimo de procesos inactivos', + max_spare_servers: 'Número máximo de procesos inactivos', + envKey: 'Nombre', + envValue: 'Valor', + environment: 'Variable de entorno', + pythonHelper: + 'Proporciona un comando de inicio completo. Ejemplo: "pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000".', + dotnetHelper: 'Proporciona un comando de inicio completo. Ejemplo: "dotnet MyWebApp.dll".', + dirHelper: 'Nota: rellena la ruta del directorio dentro del contenedor', + concurrency: 'Esquema de concurrencia', + loadStatus: 'Estado de carga', + extraHosts: 'Mapeo de host', + }, + process: { + pid: 'ID de proceso', + ppid: 'ID de proceso padre', + numThreads: 'Hilos', + memory: 'Memoria', + diskRead: 'Lectura de disco', + diskWrite: 'Escritura en disco', + netSent: 'Tráfico de salida', + netRecv: 'Tráfico de entrada', + numConnections: 'Conexiones', + startTime: 'Hora de inicio', + running: 'En ejecución', + sleep: 'En espera', + stop: 'Detenido', + idle: 'Inactivo', + zombie: 'Proceso zombi', + wait: 'Esperando', + lock: 'Bloqueo', + blocked: 'Bloqueado', + cmdLine: 'Comando de inicio', + basic: 'Básico', + mem: 'Memoria', + openFiles: 'Archivos abiertos', + env: 'Entorno', + noenv: 'Ninguno', + net: 'Conexiones de red', + laddr: 'Dirección/puerto local', + raddr: 'Dirección/puerto remoto', + stopProcess: 'Finalizar', + viewDetails: 'Ver detalles', + stopProcessWarn: '¿Seguro que deseas finalizar este proceso (PID:{0})?', + kill: 'Terminar Proceso', + killNow: 'Terminar Ahora', + killHelper: 'Terminar el proceso {0} puede hacer que algunos programas funcionen incorrectamente. ¿Continuar?', + processName: 'Nombre del proceso', + }, + tool: { + supervisor: { + loadStatusErr: 'No se pudo obtener el estado del proceso, verifica el estado del servicio Supervisor.', + notSupport: + 'Servicio Supervisor no detectado, ve a la página de librería de scripts para instalarlo manualmente', + list: 'Procesos en segundo plano', + config: 'Configuración de Supervisor', + primaryConfig: 'Ubicación del archivo de configuración principal', + notSupportCtl: `No se detectó supervisorctl, ve a la página de librería de scripts para instalarlo manualmente`, + user: 'Usuario', + command: 'Comando', + dir: 'Directorio', + numprocs: 'Número de procesos', + initWarn: + 'Esto modificará el valor de "files" en la sección "[include]" del archivo de configuración principal. El directorio de otros archivos de configuración será: "{directorio de instalación de 1Panel}/1panel/tools/supervisord/supervisor.d/".', + operatorHelper: 'La operación {1} se ejecutará en {0}, ¿quieres continuar?', + uptime: 'Tiempo en ejecución', + notStartWarn: `Supervisor no está iniciado. Inícialo primero.`, + serviceName: 'Nombre del servicio', + initHelper: + 'El servicio Supervisor está detectado pero no inicializado. Haz clic en el botón de inicialización en la barra de estado superior para configurarlo.', + serviceNameHelper: + 'Nombre del servicio Supervisor gestionado por systemctl, normalmente supervisor o supervisord', + restartHelper: + 'Esto reiniciará el servicio después de la inicialización, lo que detendrá todos los procesos en segundo plano existentes.', + RUNNING: 'En ejecución', + STOPPED: 'Detenido', + STOPPING: 'Deteniéndose', + STARTING: 'Iniciándose', + FATAL: 'Fallo al iniciar', + BACKOFF: 'Excepción al iniciar', + ERROR: 'Error', + statusCode: 'Código de estado', + manage: 'Gestión', + autoRestart: 'Reinicio automático', + EXITED: 'Finalizado', + autoRestartHelper: 'Indica si el programa se reinicia automáticamente tras un fallo', + autoStart: 'Inicio automático', + autoStartHelper: 'Indica si el servicio se inicia automáticamente cuando arranca Supervisor', + }, + }, + disk: { + management: 'Gestión de discos', + partition: 'Partición', + unmount: 'Desmontar', + unmountHelper: '¿Quieres desmontar la partición {0}?', + mount: 'Montar', + partitionAlert: + 'El particionado de disco requiere formatearlo, y los datos existentes se eliminarán. Guarda o realiza snapshots de tus datos previamente.', + mountPoint: 'Directorio de montaje', + systemDisk: 'Disco del sistema', + unpartitionedDisk: 'Disco sin particionar', + handlePartition: 'Particionar ahora', + filesystem: 'Sistema de archivos', + unmounted: 'No montado', + cannotOperate: 'No se puede operar', + systemDiskHelper: 'Aviso: El disco actual es el disco del sistema. No se puede operar sobre él.', + autoMount: 'Montaje automático', + model: 'Modelo del dispositivo', + diskType: 'Tipo de disco', + serial: 'Núm. de serie', + noFail: 'El fallo de montaje no afecta al inicio del sistema', + }, + xpack: { + expiresTrialAlert: + 'Aviso: Tu prueba de Pro expirará en {0} días y todas las funciones Pro dejarán de estar disponibles. Renueva o actualiza a la versión completa a tiempo.', + expiresAlert: + 'Aviso: Tu licencia Pro expirará en {0} días y todas las funciones Pro dejarán de estar disponibles. Renueva pronto para asegurar el uso continuo.', + menu: 'Pro', + upage: 'Constructor Web con IA', + proAlert: 'Actualiza a Pro para usar esta función', + app: { + app: 'APP', + title: 'Alias del Panel', + titleHelper: 'El alias del panel se usa para mostrar en la APP (alias por defecto)', + qrCode: 'Código QR', + apiStatusHelper: 'La APP del Panel necesita que la API esté habilitada', + apiInterfaceHelper: 'Permite acceso a la API del panel (debe estar habilitada para la APP)', + apiInterfaceHelper1: + 'El acceso requiere añadir el visitante a la lista blanca; para IPs dinámicas se recomienda usar 0.0.0.0/0 (IPv4) o ::/0 (IPv6)', + qrCodeExpired: 'Tiempo de refresco', + apiLeakageHelper: 'No divulgues el código QR. Úsalo solo en entornos de confianza.', + }, + waf: { + name: 'WAF', + blackWhite: 'Lista Blanca y Negra', + globalSetting: 'Configuración Global', + websiteSetting: 'Configuración del Sitio', + blockRecords: 'Registros Bloqueados', + world: 'Mundial', + china: 'China', + intercept: 'Intercepciones', + request: 'Peticiones', + count4xx: 'Cantidad 4xx', + count5xx: 'Cantidad 5xx', + todayStatus: 'Estado de hoy', + reqMap: 'Mapa de ataques (últimos 30 días)', + resource: 'Fuente', + count: 'Cantidad', + hight: 'Alto', + low: 'Bajo', + reqCount: 'Solicitudes', + interceptCount: 'Número de intercepciones', + requestTrends: 'Tendencia de solicitudes (últimos 7 días)', + interceptTrends: 'Tendencia de intercepciones (últimos 7 días)', + whiteList: 'Whitelist', + blackList: 'Blacklist', + ipBlackListHelper: 'Las IP en la blacklist no pueden acceder al sitio web', + ipWhiteListHelper: 'Las IP en la whitelist saltan todas las restricciones', + uaBlackListHelper: 'Las peticiones con User-Agent en blacklist serán bloqueadas', + uaWhiteListHelper: 'Las peticiones con User-Agent en whitelist saltan todas las restricciones', + urlBlackListHelper: 'Las peticiones a URLs en blacklist serán bloqueadas', + urlWhiteListHelper: 'Las peticiones a URLs en whitelist saltan todas las restricciones', + ccHelper: + 'Si un sitio recibe más de {1} peticiones de la misma IP en {0} segundos, la IP será bloqueada por {2}', + blockTime: 'Duración de bloqueo', + attackHelper: 'Si las intercepciones acumuladas superan {1} en {0} segundos, la IP se bloquea por {2}', + notFoundHelper: 'Si el número de errores 404 supera {1} en {0} segundos, la IP será bloqueada por {2}', + frequencyLimit: 'Límite de frecuencia', + regionLimit: 'Límite regional', + defaultRule: 'Reglas por defecto', + accessFrequencyLimit: 'Límite de frecuencia de acceso', + attackLimit: 'Límite de frecuencia de ataques', + notFoundLimit: 'Límite de 404', + urlLimit: 'Límite de frecuencia por URL', + urlLimitHelper: 'Configura el límite de acceso para una URL específica', + sqliDefense: 'Protección contra SQL Injection', + sqliHelper: 'Detecta SQL Injection en peticiones y las bloquea', + xssHelper: 'Detecta XSS en peticiones y las bloquea', + xssDefense: 'Protección contra XSS', + uaDefense: 'Reglas contra User-Agent malicioso', + uaHelper: 'Incluye reglas para bots maliciosos comunes', + argsDefense: 'Reglas contra parámetros maliciosos', + argsHelper: 'Bloquea peticiones con parámetros maliciosos', + cookieDefense: 'Reglas contra cookies maliciosas', + cookieHelper: 'Prohíbe cookies maliciosas en las peticiones', + headerDefense: 'Reglas contra cabeceras maliciosas', + headerHelper: 'Prohíbe cabeceras maliciosas en las peticiones', + httpRule: 'Reglas de métodos HTTP', + httpHelper: + 'Define qué métodos HTTP están permitidos. Ejemplo: solo GET. Para restringir, desactiva los métodos no deseados', + geoRule: 'Restricciones geográficas', + geoHelper: 'Restringe accesos desde ciertas regiones. Ejemplo: permitir solo China, bloqueando el resto', + ipLocation: 'Ubicación IP', + action: 'Acción', + ruleType: 'Tipo de ataque', + ipHelper: 'Introduce la dirección IP', + attackLog: 'Log de ataques', + rule: 'Regla', + ipArr: 'Rango IPv4', + ipStart: 'IP inicio', + ipEnd: 'IP fin', + ipv4: 'IPv4', + ipv6: 'IPv6', + urlDefense: 'Reglas de URL', + urlHelper: 'URL prohibida', + dirFilter: 'Filtro de directorios', + sqlInject: 'SQL Injection', + xss: 'XSS', + phpExec: 'Ejecución de scripts PHP', + oneWordTrojan: 'Troyano de una línea', + appFilter: 'Filtrado de directorios peligrosos', + webshell: 'Webshell', + args: 'Parámetros maliciosos', + protocolFilter: 'Filtro de protocolos', + javaFilter: 'Filtro de ficheros Java peligrosos', + scannerFilter: 'Filtro de escáneres', + escapeFilter: 'Filtro de escapes', + customRule: 'Reglas personalizadas', + httpMethod: 'Filtro de métodos HTTP', + fileExt: 'Restricción de extensiones', + fileExtHelper: 'Extensiones prohibidas para subir', + deny: 'Prohibir', + allow: 'Permitir', + field: 'Objeto', + pattern: 'Condición', + ruleContent: 'Contenido', + contain: 'contiene', + equal: 'igual', + regex: 'expresión regular', + notEqual: 'distinto', + customRuleHelper: 'Acciones basadas en condiciones definidas', + actionAllow: 'Permitir', + blockIP: 'Bloquear IP', + code: 'Código de respuesta', + noRes: 'Desconectar (444)', + badReq: 'Parámetros inválidos (400)', + forbidden: 'Acceso prohibido (403)', + serverErr: 'Error de servidor (500)', + resHtml: 'Página de respuesta', + allowHelper: 'Permitir acceso salta las siguientes reglas del WAF, úsalo con precaución', + captcha: 'Verificación humano-máquina', + fiveSeconds: 'Verificación de 5 segundos', + location: 'Región', + redisConfig: 'Configuración Redis', + redisHelper: 'Habilita Redis para persistir IPs bloqueadas temporalmente', + wafHelper: 'Todos los sitios perderán protección al deshabilitar', + attackIP: 'IP atacante', + attackParam: 'Detalles del ataque', + execRule: 'Regla aplicada', + acl: 'ACL', + sql: 'SQL Injection', + cc: 'Límite de frecuencia de acceso', + isBlocking: 'Bloqueado', + isFree: 'Libre', + unLock: 'Desbloquear', + unLockHelper: '¿Quieres desbloquear la IP: {0}?', + saveDefault: 'Guardar por defecto', + saveToWebsite: 'Aplicar al sitio web', + saveToWebsiteHelper: '¿Aplicar esta configuración a todos los sitios?', + websiteHelper: + 'Estas son configuraciones por defecto al crear sitios. Para aplicar, edítalo en el sitio específico', + websiteHelper2: + 'Configuraciones por defecto para sitios nuevos, modifica en cada sitio para que tengan efecto', + ipGroup: 'Grupo de IPs', + ipGroupHelper: 'Una IP o rango por línea. Soporta IPv4 e IPv6. Ejemplo: 192.168.1.1 o 192.168.1.0/24', + ipBlack: 'IP blacklist', + openRestyAlert: 'Se requiere versión de OpenResty mayor a {0}', + initAlert: + 'La primera vez es necesario inicializar. El archivo de configuración será modificado y la configuración previa se perderá. Haz copia de seguridad', + initHelper: 'La inicialización borrará la configuración existente del WAF. ¿Seguro que quieres continuar?', + mainSwitch: 'Interruptor principal', + websiteAlert: 'Crea un sitio primero', + defaultUrlBlack: 'Reglas de URL', + htmlRes: 'Página de intercepción', + urlSearchHelper: 'Introduce la URL (búsqueda difusa)', + toCreate: 'Crear', + closeWaf: 'Cerrar WAF', + closeWafHelper: 'Cerrar WAF desprotege el sitio. ¿Continuar?', + addblack: 'Añadir a blacklist', + addwhite: 'Añadir a whitelist', + addblackHelper: '¿Añadir IP:{0} a la blacklist?', + addwhiteHelper: '¿Añadir IP:{0} a la whitelist?', + defaultUaBlack: 'Regla de User-Agent', + defaultIpBlack: 'Grupo de IPs maliciosas', + cookie: 'Reglas de cookies', + urlBlack: 'Blacklist de URL', + uaBlack: 'Blacklist de User-Agent', + attackCount: 'Límite de frecuencia de ataques', + fileExtCheck: 'Restricción de subida', + geoRestrict: 'Restricción geográfica', + attacklog: 'Registro de intercepciones', + unknownWebsite: 'Acceso a dominio no autorizado', + geoRuleEmpty: 'La región no puede estar vacía', + unknown: 'Sitio no existe', + geo: 'Restricción por región', + revertHtml: '¿Restaurar {0} como página por defecto?', + five_seconds: 'Verificación de 5 segundos', + header: 'Reglas de cabeceras', + methodWhite: 'Reglas HTTP', + expiryDate: 'Fecha de expiración', + expiryDateHelper: 'Tras validación exitosa, no se verificará de nuevo durante el periodo de validez', + defaultIpBlackHelper: 'IPs maliciosas recopiladas de internet para bloquear accesos', + notFoundCount: 'Límite de errores 404', + matchValue: 'Valor de coincidencia', + headerName: 'Debe iniciar con letra o número, admite "-", longitud 3-30', + cdnHelper: 'Para webs con CDN, obtener IP real aquí', + clearLogWarn: 'No podrás recuperar los logs borrados. ¿Continuar?', + commonRuleHelper: 'Las reglas usan coincidencia difusa', + blockIPHelper: + 'Las IP bloqueadas se guardan temporalmente en OpenResty y se liberan tras reinicio. Para bloqueo permanente usa reglas', + addWhiteUrlHelper: '¿Añadir URL {0} a la whitelist?', + dashHelper: 'La versión community también soporta configuraciones globales y de sitio', + wafStatusHelper: 'WAF no está activo, habilítalo en Configuración Global', + ccMode: 'Modo', + global: 'Modo global', + uriMode: 'Modo URL', + globalHelper: 'Modo global: cuando el total de peticiones a cualquier URL supera el límite en el periodo', + uriModeHelper: 'Modo URL: cuando las peticiones a una URL superan el límite en el periodo', + ip: 'Blacklist de IPs', + globalSettingHelper: + 'Las configuraciones con etiqueta [Website] requieren activarse también en la Configuración del Sitio', + globalSettingHelper2: 'Debe habilitarse en [Configuración Global] y [Configuración del Sitio] a la vez', + urlCCHelper: 'Más de {1} peticiones a esta URL en {0} segundos bloquean la IP por {2}', + urlCCHelper2: 'La URL no puede contener parámetros', + notContain: 'No contiene', + urlcc: 'Límite de frecuencia por URL', + method: 'Método de petición', + addIpsToBlock: 'Bloqueo masivo de IPs', + addUrlsToWhite: 'Añadir URLs en whitelist en lote', + noBlackIp: 'IP ya bloqueada', + noWhiteUrl: 'URL ya en whitelist', + spiderIpHelper: + 'Incluye Baidu, Bing, Google, 360, Shenma, Sogou, ByteDance, DuckDuckGo, Yandex. Al cerrar se bloquea todo acceso de crawlers', + spiderIp: 'Pool de IPs de crawlers', + geoIp: 'Base de datos IP', + geoIpHelper: 'Usada para geolocalizar IPs', + stat: 'Reporte de ataques', + statTitle: 'Reporte', + attackIp: 'IP', + attackCountNum: 'Cantidad', + percent: 'Porcentaje', + addblackUrlHelper: '¿Añadir URL {0} a la blacklist?', + rce: 'Remote Code Execution', + software: 'Software', + cveHelper: 'Incluye vulnerabilidades de software y frameworks comunes', + vulnCheck: 'Reglas adicionales', + ssrf: 'Vulnerabilidad SSRF', + afr: 'Lectura arbitraria de ficheros', + ua: 'Acceso no autorizado', + id: 'Divulgación de información', + aa: 'Bypass de autenticación', + dr: 'Directory Traversal', + xxe: 'Vulnerabilidad XXE', + suid: 'Vulnerabilidad de serialización', + dos: 'Denegación de servicio', + afd: 'Descarga arbitraria de ficheros', + sqlInjection: 'SQL Injection', + afw: 'Escritura arbitraria de ficheros', + il: 'Fuga de información', + clearAllLog: 'Borrar todos los logs', + exportLog: 'Exportar logs', + appRule: 'Reglas de aplicación', + appRuleHelper: 'Reglas de apps comunes, reduce falsos positivos. Un sitio solo puede usar una regla', + logExternal: 'Excluir tipos de registros', + ipWhite: 'Whitelist de IPs', + urlWhite: 'Whitelist de URLs', + uaWhite: 'Whitelist de User-Agent', + logExternalHelper: + 'Tipos de registro excluidos no se guardan. Whitelist/blacklist, restricciones regionales y reglas generan muchos logs, se recomienda excluir', + ssti: 'Ataque SSTI', + crlf: 'Inyección CRLF', + strict: 'Modo estricto', + strictHelper: 'Usa reglas más estrictas para validar peticiones', + saveLog: 'Guardar log', + remoteURLHelper: 'La URL remota debe tener solo una IP por línea y sin otros caracteres', + notFound: 'No encontrado (404)', + serviceUnavailable: 'Servicio no disponible (503)', + gatewayTimeout: 'Tiempo de espera de gateway (504)', + belongToIpGroup: 'Pertenece a grupo de IPs', + notBelongToIpGroup: 'No pertenece a grupo de IPs', + unknownWebsiteKey: 'Dominio desconocido', + special: 'Especial', + fileToLarge: 'Archivo supera 1MB y no puede subirse', + uploadOverLimit: 'El archivo supera el límite, máximo 1', + importRuleHelper: 'Una regla por línea', + }, + monitor: { + name: 'Monitorización Web', + pv: 'Page Views (PV)', + uv: 'Unique Visitors (UV)', + flow: 'Tráfico', + ip: 'IP', + spider: 'Crawler', + visitors: 'Tendencia de visitantes', + today: 'Hoy', + last7days: 'Últimos 7 días', + last30days: 'Últimos 30 días', + uvMap: 'Mapa de visitantes (30 días)', + qps: 'Solicitudes en tiempo real (por minuto)', + flowSec: 'Tráfico en tiempo real (por minuto)', + excludeCode: 'Excluir códigos de estado', + excludeUrl: 'Excluir URLs', + excludeExt: 'Excluir extensiones', + cdnHelper: 'Obtiene la IP real desde la cabecera del CDN', + reqRank: 'Ranking de visitas', + refererDomain: 'Dominio referente', + os: 'Sistema operativo', + browser: 'Navegador/Cliente', + device: 'Dispositivo', + showMore: 'Más', + unknown: 'Otro', + pc: 'PC', + mobile: 'Móvil', + wechat: 'WeChat', + machine: 'Máquina', + tencent: 'Navegador Tencent', + ucweb: 'UC Browser', + '2345explorer': '2345 Browser', + huaweibrowser: 'Navegador Huawei', + log: 'Logs de solicitudes', + statusCode: 'Código de estado', + requestTime: 'Tiempo de respuesta', + flowRes: 'Tráfico de respuesta', + method: 'Método HTTP', + statusCodeHelper: 'Introduce el código de estado', + statusCodeError: 'Código de estado inválido', + methodHelper: 'Introduce el método HTTP', + all: 'Todos', + baidu: 'Baidu', + google: 'Google', + bing: 'Bing', + bytes: 'Bytedance', + sogou: 'Sogou', + failed: 'Error', + ipCount: 'Número de IPs', + spiderCount: 'Solicitudes de crawlers', + averageReqTime: 'Tiempo de respuesta medio', + totalFlow: 'Tráfico total', + logSize: 'Tamaño de logs', + realIPType: 'Método de obtención de IP real', + fromHeader: 'Desde cabecera HTTP', + fromHeaders: 'Desde lista de cabeceras', + header: 'Cabecera HTTP', + cdnConfig: 'Configuración CDN', + xff1: 'Proxy nivel 1 (X-Forwarded-For)', + xff2: 'Proxy nivel 2 (X-Forwarded-For)', + xff3: 'Proxy nivel 3 (X-Forwarded-For)', + xffHelper: 'Ejemplo: X-Forwarded-For: ,,,. El último proxy es ', + headersHelper: 'Obtiene la IP real de cabeceras comunes de CDN, tomando la primera disponible', + monitorCDNHelper: 'Modificar la config de CDN en monitorización también actualiza la del WAF', + wafCDNHelper: 'Modificar la config de CDN en el WAF también actualiza la monitorización', + statusErr: 'Formato de código de estado inválido', + shenma: 'Shenma', + duckduckgo: 'DuckDuckGo', + '360': '360 Search', + excludeUri: 'Excluir URIs', + top100Helper: 'Mostrar top 100', + logSaveDay: 'Retención de logs (días)', + cros: 'Chrome OS', + theworld: 'TheWorld Browser', + edge: 'Microsoft Edge', + maxthon: 'Maxthon Browser', + monitorStatusHelper: 'Monitorización no habilitada, actívala en Configuración', + excludeIp: 'Excluir IPs', + excludeUa: 'Excluir User-Agent', + remotePort: 'Puerto remoto', + unknown_browser: 'Navegador desconocido', + unknown_os: 'SO desconocido', + unknown_device: 'Dispositivo desconocido', + logSaveSize: 'Tamaño máximo de logs', + logSaveSizeHelper: 'Tamaño máximo de log por sitio', + '360se': '360 Secure Browser', + websites: 'Lista de sitios', + trend: 'Estadísticas de tendencias', + reqCount: 'Número de solicitudes', + uriHelper: 'Puedes usar /test/* o /*/index.php para excluir URIs', + }, + tamper: { + tamper: 'Protección contra manipulación web', + ignoreTemplate: 'Plantilla de Exclusión', + protectTemplate: 'Plantilla de Protección', + ignoreTemplateHelper: + 'Ingrese contenido de exclusión, separado por Enter o espacio. (Directorio específico ./log o nombre de directorio tmp, para excluir archivos necesita ingresar archivo específico ./data/test.html)', + protectTemplateHelper: + 'Ingrese contenido de protección, separado por Enter o espacio. (Archivo específico ./index.html, extensión de archivo .html, tipo de archivo js, para proteger directorios necesita ingresar directorio específico ./log)', + templateContent: 'Contenido de Plantilla', + template: 'Plantilla', + saveTemplate: 'Guardar como Plantilla', + tamperHelper1: + 'Para sitios web de implementación con un clic, se recomienda habilitar la protección contra manipulaciones del directorio de aplicaciones; si el sitio web no se puede usar normalmente o falla la copia de seguridad/restauración, desactive primero la protección contra manipulaciones;', + tamperHelper2: + 'Restringirá las operaciones de lectura/escritura, eliminación, permisos y modificación de propietario para archivos protegidos en directorios no excluidos', + tamperPath: 'Directorio de Protección', + tamperPathEdit: 'Modificar Ruta', + log: 'Registro de Bloqueo', + totalProtect: 'Protección Total', + todayProtect: 'Protección de Hoy', + templateRule: 'Longitud 1-512, el nombre no puede contener {0} y otros símbolos', + ignore: 'Excluir', + ignoreHelper: + 'Seleccione o ingrese contenido de exclusión, separado por Enter o espacio. (Directorio específico ./log o nombre de directorio tmp, para excluir archivos necesita ingresar o seleccionar archivo específico ./data/test.html)', + protect: 'Proteger', + protectHelper: + 'Seleccione o ingrese contenido de protección, separado por Enter o espacio. (Archivo específico ./index.html, extensión de archivo .html, tipo de archivo js, para proteger directorios necesita ingresar o seleccionar directorio específico ./log)', + tamperHelper00: 'La exclusión y protección solo admiten rutas relativas;', + tamperHelper01: + 'Después de habilitar la protección contra manipulaciones, el sistema restringirá las operaciones de creación, edición y eliminación de archivos protegidos en directorios no excluidos;', + tamperHelper02: + 'Prioridad: Protección de ruta específica > Exclusión de ruta específica > Protección > Exclusión', + tamperHelper03: + 'Las operaciones de monitoreo solo se dirigen a directorios no excluidos, monitoreando la creación de archivos no protegidos en estos directorios.', + disableHelper: + 'A punto de desactivar la protección contra manipulaciones para los siguientes sitios web, ¿continuar?', + appendOnly: 'Solo añadir', + appendOnlyHelper: + 'Restringe el borrado en este directorio, solo se permite añadir directorios excluidos o archivos no protegidos', + immutable: 'Inmutable', + immutableHelper: 'Restringe edición, borrado, permisos y cambio de propietario del archivo', + onWatch: 'Monitorización', + onWatchHelper: + 'Monitoriza y bloquea la creación de archivos protegidos o directorios no excluidos en este directorio', + forceStop: 'Forzar cierre', + forceStopHelper: 'Se forzará la desactivación de la función anti-manipulación en este sitio. ¿Continuar?', + }, + setting: { + setting: 'Configuración del panel', + title: 'Descripción del panel', + titleHelper: + 'Se mostrará en la página de login (ej.: Panel de administración de servidores Linux, recomendado 8-15 caracteres)', + logo: 'Logo (sin texto)', + logoHelper: + 'Se mostrará arriba a la izquierda cuando el menú esté colapsado (tamaño recomendado: 82px*82px)', + logoWithText: 'Logo (con texto)', + logoWithTextHelper: + 'Se mostrará arriba a la izquierda cuando el menú esté expandido (tamaño recomendado: 185px*55px)', + favicon: 'Icono del sitio', + faviconHelper: 'Icono del sitio (tamaño recomendado: 16px*16px)', + setDefault: 'Restaurar por defecto', + setHelper: 'Se guardará la configuración actual. ¿Continuar?', + setDefaultHelper: 'Todas las configuraciones se restaurarán a valores por defecto. ¿Continuar?', + logoGroup: 'Logo', + imageGroup: 'Imagen', + loginImage: 'Imagen de login', + loginImageHelper: 'Se muestra en la página de login (tamaño recomendado: 500*416px)', + loginBgType: 'Tipo de fondo de login', + loginBgImage: 'Imagen de fondo de login', + loginBgImageHelper: 'Se muestra como fondo de la página de login (tamaño recomendado: 1920*1080px)', + loginBgColor: 'Color de fondo de login', + loginBgColorHelper: 'Se muestra como color de fondo en la página de login', + image: 'Imagen', + bgColor: 'Color de fondo', + loginGroup: 'Página de login', + loginBtnLinkColor: 'Color de botones/enlaces', + loginBtnLinkColorHelper: 'Se aplica a botones y enlaces en la página de login', + }, + helper: { + wafTitle1: 'Mapa de Intercepciones', + wafContent1: 'Muestra la distribución geográfica de intercepciones en los últimos 30 días', + wafTitle2: 'Restricciones de Acceso Regional', + wafContent2: 'Restringe las fuentes de acceso al sitio web según la ubicación geográfica', + wafTitle3: 'Página de Intercepción Personalizada', + wafContent3: 'Crea una página personalizada que se muestre tras interceptar una petición', + wafTitle4: 'Reglas Personalizadas (ACL)', + wafContent4: 'Intercepta peticiones según reglas definidas por el usuario', + tamperTitle1: 'Monitoreo de Integridad de Archivos', + tamperContent1: + 'Supervisa la integridad de los archivos del sitio, incluyendo archivos core, scripts y de configuración.', + tamperTitle2: 'Escaneo y Detección en Tiempo Real', + tamperContent2: + 'Detecta archivos anómalos o manipulados escaneando en tiempo real el sistema de archivos del sitio.', + tamperTitle3: 'Configuración de Permisos de Seguridad', + tamperContent3: + 'Restringe el acceso a archivos del sitio mediante permisos adecuados y políticas de control de acceso, reduciendo la superficie de ataque.', + tamperTitle4: 'Registro y Análisis', + tamperContent4: + 'Registra accesos y operaciones sobre archivos para auditoría y análisis posteriores, ayudando a detectar amenazas de seguridad.', + settingTitle1: 'Mensaje de Bienvenida Personalizado', + settingContent1: 'Configura un mensaje de bienvenida en la página de login de 1Panel.', + settingTitle2: 'Logo Personalizado', + settingContent2: 'Permite subir logos con nombres de marca u otro texto.', + settingTitle3: 'Icono del Sitio Web', + settingContent3: + 'Permite subir un icono personalizado para reemplazar el favicon por defecto, mejorando la experiencia del usuario.', + monitorTitle1: 'Tendencia de Visitantes', + monitorContent1: 'Genera estadísticas y muestra la tendencia de visitantes del sitio web', + monitorTitle2: 'Mapa de Visitantes', + monitorContent2: 'Muestra la distribución geográfica de los visitantes del sitio', + monitorTitle3: 'Estadísticas de Acceso', + monitorContent3: + 'Genera estadísticas de solicitudes al sitio web, incluyendo crawlers, dispositivos de acceso, estados de respuesta, etc.', + monitorTitle4: 'Monitoreo en Tiempo Real', + monitorContent4: + 'Supervisa en tiempo real la información de peticiones al sitio web, incluyendo número de solicitudes y tráfico.', + alertTitle1: 'Alertas por SMS', + alertContent1: + 'Cuando se detecta uso anómalo de recursos del servidor, expiración de sitios o certificados, nueva versión disponible o expiración de contraseñas, los usuarios son notificados por SMS para garantizar respuesta oportuna.', + alertTitle2: 'Registro de Alertas', + alertContent2: + 'Permite a los usuarios consultar el historial de alertas para facilitar el seguimiento y análisis de eventos pasados.', + alertTitle3: 'Configuración de Alertas', + alertContent3: + 'Permite configurar números de teléfono, frecuencia y horarios de notificación para alertas más personalizadas.', + + nodeDashTitle1: 'Gestión de Aplicaciones', + nodeDashContent1: + 'Gestión unificada de aplicaciones multi-nodo, admite monitoreo de estado, inicio/parada rápida, conexión de terminal y copia de seguridad', + nodeDashTitle2: 'Gestión de Sitios Web', + nodeDashContent2: + 'Gestión unificada de sitios web multi-nodo, monitoreo de estado en tiempo real, admite inicio/parada por lotes y copia de seguridad rápida', + nodeDashTitle3: 'Gestión de Bases de Datos', + nodeDashContent3: + 'Gestión unificada de bases de datos multi-nodo, estado clave de un vistazo, admite copia de seguridad con un clic', + nodeDashTitle4: 'Gestión de Tareas Programadas', + nodeDashContent4: + 'Gestión unificada de tareas programadas multi-nodo, admite monitoreo de estado, inicio/parada rápida y ejecución manual por activación', + + nodeTitle1: 'Adición de Nodo en un Clic', + nodeContent1: 'Integra rápidamente múltiples nodos de servidor', + nodeTitle2: 'Actualización en Lote', + nodeContent2: 'Sincroniza y actualiza todos los nodos con una sola operación', + nodeTitle3: 'Monitoreo del Estado de Nodos', + nodeContent3: 'Supervisa en tiempo real el estado operativo de cada nodo', + nodeTitle4: 'Conexión Remota Rápida', + nodeContent4: 'Conecta directamente a la terminal remota de un nodo con un clic', + fileExchangeTitle1: 'Transmisión con Autenticación por Claves', + fileExchangeContent1: 'Autenticación vía claves SSH para garantizar seguridad en la transmisión.', + fileExchangeTitle2: 'Sincronización de Archivos Eficiente', + fileExchangeContent2: + 'Solo se sincroniza el contenido modificado, mejorando velocidad y estabilidad de transmisión.', + fileExchangeTitle3: 'Intercomunicación Multi-Nodo', + fileExchangeContent3: + 'Permite transferir fácilmente archivos entre diferentes nodos y gestionar múltiples servidores de forma flexible.', + + nodeAppTitle1: 'Gestión de Actualización de Aplicaciones', + nodeAppContent1: + 'Monitoreo unificado de actualizaciones de aplicaciones multi-nodo, admite actualización con un clic', + + appTitle1: 'Gestión Flexible del Panel', + appContent1: 'Gestiona tu servidor 1Panel en cualquier momento y lugar.', + appTitle2: 'Información Completa de Servicios', + appContent2: + 'Administra aplicaciones básicas, sitios web, Docker, bases de datos, etc., y crea nuevos servicios desde la app móvil.', + appTitle3: 'Monitoreo de Anomalías en Tiempo Real', + appContent3: + 'Consulta en la app móvil el estado del servidor, la seguridad WAF, estadísticas de tráfico web y la salud de procesos.', + clusterTitle1: 'Despliegue Maestro-Esclavo', + clusterContent1: + 'Soporta creación de instancias maestro-esclavo de MySQL/Postgres/Redis en distintos nodos, completando la asociación e inicialización automáticamente', + clusterTitle2: 'Gestión Maestro-Esclavo', + clusterContent2: + 'Página centralizada para gestionar múltiples nodos maestro-esclavo, ver sus roles y estado de ejecución.', + clusterTitle3: 'Estado de Replicación', + clusterContent3: + 'Muestra estado de replicación maestro-esclavo y retrasos, ayudando a diagnosticar problemas de sincronización', + }, + node: { + master: 'Nodo Principal', + masterBackup: 'Respaldo del Nodo Principal', + backupNode: 'Nodo de Respaldo', + backupFrequency: 'Frecuencia de Respaldo (horas)', + backupCopies: 'Copias de Retención de Respaldo', + noBackupNode: 'Actualmente no hay nodo de respaldo configurado. Selecciona uno y vuelve a intentarlo.', + masterBackupAlert: + 'No se ha configurado un respaldo del nodo principal. Para garantizar la seguridad de los datos, configura un nodo de respaldo lo antes posible y así facilitar el cambio manual en caso de fallo.', + node: 'Nodo', + addr: 'Dirección', + nodeUpgrade: 'Configuración de Actualización', + nodeUpgradeHelper: + 'Los nodos seleccionados comenzarán a actualizarse automáticamente después de que se complete la actualización del nodo maestro, sin necesidad de operación manual.', + nodeUnhealthy: 'Estado del nodo anómalo', + deletedNode: 'El nodo eliminado {0} no soporta operaciones de actualización.', + nodeUnhealthyHelper: + 'Se ha detectado un estado anómalo en el nodo. Revisa [Gestión de Nodos] e inténtalo de nuevo.', + nodeUnbind: 'Nodo no vinculado a licencia', + nodeUnbindHelper: + 'Se ha detectado que este nodo no está vinculado a una licencia. Vincúlalo desde [Ajustes del Panel - Licencia] e inténtalo de nuevo.', + memTotal: 'Memoria Total', + nodeManagement: 'Gestión Multi-Máquina', + nodeItem: 'Gestión de Nodos', + panelItem: 'Gestión de Paneles', + addNode: 'Añadir Nodo', + connInfo: 'Información de Conexión', + nodeInfo: 'Información del Nodo', + withProxy: 'Habilitar Acceso por Proxy', + withoutProxy: 'Deshabilitar Acceso por Proxy', + withProxyHelper: + 'Utilizará el proxy del sistema {0} mantenido en la configuración del panel para acceder a los nodos secundarios. ¿Continuar?', + withoutProxyHelper: + 'Dejará de usar el proxy del sistema mantenido en la configuración del panel para acceder a los nodos secundarios. ¿Continuar?', + syncInfo: 'Sincronización', + syncHelper: + 'Cuando cambian los datos en el nodo principal, se sincronizan en tiempo real con este nodo secundario.', + syncBackupAccount: 'Configuración de Cuentas de Respaldo', + syncWithMaster: + 'Tras actualizar a Pro, todos los datos se sincronizarán por defecto. Las políticas de sincronización se pueden ajustar manualmente en la gestión de nodos.', + syncProxy: 'Configuración del Proxy del Sistema', + syncProxyHelper: 'La sincronización del proxy del sistema requiere reiniciar Docker.', + syncProxyHelper1: 'Reiniciar Docker puede afectar a los contenedores actualmente en ejecución.', + syncProxyHelper2: 'Puedes reiniciar manualmente desde la página Contenedores → Configuración.', + syncProxyHelper3: + 'La sincronización del proxy del sistema requiere reiniciar Docker, lo que puede afectar a servicios en ejecución.', + syncProxyHelper4: + 'La sincronización del proxy del sistema requiere reinicio de Docker. Puedes reiniciar manualmente más tarde en Contenedores → Configuración.', + syncCustomApp: 'Sincronizar Repositorio de Apps Personalizadas', + syncAlertSetting: 'Configuración de Alertas del Sistema', + syncNodeInfo: 'Datos básicos del nodo,', + nodeSyncHelper: 'La sincronización de información de nodos incluye:', + nodeSyncHelper1: '1. Información de cuentas de respaldo públicas', + nodeSyncHelper2: '2. Información de conexión entre el nodo principal y los nodos secundarios', + nodeCheck: 'Comprobación de Disponibilidad', + checkSSH: 'Verificar conexión SSH del nodo', + checkUserPermission: 'Verificar permisos de usuario en el nodo', + isNotRoot: 'Se ha detectado que este nodo no soporta sudo sin contraseña y el usuario actual no es root', + checkLicense: 'Verificar estado de licencia del nodo', + checkService: 'Verificar servicios existentes en el nodo', + checkPort: 'Verificar accesibilidad del puerto del nodo', + panelExist: + 'Se ha detectado que este nodo ejecuta 1Panel V1. Actualiza a V2 usando el script de migración antes de añadirlo.', + coreExist: + 'Este nodo ya está configurado como nodo principal y no puede añadirse directamente como nodo secundario. Debe degradarse primero a nodo esclavo. Consulta la documentación.', + agentExist: + 'Se ha detectado que 1panel-agent ya está instalado en este nodo. Continuar conservará los datos y solo reemplazará el servicio de 1panel-agent.', + agentNotExist: + 'Se ha detectado que 1panel-agent no está instalado en este nodo, por lo que la información no puede editarse directamente. Elimínalo y vuelve a añadirlo.', + oldDataExist: + 'Se han detectado datos históricos de 1Panel V2 en este nodo. La siguiente información sobrescribirá la configuración actual:', + errLicense: 'La licencia vinculada a este nodo no es válida. Revisa e inténtalo de nuevo.', + errNodePort: + 'El puerto del nodo [ {0} ] no es accesible. Verifica si el firewall o el grupo de seguridad permiten este puerto.', + reinstallHelper: '¿Reinstalar el nodo {0}? ¿Quieres continuar?', + unhealthyCheck: 'Comprobación Anómala', + fixOperation: 'Operación de Corrección', + checkName: 'Elemento de Comprobación', + checkSSHConn: 'Comprobar disponibilidad de conexión SSH', + fixSSHConn: 'Edita manualmente el nodo para confirmar la información de conexión', + checkConnInfo: 'Comprobar información de conexión del agente', + checkStatus: 'Comprobar disponibilidad de servicios del nodo', + fixStatus: + 'Ejecuta "systemctl status 1panel-agent.service" para verificar si el servicio está en ejecución.', + checkAPI: 'Comprobar disponibilidad de la API del nodo', + fixAPI: 'Revisa los logs del nodo y verifica si los puertos del firewall están abiertos correctamente.', + forceDelete: 'Eliminación Forzada', + operateHelper: 'Los siguientes nodos se someterán a la operación {0}, ¿quieres continuar?', + forceDeleteHelper: 'La eliminación forzada ignorará errores y borrará metadatos de la base de datos.', + uninstall: 'Eliminar datos del nodo', + uninstallHelper: + 'Esto eliminará todos los datos relacionados con 1Panel de este nodo. Procede con precaución.', + baseDir: 'Directorio de Instalación', + baseDirHelper: 'Si el directorio de instalación está vacío, se instalará en /opt por defecto.', + nodePort: 'Puerto del Nodo', + offline: 'Modo Offline', + freeCount: 'Cuota gratuita [{0}]', + offlineHelper: 'Usado cuando el nodo está en un entorno sin conexión', + + appUpgrade: 'Actualización de la aplicación', + appUpgradeHelper: 'Hay {0} aplicaciones que necesitan ser actualizadas', + }, + customApp: { + name: 'Repositorio de Apps Personalizadas', + appStoreType: 'Fuente de Paquetes del App Store', + appStoreUrl: 'URL del Repositorio', + local: 'Ruta Local', + remote: 'Enlace Remoto', + imagePrefix: 'Prefijo de Imagen', + imagePrefixHelper: + 'Función: Personaliza el prefijo de la imagen y modifica el campo image en el archivo compose. Ejemplo: si el prefijo es 1panel/custom, la imagen de MaxKB pasará a ser 1panel/custom/maxkb:v1.10.0', + closeHelper: 'Cancelar el uso del repositorio de apps personalizadas', + appStoreUrlHelper: 'Solo se admite el formato .tar.gz', + postNode: 'Sincronizar al nodo secundario', + postNodeHelper: + 'Sincroniza el paquete del repositorio personalizado en tmp/customApp/apps.tar.gz dentro del directorio de instalación del nodo secundario', + nodes: 'Nodos', + selectNode: 'Seleccionar Nodo', + selectNodeError: 'Por favor, selecciona un nodo', + licenseHelper: 'La versión Pro admite la función de repositorio de aplicaciones personalizadas', + databaseHelper: 'Base de datos asociada a la aplicación, seleccione la base de datos del nodo destino', + nodeHelper: 'No se puede seleccionar el nodo actual', + migrateHelper: + 'Actualmente solo admite la migración de aplicaciones monolíticas y aplicaciones asociadas únicamente con bases de datos MySQL, MariaDB, PostgreSQL', + opensslHelper: + 'Si se utiliza copia de seguridad cifrada, las versiones de OpenSSL entre los dos nodos deben ser consistentes, de lo contrario la migración puede fallar.', + installApp: 'Instalación por lotes', + installAppHelper: 'Instalar aplicaciones por lotes en los nodos seleccionados', + }, + alert: { + isAlert: 'Alerta', + alertCount: 'Número de alertas', + clamHelper: 'Generar alerta al detectar archivos infectados en escaneo', + cronJobHelper: 'Generar alerta cuando falle la ejecución de una tarea programada', + licenseHelper: 'La versión Pro soporta alertas SMS', + alertCountHelper: 'Frecuencia máxima diaria de alertas', + alert: 'Alerta SMS', + logs: 'Logs de alertas', + list: 'Lista de alertas', + addTask: 'Crear alerta', + editTask: 'Editar alerta', + alertMethod: 'Método', + alertMsg: 'Mensaje de alerta', + alertRule: 'Reglas de alerta', + titleSearchHelper: 'Introduce el título de alerta para búsqueda difusa', + taskType: 'Tipo', + ssl: 'Expiración de certificado', + siteEndTime: 'Expiración de sitio web', + panelPwdEndTime: 'Expiración de contraseña del panel', + panelUpdate: 'Nueva versión de panel disponible', + cpu: 'Alerta de CPU', + memory: 'Alerta de memoria', + load: 'Alerta de carga', + disk: 'Alerta de disco', + website: 'Sitio web', + certificate: 'Certificado SSL', + remainingDays: 'Días restantes', + sendCount: 'Envíos', + sms: 'SMS', + wechat: 'WeChat', + dingTalk: 'DingTalk', + feiShu: 'FeiShu', + mail: 'Correo', + weCom: 'WeCom', + sendCountRulesHelper: 'Número total de alertas enviadas antes de expirar (una vez al día)', + panelUpdateRulesHelper: 'Alertas totales enviadas por nueva versión del panel (una vez al día)', + oneDaySendCountRulesHelper: 'Número máximo de alertas diarias', + siteEndTimeRulesHelper: 'Los sitios sin fecha de expiración no generan alertas', + autoRenewRulesHelper: + 'Los certificados con auto-renovación habilitada y menos de 31 días restantes no generan alerta', + panelPwdEndTimeRulesHelper: + 'La alerta de expiración de contraseña no aplica si no se ha configurado expiración', + sslRulesHelper: 'Todos los certificados SSL', + diskInfo: 'Disco', + monitoringType: 'Tipo de monitorización', + autoRenew: 'Auto-renovación', + useDisk: 'Uso de disco', + usePercentage: 'Porcentaje de uso', + changeStatus: 'Cambiar estado', + disableMsg: 'Detener la tarea de alerta evitará que envíe mensajes. ¿Continuar?', + enableMsg: 'Al habilitar la tarea podrá enviar mensajes de alerta. ¿Continuar?', + useExceed: 'Uso excedido', + useExceedRulesHelper: 'Genera alerta cuando el uso supera el valor configurado', + cpuUseExceedAvg: 'El uso medio de CPU supera el valor especificado', + memoryUseExceedAvg: 'El uso medio de memoria supera el valor especificado', + loadUseExceedAvg: 'La carga media supera el valor especificado', + cpuUseExceedAvgHelper: 'El uso medio de CPU en el periodo supera el valor configurado', + memoryUseExceedAvgHelper: 'El uso medio de memoria en el periodo supera el valor configurado', + loadUseExceedAvgHelper: 'La carga media en el periodo supera el valor configurado', + resourceAlertRulesHelper: 'Nota: múltiples alertas en 30 minutos se unifican en una', + specifiedTime: 'Tiempo especificado', + deleteTitle: 'Eliminar alerta', + deleteMsg: '¿Seguro que quieres eliminar esta tarea de alerta?', + allSslTitle: 'Alertas de expiración de certificados SSL en todos los sitios', + sslTitle: 'Alerta de expiración de certificado SSL en sitio {0}', + allSiteEndTimeTitle: 'Alertas de expiración de todos los sitios web', + siteEndTimeTitle: 'Alerta de expiración para sitio {0}', + panelPwdEndTimeTitle: 'Alerta de expiración de contraseña del panel', + panelUpdateTitle: 'Notificación de nueva versión de panel', + cpuTitle: 'Alerta de alta CPU', + memoryTitle: 'Alerta de alta memoria', + loadTitle: 'Alerta de alta carga', + diskTitle: 'Alerta de alto uso de disco en {0}', + allDiskTitle: 'Alerta de alto uso de disco', + timeRule: 'Tiempo restante menor a {0} días (si no se gestiona, se reenviará al día siguiente)', + panelUpdateRule: + 'Al detectar nueva versión del panel se envía una alerta (si no se gestiona, se reenviará al día siguiente)', + avgRule: 'Uso medio de {1} supera {2}% en {0} minutos, genera alerta, {3} envíos/día', + diskRule: 'Uso de disco en {0} supera {1}{2}, genera alerta, {3} envíos/día', + allDiskRule: 'Uso de disco supera {0}{1}, genera alerta, {2} envíos/día', + cpuName: 'CPU', + memoryName: 'Memoria', + loadName: 'Carga', + diskName: 'Disco', + syncAlertInfo: 'Push manual', + syncAlertInfoMsg: '¿Deseas enviar manualmente la tarea de alerta?', + pushError: 'Error en push', + pushSuccess: 'Push exitoso', + syncError: 'Error de sincronización', + success: 'Alerta enviada', + pushing: 'Enviando...', + error: 'Error de alerta', + cleanLog: 'Limpiar logs', + cleanAlertLogs: 'Limpiar logs de alertas', + daily: 'Alertas diarias: {0}', + cumulative: 'Alertas acumuladas: {0}', + clams: 'Alerta de escaneo antivirus', + taskName: 'Nombre de tarea', + cronJobType: 'Tipo de tarea', + clamPath: 'Directorio a escanear', + cronjob: 'Fallo en ejecución de cronjob {0}', + app: 'Backup de app', + web: 'Backup de sitio', + database: 'Backup de base de datos', + directory: 'Backup de directorio', + log: 'Backup de logs', + snapshot: 'Snapshot del sistema', + clamsRulesHelper: 'Tareas de escaneo antivirus que requieren alerta', + cronJobRulesHelper: 'Este tipo de tareas programadas necesita configuración', + clamsTitle: 'Tarea antivirus 「 {0} 」 detectó archivo infectado', + cronJobAppTitle: 'Cronjob - Backup de app 「 {0} 」 falló', + cronJobWebsiteTitle: 'Cronjob - Backup de sitio 「 {0} 」 falló', + cronJobDatabaseTitle: 'Cronjob - Backup de base de datos 「 {0} 」 falló', + cronJobDirectoryTitle: 'Cronjob - Backup de directorio 「 {0} 」 falló', + cronJobLogTitle: 'Cronjob - Backup de logs 「 {0} 」 falló', + cronJobSnapshotTitle: 'Cronjob - Backup de snapshot 「 {0} 」 falló', + cronJobShellTitle: 'Cronjob - Script shell 「 {0} 」 falló', + cronJobCurlTitle: 'Cronjob - Acceso URL 「 {0} 」 falló', + cronJobCutWebsiteLogTitle: 'Cronjob - Corte de logs web 「 {0} 」 falló', + cronJobCleanTitle: 'Cronjob - Limpieza de caché 「 {0} 」 falló', + cronJobNtpTitle: 'Cronjob - Sync hora de servidor 「 {0} 」 falló', + clamsRule: 'Escaneo antivirus detectó infección, {0} envíos/día', + cronJobAppRule: 'Fallo en backup de app, {0} envíos/día', + cronJobWebsiteRule: 'Fallo en backup de sitio, {0} envíos/día', + cronJobDatabaseRule: 'Fallo en backup de base de datos, {0} envíos/día', + cronJobDirectoryRule: 'Fallo en backup de directorio, {0} envíos/día', + cronJobLogRule: 'Fallo en backup de logs, {0} envíos/día', + cronJobSnapshotRule: 'Fallo en backup de snapshot, {0} envíos/día', + cronJobShellRule: 'Fallo en script shell, {0} envíos/día', + cronJobCurlRule: 'Fallo en acceso URL, {0} envíos/día', + cronJobCutWebsiteLogRule: 'Fallo en corte de logs web, {0} envíos/día', + cronJobCleanRule: 'Fallo en limpieza de caché, {0} envíos/día', + cronJobNtpRule: 'Fallo en sincronización de hora, {0} envíos/día', + alertSmsHelper: 'Límite SMS: {0} mensajes totales, {1} usados', + goBuy: 'Comprar más', + phone: 'Teléfono', + phoneHelper: 'Proporciona un número real para SMS de alertas', + dailyAlertNum: 'Límite diario de alertas', + dailyAlertNumHelper: 'Máximo de alertas por día (hasta 100)', + timeRange: 'Rango de tiempo', + sendTimeRange: 'Rango de envío', + sendTimeRangeHelper: 'Puede enviarse en {0}', + to: 'a', + startTime: 'Hora inicio', + endTime: 'Hora fin', + defaultPhone: 'Usar el número vinculado a la licencia', + noticeAlert: 'Alerta de notificación', + resourceAlert: 'Alerta de recursos', + agentOfflineAlertHelper: 'Con alerta offline habilitada, el nodo maestro escaneará cada 30 minutos.', + offline: 'Alerta offline', + offlineHelper: 'En modo offline, el nodo maestro escaneará cada 30 minutos para alertas.', + offlineOff: 'Habilitar alerta offline', + offlineOffHelper: 'Habilitar alerta offline hace que el nodo maestro ejecute alertas cada 30 minutos.', + offlineClose: 'Desactivar alerta offline', + offlineCloseHelper: 'Desactivarla obliga a que los sub-nodos gestionen alertas. Asegura conectividad.', + alertNotice: 'Notificación de alerta', + methodConfig: 'Config de métodos de notificación', + commonConfig: 'Config global', + smsConfig: 'SMS', + smsConfigHelper: 'Configura teléfonos para notificaciones SMS', + emailConfig: 'Correo', + emailConfigHelper: 'Configura servicio SMTP de envío de correos', + deleteConfigTitle: 'Eliminar configuración de alerta', + deleteConfigMsg: '¿Seguro que quieres eliminar esta configuración?', + test: 'Probar', + alertTestOk: 'Notificación de prueba exitosa', + alertTestFailed: 'Notificación de prueba fallida', + displayName: 'Nombre mostrado', + sender: 'Dirección remitente', + password: 'Contraseña', + host: 'Servidor SMTP', + port: 'Puerto', + encryption: 'Método de cifrado', + recipient: 'Destinatario', + licenseTime: 'Recordatorio de expiración de licencia', + licenseTimeTitle: 'Recordatorio de expiración de licencia', + displayNameHelper: 'Nombre remitente para correos', + senderHelper: 'Correo usado como remitente', + passwordHelper: 'Código de autorización del servicio de correo', + hostHelper: 'Servidor SMTP, ej. smtp.qq.com', + portHelper: 'SSL normalmente 465, TLS normalmente 587', + sslHelper: 'Si el puerto es 465, requiere SSL', + tlsHelper: 'Si el puerto es 587, requiere TLS', + triggerCondition: 'Condición de disparo', + loginFail: ' fallos de login en', + nodeException: 'Alerta de excepción de nodo', + licenseException: 'Alerta de excepción de licencia', + panelLogin: 'Alerta de login en panel', + sshLogin: 'Alerta de login SSH', + panelIpLogin: 'Alerta de login en panel por IP', + sshIpLogin: 'Alerta de login SSH por IP', + ipWhiteListHelper: 'Las IPs en whitelist no tienen restricciones ni generan alerta en login exitoso', + nodeExceptionRule: 'Alerta de nodo anómalo, {0} envíos/día', + licenseExceptionRule: 'Alerta de licencia anómala, {0} envíos/día', + panelLoginRule: 'Alerta de login en panel, {0} envíos/día', + sshLoginRule: 'Alerta de login SSH, {0} envíos/día', + userNameHelper: 'El nombre de usuario está vacío, se usará la dirección del remitente por defecto', + }, + theme: { + lingXiaGold: 'Ling Xia Gold', + classicBlue: 'Azul clásico', + freshGreen: 'Verde fresco', + customColor: 'Color personalizado', + setDefault: 'Por defecto', + setDefaultHelper: 'La paleta de colores volverá al estado inicial. ¿Continuar?', + setHelper: 'Se guardará la paleta actual como tema. ¿Continuar?', + }, + exchange: { + exchange: 'Intercambio de archivos', + exchangeConfirm: '¿Quieres transferir {1} de {0} a {2}:{3}?', + }, + cluster: { + cluster: 'Alta disponibilidad de aplicaciones', + name: 'Nombre del clúster', + addCluster: 'Añadir clúster', + installNode: 'Instalar nodo', + master: 'Nodo maestro', + slave: 'Nodo esclavo', + replicaStatus: 'Estado maestro-esclavo', + unhealthyDeleteError: 'El nodo tiene estado anómalo, revisa la lista e inténtalo de nuevo', + replicaStatusError: 'Error al obtener estado, revisa el nodo maestro', + masterHostError: 'La IP del nodo maestro no puede ser 127.0.0.1', + }, + }, +}; + +export default { + ...fit2cloudEsEsLocale, + ...message, +}; diff --git a/frontend/src/lang/modules/ja.ts b/frontend/src/lang/modules/ja.ts new file mode 100644 index 0000000..5fd38c1 --- /dev/null +++ b/frontend/src/lang/modules/ja.ts @@ -0,0 +1,4058 @@ +import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/ja'; + +const message = { + commons: { + true: '真実', + false: '間違い', + colon: ':', + example: '例えば、', + fit2cloud: 'FIT2CLOUD', + lingxia: 'Lingxia', + button: { + run: '実行', + create: '作成する', + add: '追加', + save: '保存', + set: '構成を編集します', + sync: '同期', + delete: '消去', + edit: '編集', + enable: '有効にする', + disable: '無効にします', + confirm: '確認する', + cancel: 'キャンセル', + reset: 'リセット', + restart: '再起動', + conn: '接続する', + disConn: '切断します', + clean: 'クリーン', + login: 'ログイン', + close: '近い', + off: '近い', + stop: '停止', + start: '始める', + view: 'ビュー', + watch: '時計', + handle: 'トリガー', + clone: 'クローン', + expand: '拡大する', + collapse: '崩壊', + log: 'ログ', + back: '戻る', + backup: 'バックアップ', + recover: '回復する', + retry: 'リトライ', + upload: 'アップロード', + download: 'ダウンロード', + init: '初期化', + verify: '確認する', + saveAndEnable: '保存して有効にします', + import: '輸入', + export: 'エクスポート', + power: '認可', + search: '検索', + refresh: 'リロード', + get: '得る', + upgrade: 'アップグレード', + update: '編集', + updateNow: '今すぐ更新', + ignore: '更新を無視する', + copy: 'コピー', + random: 'ランダム', + install: 'インストール', + uninstall: 'アンインストール', + fullscreen: 'フルスクリーン', + quitFullscreen: 'フルスクリーンを終了', + showAll: 'すべてを表示します', + hideSome: 'いくつかを隠します', + agree: '同意する', + notAgree: '同意しません', + preview: 'プレビュー', + open: '開ける', + notSave: '保存しないでください', + createNewFolder: '新しいフォルダーを作成します', + createNewFile: '新しいファイルを作成します', + helpDoc: '文書をヘルプします', + unbind: 'バインド', + cover: 'に覆いを', + skip: 'スキップ', + fix: '修正', + down: '停止', + up: '起動', + sure: '確認', + show: '表示する', + hide: '隠す', + visit: '訪問', + migrate: '移行', + }, + operate: { + start: '開始', + stop: '停止', + restart: '再起動', + reload: '再読み込み', + rebuild: '再構築', + sync: '同期', + up: '起動', + down: '停止', + delete: '削除', + }, + search: { + timeStart: '時間開始', + timeEnd: 'タイムエンド', + timeRange: 'に', + dateStart: '日付開始', + dateEnd: '日付の終わり', + date: '日付', + }, + table: { + all: '全て', + total: '合計{0}', + name: '名前', + type: 'タイプ', + status: '状態', + records: '記録', + group: 'グループ', + createdAt: '作成時間', + publishedAt: '公開時間', + date: '日付', + updatedAt: '時間を更新します', + operate: '操作', + message: 'メッセージ', + description: '説明', + interval: '間隔', + user: '所有者', + title: 'タイトル', + port: 'ポート', + forward: 'フォワード', + protocol: 'プロトコル', + tableSetting: 'テーブル設定', + refreshRate: 'リフレッシュレート', + selectColumn: '列を選択します', + local: 'ローカル', + serialNumber: 'シリアル番号', + manageGroup: 'グループ管理', + backToList: 'リストに戻る', + keepEdit: '編集を続ける', + }, + loadingText: { + Upgrading: 'システムのアップグレード、待ってください...', + Restarting: 'システムの再起動、待ってください...', + Recovering: 'スナップショットから回復して、待ってください...', + Rollbacking: 'スナップショットからのロールバック、お待ちください...', + }, + msg: { + noneData: '利用可能なデータはありません', + delete: `この操作削除は元に戻すことはできません。続けたいですか?`, + clean: `この操作は取り消すことはできません。続けたいですか?`, + closeDrawerHelper: 'システムは変更を保存しない可能性があります。続行しますか?', + deleteSuccess: '正常に削除されました', + loginSuccess: '正常にログインしました', + operationSuccess: '正常に完了', + copySuccess: '正常にコピーされました', + notSupportOperation: `この操作はサポートされていません`, + requestTimeout: 'リクエストがタイムアウトしました。後でもう一度やり直してください', + infoTitle: 'ヒント', + notRecords: '現在のタスクの実行レコードは生成されません', + sureLogOut: 'ログアウトしたいですか?', + createSuccess: '正常に作成されました', + updateSuccess: '正常に更新されました', + uploadSuccess: '正常にアップロードされました', + operateConfirm: '操作について確信している場合は、手動で入力してください。', + inputOrSelect: '選択または入力してください', + copyFailed: 'コピーに失敗しました', + operatorHelper: `操作「{1}」は「{0}」で実行され、元に戻すことはできません。続けたいですか?`, + notFound: '申し訳ありませんが、要求したページは存在しません。', + unSupportType: `現在のファイルタイプはサポートされていません。`, + unSupportSize: 'アップロードされたファイルは{0} mを超えています、確認してください!', + fileExist: `ファイルはすでに現在のフォルダーに存在しています。リピートアップロードはサポートされていません。`, + fileNameErr: + '名前には、英語、中国語、数字、または期間を含む1〜256文字が含まれるファイルのみをアップロードできます(。-_)', + confirmNoNull: `値{0}が空でないことを確認してください。`, + errPort: 'ポート情報が正しくありません、確認してください!', + remove: '取り除く', + backupHelper: '現在の操作は{0}をバックアップします。先に進みたいですか?', + recoverHelper: '{0}ファイルから復元。この操作は不可逆的です。続けたいですか?', + refreshSuccess: 'リフレッシュして成功します', + rootInfoErr: 'すでにルートディレクトリです', + resetSuccess: 'リセット成功', + creatingInfo: '作成、この操作は必要ありません', + offlineTips: 'オフライン版はこの操作をサポートしていません', + errImportFormat: 'インポートデータまたはフォーマットが異常です。確認して再試行してください!', + importHelper: + '競合または重複するデータをインポートする場合、インポートされた内容を基準として元のデータベースデータを更新します。', + errImport: 'ファイル内容が異常です:', + }, + login: { + username: 'ユーザー名', + password: 'パスワード', + passkey: 'パスキーでログイン', + welcome: 'ようこそ、ユーザー名とパスワードを入力してログインしてください!', + errorAuthInfo: '入力したユーザー名またはパスワードは間違っています。再入力してください!', + errorMfaInfo: '認証情報が誤っていない、もう一度やり直してください!', + captchaHelper: 'キャプチャ', + errorCaptcha: 'Captchaコードエラー!', + notSafe: 'アクセスが拒否されました', + safeEntrance1: '安全なログインは現在の環境で有効になっています', + safeEntrance2: 'SSH端末に次のコマンドを入力して、パネルエントリを表示します:1PCTLユーザー-INFO', + errIP1: '現在の環境で認定されたIPアドレスアクセスが有効になっています', + errDomain1: 'アクセスドメイン名のバインディングが現在の環境で有効になっています', + errHelper: 'バインディング情報をリセットするには、SSH端末で次のコマンドを実行します。', + codeInput: 'MFAバリデーターの6桁の検証コードを入力してください', + mfaTitle: 'MFA認定', + mfaCode: 'MFA検証コード', + title: 'Linuxサーバー管理パネル', + licenseHelper: '<コミュニティライセンス契約>', + errorAgree: 'クリックして、コミュニティソフトウェアライセンスに同意します', + logout: 'ログアウト', + agreeTitle: '合意', + agreeContent: + 'あなたの正当な権利と利益をよりよく保護するために、次の契約とラコを読んで同意してください。コミュニティライセンス契約&raquo;', + passkeyFailed: 'パスキーでのログインに失敗しました。再試行してください', + passkeyNotSupported: '現在のブラウザまたは環境はパスキーに対応していません', + passkeyToPassword: 'パスキーが使えませんか?パスワードでログインしてください', + }, + rule: { + username: 'ユーザー名を入力します', + password: 'パスワードを入力します', + rePassword: 'パスワードがパスワードと矛盾することを確認してください。', + requiredInput: 'この項目は必須です。', + requiredSelect: 'リスト内のアイテムを選択します', + illegalChar: '現在、文字 & ; $ \' ` ( ) " > < | の注入はサポートされていません', + illegalInput: `このフィールドには違法なキャラクターが含まれてはなりません。`, + commonName: + 'このフィールドは、特別なキャラクターではなく、英語、中国語、数字で構成されている必要があります。「。」、「」、および「_」文字が1〜128の文字で構成されている必要があります。', + userName: '特殊文字で始まらない、英字、漢字、数字、および_をサポート、長さ3-30', + simpleName: `このフィールドは、アンダースコアキャラクターから始めてはなりません。長さ3〜30の英語、数字、「_」文字で構成されている必要があります。`, + simplePassword: `このフィールドは、アンダースコアキャラクターから始めてはなりません。長さ1〜30の英語、数字、「_」文字で構成されている必要があります。`, + dbName: `このフィールドは、アンダースコアキャラクターから始めてはなりません。長さ1〜64の英語、数字、「_」文字で構成されている必要があります。`, + imageName: '特殊文字で始まらない、英字、数字、:@/.-_をサポート、長さ1-256', + composeName: '最初の特別な文字、小文字、数字、_、長さ1-256をサポートします', + volumeName: + 'このフィールドは、英語、数字、「。」、「 - 」、および「_」文字で構成されている必要があります。', + supervisorName: + 'このフィールドは、特別な文字以外の文字から開始する必要があり、英語、数字、「 - 」、および「_」文字が1〜128の文字で構成されている必要があります。', + complexityPassword: + 'このフィールドは、英語で構成され、長さは8〜30で、少なくとも2つの特殊文字が含まれている必要があります。', + commonPassword: 'このフィールドの長さは6を超える必要があります。', + linuxName: `このフィールドの長さは1〜128でなければなりません。フィールドには、これらの特殊文字を含めてはなりません。「{0}」。`, + email: 'このフィールドは有効な電子メールアドレスでなければなりません。', + number: 'このフィールドは数字でなければなりません。', + integer: 'このフィールドは正の整数でなければなりません。', + ip: 'このフィールドは有効なIPアドレスでなければなりません。', + host: 'このフィールドは、有効なIPアドレスまたはドメイン名でなければなりません。', + hostHelper: 'IPアドレスまたはドメイン名の入力をサポートします', + port: 'このフィールドは有効なポート番号でなければなりません。', + selectHelper: '正しい{0}ファイルを選択してください', + domain: 'このフィールドは、example.comまたはexample.com:8080のようなものでなければなりません。', + databaseName: 'このフィールドは、長さ1〜30の英語、数字、「_」文字で構成されている必要があります。', + ipErr: 'このフィールドは有効なIPアドレスでなければなりません。', + numberRange: 'このフィールドは、{0}と{1}の間の数字でなければなりません。', + paramName: 'このフィールドは、英語、数字、「。」、「 - 」、および「_」文字で構成されている必要があります。', + paramComplexity: `このフィールドは、特殊文字で開始および終了する必要はなく、英語、数字「{0}」文字で構成されている必要があります。`, + paramUrlAndPort: + 'このフィールドは、「http(s)://(domain name/ip):(ポート)」の形式でなければなりません。', + nginxDoc: 'このフィールドは、英語、数字、「」で構成されている必要があります。文字。', + appName: `このフィールドは、「 - 」と「_」文字で開始および終了してはなりません。英語、数字、 "、および「_」文字で2〜30の文字で構成されている必要があります。`, + containerName: '文字、数字、 - 、_および。;- _または。で始めることはできません。長さ:2-128', + mirror: 'ミラーアクセラレーションアドレスは、http(s)://、英語の文字(大文字と小文字の両方)、数字をサポートする必要があります。/および - 、そして空白の行を含めてはなりません。', + disableFunction: 'サポートレター、アンダースコア、および', + leechExts: 'サポートレター、数字、および', + paramSimple: '小文字と数字をサポート、長さ1〜128', + filePermission: 'ファイル許可エラー', + formatErr: 'フォーマットエラー、チェックして再試行してください', + phpExtension: '_小文字の英語と数字のみをサポートします', + paramHttp: 'http://またはhttps://で始める必要があります', + phone: '電話番号の形式は正しくありません', + authBasicPassword: '英字、数字、一般的な特殊文字をサポート、長さ1-72', + length128Err: '長さは128文字を超えることはできません', + maxLength: '長さは {0} 文字を超えることはできません', + alias: '英字、数字、-と_をサポート、長さ1-128、-_で始まるまたは終わることはできません。', + }, + res: { + paramError: 'リクエストが失敗しました。後でもう一度やり直してください!', + forbidden: '現在のユーザーには許可がありません', + serverError: 'サービスの例外', + notFound: 'リソースは存在しません', + commonError: 'リクエストに失敗しました', + }, + service: { + serviceNotStarted: `{0}サービスは開始されません。`, + }, + status: { + running: 'ランニング', + done: '終わり', + scanFailed: '不完全', + success: '成功', + waiting: '待っている', + waitForUpgrade: 'アップグレード待機中', + waiting1: '待っている', + failed: '失敗した', + stopped: '停止', + error: 'エラー', + created: '作成されました', + restarting: '再起動', + uploading: 'アップロード', + unhealthy: '不健康', + removing: '削除', + paused: '一時停止', + exited: '終了', + dead: '死んだ', + installing: 'インストール', + enabled: '有効になっています', + disabled: '無効', + normal: '普通', + building: '建物', + upgrading: 'アップグレード', + pending: '編集待ち', + rebuilding: '再構築', + deny: '拒否されました', + accept: '受け入れられました', + used: '使用済み', + unUsed: '未使用', + starting: '起動', + recreating: '再作成', + creating: '作成', + init: 'アプリケーションを待っています', + ready: '普通', + applying: '適用', + uninstalling: 'アンインストール中', + lost: '接続失効', + bound: 'バインド済み', + unbind: '未バインド', + exceptional: '異常', + free: '空き', + enable: '有効', + disable: '無効', + deleted: '削除済み', + downloading: 'ダウンロード中', + packing: 'パッキング中', + sending: '送信中', + healthy: '正常', + executing: '実行中', + installerr: 'インストールに失敗しました', + applyerror: '適用に失敗しました', + systemrestart: '中断', + starterr: '起動に失敗しました', + uperr: '起動に失敗しました', + new: '新規', + conflict: '競合', + duplicate: '重複', + unexecuted: '未実行', + }, + units: { + second: '2番目|2番目|秒', + minute: '分|分|分', + hour: '時間|時間|時間', + day: '日|日|日', + week: '週|週|週', + month: '月|月|数ヶ月', + year: '年|年|年', + time: 'RPM', + core: 'コア|コア|コア', + secondUnit: 's', + minuteUnit: 'min', + hourUnit: 'h', + dayUnit: 'd', + millisecond: 'ミリ秒', + }, + log: { + noLog: 'ログはありません', + }, + }, + menu: { + home: '概要', + apps: 'アプリストア', + website: 'ウェブサイト|ウェブサイト', + project: 'プロジェクト|プロジェクト', + config: '構成|構成', + ssh: 'SSH設定', + firewall: 'ファイアウォール', + ssl: '証明書|証明書', + database: 'データベース|データベース', + aiTools: 'AI', + mcp: 'MCP', + container: 'コンテナ|コンテナ', + cronjob: 'クロンジョブ|クロンの仕事', + system: 'システム', + security: '安全', + files: 'ファイル', + monitor: '監視', + terminal: '端子', + settings: '設定|設定', + toolbox: 'ツールボックス', + logs: 'ログ |ログ', + runtime: 'ランタイム|ランタイム', + processManage: 'プロセス|プロセス', + process: 'プロセス|プロセス', + network: 'ネットワーク|ネットワーク', + supervisor: '監督者', + tamper: '改ざん防止', + app: 'アプリケーション', + msgCenter: 'タスクセンター', + disk: 'ディスク', + }, + home: { + recommend: 'おすすめ', + dir: 'ディレクトリ', + alias: 'エイリアス', + quickDir: 'クイックディレクトリ', + minQuickJump: '少なくとも1つのクイックジャンプエントリを設定してください!', + maxQuickJump: '最大4つのクイックジャンプエントリを設定できます!', + database: 'データベース - すべて', + restart_1panel: 'パネルを再起動します', + restart_system: 'サーバーを再起動します', + operationSuccess: '操作が成功し、再起動します。後で手動でブラウザを更新してください!', + entranceHelper: `セキュリティの入り口は有効になりません。「設定 - >セキュリティ」でそれを有効にして、システムセキュリティを改善できます。`, + appInstalled: 'アプリケーション', + systemInfo: 'システム情報', + hostname: 'ホスト名', + platformVersion: 'オペレーティング·システム', + kernelVersion: 'カーネル', + kernelArch: '建築', + network: 'ネットワーク', + io: 'ディスクI/O', + ip: 'ローカルIP', + proxy: 'システムプロキシ', + baseInfo: '基本情報', + totalSend: '合計送信', + totalRecv: '総受領', + rwPerSecond: 'I/O操作', + ioDelay: 'I/Oレイテンシ', + uptime: 'それ以来', + runningTime: '稼働時間', + mem: 'システムメモリ', + swapMem: 'パーティションを交換します', + + runSmoothly: '低負荷', + runNormal: '中程度の負荷', + runSlowly: '高負荷', + runJam: '重い負荷', + + core: '物理コア', + logicCore: '論理コア', + corePercent: 'コア使用率', + cpuFrequency: 'CPU 周波数', + cpuDetailedPercent: 'CPU 占有', + cpuUser: 'ユーザー', + cpuSystem: 'システム', + cpuIdle: 'アイドル', + cpuIrq: 'IRQ', + cpuSoftirq: 'Soft IRQ', + cpuSteal: 'Steal', + cpuTop: 'CPU使用率トップ5のプロセス情報', + memTop: 'メモリ使用率トップ5のプロセス情報', + loadAverage: '最後の1分で平均を積み込みます|最後の{n}分で平均を読み込みます', + load: '負荷', + mount: 'マウントポイント', + fileSystem: 'ファイルシステム', + total: '合計', + used: '使用済み', + cache: 'キャッシュ', + free: '空き', + shard: 'シャーディング', + available: '利用可能', + percent: '利用', + goInstall: 'インストールします', + + networkCard: 'ネットワークカード', + disk: 'ディスク', + }, + tabs: { + more: 'もっと', + hide: '隠れる', + closeLeft: '左を閉じます', + closeRight: '右に閉じます', + closeCurrent: '電流を閉じます', + closeOther: '他を閉じます', + closeAll: 'すべてを閉じます', + }, + header: { + logout: 'ログアウト', + }, + database: { + manage: '管理', + deleteBackupHelper: 'データベースのバックアップを同時に削除します', + delete: '削除操作はロールバックできません、入力してください」', + deleteHelper: '「このデータベースを削除します', + create: 'データベースを作成します', + noMysql: 'データベースサービス(mysqlまたはmariadb)', + noPostgresql: 'データベースサービスpostgreSql', + goUpgrade: 'アップグレードに移動します', + goInstall: 'インストールに移動します', + isDelete: '削除されました', + permission: '権限', + format: '文字セット', + collation: '照合順序', + collationHelper: '空の場合は {0} 文字セットのデフォルトの照合順序を使用します', + permissionForIP: 'ip', + permissionAll: 'それらすべて(%)', + localhostHelper: + 'コンテナ展開でデータベース権限を"localhost"に設定すると、コンテナ外部からのアクセスができなくなります。慎重に選択してください!', + databaseConnInfo: '接続情報', + rootPassword: 'ルートパスワード', + serviceName: 'サービス名', + serviceNameHelper: '同じネットワーク内のコンテナ間のアクセス。', + backupList: 'バックアップ', + loadBackup: '輸入', + localUpload: 'ローカルアップロード', + hostSelect: 'サーバー選択', + selectHelper: 'バックアップファイル {0} をインポートしてもよろしいですか?', + remoteAccess: 'リモートアクセス', + remoteHelper: '複数のIP Comma delimited、例:172.16.10.111、172.16.10.112', + remoteConnHelper: + 'ユーザールートとしてのMySQLへのリモート接続には、セキュリティリスクがある場合があります。したがって、この操作を慎重に実行します。', + changePassword: 'パスワード', + changeConnHelper: 'この操作は現在のデータベース {0} を変更します。続行しますか?', + changePasswordHelper: + 'データベースはアプリケーションに関連付けられています。パスワードを変更すると、アプリケーションのデータベースパスワードが同時に変更されます。アプリケーションが再起動した後、変更は有効になります。', + recoverTimeoutHelper: '-1 はタイムアウト制限なしを意味します', + + confChange: '構成', + confNotFound: + '設定ファイルが見つかりませんでした。アプリストアでアプリケーションを最新バージョンにアップグレードして、再度お試しください!', + + portHelper: + 'このポートは、コンテナの露出したポートです。変更を個別に保存して、コンテナを再起動する必要があります!', + + loadFromRemote: '同期', + userBind: 'バインドユーザー', + pgBindHelper: `この操作は、新しいユーザーを作成し、ターゲットデータベースにバインドするために使用されます。現在、データベースに既存のユーザーを選択することはサポートされていません。`, + pgSuperUser: 'スーパーユーザー', + loadFromRemoteHelper: 'これにより、サーバー上のデータベース情報が1パネルに同期します。続けたいですか?', + passwordHelper: '取得できません、クリックして修正', + remote: 'リモート', + remoteDB: 'リモートサーバー|リモートサーバー', + createRemoteDB: 'リモートサーバーを追加', + unBindRemoteDB: 'リモートサーバーのバインドを解除', + unBindForce: '強制バインド', + unBindForceHelper: '結合プロセス中にすべてのエラーを無視して、最終操作が成功するようにします', + unBindRemoteHelper: + 'リモートデータベースのバインディングを解除すると、バインディング関係が削除されるだけで、リモートデータベースは直接削除されません。', + editRemoteDB: 'リモートサーバーを編集します', + localDB: 'ローカルデータベース', + address: 'データベースアドレス', + version: 'データベースバージョン', + userHelper: + 'ルートユーザーまたはルート特権を持つデータベースユーザーは、リモートデータベースにアクセスできます。', + pgUserHelper: 'スーパーユーザーの特権を持つデータベースユーザー。', + ssl: 'SSLを使用します', + clientKey: 'クライアントの秘密鍵', + clientCert: 'クライアント証明書', + caCert: '証明書として', + hasCA: 'CA証明書があります', + skipVerify: '証明書の有効性チェックを無視します', + initialDB: '初期データベース', + + formatHelper: '現在のデータベース文字セットは{0}です。文字セットの矛盾は回復の故障を引き起こす可能性があります', + dropHelper: 'ここでアップロードされたファイルをドラッグアンドドロップするか、', + clickHelper: 'クリックしてアップロードします', + supportUpType: + 'sql、sql.gz、tar.gz、.zip ファイル形式のみサポートしています。インポートする圧縮ファイルには、1つの.sqlファイルのみ、またはtest.sqlが含まれている必要があります', + + currentStatus: '現在の状態', + baseParam: '基本パラメーター', + performanceParam: 'パフォーマンスパラメーター', + runTime: '起動時間', + connections: '合計接続', + bytesSent: 'バイトを送信します', + bytesReceived: '受信バイト', + queryPerSecond: 'クエリあたりのクエリ', + txPerSecond: '1秒あたりのTX', + connInfo: 'アクティブ/ピーク接続', + connInfoHelper: '値が大きすぎる場合は、「max_connections」を増やします。', + threadCacheHit: 'スレッドキャッシュがヒットします', + threadCacheHitHelper: '低すぎる場合は、「thread_cache_size」を増やします。', + indexHit: 'インデックスヒット', + indexHitHelper: '低すぎる場合は、「key_buffer_size」を増やします。', + innodbIndexHit: 'INNODBインデックスヒット率', + innodbIndexHitHelper: '低すぎる場合は、「innodb_buffer_pool_size」を増やします。', + cacheHit: 'キャッシュヒットのクエリ', + cacheHitHelper: '低すぎる場合は、「query_cache_size」を増やします。', + tmpTableToDB: 'ディスクへの一時テーブル', + tmpTableToDBHelper: '大きすぎる場合は、「tmp_table_size」を増やしてみてください。', + openTables: 'テーブルを開きます', + openTablesHelper: '「table_open_cache」の構成値は、この値以上に等しくなければなりません。', + selectFullJoin: '完全な結合を選択します', + selectFullJoinHelper: `値が0でない場合は、データテーブルのインデックスが正しいかどうかを確認します。`, + selectRangeCheck: 'インデックスなしの結合の数', + selectRangeCheckHelper: `値が0でない場合は、データテーブルのインデックスが正しいかどうかを確認します。`, + sortMergePasses: 'ソートされたマージの数', + sortMergePassesHelper: '値が大きすぎる場合は、「sort_buffer_size」を増やします。', + tableLocksWaited: 'テーブル番号をロックします', + tableLocksWaitedHelper: + '値が大きすぎる場合は、データベースのパフォーマンスを向上させることを検討してください。', + + performanceTuning: 'パフォーマンスチューニング', + optimizationScheme: '最適化スキーム', + keyBufferSizeHelper: 'インデックスのバッファサイズ', + queryCacheSizeHelper: 'クエリキャッシュ。この関数が無効になっている場合は、このパラメーターを0に設定します。', + tmpTableSizeHelper: '一時的なテーブルキャッシュサイズ', + innodbBufferPoolSizeHelper: 'INNODBバッファサイズ', + innodbLogBufferSizeHelper: 'innodbログバッファサイズ', + sortBufferSizeHelper: '*接続、スレッドソートごとのバッファサイズ', + readBufferSizeHelper: '*接続、バッファサイズの読み取り', + readRndBufferSizeHelper: '*接続、ランダム読み取りバッファサイズ', + joinBufferSizeHelper: '*接続、アソシエーションテーブルキャッシュサイズ', + threadStackelper: '*接続、スレッドあたりのスタックサイズ', + binlogCacheSizeHelper: '* onnections、バイナリログキャッシュサイズ(4096の倍数)', + threadCacheSizeHelper: 'スレッドプールサイズ', + tableOpenCacheHelper: 'テーブルキャッシュ', + maxConnectionsHelper: 'マックス接続', + restart: '再起動', + + slowLog: '遅いログ', + noData: 'まだ遅いログはありません。', + + isOn: 'の上', + longQueryTime: 'しきい値', + thresholdRangeHelper: '正しいしきい値(1-600)を入力してください。', + + timeout: 'タイムアウト(s)', + timeoutHelper: 'アイドル接続タイムアウト期間。0は、接続が継続的にオンになっていることを示します。', + maxclients: 'マックスクライアント', + requirepassHelper: + 'この空白のままにして、パスワードが設定されていないことを示します。変更を個別に保存し、コンテナを再起動する必要があります!', + databases: 'データベースの数', + maxmemory: '最大メモリ使用量', + maxmemoryHelper: '0は制限がないことを示します。', + tcpPort: '現在のリスニングポート。', + uptimeInDays: '稼働している日。', + connectedClients: '接続されたクライアントの数。', + usedMemory: 'Redisの現在のメモリ使用。', + usedMemoryRss: 'オペレーティングシステムから要求されたメモリサイズ。', + usedMemoryPeak: 'Redisのピークメモリ消費。', + memFragmentationRatio: 'メモリフラグメンテーション比。', + totalConnectionsReceived: '実行以来、接続されているクライアントの総数。', + totalCommandsProcessed: '実行以降に実行されたコマンドの総数。', + instantaneousOpsPerSec: 'サーバーによって実行されるコマンドの数。', + keyspaceHits: 'データベースキーの回数が正常に見つかりました。', + keyspaceMisses: 'データベースキーを見つけようとする試みの失敗の数。', + hit: 'データベースキーヒット率を見つけます。', + latestForkUsec: '最後のfork()操作に費やされたマイクロ秒数。', + redisCliHelper: `「Redis-Cli」サービスは検出されません。最初にサービスを有効にします。`, + redisQuickCmd: 'Redis Quickコマンド', + recoverHelper: 'これにより、[{0}]でデータが上書きされます。続けたいですか?', + submitIt: 'データを上書きします', + + baseConf: '基本', + allConf: '全て', + restartNow: '今すぐ再起動します', + restartNowHelper1: + '構成の変更が有効になった後、システムを再起動する必要があります。データを持続する必要がある場合は、最初に保存操作を実行します。', + restartNowHelper: 'これは、システムが再起動した後にのみ有効になります。', + + persistence: '持続性', + rdbHelper1: '2番目、挿入', + rdbHelper2: 'データの部分', + rdbHelper3: '条件のいずれかを満たすと、RDBの持続性がトリガーされます。', + rdbInfo: 'ルールリストの値が1〜100000の範囲であることを確認してください', + + containerConn: 'コンテナ接続', + connAddress: '住所', + containerConnHelper: + 'この接続アドレスは、Webサイトのランタイム(PHPなど)またはコンテナで実行されているアプリケーションで使用できます。', + remoteConn: '外部接続', + remoteConnHelper2: 'コンテナ環境以外または外部接続にはこのアドレスを使用してください。', + remoteConnHelper3: + 'デフォルトアクセスアドレスはホストIPです。変更するには、パネル設定ページの「デフォルトアクセスアドレス」設定項目へ移動してください。', + localIP: 'ローカルIP', + }, + aiTools: { + model: { + model: 'モデル', + create: 'モデルを追加', + create_helper: 'を取得 "{0}"', + ollama_doc: 'Ollama の公式ウェブサイトを訪れて、さらに多くのモデルを検索して見つけることができます。', + container_conn_helper: 'コンテナ間のアクセスまたは接続にこのアドレスを使用', + ollama_sync: 'Ollamaモデルの同期中に、以下のモデルが存在しないことが判明しました。削除しますか?', + from_remote: 'このモデルは1Panelを介してダウンロードされておらず、関連するプルログはありません。', + no_logs: 'このモデルのプルログは削除されており、関連するログを表示できません。', + }, + proxy: { + proxy: 'AI プロキシ強化', + proxyHelper1: 'ドメインをバインドし、HTTPS を有効にして通信のセキュリティを強化', + proxyHelper2: 'IP アクセスを制限し、パブリックインターネットでの露出を防止', + proxyHelper3: 'ストリーミングを有効にする', + proxyHelper4: '作成後、ウェブサイトリストで確認および管理できます', + proxyHelper5: + '有効にすると、アプリストア - インストール済み - Ollama - パラメータでポートの外部アクセスを無効にし、セキュリティを向上させることができます。', + proxyHelper6: 'プロキシ設定を無効にするには、ウェブサイトリストから削除できます。', + whiteListHelper: 'ホワイトリスト内のIPのみアクセスを許可する', + }, + gpu: { + gpu: 'GPU 監視', + gpuHelper: + 'システムが NVIDIA-SMI または XPU-SMI コマンドを検出しませんでした。確認して再試行してください!', + process: 'プロセス情報', + type: 'タイプ', + typeG: 'グラフィックス', + typeC: 'コンピュート', + typeCG: 'コンピュート+グラフィックス', + processName: 'プロセス名', + shr: '共有メモリ', + temperatureHelper: 'GPU 温度が高いと GPU 周波数が低下する可能性があります', + gpuUtil: 'GPU 使用率', + temperature: '温度', + performanceState: 'パフォーマンス状態', + powerUsage: '消費電力', + memoryUsage: 'メモリ使用率', + fanSpeed: 'ファン速度', + power: '電力', + powerCurrent: '現在の電力', + powerLimit: '電力上限', + memory: 'メモリ', + memoryUsed: '使用メモリ', + memoryTotal: '総メモリ', + percent: '使用率', + + base: '基本情報', + driverVersion: 'ドライバーバージョン', + cudaVersion: 'CUDA バージョン', + processMemoryUsage: 'メモリ使用量', + performanceStateHelper: 'P0(最大パフォーマンス)から P12(最小パフォーマンス)まで', + busID: 'バスアドレス', + persistenceMode: '永続モード', + enabled: '有効', + disabled: '無効', + persistenceModeHelper: '永続モードはタスクへの応答がより迅速ですが、それに応じて待機電力消費も増加します', + displayActive: 'GPU 初期化', + displayActiveT: 'はい', + displayActiveF: 'いいえ', + ecc: 'エラー修正技術', + computeMode: '計算モード', + default: 'デフォルト', + exclusiveProcess: '排他プロセス', + exclusiveThread: '排他スレッド', + prohibited: '禁止', + defaultHelper: 'デフォルト: プロセスは同時実行可能', + exclusiveProcessHelper: + '排他プロセス: 1つのCUDAコンテキストのみがGPUを使用可能、ただし複数スレッドで共有可能', + exclusiveThreadHelper: '排他スレッド: CUDAコンテキスト内の1つのスレッドのみがGPUを使用可能', + prohibitedHelper: '禁止: プロセスの同時実行は許可されません', + migModeHelper: 'MIGインスタンスを作成するために使用され、ユーザーレイヤーでGPUの物理的隔離を実装します。', + migModeNA: 'サポートされていません', + current: 'リアルタイム監視', + history: '履歴記録', + notSupport: '現在のバージョンまたはドライバーはこのパラメータの表示をサポートしていません。', + }, + mcp: { + server: 'MCP サーバー', + create: 'サーバーを追加', + edit: 'サーバーを編集', + baseUrl: '外部アクセスパス', + baseUrlHelper: '例: http://192.168.1.2:8000', + ssePath: 'SSE パス', + ssePathHelper: '例: /sse, 他のサーバーと重複しないように注意してください', + environment: '環境変数', + envKey: '変数名', + envValue: '変数値', + externalUrl: '外部接続アドレス', + operatorHelper: '{0} に {1} 操作を実行します、続行しますか?', + domain: 'デフォルトアクセスアドレス', + domainHelper: '例: 192.168.1.1 または example.com', + bindDomain: 'ウェブサイトをバインド', + commandPlaceHolder: '現在、npx およびバイナリスタートアップコマンドのみをサポートしています', + importMcpJson: 'MCP サーバー設定をインポート', + importMcpJsonError: 'mcpServers 構造が正しくありません', + bindDomainHelper: + 'ウェブサイトをバインドした後、インストールされたすべての MCP サーバーのアクセスアドレスを変更し、ポートへの外部アクセスを閉じます', + outputTransport: '出力タイプ', + streamableHttpPath: 'ストリーミングパス', + streamableHttpPathHelper: '例:/mcp、他のサーバーと重複しないように注意してください', + npxHelper: 'npx またはバイナリで起動する mcp に適しています', + uvxHelper: 'uvx で起動する mcp に適しています', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: 'モデルディレクトリ', + commandHelper: + '外部アクセスが必要な場合は、コマンド内のポートをアプリケーションポートと同じに設定してください', + imageAlert: + 'イメージサイズが大きいため、インストール前にサーバーにイメージを手動でダウンロードすることをお勧めします', + modelSpeedup: 'モデル加速を有効化', + modelType: 'モデルタイプ', + }, + }, + container: { + create: 'コンテナを作成します', + edit: 'コンテナを編集します', + updateHelper1: 'このコンテナがアプリストアから取得されたことを検出しました。以下の2点にご注意ください:', + updateHelper2: '1.現在の変更内容は、アプリストアにインストールされているアプリケーションには同期されません。', + updateHelper3: + '2.インストールされているページでアプリケーションを変更すると、現在編集されているコンテンツが無効になります。', + updateHelper4: 'コンテナを編集するには再構築が必要であり、非存在データが失われます。続けたいですか?', + containerList: 'コンテナリスト', + operatorHelper: '{0}は次のコンテナで実行されます、続行しますか?', + operatorAppHelper: + '「{0}」操作は次のコンテナで実行され、実行中のサービスに影響を与える可能性があります。続けたいですか?', + containerDeleteHelper: + 'コンテナがアプリストアから作成されたことを検出しました。コンテナを削除しても、1Panel から完全には削除されません。完全に削除するには、アプリストアの「インストール済み」または「実行環境」などのメニューに移動して操作してください。続行しますか?', + start: '始める', + stop: '停止', + restart: '再起動', + kill: '殺す', + pause: '一時停止', + unpause: '再開する', + rename: '名前を変更します', + remove: '取り除く', + removeAll: 'すべてを削除します', + containerPrune: 'プルーン', + containerPruneHelper1: 'これにより、停止状態にあるすべてのコンテナが削除されます。', + containerPruneHelper2: + 'コンテナがアプリストアから取得された場合、クリーンアップを実行した後に、「アプリストア -> インストール済み」セクションに移動し、「再構築」ボタンをクリックして再インストールする必要があります。', + containerPruneHelper3: 'この操作は元に戻すことはできません。続けたいですか?', + imagePrune: 'プルーン', + imagePruneSome: 'クリーンラベル付けされています', + imagePruneSomeEmpty: '「none」タグのある画像はクリーニングできません。', + imagePruneSomeHelper: 'コンテナでは使用されていないタグ「None」で画像をクリーニングします。', + imagePruneAll: '未使用の清掃', + imagePruneAllEmpty: '未使用の画像をクリーニングすることはできません。', + imagePruneAllHelper: 'コンテナで使用されていない画像を清掃します。', + networkPrune: 'プルーン', + networkPruneHelper: 'これにより、すべての未使用ネットワークが削除されます。続けたいですか?', + volumePrune: 'プルーン', + volumePruneHelper: 'これにより、未使用のすべてのローカルボリュームが削除されます。続けたいですか?', + cleanSuccess: '操作は成功しました。このクリーンアップの数:{0}!', + cleanSuccessWithSpace: + '操作は成功しています。今回クリーニングされたディスクの数は{0}です。解放されたディスクスペースは{1}です!', + unExposedPort: '現在のポートマッピングアドレスは127.0.0.1であり、外部アクセスを有効にできません。', + upTime: '稼働時間', + fetch: 'フェッチ', + lines: '線', + linesHelper: '取得するには、正しい数のログを入力してください!', + lastDay: '最終日', + last4Hour: '最後の4時間', + lastHour: '最後の時間', + last10Min: '最後の10分', + cleanLog: 'クリーンログ', + downLogHelper1: 'これにより、Container {0}からすべてのログをダウンロードします。続けたいですか?', + downLogHelper2: 'これにより、コンテナ{0}から最近の{0}ログをダウンロードします。続けたいですか?', + cleanLogHelper: 'これには、コンテナを再起動する必要があり、元に戻すことはできません。続けたいですか?', + newName: '新しい名前', + source: 'リソースの使用', + cpuUsage: 'CPUの使用', + cpuTotal: 'CPU合計', + core: 'コア', + memUsage: 'メモリの使用', + memTotal: 'メモリ制限', + memCache: 'メモリキャッシュ', + loadSize: 'コンテナサイズを取得', + ip: 'IPアドレス', + cpuShare: 'CPU共有', + cpuShareHelper: + 'コンテナエンジンは、CPU株に1024の基本値を使用します。それを増やして、コンテナにCPU時間を増やすことができます。', + inputIpv4: '例:192.168.1.1', + inputIpv6: '例:2001:0DB8:85A3:0000:0000:8A2E:0370:7334', + + diskUsage: 'ディスク使用量', + localVolume: 'ローカルストレージボリューム', + buildCache: 'ビルドキャッシュ', + usage: '使用済み: {0}, 解放可能: {1}', + clean: '解放', + imageClean: + 'イメージをクリーンアップすると、すべての未使用イメージが削除されます。この操作は元に戻せません。続行しますか?', + containerClean: + 'コンテナをクリーンアップすると、停止中のすべてのコンテナ(アプリストアの停止アプリを含む)が削除されます。この操作は元に戻せません。続行しますか?', + sizeRw: 'コンテナレイヤーサイズ', + sizeRwHelper: 'コンテナ固有の書き込み可能レイヤーのサイズ', + sizeRootFs: '仮想サイズ', + sizeRootFsHelper: 'コンテナが依存するすべてのイメージレイヤー + コンテナレイヤーの合計サイズ', + + containerFromAppHelper: + 'このコンテナがアプリストアから取得されたことが検出されました。アプリの操作により、現在の編集が無効になる可能性があります。', + containerFromAppHelper1: + 'インストールされているアプリケーションリストの[PARAM]ボタンをクリックして、編集ページを入力し、コンテナ名を変更します。', + command: '指示', + console: 'コンテナインタラクション', + tty: 'pseudo-tty(-t)を割り当てる', + openStdin: '添付されていなくてもstdinを開いたままにしてください(-i)', + custom: 'カスタム', + emptyUser: '空の場合、デフォルトとしてログインします', + privileged: '特権', + privilegedHelper: + 'コンテナがホストに特定の特権操作を実行できるようにします。これにより、コンテナのリスクが増加する可能性があります。注意して使用してください!', + editComposeHelper: + '注意:環境変数は1panel.envファイルに保存され、composeでenv_fileを介して参照する必要があります。\n変数はコンテナ内部でのみ有効であり、composeファイルの${VAR}置換には参加しません。', + + upgradeHelper: 'リポジトリ名/画像名:画像バージョン', + upgradeWarning2: 'アップグレード操作では、コンテナを再構築する必要があります。続けたいですか?', + oldImage: '現在の画像', + targetImage: 'ターゲット画像', + sameImageContainer: '同一イメージコンテナ', + sameImageHelper: '同一イメージを使用するコンテナは選択後一括アップグレード可能', + imageLoadErr: 'コンテナの画像名は検出されません', + appHelper: + 'このコンテナはアプリストアから取得されたものであり、アップグレードによってサービスが利用不可になる可能性があります。', + input: '手動入力', + forcePull: '常に画像を引っ張ってください', + forcePullHelper: 'これにより、サーバー上の既存の画像が無視され、レジストリから最新の画像が引き出されます。', + server: 'ホスト', + serverExample: '80、80-88、IP:80またはIP:80-88', + containerExample: '80または80-88', + exposePort: 'ポートを公開します', + exposeAll: 'すべてを公開します', + cmdHelper: '例:nginx -g "daemon off;"', + entrypointHelper: '例:docker-entrypoint.sh', + autoRemove: '自動削除', + cpuQuota: 'CPUコアの数', + memoryLimit: 'メモリ', + limitHelper: `0に設定すると、制限がないことを意味します。最大値は{0}です`, + mount: 'マウント', + volumeOption: '音量', + hostOption: 'ホスト', + serverPath: 'サーバーパス', + containerDir: 'コンテナパス', + volumeHelper: 'ストレージボリュームのコンテンツが正しいことを確認してください', + networkEmptyHelper: 'コンテナネットワークの選択が正しいことを確認してください', + modeRW: 'rw', + modeR: 'r', + sharedLabel: '伝播モード', + private: 'プライベート', + privateHelper: 'コンテナ内とホストのマウント変更は互いに干渉しません', + rprivate: '再帰的プライベート', + rprivateHelper: 'コンテナ内のすべてのマウントはホストから完全に隔離されています', + shared: '共有', + sharedHelper: 'ホストとコンテナ内のマウント変更は互いに表示されます', + rshared: '再帰的共有', + rsharedHelper: 'ホストとコンテナ内のすべてのマウント変更が互いに表示されます', + slave: 'スレーブ', + slaveHelper: 'コンテナはホストのマウント変更を確認できますが、自身の変更はホストに影響しません', + rslave: '再帰的スレーブ', + rslaveHelper: 'コンテナ内のすべてのマウントはホストの変更を確認できますが、ホストに影響しません', + mode: 'モード', + env: '環境', + restartPolicy: 'ポリシーを再起動します', + always: 'いつも', + unlessStopped: '止まらない限り', + onFailure: 'オンフェイル(デフォルトで5回)', + no: '一度もない', + + refreshTime: '間隔を更新します', + cache: 'キャッシュ', + + image: '画像|画像', + imagePull: '引く', + imagePullHelper: '複数のイメージの選択をサポートし、各イメージ入力後にEnterキーを押して続行します', + imagePush: '押す', + imagePushHelper: + 'このイメージに複数のタグが存在することが検出されました。プッシュ時に使用するイメージ名が以下であることを確認してください:{0}', + imageDelete: '画像削除', + repoName: 'コンテナレジストリ', + imageName: '画像名', + pull: '引く', + path: 'パス', + importImage: '輸入', + buildArgs: 'ビルド引数', + imageBuild: '建てる', + pathSelect: 'パス', + label: 'ラベル', + imageTag: '画像タグ', + imageTagHelper: '複数のイメージタグの設定をサポートし、各タグ入力後にEnterキーを押して続行します', + push: '押す', + fileName: 'ファイル名', + export: '輸出', + exportImage: '画像エクスポート', + size: 'サイズ', + tag: 'タグ', + tagHelper: '1行に1つ。たとえば、 nkey1 = value1 nkey2 = value2', + imageNameHelper: '画像名とタグ、例:nginx:最新', + cleanBuildCache: 'クリーンビルドキャッシュ', + delBuildCacheHelper: `これにより、ビルド中に生成され、元に戻すことができないすべてのキャッシュされたアーティファクトが削除されます。続けたいですか?`, + urlWarning: 'URLプレフィックスには、http://またはhttps://を含める必要はありません。変更してください。', + + network: 'ネットワーク|ネットワーク', + networkHelper: + 'これにより、一部のアプリケーションとランタイム環境が適切に機能しない場合があります。続けたいですか?', + createNetwork: '作成する', + networkName: '名前', + driver: 'ドライバ', + option: 'オプション', + attachable: '取り付け可能', + parentNetworkCard: '親ネットワークカード', + subnet: 'サブネット', + scope: 'IPスコープ', + gateway: 'ゲートウェイ', + auxAddress: 'IPを除外します', + + volume: 'ボリューム|ボリューム', + volumeDir: 'ボリュームディレクトリ', + nfsEnable: 'NFSストレージを有効にします', + nfsAddress: '住所', + mountpoint: 'マウントポイント', + mountpointNFSHelper: '例えば/nfs、 /nfs-sh', + options: 'オプション', + createVolume: '作成する', + + repo: 'レジストリ', + createRepo: '追加', + httpRepoHelper: 'HTTPタイプのリポジトリを操作するにはDockerサービスの再起動が必要です。', + httpRepo: + 'HTTPプロトコルを選択するには、Dockerサービスを再起動して不安定なレジストリに追加する必要があります。', + delInsecure: 'クレジットの削除', + delInsecureHelper: 'これにより、Dockerサービスを再起動して、不安定なレジストリから削除します。続けたいですか?', + downloadUrl: 'サーバ', + imageRepo: '画像リポジトリ', + repoHelper: 'ミラーリポジトリ/組織/プロジェクトが含まれていますか?', + auth: '認証が必要です', + mirrorHelper: + '複数のミラーがある場合、たとえばnewlinesを表示する必要があります:Nhttp://xxxxxxMDaocloudIo Nhttps://xxxxxxMirrorAliyuncsCom', + registrieHelper: '複数のプライベートリポジトリが存在する場合、たとえばnewlinesを表示する必要があります。', + + compose: '構成|作曲', + composeFile: 'オーケストレーションファイル', + fromChangeHelper: 'ソースを切り替えると、現在の編集されたコンテンツがきれいになります。続けたいですか?', + composePathHelper: '構成ファイル保存パス:{0}', + composeHelper: + '1パネルの編集者またはテンプレートを介して作成された構成は、{0}/docker/composeディレクトリに保存されます。', + deleteFile: 'ファイルを削除します', + deleteComposeHelper: + '構成ファイルや永続的なファイルを含む、コンテナに関連するすべてのファイルを削除します。注意して進めてください!', + deleteCompose: 'この構成を削除します。', + createCompose: '作成する', + composeDirectory: 'ディレクトリ', + template: 'テンプレート', + composeTemplate: 'テンプレートを作成|テンプレートを作成します', + createComposeTemplate: '作成する', + content: 'コンテンツ', + contentEmpty: 'コンテンツを空にすることはできません。入力して再試行してください!', + containerNumber: 'コンテナ番号', + containerStatus: 'コンテナステータス', + exited: '終了', + running: 'ランニング ( {0} / {1} )', + composeDetailHelper: '構成は1パネルの外部に作成されます。開始および停止操作はサポートされていません。', + composeOperatorHelper: '{1}操作は{0}で実行されます。続けたいですか?', + composeDownHelper: + 'これにより、{0}構成の下のすべてのコンテナとネットワークが停止して削除されます。続けたいですか?', + composeEnvHelper2: + 'このオーケストレーションは1Panelアプリストアで作成されました。インストール済みアプリケーションで環境変数を変更してください。', + + setting: '設定|設定', + operatorStatusHelper: 'これは「{0}」Dockerサービスになります。続けたいですか?', + dockerStatus: 'Dockerサービス', + daemonJsonPathHelper: '構成パスがdocker.serviceで指定されているものと同じであることを確認してください。', + mirrors: 'レジストリミラー', + mirrorsHelper2: '詳細については、公式文書を参照してください。', + registries: '不安定なレジストリ', + ipv6Helper: + 'IPv6を有効にするときは、IPv6コンテナネットワークを追加する必要があります。特定の構成手順については、公式ドキュメントを参照してください。', + ipv6CidrHelper: 'IPv6はコンテナのプール範囲をアドレスします', + ipv6TablesHelper: 'iptablesルール用のDocker IPv6の自動構成。', + experimentalHelper: + 'IP6Tablesを有効にするには、この構成をオンにする必要があります。それ以外の場合、IP6テーブルは無視されます', + cutLog: 'ログオプション', + cutLogHelper1: '現在の構成は、新しく作成されたコンテナのみに影響します。', + cutLogHelper2: '構成を有効にするために、既存のコンテナを再作成する必要があります。', + cutLogHelper3: + 'コンテナを再現すると、データの損失が発生する可能性があることに注意してください。コンテナに重要なデータが含まれている場合は、再構築操作を実行する前に必ずバックアップしてください。', + maxSize: '最大サイズ', + maxFile: '最大ファイル', + liveHelper: + 'デフォルトでは、Docker Daemonが終了すると、実行中のコンテナをシャットダウンします。デーモンを設定して、デーモンが利用できなくなった場合にコンテナが実行され続けるように設定できます。この機能は、Live Restoreと呼ばれます。Live Restoreオプションは、デーモンのクラッシュ、計画された停止、またはアップグレードにより、コンテナのダウンタイムを短縮するのに役立ちます。', + liveWithSwarmHelper: 'ライブレストアデーモン構成は、群れモードと互換性がありません。', + iptablesDisable: 'Iptablesを閉じます', + iptablesHelper1: 'DockerのiPtablesルールの自動構成。', + iptablesHelper2: 'IPTABLEを無効にすると、コンテナが外部ネットワークと通信できなくなります。', + daemonJsonPath: 'conf path', + serviceUnavailable: `現在、Dockerサービスは開始されていません。`, + startIn: '開始する', + sockPath: 'UNIXドメインソケット', + sockPathHelper: 'Dockerデーモンとクライアントの間のコミュニケーションチャネル。', + sockPathHelper1: 'デフォルトパス:/var/run/docker-x.sock', + sockPathMsg: + 'ソケットパスの設定を保存すると、Dockerサービスが利用できなくなる可能性があります。続けたいですか?', + sockPathErr: '正しいDockerソックファイルパスを選択または入力してください', + related: '関連している', + includeAppstore: 'アプリストアから取得したコンテナを表示', + excludeAppstore: 'アプリストアコンテナを隠す', + + cleanDockerDiskZone: 'Dockerが使用するディスクスペースをクリーンアップします', + cleanImagesHelper: '(コンテナで使用されていないすべての画像をクリーンアップ)', + cleanContainersHelper: '(停止したすべての容器をクリーンアップ)', + cleanVolumesHelper: '(未使用のすべてのローカルボリュームをクリーンアップ)', + + makeImage: '画像を作成します', + newImageName: '新しい画像名', + commitMessage: 'メッセージをコミットします', + author: '著者', + ifPause: '作成中にコンテナを一時停止します', + ifMakeImageWithContainer: 'このコンテナから新しい画像を作成しますか?', + finishTime: '前回の停止時間', + }, + cronjob: { + create: 'Cronジョブを作成します', + edit: 'Cronジョブを編集します', + importHelper: + 'インポート時に同名のスケジュールタスクは自動的にスキップされます。タスクはデフォルトで【無効】状態に設定され、データ関連付け異常時には【編集待ち】状態に設定されます。', + changeStatus: 'ステータスを変更します', + disableMsg: 'これにより、スケジュールされたタスクが自動的に実行されなくなります。続けたいですか?', + enableMsg: 'これにより、スケジュールされたタスクが自動的に実行されます。続けたいですか?', + taskType: 'タイプ', + record: '記録', + viewRecords: '記録', + shell: 'シェル', + stop: '手動終了', + stopHelper: 'この操作により現在のタスクの実行が強制停止されます。続行しますか?', + log: 'バックアップログ', + logHelper: 'バックアップシステムログ', + ogHelper1: '1.1パネルシステムログ', + logHelper2: '2。サーバーのSSHログインログ', + logHelper3: '3.すべてのサイトログ', + containerCheckBox: 'コンテナ内(コンテナコマンドを入力する必要はありません)', + containerName: 'コンテナ名', + ntp: '時間同期', + ntp_helper: 'ツールボックスのクイックセットアップページでNTPサーバーを構成できます。', + app: 'バックアップアプリ', + website: 'バックアップウェブサイト', + rulesHelper: '複数の除外ルールをサポート、英語のカンマ , で区切ります。例:*.log,*.sql', + lastRecordTime: '最後の実行時間', + all: '全て', + failedRecord: '失敗記録', + successRecord: '成功した記録', + database: 'バックアップデータベース', + backupArgs: 'バックアップ引数', + backupArgsHelper: + 'リストにないバックアップ引数は手動で入力して選択できます。例:--no-data を入力し、ドロップダウンリストの最初のオプションを選択してください。', + singleTransaction: + '単一トランザクションを使用して InnoDB テーブルをバックアップします。大容量データのバックアップに適しています', + quick: 'テーブル全体をメモリにロードする代わりに、データを行単位で読み取ります。大容量データや低メモリマシンのバックアップに適しています', + skipLockTables: 'すべてのテーブルをロックせずにバックアップします。高並列データベースに適しています', + missBackupAccount: 'バックアップアカウントは見つかりませんでした', + syncDate: '同期時間', + clean: 'キャッシュクリーン', + curl: 'アクセスURL', + taskName: '名前', + cronSpec: 'トリガーサイクル', + cronSpecDoc: + 'カスタム実行周期は【分 時 日 月 曜日】形式のみサポートしています(例: 0 0 * * *)。詳細は公式ドキュメントをご参照ください。', + cronSpecHelper: '正しい実行期間を入力します', + cleanHelper: + 'この操作は、すべてのジョブ実行レコード、バックアップファイル、ログファイルを記録します。続けたいですか?', + directory: 'バックアップディレクトリ', + sourceDir: 'バックアップディレクトリ', + snapshot: 'システムスナップショット', + allOptionHelper: + '現在のタスク計画は、すべての[{0}]をバックアップすることです。直接ダウンロードは現時点ではサポートされていません。[{{0}]メニューのバックアップリストを確認できます。', + exclusionRules: '排他的ルール', + exclusionRulesHelper: + '除外ルールを選択または入力し、各セット入力後にEnterキーを押して続行します。除外ルールはこのバックアップのすべての圧縮操作に適用されます', + default_download_path: 'デフォルトのダウンロードリンク', + saveLocal: 'ローカルバックアップを保持します(クラウドストレージコピーの数と同じ)', + url: 'URLアドレス', + urlHelper: '正しいURLアドレスを入力してください', + targetHelper: 'バックアップアカウントは、パネル設定で維持されます。', + withImageHelper: + 'アプリストアのイメージをバックアップしますが、スナップショットファイルのサイズが大きくなります。', + ignoreApp: 'アプリを除外', + withImage: 'アプリケーションイメージのバックアップ', + retainCopies: '記録を保持します', + retryTimes: 'リトライ回数', + timeout: 'タイムアウト', + ignoreErr: 'エラーを無視', + ignoreErrHelper: 'バックアップ中のエラーを無視し、全てのバックアップタスクを確実に実行します', + retryTimesHelper: '0は失敗後リトライしないことを意味します', + retainCopiesHelper: '実行記録とログのために保持するコピーの数', + retainCopiesHelper1: 'バックアップファイル用に保持するコピーの数', + retainCopiesUnit: 'コピー(表示)', + cronSpecRule: 'ライン{0}の実行期間形式は正しくありません。チェックしてもう一度やり直してください!', + perMonth: '毎月', + perWeek: '毎週', + perHour: '毎時間', + perNDay: '毎日毎日)', + perDay: '毎日', + perNHour: 'N時間ごと)', + perNMinute: '毎分(s)', + perNSecond: '毎秒毎回', + per: '毎', + day: '日', + dayUnit: 'd', + monday: '月曜日', + tuesday: '火曜日', + wednesday: '水曜日', + thursday: '木曜日', + friday: '金曜日', + saturday: '土曜日', + sunday: '日曜日', + shellContent: 'スクリプト', + errRecord: '誤ったロギング', + errHandle: 'cronjob実行障害', + noRecord: 'Cronジョブをトリガーすると、ここにレコードが表示されます。', + cleanData: 'クリーンデータ', + cleanRemoteData: 'リモートデータを削除', + cleanDataHelper: 'このタスク中に生成されたバックアップファイルを削除します。', + noLogs: 'タスク出力はまだありません...', + errPath: 'バックアップパス[{0}]エラー、ダウンロードできません!', + cutWebsiteLog: 'ウェブサイトのログローテーション', + cutWebsiteLogHelper: '回転したログファイルは、1パネルのバックアップディレクトリにバックアップされます。', + syncIpGroup: 'WAF IP グループを同期', + + requestExpirationTime: 'リクエストの有効期限(時間)のアップロード', + unitHours: 'ユニット:時間', + alertTitle: '計画タスク - {0}「{1}」タスク障害アラート', + library: { + script: 'スクリプト', + syncNow: '今すぐ同期', + turnOnSync: '自動同期を有効化', + turnOnSyncHelper: '自動同期を有効にすると、毎日未明の時間帯に自動同期が実行されます', + turnOffSync: '自動同期を無効化', + turnOffSyncHelper: '自動同期を無効にするとスクリプトの同期が遅れる可能性がありますが、よろしいですか?', + isInteractive: '対話型', + interactive: '対話型スクリプト', + interactiveHelper: '実行中にユーザー入力が必要で、スケジュールタスクでは使用できません。', + library: 'スクリプトライブラリ', + create: 'スクリプトを追加', + edit: 'スクリプトを編集', + groupHelper: + 'スクリプトの特徴に基づいて異なるグループを設定することで、スクリプトのフィルタリング操作をより迅速に行うことができます。', + handleHelper: '{0} で {1} スクリプトを実行します。続行しますか?', + noSuchApp: + '{0} サービスが検出されませんでした。スクリプトライブラリを使って素早くインストールしてください!', + syncHelper: + 'システムスクリプトライブラリを同期します。この操作はシステムスクリプトのみ影響します。続行しますか?', + }, + }, + monitor: { + globalFilter: 'グローバルフィルター', + enableMonitor: '監視ステータス', + storeDays: '保存日数', + defaultNetwork: 'デフォルトネットワークカード', + defaultNetworkHelper: '監視および概要インターフェースに表示されるデフォルトのネットワークカードオプション', + defaultIO: 'デフォルトディスク', + defaultIOHelper: '監視および概要インターフェースに表示されるデフォルトのディスクオプション', + cleanMonitor: '監視記録をクリア', + cleanHelper: 'この操作により、GPUを含むすべての監視記録がクリアされます。続行しますか?', + + avgLoad: 'ロード平均', + loadDetail: '詳細を読み込みます', + resourceUsage: '利用', + networkCard: 'ネットワークインターフェイス', + read: '読む', + write: '書く', + readWriteCount: 'I/O操作', + readWriteTime: 'I/Oレイテンシ', + today: '今日', + yesterday: '昨日', + lastNDay: '過去 {0} 日間', + lastNMonth: '過去 {0} ヶ月間', + lastHalfYear: '過去半年間', + memory: 'メモリ', + percent: '割合', + cache: 'キャッシュ', + disk: 'ディスク', + network: 'ネットワーク', + up: '上', + down: '下', + interval: '収集間隔', + intervalHelper: '適切な監視収集間隔を入力してください(5秒 - 12時間)', + }, + terminal: { + local: 'ローカル', + defaultConn: 'デフォルト接続', + defaultConnHelper: + 'この操作は【{0}】のターミナルを開いた後、自動的にノードターミナルに接続します。続行しますか?', + withReset: '接続情報をリセット', + localConnJump: + 'デフォルト接続情報は【ターミナル - 設定】で管理されています。接続に失敗した場合はこちらで編集してください!', + localHelper: 'ローカル名はシステムのローカル識別にのみ使用されます。', + connLocalErr: '自動的に認証できない場合は、ローカルサーバーのログイン情報を入力してください。', + testConn: 'テスト接続', + saveAndConn: '保存して接続します', + connTestOk: '利用可能な接続情報', + connTestFailed: '接続は利用できません。接続情報を確認してください。', + host: 'ホスト|ホスト', + createConn: '新しい接続', + manageGroup: 'グループを管理します', + noHost: 'ホストはありません', + groupChange: 'グループを変更します', + expand: 'すべて拡張します', + fold: 'すべての契約', + batchInput: 'バッチ処理', + quickCommand: 'クイックコマンド|クイックコマンド', + noSuchCommand: + 'インポートしたCSVファイルにクイックコマンドデータが見つかりませんでした。確認して再試行してください!', + quickCommandHelper: '「端末 - >端子」の下部にあるクイックコマンドを使用できます。', + groupDeleteHelper: + 'グループが削除された後、グループ内のすべての接続がデフォルトグループに移行されます。続けたいですか?', + command: '指示', + quickCmd: 'クイックコマンド', + addHost: '追加', + localhost: 'localhost', + ip: '住所', + authMode: '認証', + passwordMode: 'パスワード', + rememberPassword: '認証情報を忘れないでください', + keyMode: 'privatekey', + key: '秘密鍵', + keyPassword: '秘密キーパスワード', + emptyTerminal: '現在接続されている端子はありません。', + }, + toolbox: { + common: { + toolboxHelper: '一部のインストールおよび使用に関する問題については、以下を参照してください', + }, + swap: { + swap: 'パーティションを交換します', + swapHelper1: 'スワップのサイズは、特定の要件に基づいて調整可能な物理メモリの1〜2倍である必要があります。', + swapHelper2: + 'スワップファイルを作成する前に、スワップファイルサイズが対応するディスクスペースを占有するため、システムディスクに十分な利用可能なスペースがあることを確認してください。', + swapHelper3: + 'スワップはメモリの圧力を軽減するのに役立ちますが、それは代替案にすぎません。スワップに過度に依存すると、システムのパフォーマンスが低下する可能性があります。メモリの増加を優先したり、アプリケーションメモリの使用量を最適化することをお勧めします。', + swapHelper4: '通常のシステム操作を確保するために、スワップの使用を定期的に監視することをお勧めします。', + swapDeleteHelper: + 'この操作は、スワップパーティション{0}を削除します。システムセキュリティ上の理由から、対応するファイルは自動的に削除されません。削除が必要な場合は、手動で続行してください!', + saveHelper: '最初に現在の設定を保存してください!', + saveSwap: '現在の構成を保存すると、スワップパーティション{0}サイズを{1}に調整します。続けたいですか?', + swapMin: '最小パーティションサイズは40 kbです。変更してもう一度やり直してください!', + swapMax: 'パーティションサイズの最大値は{0}です。変更してもう一度やり直してください!', + swapOff: '最小パーティションサイズは40 kbです。0に設定すると、スワップパーティションが無効になります。', + }, + device: { + dnsHelper: 'DNSサーバー', + dnsAlert: + '注意!/etc/resolv.confファイルの構成を変更すると、システムが再起動した後、ファイルがデフォルト値に復元されます。', + dnsHelper1: + '複数のDNSエントリがある場合は、新しい行に表示する必要があります。例えば、 n114.114.114.114 n8.8.8.8', + hostsHelper: 'ホスト名解像度', + hosts: 'ドメイン', + hostAlert: '隠されたコメントレコード、すべての構成ボタンをクリックして表示または設定してください', + toolbox: 'クイック設定', + hostname: 'ホスト名', + passwd: 'システムパスワード', + passwdHelper: '入力文字は$ and&&を含めることはできません', + timeZone: 'タイムゾーン', + localTime: 'サーバー時間', + timeZoneChangeHelper: 'システムタイムゾーンを変更するには、サービスを再起動する必要があります。続く?', + timeZoneHelper: `「TimeDatectl」コマンドをインストールしない場合、タイムゾーンを変更することはできません。システムはそのコマンドを使用してタイムゾーンを変更するためです。`, + timeZoneCN: '北京', + timeZoneAM: 'ロサンゼルス', + timeZoneNY: 'ニューヨーク', + ntpALi: 'アリババ', + ntpGoogle: 'グーグル', + syncSite: 'NTPサーバー', + hostnameHelper: `ホスト名の変更は、「hostnamectl」コマンドに依存します。コマンドがインストールされていない場合、変更が失敗する可能性があります。`, + userHelper: `ユーザー名は、取得の「whoami」コマンドに依存します。コマンドがインストールされていない場合、検索が失敗する可能性があります。`, + passwordHelper: `パスワードの変更は、「chpasswd」コマンドに依存します。コマンドがインストールされていない場合、変更が失敗する可能性があります。`, + hostHelper: '提供されたコンテンツには空の値があります。変更後に確認して再試行してください!', + dnsCheck: 'テストの可用性', + dnsOK: 'DNS構成情報が利用可能です!', + dnsTestFailed: `DNS構成情報は利用できません。`, + }, + fail2ban: { + sshPort: 'SSHポートを聞いてください', + sshPortHelper: '現在のFAL2BANは、ホストのSSH接続ポートに耳を傾けます', + unActive: `現在、Fail2Banサービスは有効になっていません。`, + operation: 'fail2banサービスで操作「{0}」を実行します。続けたいですか?', + fail2banChange: 'fail2ban構成の変更', + ignoreHelper: 'AllowListのIPリストは、ブロックについて無視されます。続けたいですか?', + bannedHelper: 'ブロックリストのIPリストは、サーバーによってブロックされます。続けたいですか?', + maxRetry: '最大再試行', + banTime: '禁止時間', + banTimeHelper: 'デフォルトの禁止時間は10分、-1は永続的な禁止を示します', + banTimeRule: '有効な禁止時間または-1を入力してください', + banAllTime: '恒久的な禁止', + findTime: '発見期間', + banAction: '禁止措置', + banActionOption: '{0}を使用して指定されたIPアドレスを禁止', + allPorts: '(すべてのポート)', + ignoreIP: 'IP AllowList', + bannedIP: 'IPブロックリスト', + logPath: 'ログパス', + logPathHelper: 'デフォルトは/var/log/secureまたは/var/log/auth.logです', + }, + ftp: { + ftp: 'FTPアカウント|FTPアカウント', + notStart: 'FTP Serviceは現在実行されていません。最初に開始してください!', + operation: 'これにより、FTPサービスで「{0}」操作が実行されます。続けたいですか?', + noPasswdMsg: '現在のFTPアカウントパスワードを取得できません。パスワードを設定して再試行してください!', + enableHelper: '選択したFTPアカウントを有効にすると、アクセス許可が復元されます。続けたいですか?', + disableHelper: '選択したFTPアカウントを無効にすると、アクセス許可が取り消されます。続けたいですか?', + syncHelper: 'サーバーとデータベースの間でFTPアカウントデータを同期します。続けたいですか?', + dirSystem: + 'このディレクトリはシステム予約領域です。変更するとシステムがクラッシュする可能性があります。修正して再試行してください!', + dirHelper: 'FTPを有効にするにはディレクトリ権限の変更が必要です。慎重に選択してください', + dirMsg: 'FTPを有効にすると{0}ディレクトリ全体の権限が変更されます。続行しますか?', + }, + clam: { + clam: 'ウイルススキャン', + cron: 'スケジュールされたスキャン', + cronHelper: 'プロフェッショナルバージョンは、スケジュールされたスキャン機能をサポートしています', + specErr: '実行スケジュールフォーマットエラー、チェックして再試行してください!', + disableMsg: + 'スケジュールされた実行を停止すると、このスキャンタスクが自動的に実行されなくなります。続けたいですか?', + enableMsg: + 'スケジュールされた実行を有効にすることで、このスキャンタスクは定期的に自動的に実行できます。続けたいですか?', + showFresh: '署名のアップデーターサービスを表示します', + hideFresh: '署名のアップデーターサービスを非表示にします', + clamHelper: + 'Clamavの最小推奨構成は、3ギブ以上のRAM、2.0 GHz以上のシングルコアCPU、および少なくとも5 GIBの利用可能なハードディスクスペースです。', + notStart: 'Clamav Serviceは現在実行されていません。最初に開始してください!', + removeRecord: 'ペポートファイルを削除します', + noRecords: '[トリガー]ボタンをクリックしてスキャンを開始すると、ここにレコードが表示されます。', + removeInfected: 'ウイルスファイルを削除します', + removeInfectedHelper: + 'サーバーのセキュリティと通常の操作を確保するために、タスク中に検出されたウイルスファイルを削除します。', + clamCreate: 'スキャンルールを作成します', + infectedStrategy: '感染した戦略', + removeHelper: 'ウイルスファイルを削除して、慎重に選択してください!', + move: '動く', + moveHelper: 'ウイルスファイルを指定されたディレクトリに移動します', + copyHelper: 'ウイルスファイルを指定されたディレクトリにコピーします', + none: '何もしません', + noneHelper: 'ウイルスファイルにアクションを実行しません', + scanDir: 'スキャンディレクトリ', + infectedDir: '感染したディレクトリ', + scanDate: 'スキャン日', + scanResult: 'テールをスキャンします', + tail: '線', + infectedFiles: '感染したファイル', + log: '詳細', + clamConf: 'クラマブデーモン', + clamLog: '@:toolbox.clam.clamconfログ', + freshClam: 'フレッシュクラム', + freshClamLog: '@:toolbox.clam.freshclamログ', + alertHelper: 'プロフェッショナル版は、定期スキャンとSMSアラート機能をサポートしています', + alertTitle: 'ウイルススキャンタスク({0}」感染したファイルアラートが検出されました', + }, + }, + logs: { + core: 'パネルサービス', + agent: 'ノード監視', + panelLog: 'パネルログ', + operation: '操作ログ', + login: 'ログインログ', + loginIP: 'ログインIP', + loginAddress: 'ログインアドレス', + loginAgent: 'ログインエージェント', + loginStatus: '状態', + system: 'システムログ', + deleteLogs: 'クリーンログ', + resource: 'リソース', + detail: { + dashboard: '概要', + ai: 'AI', + groups: 'グループ', + hosts: 'ホスト', + apps: 'アプリケーション', + websites: 'ウェブサイト', + containers: 'コンテナ', + files: 'ファイル管理', + runtimes: 'ランタイム', + process: 'プロセス管理', + toolbox: 'ツールボックス', + backups: 'バックアップ / 復元', + tampers: '改ざん防止', + xsetting: 'インターフェース設定', + logs: 'ログ監査', + settings: 'パネル設定', + cronjobs: 'スケジュールされたタスク', + databases: 'データベース', + waf: 'WAF', + licenses: 'ライセンス', + nodes: 'ノード', + commands: 'クイックコマンド', + }, + websiteLog: 'ウェブサイトログ', + runLog: 'ログを実行します', + errLog: 'エラーログ', + }, + file: { + fileDirNum: '{0} 個のディレクトリ、{1} 個のファイル、', + currentDir: '現在のディレクトリ', + dir: 'フォルダ', + upload: 'アップロード', + uploadFile: '@:file.upload@.lower:file.file', + uploadDirectory: '@:file.upload@.lower:file.dir', + download: 'ダウンロード', + fileName: 'ファイル名', + search: '検索', + mode: '権限', + editPermissions: '編集@:file.mode', + owner: '所有者', + file: 'ファイル', + remoteFile: 'リモートダウンロード', + share: '共有', + sync: 'データ同期', + size: 'サイズ', + updateTime: '修正', + rename: '名前を変更します', + role: '権限', + info: '属性', + linkFile: 'ソフトリンク', + batchoperation: 'バッチ操作', + shareList: '共有リスト', + zip: '圧縮', + group: 'グループ', + path: 'パス', + public: 'その他', + setRole: '設定権限', + link: 'ファイルリンク', + rRole: '読む', + wRole: '書く', + xRole: '実行可能', + name: '名前', + compress: '圧縮', + deCompress: '減圧', + compressType: '圧縮形式', + compressDst: 'パスを圧縮します', + replace: '既存のファイルを上書きします', + compressSuccess: '正常に圧縮されました', + deCompressSuccess: '減圧は成功しました', + deCompressDst: 'パスを減圧します', + linkType: 'リンクタイプ', + softLink: 'ソフトリンク', + hardLink: 'ハードリンク', + linkPath: 'リンクパス', + selectFile: '[ファイル]を選択します', + downloadUrl: 'リモートURL', + downloadStart: 'ダウンロードが始まりました', + moveSuccess: '正常に移動しました', + copySuccess: '正常にコピーされました', + pasteMsg: '対象ディレクトリの右上にある「貼り付け」ボタンをクリックしてください', + move: '動く', + calculate: '計算します', + remark: '備考', + setRemark: '備考を設定', + remarkPrompt: '備考を入力してください', + remarkPlaceholder: '備考', + remarkToggle: '備考', + remarkToggleTip: 'ファイルの備考を読み込む', + canNotDeCompress: 'このファイルを解凍できません', + uploadSuccess: '正常にアップロードします', + downloadProcess: '進捗状況をダウンロードします', + downloading: 'ダウンロード...', + infoDetail: 'ファイルプロパティ', + root: 'ルートディレクトリ', + list: 'ファイルリスト', + sub: 'サブフォルダー', + downloadSuccess: 'ダウンロードに成功しました', + theme: 'テーマ', + language: '言語', + eol: '行の終わり', + copyDir: 'コピー', + paste: 'ペースト', + changeOwner: 'ユーザーグループとユーザーグループを変更します', + containSub: '許可変更を再帰的に適用します', + ownerHelper: + 'PHP運用環境のデフォルトユーザー:ユーザーグループは1000:1000です。コンテナの内側と外側のユーザーが矛盾を示すのは普通です', + searchHelper: '*などのワイルドカードをサポート', + uploadFailed: '[{0}]ファイルアップロードファイル', + fileUploadStart: 'アップロード[{0}] ....', + currentSelect: '現在の選択:', + unsupportedType: 'サポートされていないファイルタイプ', + deleteHelper: '次のファイルを削除したいですか?デフォルトでは、削除後にリサイクルビンに入ります', + fileHelper: `注意:1. 検索結果は並べ替え機能をサポートしていません 2. フォルダはサイズで並べ替えできません。`, + forceDeleteHelper: 'ファイルを永久に削除します(リサイクルビンを入力せずに、直接削除します)', + recycleBin: 'ビンをリサイクルします', + sourcePath: 'オリジナルパス', + deleteTime: '時間を削除します', + confirmReduce: '次のファイルを復元したいですか?', + reduceSuccess: '成功して復元します', + reduce: '削減', + reduceHelper: '同じ名前のファイルまたはディレクトリが元のパスに存在する場合、上書きされます。続けたいですか?', + clearRecycleBin: 'クリーン', + clearRecycleBinHelper: 'リサイクルビンを掃除しますか?', + favorite: 'お気に入り', + removeFavorite: 'お気に入りから取り外しますか?', + addFavorite: 'お気に入りの追加/削除', + clearList: 'クリーンリスト', + deleteRecycleHelper: '次のファイルを永続的に削除する必要がありますか?', + typeErrOrEmpty: '[{0}]ファイルタイプは間違っているか、空のフォルダーです', + dropHelper: 'ここにアップロードするファイルをドラッグします', + fileRecycleBin: 'リサイクルビンを有効にします', + fileRecycleBinMsg: '{0}リサイクルビン', + wordWrap: '自動的にラップします', + deleteHelper2: '選択したファイルを削除する必要がありますか?削除操作をロールバックすることはできません', + ignoreCertificate: '不安定なサーバー接続を許可します', + ignoreCertificateHelper: + '不安定なサーバー接続を可能にすると、データが漏れたり改ざんしたりする可能性があります。ダウンロードソースを信頼する場合にのみ、このオプションを使用します。', + uploadOverLimit: 'ファイルの数は1000を超えています!圧縮してアップロードしてください', + clashDitNotSupport: 'ファイル名は、.1panel_clashを含むことを禁止されています', + clashDeleteAlert: `「リサイクルビン」フォルダーを削除することはできません`, + clashOpenAlert: '「リサイクルビン」ボタンをクリックして、リサイクルビンディレクトリを開きます', + right: 'フォワード', + back: '戻る', + top: '戻って行きます', + up: '戻って行きます', + openWithVscode: 'VSコードで開く', + vscodeHelper: + 'VSコードがローカルにインストールされ、SSHリモートプラグインが構成されていることを確認してください', + saveContentAndClose: 'ファイルが変更されましたが、保存して閉じたいですか?', + saveAndOpenNewFile: 'ファイルが変更されましたが、新しいファイルを保存して開きますか?', + noEdit: 'ファイルは変更されておらず、これを行う必要はありません!', + noNameFolder: '無題のフォルダー', + noNameFile: '無題のファイル', + minimap: 'コードミニマップ', + fileCanNotRead: 'ファイルは読み取れません', + previewTruncated: 'ファイルが大きすぎるため、末尾の内容のみ表示しています', + previewEmpty: 'ファイルが空であるか、テキストファイルではありません', + previewLargeFile: 'プレビュー', + panelInstallDir: `1Panelインストールディレクトリは削除できません`, + wgetTask: 'ダウンロードタスク', + existFileTitle: '同名ファイルの警告', + existFileHelper: 'アップロードしたファイルに同じ名前のファイルが含まれています。上書きしますか?', + existFileSize: 'ファイルサイズ(新しい -> 古い)', + existFileDirHelper: '選択したファイル/フォルダーには同じ名前のものが既に存在します。慎重に操作してください!', + coverDirHelper: '上書きするフォルダを選択すると、対象パスにコピーされます!', + noSuchFile: 'ファイルまたはディレクトリが見つかりませんでした。確認して再試行してください。', + setting: '設定', + showHide: '隠しファイルを表示', + noShowHide: '隠しファイルを表示しない', + cancelUpload: 'アップロードをキャンセル', + cancelUploadHelper: 'アップロードをキャンセルするかどうか、キャンセル後、アップロードリストはクリアされます。', + keepOneTab: '少なくとも1つのタブを保持してください', + notCanTab: 'これ以上タブを追加できません', + convert: 'フォーマットを変換', + converting: 'に変換中', + fileCanNotConvert: 'このファイルはフォーマット変換に対応していません', + formatType: 'フォーマットタイプ', + sourceFormat: '元のフォーマット', + sourceFile: '元ファイル', + saveDir: '保存ディレクトリ', + deleteSourceFile: '元ファイルを削除するかどうか', + convertHelper: '選択したファイルを別のフォーマットに変換します', + convertHelper1: '変換するファイルを選択してください', + execConvert: '変換を開始します。タスクセンターで変換ログを確認できます', + convertLogs: '変換ログ', + formatConvert: 'フォーマット変換', + }, + ssh: { + autoStart: 'オートスタート', + enable: 'AutoStartを有効にします', + disable: 'AutoStartを無効にします', + sshAlert: + 'リストデータは、ログイン日に基づいてソートされます。タイムゾーンを変更したり、他の操作を実行したりすると、ログインログの日付が逸脱を引き起こす可能性があります。', + sshAlert2: + '「ツールボックス」で「Fail2ban」を使用して、ブルートフォース攻撃を試みるIPアドレスをブロックすることができます。これにより、ホストのセキュリティが向上します。', + sshOperate: 'SSHサービスの操作「{0}」が実行されます。続けたいですか?', + sshChange: 'SSH設定', + sshChangeHelper: 'このアクションは「{0}」が「{1}」に変更されました。続けたいですか?', + sshFileChangeHelper: + '構成ファイルを変更すると、サービスの可用性が発生する場合があります。この操作を実行するときは注意してください。続けたいですか?', + port: 'ポート', + portHelper: 'SSHサービスが耳を傾けるポートを特定します。', + listenAddress: '住所を聞いてください', + allV4V6: '0.0.0.0: {0 }(IPv4)および::: {0}(IPv6)', + listenHelper: + 'IPv4とIPv6の両方の設定を空白のままにしておくと、「0.0.0.0:0:0:0}」と「::: {0}(IPv6)」で聞きます。', + addressHelper: 'SSHサービスが耳を傾けるアドレスを指定します。', + permitRootLogin: 'ルートユーザーログインを許可します', + rootSettingHelper: 'ルートユーザーのデフォルトのログインメソッドは「SSHログインを許可」です。', + rootHelper1: 'SSHログインを許可します', + rootHelper2: 'SSHログインを無効にします', + rootHelper3: 'キーログインのみが許可されています', + rootHelper4: '事前定義されたコマンドのみを実行できます。他の操作を実行することはできません。', + passwordAuthentication: 'パスワード認証', + pwdAuthHelper: 'パスワード認証を有効にするかどうか。このパラメーターはデフォルトで有効になります。', + pubkeyAuthentication: '重要な認証', + privateKey: '秘密鍵', + publicKey: '公開鍵', + password: 'パスワード', + createMode: '作成方法', + generate: '自動生成', + unSyncPass: '鍵パスワードは同期できません', + syncHelper: '同期操作は無効なキーをクリーンアップし、新しい完全なキーペアを同期します。続行しますか?', + input: '手動入力', + import: 'ファイルアップロード', + authKeys: '認証キー', + authKeysHelper: '現在の公開鍵情報を保存しますか?', + pubkey: '重要な情報', + encryptionMode: '暗号化モード', + pubKeyHelper: '現在の鍵情報はユーザー {0} にのみ有効です', + passwordHelper: '6〜10桁と英語のケースを含めることができます', + reGenerate: 'キーを再生します', + keyAuthHelper: 'キー認証を有効にするかどうか。', + useDNS: '使用済み', + dnsHelper: 'DNS解像度関数がSSHサーバーで有効になっているかどうかを制御して、接続のIDを確認します。', + analysis: '統計情報', + denyHelper: + '次のアドレスで「拒否」操作を実行します。設定後、IPはサーバーへのアクセスが禁止されます。続けたいですか?', + acceptHelper: + '次のアドレスで「受け入れる」操作を実行します。設定後、IPは通常のアクセスを取り戻します。続けたいですか?', + noAddrWarning: '[{0}]アドレスは現在選択されていません。チェックしてもう一度やり直してください!', + loginLogs: 'ログインログ', + loginMode: 'モード', + authenticating: '鍵', + publickey: '鍵', + belong: '属する', + local: '地元', + session: 'セッション|セッション', + loginTime: 'ログイン時間', + loginIP: 'ログインIP', + stopSSHWarn: 'このSSH接続を切断するかどうか', + }, + setting: { + panel: 'パネル', + user: 'パネルユーザー', + userChange: 'パネルユーザーを変更します', + userChangeHelper: 'パネルユーザーを変更すると、ログアウトします。続く?', + passwd: 'パネルパスワード', + emailHelper: 'パスワード取得用', + watermark: '透かし設定', + watermarkContent: '透かし内容', + contentHelper: '{0} はノード名、{1} はノードアドレスを表します。変数を使用するか、カスタム名を入力できます。', + watermarkColor: '透かしの色', + watermarkFont: '透かしのフォントサイズ', + watermarkHeight: '透かしの高さ', + watermarkWidth: '透かしの幅', + watermarkRotate: '回転角度', + watermarkGap: '間隔', + watermarkCloseHelper: 'システムの透かし設定を無効にしてもよろしいですか?', + watermarkOpenHelper: '現在のシステム透かし設定を保存してもよろしいですか?', + title: 'パネルエイリアス', + panelPort: 'パネルポート', + titleHelper: '英語、漢字、数字、スペース、および一般的な特殊文字を含む3~30文字の長さをサポートします', + portHelper: + '推奨されるポート範囲は8888〜65535です。注:サーバーにセキュリティグループがある場合は、事前にセキュリティグループから新しいポートを許可します', + portChange: 'ポート変更', + portChangeHelper: 'サービスポートを変更し、サービスを再起動します。続けたいですか?', + theme: 'テーマ', + menuTabs: 'タブメニュー', + dark: '暗い', + darkGold: 'ダークゴールド', + light: 'ライト', + auto: 'システムをフォローします', + language: '言語', + languageHelper: + 'デフォルトでは、ブラウザ言語に従います。このパラメーターは、現在のブラウザでのみ有効になります', + sessionTimeout: 'セッションタイムアウト', + sessionTimeoutError: '最小セッションタイムアウトは300秒です', + sessionTimeoutHelper: '{0}秒以上操作がない場合、パネルは自動的にログアウトされます。', + systemIP: 'デフォルトアクセスアドレス', + systemIPHelper: + 'アプリケーションリダイレクト、コンテナアクセスなどの機能はこのアドレスを使用して転送されます。各ノードで異なるアドレスを設定できます。', + proxy: 'サーバープロキシ', + proxyHelper: 'プロキシサーバーを設定した後、次のシナリオで効果的になります。', + proxyHelper1: + 'アプリストアからのインストールパッケージのダウンロードと同期(プロフェッショナルエディションのみ)', + proxyHelper2: 'システムの更新と更新情報検索(プロフェッショナルエディションのみ)', + proxyHelper4: + 'Dockerネットワークはプロキシサーバーを通じてアクセスされます(プロフェッショナルエディションのみ)', + proxyHelper5: 'システムタイプスクリプトライブラリの統一下載と同期(プロフェッショナル版機能)', + proxyHelper6: '証明書を申請する(プロ版機能)', + proxyHelper3: 'システムライセンスの確認と同期', + proxyType: 'プロキシタイプ', + proxyUrl: 'プロキシアドレス', + proxyPort: 'プロキシポート', + proxyPasswdKeep: 'パスワードを覚えておいてください', + proxyDocker: 'Dockerプロキシ', + proxyDockerHelper: + 'プロキシサーバーの構成をDockerに同期し、オフラインサーバーイメージの引っ張りやその他の操作をサポートします', + syncToNode: '子ノードに同期', + syncToNodeHelper: '選択したノードへの同期設定', + nodes: 'ノード', + selectNode: 'ノードを選択', + selectNodeError: 'ノードを選択してください', + apiInterface: 'APIを有効にします', + apiInterfaceClose: '閉じたら、APIインターフェイスにアクセスできません。続けたいですか?', + apiInterfaceHelper: 'サードパーティのアプリケーションにAPIにアクセスできるようにします。', + apiInterfaceAlert1: `サーバーのセキュリティリスクが増加する可能性があるため、生産環境で有効にしないでください。`, + apiInterfaceAlert2: `サードパーティのアプリケーションを使用してAPIを呼び出して、潜在的なセキュリティの脅威を防止しないでください。`, + apiInterfaceAlert3: 'APIドキュメント', + apiInterfaceAlert4: '使用ドキュメント', + apiKey: 'APIキー', + apiKeyHelper: 'APIキーは、サードパーティアプリケーションに使用されてAPIにアクセスします。', + ipWhiteList: 'IP AllowList', + ipWhiteListEgs: '1行に1つ。たとえば、 n172.161.10.111 n172.161.10.0/24', + ipWhiteListHelper: 'AllowList内のIPSはAPIにアクセスできます、0.0.0.0/0(すべての IPv4)、::/0(すべての IPv6)', + apiKeyReset: 'インターフェイスキーリセット', + apiKeyResetHelper: '関連するキーサービスは無効になります。サービスに新しいキーを追加してください', + confDockerProxy: 'Dockerプロキシを構成します', + restartNowHelper: 'Dockerプロキシの構成には、Dockerサービスを再起動する必要があります。', + restartNow: 'すぐに再起動します', + restartLater: '後で手動で再起動', + systemIPWarning: + '現在のノードにはデフォルトアクセスアドレスが設定されていません。パネル設定から設定してください!', + systemIPWarning1: `現在のサーバーアドレスは{0}に設定されており、クイックリダイレクトは不可能です!`, + defaultNetwork: 'ネットワークカード', + syncTime: 'サーバー時間', + timeZone: 'タイムゾーン', + timeZoneChangeHelper: 'タイムゾーンを変更するには、サービスを再起動する必要があります。続けたいですか?', + timeZoneHelper: + 'TimeZoneの変更は、システムのTimeDatectlサービスに依存します。1パネルサービスを再起動した後に有効になります。', + timeZoneCN: '北京', + timeZoneAM: 'ロサンゼルス', + timeZoneNY: 'ニューヨーク', + ntpALi: 'アリババ', + ntpGoogle: 'グーグル', + syncSite: 'NTPサーバー', + syncSiteHelper: 'この操作は、システム時間同期のソースとして{0}を使用します。続けたいですか?', + changePassword: 'パスワードを変更する', + oldPassword: '元のパスワード', + newPassword: '新しいパスワード', + retryPassword: 'パスワードを認証する', + noSpace: '入力情報にはスペース文字を含めることはできません', + duplicatePassword: '新しいパスワードは元のパスワードと同じになることはできません。再入力してください!', + diskClean: 'キャッシュクリーン', + developerMode: 'プレビュープログラム', + developerModeHelper: `それらが広くリリースされる前に、新しい機能と修正を体験し、早期のフィードバックを提供できます。`, + thirdParty: 'サードパーティのアカウント', + noTypeForCreate: '現在、バックアップタイプは作成されていません', + LOCAL: 'サーバーディスク', + OSS: 'アリ私たち', + S3: 'amazonS3', + mode: 'モード', + MINIO: 'ミニオ', + SFTP: 'sftp', + WebDAV: 'webdav', + WebDAVAlist: 'WebDav Connect Alistは、公式ドキュメントを参照できます', + OneDrive: 'Microsoft Onedrive', + isCN: '世紀のインターネット', + isNotCN: '国際版', + client_id: 'クライアントID', + client_secret: 'クライアントの秘密', + redirect_uri: 'URLをリダイレクトします', + onedrive_helper: 'カスタム構成は公式ドキュメントで参照できます', + refreshTime: 'トークン更新時間', + refreshStatus: 'トークン更新ステータス', + backupDir: 'バックアップディレクトリ', + codeWarning: '現在の承認コード形式が正しくありません。もう一度確認してください!', + code: '認証コード', + codeHelper: + '[取得]ボタンをクリックしてから、リダイレクトリンクの「コード」の後にコンテンツをログインしてコピーします。この入力ボックスに貼り付けます。特定の手順については、公式のドキュメントを参照してください。', + googleHelper: + 'まずGoogleアプリケーションを作成し、クライアント情報を取得してフォームに記入し、取得ボタンをクリックしてください。具体的な操作は公式ドキュメントを参照してください。', + loadCode: '取得する', + COS: 'tencent cos', + ap_beijing_1: '北京ゾーン1', + ap_beijing: '北京', + ap_nanjing: '南京', + ap_shanghai: '上海', + ap_guangzhou: '広州', + ap_chengdu: '成都', + ap_chongqing: '唐辛子', + ap_shenzhen_fsi: '深圳金融', + ap_shanghai_fsi: '上海金融', + ap_beijing_fsi: '北京金融', + ap_hongkong: '香港、中国', + ap_singapore: 'シンガポール', + ap_mumbai: 'ムンバイ', + ap_jakarta: 'ジャカルタ', + ap_seoul: 'ソウル', + ap_bangkok: 'バンコク', + ap_tokyo: '東京', + na_siliconvalley: 'シリコンバレー(米国西)', + na_ashburn: 'アッシュバーン(米国東)', + na_toronto: 'トロント', + sa_saopaulo: 'サンパウロ', + eu_frankfurt: 'フランクフルト', + KODO: 'Qiniコード', + scType: 'ストレージタイプ', + typeStandard: '標準ストレージ', + typeStandard_IA: '低頻度ストレージ', + typeArchive: 'アーカイブストレージ', + typeDeep_Archive: '深層アーカイブストレージ', + scLighthouse: 'デフォルト:軽量オブジェクトストレージはこのストレージタイプのみサポートしています', + scStandard: + '標準ストレージは、リアルタイムアクセスが多いホットファイルや頻繁なデータ交換などの業務シナリオに適しています。', + scStandard_IA: + '低頻度ストレージは、アクセス頻度が低い業務シナリオ(例:月に1〜2回程度のアクセス)に適しており、最低30日間の保存が必要です。', + scArchive: 'アーカイブストレージは、極めて低いアクセス頻度(例:半年に1回程度)の業務シナリオに適しています。', + scDeep_Archive: + '深層アーカイブストレージは、極めて低いアクセス頻度(例:年に1〜2回程度)の業務シナリオに適しています。', + archiveHelper: + 'アーカイブストレージのファイルは直接ダウンロードできず、対応するクラウドサービスプロバイダーのサイトで復元操作を行う必要があります。慎重に使用してください。', + backupAlert: + '理論的には、クラウドプロバイダーがS3プロトコルに対応していれば、現行のAmazon S3クラウドストレージを使ってバックアップできます。具体的な設定については、', + domain: 'ドメインを加速します', + backupAccount: 'バックアップアカウント|バックアップアカウント', + loadBucket: 'バケツを入手してください', + accountName: 'アカウント名', + accountKey: 'アカウントキー', + address: '住所', + path: 'パス', + + safe: '安全', + passkey: 'パスキー', + passkeyManage: '管理', + passkeyHelper: '素早くログインするため、最大 5 個のパスキーを登録できます', + passkeyRequireSSL: 'HTTPS を有効にするとパスキーを使用できます', + passkeyNotSupported: '現在のブラウザまたは環境はパスキーに対応していません', + passkeyCount: '登録済み {0}/{1}', + passkeyName: '名称', + passkeyNameHelper: 'デバイスを区別する名称を入力してください', + passkeyAdd: 'パスキーを追加', + passkeyCreatedAt: '作成時間', + passkeyLastUsedAt: '最終使用', + passkeyDeleteConfirm: '削除するとこのパスキーでログインできなくなります。続行しますか?', + passkeyLimit: '最大 5 個のパスキーを登録できます', + passkeyFailed: 'パスキーの登録に失敗しました。パネルSSL証明書が信頼できる証明書であることを確認してください', + bindInfo: 'バインド情報', + bindAll: 'すべてを聞いてください', + bindInfoHelper: + 'サービスリスニングアドレスまたはプロトコルを変更すると、サービスが利用できない場合があります。続けたいですか?', + ipv6: 'IPv6をリッスン', + bindAddress: '住所を聞いてください', + entrance: '入り口', + showEntrance: '「概要」ページで無効なアラートを表示します', + entranceHelper: + 'セキュリティの入り口を有効にすると、指定されたセキュリティの入り口を介してパネルにログインするだけです。', + entranceError: + '5〜116文字の安全なログインエントリポイントを入力してください。数字または文字のみがサポートされています。', + entranceInputHelper: 'セキュリティの入り口を無効にするために空白のままにしてください。', + randomGenerate: 'ランダム', + expirationTime: '有効期限', + unSetting: '解き放つ', + noneSetting: + 'パネルパスワードの有効期限を設定します。有効期限が切れた後、パスワードをリセットする必要があります', + expirationHelper: 'パスワードの有効期限が[0]日の場合、パスワードの有効期限機能が無効になっています', + days: '有効期限', + expiredHelper: '現在のパスワードの有効期限が切れています。もう一度パスワードを変更してください。', + timeoutHelper: + '[{0}日]パネルパスワードの有効期限が切れようとしています。有効期限が切れた後、パスワードをリセットする必要があります', + complexity: '複雑さの検証', + complexityHelper: `有効にした後、パスワード検証ルールは次のとおりです。英語、数字、少なくとも2つの特殊文字を含む8〜30文字です。`, + bindDomain: 'バインドドメイン', + unBindDomain: 'バインドドメイン', + panelSSL: 'パネルSSL', + unBindDomainHelper: + 'ドメイン名をバインドするアクションは、システムの不安を引き起こす可能性があります。続けたいですか?', + bindDomainHelper: 'ドメインにバインドした後、そのドメインのみが1パネルサービスにアクセスできます。', + bindDomainHelper1: 'ドメイン名のバインディングを無効にするために空白のままにします。', + bindDomainWarning: + 'ドメインバインディングの後、ログアウトされ、設定で指定されたドメイン名を介して1パネルサービスのみにアクセスできます。続けたいですか?', + allowIPs: '承認されたIP', + unAllowIPs: '許可されていないIP', + unAllowIPsWarning: + '空のIPを許可すると、すべてのIPがシステムにアクセスできるようになり、システムの不安定性が発生する可能性があります。続けたいですか?', + allowIPsHelper: + '承認されたIPアドレスリストを設定した後、リスト内のIPアドレスのみがパネルサービスにアクセスできます。', + allowIPsWarning: + '承認されたIPアドレスリストを設定した後、リスト内のIPアドレスのみがパネルサービスにアクセスできます。続けたいですか?', + allowIPsHelper1: `IPアドレスの制限を無効にするために空白のままにします。`, + allowIPEgs: '1行に1つ。たとえば、 \n172.16.10.111 \n172.16.10.0/24', + mfa: '二因子認証(2FA)', + mfaClose: 'MFAを無効にすると、サービスのセキュリティが減少します。続けたいですか?', + secret: '秘密', + mfaInterval: '間隔を更新する', + mfaTitleHelper: + 'タイトルは、さまざまな1パネルホストを区別するために使用されます。タイトルを変更した後、再度スキャンするか、秘密のキーを手動で追加します。', + mfaIntervalHelper: '更新時間を変更した後、再度スキャンするか、秘密のキーを手動で追加します。', + mfaAlert: + '1回限りのトークンは、現在の時刻に基づいて動的に生成された6桁の数値です。サーバー時間が同期されていることを確認してください。', + mfaHelper: '有効にした後、1回限りのトークンを検証する必要があります。', + mfaHelper1: 'たとえば、Authenticatorアプリをダウンロードしてください。', + mfaHelper2: + '1回限りのトークンを取得するには、Authenticatorアプリを使用して次のQRコードをスキャンするか、Secretキーを認証アプリにコピーします。', + mfaHelper3: 'アプリから6桁を入力します', + mfaCode: '1回限りのトークン', + sslChangeHelper: 'HTTPS設定を変更し、サービスを再起動します。続けたいですか?', + sslDisable: '無効にします', + sslDisableHelper: + 'HTTPSサービスが無効になっている場合は、有効にするためにパネルを再起動する必要があります。続けたいですか?', + noAuthSetting: '不正な設定', + noAuthSettingHelper: + 'ユーザーが未ログインで、セキュリティ入口、認証IP、またはバインドされたドメイン名を正しく入力していない場合、このレスポンスでパネルの特徴を非表示にできます。', + responseSetting: '応答設定', + help200: 'ヘルプページ', + error400: '要求の形式が正しくありません', + error401: '不正', + error403: '禁断', + error404: '見つかりません', + error408: 'リクエストタイムアウト', + error416: '範囲は満足できません', + error444: '接続が閉じた', + error500: 'サーバーエラー', + + https: 'パネルにHTTPSを設定するとアクセスセキュリティが向上します', + strictHelper: 'HTTPS以外のトラフィックはパネルに接続できません', + muxHelper: + 'パネルはHTTPポートとHTTPSポートの両方を監視し、HTTPをHTTPSにリダイレクトしますが、セキュリティが低下する可能性があります', + certType: '証明書の種類', + selfSigned: '自己署名', + selfSignedHelper: `ブラウザは、自己署名の証明書を信頼していない場合があり、セキュリティ警告を表示する場合があります。`, + select: '選択します', + domainOrIP: 'ドメインまたはIP:', + timeOut: 'タイムアウト', + rootCrtDownload: 'ルート証明書のダウンロード', + primaryKey: '主キー', + certificate: '証明書', + backupJump: + 'バックアップファイルは現在のバックアップリストにありません。ファイルディレクトリからダウンロードして、バックアップ用にインポートしてみてください。', + + snapshot: 'スナップショット|スナップショット', + noAppData: '選択可能なシステムアプリはありません', + noBackupData: '選択可能なバックアップデータはありません', + stepBaseData: '基本データ', + stepAppData: 'システムアプリ', + stepPanelData: 'システムデータ', + stepBackupData: 'バックアップデータ', + stepOtherData: 'その他のデータ', + operationLog: '操作ログを保持', + loginLog: 'アクセスログを保持', + systemLog: 'システムログを保持', + taskLog: 'タスクログを保持', + monitorData: '監視データを保持', + dockerConf: 'Docker設定保持', + selectAllImage: 'すべてのアプリイメージをバックアップ', + logLabel: 'ログ', + agentLabel: 'ノード設定', + appDataLabel: 'アプリデータ', + appImage: 'アプリイメージ', + appBackup: 'アプリバックアップ', + backupLabel: 'バックアップディレクトリ', + confLabel: '設定ファイル', + dockerLabel: 'コンテナ', + taskLabel: 'スケジュールタスク', + resourceLabel: 'アプリリソースディレクトリ', + runtimeLabel: '実行環境', + appLabel: 'アプリ', + databaseLabel: 'データベース', + snapshotLabel: 'スナップショットファイル', + websiteLabel: 'ウェブサイト', + directoryLabel: 'ディレクトリ', + appStoreLabel: 'アプリストア', + shellLabel: 'スクリプト', + tmpLabel: '一時ディレクトリ', + sslLabel: '証明書ディレクトリ', + reCreate: 'スナップショットの作成に失敗しました', + reRollback: 'スナップショットのロールバックに失敗しました', + deleteHelper: + 'サードパーティのバックアップアカウントにあるものを含むすべてのスナップショットファイルが削除されます。続けたいですか?', + status: 'スナップショットステータス', + ignoreRule: 'ルールを無視します', + editIgnoreRule: '@:commons.button.edit @.lower:setting.ignorerule', + ignoreHelper: + 'このルールは、スナップショットの作成中に1パネルデータディレクトリを圧縮およびバックアップするために使用されます。デフォルトでは、ソケットファイルは無視されます。', + ignoreHelper1: '1行に1つ。たとえば、 n*.log n/opt/1panel/cache', + panelInfo: '1パネルの基本情報を書いてください', + panelBin: 'バックアップ1パネルシステムファイル', + daemonJson: 'バックアップDocker構成ファイル', + appData: 'バックアップ1パネルからアプリをインストールしました', + panelData: 'バックアップ1パネルデータディレクトリ', + backupData: '1パネル用のバックアップローカルバックアップディレクトリ', + compress: 'スナップショットファイルを作成します', + upload: 'スナップショットファイルをアップロードします', + recoverDetail: '詳細を回復します', + createSnapshot: 'スナップショットを作成します', + importSnapshot: 'スナップショットを同期します', + lastRecoverAt: '最後の回復時間', + lastRollbackAt: '最後のロールバック時間', + reDownload: 'バックアップファイルをもう一度ダウンロードしてください', + recoverErrArch: `さまざまなサーバーアーキテクチャ間のスナップショット回復はサポートされていません!`, + recoverErrSize: + '不十分なディスクスペースが検出されました。チェックまたはクリーンアップして、再試行してください!', + recoverHelper: 'Snapshot {0}からの回復を開始して、先に進む前に次の情報を確認してください。', + recoverHelper1: '回復には、Dockerサービスと1パネルサービスを再起動する必要があります', + recoverHelper2: + 'サーバーに十分なディスクスペースがあることを確認してください(スナップショットファイルサイズ:{0}、利用可能なスペース:{1})', + recoverHelper3: + 'サーバーアーキテクチャが、スナップショットが作成されたサーバーのアーキテクチャと一致していることを確認してください(現在のサーバーアーキテクチャ:{0})', + rollback: 'ロールバック', + rollbackHelper: + 'この回復をロールバックすると、この回復からすべてのファイルを置き換え、Dockerサービスと1パネルサービスを再起動する必要がある場合があります。続けたいですか?', + + upgradeHelper: 'アップグレードには、1パネルサービスを再起動する必要があります。続けたいですか?', + rollbackLocalHelper: + 'マスターノードは直接ロールバックをサポートしていません。手動で「1pctl restore」コマンドを実行してロールバックしてください!', + noUpgrade: '現在、最新バージョンです', + upgradeNotes: 'リリースノート', + upgradeNow: '今すぐアップグレードしてください', + source: 'ソースをダウンロードします', + versionNotSame: + 'ノードのバージョンがメインノードと一致していません。ノード管理でアップグレードしてから再試行してください。', + versionCompare: + 'ノード {0} は既にアップグレード可能な最新バージョンです。マスターノードのバージョンを確認後、再試行してください!', + + about: 'について', + versionItem: '現在のバージョン', + backupCopies: '保持するバックアップ数', + backupCopiesHelper: + 'バージョンロールバック用に保持するアップグレードバックアップの数を設定します。0はすべて保持を意味します。', + backupCopiesRule: '少なくとも3つのアップグレードバックアップ記録を保持してください', + release: 'バージョン更新履歴', + releaseHelper: '現在の環境の更新履歴の取得に異常が発生しました。手動で公式ドキュメントを確認してください。', + project: 'GitHub', + issue: '問題', + doc: '公式文書', + star: '星', + description: 'Linuxサーバーパネル', + forum: '議論', + doc2: 'ドキュメント', + currentVersion: 'バージョン', + + license: 'ライセンス', + bindNode: 'ノードをバインド', + menuSetting: 'メニュー設定', + menuSettingHelper: 'サブメニューが1つしか存在しない場合、メニューバーにはそのサブメニューのみが表示されます', + showAll: 'すべてを表示します', + hideALL: 'すべてを隠します', + ifShow: '表示するかどうか', + menu: 'メニュー', + confirmMessage: 'ページは更新されて、高度なメニューリストを更新します。続く?', + recoverMessage: 'ページが更新され、メニューリストが初期状態に戻ります。続行しますか?', + compressPassword: '圧縮パスワード', + backupRecoverMessage: '圧縮または減圧パスワードを入力してください(設定しないように空白のままにしてください)', + }, + license: { + offLine: 'オフライン版', + community: '無料', + oss: '無料', + pro: '専門', + trial: '体験', + add: 'コミュニティ版を追加', + licenseBindHelper: 'ライセンスがノードにバインドされている場合にのみ、無料ノードクォータを使用できます', + licenseAlert: + 'ライセンスがノードに正常にバインドされている場合のみ、コミュニティ版ノードを追加できます。ライセンスに正常にバインドされているノードのみ切り替えがサポートされます。', + licenseUnbindHelper: + 'このライセンスにコミュニティ版ノードが存在します。バインドを解除してから再試行してください!', + subscription: 'サブスクリプション', + perpetual: '永久ライセンス', + versionConstraint: '{0} バージョン買い取り', + forceUnbind: '強制バインド解除', + forceUnbindHelper: + '強制的にバインド解除を行うと、解除プロセス中に発生するエラーを無視し、最終的にライセンスのバインドを解除します。', + updateForce: '強制更新(アンバインド中のすべてのエラーを無視し、最終操作の成功を保証します)', + trialInfo: 'バージョン', + authorizationId: 'サブスクリプション承認ID', + authorizedUser: '認定ユーザー', + lostHelper: + 'ライセンスは、再試行の最大数に達しました。プロのバージョン機能が適切に機能していることを確認するには、手動で同期ボタンをクリックしてください。세부사항: ', + disableHelper: + 'ライセンスの同期の検証は失敗しました。プロのバージョン機能が適切に機能していることを確認するには、手動で同期ボタンをクリックしてください。세부사항: ', + quickUpdate: 'クイックアップデート', + power: '許可', + unbindHelper: 'すべてのPro関連設定は、バインディングを解除した後にクリーニングされます。続けたいですか?', + importLicense: 'ライセンス', + importHelper: 'ここでライセンスファイルをクリックまたはドラッグしてください', + technicalAdvice: '技術的な相談', + advice: '相談', + levelUpPro: 'Proにアップグレードします', + licenseSync: 'ライセンス同期', + knowMorePro: 'もっと詳しく知る', + closeAlert: '現在のページはパネル設定で閉じることができます', + introduce: '機能の紹介', + waf: 'プロフェッショナルバージョンにアップグレードすると、インターセプトマップ、ログ、ブロックレコード、地理的位置ブロッキング、カスタムルール、カスタムインターセプトページなどの機能を提供できます。', + tamper: 'プロのバージョンにアップグレードすると、不正な変更や改ざんからWebサイトを保護できます。', + setting: + 'プロのバージョンにアップグレードすることで、パネルロゴ、ウェルカムメッセージ、その他の情報のカスタマイズが可能になります。', + monitor: + 'プロのバージョンにアップグレードして、Webサイトのリアルタイムステータス、訪問者の傾向、訪問者ソース、リクエストログ、その他の情報を表示します。', + alert: 'プロのバージョンにアップグレードして、SMSを介してアラーム情報を受信し、アラームログを表示し、さまざまなキーイベントを完全に制御し、心配のないシステム操作を確実にする', + app: 'モバイルアプリでサービス情報、異常監視などを表示するには、プロフェッショナル版にアップグレードしてください。', + node: 'プロフェッショナル版にアップグレードすると、1Panel で複数の Linux サーバーを管理できます。', + nodeApp: + 'プロフェッショナル版にアップグレードすると、ノードを手動で切り替えることなく、マルチノードアプリケーションのバージョンを一括アップグレードできます。', + nodeDashboard: + 'プロフェッショナル版にアップグレードすると、マルチノードのアプリケーション、ウェブサイト、データベース、スケジュールタスクを集中管理できます。', + fileExchange: 'プロフェッショナル版にアップグレードすると、複数のサーバー間でファイルを迅速に転送できます。', + cluster: + 'プロフェッショナル版にアップグレードすると、MySQL/Postgres/Redisマスタースレーブクラスタを管理できます。', + }, + clean: { + scan: 'スキャンを開始します', + scanHelper: '1パネルのランタイム中に作成されたジャンクファイルを簡単にクリーンアップできます', + clean: '今すぐきれいにします', + reScan: 'レスカン', + cleanHelper: '選択されたファイルおよびディレクトリは、クリーンアップ後にロールバックできません。続行しますか?', + statusSuggest: '(推奨クリーニング)', + statusClean: '(とてもきれい)', + statusEmpty: 'とてもきれいで、掃除は必要ありません!', + statusWarning: '(注意して進めます)', + lastCleanTime: '最後にクリーニング:{0}', + lastCleanHelper: 'ファイルとディレクトリのクリーニング:{0}、合計クリーニング:{1}', + cleanSuccessful: '正常に掃除', + currentCleanHelper: 'このセッションでクリーニングされたファイルとディレクトリ:{0}、合計クリーニング:{1}', + suggest: '(推奨)', + totalScan: 'クリーニングするジャンクファイルの合計:', + selectScan: '選択したジャンクファイルの合計:', + + system: 'システムジャンクファイル', + systemHelper: 'スナップショット、アップグレードなどのプロセスで生成された一時ファイル', + panelOriginal: 'システムスナップショット復元前のバックアップファイル', + upgrade: 'システムアップグレードのバックアップファイル', + agentPackages: '履歴バージョンの子ノードアップグレード/インストールパッケージ', + upgradeHelper: '(システムロールバックのために最新のアップグレードバックアップを保持することをお勧めします)', + rollback: '回復する前にファイルをバックアップします', + + backup: 'システムバックアップ', + backupHelper: 'ローカルバックアップアカウントに関連付けられていないバックアップファイル', + unknownBackup: 'システムバックアップ', + tmpBackup: '一時バックアップ', + unknownApp: '関連付けられていないアプリバックアップ', + unknownDatabase: '関連付けられていないデータベースバックアップ', + unknownWebsite: '関連付けられていないWebサイトバックアップ', + unknownSnapshot: '関連付けられていないスナップショットバックアップ', + + upload: '一時的なアップロードファイル', + uploadHelper: 'システムバックアップリストからアップロードされた一時ファイル', + download: '一時的なダウンロードファイル', + downloadHelper: 'システムによってサードパーティのバックアップアカウントからダウンロードされた一時ファイル', + directory: 'ディレクトリ', + + systemLog: 'ログファイル', + systemLogHelper: 'システムログ、タスクログ、ウェブサイトログファイル', + dockerLog: 'コンテナ操作ログファイル', + taskLog: 'スケジュールタスク実行ログファイル', + shell: 'シェルスクリプトスケジュールタスク', + containerShell: 'コンテナ内で実行するシェルスクリプトスケジュールタスク', + curl: 'CURLスケジュールタスク', + + docker: 'コンテナゴミ', + dockerHelper: 'コンテナ、イメージ、ボリューム、ビルドキャッシュなどのファイル', + volumes: 'ボリューム', + buildCache: 'コンテナビルドキャッシュ', + + appTmpDownload: 'Archivo de descarga temporal de la aplicación', + unknownWebsiteLog: '未関連ウェブサイトログバックアップファイル', + }, + app: { + app: 'アプリケーション|アプリケーション', + installName: '名前', + installed: 'インストール', + all: '全て', + version: 'バージョン', + detail: '詳細', + params: '編集', + author: '著者', + source: 'ソース', + appName: 'アプリケーション名', + deleteWarn: + '削除操作は、すべてのデータとバックアップを一緒に削除します。この操作はロールバックすることはできません。続けたいですか?', + syncSuccess: '正常に同期しました', + canUpgrade: '更新', + backupName: 'ファイル名', + backupPath: 'ファイルパス', + backupdate: 'バックアップ時間', + versionSelect: 'バージョンを選択してください', + operatorHelper: '操作{0}は、選択したアプリケーションで実行されます。続けたいですか?', + startOperatorHelper: 'アプリケーションが開始されます。続けたいですか?', + stopOperatorHelper: 'アプリケーションは停止します。続けたいですか?', + restartOperatorHelper: 'アプリケーションが再起動されます。続けたいですか?', + reloadOperatorHelper: 'アプリケーションはリロードされます。続けたいですか?', + checkInstalledWarn: `「{0}」が検出されませんでした。「アプリストア」に移動してインストールしてください。`, + limitHelper: 'アプリケーションはすでにインストールされています。', + deleteHelper: `「{0}」は、次のリソースに関連付けられています。チェックしてもう一度やり直してください!`, + checkTitle: 'ヒント', + defaultConfig: 'デフォルトの構成', + defaultConfigHelper: 'デフォルトの構成に復元されており、保存後に有効になります', + forceDelete: 'フォース削除', + forceDeleteHelper: 'フォース削除は、削除プロセス中のエラーを無視し、最終的にメタデータを削除します。', + deleteBackup: 'バックアップを削除します', + deleteBackupHelper: 'また、アプリケーションのバックアップを削除します', + deleteDB: 'データベースを削除します', + deleteDBHelper: 'データベースも削除します', + noService: 'いいえ{0}', + toInstall: 'インストールに移動します', + param: 'パラメーター', + alreadyRun: '年', + syncAppList: '同期', + less1Minute: '1分未満', + appOfficeWebsite: 'オフィスのウェブサイト', + github: 'Github', + document: '書類', + updatePrompt: '更新はありません', + installPrompt: 'まだインストールされていません', + updateHelper: + 'パラメーターの編集により、アプリケーションが開始されない場合があります。注意して進めてください。', + updateWarn: '更新パラメータアプリケーションを再構築する必要がありますが、続行しますか?', + busPort: 'ポート', + syncStart: '同期を開始します!後でアプリストアを更新してください', + advanced: '高度な設定', + cpuCore: 'コア', + containerName: 'コンテナ名', + containerNameHelper: 'コンテナ名は設定されていないときに自動的に生成されます', + allowPort: '外部アクセス', + allowPortHelper: '外部ポートアクセスを許可すると、ファイアウォールポートがリリースされます', + appInstallWarn: `アプリケーションは、デフォルトで外部アクセスポートを公開しません。[Advanced Settings]をクリックして公開します。`, + upgradeStart: 'アップグレードを起動します!後でページを更新してください', + toFolder: 'インストールディレクトリを開きます', + editCompose: '編集ファイルを作成します', + editComposeHelper: 'Composeファイルを編集すると、ソフトウェアのインストールの障害が発生する可能性があります', + composeNullErr: '作曲は空にすることはできません', + takeDown: '降ろす', + allReadyInstalled: 'インストール', + installHelper: '画像プルの問題がある場合は、画像アクセラレーションを構成します。', + installWarn: `外部アクセスは有効になっていないため、アプリケーションが外部ネットワークを介してアクセスできるようになります。続けたいですか?`, + showIgnore: '無視されたアプリケーションを表示します', + cancelIgnore: 'キャンセルは無視します', + ignoreList: '無視されたアプリケーション', + appHelper: + 'アプリケーションの詳細ページにアクセスして、いくつかの特別なアプリケーションのインストール命令を学びます。', + backupApp: 'アップグレード前のバックアップアプリケーション', + backupAppHelper: + 'アップグレードが失敗した場合、バックアップは自動的にロールバックされます。ログ監査システムログの障害理由を確認してください。バックアップは、デフォルトで最新の3コピーを保持します', + openrestyDeleteHelper: 'OpenRestyの強制削除により、すべてのWebサイトが削除されます。続けたいですか?', + downloadLogHelper1: '{0}アプリケーションのすべてのログがダウンロードされようとしています。続けたいですか?', + downloadLogHelper2: '{0}アプリケーションの最新{1}ログはダウンロードされようとしています。続けたいですか?', + syncAllAppHelper: 'すべてのアプリケーションが同期されます。続けたいですか?', + hostModeHelper: + '現在のアプリケーションネットワークモードはホストモードです。ポートを開く必要がある場合は、ファイアウォールページで手動で開いてください。', + showLocal: 'ローカルアプリケーションを表示します', + reload: 'リロード', + upgradeWarn: + 'アプリケーションのアップグレードは、docker-compose.ymlファイルを置き換えます。変更がある場合は、クリックしてファイルの比較を表示できます', + newVersion: '新しいバージョン', + oldVersion: '現在のバージョン', + composeDiff: 'ファイルの比較', + showDiff: '比較を表示します', + useNew: 'カスタムバージョンを使用します', + useDefault: 'デフォルトバージョンを使用します', + useCustom: 'docker-compose.ymlをカスタマイズします', + useCustomHelper: `カスタムdocker-compose.ymlファイルを使用すると、アプリケーションのアップグレードが失敗する場合があります。必要でない場合は、確認しないでください。`, + diffHelper: + '左側は古いバージョンで、右側は新しいバージョンです。編集後、クリックしてカスタムバージョンを保存します', + pullImage: '画像を引っ張ります', + pullImageHelper: 'アプリケーションが開始する前に、Docker Pullを実行して画像をプルします', + deleteImage: 'イメージを削除', + deleteImageHelper: 'アプリ関連のイメージを削除します。削除に失敗してもタスクは終了しません。', + requireMemory: 'メモリ要件', + supportedArchitectures: '対応アーキテクチャ', + link: 'リンク', + showCurrentArch: '現在のサーバーアーキテクチャのアプリケーション', + syncLocalApp: 'ローカルアプリの同期', + memoryRequiredHelper: '現在のアプリケーションは {0} メモリが必要です', + gpuConfig: 'GPUサポートを有効化', + gpuConfigHelper: + 'マシンにNVIDIA GPUが搭載され、NVIDIAドライバーとNVIDIA Docker Container Toolkitがインストールされていることを確認してください', + webUI: 'Webアクセスアドレス', + webUIPlaceholder: '例:example.com:8080/login', + defaultWebDomain: 'デフォルトアクセスアドレス', + defaultWebDomainHepler: + 'アプリケーションポートが8080の場合、アドレスはhttp(s)://デフォルトアクセスアドレス:8080にジャンプします', + webUIConfig: + '現在のノードにはデフォルトアクセスアドレスが設定されていません。アプリケーションパラメータで設定するか、パネル設定から設定してください!', + toLink: 'ジャンプ', + customAppHelper: + 'カスタムアプリストアパッケージをインストールする前に、インストールされているアプリがないことを確認してください。', + forceUninstall: '強制アンインストール', + syncCustomApp: 'カスタムアプリを同期', + ignoreAll: '後続のすべてのバージョンを無視', + ignoreVersion: '指定されたバージョンを無視', + specifyIP: 'ホスト IP をバインド', + specifyIPHelper: + 'ポートにバインドするホストアドレス/ネットワークインターフェースを設定します(この機能がわからない場合は、入力しないでください)', + uninstallDeleteBackup: 'アプリをアンインストール - バックアップを削除', + uninstallDeleteImage: 'アプリをアンインストール - イメージを削除', + upgradeBackup: 'アプリのアップグレード前にアプリをバックアップ', + noAppHelper: 'アプリケーションが検出されませんでした。タスクセンターでアプリストアの同期ログを確認してください', + isEdirWarn: 'docker-compose.yml ファイルが変更されたことを検出しました。比較を確認してください', + }, + website: { + primaryDomain: 'プライマリドメイン', + otherDomains: '他のドメイン', + static: '静的', + deployment: '展開', + supportUpType: + '.tar.gz ファイル形式のみサポートされており、圧縮パッケージには {0}.json ファイルが含まれている必要があります', + proxy: '逆プロキシ', + alias: 'エイリアス', + ftpUser: 'FTPアカウント', + ftpPassword: 'FTPパスワード', + ftpHelper: + 'Webサイトを作成すると、対応するFTPアカウントが作成され、FTPディレクトリがWebサイトディレクトリにリンクされます。', + remark: '述べる', + manageGroup: 'グループを管理します', + groupSetting: 'グループ管理', + createGroup: 'グループを作成します', + appNew: '新しいアプリケーション', + appInstalled: 'インストールされたアプリケーション', + create: 'Webサイトを作成します', + delete: 'Webサイトを削除します', + deleteApp: 'アプリケーションを削除します', + deleteBackup: 'バックアップを削除します', + domain: 'ドメイン', + domainHelper: + '1行ごとに1つのドメイン.\nワイルドカード「*」とIPアドレスをサポートします.\nポートの追加をサポートします.', + addDomain: '追加', + domainConfig: 'ドメイン', + defaultDoc: '書類', + perserver: '並行性', + perserverHelper: '現在のサイトの最大並行性を制限します', + perip: '単一のIP', + peripHelper: '単一のIPへの同時アクセスの最大数を制限する', + rate: '交通制限', + rateHelper: '各リクエストのフローを制限する(ユニット:KB)', + limitHelper: 'フロー制御を有効にします', + other: '他の', + currentSSL: '現在の証明書', + dnsAccount: 'DNSアカウント', + applySSL: '証明書申請', + SSLList: '証明書リスト', + createDnsAccount: 'DNSアカウント', + aliyun: 'エイリアン', + aliEsa: 'エイリアン ESA', + awsRoute53: 'Amazon Route 53', + manual: '手動解析', + key: '鍵', + check: 'ビュー', + acmeAccountManage: 'ACMEアカウント', + email: 'メール', + acmeAccount: 'ACMEアカウント', + provider: '検証方法', + dnsManual: '手動解決', + expireDate: '有効期限', + brand: '組織', + deploySSL: '展開', + deploySSLHelper: '証明書を展開しますか?', + ssl: '証明書|証明書', + dnsAccountManage: 'DNSプロバイダー', + renewSSL: '更新します', + renewHelper: '必ず証明書を更新しますか?', + renewSuccess: '更新証明書', + enableHTTPS: '有効にする', + aliasHelper: 'エイリアスは、ウェブサイトのディレクトリ名です', + lastBackupAt: '最後のバックアップ時間', + null: 'なし', + nginxConfig: 'nginx構成', + websiteConfig: 'ウェブサイトの設定', + proxySettings: 'プロキシ設定', + advancedSettings: '詳細設定', + cacheSettings: 'キャッシュ設定', + sniSettings: 'SNI設定', + basic: '基本', + source: '構成', + security: '安全', + nginxPer: 'パフォーマンスチューニング', + neverExpire: '一度もない', + setDefault: 'デフォルトとして設定します', + default: 'デフォルト', + deleteHelper: '関連するアプリケーションステータスは異常です。確認してください', + toApp: 'インストールされているリストに移動します', + cycle: 'サイクル', + frequency: '頻度', + ccHelper: '同じURLを{0}秒以内に{1}を超えて同じURLを要求し、CC防御をトリガーし、このIPをブロックする', + mustSave: '有効にするには、変更を保存する必要があります', + fileExt: 'ファイル拡張子', + fileExtBlock: 'ファイル拡張ブロックリスト', + value: '価値', + enable: '有効にする', + proxyAddress: 'プロキシアドレス', + proxyHelper: '例:127.0.0.1:8080', + forceDelete: 'フォース削除', + forceDeleteHelper: 'フォース削除は、削除プロセス中のエラーを無視し、最終的にメタデータを削除します。', + deleteAppHelper: '関連するアプリケーションとアプリケーションのバックアップを同時に削除する', + deleteBackupHelper: 'また、Webサイトのバックアップを削除します。', + deleteDatabaseHelper: 'ウェブサイトに関連付けられたデータベースも削除します', + deleteConfirmHelper: + '削除操作を元に戻すことはできません。 "{0}" を入力して、削除を確認します。', + staticPath: '対応するメインディレクトリはです', + limit: 'スキーム', + blog: 'フォーラム/ブログ', + imageSite: '写真サイト', + downloadSite: 'サイトをダウンロードします', + shopSite: 'モール', + doorSite: 'ポータル', + qiteSite: '企業', + videoSite: 'ビデオ', + errLog: 'エラーログ', + stopHelper: + 'サイトを停止した後、正常にアクセスできなくなり、ユーザーは訪問時に現在のサイトの停止ページを表示します。続けたいですか?', + startHelper: 'サイトを有効にした後、ユーザーは通常、サイトのコンテンツにアクセスできますが、続行しますか?', + sitePath: 'ディレクトリ', + siteAlias: 'サイトエイリアス', + primaryPath: 'ルートディレクトリ', + folderTitle: 'ウェブサイトには主に次のフォルダーが含まれています', + wafFolder: 'ファイアウォールルール', + indexFolder: 'ウェブサイトルートディレクトリ', + sslFolder: 'ウェブサイト証明書', + enableOrNot: '有効にする', + oldSSL: '既存の証明書', + manualSSL: '輸入証明書', + select: '選択します', + selectSSL: '選択証明書を選択します', + privateKey: 'キー(キー)', + certificate: '証明書(PEM形式)', + HTTPConfig: 'HTTPオプション', + HTTPSOnly: 'HTTP要求をブロックします', + HTTPToHTTPS: 'HTTPSにリダイレクトします', + HTTPAlso: '直接HTTPリクエストを許可します', + sslConfig: 'SSLオプション', + disableHTTPS: 'httpsを無効にします', + disableHTTPSHelper: 'HTTPSを無効にすると、証明書関連の構成が削除されますが、続行しますか?', + SSLHelper: + '注:違法なWebサイトにSSL証明書を使用しないでください。 nif httpsアクセスを開いた後に使用できません。.', + SSLConfig: '証明書設定', + SSLProConfig: 'プロトコル設定', + supportProtocol: 'プロトコルバージョン', + encryptionAlgorithm: '暗号化アルゴリズム', + notSecurity: '(安全ではない)', + encryptHelper: + "暗号化しようと証明書を発行するための周波数制限がありますが、通常のニーズを満たすには十分です。頻繁に操作すると、発行の失敗が発生します。特定の制限については、参照してください公式文書", + ipValue: '価値', + ext: 'ファイル拡張子', + wafInputHelper: 'ラインごとに入力します。1つの行', + data: 'データ', + ever: '永続', + nextYear: '1年後', + noLog: 'ログは見つかりません', + defaultServer: 'デフォルトサイト', + noDefaultServer: '設定されていません', + defaultServerHelper: + 'デフォルトサイトを設定した後、すべての未バインドのドメイン名とIPはデフォルトサイトにリダイレクトされます\nこれにより、悪意のある解決を効果的に防ぐことができます\nただし、WAFの未承認ドメイン名の遮断が失敗することもあります', + restoreHelper: 'このバックアップを使用して復元することは間違いありませんか?', + websiteDeploymentHelper: + 'インストールされたアプリケーションを使用するか、新しいアプリケーションを作成してWebサイトを作成します。', + websiteStatictHelper: 'ホストにWebサイトディレクトリを作成します。', + websiteProxyHelper: + 'リバースプロキシを使用して、既存のサービスをプロキシします。たとえば、サービスがインストールされ、ポート8080で実行されている場合、プロキシアドレスは「http://127.0.0.1:8080」になります。', + runtimeProxyHelper: 'Webサイトランタイムを使用してWebサイトを作成します。', + runtime: 'ランタイム', + deleteRuntimeHelper: + 'ランタイムアプリケーションはWebサイトと一緒に削除する必要があります。注意して処理してください', + proxyType: 'ネットワークタイプ', + unix: 'UNIXネットワーク', + tcp: 'TCP/IPネットワーク', + phpFPM: 'fpm config', + phpConfig: 'php config', + updateConfig: 'configを更新します', + isOn: 'の上', + isOff: 'オフ', + rewrite: '擬似静的', + rewriteMode: 'スキーム', + current: '現在', + rewriteHelper: + '擬似静的に設定すると、ウェブサイトがアクセスできない場合、デフォルトの設定に戻るようにしてください。', + runDir: 'ディレクトリを実行します', + runUserHelper: + 'PHPコンテナランタイム環境を介して展開されているWebサイトの場合、インデックスおよびサブディレクトリのすべてのファイルとフォルダーの所有者とユーザーグループを1000に設定する必要があります。ローカルPHP環境については、ローカルPHP-FPMユーザーとユーザーグループの設定を参照してください。', + userGroup: 'ユーザー/グループ', + uGroup: 'グループ', + proxyPath: 'プロキシパス', + proxyPass: 'ターゲットURL', + cache: 'キャッシュ', + cacheTime: 'キャッシュ期間', + enableCache: 'キャッシュ', + proxyHost: 'プロキシホスト', + disabled: '停止', + startProxy: 'これにより、リバースプロキシが開始されます。続けたいですか?', + stopProxy: 'これにより、逆プロキシが停止します。続けたいですか?', + sourceFile: 'ソース', + proxyHelper1: 'このディレクトリにアクセスすると、ターゲットURLのコンテンツが返されて表示されます。', + proxyPassHelper: 'ターゲットURLは有効でアクセス可能でなければなりません。', + proxyHostHelper: 'リクエストヘッダーのドメイン名をプロキシサーバーに渡します。', + replacementHelper: '最大5つの交換を追加できます。交換が不要な場合は、空白のままにしてください。', + modifier: '一致するルール', + modifierHelper: '例: "="は正確な一致、 "〜"は通常の一致、^〜」はパスの始まりなどと一致します。', + replace: 'テキスト置換', + replaceHelper: + 'nginxのテキスト置換機能は、リバースプロキシ時にレスポンス内容の文字列を置換することができます。バックエンドから返されるHTML、CSS、JavaScriptなどのファイル内のリンクアドレス、APIアドレスなどを変更するためによく使用されます。正規表現マッチングをサポートしており、複雑なコンテンツ置換のニーズに対応できます。', + addReplace: '追加', + replaced: '検索文字列(空にすることはできません)', + replaceText: '文字列に置き換えます', + replacedErr: '検索文字列を空にすることはできません', + replacedErr2: '検索文字列を繰り返すことはできません', + replacedListEmpty: 'テキスト置換ルールがありません', + proxySslName: 'プロキシSNI名', + basicAuth: '基本認証', + editBasicAuthHelper: + 'パスワードは非対称的に暗号化されており、反響することはできません。編集はパスワードをリセットする必要があります', + antiLeech: '反リーチ', + extends: '拡大', + browserCache: 'キャッシュ', + noModify: '変更しない', + serverCache: 'サーバーキャッシュ', + leechLog: '反リーチログを記録します', + accessDomain: '許可されたドメイン', + leechReturn: '応答リソース', + noneRef: '空のリファラーを許可します', + disable: '有効になっていません', + disableLeechHelper: '反リーチを無効にするかどうか', + disableLeech: '反リーチを無効にします', + ipv6: '緑', + leechReturnError: 'HTTPステータスコードを入力してください', + blockedRef: '非標準のリファラーを許可', + accessControl: '反リーチ制御', + leechcacheControl: 'キャッシュ制御', + logEnableControl: '静的アセットのリクエストを記録', + leechSpecialValidHelper: + '「空のリファラーを許可」を有効にすると、リファラーのないリクエスト(直接アクセス等)はブロックされません。「非標準のリファラーを許可」を有効にすると、http/httpsで始まらないリファラー(クライアントからのリクエスト等)をすべて許可します。', + leechInvalidReturnHelper: 'ブロック後に返すHTTPステータスコード', + leechlogControlHelper: + '静的アセットのリクエストを記録します。運用環境では過剰で無意味なログを避けるため、通常は無効にします', + selectAcme: 'ACMEアカウントを選択します', + imported: '手動で作成されます', + importType: 'インポートタイプ', + pasteSSL: 'コードを貼り付けます', + localSSL: 'サーバーファイルを選択します', + privateKeyPath: '秘密キーファイル', + certificatePath: '証明書ファイル', + ipWhiteListHelper: 'IP AllowListの役割:すべてのルールはIP AllowListに対して無効です', + redirect: 'リダイレクト', + sourceDomain: 'ソースドメイン', + targetURL: 'ターゲットURLアドレス', + keepPath: 'uri params', + path: 'パス', + redirectType: 'リダイレクトタイプ', + redirectWay: '方法', + keep: '保つ', + notKeep: '保持しないでください', + redirectRoot: 'ホームページにリダイレクトします', + redirectHelper: '301永久リダイレクト、302一時的なリダイレクト', + changePHPVersionWarn: + 'PHPバージョンを切り替えると、元のPHPコンテナが削除されます(マウントされたWebサイトコードは失われません)、続行しますか?', + changeVersion: 'スイッチバージョン', + retainConfig: 'php-fpm.confとphp.iniファイルを保持するかどうか', + runDirHelper2: 'セカンダリランニングディレクトリがインデックスディレクトリの下にあることを確認してください', + openrestyHelper: 'OpenRestyデフォルトのHTTPポート:{0} HTTPSポート:{1}。', + primaryDomainHelper: '例:example.comまたはexample.com:8080', + acmeAccountType: 'アカウントタイプ', + keyType: 'キーアルゴリズム', + tencentCloud: 'テンセントクラウド', + containWarn: 'ドメイン名にはメインドメインが含まれています。再入力してください', + rewriteHelper2: + 'アプリストアからインストールされたWordPressなどのアプリケーションは、通常、擬似静的設定が事前に設定されています。それらを再設定すると、エラーが発生する可能性があります。', + websiteBackupWarn: + 'ローカルバックアップのインポートをサポートするだけで、他のマシンからバックアップをインポートすることは回復の故障を引き起こす可能性があります', + ipWebsiteWarn: + 'ドメイン名としてIPを持つWebサイトは、正常にアクセスするデフォルトサイトとして設定する必要があります。', + hstsHelper: 'HSTを有効にすると、Webサイトのセキュリティが向上する可能性があります', + includeSubDomains: 'サブドメイン', + hstsIncludeSubDomainsHelper: '有効化すると、HSTSポリシーが現在のドメインのすべてのサブドメインに適用されます。', + defaultHtml: 'デフォルトページ', + website404: 'ウェブサイト404エラーページ', + domain404: 'ウェブサイトドメインは存在しません', + indexHtml: '静的ウェブサイトのインデックス', + stopHtml: 'ウェブサイトを停止しました', + indexPHP: 'PHP Webサイトのインデックス', + sslExpireDate: '証明書の有効期限', + website404Helper: + 'ウェブサイト404エラーページは、PHPランタイム環境Webサイトと静的Webサイトのみをサポートしています', + sni: '起源は悲しい', + sniHelper: + '逆プロキシバックエンドがHTTPSの場合、Origin SNIを設定する必要がある場合があります。詳細については、CDNサービスプロバイダーのドキュメントを参照してください。', + huaweicloud: 'huaweiCloud', + createDb: 'データベースを作成', + enableSSLHelper: 'SSLの有効化に失敗しても、ウェブサイトの作成には影響しません。', + batchAdd: 'ドメインを一括追加', + batchInput: 'バッチ入力', + domainNotFQDN: 'このドメインはパブリックネットワークからアクセスできない可能性があります', + domainInvalid: 'ドメイン形式が正しくありません', + domainBatchHelper: '1行に1ドメイン、形式: domain:port@ssl\n例: example.com:443@ssl または example.com', + generateDomain: '生成', + global: 'グローバル', + subsite: 'サブサイト', + subsiteHelper: + 'サブサイトは、既存のPHPまたは静的ウェブサイトのディレクトリをルートディレクトリとして選択できます。', + parentWebsite: '親ウェブサイト', + deleteSubsite: '現在のウェブサイトを削除するには、まずサブサイト {0} を削除する必要があります。', + loadBalance: 'ロードバランシング', + server: 'ノード', + algorithm: 'アルゴリズム', + ipHash: 'IPハッシュ', + ipHashHelper: + 'クライアントのIPアドレスに基づいてリクエストを特定のサーバーに分散し、特定のクライアントが常に同じサーバーにルーティングされるようにします。', + leastConn: '最小接続', + leastConnHelper: 'アクティブな接続数が最も少ないサーバーにリクエストを送信します。', + leastTime: '最小時間', + leastTimeHelper: 'アクティブな接続時間が最も短いサーバーにリクエストを送信します。', + defaultHelper: + 'デフォルトの方法では、リクエストは各サーバーに均等に分散されます。サーバーに重み設定がある場合、指定された重みに従ってリクエストが分散されます。重みが高いサーバーほど多くのリクエストを受け取ります。', + weight: '重み', + maxFails: '最大失敗回数', + maxConns: '最大接続数', + strategy: '戦略', + strategyDown: '無効', + strategyBackup: 'バックアップ', + ipHashBackupErr: 'IPハッシュはバックアップノードをサポートしていません', + failTimeout: '障害タイムアウト', + failTimeoutHelper: + 'サーバーのヘルスチェックの時間ウィンドウの長さ。この期間内に累積障害回数がしきい値に達すると、サーバーは一時的に削除され、同じ時間経過後に再試行されます。デフォルト 10 秒', + + staticChangePHPHelper: '現在は静的ウェブサイトですが、PHPウェブサイトに切り替えることができます。', + proxyCache: 'リバースプロキシキャッシュ', + cacheLimit: 'キャッシュスペース制限', + shareCache: 'キャッシュカウントメモリサイズ', + cacheExpire: 'キャッシュ有効期限', + shareCacheHelper: '1Mのメモリで約8000個のキャッシュオブジェクトを保存できます。', + cacheLimitHelper: '制限を超えると、古いキャッシュが自動的に削除されます。', + cacheExpireHelper: '有効期限内にヒットしないキャッシュは削除されます。', + realIP: 'リアルIP', + ipFrom: 'IPソース', + ipFromHelper: + '信頼できるIPソースを設定することで、OpenRestyはHTTPヘッダーのIP情報を分析し、訪問者のリアルIPアドレスを正確に識別して記録します(アクセスログを含む)。', + ipFromExample1: 'フロントエンドがFrpなどのツールの場合、FrpのIPアドレス(例:127.0.0.1)を入力できます。', + ipFromExample2: 'フロントエンドがCDNの場合、CDNのIP範囲を入力できます。', + ipFromExample3: + '不明な場合は、0.0.0.0/0(IPv4)または::/0(IPv6)を入力できます。[注意:任意のソースIPを許可することは安全ではありません。]', + http3Helper: + 'HTTP/3はHTTP/2のアップグレード版で、より高速な接続速度とパフォーマンスを提供します。ただし、すべてのブラウザがHTTP/3をサポートしているわけではなく、有効にすると一部のブラウザがサイトにアクセスできなくなる可能性があります。', + cors: 'クロスオリジンリソース共有(CORS)', + enableCors: 'CORSを有効にする', + allowOrigins: '許可されたドメイン', + allowMethods: '許可されたリクエストメソッド', + allowHeaders: '許可されたリクエストヘッダー', + allowCredentials: 'Cookieの送信を許可する', + preflight: 'プリフライトリクエストの高速応答', + preflightHleper: + '有効にすると、ブラウザがクロスオリジンのプリフライトリクエスト(OPTIONSリクエスト)を送信した場合、システムは自動的に204ステータスコードを返し、必要なクロスオリジン応答ヘッダーを設定します', + + changeDatabase: 'データベースを切り替え', + changeDatabaseHelper1: 'データベースの関連付けは、ウェブサイトのバックアップと復元に使用されます。', + changeDatabaseHelper2: '別のデータベースに切り替えると、以前のバックアップが復元できなくなる可能性があります。', + saveCustom: 'テンプレートとして保存', + rainyun: '雨雲', + volcengine: 'volcengine', + runtimePortHelper: '現在の実行環境には複数のポートがあります。プロキシポートを選択してください。', + runtimePortWarn: '現在の実行環境にはポートがありません。プロキシできません', + cacheWarn: 'まずリバースプロキシのキャッシュスイッチをオフにしてください', + loadBalanceHelper: + '負荷分散を作成した後、「リバースプロキシ」に移動し、プロキシを追加してバックエンドアドレスを次のように設定してください:http://<負荷分散名>。', + favorite: 'お気に入り', + cancelFavorite: 'お気に入りを解除', + useProxy: 'プロキシを使用', + useProxyHelper: 'パネル設定のプロキシサーバーアドレスを使用', + westCN: '西部デジタル', + openBaseDir: 'クロスサイト攻撃を防ぐ', + openBaseDirHelper: + 'open_basedir は PHP ファイルのアクセスパスを制限し、クロスサイトアクセスを防ぎセキュリティを向上させるために使用されます', + serverCacheTime: 'サーバーキャッシュ時間', + serverCacheTimeHelper: + 'リクエストがサーバー上でキャッシュされる時間。この期間中、同一のリクエストはオリジンサーバーにリクエストせず、キャッシュされた結果を直接返します。', + browserCacheTime: 'ブラウザキャッシュ時間', + browserCacheTimeHelper: + '静的リソースがブラウザのローカルにキャッシュされる時間、冗長なリクエストを減らします。有効期限前にユーザーがページをリフレッシュすると、ローカルキャッシュが直接使用されます。', + donotLinkeDB: 'データベースをリンクしない', + toWebsiteDir: 'ウェブサイトディレクトリに入る', + execParameters: '実行パラメータ', + extCommand: '補足コマンド', + mirror: 'ミラーソース', + execUser: '実行ユーザー', + execDir: '実行ディレクトリ', + packagist: '中国フルミラー', + + batchOpreate: 'バッチ操作', + batchOpreateHelper: 'ウェブサイトをバッチ{0}しますか?', + stream: 'TCP/UDP プロキシ', + streamPorts: '待ち受けポート', + streamPortsHelper: + '外部リスニングポート番号を設定します。クライアントはこのポートを通じてサービスにアクセスします。カンマで区切ってください。例:5222,5223', + streamHelper: 'TCP/UDP ポート転送とロードバランシング', + udp: 'UDPを有効化', + + syncHtmlHelper: 'PHP と静的ウェブサイトに同期', + }, + php: { + short_open_tag: '短いタグサポート', + max_execution_time: '最大スクリプト実行時間', + max_input_time: '最大入力時間', + memory_limit: 'スクリプトメモリ制限', + post_max_size: 'データの最大サイズを投稿します', + file_uploads: 'ファイルのアップロードを許可するかどうか', + upload_max_filesize: 'ファイルのアップロードに許可されている最大サイズ', + max_file_uploads: '同時にアップロードできるファイルの最大数', + default_socket_timeout: 'ソケットタイムアウト', + error_reporting: 'エラーレベル', + display_errors: '詳細なエラー情報を出力するかどうか', + cgi_fix_pathinfo: 'Pathinfoを開くかどうか', + date_timezone: 'タイムゾーン', + disableFunction: '機能を無効にします', + disableFunctionHelper: 'execなど、無効にする関数を入力してください。複数、分割を使用してください', + uploadMaxSize: 'アップロード制限', + indexHelper: + 'PHP Webサイトの通常の操作を確保するために、コードをインデックスディレクトリに配置して、名前変更を避けてください', + extensions: '拡張テンプレート', + extension: '拡大', + extensionHelper: '複数の拡張機能を使用して、分割してください', + toExtensionsList: '拡張リストを表示します', + containerConfig: 'コンテナ設定', + containerConfigHelper: '環境変数などの情報は、作成完了後に設定 - コンテナ設定で変更できます', + dateTimezoneHelper: '例:TZ=Asia/Shanghai(必要に応じて追加してください)', + }, + nginx: { + serverNamesHashBucketSizeHelper: 'サーバー名のハッシュテーブルサイズ', + clientHeaderBufferSizeHelper: 'クライアントが要求するヘッダーバッファサイズ', + clientMaxBodySizeHelper: '最大アップロードファイル', + keepaliveTimeoutHelper: '接続タイムアウト', + gzipMinLengthHelper: '最小圧縮ファイル', + gzipCompLevelHelper: '圧縮率', + gzipHelper: '伝送の圧縮を有効にします', + connections: 'アクティブな接続', + accepts: '受け入れます', + handled: '処理', + requests: 'リクエスト', + reading: '読む', + writing: '書き込み', + waiting: '待っている', + status: '現在のステータス', + configResource: '構成', + saveAndReload: '保存してリロードします', + clearProxyCache: '逆プロキシキャッシュをきれいにします', + clearProxyCacheWarn: + 'キャッシュで構成されたすべてのWebサイトが影響を受け、「OpenResty」が再起動されます。続けたいですか?', + create: 'モジュールを追加', + update: 'モジュールを編集', + params: 'パラメータ', + packages: 'パッケージ', + script: 'スクリプト', + module: 'モジュール', + build: 'ビルド', + buildWarn: + 'OpenRestyのビルドには一定量のCPUとメモリを確保する必要があり、時間がかかる場合がありますので、お待ちください。', + mirrorUrl: 'ソフトウェアソース', + paramsHelper: '例:--add-module=/tmp/ngx_brotli', + packagesHelper: '例:git,curl カンマ区切り', + scriptHelper: + 'コンパイル前に実行するスクリプト、通常はモジュールソースコードのダウンロード、依存関係のインストールなど', + buildHelper: + 'モジュールの追加/変更後にビルドをクリックします。ビルドが成功すると、OpenRestyは自動的に再起動します。', + defaultHttps: 'HTTPS 改ざん防止', + defaultHttpsHelper1: 'これを有効にすると、HTTPS 改ざん問題を解決できます。', + sslRejectHandshake: 'デフォルト SSL ハンドシェイクを拒否', + sslRejectHandshakeHelper: + '有効にすると証明書の漏洩を防げますが、デフォルト Web サイトを設定するとこの設定は無効になります', + }, + ssl: { + create: 'リクエスト', + provider: 'タイプ', + manualCreate: '手動で作成されます', + acmeAccount: 'ACMEアカウント', + resolveDomain: 'ドメイン名を解決します', + err: 'エラー', + value: '記録値', + dnsResolveHelper: 'DNS Resolution Service Providerにアクセスして、次の解像度レコードを追加してください。', + detail: '詳細', + msg: '情報', + ssl: '証明書', + key: '秘密鍵', + startDate: '有効時間', + organization: '発行組織', + renewConfirm: 'これにより、ドメイン名{0}の新しい証明書が更新されます。続けたいですか?', + autoRenew: '自動更新', + autoRenewHelper: '有効期限の30日前に自動的に更新します', + renewSuccess: '更新成功', + renewWebsite: + 'この証明書は次のWebサイトに関連付けられており、アプリケーションはこれらのWebサイトに同時に適用されます', + createAcme: 'アカウントを作成する', + acmeHelper: 'ACMEアカウントは、無料の証明書を申請するために使用されます', + upload: '輸入', + applyType: 'タイプ', + apply: '更新します', + applyStart: '証明書申請が開始されます', + getDnsResolve: 'DNS解像度の値を取得してください、待ってください...', + selfSigned: '自己署名CA', + ca: '証明書当局', + commonName: '一般名', + caName: '証明書当局名', + company: '組織名', + department: '組織ユニット名', + city: 'ローカリティ名', + province: '州または州の名前', + country: '国名(2文字コード)', + commonNameHelper: '例えば、', + selfSign: '発行証明書', + days: '有効期間', + domainHelper: '1行ごとに1つのドメイン名、 *およびIPアドレスをサポートします', + pushDir: '証明書をローカルディレクトリにプッシュします', + dir: 'ディレクトリ', + pushDirHelper: '証明書ファイル「FullChain.PEM」とキーファイル「Privkey.Pem」がこのディレクトリで生成されます。', + organizationDetail: '組織の詳細', + fromWebsite: 'ウェブサイトから', + dnsMauanlHelper: '手動解像度モードでは、作成後に適用ボタンをクリックしてDNS解像度値を取得する必要があります', + httpHelper: + 'HTTPモードを使用するにはOpenRestyをインストールする必要があり、ワイルドカードドメイン証明書の申請はサポートされていません。', + buypassHelper: `中国本土では、ブライパスはアクセスできません`, + googleHelper: 'EAB HMACキーとEABキッドを取得する方法', + googleCloudHelper: `GoogleクラウドAPIは中国本土のほとんどの地域でアクセスできません`, + skipDNSCheck: 'DNSチェックをスキップします', + skipDNSCheckHelper: '認定リクエスト中にタイムアウトの問題が発生した場合にのみ、こちらを確認してください。', + cfHelper: 'グローバルAPIキーを使用しないでください', + deprecated: '非推奨されます', + deprecatedHelper: + 'メンテナンスは停止されており、将来のバージョンでは放棄される可能性があります。分析にはTencent Cloudメソッドを使用してください', + disableCNAME: 'cnameを無効にします', + disableCNAMEHelper: 'ドメイン名にCNAMEレコードがあり、リクエストが失敗するかどうかを確認してください。', + nameserver: 'DNSサーバー', + nameserverHelper: 'カスタムDNSサーバーを使用して、ドメイン名を確認します。', + edit: '編集証明書', + execShell: '認定リクエスト後にスクリプトを実行します。', + shell: 'スクリプトコンテンツ', + shellHelper: + 'スクリプトのデフォルトの実行ディレクトリは、1Panelインストールディレクトリです。証明書がローカルディレクトリにプッシュされた場合、実行ディレクトリは証明書プッシュディレクトリになります。デフォルトの実行タイムアウトは30分です。', + customAcme: 'カスタム ACME サービス', + customAcmeURL: 'ACME サービス URL', + baiduCloud: '百度クラウド', + pushNode: '他のノードに同期', + pushNodeHelper: '申請/更新後に選択したノードにプッシュ', + fromMaster: 'マスターノードからのプッシュ', + hostedZoneID: 'Hosted Zone ID', + isIP: 'IP証明書', + useEAB: 'EAB認証を使用', + }, + firewall: { + create: 'ルールを作成します', + edit: 'ルールを編集します', + ccDeny: 'CC保護', + ipWhiteList: 'IP AllowList', + ipBlockList: 'IPブロックリスト', + fileExtBlockList: 'ファイル拡張ブロックリスト', + urlWhiteList: 'URL AllowList', + urlBlockList: 'URLブロックリスト', + argsCheck: 'パラメーターチェックを取得します', + postCheck: 'パラメーターの検証を投稿します', + cookieBlockList: 'クッキーブロックリスト', + + dockerHelper: + '現在のファイアウォールではコンテナのポートマッピングを無効にできません。インストール済みアプリケーションは【インストール済み】ページでアプリケーションパラメータを編集し、ポート開放ルールを設定できます。', + iptablesHelper: + 'システムが {0} ファイアウォールを使用していることを検出しました。iptables に切り替えるには、まず手動でアンインストールしてください!', + quickJump: 'クイックアクセス', + used: '使用済み', + unUsed: '未使用', + dockerRestart: 'ファイアウォール操作にはDockerサービスの再起動が必要です', + firewallHelper: '{0}システムファイアウォール', + firewallNotStart: `現在、システムファイアウォールは有効になっていません。最初に有効にします。`, + restartFirewallHelper: 'この操作は、現在のファイアウォールを再起動します。続けたいですか?', + stopFirewallHelper: 'これにより、サーバーはセキュリティ保護を失います。続けたいですか?', + startFirewallHelper: + 'ファイアウォールが有効になった後、サーバーのセキュリティをよりよく保護できます。続けたいですか?', + noPing: 'pingを無効にします', + enableBanPing: 'Ping禁止', + disableBanPing: 'Ping禁止解除', + noPingTitle: 'pingを無効にします', + noPingHelper: `これによりPingが無効になり、サーバーはICMP応答をエコーし​​ません。続けたいですか?`, + onPingHelper: 'これによりPingが可能になり、ハッカーはサーバーを発見する場合があります。続けたいですか?', + changeStrategy: '{0}戦略を変更します', + changeStrategyIPHelper1: + 'IPアドレス戦略を[拒否]に変更します。IPアドレスが設定された後、サーバーへのアクセスは禁止されています。続けたいですか?', + changeStrategyIPHelper2: + 'IPアドレス戦略を[許可]に変更します。IPアドレスが設定された後、通常のアクセスが復元されます。続けたいですか?', + changeStrategyPortHelper1: + 'ポートポリシーを[ドロップ]に変更します。ポートポリシーが設定された後、外部アクセスが拒否されます。続けたいですか?', + changeStrategyPortHelper2: + 'ポートポリシーを[受け入れる]に変更します。ポートポリシーが設定されると、通常のポートアクセスが復元されます。続けたいですか?', + stop: '停止', + portFormatError: 'このフィールドは有効なポートでなければなりません。', + portHelper1: '複数のポート、例えば8080および8081', + portHelper2: '範囲ポート、例えば8080-8089', + changeStrategyHelper: + '[{1}] {0}戦略を[{2}]に変更します。設定後、{0}は外部から{2}にアクセスします。続けたいですか?', + + strategy: '戦略', + accept: '受け入れる', + drop: '落とす', + anyWhere: 'どれでも', + address: '指定されたIPS', + addressHelper: 'IPアドレスまたはIPセグメントをサポートします', + allow: '許可する', + deny: '拒否', + addressFormatError: 'このフィールドは有効なIPアドレスでなければなりません。', + addressHelper1: 'IPアドレスまたはIP範囲をサポートします。たとえば、「172.16.10.11」または「172.16.10.0/24」。', + addressHelper2: '複数のIPアドレスの場合、コンマと分離します。たとえば、「172.16.10.11、172.16.0.0/24」。', + allIP: 'すべてのIP', + portRule: 'ルール|ルール', + createPortRule: '@:commons.button.create @.lower:firewall.portrule', + forwardRule: 'ポートフォワードルール|ポートフォワードルール', + createForwardRule: '@:commons.button.create @:firewall.forwardrule', + ipRule: 'IPルール|IPルール', + createIpRule: '@:commons.button.create @:firewall.iprule', + userAgent: 'ユーザーエージェントフィルター', + sourcePort: 'ソースポート', + targetIP: '宛先IP', + targetPort: '宛先ポート', + forwardHelper1: 'ローカルポートに転送する場合は、宛先IPを「127.0.0.1」に設定する必要があります。', + forwardHelper2: '宛先IPを空白のままにして、ローカルポートに転送します。', + forwardPortHelper: 'ポート範囲をサポートします。例: 8080-8089', + forwardInboundInterface: '転送入站ネットワークインターフェース', + exportHelper: '{0} 件のファイアウォールルールをエクスポートします。続行しますか?', + importSuccess: '{0} 件のルールを正常にインポートしました', + importPartialSuccess: 'インポート完了: {0} 件成功、{1} 件失敗', + + ipv4Limit: '現在の操作は IPv4 アドレスのみをサポートしています', + basicStatus: '現在のチェーン {0} は未バインドです。まずバインドしてください!', + baseIptables: 'iptables サービス', + forwardIptables: 'iptables ポート転送サービス', + advanceIptables: 'iptables 高度な設定サービス', + initMsg: '{0} を初期化します。続行しますか?', + initHelper: + '{0} が初期化されていないことを検出しました。上部ステータスバーの初期化ボタンをクリックして設定してください!', + bindHelper: 'バインド - ファイアウォールルールは状態がバインドされている場合のみ有効になります。確認しますか?', + unbindHelper: + 'アンバインド - アンバインドすると、追加されたすべてのファイアウォールルールが無効になります。注意して操作してください。確認しますか?', + defaultStrategy: '現在のチェーン {0} のデフォルトポリシーは {1} です', + defaultStrategy2: + '現在のチェーン {0} のデフォルトポリシーは {1} です。現在の状態は未バインドです。追加されたファイアウォールルールはバインド後に有効になります!', + filterRule: 'フィルタールール', + filterHelper: + 'フィルタールールを使用すると、INPUT/OUTPUT レベルでネットワークトラフィックを制御できます。システムをロックしないように注意して設定してください。', + chain: 'チェーン', + targetChain: 'ターゲットチェーン', + sourceIP: '送信元 IP', + destIP: '宛先 IP', + inboundDirection: 'インバウンド方向', + outboundDirection: 'アウトバウンド方向', + destPort: '宛先ポート', + action: 'アクション', + reject: '拒否', + sourceIPHelper: 'CIDR 形式、例: 192.168.1.0/24。すべてのアドレスの場合は空のまま', + destIPHelper: 'CIDR 形式、例: 10.0.0.0/8。すべてのアドレスの場合は空のまま', + portHelper: '0 は任意のポートを意味します', + allPorts: 'すべてのポート', + deleteRuleConfirm: '{0} 個のルールを削除します。続行しますか?', + }, + runtime: { + runtime: 'ランタイム', + workDir: '作業ディレクトリ', + create: 'ランタイムを作成します', + localHelper: + 'ローカル環境のインストールおよびオフライン環境の使用に関する問題については、以下を参照してください ', + versionHelper: 'PHPバージョン、例えばv8.0', + buildHelper: + '拡張機能が多いほど、イメージ作成時にCPUの負荷が高くなります。環境作成後に拡張機能をインストールすることもできます。', + openrestyWarn: 'PHPは、使用するためにバージョン1.21.4.1以降にOpenRestyにアップグレードする必要があります', + toupgrade: 'アップグレードします', + edit: 'ランタイムを編集します', + extendHelper: + 'リストにない拡張機能は手動で入力して選択できます。例えば、「sockets」と入力し、ドロップダウンリストの最初の項目を選択して拡張機能リストを表示します。', + rebuildHelper: '拡張機能を編集した後、有効にするためにPHPアプリケーションを再構築する必要があります', + rebuild: 'PHPアプリを再構築します', + source: 'PHP拡張ソース', + ustc: '中国科学技術大学', + netease: 'netease', + aliyun: 'アリババクラウド', + default: 'デフォルト', + tsinghua: 'ツィンガ大学', + xtomhk: 'Xtom Mirror Station(香港)', + xtom: 'XTOMミラーステーション(グローバル)', + phpsourceHelper: 'ネットワーク環境に従って適切なソースを選択してください。', + appPort: 'アプリポート', + externalPort: '外部ポート', + packageManager: 'パッケージマネージャー', + codeDir: 'コードディレクトリ', + appPortHelper: 'アプリケーションで使用されるポート。', + externalPortHelper: '港は外の世界にさらされました。', + runScript: 'スクリプトを実行します', + runScriptHelper: '起動コマンドリストは、ソースディレクトリのpackage.jsonファイルから解析されます。', + open: '開ける', + operatorHelper: '{0}操作は、選択した動作環境で実行されます。続けたいですか?', + taobao: 'タオバオ', + tencent: 'テンセント', + imageSource: '画像ソース', + moduleManager: 'モジュール管理', + module: 'モジュール', + nodeOperatorHelper: + '{0} {1}モジュールですか?操作は、動作環境で異常を引き起こす可能性があります。進む前に確認してください', + customScript: 'カスタムスタートアップコマンド', + customScriptHelper: + '完全な起動コマンドを入力してください。例:npm run start。PM2の起動コマンドはpm2-runtimeに置き換えてください。そうしないと起動に失敗します。', + portError: `同じポートを繰り返さないでください。`, + systemRestartHelper: 'ステータスの説明:中断 - システムの再起動によりステータスの取得が失敗しました', + javaScriptHelper: '完全な起動コマンドを提供します。たとえば、「Java -Jar Halo.Jar -XMX1024M -XMS256M」。', + javaDirHelper: 'ディレクトリにはjarファイルが含まれている必要があり、サブディレクトリも受け入れられます', + goHelper: '完全な起動コマンドを提供します。たとえば、「Go Run Main.go」または「./Main」。', + goDirHelper: 'ディレクトリまたはサブディレクトリには、goファイルまたはバイナリファイルを含める必要があります。', + pythonHelper: + '完全な起動コマンドを提供します。たとえば、「PIP Install -R Repormations.txt && python manage.py runserver 0.0.0.0:5000」。', + dotnetHelper: '完全な起動コマンドを入力してください。例えば、dotnet MyWebApp.dll', + dirHelper: 'ノート: コンテナ内のディレクトリパスを入力してください', + concurrency: '並行処理スキーム', + loadStatus: '負荷状態', + extraHosts: 'ホストマッピング', + }, + process: { + pid: 'プロセスID', + ppid: '親pid', + numThreads: 'スレッド', + memory: 'メモリ', + diskRead: 'ディスク読み取り', + diskWrite: 'ディスク書き込み', + netSent: 'アップリンク', + netRecv: '下流', + numConnections: '接続', + startTime: '開始時間', + state: '州', + running: 'ランニング', + sleep: '寝る', + stop: '停止', + idle: 'アイドル', + zombie: 'ゾンビプロセス', + wait: '待っている', + lock: 'ロック', + blocked: 'ブロックされています', + cmdLine: 'コマンドを開始します', + basic: '基本', + mem: 'メモリ', + openFiles: 'オープンファイル', + env: '環境', + noenv: 'なし', + net: 'ネットワーク接続', + laddr: 'ソースアドレス/ポート', + raddr: '宛先アドレス/ポート', + stopProcess: '終わり', + viewDetails: '詳細', + stopProcessWarn: 'このプロセスを終了したいですか(PID:{0})?', + kill: 'プロセス終了', + killNow: '今すぐ終了', + killHelper: 'プロセス {0} を終了すると一部のプログラムが正常に動作しなくなる可能性があります。続行しますか?', + processName: 'プロセス名', + }, + tool: { + supervisor: { + loadStatusErr: + 'プロセスステータスの取得に失敗しました。スーパーバイザーサービスのステータスを確認してください。', + notSupport: + 'Supervisor サービスが検出されませんでした。スクリプトライブラリページに移動して手動でインストールしてください', + list: 'デーモンプロセス|デーモンプロセス', + config: 'スーパーバイザー構成', + primaryConfig: 'メイン構成ファイルの場所', + notSupportCtl: `supervisorctlは検出されません。スクリプトライブラリページに移動して手動でインストールしてください。`, + user: 'ユーザー', + command: '指示', + dir: 'ディレクトリ', + numprocs: '番号。プロセスの', + initWarn: + 'これにより、メイン構成ファイルの[[include] [include]セクションの「ファイル」値が変更されます。他の構成ファイルのディレクトリは、「{1Panel Installation Directory} /1Panel/tools/supervisord/supervisor.d/」になります。', + operatorHelper: '操作{1}は{0}で実行されます、続行しますか?', + uptime: '実行時間', + notStartWarn: `スーパーバイザーは開始されません。最初に開始します。`, + serviceName: 'サービス名', + initHelper: + 'Supervisor サービスが検出されましたが、初期化されていません。上部のステータスバーにある初期化ボタンをクリックして設定してください。', + serviceNameHelper: 'Systemctlが管理するスーパーバイザーサービス名、通常は監督者または監督者', + restartHelper: 'これにより、初期化後にサービスが再起動され、既存のすべてのデーモンプロセスが停止します。', + RUNNING: 'ランニング', + STOPPED: '停止', + STOPPING: '停止', + STARTING: '起動', + FATAL: '開始に失敗しました', + BACKOFF: '例外を開始します', + ERROR: 'エラー', + statusCode: 'ステータスコード', + manage: '管理', + autoRestart: '自動再起動', + EXITED: '終了しました', + autoRestartHelper: 'プログラムが異常終了した後に自動的に再起動するかどうか', + autoStart: '自動起動', + autoStartHelper: 'Supervisor 起動後にサービスを自動的に起動するかどうか', + }, + }, + disk: { + management: 'ディスク管理', + partition: 'パーティション', + unmount: 'アンマウント', + unmountHelper: 'パーティション {0} をアンマウントしますか?', + mount: 'マウント', + partitionAlert: + 'ディスクのパーティション分割にはディスクのフォーマットが必要で、既存のデータは削除されます。事前にデータを保存またはスナップショットを取ってください。', + mountPoint: 'マウントディレクトリ', + systemDisk: 'システムディスク', + unpartitionedDisk: '未パーティションディスク', + handlePartition: '今すぐパーティション', + filesystem: 'ファイルシステム', + unmounted: 'アンマウント', + cannotOperate: '操作不可', + systemDiskHelper: 'ヒント: 現在のディスクはシステムディスクです。操作できません。', + autoMount: '自動マウント', + model: 'デバイスモデル', + diskType: 'ディスクタイプ', + serial: 'シリアルナンバー', + noFail: 'マウント失敗はシステム起動に影響しません', + }, + xpack: { + expiresTrialAlert: + 'ご注意: あなたのProトライアルは{0}日後に終了し、すべてのPro機能が使用できなくなります。適時に更新またはフルバージョンにアップグレードしてください。', + expiresAlert: + 'ご注意: あなたのProライセンスは{0}日後に終了し、すべてのPro機能が使用できなくなります。継続的な使用のために速やかに更新してください。', + menu: 'Рro', + upage: 'AIウェブサイトビルダー', + proAlert: 'この機能を使用するにはProにアップグレードしてください', + app: { + app: 'APP', + title: 'パネルの別名', + titleHelper: 'パネルのエイリアスは、APP端末での表示に使用されます(デフォルトのパネルエイリアス)', + qrCode: 'QRコード', + apiStatusHelper: 'パネルAPPはAPIインターフェース機能を有効にする必要があります', + apiInterfaceHelper: + 'パネルAPIインターフェースアクセスをサポート(この機能はパネルアプリで有効にする必要があります)', + apiInterfaceHelper1: + 'パネルアプリのアクセスには訪問者をホワイトリストに追加する必要があります。固定IPでない場合、0.0.0.0/0(すべての IPv4)、::/0(すべての IPv6)を追加することをお勧めします', + qrCodeExpired: 'リフレッシュ時間', + apiLeakageHelper: 'QRコードを漏洩しないでください。信頼できる環境でのみ使用してください。', + }, + waf: { + name: 'WAF', + blackWhite: 'ブラックリストとホワイトリスト', + globalSetting: 'グローバル設定', + websiteSetting: 'ウェブサイト設定', + blockRecords: 'ブロック記録', + world: '世界', + china: '中国', + intercept: 'インターセプト', + request: 'リクエスト', + count4xx: '4xxの数', + count5xx: '5xxの数', + todayStatus: '今日のステータス', + reqMap: '攻撃マップ(過去30日間)', + resource: 'リソース', + count: '数', + hight: '高い', + low: '低い', + reqCount: 'リクエスト数', + interceptCount: 'インターセプト数', + requestTrends: 'リクエストトレンド(過去7日間)', + interceptTrends: 'インターセプトトレンド(過去7日間)', + whiteList: 'ホワイトリスト', + blackList: 'ブラックリスト', + ipBlackListHelper: 'ブラックリストに登録されたIPアドレスはウェブサイトへのアクセスがブロックされます', + ipWhiteListHelper: 'ホワイトリストに登録されたIPアドレスはすべての制限をバイパスします', + uaBlackListHelper: 'ブラックリストに登録されたUser-Agentのリクエストはブロックされます', + uaWhiteListHelper: 'ホワイトリストに登録されたUser-Agentのリクエストはすべての制限をバイパスします', + urlBlackListHelper: 'ブラックリストに登録されたURLへのリクエストはブロックされます', + urlWhiteListHelper: 'ホワイトリストに登録されたURLへのリクエストはすべての制限をバイパスします', + ccHelper: + 'もしサイトが{1}回以上のリクエストを同一IPから{0}秒以内に受けた場合、そのIPは{2}間ブロックされます', + blockTime: 'ブロック時間', + attackHelper: 'もし累積的なインターセプトが{1}回以上{0}秒以内に発生した場合、そのIPは{2}間ブロックされます', + notFoundHelper: 'もし404エラーが{1}回以上{0}秒以内に返された場合、そのIPは{2}間ブロックされます', + frequencyLimit: '頻度制限', + regionLimit: '地域制限', + defaultRule: 'デフォルトルール', + accessFrequencyLimit: 'アクセス頻度制限', + attackLimit: '攻撃頻度制限', + notFoundLimit: '404頻度制限', + urlLimit: 'URL頻度制限', + urlLimitHelper: '単一URLのアクセス頻度を設定します', + sqliDefense: 'SQLインジェクション防止', + sqliHelper: 'リクエストでSQLインジェクションを検出してブロックします', + xssHelper: 'リクエストでXSSを検出してブロックします', + xssDefense: 'XSS防止', + uaDefense: '悪意のあるUser-Agentルール', + uaHelper: '一般的な悪意のあるボットを識別するルールが含まれています', + argsDefense: '悪意のあるパラメータルール', + argsHelper: '悪意のあるパラメータを含むリクエストをブロックします', + cookieDefense: '悪意のあるCookieルール', + cookieHelper: 'リクエストに悪意のあるCookieを持ち込むことを禁止します', + headerDefense: '悪意のあるヘッダールール', + headerHelper: '悪意のあるヘッダーを含むリクエストを禁止します', + httpRule: 'HTTPリクエストメソッドルール', + httpHelper: + 'アクセスを許可するメソッドタイプを設定します。特定のタイプのアクセスを制限したい場合は、そのタイプのボタンをオフにしてください。例えば、GETタイプのみのアクセスを許可する場合は、GET以外のボタンをオフにする必要があります', + geoRule: '地域アクセス制限', + geoHelper: + '特定の地域からのウェブサイトへのアクセスを制限します。例えば、中国本土からのアクセスを許可し、それ以外の地域からのリクエストをブロックすることができます', + ipLocation: 'IP位置', + action: 'アクション', + ruleType: '攻撃タイプ', + ipHelper: 'IPアドレスを入力してください', + attackLog: '攻撃ログ', + rule: 'ルール', + ipArr: 'IPV4範囲', + ipStart: '開始IP', + ipEnd: '終了IP', + ipv4: 'IPv4', + ipv6: 'IPv6', + urlDefense: 'URLルール', + urlHelper: '禁止されたURL', + dirFilter: 'ディレクトリフィルター', + sqlInject: 'SQLインジェクション', + xss: 'XSS', + phpExec: 'PHPスクリプト実行', + oneWordTrojan: 'ワンワードトロイの木馬', + appFilter: '危険なディレクトリフィルタリング', + webshell: 'Webシェル', + args: '悪意のあるパラメータ', + protocolFilter: 'プロトコルフィルター', + javaFilter: 'Java危険ファイルフィルタリング', + scannerFilter: 'スキャナーフィルター', + escapeFilter: 'エスケープフィルター', + customRule: 'カスタムルール', + httpMethod: 'HTTPメソッドフィルター', + fileExt: 'ファイルアップロード制限', + fileExtHelper: 'アップロード禁止ファイル拡張子', + deny: '禁止', + allow: '許可', + field: 'オブジェクト', + pattern: '条件', + ruleContent: 'コンテンツ', + contain: '含む', + equal: '等しい', + regex: '正規表現', + notEqual: '等しくない', + customRuleHelper: '指定された条件に基づいてアクションを実行', + actionAllow: '許可', + blockIP: 'IPをブロック', + code: 'ステータスコードを返す', + noRes: '切断(444)', + badReq: '無効なパラメータ(400)', + forbidden: 'アクセス禁止(403)', + serverErr: 'サーバーエラー(500)', + resHtml: '応答ページ', + allowHelper: 'アクセスを許可すると、後続のWAFルールをスキップします。慎重に使用してください', + captcha: '人間と機械の検証', + fiveSeconds: '5秒検証', + location: '地域', + redisConfig: 'Redis設定', + redisHelper: 'Redisを有効にして、一時的にブロックされたIPを永続化します', + wafHelper: 'WAFを閉じると、すべてのウェブサイトが保護を失います', + attackIP: '攻撃IP', + attackParam: '攻撃詳細', + execRule: 'ヒットしたルール', + acl: 'ACL', + sql: 'SQLインジェクション', + cc: 'アクセス頻度制限', + isBlocking: 'ブロック中', + isFree: 'ブロック解除', + unLock: 'ロック解除', + unLockHelper: 'IP:{0}をブロック解除しますか?', + saveDefault: 'デフォルトを保存', + saveToWebsite: 'ウェブサイトに適用', + saveToWebsiteHelper: '現在の設定をすべてのウェブサイトに適用しますか?', + websiteHelper: 'ウェブサイトを作成するためのデフォルト設定です。変更をウェブサイトに適用する必要があります', + websiteHelper2: + 'ウェブサイトを作成するためのデフォルト設定です。ウェブサイトで特定の設定を変更してください', + ipGroup: 'IPグループ', + ipGroupHelper: + '1行に1つのIPまたはIPセグメントを入力、IPv4およびIPv6をサポートします。例: 192.168.1.1または192.168.1.0/24', + ipBlack: 'IPブラックリスト', + openRestyAlert: 'OpenRestyのバージョンは{0}より高くする必要があります', + initAlert: + '初回使用時には初期化が必要です。ウェブサイトの設定ファイルが変更され、元のWAF設定が失われます。事前にOpenRestyのバックアップを取ってください', + initHelper: '初期化操作により、既存のWAF設定がクリアされます。初期化してもよろしいですか?', + mainSwitch: 'メインスイッチ', + websiteAlert: 'まずウェブサイトを作成してください', + defaultUrlBlack: 'URLルール', + htmlRes: 'インターセプトページ', + urlSearchHelper: 'URLを入力して、曖昧検索をサポートしてください', + toCreate: '作成', + closeWaf: 'WAFを閉じる', + closeWafHelper: 'WAFを閉じると、ウェブサイトは保護を失います。続行しますか?', + addblack: 'ブラック追加', + addwhite: 'ホワイト追加', + addblackHelper: 'IP:{0}をデフォルトのブラックリストに追加しますか?', + addwhiteHelper: 'IP:{0}をデフォルトのホワイトリストに追加しますか?', + defaultUaBlack: 'ユーザーエージェントルール', + defaultIpBlack: '悪意のあるIPグループ', + cookie: 'クッキールール', + urlBlack: 'URLブラックリスト', + uaBlack: 'ユーザーエージェントブラックリスト', + attackCount: '攻撃頻度制限', + fileExtCheck: 'ファイルアップロード制限', + geoRestrict: '地域アクセス制限', + attacklog: '遮断記録', + unknownWebsite: '認証されていないドメイン名アクセス', + geoRuleEmpty: '地域は空にできません', + unknown: 'ウェブサイトが存在しません', + geo: '地域制限', + revertHtml: '{0} をデフォルトページに戻しますか?', + five_seconds: '5秒認証', + header: 'ヘッダールール', + methodWhite: 'HTTPルール', + expiryDate: '有効期限', + expiryDateHelper: '認証後、有効期間内は再認証されません', + defaultIpBlackHelper: 'インターネットから収集された悪意のあるIPをアクセス防止のために使用', + notFoundCount: '404頻度制限', + matchValue: '一致する値', + headerName: 'このフィールドは、特別なキャラクターではなく、英語、数字、-をサポート、長さは3-30', + cdnHelper: 'CDNを使用しているウェブサイトは、ここで正しいソースIPを取得できます', + clearLogWarn: 'ログをクリアすると元に戻せません。続けますか?', + commonRuleHelper: 'ルールは部分一致です', + blockIPHelper: + 'ブロックされたIPはOpenRestyに一時的に保存され、OpenRestyを再起動すると解除されます。ブロック機能で永久的にブロックできます', + addWhiteUrlHelper: 'URL {0} をホワイトリストに追加しますか?', + dashHelper: 'コミュニティバージョンでもグローバル設定とウェブサイト設定の機能を使用できます', + wafStatusHelper: 'WAFが有効ではありません。グローバル設定で有効にしてください', + ccMode: 'モード', + global: 'グローバルモード', + uriMode: 'URLモード', + globalHelper: + 'グローバルモード: 任意のURLに対するリクエストの合計数が一定時間内にしきい値を超えるとトリガーされます', + uriModeHelper: 'URLモード: 単一のURLに対するリクエストの数が一定時間内にしきい値を超えるとトリガーされます', + ip: 'IPブラックリスト', + globalSettingHelper: + '[ウェブサイト] タグの設定は [ウェブサイト設定] で有効にする必要があり、グローバル設定は新しく作成されたウェブサイトのデフォルト設定です', + globalSettingHelper2: '設定は [グローバル設定] と [ウェブサイト設定] の両方で有効にする必要があります', + urlCCHelper: + '{0} 秒以内にこの URL に対して {1} 回を超えるリクエストがあったため、この IP をブロックします {2}', + urlCCHelper2: 'URL にパラメータを含めることはできません', + notContain: '含まない', + urlcc: 'URL 頻度制限', + method: 'リクエストタイプ', + addIpsToBlock: 'IP を一括ブロック', + addUrlsToWhite: 'URL を一括でホワイトリストに追加', + noBlackIp: 'IP は既にブロックされているため、再度ブロックする必要はありません', + noWhiteUrl: 'URL は既にホワイトリストに含まれているため、再度追加する必要はありません', + spiderIpHelper: + '百度、Bing、Google、360、神马、搜狗、字节、DuckDuckGo、Yandexを含みます。これを閉じると、すべてのクローラーのアクセスがブロックされます。', + spiderIp: 'スパイダー IP プール', + geoIp: 'IP アドレスライブラリ', + geoIpHelper: 'IP の地理的位置を確認するために使用されます', + stat: '攻撃レポート', + statTitle: 'レポート', + attackIp: '攻撃 IP', + attackCountNum: '攻撃回数', + percent: '割合', + addblackUrlHelper: 'URL: {0} をデフォルトのブラックリストに追加しますか?', + rce: 'リモートコード実行', + software: 'ソフトウェア', + cveHelper: '一般的なソフトウェアやフレームワークの脆弱性を含みます', + vulnCheck: '補足ルール', + ssrf: 'SSRF 脆弱性', + afr: '任意ファイル読み取り', + ua: '未承認アクセス', + id: '情報漏洩', + aa: '認証回避', + dr: 'ディレクトリトラバーサル', + xxe: 'XXE 脆弱性', + suid: 'シリアライズ脆弱性', + dos: 'サービス拒否脆弱性', + afd: '任意ファイルダウンロード', + sqlInjection: 'SQL インジェクション', + afw: '任意ファイル書き込み', + il: '情報漏洩', + clearAllLog: 'すべてのログをクリア', + exportLog: 'ログをエクスポート', + appRule: 'アプリケーションルール', + appRuleHelper: + '一般的なアプリケーションルール。有効にすると誤検出を減らすことができます。1つのウェブサイトにつき1つのルールのみ使用可能です', + logExternal: '記録タイプを除外', + ipWhite: 'IP ホワイトリスト', + urlWhite: 'URL ホワイトリスト', + uaWhite: 'ユーザーエージェントホワイトリスト', + logExternalHelper: + '除外された記録タイプはログに記録されません。ブラックリスト/ホワイトリスト、地域アクセス制限、カスタムルールは大量のログを生成します。除外をお勧めします', + ssti: 'SSTI 攻撃', + crlf: 'CRLF インジェクション', + strict: '厳格モード', + strictHelper: 'より厳格なルールを使用してリクエストを検証します', + saveLog: 'ログを保存', + remoteURLHelper: 'リモート URL は、1行に1つのIPで、他の文字がないことを保証する必要があります', + notFound: 'Not Found (404)', + serviceUnavailable: 'サービスを利用できません (503)', + gatewayTimeout: 'ゲートウェイタイムアウト (504)', + belongToIpGroup: 'IP グループに属しています', + notBelongToIpGroup: 'IP グループに属していません', + unknownWebsiteKey: '未知のドメイン', + special: '特別な文字は使用できません', + fileToLarge: 'ファイルが1MBを超えており、アップロードできません', + uploadOverLimit: 'アップロードファイル数が制限を超えています、最大1ファイル', + importRuleHelper: '1行に1つのルール', + }, + monitor: { + name: 'ウェブサイトモニタリング', + pv: 'ページビュー', + uv: 'ユニークビジター', + flow: 'トラフィックフロー', + ip: 'IP', + spider: 'スパイダー', + visitors: '訪問者のトレンド', + today: '今日', + last7days: '過去7日間', + last30days: '過去30日間', + uvMap: '訪問者マップ(30日間)', + qps: 'リアルタイムリクエスト(分単位)', + flowSec: 'リアルタイムトラフィック(分単位)', + excludeCode: 'ステータスコードを除外', + excludeUrl: 'URLを除外', + excludeExt: '拡張子を除外', + cdnHelper: 'CDN提供のヘッダーから実際のIPを取得', + reqRank: '訪問ランキング', + refererDomain: 'リファラードメイン', + os: 'システム', + browser: 'ブラウザ/クライアント', + device: 'デバイス', + showMore: 'もっと見る', + unknown: 'その他', + pc: 'コンピュータ', + mobile: 'モバイルデバイス', + wechat: 'WeChat', + machine: 'マシン', + tencent: 'Tencentブラウザ', + ucweb: 'UCブラウザ', + '2345explorer': '2345ブラウザ', + huaweibrowser: 'Huaweiブラウザ', + log: 'リクエストログ', + statusCode: 'ステータスコード', + requestTime: '応答時間', + flowRes: 'レスポンストラフィック', + method: 'リクエストメソッド', + statusCodeHelper: '上記のステータスコードを入力してください', + statusCodeError: '無効なステータスコードのタイプ', + methodHelper: '上記のリクエストメソッドを入力してください', + all: 'すべて', + baidu: 'Baidu', + google: 'Google', + bing: 'Bing', + bytes: '今日のヘッドライン', + sogou: 'Sogou', + failed: 'エラー', + ipCount: 'IP数', + spiderCount: 'スパイダリクエスト', + averageReqTime: '平均応答時間', + totalFlow: '総トラフィック', + logSize: 'ログファイルのサイズ', + realIPType: '実際のIP取得方法', + fromHeader: 'HTTPヘッダーから取得', + fromHeaders: 'ヘッダーリストから取得', + header: 'HTTPヘッダー', + cdnConfig: 'CDN設定', + xff1: 'X-Forwarded-Forの最初のプロキシ', + xff2: 'X-Forwarded-Forの第二のプロキシ', + xff3: 'X-Forwarded-Forの第三のプロキシ', + xffHelper: + '例: X-Forwarded-For: ,,, 上位のプロキシは最後のIP を取得します', + headersHelper: '一般的に使用されるCDNのHTTPヘッダーから実際のIPを取得し、最初の利用可能な値を選択', + monitorCDNHelper: 'ウェブサイトモニタリングのためにCDN設定を変更すると、WAF CDN設定も更新されます', + wafCDNHelper: 'WAF CDN設定を変更すると、ウェブサイトモニタリングのCDN設定も更新されます', + statusErr: '無効なステータスコード形式', + shenma: '神馬検索', + duckduckgo: 'DuckDuckGo', + '360': '360検索', + excludeUri: 'URIを除外', + top100Helper: '上位100データを表示', + logSaveDay: 'ログ保持期間(日数)', + cros: 'Chrome OS', + theworld: 'TheWorldブラウザ', + edge: 'Microsoft Edge', + maxthon: 'Maxthonブラウザ', + monitorStatusHelper: 'モニタリングは有効ではありません。設定で有効にしてください', + excludeIp: 'IPアドレスを除外', + excludeUa: 'ユーザーエージェントを除外', + remotePort: 'リモートポート', + unknown_browser: '不明', + unknown_os: '不明', + unknown_device: '不明', + logSaveSize: '最大ログ保存サイズ', + logSaveSizeHelper: 'これは単一ウェブサイトのログ保存サイズです', + '360se': '360 セキュリティブラウザ', + websites: 'ウェブサイトリスト', + trend: 'トレンド統計', + reqCount: 'リクエスト数', + uriHelper: '/test/* や /*/index.php を使用して Uri を除外できます', + }, + tamper: { + tamper: 'ウェブサイトの改ざん防止', + ignoreTemplate: '除外テンプレート', + protectTemplate: '保護テンプレート', + ignoreTemplateHelper: + '除外コンテンツを入力してください、Enterまたはスペースで区切ります。(特定のディレクトリ ./log またはディレクトリ名 tmp、ファイルを除外するには特定のファイル ./data/test.html を入力する必要があります)', + protectTemplateHelper: + '保護コンテンツを入力してください、Enterまたはスペースで区切ります。(特定のファイル ./index.html、ファイル拡張子 .html、ファイルタイプ js、ディレクトリを保護するには特定のディレクトリ ./log を入力する必要があります)', + templateContent: 'テンプレート内容', + template: 'テンプレート', + saveTemplate: 'テンプレートとして保存', + tamperHelper1: + 'ワンクリックデプロイメントタイプのウェブサイトでは、アプリケーションディレクトリの改ざん防止機能を有効にすることを推奨します。ウェブサイトが正常に使用できない場合やバックアップ/復元が失敗した場合は、まず改ざん防止機能を無効にしてください。', + tamperHelper2: + '除外されていないディレクトリ内の保護ファイルに対する読み書き、削除、権限、所有者変更操作を制限します', + tamperPath: '保護ディレクトリ', + tamperPathEdit: 'パスの変更', + log: 'ブロックログ', + totalProtect: '総保護', + todayProtect: '本日の保護', + templateRule: '長さ1-512、名前に{0}などの記号を含めることはできません', + ignore: '除外', + ignoreHelper: + '除外コンテンツを選択または入力してください、Enterまたはスペースで区切ります。(特定のディレクトリ ./log またはディレクトリ名 tmp、ファイルを除外するには特定のファイル ./data/test.html を入力または選択する必要があります)', + protect: '保護', + protectHelper: + '保護コンテンツを選択または入力してください、Enterまたはスペースで区切ります。(特定のファイル ./index.html、ファイル拡張子 .html、ファイルタイプ js、ディレクトリを保護するには特定のディレクトリ ./log を入力または選択する必要があります)', + tamperHelper00: '除外と保護は相対パスのみ入力可能です。', + tamperHelper01: + '改ざん防止を有効にすると、システムは除外されていないディレクトリ内の保護ファイルの作成、編集、削除操作を制限します。', + tamperHelper02: '優先順位:特定パス保護 > 特定パス除外 > 保護 > 除外', + tamperHelper03: + '監視操作は除外されていないディレクトリのみを対象とし、これらのディレクトリ内の非保護ファイルの作成を監視します。', + disableHelper: '以下のウェブサイトの改ざん防止機能を無効にしますか?続行しますか?', + appendOnly: '追加のみ', + appendOnlyHelper: + 'このディレクトリ内のファイルの削除操作を制限し、除外ディレクトリまたは非保護ファイルの追加のみを許可します', + immutable: 'イミュータブル', + immutableHelper: 'このファイルの編集、削除、権限、所有者変更操作を制限します', + onWatch: '監視', + onWatchHelper: + 'このディレクトリ内での保護ファイルまたは非除外ディレクトリの作成を監視およびインターセプトします', + forceStop: '強制終了', + forceStopHelper: 'このウェブサイトディレクトリの改ざん防止機能を強制的に無効にします。続行しますか?', + }, + setting: { + setting: 'パネル設定', + title: 'パネルの説明', + titleHelper: + 'ユーザーログインページに表示されます(例:Linuxサーバー運用管理パネル、推奨文字数:8〜15文字)', + logo: 'ロゴ(テキストなし)', + logoHelper: + 'メニューが折りたたまれている場合、管理ページの左上隅に表示されます(推奨画像サイズ:82px*82px)', + logoWithText: 'ロゴ(テキストあり)', + logoWithTextHelper: + 'メニューが展開されている場合、管理ページの左上隅に表示されます(推奨画像サイズ:185px*55px)', + favicon: 'ウェブサイトアイコン', + faviconHelper: 'ウェブサイトアイコン(推奨画像サイズ:16px*16px)', + setDefault: 'デフォルトに戻す', + setHelper: '現在の設定が保存されます。続けますか?', + setDefaultHelper: 'すべてのパネル設定がデフォルトに戻されます。続けますか?', + logoGroup: 'ロゴ', + imageGroup: '画像', + loginImage: 'ログインページの画像', + loginImageHelper: 'ログインページに表示されます(推奨画像サイズ:500×416px)', + loginBgType: 'ログインページ背景タイプ', + loginBgImage: 'ログインページ背景画像', + loginBgImageHelper: 'ログインページの背景画像として表示されます(推奨画像サイズ:1920×1080px)', + loginBgColor: 'ログインページ背景色', + loginBgColorHelper: 'ログインページの背景色として表示されます', + image: '画像', + bgColor: '背景色', + loginGroup: 'ログインページ', + loginBtnLinkColor: 'ボタン/リンクの色', + loginBtnLinkColorHelper: 'ログインページに表示されるボタン/リンクの色になります', + }, + helper: { + wafTitle1: 'インターセプションマップ', + wafContent1: '過去30日間のインターセプトの地理的分布を表示', + wafTitle2: '地域別アクセス制限', + wafContent2: '地理的な位置に基づいてウェブサイトのアクセス元を制限', + wafTitle3: 'カスタムインターセプションページ', + wafContent3: 'リクエストがインターセプトされた後に表示するカスタムページを作成', + wafTitle4: 'カスタムルール (ACL)', + wafContent4: 'カスタムルールに基づいてリクエストをインターセプト', + + tamperTitle1: 'ファイル整合性監視', + tamperContent1: + 'ウェブサイトのファイルの整合性を監視、コアファイル、スクリプトファイル、設定ファイルを含む。', + tamperTitle2: 'リアルタイムスキャンと検出', + tamperContent2: + 'ウェブサイトのファイルシステムをリアルタイムでスキャンし、異常や改竄されたファイルを検出。', + tamperTitle3: 'セキュリティ権限設定', + tamperContent3: + '適切な権限設定とアクセス制御ポリシーを通じてウェブサイトファイルへのアクセスを制限し、潜在的な攻撃対象面を減少。', + tamperTitle4: 'ログ記録と分析', + tamperContent4: + 'ファイルのアクセスおよび操作ログを記録し、後の監査および分析に使用、また潜在的なセキュリティ脅威を特定。', + + settingTitle1: 'カスタムウェルカムメッセージ', + settingContent1: '1Panelのログインページにカスタムウェルカムメッセージを設定。', + settingTitle2: 'カスタムロゴ', + settingContent2: 'ブランド名やその他のテキストを含むロゴ画像をアップロードできる。', + settingTitle3: 'カスタムウェブサイトアイコン', + settingContent3: + 'カスタムアイコンをアップロードして、デフォルトのブラウザアイコンを置き換え、ユーザー体験を向上。', + + monitorTitle1: '訪問者トレンド', + monitorContent1: 'ウェブサイト訪問者のトレンドを統計および表示', + monitorTitle2: '訪問者マップ', + monitorContent2: 'ウェブサイト訪問者の地理的分布を統計および表示', + monitorTitle3: 'アクセス統計', + monitorContent3: + 'ウェブサイトリクエスト情報を統計、スパイダー、アクセスデバイス、リクエストステータスなどを含む。', + monitorTitle4: 'リアルタイムモニタリング', + monitorContent4: + 'ウェブサイトリクエスト情報をリアルタイムでモニタリング、リクエスト数、トラフィックなどを含む。', + + alertTitle1: 'SMSアラート', + alertContent1: + 'サーバーリソース使用量の異常、ウェブサイトおよび証明書の有効期限、新しいバージョンの更新、パスワードの期限切れなどが発生した場合、ユーザーにSMSアラートで通知し、タイムリーな処理を確保。', + alertTitle2: 'アラートログ', + alertContent2: + 'ユーザーがアラートログを表示できる機能を提供し、過去のアラートイベントを追跡および分析しやすく。', + alertTitle3: 'アラート設定', + alertContent3: + 'カスタム電話番号、日々のプッシュ頻度、日々のプッシュ時間設定を提供し、ユーザーがより合理的なプッシュアラートを設定しやすく。', + + nodeDashTitle1: 'アプリケーション管理', + nodeDashContent1: + 'マルチノードアプリケーションの統一管理、ステータス監視、迅速な起動/停止、端末接続、バックアップをサポート', + nodeDashTitle2: 'Webサイト管理', + nodeDashContent2: + 'マルチノードWebサイトの統一管理、リアルタイムステータス監視、一括起動/停止と迅速なバックアップをサポート', + nodeDashTitle3: 'データベース管理', + nodeDashContent3: + 'マルチノードデータベースの統一管理、主要ステータスが一目で分かり、ワンクリックバックアップをサポート', + nodeDashTitle4: 'スケジュールタスク管理', + nodeDashContent4: + 'マルチノードスケジュールタスクの統一管理、ステータス監視、迅速な起動/停止、手動トリガー実行をサポート', + + nodeTitle1: 'ワンクリックノード追加', + nodeContent1: '複数のサーバーノードを迅速に統合', + nodeTitle2: 'バッチアップグレード', + nodeContent2: '一度の操作ですべてのノードを同期アップグレード', + nodeTitle3: 'ノードステータス監視', + nodeContent3: '各ノードの運用状況をリアルタイムで把握', + nodeTitle4: '迅速なリモート接続', + nodeContent4: 'ワンクリックでノードリモート端末に直接接続', + + fileExchangeTitle1: 'キー認証伝送', + fileExchangeContent1: 'SSHキーを介して認証し、伝送のセキュリティを確保します。', + fileExchangeTitle2: '効率的なファイル同期', + fileExchangeContent2: '変更されたコンテンツのみを同期し、伝送速度と安定性を大幅に向上させます。', + fileExchangeTitle3: 'マルチノード相互通信のサポート', + fileExchangeContent3: + '異なるノード間でプロジェクトファイルを簡単に転送し、複数のサーバーを柔軟に管理します。', + + nodeAppTitle1: 'アプリケーションアップグレード管理', + nodeAppContent1: 'マルチノードアプリケーションの更新状況を統一監視、ワンクリックアップグレードをサポート', + + appTitle1: '柔軟なパネル管理', + appContent1: 'いつでもどこでも1Panelサーバーを簡単に管理できます。', + appTitle2: '包括的なサービス情報', + appContent2: + 'モバイル端末でアプリケーション、ウェブサイト、Docker、データベースなどの基本的な管理を行い、アプリやウェブサイトの迅速な作成をサポートします。', + appTitle3: 'リアルタイム異常監視', + appContent3: + 'モバイル端末でサーバーステータス、WAFセキュリティ監視、ウェブサイトの訪問統計、プロセスの健康状態をリアルタイムで確認できます。', + + clusterTitle1: 'マスタースレーブ展開', + clusterContent1: + '異なるノードで MySQL/Postgres/Redis マスタースレーブインスタンスを作成することをサポートし、自動的にマスタースレーブ関連付けと初期化を完了します', + clusterTitle2: 'マスタースレーブ管理', + clusterContent2: + '統一されたページで複数のマスタースレーブノードを一元的に管理し、それらの役割、実行状態などを表示します', + clusterTitle3: 'レプリケーション状態', + clusterContent3: + 'マスタースレーブレプリケーション状態と遅延情報を表示し、同期の問題を解決するのに役立ちます', + }, + node: { + master: '主ノード', + masterBackup: 'マスターノードバックアップ', + backupNode: 'バックアップノード', + backupFrequency: 'バックアップ頻度(時間)', + backupCopies: 'バックアップ保持数', + noBackupNode: '現在バックアップノードが空です。保存するバックアップノードを選択して再試行してください!', + masterBackupAlert: + '現在マスターノードのバックアップが設定されていません。データセキュリティを確保するため、障害時に新しいマスターノードに手動で切り替えられるよう、速やかにバックアップノードを設定してください。', + node: 'ノード', + addr: 'アドレス', + nodeUpgrade: '更新設定', + nodeUpgradeHelper: + '選択したノードは、マスターノードのアップグレード完了後に自動的にアップグレードを開始し、手動操作は不要です。', + nodeUnhealthy: 'ノード状態異常', + deletedNode: '削除済みノード {0} は現在アップグレード操作をサポートしていません!', + nodeUnhealthyHelper: 'ノード状態異常を検出しました。[ノード管理]で確認してから再試行してください!', + nodeUnbind: 'ノードがライセンスにバインドされていません', + nodeUnbindHelper: + 'このノードがライセンスにバインドされていないことを検出しました。[パネル設定 - ライセンス]メニューでバインドしてから再試行してください!', + memTotal: '総メモリ', + nodeManagement: 'マルチマシン管理', + nodeItem: 'ノード管理', + panelItem: 'パネル管理', + addPanel: 'パネル追加', + addPanelHelper: + 'Después de agregar el panel exitosamente, puede acceder rápidamente al panel objetivo en [Resumen - Paneles].', + panel: 'Panel 1Panel', + others: 'Otros Paneles', + addNode: 'ノードを追加', + connInfo: '接続情報', + nodeInfo: 'ノード情報', + withProxy: 'プロキシアクセスを有効化', + withoutProxy: 'プロキシアクセスを無効化', + withProxyHelper: + 'パネル設定で管理されているシステムプロキシ {0} を使用して子ノードにアクセスします。続行しますか?', + withoutProxyHelper: + 'パネル設定で管理されているシステムプロキシを使用して子ノードにアクセスすることを停止します。続行しますか?', + syncInfo: 'データ同期,', + syncHelper: 'マスターノードのデータが変更されると、この子ノードにリアルタイムで同期されます,', + syncBackupAccount: 'バックアップアカウント設定', + syncWithMaster: + 'プロ版にアップグレード後、すべてのデータがデフォルトで同期されます。ノード管理で同期ポリシーを手動調整できます。', + syncProxy: 'システムプロキシ設定', + syncProxyHelper: 'システムプロキシ設定の同期にはDockerの再起動が必要です', + syncProxyHelper1: 'Dockerの再起動は現在実行中のコンテナサービスに影響する可能性があります。', + syncProxyHelper2: 'コンテナ - 設定 ページで手動で再起動できます。', + syncProxyHelper3: + 'システムプロキシ設定の同期にはDockerの再起動が必要で、現在実行中のコンテナサービスに影響する可能性があります', + syncProxyHelper4: + 'システムプロキシ設定の同期にはDockerの再起動が必要です。後でコンテナ - 設定 ページで手動で再起動できます。', + syncCustomApp: 'カスタムアプリリポジトリを同期', + syncAlertSetting: 'システムアラート設定', + syncNodeInfo: 'ノード基本データ,', + nodeSyncHelper: 'ノード情報の同期は、以下の情報を同期します:', + nodeSyncHelper1: '1. 公共のバックアップアカウント情報', + nodeSyncHelper2: '2. 主ノードとサブノードの接続情報', + + nodeCheck: '可用性チェック', + checkSSH: 'ノードSSH接続を確認', + checkUserPermission: 'ノードユーザー権限を確認', + isNotRoot: + 'このノードではパスワードなしsudoがサポートされておらず、現在のユーザーがrootではないことが検出されました', + checkLicense: 'ノードライセンス状態を確認', + checkService: 'ノードの既存サービス情報を確認', + checkPort: 'ノードポート到達性を確認', + panelExist: + 'このノードで1Panel V1サービスが実行中です。追加前に移行スクリプトでV2へアップグレードしてください。', + coreExist: + '現在のノードはマスターノードとして有効化済みのため、直接スレーブノードとして追加できません。追加する前にまずスレーブノードにダウングレードしてください。詳細はドキュメントを参照してください。', + agentExist: + 'このノードに1panel-agentが既にインストールされています。続行すると既存データを保持し、1panel-agentサービスのみを置換します。', + agentNotExist: + 'このノードに1panel-agentがインストールされていないため、ノード情報を直接編集できません。一度削除してから再度追加してください。', + oldDataExist: 'このノードに1Panel V2の過去データが検出されました。以下の情報で現在の設定を上書きします:', + errLicense: 'このノードに紐づけられたライセンスが利用できません。確認して再試行してください!', + errNodePort: + 'ノードポート[ {0} ]にアクセスできないことが検出されました。ファイアウォールまたはセキュリティグループでこのポートが開放されているか確認してください。', + + reinstallHelper: 'ノード{0}を再インストールします。続行しますか?', + unhealthyCheck: '異常チェック', + fixOperation: '修正操作', + checkName: 'チェック項目', + checkSSHConn: 'SSH接続の可用性を確認', + fixSSHConn: 'ノードを手動で編集し、接続情報を確認', + checkConnInfo: 'エージェント接続情報を確認', + checkStatus: 'ノードサービスの可用性を確認', + fixStatus: '「systemctl status 1panel-agent.service」を実行して、サービスが起動しているか確認します。', + checkAPI: 'ノードAPIの可用性を確認', + fixAPI: 'ノードのログを確認し、ファイアウォールのポートが正常に開放されているか確認します。', + forceDelete: '強制削除', + operateHelper: '以下のノードに{0}操作を行います。続行しますか?', + operatePanelHelper: '以下のパネルに対して {0} 操作を実行します。続行しますか?', + forceDeleteHelper: '強制削除はノード削除エラーを無視し、データベースメタデータを削除します', + uninstall: 'ノードデータを削除', + uninstallHelper: 'ノードに関連するすべての1Panelデータが削除されます。慎重に選択してください!', + baseDir: 'インストールディレクトリ', + baseDirHelper: 'インストールディレクトリが空の場合、デフォルトで/optディレクトリにインストールされます', + nodePort: 'ノードポート', + offline: 'オフラインモード', + freeCount: '無料枠 [{0}]', + offlineHelper: 'ノードがオフライン環境にある場合に使用', + + appUpgrade: 'アプリのアップグレード', + appUpgradeHelper: 'アップグレードが必要なアプリが {0} 個あります', + }, + customApp: { + name: 'カスタムアプリリポジトリ', + appStoreType: 'アプリストアパッケージソース', + appStoreUrl: 'リポジトリURL', + local: 'ローカルパス', + remote: 'リモートリンク', + imagePrefix: 'イメージプレフィックス', + imagePrefixHelper: + '機能: イメージプレフィックスをカスタマイズし、composeファイル内のイメージフィールドを変更します。例えば、イメージプレフィックスを1panel/customに設定した場合、MaxKBのイメージフィールドは1panel/custom/maxkb:v1.10.0に変更されます', + closeHelper: 'カスタムアプリリポジトリの使用をキャンセルしますか', + appStoreUrlHelper: '.tar.gz形式のみサポートされます', + postNode: 'サブノードへ同期', + postNodeHelper: + 'カスタムストアパッケージを子ノードのインストールディレクトリの tmp/customApp/apps.tar.gz に同期します', + nodes: 'ノードを選択', + selectNode: 'ノードを選択', + selectNodeError: 'ノードを選択してください', + licenseHelper: 'プロバージョンはカスタムアプリケーションリポジトリ機能をサポートしています', + databaseHelper: 'アプリケーション関連データベース、ターゲットノードのデータベースを選択してください', + nodeHelper: '現在のノードは選択できません', + migrateHelper: + '現在、単体アプリケーションと MySQL、MariaDB、PostgreSQL データベースのみに関連するアプリケーションの移行のみをサポートしています', + opensslHelper: + '暗号化バックアップを使用する場合、2つのノード間のOpenSSLバージョンは一致している必要があります。そうしないと、移行が失敗する可能性があります。', + installApp: '一括インストール', + installAppHelper: '選択したノードにアプリを一括インストール', + }, + alert: { + isAlert: 'アラート', + alertCount: 'アラート数', + clamHelper: '感染したファイルをスキャンするときにアラートをトリガーします', + cronJobHelper: 'タスクの実行が失敗したときにアラートをトリガーします', + licenseHelper: 'プロのバージョンはSMSアラートをサポートします', + alertCountHelper: '最大毎日のアラーム周波数', + alert: 'SMSアラート', + logs: 'アラートログ', + list: 'アラートリスト', + addTask: 'アラートを作成', + editTask: 'アラートを編集', + alertMethod: '方法', + alertMsg: 'アラートメッセージ', + alertRule: 'アラートルール', + titleSearchHelper: 'アラートタイトルを入力して検索します', + taskType: 'タイプ', + ssl: '証明書期限切れ', + siteEndTime: 'ウェブサイト期限切れ', + panelPwdEndTime: 'パネルパスワード期限切れ', + panelUpdate: '新しいパネルバージョンあり', + cpu: 'サーバーCPUアラート', + memory: 'サーバーメモリアラート', + load: 'サーバーロードアラート', + disk: 'サーバーディスクアラート', + website: 'ウェブサイト', + certificate: 'SSL証明書', + remainingDays: '残り日数', + sendCount: '送信回数', + sms: 'SMS', + wechat: 'WeChat', + dingTalk: 'DingTalk', + feiShu: 'FeiShu', + mail: 'メール', + weCom: 'WeCom', + sendCountRulesHelper: '期限前に送信されるアラートの合計(1日1回)', + panelUpdateRulesHelper: '新しいパネルバージョンに関するアラートの合計(1日1回)', + oneDaySendCountRulesHelper: '1日に送信できる最大アラート回数', + siteEndTimeRulesHelper: '期限が設定されていないウェブサイトはアラートをトリガーしません', + autoRenewRulesHelper: '自動更新が有効な証明書で残り日数が31日未満の場合、アラートはトリガーされません', + panelPwdEndTimeRulesHelper: 'パネルパスワード期限切れのアラートは設定されていない場合は使用できません', + sslRulesHelper: 'すべてのSSL証明書', + diskInfo: 'ディスク', + monitoringType: '監視タイプ', + autoRenew: '自動更新', + useDisk: 'ディスク使用率', + usePercentage: '使用割合', + changeStatus: 'ステータスを変更', + disableMsg: + 'アラートタスクを停止すると、このタスクはアラートメッセージを送信できなくなります。続行しますか?', + enableMsg: + 'アラートタスクを有効にすると、このタスクはアラートメッセージを送信できるようになります。続行しますか?', + useExceed: '使用率超過', + useExceedRulesHelper: '設定した値を超えた場合にアラートをトリガー', + cpuUseExceedAvg: '平均CPU使用率が指定した値を超過', + memoryUseExceedAvg: '平均メモリ使用率が指定した値を超過', + loadUseExceedAvg: '平均負荷使用率が指定した値を超過', + cpuUseExceedAvgHelper: '指定時間内の平均CPU使用率が指定した値を超過', + memoryUseExceedAvgHelper: '指定時間内の平均メモリ使用率が指定した値を超過', + loadUseExceedAvgHelper: '指定時間内の平均負荷使用率が指定した値を超過', + resourceAlertRulesHelper: '注意:30分以内に連続してアラートが発生した場合、は1回だけ送信されます', + specifiedTime: '指定時間', + deleteTitle: 'アラートを削除', + deleteMsg: 'アラートタスクを削除してもよろしいですか?', + allSslTitle: 'すべてのウェブサイトSSL証明書の期限切れアラート', + sslTitle: 'ウェブサイト{0}のSSL証明書期限切れアラート', + allSiteEndTimeTitle: 'すべてのウェブサイト期限切れアラート', + siteEndTimeTitle: 'ウェブサイト{0}の期限切れアラート', + panelPwdEndTimeTitle: 'パネルパスワード期限切れアラート', + panelUpdateTitle: '新しいパネルバージョン通知', + cpuTitle: '高CPU使用率アラート', + memoryTitle: '高メモリ使用率アラート', + loadTitle: '高負荷アラート', + diskTitle: 'マウントディレクトリ{0}の高ディスク使用率アラート', + allDiskTitle: '高ディスク使用率アラート', + timeRule: '残り時間が{0}日未満(処理されない場合、翌日再送信)', + panelUpdateRule: + '新しいパネルバージョンが検出されたときに1回アラートを送信(処理されない場合、翌日再送信)', + avgRule: '指定された時間内の平均{1}使用率が{2}%を超過するとアラートがトリガーされ、1日{3}回送信', + diskRule: 'マウントディレクトリ{0}のディスク使用率が{1}{2}を超過するとアラートがトリガーされ、1日{3}回送信', + allDiskRule: 'ディスク使用率が{0}{1}を超過するとアラートがトリガーされ、1日{2}回送信', + cpuName: 'CPU', + memoryName: 'メモリ', + loadName: '負荷', + diskName: 'ディスク', + syncAlertInfo: '手動プッシュ', + syncAlertInfoMsg: 'アラートタスクを手動でプッシュしてもよろしいですか?', + pushError: 'プッシュ失敗', + pushSuccess: 'プッシュ成功', + syncError: '同期失敗', + success: 'アラート成功', + pushing: 'プッシュ中...', + error: 'アラート失敗', + cleanLog: 'ログをクリア', + cleanAlertLogs: 'アラートログをクリア', + daily: '1日のアラート数:{0}', + cumulative: '累積アラート数:{0}', + clams: 'ウイルススキャン', + taskName: 'タスク名', + cronJobType: 'タスクタイプ', + clamPath: 'スキャンディレクトリ', + cronjob: 'スケジュールタスクの実行{0}で異常が発生しました', + app: 'アプリバックアップ', + web: 'ウェブサイトバックアップ', + database: 'データベースバックアップ', + directory: 'ディレクトリバックアップ', + log: 'ログバックアップ', + snapshot: 'システムスナップショット', + clamsRulesHelper: 'アラートが必要なウイルススキャンタスク', + cronJobRulesHelper: 'このタイプのスケジュールタスクには設定が必要です', + clamsTitle: 'ウイルススキャンタスク「{0}」が感染ファイルを検出したアラート', + cronJobAppTitle: 'Cronジョブ - アプリバックアップ「{0}」タスク失敗アラート', + cronJobWebsiteTitle: 'Cronジョブ - ウェブサイトバックアップ「{0}」タスク失敗アラート', + cronJobDatabaseTitle: 'Cronジョブ - データベースバックアップ「{0}」タスク失敗アラート', + cronJobDirectoryTitle: 'Cronジョブ - ディレクトリバックアップ「{0}」タスク失敗アラート', + cronJobLogTitle: 'Cronジョブ - ログバックアップ「{0}」タスク失敗アラート', + cronJobSnapshotTitle: 'Cronジョブ - システムスナップショット「{0}」タスク失敗アラート', + cronJobShellTitle: 'Cronジョブ - シェルスクリプト「{0}」タスク失敗アラート', + cronJobCurlTitle: 'Cronジョブ - URLアクセス「{0}」タスク失敗アラート', + cronJobCutWebsiteLogTitle: 'Cronジョブ - ウェブサイトログカット「{0}」タスク失敗アラート', + cronJobCleanTitle: 'Cronジョブ - キャッシュクリーニング「{0}」タスク失敗アラート', + cronJobNtpTitle: 'Cronジョブ - サーバー時間同期「{0}」タスク失敗アラート', + clamsRule: 'ウイルススキャンで感染ファイルが検出されたアラート、1日に{0}回送信', + cronJobAppRule: 'アプリバックアップタスク失敗アラート、1日に{0}回送信', + cronJobWebsiteRule: 'ウェブサイトバックアップタスク失敗アラート、1日に{0}回送信', + cronJobDatabaseRule: 'データベースバックアップタスク失敗アラート、1日に{0}回送信', + cronJobDirectoryRule: 'ディレクトリバックアップタスク失敗アラート、1日に{0}回送信', + cronJobLogRule: 'ログバックアップタスク失敗アラート、1日に{0}回送信', + cronJobSnapshotRule: 'スナップショットバックアップタスク失敗アラート、1日に{0}回送信', + cronJobShellRule: 'シェルスクリプトタスク失敗アラート、1日に{0}回送信', + cronJobCurlRule: 'URLアクセスタスク失敗アラート、1日に{0}回送信', + cronJobCutWebsiteLogRule: 'ウェブサイトログカットタスク失敗アラート、1日に{0}回送信', + cronJobCleanRule: 'キャッシュクリーニングタスク失敗アラート、1日に{0}回送信', + cronJobNtpRule: 'サーバー時間同期タスク失敗アラート、1日に{0}回送信', + alertSmsHelper: 'SMS制限:合計{0}メッセージ、{1}回使用済み', + goBuy: 'さらに購入', + phone: '電話', + phoneHelper: 'アラートメッセージのために実際の電話番号を提供してください', + dailyAlertNum: '毎日のアラート数', + dailyAlertNumHelper: '1日のアラート数の合計、最大100件まで', + timeRange: '時間範囲', + sendTimeRange: '送信の時間範囲', + sendTimeRangeHelper: 'アラートを送信できる時間範囲は{0}です', + to: '-', + startTime: '開始時間', + endTime: '終了時間', + defaultPhone: 'ライセンスに紐付けられたアカウントの電話番号をデフォルトにする', + noticeAlert: '通知アラート', + resourceAlert: 'リソースアラート', + agentOfflineAlertHelper: + 'ノードでオフラインアラートを有効にすると、メインノードが30分ごとにスキャンしてアラートタスクを実行します。', + offline: 'オフラインアラート', + offlineHelper: + 'オフラインアラートに設定すると、メインノードが30分ごとにスキャンしてアラートタスクを実行します。', + offlineOff: 'オフラインアラートを有効にする', + offlineOffHelper: + 'オフラインアラートを有効にすると、メインノードが30分ごとにスキャンしてアラートタスクを実行します。', + offlineClose: 'オフラインアラートを無効にする', + offlineCloseHelper: + 'オフラインアラートを無効にすると、サブノードが独自にアラートを処理する必要があります。アラートの失敗を防ぐため、ネットワーク接続が良好であることを確認してください。', + alertNotice: 'アラート通知', + methodConfig: '通知方法の設定', + commonConfig: 'グローバル設定', + smsConfig: 'SMS', + smsConfigHelper: 'SMS 通知の電話番号を設定する', + emailConfig: 'メール', + emailConfigHelper: 'SMTP メール送信サービスを設定する', + deleteConfigTitle: 'アラート設定を削除', + deleteConfigMsg: 'アラート設定を削除してもよろしいですか?', + test: 'テスト', + alertTestOk: 'テスト通知に成功しました', + alertTestFailed: 'テスト通知に失敗しました', + displayName: '表示名', + sender: '送信元アドレス', + password: 'パスワード', + host: 'SMTP サーバー', + port: 'ポート番号', + encryption: '暗号化方式', + recipient: '受信者', + licenseTime: 'ライセンスの有効期限切れ通知', + licenseTimeTitle: 'ライセンスの有効期限切れ通知', + displayNameHelper: 'メールの送信者表示名', + senderHelper: 'メール送信に使用するメールアドレス', + passwordHelper: 'メールサービスの認証コード', + hostHelper: 'SMTP サーバーアドレス(例:smtp.qq.com)', + portHelper: 'SSLは通常465、TLSは通常587', + sslHelper: 'SMTPポートが465の場合、通常はSSLが必要です', + tlsHelper: 'SMTPポートが587の場合、通常はTLSが必要です', + triggerCondition: 'トリガー条件', + loginFail: '以内にログイン失敗', + nodeException: 'ノード異常アラート', + licenseException: 'ライセンス異常アラート', + panelLogin: 'パネルログイン異常アラート', + sshLogin: 'SSHログイン異常アラート', + panelIpLogin: 'パネルログインIP異常アラート', + sshIpLogin: 'SSHログインIP異常アラート', + ipWhiteListHelper: + 'ホワイトリストに登録されたIPはルールの制限を受けず、ログインが成功してもアラートは発生しません', + nodeExceptionRule: 'ノード異常アラートは、1日あたり{0}回送信', + licenseExceptionRule: 'ライセンス異常アラートは、1日あたり{0}回送信', + panelLoginRule: 'パネルログインアラートは、1日あたり{0}回送信', + sshLoginRule: 'SSHログインアラートは、1日あたり{0}回送信', + userNameHelper: 'ユーザー名が空の場合、送信者のアドレスがデフォルトで使用されます', + }, + theme: { + lingXiaGold: '凌霞金', + classicBlue: 'クラシックブルー', + freshGreen: 'フレッシュグリーン', + customColor: 'カスタムカラー', + setDefault: 'デフォルトに戻す', + setDefaultHelper: 'テーマカラーを初期状態に戻そうとしています。続行しますか?', + setHelper: '現在選択されているテーマカラーを保存しようとしています。続行しますか?', + }, + exchange: { + exchange: 'ファイル交換', + exchangeConfirm: '{0} ノードのファイル/フォルダ {1} を {2} ノードの {3} ディレクトリに転送しますか?', + }, + cluster: { + cluster: 'アプリケーションの高可用性', + name: 'クラスタ名', + addCluster: 'クラスタを追加', + installNode: 'ノードをインストール', + master: 'マスターノード', + slave: 'スレーブノード', + replicaStatus: 'マスタースレーブステータス', + unhealthyDeleteError: + 'インストールノードのステータスが異常です。ノードリストを確認してから再試行してください!', + replicaStatusError: 'ステータスの取得が異常です。マスターノードを確認してください。', + masterHostError: 'マスターノードのIPは127.0.0.1にできません', + }, + }, +}; +export default { + ...fit2cloudEnLocale, + ...message, +}; diff --git a/frontend/src/lang/modules/ko.ts b/frontend/src/lang/modules/ko.ts new file mode 100644 index 0000000..3dfda94 --- /dev/null +++ b/frontend/src/lang/modules/ko.ts @@ -0,0 +1,3981 @@ +import fit2cloudKoLocale from 'fit2cloud-ui-plus/src/locale/lang/en'; + +const message = { + commons: { + true: '참', + false: '거짓', + colon: ': ', + example: '예를 들어, ', + fit2cloud: 'FIT2CLOUD', + lingxia: 'Lingxia', + button: { + run: '실행', + create: '생성', + add: '추가', + save: '저장', + set: '설정 수정', + sync: '동기화', + delete: '삭제', + edit: '편집', + enable: '활성화', + disable: '비활성화', + confirm: '확인', + cancel: '취소', + reset: '재설정', + restart: '재시작', + conn: '연결', + disConn: '연결 해제', + clean: '정리', + login: '로그인', + close: '닫기', + off: '꺼짐', + stop: '중지', + start: '시작', + view: '보기', + watch: '감시', + handle: '트리거', + clone: '복제', + expand: '확장', + collapse: '축소', + log: '로그', + back: '뒤로', + backup: '백업', + recover: '복구', + retry: '재시도', + upload: '업로드', + download: '다운로드', + init: '초기화', + verify: '검증', + saveAndEnable: '저장 및 활성화', + import: '가져오기', + export: '내보내기', + power: '권한 부여', + search: '검색', + refresh: '새로고침', + get: '가져오기', + upgrade: '업그레이드', + update: '업데이트', + updateNow: '지금 업데이트', + ignore: '업그레이드 무시', + install: '설치', + copy: '복사', + random: '무작위', + uninstall: '제거', + fullscreen: '전체 화면', + quitFullscreen: '전체 화면 종료', + showAll: '모두 보기', + hideSome: '일부 숨기기', + agree: '동의', + notAgree: '동의하지 않음', + preview: '미리 보기', + open: '열기', + notSave: '저장하지 않음', + createNewFolder: '새 폴더 생성', + createNewFile: '새 파일 생성', + helpDoc: '도움말 문서', + unbind: '연결 해제', + cover: '덮어쓰기', + skip: '건너뛰기', + fix: '수정', + down: '중지', + up: '시작', + sure: '확인', + show: '보기', + hide: '숨기기', + visit: '방문', + migrate: '마이그레이션', + }, + operate: { + start: '시작', + stop: '중지', + restart: '재시작', + reload: '다시 로드', + rebuild: '재구축', + sync: '동기화', + up: '실행', + down: '중지', + delete: '삭제', + }, + search: { + timeStart: '시작 시간', + timeEnd: '종료 시간', + timeRange: '부터', + dateStart: '시작 날짜', + dateEnd: '종료 날짜', + date: '날짜', + }, + table: { + all: '전체', + total: '총 {0}', + name: '이름', + type: '유형', + status: '상태', + records: '기록', + group: '그룹', + createdAt: '생성 시간', + publishedAt: '게시 시간', + date: '날짜', + updatedAt: '업데이트 시간', + operate: '작업', + message: '메시지', + description: '설명', + interval: '간격', + user: '소유자', + title: '제목', + port: '포트', + forward: '포워드', + protocol: '프로토콜', + tableSetting: '테이블 설정', + refreshRate: '새로 고침 속도', + selectColumn: '열 선택', + local: '로컬', + serialNumber: '일련 번호', + manageGroup: '그룹 관리', + backToList: '목록으로 돌아가기', + keepEdit: '계속 편집', + }, + loadingText: { + Upgrading: '시스템 업그레이드 중입니다. 잠시만 기다려 주십시오...', + Restarting: '시스템 재시작 중입니다. 잠시만 기다려 주십시오...', + Recovering: '스냅샷에서 복구 중입니다. 잠시만 기다려 주십시오...', + Rollbacking: '스냅샷에서 롤백 중입니다. 잠시만 기다려 주십시오...', + }, + msg: { + noneData: '데이터가 없습니다', + delete: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`, + clean: `이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`, + closeDrawerHelper: '시스템에서 변경 사항을 저장하지 않을 수 있습니다. 계속하시겠습니까?', + deleteSuccess: '삭제 완료', + loginSuccess: '로그인 성공', + operationSuccess: '작업 완료', + copySuccess: '복사 완료', + notSupportOperation: `이 작업은 지원되지 않습니다`, + requestTimeout: '요청이 시간 초과되었습니다. 나중에 다시 시도해 주십시오', + infoTitle: '안내', + notRecords: '현재 작업에 대한 실행 기록이 생성되지 않았습니다', + sureLogOut: '로그아웃하시겠습니까?', + createSuccess: '생성 완료', + updateSuccess: '업데이트 완료', + uploadSuccess: '업로드 성공', + operateConfirm: '작업을 확인하려면 수동으로 입력하십시오: ', + inputOrSelect: '선택하거나 입력해 주십시오', + copyFailed: '복사 실패', + operatorHelper: `"{0}"에 대해 "{1}" 작업이 수행되며 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?`, + notFound: '죄송합니다. 요청하신 페이지를 찾을 수 없습니다.', + unSupportType: `현재 파일 형식은 지원되지 않습니다.`, + unSupportSize: '업로드된 파일이 {0}M을 초과했습니다. 확인해 주십시오!', + fileExist: `현재 폴더에 이미 동일한 파일이 존재합니다. 중복 업로드는 지원되지 않습니다.`, + fileNameErr: '파일 이름은 1~256 자 사이의 영어, 중국어, 숫자, 또는 점(.-_)만 포함해야 합니다.', + confirmNoNull: `{0} 값이 비어 있지 않은지 확인하십시오.`, + errPort: '포트 정보가 올바르지 않습니다. 확인해 주십시오!', + remove: '제거', + backupHelper: '현재 작업은 {0}을(를) 백업합니다. 계속하시겠습니까?', + recoverHelper: '{0} 파일에서 복원 중입니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?', + refreshSuccess: '새로 고침 완료', + rootInfoErr: '이미 루트 디렉토리입니다', + resetSuccess: '초기화 완료', + creatingInfo: '생성 중입니다. 이 작업이 필요하지 않습니다', + offlineTips: '오프라인 버전은 이 작업을 지원하지 않습니다', + errImportFormat: '가져오기 데이터 또는 형식이 비정상입니다. 확인 후 다시 시도하세요!', + importHelper: + '충돌하거나 중복되는 데이터를 가져올 때 가져온 내용을 기준으로 원래 데이터베이스 데이터를 업데이트합니다.', + errImport: '파일 내용이 비정상입니다:', + }, + login: { + username: '사용자 이름', + password: '비밀번호', + passkey: '패스키 로그인', + welcome: '다시 오신 것을 환영합니다. 사용자 이름과 비밀번호를 입력하여 로그인하세요!', + errorAuthInfo: '입력한 사용자 이름 또는 비밀번호가 잘못되었습니다. 다시 입력해주세요!', + errorMfaInfo: '인증 정보가 잘못되었습니다. 다시 시도해주세요!', + captchaHelper: '캡챠', + errorCaptcha: '캡챠 코드 오류!', + notSafe: '접근이 거부되었습니다', + safeEntrance1: '현재 환경에서 보안 로그인이 활성화되었습니다', + safeEntrance2: 'SSH 터미널에서 다음 명령어를 입력하여 패널 진입 경로를 확인하세요: 1pctl user-info', + errIP1: '현재 환경에서 승인된 IP 주소 접근이 활성화되었습니다', + errDomain1: '현재 환경에서 도메인 이름 바인딩이 활성화되었습니다', + errHelper: '바인딩 정보를 재설정하려면 SSH 터미널에서 다음 명령어를 실행하세요:', + codeInput: 'MFA 인증기의 6자리 인증 코드를 입력하세요', + mfaTitle: 'MFA 인증', + mfaCode: 'MFA 인증 코드', + title: 'Linux 서버 관리 패널', + licenseHelper: '<커뮤니티 라이선스 계약>', + errorAgree: '커뮤니티 소프트웨어 라이선스에 동의하려면 클릭하세요', + logout: '로그아웃', + agreeTitle: '동의', + agreeContent: + '귀하의 합법적인 권리와 이익을 보다 잘 보호하기 위해, 다음 « 커뮤니티 라이선스 계약 »을 읽고 동의해주세요.', + passkeyFailed: '패스키 로그인에 실패했습니다. 다시 시도하세요', + passkeyNotSupported: '현재 브라우저 또는 환경에서 패스키를 지원하지 않습니다', + passkeyToPassword: '패스키 사용에 문제가 있나요? 비밀번호로 로그인하세요', + }, + rule: { + username: '사용자 이름을 입력하세요', + password: '비밀번호를 입력하세요', + rePassword: '확인 비밀번호가 비밀번호와 일치하지 않습니다.', + requiredInput: '이 필드는 필수 항목입니다.', + requiredSelect: '목록에서 항목을 선택하세요', + illegalChar: '현재 & ; $ \' ` ( ) " > < | 문자 주입은 지원되지 않습니다', + illegalInput: '이 필드에는 유효하지 않은 문자가 포함될 수 없습니다.', + commonName: + '이 필드는 특수 문자로 시작할 수 없으며, 영어, 한자, 숫자, ".", "-", "_" 문자로 구성되어야 하며 길이는 1-128자여야 합니다.', + userName: '특수 문자로 시작하지 않고, 영어, 한국어, 숫자 및 _, 길이 3-30 지원', + simpleName: + '이 필드는 "_"로 시작할 수 없으며, 영어, 숫자 및 "_" 문자로 구성되어야 하며 길이는 3-30 자여야 합니다.', + simplePassword: + '이 필드는 "_"로 시작할 수 없으며, 영어, 숫자 및 "_" 문자로 구성되어야 하며 길이는 1-30 자여야 합니다.', + dbName: '이 필드는 "_"로 시작할 수 없으며, 영어, 숫자 및 "_" 문자로 구성되어야 하며 길이는 1-64 자여야 합니다.', + imageName: '특수 문자로 시작하지 않고, 영어, 숫자, :@/.-_ 지원, 길이 1-256', + composeName: '특수 문자로 시작할 수 없으며, 소문자, 숫자, "-", "_"를 지원하며 길이는 1-256 자여야 합니다.', + volumeName: '이 필드는 영어, 숫자, ".", "-", "_" 문자로 구성되어야 하며 길이는 2-30 자여야 합니다.', + supervisorName: + '이 필드는 특수 문자로 시작할 수 없으며, 영어, 숫자, "-", "_" 문자로 구성되어야 하며 길이는 1-128 자여야 합니다.', + complexityPassword: + '이 필드는 영어와 숫자로 구성되어야 하며 길이는 8-30 자이고 최소 두 개의 특수 문자가 포함되어야 합니다.', + commonPassword: '이 필드 길이는 6 자 이상이어야 합니다.', + linuxName: '이 필드 길이는 1-128 자 사이여야 하며, 다음 특수 문자를 포함할 수 없습니다: "{0}".', + email: '이 필드는 유효한 이메일 주소여야 합니다.', + number: '이 필드는 숫자여야 합니다.', + integer: '이 필드는 양의 정수여야 합니다.', + ip: '이 필드는 유효한 IP 주소여야 합니다.', + host: '이 필드는 유효한 IP 주소 또는 도메인 이름이어야 합니다.', + hostHelper: 'IP 주소 또는 도메인 이름 입력을 지원합니다', + port: '이 필드는 유효한 포트 번호여야 합니다.', + selectHelper: '올바른 {0} 파일을 선택하세요', + domain: '이 필드는 다음 형식이어야 합니다: example.com 또는 example.com:8080.', + databaseName: '이 필드는 영어, 숫자 및 "_" 문자로 구성되어야 하며 길이는 1-30 자여야 합니다.', + ipErr: '이 필드는 유효한 IP 주소여야 합니다.', + numberRange: '이 필드는 {0}에서 {1} 사이의 숫자여야 합니다.', + paramName: '이 필드는 영어, 숫자, ".", "-", "_" 문자로 구성되어야 하며 길이는 2-30 자여야 합니다.', + paramComplexity: + '이 필드는 특수 문자로 시작하거나 끝날 수 없으며, 영어, 숫자, "{0}" 문자로 구성되어야 하며 길이는 6-128 자여야 합니다.', + paramUrlAndPort: '이 필드는 "http(s)://(도메인 이름/IP):(포트)" 형식이어야 합니다.', + nginxDoc: '이 필드는 영어, 숫자 및 "." 문자로 구성되어야 합니다.', + appName: + '소문자, 숫자, "-", "_"를 지원하며 길이는 2-30 자이고, "-" 또는 "_"로 시작하거나 끝날 수 없습니다.', + containerName: + '영어, 숫자, "-", "_", "."를 지원하며, "-", "_", "."로 시작할 수 없고 길이는 2-128 자여야 합니다.', + mirror: '미러 가속 주소는 http(s)://로 시작해야 하며, 대소문자 영어, 숫자, ".", "/", "-"를 지원하고 공백을 포함할 수 없습니다.', + disableFunction: '영어 문자, 밑줄 및 ,만 지원합니다', + leechExts: '영어 문자, 숫자 및 ,만 지원합니다', + paramSimple: '소문자와 숫자를 지원하며 길이는 1-128 자여야 합니다', + filePermission: '파일 권한 오류', + formatErr: '형식 오류입니다. 확인 후 다시 시도하세요', + phpExtension: '소문자 영어와 숫자, "_"만 지원합니다', + paramHttp: 'http:// 또는 https:// 로 시작해야 합니다', + phone: '전화번호 형식이 올바르지 않습니다', + authBasicPassword: '알파벳, 숫자 및 일반 특수 문자 지원, 길이 1-72', + length128Err: '길이는 128자를 초과할 수 없습니다', + maxLength: '길이는 {0}자를 초과할 수 없습니다', + alias: '영어, 숫자, - 및 _ 지원, 길이 1-128, -_로 시작하거나 끝날 수 없습니다.', + }, + res: { + paramError: '요청이 실패했습니다. 나중에 다시 시도하세요!', + forbidden: '현재 사용자는 권한이 없습니다', + serverError: '서비스 예외', + notFound: '리소스가 존재하지 않습니다', + commonError: '요청이 실패했습니다', + }, + service: { + serviceNotStarted: `{0} 서비스가 시작되지 않았습니다.`, + }, + status: { + running: '실행 중', + done: '완료', + scanFailed: '불완전', + success: '성공', + waiting: '대기 중', + waitForUpgrade: '업그레이드 대기 중', + waiting1: '대기 중', + failed: '실패', + stopped: '중지됨', + error: '오류', + created: '생성됨', + restarting: '재시작 중', + uploading: '업로드 중', + unhealthy: '비정상', + removing: '제거 중', + paused: '일시 중지', + exited: '종료됨', + dead: '중단됨', + installing: '설치 중', + enabled: '활성화됨', + disabled: '비활성화됨', + normal: '정상', + building: '빌드 중', + upgrading: '업그레이드 중', + pending: '편집 대기', + rebuilding: '재빌드 중', + deny: '거부됨', + accept: '수락됨', + used: '사용 중', + unUsed: '사용 안 함', + starting: '시작 중', + recreating: '재생성 중', + creating: '생성 중', + init: '애플리케이션 대기 중', + ready: '정상', + applying: '적용 중', + uninstalling: '제거 중', + lost: '연결 끊김', + bound: '바인딩됨', + unbind: '미바인드', + exceptional: '예외', + free: '여유', + enable: '활성화됨', + disable: '비활성화됨', + deleted: '삭제됨', + downloading: '다운로드 중', + packing: '패키징 중', + sending: '전송 중', + healthy: '정상', + executing: '실행 중', + installerr: '설치 실패', + applyerror: '적용 실패', + systemrestart: '중단됨', + starterr: '시작 실패', + uperr: '실행 실패', + new: '신규', + conflict: '충돌', + duplicate: '중복', + unexecuted: '실행되지 않음', + }, + units: { + second: '초 | 초 | 초', + minute: '분 | 분 | 분', + hour: '시간 | 시간 | 시간', + day: '일 | 일 | 일', + week: '주 | 주 | 주', + month: '월 | 월 | 월', + year: '년 | 년 | 년', + time: '시간', + core: '코어 | 코어 | 코어', + secondUnit: '초', + minuteUnit: '분', + hourUnit: '시간', + dayUnit: '일', + millisecond: '밀리초', + }, + log: { + noLog: '로그 없음', + }, + }, + menu: { + home: '개요', + apps: '앱 스토어', + website: '웹사이트 | 웹사이트들', + project: '프로젝트 | 프로젝트들', + config: '구성 | 구성들', + ssh: 'SSH 설정', + firewall: '방화벽', + ssl: '인증서 | 인증서들', + database: '데이터베이스 | 데이터베이스들', + aiTools: 'AI', + mcp: 'MCP', + container: '컨테이너 | 컨테이너들', + cronjob: '크론 작업 | 크론 작업들', + system: '시스템', + security: '보안', + files: '파일', + monitor: '모니터링', + terminal: '터미널', + settings: '설정 | 설정들', + toolbox: '툴박스', + logs: '로그 | 로그들', + runtime: '런타임 | 런타임들', + processManage: '프로세스 | 프로세스들', + process: '프로세스 | 프로세스들', + network: '네트워크 | 네트워크들', + supervisor: '슈퍼바이저', + tamper: '변조 방지', + app: '애플리케이션', + msgCenter: '작업 센터', + disk: '디스크', + }, + home: { + recommend: '추천', + dir: '디렉토리', + alias: '별칭', + quickDir: '빠른 디렉토리', + minQuickJump: '최소 하나의 빠른 점프 항목을 설정해 주세요!', + maxQuickJump: '최대 네 개의 빠른 점프 항목을 설정할 수 있습니다!', + database: '데이터베이스 - 전체', + restart_1panel: '패널 재시작', + restart_system: '서버 재시작', + operationSuccess: + '작업이 성공적으로 완료되었습니다. 시스템이 재부팅 중입니다. 나중에 브라우저를 수동으로 새로 고침하세요!', + entranceHelper: `보안 입구가 활성화되어 있지 않습니다. "설정 -> 보안"에서 활성화하여 시스템 보안을 강화할 수 있습니다.`, + appInstalled: '설치된 애플리케이션', + systemInfo: '시스템 정보', + hostname: '호스트 이름', + platformVersion: '운영 체제', + kernelVersion: '커널', + kernelArch: '아키텍처', + network: '네트워크', + io: '디스크 I/O', + ip: '로컬 IP', + proxy: '시스템 프록시', + baseInfo: '기본 정보', + totalSend: '총 송신', + totalRecv: '총 수신', + rwPerSecond: 'I/O 작업', + ioDelay: 'I/O 지연 시간', + uptime: '작동 시간', + runningTime: '가동 시간', + mem: '시스템 메모리', + swapMem: '스왑 파티션', + + runSmoothly: '낮은 부하', + runNormal: '보통 부하', + runSlowly: '높은 부하', + runJam: '심한 부하', + + core: '물리적 코어', + logicCore: '논리 코어', + corePercent: '코어 사용률', + cpuFrequency: 'CPU 주파수', + cpuDetailedPercent: 'CPU 사용률 상세', + cpuUser: '사용자', + cpuSystem: '시스템', + cpuIdle: '유휴', + cpuIrq: 'IRQ', + cpuSoftirq: 'Soft IRQ', + cpuSteal: 'Steal', + cpuTop: 'CPU 사용률 상위 5개 프로세스 정보', + memTop: '메모리 사용률 상위 5개 프로세스 정보', + loadAverage: '지난 1분의 평균 부하 | 지난 {n} 분의 평균 부하', + load: '부하', + mount: '마운트 지점', + fileSystem: '파일 시스템', + total: '전체', + used: '사용', + cache: '�시', + free: '여유', + shard: '샤딩', + available: '사용 가능', + percent: '사용률', + goInstall: 'Go 설치', + + networkCard: '네트워크 카드', + disk: '디스크', + }, + tabs: { + more: '더 보기', + hide: '숨기기', + closeLeft: '왼쪽 닫기', + closeRight: '오른쪽 닫기', + closeCurrent: '현재 탭 닫기', + closeOther: '다른 탭 닫기', + closeAll: '모두 닫기', + }, + header: { + logout: '로그아웃', + }, + database: { + manage: '관리', + deleteBackupHelper: '데이터베이스 백업을 동시에 삭제', + delete: '삭제 작업은 되돌릴 수 없습니다. 삭제하려면 "', + deleteHelper: '"를 입력하세요.', + create: '데이터베이스 생성', + noMysql: '데이터베이스 서비스 (MySQL 또는 MariaDB)', + noPostgresql: '데이터베이스 서비스 PostgreSQL', + goUpgrade: '업그레이드로 이동', + goInstall: '설치로 이동', + isDelete: '삭제됨', + permission: '권한', + format: '문자 집합', + collation: '콜레이션', + collationHelper: '비어 있으면 {0} 문자 집합의 기본 콜레이션을 사용합니다', + permissionForIP: 'IP', + permissionAll: '모두(%)', + localhostHelper: + '컨테이너 배포 시 데이터베이스 권한을 "localhost"로 설정하면 컨테이너 외부에서 접근할 수 없게 됩니다. 신중하게 선택하세요!', + databaseConnInfo: '연결 정보', + rootPassword: '루트 비밀번호', + serviceName: '서비스 이름', + serviceNameHelper: '같은 네트워크 내 컨테이너 간의 접근.', + backupList: '백업', + loadBackup: '불러오기', + localUpload: '로컬 업로드', + hostSelect: '서버 선택', + selectHelper: '백업 파일 {0}을(를) 가져오시겠습니까?', + remoteAccess: '원격 접근', + remoteHelper: '여러 IP 를 쉼표로 구분하여 입력, 예: 172.16.10.111, 172.16.10.112', + remoteConnHelper: + 'MySQL 의 root 사용자로 원격 접속은 보안 위험을 초래할 수 있습니다. 따라서 이 작업은 신중히 수행해야 합니다.', + changePassword: '비밀번호', + changeConnHelper: '이 작업은 현재 데이터베이스 {0}을(를) 수정합니다. 계속하시겠습니까?', + changePasswordHelper: + '데이터베이스가 애플리케이션과 연결되어 있습니다. 비밀번호를 변경하면 애플리케이션의 데이터베이스 비밀번호도 변경됩니다. 변경 사항은 애플리케이션이 재시작된 후에 적용됩니다.', + recoverTimeoutHelper: '-1은 제한 시간 제한 없음을 의미합니다', + confChange: '설정', + confNotFound: + '설정 파일을 찾을 수 없습니다. 앱 스토어에서 애플리케이션을 최신 버전으로 업그레이드하고 다시 시도해주세요!', + portHelper: '이 포트는 컨테이너의 노출된 포트입니다. 수정을 별도로 저장하고 컨테이너를 재시작해야 합니다!', + loadFromRemote: '동기화', + userBind: '사용자 바인딩', + pgBindHelper: `이 작업은 새 사용자를 생성하여 대상 데이터베이스에 바인딩하는 데 사용됩니다. 현재 데이터베이스에 이미 존재하는 사용자 선택은 지원되지 않습니다.`, + pgSuperUser: '슈퍼 사용자', + loadFromRemoteHelper: '이 작업은 서버의 데이터베이스 정보를 1Panel로 동기화합니다. 계속 진행하시겠습니까?', + passwordHelper: '확인 불가, 수정하려면 클릭', + remote: '원격', + remoteDB: '원격 서버 | 원격 서버들', + createRemoteDB: '원격 DB 바인딩', + unBindRemoteDB: '원격 DB 바인딩 해제', + unBindForce: '강제 바인딩 해제', + unBindForceHelper: '바인딩 해제 중 발생하는 모든 오류를 무시하고 최종 작업을 성공적으로 완료합니다.', + unBindRemoteHelper: + '원격 데이터베이스 바인딩 해제는 바인딩 관계만 제거하며, 원격 데이터베이스 자체는 삭제되지 않습니다.', + editRemoteDB: '원격 서버 편집', + localDB: '로컬 데이터베이스', + address: '데이터베이스 주소', + version: '데이터베이스 버전', + userHelper: '루트 사용자 또는 루트 권한을 가진 데이터베이스 사용자가 원격 데이터베이스에 접근할 수 있습니다.', + pgUserHelper: '슈퍼 사용자 권한을 가진 데이터베이스 사용자.', + ssl: 'SSL 사용', + clientKey: '클라이언트 개인 키', + clientCert: '클라이언트 인증서', + caCert: 'CA 인증서', + hasCA: 'CA 인증서 있음', + skipVerify: '인증서 유효성 검사 무시', + initialDB: '초기 데이터베이스', + + formatHelper: '현재 데이터베이스 문자셋은 {0} 입니다. 문자셋 불일치로 인해 복구에 실패할 수 있습니다.', + dropHelper: '여기에 업로드한 파일을 드래그 앤 드롭하거나', + clickHelper: '클릭하여 업로드', + supportUpType: + 'sql, sql.gz, tar.gz, .zip 파일 형식만 지원합니다. 가져오는 압축 파일에는 하나의 .sql 파일만 있거나 test.sql이 포함되어 있어야 합니다', + + currentStatus: '현재 상태', + baseParam: '기본 파라미터', + performanceParam: '성능 파라미터', + runTime: '시작 시간', + connections: '전체 연결', + bytesSent: '전송된 바이트', + bytesReceived: '수신된 바이트', + queryPerSecond: '초당 쿼리', + txPerSecond: '초당 전송', + connInfo: '활성/최대 연결', + connInfoHelper: '값이 너무 크면 "max_connections" 값을 증가시켜야 합니다.', + threadCacheHit: '스레드 캐시 적중', + threadCacheHitHelper: '값이 너무 낮으면 "thread_cache_size" 값을 증가시켜야 합니다.', + indexHit: '인덱스 적중', + indexHitHelper: '값이 너무 낮으면 "key_buffer_size" 값을 증가시켜야 합니다.', + innodbIndexHit: 'Innodb 인덱스 적중률', + innodbIndexHitHelper: '값이 너무 낮으면 "innodb_buffer_pool_size" 값을 증가시켜야 합니다.', + cacheHit: '쿼리 캐시 적중', + cacheHitHelper: '값이 너무 낮으면 "query_cache_size" 값을 증가시켜야 합니다.', + tmpTableToDB: '디스크로 임시 테이블', + tmpTableToDBHelper: '값이 너무 크면 "tmp_table_size" 값을 증가시켜야 합니다.', + openTables: '열린 테이블', + openTablesHelper: '"table_open_cache" 설정 값이 이 값 이상이어야 합니다.', + selectFullJoin: '전체 조인 선택', + selectFullJoinHelper: '값이 0이 아니면 데이터 테이블의 인덱스가 올바른지 확인하십시오.', + selectRangeCheck: '인덱스 없는 조인 수', + selectRangeCheckHelper: '값이 0이 아니면 데이터 테이블의 인덱스가 올바른지 확인하십시오.', + sortMergePasses: '정렬된 병합 횟수', + sortMergePassesHelper: '값이 너무 크면 "sort_buffer_size" 값을 증가시켜야 합니다.', + tableLocksWaited: '테이블 잠금 대기', + tableLocksWaitedHelper: '값이 너무 크면 데이터베이스 성능을 증가시키는 것을 고려해야 합니다.', + + performanceTuning: '성능 튜닝', + optimizationScheme: '최적화 방안', + keyBufferSizeHelper: '인덱스용 버퍼 크기', + queryCacheSizeHelper: '쿼리 캐시. 이 기능이 비활성화된 경우 이 값을 0으로 설정하세요.', + tmpTableSizeHelper: '임시 테이블 캐시 크기', + innodbBufferPoolSizeHelper: 'Innodb 버퍼 크기', + innodbLogBufferSizeHelper: 'Innodb 로그 버퍼 크기', + sortBufferSizeHelper: '* 연결당, 스레드 정렬 버퍼 크기', + readBufferSizeHelper: '* 연결당, 읽기 버퍼 크기', + readRndBufferSizeHelper: '* 연결당, 임의 읽기 버퍼 크기', + joinBufferSizeHelper: '* 연결당, 조인 테이블 캐시 크기', + threadStackelper: '* 연결당, 스레드별 스택 크기', + binlogCacheSizeHelper: '* 연결당, 이진 로그 캐시 크기 (4096의 배수)', + threadCacheSizeHelper: '스레드 풀 크기', + tableOpenCacheHelper: '테이블 캐시', + maxConnectionsHelper: '최대 연결 수', + restart: '재시작', + + slowLog: '느린 로그', + noData: '아직 느린 로그가 없습니다.', + + isOn: '켜짐', + longQueryTime: '임계값(s)', + thresholdRangeHelper: '올바른 임계값을 입력하십시오 (1 - 600).', + + timeout: '타임아웃(s)', + timeoutHelper: '유휴 연결의 타임아웃 기간. 0은 연결이 지속적으로 유지됨을 의미합니다.', + maxclients: '최대 클라이언트', + requirepassHelper: + '비밀번호가 설정되지 않은 경우 이 필드를 비워 두세요. 변경 사항은 별도로 저장하고 컨테이너를 재시작해야 합니다!', + databases: '데이터베이스 수', + maxmemory: '최대 메모리 사용량', + maxmemoryHelper: '0은 제한이 없음을 의미합니다.', + tcpPort: '현재 수신 포트.', + uptimeInDays: '운영 일수.', + connectedClients: '연결된 클라이언트 수.', + usedMemory: '현재 Redis 의 메모리 사용량.', + usedMemoryRss: '운영 체제에서 요청한 메모리 크기.', + usedMemoryPeak: 'Redis 의 최대 메모리 소비량.', + memFragmentationRatio: '메모리 단편화 비율.', + totalConnectionsReceived: '시작 이후 총 연결된 클라이언트 수.', + totalCommandsProcessed: '실행된 총 명령 수.', + instantaneousOpsPerSec: '초당 서버에서 실행된 명령 수.', + keyspaceHits: '데이터베이스 키가 성공적으로 발견된 횟수.', + keyspaceMisses: '데이터베이스 키를 찾지 못한 횟수.', + hit: '데이터베이스 키 발견 비율.', + latestForkUsec: '마지막 fork() 작업에 소요된 마이크로초 수.', + redisCliHelper: `"redis-cli" 서비스가 감지되지 않았습니다. 서비스를 먼저 활성화하십시오.`, + redisQuickCmd: 'Redis 빠른 명령', + recoverHelper: '이 작업은 데이터를 [{0}]으로 덮어씁니다. 계속하시겠습니까?', + submitIt: '데이터 덮어쓰기', + + baseConf: '기본 설정', + allConf: '모든 설정', + restartNow: '지금 재시작', + restartNowHelper1: + '구성 변경 사항이 적용되려면 시스템을 재시작해야 합니다. 데이터가 지속되어야 하는 경우 먼저 저장 작업을 수행하십시오.', + restartNowHelper: '이 작업은 시스템이 재시작된 후에만 적용됩니다.', + + persistence: '지속성', + rdbHelper1: '초 단위, 삽입', + rdbHelper2: '데이터 항목 수', + rdbHelper3: '조건을 충족하면 RDB 지속성이 트리거됩니다.', + rdbInfo: '규칙 목록의 값이 1에서 100000 사이여야 합니다.', + + containerConn: '컨테이너 연결', + connAddress: '주소', + containerConnHelper: + '이 연결 주소는 웹사이트 런타임(PHP 등) 또는 컨테이너에서 실행 중인 애플리케이션에서 사용할 수 있습니다.', + remoteConn: '외부 연결', + remoteConnHelper2: '컨테이너 환경이 아닌 경우 또는 외부 연결에는 이 주소를 사용하십시오.', + remoteConnHelper3: + '기본 접근 주소는 호스트 IP입니다. 수정하려면 패널 설정 페이지의 "기본 접근 주소" 구성 항목으로 이동하세요.', + localIP: '로컬 IP', + }, + aiTools: { + model: { + model: '모델', + create: '모델 추가', + create_helper: '가져오기 "{0}"', + ollama_doc: 'Ollama 공식 웹사이트를 방문하여 더 많은 모델을 검색하고 찾을 수 있습니다.', + container_conn_helper: '컨테이너 간 접근 또는 연결에 이 주소를 사용', + ollama_sync: 'Ollama 모델 동기화 중 다음 모델이 존재하지 않음을 발견했습니다. 삭제하시겠습니까?', + from_remote: '이 모델은 1Panel을 통해 다운로드되지 않았으며 관련 풀 로그가 없습니다.', + no_logs: '이 모델의 풀 로그가 삭제되어 관련 로그를 볼 수 없습니다.', + }, + proxy: { + proxy: 'AI 프록시 강화', + proxyHelper1: '도메인을 바인딩하고 HTTPS를 활성화하여 전송 보안을 강화', + proxyHelper2: 'IP 접근을 제한하여 공용 인터넷에서의 노출을 방지', + proxyHelper3: '스트리밍을 활성화', + proxyHelper4: '생성 후, 웹사이트 목록에서 이를 보고 관리할 수 있습니다', + proxyHelper5: + '활성화한 후, 앱 스토어 - 설치됨 - Ollama - 매개변수에서 포트 외부 접근을 비활성화하여 보안을 강화할 수 있습니다.', + proxyHelper6: '프록시 구성을 비활성화하려면 웹사이트 목록에서 삭제할 수 있습니다.', + whiteListHelper: '화이트리스트에 있는 IP만 접근 허용', + }, + gpu: { + gpu: 'GPU 모니터링', + gpuHelper: '시스템에서 NVIDIA-SMI 또는 XPU-SMI 명령을 감지하지 못했습니다. 확인하고 다시 시도하세요!', + process: '프로세스 정보', + type: '유형', + typeG: '그래픽', + typeC: '컴퓨팅', + typeCG: '컴퓨팅+그래픽', + processName: '프로세스 이름', + shr: '공유 메모리', + temperatureHelper: 'GPU 온도가 높으면 GPU 주파수가 감소할 수 있습니다', + gpuUtil: 'GPU 사용률', + temperature: '온도', + performanceState: '성능 상태', + powerUsage: '전력 소비', + memoryUsage: '메모리 사용률', + fanSpeed: '팬 속도', + power: '전력', + powerCurrent: '현재 전력', + powerLimit: '전력 제한', + memory: '메모리', + memoryUsed: '사용된 메모리', + memoryTotal: '전체 메모리', + percent: '사용률', + + base: '기본 정보', + driverVersion: '드라이버 버전', + cudaVersion: 'CUDA 버전', + processMemoryUsage: '메모리 사용량', + performanceStateHelper: 'P0(최대 성능)부터 P12(최소 성능)까지', + busID: '버스 주소', + persistenceMode: '지속성 모드', + enabled: '활성화', + disabled: '비활성화', + persistenceModeHelper: '지속성 모드는 작업에 더 빠르게 응답하지만 대기 전력 소비도 그에 따라 증가합니다', + displayActive: 'GPU 초기화', + displayActiveT: '예', + displayActiveF: '아니오', + ecc: '오류 검사 및 수정 기술', + computeMode: '계산 모드', + default: '기본값', + exclusiveProcess: '배타적 프로세스', + exclusiveThread: '배타적 스레드', + prohibited: '금지됨', + defaultHelper: '기본값: 프로세스가 동시에 실행될 수 있음', + exclusiveProcessHelper: + '배타적 프로세스: 하나의 CUDA 컨텍스트만 GPU를 사용할 수 있지만 여러 스레드에서 공유 가능', + exclusiveThreadHelper: '배타적 스레드: CUDA 컨텍스트의 하나의 스레드만 GPU를 사용할 수 있음', + prohibitedHelper: '금지됨: 프로세스 동시 실행이 허용되지 않음', + migModeHelper: 'MIG 인스턴스를 생성하는 데 사용되며 사용자 레이어에서 GPU의 물리적 격리를 구현합니다.', + migModeNA: '지원되지 않음', + current: '실시간 모니터링', + history: '기록', + notSupport: '현재 버전 또는 드라이버는 이 매개변수 표시를 지원하지 않습니다.', + }, + mcp: { + server: 'MCP サーバー', + create: 'サーバーを追加', + edit: 'サーバーを編集', + baseUrl: '外部アクセスパス', + baseUrlHelper: '例: http://192.168.1.2:8000', + ssePath: 'SSE パス', + ssePathHelper: '例: /sse, 他のサーバーと重複しないように注意してください', + environment: '環境変数', + envKey: '変数名', + envValue: '変数値', + externalUrl: '外部接続アドレス', + operatorHelper: '{0} に {1} 操作を実行します、続行しますか?', + domain: 'デフォルトアクセスアドレス', + domainHelper: '例: 192.168.1.1 または example.com', + bindDomain: 'ウェブサイトをバインド', + commandPlaceHolder: '현재 npx 및 바이너리 시작 명령만 지원합니다', + importMcpJson: 'MCP サーバー設定をインポート', + importMcpJsonError: 'mcpServers 構造が正しくありません', + bindDomainHelper: + '웹사이트를 바인딩한 후, 설치된 모든 MCP 서버의 접근 주소를 수정하고 포트의 외부 접근을 닫습니다', + outputTransport: '출력 유형', + streamableHttpPath: '스트리밍 경로', + streamableHttpPathHelper: '예: /mcp, 다른 서버와 중복되지 않도록 주의하세요', + npxHelper: 'npx 또는 바이너리로 시작하는 mcp에 적합', + uvxHelper: 'uvx로 시작하는 mcp에 적합', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: '모델 디렉토리', + commandHelper: '외부 액세스가 필요한 경우 명령에서 포트를 애플리케이션 포트와 동일하게 설정하십시오', + imageAlert: '이미지 크기가 크므로 설치 전에 서버에 이미지를 수동으로 다운로드하는 것이 좋습니다', + modelSpeedup: '모델 가속 활성화', + modelType: '모델 유형', + }, + }, + container: { + create: '컨테이너 만들기', + edit: '컨테이너 편집', + updateHelper1: '이 컨테이너가 앱 스토어에서 왔음을 감지했습니다. 다음 두 가지 사항을 유의하십시오:', + updateHelper2: '1. 현재 수정 사항은 앱 스토어에 설치된 애플리케이션에 동기화되지 않습니다.', + updateHelper3: '2. 설치된 페이지에서 애플리케이션을 수정하면 현재 편집된 내용이 무효화됩니다.', + updateHelper4: '컨테이너 편집에는 재빌드가 필요하며, 비지속적인 데이터는 손실됩니다. 계속하시겠습니까?', + containerList: '컨테이너 목록', + operatorHelper: '{0} 작업이 다음 컨테이너에서 수행됩니다. 계속하시겠습니까?', + operatorAppHelper: + '"{0}" 작업이 다음 컨테이너에서 수행되며, 실행 중인 서비스에 영향을 미칠 수 있습니다. 계속하시겠습니까?', + containerDeleteHelper: + "컨테이너가 앱 스토어에서 생성된 것으로 감지되었습니다. 컨테이너를 삭제해도 1Panel에서 완전히 제거되지 않습니다. 완전히 삭제하려면 앱 스토어 -> '설치됨' 또는 '런타임 환경' 메뉴로 이동하여 작업하십시오. 계속하시겠습니까?", + start: '시작', + stop: '중지', + restart: '재시작', + kill: '강제 종료', + pause: '일시 정지', + unpause: '재개', + rename: '이름 변경', + remove: '제거', + removeAll: '모두 제거', + containerPrune: '정리', + containerPruneHelper1: '이 작업은 중지된 모든 컨테이너를 삭제합니다.', + containerPruneHelper2: + "앱 스토어에서 가져온 컨테이너는 정리 후 '앱 스토어 -> 설치됨' 페이지로 이동하여 '재빌드' 버튼을 클릭하여 재설치해야 합니다.", + containerPruneHelper3: '이 작업은 취소할 수 없습니다. 계속하시겠습니까?', + imagePrune: '정리', + imagePruneSome: '라벨 없는 이미지 정리', + imagePruneSomeEmpty: "라벨이 'none'인 이미지가 정리되지 않았습니다.", + imagePruneSomeHelper: "컨테이너에서 사용되지 않는 'none' 태그가 붙은 이미지를 정리합니다.", + imagePruneAll: '사용되지 않는 이미지 정리', + imagePruneAllEmpty: '사용되지 않는 이미지가 정리되지 않았습니다.', + imagePruneAllHelper: '사용되지 않는 이미지를 정리합니다.', + networkPrune: '정리', + networkPruneHelper: '사용되지 않는 네트워크를 모두 제거합니다. 계속하시겠습니까?', + volumePrune: '정리', + volumePruneHelper: '사용되지 않는 로컬 볼륨을 모두 제거합니다. 계속하시겠습니까?', + cleanSuccess: '작업이 성공적으로 완료되었습니다. 이번 정리에서 {0}개의 항목이 정리되었습니다!', + cleanSuccessWithSpace: + '작업이 성공적으로 완료되었습니다. 이번 정리에서 {0}개의 디스크가 정리되었으며, 확보된 디스크 공간은 {1}입니다!', + unExposedPort: '현재 포트 매핑 주소는 127.0.0.1로 외부 액세스를 활성화할 수 없습니다.', + upTime: '업타임', + fetch: '가져오기', + lines: '라인', + linesHelper: '올바른 로그 수를 입력하세요!', + lastDay: '지난 하루', + last4Hour: '지난 4시간', + lastHour: '지난 1시간', + last10Min: '지난 10분', + cleanLog: '로그 정리', + downLogHelper1: '이 작업은 컨테이너 {0}의 모든 로그를 다운로드합니다. 계속하시겠습니까?', + downLogHelper2: '이 작업은 컨테이너 {0}의 최근 {0}개의 로그를 다운로드합니다. 계속하시겠습니까?', + cleanLogHelper: '이 작업은 컨테이너를 재시작해야 하며 취소할 수 없습니다. 계속하시겠습니까?', + newName: '새 이름', + source: '리소스 사용', + cpuUsage: 'CPU 사용', + cpuTotal: '전체 CPU', + core: '코어', + memUsage: '메모리 사용', + memTotal: '메모리 한도', + memCache: '메모리 캐시', + loadSize: '컨테이너 크기 가져오기', + ip: 'IP 주소', + cpuShare: 'CPU 공유', + cpuShareHelper: + '컨테이너 엔진은 기본값으로 1024를 사용합니다. 이를 늘리면 컨테이너에 더 많은 CPU 시간을 할당할 수 있습니다.', + inputIpv4: '예시: 192.168.1.1', + inputIpv6: '예시: 2001:0db8:85a3:0000:0000:8a2e:0370:7334', + + diskUsage: '디스크 사용량', + localVolume: '로컬 스토리지 볼륨', + buildCache: '빌드 캐시', + usage: '사용됨: {0}, 해제 가능: {1}', + clean: '해제', + imageClean: + '이미지 정리를 수행하면 사용되지 않은 모든 이미지가 삭제됩니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?', + containerClean: + '컨테이너 정리를 수행하면 중지된 모든 컨테이너(앱 스토어의 중지된 앱 포함)가 삭제됩니다. 이 작업은 되돌릴 수 없습니다. 계속하시겠습니까?', + sizeRw: '컨테이너 레이어 크기', + sizeRwHelper: '컨테이너에 고유한 쓰기 가능 레이어 크기', + sizeRootFs: '가상 크기', + sizeRootFsHelper: '컨테이너가 의존하는 모든 이미지 레이어 + 컨테이너 레이어의 총 크기', + + containerFromAppHelper: + '이 컨테이너가 앱 스토어에서 왔음을 감지했습니다. 앱 작업으로 현재 편집이 무효화될 수 있습니다.', + containerFromAppHelper1: + '설치된 애플리케이션 목록에서 [매개변수] 버튼을 클릭하여 편집 페이지로 이동하고 컨테이너 이름을 수정하세요.', + command: '명령어', + console: '컨테이너 상호작용', + tty: '가상 TTY 할당 (-t)', + openStdin: 'STDIN 을 열어둡니다. 연결되지 않더라도 계속 열려있습니다 (-i)', + custom: '사용자 정의', + emptyUser: '비워두면 기본값으로 로그인합니다.', + privileged: '특권 모드', + privilegedHelper: + '컨테이너가 호스트에서 특정 특권 작업을 수행할 수 있도록 허용합니다. 이는 보안 위험을 초래할 수 있으므로 주의해서 사용하십시오.', + upgradeHelper: '레포지토리 이름/이미지 이름: 이미지 버전', + upgradeWarning2: + '업그레이드 작업은 컨테이너를 재빌드해야 하며, 비지속적인 데이터가 손실됩니다. 계속하시겠습니까?', + oldImage: '현재 이미지', + sameImageContainer: '동일 이미지 컨테이너', + sameImageHelper: '동일한 이미지를 사용하는 컨테이너는 선택 후 일괄 업그레이드 가능', + targetImage: '대상 이미지', + imageLoadErr: '컨테이너에 대한 이미지 이름이 감지되지 않았습니다.', + appHelper: '이 컨테이너는 앱 스토어에서 왔으며 업그레이드 시 서비스가 중단될 수 있습니다.', + input: '수동 입력', + forcePull: '이미지 강제 풀', + forcePullHelper: '이 작업은 서버에 있는 기존 이미지를 무시하고 레지스트리에서 최신 이미지를 강제로 가져옵니다.', + server: '호스트', + serverExample: '80, 80-88, ip:80 또는 ip:80-88', + containerExample: '80 또는 80-88', + exposePort: '포트 노출', + exposeAll: '모든 포트 노출', + cmdHelper: '예시: nginx -g "daemon off;"', + entrypointHelper: '예시: docker-entrypoint.sh', + autoRemove: '자동 제거', + cpuQuota: 'CPU 코어 수', + memoryLimit: '메모리', + limitHelper: '0으로 설정하면 제한이 없으며, 최대값은 {0}입니다.', + mount: '마운트', + volumeOption: '볼륨', + hostOption: '호스트', + serverPath: '서버 경로', + containerDir: '컨테이너 경로', + volumeHelper: '저장소 볼륨의 내용이 올바른지 확인하십시오.', + networkEmptyHelper: '컨테이너 네트워크 선택이 올바른지 확인해 주세요', + modeRW: '읽기/쓰기', + modeR: '읽기 전용', + sharedLabel: '전파 모드', + private: '비공개', + privateHelper: '컨테이너와 호스트의 마운트 변경 사항이 서로 영향을 주지 않음', + rprivate: '재귀적 비공개', + rprivateHelper: '컨테이너 내 모든 마운트가 호스트와 완전히 격리됨', + shared: '공유', + sharedHelper: '호스트와 컨테이너의 마운트 변경 사항이 서로 보임', + rshared: '재귀적 공유', + rsharedHelper: '호스트와 컨테이너의 모든 마운트 변경 사항이 서로 보임', + slave: '슬레이브', + slaveHelper: '컨테이너는 호스트 마운트 변경 사항을 볼 수 있지만, 자신의 변경 사항은 호스트에 영향을 주지 않음', + rslave: '재귀적 슬레이브', + rslaveHelper: '컨테이너 내 모든 마운트가 호스트 변경 사항을 볼 수 있지만 호스트에 영향을 주지 않음', + mode: '모드', + env: '환경', + restartPolicy: '재시작 정책', + always: '항상', + unlessStopped: '중지되지 않는 한', + onFailure: '실패 시 (기본 5회)', + no: '절대', + refreshTime: '새로 고침 간격', + cache: '캐시', + image: '이미지 | 이미지들', + imagePull: '풀', + imagePullHelper: '여러 이미지 선택 풀링을 지원하며, 각 이미지 입력 후 Enter 키를 눌러 계속합니다', + imagePush: '푸시', + imagePushHelper: + '이 이미지에 여러 태그가 있는 것으로 감지되었습니다. 푸시 시 사용할 이미지 이름이 다음인지 확인하세요: {0}', + imageDelete: '이미지 삭제', + repoName: '컨테이너 저장소 이름', + imageName: '이미지 이름', + pull: '풀', + path: '경로', + importImage: '가져오기', + buildArgs: '빌드 인수', + imageBuild: '이미지 빌드', + pathSelect: '경로', + label: '레이블', + imageTag: '이미지 태그', + imageTagHelper: '여러 이미지 태그 설정을 지원하며, 각 태그 입력 후 Enter 키를 눌러 계속합니다', + push: '푸시', + fileName: '파일 이름', + export: '내보내기', + exportImage: '이미지 내보내기', + size: '크기', + tag: '태그', + tagHelper: '한 줄에 하나씩. 예시:\nkey1=value1\nkey2=value2', + imageNameHelper: '이미지 이름과 태그, 예시: nginx:latest', + cleanBuildCache: '빌드 캐시 정리', + delBuildCacheHelper: `이 작업은 빌드 중 생성된 모든 캐시된 아티팩트를 삭제하며 되돌릴 수 없습니다. 계속 하시겠습니까?`, + urlWarning: 'URL 접두어에 http:// 또는 https://를 포함할 필요는 없습니다. 수정해 주세요.', + + network: '네트워크 | 네트워크들', + networkHelper: '이로 인해 일부 애플리케이션과 실행 환경이 제대로 작동하지 않을 수 있습니다. 계속 하시겠습니까?', + createNetwork: '생성', + networkName: '이름', + driver: '드라이버', + option: '옵션', + attachable: '연결 가능', + parentNetworkCard: '부모 네트워크 카드', + subnet: '서브넷', + scope: 'IP 범위', + gateway: '게이트웨이', + auxAddress: '제외 IP', + + volume: '볼륨 | 볼륨들', + volumeDir: '볼륨 디렉터리', + nfsEnable: 'NFS 스토리지 사용', + nfsAddress: '주소', + mountpoint: '마운트 지점', + mountpointNFSHelper: '예: /nfs, /nfs-share', + options: '옵션', + createVolume: '생성', + + repo: '레지스트리', + createRepo: '추가', + httpRepoHelper: 'HTTP 타입 저장소 작업 시 Docker 서비스 재시작이 필요합니다.', + httpRepo: 'HTTP 프로토콜을 선택하면 Docker 서비스를 재시작하여 불안정한 레지스트리에 추가해야 합니다.', + delInsecure: '신뢰할 수 없는 항목 삭제', + delInsecureHelper: + '이 작업은 Docker 서비스를 재시작하여 불안정한 레지스트리에서 제거합니다. 계속 하시겠습니까?', + downloadUrl: '서버', + imageRepo: '이미지 레포지토리', + repoHelper: '거울 레포지토리/조직/프로젝트가 포함되어 있습니까?', + auth: '인증 필요', + mirrorHelper: + '거울이 여러 개 있을 경우 각 줄에 하나씩 표시해야 합니다. 예시:\nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com', + registrieHelper: + '개인 레지스트리가 여러 개 있을 경우 각 줄에 하나씩 표시해야 합니다. 예시:\n172.16.10.111:8081 \n172.16.10.112:8081', + + compose: '컴포즈 | 컴포즈들', + composeFile: '컴포즈 파일', + fromChangeHelper: '소스를 변경하면 현재 편집한 내용이 삭제됩니다. 계속 하시겠습니까?', + composePathHelper: '구성 파일 저장 경로: {0}', + composeHelper: '1Panel 에디터나 템플릿을 통해 생성된 컴포지션은 {0}/docker/compose 디렉토리에 저장됩니다.', + deleteFile: '파일 삭제', + deleteComposeHelper: + '이 작업은 컴포즈와 관련된 모든 파일을 삭제합니다. 구성을 포함한 지속적인 파일도 포함됩니다. 신중히 진행해 주세요!', + deleteCompose: '" 이 컴포즈를 삭제하시겠습니까?', + createCompose: '생성', + composeDirectory: '디렉토리', + template: '템플릿', + composeTemplate: '컴포즈 템플릿 | 컴포즈 템플릿들', + createComposeTemplate: '생성', + content: '내용', + contentEmpty: '컴포즈 내용이 비어 있습니다. 입력 후 다시 시도해 주세요!', + containerNumber: '컨테이너 수', + containerStatus: '컨테이너 상태', + exited: '종료됨', + running: '실행 중 ( {0} / {1} )', + composeDetailHelper: '이 컴포즈는 1Panel 외부에서 생성되었습니다. 시작 및 중지 작업은 지원되지 않습니다.', + composeOperatorHelper: '{1} 작업이 {0}에서 수행됩니다. 계속 하시겠습니까?', + composeDownHelper: + '이 작업은 {0} 컴포즈 아래의 모든 컨테이너와 네트워크를 중지하고 제거합니다. 계속 하시겠습니까?', + composeEnvHelper2: + '이 오케스트레이션은 1Panel 앱 스토어에서 생성되었습니다. 설치된 애플리케이션에서 환경 변수를 수정하세요.', + + setting: '설정 | 설정들', + operatorStatusHelper: '이 작업은 Docker 서비스를 "{0}" 합니다. 계속 하시겠습니까?', + dockerStatus: 'Docker 서비스', + daemonJsonPathHelper: '구성 경로가 docker.service 에 지정된 경로와 동일한지 확인하십시오.', + mirrors: '레지스트리 미러들', + mirrorsHelper: '', + mirrorsHelper2: '자세한 내용은 공식 문서를 참조하십시오.', + registries: '불안정한 레지스트리들', + ipv6Helper: + 'IPv6를 활성화하려면 IPv6 컨테이너 네트워크를 추가해야 합니다. 구체적인 구성 단계를 공식 문서에서 참조하십시오.', + ipv6CidrHelper: '컨테이너를 위한 IPv6 주소 풀 범위', + ipv6TablesHelper: 'ip6tables 규칙에 대해 Docker IPv6 을 자동 구성합니다.', + experimentalHelper: 'ip6tables 를 활성화하려면 이 구성을 켜야 합니다. 그렇지 않으면 ip6tables가 무시됩니다.', + cutLog: '로그 옵션', + cutLogHelper1: '현재 구성은 새로 생성된 컨테이너에만 영향을 미칩니다.', + cutLogHelper2: '기존 컨테이너는 재생성해야 구성이 적용됩니다.', + cutLogHelper3: + '컨테이너를 재생성하면 데이터 손실이 발생할 수 있습니다. 중요한 데이터가 포함된 컨테이너는 재구성 전 백업을 꼭 해주세요.', + maxSize: '최대 크기', + maxFile: '최대 파일', + liveHelper: + '기본적으로 Docker 데몬이 종료되면 실행 중인 컨테이너도 종료됩니다. 데몬이 비활성화된 상태에서 컨테이너를 계속 실행하려면 데몬을 설정할 수 있습니다. 이 기능은 라이브 복구라고 불리며, 데몬 충돌, 예정된 중단 또는 업그레이드로 인한 컨테이너 다운타임을 줄이는 데 도움을 줍니다.', + liveWithSwarmHelper: 'live-restore 데몬 구성은 스웜 모드와 호환되지 않습니다.', + iptablesDisable: 'iptables 비활성화', + iptablesHelper1: 'Docker 에 대한 iptables 규칙을 자동으로 구성합니다.', + iptablesHelper2: 'iptables 를 비활성화하면 컨테이너가 외부 네트워크와 통신할 수 없습니다.', + daemonJsonPath: '구성 경로', + serviceUnavailable: `현재 Docker 서비스가 시작되지 않았습니다.`, + startIn: '시작하려면', + sockPath: '유닉스 도메인 소켓', + sockPathHelper: 'Docker 데몬과 클라이언트 간의 통신 채널입니다.', + sockPathHelper1: '기본 경로: /var/run/docker-x.sock', + sockPathMsg: '소켓 경로 설정을 저장하면 Docker 서비스가 사용 불가능할 수 있습니다. 계속 하시겠습니까?', + sockPathErr: '올바른 Docker 소켓 파일 경로를 선택하거나 입력해 주세요.', + related: '관련', + includeAppstore: '앱 스토어에서 컨테이너 표시', + excludeAppstore: '앱스토어 컨테이너 숨기기', + + cleanDockerDiskZone: 'Docker 에서 사용하는 디스크 공간 정리', + cleanImagesHelper: '(사용되지 않는 모든 이미지를 정리합니다.)', + cleanContainersHelper: '(정지된 모든 컨테이너를 정리합니다.)', + cleanVolumesHelper: '(사용되지 않는 모든 로컬 볼륨을 정리합니다.)', + + makeImage: '이미지 생성', + newImageName: '새 이미지 이름', + commitMessage: '커밋 메시지', + author: '작성자', + ifPause: '생성 중 컨테이너 일시 정지', + ifMakeImageWithContainer: '이 컨테이너에서 새 이미지를 생성하시겠습니까?', + finishTime: '마지막 중지 시간', + }, + cronjob: { + create: '크론 작업 생성', + edit: '크론 작업 수정', + importHelper: + '가져오기 시 동일한 이름의 예약 작업은 자동으로 건너뜁니다. 작업은 기본적으로 【비활성화】 상태로 설정되며, 데이터 연동 이상 시 【편집 대기】 상태로 설정됩니다.', + changeStatus: '상태 변경', + disableMsg: '이 작업은 예약된 작업이 자동으로 실행되지 않도록 멈춥니다. 계속하시겠습니까?', + enableMsg: '이 작업은 예약된 작업이 자동으로 실행되도록 허용합니다. 계속하시겠습니까?', + taskType: '작업 유형', + record: '레코드', + viewRecords: '레코드 보기', + shell: '셸', + stop: '수동 중지', + stopHelper: '이 작업은 현재 작업 실행을 강제로 중지합니다. 계속하시겠습니까?', + log: '백업 로그', + logHelper: '시스템 백업 로그', + ogHelper1: '1. 1Panel 시스템 로그', + logHelper2: '2. 서버 SSH 로그인 로그', + logHelper3: '3. 모든 사이트 로그', + containerCheckBox: '컨테이너 내 (컨테이너 명령어 입력 불필요)', + containerName: '컨테이너 이름', + ntp: '시간 동기화', + ntp_helper: 'Toolbox 의 빠른 설정 페이지에서 NTP 서버를 구성할 수 있습니다.', + app: '백업 앱', + website: '백업 웹사이트', + rulesHelper: '여러 개의 제외 규칙 지원, 영어 쉼표 , 로 구분. 예: *.log,*.sql', + lastRecordTime: '마지막 실행 시간', + all: '전체', + failedRecord: '실패한 레코드', + successRecord: '성공한 레코드', + database: '백업 데이터베이스', + backupArgs: '백업 인수', + backupArgsHelper: + '목록에 없는 백업 인수는 수동으로 입력하여 선택할 수 있습니다. 예: --no-data를 입력하고 드롭다운 목록의 첫 번째 옵션을 선택하세요.', + singleTransaction: '단일 트랜잭션을 사용하여 InnoDB 테이블을 백업하며, 대용량 데이터 백업에 적합합니다', + quick: '전체 테이블을 메모리에 로드하는 대신 데이터를 행별로 읽습니다. 대용량 데이터 및 저메모리 시스템 백업에 적합합니다', + skipLockTables: '모든 테이블을 잠그지 않고 백업합니다. 높은 동시성 데이터베이스에 적합합니다', + missBackupAccount: '백업 계정을 찾을 수 없습니다', + syncDate: '동기화 시간', + clean: '캐시 정리', + curl: '접속 URL', + taskName: '작업 이름', + cronSpec: '트리거 주기', + cronSpecDoc: + '사용자 정의 실행 주기는 [분 시 일 월 요일] 형식만 지원합니다 (예: 0 0 * * *). 자세한 내용은 공식 문서를 참조하세요.', + cronSpecHelper: '올바른 실행 주기를 입력해 주세요', + cleanHelper: '이 작업은 모든 작업 실행 레코드, 백업 파일, 로그 파일을 기록합니다. 계속하시겠습니까?', + directory: '백업 디렉토리', + sourceDir: '백업 디렉토리', + snapshot: '시스템 스냅샷', + allOptionHelper: `현재 작업 계획은 모든 [{0}]을 백업하는 것입니다. 현재 직접 다운로드는 지원되지 않습니다. [{0}] 메뉴에서 백업 목록을 확인하실 수 있습니다.`, + exclusionRules: '배제 규칙', + exclusionRulesHelper: + '제외 규칙을 선택하거나 입력하고, 각 세트 입력 후 Enter 키를 눌러 계속합니다. 제외 규칙은 이 백업의 모든 압축 작업에 적용됩니다', + default_download_path: '기본 다운로드 링크', + saveLocal: '로컬 백업 보관 (클라우드 저장소 복사본 수와 동일)', + url: 'URL 주소', + urlHelper: '올바른 URL 주소를 입력해 주세요', + targetHelper: '백업 계정은 패널 설정에서 관리됩니다.', + withImageHelper: '앱 스토어 이미지를 백업하지만 스냅샷 파일 크기가 증가합니다.', + ignoreApp: '앱 제외', + withImage: '애플리케이션 이미지 백업', + retainCopies: '기록 보관', + retryTimes: '재시도 횟수', + timeout: '타임아웃', + ignoreErr: '오류 무시', + ignoreErrHelper: '백업 과정에서 발생하는 오류를 무시하여 모든 백업 작업이 실행되도록 합니다', + retryTimesHelper: '0은 실패 후 재시도 안 함을 의미합니다', + retainCopiesHelper: '실행 기록과 로그에 대해 보관할 복사본 수', + retainCopiesHelper1: '백업 파일에 대해 보관할 복사본 수', + retainCopiesUnit: '개 (보기)', + cronSpecRule: '라인 {0}의 실행 주기 형식이 잘못되었습니다. 확인 후 다시 시도해 주세요!', + perMonthHelper: '매월 {0}일 {1}:{2}에 실행', + perWeekHelper: '매주 {0}일 {1}:{2}에 실행', + perDayHelper: '매일 {0}:{1}에 실행', + perHourHelper: '매시간 {0}분에 실행', + perNDayHelper: '매 {0}일마다 {1}:{2}에 실행', + perNHourHelper: '매 {0}시간마다 {1}에 실행', + perNMinuteHelper: '매 {0}분마다 실행', + perNSecondHelper: '매 {0}초마다 실행', + perMonth: '매월', + perWeek: '매주', + perHour: '매시간', + perNDay: '매 N일', + perDay: '매일', + perNHour: '매 N시간', + perNMinute: '매 N분', + perNSecond: '매 N초', + day: '일', + dayUnit: 'd', + monday: '월요일', + tuesday: '화요일', + wednesday: '수요일', + thursday: '목요일', + friday: '금요일', + saturday: '토요일', + sunday: '일요일', + shellContent: '스크립트', + errRecord: '잘못된 로깅', + errHandle: '크론 작업 실행 실패', + noRecord: '크론 작업을 트리거하고 나면 여기에 레코드가 표시됩니다.', + cleanData: '데이터 정리', + cleanRemoteData: '원격 데이터 삭제', + cleanDataHelper: '이 작업에서 생성된 백업 파일을 삭제합니다.', + noLogs: '작업 출력이 아직 없습니다...', + errPath: '백업 경로 [{0}] 오류, 다운로드할 수 없습니다!', + cutWebsiteLog: '웹사이트 로그 회전', + cutWebsiteLogHelper: '회전된 로그 파일은 1Panel 의 백업 디렉토리로 백업됩니다.', + syncIpGroup: 'WAF IP 그룹 동기화', + + requestExpirationTime: '업로드 요청 만료 시간(시간)', + unitHours: '단위: 시간', + alertTitle: '예정된 작업 - {0} 「{1}」 작업 실패 경고', + library: { + script: '스크립트', + syncNow: '지금 동기화', + turnOnSync: '자동 동기화 켜기', + turnOnSyncHelper: '자동 동기화를 켜면 매일 새벽 시간에 자동 동기화가 수행됩니다', + turnOffSync: '자동 동기화 끄기', + turnOffSyncHelper: '자동 동기화를 끄면 스크립트 동기화가 지연될 수 있습니다. 확인하시겠습니까?', + isInteractive: '대화형', + interactive: '대화형 스크립트', + interactiveHelper: '실행 중 사용자 입력이 필요하며 예약 작업에서는 사용할 수 없습니다.', + library: '스크립트 라이브러리', + remoteLibrary: 'リモートスクリプトライブラリ', + create: '스크립트 추가', + edit: '스크립트 수정', + groupHelper: + '스크립트 특성에 따라 다양한 그룹을 설정하여 스크립트 필터링 작업을 더 빠르게 수행할 수 있습니다.', + handleHelper: '{0} 에서 {1} 스크립트를 실행합니다. 계속하시겠습니까?', + noSuchApp: '{0} 서비스가 감지되지 않았습니다. 스크립트 라이브러리를 사용하여 먼저 빠르게 설치하세요!', + syncHelper: + '시스템 스크립트 라이브러리를 동기화합니다. 이 작업은 시스템 스크립트에만 적용됩니다. 계속하시겠습니까?', + }, + }, + monitor: { + globalFilter: '전역 필터', + enableMonitor: '모니터링 상태', + storeDays: '보관 일수', + defaultNetwork: '기본 네트워크 카드', + defaultNetworkHelper: '모니터링 및 개요 인터페이스에 표시되는 기본 네트워크 카드 옵션', + defaultIO: '기본 디스크', + defaultIOHelper: '모니터링 및 개요 인터페이스에 표시되는 기본 디스크 옵션', + cleanMonitor: '모니터링 기록 지우기', + cleanHelper: '이 작업은 GPU를 포함한 모든 모니터링 기록을 지웁니다. 계속하시겠습니까?', + + avgLoad: '평균 부하', + loadDetail: '부하 세부사항', + resourceUsage: '자원 사용률', + networkCard: '네트워크 인터페이스', + read: '읽기', + write: '쓰기', + readWriteCount: 'I/O 작업', + readWriteTime: 'I/O 지연 시간', + today: '오늘', + yesterday: '어제', + lastNDay: '최근 {0}일', + lastNMonth: '최근 {0}개월', + lastHalfYear: '최근 반년', + memory: '메모리', + percent: '비율', + cache: '캐시', + disk: '디스크', + network: '네트워크', + up: '업', + down: '다운', + interval: '수집 간격', + intervalHelper: '적절한 모니터링 수집 간격을 입력하세요 (5초 - 12시간)', + }, + terminal: { + local: '로컬', + defaultConn: '기본 연결', + defaultConnHelper: '이 작업은 【{0}】의 터미널을 연 후 자동으로 노드 터미널에 연결됩니다. 계속하시겠습니까?', + withReset: '연결 정보 재설정', + localConnJump: '기본 연결 정보는 [터미널 - 설정]에서 관리됩니다. 연결 실패 시 해당 위치에서 편집하세요!', + localHelper: '로컬 이름은 시스템 로컬 식별에만 사용됩니다.', + connLocalErr: '자동 인증에 실패했습니다. 로컬 서버 로그인 정보를 입력해주세요.', + testConn: '연결 테스트', + saveAndConn: '저장 후 연결', + connTestOk: '연결 정보가 유효합니다.', + connTestFailed: '연결할 수 없습니다. 연결 정보를 확인해주세요.', + host: '호스트 | 호스트들', + createConn: '새 연결', + manageGroup: '그룹 관리', + noHost: '호스트 없음', + groupChange: '그룹 변경', + expand: '모두 확장', + fold: '모두 축소', + batchInput: '배치 처리', + quickCommand: '빠른 명령 | 빠른 명령들', + noSuchCommand: '가져온 CSV 파일에서 빠른 명령어 데이터를 찾을 수 없습니다. 확인 후 다시 시도하세요!', + quickCommandHelper: '"터미널 -> 터미널" 하단에서 빠른 명령을 사용할 수 있습니다.', + groupDeleteHelper: '그룹을 제거하면 해당 그룹의 모든 연결이 기본 그룹으로 이동됩니다. 계속하시겠습니까?', + command: '명령', + quickCmd: '빠른 명령', + addHost: '추가', + localhost: '로컬호스트', + ip: '주소', + authMode: '인증 방식', + passwordMode: '비밀번호', + rememberPassword: '인증 정보 기억하기', + keyMode: '개인 키', + key: '개인 키', + keyPassword: '개인 키 비밀번호', + emptyTerminal: '현재 연결된 터미널이 없습니다.', + }, + toolbox: { + common: { + toolboxHelper: '일부 설치 및 사용 문제는 다음을 참고하세요', + }, + swap: { + swap: '스왑 파티션', + swapHelper1: '스왑 크기는 물리적 메모리의 1~2배로 설정해야 하며, 특정 요구 사항에 따라 조정 가능합니다.', + swapHelper2: + '스왑 파일을 생성하기 전에 시스템 디스크에 충분한 가용 공간이 있는지 확인하세요. 스왑 파일 크기만큼 디스크 공간이 점유됩니다.', + swapHelper3: + '스왑은 메모리 압력을 완화하는 데 도움이 될 수 있지만, 단지 대안일 뿐입니다. 스왑에 과도하게 의존하면 시스템 성능이 저하될 수 있으므로 메모리를 추가하거나 애플리케이션 메모리 사용을 최적화하는 것을 우선적으로 고려해야 합니다.', + swapHelper4: '스왑 사용량을 정기적으로 모니터링하여 시스템이 정상적으로 작동하는지 확인하는 것이 좋습니다.', + swapDeleteHelper: + '이 작업은 스왑 파티션 {0}을 제거합니다. 시스템 보안상의 이유로 해당 파일은 자동으로 삭제되지 않습니다. 삭제가 필요한 경우 수동으로 진행하세요!', + saveHelper: '현재 설정을 먼저 저장해주세요!', + saveSwap: '현재 구성을 저장하면 스왑 파티션 {0}의 크기가 {1}(으)로 조정됩니다. 계속하시겠습니까?', + swapMin: '최소 파티션 크기는 40 KB입니다. 수정 후 다시 시도해주세요!', + swapMax: '파티션 크기의 최대값은 {0}입니다. 수정 후 다시 시도해주세요!', + swapOff: '최소 파티션 크기는 40 KB입니다. 0으로 설정하면 스왑 파티션이 비활성화됩니다.', + }, + device: { + dnsHelper: 'DNS 서버', + dnsAlert: '/etc/resolv.conf 파일의 구성을 수정하면 시스템 재부팅 후 파일이 기본값으로 복원됩니다.', + dnsHelper1: 'DNS 항목이 여러 개인 경우 각 항목을 새 줄에 표시해야 합니다. 예:\n114.114.114.114\n8.8.8.8', + hostsHelper: '호스트 이름 해석', + hosts: '도메인', + hostAlert: '주석 처리된 기록은 숨겨져 있습니다. 모든 구성 버튼을 클릭하여 보거나 설정하세요.', + toolbox: '빠른 설정', + hostname: '호스트 이름', + passwd: '시스템 비밀번호', + passwdHelper: '입력 문자는 $ 및 &를 포함할 수 없습니다.', + timeZone: '시간대', + localTime: '서버 시간', + timeZoneChangeHelper: '시스템 시간대를 변경하려면 서비스를 재시작해야 합니다. 계속하시겠습니까?', + timeZoneHelper: `"timedatectl" 명령이 설치되지 않은 경우 시간대를 변경할 수 없습니다. 시스템은 시간대 변경에 이 명령을 사용합니다.`, + timeZoneCN: '베이징', + timeZoneAM: '로스앤젤레스', + timeZoneNY: '뉴욕', + ntpALi: '알리바바', + ntpGoogle: '구글', + syncSite: 'NTP 서버', + hostnameHelper: `호스트 이름 수정은 "hostnamectl" 명령에 따라 달라집니다. 명령이 설치되지 않은 경우 수정이 실패할 수 있습니다.`, + userHelper: `사용자 이름은 "whoami" 명령을 사용하여 검색됩니다. 명령이 설치되지 않은 경우 검색이 실패할 수 있습니다.`, + passwordHelper: `비밀번호 수정은 "chpasswd" 명령에 따라 달라집니다. 명령이 설치되지 않은 경우 수정이 실패할 수 있습니다.`, + hostHelper: '제공된 내용에 빈 값이 포함되어 있습니다. 확인 후 수정하여 다시 시도해주세요!', + dnsCheck: '가용성 테스트', + dnsOK: 'DNS 구성 정보가 유효합니다!', + dnsTestFailed: 'DNS 구성 정보가 유효하지 않습니다.', + }, + fail2ban: { + sshPort: 'SSH 포트 청취', + sshPortHelper: '현재 Fail2ban 은 호스트의 SSH 연결 포트를 청취합니다.', + unActive: '현재 Fail2ban 서비스가 활성화되어 있지 않습니다.', + operation: 'Fail2ban 서비스에서 "{0}" 작업을 수행합니다. 계속하시겠습니까?', + fail2banChange: 'Fail2ban 구성 수정', + ignoreHelper: '허용 목록에 있는 IP는 차단되지 않습니다. 계속하시겠습니까?', + bannedHelper: '차단 목록에 있는 IP는 서버에 의해 차단됩니다. 계속하시겠습니까?', + maxRetry: '최대 재시도 횟수', + banTime: '차단 시간', + banTimeHelper: '기본 차단 시간은 10분이며, -1은 영구 차단을 나타냅니다.', + banTimeRule: '유효한 차단 시간 또는 -1을 입력하세요.', + banAllTime: '영구 차단', + findTime: '탐지 기간', + banAction: '차단 작업', + banActionOption: '{0}을(를) 사용하여 지정된 IP 주소 차단', + allPorts: ' (모든 포트)', + ignoreIP: 'IP 허용 목록', + bannedIP: 'IP 차단 목록', + logPath: '로그 경로', + logPathHelper: '기본값은 /var/log/secure 또는 /var/log/auth.log입니다.', + }, + ftp: { + ftp: 'FTP 계정 | FTP 계정들', + notStart: 'FTP 서비스가 현재 실행 중이 아닙니다. 먼저 시작하세요!', + operation: 'FTP 서비스에서 "{0}" 작업을 수행합니다. 계속하시겠습니까?', + noPasswdMsg: '현재 FTP 계정의 비밀번호를 가져올 수 없습니다. 비밀번호를 설정한 후 다시 시도하세요!', + enableHelper: '선택한 FTP 계정을 활성화하면 접근 권한이 복원됩니다. 계속하시겠습니까?', + disableHelper: '선택한 FTP 계정을 비활성화하면 접근 권한이 취소됩니다. 계속하시겠습니까?', + syncHelper: '서버와 데이터베이스 간의 FTP 계정 데이터를 동기화합니다. 계속하시겠습니까?', + dirSystem: + '이 디렉터리는 시스템 예약 디렉터리입니다. 수정 시 시스템 충돌이 발생할 수 있으니 수정 후 다시 시도하세요!', + dirHelper: 'FTP 활성화를 위해 디렉터리 권한 변경이 필요합니다. 신중하게 선택하세요', + dirMsg: 'FTP 활성화 시 {0} 디렉터리 전체의 권한이 변경됩니다. 계속하시겠습니까?', + }, + clam: { + clam: '바이러스 검사', + cron: '예약 스캔', + cronHelper: '전문 버전에서 예약 스캔 기능을 지원합니다.', + specErr: '실행 일정 형식 오류입니다. 확인 후 다시 시도하세요!', + disableMsg: '예약 실행을 중지하면 이 스캔 작업이 자동으로 실행되지 않습니다. 계속하시겠습니까?', + enableMsg: '예약 실행을 활성화하면 이 스캔 작업이 정기적으로 자동 실행됩니다. 계속하시겠습니까?', + showFresh: '서명 업데이트 서비스 표시', + hideFresh: '서명 업데이트 서비스 숨기기', + clamHelper: + 'ClamAV의 최소 권장 구성은 다음과 같습니다: RAM 3 GiB 이상, 2.0 GHz 이상의 단일 코어 CPU, 최소 5 GiB의 사용 가능한 하드 디스크 공간.', + notStart: 'ClamAV 서비스가 현재 실행 중이 아닙니다. 먼저 시작하세요!', + removeRecord: '보고서 파일 삭제', + noRecords: '"Trigger" 버튼을 클릭하여 스캔을 시작하면 이곳에서 기록을 확인할 수 있습니다.', + removeInfected: '바이러스 파일 삭제', + removeInfectedHelper: '작업 중 감지된 바이러스 파일을 삭제하여 서버 보안 및 정상 작동을 보장합니다.', + clamCreate: '스캔 규칙 생성', + infectedStrategy: '감염 파일 처리 전략', + removeHelper: '바이러스 파일을 삭제합니다. 신중히 선택하세요!', + move: '이동', + moveHelper: '바이러스 파일을 지정된 디렉토리로 이동합니다.', + copyHelper: '바이러스 파일을 지정된 디렉토리로 복사합니다.', + none: '조치 안 함', + noneHelper: '바이러스 파일에 대해 아무 조치도 취하지 않습니다.', + scanDir: '스캔 디렉토리', + infectedDir: '감염 파일 디렉토리', + scanDate: '스캔 날짜', + scanResult: '스캔 로그 출력', + tail: '라인', + infectedFiles: '감염된 파일', + log: '상세 내용', + clamConf: 'Clam AV 데몬', + clamLog: '@:toolbox.clam.clamConf 로그', + freshClam: 'FreshClam', + freshClamLog: '@:toolbox.clam.freshClam 로그', + alertHelper: '전문 버전에서 예약 스캔 및 SMS 알림을 지원합니다.', + alertTitle: '바이러스 스캔 작업 「{0}」에서 감염된 파일을 발견했습니다.', + }, + }, + logs: { + core: '패널 서비스', + agent: '노드 모니터링', + panelLog: '패널 로그', + operation: '작업 로그', + login: '로그인 로그', + loginIP: '로그인 IP', + loginAddress: '로그인 주소', + loginAgent: '로그인 에이전트', + loginStatus: '상태', + system: '시스템 로그', + deleteLogs: '로그 정리', + resource: '자원', + detail: { + dashboard: '개요', + ai: 'AI', + groups: '그룹', + hosts: '호스트', + apps: '애플리케이션', + websites: '웹사이트', + containers: '컨테이너', + files: '파일 관리', + runtimes: '실행 환경', + process: '프로세스 관리', + toolbox: '도구 상자', + backups: '백업 / 복원', + tampers: '변조 방지', + xsetting: '인터페이스 설정', + logs: '로그 감사', + settings: '패널 설정', + cronjobs: '예약 작업', + waf: 'WAF', + databases: '데이터베이스', + licenses: '라이선스', + nodes: '노드', + commands: '빠른 명령', + }, + websiteLog: '웹사이트 로그', + runLog: '실행 로그', + errLog: '에러 로그', + }, + file: { + fileDirNum: '총 {0}개 디렉터리, {1}개 파일,', + currentDir: '현재 디렉터리', + dir: '폴더', + upload: '업로드', + uploadFile: '@:file.upload @.lower:file.file', + uploadDirectory: '@:file.upload @.lower:file.dir', + download: '다운로드', + fileName: '파일 이름', + search: '검색', + mode: '권한', + editPermissions: '@:file.mode', + owner: '소유자', + file: '파일', + remoteFile: '원격에서 다운로드', + share: '공유', + sync: '데이터 동기화', + size: '크기', + updateTime: '수정됨', + rename: '이름 바꾸기', + role: '권한', + info: '속성', + linkFile: '소프트 링크', + batchoperation: '일괄 작업', + shareList: '공유 목록', + zip: '압축됨', + group: '그룹', + path: '경로', + public: '기타', + setRole: '권한 설정', + link: '파일 링크', + rRole: '읽기', + wRole: '쓰기', + xRole: '실행 가능', + name: '이름', + compress: '압축', + deCompress: '압축 해제', + compressType: '압축 형식', + compressDst: '압축 경로', + replace: '기존 파일 덮어쓰기', + compressSuccess: '압축 성공', + deCompressSuccess: '압축 해제 성공', + deCompressDst: '압축 해제 경로', + linkType: '링크 유형', + softLink: '소프트 링크', + hardLink: '하드 링크', + linkPath: '링크 경로', + selectFile: '파일 선택', + downloadUrl: '원격 URL', + downloadStart: '다운로드 시작됨', + moveSuccess: '이동 성공', + copySuccess: '복사 성공', + pasteMsg: '대상 디렉토리의 오른쪽 상단에 있는 [붙여넣기] 버튼을 클릭하세요', + move: '이동', + calculate: '계산', + remark: '비고', + setRemark: '비고 설정', + remarkPrompt: '비고를 입력하세요', + remarkPlaceholder: '비고', + remarkToggle: '비고', + remarkToggleTip: '파일 비고 로드', + canNotDeCompress: '이 파일은 압축 해제할 수 없습니다', + uploadSuccess: '업로드 성공', + downloadProcess: '다운로드 진행률', + downloading: '다운로드 중...', + infoDetail: '파일 속성', + root: '루트 디렉터리', + list: '파일 목록', + sub: '하위 폴더', + downloadSuccess: '다운로드 성공', + theme: '테마', + language: '언어', + eol: '줄 끝', + copyDir: '복사', + paste: '붙여넣기', + changeOwner: '사용자 및 그룹 수정', + containSub: '권한 변경을 하위 폴더에 적용', + ownerHelper: + 'PHP 운영 환경의 기본 사용자: 사용자 그룹은 1000:1000 입니다. 컨테이너 내부 및 외부에서 표시되는 불일치는 정상입니다.', + searchHelper: '* 등의 와일드카드를 지원합니다', + uploadFailed: '[{0}] 파일 업로드 실패', + fileUploadStart: '[{0}] 업로드 중....', + currentSelect: '현재 선택: ', + unsupportedType: '지원되지 않는 파일 유형', + deleteHelper: '다음 파일을 삭제하시겠습니까? 기본적으로 삭제 후 휴지통으로 이동합니다.', + fileHelper: `참고:\n1. 검색 결과는 정렬할 수 없습니다.\n2. 폴더는 크기로 정렬할 수 없습니다.`, + forceDeleteHelper: '파일을 영구적으로 삭제합니다(휴지통으로 이동하지 않고 바로 삭제).', + recycleBin: '휴지통', + sourcePath: '원래 경로', + deleteTime: '삭제 시간', + confirmReduce: '다음 파일을 복원하시겠습니까?', + reduceSuccess: '복원 성공', + reduce: '복원', + reduceHelper: '원래 경로에 동일한 이름의 파일이나 디렉터리가 있으면 덮어씁니다. 계속하시겠습니까?', + clearRecycleBin: '정리', + clearRecycleBinHelper: '휴지통을 정리하시겠습니까?', + favorite: '즐겨찾기', + removeFavorite: '즐겨찾기에서 제거하시겠습니까?', + addFavorite: '즐겨찾기 추가 / 제거', + clearList: '목록 정리', + deleteRecycleHelper: '다음 파일을 영구적으로 삭제하시겠습니까?', + typeErrOrEmpty: '[{0}] 파일 유형이 잘못되었거나 빈 폴더입니다.', + dropHelper: '업로드하려는 파일을 여기에 드래그하세요', + fileRecycleBin: '휴지통 활성화', + fileRecycleBinMsg: '{0} 휴지통', + wordWrap: '자동 줄바꿈', + deleteHelper2: '선택한 파일을 삭제하시겠습니까? 삭제 작업은 되돌릴 수 없습니다.', + ignoreCertificate: '안전하지 않은 서버 연결 허용', + ignoreCertificateHelper: + '안전하지 않은 서버 연결을 허용하면 데이터 유출 또는 변조가 발생할 수 있습니다. 이 옵션은 다운로드 소스를 신뢰할 때만 사용하세요.', + uploadOverLimit: '파일 수가 1000 개를 초과했습니다! 압축하여 업로드하세요.', + clashDitNotSupport: '파일 이름에 .1panel_clash 를 포함할 수 없습니다.', + clashDeleteAlert: `"휴지통" 폴더는 삭제할 수 없습니다.`, + clashOpenAlert: '휴지통 디렉터리를 열려면 "휴지통" 버튼을 클릭하세요.', + right: '앞으로', + back: '뒤로', + top: '처음으로 돌아가기', + up: '뒤로가기', + openWithVscode: 'VS Code 로 열기', + vscodeHelper: '로컬에 VS Code 가 설치되어 있고 SSH Remote 플러그인이 구성되어 있는지 확인하세요.', + saveContentAndClose: '파일이 수정되었습니다. 저장 후 닫으시겠습니까?', + saveAndOpenNewFile: '파일이 수정되었습니다. 저장 후 새 파일을 열겠습니까?', + noEdit: '파일이 수정되지 않았습니다. 이 작업은 필요하지 않습니다!', + noNameFolder: '제목 없는 폴더', + noNameFile: '제목 없는 파일', + minimap: '코드 미니맵', + fileCanNotRead: '파일을 읽을 수 없습니다.', + previewTruncated: '파일이 너무 커서 마지막 부분만 표시됩니다', + previewEmpty: '파일이 비어 있거나 텍스트 파일이 아닙니다', + previewLargeFile: '미리보기', + panelInstallDir: `1Panel 설치 디렉터리는 삭제할 수 없습니다.`, + wgetTask: '다운로드 작업', + existFileTitle: '동일한 이름의 파일 경고', + existFileHelper: '업로드한 파일에 동일한 이름의 파일이 포함되어 있습니다. 덮어쓰시겠습니까?', + existFileSize: '파일 크기 (새로운 -> 오래된)', + existFileDirHelper: '선택한 파일/폴더에 동일한 이름이 이미 존재합니다. 신중하게 작업하세요!', + coverDirHelper: '덮어쓸 폴더를 선택하면 대상 경로로 복사됩니다!', + noSuchFile: '파일 또는 디렉터리를 찾을 수 없습니다. 확인 후 다시 시도하세요.', + setting: '설정', + showHide: '숨김 파일 표시', + noShowHide: '숨김 파일 숨기기', + cancelUpload: '업로드 취소', + cancelUploadHelper: '업로드를 취소할지 여부, 취소 후 업로드 목록이 비워집니다.', + keepOneTab: '최소한 하나의 탭을 유지하세요', + notCanTab: '더 이상 탭을 추가할 수 없습니다', + convert: '파일 형식 변환', + converting: '로 변환 중', + fileCanNotConvert: '이 파일은 형식 변환을 지원하지 않습니다', + formatType: '형식 종류', + sourceFormat: '원본 형식', + sourceFile: '원본 파일', + saveDir: '저장 디렉토리', + deleteSourceFile: '원본 파일 삭제 여부', + convertHelper: '선택한 파일을 다른 형식으로 변환합니다', + convertHelper1: '변환할 파일을 선택하세요', + execConvert: '변환을 시작합니다. 작업 센터에서 변환 로그를 확인할 수 있습니다', + convertLogs: '변환 로그', + formatConvert: '형식 변환', + }, + ssh: { + autoStart: '자동 시작', + enable: '자동 시작 활성화', + disable: '자동 시작 비활성화', + sshAlert: + '목록 데이터는 로그인 날짜를 기준으로 정렬됩니다. 시간대 변경이나 다른 작업이 수행되면 로그인 로그의 날짜에 차이가 발생할 수 있습니다.', + sshAlert2: + '"Fail2ban"을 "도구 상자"에서 사용하여 무차별 대입 공격을 시도하는 IP 주소를 차단할 수 있으며, 이는 호스트의 보안을 강화하는 데 도움이 됩니다.', + sshOperate: 'SSH 서비스에서 "{0}" 작업을 수행합니다. 계속하시겠습니까?', + sshChange: 'SSH 설정', + sshChangeHelper: '"{0}"을(를) "{1}"로 변경하였습니다. 계속하시겠습니까?', + sshFileChangeHelper: + '구성 파일을 수정하면 서비스 가용성에 영향을 미칠 수 있습니다. 이 작업을 수행할 때는 주의하십시오. 계속하시겠습니까?', + port: '포트', + portHelper: 'SSH 서비스가 수신하는 포트를 지정하십시오.', + listenAddress: '수신 주소', + allV4V6: '0.0.0.0:{0}(IPv4) 및 :::{0}(IPv6)', + listenHelper: 'IPv4와 IPv6 설정을 모두 비워두면 "0.0.0.0:{0}(IPv4)"와 ":::{0}(IPv6)"에서 수신합니다.', + addressHelper: 'SSH 서비스가 수신할 주소를 지정하십시오.', + permitRootLogin: '루트 사용자 로그인 허용', + rootSettingHelper: '루트 사용자의 기본 로그인 방법은 "SSH 로그인 허용"입니다.', + rootHelper1: 'SSH 로그인 허용', + rootHelper2: 'SSH 로그인 비허용', + rootHelper3: '키 로그인만 허용', + rootHelper4: '미리 정의된 명령만 실행할 수 있습니다. 다른 작업은 수행할 수 없습니다.', + passwordAuthentication: '비밀번호 인증', + pwdAuthHelper: '비밀번호 인증을 활성화할지 여부입니다. 기본적으로 이 매개변수는 활성화되어 있습니다.', + pubkeyAuthentication: '키 인증', + privateKey: '개인 키', + publicKey: '공개 키', + password: '비밀번호', + createMode: '생성 방식', + generate: '자동 생성', + unSyncPass: '키 비밀번호 동기화 불가', + syncHelper: '동기화 작업으로 유효하지 않은 키를 정리하고 새로운 완전한 키 쌍을 동기화합니다. 계속하시겠습니까?', + input: '수동 입력', + import: '파일 업로드', + authKeys: 'Kunci Pengesahan', + authKeysHelper: '현재 공개 키 정보를 저장하시겠습니까?', + pubkey: '키 정보', + encryptionMode: '암호화 모드', + pubKeyHelper: '현재 키 정보는 사용자 {0}에게만 적용됩니다', + passwordHelper: '6~10자리 숫자 및 영어 대소문자를 포함할 수 있습니다.', + reGenerate: '키 재생성', + keyAuthHelper: '키 인증을 활성화할지 여부입니다.', + useDNS: 'useDNS', + dnsHelper: 'SSH 서버에서 DNS 확인 기능을 활성화하여 연결의 신원을 확인할지 여부를 제어합니다.', + analysis: '통계 정보', + denyHelper: + "'거부' 작업을 다음 주소에서 수행합니다. 설정 후 해당 IP는 서버에 접근할 수 없습니다. 계속하시겠습니까?", + acceptHelper: + "'수락' 작업을 다음 주소에서 수행합니다. 설정 후 해당 IP는 정상적으로 접근할 수 있습니다. 계속하시겠습니까?", + noAddrWarning: '현재 [{0}] 주소가 선택되지 않았습니다. 확인 후 다시 시도하십시오!', + loginLogs: '로그인 로그', + loginMode: '모드', + authenticating: '키', + publickey: '키', + belong: '소속', + local: '로컬', + session: '세션 | 세션들', + loginTime: '로그인 시간', + loginIP: '로그인 IP', + stopSSHWarn: '이 SSH 연결을 끊으시겠습니까?', + }, + setting: { + panel: '패널', + user: '패널 사용자', + userChange: '패널 사용자 변경', + userChangeHelper: '패널 사용자를 변경하면 로그아웃됩니다. 계속하시겠습니까?', + passwd: '패널 비밀번호', + emailHelper: '비밀번호 복구용', + watermark: '워터마크 설정', + watermarkContent: '워터마크 내용', + contentHelper: + '{0} 은 노드 이름, {1} 은 노드 주소를 나타냅니다. 변수를 사용하거나 사용자 정의 이름을 입력할 수 있습니다.', + watermarkColor: '워터마크 색상', + watermarkFont: '워터마크 글꼴 크기', + watermarkHeight: '워터마크 높이', + watermarkWidth: '워터마크 너비', + watermarkRotate: '회전 각도', + watermarkGap: '간격', + watermarkCloseHelper: '시스템 워터마크 설정을 해제하시겠습니까?', + watermarkOpenHelper: '현재 시스템 워터마크 설정을 저장하시겠습니까?', + title: '패널 별칭', + panelPort: '패널 포트', + titleHelper: '영문자, 한자, 숫자, 공백, 일반 특수 문자를 포함하여 3~30자의 길이를 지원합니다.', + portHelper: + '권장 포트 범위는 8888에서 65535 사이입니다. 참고: 서버에 보안 그룹이 있는 경우, 보안 그룹에서 새로운 포트를 사전에 허용해야 합니다.', + portChange: '포트 변경', + portChangeHelper: '서비스 포트를 수정하고 서비스를 다시 시작합니다. 계속하시겠습니까?', + theme: '테마', + menuTabs: '메뉴 탭', + dark: '다크', + darkGold: '다크 골드', + light: '라이트', + auto: '시스템 따라가기', + language: '언어', + languageHelper: '기본적으로 브라우저 언어를 따릅니다. 이 설정은 현재 브라우저에서만 적용됩니다.', + sessionTimeout: '세션 타임아웃', + sessionTimeoutError: '최소 세션 타임아웃은 300초입니다.', + sessionTimeoutHelper: '패널에서 {0}초 이상 조작이 없을 경우 자동으로 로그아웃됩니다.', + systemIP: '기본 접근 주소', + systemIPHelper: + '애플리케이션 리다이렉트, 컨테이너 접근 등의 기능은 이 주소를 사용하여 라우팅됩니다. 각 노드마다 다른 주소를 설정할 수 있습니다.', + proxy: '서버 프록시', + proxyHelper: '프록시 서버를 설정한 후 다음 시나리오에서 적용됩니다:', + proxyHelper1: '설치 패키지 다운로드 및 앱 스토어 동기화 (전문 버전에서만 제공)', + proxyHelper2: '시스템 업데이트 및 업데이트 정보 가져오기 (전문 버전에서만 제공)', + proxyHelper4: 'Docker 네트워크가 프록시 서버를 통해 액세스됩니다 (전문 버전에서만 제공)', + proxyHelper3: '시스템 라이선스 인증 및 동기화', + proxyHelper5: '시스템 유형 스크립트 라이브러리의 통합 다운로드 및 동기화 (프로페셔널 에디션 기능)', + proxyHelper6: '인증서 신청 (프로 버전 기능)', + proxyType: '프록시 유형', + proxyUrl: '프록시 주소', + proxyPort: '프록시 포트', + proxyPasswdKeep: '비밀번호 기억', + proxyDocker: 'Docker 프록시', + proxyDockerHelper: + '프록시 서버 구성을 Docker 에 동기화하여 오프라인 서버 이미지 가져오기 등의 작업을 지원합니다.', + syncToNode: '자식 노드로 동기화', + syncToNodeHelper: '다른 노드로 설정 동기화', + nodes: '노드', + selectNode: '노드 선택', + selectNodeError: '노드를 선택해 주세요', + apiInterface: 'API 활성화', + apiInterfaceClose: '비활성화하면 API 인터페이스에 접근할 수 없습니다. 계속하시겠습니까?', + apiInterfaceHelper: '서드파티 애플리케이션이 API 에 접근할 수 있도록 허용합니다.', + apiInterfaceAlert1: '운영 환경에서는 활성화하지 마십시오. 서버 보안 위험이 증가할 수 있습니다.', + apiInterfaceAlert2: '잠재적인 보안 위협을 방지하기 위해 서드파티 애플리케이션으로 API를 호출하지 마십시오.', + apiInterfaceAlert3: 'API 문서', + apiInterfaceAlert4: '사용 설명서', + apiKey: 'API 키', + apiKeyHelper: 'API 키는 서드파티 애플리케이션이 API 에 접근하는 데 사용됩니다.', + ipWhiteList: 'IP 허용 목록', + ipWhiteListEgs: '한 줄에 하나씩 입력하십시오. 예:\n172.161.10.111\n172.161.10.0/24', + ipWhiteListHelper: '허용 목록에 있는 IP만 API 에 접근할 수 있습니다. 0.0.0.0/0(모든 IPv4), ::/0(모든 IPv6)', + apiKeyValidityTime: '인터페이스 키 유효 기간', + apiKeyValidityTimeEgs: '인터페이스 키 유효 기간 (분 단위)', + apiKeyValidityTimeHelper: + '인터페이스 타임스탬프가 현재 타임스탬프와의 차이가 허용 범위 내에 있을 경우 유효합니다. 값이 0이면 검증이 비활성화됩니다.', + apiKeyReset: '인터페이스 키 재설정', + apiKeyResetHelper: '연관된 키 서비스가 무효화됩니다. 서비스에 새 키를 추가하십시오.', + confDockerProxy: 'Docker 프록시 구성', + restartNowHelper: 'Docker 프록시 구성을 위해 Docker 서비스를 재시작해야 합니다.', + restartNow: '즉시 재시작', + restartLater: '나중에 수동으로 재시작', + systemIPWarning: '현재 노드에 기본 접근 주소가 설정되지 않았습니다. 패널 설정에서 설정해 주세요!', + systemIPWarning1: '현재 서버 주소는 {0}으로 설정되어 있어 빠른 리디렉션이 불가능합니다!', + defaultNetwork: '네트워크 카드', + syncTime: '서버 시간', + timeZone: '시간대', + timeZoneChangeHelper: '시간대를 변경하면 서비스를 재시작해야 합니다. 계속하시겠습니까?', + timeZoneHelper: '시간대 변경은 시스템 timedatectl 서비스에 따라 작동하며, 1Panel 서비스 재시작 후 적용됩니다.', + timeZoneCN: '베이징', + timeZoneAM: '로스앤젤레스', + timeZoneNY: '뉴욕', + ntpALi: '알리바바', + ntpGoogle: '구글', + syncSite: 'NTP 서버', + syncSiteHelper: '이 작업은 {0}을(를) 시스템 시간 동기화의 소스로 사용합니다. 계속하시겠습니까?', + changePassword: '비밀번호 변경', + oldPassword: '기존 비밀번호', + newPassword: '새 비밀번호', + retryPassword: '비밀번호 확인', + noSpace: '입력 정보에 공백 문자를 포함할 수 없습니다.', + duplicatePassword: '새 비밀번호는 기존 비밀번호와 동일할 수 없습니다. 다시 입력하십시오!', + diskClean: '캐시 정리', + developerMode: '미리보기 프로그램', + developerModeHelper: '새로운 기능과 수정 사항을 정식 출시 전에 체험하고 초기 피드백을 제공할 수 있습니다.', + thirdParty: '서드파티 계정', + noTypeForCreate: '현재 생성된 백업 유형이 없습니다.', + LOCAL: '서버 디스크', + OSS: 'Ali OSS', + S3: 'Amazon S3', + mode: '모드', + MINIO: 'MinIO', + SFTP: 'SFTP', + WebDAV: 'WebDAV', + WebDAVAlist: 'Alist 에 WebDAV 를 연결하는 방법은 공식 문서를 참조하십시오.', + OneDrive: 'Microsoft OneDrive', + isCN: 'Century Internet', + isNotCN: '국제 버전', + client_id: '클라이언트 ID', + client_secret: '클라이언트 시크릿', + redirect_uri: '리디렉션 URL', + onedrive_helper: '사용자 정의 구성은 공식 문서를 참조하십시오.', + refreshTime: '토큰 갱신 시간', + refreshStatus: '토큰 갱신 상태', + backupDir: '백업 디렉터리', + codeWarning: '현재 인증 코드 형식이 올바르지 않습니다. 다시 확인하십시오!', + code: '인증 코드', + codeHelper: + '"획득" 버튼을 클릭한 다음 OneDrive 에 로그인하여 리디렉션된 링크에서 "code" 이후의 내용을 복사하십시오. 이 입력 상자에 붙여넣으십시오. 자세한 지침은 공식 문서를 참조하십시오.', + googleHelper: + '먼저 Google 애플리케이션을 생성하고 클라이언트 정보를 획득한 후 양식을 작성하고 획득 버튼을 클릭하세요. 구체적인 작업은 공식 문서를 참조하십시오.', + loadCode: '획득', + COS: 'Tencent COS', + ap_beijing_1: '베이징 지역 1', + ap_beijing: '베이징', + ap_nanjing: '난징', + ap_shanghai: '상하이', + ap_guangzhou: '광저우', + ap_chengdu: '청두', + ap_chongqing: '충칭', + ap_shenzhen_fsi: '선전 금융', + ap_shanghai_fsi: '상하이 금융', + ap_beijing_fsi: '베이징 금융', + ap_hongkong: '홍콩, 중국', + ap_singapore: '싱가포르', + ap_mumbai: '뭄바이', + ap_jakarta: '자카르타', + ap_seoul: '서울', + ap_bangkok: '방콕', + ap_tokyo: '도쿄', + na_siliconvalley: '실리콘밸리 (미국 서부)', + na_ashburn: '애쉬번 (미국 동부)', + na_toronto: '토론토', + sa_saopaulo: '상파울루', + eu_frankfurt: '프랑크푸르트', + KODO: 'Qiniu Kodo', + scType: '스토리지 유형', + typeStandard: '표준', + typeStandard_IA: '저빈도 표준', + typeArchive: '아카이브', + typeDeep_Archive: '심층 아카이브', + scLighthouse: '기본값: 경량 오브젝트 스토리지는 이 스토리지 유형만 지원합니다', + scStandard: + '표준 스토리지는 실시간 접근, 빈번한 데이터 상호작용이 필요한 핫 파일이 많은 비즈니스 시나리오에 적합합니다.', + scStandard_IA: + '저빈도 스토리지는 접근 빈도가 비교적 낮고 최소 30일 동안 데이터를 저장하는 비즈니스 시나리오에 적합합니다.', + scArchive: '아카이브 스토리지는 접근 빈도가 극히 낮은 비즈니스 시나리오에 적합합니다.', + scDeep_Archive: '내구성이 뛰어난 콜드 스토리지는 접근 빈도가 극히 낮은 비즈니스 시나리오에 적합합니다.', + archiveHelper: + '아카이브 스토리지 파일은 직접 다운로드할 수 없으며, 해당 클라우드 서비스 제공자의 웹사이트를 통해 복원해야 합니다. 신중히 사용하십시오!', + backupAlert: 'S3 프로토콜을 지원하는 클라우드 공급자는 Amazon S3 를 사용하여 백업할 수 있습니다.', + domain: '가속 도메인', + backupAccount: '백업 계정 | 백업 계정', + loadBucket: '버킷 가져오기', + accountName: '계정 이름', + accountKey: '계정 키', + address: '주소', + path: '경로', + + safe: '보안', + passkey: '패스키', + passkeyManage: '관리', + passkeyHelper: '빠른 로그인을 위해 최대 5개의 패스키를 등록할 수 있습니다', + passkeyRequireSSL: 'HTTPS를 활성화하면 패스키를 사용할 수 있습니다', + passkeyNotSupported: '현재 브라우저 또는 환경에서 패스키를 지원하지 않습니다', + passkeyCount: '등록됨 {0}/{1}', + passkeyName: '이름', + passkeyNameHelper: '기기를 구분할 이름을 입력하세요', + passkeyAdd: '패스키 추가', + passkeyCreatedAt: '생성 시간', + passkeyLastUsedAt: '최근 사용', + passkeyDeleteConfirm: '삭제하면 이 패스키로 로그인할 수 없습니다. 계속하시겠습니까?', + passkeyLimit: '최대 5개의 패스키를 등록할 수 있습니다', + passkeyFailed: '패스키 등록에 실패했습니다. 패널 SSL 인증서가 신뢰할 수 있는지 확인하세요', + bindInfo: '바인딩 정보', + bindAll: '모두 수신', + bindInfoHelper: '서비스 수신 주소나 프로토콜 변경은 서비스 불가 상태를 초래할 수 있습니다. 계속하시겠습니까?', + ipv6: 'IPv6 수신', + bindAddress: '수신 주소', + entrance: '진입점', + showEntrance: '"개요" 페이지에서 비활성화 경고 표시', + entranceHelper: '보안 진입점을 활성화하면 지정된 보안 진입점을 통해서만 패널에 로그인할 수 있습니다.', + entranceError: '5-116자의 안전한 로그인 진입점을 입력하십시오. 숫자나 문자만 지원됩니다.', + entranceInputHelper: '공백으로 남기면 보안 진입점이 비활성화됩니다.', + randomGenerate: '랜덤', + expirationTime: '만료 날짜', + unSetting: '미설정', + noneSetting: '패널 비밀번호의 만료 시간을 설정합니다. 만료 후 비밀번호를 재설정해야 합니다.', + expirationHelper: '비밀번호 만료 시간이 [0]일인 경우 비밀번호 만료 기능이 비활성화됩니다.', + days: '만료 일수', + expiredHelper: '현재 비밀번호가 만료되었습니다. 비밀번호를 다시 변경하십시오.', + timeoutHelper: '[ {0}일 ] 패널 비밀번호가 곧 만료됩니다. 만료 후 비밀번호를 재설정해야 합니다.', + complexity: '복잡성 검증', + complexityHelper: + '활성화하면 비밀번호 검증 규칙이 8-30 자, 영어, 숫자 및 최소 두 개의 특수 문자 포함으로 설정됩니다.', + bindDomain: '도메인 바인딩', + unBindDomain: '도메인 바인딩 해제', + panelSSL: '패널 SSL', + unBindDomainHelper: '도메인 이름 바인딩 해제 작업은 시스템 보안에 영향을 미칠 수 있습니다. 계속하시겠습니까?', + bindDomainHelper: '도메인을 바인딩한 후에는 해당 도메인을 통해서만 1Panel 서비스에 접근할 수 있습니다.', + bindDomainHelper1: '공백으로 남기면 도메인 이름 바인딩이 비활성화됩니다.', + bindDomainWarning: + '도메인 바인딩 후 로그아웃되며 설정에 지정된 도메인 이름을 통해서만 1Panel 서비스에 접근할 수 있습니다. 계속하시겠습니까?', + allowIPs: '허가된 IP', + unAllowIPs: '허가되지 않은 IP', + unAllowIPsWarning: + '허가되지 않은 빈 IP를 설정하면 모든 IP가 시스템에 접근할 수 있어 시스템 보안에 영향을 미칠 수 있습니다. 계속하시겠습니까?', + allowIPsHelper: '허가된 IP 주소 목록을 설정하면 목록에 있는 IP 주소만 패널 서비스에 접근할 수 있습니다.', + allowIPsWarning: + '허가된 IP 주소 목록을 설정하면 목록에 있는 IP 주소만 패널 서비스에 접근할 수 있습니다. 계속하시겠습니까?', + allowIPsHelper1: '공백으로 남기면 IP 주소 제한이 비활성화됩니다.', + allowIPEgs: '한 줄에 하나씩 입력하십시오. 예:\n172.16.10.111\n172.16.10.0/24', + mfa: '2단계 인증 (2FA)', + mfaClose: 'MFA를 비활성화하면 서비스 보안이 낮아집니다. 계속하시겠습니까?', + secret: '비밀키', + mfaInterval: '갱신 간격(초)', + mfaTitleHelper: + '제목은 다른 1Panel 호스트를 구별하는 데 사용됩니다. 제목을 수정한 후 다시 스캔하거나 비밀키를 수동으로 추가하세요.', + mfaIntervalHelper: '갱신 시간을 수정한 후 다시 스캔하거나 비밀키를 수동으로 추가하세요.', + mfaAlert: + '일회용 토큰은 현재 시간을 기반으로 생성된 동적 6자리 숫자입니다. 서버 시간이 동기화되어 있는지 확인하세요.', + mfaHelper: '활성화 후 일회용 토큰 검증이 필요합니다.', + mfaHelper1: '인증 앱을 다운로드하세요. 예를 들어,', + mfaHelper2: + '다음 QR 코드를 인증 앱으로 스캔하거나 비밀키를 복사하여 인증 앱에 입력하여 일회용 토큰을 획득하세요.', + mfaHelper3: '앱에서 생성된 6자리 숫자를 입력하세요.', + mfaCode: '일회용 토큰', + sslChangeHelper: 'HTTPS 설정을 수정하고 서비스를 재시작합니다. 계속하시겠습니까?', + sslDisable: '비활성화', + sslDisableHelper: 'HTTPS 서비스를 비활성화하면 패널을 재시작해야 적용됩니다. 계속하시겠습니까?', + noAuthSetting: '비인가 설정', + noAuthSettingHelper: + '지정된 보안 진입점을 사용하지 않거나, 지정된 IP 나 도메인에서 패널에 접근하지 않는 경우 이 응답은 패널 특성을 숨길 수 있습니다.', + responseSetting: '응답 설정', + help200: '도움말 페이지', + error400: '잘못된 요청', + error401: '권한 없음', + error403: '접근 금지', + error404: '페이지를 찾을 수 없음', + error408: '요청 시간 초과', + error416: '범위 불만족', + error444: '연결 닫힘', + error500: '서버 오류', + + https: '패널에 HTTPS를 설정하면 액세스 보안이 향상됩니다', + strictHelper: '비 HTTPS 트래픽은 패널에 연결할 수 없습니다', + muxHelper: '패널은 HTTP 및 HTTPS 포트 모두를 수신하고 HTTP를 HTTPS로 리디렉션하지만 보안이 저하될 수 있습니다', + certType: '인증서 유형', + selfSigned: '자가 서명', + selfSignedHelper: '자가 서명 인증서는 브라우저에서 신뢰하지 않을 수 있으며 보안 경고가 표시될 수 있습니다.', + select: '선택', + domainOrIP: '도메인 또는 IP:', + timeOut: '시간 초과', + rootCrtDownload: '루트 인증서 다운로드', + primaryKey: '개인 키', + certificate: '인증서', + backupJump: '현재 백업 목록에 없는 파일입니다. 파일 디렉토리에서 다운로드하여 백업에 가져오기를 시도하세요.', + + snapshot: '스냅샷 | 스냅샷들', + noAppData: '선택할 수 있는 시스템 앱이 없습니다', + noBackupData: '선택할 수 있는 백업 데이터가 없습니다', + stepBaseData: '기본 데이터', + stepAppData: '시스템 앱', + stepPanelData: '시스템 데이터', + stepBackupData: '백업 데이터', + stepOtherData: '기타 데이터', + operationLog: '작업 로그 유지', + loginLog: '접속 로그 유지', + systemLog: '시스템 로그 유지', + taskLog: '작업 로그 유지', + monitorData: '모니터링 데이터 유지', + dockerConf: 'Docker 설정', + selectAllImage: '모든 앱 이미지를 백업', + logLabel: '로그', + agentLabel: '노드 설정', + appDataLabel: '앱 데이터', + appImage: '앱 이미지', + appBackup: '앱 백업', + backupLabel: '백업 디렉토리', + confLabel: '설정 파일', + dockerLabel: '컨테이너', + taskLabel: '예약 작업', + resourceLabel: '앱 리소스 디렉토리', + runtimeLabel: '실행 환경', + appLabel: '앱', + databaseLabel: '데이터베이스', + snapshotLabel: '스냅샷 파일', + websiteLabel: '웹사이트', + directoryLabel: '디렉토리', + appStoreLabel: '앱 스토어', + shellLabel: '스크립트', + tmpLabel: '임시 디렉토리', + sslLabel: '인증서 디렉토리', + reCreate: '스냅샷 생성 실패', + reRollback: '스냅샷 롤백 실패', + deleteHelper: '타사 백업 계정에 포함된 스냅샷 파일을 포함하여 모든 스냅샷 파일이 삭제됩니다. 계속하시겠습니까?', + status: '스냅샷 상태', + ignoreRule: '무시 규칙', + editIgnoreRule: '@:commons.button.edit @.lower:setting.ignoreRule', + ignoreHelper: + '이 규칙은 스냅샷을 생성할 때 1Panel 데이터 디렉토리를 압축하고 백업하는 데 사용됩니다. 기본적으로 소켓 파일은 무시됩니다.', + ignoreHelper1: '한 줄에 하나씩 입력하세요. 예:\n*.log\n/opt/1panel/cache', + panelInfo: '1Panel 기본 정보 쓰기', + panelBin: '1Panel 시스템 파일 백업', + daemonJson: 'Docker 구성 파일 백업', + appData: '1Panel 에서 설치된 앱 백업', + panelData: '1Panel 데이터 디렉토리 백업', + backupData: '1Panel 의 로컬 백업 디렉토리 백업', + compress: '스냅샷 파일 생성', + upload: '스냅샷 파일 업로드', + recoverDetail: '복구 세부정보', + createSnapshot: '스냅샷 생성', + importSnapshot: '스냅샷 동기화', + importHelper: '스냅샷 디렉토리: ', + lastRecoverAt: '마지막 복구 시간', + lastRollbackAt: '마지막 롤백 시간', + reDownload: '백업 파일 다시 다운로드', + recoverErrArch: '서버 아키텍처가 다른 스냅샷 복구는 지원되지 않습니다!', + recoverErrSize: '디스크 공간이 부족합니다. 확인하거나 정리 후 다시 시도하세요!', + recoverHelper: '스냅샷 {0} 에서 복구를 시작합니다. 계속하기 전에 다음 정보를 확인하세요:', + recoverHelper1: '복구에는 Docker 및 1Panel 서비스를 재시작해야 합니다.', + recoverHelper2: '서버에 충분한 디스크 공간이 있는지 확인하세요. (스냅샷 파일 크기: {0}, 사용 가능한 공간: {1})', + recoverHelper3: + '서버 아키텍처가 스냅샷이 생성된 서버의 아키텍처와 일치하는지 확인하세요. (현재 서버 아키텍처: {0})', + rollback: '롤백', + rollbackHelper: + '이 복구를 롤백하면 해당 복구의 모든 파일이 대체되며 Docker 및 1Panel 서비스를 재시작해야 할 수 있습니다. 계속하시겠습니까?', + + upgradeHelper: '업그레이드에는 1Panel 서비스를 재시작해야 합니다. 계속하시겠습니까?', + rollbackLocalHelper: + '마스터 노드는 직접 롤백을 지원하지 않습니다. 수동으로 [1pctl restore] 명령어를 실행하여 롤백하세요!', + noUpgrade: '현재 최신 버전입니다', + upgradeNotes: '릴리스 노트', + upgradeNow: '지금 업그레이드', + source: '다운로드 소스', + versionNotSame: '노드 버전이 메인 노드와 일치하지 않습니다. 노드 관리에서 업그레이드한 후 다시 시도해 주세요.', + versionCompare: + '노드 {0}이(가) 이미 업그레이드 가능한 최신 버전입니다. 마스터 노드 버전을 확인 후 다시 시도하세요!', + + about: '정보', + versionItem: '현재 버전', + backupCopies: '보관할 백업 복사본 수', + backupCopiesHelper: + '버전 롤백을 위해 보관할 업그레이드 백업 복사본 수를 설정합니다. 0은 모두 보관을 의미합니다.', + backupCopiesRule: '최소 3개의 업그레이드 백업 기록을 보관하세요', + release: '버전 업데이트 로그', + releaseHelper: + '현재 환경의 업데이트 로그를 가져오는 중 오류가 발생했습니다. 공식 문서에서 수동으로 확인하실 수 있습니다.', + project: 'GitHub', + issue: '이슈', + doc: '공식 문서', + star: '별', + description: '리눅스 서버 패널', + forum: '토론', + doc2: '문서', + currentVersion: '버전', + + license: '라이선스', + bindNode: '노드 바인딩', + menuSetting: '메뉴 설정', + menuSettingHelper: '하위 메뉴가 1개만 존재할 경우, 메뉴 바에는 해당 하위 메뉴만 표시됩니다', + showAll: '모두 표시', + hideALL: '모두 숨기기', + ifShow: '표시 여부', + menu: '메뉴', + confirmMessage: '고급 메뉴 목록을 업데이트하려면 페이지가 새로 고쳐집니다. 계속하시겠습니까?', + recoverMessage: '페이지가 새로고침되어 메뉴 목록이 초기 상태로 복원됩니다. 계속하시겠습니까?', + compressPassword: '압축 비밀번호', + backupRecoverMessage: '압축 또는 압축 해제 비밀번호를 입력하세요 (설정하지 않으려면 비워 두세요)', + }, + license: { + offLine: '오프라인 버전', + community: 'OSS', + oss: '오픈 소스 소프트웨어', + pro: 'Pro', + trial: '체험판', + add: '커뮤니티 에디션 추가', + licenseBindHelper: '라이선스가 노드에 바인딩된 경우에만 무료 노드 할당량을 사용할 수 있습니다', + licenseAlert: + '라이선스가 노드에 정상적으로 바인딩된 경우에만 커뮤니티 에디션 노드를 추가할 수 있습니다. 라이선스에 정상적으로 바인딩된 노드만 전환이 지원됩니다.', + licenseUnbindHelper: '이 라이선스에 커뮤니티 에디션 노드가 존재합니다. 바인딩 해제 후 다시 시도하세요!', + subscription: '구독', + perpetual: '영구 라이선스', + versionConstraint: '{0} 버전 일시불 구매', + forceUnbind: '강제 바인딩 해제', + forceUnbindHelper: + '강제 바인딩 해제를 수행하면 해제 과정에서 발생하는 오류를 무시하고 궁극적으로 라이센스 바인딩을 해제합니다.', + updateForce: '강제 업데이트 (바인딩 해제 과정의 모든 오류를 무시하고 최종 작업 성공을 보장합니다)', + trialInfo: '버전', + authorizationId: '구독 인증 ID', + authorizedUser: '인증된 사용자', + lostHelper: + '라이센스가 최대 재시도 횟수를 초과했습니다. 전문가 버전 기능이 제대로 작동하는지 확인하려면 동기화 버튼을 수동으로 클릭하세요.', + disableHelper: + '라이센스 동기화 검증에 실패했습니다. 전문가 버전 기능이 제대로 작동하는지 확인하려면 동기화 버튼을 수동으로 클릭하세요.', + quickUpdate: '빠른 업데이트', + power: '권한 부여', + unbindHelper: '연결 해제 후 모든 Pro 관련 설정이 초기화됩니다. 계속하시겠습니까?', + importLicense: '라이센스', + importHelper: '라이센스 파일을 여기에 클릭하거나 드래그하세요', + technicalAdvice: '기술 상담', + advice: '상담', + levelUpPro: 'Pro 로 업그레이드', + licenseSync: '라이센스 동기화', + knowMorePro: '더 알아보기', + closeAlert: '현재 페이지는 패널 설정에서 닫을 수 있습니다.', + introduce: '기능 소개', + waf: '전문 버전으로 업그레이드하면 차단 맵, 로그, 차단 기록, 지리적 위치 차단, 사용자 정의 규칙, 사용자 정의 차단 페이지 등의 기능을 제공받을 수 있습니다.', + tamper: '전문 버전으로 업그레이드하면 웹사이트를 무단 수정이나 변조로부터 보호할 수 있습니다.', + setting: '전문 버전으로 업그레이드하면 패널 로고, 환영 메시지 등 정보를 사용자 정의할 수 있습니다.', + monitor: + '전문 버전으로 업그레이드하면 웹사이트의 실시간 상태, 방문자 트렌드, 방문자 출처, 요청 로그 등 정보를 확인할 수 있습니다.', + alert: '전문 버전으로 업그레이드하면 SMS 를 통해 알림 정보를 받고 알림 로그를 볼 수 있으며, 다양한 주요 이벤트를 완벽하게 제어하여 시스템 운영을 걱정 없이 유지할 수 있습니다.', + node: '프로페셔널 에디션으로 업그레이드하면 1Panel로 여러 Linux 서버를 관리할 수 있습니다.', + nodeApp: + '프로페셔널 에디션으로 업그레이드하면 노드를 수동으로 전환하지 않고도 다중 노드 애플리케이션 버전을 통합 업그레이드할 수 있습니다.', + nodeDashboard: + '프로페셔널 에디션으로 업그레이드하면 다중 노드 애플리케이션, 웹사이트, 데이터베이스 및 예약 작업을 중앙 집중식으로 관리할 수 있습니다.', + fileExchange: '프로페셔널 에디션으로 업그레이드하여 여러 서버 간에 파일을 빠르게 전송할 수 있습니다.', + app: '프로페셔널 버전으로 업그레이드하면 모바일 APP을 통해 서비스 정보, 이상 모니터링 등을 확인할 수 있습니다.', + cluster: + '프로페셔널 에디션으로 업그레이드하면 MySQL/Postgres/Redis 마스터-슬레이브 클러스터를 관리할 수 있습니다.', + }, + clean: { + scan: '스캔 시작', + scanHelper: '1Panel 실행 중에 생성된 불필요한 파일을 쉽게 정리합니다.', + clean: '지금 정리', + reScan: '다시 스캔', + cleanHelper: '선택된 파일 및 디렉터리는 정리 후 롤백할 수 없습니다. 계속하시겠습니까?', + statusSuggest: '(권장 정리)', + statusClean: '(매우 깨끗함)', + statusEmpty: '매우 깨끗합니다. 정리가 필요하지 않습니다!', + statusWarning: '(주의하여 진행)', + lastCleanTime: '마지막 정리 시간: {0}', + lastCleanHelper: '정리된 파일 및 디렉터리: {0}, 총 정리된 크기: {1}', + cleanSuccessful: '정리 성공', + currentCleanHelper: '이번 세션에서 정리된 파일 및 디렉터리: {0}, 총 정리된 크기: {1}', + suggest: '(권장)', + totalScan: '정리해야 할 총 불필요 파일: ', + selectScan: '선택된 불필요 파일 총합: ', + + system: '시스템 불필요 파일', + systemHelper: '스냅샷, 업그레이드 및 기타 프로세스 중에 생성된 임시 파일', + panelOriginal: '시스템 스냅샷 복원 전 백업 파일', + upgrade: '시스템 업그레이드 백업 파일', + agentPackages: '이전 버전 자식 노드 업그레이드/설치 패키지', + upgradeHelper: '(시스템 롤백을 위해 최신 업그레이드 백업을 유지하는 것이 좋습니다)', + rollback: '복구 전 백업 파일', + + backup: '시스템 백업', + backupHelper: '로컬 백업 계정에 연결되지 않은 백업 파일', + unknownBackup: '시스템 백업', + tmpBackup: '임시 백업', + unknownApp: '연결되지 않은 앱 백업', + unknownDatabase: '연결되지 않은 데이터베이스 백업', + unknownWebsite: '연결되지 않은 웹사이트 백업', + unknownSnapshot: '연결되지 않은 스냅샷 백업', + + upload: '임시 업로드 파일', + uploadHelper: '시스템 백업 리스트에서 업로드된 임시 파일', + download: '임시 다운로드 파일', + downloadHelper: '시스템에서 제3자 백업 계정으로 다운로드된 임시 파일', + directory: '디렉터리', + + systemLog: '로그 파일', + systemLogHelper: '시스템 로그, 작업 로그, 웹사이트 로그 파일', + dockerLog: '컨테이너 작업 로그 파일', + taskLog: '예약 작업 실행 로그 파일', + shell: '셸 스크립트 예약 작업', + containerShell: '컨테이너 내에서 실행하는 셸 스크립트 예약 작업', + curl: 'CURL 예약 작업', + + docker: '컨테이너 쓰레기', + dockerHelper: '컨테이너, 이미지, 볼륨, 빌드 캐시 등의 파일', + volumes: '볼륨', + buildCache: '컨테이너 빌드 캐시', + + appTmpDownload: '앱 임시 다운로드 파일', + unknownWebsiteLog: '연결되지 않은 웹사이트 로그 백업 파일', + }, + app: { + app: '애플리케이션 | 애플리케이션들', + installName: '이름', + installed: '설치됨', + all: '모두', + version: '버전', + detail: '세부사항', + params: '편집', + author: '저자', + source: '출처', + appName: '애플리케이션 이름', + deleteWarn: + '삭제 작업은 모든 데이터와 백업을 함께 삭제합니다. 이 작업은 되돌릴 수 없습니다. 계속 하시겠습니까?', + syncSuccess: '동기화 성공', + canUpgrade: '업데이트', + backupName: '파일 이름', + backupPath: '파일 경로', + backupdate: '백업 시간', + versionSelect: '버전을 선택하세요', + operatorHelper: '선택한 애플리케이션에 대해 {0} 작업이 수행됩니다. 계속 하시겠습니까?', + startOperatorHelper: '애플리케이션이 시작됩니다. 계속 하시겠습니까?', + stopOperatorHelper: '애플리케이션이 중지됩니다. 계속 하시겠습니까?', + restartOperatorHelper: '애플리케이션이 재시작됩니다. 계속 하시겠습니까?', + reloadOperatorHelper: '애플리케이션이 다시 로드됩니다. 계속 하시겠습니까?', + checkInstalledWarn: '"{0}"이(가) 감지되지 않았습니다. "앱 스토어"로 가서 설치하세요.', + limitHelper: '애플리케이션은 이미 설치되었습니다.', + deleteHelper: '"{0}"은(는) 다음 리소스와 연결되어 있습니다. 확인 후 다시 시도하세요!', + checkTitle: '힌트', + defaultConfig: '기본 설정', + defaultConfigHelper: '기본 설정으로 복원되었습니다. 저장 후 적용됩니다.', + forceDelete: '강제 삭제', + forceDeleteHelper: '강제 삭제는 삭제 과정 중 오류를 무시하고 메타데이터를 삭제합니다.', + deleteBackup: '백업 삭제', + deleteBackupHelper: '애플리케이션 백업도 삭제됩니다.', + deleteDB: '데이터베이스 삭제', + deleteDBHelper: '데이터베이스도 삭제됩니다.', + noService: '{0} 없음', + toInstall: '설치하러 가기', + param: '파라미터', + alreadyRun: '나이', + syncAppList: '동기화', + less1Minute: '1분 미만', + appOfficeWebsite: '오피스 웹사이트', + github: 'GitHub', + document: '문서', + updatePrompt: '업데이트가 없습니다.', + installPrompt: '아직 설치된 앱이 없습니다.', + updateHelper: '파라미터를 수정하면 애플리케이션이 시작되지 않을 수 있습니다. 주의하세요.', + updateWarn: '업데이트 파라미터는 애플리케이션을 재빌드해야 합니다. 계속 하시겠습니까?', + busPort: '포트', + syncStart: '동기화 시작! 나중에 앱 스토어를 새로고침 해주세요.', + advanced: '고급 설정', + cpuCore: '코어', + containerName: '컨테이너 이름', + containerNameHelper: '설정되지 않으면 컨테이너 이름이 자동으로 생성됩니다.', + allowPort: '외부 접근 허용', + allowPortHelper: '외부 포트 접근을 허용하면 방화벽 포트가 열립니다.', + appInstallWarn: + '애플리케이션은 기본적으로 외부 접근 포트를 노출하지 않습니다. "고급 설정"을 클릭하여 노출할 수 있습니다.', + upgradeStart: '업그레이드 시작! 나중에 페이지를 새로고침 해주세요.', + toFolder: '설치 디렉터리 열기', + editCompose: 'Compose 파일 편집', + editComposeHelper: 'Compose 파일을 편집하면 소프트웨어 설치가 실패할 수 있습니다.', + composeNullErr: 'Compose는 비어 있을 수 없습니다.', + takeDown: '내리기', + allReadyInstalled: '설치됨', + installHelper: '이미지 풀 문제 시 이미지 가속을 구성하세요.', + installWarn: + '외부 접근이 활성화되지 않아 애플리케이션이 외부 네트워크에서 접근할 수 없습니다. 계속 하시겠습니까?', + showIgnore: '무시된 애플리케이션 보기', + cancelIgnore: '무시 취소', + ignoreList: '무시된 애플리케이션', + appHelper: '특수 애플리케이션 설치 지침을 보려면 애플리케이션 상세 페이지로 이동하세요.', + backupApp: '업그레이드 전 애플리케이션 백업', + backupAppHelper: + '업그레이드 실패 시 백업이 자동으로 롤백됩니다. 로그 감사 시스템 로그에서 실패 원인을 확인하세요. 백업은 기본적으로 최신 3개 복사본을 유지합니다.', + openrestyDeleteHelper: 'OpenResty 강제 삭제는 모든 웹사이트를 삭제합니다. 계속 하시겠습니까?', + downloadLogHelper1: '{0} 애플리케이션의 모든 로그가 다운로드됩니다. 계속 하시겠습니까?', + downloadLogHelper2: '{0} 애플리케이션의 최신 {1} 로그가 다운로드됩니다. 계속 하시겠습니까?', + syncAllAppHelper: '모든 애플리케이션이 동기화됩니다. 계속 하시겠습니까?', + hostModeHelper: + '현재 애플리케이션 네트워크 모드는 호스트 모드입니다. 포트를 열어야 할 경우 방화벽 페이지에서 수동으로 열어주세요.', + showLocal: '로컬 애플리케이션 보기', + reload: '새로고침', + upgradeWarn: + '애플리케이션 업그레이드는 docker-compose.yml 파일을 교체합니다. 변경 사항이 있으면 파일 비교를 클릭하여 확인할 수 있습니다.', + newVersion: '새 버전', + oldVersion: '현재 버전', + composeDiff: '파일 비교', + showDiff: '비교 보기', + useNew: '사용자 정의 버전 사용', + useDefault: '기본 버전 사용', + useCustom: '사용자 정의 docker-compose.yml 사용', + useCustomHelper: + '사용자 정의 docker-compose.yml 파일을 사용하면 애플리케이션 업그레이드가 실패할 수 있습니다. 필요하지 않으면 체크하지 마세요.', + diffHelper: '왼쪽은 이전 버전, 오른쪽은 새 버전입니다. 편집 후 사용자 정의 버전을 저장하려면 클릭하세요.', + pullImage: '이미지 풀', + pullImageHelper: '애플리케이션 시작 전에 docker pull 을 실행하여 이미지를 다운로드하세요.', + deleteImage: '이미지 삭제', + deleteImageHelper: '애플리케이션 관련 이미지를 삭제합니다. 삭제에 실패하더라도 작업은 종료되지 않습니다.', + requireMemory: '메모리 요구사항', + supportedArchitectures: '지원 아키텍처', + link: '링크', + showCurrentArch: '현재 서버 아키텍처 애플리케이션', + syncLocalApp: '로컬 애플리케이션 동기화', + memoryRequiredHelper: '현재 애플리케이션은 {0} 메모리가 필요합니다', + gpuConfig: 'GPU 지원 활성화', + gpuConfigHelper: + 'NVIDIA GPU가 장착되어 있으며 NVIDIA 드라이버와 NVIDIA Docker Container Toolkit이 설치되어 있는지 확인하세요', + webUI: '웹 접속 주소', + webUIPlaceholder: '예: example.com:8080/login', + defaultWebDomain: '기본 접속 주소', + defaultWebDomainHepler: '애플리케이션 포트가 8080인 경우 접속 주소는 http(s)://기본 접속 주소:8080입니다', + webUIConfig: + '현재 노드에 기본 접근 주소가 설정되지 않았습니다. 애플리케이션 매개변수에서 설정하거나 패널 설정으로 이동하여 설정하세요!', + toLink: '이동', + customAppHelper: '사용자 정의 앱 스토어 패키지를 설치하기 전에 설치된 앱이 없는지 확인하십시오.', + forceUninstall: '강제 제거', + syncCustomApp: 'カスタムアプリを同期', + ignoreAll: '후속 모든 버전 무시', + ignoreVersion: '지정된 버전 무시', + specifyIP: '호스트 IP 바인딩', + specifyIPHelper: + '포트 바인딩을 위한 호스트 주소/네트워크 인터페이스를 설정합니다 (이 기능을 잘 모를 경우, 입력하지 마십시오)', + uninstallDeleteBackup: '앱 제거 - 백업 삭제', + uninstallDeleteImage: '앱 제거 - 이미지 삭제', + upgradeBackup: '앱 업그레이드 전 앱 백업', + noAppHelper: '애플리케이션이 감지되지 않았습니다. 작업 센터에서 앱 스토어 동기화 로그를 확인해 주세요', + isEdirWarn: 'docker-compose.yml 파일이 수정된 것을 감지했습니다. 비교를 확인해 주세요', + }, + website: { + primaryDomain: '기본 도메인', + otherDomains: '기타 도메인', + static: '정적', + deployment: '배포', + supportUpType: '.tar.gz 파일 형식만 지원되며, 압축 패키지에는 {0}.json 파일이 포함되어야 합니다', + proxy: '리버스 프록시', + alias: '별칭', + ftpUser: 'FTP 계정', + ftpPassword: 'FTP 비밀번호', + ftpHelper: '웹사이트를 생성하면 해당 FTP 계정이 생성되고 FTP 디렉터리는 웹사이트 디렉터리와 연결됩니다.', + remark: '비고', + manageGroup: '그룹 관리', + groupSetting: '그룹 설정', + createGroup: '그룹 생성', + appNew: '새로운 애플리케이션', + appInstalled: '설치된 애플리케이션', + create: '생성', + delete: '웹사이트 삭제', + deleteApp: '애플리케이션 삭제', + deleteBackup: '백업 삭제', + domain: '도메인', + domainHelper: "한 줄에 하나의 도메인.\n와일드카드 '*'와 IP 주소를 지원합니다.\n포트 추가를 지원합니다.", + addDomain: '추가', + domainConfig: '도메인 설정', + defaultDoc: '기본 문서', + perserver: '동시 연결', + perserverHelper: '현재 사이트의 최대 동시 연결 수를 제한합니다.', + perip: '단일 IP', + peripHelper: '단일 IP의 최대 동시 접속 수를 제한합니다.', + rate: '트래픽 제한', + rateHelper: '요청당 트래픽을 제한합니다 (단위: KB)', + limitHelper: '트래픽 제어 활성화', + other: '기타', + currentSSL: '현재 인증서', + dnsAccount: 'DNS 계정', + applySSL: '인증서 신청', + SSLList: '인증서 목록', + createDnsAccount: 'DNS 계정 생성', + aliyun: '알리윤', + aliEsa: '알리윤 ESA', + awsRoute53: 'Amazon Route 53', + manual: '수동 설정', + key: '키', + check: '보기', + acmeAccountManage: 'ACME 계정 관리', + email: '이메일', + acmeAccount: 'ACME 계정', + provider: '검증 방법', + dnsManual: '수동 설정', + expireDate: '만료일', + brand: '기관', + deploySSL: '배포', + deploySSLHelper: '인증서를 배포하시겠습니까?', + ssl: '인증서 | 인증서들', + dnsAccountManage: 'DNS 제공자 관리', + renewSSL: '갱신', + renewHelper: '인증서를 갱신하시겠습니까?', + renewSuccess: '인증서 갱신 성공', + enableHTTPS: 'HTTPS 활성화', + aliasHelper: '별칭은 웹사이트의 디렉터리 이름입니다.', + lastBackupAt: '마지막 백업 시간', + null: '없음', + nginxConfig: 'Nginx 설정', + websiteConfig: '웹사이트 설정', + proxySettings: '프록시 설정', + advancedSettings: '고급 설정', + cacheSettings: '캐시 설정', + sniSettings: 'SNI 설정', + basic: '기본', + source: '구성', + security: '보안', + nginxPer: '성능 튜닝', + neverExpire: '만료 없음', + setDefault: '기본값으로 설정', + default: '기본값', + deleteHelper: '관련 애플리케이션 상태가 비정상입니다. 확인해 주세요.', + toApp: '설치된 목록으로 이동', + cycle: '주기', + frequency: '빈도', + ccHelper: '{0}초 내에 동일한 URL을 {1}회 이상 누적 요청하면 CC 방어가 발동되며, 해당 IP가 차단됩니다.', + mustSave: '변경 사항은 저장해야 적용됩니다.', + fileExt: '파일 확장자', + fileExtBlock: '파일 확장자 차단 목록', + value: '값', + enable: '활성화', + proxyAddress: '프록시 주소', + proxyHelper: '예: 127.0.0.1:8080', + forceDelete: '강제 삭제', + forceDeleteHelper: '강제 삭제는 삭제 과정에서 발생하는 오류를 무시하고 최종적으로 메타데이터를 삭제합니다.', + deleteAppHelper: '관련 애플리케이션 및 애플리케이션 백업을 동시에 삭제합니다.', + deleteBackupHelper: '웹사이트 백업도 삭제합니다.', + deleteDatabaseHelper: '웹사이트와 연결된 데이터베이스도 삭제합니다', + deleteConfirmHelper: + '삭제 작업은 되돌릴 수 없습니다. 확인하려면 "{0}"을(를) 입력하세요.', + staticPath: '해당 주요 디렉터리는', + limit: '제한', + blog: '포럼/블로그', + imageSite: '이미지 사이트', + downloadSite: '다운로드 사이트', + shopSite: '쇼핑몰', + doorSite: '포털', + qiteSite: '기업', + videoSite: '비디오', + errLog: '오류 로그', + stopHelper: + '사이트를 중지하면 정상적으로 액세스할 수 없으며, 사용자가 해당 사이트의 중지 페이지를 보게 됩니다. 계속하시겠습니까?', + startHelper: '사이트를 활성화하면 사용자가 정상적으로 사이트 내용을 액세스할 수 있습니다. 계속하시겠습니까?', + sitePath: '디렉터리', + siteAlias: '사이트 별칭', + primaryPath: '루트 디렉터리', + folderTitle: '웹사이트는 다음과 같은 폴더를 포함합니다.', + wafFolder: '방화벽 규칙', + indexFolder: '웹사이트 루트 디렉터리', + sslFolder: '웹사이트 인증서', + enableOrNot: '활성화 여부', + oldSSL: '기존 인증서', + manualSSL: '인증서 가져오기', + select: '선택', + selectSSL: '인증서 선택', + privateKey: '키(KEY)', + certificate: '인증서(PEM 형식)', + HTTPConfig: 'HTTP 옵션', + HTTPSOnly: 'HTTP 요청 차단', + HTTPToHTTPS: 'HTTPS로 리디렉션', + HTTPAlso: 'HTTP 요청 허용', + sslConfig: 'SSL 옵션', + disableHTTPS: 'HTTPS 비활성화', + disableHTTPSHelper: 'HTTPS 비활성화는 인증서 관련 구성을 삭제합니다. 계속하시겠습니까?', + SSLHelper: + '주의: 불법 웹사이트에는 SSL 인증서를 사용하지 마세요.\nHTTPS 액세스가 안 되는 경우 보안 그룹에서 443 포트를 올바르게 해제했는지 확인하세요.', + SSLConfig: '인증서 설정', + SSLProConfig: '프로토콜 설정', + supportProtocol: '프로토콜 버전', + encryptionAlgorithm: '암호화 알고리즘', + notSecurity: '(안전하지 않음)', + encryptHelper: + "Let's Encrypt 는 인증서 발급에 빈도 제한이 있지만 일반적인 요구 사항을 충족하기에 충분합니다. 너무 자주 작업하면 발급 실패가 발생할 수 있습니다. 자세한 제한 사항은 공식 문서를 참조하세요.", + ipValue: '값', + ext: '파일 확장자', + wafInputHelper: '줄 단위로 데이터 입력, 한 줄에 하나씩', + data: '데이터', + ever: '영구', + nextYear: '1년 후', + noLog: '로그를 찾을 수 없습니다.', + defaultServer: '기본 사이트', + noDefaultServer: '설정되지 않음', + defaultServerHelper: + '기본 사이트를 설정한 후, 바인딩되지 않은 모든 도메인 이름과 IP는 기본 사이트로 리디렉션됩니다\n이는 악의적인 해석을 효과적으로 방지할 수 있습니다\n하지만 WAF의 무단 도메인 차단이 실패할 수도 있습니다', + restoreHelper: '이 백업을 사용하여 복원하시겠습니까?', + websiteDeploymentHelper: '설치된 애플리케이션을 사용하거나 새 애플리케이션을 생성하여 웹사이트를 만드세요.', + websiteStatictHelper: '호스트에 웹사이트 디렉터리를 생성합니다.', + websiteProxyHelper: + '리버스 프록시를 사용하여 기존 서비스를 프록시합니다. 예를 들어, 포트 8080 에서 실행 중인 서비스를 프록시하려면 프록시 주소는 "http://127.0.0.1:8080"이 됩니다.', + runtimeProxyHelper: '웹사이트 런타임을 사용하여 웹사이트를 만드세요.', + runtime: '런타임', + deleteRuntimeHelper: '런타임 애플리케이션은 웹사이트와 함께 삭제해야 하므로 신중하게 처리하세요.', + proxyType: '네트워크 유형', + unix: '유닉스 네트워크', + tcp: 'TCP/IP 네트워크', + phpFPM: 'FPM 구성', + phpConfig: 'PHP 구성', + updateConfig: '구성 업데이트', + isOn: '켜짐', + isOff: '꺼짐', + rewrite: '의사 정적', + rewriteMode: '방식', + current: '현재', + rewriteHelper: '의사 정적 설정으로 인해 웹사이트에 접근할 수 없게 되면 기본 설정으로 되돌려보세요.', + runDir: '실행 디렉터리', + runUserHelper: + 'PHP 컨테이너 런타임 환경에서 배포된 웹사이트의 경우, 인덱스 및 하위 디렉터리 아래의 모든 파일 및 폴더의 소유자와 사용자 그룹을 1000으로 설정해야 합니다. 로컬 PHP 환경의 경우, 로컬 PHP-FPM 사용자 및 사용자 그룹 설정을 참조하세요.', + userGroup: '사용자/그룹', + uGroup: '그룹', + proxyPath: '프록시 경로', + proxyPass: '대상 URL', + cache: '캐시', + cacheTime: '캐시 지속 시간', + enableCache: '캐시 활성화', + proxyHost: '프록시 호스트', + disabled: '중지됨', + startProxy: '리버스 프록시를 시작합니다. 계속하시겠습니까?', + stopProxy: '리버스 프록시를 중지합니다. 계속하시겠습니까?', + sourceFile: '소스', + proxyHelper1: '이 디렉터리에 접근할 때 대상 URL 의 내용이 반환되고 표시됩니다.', + proxyPassHelper: '대상 URL 은 유효하고 접근 가능해야 합니다.', + proxyHostHelper: '요청 헤더에 있는 도메인 이름을 프록시 서버로 전달합니다.', + replacementHelper: '최대 5개의 교체를 추가할 수 있으며, 교체가 필요하지 않은 경우 비워두세요.', + modifier: '매칭 규칙', + modifierHelper: '예: = 는 정확히 일치, ~ 는 정규식 일치, ^~ 는 경로 시작 부분 일치 등을 나타냅니다.', + replace: '텍스트 교체', + replaceHelper: + 'nginx 텍스트 교체 기능은 리버스 프록시 중 응답 내용의 문자열을 대체할 수 있습니다. 백엔드에서 반환된 HTML, CSS, JavaScript 및 기타 파일의 링크, API 주소 등을 수정하는 데 일반적으로 사용됩니다. 복잡한 콘텐츠 교체 요구 사항에 대해 정규식 일치를 지원합니다.', + addReplace: '추가', + replaced: '검색 문자열 (비울 수 없음)', + replaceText: '교체할 문자열', + replacedErr: '검색 문자열은 비워둘 수 없습니다', + replacedErr2: '검색 문자열은 중복될 수 없습니다', + replacedListEmpty: '텍스트 교체 규칙 없음', + proxySslName: '프록시 SNI 이름', + basicAuth: '기본 인증', + editBasicAuthHelper: + '비밀번호는 비대칭으로 암호화되어 표시할 수 없습니다. 수정하려면 비밀번호를 재설정해야 합니다.', + antiLeech: '링크 차단', + extends: '확장', + browserCache: '캐시', + noModify: '수정 안 함', + serverCache: '서버 캐시', + leechLog: '링크 차단 로그 기록', + accessDomain: '허용된 도메인', + leechReturn: '응답 리소스', + noneRef: '빈 참조 허용', + disable: '비활성화', + disableLeechHelper: '링크 차단을 비활성화할지 여부', + disableLeech: '링크 차단 비활성화', + ipv6: 'IPv6 수신 대기', + leechReturnError: 'HTTP 상태 코드를 입력하세요', + blockedRef: '비표준 참조 허용', + accessControl: '링크 차단 제어', + leechcacheControl: '캐시 제어', + logEnableControl: '정적 리소스 요청 로그 기록', + leechSpecialValidHelper: + "'빈 참조 허용'을 활성화하면 리퍼러가 없는 요청(직접 접근 등)은 차단되지 않습니다. '비표준 참조 허용'을 활성화하면 http/https로 시작하지 않는 모든 리퍼러 요청(클라이언트 요청 등)을 허용합니다.", + leechInvalidReturnHelper: '차단된 요청에 대해 반환할 HTTP 상태 코드', + leechlogControlHelper: + '정적 리소스 요청을 기록합니다. 운영 환경에서는 과도하고 불필요한 로그를 피하기 위해 보통 비활성화합니다', + selectAcme: 'Acme 계정 선택', + imported: '수동으로 생성됨', + importType: '가져오기 유형', + pasteSSL: '코드 붙여넣기', + localSSL: '서버 파일 선택', + privateKeyPath: '개인 키 파일', + certificatePath: '인증서 파일', + ipWhiteListHelper: 'IP 허용 목록의 역할: 모든 규칙이 IP 허용 목록에 대해 무효화됩니다.', + redirect: '리디렉션', + sourceDomain: '소스 도메인', + targetURL: '대상 URL 주소', + keepPath: 'URI 매개변수', + path: '경로', + redirectType: '리디렉션 유형', + redirectWay: '방식', + keep: '유지', + notKeep: '유지하지 않음', + redirectRoot: '홈페이지로 리디렉션', + redirectHelper: '301 영구 리디렉션, 302 임시 리디렉션', + changePHPVersionWarn: + 'PHP 버전을 변경하면 기존 PHP 컨테이너가 삭제됩니다 (마운트된 웹사이트 코드는 손실되지 않습니다). 계속하시겠습니까?', + changeVersion: '버전 전환', + retainConfig: 'php-fpm.conf 및 php.ini 파일을 유지할지 여부', + runDirHelper2: '보조 실행 디렉터리가 인덱스 디렉터리 아래에 있는지 확인하세요.', + openrestyHelper: + 'OpenResty 기본 HTTP 포트: {0}, HTTPS 포트: {1}, 이는 웹사이트 도메인 접속 및 HTTPS 강제 리디렉션에 영향을 미칠 수 있습니다.', + primaryDomainHelper: '예: example.com 또는 example.com:8080', + acmeAccountType: '계정 유형', + keyType: '키 알고리즘', + tencentCloud: '텐센트 클라우드', + containWarn: '도메인 이름에 메인 도메인이 포함되어 있습니다. 다시 입력하세요.', + rewriteHelper2: + '앱 스토어에서 설치된 WordPress 와 같은 응용 프로그램은 일반적으로 사전 설정된 가상 정적 구성이 포함됩니다. 이를 재구성하면 오류가 발생할 수 있습니다.', + websiteBackupWarn: + '로컬 백업 가져오기만 지원합니다. 다른 기기에서 가져온 백업은 복구 실패를 초래할 수 있습니다.', + ipWebsiteWarn: + 'IP를 도메인 이름으로 사용하는 웹사이트는 정상적으로 접속되기 위해 기본 사이트로 설정해야 합니다.', + hstsHelper: 'HSTS 를 활성화하면 웹사이트 보안을 강화할 수 있습니다.', + includeSubDomains: '서브도메인', + hstsIncludeSubDomainsHelper: '활성화하면 HSTS 정책이 현재 도메인의 모든 서브도메인에 적용됩니다.', + defaultHtml: '기본 페이지', + website404: '웹사이트 404 오류 페이지', + domain404: '웹사이트 도메인이 존재하지 않습니다.', + indexHtml: '정적 웹사이트 인덱스', + stopHtml: '중지된 웹사이트', + indexPHP: 'PHP 웹사이트 인덱스', + sslExpireDate: '인증서 만료 날짜', + website404Helper: '웹사이트 404 오류 페이지는 PHP 실행 환경 웹사이트 및 정적 웹사이트만 지원합니다.', + sni: '원본 SNI', + sniHelper: + '역방향 프록시 백엔드가 HTTPS 인 경우 원본 SNI 를 설정해야 할 수 있습니다. 자세한 내용은 CDN 서비스 제공자의 문서를 참조하세요.', + huaweicloud: '화웨이 클라우드', + createDb: '데이터베이스 생성', + enableSSLHelper: 'SSL 활성화 실패는 웹사이트 생성에 영향을 미치지 않습니다.', + batchAdd: '도메인 일괄 추가', + batchInput: '일괄 입력', + domainNotFQDN: '이 도메인은 공용 네트워크에서 액세스할 수 없을 수 있습니다', + domainInvalid: '도메인 형식이 올바르지 않습니다', + domainBatchHelper: '한 줄에 하나의 도메인, 형식: domain:port@ssl\n예: example.com:443@ssl 또는 example.com', + generateDomain: '생성', + global: '글로벌', + subsite: '하위 사이트', + subsiteHelper: '하위 사이트는 기존 PHP 또는 정적 웹사이트의 디렉토리를 루트 디렉토리로 선택할 수 있습니다.', + parentWebsite: '상위 웹사이트', + deleteSubsite: '현재 웹사이트를 삭제하려면 먼저 하위 사이트 {0}를 삭제해야 합니다.', + loadBalance: '로드 밸런싱', + server: '노드', + algorithm: '알고리즘', + ipHash: 'IP 해시', + ipHashHelper: + '클라이언트 IP 주소를 기반으로 요청을 특정 서버에 분배하여 특정 클라이언트가 항상 동일한 서버로 라우팅되도록 합니다.', + leastConn: '최소 연결', + leastConnHelper: '활성 연결 수가 가장 적은 서버로 요청을 보냅니다.', + leastTime: '최소 시간', + leastTimeHelper: '활성 연결 시간이 가장 짧은 서버로 요청을 보냅니다.', + defaultHelper: + '기본 방법으로, 요청은 각 서버에 균등하게 분배됩니다. 서버에 가중치 설정이 있는 경우 지정된 가중치에 따라 요청이 분배됩니다. 가중치가 높은 서버는 더 많은 요청을 받습니다.', + weight: '가중치', + maxFails: '최대 실패 횟수', + maxConns: '최대 연결 수', + strategy: '전략', + strategyDown: '비활성화', + strategyBackup: '백업', + ipHashBackupErr: 'IP 해시는 백업 노드를 지원하지 않습니다', + failTimeout: '장애 시간 초과', + failTimeoutHelper: + '서버 상태 점검 시간 창 길이. 이 기간 동안 누적 실패 횟수가 임계값에 도달하면 서버가 일시적으로 제거되고 동일한 시간 후에 재시도됩니다. 기본값 10초', + + staticChangePHPHelper: '현재 정적 웹사이트이며 PHP 웹사이트로 전환할 수 있습니다.', + proxyCache: '리버스 프록시 캐시', + cacheLimit: '캐시 공간 제한', + shareCache: '캐시 카운트 메모리 크기', + cacheExpire: '캐시 만료 시간', + shareCacheHelper: '1M 메모리로 약 8000개의 캐시 객체를 저장할 수 있습니다.', + cacheLimitHelper: '제한을 초과하면 이전 캐시가 자동으로 삭제됩니다.', + cacheExpireHelper: '만료 시간 내에 히트되지 않은 캐시는 삭제됩니다.', + realIP: '실제 IP', + ipFrom: 'IP 소스', + ipFromHelper: + '신뢰할 수 있는 IP 소스를 구성함으로써 OpenResty는 HTTP 헤더의 IP 정보를 분석하여 방문자의 실제 IP 주소를 정확하게 식별하고 기록합니다(액세스 로그 포함).', + ipFromExample1: '프론트엔드가 Frp와 같은 도구인 경우 Frp의 IP 주소(예: 127.0.0.1)를 입력할 수 있습니다.', + ipFromExample2: '프론트엔드가 CDN인 경우 CDN의 IP 범위를 입력할 수 있습니다.', + ipFromExample3: + '확실하지 않은 경우 0.0.0.0/0 (IPv4) 또는 ::/0 (IPv6)를 입력할 수 있습니다. [주의: 모든 소스 IP를 허용하는 것은 안전하지 않습니다.]', + http3Helper: + 'HTTP/3는 HTTP/2의 업그레이드 버전으로, 더 빠른 연결 속도와 더 나은 성능을 제공합니다. 그러나 모든 브라우저가 HTTP/3를 지원하는 것은 아니며, 활성화하면 일부 브라우저가 사이트에 접근하지 못할 수 있습니다.', + cors: '교차 출처 리소스 공유(CORS)', + enableCors: 'CORS 활성화', + allowOrigins: '허용된 도메인', + allowMethods: '허용된 요청 메서드', + allowHeaders: '허용된 요청 헤더', + allowCredentials: '쿠키 전송 허용', + preflight: '프리플라이트 요청 빠른 응답', + preflightHleper: + '활성화하면 브라우저가 교차 출처 프리플라이트 요청(OPTIONS 요청)을 보낼 때 시스템이 자동으로 204 상태 코드를 반환하고 필요한 교차 출처 응답 헤더를 설정합니다', + + changeDatabase: '데이터베이스 전환', + changeDatabaseHelper1: '데이터베이스 연관은 웹사이트 백업 및 복원에 사용됩니다.', + changeDatabaseHelper2: '다른 데이터베이스로 전환하면 이전 백업을 복원할 수 없게 될 수 있습니다.', + saveCustom: '템플릿으로 저장', + rainyun: 'Rainyun', + volcengine: 'volcengine', + runtimePortHelper: '현재 실행 환경에 여러 포트가 있습니다. 프록시 포트를 선택하세요.', + runtimePortWarn: '현재 실행 환경에 포트가 없습니다. 프록시할 수 없습니다', + cacheWarn: '먼저 리버스 프록시의 캐시 스위치를 끄십시오', + loadBalanceHelper: + '로드 밸런싱을 생성한 후, "리버스 프록시"로 이동하여 프록시를 추가하고 백엔드 주소를 다음으로 설정하세요: http://<로드 밸런싱 이름>.', + favorite: '즐겨찾기', + cancelFavorite: '즐겨찾기 취소', + useProxy: '프록시 사용', + useProxyHelper: '패널 설정의 프록시 서버 주소 사용', + westCN: '서부 디지털', + openBaseDir: '사이트 간 공격 방지', + openBaseDirHelper: + 'open_basedir는 PHP 파일 액세스 경로를 제한하여 사이트 간 액세스를 방지하고 보안을 향상시키는 데 사용됩니다', + serverCacheTime: '서버 캐시 시간', + serverCacheTimeHelper: + '요청이 서버에서 캐시되는 시간. 이 기간 동안 동일한 요청은 원본 서버에 요청하지 않고 캐시된 결과를 직접 반환합니다.', + browserCacheTime: '브라우저 캐시 시간', + browserCacheTimeHelper: + '정적 리소스가 브라우저 로컬에 캐시되는 시간, 중복 요청을 줄입니다. 유효기간 전에 사용자가 페이지를 새로 고치면 로컬 캐시가 직접 사용됩니다.', + donotLinkeDB: '데이터베이스 연결하지 않기', + toWebsiteDir: '웹사이트 디렉토리로 이동', + execParameters: '실행 매개변수', + extCommand: '추가 명령', + mirror: '미러 소스', + execUser: '실행 사용자', + execDir: '실행 디렉토리', + packagist: '중국 전체 미러', + + batchOpreate: '일괄 작업', + batchOpreateHelper: '웹사이트를 일괄 {0}, 계속 작업하시겠습니까?', + stream: 'TCP/UDP 프록시', + streamPorts: '수신 포트', + streamPortsHelper: + '외부 수신 포트 번호를 설정합니다. 클라이언트는 이 포트를 통해 서비스에 액세스합니다. 쉼표로 구분하세요. 예: 5222,5223', + streamHelper: 'TCP/UDP 포트 포워딩 및 로드 밸런싱', + udp: 'UDP 활성화', + + syncHtmlHelper: 'PHP 및 정적 웹사이트에 동기화', + }, + php: { + short_open_tag: '짧은 태그 지원', + max_execution_time: '최대 스크립트 실행 시간', + max_input_time: '최대 입력 시간', + memory_limit: '스크립트 메모리 제한', + post_max_size: 'POST 데이터 최대 크기', + file_uploads: '파일 업로드 허용 여부', + upload_max_filesize: '업로드 가능한 파일의 최대 크기', + max_file_uploads: '한 번에 업로드 가능한 파일의 최대 개수', + default_socket_timeout: '소켓 타임아웃', + error_reporting: '에러 수준', + display_errors: '상세한 에러 정보 출력 여부', + cgi_fix_pathinfo: 'pathinfo 활성화 여부', + date_timezone: '시간대', + disableFunction: '비활성화 함수', + disableFunctionHelper: '비활성화할 함수를 입력하세요. 예: exec, 여러 항목은 쉼표로 구분', + uploadMaxSize: '업로드 제한', + indexHelper: 'PHP 웹사이트의 정상 작동을 위해 코드를 인덱스 디렉터리에 배치하고 이름 변경을 피하세요.', + extensions: '확장 템플릿', + extension: '확장', + extensionHelper: '여러 확장은 쉼표로 구분하여 입력하세요.', + toExtensionsList: '확장 목록 보기', + containerConfig: '컨테이너 구성', + containerConfigHelper: '환경 변수 및 기타 정보는 생성 후 구성 - 컨테이너 구성에서 수정할 수 있습니다', + dateTimezoneHelper: '예: TZ=Asia/Shanghai(필요에 따라 추가하세요)', + }, + nginx: { + serverNamesHashBucketSizeHelper: '서버 이름의 해시 테이블 크기', + clientHeaderBufferSizeHelper: '클라이언트가 요청한 헤더 버퍼 크기', + clientMaxBodySizeHelper: '최대 업로드 파일 크기', + keepaliveTimeoutHelper: '연결 시간 초과', + gzipMinLengthHelper: '최소 압축 파일 크기', + gzipCompLevelHelper: '압축률', + gzipHelper: '전송을 위한 압축 활성화', + connections: '활성 연결', + accepts: '수락', + handled: '처리됨', + requests: '요청', + reading: '읽기 중', + writing: '쓰기 중', + waiting: '대기 중', + status: '현재 상태', + configResource: '구성', + saveAndReload: '저장 및 다시 로드', + clearProxyCache: '리버스 프록시 캐시 삭제', + clearProxyCacheWarn: + '캐시가 구성된 모든 웹사이트에 영향을 미치며 OpenResty 가 다시 시작됩니다. 계속하시겠습니까?', + create: '모듈 추가', + update: '모듈 편집', + params: '매개변수', + packages: '패키지', + script: '스크립트', + module: '모듈', + build: '빌드', + buildWarn: 'OpenResty 빌드는 CPU와 메모리의 일정량을 예약해야 하며, 시간이 오래 걸릴 수 있으니 기다려 주세요.', + mirrorUrl: '소프트웨어 소스', + paramsHelper: '예: --add-module=/tmp/ngx_brotli', + packagesHelper: '예: git,curl 쉼표로 구분', + scriptHelper: '컴파일 전에 실행할 스크립트, 일반적으로 모듈 소스 코드 다운로드, 종속성 설치 등', + buildHelper: '모듈 추가/수정 후 빌드를 클릭하세요. 빌드가 성공하면 OpenResty가 자동으로 재시작됩니다.', + defaultHttps: 'HTTPS 변조 방지', + defaultHttpsHelper1: '이를 활성화하면 HTTPS 변조 문제를 해결할 수 있습니다.', + sslRejectHandshake: '기본 SSL 핸드셰이크 거부', + sslRejectHandshakeHelper: + '활성화하면 인증서 누출을 방지할 수 있지만, 기본 웹사이트를 설정하면 이 설정이 무효화됩니다', + }, + ssl: { + create: '요청', + provider: '유형', + manualCreate: '수동 생성됨', + acmeAccount: 'ACME 계정', + resolveDomain: '도메인 이름 확인', + err: '오류', + value: '레코드 값', + dnsResolveHelper: 'DNS 해석 서비스 제공업체에서 다음 해석 레코드를 추가하세요:', + detail: '세부 정보', + msg: '정보', + ssl: '인증서', + key: '개인 키', + startDate: '유효 시작 시간', + organization: '발급 기관', + renewConfirm: '도메인 이름 {0}에 대해 새 인증서를 갱신합니다. 계속하시겠습니까?', + autoRenew: '자동 갱신', + autoRenewHelper: '만료 30일 전에 자동으로 갱신', + renewSuccess: '갱신 성공', + renewWebsite: '이 인증서는 다음 웹사이트에 연결되었으며, 적용은 이러한 웹사이트에 동시에 적용됩니다.', + createAcme: '계정 생성', + acmeHelper: 'Acme 계정은 무료 인증서를 신청하는 데 사용됩니다.', + upload: '가져오기', + applyType: '유형', + apply: '갱신', + applyStart: '인증서 신청 시작', + getDnsResolve: 'DNS 해석 값을 가져오는 중입니다. 잠시 기다려주세요...', + selfSigned: '자체 서명된 CA', + ca: '인증 기관', + commonName: '공통 이름', + caName: '인증 기관 이름', + company: '기관 이름', + department: '조직 단위 이름', + city: '지역 이름', + province: '주 또는 지방 이름', + country: '국가 이름 (2자리 코드)', + commonNameHelper: '예를 들어, ', + selfSign: '인증서 발급', + days: '유효 기간', + domainHelper: '줄당 하나의 도메인 이름, * 및 IP 주소 지원', + pushDir: '로컬 디렉토리로 인증서 푸시', + dir: '디렉토리', + pushDirHelper: '인증서 파일 "fullchain.pem" 및 키 파일 "privkey.pem"이 이 디렉토리에 생성됩니다.', + organizationDetail: '조직 세부 정보', + fromWebsite: '웹사이트에서 가져오기', + dnsMauanlHelper: '수동 해석 모드에서는 생성 후 신청 버튼을 클릭하여 DNS 해석 값을 얻어야 합니다.', + httpHelper: + 'HTTP 모드를 사용하려면 OpenResty를 설치해야 하며, 와일드카드 도메인 인증서 신청을 지원하지 않습니다.', + buypassHelper: 'Buypass 는 중국 본토에서 접근할 수 없습니다.', + googleHelper: 'EAB HmacKey 및 EAB kid 를 얻는 방법', + googleCloudHelper: 'Google Cloud API 는 중국 본토 대부분에서 접근할 수 없습니다.', + skipDNSCheck: 'DNS 확인 건너뛰기', + skipDNSCheckHelper: '인증 요청 중 타임아웃 문제가 발생할 경우에만 선택하세요.', + cfHelper: 'Global API Key 를 사용하지 마세요.', + deprecated: '더 이상 지원되지 않습니다.', + deprecatedHelper: + '유지 관리가 중단되었으며 향후 버전에서 제외될 수 있습니다. Tencent Cloud 방법을 사용하여 분석하세요.', + disableCNAME: 'CNAME 비활성화', + disableCNAMEHelper: '도메인 이름에 CNAME 레코드가 있고 요청이 실패할 경우 선택하세요.', + nameserver: 'DNS 서버', + nameserverHelper: '사용자 지정 DNS 서버를 사용하여 도메인 이름을 확인합니다.', + edit: '인증서 편집', + execShell: '인증 요청 후 스크립트 실행', + shell: '스크립트 내용', + shellHelper: + '스크립트의 기본 실행 디렉토리는 1Panel 설치 디렉토리입니다. 인증서가 로컬 디렉토리에 푸시되는 경우 실행 디렉토리는 인증서 푸시 디렉토리가 됩니다. 기본 실행 제한 시간은 30분입니다.', + customAcme: '사용자 정의 ACME 서비스', + customAcmeURL: 'ACME 서비스 URL', + baiduCloud: '바이두 클라우드', + pushNode: '다른 노드에 동기화', + pushNodeHelper: '신청/갱신 후 선택한 노드로 푸시', + fromMaster: '마스터 노드에서 푸시', + hostedZoneID: 'Hosted Zone ID', + isIP: 'IP 인증서', + useEAB: 'EAB 인증 사용', + }, + firewall: { + create: '규칙 만들기', + edit: '규칙 수정', + ccDeny: 'CC 보호', + ipWhiteList: 'IP 허용 목록', + ipBlockList: 'IP 차단 목록', + fileExtBlockList: '파일 확장자 차단 목록', + urlWhiteList: 'URL 허용 목록', + urlBlockList: 'URL 차단 목록', + argsCheck: 'GET 파라미터 검사', + postCheck: 'POST 파라미터 검사', + cookieBlockList: '쿠키 차단 목록', + dockerHelper: + '현재 방화벽은 컨테이너 포트 매핑을 비활성화할 수 없습니다. 설치된 애플리케이션은 [설치됨] 페이지에서 애플리케이션 매개변수를 편집하고 포트 해제 규칙을 구성할 수 있습니다.', + iptablesHelper: + '시스템이 {0} 방화벽을 사용 중인 것으로 감지되었습니다. iptables로 전환하려면 먼저 수동으로 제거하세요!', + used: '사용됨', + unUsed: '사용 안 함', + dockerRestart: '방화벽 작업에는 Docker 서비스 재시작이 필요합니다', + firewallHelper: '{0} 시스템 방화벽', + firewallNotStart: '현재 시스템 방화벽이 활성화되지 않았습니다. 먼저 활성화하세요.', + restartFirewallHelper: '이 작업은 현재 방화벽을 재시작합니다. 계속하시겠습니까?', + stopFirewallHelper: '이 작업은 서버 보안을 잃게 만듭니다. 계속하시겠습니까?', + startFirewallHelper: '방화벽이 활성화되면 서버 보안이 강화됩니다. 계속하시겠습니까?', + noPing: 'Ping 비활성화', + enableBanPing: 'Ping 차단', + disableBanPing: 'Ping 차단 해제', + noPingTitle: 'Ping 비활성화', + noPingHelper: '이 작업은 Ping 을 비활성화하며 서버는 ICMP 응답을 보내지 않게 됩니다. 계속하시겠습니까?', + onPingHelper: '이 작업은 Ping 을 활성화하여 해커가 서버를 발견할 수 있습니다. 계속하시겠습니까?', + changeStrategy: '{0} 전략 변경', + changeStrategyIPHelper1: + 'IP 주소 전략을 [거부]로 변경합니다. 설정 후 해당 IP 주소는 서버 접근이 차단됩니다. 계속하시겠습니까?', + changeStrategyIPHelper2: + 'IP 주소 전략을 [허용]으로 변경합니다. 설정 후 해당 IP 주소는 정상적으로 접근할 수 있습니다. 계속하시겠습니까?', + changeStrategyPortHelper1: + '포트 정책을 [차단]으로 변경합니다. 설정 후 외부 접근이 차단됩니다. 계속하시겠습니까?', + changeStrategyPortHelper2: + '포트 정책을 [허용]으로 변경합니다. 설정 후 정상적으로 포트 접근이 복원됩니다. 계속하시겠습니까?', + stop: '정지', + portFormatError: '이 필드는 유효한 포트이어야 합니다.', + portHelper1: '여러 포트, 예: 8080, 8081', + portHelper2: '포트 범위, 예: 8080-8089', + changeStrategyHelper: + '[{1}] {0} 전략을 [{2}]로 변경합니다. 설정 후 {0}은(는) {2}로 외부 접근을 허용합니다. 계속하시겠습니까?', + + strategy: '전략', + accept: '허용', + drop: '차단', + anyWhere: '어디든지', + address: '지정된 IP', + addressHelper: 'IP 주소 또는 IP 범위를 지원합니다.', + allow: '허용', + deny: '거부', + addressFormatError: '이 필드는 유효한 IP 주소여야 합니다.', + addressHelper1: "IP 주소 또는 IP 범위가 필요합니다. 예: '172.16.10.11' 또는 '172.16.10.0/24'.", + addressHelper2: "여러 IP 주소는 쉼표로 구분합니다. 예: '172.16.10.11, 172.16.0.0/24'.", + allIP: '모든 IP', + portRule: '규칙 | 규칙들', + createPortRule: '@:commons.button.create @.lower:firewall.portRule', + forwardRule: '포트 전달 규칙 | 포트 전달 규칙들', + createForwardRule: '@:commons.button.create @:firewall.forwardRule', + ipRule: 'IP 규칙 | IP 규칙들', + createIpRule: '@:commons.button.create @:firewall.ipRule', + userAgent: 'User-Agent 필터', + sourcePort: '소스 포트', + targetIP: '대상 IP', + targetPort: '대상 포트', + forwardHelper1: "로컬 포트로 전달하려면, 대상 IP 를 '127.0.0.1'로 설정해야 합니다.", + forwardHelper2: '대상 IP 를 비워두면 로컬 포트로 전달됩니다.', + forwardPortHelper: '포트 범위를 지원합니다, 예: 8080-8089', + forwardInboundInterface: '포워딩 인바운드 네트워크 인터페이스', + exportHelper: '{0}개의 방화벽 규칙을 내보내려고 합니다. 계속하시겠습니까?', + importSuccess: '{0}개의 규칙을 성공적으로 가져왔습니다', + importPartialSuccess: '가져오기 완료: 성공 {0}건, 실패 {1}건', + + ipv4Limit: '현재 작업은 IPv4 주소만 지원합니다', + basicStatus: '현재 체인 {0}이(가) 바인딩되지 않았습니다. 먼저 바인딩하세요!', + baseIptables: 'iptables 서비스', + forwardIptables: 'iptables 포트 포워딩 서비스', + advanceIptables: 'iptables 고급 구성 서비스', + initMsg: '{0}을(를) 초기화하려고 합니다. 계속하시겠습니까?', + initHelper: '{0}이(가) 초기화되지 않았습니다. 상단 상태 표시줄의 초기화 버튼을 클릭하여 구성하세요!', + bindHelper: '바인딩 - 방화벽 규칙은 상태가 바인딩된 경우에만 효과가 있습니다. 확인하시겠습니까?', + unbindHelper: + '바인딩 해제 - 바인딩 해제 시 추가된 모든 방화벽 규칙이 무효화됩니다. 주의하여 진행하세요. 확인하시겠습니까?', + defaultStrategy: '현재 체인 {0}의 기본 정책은 {1}입니다', + defaultStrategy2: + '현재 체인 {0}의 기본 정책은 {1}입니다. 현재 상태는 바인딩되지 않았습니다. 추가된 방화벽 규칙은 바인딩 후에 효과가 발생합니다!', + filterRule: '필터 규칙', + filterHelper: + '필터 규칙을 사용하면 INPUT/OUTPUT 수준에서 네트워크 트래픽을 제어할 수 있습니다. 시스템 잠금을 방지하기 위해 주의하여 구성하세요.', + chain: '체인', + targetChain: '대상 체인', + sourceIP: '소스 IP', + destIP: '대상 IP', + inboundDirection: '인바운드 방향', + outboundDirection: '아웃바운드 방향', + destPort: '대상 포트', + action: '동작', + reject: '거부', + sourceIPHelper: 'CIDR 형식, 예: 192.168.1.0/24. 모든 주소의 경우 비워 둠', + destIPHelper: 'CIDR 형식, 예: 10.0.0.0/8. 모든 주소의 경우 비워 둠', + portHelper: '0은 모든 포트를 의미합니다', + allPorts: '모든 포트', + deleteRuleConfirm: '{0}개의 규칙을 삭제합니다. 계속하시겠습니까?', + }, + runtime: { + runtime: '실행 환경', + workDir: '작업 디렉토리', + create: '실행 환경 생성', + localHelper: '로컬 환경 설치 및 오프라인 환경 사용 관련 문제는 다음을 참조하세요 ', + versionHelper: 'PHP 버전, 예: v8.0', + buildHelper: + '확장 기능이 많을수록 이미지 생성 시 CPU 사용량이 증가합니다. 환경 생성 후 확장 기능을 설치하는 것도 가능합니다.', + openrestyWarn: 'PHP는 OpenResty 버전 1.21.4.1 이상으로 업그레이드해야 사용 가능합니다.', + toupgrade: '업그레이드하기', + edit: '실행 환경 수정', + extendHelper: + '목록에 없는 확장 프로그램은 수동으로 입력하고 선택할 수 있습니다. 예를 들어 "sockets"를 입력한 후 드롭다운 목록에서 첫 번째 옵션을 선택하여 확장 목록을 확인하세요.', + rebuildHelper: '확장 기능을 수정한 후에는 PHP 애플리케이션을 재빌드해야 적용됩니다.', + rebuild: 'PHP 애플리케이션 재빌드', + source: 'PHP 확장 소스', + ustc: '중국과학기술대학', + netease: '네티이즈', + aliyun: '알리바바 클라우드', + default: '기본', + tsinghua: '칭화대학교', + xtomhk: 'XTOM 미러 사이트 (홍콩)', + xtom: 'XTOM 미러 사이트 (전 세계)', + phpsourceHelper: '네트워크 환경에 맞는 적절한 소스를 선택하세요.', + appPort: '앱 포트', + externalPort: '외부 포트', + packageManager: '패키지 관리자', + codeDir: '코드 디렉터리', + appPortHelper: '애플리케이션이 사용하는 포트.', + externalPortHelper: '외부에 노출된 포트.', + runScript: '실행 스크립트', + runScriptHelper: '시작 명령 목록은 소스 디렉터리의 package.json 파일에서 분석됩니다.', + open: '열기', + operatorHelper: '{0} 작업이 선택된 운영 환경에서 수행됩니다. 계속하시겠습니까?', + taobao: '타오바오', + tencent: '텐센트', + imageSource: '이미지 소스', + moduleManager: '모듈 관리', + module: '모듈', + nodeOperatorHelper: + '{0} {1} 모듈인가요? 이 작업은 운영 환경에 비정상을 일으킬 수 있으므로 진행 전에 확인해 주세요.', + customScript: '사용자 정의 시작 명령', + customScriptHelper: + '전체 시작 명령어를 입력해 주세요. 예: npm run start. PM2 시작 명령어는 pm2-runtime로 변경해 주세요. 그렇지 않으면 시작에 실패합니다.', + portError: '포트를 중복 사용하지 마세요.', + systemRestartHelper: '상태 설명: 중단 - 시스템 재시작으로 인해 상태 가져오기가 실패했습니다.', + javaScriptHelper: '전체 시작 명령을 제공하세요. 예: "java -jar halo.jar -Xmx1024M -Xms256M".', + javaDirHelper: '디렉터리는 jar 파일을 포함해야 하며, 하위 디렉터리도 허용됩니다.', + goHelper: '전체 시작 명령을 제공하세요. 예: "go run main.go" 또는 "./main".', + goDirHelper: '디렉터리 또는 하위 디렉터리는 Go 또는 바이너리 파일을 포함해야 합니다.', + pythonHelper: + '전체 시작 명령을 제공하세요. 예: "pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000".', + dotnetHelper: '완전한 시작 명령을 입력하세요. 예: dotnet MyWebApp.dll', + dirHelper: '주의: 컨테이너 내의 디렉토리 경로를 입력하세요', + concurrency: '동시성 체계', + loadStatus: '부하 상태', + extraHosts: '호스트 매핑', + }, + process: { + pid: '프로세스 ID', + ppid: '부모 PID', + numThreads: '스레드', + memory: '메모리', + diskRead: '디스크 읽기', + diskWrite: '디스크 쓰기', + netSent: '업링크', + netRecv: '다운스트림', + numConnections: '연결', + startTime: '시작 시간', + state: '상태', + running: '실행 중', + sleep: '대기 중', + stop: '중지', + idle: '유휴', + zombie: '좀비 프로세스', + wait: '대기', + lock: '잠금', + blocked: '차단됨', + cmdLine: '시작 명령', + basic: '기본', + mem: '메모리', + openFiles: '열린 파일', + env: '환경 변수', + noenv: '없음', + net: '네트워크 연결', + laddr: '출발지 주소/포트', + raddr: '목적지 주소/포트', + stopProcess: '종료', + viewDetails: '세부 사항', + stopProcessWarn: '이 프로세스(PID:{0})를 종료하시겠습니까?', + kill: '프로세스 종료', + killNow: '즉시 종료', + killHelper: + '프로세스 {0}을(를) 종료하면 일부 프로그램이 정상적으로 작동하지 않을 수 있습니다. 계속하시겠습니까?', + processName: '프로세스 이름', + }, + tool: { + supervisor: { + loadStatusErr: '프로세스 상태를 가져오지 못했습니다. supervisor 서비스의 상태를 확인하세요.', + notSupport: 'Supervisor 서비스를 감지하지 못했습니다. 스크립트 라이브러리 페이지에서 수동으로 설치하세요', + list: '데몬 프로세스 | 데몬 프로세스들', + config: 'Supervisor 설정', + primaryConfig: '주 설정 파일 위치', + notSupportCtl: `supervisorctl 이 감지되지 않았습니다. 스크립트 라이브러리 페이지에서 수동으로 설치하세요.`, + user: '사용자', + command: '명령어', + dir: '디렉토리', + numprocs: '프로세스 수', + initWarn: + '이 작업은 주 설정 파일의 "[include]" 섹션에 있는 "files" 값을 수정합니다. 다른 설정 파일의 디렉토리는 "{1Panel 설치 디렉토리}/1panel/tools/supervisord/supervisor.d/"입니다.', + operatorHelper: '{1} 작업을 {0}에서 수행합니다. 계속하시겠습니까?', + uptime: '운영 시간', + notStartWarn: `Supervisor 가 시작되지 않았습니다. 먼저 시작하세요.`, + serviceName: '서비스 이름', + initHelper: + 'Supervisor 서비스가 감지되었지만 초기화되지 않았습니다. 상단 상태 표시줄의 초기화 버튼을 클릭하여 구성하세요.', + serviceNameHelper: 'systemctl 로 관리되는 Supervisor 서비스 이름, 보통 supervisor 또는 supervisord입니다.', + restartHelper: + '이 작업은 초기화 후 서비스를 재시작합니다. 이로 인해 기존의 모든 데몬 프로세스가 중지됩니다.', + RUNNING: '실행 중', + STOPPED: '중지됨', + STOPPING: '중지 중', + STARTING: '시작 중', + FATAL: '시작 실패', + BACKOFF: '시작 예외', + ERROR: '오류', + statusCode: '상태 코드', + manage: '관리', + autoRestart: '자동 재시작', + EXITED: '종료됨', + autoRestartHelper: '프로그램이 비정상적으로 종료된 후 자동으로 재시작할지 여부', + autoStart: '자동 시작', + autoStartHelper: 'Supervisor 시작 후 서비스를 자동으로 시작할지 여부', + }, + }, + disk: { + management: '디스크 관리', + partition: '파티션', + unmount: '마운트 해제', + unmountHelper: '파티션 {0} 을(를) 마운트 해제하시겠습니까?', + mount: '마운트', + partitionAlert: + '디스크 파티션 작업은 디스크 포맷이 필요하며, 기존 데이터는 삭제됩니다. 데이터를 미리 저장하거나 스냅샷을 찍어주세요.', + mountPoint: '마운트 디렉토리', + systemDisk: '시스템 디스크', + unpartitionedDisk: '미파티션 디스크', + handlePartition: '지금 파티션', + filesystem: '파일 시스템', + unmounted: '마운트 해제됨', + cannotOperate: '작업 불가', + systemDiskHelper: '힌트: 현재 디스크는 시스템 디스크입니다. 작업할 수 없습니다.', + autoMount: '자동 마운트', + model: '장치 모델', + diskType: '디스크 유형', + serial: '시리얼 번호', + noFail: '마운트 실패는 시스템 시작에 영향을 미치지 않습니다', + }, + xpack: { + expiresTrialAlert: + '친절한 알림: 귀하의 Pro 체험판이 {0}일 후 만료되며, 모든 Pro 기능에 더 이상 접근할 수 없습니다. 제때 갱신하거나 전체 버전으로 업그레이드하시기 바랍니다.', + expiresAlert: + '친절한 알림: 귀하의 Pro 라이선스가 {0}일 후 만료되며, 모든 Pro 기능에 더 이상 접근할 수 없습니다. 지속적인 사용을 위해 신속하게 갱신하시기 바랍니다.', + menu: 'Pro', + upage: 'AI 웹사이트 빌더', + proAlert: '이 기능을 사용하려면 Pro로 업그레이드하세요', + app: { + app: 'APP', + title: '패널 별칭', + titleHelper: '패널 별칭은 APP 에서 표시되는 데 사용됩니다(기본 패널 별칭)', + qrCode: 'QR 코드', + apiStatusHelper: '패널 APP 는 API 인터페이스 기능을 활성화해야 합니다', + apiInterfaceHelper: '패널 API 인터페이스 액세스를 지원합니다(패널 앱에서 이 기능을 활성화해야 합니다)', + apiInterfaceHelper1: + '패널 앱의 액세스에는 방문자를 화이트리스트에 추가해야 하며, 고정 IP가 아닌 경우 0.0.0.0/0(모든 IPv4), ::/0(모든 IPv6) 을 추가하는 것이 좋습니다', + qrCodeExpired: '새로 고침 시간', + apiLeakageHelper: 'QR 코드를 누출하지 마십시오. 신뢰할 수 있는 환경에서만 사용하십시오.', + }, + waf: { + WAF: 'WAF', + blackWhite: '블랙 및 화이트 목록', + globalSetting: '전역 설정', + websiteSetting: '웹사이트 설정', + blockRecords: '차단된 기록', + world: '전 세계', + china: '중국', + intercept: '차단', + request: '요청', + count4xx: '4xx 수량', + count5xx: '5xx 수량', + todayStatus: '오늘의 상태', + reqMap: '공격 맵 (최근 30일)', + resource: '출처', + count: '수량', + hight: '높음', + low: '낮음', + reqCount: '요청 수', + interceptCount: '차단 수', + requestTrends: '요청 트렌드 (최근 7일)', + interceptTrends: '차단 트렌드 (최근 7일)', + whiteList: '화이트 목록', + blackList: '블랙 목록', + ipBlackListHelper: '블랙 목록에 있는 IP 주소는 웹사이트에 접근할 수 없습니다.', + ipWhiteListHelper: '화이트 목록에 있는 IP 주소는 모든 제한을 우회합니다.', + uaBlackListHelper: '블랙 목록에 있는 User-Agent 값의 요청은 차단됩니다.', + uaWhiteListHelper: '화이트 목록에 있는 User-Agent 값의 요청은 모든 제한을 우회합니다.', + urlBlackListHelper: '블랙 목록에 있는 URL 로의 요청은 차단됩니다.', + urlWhiteListHelper: '화이트 목록에 있는 URL 로의 요청은 모든 제한을 우회합니다.', + ccHelper: '같은 IP 에서 {0}초 이내에 {1}회 이상 요청을 받으면 해당 IP 는 {2}동안 차단됩니다.', + blockTime: '차단 기간', + attackHelper: '누적 차단이 {0}초 이내에 {1}회를 초과하면 해당 IP 는 {2}동안 차단됩니다.', + notFoundHelper: '누적된 요청이 {0}초 이내에 404 오류를 {1}번 이상 반환하면 해당 IP 는 {2}동안 차단됩니다.', + frequencyLimit: '접속 빈도 제한', + regionLimit: '지역 제한', + defaultRule: '기본 규칙', + accessFrequencyLimit: '접속 빈도 제한', + attackLimit: '공격 빈도 제한', + notFoundLimit: '404 빈도 제한', + urlLimit: 'URL 빈도 제한', + urlLimitHelper: '단일 URL 의 접속 빈도를 설정합니다.', + sqliDefense: 'SQL 인젝션 보호', + sqliHelper: '요청에서 SQL 인젝션을 감지하여 차단합니다.', + xssHelper: '요청에서 XSS 를 감지하여 차단합니다.', + xssDefense: 'XSS 보호', + uaDefense: '악성 User-Agent 규칙', + uaHelper: '일반적인 악성 봇을 식별하는 규칙이 포함되어 있습니다.', + argsDefense: '악성 파라미터 규칙', + argsHelper: '악성 파라미터를 포함한 요청을 차단합니다.', + cookieDefense: '악성 쿠키 규칙', + cookieHelper: '악성 쿠키가 요청에 포함되지 않도록 차단합니다.', + headerDefense: '악성 헤더 규칙', + headerHelper: '악성 헤더가 포함된 요청을 차단합니다.', + httpRule: 'HTTP 요청 방법 규칙', + httpHelper: + '허용된 접근 방식 유형을 설정합니다. 특정 접근 방식을 제한하려면 해당 유형의 버튼을 끄십시오. 예를 들어: GET 방식만 허용하려면 GET 을 제외한 다른 방식의 버튼을 끄세요.', + geoRule: '지역별 접근 제한', + geoHelper: + '특정 지역에서만 웹사이트 접근을 허용하고 다른 지역에서의 접근을 차단할 수 있습니다. 예를 들어: 중국 본토에서만 접근을 허용하고, 그 외 지역에서의 접근을 차단할 수 있습니다.', + ipLocation: 'IP 위치', + action: '동작', + ruleType: '공격 유형', + ipHelper: 'IP 주소를 입력하세요', + attackLog: '공격 로그', + rule: '규칙', + ipArr: 'IPV4 범위', + ipStart: '시작 IP', + ipEnd: '끝 IP', + ipv4: 'IPv4', + ipv6: 'IPv6', + urlDefense: 'URL 규칙', + urlHelper: '금지된 URL', + dirFilter: '디렉터리 필터', + sqlInject: 'SQL 인젝션', + xss: 'XSS', + phpExec: 'PHP 스크립트 실행', + oneWordTrojan: '단어 트로이', + appFilter: '위험한 디렉터리 필터링', + webshell: '웹쉘', + args: '악성 파라미터', + protocolFilter: '프로토콜 필터', + javaFilter: '자바 위험 파일 필터링', + scannerFilter: '스캐너 필터', + escapeFilter: '이스케이프 필터', + customRule: '사용자 정의 규칙', + httpMethod: 'HTTP 메소드 필터', + fileExt: '파일 업로드 제한', + fileExtHelper: '업로드 제한 파일 확장자', + deny: '금지', + allow: '허용', + field: '객체', + pattern: '조건', + ruleContent: '내용', + contain: '포함', + equal: '같음', + regex: '정규 표현식', + notEqual: '같지 않음', + customRuleHelper: '지정된 조건에 따라 동작을 수행합니다.', + actionAllow: '허용', + blockIP: 'IP 차단', + code: '상태 코드 반환', + noRes: '연결 끊기 (444)', + badReq: '잘못된 매개변수 (400)', + forbidden: '접근 금지 (403)', + serverErr: '서버 오류 (500)', + resHtml: '응답 페이지', + allowHelper: '허용된 접근은 후속 WAF 규칙을 건너뛰게 하므로 주의해서 사용하세요.', + captcha: '사람-컴퓨터 검증', + fiveSeconds: '5초 검증', + location: '지역', + redisConfig: 'Redis 설정', + redisHelper: 'Redis 를 활성화하여 일시적으로 차단된 IP를 유지합니다.', + wafHelper: 'WAF 를 종료하면 모든 웹사이트 보호가 해제됩니다.', + attackIP: '공격 IP', + attackParam: '공격 세부사항', + execRule: '적용된 규칙', + acl: 'ACL', + sql: 'SQL 인젝션', + cc: '접속 빈도 제한', + isBlocking: '차단됨', + isFree: '차단 해제됨', + unLock: '차단 해제', + unLockHelper: 'IP: {0}의 차단을 해제하시겠습니까?', + saveDefault: '기본값 저장', + saveToWebsite: '웹사이트에 적용', + saveToWebsiteHelper: '현재 설정을 모든 웹사이트에 적용하시겠습니까?', + websiteHelper: '웹사이트를 생성할 때의 기본 설정입니다. 수정 사항은 웹사이트에 적용해야 효력이 있습니다.', + websiteHelper2: '웹사이트 생성 시 기본 설정이 제공됩니다. 웹사이트에서 해당 설정을 수정해 주세요.', + ipGroup: 'IP 그룹', + ipGroupHelper: + '한 줄에 하나의 IP 또는 IP 범위를 입력하세요, IPv4 및 IPv6 을 지원합니다. 예: 192.168.1.1 또는 192.168.1.0/24', + ipBlack: 'IP 블랙리스트', + openRestyAlert: 'OpenResty 버전은 {0} 이상이어야 합니다.', + initAlert: + '처음 사용 시 초기화가 필요하며, 웹사이트 설정 파일이 수정됩니다. 기존 WAF 설정은 손실될 수 있습니다. 미리 백업해 주세요.', + initHelper: '초기화 작업은 기존 WAF 설정을 삭제합니다. 초기화하시겠습니까?', + mainSwitch: '메인 스위치', + websiteAlert: '먼저 웹사이트를 생성해 주세요.', + defaultUrlBlack: 'URL 규칙', + htmlRes: '차단 페이지', + urlSearchHelper: 'URL 을 입력하여 유사 검색을 지원합니다.', + toCreate: '생성', + closeWaf: 'WAF 종료', + closeWafHelper: 'WAF 를 종료하면 웹사이트 보호가 해제됩니다. 계속하시겠습니까?', + addblack: '블랙 추가', + addwhite: '화이트 추가', + addblackHelper: 'IP: {0}를 기본 블랙리스트에 추가하시겠습니까?', + addwhiteHelper: 'IP: {0}를 기본 화이트리스트에 추가하시겠습니까?', + defaultUaBlack: 'User-Agent 규칙', + defaultIpBlack: '악성 IP 그룹', + cookie: '쿠키 규칙', + urlBlack: 'URL 블랙리스트', + uaBlack: 'User-Agent 블랙리스트', + attackCount: '공격 빈도 제한', + fileExtCheck: '파일 업로드 제한', + geoRestrict: '지역 접근 제한', + attacklog: '차단 기록', + unknownWebsite: '허가되지 않은 도메인 접근', + geoRuleEmpty: '지역은 비워 둘 수 없습니다.', + unknown: '웹사이트 없음', + geo: '지역 제한', + revertHtml: '{0}을 기본 페이지로 복원하시겠습니까?', + five_seconds: '5초 검증', + header: '헤더 규칙', + methodWhite: 'HTTP 규칙', + expiryDate: '만료 날짜', + expiryDateHelper: '검증을 통과한 후 유효 기간 내에는 더 이상 검증하지 않습니다.', + defaultIpBlackHelper: '인터넷에서 수집된 일부 악성 IP로 접근을 차단합니다.', + notFoundCount: '404 빈도 제한', + matchValue: '매칭 값', + headerName: '이 필드는 특수 문자로 시작할 수 없으며,영어, 숫자, -, 길이 3-30 지원', + cdnHelper: 'CDN 을 사용하는 웹사이트는 여기서 원본 IP를 확인할 수 있습니다.', + clearLogWarn: '로그를 삭제하면 복구할 수 없습니다. 계속하시겠습니까?', + commonRuleHelper: '규칙은 유사 매칭 방식입니다.', + blockIPHelper: + '차단된 IP는 OpenResty 에 일시적으로 저장되며, OpenResty 재시작 시 차단이 해제됩니다. 차단 기능을 통해 영구적으로 차단할 수 있습니다.', + addWhiteUrlHelper: 'URL {0}를 화이트리스트에 추가하시겠습니까?', + dashHelper: '커뮤니티 버전은 전역 설정 및 웹사이트 설정에서 기능을 사용할 수 있습니다.', + wafStatusHelper: 'WAF 가 활성화되지 않았습니다. 전역 설정에서 활성화해 주세요.', + ccMode: '모드', + global: '전역 모드', + uriMode: 'URL 모드', + globalHelper: '전역 모드: 특정 시간 내에 모든 URL에 대한 요청 수가 임계값을 초과하면 트리거됩니다.', + uriModeHelper: 'URL 모드: 특정 시간 내에 개별 URL에 대한 요청 수가 임계값을 초과하면 트리거됩니다.', + ip: 'IP 블랙리스트', + globalSettingHelper: + '[웹사이트] 태그가 있는 설정은 [웹사이트 설정]에서 활성화해야 하며, 전역 설정은 새로 생성된 웹사이트의 기본 설정입니다.', + globalSettingHelper2: '[전역 설정]과 [웹사이트 설정]에서 모두 활성화해야 설정이 적용됩니다.', + urlCCHelper: '{0} 초 이내에 이 URL에 대해 {1} 회를 초과하는 요청이 있어 이 IP를 차단합니다 {2}', + urlCCHelper2: 'URL에 매개변수를 포함할 수 없습니다', + notContain: '포함하지 않음', + urlcc: 'URL 빈도 제한', + method: '요청 유형', + addIpsToBlock: 'IP 일괄 차단', + addUrlsToWhite: 'URL을 일괄 허용 목록에 추가', + noBlackIp: 'IP가 이미 차단되어 있으므로 다시 차단할 필요가 없습니다', + noWhiteUrl: 'URL이 이미 허용 목록에 포함되어 있으므로 다시 추가할 필요가 없습니다', + spiderIpHelper: + '바이두, 빙, 구글, 360, 신마, 소구, 바이트댄스, DuckDuckGo, Yandex가 포함됩니다. 이를 끄면 모든 스파이더의 접근이 차단됩니다.', + spiderIp: '스파이더 IP 풀', + geoIp: 'IP 주소 라이브러리', + geoIpHelper: 'IP의 지리적 위치를 확인하는 데 사용됩니다', + stat: '공격 보고서', + statTitle: '보고서', + attackIp: '공격 IP', + attackCountNum: '공격 횟수', + percent: '비율', + addblackUrlHelper: 'URL: {0}을(를) 기본 블랙리스트에 추가할까요?', + rce: '원격 코드 실행', + software: '소프트웨어', + cveHelper: '일반적인 소프트웨어 및 프레임워크의 취약점을 포함', + vulnCheck: '보충 규칙', + ssrf: 'SSRF 취약점', + afr: '임의 파일 읽기', + ua: '무단 액세스', + id: '정보 누출', + aa: '인증 우회', + dr: '디렉토리 순회', + xxe: 'XXE 취약점', + suid: '직렬화 취약점', + dos: '서비스 거부 취약점', + afd: '임의 파일 다운로드', + sqlInjection: 'SQL 인젝션', + afw: '임의 파일 쓰기', + il: '정보 유출', + clearAllLog: '모든 로그 삭제', + exportLog: '로그 내보내기', + appRule: '애플리케이션 규칙', + appRuleHelper: + '일반적인 애플리케이션 규칙. 활성화하면 오탐지를 줄일 수 있습니다. 하나의 웹사이트는 하나의 규칙만 사용할 수 있습니다', + logExternal: '기록 유형 제외', + ipWhite: 'IP 허용 목록', + urlWhite: 'URL 허용 목록', + uaWhite: '사용자 에이전트 허용 목록', + logExternalHelper: + '제외된 기록 유형은 로그에 기록되지 않습니다. 블랙리스트/허용 목록, 지역 액세스 제한, 사용자 정의 규칙은 많은 로그를 생성합니다. 제외를 권장합니다', + ssti: 'SSTI 공격', + crlf: 'CRLF 인젝션', + strict: '엄격 모드', + strictHelper: '더 엄격한 규칙을 사용하여 요청을 검증합니다', + saveLog: '로그 저장', + remoteURLHelper: '원격 URL은 한 줄에 하나의 IP만 포함하고 다른 문자는 포함하지 않아야 합니다', + notFound: 'Not Found (404)', + serviceUnavailable: '서비스 불가 (503)', + gatewayTimeout: '게이트웨이 시간 초과 (504)', + belongToIpGroup: 'IP 그룹에 속함', + notBelongToIpGroup: 'IP 그룹에 속하지 않음', + unknownWebsiteKey: '알 수 없는 도메인', + special: '특수 문자', + fileToLarge: '파일이 1MB를 초과하여 업로드할 수 없습니다', + uploadOverLimit: '업로드된 파일 수가 제한을 초과했습니다, 최대 1개', + importRuleHelper: '한 줄에 하나의 규칙', + }, + monitor: { + name: '웹사이트 모니터링', + pv: '페이지 조회수', + uv: '고유 방문자 수', + flow: '트래픽 흐름', + ip: 'IP', + spider: '스파이더', + visitors: '방문자 추세', + today: '오늘', + last7days: '지난 7일', + last30days: '지난 30일', + uvMap: '방문자 지도 (30일)', + qps: '실시간 요청 (분당)', + flowSec: '실시간 트래픽 (분당)', + excludeCode: '상태 코드 제외', + excludeUrl: 'URL 제외', + excludeExt: '확장자 제외', + cdnHelper: 'CDN 제공 헤더에서 실제 IP 가져오기', + reqRank: '방문 순위', + refererDomain: '참조 도메인', + os: '운영 체제', + browser: '브라우저/클라이언트', + device: '장치', + showMore: '더보기', + unknown: '기타', + pc: '컴퓨터', + mobile: '모바일 장치', + wechat: '위챗', + machine: '머신', + tencent: '텐센트 브라우저', + ucweb: 'UC 브라우저', + '2345explorer': '2345 브라우저', + huaweibrowser: '화웨이 브라우저', + log: '요청 로그', + statusCode: '상태 코드', + requestTime: '응답 시간', + flowRes: '응답 트래픽', + method: '요청 메서드', + statusCodeHelper: '상태 코드를 입력하세요', + statusCodeError: '유효하지 않은 상태 코드 유형', + methodHelper: '요청 메서드를 입력하세요', + all: '전체', + baidu: '바이두', + google: '구글', + bing: '빙', + bytes: '오늘의 헤드라인', + sogou: '소구', + failed: '오류', + ipCount: 'IP 수', + spiderCount: '스파이더 요청', + averageReqTime: '평균 응답 시간', + totalFlow: '총 트래픽', + logSize: '로그 파일 크기', + realIPType: '실제 IP 가져오기 방법', + fromHeader: 'HTTP 헤더에서 가져오기', + fromHeaders: '헤더 목록에서 가져오기', + header: 'HTTP 헤더', + cdnConfig: 'CDN 구성', + xff1: 'X-Forwarded-For 의 1단계 프록시', + xff2: 'X-Forwarded-For 의 2단계 프록시', + xff3: 'X-Forwarded-For 의 3단계 프록시', + xffHelper: + '예: X-Forwarded-For: ,,,. 최상위 프록시는 마지막 IP 를 가져옵니다', + headersHelper: + '일반적으로 사용되는 CDN HTTP 헤더에서 실제 IP를 가져오며, 사용 가능한 첫 번째 값을 선택합니다', + monitorCDNHelper: '웹사이트 모니터링을 위한 CDN 구성을 수정하면 WAF CDN 설정도 업데이트됩니다', + wafCDNHelper: 'WAF CDN 구성을 수정하면 웹사이트 모니터링 CDN 설정도 업데이트됩니다', + statusErr: '잘못된 상태 코드 형식', + shenma: '션마 검색', + duckduckgo: '덕덕고', + '360': '360 검색', + excludeUri: 'URI 제외', + top100Helper: '상위 100 개의 데이터를 표시합니다', + logSaveDay: '로그 보관 기간 (일)', + cros: '크롬 OS', + theworld: '더월드 브라우저', + edge: '마이크로소프트 엣지', + maxthon: '맥스톤 브라우저', + monitorStatusHelper: '모니터링이 활성화되지 않았습니다. 설정에서 활성화하세요', + excludeIp: 'IP 주소 제외', + excludeUa: '사용자 에이전트 제외', + remotePort: '원격 포트', + unknown_browser: '알 수 없음', + unknown_os: '알 수 없음', + unknown_device: '알 수 없음', + logSaveSize: '최대 로그 저장 크기', + logSaveSizeHelper: '이것은 단일 웹사이트의 로그 저장 크기입니다', + '360se': '360 보안 브라우저', + websites: '웹사이트 목록', + trend: '추세 통계', + reqCount: '요청 수', + uriHelper: '/test/* 또는 /*/index.php를 사용하여 Uri를 제외할 수 있습니다', + }, + tamper: { + tamper: '웹사이트 변조 방지', + ignoreTemplate: '제외 템플릿', + protectTemplate: '보호 템플릿', + ignoreTemplateHelper: + '제외 내용을 입력하세요, Enter 또는 공백으로 구분합니다. (특정 디렉토리 ./log 또는 디렉토리 이름 tmp, 파일을 제외하려면 특정 파일 ./data/test.html을 입력해야 합니다)', + protectTemplateHelper: + '보호 내용을 입력하세요, Enter 또는 공백으로 구분합니다. (특정 파일 ./index.html, 파일 확장자 .html, 파일 유형 js, 디렉토리를 보호하려면 특정 디렉토리 ./log을 입력해야 합니다)', + templateContent: '템플릿 내용', + template: '템플릿', + saveTemplate: '템플릿으로 저장', + tamperHelper1: + '원클릭 배포 유형의 웹사이트의 경우 애플리케이션 디렉토리 변조 방지 기능을 활성화하는 것이 좋습니다. 웹사이트가 정상적으로 사용되지 않거나 백업/복원이 실패하는 경우 먼저 변조 방지 기능을 비활성화하세요.', + tamperHelper2: + '제외되지 않은 디렉토리에서 보호된 파일에 대한 읽기/쓰기, 삭제, 권한 및 소유자 수정 작업을 제한합니다', + tamperPath: '보호 디렉토리', + tamperPathEdit: '경로 수정', + log: '차단 로그', + totalProtect: '총 보호', + todayProtect: '오늘 보호', + templateRule: '길이 1-512, 이름에 {0} 등의 기호를 포함할 수 없습니다', + ignore: '제외', + ignoreHelper: + '제외 내용을 선택하거나 입력하세요, Enter 또는 공백으로 구분합니다. (특정 디렉토리 ./log 또는 디렉토리 이름 tmp, 파일을 제외하려면 특정 파일 ./data/test.html을 입력하거나 선택해야 합니다)', + protect: '보호', + protectHelper: + '보호 내용을 선택하거나 입력하세요, Enter 또는 공백으로 구분합니다. (특정 파일 ./index.html, 파일 확장자 .html, 파일 유형 js, 디렉토리를 보호하려면 특정 디렉토리 ./log을 입력하거나 선택해야 합니다)', + tamperHelper00: '제외와 보호는 상대 경로만 입력 가능합니다.', + tamperHelper01: + '변조 방지를 활성화한 후 시스템은 제외되지 않은 디렉토리에서 보호된 파일의 생성, 편집 및 삭제 작업을 제한합니다.', + tamperHelper02: '우선순위: 특정 경로 보호 > 특정 경로 제외 > 보호 > 제외', + tamperHelper03: + '모니터링 작업은 제외되지 않은 디렉토리만 대상으로 하며, 이러한 디렉토리에서 비보호 파일의 생성을 모니터링합니다.', + disableHelper: '다음 웹사이트의 변조 방지 기능을 비활성화하려고 합니다. 계속하시겠습니까?', + appendOnly: '추가 전용', + appendOnlyHelper: + '이 디렉토리 내 파일의 삭제 작업을 제한하며, 제외 디렉토리 또는 비보호 파일 추가만 허용합니다', + immutable: '불변', + immutableHelper: '이 파일의 편집, 삭제, 권한 및 소유자 수정 작업을 제한합니다', + onWatch: '감시', + onWatchHelper: '이 디렉토리 내 보호 파일 또는 비제외 디렉토리 생성에 대한 감시 및 가로채기를 수행합니다', + forceStop: '강제 종료', + forceStopHelper: '이 웹사이트 디렉토리의 변조 방지 기능을 강제로 비활성화하려고 합니다. 계속하시겠습니까?', + }, + setting: { + setting: '패널 설정', + title: '패널 설명', + titleHelper: '사용자 로그인 페이지에 표시됩니다 (예: Linux 서버 운영 및 유지 관리 패널, 권장 길이: 8-15자)', + logo: '로고 (텍스트 없음)', + logoHelper: '메뉴가 축소되었을 때 관리 페이지의 왼쪽 상단에 표시됩니다 (권장 이미지 크기: 82px*82px)', + logoWithText: '로고 (텍스트 포함)', + logoWithTextHelper: + '메뉴가 확장되었을 때 관리 페이지의 왼쪽 상단에 표시됩니다 (권장 이미지 크기: 185px*55px)', + favicon: '웹사이트 아이콘', + faviconHelper: '웹사이트 아이콘 (권장 이미지 크기: 16px*16px)', + setDefault: '기본값 복원', + setHelper: '현재 설정이 저장됩니다. 계속하시겠습니까?', + setDefaultHelper: '모든 패널 설정이 기본값으로 복원됩니다. 계속하시겠습니까?', + logoGroup: '로고', + imageGroup: '이미지', + loginImage: '로그인 페이지 이미지', + loginImageHelper: '로그인 페이지에 표시됩니다 (권장 크기: 500*416px)', + loginBgType: '로그인 배경 유형', + loginBgImage: '로그인 배경 이미지', + loginBgImageHelper: '로그인 페이지의 배경 이미지로 표시됩니다 (권장 크기: 1920*1080px)', + loginBgColor: '로그인 배경 색상', + loginBgColorHelper: '로그인 페이지의 배경 색상으로 표시됩니다', + image: '이미지', + bgColor: '배경 색상', + loginGroup: '로그인 페이지', + loginBtnLinkColor: '버튼/링크 색상', + loginBtnLinkColorHelper: '로그인 페이지의 버튼/링크 색상으로 표시됩니다', + }, + helper: { + wafTitle1: '차단 지도', + wafContent1: '지난 30일 동안의 차단 지역 분포를 표시합니다.', + wafTitle2: '지역별 접근 제한', + wafContent2: '지리적 위치에 따라 웹사이트 접근 소스를 제한합니다.', + wafTitle3: '사용자 지정 차단 페이지', + wafContent3: '요청이 차단된 후 표시할 사용자 지정 페이지를 생성합니다.', + wafTitle4: '사용자 지정 규칙 (ACL)', + wafContent4: '사용자 지정 규칙에 따라 요청을 차단합니다.', + + tamperTitle1: '파일 무결성 모니터링', + tamperContent1: '핵심 파일, 스크립트 파일, 구성 파일을 포함한 웹사이트 파일의 무결성을 모니터링합니다.', + tamperTitle2: '실시간 스캔 및 감지', + tamperContent2: '웹사이트 파일 시스템을 실시간으로 스캔하여 비정상적이거나 변조된 파일을 감지합니다.', + tamperTitle3: '보안 권한 설정', + tamperContent3: + '적절한 권한 설정과 접근 제어 정책을 통해 웹사이트 파일 접근을 제한하여 잠재적 공격 면적을 줄입니다.', + tamperTitle4: '로그 기록 및 분석', + tamperContent4: + '파일 접근 및 작업 로그를 기록하여 관리자가 감사 및 분석을 수행할 수 있도록 하고, 잠재적 보안 위협을 식별합니다.', + + settingTitle1: '사용자 정의 환영 메시지', + settingContent1: '1Panel 로그인 페이지에 사용자 정의 환영 메시지를 설정합니다.', + settingTitle2: '사용자 정의 로고', + settingContent2: '브랜드명이나 텍스트가 포함된 로고 이미지를 업로드할 수 있습니다.', + settingTitle3: '사용자 정의 웹사이트 아이콘', + settingContent3: '브라우저 기본 아이콘을 대체할 사용자 정의 아이콘을 업로드하여 사용자 경험을 개선합니다.', + + monitorTitle1: '방문자 추세', + monitorContent1: '웹사이트 방문자 추세를 통계적으로 표시합니다.', + monitorTitle2: '방문자 지도', + monitorContent2: '웹사이트 방문자의 지리적 분포를 통계적으로 표시합니다.', + monitorTitle3: '접속 통계', + monitorContent3: '웹사이트 요청 정보(스파이더, 접속 장치, 요청 상태 등)에 대한 통계를 제공합니다.', + monitorTitle4: '실시간 모니터링', + monitorContent4: '웹사이트 요청 정보(요청 수, 트래픽 등)를 실시간으로 모니터링합니다.', + + alertTitle1: 'SMS 알림', + alertContent1: + '서버 리소스 사용량 이상, 웹사이트 및 인증서 만료, 새로운 버전 업데이트, 비밀번호 만료 등의 문제가 발생하면 SMS 알림을 통해 사용자가 신속히 처리할 수 있도록 합니다.', + alertTitle2: '알림 로그', + alertContent2: + '사용자에게 알림 로그를 조회할 수 있는 기능을 제공하여 과거 알림 이벤트를 추적하고 분석할 수 있도록 합니다.', + alertTitle3: '알림 설정', + alertContent3: + '사용자에게 전화번호, 일일 푸시 빈도, 일일 푸시 시간 등을 사용자 정의할 수 있는 설정을 제공하여 보다 합리적인 푸시 알림을 설정할 수 있도록 합니다.', + + nodeDashTitle1: '애플리케이션 관리', + nodeDashContent1: + '다중 노드 애플리케이션의 통합 관리, 상태 모니터링, 빠른 시작/중지, 터미널 연결 및 백업 지원', + nodeDashTitle2: '웹사이트 관리', + nodeDashContent2: '다중 노드 웹사이트의 통합 관리, 실시간 상태 모니터링, 일괄 시작/중지 및 빠른 백업 지원', + nodeDashTitle3: '데이터베이스 관리', + nodeDashContent3: '다중 노드 데이터베이스의 통합 관리, 주요 상태 한눈에 확인, 원클릭 백업 지원', + nodeDashTitle4: '예약 작업 관리', + nodeDashContent4: '다중 노드 예약 작업의 통합 관리, 상태 모니터링, 빠른 시작/중지 및 수동 트리거 실행 지원', + + nodeTitle1: '원클릭 노드 추가', + nodeContent1: '여러 서버 노드를 빠르게 통합', + nodeTitle2: '일괄 업그레이드', + nodeContent2: '한 번의 작업으로 모든 노드를 동기화 및 업그레이드', + nodeTitle3: '노드 상태 모니터링', + nodeContent3: '각 노드의 운영 상태를 실시간으로 파악', + nodeTitle4: '빠른 원격 연결', + nodeContent4: '원클릭으로 노드 원격 터미널에 직접 연결', + + fileExchangeTitle1: '키 인증 전송', + fileExchangeContent1: 'SSH 키를 통해 인증하여 전송 보안을 보장합니다.', + fileExchangeTitle2: '효율적인 파일 동기화', + fileExchangeContent2: '변경된 내용만 동기화하여 전송 속도와 안정성을 크게 향상시킵니다.', + fileExchangeTitle3: '다중 노드 상호 통신 지원', + fileExchangeContent3: '다른 노드 간에 프로젝트 파일을 쉽게 전송하고, 여러 서버를 유연하게 관리합니다.', + + nodeAppTitle1: '애플리케이션 업그레이드 관리', + nodeAppContent1: '다중 노드 애플리케이션 업데이트 통합 모니터링, 원클릭 업그레이드 지원', + + appTitle1: '유연한 패널 관리', + appContent1: '언제 어디서나 1Panel 서버를 쉽게 관리하세요.', + appTitle2: '종합적인 서비스 정보', + appContent2: + '모바일 앱에서 애플리케이션, 웹사이트, Docker, 데이터베이스 등의 기본 관리를 하고, 애플리케이션과 웹사이트의 빠른 생성을 지원합니다.', + appTitle3: '실시간 이상 모니터링', + appContent3: + '모바일 앱에서 서버 상태, WAF 보안 모니터링, 웹사이트 방문 통계 및 프로세스 건강 상태를 실시간으로 확인하세요.', + + clusterTitle1: '마스터-슬레이브 배포', + clusterContent1: + '다른 노드에서 MySQL/Postgres/Redis 마스터-슬레이브 인스턴스를 생성하는 것을 지원하며, 자동으로 마스터-슬레이브 연결 및 초기화를 완료합니다', + clusterTitle2: '마스터-슬레이브 관리', + clusterContent2: + '통합 페이지에서 여러 마스터-슬레이브 노드를 중앙에서 관리하고, 역할, 실행 상태 등을 확인합니다', + clusterTitle3: '복제 상태', + clusterContent3: + '마스터-슬레이브 복제 상태 및 지연 정보를 표시하여 동기화 문제를 해결하는 데 도움을 줍니다', + }, + node: { + master: '주 노드', + masterBackup: '마스터 노드 백업', + backupNode: '백업 노드', + backupFrequency: '백업 주기(시간)', + backupCopies: '백업 기록 보관 수', + noBackupNode: '현재 백업 노드가 비어 있습니다. 저장할 백업 노드를 선택한 후 다시 시도하십시오!', + masterBackupAlert: + '현재 마스터 노드 백업이 구성되지 않았습니다. 데이터 보안을 위해 장애 시 새로운 마스터 노드로 수동 전환이 가능하도록 가능한 빨리 백업 노드를 설정하십시오.', + node: '노드', + addr: '주소', + nodeUpgrade: '업데이트 설정', + nodeUpgradeHelper: + '선택된 노드는 마스터 노드 업그레이드 완료 후 자동으로 업그레이드를 시작하며 수동 작업이 필요하지 않습니다.', + nodeUnhealthy: '노드 상태 이상', + deletedNode: '삭제된 노드 {0}은(는) 현재 업그레이드 작업을 지원하지 않습니다!', + nodeUnhealthyHelper: '노드 상태 이상이 감지되었습니다. [노드 관리]에서 확인 후 다시 시도하세요!', + nodeUnbind: '노드가 라이선스에 바인딩되지 않음', + nodeUnbindHelper: + '이 노드가 라이선스에 바인딩되지 않은 것으로 감지되었습니다. [패널 설정 - 라이선스] 메뉴에서 바인딩 후 다시 시도하세요!', + memTotal: '총 메모리', + nodeManagement: '다중 머신 관리', + nodeItem: '노드 관리', + panelItem: '패널 관리', + addPanel: '패널 추가', + addPanelHelper: 'パネルの追加に成功後、[概要 - パネル]で対象パネルに迅速にアクセスできます。', + panel: '1Panel パネル', + others: 'その他のパネル', + addNode: '노드 추가', + connInfo: '연결 정보', + nodeInfo: '노드 정보', + withProxy: '프록시 액세스 활성화', + withoutProxy: '프록시 액세스 비활성화', + withProxyHelper: + '패널 설정에서 유지 관리되는 시스템 프록시 {0}을(를) 사용하여 자식 노드에 액세스합니다. 계속하시겠습니까?', + withoutProxyHelper: + '패널 설정에서 유지 관리되는 시스템 프록시를 사용하여 자식 노드에 액세스하는 것을 중지합니다. 계속하시겠습니까?', + syncInfo: '데이터 동기화,', + syncHelper: '마스터 노드 데이터가 변경되면, 이 자식 노드에 실시간으로 동기화됩니다,', + syncBackupAccount: '백업 계정 설정', + syncWithMaster: + '프로 버전으로 업그레이드 후 모든 데이터가 기본적으로 동기화됩니다. 노드 관리에서 동기화 정책을 수동으로 조정할 수 있습니다.', + syncProxy: '시스템 프록시 설정', + syncProxyHelper: '시스템 프록시 설정 동기화에는 Docker 재시작이 필요합니다', + syncProxyHelper1: 'Docker 재시작은 현재 실행 중인 컨테이너 서비스에 영향을 줄 수 있습니다.', + syncProxyHelper2: '컨테이너 - 설정 페이지에서 수동으로 재시작할 수 있습니다.', + syncProxyHelper3: + '시스템 프록시 설정 동기화에는 Docker 재시작이 필요하며, 현재 실행 중인 컨테이너 서비스에 영향을 줄 수 있습니다', + syncProxyHelper4: + '시스템 프록시 설정 동기화에는 Docker 재시작이 필요합니다. 나중에 컨테이너 - 설정 페이지에서 수동으로 재시작할 수 있습니다.', + syncCustomApp: '사용자 정의 앱 저장소 동기화', + syncAlertSetting: '시스템 경고 설정', + syncNodeInfo: '노드 기본 데이터,', + nodeSyncHelper: '노드 정보 동기화는 다음 정보를 동기화합니다:', + nodeSyncHelper1: '1. 공용 백업 계정 정보', + nodeSyncHelper2: '2. 주 노드와 하위 노드 간의 연결 정보', + + nodeCheck: '가용성 확인', + checkSSH: '노드 SSH 연결 확인', + checkUserPermission: '노드 사용자 권한 확인', + isNotRoot: + '이 노드에서 비밀번호 없이 sudo를 사용할 수 없으며 현재 사용자가 root가 아닌 것으로 감지되었습니다', + checkLicense: '노드 라이선스 상태 확인', + checkService: '노드의 기존 서비스 정보 확인', + checkPort: '노드 포트 접근 가능 여부 확인', + panelExist: + '이 노드에서 1Panel V1 서비스가 실행 중인 것으로 감지되었습니다. 추가 전에 마이그레이션 스크립트로 V2로 업그레이드하십시오.', + coreExist: + '현재 노드가 마스터 노드로 활성화되어 있어 슬레이브 노드로 직접 추가할 수 없습니다. 추가하기 전에 먼저 슬레이브 노드로 다운그레이드하십시오. 자세한 내용은 문서를 참조하십시오.', + agentExist: + '이 노드에 1panel-agent가 이미 설치되어 있는 것으로 감지되었습니다. 계속하면 기존 데이터를 유지하고 1panel-agent 서비스만 교체됩니다.', + agentNotExist: + '이 노드에 1panel-agent가 설치되지 않아 노드 정보를 직접 편집할 수 없습니다. 삭제 후 다시 추가해 주세요.', + oldDataExist: + '이 노드에서 1Panel V2 기록 데이터가 감지되었습니다. 다음 정보를 사용하여 현재 설정을 덮어씁니다:', + errLicense: '이 노드에 바인딩된 라이선스를 사용할 수 없습니다. 확인 후 다시 시도하십시오!', + errNodePort: + '노드 포트 [ {0} ]에 접근할 수 없는 것으로 감지되었습니다. 방화벽 또는 보안 그룹에서 해당 포트가 허용되었는지 확인하십시오.', + + reinstallHelper: '노드 {0}를 재설치합니다. 계속하시겠습니까?', + unhealthyCheck: '비정상 체크', + fixOperation: '수정 작업', + checkName: '체크 항목', + checkSSHConn: 'SSH 연결 가능성 확인', + fixSSHConn: '노드를 수동으로 편집하여 연결 정보를 확인합니다', + checkConnInfo: '에이전트 연결 정보 확인', + checkStatus: '노드 서비스 가용성 확인', + fixStatus: '"systemctl status 1panel-agent.service"를 실행하여 서비스가 실행 중인지 확인합니다.', + checkAPI: '노드 API 가용성 확인', + fixAPI: '노드 로그를 확인하고 방화벽 포트가 정상적으로 열려 있는지 확인합니다.', + forceDelete: '강제 삭제', + operateHelper: '다음 노드에 대해 {0} 작업을 수행합니다. 계속하시겠습니까?', + operatePanelHelper: '다음 패널에 대해 {0} 작업을 수행합니다. 계속하시겠습니까?', + forceDeleteHelper: '강제 삭제는 노드 삭제 오류를 무시하고 데이터베이스 메타데이터를 삭제합니다', + uninstall: '노드 데이터 삭제', + uninstallHelper: '이 작업은 노드의 모든 1Panel 관련 데이터를 삭제합니다. 신중하게 선택하세요!', + baseDir: '설치 디렉토리', + baseDirHelper: '설치 디렉토리가 비어 있으면 기본적으로 /opt 디렉토리에 설치됩니다', + nodePort: '노드 포트', + offline: '오프라인 모드', + freeCount: '무료 할당량 [{0}]', + offlineHelper: '노드가 오프라인 환경일 때 사용', + + appUpgrade: '앱 업그레이드', + appUpgradeHelper: '업그레이드가 필요한 앱이 {0}개 있습니다', + }, + customApp: { + name: '사용자 정의 앱 저장소', + appStoreType: '앱 스토어 패키지 소스', + appStoreUrl: '저장소 URL', + local: '로컬 경로', + remote: '원격 링크', + imagePrefix: '이미지 접두사', + imagePrefixHelper: + '기능: 이미지 접두사를 사용자 정의하고 compose 파일의 이미지 필드를 수정합니다. 예를 들어 이미지 접두사를 1panel/custom으로 설정하면 MaxKB의 이미지 필드는 1panel/custom/maxkb:v1.10.0으로 변경됩니다', + closeHelper: '사용자 정의 앱 저장소 사용 취소', + appStoreUrlHelper: '.tar.gz 형식만 지원합니다', + postNode: '서브 노드로 동기화', + postNodeHelper: + '사용자 정의 스토어 패키지를 하위 노드의 설치 디렉토리에 있는 tmp/customApp/apps.tar.gz로 동기화합니다', + nodes: '노드 선택', + selectNode: '노드 선택', + selectNodeError: '노드를 선택하세요', + licenseHelper: '프로 버전은 사용자 정의 애플리케이션 저장소 기능을 지원합니다', + databaseHelper: '애플리케이션 관련 데이터베이스, 대상 노드 데이터베이스를 선택하세요', + nodeHelper: '현재 노드는 선택할 수 없습니다', + migrateHelper: + '현재 단일 애플리케이션과 MySQL, MariaDB, PostgreSQL 데이터베이스만 연결된 애플리케이션의 마이그레이션만 지원합니다', + opensslHelper: + '암호화된 백업을 사용하는 경우 두 노드 간의 OpenSSL 버전이 일치해야 합니다. 그렇지 않으면 마이그레이션이 실패할 수 있습니다.', + installApp: '일괄 설치', + installAppHelper: '선택한 노드에 앱 일괄 설치', + }, + alert: { + isAlert: '알림', + alertCount: '알림 횟수', + clamHelper: '감염된 파일을 스캔할 때 알림 트리거', + cronJobHelper: '작업 실행 실패 시 알림 트리거', + licenseHelper: '전문 버전에서는 SMS 알림을 지원합니다.', + alertCountHelper: '최대 일일 알림 빈도', + alert: 'SMS 알림', + logs: '알림 로그', + list: '알림 목록', + addTask: '알림 생성', + editTask: '알림 수정', + alertMethod: '알림 방법', + alertMsg: '알림 메시지', + alertRule: '알림 규칙', + titleSearchHelper: '알림 제목을 입력하여 검색하세요', + taskType: '유형', + ssl: 'SSL 인증서 만료', + siteEndTime: '웹사이트 만료', + panelPwdEndTime: '패널 비밀번호 만료', + panelUpdate: '새 패널 버전 사용 가능', + cpu: '서버 CPU 알림', + memory: '서버 메모리 알림', + load: '서버 부하 알림', + disk: '서버 디스크 알림', + website: '웹사이트', + certificate: 'SSL 인증서', + remainingDays: '남은 일수', + sendCount: '발송 횟수', + sms: 'SMS', + wechat: '위챗', + dingTalk: '딩톡', + feiShu: '페이슈', + mail: '이메일', + weCom: 'WeCom', + sendCountRulesHelper: '만료 전 발송된 총 알림 수 (하루 1회)', + panelUpdateRulesHelper: '새 패널 버전에 대한 총 알림 수 (하루 1회)', + oneDaySendCountRulesHelper: '하루 최대 발송 가능한 알림 수', + siteEndTimeRulesHelper: '만료되지 않는 웹사이트는 알림이 발생하지 않습니다', + autoRenewRulesHelper: '자동 갱신이 설정된 인증서의 남은 일수가 31일 미만이면 알림이 발생하지 않습니다', + panelPwdEndTimeRulesHelper: '만료가 설정되지 않은 경우 패널 비밀번호 만료 알림이 비활성화됩니다', + sslRulesHelper: '모든 SSL 인증서', + diskInfo: '디스크', + monitoringType: '모니터링 유형', + autoRenew: '자동 갱신', + useDisk: '디스크 사용량', + usePercentage: '사용 비율', + changeStatus: '상태 변경', + disableMsg: '알림 작업을 중지하면 알림 메시지가 전송되지 않습니다. 계속하시겠습니까?', + enableMsg: '알림 작업을 활성화하면 알림 메시지가 전송됩니다. 계속하시겠습니까?', + useExceed: '사용량 초과', + useExceedRulesHelper: '사용량이 설정 값을 초과하면 알림을 트리거합니다', + cpuUseExceedAvg: '평균 CPU 사용량이 지정된 값을 초과함', + memoryUseExceedAvg: '평균 메모리 사용량이 지정된 값을 초과함', + loadUseExceedAvg: '평균 부하 사용량이 지정된 값을 초과함', + cpuUseExceedAvgHelper: '지정된 시간 내의 평균 CPU 사용량이 지정된 값을 초과함', + memoryUseExceedAvgHelper: '지정된 시간 내의 평균 메모리 사용량이 지정된 값을 초과함', + loadUseExceedAvgHelper: '지정된 시간 내의 평균 부하 사용량이 지정된 값을 초과함', + resourceAlertRulesHelper: '참고: 30분 내에 연속적인 알림은 한 번만 발송됩니다', + specifiedTime: '지정된 시간', + deleteTitle: '알림 삭제', + deleteMsg: '알림 작업을 삭제하시겠습니까?', + allSslTitle: '모든 웹사이트 SSL 인증서 만료 알림', + sslTitle: '웹사이트 {0}의 SSL 인증서 만료 알림', + allSiteEndTimeTitle: '모든 웹사이트 만료 알림', + siteEndTimeTitle: '웹사이트 {0} 만료 알림', + panelPwdEndTimeTitle: '패널 비밀번호 만료 알림', + panelUpdateTitle: '새 패널 버전 알림', + cpuTitle: '고 CPU 사용량 알림', + memoryTitle: '고 메모리 사용량 알림', + loadTitle: '고 부하 알림', + diskTitle: '디스크 사용량 초과 알림 {0}', + allDiskTitle: '고 디스크 사용량 알림', + timeRule: '{0}일 이하 남은 시간 (처리되지 않으면 다음 날 다시 전송)', + panelUpdateRule: '새 패널 버전이 감지되면 한 번 알림을 전송 (처리되지 않으면 다음 날 다시 전송)', + avgRule: '{0}분 내 평균 {1} 사용량이 {2}% 초과 시 알림을 트리거하며 하루에 {3}번 발송', + diskRule: '디스크 사용량이 마운트 디렉토리 {0}에서 {1}{2}를 초과하면 알림을 트리거하고 하루에 {3}번 발송', + allDiskRule: '디스크 사용량이 {0}{1}을 초과하면 알림을 트리거하고 하루에 {2}번 발송', + cpuName: ' CPU ', + memoryName: '메모리', + loadName: '부하', + diskName: '디스크', + syncAlertInfo: '수동 푸시', + syncAlertInfoMsg: '알림 작업을 수동으로 푸시하시겠습니까?', + pushError: '푸시 실패', + pushSuccess: '푸시 성공', + syncError: '동기화 실패', + success: '알림 성공', + pushing: '푸시 중...', + error: '알림 실패', + cleanLog: '로그 정리', + cleanAlertLogs: '알림 로그 정리', + daily: '일일 알림 수: {0}', + cumulative: '누적 알림 수: {0}', + clams: '바이러스 검사', + taskName: '작업 이름', + cronJobType: '작업 유형', + clamPath: '검사 디렉토리', + cronjob: '예약 작업 실행 {0} 중 오류가 발생했습니다', + app: '백업 애플리케이션', + web: '백업 웹사이트', + database: '백업 데이터베이스', + directory: '백업 디렉토리', + log: '백업 로그', + snapshot: '시스템 스냅샷', + clamsRulesHelper: '알림이 필요한 바이러스 검사 작업', + cronJobRulesHelper: '이 유형의 예약된 작업은 구성해야 합니다', + clamsTitle: '바이러스 검사 작업 「 {0} 」 감염된 파일 알림', + cronJobAppTitle: '크론 작업 - 백업 애플리케이션 「 {0} 」 작업 실패 알림', + cronJobWebsiteTitle: '크론 작업 - 백업 웹사이트「 {0} 」작업 실패 알림', + cronJobDatabaseTitle: '크론 작업 - 백업 데이터베이스「 {0} 」작업 실패 알림', + cronJobDirectoryTitle: '크론 작업 - 백업 디렉토리「 {0} 」작업 실패 알림', + cronJobLogTitle: '크론 작업 - 백업 로그「 {0} 」작업 실패 알림', + cronJobSnapshotTitle: '크론 작업 - 백업 스냅샷「 {0} 」작업 실패 알림', + cronJobShellTitle: '크론 작업 - 셸 스크립트 「 {0} 」작업 실패 알림', + cronJobCurlTitle: '크론 작업 - URL 접근「 {0} 」작업 실패 알림', + cronJobCutWebsiteLogTitle: '크론 작업 - 웹사이트 로그 자르기「 {0} 」작업 실패 알림', + cronJobCleanTitle: '크론 작업 - 캐시 정리「 {0} 」작업 실패 알림', + cronJobNtpTitle: '크론 작업 - 서버 시간 동기화「 {0} 」작업 실패 알림', + clamsRule: '바이러스 검사로 감염된 파일 알림, 하루에 {0}번 발송', + cronJobAppRule: '백업 애플리케이션 작업 실패 알림, 하루에 {0}번 발송', + cronJobWebsiteRule: '백업 웹사이트 작업 실패 알림, 하루에 {0}번 발송', + cronJobDatabaseRule: '백업 데이터베이스 작업 실패 알림, 하루에 {0}번 발송', + cronJobDirectoryRule: '백업 디렉토리 작업 실패 알림, 하루에 {0}번 발송', + cronJobLogRule: '백업 로그 작업 실패 알림, 하루에 {0}번 발송', + cronJobSnapshotRule: '백업 스냅샷 작업 실패 알림, 하루에 {0}번 발송', + cronJobShellRule: '셸 스크립트 작업 실패 알림, 하루에 {0}번 발송', + cronJobCurlRule: 'URL 접근 작업 실패 알림, 하루에 {0}번 발송', + cronJobCutWebsiteLogRule: '웹사이트 로그 자르기 작업 실패 알림, 하루에 {0}번 발송', + cronJobCleanRule: '캐시 정리 작업 실패 알림, 하루에 {0}번 발송', + cronJobNtpRule: '서버 시간 동기화 작업 실패 알림, 하루에 {0}번 발송', + alertSmsHelper: 'SMS 한도: 총 {0}개의 메시지, {1}개 사용됨', + goBuy: '추가 구매', + phone: '전화', + phoneHelper: '알림 메시지를 위한 실제 전화번호를 제공하세요', + dailyAlertNum: '일일 알림 한도', + dailyAlertNumHelper: '하루에 보낼 수 있는 최대 알림 수 (최대 100개)', + timeRange: '시간 범위', + sendTimeRange: '발송 시간 범위', + sendTimeRangeHelper: '{0} 시간 범위를 푸시할 수 있습니다', + to: '부터', + startTime: '시작 시간', + endTime: '종료 시간', + defaultPhone: '기본적으로 라이선스에 묶인 계정의 전화번호 사용', + noticeAlert: '공지 알림', + resourceAlert: '리소스 알림', + agentOfflineAlertHelper: + '노드에서 오프라인 알림이 활성화되면, 메인 노드가 30분마다 스캔하여 알림 작업을 수행합니다.', + offline: '오프라인 알림', + offlineHelper: '오프라인 알림으로 설정하면, 메인 노드가 30분마다 스캔하여 알림 작업을 수행합니다.', + offlineOff: '오프라인 알림 활성화', + offlineOffHelper: '오프라인 알림을 활성화하면, 메인 노드가 30분마다 스캔하여 알림 작업을 수행합니다.', + offlineClose: '오프라인 알림 비활성화', + offlineCloseHelper: + '오프라인 알림을 비활성화하면, 하위 노드가 알림을 직접 처리해야 합니다. 알림 실패를 방지하려면 네트워크 연결이 원활한지 확인하세요.', + alertNotice: '알림 통지', + methodConfig: '알림 방법 설정', + commonConfig: '전역 설정', + smsConfig: 'SMS', + smsConfigHelper: 'SMS 알림을 받을 전화번호를 설정합니다', + emailConfig: '이메일', + emailConfigHelper: 'SMTP 이메일 발송 서비스를 설정합니다', + deleteConfigTitle: '알림 설정 삭제', + deleteConfigMsg: '알림 설정을 삭제하시겠습니까?', + test: '테스트', + alertTestOk: '테스트 알림 성공', + alertTestFailed: '테스트 알림 실패', + displayName: '표시 이름', + sender: '발신 주소', + password: '비밀번호', + host: 'SMTP 서버', + port: '포트 번호', + encryption: '암호화 방식', + recipient: '수신자', + licenseTime: '라이선스 만료 알림', + licenseTimeTitle: '라이선스 만료 알림', + displayNameHelper: '이메일 발신자 표시 이름', + senderHelper: '이메일 발송에 사용되는 주소', + passwordHelper: '메일 서비스의 인증 코드', + hostHelper: 'SMTP 서버 주소, 예: smtp.qq.com', + portHelper: 'SSL 은 일반적으로 465, TLS 는 587', + sslHelper: 'SMTP 포트가 465 이면 일반적으로 SSL 이 필요합니다', + tlsHelper: 'SMTP 포트가 587 이면 일반적으로 TLS 가 필요합니다', + triggerCondition: '트리거 조건', + loginFail: ' 이내 로그인 실패', + nodeException: '노드 이상 알림', + licenseException: '라이선스 이상 알림', + panelLogin: '패널 로그인 이상 알림', + sshLogin: 'SSH 로그인 이상 알림', + panelIpLogin: '패널 로그인 IP 이상 알림', + sshIpLogin: 'SSH 로그인 IP 이상 알림', + ipWhiteListHelper: + '화이트리스트에 있는 IP는 규칙의 제한을 받지 않으며, 로그인에 성공해도 알림이 발생하지 않습니다', + nodeExceptionRule: '노드 이상 알림은 하루 {0}회 전송', + licenseExceptionRule: '라이선스 이상 알림은 하루 {0}회 전송', + panelLoginRule: '패널 로그인 알림은 하루 {0}회 전송', + sshLoginRule: 'SSH 로그인 알림은 하루 {0}회 전송', + userNameHelper: '사용자 이름이 비어 있으면 기본적으로 발신자 주소가 사용됩니다', + }, + theme: { + lingXiaGold: '링샤 골드', + classicBlue: '클래식 블루', + freshGreen: '프레시 그린', + customColor: '사용자 정의 색상', + setDefault: '기본값', + setDefaultHelper: '테마 색상 스킴이 초기 상태로 복원됩니다. 계속하시겠습니까?', + setHelper: '현재 선택한 테마 색상 스킴이 저장됩니다. 계속하시겠습니까?', + }, + exchange: { + exchange: '파일 교환', + exchangeConfirm: '{0} 노드의 파일/폴더 {1}을(를) {2} 노드의 {3} 디렉토리로 전송하시겠습니까?', + }, + cluster: { + cluster: '애플리케이션 고가용성', + name: '클러스터 이름', + addCluster: '클러스터 추가', + installNode: '노드 설치', + master: '마스터 노드', + slave: '슬레이브 노드', + replicaStatus: '마스터-슬레이브 상태', + unhealthyDeleteError: '설치 노드 상태가 비정상입니다. 노드 목록을 확인한 후 다시 시도하세요!', + replicaStatusError: '상태 획득이 비정상입니다. 마스터 노드를 확인하세요.', + masterHostError: '마스터 노드의 IP는 127.0.0.1이 될 수 없습니다', + }, + }, +}; + +export default { + ...fit2cloudKoLocale, + ...message, +}; diff --git a/frontend/src/lang/modules/ms.ts b/frontend/src/lang/modules/ms.ts new file mode 100644 index 0000000..e2866a0 --- /dev/null +++ b/frontend/src/lang/modules/ms.ts @@ -0,0 +1,4136 @@ +import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/ms'; + +const message = { + commons: { + true: 'true', + false: 'false', + colon: ': ', + example: 'Sebagai contoh, ', + fit2cloud: 'FIT2CLOUD', + lingxia: 'Lingxia', + button: { + run: 'Jalankan', + create: 'Cipta', + add: 'Tambah', + save: 'Simpan', + set: 'Sunting tetapan', + sync: 'Selaras', + delete: 'Padam', + edit: 'Sunting', + enable: 'Aktif', + disable: 'Nyahaktif', + confirm: 'Sah', + cancel: 'Batal', + reset: 'Tetapkan semula', + restart: 'Mulakan semula', + conn: 'Sambung', + disConn: 'Putus sambungan', + clean: 'Bersih', + login: 'Log masuk', + close: 'Tutup', + off: 'Tutup', + stop: 'Henti', + start: 'Mula', + view: 'Lihat', + watch: 'Pantau', + handle: 'Picu', + clone: 'Klon', + expand: 'Kembang', + collapse: 'Runtuh', + log: 'Log', + back: 'Kembali', + backup: 'Sandaran', + recover: 'Pulih', + retry: 'Cuba semula', + upload: 'Muat naik', + download: 'Muat turun', + init: 'Mulakan', + verify: 'Sahkan', + saveAndEnable: 'Simpan dan aktifkan', + import: 'Import', + export: 'Eksport', + power: 'Pemberian Kuasa', + search: 'Cari', + refresh: 'Segarkan', + get: 'Dapatkan', + upgrade: 'Tingkatkan', + update: 'kemas kini', + updateNow: 'Kemas Kini Sekarang', + ignore: 'Abaikan peningkatan', + install: 'pasang', + copy: 'Salin', + random: 'Rawak', + uninstall: 'Nyahpasang', + fullscreen: 'Masuk ke skrin penuh', + quitFullscreen: 'Keluar dari skrin penuh', + showAll: 'Tunjukkan Semua', + hideSome: 'Sembunyikan Sebahagian', + agree: 'Setuju', + notAgree: 'Tidak Setuju', + preview: 'Pratonton', + open: 'Buka', + notSave: 'Tidak Disimpan', + createNewFolder: 'Cipta folder baru', + createNewFile: 'Cipta fail baru', + helpDoc: 'Dokumen Bantuan', + unbind: 'Nyahkaitkan', + cover: 'Tindih', + skip: 'Langkau', + fix: 'Betulkan', + down: 'Hentikan', + up: 'Mulakan', + sure: 'Sahkan', + show: 'Tunjukkan', + hide: 'Sembunyikan', + visit: 'Lawati', + migrate: 'Migrasi', + }, + operate: { + start: 'Mula', + stop: 'Hentikan', + restart: 'Mulai Semula', + reload: 'Muat Semula', + rebuild: 'Bangun Semula', + sync: 'Segerakkan', + up: 'Naik', + down: 'Turun', + delete: 'Padam', + }, + search: { + timeStart: 'Masa mula', + timeEnd: 'Masa tamat', + timeRange: 'Hingga', + dateStart: 'Tarikh mula', + dateEnd: 'Tarikh tamat', + date: 'Tarikh', + }, + table: { + all: 'Semua', + total: 'Jumlah {0}', + name: 'Nama', + type: 'Jenis', + status: 'Status', + records: 'Rekod', + group: 'Kumpulan', + createdAt: 'Waktu dicipta', + publishedAt: 'Waktu diterbitkan', + date: 'Tarikh', + updatedAt: 'Waktu dikemas kini', + operate: 'Operasi', + message: 'Mesej', + description: 'Penerangan', + interval: 'Selang masa', + user: 'Pemilik', + title: 'Tajuk', + port: 'Port', + forward: 'Hadapan', + protocol: 'Protokol', + tableSetting: 'Tetapan jadual', + refreshRate: 'Kadar penyegaran', + selectColumn: 'Pilih lajur', + local: 'Tempatan', + serialNumber: 'Nombor siri', + manageGroup: 'Urus Kumpulan', + backToList: 'Kembali ke Senarai', + keepEdit: 'Teruskan Mengedit', + }, + loadingText: { + Upgrading: 'Peningkatan sistem, sila tunggu...', + Restarting: 'Memulakan semula sistem, sila tunggu...', + Recovering: 'Memulihkan daripada snapshot, sila tunggu...', + Rollbacking: 'Mengembalikan daripada snapshot, sila tunggu...', + }, + msg: { + noneData: 'Tiada data tersedia', + delete: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?', + clean: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?', + closeDrawerHelper: 'Sistem mungkin tidak menyimpan perubahan yang anda buat. Adakah anda ingin teruskan?', + deleteSuccess: 'Berjaya dipadam', + loginSuccess: 'Berjaya log masuk', + operationSuccess: 'Berjaya dilakukan', + copySuccess: 'Berjaya disalin', + notSupportOperation: 'Operasi ini tidak disokong', + requestTimeout: 'Permintaan telah tamat masa, sila cuba lagi nanti', + infoTitle: 'Petunjuk', + notRecords: 'Tiada rekod pelaksanaan dijana untuk tugas semasa', + sureLogOut: 'Adakah anda pasti mahu log keluar?', + createSuccess: 'Berjaya dicipta', + updateSuccess: 'Berjaya dikemas kini', + uploadSuccess: 'Berjaya dimuat naik', + operateConfirm: 'Jika anda pasti dengan operasi ini, sila masukkan secara manual: ', + inputOrSelect: 'Sila pilih atau masukkan', + copyFailed: 'Gagal disalin', + operatorHelper: + 'Operasi "{1}" akan dilakukan pada "{0}" dan tidak boleh diundur. Adakah anda mahu meneruskan?', + notFound: 'Maaf, halaman yang anda minta tidak wujud.', + unSupportType: 'Jenis fail semasa tidak disokong.', + unSupportSize: 'Fail yang dimuat naik melebihi {0}M, sila sahkan!', + fileExist: 'Fail sudah wujud dalam folder semasa. Memuat naik semula tidak disokong.', + fileNameErr: + 'Anda hanya boleh memuat naik fail dengan nama yang mengandungi 1 hingga 256 aksara, termasuk Bahasa Inggeris, Cina, nombor, atau titik (.-_)', + confirmNoNull: 'Pastikan nilai {0} tidak kosong.', + errPort: 'Maklumat port tidak betul, sila sahkan!', + remove: 'Buang', + backupHelper: 'Operasi semasa akan membuat sandaran {0}. Adakah anda mahu meneruskan?', + recoverHelper: + 'Memulihkan daripada fail {0}. Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?', + refreshSuccess: 'Berjaya disegarkan', + rootInfoErr: 'Ia sudah menjadi direktori akar', + resetSuccess: 'Berjaya ditetapkan semula', + creatingInfo: 'Sedang mencipta, operasi ini tidak diperlukan', + offlineTips: 'Versi luar talian tidak menyokong operasi ini', + errImportFormat: 'Data import atau format adalah tidak normal, sila periksa dan cuba lagi!', + importHelper: + 'Apabila mengimport data yang bercanggah atau pendua, kandungan yang diimport akan digunakan sebagai piawai untuk mengemas kini data pangkalan data asal.', + errImport: 'Kandungan fail adalah tidak normal:', + }, + login: { + username: 'Nama Pengguna', + password: 'Kata Laluan', + passkey: 'Log masuk Passkey', + welcome: 'Selamat datang kembali, sila masukkan nama pengguna dan kata laluan anda untuk log masuk!', + errorAuthInfo: 'Nama pengguna atau kata laluan yang anda masukkan tidak betul, sila cuba lagi!', + errorMfaInfo: 'Maklumat pengesahan tidak betul, sila cuba lagi!', + captchaHelper: 'Captcha', + errorCaptcha: 'Ralat kod captcha!', + notSafe: 'Akses Ditolak', + safeEntrance1: 'Log masuk selamat telah diaktifkan dalam persekitaran semasa', + safeEntrance2: 'Masukkan arahan berikut pada terminal SSH untuk melihat pintu masuk panel: 1pctl user-info', + errIP1: 'Akses alamat IP yang dibenarkan diaktifkan dalam persekitaran semasa', + errDomain1: 'Pengikatan nama domain akses diaktifkan dalam persekitaran semasa', + errHelper: 'Untuk menetapkan semula maklumat pengikatan, jalankan arahan berikut pada terminal SSH: ', + codeInput: 'Sila masukkan kod pengesahan 6 digit dari pengesah MFA', + mfaTitle: 'Pengesahan MFA', + mfaCode: 'Kod pengesahan MFA', + title: 'Panel Pengurusan Pelayan Linux', + licenseHelper: '', + errorAgree: 'Klik untuk bersetuju dengan Lesen Perisian Komuniti', + logout: 'Log keluar', + agreeTitle: 'Agreement', + agreeContent: + 'Untuk melindungi hak dan kepentingan sah anda dengan lebih baik, sila baca dan setuju dengan perjanjian berikut « Perjanjian Lesen Komuniti »', + passkeyFailed: 'Log masuk Passkey gagal, sila cuba lagi', + passkeyNotSupported: 'Pelayar atau persekitaran semasa tidak menyokong passkey', + passkeyToPassword: 'Ada masalah menggunakan passkey? Gunakan kata laluan sahaja', + }, + rule: { + username: 'Masukkan nama pengguna', + password: 'Masukkan kata laluan', + rePassword: 'Pengesahan kata laluan tidak sepadan dengan kata laluan.', + requiredInput: 'Ruangan ini wajib diisi.', + requiredSelect: 'Pilih satu item dalam senarai', + illegalChar: 'Suntikan aksara & ; $ \' ` ( ) " > < | tidak disokong buat masa ini', + illegalInput: 'Ruangan ini tidak boleh mengandungi aksara tidak sah.', + commonName: + 'Ruangan ini mesti bermula dengan aksara bukan khas dan mesti terdiri daripada aksara rumi, Cina, nombor, ".", "-", dan "_" dengan panjang 1-128 aksara.', + userName: + 'Menyokong bermula dengan bukan aksara khas, Bahasa Inggeris, Bahasa Cina, nombor, dan _, panjang 3-30', + simpleName: `Ruangan ini tidak boleh bermula dengan aksara garis bawah ("_") dan mesti terdiri daripada aksara rumi, nombor, dan "_" dengan panjang 3-30 aksara.`, + simplePassword: `Ruangan ini tidak boleh bermula dengan aksara garis bawah ("_") dan mesti terdiri daripada aksara rumi, nombor, dan "_" dengan panjang 1-30 aksara.`, + dbName: `Ruangan ini tidak boleh bermula dengan aksara garis bawah ("_") dan mesti terdiri daripada aksara rumi, nombor, dan "_" dengan panjang 1-64 aksara.`, + imageName: 'Menyokong bermula dengan bukan aksara khas, Bahasa Inggeris, nombor, :@/.-_, panjang 1-256', + composeName: + 'Menyokong aksara bukan khas pada permulaan, huruf kecil, nombor, "-", dan "_", dengan panjang 1-256 aksara.', + volumeName: + 'Ruangan ini mesti terdiri daripada aksara Bahasa Inggeris, nombor, ".", "-", dan "_" dengan panjang 2-30 aksara.', + supervisorName: + 'Ruangan ini mesti bermula dengan aksara bukan khas dan mesti terdiri daripada aksara rumi, nombor, "-", dan "_" dengan panjang 1-128 aksara.', + complexityPassword: + 'Ruangan ini mesti terdiri daripada aksara rumi, nombor dengan panjang 8-30 aksara dan mengandungi sekurang-kurangnya dua aksara khas.', + commonPassword: 'Panjang ruangan ini mesti melebihi 6 aksara.', + linuxName: `Panjang ruangan ini mesti antara 1 hingga 128 aksara. Ruangan ini tidak boleh mengandungi aksara khas berikut: "{0}".`, + email: 'Ruangan ini mesti mengandungi alamat emel yang sah.', + number: 'Ruangan ini mesti mengandungi nombor.', + integer: 'Ruangan ini mesti mengandungi integer positif.', + ip: 'Ruangan ini mesti mengandungi alamat IP yang sah.', + host: 'Ruangan ini mesti mengandungi alamat IP atau nama domain yang sah.', + hostHelper: 'Menyokong input alamat IP atau nama domain', + port: 'Ruangan ini mesti mengandungi nombor port yang sah.', + selectHelper: 'Sila pilih fail {0} yang betul', + domain: 'Ruangan ini mesti dalam format: example.com atau example.com:8080.', + databaseName: + 'Ruangan ini mesti terdiri daripada aksara Bahasa Inggeris, nombor, dan "_" dengan panjang 1-30 aksara.', + ipErr: 'Ruangan ini mesti mengandungi alamat IP yang sah.', + numberRange: 'Ruangan ini mesti mengandungi nombor antara {0} dan {1}.', + paramName: + 'Ruangan ini mesti terdiri daripada aksara rumi, nombor, ".", "-", dan "_" dengan panjang 2-30 aksara.', + paramComplexity: + 'Ruangan ini tidak boleh bermula atau berakhir dengan aksara khas dan mesti terdiri daripada aksara rumi, nombor, "{0}" dengan panjang 6-128 aksara.', + paramUrlAndPort: 'Ruangan ini mesti dalam format "http(s)://(nama domain/IP):(port)".', + nginxDoc: 'Ruangan ini mesti terdiri daripada aksara rumi, nombor, dan ".".', + appName: + 'Ruangan ini tidak boleh bermula atau berakhir dengan "-" dan "_" dan mesti terdiri daripada aksara rumi, nombor, "-", dan "_" dengan panjang 2-30 aksara.', + containerName: + 'Menyokong huruf, nombor, -, _ dan .; tidak boleh bermula dengan -, _ atau .; panjang: 2-128 aksara.', + mirror: 'Alamat pecutan cermin mesti bermula dengan http(s)://, menyokong huruf (huruf besar dan kecil), nombor, ., / dan -, serta tidak boleh mengandungi baris kosong.', + disableFunction: 'Hanya menyokong huruf, garis bawah, dan ,', + leechExts: 'Hanya menyokong huruf, nombor, dan ,', + paramSimple: 'Menyokong huruf kecil dan nombor, panjang 1-128 aksara.', + filePermission: 'Ralat Kebenaran Fail', + formatErr: 'Ralat format, sila semak dan cuba lagi', + phpExtension: 'Hanya menyokong huruf kecil, _, dan nombor', + paramHttp: 'Mesti bermula dengan http:// atau https://', + phone: 'Format nombor telefon tidak betul.', + authBasicPassword: 'Menyokong huruf, nombor, dan aksara khas biasa, panjang 1-72', + length128Err: 'Panjang tidak boleh melebihi 128 aksara', + maxLength: 'Panjang tidak boleh melebihi {0} aksara', + alias: 'Menyokong Bahasa Inggeris, nombor, - dan _, panjang 1-128, dan tidak boleh bermula atau berakhir dengan -_.', + }, + res: { + paramError: 'Permintaan gagal, sila cuba lagi nanti!', + forbidden: 'Pengguna semasa tidak mempunyai kebenaran', + serverError: 'Kecacatan perkhidmatan', + notFound: 'Sumber tidak wujud', + commonError: 'Permintaan gagal', + }, + service: { + serviceNotStarted: `Perkhidmatan {0} belum dimulakan.`, + }, + status: { + running: 'Sedang Berjalan', + done: 'Selesai', + scanFailed: 'Tidak Lengkap', + success: 'Berjaya', + waiting: 'Menunggu', + waitForUpgrade: 'Tunggu Naik Taraf', + waiting1: 'Menunggu', + failed: 'Gagal', + stopped: 'Dihentikan', + error: 'Ralat', + created: 'Dicipta', + restarting: 'Memulakan Semula', + uploading: 'Sedang Memuat Naik', + unhealthy: 'Tidak Sihat', + removing: 'Sedang Membuang', + paused: 'Dijeda', + exited: 'Keluar', + dead: 'Mati', + installing: 'Sedang Memasang', + enabled: 'Diaktifkan', + disabled: 'Dilumpuhkan', + normal: 'Normal', + building: 'Sedang Membina', + upgrading: 'Sedang Meningkatkan', + pending: 'Menunggu Edit', + rebuilding: 'Sedang Membina Semula', + deny: 'Ditolak', + accept: 'Diterima', + used: 'Digunakan', + unUsed: 'Tidak Digunakan', + starting: 'Sedang Memulakan', + recreating: 'Sedang Mencipta Semula', + creating: 'Sedang Mencipta', + init: 'Menunggu aplikasi', + ready: 'Normal', + applying: 'Sedang Memohon', + uninstalling: 'Menyahpasang', + lost: 'Hilang', + bound: 'Terikat', + unbind: 'Tidak terikat', + exceptional: 'Luar biasa', + free: 'Bebas', + enable: 'Aktif', + disable: 'Dilumpuhkan', + deleted: 'Dihapus', + downloading: 'Memuat turun', + packing: 'Membungkus', + sending: 'Menghantar', + healthy: 'Sihat', + executing: 'Melaksanakan', + installerr: 'Pemasangan gagal', + applyerror: 'Permohonan gagal', + systemrestart: 'Dihentikan', + starterr: 'Permulaan gagal', + uperr: 'Permulaan gagal', + new: 'Baru', + conflict: 'Konflik', + duplicate: 'Pendua', + unexecuted: 'Tidak Dilaksanakan', + }, + units: { + second: 'saat | saat | saat', + minute: 'minit | minit | minit', + hour: 'jam | jam | jam', + day: 'hari | hari | hari', + week: 'minggu | minggu | minggu', + month: 'bulan | bulan | bulan', + year: 'tahun | tahun | tahun', + time: 'masa', + core: 'teras | teras | teras', + secondUnit: 's', + minuteUnit: 'min', + hourUnit: 'h', + dayUnit: 'd', + millisecond: 'Milisaat', + }, + log: { + noLog: 'Tiada log sedia ada', + }, + }, + menu: { + home: 'Overview', + apps: 'App Store', + website: 'Website | Websites', + project: 'Project | Projects', + config: 'Configuration | Configurations', + ssh: 'SSH Settings', + firewall: 'Firewall', + ssl: 'Certificate | Certificates', + database: 'Database | Databases', + aiTools: 'AI', + mcp: 'MCP', + container: 'Container | Containers', + cronjob: 'Cron Job | Cron Jobs', + system: 'System', + security: 'Security', + files: 'Files', + monitor: 'Monitoring', + terminal: 'Terminal', + settings: 'Setting | Settings', + toolbox: 'Toolbox', + logs: 'Log | Logs', + runtime: 'Runtime | Runtimes', + processManage: 'Process | Processes', + process: 'Process | Processes', + network: 'Network | Networks', + supervisor: 'Supervisor', + tamper: 'Bukti Pengubahsuaian', + app: 'Aplikasi', + msgCenter: 'Pusat Tugas', + disk: 'Disk', + }, + home: { + recommend: 'cadangan', + dir: 'direktori', + alias: 'Alias', + quickDir: 'Direktori Pantas', + minQuickJump: 'Sila tetapkan sekurang-kurangnya satu entri lompat pantas!', + maxQuickJump: 'Anda boleh menetapkan sehingga empat entri lompat pantas!', + database: 'Pangkalan Data - Semua', + restart_1panel: 'Mulakan semula panel', + restart_system: 'Mulakan semula pelayan', + operationSuccess: 'Operasi berjaya, sedang memulakan semula, sila segarkan pelayar secara manual nanti!', + entranceHelper: + 'Pintu masuk keselamatan tidak diaktifkan. Anda boleh mengaktifkannya di "Tetapan -> Keselamatan" untuk meningkatkan keselamatan sistem.', + appInstalled: 'Aplikasi yang dipasang', + systemInfo: 'Maklumat sistem', + hostname: 'Nama hos', + platformVersion: 'Sistem pengendalian', + kernelVersion: 'Kernel', + kernelArch: 'Seni bina', + network: 'Rangkaian', + io: 'Disk I/O', + ip: 'Local IP', + proxy: 'System proxy', + baseInfo: 'Base info', + totalSend: 'Jumlah dihantar', + totalRecv: 'Jumlah diterima', + rwPerSecond: 'I/O operations', + ioDelay: 'I/O latency', + uptime: 'Up since', + runningTime: 'Uptime', + mem: 'Memori Sistem', + swapMem: 'Swap partition', + + runSmoothly: 'Beban rendah', + runNormal: 'Beban sederhana', + runSlowly: 'Beban tinggi', + runJam: 'Beban berat', + + core: 'Teras Fizikal', + corePercent: 'Penggunaan Teras', + cpuFrequency: 'Frekuensi CPU', + cpuDetailedPercent: 'Pengagihan Masa CPU', + cpuUser: 'Pengguna', + cpuSystem: 'Sistem', + cpuIdle: 'Tidak Aktif', + cpuIrq: 'IRQ', + cpuSoftirq: 'Soft IRQ', + cpuSteal: 'Dicuri', + cpuTop: 'Maklumat Proses 5 Teratas Mengikut Penggunaan CPU', + memTop: 'Maklumat Proses 5 Teratas Mengikut Penggunaan Memori', + logicCore: 'Teras Logik', + loadAverage: 'Purata beban dalam 1 minit terakhir | Purata beban dalam {n} minit terakhir', + load: 'Beban', + mount: 'Titik Pemasangan', + fileSystem: 'Sistem Fail', + total: 'Jumlah', + used: 'Digunakan', + cache: 'Cache', + free: 'Bebas', + shard: 'Shard', + available: 'Tersedia', + percent: 'Penggunaan', + goInstall: 'Pergi pasang', + + networkCard: 'Kad rangkaian', + disk: 'Disk', + }, + tabs: { + more: 'Lagi', + hide: 'Sembunyi', + closeLeft: 'Tutup kiri', + closeRight: 'Tutup kanan', + closeCurrent: 'Tutup semasa', + closeOther: 'Tutup lain', + closeAll: 'Tutup Semua', + }, + header: { + logout: 'Log keluar', + }, + database: { + manage: 'Pengurusan', + deleteBackupHelper: 'Padam sandaran pangkalan data secara serentak', + delete: 'Operasi padam tidak boleh diundurkan, sila masukkan "', + deleteHelper: '" untuk memadam pangkalan data ini', + create: 'Cipta pangkalan data', + noMysql: 'Perkhidmatan pangkalan data (MySQL atau MariaDB)', + noPostgresql: 'Perkhidmatan pangkalan data PostgreSQL', + goUpgrade: 'Pergi tingkatkan', + goInstall: 'Pergi pasang', + isDelete: 'Dihapuskan', + permission: 'Kebenaran', + format: 'Set Aksara', + collation: 'Kolasi', + collationHelper: 'Jika kosong, gunakan kolasi lalai set aksara {0}', + permissionForIP: 'IP', + permissionAll: 'Kesemuanya(%)', + localhostHelper: + 'Mengkonfigurasi kebenaran pangkalan data sebagai "localhost" untuk penyebaran kontena akan menghalang akses luar ke kontena. Sila pilih dengan teliti!', + databaseConnInfo: 'Maklumat sambungan', + rootPassword: 'Kata laluan root', + serviceName: 'Nama Perkhidmatan', + serviceNameHelper: 'Akses antara kontena dalam rangkaian yang sama.', + backupList: 'Sandaran', + loadBackup: 'Import', + localUpload: 'Muat Naik Tempatan', + hostSelect: 'Pemilihan Pelayan', + selectHelper: 'Adakah anda pasti ingin mengimport fail sandaran {0}?', + remoteAccess: 'Akses jauh', + remoteHelper: 'Berbilang IP dipisahkan dengan koma, contoh: 172.16.10.111, 172.16.10.112', + remoteConnHelper: + 'Sambungan jauh ke MySQL sebagai pengguna root mungkin mempunyai risiko keselamatan. Oleh itu, lakukan operasi ini dengan berhati-hati.', + changePassword: 'Kata laluan', + changeConnHelper: 'Operasi ini akan mengubah pangkalan data semasa {0}. Adakah anda ingin meneruskan?', + changePasswordHelper: + 'Pangkalan data telah dikaitkan dengan aplikasi. Menukar kata laluan akan menukar kata laluan pangkalan data aplikasi pada masa yang sama. Perubahan ini akan berkuat kuasa selepas aplikasi dimulakan semula.', + recoverTimeoutHelper: '-1 bermaksud tiada had masa tamat', + + confChange: 'Konfigurasi', + confNotFound: + 'Fail konfigurasi tidak dapat dijumpai. Sila tingkatkan aplikasi ke versi terkini di gedung aplikasi dan cuba lagi!', + + portHelper: + 'Port ini adalah port yang didedahkan oleh kontena. Anda perlu menyimpan pengubahsuaian secara berasingan dan memulakan semula kontena!', + + loadFromRemote: 'Selaras', + userBind: 'Kaitkan pengguna', + pgBindHelper: + 'Operasi ini digunakan untuk mencipta pengguna baharu dan mengaitkannya dengan pangkalan data sasaran. Pada masa ini, memilih pengguna yang sudah wujud dalam pangkalan data tidak disokong.', + pgSuperUser: 'Pengguna Super', + loadFromRemoteHelper: + 'Ini akan menyelaraskan maklumat pangkalan data di pelayan ke 1Panel. Adakah anda mahu meneruskan?', + passwordHelper: 'Tidak dapat diperoleh, klik untuk ubah', + remote: 'Jauh', + remoteDB: 'Pelayan jauh | Pelayan-pelayan jauh', + createRemoteDB: 'Kaitkan @.lower:database.remoteDB', + unBindRemoteDB: 'Nyahkaitkan @.lower:database.remoteDB', + unBindForce: 'Paksa nyahkait', + unBindForceHelper: 'Abaikan semua ralat semasa proses nyahkait untuk memastikan operasi akhir berjaya', + unBindRemoteHelper: + 'Nyahkaitkan pangkalan data jauh hanya akan menghapuskan hubungan pengikatan dan tidak akan terus memadamkan pangkalan data jauh.', + editRemoteDB: 'Edit pelayan jauh', + localDB: 'Pangkalan data tempatan', + address: 'Alamat pangkalan data', + version: 'Versi pangkalan data', + userHelper: + 'Pengguna root atau pengguna pangkalan data dengan keistimewaan root boleh mengakses pangkalan data jauh.', + pgUserHelper: 'Pengguna pangkalan data dengan keistimewaan superuser.', + ssl: 'Gunakan SSL', + clientKey: 'Kunci peribadi klien', + clientCert: 'Sijil klien', + caCert: 'Sijil CA', + hasCA: 'Mempunyai sijil CA', + skipVerify: 'Abaikan pemeriksaan kesahihan sijil', + initialDB: 'Pangkalan Data Awal', + + formatHelper: + 'Set aksara pangkalan data semasa adalah {0}, ketidakkonsistenan set aksara mungkin menyebabkan kegagalan pemulihan.', + dropHelper: 'Anda boleh seret dan lepaskan fail yang ingin dimuat naik di sini atau', + clickHelper: 'klik untuk memuat naik', + supportUpType: + 'Hanya menyokong format fail sql, sql.gz, tar.gz, .zip. Fail termampat yang diimport mesti mengandungi hanya satu fail .sql atau termasuk test.sql', + + currentStatus: 'Keadaan semasa', + baseParam: 'Parameter asas', + performanceParam: 'Parameter prestasi', + runTime: 'Waktu mula', + connections: 'Jumlah sambungan', + bytesSent: 'Byte dihantar', + bytesReceived: 'Byte diterima', + queryPerSecond: 'Pertanyaan per saat', + txPerSecond: 'Tx per saat', + connInfo: 'sambungan aktif/puncak', + connInfoHelper: 'Jika nilai terlalu besar, tingkatkan "max_connections".', + threadCacheHit: 'Cache benang berjaya', + threadCacheHitHelper: 'Jika terlalu rendah, tingkatkan "thread_cache_size".', + indexHit: 'Indeks berjaya', + indexHitHelper: 'Jika terlalu rendah, tingkatkan "key_buffer_size".', + innodbIndexHit: 'Kadar berjaya indeks Innodb', + innodbIndexHitHelper: 'Jika terlalu rendah, tingkatkan "innodb_buffer_pool_size".', + cacheHit: 'Berjaya pertanyaan cache', + cacheHitHelper: 'Jika terlalu rendah, tingkatkan "query_cache_size".', + tmpTableToDB: 'Jadual sementara ke cakera', + tmpTableToDBHelper: 'Jika terlalu besar, cuba tingkatkan "tmp_table_size".', + openTables: 'Jadual dibuka', + openTablesHelper: 'Nilai konfigurasi "table_open_cache" mesti lebih besar atau sama dengan nilai ini.', + selectFullJoin: 'Pilih penyertaan penuh', + selectFullJoinHelper: 'Jika nilai bukan 0, periksa sama ada indeks jadual data adalah betul.', + selectRangeCheck: 'Bilangan penyertaan tanpa indeks', + selectRangeCheckHelper: 'Jika nilai bukan 0, periksa sama ada indeks jadual data adalah betul.', + sortMergePasses: 'Bilangan penggabungan terisih', + sortMergePassesHelper: 'Jika nilai terlalu besar, tingkatkan "sort_buffer_size".', + tableLocksWaited: 'Bilangan kunci jadual', + tableLocksWaitedHelper: + 'Jika nilai terlalu besar, pertimbangkan untuk meningkatkan prestasi pangkalan data anda.', + + performanceTuning: 'Penalaan prestasi', + optimizationScheme: 'Skema pengoptimuman', + keyBufferSizeHelper: 'Saiz penimbal untuk indeks', + queryCacheSizeHelper: 'Cache pertanyaan. Jika fungsi ini dilumpuhkan, tetapkan parameter ini kepada 0.', + tmpTableSizeHelper: 'Saiz cache jadual sementara', + innodbBufferPoolSizeHelper: 'Saiz penimbal Innodb', + innodbLogBufferSizeHelper: 'Saiz penimbal log Innodb', + sortBufferSizeHelper: '* sambungan, saiz penimbal per urutan benang', + readBufferSizeHelper: '* sambungan, saiz penimbal bacaan', + readRndBufferSizeHelper: '* sambungan, saiz penimbal bacaan rawak', + joinBufferSizeHelper: '* sambungan, saiz cache jadual perkaitan', + threadStackelper: '* sambungan, saiz tumpukan per benang', + binlogCacheSizeHelper: '* sambungan, saiz cache log binari (gandaan 4096)', + threadCacheSizeHelper: 'Saiz kolam benang', + tableOpenCacheHelper: 'Cache jadual', + maxConnectionsHelper: 'Sambungan maksimum', + restart: 'Mulakan semula', + + slowLog: 'Log lambat', + noData: 'Tiada log lambat lagi.', + + isOn: 'Hidup', + longQueryTime: 'Ambang (saat)', + thresholdRangeHelper: 'Sila masukkan ambang yang betul (1 - 600).', + + timeout: 'Tamat masa(saat)', + timeoutHelper: 'Tempoh tamat masa sambungan tidak aktif. 0 menunjukkan sambungan sentiasa aktif.', + maxclients: 'Klien maksimum', + requirepassHelper: + 'Biarkan ruangan ini kosong untuk menunjukkan bahawa tiada kata laluan telah ditetapkan. Perubahan perlu disimpan secara berasingan dan kontena perlu dimulakan semula!', + databases: 'Bilangan pangkalan data', + maxmemory: 'Penggunaan memori maksimum', + maxmemoryHelper: '0 menunjukkan tiada had.', + tcpPort: 'Port mendengar semasa.', + uptimeInDays: 'Hari beroperasi.', + connectedClients: 'Bilangan klien yang disambungkan.', + usedMemory: 'Penggunaan memori semasa Redis.', + usedMemoryRss: 'Saiz memori yang diminta daripada sistem pengendalian.', + usedMemoryPeak: 'Penggunaan memori puncak Redis.', + memFragmentationRatio: 'Nisbah pemecahan memori.', + totalConnectionsReceived: 'Jumlah bilangan klien yang disambungkan sejak dijalankan.', + totalCommandsProcessed: 'Jumlah bilangan arahan yang dilaksanakan sejak dijalankan.', + instantaneousOpsPerSec: 'Bilangan arahan yang dilaksanakan oleh pelayan setiap saat.', + keyspaceHits: 'Bilangan kali kunci pangkalan data berjaya dijumpai.', + keyspaceMisses: 'Bilangan percubaan gagal untuk mencari kunci pangkalan data.', + hit: 'Nisbah pencarian kunci pangkalan data yang berjaya.', + latestForkUsec: 'Bilangan mikrodetik yang dihabiskan pada operasi fork() terakhir.', + redisCliHelper: '"redis-cli" perkhidmatan tidak dikesan. Aktifkan perkhidmatan terlebih dahulu.', + redisQuickCmd: 'Arahan pantas Redis', + recoverHelper: 'Ini akan menimpa data dengan [{0}]. Adakah anda mahu meneruskan?', + submitIt: 'Tindih data', + + baseConf: 'Asas', + allConf: 'Semua', + restartNow: 'Mula semula sekarang', + restartNowHelper1: + 'Anda perlu memulakan semula sistem selepas perubahan konfigurasi berkuat kuasa. Jika data anda perlu dipelihara, lakukan operasi simpan terlebih dahulu.', + restartNowHelper: 'Perubahan ini hanya akan berkuat kuasa selepas sistem dimulakan semula.', + + persistence: 'Ketekalan', + rdbHelper1: 'saat, masukkan', + rdbHelper2: 'data', + rdbHelper3: 'Memenuhi mana-mana syarat akan mencetuskan ketekalan RDB.', + rdbInfo: 'Pastikan nilai dalam senarai peraturan berada dalam julat 1 hingga 100000', + + containerConn: 'Sambungan kontena', + connAddress: 'Alamat', + containerConnHelper: + 'Alamat sambungan ini boleh digunakan oleh aplikasi yang berjalan pada runtime laman web (PHP, dll.) atau kontena.', + remoteConn: 'Sambungan luaran', + remoteConnHelper2: 'Gunakan alamat ini untuk persekitaran bukan kontena atau sambungan luar.', + remoteConnHelper3: + 'Alamat akses lalai ialah IP hos. Untuk mengubahnya, pergi ke item konfigurasi "Alamat Akses Lalai" pada halaman tetapan panel.', + localIP: 'IP Tempatan', + }, + aiTools: { + model: { + model: 'Model', + create: 'Tambah Model', + create_helper: 'Tarik "{0}"', + ollama_doc: 'Anda boleh melawat laman web rasmi Ollama untuk mencari dan menemui lebih banyak model.', + container_conn_helper: 'Gunakan alamat ini untuk akses atau sambungan antara kontena', + ollama_sync: + 'Sincronizando o modelo Ollama, encontrou que os seguintes modelos não existem, deseja excluí-los?', + from_remote: 'Este modelo não foi baixado via 1Panel, sem logs de pull relacionados.', + no_logs: 'Os logs de pull deste modelo foram excluídos e não podem ser visualizados.', + }, + proxy: { + proxy: 'Peningkatan Proksi AI', + proxyHelper1: 'Ikatkan domain dan aktifkan HTTPS untuk meningkatkan keselamatan penghantaran', + proxyHelper2: 'Hadkan akses IP untuk mengelakkan pendedahan di internet awam', + proxyHelper3: 'Aktifkan penstriman', + proxyHelper4: 'Setelah selesai, anda boleh melihat dan mengurusnya dalam senarai laman web', + proxyHelper5: + 'Selepas diaktifkan, anda boleh melumpuhkan akses luaran ke port dalam App Store - Dipasang - Ollama - Parameter untuk meningkatkan keselamatan.', + proxyHelper6: 'Untuk melumpuhkan konfigurasi proksi, anda boleh memadamnya dari senarai laman web.', + whiteListHelper: 'Hadkan akses kepada hanya IP dalam senarai putih', + }, + gpu: { + gpu: 'Pemantauan GPU', + gpuHelper: 'Sistem tidak mengesan arahan NVIDIA-SMI atau XPU-SMI. Sila periksa dan cuba lagi!', + process: 'Maklumat Proses', + type: 'Jenis', + typeG: 'Grafik', + typeC: 'Pengiraan', + typeCG: 'Pengiraan+Grafik', + processName: 'Nama Proses', + shr: 'Memori Kongsi', + temperatureHelper: 'Suhu GPU tinggi boleh menyebabkan pengurangan frekuensi GPU', + gpuUtil: 'Penggunaan GPU', + temperature: 'Suhu', + performanceState: 'Status Prestasi', + powerUsage: 'Penggunaan Kuasa', + memoryUsage: 'Penggunaan Memori', + fanSpeed: 'Kelajuan Kipas', + power: 'Kuasa', + powerCurrent: 'Kuasa Semasa', + powerLimit: 'Had Kuasa', + memory: 'Memori', + memoryUsed: 'Memori Digunakan', + memoryTotal: 'Jumlah Memori', + percent: 'Penggunaan', + base: 'Maklumat Asas', + + driverVersion: 'Versi Pemacu', + cudaVersion: 'Versi CUDA', + processMemoryUsage: 'Penggunaan Memori', + performanceStateHelper: 'Dari P0 (prestasi maksimum) hingga P12 (prestasi minimum)', + busID: 'Alamat Bas', + persistenceMode: 'Mod Kegigihan', + enabled: 'Diaktifkan', + disabled: 'Dilumpuhkan', + persistenceModeHelper: + 'Mod kegigihan bertindak balas kepada tugas dengan lebih cepat, tetapi penggunaan kuasa siap sedia akan meningkat dengan sewajarnya', + displayActive: 'Permulaan GPU', + displayActiveT: 'Ya', + displayActiveF: 'Tidak', + ecc: 'Teknologi Pemeriksaan dan Pembetulan Ralat', + computeMode: 'Mod Pengiraan', + default: 'Lalai', + exclusiveProcess: 'Proses Eksklusif', + exclusiveThread: 'Benang Eksklusif', + prohibited: 'Dilarang', + defaultHelper: 'Lalai: Proses boleh dilaksanakan serentak', + exclusiveProcessHelper: + 'Proses Eksklusif: Hanya satu konteks CUDA boleh menggunakan GPU, tetapi boleh dikongsi oleh berbilang benang', + exclusiveThreadHelper: 'Benang Eksklusif: Hanya satu benang dalam konteks CUDA boleh menggunakan GPU', + prohibitedHelper: 'Dilarang: Pelaksanaan proses serentak tidak dibenarkan', + migModeHelper: + 'Digunakan untuk mencipta contoh MIG, melaksanakan pengasingan fizikal GPU pada lapisan pengguna.', + migModeNA: 'Tidak Disokong', + current: 'Pemantauan Masa Nyata', + history: 'Rekod Sejarah', + notSupport: 'Versi atau pemacu semasa tidak menyokong paparan parameter ini.', + }, + mcp: { + server: 'Pelayan MCP', + create: 'Tambah Pelayan', + edit: 'Edit Pelayan', + baseUrl: 'Laluan Akses Luar', + baseUrlHelper: 'Contoh: http://192.168.1.2:8000', + ssePath: 'Laluan SSE', + ssePathHelper: 'Contoh: /sse, berhati-hati jangan bertindan dengan pelayan lain', + environment: 'Pemboleh Ubah Persekitaran', + envKey: 'Nama Pemboleh Ubah', + envValue: 'Nilai Pemboleh Ubah', + externalUrl: 'Alamat Sambungan Luar', + operatorHelper: 'Akan melakukan operasi {1} pada {0}, teruskan?', + domain: 'Alamat Akses Lalai', + domainHelper: 'Contoh: 192.168.1.1 atau example.com', + bindDomain: 'Sematkan Laman Web', + commandPlaceHolder: 'Kini hanya menyokong perintah pelancaran npx dan binari', + importMcpJson: 'Import Konfigurasi Pelayan MCP', + importMcpJsonError: 'Struktur mcpServers tidak betul', + bindDomainHelper: + 'Setelah mengikat laman web, ia akan mengubah alamat akses semua Pelayan MCP yang dipasang dan menutup akses luaran ke pelabuhan', + outputTransport: 'Jenis Output', + streamableHttpPath: 'Laluan Streaming', + streamableHttpPathHelper: 'Contoh: /mcp, elakkan daripada bertindan dengan pelayan lain', + npxHelper: 'Sesuai untuk mcp yang dimulakan dengan npx atau binari', + uvxHelper: 'Sesuai untuk mcp yang dimulakan dengan uvx', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: 'Direktori Model', + commandHelper: 'Jika akses luar diperlukan, tetapkan port dalam arahan sama dengan port aplikasi', + imageAlert: + 'Disebabkan saiz imej yang besar, disyorkan untuk memuat turun imej secara manual ke pelayan sebelum pemasangan', + modelSpeedup: 'Dayakan pecutan model', + modelType: 'Jenis model', + }, + }, + container: { + create: 'Cipta kontena', + edit: 'Sunting kontena', + updateHelper1: 'Dikesan bahawa kontena ini berasal dari gedung aplikasi. Sila perhatikan dua perkara berikut:', + updateHelper2: '1. Pengubahsuaian semasa tidak akan diselaraskan ke aplikasi yang dipasang di gedung aplikasi.', + updateHelper3: + '2. Jika anda mengubah aplikasi di halaman yang dipasang, kandungan yang sedang diedit akan menjadi tidak sah.', + updateHelper4: + 'Mengedit kontena memerlukan pembinaan semula, dan sebarang data yang tidak berterusan akan hilang. Adakah anda mahu meneruskan?', + containerList: 'Senarai kontena', + operatorHelper: '{0} akan dilakukan pada kontena berikut. Adakah anda mahu meneruskan?', + operatorAppHelper: + 'Operasi "{0}" akan dilakukan pada kontena berikut dan mungkin mempengaruhi perkhidmatan yang sedang berjalan. Adakah anda mahu meneruskan?', + containerDeleteHelper: + "Dikesan bahawa bekas ini berasal dari Kedai Apl. Memadam bekas tidak akan mengalihnya sepenuhnya dari 1Panel. Untuk memadam sepenuhnya, sila pergi ke Kedai Apl -> menu 'Dipasang' atau 'Persekitaran Runtime'. Teruskan?", + start: 'Mulakan', + stop: 'Hentikan', + restart: 'Mulakan semula', + kill: 'Hentikan paksa', + pause: 'Jeda', + unpause: 'Sambung semula', + rename: 'Tukar nama', + remove: 'Buang', + removeAll: 'Buang semua', + containerPrune: 'Prune', + containerPruneHelper1: 'Ini akan memadam semua kontena yang berada dalam keadaan dihentikan.', + containerPruneHelper2: + 'Jika kontena berasal dari gedung aplikasi, anda perlu ke "Gedung Aplikasi -> Dipasang" dan klik butang "Bangun Semula" untuk memasangnya semula selepas pembersihan.', + containerPruneHelper3: 'Operasi ini tidak boleh diundur. Adakah anda mahu meneruskan?', + imagePrune: 'Prune', + imagePruneSome: 'Bersihkan yang tidak berlabel', + imagePruneSomeEmpty: 'Tiada imej dengan tag "none" yang boleh dibersihkan.', + imagePruneSomeHelper: 'Bersihkan imej dengan tag "none" yang tidak digunakan oleh mana-mana kontena.', + imagePruneAll: 'Bersihkan yang tidak digunakan', + imagePruneAllEmpty: 'Tiada imej yang tidak digunakan boleh dibersihkan.', + imagePruneAllHelper: 'Bersihkan imej yang tidak digunakan oleh mana-mana kontena.', + networkPrune: 'Prune', + networkPruneHelper: 'Ini akan membuang semua rangkaian yang tidak digunakan. Adakah anda mahu meneruskan?', + volumePrune: 'Prune', + volumePruneHelper: 'Ini akan membuang semua volum tempatan yang tidak digunakan. Adakah anda mahu meneruskan?', + cleanSuccess: 'Operasi berjaya, bilangan yang dibersihkan kali ini: {0}!', + cleanSuccessWithSpace: + 'Operasi berjaya. Bilangan cakera yang dibersihkan kali ini ialah {0}. Ruang cakera yang dibebaskan ialah {1}!', + unExposedPort: 'Alamat pemetaan port semasa ialah 127.0.0.1, yang tidak dapat mengaktifkan akses luaran.', + upTime: 'Waktu hidup', + fetch: 'Dapatkan', + lines: 'Baris', + linesHelper: 'Sila masukkan bilangan log yang betul untuk diambil!', + lastDay: 'Hari terakhir', + last4Hour: '4 jam terakhir', + lastHour: 'Jam terakhir', + last10Min: '10 minit terakhir', + cleanLog: 'Bersihkan log', + downLogHelper1: 'Ini akan memuat turun semua log dari kontena {0}. Adakah anda mahu meneruskan?', + downLogHelper2: 'Ini akan memuat turun log terkini {0} dari kontena {0}. Adakah anda mahu meneruskan?', + cleanLogHelper: 'Ini memerlukan memulakan semula kontena dan tidak boleh diundur. Adakah anda mahu meneruskan?', + newName: 'Nama baru', + source: 'Penggunaan sumber', + cpuUsage: 'Penggunaan CPU', + cpuTotal: 'Jumlah CPU', + core: 'Teras', + memUsage: 'Penggunaan memori', + memTotal: 'Had memori', + memCache: 'Cache memori', + loadSize: 'Dapatkan Saiz Bekas', + ip: 'Alamat IP', + cpuShare: 'Bahagian CPU', + cpuShareHelper: + 'Enjin kontena menggunakan nilai asas 1024 untuk bahagian CPU. Anda boleh meningkatkannya untuk memberikan lebih masa CPU kepada kontena.', + inputIpv4: 'Contoh: 192.168.1.1', + inputIpv6: 'Contoh: 2001:0db8:85a3:0000:0000:8a2e:0370:7334', + + diskUsage: 'Penggunaan Cakera', + localVolume: 'Isipadu Storan Tempatan', + buildCache: 'Cache Binaan', + usage: 'Digunakan: {0}, Boleh Dibebaskan: {1}', + clean: 'Bebaskan', + imageClean: + 'Membersihkan imej akan memadam semua imej yang tidak digunakan. Operasi ini tidak boleh dikembalikan. Teruskan?', + containerClean: + 'Membersihkan bekas akan memadam semua bekas yang dihentikan (termasuk aplikasi berhenti dari Kedai Apl). Operasi ini tidak boleh dikembalikan. Teruskan?', + sizeRw: 'Saiz Lapisan Bekas', + sizeRwHelper: 'Saiz lapisan boleh tulis yang unik untuk bekas', + sizeRootFs: 'Saiz Maya', + sizeRootFsHelper: 'Jumlah saiz semua lapisan imej yang disandarkan oleh bekas + lapisan bekas', + + containerFromAppHelper: + 'Dikesan bahawa kontena ini berasal dari gedung aplikasi. Operasi aplikasi boleh menyebabkan suntingan semasa menjadi tidak sah.', + containerFromAppHelper1: + 'Klik butang [Param] dalam senarai aplikasi yang dipasang untuk memasuki halaman penyuntingan dan mengubah nama kontena.', + command: 'Arahan', + console: 'Interaksi kontena', + tty: 'Peruntukkan TTY palsu (-t)', + openStdin: 'Pastikan STDIN terbuka walaupun tidak disambungkan (-i)', + custom: 'Kustom', + emptyUser: 'Apabila kosong, anda akan log masuk sebagai lalai', + privileged: 'Privileged', + privilegedHelper: + 'Benarkan kontena menjalankan operasi teristimewa tertentu pada hos, yang boleh meningkatkan risiko kontena. Gunakan dengan berhati-hati!', + + upgradeHelper: 'Nama Repository/Nama Imej: Versi Imej', + upgradeWarning2: + 'Operasi peningkatan memerlukan pembinaan semula kontena, sebarang data yang tidak disimpan akan hilang. Adakah anda mahu meneruskan?', + oldImage: 'Imej semasa', + sameImageContainer: 'Kontena imej sama', + sameImageHelper: 'Kontena yang menggunakan imej sama boleh dinaik taraf secara berkumpulan setelah dipilih', + targetImage: 'Imej sasaran', + imageLoadErr: 'Tiada nama imej dikesan untuk kontena', + appHelper: + 'Kontena berasal dari gedung aplikasi, dan peningkatan boleh menyebabkan perkhidmatan tidak tersedia.', + input: 'Input manual', + forcePull: 'Tarik imej sentiasa ', + forcePullHelper: 'Ini akan mengabaikan imej sedia ada di pelayan dan menarik imej terkini dari pendaftaran.', + server: 'Hos', + serverExample: '80, 80-88, ip:80 atau ip:80-88', + containerExample: '80 atau 80-88', + exposePort: 'Dedahkan port', + exposeAll: 'Dedahkan semua', + cmdHelper: 'Contoh: nginx -g "daemon off;"', + entrypointHelper: 'Contoh: docker-entrypoint.sh', + autoRemove: 'Buang automatik', + cpuQuota: 'Bilangan teras CPU', + memoryLimit: 'Memori', + limitHelper: 'Jika ditetapkan kepada 0, ia bermakna tiada had. Nilai maksimum ialah {0}', + mount: 'Mount', + volumeOption: 'Volume', + hostOption: 'Hos', + serverPath: 'Laluan pelayan', + containerDir: 'Laluan kontena', + networkEmptyHelper: 'Sila pastikan pemilihan rangkaian bekas adalah betul', + modeRW: 'RW', + modeR: 'R', + sharedLabel: 'Mod Penyebaran', + private: 'Peribadi', + privateHelper: 'Perubahan pemasangan dalam bekas dan hos tidak saling mempengaruhi', + rprivate: 'Peribadi Rekursif', + rprivateHelper: 'Semua pemasangan dalam bekas diasingkan sepenuhnya dari hos', + shared: 'Berkongsi', + sharedHelper: 'Perubahan pemasangan dalam hos dan bekas kelihatan antara satu sama lain', + rshared: 'Berkongsi Rekursif', + rsharedHelper: 'Semua perubahan pemasangan dalam hos dan bekas kelihatan antara satu sama lain', + slave: 'Hamba', + slaveHelper: 'Bekas dapat melihat perubahan pemasangan hos, tetapi perubahan sendiri tidak mempengaruhi hos', + rslave: 'Hamba Rekursif', + rslaveHelper: 'Semua pemasangan dalam bekas dapat melihat perubahan hos, tetapi tidak mempengaruhi hos', + mode: 'Mod', + env: 'Persekitaran', + restartPolicy: 'Polisi Mulakan Semula', + always: 'sentiasa', + unlessStopped: 'melainkan dihentikan', + onFailure: 'gagal (lima kali secara lalai)', + no: 'tidak pernah', + + refreshTime: 'Selang penyegaran', + cache: 'Cache', + + image: 'Imej | Imej-imej', + imagePull: 'Tarik', + imagePullHelper: + 'Menyokong pemilihan berbilang imej untuk ditarik, tekan Enter selepas memasukkan setiap imej untuk teruskan', + imagePush: 'Tekan', + imagePushHelper: + 'Terdapat pengesahan bahawa imej ini mempunyai beberapa tag. Sila pastikan nama imej yang digunakan untuk menolak adalah: {0}', + imageDelete: 'Padam imej', + repoName: 'Pendaftaran kontena', + imageName: 'Nama imej', + pull: 'Tarik', + path: 'Laluan', + importImage: 'Import', + buildArgs: 'Argumen Binaan', + imageBuild: 'Bina', + pathSelect: 'Laluan', + label: 'Label', + imageTag: 'Tag imej', + imageTagHelper: + 'Menyokong penetapan berbilang tag imej, tekan Enter selepas memasukkan setiap tag untuk teruskan', + push: 'Tekan', + fileName: 'Nama fail', + export: 'Eksport', + exportImage: 'Eksport imej', + size: 'Saiz', + tag: 'Tag', + tagHelper: 'Satu setiap baris. Sebagai contoh,\nkey1=value1\nkey2=value2', + imageNameHelper: 'Nama dan Tag imej, sebagai contoh: nginx:latest', + cleanBuildCache: 'Bersihkan cache bina', + delBuildCacheHelper: + 'Ini akan memadam semua artefak cache yang dijana semasa binaan dan tidak boleh diundur. Adakah anda mahu meneruskan?', + urlWarning: 'Awalan URL tidak perlu termasuk http:// atau https://. Sila ubah.', + + network: 'Rangkaian | Rangkaian-rangkaian', + networkHelper: + 'Ini boleh menyebabkan beberapa aplikasi dan persekitaran runtime tidak berfungsi dengan betul. Adakah anda mahu meneruskan?', + createNetwork: 'Cipta', + networkName: 'Nama', + driver: 'Pemacu', + option: 'Pilihan', + attachable: 'Boleh dilampirkan', + parentNetworkCard: 'Kad Rangkaian Induk', + subnet: 'Subnet', + scope: 'Skop IP', + gateway: 'Gerbang', + auxAddress: 'Kecualikan IP', + + volume: 'Volum | Volum-volum', + volumeDir: 'Direktori volum', + nfsEnable: 'Aktifkan storan NFS', + nfsAddress: 'Alamat', + mountpoint: 'Titik pemasangan', + mountpointNFSHelper: 'contoh: /nfs, /nfs-share', + options: 'Pilihan', + createVolume: 'Cipta', + + repo: 'Pendaftaran', + createRepo: 'Tambah', + httpRepoHelper: 'Mengoperasikan repositori jenis HTTP memerlukan mulakan semula perkhidmatan Docker.', + httpRepo: + 'Memilih protokol HTTP memerlukan memulakan semula perkhidmatan Docker untuk menambahkannya ke pendaftaran tidak selamat.', + delInsecure: 'Padamkan pendaftaran tidak selamat', + delInsecureHelper: + 'Ini akan memulakan semula perkhidmatan Docker untuk mengeluarkannya dari pendaftaran tidak selamat. Adakah anda mahu meneruskan?', + downloadUrl: 'Pelayan', + imageRepo: 'Repo imej', + repoHelper: 'Adakah ia termasuk repositori cermin/organisasi/projek?', + auth: 'Memerlukan pengesahan', + mirrorHelper: + 'Jika terdapat banyak cermin, baris baru mesti dipaparkan, contohnya:\nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com', + registrieHelper: + 'Jika terdapat banyak repositori persendirian, baris baru mesti dipaparkan, contohnya:\n172.16.10.111:8081 \n172.16.10.112:8081', + + compose: 'Compose | Compose-compose', + composeFile: 'Fail Susunan', + fromChangeHelper: 'Menukar sumber akan membersihkan kandungan yang sedang diedit. Adakah anda mahu meneruskan?', + composePathHelper: 'Laluan simpan fail konfigurasi: {0}', + composeHelper: + 'Komposisi yang dicipta melalui editor atau templat 1Panel akan disimpan dalam direktori {0}/docker/compose.', + deleteFile: 'Padam fail', + deleteComposeHelper: + 'Padam semua fail berkaitan komposisi kontena, termasuk fail konfigurasi dan fail berterusan. Sila berhati-hati!', + deleteCompose: 'Padam komposisi ini.', + createCompose: 'Cipta', + composeDirectory: 'Direktori', + template: 'Templat', + composeTemplate: 'Templat Compose | Templat Compose', + createComposeTemplate: 'Cipta', + content: 'Kandungan', + contentEmpty: 'Kandungan Compose tidak boleh kosong, sila masukkan dan cuba lagi!', + containerNumber: 'Bilangan kontena', + containerStatus: 'Status kontena', + exited: 'Keluar', + running: 'Berjalan ( {0} / {1} )', + composeDetailHelper: 'Komposisi dibuat di luar 1Panel. Operasi mula dan berhenti tidak disokong.', + composeOperatorHelper: 'Operasi {1} akan dilakukan pada {0}. Adakah anda mahu meneruskan?', + composeDownHelper: + 'Ini akan menghentikan dan menghapuskan semua kontena dan rangkaian di bawah komposisi {0}. Adakah anda mahu meneruskan?', + composeEnvHelper2: + 'Penyelarasan ini dibuat oleh Kedai Apl 1Panel. Sila ubah pembolehubah persekitaran dalam aplikasi yang dipasang.', + + setting: 'Tetapan | Tetapan', + operatorStatusHelper: 'Ini akan "{0}" perkhidmatan Docker. Adakah anda mahu meneruskan?', + dockerStatus: 'Perkhidmatan Docker', + daemonJsonPathHelper: 'Pastikan laluan konfigurasi sama seperti yang dinyatakan dalam docker.service.', + mirrors: 'Cermin pendaftaran', + mirrorsHelper: '', + mirrorsHelper2: 'Untuk maklumat lanjut, lihat dokumen rasmi.', + registries: 'Pendaftaran tidak selamat', + ipv6Helper: + 'Apabila IPv6 diaktifkan, anda perlu menambah rangkaian kontena IPv6. Rujuk dokumen rasmi untuk langkah konfigurasi tertentu.', + ipv6CidrHelper: 'Julat kolam alamat IPv6 untuk kontena', + ipv6TablesHelper: 'Konfigurasi automatik Docker IPv6 untuk peraturan iptables.', + experimentalHelper: + 'Mengaktifkan ip6tables memerlukan konfigurasi ini dihidupkan; jika tidak, ip6tables akan diabaikan.', + cutLog: 'Pilihan log', + cutLogHelper1: 'Konfigurasi semasa hanya akan mempengaruhi kontena yang baru dicipta.', + cutLogHelper2: 'Kontena sedia ada perlu dicipta semula agar konfigurasi berkuat kuasa.', + cutLogHelper3: + 'Harap maklum bahawa mencipta semula kontena boleh menyebabkan kehilangan data. Jika kontena anda mengandungi data penting, pastikan membuat sandaran sebelum melakukan operasi pembinaan semula.', + maxSize: 'Saiz maksimum', + maxFile: 'Fail maksimum', + liveHelper: + 'Secara lalai, apabila daemon Docker ditamatkan, ia mematikan kontena yang sedang berjalan. Anda boleh mengkonfigurasi daemon supaya kontena kekal berjalan jika daemon menjadi tidak tersedia. Fungsi ini dipanggil pemulihan langsung. Pilihan pemulihan langsung membantu mengurangkan waktu henti kontena akibat kerosakan daemon, pemadaman terancang, atau peningkatan.', + liveWithSwarmHelper: 'Konfigurasi daemon live-restore tidak serasi dengan mod swarm.', + iptablesDisable: 'Tutup iptables', + iptablesHelper1: 'Konfigurasi automatik peraturan iptables untuk Docker.', + iptablesHelper2: + 'Melumpuhkan iptables akan menyebabkan kontena tidak dapat berkomunikasi dengan rangkaian luaran.', + daemonJsonPath: 'Laluan Konfigurasi', + serviceUnavailable: 'Perkhidmatan Docker tidak dimulakan pada masa ini.', + startIn: ' untuk dimulakan', + sockPath: 'Socket domain Unix', + sockPathHelper: 'Saluran komunikasi antara daemon Docker dan klien.', + sockPathHelper1: 'Laluan lalai: /var/run/docker-x.sock', + sockPathMsg: + 'Menyimpan tetapan Laluan Socket boleh menyebabkan perkhidmatan Docker tidak tersedia. Adakah anda mahu meneruskan?', + sockPathErr: 'Sila pilih atau masukkan laluan fail sock Docker yang betul', + related: 'Berkaitan', + includeAppstore: 'Tunjukkan kontena dari gedung aplikasi', + excludeAppstore: 'Sembunyikan Kontena Kedai Aplikasi', + + cleanDockerDiskZone: 'Bersihkan ruang cakera yang digunakan oleh Docker', + cleanImagesHelper: '( Bersihkan semua imej yang tidak digunakan oleh mana-mana kontena )', + cleanContainersHelper: '( Bersihkan semua kontena yang dihentikan )', + cleanVolumesHelper: '( Bersihkan semua volum tempatan yang tidak digunakan )', + + makeImage: 'Cipta imej', + newImageName: 'Nama imej baru', + commitMessage: 'Mesej komit', + author: 'Pengarang', + ifPause: 'Jeda Kontena Semasa Penciptaan', + ifMakeImageWithContainer: 'Cipta Imej Baru daripada Kontena Ini?', + finishTime: 'Masa berhenti terakhir', + }, + cronjob: { + create: 'Cipta tugas cron', + edit: 'Edit tugas cron', + importHelper: + 'Tugas terjadual dengan nama sama akan dilangkau secara automatik semasa import. Tugas akan ditetapkan ke status 【Lumpuh】 secara lalai, dan ditetapkan ke status 【Menunggu Edit】 apabila perkaitan data tidak normal.', + changeStatus: 'Tukar status', + disableMsg: + 'Ini akan menghentikan tugas berjadual daripada dilaksanakan secara automatik. Adakah anda mahu meneruskan?', + enableMsg: 'Ini akan membenarkan tugas berjadual dilaksanakan secara automatik. Adakah anda mahu meneruskan?', + taskType: 'Jenis', + record: 'Rekod', + viewRecords: 'Rekod', + shell: 'Shell', + stop: 'Hentikan Manual', + stopHelper: 'Operasi ini akan memaksa menghentikan pelaksanaan tugas semasa. Teruskan?', + log: 'Log sandaran', + logHelper: 'Log sistem sandaran', + ogHelper1: '1. Log Sistem 1Panel ', + logHelper2: '2. Log log masuk SSH pelayan ', + logHelper3: '3. Semua log laman web ', + containerCheckBox: 'Dalam kontena (tidak perlu masukkan arahan kontena)', + containerName: 'Nama kontena', + ntp: 'Penyelarasan masa', + ntp_helper: 'Anda boleh mengkonfigurasi pelayan NTP di halaman Tetapan Pantas Alat.', + app: 'Aplikasi sandaran', + website: 'Laman web sandaran', + rulesHelper: 'Menyokong pelbagai peraturan pengecualian, dipisahkan dengan koma Inggeris , contoh: *.log,*.sql', + lastRecordTime: 'Waktu pelaksanaan terakhir', + all: 'Semua', + failedRecord: 'Rekod kegagalan', + successRecord: 'Rekod berjaya', + database: 'Pangkalan data sandaran', + backupArgs: 'Argumen Sandaran', + backupArgsHelper: + 'Argumen sandaran yang tidak disenaraikan boleh dimasukkan dan dipilih secara manual. Contoh: Masukkan --no-data dan pilih pilihan pertama dari senarai juntai bawah.', + singleTransaction: + 'Sandaran jadual InnoDB menggunakan transaksi tunggal, sesuai untuk sandaran data isipadu besar', + quick: 'Baca data baris demi baris daripada memuatkan keseluruhan jadual ke dalam ingatan, sesuai untuk sandaran data isipadu besar dan mesin ingatan rendah', + skipLockTables: 'Sandaran tanpa mengunci semua jadual, sesuai untuk pangkalan data konkuren tinggi', + missBackupAccount: 'Akaun sandaran tidak dijumpai', + syncDate: 'Waktu penyelarasan', + clean: 'Bersihkan cache', + curl: 'Akses URL', + taskName: 'Nama', + cronSpec: 'Kitaran pencetus', + cronSpecDoc: + 'Kitaran pelaksanaan tersuai hanya menyokong format [minit jam hari bulan minggu], contohnya 0 0 * * *. Untuk maklumat lanjut, sila rujuk dokumen rasmi.', + cronSpecHelper: 'Masukkan tempoh pelaksanaan yang betul', + cleanHelper: + 'Operasi ini merekodkan semua rekod pelaksanaan tugas, fail sandaran, dan fail log. Adakah anda mahu meneruskan?', + directory: 'Direktori sandaran', + sourceDir: 'Direktori sumber', + snapshot: 'Snapshot sistem', + allOptionHelper: + 'Pelan tugas semasa adalah untuk menyandarkan semua [{0}]. Muat turun terus tidak disokong buat masa ini. Anda boleh menyemak senarai sandaran dalam menu [{0}].', + exclusionRules: 'Peraturan pengecualian', + exclusionRulesHelper: + 'Pilih atau masukkan peraturan pengecualian, tekan Enter selepas setiap set untuk teruskan. Peraturan pengecualian akan digunakan untuk semua operasi mampatan dalam sandaran ini', + default_download_path: 'Pautan muat turun lalai', + saveLocal: 'Simpan sandaran tempatan (sama seperti bilangan salinan storan awan)', + url: 'Alamat URL', + urlHelper: 'Sila masukkan alamat URL yang sah', + targetHelper: 'Akaun sandaran diselenggara dalam tetapan panel.', + withImageHelper: 'Sandarkan imej kedai aplikasi, tetapi ini akan meningkatkan saiz fail snapshot.', + ignoreApp: 'Kecualikan aplikasi', + withImage: 'Sandarkan Imej Aplikasi', + retainCopies: 'Simpan salinan', + retryTimes: 'Bilangan Cubaan Semula', + timeout: 'Masa Tamat', + ignoreErr: 'Abaikan ralat', + ignoreErrHelper: 'Abaikan ralat semasa sandaran untuk memastikan semua tugas sandaran dilaksanakan', + retryTimesHelper: '0 bermaksud tiada cubaan semula selepas gagal', + retainCopiesHelper: 'Bilangan salinan untuk menyimpan rekod pelaksanaan dan log', + retainCopiesHelper1: 'Bilangan salinan untuk menyimpan fail sandaran', + retainCopiesUnit: ' salinan (Lihat)', + cronSpecRule: 'Format tempoh pelaksanaan dalam baris {0} adalah salah. Sila semak dan cuba lagi!', + perMonthHelper: 'Laksanakan pada hari ke-{0} setiap bulan pada {1}:{2}', + perWeekHelper: 'Laksanakan setiap minggu pada hari {0} pada {1}:{2}', + perDayHelper: 'Laksanakan setiap hari pada {0}:{1}', + perHourHelper: 'Laksanakan setiap jam pada minit ke-{0}', + perNDayHelper: 'Laksanakan setiap {0} hari pada {1}:{2}', + perNHourHelper: 'Laksanakan setiap {0} jam pada {1}', + perNMinuteHelper: 'Laksanakan setiap {0} minit', + perNSecondHelper: 'Laksanakan setiap {0} saat', + perMonth: 'Setiap bulan', + perWeek: 'Setiap minggu', + perHour: 'Setiap jam', + perNDay: 'Setiap N hari', + perDay: 'Setiap hari', + perNHour: 'Setiap N jam', + perNMinute: 'Setiap N minit', + perNSecond: 'Setiap N saat', + day: 'hari', + dayUnit: 'h', + monday: 'Isnin', + tuesday: 'Selasa', + wednesday: 'Rabu', + thursday: 'Khamis', + friday: 'Jumaat', + saturday: 'Sabtu', + sunday: 'Ahad', + shellContent: 'Skrip', + errRecord: 'Log salah', + errHandle: 'Kegagalan pelaksanaan tugas cron', + noRecord: 'Picu Tugas Cron, dan anda akan melihat rekod di sini.', + cleanData: 'Bersihkan data', + cleanRemoteData: 'Padam data jarak jauh', + cleanDataHelper: 'Padam fail sandaran yang dijana semasa tugas ini.', + noLogs: 'Tiada keluaran tugas lagi...', + errPath: 'Laluan sandaran [{0}] salah, tidak boleh dimuat turun!', + cutWebsiteLog: 'Putaran log laman web', + cutWebsiteLogHelper: 'Fail log yang diputar akan disandarkan ke direktori sandaran 1Panel.', + syncIpGroup: 'Segerakkan kumpulan IP WAF', + + requestExpirationTime: 'Waktu luput permintaan muat naik (Jam)', + unitHours: 'Unit: Jam', + alertTitle: 'Tugas Terancang - {0} 「{1}」 Amaran Kegagalan Tugas', + library: { + script: 'Skrip', + syncNow: 'Segerakan Sekarang', + turnOnSync: 'Hidupkan Segerakan Auto', + turnOnSyncHelper: + 'Menghidupkan segerakan auto akan melakukan penyegerakan automatik pada waktu awal pagi setiap hari', + turnOffSync: 'Matikan Segerakan Auto', + turnOffSyncHelper: 'Mematikan segerakan auto mungkin menyebabkan kelewatan penyegerakan skrip, sahkan?', + isInteractive: 'Interaktif', + interactive: 'Skrip interaktif', + interactiveHelper: + 'Memerlukan input pengguna semasa pelaksanaan dan tidak boleh digunakan dalam tugas terjadual.', + library: 'Perpustakaan Skrip', + remoteLibrary: 'Pustaka Skrip Jauh', + create: 'Tambah Skrip', + edit: 'Sunting Skrip', + groupHelper: + 'Tetapkan kumpulan yang berbeza berdasarkan ciri skrip, yang membolehkan operasi penapisan skrip dilakukan dengan lebih pantas.', + handleHelper: 'Akan melaksanakan skrip {1} pada {0}, teruskan?', + noSuchApp: + 'Perkhidmatan {0} tidak dikesan. Sila pasang dengan cepat menggunakan pustaka skrip terlebih dahulu!', + syncHelper: 'Akan menyelaraskan pustaka skrip sistem. Operasi ini hanya melibatkan skrip sistem. Teruskan?', + }, + }, + monitor: { + globalFilter: 'Penapis Global', + enableMonitor: 'Status Pemantauan', + storeDays: 'Hari Penyimpanan', + defaultNetwork: 'Kad Rangkaian Lalai', + defaultNetworkHelper: + 'Pilihan kad rangkaian lalai yang dipaparkan dalam antara muka pemantauan dan gambaran keseluruhan', + defaultIO: 'Cakera Lalai', + defaultIOHelper: 'Pilihan cakera lalai yang dipaparkan dalam antara muka pemantauan dan gambaran keseluruhan', + cleanMonitor: 'Kosongkan Rekod Pemantauan', + cleanHelper: 'Operasi ini akan membersihkan semua rekod pemantauan termasuk GPU. Teruskan?', + + avgLoad: 'Purata beban', + loadDetail: 'Butiran beban', + resourceUsage: 'Penggunaan sumber', + networkCard: 'Antara muka rangkaian', + read: 'Baca', + write: 'Tulis', + readWriteCount: 'Operasi I/O', + readWriteTime: 'Kelewatan I/O', + today: 'Hari ini', + yesterday: 'Semalam', + lastNDay: '{0} hari terakhir', + lastNMonth: '{0} bulan terakhir', + lastHalfYear: 'Setengah tahun terakhir', + memory: 'Memori', + percent: 'Peratusan', + cache: 'Cache', + disk: 'Cakera', + network: 'Rangkaian', + up: 'Naik', + down: 'Turun', + interval: 'Selang Kumpulan', + intervalHelper: 'Sila masukkan selang kumpulan pemantauan yang sesuai (5 saat - 12 jam)', + }, + terminal: { + local: 'Tempatan', + defaultConn: 'Sambungan Lalai', + defaultConnHelper: + 'Operasi ini akan menyambung secara automatik ke terminal nod selepas membuka terminal untuk 【{0}】. Teruskan?', + withReset: 'Tetapkan Semula Maklumat Sambungan', + localConnJump: + 'Maklumat sambungan lalai dikekalkan dalam [Terminal - Tetapan]. Jika sambungan gagal, sila edit di sana!', + localHelper: 'Nama tempatan hanya digunakan untuk pengenalan sistem tempatan.', + connLocalErr: 'Tidak dapat mengesahkan secara automatik, sila isi maklumat log masuk pelayan tempatan.', + testConn: 'Uji sambungan', + saveAndConn: 'Simpan dan sambung', + connTestOk: 'Maklumat sambungan tersedia', + connTestFailed: 'Sambungan tidak tersedia, sila semak maklumat sambungan.', + host: 'Hos | Hos-hos', + createConn: 'Sambungan baru', + manageGroup: 'Urus kumpulan', + noHost: 'Tiada hos', + groupChange: 'Tukar kumpulan', + expand: 'Kembangkan semua', + fold: 'Kontrak semua', + batchInput: 'Pemprosesan kelompok', + quickCommand: 'Arahan pantas | Arahan pantas', + noSuchCommand: 'Tiada data arahan pantas ditemui dalam fail CSV yang diimport, sila periksa dan cuba lagi!', + quickCommandHelper: 'Anda boleh menggunakan arahan pantas di bahagian bawah "Terminal -> Terminal".', + groupDeleteHelper: + 'Selepas kumpulan dikeluarkan, semua sambungan dalam kumpulan akan dipindahkan ke kumpulan lalai. Adakah anda mahu meneruskan?', + command: 'Arahan', + quickCmd: 'Arahan pantas', + addHost: 'Tambah', + localhost: 'Localhost', + ip: 'Alamat', + authMode: 'Pengesahan', + passwordMode: 'Kata laluan', + rememberPassword: 'Ingat maklumat pengesahan', + keyMode: 'Kunci Peribadi', + key: 'Kunci peribadi', + keyPassword: 'Kata laluan kunci peribadi', + emptyTerminal: 'Tiada terminal yang sedang disambungkan.', + }, + toolbox: { + common: { + toolboxHelper: 'Untuk beberapa isu pemasangan dan penggunaan, sila rujuk kepada', + }, + swap: { + swap: 'Partition Swap', + swapHelper1: + 'Saiz partition swap harus 1 hingga 2 kali ganda memori fizikal, boleh disesuaikan berdasarkan keperluan tertentu;', + swapHelper2: + 'Sebelum mencipta fail swap, pastikan cakera sistem mempunyai ruang yang mencukupi, kerana saiz fail swap akan menggunakan ruang cakera yang bersamaan;', + swapHelper3: + 'Swap boleh membantu mengurangkan tekanan memori, tetapi ia hanya alternatif. Kebergantungan berlebihan pada swap boleh menyebabkan penurunan prestasi sistem. Disarankan untuk mengutamakan peningkatan memori atau pengoptimuman penggunaan memori aplikasi;', + swapHelper4: + 'Disarankan untuk memantau penggunaan swap secara berkala bagi memastikan operasi sistem berjalan dengan normal.', + swapDeleteHelper: + 'Operasi ini akan membuang partition Swap {0}. Atas sebab keselamatan sistem, fail yang sepadan tidak akan dipadamkan secara automatik. Jika pemadaman diperlukan, sila lakukan secara manual!', + saveHelper: 'Sila simpan tetapan semasa terlebih dahulu!', + saveSwap: + 'Menyimpan konfigurasi semasa akan menyesuaikan saiz partition Swap {0} kepada {1}. Adakah anda mahu meneruskan?', + swapMin: 'Saiz minimum partition adalah 40 KB. Sila ubah dan cuba lagi!', + swapMax: 'Nilai maksimum untuk saiz partition adalah {0}. Sila ubah dan cuba lagi!', + swapOff: 'Saiz minimum partition adalah 40 KB. Menetapkannya kepada 0 akan mematikan partition Swap.', + }, + device: { + dnsHelper: 'Pelayan DNS', + dnsAlert: + 'Perhatian! Mengubah konfigurasi fail /etc/resolv.conf akan mengembalikan fail ke nilai lalai selepas sistem dimulakan semula.', + dnsHelper1: + 'Apabila terdapat pelbagai entri DNS, ia perlu dipaparkan pada baris baru. Contoh:\n114.114.114.114\n8.8.8.8', + hostsHelper: 'Resolusi nama hos', + hosts: 'Domain', + hostAlert: 'Rekod komen tersembunyi, sila klik butang Semua Konfigurasi untuk melihat atau menetapkan', + toolbox: 'Tetapan pantas', + hostname: 'Nama hos', + passwd: 'Kata laluan sistem', + passwdHelper: 'Aksara input tidak boleh termasuk $ dan &', + timeZone: 'Zon waktu', + localTime: 'Waktu pelayan', + timeZoneChangeHelper: 'Mengubah zon waktu sistem memerlukan memulakan semula perkhidmatan. Teruskan?', + timeZoneHelper: + 'Jika anda tidak memasang arahan "timedatectl", anda mungkin tidak dapat mengubah zon waktu kerana sistem menggunakan arahan itu untuk mengubah zon waktu.', + timeZoneCN: 'Beijing', + timeZoneAM: 'Los Angeles', + timeZoneNY: 'New York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'Pelayan NTP', + hostnameHelper: + 'Pengubahan nama hos bergantung pada arahan "hostnamectl". Jika arahan itu tidak dipasang, pengubahan mungkin gagal.', + userHelper: + 'Nama pengguna bergantung pada arahan "whoami" untuk pengambilan. Jika arahan itu tidak dipasang, pengambilan mungkin gagal.', + passwordHelper: + 'Pengubahan kata laluan bergantung pada arahan "chpasswd". Jika arahan itu tidak dipasang, pengubahan mungkin gagal.', + hostHelper: + 'Terdapat nilai kosong dalam kandungan yang disediakan. Sila semak dan cuba lagi selepas pengubahsuaian!', + dnsCheck: 'Uji Ketersediaan', + dnsOK: 'Maklumat konfigurasi DNS tersedia!', + dnsTestFailed: 'Maklumat konfigurasi DNS tidak tersedia.', + }, + fail2ban: { + sshPort: 'Dengar pada port SSH', + sshPortHelper: 'Fail2ban semasa mendengar pada port sambungan SSH hos', + unActive: 'Perkhidmatan Fail2ban tidak diaktifkan pada masa ini.', + operation: 'Anda akan melaksanakan operasi "{0}" pada perkhidmatan Fail2ban. Adakah anda mahu meneruskan?', + fail2banChange: 'Pengubahan Konfigurasi Fail2ban', + ignoreHelper: + 'Senarai IP dalam senarai dibenarkan akan diabaikan untuk disekat. Adakah anda mahu meneruskan?', + bannedHelper: 'Senarai IP dalam senarai sekatan akan disekat oleh pelayan. Adakah anda mahu meneruskan?', + maxRetry: 'Percubaan maksimum', + banTime: 'Waktu sekatan', + banTimeHelper: 'Waktu sekatan lalai adalah 10 minit, -1 menunjukkan sekatan kekal', + banTimeRule: 'Sila masukkan waktu sekatan yang sah atau -1', + banAllTime: 'Sekatan kekal', + findTime: 'Tempoh penemuan', + banAction: 'Tindakan sekatan', + banActionOption: 'Sekat alamat IP tertentu menggunakan {0}', + allPorts: ' (Semua Port)', + ignoreIP: 'Senarai IP yang dibenarkan', + bannedIP: 'Senarai IP yang disekat', + logPath: 'Laluan log', + logPathHelper: 'Lalai adalah /var/log/secure atau /var/log/auth.log', + }, + ftp: { + ftp: 'Akaun FTP | Akaun FTP', + notStart: 'Perkhidmatan FTP tidak berjalan pada masa ini, sila mulakan dahulu!', + operation: 'Ini akan melaksanakan operasi "{0}" pada perkhidmatan FTP. Adakah anda mahu meneruskan?', + noPasswdMsg: + 'Tidak dapat mendapatkan kata laluan akaun FTP semasa, sila tetapkan kata laluan dan cuba lagi!', + enableHelper: + 'Mengaktifkan akaun FTP yang dipilih akan memulihkan kebenaran aksesnya. Adakah anda mahu meneruskan?', + disableHelper: + 'Melumpuhkan akaun FTP yang dipilih akan membatalkan kebenaran aksesnya. Adakah anda mahu meneruskan?', + syncHelper: 'Selaraskan data akaun FTP antara pelayan dan pangkalan data. Adakah anda mahu meneruskan?', + dirSystem: + 'Direktori ini dikhaskan untuk sistem. Pengubahsuaian mungkin menyebabkan sistem rosak, sila ubah dan cuba lagi!', + dirHelper: 'Membolehkan FTP memerlukan perubahan kebenaran direktori - sila pilih dengan berhati-hati', + dirMsg: 'Membolehkan FTP akan mengubah kebenaran untuk seluruh direktori {0}. Teruskan?', + }, + clam: { + clam: 'Imbasan virus', + cron: 'Imbasan berjadual', + cronHelper: 'Versi profesional menyokong ciri imbasan berjadual', + specErr: 'Ralat format jadual pelaksanaan, sila semak dan cuba lagi!', + disableMsg: + 'Menghentikan pelaksanaan berjadual akan menghalang tugas imbasan ini daripada berjalan secara automatik. Adakah anda mahu meneruskan?', + enableMsg: + 'Mengaktifkan pelaksanaan berjadual akan membolehkan tugas imbasan ini berjalan secara automatik pada selang waktu yang tetap. Adakah anda mahu meneruskan?', + showFresh: 'Tunjukkan perkhidmatan pengemas kini tanda tangan', + hideFresh: 'Sembunyikan perkhidmatan pengemas kini tanda tangan', + clamHelper: + 'Konfigurasi minimum yang disyorkan untuk ClamAV ialah: RAM 3 GiB atau lebih, CPU teras tunggal dengan 2.0 GHz atau lebih tinggi, dan sekurang-kurangnya 5 GiB ruang cakera keras yang tersedia.', + notStart: 'Perkhidmatan ClamAV tidak berjalan pada masa ini, sila mulakan dahulu!', + removeRecord: 'Padam fail laporan', + noRecords: 'Klik butang "Picu" untuk memulakan imbasan dan anda akan melihat rekod di sini.', + removeInfected: 'Padam fail virus', + removeInfectedHelper: + 'Padam fail virus yang dikesan semasa tugas untuk memastikan keselamatan pelayan dan operasi normal.', + clamCreate: 'Cipta peraturan imbasan', + infectedStrategy: 'Strategi fail dijangkiti', + removeHelper: 'Padam fail virus, pilih dengan berhati-hati!', + move: 'Pindah', + moveHelper: 'Pindahkan fail virus ke direktori yang ditentukan', + copyHelper: 'Salin fail virus ke direktori yang ditentukan', + none: 'Jangan buat apa-apa', + noneHelper: 'Tidak mengambil tindakan ke atas fail virus', + scanDir: 'Imbas direktori', + infectedDir: 'Direktori fail dijangkiti', + scanDate: 'Tarikh Imbasan', + scanResult: 'Hujung log imbasan', + tail: 'Baris', + infectedFiles: 'Fail dijangkiti', + log: 'Butiran', + clamConf: 'Daemon Clam AV', + clamLog: '@:toolbox.clam.clamConf log', + freshClam: 'FreshClam', + freshClamLog: '@:toolbox.clam.freshClam log', + alertHelper: 'Versi profesional menyokong imbasan berjadual dan amaran SMS', + alertTitle: 'Tugas imbasan virus 「{0}」 mengesan amaran fail dijangkiti', + }, + }, + logs: { + core: 'Perkhidmatan Panel', + agent: 'Pemantauan Nod', + panelLog: 'Log Panel', + operation: 'Log Operasi', + login: 'Log Masuk', + loginIP: 'IP Log Masuk', + loginAddress: 'Alamat Log Masuk', + loginAgent: 'Ejen Log Masuk', + loginStatus: 'Status', + system: 'Log Sistem', + deleteLogs: 'Bersihkan Log', + resource: 'Sumber', + detail: { + dashboard: 'Overview', + ai: 'AI', + groups: 'Kumpulan', + hosts: 'Hos', + apps: 'Aplikasi', + websites: 'Laman Web', + containers: 'Kontena', + files: 'Pengurusan Fail', + runtimes: 'Persekitaran Jalankan', + process: 'Pengurusan Proses', + toolbox: 'Kotak Alat', + backups: 'Sandaran / Pulihkan', + tampers: 'Perlindungan daripada Pinda', + xsetting: 'Tetapan Antara Muka', + logs: 'Audit Log', + settings: 'Tetapan Panel', + cronjobs: 'Tugas Terjadual', + waf: 'WAF', + databases: 'Pangkalan', + licenses: 'lesen', + nodes: 'nod', + commands: 'Perintah Pantas', + }, + websiteLog: 'Log Laman Web', + runLog: 'Log Jalankan', + errLog: 'Log Ralat', + }, + file: { + fileDirNum: '{0} direktori, {1} fail,', + currentDir: 'Direktori Semasa', + dir: 'Folder', + upload: 'Muat naik', + uploadFile: 'Muat naik fail', + uploadDirectory: 'Muat naik direktori', + download: 'Muat turun', + fileName: 'Nama fail', + search: 'Cari', + mode: 'Kebenaran', + editPermissions: 'Edit kebenaran', + owner: 'Pemilik', + file: 'Fail', + remoteFile: 'Muat turun dari jarak jauh', + share: 'Kongsi', + sync: 'Penyelarasan Data', + size: 'Saiz', + updateTime: 'Diubah suai', + rename: 'Tukar nama', + role: 'Kebenaran', + info: 'Atribut', + linkFile: 'Pautan lembut', + batchoperation: 'Operasi kelompok', + shareList: 'Senarai kongsi', + zip: 'Dimampatkan', + group: 'Kumpulan', + path: 'Laluan', + public: 'Lain-lain', + setRole: 'Tetapkan kebenaran', + link: 'Pautan fail', + rRole: 'Baca', + wRole: 'Tulis', + xRole: 'Boleh laksana', + name: 'Nama', + compress: 'Mampatkan', + deCompress: 'Nyahmampatkan', + compressType: 'Format mampatan', + compressDst: 'Laluan mampatan', + replace: 'Timpa fail sedia ada', + compressSuccess: 'Berjaya dimampatkan', + deCompressSuccess: 'Berjaya dinyahmampatkan', + deCompressDst: 'Laluan nyahmampatan', + linkType: 'Jenis pautan', + softLink: 'Pautan lembut', + hardLink: 'Pautan keras', + linkPath: 'Laluan pautan', + selectFile: 'Pilih fail', + downloadUrl: 'URL Jarak Jauh', + downloadStart: 'Muat turun bermula', + moveSuccess: 'Berjaya dipindahkan', + copySuccess: 'Berjaya disalin', + pasteMsg: 'Sila klik butang "Tampal" di bahagian kanan atas direktori sasaran', + move: 'Pindah', + calculate: 'Kira', + remark: 'Catatan', + setRemark: 'Tetapkan catatan', + remarkPrompt: 'Masukkan catatan', + remarkPlaceholder: 'Catatan', + remarkToggle: 'Catatan', + remarkToggleTip: 'Muatkan catatan fail', + canNotDeCompress: 'Tidak dapat nyahmampatkan fail ini', + uploadSuccess: 'Berjaya dimuat naik', + downloadProcess: 'Kemajuan muat turun', + downloading: 'Sedang muat turun...', + infoDetail: 'Sifat fail', + root: 'Direktori akar', + list: 'Senarai fail', + sub: 'Subfolder', + downloadSuccess: 'Berjaya dimuat turun', + theme: 'Tema', + language: 'Bahasa', + eol: 'Akhir baris', + copyDir: 'Salin', + paste: 'Tampal', + changeOwner: 'Ubah suai pengguna dan kumpulan pengguna', + containSub: 'Guna perubahan kebenaran secara rekursif', + ownerHelper: + 'Pengguna lalai persekitaran operasi PHP: kumpulan pengguna adalah 1000:1000, adalah normal jika pengguna di dalam dan di luar kontena menunjukkan ketidakkonsistenan', + searchHelper: 'Sokong wildcard seperti *', + uploadFailed: '[{0}] Gagal memuat naik fail', + fileUploadStart: 'Memuat naik [{0}]....', + currentSelect: 'Pilihan semasa: ', + unsupportedType: 'Jenis fail tidak disokong', + deleteHelper: + 'Adakah anda pasti mahu memadam fail berikut? Secara lalai, ia akan masuk ke tong kitar semula selepas dipadamkan', + fileHelper: 'Nota:\n1. Keputusan carian tidak boleh diisih.\n2. Folder tidak boleh diisih mengikut saiz.', + forceDeleteHelper: 'Padamkan fail secara kekal (tanpa masuk ke tong kitar semula, padam terus)', + recycleBin: 'Tong kitar semula', + sourcePath: 'Laluan asal', + deleteTime: 'Masa padam', + confirmReduce: 'Adakah anda pasti mahu memulihkan fail berikut?', + reduceSuccess: 'Berjaya dipulihkan', + reduce: 'Pulihkan', + reduceHelper: + 'Jika fail atau direktori dengan nama yang sama wujud di laluan asal, ia akan ditimpa. Adakah anda mahu meneruskan?', + clearRecycleBin: 'Bersihkan', + clearRecycleBinHelper: 'Adakah anda mahu membersihkan tong kitar semula?', + favorite: 'Kegemaran', + removeFavorite: 'Buang daripada kegemaran?', + addFavorite: 'Tambah/Buang ke Kegemaran', + clearList: 'Bersihkan senarai', + deleteRecycleHelper: 'Adakah anda pasti mahu memadam fail berikut secara kekal?', + typeErrOrEmpty: '[{0}] jenis fail salah atau folder kosong', + dropHelper: 'Seret fail yang anda mahu muat naik ke sini', + fileRecycleBin: 'Aktifkan tong kitar semula', + fileRecycleBinMsg: '{0} tong kitar semula', + wordWrap: 'Bungkus secara automatik', + deleteHelper2: 'Adakah anda pasti mahu memadam fail yang dipilih? Operasi pemadaman tidak boleh dipulihkan', + ignoreCertificate: 'Benarkan sambungan pelayan tidak selamat', + ignoreCertificateHelper: + 'Membenarkan sambungan pelayan tidak selamat boleh menyebabkan kebocoran atau pengubahan data. Gunakan pilihan ini hanya jika mempercayai sumber muat turun.', + uploadOverLimit: 'Bilangan fail melebihi 1000! Sila mampatkan dan muat naik', + clashDitNotSupport: 'Nama fail dilarang mengandungi .1panel_clash', + clashDeleteAlert: 'Folder "Tong Kitar Semula" tidak boleh dipadamkan', + clashOpenAlert: 'Sila klik butang "Tong Kitar Semula" untuk membuka direktori tong kitar semula', + right: 'Ke hadapan', + back: 'Ke belakang', + top: 'Pergi ke atas', + up: 'Kembali', + openWithVscode: 'Buka dengan VS Code', + vscodeHelper: 'Pastikan VS Code dipasang secara tempatan dan pemalam SSH Remote dikonfigurasikan', + saveContentAndClose: 'Fail telah diubah suai, adakah anda mahu menyimpan dan menutupnya?', + saveAndOpenNewFile: 'Fail telah diubah suai, adakah anda mahu menyimpan dan membuka fail baru?', + noEdit: 'Fail tidak diubah suai, tiada keperluan untuk tindakan ini!', + noNameFolder: 'Folder tanpa nama', + noNameFile: 'Fail tanpa nama', + minimap: 'Peta mini kod', + fileCanNotRead: 'Fail tidak dapat dibaca', + previewTruncated: 'Fail terlalu besar, hanya menunjukkan bahagian terakhir', + previewEmpty: 'Fail kosong atau bukan fail teks', + previewLargeFile: 'Pratonton', + panelInstallDir: 'Direktori pemasangan 1Panel tidak boleh dipadamkan', + wgetTask: 'Tugas Muat Turun', + existFileTitle: 'Amaran fail dengan nama yang sama', + existFileHelper: 'Fail yang dimuat naik mengandungi fail dengan nama yang sama. Adakah anda mahu menimpanya?', + existFileSize: 'Saiz fail (baru -> lama)', + existFileDirHelper: 'Fail/folder yang dipilih mempunyai nama yang sama. Sila berhati-hati!\n', + coverDirHelper: 'Folder yang dipilih untuk ditimpa akan disalin ke laluan destinasi!', + noSuchFile: 'Fail atau direktori tidak ditemui. Sila periksa dan cuba lagi.', + setting: 'tetapan', + showHide: 'Tunjukkan fail tersembunyi', + noShowHide: 'Jangan tunjukkan fail tersembunyi', + cancelUpload: 'Batalkan Muat Naik', + cancelUploadHelper: + 'Adakah hendak membatalkan muat naik, selepas pembatalan senarai muat naik akan dikosongkan.', + keepOneTab: 'Pastikan sekurang-kurangnya satu tab dikekalkan', + notCanTab: 'Tidak dapat menambah tab lagi', + convert: 'Tukar Format', + converting: 'Menukar Ke', + fileCanNotConvert: 'Fail ini tidak menyokong penukaran format', + formatType: 'Jenis Format', + sourceFormat: 'Format Asal', + sourceFile: 'Fail Asal', + saveDir: 'Direktori Simpanan', + deleteSourceFile: 'Padam Fail Asal', + convertHelper: 'Tukar fail yang dipilih ke format lain', + convertHelper1: 'Sila pilih fail yang hendak ditukar', + execConvert: 'Mulakan penukaran. Anda boleh melihat log penukaran di Pusat Tugas', + convertLogs: 'Log Penukaran', + formatConvert: 'Penukaran Format', + }, + ssh: { + autoStart: 'Mula automatik', + enable: 'Aktifkan Mula Automatik', + disable: 'Nyahaktif Mula Automatik', + sshAlert: + 'Data senarai disusun berdasarkan tarikh log masuk. Menukar zon waktu atau melakukan operasi lain boleh menyebabkan penyimpangan dalam tarikh log log masuk.', + sshAlert2: + 'Anda boleh menggunakan "Fail2ban" di "Kotak Alat" untuk menyekat alamat IP yang cuba menyerang secara kasar, dan ini akan meningkatkan keselamatan hos.', + sshOperate: 'Operasi "{0}" pada perkhidmatan SSH akan dilaksanakan. Adakah anda mahu meneruskan?', + sshChange: 'Tetapan SSH', + sshChangeHelper: 'Tindakan ini mengubah "{0}" kepada "{1}". Adakah anda mahu meneruskan?', + sshFileChangeHelper: + 'Mengubah fail konfigurasi boleh menyebabkan ketidaktersediaan perkhidmatan. Lakukan operasi ini dengan berhati-hati. Adakah anda mahu meneruskan?', + port: 'Port', + portHelper: 'Tentukan port yang didengar oleh perkhidmatan SSH.', + listenAddress: 'Alamat dengar', + allV4V6: '0.0.0.0:{0}(IPv4) dan :::{0}(IPv6)', + listenHelper: + 'Membiarkan tetapan IPv4 dan IPv6 kosong akan mendengar pada "0.0.0.0:{0}(IPv4)" dan ":::{0}(IPv6)".', + addressHelper: 'Tentukan alamat yang didengar oleh perkhidmatan SSH.', + permitRootLogin: 'Benarkan log masuk pengguna root', + rootSettingHelper: 'Kaedah log masuk lalai untuk pengguna root ialah "Benarkan log masuk SSH".', + rootHelper1: 'Benarkan log masuk SSH', + rootHelper2: 'Lumpuhkan log masuk SSH', + rootHelper3: 'Hanya log masuk dengan kunci dibenarkan', + rootHelper4: 'Hanya arahan yang telah ditetapkan boleh dilaksanakan. Operasi lain tidak dibenarkan.', + passwordAuthentication: 'Pengesahan kata laluan', + pwdAuthHelper: 'Sama ada untuk mengaktifkan pengesahan kata laluan. Parameter ini diaktifkan secara lalai.', + pubkeyAuthentication: 'Pengesahan kunci', + privateKey: 'Kunci Persendirian', + publicKey: 'Kunci Awam', + password: 'Kata Laluan', + createMode: 'Kaedah Penciptaan', + generate: 'Jana Automatik', + unSyncPass: 'Kata laluan kunci tidak dapat diselaraskan', + syncHelper: + 'Operasi segerak akan membersihkan kunci tidak sah dan menyegerakkan pasangan kunci baru yang lengkap. Teruskan?', + input: 'Input Manual', + import: 'Muat Naik Fail', + authKeys: 'Kunci Pengesahan', + authKeysHelper: 'Simpan maklumat kunci awam semasa?', + pubkey: 'Maklumat kunci', + pubKeyHelper: 'Maklumat kunci semasa hanya berkuat kuasa untuk pengguna {0}', + encryptionMode: 'Mod penyulitan', + passwordHelper: 'Boleh mengandungi 6 hingga 10 angka dan huruf dalam kedua-dua huruf besar dan kecil', + reGenerate: 'Jana semula kunci', + keyAuthHelper: 'Sama ada untuk mengaktifkan pengesahan kunci.', + useDNS: 'Gunakan DNS', + dnsHelper: + 'Kawal sama ada fungsi resolusi DNS diaktifkan pada pelayan SSH untuk mengesahkan identiti sambungan.', + analysis: 'Maklumat statistik', + denyHelper: + "Melaksanakan operasi 'tolak' pada alamat berikut. Selepas menetapkan, IP akan dilarang mengakses pelayan. Adakah anda mahu meneruskan?", + acceptHelper: + "Melaksanakan operasi 'terima' pada alamat berikut. Selepas menetapkan, IP akan mendapatkan semula akses normal. Adakah anda mahu meneruskan?", + noAddrWarning: 'Tiada alamat [{0}] yang dipilih pada masa ini. Sila periksa dan cuba lagi!', + loginLogs: 'Log Masuk', + loginMode: 'Mod', + authenticating: 'Kunci', + publickey: 'Kunci', + belong: 'Milikan', + local: 'Tempatan', + session: 'Sesi | Sesi-sesi', + loginTime: 'Waktu log masuk', + loginIP: 'IP log masuk', + stopSSHWarn: 'Adakah anda mahu memutuskan sambungan SSH ini', + }, + setting: { + panel: 'Panel', + user: 'Pengguna panel', + userChange: 'Tukar pengguna panel', + userChangeHelper: 'Menukar pengguna panel akan menyebabkan anda log keluar. Teruskan?', + passwd: 'Kata laluan panel', + emailHelper: 'Untuk pemulihan kata laluan', + watermark: 'Tetapan Tanda Air', + watermarkContent: 'Kandungan Tanda Air', + contentHelper: + '{0} mewakili nama nod, {1} mewakili alamat nod. Anda boleh menggunakan pembolehubah atau mengisi nama tersuai.', + watermarkColor: 'Warna Tanda Air', + watermarkFont: 'Saiz Fon Tanda Air', + watermarkHeight: 'Ketinggian Tanda Air', + watermarkWidth: 'Lebar Tanda Air', + watermarkRotate: 'Sudut Putaran', + watermarkGap: 'Jarak', + watermarkCloseHelper: 'Adakah anda pasti ingin mematikan tetapan tanda air sistem?', + watermarkOpenHelper: 'Adakah anda pasti ingin menyimpan tetapan tanda air sistem semasa?', + title: 'Alias panel', + panelPort: 'Port panel', + titleHelper: + 'Menyokong panjang 3 hingga 30 aksara dengan huruf Inggeris, huruf Cina, nombor, ruang kosong dan aksara khas yang biasa', + portHelper: + 'Julat port yang disarankan ialah 8888 hingga 65535. Nota: Jika pelayan mempunyai kumpulan keselamatan, benarkan port baru dari kumpulan keselamatan terlebih dahulu', + portChange: 'Tukar port', + portChangeHelper: 'Ubah port perkhidmatan dan mulakan semula perkhidmatan. Adakah anda mahu meneruskan?', + theme: 'Tema', + menuTabs: 'Tab menu', + dark: 'Gelap', + darkGold: 'Emas Gelap', + light: 'Terang', + auto: 'Ikut Sistem', + language: 'Bahasa', + languageHelper: + 'Secara lalai, ia mengikuti bahasa penyemak imbas. Parameter ini hanya berkuat kuasa pada penyemak imbas semasa', + sessionTimeout: 'Tempoh tamat sesi', + sessionTimeoutError: 'Tempoh tamat sesi minimum ialah 300 saat', + sessionTimeoutHelper: 'Panel akan log keluar secara automatik jika tiada operasi lebih daripada {0} saat.', + systemIP: 'Alamat akses lalai', + systemIPHelper: + 'Penghantaran semula aplikasi, akses kontena dan fungsi lain akan menggunakan alamat ini untuk penghalaan. Setiap nod boleh ditetapkan dengan alamat yang berbeza.', + proxy: 'Proksi pelayan', + proxyHelper: 'Ia akan berkuat kuasa dalam senario berikut selepas anda menyediakan pelayan proksi:', + proxyHelper1: 'Muat turun pakej pemasangan dan penyelarasan dari kedai aplikasi (Edisi Profesional sahaja)', + proxyHelper2: 'Kemas kini sistem dan pengambilan maklumat kemas kini (Edisi Profesional sahaja)', + proxyHelper4: 'Rangkaian Docker akan diakses melalui pelayan proksi (Edisi Profesional sahaja)', + proxyHelper3: 'Pengesahan dan penyelarasan lesen sistem', + proxyHelper5: 'Muat turun dan penyegerakan bersepadu untuk pustaka skrip jenis sistem (Ciri Edisi Profesional)', + proxyHelper6: 'Mohon sijil (Fungsi versi Pro)', + proxyType: 'Jenis proksi', + proxyUrl: 'Alamat proksi', + proxyPort: 'Port proksi', + proxyPasswdKeep: 'Ingat Kata Laluan', + proxyDocker: 'Proksi Docker', + proxyDockerHelper: + 'Selaraskan konfigurasi pelayan proksi ke Docker, menyokong tarikan imej pelayan luar talian dan operasi lain', + syncToNode: 'Penyegerakan ke nod anak', + syncToNodeHelper: 'Penyegerakan tetapan ke nod lain', + nodes: 'Nod', + selectNode: 'Pilih nod', + selectNodeError: 'Sila pilih nod', + apiInterface: 'Aktifkan API', + apiInterfaceClose: 'Setelah ditutup, antara muka API tidak boleh diakses. Adakah anda mahu meneruskan?', + apiInterfaceHelper: 'Benarkan aplikasi pihak ketiga mengakses API.', + apiInterfaceAlert1: + 'Jangan aktifkan dalam persekitaran pengeluaran kerana ia mungkin meningkatkan risiko keselamatan pelayan.', + apiInterfaceAlert2: + 'Jangan gunakan aplikasi pihak ketiga untuk memanggil API bagi mengelakkan potensi ancaman keselamatan.', + apiInterfaceAlert3: 'Dokumen API', + apiInterfaceAlert4: 'Dokumen Penggunaan', + apiKey: 'Kunci API', + apiKeyHelper: 'Kunci API digunakan untuk aplikasi pihak ketiga mengakses API.', + ipWhiteList: 'Senarai putih IP', + ipWhiteListEgs: 'Satu per baris. Contoh,\n172.161.10.111\n172.161.10.0/24', + ipWhiteListHelper: 'IP dalam senarai putih boleh mengakses API, 0.0.0.0/0 (semua IPv4), ::/0 (semua IPv6)', + apiKeyValidityTime: 'Tempoh sah kunci antara muka', + apiKeyValidityTimeEgs: 'Tempoh sah kunci antara muka (dalam minit)', + apiKeyValidityTimeHelper: + 'Cap waktu antara muka sah jika perbezaannya dengan cap waktu semasa (dalam minit) berada dalam julat yang dibenarkan. Nilai 0 melumpuhkan pengesahan.', + apiKeyReset: 'Tetapkan semula kunci antara muka', + apiKeyResetHelper: + 'Perkhidmatan kunci yang berkaitan akan menjadi tidak sah. Sila tambah kunci baru pada perkhidmatan', + confDockerProxy: 'Konfigurasi proksi docker', + restartNowHelper: 'Mengkonfigurasi proksi Docker memerlukan memulakan semula perkhidmatan Docker.', + restartNow: 'Mulakan semula sekarang', + restartLater: 'Mulakan semula secara manual nanti', + systemIPWarning: + 'Nod semasa belum mempunyai alamat akses lalai yang dikonfigurasi. Sila pergi ke tetapan panel untuk mengkonfigurasinya!', + systemIPWarning1: 'Alamat pelayan semasa ditetapkan kepada {0}, dan pengalihan cepat tidak mungkin!', + defaultNetwork: 'Kad rangkaian', + syncTime: 'Waktu Pelayan', + timeZone: 'Zon Waktu', + timeZoneChangeHelper: + 'Menukar zon waktu memerlukan memulakan semula perkhidmatan. Adakah anda mahu meneruskan?', + timeZoneHelper: + 'Pengubahan zon waktu bergantung pada perkhidmatan sistem timedatectl. Berkuat kuasa selepas mulakan semula perkhidmatan 1Panel.', + timeZoneCN: 'Beijing', + timeZoneAM: 'Los Angeles', + timeZoneNY: 'New York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'Ntp Server', + syncSiteHelper: + 'Operasi ini akan menggunakan {0} sebagai sumber untuk penyegerakan masa sistem. Adakah anda mahu meneruskan?', + changePassword: 'Tukar Kata Laluan', + oldPassword: 'Kata laluan asal', + newPassword: 'Kata laluan baru', + retryPassword: 'Sahkan kata laluan', + noSpace: 'Maklumat input tidak boleh mengandungi aksara ruang', + duplicatePassword: 'Kata laluan baru tidak boleh sama dengan kata laluan asal, sila masukkan semula!', + diskClean: 'Pembersihan Cache', + developerMode: 'Program Pratonton', + developerModeHelper: + 'Anda akan dapat mengalami ciri dan pembaikan baru sebelum ia dilancarkan secara meluas dan memberikan maklum balas awal.', + thirdParty: 'Akaun pihak ketiga', + noTypeForCreate: 'Tiada jenis sandaran yang sedang dibuat', + LOCAL: 'Cakera pelayan', + OSS: 'Ali OSS', + S3: 'Amazon S3', + mode: 'Mode', + MINIO: 'MinIO', + SFTP: 'SFTP', + WebDAV: 'WebDAV', + WebDAVAlist: 'WebDAV sambungkan Alist boleh merujuk kepada dokumentasi rasmi', + OneDrive: 'Microsoft OneDrive', + isCN: 'Century Internet', + isNotCN: 'Versi Antarabangsa', + client_id: 'ID Klien', + client_secret: 'Rahsia Klien', + redirect_uri: 'URL Penghalaan Semula', + onedrive_helper: 'Konfigurasi tersuai boleh dirujuk dalam dokumentasi rasmi', + refreshTime: 'Waktu Penyegaran Token', + refreshStatus: 'Status Penyegaran Token', + backupDir: 'Direktori Sandaran', + codeWarning: 'Format kod kebenaran semasa tidak betul, sila sahkan semula!', + code: 'Kod Auth', + codeHelper: + 'Sila klik butang "Peroleh", kemudian log masuk ke OneDrive dan salin kandungan selepas "code" dalam pautan yang telah diarahkan semula. Tampalkan kandungan tersebut ke dalam kotak input ini. Untuk arahan spesifik, sila rujuk dokumentasi rasmi.', + googleHelper: + 'Sila buat aplikasi Google dan dapatkan maklumat klien terlebih dahulu, isi borang dan klik butang dapatkan. Untuk operasi khusus, sila rujuk dokumentasi rasmi.', + loadCode: 'Peroleh', + COS: 'Tencent COS', + ap_beijing_1: 'Beijing Zone 1', + ap_beijing: 'Beijing', + ap_nanjing: 'Nanjing', + ap_shanghai: 'Shanghai', + ap_guangzhou: 'Guangzhou', + ap_chengdu: 'Chengdu', + ap_chongqing: 'Chongqing', + ap_shenzhen_fsi: 'Shenzhen Financial', + ap_shanghai_fsi: 'Shanghai Financial', + ap_beijing_fsi: 'Beijing Financial', + ap_hongkong: 'Hong Kong, China', + ap_singapore: 'Singapore', + ap_mumbai: 'Mumbai', + ap_jakarta: 'Jakarta', + ap_seoul: 'Seoul', + ap_bangkok: 'Bangkok', + ap_tokyo: 'Tokyo', + na_siliconvalley: 'Silicon Valley (US West)', + na_ashburn: 'Ashburn (US East)', + na_toronto: 'Toronto', + sa_saopaulo: 'Sao Paulo', + eu_frankfurt: 'Frankfurt', + KODO: 'Qiniu Kodo', + scType: ' Jenis storan', + typeStandard: 'Standard', + typeStandard_IA: 'Standard_IA', + typeArchive: 'Archive', + typeDeep_Archive: 'Deep_Archive', + scLighthouse: 'Lalai, Penyimpanan objek ringan hanya menyokong jenis penyimpanan ini', + scStandard: + 'Storan Standard sesuai untuk senario perniagaan dengan sejumlah besar fail panas yang memerlukan akses masa nyata, interaksi data yang kerap, dan sebagainya.', + scStandard_IA: + 'Storan kekerapan rendah sesuai untuk senario perniagaan dengan kekerapan akses yang agak rendah dan menyimpan data sekurang-kurangnya 30 hari.', + scArchive: 'Storan arkib sesuai untuk senario perniagaan dengan kekerapan akses yang sangat rendah.', + scDeep_Archive: + 'Storan sejuk tahan lama sesuai untuk senario perniagaan dengan kekerapan akses yang sangat rendah.', + archiveHelper: + 'Fail storan arkib tidak boleh dimuat turun secara langsung dan mesti dipulihkan terlebih dahulu melalui laman web penyedia perkhidmatan awan yang berkaitan. Sila gunakan dengan berhati-hati!', + backupAlert: + 'Jika penyedia awan serasi dengan protokol S3, anda boleh menggunakan Amazon S3 secara langsung untuk sandaran.', + domain: 'Domain pemecutan', + backupAccount: 'Akaun sandaran | Akaun sandaran', + loadBucket: 'Dapatkan baldi', + accountName: 'Nama akaun', + accountKey: 'Kunci akaun', + address: 'Alamat', + path: 'Laluan', + + safe: 'Keselamatan', + passkey: 'Passkey', + passkeyManage: 'Urus', + passkeyHelper: 'Untuk log masuk pantas, maksimum 5 passkey boleh dipautkan', + passkeyRequireSSL: 'Aktifkan HTTPS untuk menggunakan passkey', + passkeyNotSupported: 'Pelayar atau persekitaran semasa tidak menyokong passkey', + passkeyCount: 'Dipaut {0}/{1}', + passkeyName: 'Nama', + passkeyNameHelper: 'Masukkan nama untuk membezakan peranti', + passkeyAdd: 'Tambah Passkey', + passkeyCreatedAt: 'Dicipta', + passkeyLastUsedAt: 'Terakhir digunakan', + passkeyDeleteConfirm: 'Selepas dipadam, passkey ini tidak boleh digunakan untuk log masuk. Teruskan?', + passkeyLimit: 'Maksimum 5 passkey boleh dipautkan', + passkeyFailed: + 'Pendaftaran passkey gagal, sila pastikan sertifikat SSL panel adalah sertifikat yang dipercayai', + bindInfo: 'Maklumat ikatan', + bindAll: 'Dengar Semua', + bindInfoHelper: + 'Mengubah alamat atau protokol perkhidmatan pendengaran boleh menyebabkan ketidaktersediaan perkhidmatan. Adakah anda mahu meneruskan?', + ipv6: 'Dengar IPv6', + bindAddress: 'Alamat pendengaran', + entrance: 'Pintu masuk', + showEntrance: 'Tunjukkan amaran dilumpuhkan di halaman "Tinjauan"', + entranceHelper: + 'Mengaktifkan pintu masuk keselamatan hanya akan membolehkan log masuk ke panel melalui pintu masuk keselamatan yang ditentukan.', + entranceError: + 'Sila masukkan titik masuk log masuk yang selamat sepanjang 5-116 aksara, hanya nombor atau huruf yang disokong.', + entranceInputHelper: 'Biarkan kosong untuk melumpuhkan pintu masuk keselamatan.', + randomGenerate: 'Rawak', + expirationTime: 'Tarikh Tamat Tempoh', + unSetting: 'Tidak ditetapkan', + noneSetting: + 'Tetapkan masa tamat tempoh untuk kata laluan panel. Selepas tamat tempoh, anda perlu menetapkan semula kata laluan', + expirationHelper: + 'Jika masa tamat tempoh kata laluan ialah [0] hari, fungsi tamat tempoh kata laluan dilumpuhkan', + days: 'Hari Tamat Tempoh', + expiredHelper: 'Kata laluan semasa telah tamat tempoh. Sila tukar kata laluan lagi.', + timeoutHelper: + '[ {0} hari ] Kata laluan panel akan tamat tempoh. Selepas tamat tempoh, anda perlu menetapkan semula kata laluan', + complexity: 'Pengesahan kerumitan', + complexityHelper: + 'Selepas anda mengaktifkannya, peraturan pengesahan kata laluan akan menjadi: 8-30 aksara, termasuk bahasa Inggeris, nombor, dan sekurang-kurangnya dua aksara khas.', + bindDomain: 'Ikatan domain', + unBindDomain: 'Buka ikatan domain', + panelSSL: 'Panel SSL', + unBindDomainHelper: + 'Tindakan membuka ikatan nama domain boleh menyebabkan ketidakamanan sistem. Adakah anda mahu meneruskan?', + bindDomainHelper: 'Selepas anda mengikat domain, hanya domain itu yang boleh mengakses perkhidmatan 1Panel.', + bindDomainHelper1: 'Biarkan kosong untuk melumpuhkan ikatan nama domain.', + bindDomainWarning: + 'Selepas ikatan domain, anda akan log keluar dan hanya boleh mengakses perkhidmatan 1Panel melalui nama domain yang ditentukan dalam tetapan. Adakah anda mahu meneruskan?', + allowIPs: 'IP Dibenarkan', + unAllowIPs: 'IP Tidak Dibenarkan', + unAllowIPsWarning: + 'Membenarkan IP kosong akan membolehkan semua IP mengakses sistem, yang boleh menyebabkan ketidakamanan sistem. Adakah anda mahu meneruskan?', + allowIPsHelper: + 'Selepas anda menetapkan senarai alamat IP yang dibenarkan, hanya alamat IP dalam senarai yang boleh mengakses perkhidmatan panel.', + allowIPsWarning: + 'Selepas anda menetapkan senarai alamat IP yang dibenarkan, hanya alamat IP dalam senarai yang boleh mengakses perkhidmatan panel. Adakah anda mahu meneruskan?', + allowIPsHelper1: 'Biarkan kosong untuk melumpuhkan sekatan alamat IP.', + allowIPEgs: 'Satu per baris. Contoh,\n172.16.10.111\n172.16.10.0/24', + mfa: 'Pengesahan dua faktor (2FA)', + mfaClose: 'Melumpuhkan MFA akan mengurangkan keselamatan perkhidmatan. Adakah anda mahu meneruskan?', + secret: 'Rahsia', + mfaInterval: 'Selang penyegaran (saat)', + mfaTitleHelper: + 'Judul digunakan untuk membezakan hos 1Panel yang berbeza. Imbas lagi atau tambahkan kunci rahsia secara manual selepas anda mengubah judul.', + mfaIntervalHelper: + 'Imbas lagi atau tambahkan kunci rahsia secara manual selepas anda mengubah masa penyegaran.', + mfaAlert: + 'Token satu kali ialah nombor 6 digit yang dijana secara dinamik berdasarkan masa semasa. Pastikan masa pelayan disegerakkan.', + mfaHelper: 'Selepas anda mengaktifkannya, token satu kali perlu disahkan.', + mfaHelper1: 'Muat turun aplikasi pengesahan, contohnya,', + mfaHelper2: + 'Untuk mendapatkan token satu kali, imbas kod QR berikut menggunakan aplikasi pengesahan anda atau salin kunci rahsia ke dalam aplikasi pengesahan anda.', + mfaHelper3: 'Masukkan enam digit dari aplikasi', + mfaCode: 'Token satu kali', + sslChangeHelper: 'Ubah tetapan https dan mulakan semula perkhidmatan. Adakah anda mahu meneruskan?', + sslDisable: 'Lumpuhkan', + sslDisableHelper: + 'Jika perkhidmatan https dilumpuhkan, anda perlu memulakan semula panel untuk ia berkuat kuasa. Adakah anda mahu meneruskan?', + noAuthSetting: 'Tetapan tidak dibenarkan', + noAuthSettingHelper: + 'Apabila pengguna tidak log masuk dengan pintu masuk keselamatan yang ditentukan, atau tidak mengakses panel dari IP atau nama domain yang ditentukan, respons ini boleh menyembunyikan ciri panel.', + responseSetting: 'Tetapan respons', + help200: 'Halaman Bantuan', + error400: 'Permintaan Buruk', + error401: 'Tidak Dibenarkan', + error403: 'Dilarang', + error404: 'Tidak Dijumpai', + error408: 'Permintaan Tamat Masa', + error416: 'Julat Tidak Memuaskan', + error444: 'Sambungan ditutup', + error500: 'Ralat Pelayan', + + https: 'Mengatur HTTPS untuk panel meningkatkan keselamatan akses', + strictHelper: 'Trafik bukan HTTPS tidak boleh menyambung ke panel', + muxHelper: + 'Panel akan mendengar pada kedua-dua port HTTP dan HTTPS dan mengarahkan HTTP ke HTTPS, tetapi ini mungkin mengurangkan keselamatan', + certType: 'Jenis sijil', + selfSigned: 'Diterbitkan sendiri', + selfSignedHelper: + 'Pelayar mungkin tidak mempercayai sijil diterbitkan sendiri dan mungkin memaparkan amaran keselamatan.', + select: 'Pilih', + domainOrIP: 'Domain atau IP:', + timeOut: 'Tamat Masa', + rootCrtDownload: 'Muat turun sijil akar', + primaryKey: 'Kunci utama', + certificate: 'Sijil', + backupJump: + 'Fail sandaran tidak berada dalam senarai sandaran semasa, sila cuba muat turun dari direktori fail dan import untuk sandaran.', + + snapshot: 'Snapshot | Snapshots', + noAppData: 'Tiada aplikasi sistem yang boleh dipilih', + noBackupData: 'Tiada data sandaran yang boleh dipilih', + stepBaseData: 'Data Asas', + stepAppData: 'Aplikasi Sistem', + stepPanelData: 'Data Sistem', + stepBackupData: 'Data Sandaran', + stepOtherData: 'Data Lain', + operationLog: 'Simpan log operasi', + loginLog: 'Simpan log akses', + systemLog: 'Simpan log sistem', + taskLog: 'Simpan log tugas', + monitorData: 'Simpan data pemantauan', + dockerConf: 'Simpan Konfigurasi Docker', + selectAllImage: 'Simpan semua imej aplikasi', + logLabel: 'Log', + agentLabel: 'Konfigurasi Nod', + appDataLabel: 'Data Aplikasi', + appImage: 'Imej Aplikasi', + appBackup: 'Sandaran Aplikasi', + backupLabel: 'Direktori Sandaran', + confLabel: 'Fail Konfigurasi', + dockerLabel: 'Konteks', + taskLabel: 'Tugas Berjadual', + resourceLabel: 'Direktori Sumber Aplikasi', + runtimeLabel: 'Persekitaran Runtime', + appLabel: 'Aplikasi', + databaseLabel: 'Pangkalan Data', + snapshotLabel: 'Fail Snapshot', + websiteLabel: 'Laman Web', + directoryLabel: 'Direktori', + appStoreLabel: 'Kedai Aplikasi', + shellLabel: 'Skrip', + tmpLabel: 'Direktori Sementara', + sslLabel: 'Direktori Sijil', + reCreate: 'Gagal membuat snapshot', + reRollback: 'Gagal membalikkan snapshot', + deleteHelper: + 'Semua fail snapshot termasuk yang ada dalam akaun sandaran pihak ketiga akan dipadamkan. Adakah anda mahu meneruskan?', + status: 'Status snapshot', + ignoreRule: 'Aturan abaikan', + editIgnoreRule: 'Sunting aturan abaikan', + ignoreHelper: + 'Aturan ini akan digunakan untuk memampatkan dan menyandarkan direktori data 1Panel semasa membuat snapshot. Secara lalai, fail soket diabaikan.', + ignoreHelper1: 'Satu per baris. Contoh,\n*.log\n/opt/1panel/cache', + panelInfo: 'Tulis maklumat asas 1Panel', + panelBin: 'Sandarkan fail sistem 1Panel', + daemonJson: 'Sandarkan fail konfigurasi Docker', + appData: 'Sandarkan aplikasi yang dipasang dari 1Panel', + panelData: 'Sandarkan direktori data 1Panel', + backupData: 'Sandarkan direktori sandaran tempatan untuk 1Panel', + compress: 'Cipta fail snapshot', + upload: 'Muat naik fail snapshot', + recoverDetail: 'Butiran pemulihan', + createSnapshot: 'Cipta snapshot', + importSnapshot: 'Segerakkan snapshot', + recover: 'Pulihkan', + lastRecoverAt: 'Waktu pemulihan terakhir', + lastRollbackAt: 'Waktu pemulangan terakhir', + reDownload: 'Muat turun semula fail sandaran', + recoverErrArch: 'Pemulihan snapshot antara seni bina pelayan yang berbeza tidak disokong!', + recoverErrSize: 'Kekurangan ruang cakera dikesan, sila periksa atau bersihkan dan cuba lagi!', + recoverHelper: 'Memulakan pemulihan dari snapshot {0}, sila sahkan maklumat berikut sebelum meneruskan:', + recoverHelper1: 'Pemulihan memerlukan mulakan semula perkhidmatan Docker dan 1Panel', + recoverHelper2: + 'Pastikan terdapat ruang cakera yang mencukupi pada pelayan (Saiz fail snapshot: {0}, Ruang tersedia: {1})', + recoverHelper3: + 'Pastikan seni bina pelayan sepadan dengan seni bina pelayan di mana snapshot dicipta (Seni bina pelayan semasa: {0})', + rollback: 'Pulangkan semula', + rollbackHelper: + 'Pulangkan semula pemulihan ini akan menggantikan semua fail dari pemulihan ini, dan mungkin memerlukan mulakan semula perkhidmatan Docker dan 1Panel. Adakah anda mahu meneruskan?', + + upgradeHelper: 'Kemas kini memerlukan mulakan semula perkhidmatan 1Panel. Adakah anda mahu meneruskan?', + rollbackLocalHelper: + 'Nod utama tidak menyokong rollback secara langsung. Sila laksanakan arahan [1pctl restore] secara manual untuk rollback!', + noUpgrade: 'Ia adalah versi terbaru pada masa ini', + upgradeNotes: 'Nota pelepasan', + upgradeNow: 'Kemas kini sekarang', + source: 'Sumber muat turun', + versionNotSame: + 'Versi nod tidak sepadan dengan nod utama. Sila naik taraf di Pengurusan Nod sebelum mencuba semula.', + versionCompare: + 'Nod {0} telah berada pada versi terkini yang boleh dinaik taraf. Sila periksa versi nod utama dan cuba lagi!', + + about: 'Mengenai', + versionItem: 'Versi Semasa', + backupCopies: 'Bilangan Salinan untuk Disimpan', + backupCopiesHelper: + 'Tetapkan bilangan salinan sandaran naik taraf untuk disimpan untuk pemulihan versi. 0 bermakna simpan semua.', + backupCopiesRule: 'Sila simpan sekurang-kurangnya 3 rekod sandaran naik taraf', + release: 'Log Kemaskini Versi', + releaseHelper: + 'Pengambilan log kemaskini untuk persekitaran semasa mengalami异常. Anda boleh menyemak dokumentasi rasmi secara manual.', + project: 'GitHub', + issue: 'Isu', + doc: 'Dokumen rasmi', + star: 'Bintang', + description: 'Panel Pelayan Linux', + forum: 'Perbincangan', + doc2: 'Dokumen', + currentVersion: 'Versi', + + license: 'Lesen', + bindNode: 'Ikatan Nod', + menuSetting: 'Tetapan Menu', + menuSettingHelper: 'Apabila hanya terdapat 1 submenu, bar menu hanya akan memaparkan submenu tersebut', + showAll: 'Papar Semua', + hideALL: 'Sembunyikan Semua', + ifShow: 'Sama ada untuk Dipaparkan', + menu: 'Menu', + confirmMessage: 'Halaman akan disegarkan untuk mengemas kini senarai menu lanjutan. Teruskan?', + recoverMessage: 'Halaman akan disegarkan dan senarai menu akan dipulihkan ke keadaan asal. Teruskan?', + compressPassword: 'Kata laluan mampatan', + backupRecoverMessage: + 'Sila masukkan kata laluan mampatan atau nyahmampatan (biarkan kosong jika tidak menetapkan)', + }, + license: { + offLine: 'Versi Luar Talian', + community: 'OSS', + oss: 'Perisian Sumber Terbuka', + pro: 'Pro', + trial: 'Percubaan', + add: 'Tambah Edisi Komuniti', + licenseBindHelper: 'Kuota nod percuma hanya boleh digunakan apabila lesen terikat pada nod', + licenseAlert: + 'Nod Edisi Komuniti hanya boleh ditambah apabila lesen terikat dengan betul pada nod. Hanya nod yang terikat dengan betul pada lesen menyokong penukaran.', + licenseUnbindHelper: 'Nod Edisi Komuniti dikesan untuk lesen ini. Sila lepaskan ikatan dan cuba lagi!', + subscription: 'Langganan', + perpetual: 'Lesen Perpetual', + versionConstraint: '{0} Pembelian versi', + forceUnbind: 'Paksakan Nyahikat', + forceUnbindHelper: + 'Memaksa nyahikat akan mengabaikan sebarang ralat yang berlaku semasa proses nyahikat dan akhirnya melepaskan ikatan lesen.', + updateForce: 'Kemas kini paksa (abaikan semua ralat semasa nyahikatan untuk memastikan operasi akhir berjaya)', + trialInfo: 'Versi', + authorizationId: 'ID Kebenaran Langganan', + authorizedUser: 'Pengguna yang Dibenarkan', + lostHelper: + 'Lesen telah mencapai jumlah percubaan semula maksimum. Sila klik butang penyegerakan secara manual untuk memastikan ciri versi profesional berfungsi dengan baik. butiran: ', + disableHelper: + 'Pengesahan penyegerakan lesen gagal. Sila klik butang penyegerakan secara manual untuk memastikan ciri versi profesional berfungsi dengan baik. butiran: ', + quickUpdate: 'Kemas Kini Pantas', + power: 'Kebenaran', + unbindHelper: 'Semua Tetapan berkaitan Pro akan dibersihkan selepas nyahikat. Adakah anda mahu meneruskan?', + importLicense: 'Lesen', + importHelper: 'Sila klik atau seret fail lesen ke sini', + technicalAdvice: 'Konsultasi Teknikal', + advice: 'Konsultasi', + levelUpPro: 'Tingkatkan ke Pro', + licenseSync: 'Penyegerakan Lesen', + knowMorePro: 'Ketahui Lebih Lanjut', + closeAlert: 'Halaman semasa boleh ditutup dalam tetapan panel', + introduce: 'Pengenalan Ciri', + waf: 'Menaik taraf ke versi profesional boleh menyediakan ciri seperti peta pencegahan, log, rekod blok, sekatan lokasi geografi, peraturan tersuai, halaman pencegahan tersuai, dan sebagainya.', + tamper: 'Menaik taraf ke versi profesional boleh melindungi laman web daripada pengubahsuaian atau manipulasi tanpa kebenaran.', + setting: + 'Menaik taraf ke versi profesional membolehkan penyesuaian logo panel, mesej selamat datang, dan maklumat lain.', + monitor: + 'Tingkatkan ke versi profesional untuk melihat status masa nyata laman web, tren pelawat, sumber pelawat, log permintaan dan maklumat lain.', + alert: 'Tingkatkan ke versi profesional untuk menerima maklumat amaran melalui SMS dan melihat log amaran, mengawal sepenuhnya pelbagai acara utama, dan memastikan operasi sistem bebas kerisauan.', + node: 'Menaik taraf ke Edisi Profesional membolehkan anda menguruskan berbilang pelayan Linux dengan 1Panel.', + nodeApp: + 'Menaik taraf ke Edisi Profesional membolehkan peningkatan bersatu versi aplikasi berbilang nod tanpa menukar nod secara manual.', + nodeDashboard: + 'Menaik taraf ke Edisi Profesional membolehkan pengurusan berpusat aplikasi, laman web, pangkalan data dan tugas berjadual berbilang nod.', + fileExchange: 'Naik taraf ke Edisi Professional untuk menghantar fail dengan cepat antara pelbagai pelayan.', + app: 'Tingkatkan ke versi profesional untuk melihat maklumat perkhidmatan, pemantauan abnormal, dll melalui aplikasi mudah alih.', + cluster: + 'Versi Profesional menaik taraf membolehkan anda menguruskan kelompok induk-hamba MySQL/Postgres/Redis.', + }, + clean: { + scan: 'Mulakan imbasan', + scanHelper: 'Bersihkan fail sampah dengan mudah yang dihasilkan semasa operasi 1Panel', + clean: 'Bersihkan sekarang', + reScan: 'Imbas semula', + cleanHelper: 'Fail dan direktori terpilih tidak boleh dipulihkan selepas pembersihan. Teruskan?', + statusSuggest: '(Disyorkan untuk Pembersihan)', + statusClean: '(Sangat bersih)', + statusEmpty: 'Sangat bersih, tiada pembersihan diperlukan!', + statusWarning: '(Berhati-hati)', + lastCleanTime: 'Dibersihkan Terakhir: {0}', + lastCleanHelper: 'Fail dan direktori yang dibersihkan: {0}, jumlah yang dibersihkan: {1}', + cleanSuccessful: 'Berjaya dibersihkan', + currentCleanHelper: 'Fail dan direktori dibersihkan dalam sesi ini: {0}, Jumlah yang dibersihkan: {1}', + suggest: '(Disyorkan)', + totalScan: 'Jumlah fail sampah untuk dibersihkan: ', + selectScan: 'Jumlah fail sampah yang dipilih: ', + + system: 'Fail Sampah Sistem', + systemHelper: 'Fail sementara dijana semasa snapshot, naik taraf, dan proses lain', + panelOriginal: 'Fail sandaran sebelum pemulihan snapshot sistem', + upgrade: 'Fail sandaran naik taraf sistem', + agentPackages: 'Pakej naik taraf/pemasangan nod anak versi sejarah', + upgradeHelper: '(Disarankan untuk mengekalkan sandaran peningkatan terbaru untuk pemulihan sistem)', + rollback: 'Fail sandaran sebelum pemulihan', + + backup: 'Sandaran Sistem', + backupHelper: 'Fail sandaran tidak dikaitkan dengan akaun sandaran tempatan', + unknownBackup: 'Sandaran Sistem', + tmpBackup: 'Sandaran Sementara', + unknownApp: 'Sandaran Apl Tidak Dikaitkan', + unknownDatabase: 'Sandaran Pangkalan Data Tidak Dikaitkan', + unknownWebsite: 'Sandaran Laman Web Tidak Dikaitkan', + unknownSnapshot: 'Sandaran Snapshot Tidak Dikaitkan', + + upload: 'Fail Muat Naik Sementara', + uploadHelper: 'Fail sementara dimuat naik dari senarai sandaran sistem', + download: 'Fail Muat Turun Sementara', + downloadHelper: 'Fail sementara dimuat turun dari akaun sandaran pihak ketiga oleh sistem', + directory: 'Direktori', + + systemLog: 'Fail log', + systemLogHelper: 'Log sistem, log tugas, fail log laman web', + dockerLog: 'Fail log operasi bekas', + taskLog: 'Fail log pelaksanaan tugas berjadual', + shell: 'Tugas berjadual skrip Shell', + containerShell: 'Tugas berjadual skrip Shell dilaksanakan di dalam bekas', + curl: 'Tugas berjadual CURL', + + docker: 'Sampah bekas', + dockerHelper: 'Fail seperti bekas, imej, isipadu, cache binaan, dsb.', + volumes: 'Isipadu', + buildCache: 'Cache Pembinaan Kontena', + + appTmpDownload: 'Fail muat turun sementara aplikasi', + unknownWebsiteLog: 'Fail sandaran log laman web tidak dikaitkan', + }, + app: { + app: 'Aplikasi | Aplikasi', + installName: 'Nama', + installed: 'Telah Dipasang', + all: 'Semua', + version: 'Versi', + detail: 'Butiran', + params: 'Edit', + author: 'Pengarang', + source: 'Sumber', + appName: 'Nama Aplikasi', + deleteWarn: + 'Operasi memadam akan memadam semua data dan sandaran bersama. Operasi ini tidak boleh dipulihkan. Adakah anda mahu meneruskan?', + syncSuccess: 'Disegerakkan dengan berjaya', + canUpgrade: 'Kemaskini', + backupName: 'Nama Fail', + backupPath: 'Laluan Fail', + backupdate: 'Masa Sandaran', + versionSelect: 'Sila pilih versi', + operatorHelper: 'Operasi {0} akan dilaksanakan pada aplikasi yang dipilih. Adakah anda mahu meneruskan?', + startOperatorHelper: 'Aplikasi akan dimulakan. Adakah anda mahu meneruskan?', + stopOperatorHelper: 'Aplikasi akan dihentikan. Adakah anda mahu meneruskan?', + restartOperatorHelper: 'Aplikasi akan dimulakan semula. Adakah anda mahu meneruskan?', + reloadOperatorHelper: 'Aplikasi akan dimuat semula. Adakah anda mahu meneruskan?', + checkInstalledWarn: '"{0}" tidak dikesan. Pergi ke "Kedai Aplikasi" untuk memasang.', + limitHelper: 'Aplikasi ini telah dipasang.', + deleteHelper: '"{0}" telah dikaitkan dengan sumber berikut. Sila semak dan cuba lagi!', + checkTitle: 'Petunjuk', + defaultConfig: 'Konfigurasi lalai', + defaultConfigHelper: 'Telah dipulihkan ke konfigurasi lalai, akan berkuat kuasa selepas menyimpan', + forceDelete: 'Padam Paksa', + forceDeleteHelper: 'Padam paksa akan mengabaikan ralat semasa proses pemadaman dan akhirnya memadam metadata.', + deleteBackup: 'Padam sandaran', + deleteBackupHelper: 'Juga padam sandaran aplikasi', + deleteDB: 'Padam pangkalan data', + deleteDBHelper: 'Juga padam pangkalan data', + noService: 'Tiada {0}', + toInstall: 'Pergi ke pasang', + param: 'Parameter', + alreadyRun: 'Umur', + syncAppList: 'Segerak', + less1Minute: 'Kurang daripada 1 minit', + appOfficeWebsite: 'Laman web rasmi', + github: 'Github', + document: 'Dokumen', + updatePrompt: 'Tiada kemaskini tersedia', + installPrompt: 'Belum ada aplikasi yang dipasang', + updateHelper: 'Mengedit parameter boleh menyebabkan aplikasi gagal dimulakan. Sila berhati-hati.', + updateWarn: 'Kemaskini parameter memerlukan aplikasi dibina semula. Adakah anda mahu meneruskan?', + busPort: 'Port', + syncStart: 'Mulakan penyegerakan! Sila segar semula kedai aplikasi kemudian', + advanced: 'Tetapan lanjutan', + cpuCore: 'teras', + containerName: 'Nama kontena', + containerNameHelper: 'Nama kontena akan dijana secara automatik jika tidak ditetapkan', + allowPort: 'Akses luaran', + allowPortHelper: 'Membenarkan akses port luaran akan membuka port firewall', + appInstallWarn: + 'Aplikasi tidak membuka port akses luaran secara lalai. Klik "Tetapan lanjutan" untuk membukanya.', + upgradeStart: 'Mulakan peningkatan! Sila segar semula halaman kemudian', + toFolder: 'Buka direktori pemasangan', + editCompose: 'Edit fail compose', + editComposeHelper: 'Mengedit fail compose boleh menyebabkan pemasangan perisian gagal', + composeNullErr: 'Compose tidak boleh kosong', + takeDown: 'Henti Operasi', + allReadyInstalled: 'Telah Dipasang', + installHelper: 'Jika terdapat isu tarikan imej, konfigurasikan pecutan imej.', + installWarn: + 'Akses luaran tidak diaktifkan, yang menghalang aplikasi daripada diakses melalui rangkaian luaran. Adakah anda mahu meneruskan?', + showIgnore: 'Lihat aplikasi yang diabaikan', + cancelIgnore: 'Batal abaikan', + ignoreList: 'Aplikasi yang diabaikan', + appHelper: 'Pergi ke halaman butiran aplikasi untuk mengetahui arahan pemasangan bagi aplikasi tertentu.', + backupApp: 'Sandarkan aplikasi sebelum kemaskini', + backupAppHelper: 'Jika kemaskini gagal, sandaran akan dipulihkan secara automatik.', + openrestyDeleteHelper: 'Padam paksa OpenResty akan memadam semua laman web. Adakah anda mahu meneruskan?', + downloadLogHelper1: 'Semua log aplikasi {0} akan dimuat turun. Adakah anda mahu meneruskan?', + downloadLogHelper2: 'Log terkini {1} aplikasi {0} akan dimuat turun. Adakah anda mahu meneruskan?', + syncAllAppHelper: 'Semua aplikasi akan disegerakkan. Adakah anda mahu meneruskan?', + hostModeHelper: 'Mod rangkaian aplikasi semasa ialah mod hos.', + showLocal: 'Papar aplikasi tempatan', + reload: 'Muat Semula', + upgradeWarn: 'Meningkatkan aplikasi akan menggantikan fail docker-compose.yml.', + newVersion: 'Versi baru', + oldVersion: 'Versi semasa', + composeDiff: 'Perbandingan fail', + showDiff: 'Lihat perbandingan', + useNew: 'Guna versi tersuai', + useDefault: 'Guna versi lalai', + useCustom: 'Sesuaikan docker-compose.yml', + useCustomHelper: 'Menggunakan fail docker-compose.yml tersuai boleh menyebabkan kemaskini gagal.', + diffHelper: 'Bahagian kiri ialah versi lama, kanan ialah versi baru.', + pullImage: 'Tarik Imej', + pullImageHelper: 'Laksanakan docker pull untuk menarik imej sebelum aplikasi dimulakan.', + deleteImage: 'Padam Imej', + deleteImageHelper: 'Padam imej yang berkaitan dengan aplikasi. Tugas tidak akan tamat jika pemadaman gagal.', + requireMemory: 'Keperluan Memori', + supportedArchitectures: 'Seni Bina yang Disokong', + link: 'Pautan', + showCurrentArch: 'Aplikasi seni bina pelayan semasa', + syncLocalApp: 'Segerakkan Aplikasi Tempatan', + memoryRequiredHelper: 'Aplikasi semasa memerlukan {0} memori', + gpuConfig: 'Aktifkan Sokongan GPU', + gpuConfigHelper: + 'Pastikan mesin mempunyai NVIDIA GPU dan memasang pemacu NVIDIA serta NVIDIA Docker Container Toolkit', + webUI: 'Alamat Akses Web', + webUIPlaceholder: 'Contoh: example.com:8080/login', + defaultWebDomain: 'Alamat Akses Lalai', + defaultWebDomainHepler: + 'Jika port aplikasi adalah 8080, alamat loncatan akan menjadi http(s)://alamat akses lalai:8080', + webUIConfig: + 'Nod semasa belum mempunyai alamat akses lalai yang dikonfigurasi. Sila tetapkan dalam parameter aplikasi atau pergi ke tetapan panel untuk mengkonfigurasinya!', + toLink: 'Loncat', + customAppHelper: + 'Sebelum memasang pakej kedai aplikasi tersuai, sila pastikan tidak ada aplikasi yang dipasang.', + forceUninstall: 'Paksa Nyahpasang', + syncCustomApp: 'Segerakan Aplikasi Tersuai', + ignoreAll: 'Abaikan semua versi berikutnya', + ignoreVersion: 'Abaikan versi yang ditentukan', + specifyIP: 'Bind IP Hos', + specifyIPHelper: + 'Tetapkan alamat hos/antara muka rangkaian untuk mengikat port (jika anda tidak pasti mengenai ini, jangan isi)', + uninstallDeleteBackup: 'Cop Terhapus Semasa Nyahpasang Aplikasi', + uninstallDeleteImage: 'Imej Terhapus Semasa Nyahpasang Aplikasi', + upgradeBackup: 'Sandaran Aplikasi Sebelum Naik Taraf', + noAppHelper: 'Tiada aplikasi dikesan, sila pergi ke pusat tugas untuk melihat log penyegerakan kedai aplikasi', + isEdirWarn: 'Mengesan pengubahsuaian pada fail docker-compose.yml, sila semak perbandingan', + }, + website: { + primaryDomain: 'Domain Utama', + otherDomains: 'Domain Lain', + static: 'Statik', + deployment: 'Penerapan', + supportUpType: 'Hanya format fail .tar.gz yang disokong, dan pakej termampat mesti mengandungi fail {0}.json', + proxy: 'Proksi Terbalik', + alias: 'Alias', + ftpUser: 'Akaun FTP', + ftpPassword: 'Kata Laluan FTP', + ftpHelper: + 'Selepas membuat laman web, akaun FTP akan dibuat dan direktori FTP akan memautkan ke direktori laman web.', + remark: 'Catatan', + manageGroup: 'Urus kumpulan', + groupSetting: 'Pengurusan Kumpulan', + createGroup: 'Cipta kumpulan', + appNew: 'Aplikasi Baru', + appInstalled: 'Aplikasi yang Dipasang', + create: 'Cipta laman web', + delete: 'Padam Laman Web', + deleteApp: 'Padam Aplikasi', + deleteBackup: 'Padam Sandaran', + domain: 'Domain', + domainHelper: 'Satu domain per baris.\nSokong wildcard "*" dan alamat IP.\nSokong penambahan port.', + addDomain: 'Tambah', + domainConfig: 'Domain', + defaultDoc: 'Dokumen', + perserver: 'Serentak', + perserverHelper: 'Hadkan serentak maksimum untuk laman web semasa', + perip: 'IP Tunggal', + peripHelper: 'Hadkan jumlah maksimum akses serentak untuk satu IP', + rate: 'Had trafik', + rateHelper: 'Had aliran setiap permintaan (unit: KB)', + limitHelper: 'Aktifkan kawalan aliran', + other: 'Lain-lain', + currentSSL: 'Sijil Semasa', + dnsAccount: 'Akaun DNS', + applySSL: 'Permohonan Sijil', + SSLList: 'Senarai Sijil', + createDnsAccount: 'Akaun DNS', + aliyun: 'Aliyun', + aliEsa: 'Aliyun ESA', + awsRoute53: 'Amazon Route 53', + manual: 'Penyelesaian Manual', + key: 'Kunci', + check: 'Lihat', + acmeAccountManage: 'Pengurusan Akaun ACME', + email: 'E-mel', + acmeAccount: 'Akaun ACME', + provider: 'Kaedah Pengesahan', + dnsManual: 'Penyelesaian Manual', + expireDate: 'Tarikh Luput', + brand: 'Organisasi', + deploySSL: 'Penerapan', + deploySSLHelper: 'Adakah anda pasti mahu menerapkan sijil?', + ssl: 'Sijil | Sijil-Sijil', + dnsAccountManage: 'Penyedia DNS', + renewSSL: 'Perbaharui', + renewHelper: 'Adakah anda pasti mahu memperbaharui sijil?', + renewSuccess: 'Sijil diperbaharui', + enableHTTPS: 'Aktifkan', + aliasHelper: 'Alias ialah nama direktori laman web', + lastBackupAt: 'masa sandaran terakhir', + null: 'tiada', + nginxConfig: 'Konfigurasi Nginx', + websiteConfig: 'Tetapan Laman Web', + proxySettings: 'Tetapan Proksi', + advancedSettings: 'Tetapan Lanjutan', + cacheSettings: 'Tetapan Cache', + sniSettings: 'Tetapan SNI', + basic: 'Asas', + source: 'Konfigurasi', + security: 'Keselamatan', + nginxPer: 'Penalaan Prestasi Nginx', + neverExpire: 'Tidak Pernah Luput', + setDefault: 'Tetapkan sebagai lalai', + default: 'Lalai', + deleteHelper: 'Status aplikasi berkaitan tidak normal, sila semak', + toApp: 'Pergi ke senarai dipasang', + cycle: 'Kitaran', + frequency: 'Kekerapan', + ccHelper: + 'Permintaan akumulatif untuk URL yang sama lebih daripada {1} kali dalam {0} saat, mencetuskan pertahanan CC, menyekat IP ini', + mustSave: 'Pengubahsuaian perlu disimpan untuk berkuat kuasa', + fileExt: 'Sambungan fail', + fileExtBlock: 'Senarai blok sambungan fail', + value: 'Nilai', + enable: 'Aktifkan', + proxyAddress: 'Alamat Proksi', + proxyHelper: 'Contoh: 127.0.0.1:8080', + forceDelete: 'Padam Paksa', + forceDeleteHelper: 'Padam paksa akan mengabaikan ralat semasa proses pemadaman dan akhirnya memadam metadata.', + deleteAppHelper: 'Padam aplikasi yang berkaitan dan sandaran aplikasi pada masa yang sama', + deleteBackupHelper: 'Juga padamkan sandaran laman web.', + deleteDatabaseHelper: 'Juga hapuskan pangkalan data yang berkaitan dengan laman web', + deleteConfirmHelper: `Operasi pemadaman tidak boleh dibatalkan. Masukkan "{0}" untuk mengesahkan pemadaman.`, + staticPath: 'Direktori utama yang sepadan ialah ', + limit: 'Skim', + blog: 'Forum/Blog', + imageSite: 'Laman Gambar', + downloadSite: 'Laman Muat Turun', + shopSite: 'Pusat Membeli-belah', + doorSite: 'Portal', + qiteSite: 'Syarikat', + videoSite: 'Video', + errLog: 'Log Ralat', + stopHelper: + 'Selepas menghentikan laman web, ia tidak akan dapat diakses dengan normal, dan pengguna akan melihat halaman berhenti laman web semasa apabila mengunjunginya. Adakah anda mahu meneruskan?', + startHelper: + 'Selepas mengaktifkan laman web, pengguna boleh mengakses kandungan laman web seperti biasa. Adakah anda mahu meneruskan?', + sitePath: 'Direktori', + siteAlias: 'Alias Laman', + primaryPath: 'Direktori utama', + folderTitle: 'Laman web ini terutamanya mengandungi folder berikut', + wafFolder: 'Peraturan firewall', + indexFolder: 'Direktori akar laman web', + sslFolder: 'Sijil laman web', + enableOrNot: 'Aktifkan', + oldSSL: 'Sijil sedia ada', + manualSSL: 'Import sijil', + select: 'Pilih', + selectSSL: 'Pilih Sijil', + privateKey: 'Kunci (KEY)', + certificate: 'Sijil (format PEM)', + HTTPConfig: 'Pilihan HTTP', + HTTPSOnly: 'Blok permintaan HTTP', + HTTPToHTTPS: 'Alihkan ke HTTPS', + HTTPAlso: 'Benarkan permintaan HTTP langsung', + sslConfig: 'Pilihan SSL', + disableHTTPS: 'Lumpuhkan HTTPS', + disableHTTPSHelper: 'Melumpuhkan HTTPS akan memadam konfigurasi berkaitan sijil. Adakah anda mahu meneruskan?', + SSLHelper: + 'Nota: Jangan gunakan sijil SSL untuk laman web tidak sah.\nJika akses HTTPS tidak dapat digunakan selepas diaktifkan, semak sama ada kumpulan keselamatan telah melepaskan port 443 dengan betul.', + SSLConfig: 'Tetapan sijil', + SSLProConfig: 'Tetapan protokol', + supportProtocol: 'Versi protokol', + encryptionAlgorithm: 'Algoritma penyulitan', + notSecurity: '(tidak selamat)', + encryptHelper: + "Let's Encrypt mempunyai had kekerapan untuk mengeluarkan sijil, tetapi mencukupi untuk memenuhi keperluan biasa. Operasi terlalu kerap akan menyebabkan kegagalan pengeluaran. Untuk sekatan tertentu, sila lihat dokumen rasmi", + ipValue: 'Nilai', + ext: 'sambungan fail', + wafInputHelper: 'Masukkan data secara berbaris, satu baris', + data: 'data', + ever: 'kekal', + nextYear: 'Satu tahun kemudian', + noLog: 'Tiada log ditemui', + defaultServer: 'Tapak lalai', + noDefaultServer: 'Tidak ditetapkan', + defaultServerHelper: + 'Setelah menetapkan laman lalai, semua nama domain dan IP yang tidak terikat akan diarahkan ke laman lalai\nIni dapat mencegah penyelesaian yang jahat secara berkesan\nNamun, ini juga boleh menyebabkan kegagalan penghalang nama domain tanpa kebenaran WAF', + restoreHelper: 'Adakah anda pasti mahu memulihkan menggunakan sandaran ini?', + websiteDeploymentHelper: 'Gunakan aplikasi yang dipasang atau buat aplikasi baharu untuk mencipta laman web.', + websiteStatictHelper: 'Cipta direktori laman web pada hos.', + websiteProxyHelper: + 'Gunakan proksi terbalik untuk memproksi perkhidmatan sedia ada. Contohnya, jika perkhidmatan dipasang dan berjalan pada port 8080, alamat proksi akan menjadi "http://127.0.0.1:8080".', + runtimeProxyHelper: 'Gunakan runtime laman web untuk mencipta laman web.', + runtime: 'Runtime', + deleteRuntimeHelper: 'Aplikasi Runtime perlu dipadamkan bersama laman web, sila berhati-hati', + proxyType: 'Jenis Rangkaian', + unix: 'Rangkaian Unix', + tcp: 'Rangkaian TCP/IP', + phpFPM: 'Konfigurasi FPM', + phpConfig: 'Konfigurasi PHP', + updateConfig: 'Kemas kini Konfigurasi', + isOn: 'Hidup', + isOff: 'Mati', + rewrite: 'Pseudo-statik', + rewriteMode: 'Skema', + current: 'Semasa', + rewriteHelper: + 'Jika menetapkan pseudo-statik menyebabkan laman web tidak dapat diakses, cuba kembali ke tetapan lalai.', + runDir: 'Direktori Jalankan', + runUserHelper: + 'Untuk laman web yang dikerahkan melalui persekitaran runtime kontena PHP, anda perlu menetapkan pemilik dan kumpulan pengguna bagi semua fail dan folder di bawah indeks dan subdirektori ke 1000. Untuk persekitaran PHP tempatan, rujuk tetapan pengguna PHP-FPM tempatan', + userGroup: 'Pengguna/Kumpulan', + uGroup: 'Kumpulan', + proxyPath: 'Laluan Proksi', + proxyPass: 'URL Sasaran', + cache: 'Cache', + cacheTime: 'Tempoh Cache', + enableCache: 'Cache', + proxyHost: 'Hos Proksi', + disabled: 'Dihentikan', + startProxy: 'Ini akan memulakan proksi terbalik. Adakah anda ingin meneruskan?', + stopProxy: 'Ini akan menghentikan proksi terbalik. Adakah anda ingin meneruskan?', + sourceFile: 'Sumber', + proxyHelper1: 'Semasa mengakses direktori ini, kandungan URL sasaran akan dikembalikan dan dipaparkan.', + proxyPassHelper: 'URL sasaran mesti sah dan boleh diakses.', + proxyHostHelper: 'Hantar nama domain dalam header permintaan ke pelayan proksi.', + replacementHelper: 'Hingga 5 penggantian boleh ditambah, sila kosongkan jika tiada penggantian diperlukan.', + modifier: 'Peraturan padanan', + modifierHelper: + 'Contoh: "=" adalah padanan tepat, "~" adalah padanan biasa, "^~" memadankan permulaan laluan, dan sebagainya.', + replace: 'Penggantian Teks', + replaceHelper: + 'Ciri penggantian teks nginx membenarkan penggantian rentetan dalam kandungan respons semasa proksi terbalik. Ia biasanya digunakan untuk mengubah suai pautan, alamat API, dll., dalam HTML, CSS, JavaScript, dan fail lain yang dikembalikan oleh backend. Ia menyokong padanan ungkapan biasa untuk keperluan penggantian kandungan yang kompleks.', + addReplace: 'Tambah', + replaced: 'String Carian (tidak boleh kosong)', + replaceText: 'Ganti dengan string', + replacedErr: 'String Carian tidak boleh kosong', + replacedErr2: 'String Carian tidak boleh berulang', + replacedListEmpty: 'Tiada peraturan penggantian teks', + proxySslName: 'Nama SNI Proksi', + basicAuth: 'Pengesahan Asas', + editBasicAuthHelper: + 'Kata laluan disulitkan secara tidak simetri dan tidak dapat dipaparkan. Penyuntingan perlu menetapkan semula kata laluan', + antiLeech: 'Anti-leech', + extends: 'Pelanjutan', + browserCache: 'Cache', + noModify: 'Tidak Ubah', + serverCache: 'Cache Pelayan', + leechLog: 'Rekod log anti-leech', + accessDomain: 'Domain yang dibenarkan', + leechReturn: 'Sumber tindak balas', + noneRef: 'Benarkan referrer kosong', + disable: 'tidak diaktifkan', + disableLeechHelper: 'Adakah anda ingin mematikan anti-leech', + disableLeech: 'Matikan anti-leech', + ipv6: 'Dengar IPv6', + leechReturnError: 'Sila isikan kod status HTTP', + blockedRef: 'Benarkan referrer tidak standard', + accessControl: 'Kawalan anti-leech', + leechcacheControl: 'Kawalan cache', + logEnableControl: 'Log permintaan aset statik', + leechSpecialValidHelper: + "Apabila 'Benarkan referrer kosong' didayakan, permintaan tanpa referrer (akses terus dan sebagainya) tidak akan disekat; mendayakan 'Benarkan referrer tidak standard' akan membenarkan mana-mana referrer yang tidak bermula dengan http/https (permintaan klien dan sebagainya).", + leechInvalidReturnHelper: 'Kod status HTTP yang dipulangkan selepas menyekat permintaan hotlink', + leechlogControlHelper: + 'Merekod permintaan aset statik; biasanya dimatikan dalam produksi untuk mengelakkan log berlebihan yang tidak perlu', + selectAcme: 'Pilih akaun Acme', + imported: 'Dibuat secara manual', + importType: 'Jenis import', + pasteSSL: 'Tampal kod', + localSSL: 'Pilih fail pelayan', + privateKeyPath: 'Fail kunci peribadi', + certificatePath: 'Fail sijil', + ipWhiteListHelper: 'Peranan senarai putih IP: semua peraturan tidak sah untuk senarai putih IP', + redirect: 'Alihkan', + sourceDomain: 'Domain sumber', + targetURL: 'Alamat URL Sasaran', + keepPath: 'Parameter URI', + path: 'laluan', + redirectType: 'Jenis pengalihan', + redirectWay: 'Cara', + keep: 'menyimpan', + notKeep: 'Jangan simpan', + redirectRoot: 'Alihkan ke halaman utama', + redirectHelper: 'Pengalihan kekal 301, pengalihan sementara 302', + changePHPVersionWarn: + 'Menukar versi PHP akan memadamkan kontena PHP asal (kod laman web yang telah dimuatkan tidak akan hilang), teruskan? ', + changeVersion: 'Tukar versi', + retainConfig: 'Adakah untuk menyimpan fail php-fpm.conf dan php.ini', + runDirHelper2: 'Sila pastikan direktori berjalan sekunder berada di bawah direktori indeks', + openrestyHelper: + 'Port HTTP lalai OpenResty: {0} Port HTTPS: {1}, yang mungkin mempengaruhi akses nama domain laman web dan pengalihan HTTPS paksa', + primaryDomainHelper: 'Contoh: example.com atau example.com:8080', + acmeAccountType: 'Jenis akaun', + keyType: 'Algoritma Kunci', + tencentCloud: 'Tencent Cloud', + containWarn: 'Nama domain mengandungi domain utama, sila masukkan semula', + rewriteHelper2: + 'Aplikasi seperti WordPress yang dipasang dari kedai aplikasi biasanya dilengkapi dengan konfigurasi pseudo-statik praset. Mengkonfigurasi semula mereka boleh menyebabkan ralat.', + websiteBackupWarn: + 'Hanya menyokong pengimportan sandaran tempatan, pengimportan sandaran dari mesin lain boleh menyebabkan kegagalan pemulihan', + ipWebsiteWarn: + 'Laman web dengan IP sebagai nama domain perlu disetkan sebagai laman web lalai untuk diakses secara normal.', + hstsHelper: 'Mengaktifkan HSTS boleh meningkatkan keselamatan laman web', + includeSubDomains: 'SubDomains', + hstsIncludeSubDomainsHelper: + 'Apabila diaktifkan, dasar HSTS akan digunakan pada semua subdomain bagi domain semasa.', + defaultHtml: 'Halaman lalai', + website404: 'Halaman ralat 404 laman web', + domain404: 'Domain laman web tidak wujud', + indexHtml: 'Indeks untuk laman web statik', + stopHtml: 'Laman web dihentikan', + indexPHP: 'Indeks untuk laman web PHP', + sslExpireDate: 'Tarikh Tamat Tempoh Sijil', + website404Helper: + 'Halaman ralat 404 laman web hanya menyokong laman web persekitaran runtime PHP dan laman web statik', + sni: 'Sumber SNI', + sniHelper: + 'Apabila backend proksi terbalik adalah HTTPS, anda mungkin perlu menetapkan sumber SNI. Sila rujuk dokumentasi penyedia perkhidmatan CDN untuk butiran.', + huaweicloud: 'Huawei Cloud', + createDb: 'Cipta Pangkalan Data', + enableSSLHelper: 'Kegagalan mengaktifkan SSL tidak akan menjejaskan penciptaan laman web.', + batchAdd: 'Tambah Domain Secara Batch', + batchInput: 'Input Kelompok', + domainNotFQDN: 'Domain ini mungkin tidak boleh diakses di rangkaian awam', + domainInvalid: 'Format domain tidak sah', + domainBatchHelper: + 'Satu domain per baris, format: domain:port@ssl\nContoh: example.com:443@ssl atau example.com', + generateDomain: 'Hasilkan', + global: 'Global', + subsite: 'Sublaman', + subsiteHelper: + 'Sublaman boleh memilih direktori laman web PHP atau statik yang sedia ada sebagai direktori akar.', + parentWebsite: 'Laman Web Induk', + deleteSubsite: 'Untuk memadam laman web semasa, anda mesti memadam sublaman {0} terlebih dahulu.', + loadBalance: 'Pengimbangan Beban', + server: 'Nod', + algorithm: 'Algoritma', + ipHash: 'IP Hash', + ipHashHelper: + 'Mengagihkan permintaan ke pelayan tertentu berdasarkan alamat IP klien, memastikan klien tertentu sentiasa diarahkan ke pelayan yang sama.', + leastConn: 'Sambungan Terkecil', + leastConnHelper: 'Menghantar permintaan ke pelayan dengan sambungan aktif paling sedikit.', + leastTime: 'Masa Terkecil', + leastTimeHelper: 'Menghantar permintaan ke pelayan dengan masa sambungan aktif terpendek.', + defaultHelper: + 'Kaedah lalai, permintaan diagihkan secara merata ke setiap pelayan. Jika pelayan mempunyai konfigurasi berat, permintaan diagihkan mengikut berat yang ditentukan. Pelayan dengan berat lebih tinggi menerima lebih banyak permintaan.', + weight: 'Berat', + maxFails: 'Kegagalan Maksimum', + maxConns: 'Sambungan Maksimum', + strategy: 'Strategi', + strategyDown: 'Lumpuh', + strategyBackup: 'Sandaran', + ipHashBackupErr: 'Hash IP tidak menyokong nod sandaran', + failTimeout: 'Masa tamat kegagalan', + failTimeoutHelper: + 'Panjang tetingkap masa untuk pemeriksaan kesihatan pelayan. Apabila bilangan kegagalan terkumpul mencapai ambang dalam tempoh ini, pelayan akan dikeluarkan buat sementara waktu dan dicuba semula selepas tempoh yang sama. Lalai 10 saat', + + staticChangePHPHelper: 'Kini laman web statik, boleh ditukar ke laman web PHP.', + proxyCache: 'Cache Proksi Terbalik', + cacheLimit: 'Had Ruang Cache', + shareCache: 'Saiz Memori Kiraan Cache', + cacheExpire: 'Masa Tamat Cache', + shareCacheHelper: '1M memori boleh menyimpan kira-kira 8000 objek cache.', + cacheLimitHelper: 'Melebihi had akan menghapus cache lama secara automatik.', + cacheExpireHelper: 'Cache yang tidak dipenuhi dalam masa tamat akan dihapuskan.', + realIP: 'IP Sebenar', + ipFrom: 'Sumber IP', + ipFromHelper: + 'Dengan mengkonfigurasi sumber IP yang dipercayai, OpenResty akan menganalisis maklumat IP dalam HTTP Header untuk mengenal pasti dan merekodkan alamat IP sebenar pelawat, termasuk dalam log akses.', + ipFromExample1: 'Jika frontend adalah alat seperti Frp, anda boleh mengisi alamat IP Frp, seperti 127.0.0.1.', + ipFromExample2: 'Jika frontend adalah CDN, anda boleh mengisi julat IP CDN.', + ipFromExample3: + 'Jika tidak pasti, anda boleh mengisi 0.0.0.0/0 (IPv4) atau ::/0 (IPv6). [Nota: Membenarkan sebarang sumber IP tidak selamat.]', + http3Helper: + 'HTTP/3 adalah versi naik taraf HTTP/2, menyediakan kelajuan sambungan yang lebih pantas dan prestasi yang lebih baik. Walau bagaimanapun, tidak semua penyemak imbas menyokong HTTP/3, dan mengaktifkannya mungkin menyebabkan beberapa penyemak imbas tidak dapat mengakses laman web.', + cors: 'Perkongsian Sumber Asal Silang (CORS)', + enableCors: 'Dayakan CORS', + allowOrigins: 'Domain yang dibenarkan', + allowMethods: 'Kaedah permintaan yang dibenarkan', + allowHeaders: 'Pengepala permintaan yang dibenarkan', + allowCredentials: 'Benarkan kuki dihantar', + preflight: 'Tindak balas pantas permintaan preflight', + preflightHleper: + 'Apabila didayakan, apabila pelayar menghantar permintaan preflight asal silang (permintaan OPTIONS), sistem akan secara automatik mengembalikan kod status 204 dan menetapkan pengepala respons asal silang yang diperlukan', + + changeDatabase: 'Tukar Pangkalan Data', + changeDatabaseHelper1: 'Perkaitan pangkalan data digunakan untuk sandaran dan pemulihan laman web.', + changeDatabaseHelper2: + 'Menukar ke pangkalan data lain mungkin menyebabkan sandaran sebelumnya tidak dapat dipulihkan.', + saveCustom: 'Simpan sebagai Templat', + rainyun: 'Rainyun', + volcengine: 'Volcengine', + runtimePortHelper: 'Persekitaran runtime semasa mempunyai beberapa port. Sila pilih port proksi.', + runtimePortWarn: 'Persekitaran runtime semasa tidak mempunyai port, tidak dapat proksi', + cacheWarn: 'Sila matikan suis cache dalam pembalikan proksi terlebih dahulu', + loadBalanceHelper: + 'Setelah mencipta pengimbang beban, sila pergi ke "Reverse Proxy", tambahkan proksi dan tetapkan alamat backend ke: http://.', + favorite: 'Kegemaran', + cancelFavorite: 'Batalkan Kegemaran', + useProxy: 'Gunakan Proksi', + useProxyHelper: 'Gunakan alamat pelayan proksi dalam tetapan panel', + westCN: 'West Digital', + openBaseDir: 'Pencegahan Serangan Lintas Situs', + openBaseDirHelper: + 'open_basedir digunakan untuk membatasi jalur akses file PHP, yang membantu mencegah akses lintas situs dan meningkatkan keamanan', + serverCacheTime: 'Masa Cache Pelayan', + serverCacheTimeHelper: + 'Masa permintaan di-cache di pelayan. Semasa tempoh ini, permintaan yang sama akan mengembalikan hasil cache terus tanpa meminta pelayan asal.', + browserCacheTime: 'Masa Cache Pelayar', + browserCacheTimeHelper: + 'Masa sumber statik di-cache secara tempatan di pelayar, mengurangkan permintaan berulang. Pengguna akan menggunakan cache tempatan secara langsung sebelum tamat tempoh semasa menyegarkan halaman.', + donotLinkeDB: 'Jangan Sambungkan Pangkalan Data', + toWebsiteDir: 'Masuk ke Direktori Laman Web', + execParameters: 'Parameter Pelaksanaan', + extCommand: 'Arahan Tambahan', + mirror: 'Sumber Cermin', + execUser: 'Pengguna Melaksanakan', + execDir: 'Direktori Pelaksanaan', + packagist: 'Cermin Penuh China', + + batchOpreate: 'Operasi Pukal', + batchOpreateHelper: 'Pukal {0} laman web, teruskan operasi?', + stream: 'Proksi TCP/UDP', + streamPorts: 'Port Mendengar', + streamPortsHelper: + 'Tetapkan nombor port pendengaran luaran, pelanggan akan mengakses perkhidmatan melalui port ini, dipisahkan dengan koma, cth., 5222,5223', + streamHelper: 'Penerusan Port dan Pengimbangan Beban TCP/UDP', + udp: 'Dayakan UDP', + + syncHtmlHelper: 'Segerakkan ke PHP dan laman web statik', + }, + php: { + short_open_tag: 'Sokongan tag pendek', + max_execution_time: 'Masa maksimum pelaksanaan skrip', + max_input_time: 'Masa input maksimum', + memory_limit: 'Had memori skrip', + post_max_size: 'Saiz maksimum data POST', + file_uploads: 'Sama ada membenarkan muat naik fail', + upload_max_filesize: 'Saiz maksimum fail yang dibenarkan untuk dimuat naik', + max_file_uploads: 'Bilangan maksimum fail yang dibenarkan untuk dimuat naik pada masa yang sama', + default_socket_timeout: 'Masa tamat soket', + error_reporting: 'Tahap kesilapan', + display_errors: 'Sama ada untuk output maklumat ralat terperinci', + cgi_fix_pathinfo: 'Sama ada untuk membuka pathinfo', + date_timezone: 'Zon waktu', + disableFunction: 'Lumpuhkan fungsi', + disableFunctionHelper: + 'Masukkan fungsi yang ingin dilumpuhkan, seperti exec, gunakan pemisah untuk banyak fungsi', + uploadMaxSize: 'Had muat naik', + indexHelper: + 'Untuk memastikan operasi laman web PHP berjalan lancar, sila letakkan kod dalam direktori indeks dan elakkan menamakan semula', + extensions: 'Templat sambungan', + extension: 'Sambungan', + extensionHelper: 'Gunakan pemisah untuk banyak sambungan', + toExtensionsList: 'Lihat senarai sambungan', + containerConfig: 'Konfigurasi Bekas', + containerConfigHelper: + 'Pembolehubah persekitaran dan maklumat lain boleh diubah suai dalam Konfigurasi - Konfigurasi Bekas selepas penciptaan', + dateTimezoneHelper: 'Contoh: TZ=Asia/Shanghai (Sila tambahkan jika perlu)', + }, + nginx: { + serverNamesHashBucketSizeHelper: 'Saiz jadual hash nama pelayan', + clientHeaderBufferSizeHelper: 'Saiz buffer header yang diminta oleh klien', + clientMaxBodySizeHelper: 'Fail muat naik maksimum', + keepaliveTimeoutHelper: 'Masa tamat sambungan', + gzipMinLengthHelper: 'Saiz minimum fail untuk pemampatan', + gzipCompLevelHelper: 'Kadar mampatan', + gzipHelper: 'Aktifkan pemampatan untuk penghantaran', + connections: 'Sambungan aktif', + accepts: 'Diterima', + handled: 'Diuruskan', + requests: 'Permintaan', + reading: 'Membaca', + writing: 'Menulis', + waiting: 'Menunggu', + status: 'Status Semasa', + configResource: 'Konfigurasi', + saveAndReload: 'Simpan dan muat semula', + clearProxyCache: 'Bersihkan cache proksi terbalik', + clearProxyCacheWarn: + 'Semua laman web yang dikonfigurasi dengan cache akan terjejas dan "OpenResty" akan dimulakan semula. Adakah anda mahu meneruskan?', + create: 'Tambah Modul', + update: 'Edit Modul', + params: 'Parameter', + packages: 'Pakej', + script: 'Script', + module: 'Modul', + build: 'Bina', + buildWarn: + 'Membina OpenResty memerlukan menyediakan sejumlah CPU dan memori, dan prosesnya mengambil masa yang lama, sila bersabar.', + mirrorUrl: 'Sumber Perisian', + paramsHelper: 'Contoh: --add-module=/tmp/ngx_brotli', + packagesHelper: 'Contoh: git,curl dipisahkan oleh koma', + scriptHelper: + 'Skrip yang dilaksanakan sebelum penyusunan, biasanya untuk memuat turun sumber kod modul, memasang kebergantungan, dll.', + buildHelper: + 'Klik Bina selepas menambah/mengubah suai modul. Pembinaan yang berjaya akan memulakan semula OpenResty secara automatik.', + defaultHttps: 'HTTPS Anti-tampering', + defaultHttpsHelper1: 'Mengaktifkan ini dapat menyelesaikan masalah tampering HTTPS.', + sslRejectHandshake: 'Tolak jabat tangan SSL lalai', + sslRejectHandshakeHelper: + 'Mengaktifkan ini boleh mengelakkan kebocoran sijil, menetapkan laman web lalai akan membatalkan tetapan ini', + }, + ssl: { + create: 'Permintaan', + provider: 'Jenis', + manualCreate: 'Dicipta secara manual', + acmeAccount: 'Akaun ACME', + resolveDomain: 'Selesaikan nama domain', + err: 'Ralat', + value: 'Nilai rekod', + dnsResolveHelper: 'Sila pergi ke pembekal perkhidmatan resolusi DNS untuk menambah rekod resolusi berikut:', + detail: 'Perincian', + msg: 'Maklumat', + ssl: 'Sijil', + key: 'Kunci peribadi', + startDate: 'Waktu berkuatkuasa', + organization: 'Organisasi penerbit', + renewConfirm: 'Ini akan memperbaharui sijil baru untuk nama domain {0}. Adakah anda mahu meneruskan?', + autoRenew: 'Pembaharuan Automatik', + autoRenewHelper: 'Perbaharui secara automatik 30 hari sebelum tamat tempoh', + renewSuccess: 'Pembaharuan berjaya', + renewWebsite: + 'Sijil ini telah dikaitkan dengan laman web berikut, dan aplikasi akan digunakan pada laman web ini secara serentak', + createAcme: 'Buat Akaun', + acmeHelper: 'Akaun Acme digunakan untuk memohon sijil percuma', + upload: 'Import', + applyType: 'Jenis', + apply: 'Perbaharui', + applyStart: 'Permohonan sijil bermula', + getDnsResolve: 'Mendapatkan nilai resolusi DNS, sila tunggu...', + selfSigned: 'CA Ditandatangani Sendiri', + ca: 'Pihak berkuasa sijil', + commonName: 'Nama biasa', + caName: 'Nama pihak berkuasa sijil', + company: 'Nama organisasi', + department: 'Nama unit organisasi', + city: 'Nama bandar', + province: 'Nama negeri atau wilayah', + country: 'Kod negara (2 huruf)', + commonNameHelper: 'Sebagai contoh, ', + selfSign: 'Keluarkan sijil', + days: 'Tempoh sah', + domainHelper: 'Satu nama domain setiap baris, menyokong * dan alamat IP', + pushDir: 'Tolakkan sijil ke direktori tempatan', + dir: 'Direktori', + pushDirHelper: 'Fail sijil "fullchain.pem" dan fail kunci "privkey.pem" akan dihasilkan dalam direktori ini.', + organizationDetail: 'Butiran organisasi', + fromWebsite: 'Daripada laman web', + dnsMauanlHelper: + 'Dalam mod resolusi manual, anda perlu klik butang mohon selepas penciptaan untuk mendapatkan nilai resolusi DNS', + httpHelper: + 'Menggunakan mod HTTP memerlukan pemasangan OpenResty dan tidak menyokong permohonan sijil domain wildcard.', + buypassHelper: `Buypass tidak boleh diakses di tanah besar China`, + googleHelper: 'Cara mendapatkan EAB HmacKey dan EAB kid', + googleCloudHelper: `Google Cloud API tidak boleh diakses di kebanyakan kawasan tanah besar China`, + skipDNSCheck: 'Langkau semakan DNS', + skipDNSCheckHelper: 'Semak di sini hanya jika anda menghadapi isu tamat masa semasa permintaan pengesahan.', + cfHelper: 'Jangan gunakan Global API Key', + deprecated: 'akan dihentikan', + deprecatedHelper: + 'Penyelenggaraan telah dihentikan dan mungkin akan dibuang dalam versi masa hadapan. Sila gunakan kaedah Tencent Cloud untuk analisis', + disableCNAME: 'Lumpuhkan CNAME', + disableCNAMEHelper: 'Semak di sini jika nama domain mempunyai rekod CNAME dan permintaan gagal.', + nameserver: 'Pelayan DNS', + nameserverHelper: 'Gunakan pelayan DNS tersuai untuk mengesahkan nama domain.', + edit: 'Edit sijil', + execShell: 'Jalankan skrip selepas permintaan pengesahan.', + shell: 'Kandungan skrip', + shellHelper: + 'Direktori pelaksanaan lalai skrip adalah direktori pemasangan 1Panel. Jika sijil ditolak ke direktori tempatan, direktori pelaksanaan akan menjadi direktori tolak sijil. Tamat masa pelaksanaan lalai ialah 30 minit.', + customAcme: 'Perkhidmatan ACME Tersuai', + customAcmeURL: 'URL Perkhidmatan ACME', + baiduCloud: 'Baidu Cloud', + pushNode: 'Segerakan ke Nod Lain', + pushNodeHelper: 'Tolak ke nod terpilih selepas permohonan/pembaharuan', + fromMaster: 'Tolak dari Nod Utama', + hostedZoneID: 'Hosted Zone ID', + isIP: 'Sijil IP', + useEAB: 'Gunakan pengesahan EAB', + }, + firewall: { + create: 'Buat peraturan', + edit: 'Edit peraturan', + ccDeny: 'Perlindungan CC', + ipWhiteList: 'Senarai putih IP', + ipBlockList: 'Senarai blok IP', + fileExtBlockList: 'Senarai blok sambungan fail', + urlWhiteList: 'Senarai putih URL', + urlBlockList: 'Senarai blok URL', + argsCheck: 'Pengesahan parameter GET', + postCheck: 'Pengesahan parameter POST', + cookieBlockList: 'Senarai blok Cookie', + + dockerHelper: + 'Firewall semasa tidak boleh melumpuhkan pemetaan port bekas. Aplikasi yang dipasang boleh pergi ke halaman [Dipasang] untuk mengedit parameter aplikasi dan mengkonfigurasi peraturan pelepasan port.', + iptablesHelper: + 'Dikesan sistem menggunakan firewall {0}. Untuk beralih ke iptables, sila nyahpasang secara manual dahulu!', + quickJump: 'Akses pantas', + used: 'Digunakan', + unUsed: 'Tidak Digunakan', + dockerRestart: 'Operasi firewall memerlukan memulakan semula perkhidmatan Docker', + firewallHelper: '{0} firewall sistem', + firewallNotStart: `Firewall sistem belum diaktifkan. Aktifkannya dahulu.`, + restartFirewallHelper: 'Operasi ini akan memulakan semula firewall semasa. Adakah anda mahu meneruskan?', + stopFirewallHelper: + 'Ini akan menyebabkan pelayan kehilangan perlindungan keselamatan. Adakah anda mahu meneruskan?', + startFirewallHelper: + 'Selepas firewall diaktifkan, keselamatan pelayan boleh dilindungi dengan lebih baik. Adakah anda mahu meneruskan?', + noPing: 'Lumpuhkan ping', + enableBanPing: 'Sekat Ping', + disableBanPing: 'Nyahsekat Ping', + noPingTitle: 'Lumpuhkan ping', + noPingHelper: `Ini akan melumpuhkan ping, dan pelayan tidak akan memberikan tindak balas ICMP. Adakah anda mahu meneruskan?`, + onPingHelper: + 'Ini akan mengaktifkan ping, dan penggodam mungkin menemui pelayan anda. Adakah anda mahu meneruskan?', + changeStrategy: 'Tukar strategi {0}', + changeStrategyIPHelper1: + 'Tukar strategi alamat IP kepada [deny]. Selepas alamat IP ditetapkan, akses kepada pelayan dilarang. Adakah anda mahu meneruskan?', + changeStrategyIPHelper2: + 'Tukar strategi alamat IP kepada [allow]. Selepas alamat IP ditetapkan, akses normal dipulihkan. Adakah anda mahu meneruskan?', + changeStrategyPortHelper1: + 'Tukar dasar port kepada [drop]. Selepas dasar port ditetapkan, akses luaran ditolak. Adakah anda mahu meneruskan?', + changeStrategyPortHelper2: + 'Tukar dasar port kepada [accept]. Selepas dasar port ditetapkan, akses port biasa akan dipulihkan. Adakah anda mahu meneruskan?', + stop: 'Hentikan', + portFormatError: 'Medan ini mesti port yang sah.', + portHelper1: 'Pelbagai port, contohnya 8080 dan 8081', + portHelper2: 'Port rentang, contohnya 8080-8089', + changeStrategyHelper: + 'Tukar strategi {0} [{1}] kepada [{2}]. Selepas tetapan, {0} akan mengakses {2} secara luaran. Adakah anda mahu meneruskan?', + + strategy: 'Strategi', + accept: 'Terima', + drop: 'Lumpuhkan', + anyWhere: 'Mana-mana', + address: 'Alamat IP tertentu', + addressHelper: 'Sokong alamat IP atau segmen IP', + allow: 'Benarkan', + deny: 'Tolak', + addressFormatError: 'Medan ini mesti alamat IP yang sah.', + addressHelper1: 'Sokong alamat IP atau julat IP. Sebagai contoh, "172.16.10.11" atau "172.16.10.0/24".', + addressHelper2: 'Untuk pelbagai alamat IP, pisahkan dengan koma. Contohnya, "172.16.10.11, 172.16.0.0/24".', + allIP: 'Semua IP', + portRule: 'Peraturan | Peraturan', + createPortRule: '@:commons.button.create @.lower:firewall.portRule', + forwardRule: 'Peraturan Pemajuan Port | Peraturan Pemajuan Port', + createForwardRule: '@:commons.button.create @:firewall.forwardRule', + ipRule: 'Peraturan IP | Peraturan IP', + createIpRule: '@:commons.button.create @:firewall.ipRule', + userAgent: 'Penapis User-Agent', + sourcePort: 'Port sumber', + targetIP: 'IP sasaran', + targetPort: 'Port sasaran', + forwardHelper1: 'Jika anda ingin memajukan ke port tempatan, IP sasaran harus ditetapkan kepada "127.0.0.1".', + forwardHelper2: 'Biarkan IP sasaran kosong untuk memajukan ke port tempatan.', + forwardPortHelper: 'Menyokong julat port, cth: 8080-8089', + forwardInboundInterface: 'Antara Muka Rangkaian Masukan Penerusan', + exportHelper: 'Akan mengeksport {0} peraturan firewall. Teruskan?', + importSuccess: '{0} peraturan berjaya diimport', + importPartialSuccess: 'Import selesai: {0} berjaya, {1} gagal', + + ipv4Limit: 'Operasi semasa hanya menyokong alamat IPv4', + basicStatus: 'Rantaian semasa {0} tidak terikat, sila ikat dahulu!', + baseIptables: 'Perkhidmatan iptables', + forwardIptables: 'Perkhidmatan Penerusan Port iptables', + advanceIptables: 'Perkhidmatan Konfigurasi Lanjutan iptables', + initMsg: 'Akan memulakan {0}, teruskan?', + initHelper: 'Mengesan {0} tidak dimulakan. Sila klik butang pemulaan di bar status atas untuk mengkonfigurasi!', + bindHelper: 'Ikat - Peraturan firewall hanya akan berkuat kuasa apabila status terikat. Sahkan?', + unbindHelper: + 'Nyahikat - Apabila tidak terikat, semua peraturan firewall yang ditambah akan menjadi tidak sah. Teruskan dengan berhati-hati. Sahkan?', + defaultStrategy: 'Dasar lalai untuk rantaian semasa {0} adalah {1}', + defaultStrategy2: + 'Dasar lalai untuk rantaian semasa {0} adalah {1}, status semasa adalah tidak terikat. Peraturan firewall yang ditambah akan berkuat kuasa selepas pengikatan!', + filterRule: 'Peraturan Penapis', + filterHelper: + 'Peraturan penapis membolehkan anda mengawal trafik rangkaian pada tahap INPUT/OUTPUT. Konfigurasikan dengan berhati-hati untuk mengelakkan mengunci sistem.', + chain: 'Rantai', + targetChain: 'Rantai Sasaran', + sourceIP: 'IP Sumber', + destIP: 'IP Destinasi', + inboundDirection: 'Arah Masuk', + outboundDirection: 'Arah Keluar', + destPort: 'Port Destinasi', + action: 'Tindakan', + reject: 'Tolak', + sourceIPHelper: 'Format CIDR, cth. 192.168.1.0/24. Biarkan kosong untuk semua alamat', + destIPHelper: 'Format CIDR, cth. 10.0.0.0/8. Biarkan kosong untuk semua alamat', + portHelper: '0 bermaksud mana-mana port', + allPorts: 'Semua Port', + deleteRuleConfirm: 'Akan memadam {0} peraturan. Teruskan?', + }, + runtime: { + runtime: 'Runtime', + workDir: 'Direktori kerja', + create: 'Cipta runtime', + localHelper: + 'Untuk masalah pemasangan persekitaran tempatan dan penggunaan persekitaran luar talian, sila rujuk ', + versionHelper: 'Versi PHP, contohnya v8.0', + buildHelper: + 'Semakin banyak sambungan, semakin tinggi penggunaan CPU semasa membuat imej. Sambungan boleh dipasang selepas persekitaran dibuat.', + openrestyWarn: 'PHP perlu dinaik taraf kepada OpenResty versi 1.21.4.1 atau lebih tinggi untuk digunakan', + toupgrade: 'Naik Taraf', + edit: 'Edit runtime', + extendHelper: + 'Sambungan yang tidak disenaraikan boleh dimasukkan dan dipilih secara manual. Sebagai contoh, masukkan "sockets" dan pilih pilihan pertama dari senarai juntai bawah untuk melihat senarai sambungan.', + rebuildHelper: 'Selepas mengedit sambungan, anda perlu membina semula aplikasi PHP untuk ia berkesan', + rebuild: 'Bina Semula Aplikasi PHP', + source: 'Sumber sambungan PHP', + ustc: 'Universiti Sains dan Teknologi China', + netease: 'Netease', + aliyun: 'Alibaba Cloud', + default: 'Default', + tsinghua: 'Universiti Tsinghua', + xtomhk: 'Stesen Cermin XTOM (Hong Kong)', + xtom: 'Stesen Cermin XTOM (Global)', + phpsourceHelper: 'Pilih sumber yang sesuai mengikut persekitaran rangkaian anda.', + appPort: 'Port aplikasi', + externalPort: 'Port luaran', + packageManager: 'Pengurus pakej', + codeDir: 'Direktori kod', + appPortHelper: 'Port yang digunakan oleh aplikasi.', + externalPortHelper: 'Port yang terdedah kepada dunia luar.', + runScript: 'Skrip run', + runScriptHelper: 'Senarai arahan permulaan diuraikan dari fail package.json dalam direktori sumber.', + open: 'Buka', + operatorHelper: + 'Operasi {0} akan dilakukan pada persekitaran operasi yang dipilih. Adakah anda mahu meneruskan?', + taobao: 'Taobao', + tencent: 'Tencent', + imageSource: 'Sumber imej', + moduleManager: 'Pengurusan Modul', + module: 'Modul', + nodeOperatorHelper: + 'Adakah {0} {1} modul? Operasi ini mungkin menyebabkan ketidaknormalan dalam persekitaran operasi. Sila pastikan sebelum meneruskan', + customScript: 'Arahan permulaan tersuai', + customScriptHelper: + 'Sila masukkan arahan permulaan penuh, contoh: npm run start. Untuk arahan permulaan PM2, sila gantikan dengan pm2-runtime, jika tidak ia akan gagal bermula.', + portError: 'Jangan ulangi port yang sama.', + systemRestartHelper: 'Huraian status: Gangguan - status gagal diperoleh kerana sistem dimulakan semula', + javaScriptHelper: 'Sediakan arahan permulaan penuh. Contohnya, "java -jar halo.jar -Xmx1024M -Xms256M".', + javaDirHelper: 'Direktori mesti mengandungi fail jar, subdirektori juga diterima', + goHelper: 'Sediakan arahan permulaan penuh. Contohnya, "go run main.go" atau "./main".', + goDirHelper: 'Direktori atau subdirektori mesti mengandungi fail Go atau binari.', + pythonHelper: + 'Sediakan arahan permulaan penuh. Contohnya, "pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000".', + dotnetHelper: 'Sila isi arahan pelancaran lengkap, contohnya dotnet MyWebApp.dll', + dirHelper: 'Nota: Sila isi laluan direktori di dalam bekas', + concurrency: 'Skim Serentak', + loadStatus: 'Status Beban', + extraHosts: 'Pemetaan hos', + }, + process: { + pid: 'Process ID', + ppid: 'Parent PID', + numThreads: 'Threads', + memory: 'Memory', + diskRead: 'Disk read', + diskWrite: 'Disk write', + netSent: 'uplink', + netRecv: 'downstream', + numConnections: 'Connections', + startTime: 'Start time', + state: 'State', + running: 'Running', + sleep: 'sleep', + stop: 'stop', + idle: 'idle', + zombie: 'zombie process', + wait: 'waiting', + lock: 'lock', + blocked: 'blocked', + cmdLine: 'Start command', + basic: 'Basic', + mem: 'Memory', + openFiles: 'Open files', + env: 'Environments', + noenv: 'None', + net: 'Network connections', + laddr: 'Source address/port', + raddr: 'Destination address/port', + stopProcess: 'End', + viewDetails: 'Details', + stopProcessWarn: 'Are you sure you want to end this process (PID:{0})?', + kill: 'Hentikan Proses', + killNow: 'Hentikan Sekarang', + killHelper: + 'Menghentikan proses {0} mungkin menyebabkan beberapa program tidak berfungsi dengan normal. Teruskan?', + processName: 'Process name', + }, + tool: { + supervisor: { + loadStatusErr: 'Gagal mendapatkan status proses, sila semak status perkhidmatan supervisor.', + notSupport: + 'Perkhidmatan Supervisor tidak dikesan. Sila pergi ke halaman pustaka skrip untuk pemasangan manual', + list: 'Proses Daemon | Proses Daemon', + config: 'Konfigurasi Supervisor', + primaryConfig: 'Lokasi fail konfigurasi utama', + notSupportCtl: 'supervisorctl tidak dikesan. Sila pergi ke halaman pustaka skrip untuk pemasangan manual.', + user: 'Pengguna', + command: 'Perintah', + dir: 'Direktori', + numprocs: 'Bil. proses', + initWarn: + 'Ini akan mengubah nilai "files" dalam bahagian "[include]" dalam fail konfigurasi utama. Direktori fail konfigurasi lain akan menjadi: "{direktori pemasangan 1Panel}/1panel/tools/supervisord/supervisor.d/".', + operatorHelper: 'Operasi {1} akan dilakukan pada {0}, teruskan?', + uptime: 'Masa berjalan', + notStartWarn: 'Supervisor belum dimulakan. Mulakan dahulu.', + serviceName: 'Nama perkhidmatan', + initHelper: + 'Perkhidmatan Supervisor dikesan tetapi belum dimulakan. Sila klik butang permulaan di bar status atas untuk konfigurasi.', + serviceNameHelper: + 'Nama perkhidmatan Supervisor yang diuruskan oleh systemctl, biasanya supervisor atau supervisord', + restartHelper: + 'Ini akan memulakan semula perkhidmatan selepas inisialisasi, menyebabkan semua proses daemon sedia ada berhenti.', + RUNNING: 'Berjalan', + STOPPED: 'Berhenti', + STOPPING: 'Sedang Berhenti', + STARTING: 'Sedang Bermula', + FATAL: 'Gagal bermula', + BACKOFF: 'Pengecualian permulaan', + ERROR: 'Ralat', + statusCode: 'Kod status', + manage: 'Pengurusan', + autoRestart: 'Auto Restart', + EXITED: 'Telah keluar', + autoRestartHelper: + 'Sama ada untuk memulakan semula program secara automatik selepas ia tamat secara luar jangka', + autoStart: 'Mula Automatik', + autoStartHelper: 'Sama ada untuk memulakan perkhidmatan secara automatik selepas Supervisor mula', + }, + }, + disk: { + systemDisk: 'Cakera Sistem', + unpartitionedDisk: 'Cakera Tidak Dibahagikan', + handlePartition: 'Bahagikan Sekarang', + filesystem: 'Sistem Fail', + unmounted: 'Tidak Dikaitkan', + cannotOperate: 'Tidak Boleh Beroperasi', + systemDiskHelper: 'Petunjuk: Cakera semasa adalah cakera sistem, tidak boleh dioperasikan.', + autoMount: 'Pemasangan Automatik', + model: 'Model Peranti', + diskType: 'Jenis Cakera', + serial: 'Nombor Siri', + noFail: 'Kegagalan pemasangan tidak menjejaskan permulaan sistem', + }, + xpack: { + expiresTrialAlert: + 'Peringatan mesra: Percubaan Pro anda akan tamat dalam {0} hari, dan semua ciri Pro tidak lagi dapat diakses. Sila perbaharui atau naik taraf ke versi penuh tepat pada masanya.', + expiresAlert: + 'Peringatan mesra: Lesen Pro anda akan tamat dalam {0} hari, dan semua ciri Pro tidak lagi dapat diakses. Sila perbaharui segera untuk memastikan penggunaan berterusan.', + menu: 'Pro', + upage: 'Pembina Laman Web AI', + proAlert: 'Tingkatkan ke Pro untuk menggunakan ciri ini', + app: { + app: 'APP', + title: 'Nama Panel', + titleHelper: 'Alias panel digunakan untuk paparan di APP (alias panel lalai)', + qrCode: 'Kod QR', + apiStatusHelper: 'APP Panel perlu mengaktifkan fungsi API', + apiInterfaceHelper: + 'Menyokong akses antara muka API panel (fungsi ini perlu diaktifkan untuk aplikasi panel)', + apiInterfaceHelper1: + 'Akses aplikasi panel memerlukan penambahan pelawat ke dalam senarai putih, untuk IP yang tidak tetap, disyorkan untuk menambah 0.0.0.0/0(semua IPv4), ::/0 (semua IPv6)', + qrCodeExpired: 'Masa penyegaran', + apiLeakageHelper: 'Jangan dedahkan kod QR. Pastikan ia hanya digunakan di persekitaran yang dipercayai.', + }, + waf: { + name: 'WAF', + blackWhite: 'Senarai Hitam dan Putih', + globalSetting: 'Tetapan Global', + websiteSetting: 'Tetapan Laman Web', + blockRecords: 'Rekod Diblok', + world: 'Dunia', + china: 'China', + intercept: 'Sekatan', + request: 'Permintaan', + count4xx: 'Kuantiti 4xx', + count5xx: 'Kuantiti 5xx', + todayStatus: 'Status Hari Ini', + reqMap: 'Peta Serangan (30 Hari Lepas)', + resource: 'Sumber', + count: 'Kuantiti', + hight: 'Tinggi', + low: 'Rendah', + reqCount: 'Permintaan', + interceptCount: 'Jumlah Sekatan', + requestTrends: 'Tren Permintaan (7 Hari Lepas)', + interceptTrends: 'Tren Sekatan (7 Hari Lepas)', + whiteList: 'Senarai Putih', + blackList: 'Senarai Hitam', + ipBlackListHelper: 'Alamat IP dalam senarai hitam disekat daripada mengakses laman web', + ipWhiteListHelper: 'Alamat IP dalam senarai putih akan melepasi semua sekatan', + uaBlackListHelper: 'Permintaan dengan nilai User-Agent dalam senarai hitam akan disekat', + uaWhiteListHelper: 'Permintaan dengan nilai User-Agent dalam senarai putih akan melepasi semua sekatan', + urlBlackListHelper: 'Permintaan ke URL dalam senarai hitam akan disekat', + urlWhiteListHelper: 'Permintaan ke URL dalam senarai putih akan melepasi semua sekatan', + ccHelper: + 'Jika laman web menerima lebih daripada {1} permintaan daripada IP yang sama dalam {0} saat, IP akan disekat selama {2}', + blockTime: 'Tempoh Sekatan', + attackHelper: 'Jika sekatan terkumpul melebihi {1} dalam {0} saat, IP akan disekat selama {2}', + notFoundHelper: + 'Jika permintaan yang mengembalikan ralat 404 lebih daripada {1} kali dalam {0} saat, IP akan disekat selama {2}', + frequencyLimit: 'Had Kekerapan', + regionLimit: 'Had Wilayah', + defaultRule: 'Peraturan Lalai', + accessFrequencyLimit: 'Had Kekerapan Akses', + attackLimit: 'Had Kekerapan Serangan', + notFoundLimit: 'Had Kekerapan 404', + urlLimit: 'Had Kekerapan URL', + urlLimitHelper: 'Tetapkan kekerapan akses untuk URL tunggal', + sqliDefense: 'Perlindungan Suntikan SQL', + sqliHelper: 'Mengesan suntikan SQL dalam permintaan dan menyekatnya', + xssHelper: 'Mengesan XSS dalam permintaan dan menyekatnya', + xssDefense: 'Perlindungan XSS', + uaDefense: 'Peraturan User-Agent Berbahaya', + uaHelper: 'Termasuk peraturan untuk mengenal pasti bot berbahaya biasa', + argsDefense: 'Peraturan Parameter Berbahaya', + argsHelper: 'Menyekat permintaan yang mengandungi parameter berbahaya', + cookieDefense: 'Peraturan Cookie Berbahaya', + cookieHelper: 'Melarang cookie berbahaya daripada dibawa dalam permintaan', + headerDefense: 'Peraturan Header Berbahaya', + headerHelper: 'Melarang permintaan daripada mengandungi header berbahaya', + httpRule: 'Peraturan Kaedah Permintaan HTTP', + httpHelper: + 'Tetapkan jenis kaedah yang dibenarkan untuk diakses. Jika anda ingin mengehadkan jenis akses tertentu, sila matikan jenis butang ini. Contoh: hanya akses jenis GET dibenarkan, maka anda perlu matikan butang lain selain GET', + geoRule: 'Sekatan Akses Wilayah', + geoHelper: + 'Sekat akses ke laman web anda dari wilayah tertentu, sebagai contoh: jika akses dibenarkan dari China Tanah Besar, maka permintaan dari luar China Tanah Besar akan disekat', + ipLocation: 'Lokasi IP', + action: 'Tindakan', + ruleType: 'Jenis Serangan', + ipHelper: 'Masukkan alamat IP', + attackLog: 'Log Serangan', + rule: 'Peraturan', + ipArr: 'Julat IPV4', + ipStart: 'IP Mula', + ipEnd: 'IP Akhir', + ipv4: 'IPv4', + ipv6: 'IPv6', + urlDefense: 'Peraturan URL', + urlHelper: 'URL Dilarang', + dirFilter: 'Penapis Direktori', + sqlInject: 'Suntikan SQL', + xss: 'XSS', + phpExec: 'Pelaksanaan Skrip PHP', + oneWordTrojan: 'Trojan Satu Perkataan', + appFilter: 'Penapisan Direktori Berbahaya', + webshell: 'Webshell', + args: 'Parameter Berbahaya', + protocolFilter: 'Penapis Protokol', + javaFilter: 'Penapisan Fail Berbahaya Java', + scannerFilter: 'Penapis Pencari', + escapeFilter: 'Penapis Pengekalan', + customRule: 'Peraturan Kustom', + httpMethod: 'Penapis Kaedah HTTP', + fileExt: 'Had Muat Naik Fail', + fileExtHelper: 'Lanjaran fail yang dilarang untuk dimuat naik', + deny: 'Dilarang', + allow: 'Benarkan', + field: 'Objek', + pattern: 'Keadaan', + ruleContent: 'Kandungan', + contain: 'termasuk', + equal: 'sama dengan', + regex: 'peraturan biasa', + notEqual: 'Tidak sama dengan', + customRuleHelper: 'Ambil tindakan berdasarkan keadaan yang ditetapkan', + actionAllow: 'Benarkan', + blockIP: 'Sekat IP', + code: 'Kod Status Kembali', + noRes: 'Putuskan sambungan (444)', + badReq: 'Parameter Tidak Sah (400)', + forbidden: 'Akses Dilarang (403)', + serverErr: 'Ralat Server (500)', + resHtml: 'Halaman Respons', + allowHelper: 'Membenarkan akses akan melangkau peraturan WAF seterusnya, sila gunakan dengan berhati-hati', + captcha: 'verifikasi manusia-mesin', + fiveSeconds: 'Verifikasi 5-Saat', + location: 'Wilayah', + redisConfig: 'Konfigurasi Redis', + redisHelper: 'Aktifkan Redis untuk menyimpan IP yang disekat sementara', + wafHelper: 'Semua laman web akan kehilangan perlindungan selepas menutup', + attackIP: 'IP Serangan', + attackParam: 'Butiran Serangan', + execRule: 'Peraturan Dilanggar', + acl: 'ACL', + sql: 'Suntikan SQL', + cc: 'Had Kekerapan Akses', + isBlocking: 'Disekat', + isFree: 'Tidak Disekat', + unLock: 'Buka Kunci', + unLockHelper: 'Adakah anda mahu membuka kunci IP: {0}?', + saveDefault: 'Simpan Lalai', + saveToWebsite: 'Terapkan ke Laman Web', + saveToWebsiteHelper: 'Terapkan tetapan semasa ke semua laman web?', + websiteHelper: + 'Ini adalah tetapan lalai untuk mencipta laman web. Pengubahsuaian perlu diterapkan ke laman web untuk berkuat kuasa', + websiteHelper2: + 'Ini adalah tetapan lalai untuk mencipta laman web. Sila ubah suai konfigurasi tertentu di laman web', + ipGroup: 'Kumpulan IP', + ipGroupHelper: + 'Satu IP atau segmen IP setiap baris, menyokong IPv4 dan IPv6, sebagai contoh: 192.168.1.1 atau 192.168.1.0/24', + ipBlack: 'Senarai Hitam IP', + openRestyAlert: 'Versi OpenResty perlu lebih tinggi daripada {0}', + initAlert: + 'Penyediaan diperlukan untuk penggunaan kali pertama, fail konfigurasi laman web akan diubah, dan konfigurasi WAF asal akan hilang. Sila buat sandaran OpenResty terlebih dahulu', + initHelper: + 'Operasi penyediaan akan membersihkan konfigurasi WAF yang sedia ada. Adakah anda pasti mahu menyediakan semula?', + mainSwitch: 'Suis Utama', + websiteAlert: 'Sila cipta laman web terlebih dahulu', + defaultUrlBlack: 'Peraturan URL', + htmlRes: 'Halaman Sekat', + urlSearchHelper: 'Sila masukkan URL untuk sokongan carian samar', + toCreate: 'Cipta', + closeWaf: 'Tutup WAF', + closeWafHelper: 'Menutup WAF akan menyebabkan laman web kehilangan perlindungan, adakah anda mahu teruskan', + addblack: 'Hitam', + addwhite: 'Tambah putih', + addblackHelper: 'Tambah IP:{0} ke senarai hitam lalai?', + addwhiteHelper: 'Tambah IP:{0} ke senarai putih lalai?', + defaultUaBlack: 'Peraturan User-Agent', + defaultIpBlack: 'Kumpulan IP Berbahaya', + cookie: 'Peraturan Cookie', + urlBlack: 'Senarai Hitam URL', + uaBlack: 'Senarai Hitam User-Agent', + attackCount: 'Had Kekerapan Serangan', + fileExtCheck: 'Had Muat Naik Fail', + geoRestrict: 'Sekatan Akses Wilayah', + attacklog: 'Rekod Sekat', + unknownWebsite: 'Akses domain yang tidak sah', + geoRuleEmpty: 'Wilayah tidak boleh kosong', + unknown: 'Laman Web Tidak Wujud', + geo: 'Sekatan Wilayah', + revertHtml: 'Adakah anda mahu memulihkan {0} sebagai halaman lalai?', + five_seconds: 'Verifikasi 5-Saat', + header: 'Peraturan Header', + methodWhite: 'Peraturan HTTP', + expiryDate: 'Tarikh Tamat', + expiryDateHelper: 'Selepas lulus verifikasi, ia tidak akan disahkan lagi dalam tempoh sah', + defaultIpBlackHelper: 'Beberapa IP berbahaya yang dikumpul dari Internet untuk mencegah akses', + notFoundCount: 'Had Kekerapan 404', + matchValue: 'Nilai Padanan', + headerName: 'Menyokong Bahasa Inggeris, nombor, -, panjang 3-30', + cdnHelper: 'Laman web yang menggunakan CDN boleh dibuka di sini untuk mendapatkan IP sumber yang betul', + clearLogWarn: 'Pembersihan log tidak akan mungkin, adakah anda mahu meneruskan?', + commonRuleHelper: 'Peraturan adalah padanan samar', + blockIPHelper: + 'IP yang disekat disimpan sementara dalam OpenResty dan akan dibuka kunci apabila anda memulakan semula OpenResty. Mereka boleh disekat secara kekal melalui fungsi sekatan', + addWhiteUrlHelper: 'Tambah URL {0} ke senarai putih?', + dashHelper: 'Versi komuniti juga boleh menggunakan fungsi dalam tetapan global dan tetapan laman web', + wafStatusHelper: 'WAF tidak diaktifkan, sila aktifkan dalam tetapan global', + ccMode: 'Mod', + global: 'Mod Global', + uriMode: 'Mod URL', + globalHelper: + 'Mod Global: Digerakkan apabila jumlah permintaan ke mana-mana URL dalam unit masa melebihi had', + uriModeHelper: 'Mod URL: Digerakkan apabila jumlah permintaan ke satu URL dalam unit masa melebihi had', + + ip: 'Senarai Hitam IP', + globalSettingHelper: + 'Tetapan dengan tag [Laman Web] perlu diaktifkan dalam [Tetapan Laman Web], dan tetapan global hanya tetapan lalai untuk laman web yang baru dicipta', + globalSettingHelper2: + 'Tetapan perlu diaktifkan dalam kedua-dua [Tetapan Global] dan [Tetapan Laman Web] pada masa yang sama', + urlCCHelper: '{1} kepingatan permintaan URL ini dalam {0} saat, menyekat IP ini {2}', + urlCCHelper2: 'URL tidak boleh mengandung parameter', + notContain: 'Tidak mengandung', + urlcc: 'Had frekuensi URL', + method: 'Jenis permintaan', + addIpsToBlock: 'Sekat IP secara pukal', + addUrlsToWhite: 'Tambah URL ke senarai putih secara pukal', + noBlackIp: 'IP telah disekat, tidak perlu disekat semula', + noWhiteUrl: 'URL telah dimasukkan ke senarai putih, tidak perlu ditambah semula', + spiderIpHelper: + 'Termasuk Baidu, Bing, Google, 360, Shenma, Sogou, ByteDance, DuckDuckGo, Yandex. Menutup ini akan menyekat semua akses labah-labah.', + spiderIp: 'Kolam IP labah-labah', + geoIp: 'Pustaka Alamat IP', + geoIpHelper: 'Digunakan untuk mengesahkan lokasi geografi IP', + stat: 'Laporan Serangan', + statTitle: 'Laporan', + attackIp: 'IP Serangan', + attackCountNum: 'Bilangan Serangan', + percent: 'Peratusan', + addblackUrlHelper: 'Adakah anda mahu menambah URL: {0} ke senarai hitam lalai?', + rce: 'Pelaksanaan Kod Jarak Jauh', + software: 'Perisian', + cveHelper: 'Mengandungi kelemahan biasa perisian dan rangka kerja', + vulnCheck: 'Peraturan Tambahan', + ssrf: 'Kelemahan SSRF', + afr: 'Pembacaan Fail Arbitrari', + ua: 'Akses Tanpa Kebenaran', + id: 'Pendedahan Maklumat', + aa: 'Mengelakkan Pengesahan', + dr: 'Penembusan Direktori', + xxe: 'Kelemahan XXE', + suid: 'Kelemahan Serialisasi', + dos: 'Kelemahan Penafian Perkhidmatan', + afd: 'Muat Turun Fail Arbitrari', + sqlInjection: 'Tambahan SQL', + afw: 'Penulisan Fail Arbitrari', + il: 'Pendedahan Maklumat', + clearAllLog: 'Kosongkan Semua Log', + exportLog: 'Eksport Log', + appRule: 'Peraturan Aplikasi', + appRuleHelper: + 'Peraturan aplikasi biasa, membolehkan boleh mengurangkan positif palsu, satu laman web hanya boleh menggunakan satu peraturan', + logExternal: 'Kecualikan Jenis Rekod', + ipWhite: 'Senarai Putih IP', + urlWhite: 'Senarai Putih URL', + uaWhite: 'Senarai Putih User-Agent', + logExternalHelper: + 'Jenis rekod yang dikecualikan tidak akan direkodkan dalam log, senarai hitam/putih, sekatan akses wilayah, dan peraturan tersuai akan menghasilkan banyak log, disarankan untuk mengeluarkan', + ssti: 'Serangan SSTI', + crlf: 'Penyuntikan CRLF', + strict: 'Mod Strict', + strictHelper: 'Gunakan peraturan yang lebih ketat untuk mengesahkan permintaan', + saveLog: 'Simpan Log', + remoteURLHelper: 'URL jauh perlu memastikan satu IP setiap baris dan tiada aksara lain', + notFound: 'Not Found (404)', + serviceUnavailable: 'Perkhidmatan Tidak Tersedia (503)', + gatewayTimeout: 'Timeout Gateway (504)', + belongToIpGroup: 'Tergolong dalam Kumpulan IP', + notBelongToIpGroup: 'Tidak tergolong dalam Kumpulan IP', + unknownWebsiteKey: 'Domain Tidak Diketahui', + special: 'Peraturan Khas', + fileToLarge: 'Fail melebihi 1MB dan tidak dapat dimuat naik', + uploadOverLimit: 'Fail yang dimuat naik melebihi had kuantiti, maksimum 1 fail', + importRuleHelper: 'Satu peraturan per baris', + }, + monitor: { + name: 'Pemantauan Laman Web', + pv: 'Paparan Halaman', + uv: 'Pelawat Unik', + flow: 'Aliran Trafik', + ip: 'IP', + spider: 'Spider', + visitors: 'Tren Pelawat', + today: 'Hari Ini', + last7days: '7 Hari Terakhir', + last30days: '30 Hari Terakhir', + uvMap: 'Peta Pelawat (30th)', + qps: 'Permintaan Masa Sebenar (setiap minit)', + flowSec: 'Trafik Masa Sebenar (setiap minit)', + excludeCode: 'Kecualikan Kod Status', + excludeUrl: 'Kecualikan URL', + excludeExt: 'Kecualikan Sambungan', + cdnHelper: 'Dapatkan IP sebenar dari Header yang disediakan oleh CDN', + reqRank: 'Peringkat Lawatan', + refererDomain: 'Domain Rujukan', + os: 'Sistem', + browser: 'Pelayar/Klien', + device: 'Peranti', + showMore: 'Lebih', + unknown: 'Lain-lain', + pc: 'Komputer', + mobile: 'Peranti Mudah Alih', + wechat: 'WeChat', + machine: 'Mesin', + tencent: 'Pelayar Tencent', + ucweb: 'Pelayar UC', + '2345explorer': 'Pelayar 2345', + huaweibrowser: 'Pelayar Huawei', + log: 'Log Permintaan', + statusCode: 'Kod Status', + requestTime: 'Masa Respons', + flowRes: 'Trafik Respons', + method: 'Kaedah Permintaan', + statusCodeHelper: 'Masukkan kod status di atas', + statusCodeError: 'Jenis kod status tidak sah', + methodHelper: 'Masukkan kaedah permintaan di atas', + all: 'Semua', + baidu: 'Baidu', + google: 'Google', + bing: 'Bing', + bytes: 'Tajuk Hari Ini', + sogou: 'Sogou', + failed: 'Ralat', + ipCount: 'Kiraan IP', + spiderCount: 'Permintaan Spider', + averageReqTime: 'Masa Respons Purata', + totalFlow: 'Jumlah Trafik', + logSize: 'Saiz Fail Log', + realIPType: 'Kaedah pemerolehan IP sebenar', + fromHeader: 'Dapatkan dari Header HTTP', + fromHeaders: 'Dapatkan dari senarai Header', + header: 'Header HTTP', + cdnConfig: 'Konfigurasi CDN', + xff1: 'Proksi Tahap Pertama dari X-Forwarded-For', + xff2: 'Proksi Tahap Kedua dari X-Forwarded-For', + xff3: 'Proksi Tahap Ketiga dari X-Forwarded-For', + xffHelper: + 'Contoh: X-Forwarded-For: ,,, Proksi tahap atas akan mengambil IP terakhir ', + headersHelper: + 'Dapatkan IP sebenar dari header CDN HTTP yang biasa digunakan, memilih nilai pertama yang tersedia', + monitorCDNHelper: + 'Mengubah konfigurasi CDN untuk pemantauan laman web juga akan mengemas kini tetapan WAF CDN', + wafCDNHelper: 'Mengubah konfigurasi WAF CDN juga akan mengemas kini tetapan CDN pemantauan laman web', + statusErr: 'Format kod status tidak sah', + shenma: 'Shenma Search', + duckduckgo: 'DuckDuckGo', + '360': 'Pencarian 360', + excludeUri: 'Kecualikan URI', + top100Helper: 'Tunjukkan data 100 teratas', + logSaveDay: 'Tempoh Penahanan Log (hari)', + cros: 'Chrome OS', + theworld: 'Pelayar TheWorld', + edge: 'Microsoft Edge', + maxthon: 'Pelayar Maxthon', + monitorStatusHelper: 'Pemantauan tidak diaktifkan, sila aktifkan dalam tetapan', + excludeIp: 'Kecualikan Alamat IP', + excludeUa: 'Kecualikan User-Agent', + remotePort: 'Port Jauh', + unknown_browser: 'Tidak Diketahui', + unknown_os: 'Tidak Diketahui', + unknown_device: 'Tidak Diketahui', + logSaveSize: 'Saiz Simpanan Log Maksimum', + logSaveSizeHelper: 'Ini adalah saiz simpanan log untuk satu laman web', + '360se': '360 Pelayar Keselamatan', + websites: 'Senarai Laman Web', + trend: 'Statistik Trend', + reqCount: 'Jumlah Permintaan', + uriHelper: 'Anda boleh menggunakan /test/* atau /*/index.php untuk mengecualikan Uri', + }, + tamper: { + tamper: 'Perlindungan daripada peng篡改 laman web', + ignoreTemplate: 'Templat Pengecualian', + protectTemplate: 'Templat Perlindungan', + ignoreTemplateHelper: + 'Sila masukkan kandungan pengecualian, dipisahkan oleh Enter atau ruang. (Direktori khusus ./log atau nama direktori tmp, untuk mengecualikan fail perlu memasukkan fail khusus ./data/test.html)', + protectTemplateHelper: + 'Sila masukkan kandungan perlindungan, dipisahkan oleh Enter atau ruang. (Fail khusus ./index.html, sambungan fail .html, jenis fail js, untuk melindungi direktori perlu memasukkan direktori khusus ./log)', + templateContent: 'Kandungan Templat', + template: 'Templat', + saveTemplate: 'Simpan sebagai Templat', + tamperHelper1: + 'Untuk laman web jenis penyebaran satu klik, disyorkan untuk mengaktifkan fungsi anti-pengubahsuaian direktori aplikasi; jika laman web tidak dapat digunakan secara normal atau sandaran/pemulihan gagal, sila lumpuhkan fungsi anti-pengubahsuaian terlebih dahulu;', + tamperHelper2: + 'Akan menyekat operasi baca/tulis, padam, keizinan, dan pengubahsuaian pemilik untuk fail dilindungi dalam direktori tidak dikecualikan', + tamperPath: 'Direktori Perlindungan', + tamperPathEdit: 'Ubah Laluan', + log: 'Log Sekatan', + totalProtect: 'Perlindungan Total', + todayProtect: 'Perlindungan Hari Ini', + templateRule: 'Panjang 1-512, nama tidak boleh mengandungi {0} dan simbol lain', + ignore: 'Kecualikan', + ignoreHelper: + 'Sila pilih atau masukkan kandungan pengecualian, dipisahkan oleh Enter atau ruang. (Direktori khusus ./log atau nama direktori tmp, untuk mengecualikan fail perlu memasukkan atau memilih fail khusus ./data/test.html)', + protect: 'Lindungi', + protectHelper: + 'Sila pilih atau masukkan kandungan perlindungan, dipisahkan oleh Enter atau ruang. (Fail khusus ./index.html, sambungan fail .html, jenis fail js, untuk melindungi direktori perlu memasukkan atau memilih direktori khusus ./log)', + tamperHelper00: 'Pengecualian dan perlindungan hanya menyokong laluan relatif;', + tamperHelper01: + 'Selepas mengaktifkan perlindungan anti-pengubahsuaian, sistem akan menyekat operasi penciptaan, penyuntingan, dan pemadaman fail dilindungi dalam direktori tidak dikecualikan;', + tamperHelper02: + 'Keutamaan: Perlindungan laluan khusus > Pengecualian laluan khusus > Perlindungan > Pengecualian', + tamperHelper03: + 'Operasi pemantauan hanya mensasarkan direktori tidak dikecualikan, memantau penciptaan fail tidak dilindungi dalam direktori ini.', + disableHelper: 'Akan melumpuhkan fungsi anti-pengubahsuaian untuk laman web berikut, teruskan?', + appendOnly: 'Hanya Tambahan', + appendOnlyHelper: + 'Menyekat operasi pemadaman fail dalam direktori ini, hanya membenarkan penambahan direktori yang dikecualikan atau fail tidak dilindungi', + immutable: 'Tidak Berubah', + immutableHelper: + 'Menyekat operasi suntingan, pemadaman, keizinan, dan pengubahsuaian pemilik untuk fail ini', + onWatch: 'Pantau', + onWatchHelper: + 'Memantau dan mencegat penciptaan fail dilindungi atau direktori tidak dikecualikan dalam direktori ini', + forceStop: 'Tutup Paksa', + forceStopHelper: + 'Akan melumpuhkan fungsi anti-pengubahsuaian untuk direktori laman web ini secara paksa. Teruskan?', + }, + setting: { + setting: 'Tetapan Panel', + title: 'Deskripsi Panel', + titleHelper: + 'Akan dipaparkan pada halaman log masuk pengguna (contoh: Panel pengurusan operasi dan penyelenggaraan pelayan Linux, disarankan 8-15 aksara)', + logo: 'Logo (Tanpa Teks)', + logoHelper: + 'Akan dipaparkan di sudut kiri atas halaman pengurusan apabila menu dikurangkan (saiz imej yang disarankan: 82px*82px)', + logoWithText: 'Logo (Dengan Teks)', + logoWithTextHelper: + 'Akan dipaparkan di sudut kiri atas halaman pengurusan apabila menu diperluaskan (saiz imej yang disarankan: 185px*55px)', + favicon: 'Ikon Laman Web', + faviconHelper: 'Ikon laman web (saiz imej yang disarankan: 16px*16px)', + setDefault: 'Pulihkan Tetapan Asal', + setHelper: 'Tetapan semasa akan disimpan. Adakah anda ingin meneruskan?', + setDefaultHelper: 'Semua tetapan panel akan dikembalikan ke asal. Adakah anda ingin meneruskan?', + logoGroup: 'Logo', + imageGroup: 'Imej', + loginImage: 'Imej Halaman Log Masuk', + loginImageHelper: 'Akan dipaparkan di halaman log masuk (Saiz disyorkan: 500*416px)', + loginBgType: 'Jenis Latar Halaman Log Masuk', + loginBgImage: 'Imej Latar Halaman Log Masuk', + loginBgImageHelper: + 'Akan dipaparkan sebagai latar belakang halaman log masuk (Saiz disyorkan: 1920*1080px)', + loginBgColor: 'Warna Latar Halaman Log Masuk', + loginBgColorHelper: 'Akan dipaparkan sebagai warna latar belakang halaman log masuk', + image: 'Imej', + bgColor: 'Warna Latar', + loginGroup: 'Halaman Log Masuk', + loginBtnLinkColor: 'Warna Butang/Pautan', + loginBtnLinkColorHelper: 'Akan dipaparkan sebagai warna butang/pautan di halaman log masuk', + }, + helper: { + wafTitle1: 'Peta Pencegahan', + wafContent1: 'Memaparkan taburan geografi pencegahan dalam tempoh 30 hari yang lalu', + wafTitle2: 'Sekatan Akses Wilayah', + wafContent2: 'Menyekat sumber akses laman web mengikut lokasi geografi', + wafTitle3: 'Halaman Pencegahan Tersuai', + wafContent3: 'Cipta halaman tersuai untuk dipaparkan selepas permintaan disekat', + wafTitle4: 'Peraturan Tersuai (ACL)', + wafContent4: 'Sekat permintaan mengikut peraturan tersuai', + + tamperTitle1: 'Pemantauan Integriti Fail', + tamperContent1: 'Pantau integriti fail laman web, termasuk fail teras, skrip, dan fail konfigurasi.', + tamperTitle2: 'Pengimbasan dan Pengesanan Masa Nyata', + tamperContent2: + 'Kesan fail yang tidak normal atau diubah suai dengan mengimbas sistem fail laman web secara masa nyata.', + tamperTitle3: 'Tetapan Kebenaran Keselamatan', + tamperContent3: + 'Hadkan akses ke fail laman web melalui tetapan kebenaran yang sesuai dan dasar kawalan akses, mengurangkan permukaan serangan yang berpotensi.', + tamperTitle4: 'Log dan Analisis', + tamperContent4: + 'Rekod log akses dan operasi fail untuk audit dan analisis selanjutnya oleh pentadbir serta mengenal pasti potensi ancaman keselamatan.', + + settingTitle1: 'Mesej Selamat Datang Tersuai', + settingContent1: 'Tetapkan mesej selamat datang tersuai pada halaman log masuk 1Panel.', + settingTitle2: 'Logo Tersuai', + settingContent2: 'Benarkan memuat naik gambar logo yang mengandungi nama jenama atau teks lain.', + settingTitle3: 'Ikon Laman Web Tersuai', + settingContent3: + 'Benarkan memuat naik ikon tersuai untuk menggantikan ikon pelayar lalai, meningkatkan pengalaman pengguna.', + + monitorTitle1: 'Trend Pelawat', + monitorContent1: 'Statistik dan memaparkan trend pelawat laman web', + monitorTitle2: 'Peta Pelawat', + monitorContent2: 'Statistik dan memaparkan taburan geografi pelawat laman web', + monitorTitle3: 'Statistik Akses', + monitorContent3: + 'Statistik maklumat permintaan laman web, termasuk labah-labah, peranti akses, status permintaan, dan sebagainya.', + monitorTitle4: 'Pemantauan Masa Nyata', + monitorContent4: + 'Pemantauan masa nyata maklumat permintaan laman web, termasuk bilangan permintaan, trafik, dan sebagainya.', + + alertTitle1: 'Amaran SMS', + alertContent1: + 'Apabila berlaku penggunaan sumber pelayan yang tidak normal, tamat tempoh laman web dan sijil, kemas kini versi baru, tamat tempoh kata laluan, dan sebagainya, pengguna akan diberitahu melalui amaran SMS untuk memastikan pemprosesan tepat pada masanya.', + alertTitle2: 'Log Amaran', + alertContent2: + 'Memberikan fungsi kepada pengguna untuk melihat log amaran bagi memudahkan penjejakan dan analisis peristiwa amaran sejarah.', + alertTitle3: 'Tetapan Amaran', + alertContent3: + 'Memberikan pengguna konfigurasi nombor telefon tersuai, kekerapan push harian, dan masa push harian, memudahkan pengguna untuk menetapkan amaran push yang lebih munasabah.', + + nodeDashTitle1: 'Pengurusan Aplikasi', + nodeDashContent1: + 'Pengurusan bersatu aplikasi berbilang nod, menyokong pemantauan status, mula/henti pantas, sambungan terminal dan sandaran', + nodeDashTitle2: 'Pengurusan Laman Web', + nodeDashContent2: + 'Pengurusan bersatu laman web berbilang nod, pemantauan status masa nyata, menyokong mula/henti kelompok dan sandaran pantas', + nodeDashTitle3: 'Pengurusan Pangkalan Data', + nodeDashContent3: + 'Pengurusan bersatu pangkalan data berbilang nod, status utama sekilas, menyokong sandaran satu klik', + nodeDashTitle4: 'Pengurusan Tugas Berjadual', + nodeDashContent4: + 'Pengurusan bersatu tugas berjadual berbilang nod, menyokong pemantauan status, mula/henti pantas dan pelaksanaan pencetus manual', + + nodeTitle1: 'Satu Klik Tambah Node', + nodeContent1: 'Mengintegrasikan pelbagai nod pelayan dengan cepat', + nodeTitle2: 'Kelompok Naik Taraf', + nodeContent2: 'Segerakan dan naik taraf semua nod dengan satu operasi', + nodeTitle3: 'Pemantauan Status Node', + nodeContent3: 'Memantau status operasi setiap nod secara real-time', + nodeTitle4: 'Sambungan Jauh Pantas', + nodeContent4: 'Sambung terus ke terminal jauh nod dengan satu klik', + + fileExchangeTitle1: 'Pengangkutan Pengesahan Kunci', + fileExchangeContent1: 'Mengesahkan melalui kunci SSH untuk memastikan keselamatan pengangkutan.', + fileExchangeTitle2: 'Segerakan Fail Cekap', + fileExchangeContent2: + 'Hanya menyegerakkan kandungan yang berubah untuk meningkatkan kelajuan dan kestabilan pengangkutan secara signifikan.', + fileExchangeTitle3: 'Sokongan Pertukaran Inter-Node', + fileExchangeContent3: + 'Mudah memindahkan fail projek antara nod yang berbeza, mengurus pelbagai pelayan dengan fleksibiliti.', + + nodeAppTitle1: 'Pengurusan Naik Taraf Aplikasi', + nodeAppContent1: 'Pemantauan bersatu kemas kini aplikasi berbilang nod, menyokong naik taraf satu klik', + + appTitle1: 'Pengurusan Panel yang Fleksibel', + appContent1: 'Uruskan pelayan 1Panel anda dengan mudah pada bila-bila masa dan di mana sahaja.', + appTitle2: 'Maklumat Perkhidmatan Komprehensif', + appContent2: + 'Uruskan aplikasi, laman web, Docker, pangkalan data, dll melalui aplikasi mudah alih dan sokong pembinaan pantas aplikasi dan laman web.', + appTitle3: 'Pemantauan Anomali Masa Nyata', + appContent3: + 'Lihat status pelayan, pemantauan keselamatan WAF, statistik pelawat laman web dan status kesihatan proses di aplikasi mudah alih secara masa nyata.', + + clusterTitle1: 'Penyebaran Utama-Hamba', + clusterContent1: + 'Menyokong penciptaan contoh utama-hamba MySQL/Postgres/Redis pada nod yang berbeza, secara automatik melengkapkan perhubungan utama-hamba dan permulaan', + clusterTitle2: 'Pengurusan Utama-Hamba', + clusterContent2: + 'Halaman terpadu untuk menguruskan pelbagai nod utama-hamba, lihat peranan, status berjalan, dsb.', + clusterTitle3: 'Status Replikasi', + clusterContent3: + 'Memaparkan status replikasi utama-hamba dan maklumat kelewatan, membantu menyelesaikan masalah sinkronisasi', + }, + node: { + master: 'Nod Utama', + masterBackup: 'Sandaran Nod Master', + backupNode: 'Nod Sandaran', + backupFrequency: 'Kekerapan Sandaran (jam)', + backupCopies: 'Bilangan salinan sandaran yang disimpan', + noBackupNode: 'Nod sandaran kosong. Sila pilih nod sandaran untuk disimpan dan cuba lagi!', + masterBackupAlert: + 'Sandaran nod master belum dikonfigurasikan. Untuk memastikan keselamatan data, sila sediakan nod sandaran secepat mungkin untuk memudahkan pertukaran manual ke nod master baru sekiranya berlaku kegagalan.', + node: 'Nod', + addr: 'Alamat', + nodeUpgrade: 'Tetapan Kemas Kini', + nodeUpgradeHelper: + 'Nod terpilih akan mula dinaik taraf secara automatik selepas naik taraf nod induk selesai, tiada operasi manual diperlukan.', + nodeUnhealthy: 'Status nod tidak normal', + deletedNode: 'Nod {0} yang telah dipadam tidak menyokong operasi naik taraf buat masa ini!', + nodeUnhealthyHelper: 'Status nod tidak normal dikesan. Sila semak dalam [Pengurusan Nod] dan cuba lagi!', + nodeUnbind: 'Nod tidak terikat pada lesen', + nodeUnbindHelper: + 'Terdeteksi nod ini tidak terikat pada lesen. Sila ikat dalam menu [Tetapan Panel - Lesen] dan cuba lagi!', + memTotal: 'Jumlah Memori', + nodeManagement: 'Pengurusan Multi-Mesin', + nodeItem: 'Pengurusan Nod', + panelItem: 'Pengurusan Panel', + addPanel: 'Tambah Panel', + addPanelHelper: + 'Selepas berjaya menambah panel, anda boleh mengakses panel sasaran dengan pantas di [Gambaran Keseluruhan - Panel].', + panel: 'Panel 1Panel', + others: 'Panel Lain', + addNode: 'Tambah Nod', + connInfo: 'Maklumat Sambungan', + nodeInfo: 'Maklumat Nod', + withProxy: 'Dayakan Akses Proksi', + withoutProxy: 'Lumpuhkan Akses Proksi', + withProxyHelper: + 'Akan menggunakan proksi sistem {0} yang dikekalkan dalam tetapan panel untuk mengakses nod anak. Teruskan?', + withoutProxyHelper: + 'Akan berhenti menggunakan proksi sistem yang dikekalkan dalam tetapan panel untuk mengakses nod anak. Teruskan?', + syncInfo: 'Penyegerakan data,', + syncHelper: 'Apabila data nod induk berubah, ia akan disegerakkan ke nod anak ini secara masa nyata,', + syncBackupAccount: 'Tetapan akaun sandaran', + syncWithMaster: + 'Selepas menaik taraf ke Pro, semua data akan diselaraskan secara lalai. Dasar penyelarasan boleh disesuaikan secara manual dalam pengurusan nod.', + syncProxy: 'Tetapan proksi sistem', + syncProxyHelper: 'Penyelarasan tetapan proksi sistem memerlukan mulakan semula Docker', + syncProxyHelper1: 'Memulakan semula Docker mungkin menjejaskan perkhidmatan kontena yang sedang berjalan.', + syncProxyHelper2: 'Anda boleh mulakan semula secara manual di halaman Kontena - Konfigurasi.', + syncProxyHelper3: + 'Penyelarasan tetapan proksi sistem memerlukan mulakan semula Docker, yang mungkin menjejaskan perkhidmatan kontena yang sedang berjalan', + syncProxyHelper4: + 'Penyelarasan tetapan proksi sistem memerlukan mulakan semula Docker. Anda boleh mulakan semula secara manual di halaman Kontena - Konfigurasi nanti.', + syncCustomApp: 'Segerakan Repositori Aplikasi Tersuai', + syncAlertSetting: 'Tetapan amaran sistem', + syncNodeInfo: 'Data asas nod,', + nodeSyncHelper: 'Penyelarasan maklumat nod akan menyelaraskan maklumat berikut:', + nodeSyncHelper1: '1. Maklumat akaun sandaran awam', + nodeSyncHelper2: '2. Maklumat sambungan antara nod utama dan nod sub', + + nodeCheck: 'Semakan ketersediaan', + checkSSH: 'Periksa sambungan SSH nod', + checkUserPermission: 'Semak kebenaran pengguna nod', + isNotRoot: 'Dikesan sudo tanpa kata laluan tidak disokong pada nod ini dan pengguna semasa bukan root', + checkLicense: 'Periksa status lesen nod', + checkService: 'Periksa maklumat perkhidmatan sedia ada pada nod', + checkPort: 'Periksa kebolehcapaian port nod', + panelExist: + 'Mengesan nod ini sedang menjalankan perkhidmatan 1Panel V1. Sila naik taraf ke V2 menggunakan skrip migrasi sebelum menambah.', + coreExist: + 'Nod semasa telah didayakan sebagai nod induk dan tidak boleh ditambah terus sebagai nod hamba. Sila turun taraf kepada nod hamba terlebih dahulu sebelum menambah, rujuk dokumentasi untuk butiran.', + agentExist: + 'Mengesan 1panel-agent telah dipasang pada nod ini. Penerusan akan mengekalkan data sedia ada dan hanya menggantikan perkhidmatan 1panel-agent.', + agentNotExist: + 'Terdapat pengesahan bahawa 1panel-agent tidak dipasang pada nod ini, maklumat nod tidak boleh disunting secara langsung. Sila padam dan tambah semula.', + oldDataExist: + 'Mengesan data sejarah 1Panel V2 pada nod ini. Maklumat berikut akan digunakan untuk menimpa tetapan semasa:', + errLicense: 'Lesen yang terikat pada nod ini tidak tersedia. Sila semak dan cuba lagi!', + errNodePort: + 'Port nod [ {0} ] dikesan tidak boleh diakses. Sila semak sama ada firewall atau kumpulan keselamatan telah membenarkan port ini.', + + reinstallHelper: 'Pasang semula nod {0}, adakah anda ingin meneruskan?', + unhealthyCheck: 'Pemeriksaan Tidak Normal', + fixOperation: 'Operasi Pembetulan', + checkName: 'Item Pemeriksaan', + checkSSHConn: 'Periksa Ketersediaan Sambungan SSH', + fixSSHConn: 'Edit nod secara manual untuk mengesahkan maklumat sambungan', + checkConnInfo: 'Periksa Maklumat Sambungan Ejen', + checkStatus: 'Periksa Ketersediaan Perkhidmatan Nod', + fixStatus: + 'Jalankan "systemctl status 1panel-agent.service" untuk memeriksa sama ada perkhidmatan sedang berjalan.', + checkAPI: 'Periksa Ketersediaan API Nod', + fixAPI: 'Semak log nod dan periksa sama ada port firewall dibuka dengan betul.', + forceDelete: 'Hapus Secara Paksa', + operateHelper: 'Operasi {0} akan dilakukan pada nod berikut, adakah anda ingin meneruskan?', + operatePanelHelper: 'Operasi {0} akan dilakukan pada panel berikut. Teruskan?', + forceDeleteHelper: + 'Hapus secara paksa akan mengabaikan ralat penghapusan nod dan menghapus metadata pangkalan data', + uninstall: 'Padam data nod', + uninstallHelper: 'Ini akan memadamkan semua data berkaitan 1Panel bagi nod. Pilih dengan berhati-hati!', + baseDir: 'Direktori Pemasangan', + baseDirHelper: 'Apabila direktori pemasangan kosong, secara lalai akan dipasang di direktori /opt', + nodePort: 'Port Nod', + offline: 'Mod luar talian', + freeCount: 'Had percuma [{0}]', + offlineHelper: 'Digunakan apabila nod berada dalam persekitaran luar talian', + + appUpgrade: 'Naik Taraf Apl', + appUpgradeHelper: 'Terdapat {0} apl yang perlu dinaik taraf', + }, + customApp: { + name: 'Repositori Aplikasi Khusus', + appStoreType: 'Sumber Pakej App Store', + appStoreUrl: 'URL Repositori', + local: 'Laluan Tempatan', + remote: 'Pautan Jauh', + imagePrefix: 'Awalan Imej', + imagePrefixHelper: + 'Fungsi: Sesuaikan awalan imej dan ubah medan imej dalam fail compose. Contohnya, apabila awalan imej ditetapkan kepada 1panel/custom, medan imej untuk MaxKB akan bertukar kepada 1panel/custom/maxkb:v1.10.0', + closeHelper: 'Batalkan penggunaan repositori aplikasi khusus', + appStoreUrlHelper: 'Hanya format .tar.gz disokong', + postNode: 'Segerakkan ke sub-node', + postNodeHelper: + 'Segerakan pakej kedai tersuai ke tmp/customApp/apps.tar.gz dalam direktori pemasangan nod anak', + nodes: 'Pilih Node', + selectNode: 'Pilih Node', + selectNodeError: 'Sila pilih node', + licenseHelper: 'Versi Pro menyokong fungsi gudang aplikasi tersuai', + databaseHelper: 'Pangkalan data berkaitan aplikasi, sila pilih pangkalan data nod sasaran', + nodeHelper: 'Tidak boleh memilih nod semasa', + migrateHelper: + 'Kini hanya menyokong penghijrahan aplikasi monolitik dan aplikasi yang hanya dikaitkan dengan pangkalan data MySQL, MariaDB, PostgreSQL', + opensslHelper: + 'Jika menggunakan sandaran terenkripsi, versi OpenSSL antara dua nod mesti konsisten, jika tidak penghijrahan mungkin gagal.', + installApp: 'Pemasangan kelompok', + installAppHelper: 'Pasang aplikasi secara kelompok ke nod yang dipilih', + }, + alert: { + isAlert: 'Amaran', + alertCount: 'Bilangan Amaran', + clamHelper: 'Hantar amaran apabila terdapat fail yang dijangkiti semasa imbasan', + cronJobHelper: 'Hantar amaran apabila pelaksanaan tugas gagal', + licenseHelper: 'Versi profesional menyokong amaran SMS', + alertCountHelper: 'Kekerapan maksimum amaran harian', + alert: 'Amaran SMS', + logs: 'Log Amaran', + list: 'Senarai Amaran', + addTask: 'Cipta Amaran', + editTask: 'Edit Amaran', + alertMethod: 'Kaedah', + alertMsg: 'Mesej Amaran', + alertRule: 'Peraturan Amaran', + titleSearchHelper: 'Masukkan tajuk amaran untuk pencarian kabur', + taskType: 'Jenis', + ssl: 'Sijil Tamat Tempoh', + siteEndTime: 'Tamat Tempoh Laman Web', + panelPwdEndTime: 'Kata Laluan Panel Tamat Tempoh', + panelUpdate: 'Versi Panel Baharu Tersedia', + cpu: 'Amaran CPU Pelayan', + memory: 'Amaran Memori Pelayan', + load: 'Amaran Beban Pelayan', + disk: 'Amaran Cakera Pelayan', + website: 'Laman Web', + certificate: 'Sijil SSL', + remainingDays: 'Hari yang Tinggal', + sendCount: 'Bilangan Hantar', + sms: 'SMS', + wechat: 'WeChat', + dingTalk: 'DingTalk', + feiShu: 'FeiShu', + mail: 'E-mel', + weCom: 'WeCom', + sendCountRulesHelper: 'Jumlah amaran dihantar sebelum tamat tempoh (sekali sehari)', + panelUpdateRulesHelper: 'Jumlah amaran dihantar untuk versi panel baharu (sekali sehari)', + oneDaySendCountRulesHelper: 'Maksimum amaran dihantar setiap hari', + siteEndTimeRulesHelper: 'Laman web yang tidak pernah tamat tempoh tidak akan mencetuskan amaran', + autoRenewRulesHelper: + 'Sijil dengan pembaharuan automatik diaktifkan dan baki hari kurang daripada 31 tidak akan mencetuskan amaran', + panelPwdEndTimeRulesHelper: + 'Amaran tamat tempoh kata laluan panel tidak tersedia jika tiada tempoh tamat ditetapkan', + sslRulesHelper: 'Semua Sijil SSL', + diskInfo: 'Cakera', + monitoringType: 'Jenis Pemantauan', + autoRenew: 'Pembaharuan Automatik', + useDisk: 'Penggunaan Cakera', + usePercentage: 'Peratusan Penggunaan', + changeStatus: 'Tukar Status', + disableMsg: + 'Menghentikan tugas amaran akan menghalang tugas ini daripada menghantar mesej amaran. Adakah anda ingin meneruskan?', + enableMsg: + 'Mengaktifkan tugas amaran akan membolehkan tugas ini menghantar mesej amaran. Adakah anda ingin meneruskan?', + useExceed: 'Penggunaan Melebihi', + useExceedRulesHelper: 'Cetuskan amaran apabila penggunaan melebihi nilai yang ditetapkan', + cpuUseExceedAvg: 'Penggunaan CPU purata melebihi nilai yang ditetapkan', + memoryUseExceedAvg: 'Penggunaan memori purata melebihi nilai yang ditetapkan', + loadUseExceedAvg: 'Penggunaan beban purata melebihi nilai yang ditetapkan', + cpuUseExceedAvgHelper: 'Penggunaan CPU purata dalam masa tertentu melebihi nilai yang ditetapkan', + memoryUseExceedAvgHelper: 'Penggunaan memori purata dalam masa tertentu melebihi nilai yang ditetapkan', + loadUseExceedAvgHelper: 'Penggunaan beban purata dalam masa tertentu melebihi nilai yang ditetapkan', + resourceAlertRulesHelper: 'Nota: Amaran berterusan dalam masa 30 minit hanya akan menghantar satu', + specifiedTime: 'Masa Tertentu', + deleteTitle: 'Padam Amaran', + deleteMsg: 'Adakah anda pasti ingin memadam tugas amaran?', + + allSslTitle: 'Semua Amaran Tamat Tempoh Sijil SSL Laman Web', + sslTitle: 'Amaran Tamat Tempoh Sijil SSL untuk Laman Web {0}', + allSiteEndTimeTitle: 'Semua Amaran Tamat Tempoh Laman Web', + siteEndTimeTitle: 'Amaran Tamat Tempoh Laman Web {0}', + panelPwdEndTimeTitle: 'Amaran Tamat Tempoh Kata Laluan Panel', + panelUpdateTitle: 'Pemberitahuan Versi Panel Baharu', + cpuTitle: 'Amaran Penggunaan CPU Tinggi', + memoryTitle: 'Amaran Penggunaan Memori Tinggi', + loadTitle: 'Amaran Beban Tinggi', + diskTitle: 'Amaran Penggunaan Cakera Tinggi untuk Direktori {0}', + allDiskTitle: 'Amaran Penggunaan Cakera Tinggi', + + timeRule: 'Masa baki kurang daripada {0} hari (jika tidak diurus, akan dihantar semula keesokan hari)', + panelUpdateRule: + 'Hantar satu amaran apabila versi panel baharu dikesan (jika tidak diurus, akan dihantar semula keesokan hari)', + avgRule: + 'Purata penggunaan {1} melebihi {2}% dalam {0} minit, mencetuskan amaran, dihantar {3} kali sehari', + diskRule: + 'Penggunaan cakera untuk direktori {0} melebihi {1}{2}, mencetuskan amaran, dihantar {3} kali sehari', + allDiskRule: 'Penggunaan cakera melebihi {0}{1}, mencetuskan amaran, dihantar {2} kali sehari', + + cpuName: ' CPU ', + memoryName: 'Memori', + loadName: 'Beban', + diskName: 'Cakera', + + syncAlertInfo: 'Tekan Manual', + syncAlertInfoMsg: 'Adakah anda ingin menekan tugas amaran secara manual?', + pushError: 'Tekanan Gagal', + pushSuccess: 'Tekanan Berjaya', + syncError: 'Penyelarasan Gagal', + success: 'Amaran Berjaya', + pushing: 'Sedang menghantar...', + error: 'Amaran gagal', + cleanLog: 'Bersihkan Log', + cleanAlertLogs: 'Bersihkan Log Amaran', + daily: 'Bilangan Amaran Harian: {0}', + cumulative: 'Bilangan Amaran Kumulatif: {0}', + clams: 'Imbasan Virus', + taskName: 'Nama Tugas', + cronJobType: 'Jenis Tugas', + clamPath: 'Direktori Imbasan', + cronjob: 'Pelaksanaan tugas berjadual {0} gagal', + app: 'Sandaran Aplikasi', + web: 'Sandaran Laman Web', + database: 'Sandaran Pangkalan Data', + directory: 'Sandaran Direktori', + log: 'Log Sandaran', + snapshot: 'Snapshot Sistem', + clamsRulesHelper: 'Tugas imbasan virus yang memerlukan amaran', + cronJobRulesHelper: 'Jenis tugas berjadual ini perlu dikonfigurasikan', + clamsTitle: 'Tugas imbasan virus 「 {0} 」 mengesan amaran fail dijangkiti', + cronJobAppTitle: 'Cronjob - Sandaran Aplikasi 「 {0} 」 Amaran Kegagalan Tugas', + cronJobWebsiteTitle: 'Cronjob - Sandaran Laman Web 「 {0} 」 Amaran Kegagalan Tugas', + cronJobDatabaseTitle: 'Cronjob - Sandaran Pangkalan Data 「 {0} 」 Amaran Kegagalan Tugas', + cronJobDirectoryTitle: 'Cronjob - Sandaran Direktori 「 {0} 」 Amaran Kegagalan Tugas', + cronJobLogTitle: 'Cronjob - Sandaran Log 「 {0} 」 Amaran Kegagalan Tugas', + cronJobSnapshotTitle: 'Cronjob - Sandaran Snapshot 「 {0} 」 Amaran Kegagalan Tugas', + cronJobShellTitle: 'Cronjob - Skrip Shell 「 {0} 」 Amaran Kegagalan Tugas', + cronJobCurlTitle: 'Cronjob - Akses URL 「 {0} 」 Amaran Kegagalan Tugas', + cronJobCutWebsiteLogTitle: 'Cronjob - Potong log laman web 「 {0} 」 Amaran Kegagalan Tugas', + cronJobCleanTitle: 'Cronjob - Pembersihan Cache 「 {0} 」 Amaran Kegagalan Tugas', + cronJobNtpTitle: 'Cronjob - Penyelarasan masa pelayan 「 {0} 」 Amaran Kegagalan Tugas', + clamsRule: 'Imbasan virus mengesan fail dijangkiti, dihantar {0} kali sehari', + cronJobAppRule: 'Amaran kegagalan tugas sandaran aplikasi, dihantar {0} kali sehari', + cronJobWebsiteRule: 'Amaran kegagalan tugas sandaran laman web, dihantar {0} kali sehari', + cronJobDatabaseRule: 'Amaran kegagalan tugas sandaran pangkalan data, dihantar {0} kali sehari', + cronJobDirectoryRule: 'Amaran kegagalan tugas sandaran direktori, dihantar {0} kali sehari', + cronJobLogRule: 'Amaran kegagalan tugas sandaran log, dihantar {0} kali sehari', + cronJobSnapshotRule: 'Amaran kegagalan tugas sandaran snapshot, dihantar {0} kali sehari', + cronJobShellRule: 'Amaran kegagalan tugas skrip shell, dihantar {0} kali sehari', + cronJobCurlRule: 'Amaran kegagalan tugas akses URL, dihantar {0} kali sehari', + cronJobCutWebsiteLogRule: 'Amaran kegagalan tugas potong log laman web, dihantar {0} kali sehari', + cronJobCleanRule: 'Amaran kegagalan tugas pembersihan cache, dihantar {0} kali sehari', + cronJobNtpRule: 'Amaran kegagalan tugas penyelarasan masa pelayan, dihantar {0} kali sehari', + alertSmsHelper: 'Had SMS: jumlah {0} mesej, {1} telah digunakan', + goBuy: 'Beli Lebih Banyak', + phone: 'Telefon', + phoneHelper: 'Berikan nombor telefon sebenar untuk mesej amaran', + dailyAlertNum: 'Had Amaran Harian', + dailyAlertNumHelper: 'Bilangan maksimum amaran sehari (sehingga 100)', + timeRange: 'Julat Masa', + sendTimeRange: 'Hantar julat masa', + sendTimeRangeHelper: 'Boleh tekan julat masa {0}', + to: '-', + startTime: 'Masa Mula', + endTime: 'Masa Tamat', + defaultPhone: 'Secara lalai ke nombor telefon akaun terikat lesen', + noticeAlert: 'Amaran Pemberitahuan', + resourceAlert: 'Amaran Sumber', + agentOfflineAlertHelper: + 'Apabila amaran luar talian diaktifkan untuk nod, nod utama akan mengimbas setiap 30 minit untuk melaksanakan tugas amaran.', + offline: 'Amaran Luar Talian', + offlineHelper: + 'Apabila ditetapkan kepada amaran luar talian, nod utama akan mengimbas setiap 30 minit untuk melaksanakan tugas amaran.', + offlineOff: 'Aktifkan Amaran Luar Talian', + offlineOffHelper: + 'Mengaktifkan amaran luar talian akan menyebabkan nod utama mengimbas setiap 30 minit untuk melaksanakan tugas amaran.', + offlineClose: 'Lumpuhkan Amaran Luar Talian', + offlineCloseHelper: + 'Melumpuhkan amaran luar talian memerlukan subnod untuk mengendalikan amaran secara kendiri. Sila pastikan sambungan rangkaian adalah baik untuk mengelakkan kegagalan amaran.', + alertNotice: 'Pemberitahuan Amaran', + methodConfig: 'Konfigurasi Kaedah Pemberitahuan', + commonConfig: 'Konfigurasi Global', + smsConfig: 'SMS', + smsConfigHelper: 'Konfigurasi nombor untuk pemberitahuan SMS', + emailConfig: 'E-mel', + emailConfigHelper: 'Konfigurasi perkhidmatan penghantaran e-mel SMTP', + deleteConfigTitle: 'Padam Konfigurasi Amaran', + deleteConfigMsg: 'Adakah anda pasti mahu memadam konfigurasi amaran?', + test: 'Ujian', + alertTestOk: 'Pemberitahuan ujian berjaya', + alertTestFailed: 'Pemberitahuan ujian gagal', + displayName: 'Nama Paparan', + sender: 'Alamat Pengirim', + password: 'Kata Laluan', + host: 'Pelayan SMTP', + port: 'Nombor Port', + encryption: 'Kaedah Penyulitan', + recipient: 'Penerima', + licenseTime: 'Peringatan Tamat Tempoh Lesen', + licenseTimeTitle: 'Peringatan Tamat Tempoh Lesen', + displayNameHelper: 'Nama paparan pengirim e-mel', + senderHelper: 'Alamat e-mel yang digunakan untuk menghantar e-mel', + passwordHelper: 'Kod kebenaran untuk perkhidmatan e-mel', + hostHelper: 'Alamat pelayan SMTP, contoh: smtp.qq.com', + portHelper: 'SSL biasanya 465, TLS biasanya 587', + sslHelper: 'Jika port SMTP ialah 465, SSL biasanya diperlukan', + tlsHelper: 'Jika port SMTP ialah 587, TLS biasanya diperlukan', + triggerCondition: 'Syarat Pencetus', + loginFail: ' kegagalan log masuk dalam', + nodeException: 'Amaran Kerosakan Nod', + licenseException: 'Amaran Kerosakan Lesen', + panelLogin: 'Amaran Log Masuk Panel Tidak Normal', + sshLogin: 'Amaran Log Masuk SSH Tidak Normal', + panelIpLogin: 'Amaran IP Log Masuk Panel Tidak Normal', + sshIpLogin: 'Amaran IP Log Masuk SSH Tidak Normal', + ipWhiteListHelper: + 'IP dalam senarai putih tidak tertakluk kepada peraturan, dan tiada amaran akan dikeluarkan apabila log masuk berjaya', + nodeExceptionRule: 'Amaran kerosakan nod, dihantar {0} kali sehari', + licenseExceptionRule: 'Amaran kerosakan lesen, dihantar {0} kali sehari', + panelLoginRule: 'Amaran log masuk panel, dihantar {0} kali sehari', + sshLoginRule: 'Amaran log masuk SSH, dihantar {0} kali sehari', + userNameHelper: 'Nama pengguna kosong, alamat penghantar akan digunakan secara lalai', + }, + theme: { + lingXiaGold: 'Ling Xia Emas', + classicBlue: 'Biru Klasik', + freshGreen: 'Hijau Segar', + customColor: 'Warna Tersuai', + setDefault: 'Lalai', + setDefaultHelper: 'Skema warna tema akan dipulihkan ke keadaan asalnya. Adakah anda ingin meneruskan?', + setHelper: 'Skema warna tema yang dipilih sekarang akan disimpan. Adakah anda ingin meneruskan?', + }, + exchange: { + exchange: 'Pertukaran Fail', + exchangeConfirm: 'Adakah anda mahu memindahkan fail/folder {1} dari node {0} ke direktori {3} node {2}?', + }, + cluster: { + cluster: 'Aplikasi Tinggi Ketersediaan', + name: 'Nama Kluster', + addCluster: 'Tambah Kluster', + installNode: 'Pasang Node', + master: 'Node Utama', + slave: 'Node Hamba', + replicaStatus: 'Utama-Hamba Status', + unhealthyDeleteError: 'Status nod pemasangan tidak normal, sila periksa senarai nod dan cuba lagi!', + replicaStatusError: 'Pengambilan status tidak normal, sila periksa nod utama.', + masterHostError: 'IP nod utama tidak boleh 127.0.0.1', + }, + }, +}; + +export default { + ...fit2cloudEnLocale, + ...message, +}; diff --git a/frontend/src/lang/modules/pt-br.ts b/frontend/src/lang/modules/pt-br.ts new file mode 100644 index 0000000..df57980 --- /dev/null +++ b/frontend/src/lang/modules/pt-br.ts @@ -0,0 +1,4157 @@ +import fit2cloudPtBrLocale from 'fit2cloud-ui-plus/src/locale/lang/pt-br'; + +const message = { + commons: { + true: 'verdadeiro', + false: 'falso', + colon: ': ', + example: 'Por exemplo, ', + fit2cloud: 'FIT2CLOUD', + lingxia: 'Lingxia', + button: { + run: 'Executar', + create: 'Criar', + add: 'Adicionar', + save: 'Salvar', + set: 'Editar configuração', + sync: 'Sincronizar', + delete: 'Excluir', + edit: 'Editar', + enable: 'Ativar', + disable: 'Desativar', + confirm: 'Confirmar', + cancel: 'Cancelar', + reset: 'Redefinir', + restart: 'Reiniciar', + conn: 'Conectar', + disConn: 'Desconectar', + clean: 'Limpar', + login: 'Entrar', + close: 'Fechar', + off: 'Fechar', + stop: 'Parar', + start: 'Iniciar', + view: 'Visualizar', + watch: 'Monitorar', + handle: 'Disparar', + clone: 'Clonar', + expand: 'Expandir', + collapse: 'Recolher', + log: 'Logs', + back: 'Voltar', + backup: 'Backup', + recover: 'Restaurar', + retry: 'Tentar novamente', + upload: 'Fazer upload', + download: 'Fazer download', + init: 'Inicializar', + verify: 'Verificar', + saveAndEnable: 'Salvar e ativar', + import: 'Importar', + export: 'Exportar', + power: 'Autorização', + search: 'Pesquisar', + refresh: 'Atualizar', + get: 'Obter', + upgrade: 'Atualizar versão', + update: 'atualizar', + updateNow: 'Atualizar Agora', + ignore: 'Ignorar atualização', + install: 'instalar', + copy: 'Copiar', + random: 'Aleatório', + uninstall: 'Desinstalar', + fullscreen: 'Entrar em tela cheia', + quitFullscreen: 'Sair da tela cheia', + showAll: 'Exibir tudo', + hideSome: 'Ocultar alguns', + agree: 'Concordo', + notAgree: 'Não concordo', + preview: 'Pré-visualizar', + open: 'Abrir', + notSave: 'Não salvar', + createNewFolder: 'Criar nova pasta', + createNewFile: 'Criar novo arquivo', + helpDoc: 'Documento de ajuda', + unbind: 'Desvincular', + cover: 'Substituir', + skip: 'Pular', + fix: 'Corrigir', + down: 'Parar', + up: 'Iniciar', + sure: 'Confirmar', + show: 'Exibir', + hide: 'Ocultar', + visit: 'Visitar', + migrate: 'Migrar', + }, + operate: { + start: 'Iniciar', + stop: 'Parar', + restart: 'Reiniciar', + reload: 'Recarregar', + rebuild: 'Reconstruir', + sync: 'Sincronizar', + up: 'Iniciar', + down: 'Parar', + delete: 'Excluir', + }, + search: { + timeStart: 'Hora inicial', + timeEnd: 'Hora final', + timeRange: 'Até', + dateStart: 'Data inicial', + dateEnd: 'Data final', + date: 'Data', + }, + table: { + all: 'Todos', + total: 'Total de {0}', + name: 'Nome', + type: 'Tipo', + status: 'Status', + records: 'Registros', + group: 'Grupo', + createdAt: 'Data de criação', + publishedAt: 'Data de publicação', + date: 'Data', + updatedAt: 'Data de atualização', + operate: 'Operações', + message: 'Mensagem', + description: 'Descrição', + interval: 'Intervalo', + user: 'Responsável', + title: 'Título', + port: 'Porta', + forward: 'Encaminhar', + protocol: 'Protocolo', + tableSetting: 'Configuração da tabela', + refreshRate: 'Taxa de atualização', + selectColumn: 'Selecionar coluna', + local: 'Local', + serialNumber: 'Número de série', + manageGroup: 'Gerenciar Grupos', + backToList: 'Voltar à Lista', + keepEdit: 'Continuar Editando', + }, + loadingText: { + Upgrading: 'Atualizando o sistema, por favor, aguarde...', + Restarting: 'Reiniciando o sistema, por favor, aguarde...', + Recovering: 'Restaurando a partir de um snapshot, por favor, aguarde...', + Rollbacking: 'Revertendo para um snapshot, por favor, aguarde...', + }, + msg: { + noneData: 'Nenhum dado disponível', + delete: 'Esta operação de exclusão não pode ser desfeita. Deseja continuar?', + clean: 'Esta operação de limpeza não pode ser desfeita. Deseja continuar?', + closeDrawerHelper: 'O sistema pode não salvar as alterações que você fez. Deseja continuar?', + deleteSuccess: 'Excluído com sucesso', + loginSuccess: 'Login realizado com sucesso', + operationSuccess: 'Operação concluída com sucesso', + copySuccess: 'Copiado com sucesso', + notSupportOperation: 'Esta operação não é suportada', + requestTimeout: 'A solicitação expirou, tente novamente mais tarde', + infoTitle: 'Aviso', + notRecords: 'Nenhum registro de execução foi gerado para a tarefa atual', + sureLogOut: 'Tem certeza de que deseja sair?', + createSuccess: 'Criado com sucesso', + updateSuccess: 'Atualizado com sucesso', + uploadSuccess: 'Enviado com sucesso', + operateConfirm: 'Se você tem certeza da operação, insira-a manualmente: ', + inputOrSelect: 'Por favor, selecione ou insira', + copyFailed: 'Falha ao copiar', + operatorHelper: 'A operação "{1}" será realizada em "{0}" e não poderá ser desfeita. Deseja continuar?', + notFound: 'Desculpe, a página solicitada não existe.', + unSupportType: 'O tipo de arquivo atual não é suportado.', + unSupportSize: 'O arquivo enviado excede {0}M, por favor confirme!', + fileExist: 'O arquivo já existe na pasta atual. Não é possível enviar novamente.', + fileNameErr: + 'Você pode enviar apenas arquivos cujo nome contenha de 1 a 256 caracteres, incluindo letras, números ou os caracteres (. - _)', + confirmNoNull: 'Certifique-se de que o valor {0} não está vazio.', + errPort: 'Informação de porta incorreta, por favor confirme!', + remove: 'Remover', + backupHelper: 'A operação atual fará o backup de {0}. Deseja continuar?', + recoverHelper: 'Restaurando a partir do arquivo {0}. Esta operação é irreversível. Deseja continuar?', + refreshSuccess: 'Atualizado com sucesso', + rootInfoErr: 'Já está no diretório raiz', + resetSuccess: 'Redefinido com sucesso', + creatingInfo: 'Criando, não é necessário realizar esta operação', + offlineTips: 'A versão offline não suporta esta operação', + errImportFormat: 'Dados de importação ou formato estão anormais, verifique e tente novamente!', + importHelper: + 'Ao importar dados conflitantes ou duplicados, o conteúdo importado será usado como padrão para atualizar os dados originais do banco de dados.', + errImport: 'O conteúdo do arquivo está anormal:', + }, + login: { + username: 'Usuário', + password: 'Senha', + passkey: 'Login com Passkey', + welcome: 'Bem-vindo de volta, insira seu usuário e senha para acessar!', + errorAuthInfo: 'O nome de usuário ou senha que você inseriu está incorreto, tente novamente!', + errorMfaInfo: 'Informações de autenticação incorretas, tente novamente!', + captchaHelper: 'Captcha', + errorCaptcha: 'Erro no código Captcha!', + notSafe: 'Acesso Negado', + safeEntrance1: 'O login seguro foi habilitado no ambiente atual', + safeEntrance2: + 'Digite o seguinte comando no terminal SSH para visualizar a entrada do painel: 1pctl user-info', + errIP1: 'O acesso por endereço IP autorizado está habilitado no ambiente atual', + errDomain1: 'O acesso por nome de domínio vinculado está habilitado no ambiente atual', + errHelper: 'Para redefinir as informações de vinculação, execute o seguinte comando no terminal SSH:', + codeInput: 'Por favor, insira o código de verificação de 6 dígitos do validador MFA', + mfaTitle: 'Autenticação MFA', + mfaCode: 'Código de verificação MFA', + title: 'Painel de Gerenciamento de Servidores Linux', + licenseHelper: '', + errorAgree: 'Clique para concordar com o Acordo de Licença de Software Comunitário', + logout: 'Sair', + agreeTitle: 'Termo de Aceite', + agreeContent: + 'Para proteger melhor seus direitos e interesses legítimos, leia e concorde com o seguinte termo « Acordo de Licença Comunitária »', + passkeyFailed: 'Falha no login com Passkey, tente novamente', + passkeyNotSupported: 'O navegador ou ambiente atual não suporta passkeys', + passkeyToPassword: 'Está com problemas para usar uma passkey? Use a senha', + }, + rule: { + username: 'Insira um nome de usuário', + password: 'Insira uma senha', + rePassword: 'A confirmação da senha não corresponde à senha.', + requiredInput: 'Este campo é obrigatório.', + requiredSelect: 'Selecione um item na lista', + illegalChar: 'Atualmente não há suporte para injeção dos caracteres & ; $ \' ` ( ) " > < |', + illegalInput: 'Este campo não deve conter caracteres ilegais.', + commonName: + 'Este campo deve começar com caracteres não especiais e consistir em letras, números, ".", "-", e "_" com comprimento de 1-128.', + userName: 'Suporta não começar com caracteres especiais, inglês, chinês, números e _, comprimento 3-30', + simpleName: + 'Este campo não deve começar com "_" e deve conter letras, números e "_" com comprimento de 3-30.', + simplePassword: + 'Este campo não deve começar com "_" e deve conter letras, números e "_" com comprimento de 1-30.', + dbName: 'Este campo não deve começar com "_" e deve conter letras, números e "_" com comprimento de 1-64.', + imageName: 'Suporta não começar com caracteres especiais, inglês, números, :@/.-_, comprimento 1-256', + composeName: + 'Deve começar com caracteres não especiais, conter letras minúsculas, números, "-" e "_" com comprimento de 1-256.', + volumeName: 'Este campo deve conter letras, números, ".", "-", e "_" com comprimento de 2-30.', + supervisorName: + 'Este campo deve começar com caracteres não especiais e conter letras, números, "-" e "_" com comprimento de 1-128.', + complexityPassword: + 'Este campo deve conter letras, números com comprimento de 8-30 e pelo menos dois caracteres especiais.', + commonPassword: 'O comprimento deste campo deve ser maior que 6.', + linuxName: + 'O comprimento deste campo deve estar entre 1 e 128. Não pode conter os seguintes caracteres especiais: "{0}".', + email: 'Este campo deve ser um endereço de email válido.', + number: 'Este campo deve ser um número.', + integer: 'Este campo deve ser um número inteiro positivo.', + ip: 'Este campo deve ser um endereço IP válido.', + host: 'Este campo deve ser um endereço IP ou nome de domínio válido.', + hostHelper: 'Suporta a entrada de endereço IP ou nome de domínio', + port: 'Este campo deve ser um número de porta válido.', + selectHelper: 'Selecione o arquivo {0} correto', + domain: 'Este campo deve estar no formato: exemplo.com ou exemplo.com:8080.', + databaseName: 'Este campo deve conter letras, números e "_" com comprimento de 1-30.', + ipErr: 'Este campo deve ser um endereço IP válido.', + numberRange: 'Este campo deve ser um número entre {0} e {1}.', + paramName: 'Este campo deve conter letras, números, ".", "-", e "_" com comprimento de 2-30.', + paramComplexity: + 'Este campo não deve começar ou terminar com caracteres especiais e deve conter letras, números e "{0}" com comprimento de 6-128.', + paramUrlAndPort: 'Este campo deve estar no formato "http(s)://(nome do domínio/IP):(porta)".', + nginxDoc: 'Este campo deve conter letras, números e ".".', + appName: + 'Este campo não deve começar ou terminar com "-" ou "_" e deve conter letras, números, "-", e "_" com comprimento de 2-30.', + containerName: + 'Suporta letras, números, "-", "_" e "."; não pode começar com "-", "_" ou "."; comprimento: 2-128.', + mirror: 'O endereço de aceleração do mirror deve começar com http(s)://, suportar letras (maiúsculas e minúsculas), números, ".", "/" e "-", e não deve conter linhas em branco.', + disableFunction: 'Suporta apenas letras, underscores e,', + leechExts: 'Suporta apenas letras, números e,', + paramSimple: 'Suporta letras minúsculas e números, comprimento 1-128', + filePermission: 'Erro de permissão de arquivo', + formatErr: 'Erro de formato, verifique e tente novamente', + phpExtension: 'Suporta apenas _, letras minúsculas e números', + paramHttp: 'Deve começar com http:// ou https://', + phone: 'O formato do número de telefone está incorreto', + authBasicPassword: 'Suporta letras, números e caracteres especiais comuns, comprimento 1-72', + length128Err: 'O comprimento não pode exceder 128 caracteres', + maxLength: 'O comprimento não pode exceder {0} caracteres', + alias: 'Suporta letras, números, - e _, comprimento de 1 a 128, e não pode começar ou terminar com -_.', + }, + res: { + paramError: 'A solicitação falhou, por favor, tente novamente mais tarde!', + forbidden: 'O usuário atual não tem permissão', + serverError: 'Exceção no serviço', + notFound: 'O recurso não existe', + commonError: 'A solicitação falhou', + }, + service: { + serviceNotStarted: `O serviço {0} não está iniciado.`, + }, + status: { + running: 'Em execução', + done: 'Concluído', + scanFailed: 'Incompleto', + success: 'Sucesso', + waiting: 'Aguardando', + waitForUpgrade: 'Aguardar Atualização', + waiting1: 'Aguardando', + failed: 'Falhou', + stopped: 'Parado', + error: 'Erro', + created: 'Criado', + restarting: 'Reiniciando', + uploading: 'Enviando', + unhealthy: 'Indisponível', + removing: 'Removendo', + paused: 'Pausado', + exited: 'Finalizado', + dead: 'Morto', + installing: 'Instalando', + enabled: 'Habilitado', + disabled: 'Desabilitado', + normal: 'Normal', + building: 'Construindo', + upgrading: 'Atualizando', + pending: 'Aguardando Edição', + rebuilding: 'Reconstruindo', + deny: 'Negado', + accept: 'Aceito', + used: 'Usado', + unUsed: 'Não usado', + starting: 'Iniciando', + recreating: 'Reconstruindo', + creating: 'Criando', + init: 'Aguardando aplicação', + ready: 'Normal', + applying: 'Aplicando', + uninstalling: 'Desinstalando', + lost: 'Perdido', + bound: 'Vinculado', + unbind: 'Desvinculado', + exceptional: 'Excepcional', + free: 'Livre', + enable: 'Habilitado', + disable: 'Desabilitado', + deleted: 'Excluído', + downloading: 'Baixando', + packing: 'Empacotando', + sending: 'Enviando', + healthy: 'Saudável', + executing: 'Executando', + installerr: 'Falha na instalação', + applyerror: 'Falha na aplicação', + systemrestart: 'Interrompido', + starterr: 'Falha na inicialização', + uperr: 'Falha na inicialização', + new: 'Novo', + conflict: 'Conflito', + duplicate: 'Duplicado', + unexecuted: 'Não Executado', + }, + units: { + second: 'segundo | segundos | segundos', + minute: 'minuto | minutos | minutos', + hour: 'hora | horas | horas', + day: 'dia | dias | dias', + week: 'semana | semanas | semanas', + month: 'mês | meses | meses', + year: 'ano | anos | anos', + time: 'rqm', + core: 'núcleo | núcleos | núcleos', + secondUnit: 's', + minuteUnit: 'min', + hourUnit: 'h', + dayUnit: 'd', + millisecond: 'Milissegundo', + }, + log: { + noLog: 'Nenhum log disponível', + }, + }, + menu: { + home: 'Visão Geral', + apps: 'Loja de Aplicativos', + website: 'Site | Sites', + project: 'Projeto | Projetos', + config: 'Configuração | Configurações', + ssh: 'Configurações SSH', + firewall: 'Firewall', + ssl: 'Certificado | Certificados', + database: 'Banco de Dados | Bancos de Dados', + aiTools: 'AI', + mcp: 'MCP', + container: 'Container | Containers', + cronjob: 'Tarefa Cron | Tarefas Cron', + system: 'Sistema', + security: 'Segurança', + files: 'Arquivos', + monitor: 'Monitoramento', + terminal: 'Terminal', + settings: 'Configuração | Configurações', + toolbox: 'Caixa de Ferramentas', + logs: 'Log | Logs', + runtime: 'Tempo de Execução | Tempos de Execução', + processManage: 'Processo | Processos', + process: 'Processo | Processos', + network: 'Rede | Redes', + supervisor: 'Supervisor', + tamper: 'À prova de violação', + app: 'Aplicativo', + msgCenter: 'Central de Tarefas', + disk: 'Disco', + }, + home: { + recommend: 'recomendar', + dir: 'dir', + alias: 'Apelido', + quickDir: 'Diretório Rápido', + minQuickJump: 'Defina pelo menos uma entrada de salto rápido!', + maxQuickJump: 'Você pode definir até quatro entradas de salto rápido!', + database: 'Banco de Dados - Todos', + restart_1panel: 'Reiniciar painel', + restart_system: 'Reiniciar servidor', + operationSuccess: 'Operação bem-sucedida, reiniciando, por favor, atualize o navegador manualmente mais tarde!', + entranceHelper: + 'A entrada de segurança não está ativada. Você pode ativá-la em "Configurações -> Segurança" para melhorar a segurança do sistema.', + appInstalled: 'Aplicações', + systemInfo: 'Informações do sistema', + hostname: 'Nome do host', + platformVersion: 'Sistema operacional', + kernelVersion: 'Versão do Kernel', + kernelArch: 'Arquitetura', + network: 'Rede', + io: 'Leitura/Gravação de Disco', + ip: 'IP local', + proxy: 'Proxy do sistema', + baseInfo: 'Informações básicas', + totalSend: 'Total enviado', + totalRecv: 'Total recebido', + rwPerSecond: 'Operações de I/O', + ioDelay: 'Latência de I/O', + uptime: 'Tempo de atividade', + runningTime: 'Tempo de execução', + mem: 'Memória do Sistema', + swapMem: 'Partição Swap', + + runSmoothly: 'Baixo carregamento', + runNormal: 'Carregamento moderado', + runSlowly: 'Carregamento alto', + runJam: 'Carregamento pesado', + + core: 'Núcleo físico', + logicCore: 'Núcleo lógico', + corePercent: 'Uso do Núcleo', + cpuFrequency: 'Frequência CPU', + cpuDetailedPercent: 'Distribuição do Tempo de CPU', + cpuUser: 'Usuário', + cpuSystem: 'Sistema', + cpuIdle: 'Ocioso', + cpuIrq: 'IRQ', + cpuSoftirq: 'Soft IRQ', + cpuSteal: 'Roubado', + cpuTop: 'Top 5 Processos por Uso de CPU', + memTop: 'Top 5 Processos por Uso de Memória', + loadAverage: 'Média de carga nos últimos 1 minuto | Média de carga nos últimos {n} minutos', + load: 'Carga', + mount: 'Ponto de montagem', + fileSystem: 'Sistema de arquivos', + total: 'Total', + used: 'Usado', + cache: 'Cache', + free: 'Livre', + shard: 'Fragmentado', + available: 'Disponível', + percent: 'Utilização', + goInstall: 'Ir para instalação', + + networkCard: 'Placa de rede', + disk: 'Disco', + }, + tabs: { + more: 'Mais', + hide: 'Esconder', + closeLeft: 'Fechar à esquerda', + closeRight: 'Fechar à direita', + closeCurrent: 'Fechar atual', + closeOther: 'Fechar outros', + closeAll: 'Fechar todos', + }, + header: { + logout: 'Logout', + }, + database: { + manage: 'Gerenciamento', + deleteBackupHelper: 'Excluir backups do banco de dados simultaneamente', + delete: 'A operação de exclusão não pode ser desfeita, insira "', + deleteHelper: '" para excluir este banco de dados', + create: 'Criar banco de dados', + noMysql: 'Serviço de banco de dados (MySQL ou MariaDB)', + noPostgresql: 'Serviço de banco de dados PostgreSQL', + goUpgrade: 'Ir para atualização', + goInstall: 'Ir para instalação', + isDelete: 'Excluído', + permission: 'Permissões', + format: 'Conjunto de Caracteres', + collation: 'Collation', + collationHelper: 'Se vazio, use a collation padrão do conjunto de caracteres {0}', + permissionForIP: 'IP', + permissionAll: 'Todos (% de)', + localhostHelper: + 'Configurar permissões de banco de dados como "localhost" para implantação em contêiner impedirá o acesso externo ao contêiner. Por favor, escolha com cuidado!', + databaseConnInfo: 'Informações de conexão', + rootPassword: 'Senha root', + serviceName: 'Nome do serviço', + serviceNameHelper: 'Acesso entre containers na mesma rede.', + backupList: 'Backup', + loadBackup: 'Importar', + localUpload: 'Upload Local', + hostSelect: 'Seleção de Servidor', + selectHelper: 'Tem certeza de que deseja importar o arquivo de backup {0}?', + remoteAccess: 'Acesso remoto', + remoteHelper: 'Vários IPs separados por vírgula, exemplo: 172.16.10.111, 172.16.10.112', + remoteConnHelper: + 'Conectar-se ao MySQL como usuário root pode representar riscos de segurança. Realize esta operação com cautela.', + changePassword: 'Senha', + changeConnHelper: 'Esta operação modificará o banco de dados atual {0}. Deseja continuar?', + changePasswordHelper: + 'O banco de dados está associado a um aplicativo. Alterar a senha alterará a senha do banco de dados do aplicativo ao mesmo tempo. A mudança surtirá efeito após a reinicialização do aplicativo.', + recoverTimeoutHelper: '-1 significa sem limite de tempo limite', + + confChange: 'Configuração', + confNotFound: + 'Não foi possível encontrar o arquivo de configuração. Atualize o aplicativo para a versão mais recente na loja de aplicativos e tente novamente!', + + portHelper: + 'Esta porta é a porta exposta do container. Você precisa salvar a modificação separadamente e reiniciar o container!', + + loadFromRemote: 'Sincronizar', + userBind: 'Vincular usuário', + pgBindHelper: `Esta operação cria um novo usuário e o vincula ao banco de dados alvo. A seleção de usuários já existentes no banco de dados não é suportada.`, + pgSuperUser: 'Superusuário', + loadFromRemoteHelper: + 'Isso sincronizará as informações do banco de dados no servidor para o 1Panel. Deseja continuar?', + passwordHelper: 'Não é possível obter, clique para modificar', + remote: 'Remoto', + remoteDB: 'Servidor remoto | Servidores remotos', + createRemoteDB: 'Vincular @.lower:database.remoteDB', + unBindRemoteDB: 'Desvincular @.lower:database.remoteDB', + unBindForce: 'Forçar desvinculação', + unBindForceHelper: + 'Ignorar todos os erros durante o processo de desvinculação para garantir que a operação final seja bem-sucedida', + unBindRemoteHelper: + 'Desvincular o banco de dados remoto removerá apenas a relação de vinculação e não excluirá diretamente o banco de dados remoto', + editRemoteDB: 'Editar servidor remoto', + localDB: 'Banco de dados local', + address: 'Endereço do banco de dados', + version: 'Versão do banco de dados', + userHelper: + 'O usuário root ou um usuário do banco de dados com privilégios de root pode acessar o banco de dados remoto.', + pgUserHelper: 'Usuário do banco de dados com privilégios de superusuário.', + ssl: 'Usar SSL', + clientKey: 'Chave privada do cliente', + clientCert: 'Certificado do cliente', + caCert: 'Certificado CA', + hasCA: 'Possui certificado CA', + skipVerify: 'Ignorar verificação de validade do certificado', + initialDB: 'Banco de Dados Inicial', + + formatHelper: + 'O conjunto de caracteres atual do banco de dados é {0}, a inconsistência no conjunto de caracteres pode causar falha na recuperação', + dropHelper: 'Você pode arrastar e soltar o arquivo carregado aqui ou', + clickHelper: 'clicar para fazer upload', + supportUpType: + 'Suporta apenas os formatos de arquivo sql, sql.gz, tar.gz, .zip. O arquivo compactado importado deve conter apenas um arquivo .sql ou incluir test.sql', + + currentStatus: 'Estado atual', + baseParam: 'Parâmetro básico', + performanceParam: 'Parâmetro de desempenho', + runTime: 'Tempo de inicialização', + connections: 'Conexões totais', + bytesSent: 'Bytes enviados', + bytesReceived: 'Bytes recebidos', + queryPerSecond: 'Consultas por segundo', + txPerSecond: 'Tx por segundo', + connInfo: 'conexões ativas/pico', + connInfoHelper: 'Se o valor for muito grande, aumente "max_connections".', + threadCacheHit: 'Acuracidade do cache de threads', + threadCacheHitHelper: 'Se for muito baixo, aumente "thread_cache_size".', + indexHit: 'Acuracidade de índice', + indexHitHelper: 'Se for muito baixo, aumente "key_buffer_size".', + innodbIndexHit: 'Taxa de acerto de índice InnoDB', + innodbIndexHitHelper: 'Se for muito baixo, aumente "innodb_buffer_pool_size".', + cacheHit: 'Acuracidade da consulta em cache', + cacheHitHelper: 'Se for muito baixo, aumente "query_cache_size".', + tmpTableToDB: 'Tabela temporária para disco', + tmpTableToDBHelper: 'Se for muito grande, tente aumentar "tmp_table_size".', + openTables: 'Tabelas abertas', + openTablesHelper: 'O valor da configuração de "table_open_cache" deve ser maior ou igual a este valor.', + selectFullJoin: 'Join completo de seleção', + selectFullJoinHelper: `Se o valor não for 0, verifique se o índice da tabela de dados está correto.`, + selectRangeCheck: 'Número de joins sem índice', + selectRangeCheckHelper: `Se o valor não for 0, verifique se o índice da tabela de dados está correto.`, + sortMergePasses: 'Número de mesclagens ordenadas', + sortMergePassesHelper: 'Se o valor for muito grande, aumente "sort_buffer_size".', + tableLocksWaited: 'Número de bloqueios de tabela', + tableLocksWaitedHelper: 'Se o valor for muito grande, considere aumentar o desempenho do banco de dados.', + + performanceTuning: 'Ajuste de desempenho', + optimizationScheme: 'Plano de otimização', + keyBufferSizeHelper: 'Tamanho do buffer para índice', + queryCacheSizeHelper: 'Cache de consulta. Se essa função estiver desativada, defina este parâmetro como 0.', + tmpTableSizeHelper: 'Tamanho do cache de tabela temporária', + innodbBufferPoolSizeHelper: 'Tamanho do buffer InnoDB', + innodbLogBufferSizeHelper: 'Tamanho do buffer de log InnoDB', + sortBufferSizeHelper: '* conexões, buffer de cada thread de ordenação', + readBufferSizeHelper: '* conexões, tamanho do buffer de leitura', + readRndBufferSizeHelper: '* conexões, tamanho do buffer de leitura aleatória', + joinBufferSizeHelper: '* conexões, tamanho do cache da tabela de associação', + threadStackelper: '* conexões, tamanho do stack por thread', + binlogCacheSizeHelper: '* conexões, tamanho do cache de log binário (múltiplos de 4096)', + threadCacheSizeHelper: 'Tamanho do pool de threads', + tableOpenCacheHelper: 'Cache de tabelas', + maxConnectionsHelper: 'Conexões máximas', + restart: 'Reiniciar', + + slowLog: 'Logs lentos', + noData: 'Ainda não há logs lentos.', + + isOn: 'Ligado', + longQueryTime: 'limite (segundos)', + thresholdRangeHelper: 'Por favor, insira o limite correto (1 - 600).', + + timeout: 'Tempo limite(segundos)', + timeoutHelper: 'Período de timeout de conexão ociosa. 0 indica que a conexão permanece ativa continuamente.', + maxclients: 'Máximo de clientes', + requirepassHelper: + 'Deixe este campo em branco para indicar que nenhuma senha foi definida. Mudanças precisam ser salvas separadamente e o container reiniciado!', + databases: 'Número de bancos de dados', + maxmemory: 'Uso máximo de memória', + maxmemoryHelper: '0 indica sem restrição.', + tcpPort: 'Porta de escuta atual.', + uptimeInDays: 'Dias de operação.', + connectedClients: 'Número de clientes conectados.', + usedMemory: 'Uso atual de memória do Redis.', + usedMemoryRss: 'Tamanho da memória solicitado ao sistema operacional.', + usedMemoryPeak: 'Pico de consumo de memória do Redis.', + memFragmentationRatio: 'Taxa de fragmentação de memória.', + totalConnectionsReceived: 'Total de clientes conectados desde a execução.', + totalCommandsProcessed: 'Número total de comandos executados desde a execução.', + instantaneousOpsPerSec: 'Número de comandos executados pelo servidor por segundo.', + keyspaceHits: 'Número de vezes que uma chave de banco de dados foi encontrada.', + keyspaceMisses: 'Número de tentativas falhas de encontrar a chave do banco de dados.', + hit: 'Taxa de acerto de chave de banco de dados.', + latestForkUsec: 'Número de microssegundos gastos na última operação fork()', + redisCliHelper: `"redis-cli" não foi detectado. Habilite o serviço primeiro.`, + redisQuickCmd: 'Comandos rápidos Redis', + recoverHelper: 'Isso sobrescreverá os dados com [{0}]. Deseja continuar?', + submitIt: 'Sobrescrever os dados', + + baseConf: 'Básico', + allConf: 'Todos', + restartNow: 'Reiniciar agora', + restartNowHelper1: + 'Você precisa reiniciar o sistema após as mudanças na configuração entrarem em vigor. Se seus dados precisarem ser persistidos, execute primeiro a operação de salvar.', + restartNowHelper: 'Isso só terá efeito após o sistema reiniciar.', + + persistence: 'Persistência', + rdbHelper1: 'segundo(s), inserir', + rdbHelper2: 'registros de dados', + rdbHelper3: 'Atender a qualquer uma das condições acionará a persistência RDB.', + rdbInfo: 'Certifique-se de que o valor na lista de regras esteja entre 1 e 100000', + + containerConn: 'Conexão do contêiner', + connAddress: 'Endereço', + containerConnHelper: + 'Este endereço de conexão pode ser utilizado por aplicações que estão em execução nos ambientes do site (PHP, etc.) ou no contêiner.', + remoteConn: 'Conexão externa', + remoteConnHelper2: 'Use este endereço para ambientes não-container ou conexões externas.', + remoteConnHelper3: + 'O endereço de acesso padrão é o IP do host. Para modificá-lo, acesse o item de configuração "Endereço de Acesso Padrão" na página de configurações do painel.', + localIP: 'IP local', + }, + aiTools: { + model: { + model: 'Modelo', + create: 'Adicionar Modelo', + create_helper: 'Puxar "{0}"', + ollama_doc: 'Você pode visitar o site oficial da Ollama para pesquisar e encontrar mais modelos.', + container_conn_helper: 'Use este endereço para acesso ou conexão entre contêineres', + ollama_sync: + 'Menyelaraskan model Ollama mendapati model berikut tidak wujud, adakah anda ingin memadamnya?', + from_remote: 'Model ini tidak dimuat turun melalui 1Panel, tiada log pengambilan berkaitan.', + no_logs: 'Log pengambilan untuk model ini telah dipadam dan tidak dapat dilihat.', + }, + proxy: { + proxy: 'Melhoria de Proxy AI', + proxyHelper1: 'Vincule o domínio e habilite o HTTPS para aumentar a segurança na transmissão', + proxyHelper2: 'Limite o acesso por IP para evitar exposição na internet pública', + proxyHelper3: 'Habilite a transmissão em fluxo', + proxyHelper4: 'Após a criação, você pode visualizar e gerenciar no lista de sites', + proxyHelper6: 'Para desativar a configuração de proxy, você pode excluí-la da lista de sites.', + whiteListHelper: 'Restringir o acesso apenas aos IPs na lista branca', + }, + gpu: { + gpu: 'Monitoramento de GPU', + gpuHelper: 'O sistema não detectou comandos NVIDIA-SMI ou XPU-SMI. Verifique e tente novamente!', + process: 'Informações do Processo', + type: 'Tipo', + typeG: 'Gráficos', + typeC: 'Computação', + typeCG: 'Computação+Gráficos', + processName: 'Nome do Processo', + shr: 'Memória Compartilhada', + temperatureHelper: 'Alta temperatura da GPU pode causar redução na frequência da GPU', + gpuUtil: 'Utilização da GPU', + temperature: 'Temperatura', + performanceState: 'Estado de Desempenho', + powerUsage: 'Consumo de Energia', + memoryUsage: 'Utilização de Memória', + fanSpeed: 'Velocidade do Ventilador', + power: 'Energia', + powerCurrent: 'Energia Atual', + powerLimit: 'Limite de Energia', + memory: 'Memória', + memoryUsed: 'Memória Usada', + memoryTotal: 'Memória Total', + percent: 'Utilização', + + base: 'Informações Básicas', + driverVersion: 'Versão do Driver', + cudaVersion: 'Versão do CUDA', + processMemoryUsage: 'Uso de Memória', + performanceStateHelper: 'De P0 (desempenho máximo) a P12 (desempenho mínimo)', + busID: 'Endereço do Barramento', + persistenceMode: 'Modo de Persistência', + enabled: 'Habilitado', + disabled: 'Desabilitado', + persistenceModeHelper: + 'O modo de persistência responde às tarefas mais rapidamente, mas o consumo de energia em espera aumentará correspondentemente', + displayActive: 'Inicialização da GPU', + displayActiveT: 'Sim', + displayActiveF: 'Não', + ecc: 'Tecnologia de Verificação e Correção de Erros', + computeMode: 'Modo de Computação', + default: 'Padrão', + exclusiveProcess: 'Processo Exclusivo', + exclusiveThread: 'Thread Exclusiva', + prohibited: 'Proibido', + defaultHelper: 'Padrão: Os processos podem executar simultaneamente', + exclusiveProcessHelper: + 'Processo Exclusivo: Apenas um contexto CUDA pode usar a GPU, mas pode ser compartilhado por múltiplas threads', + exclusiveThreadHelper: 'Thread Exclusiva: Apenas uma thread em um contexto CUDA pode usar a GPU', + prohibitedHelper: 'Proibido: A execução simultânea de processos não é permitida', + migModeHelper: + 'Usado para criar instâncias MIG, implementando isolamento físico da GPU na camada do usuário.', + migModeNA: 'Não Suportado', + current: 'Monitoramento em Tempo Real', + history: 'Registros Históricos', + notSupport: 'A versão atual ou o driver não suportam exibir este parâmetro.', + }, + mcp: { + server: 'Servidor MCP', + create: 'Adicionar Servidor', + edit: 'Editar Servidor', + baseUrl: 'Caminho de Acesso Externo', + baseUrlHelper: 'Por exemplo: http://192.168.1.2:8000', + ssePath: 'Caminho SSE', + ssePathHelper: 'Por exemplo: /sse, tome cuidado para não duplicar com outros servidores', + environment: 'Variáveis de Ambiente', + envKey: 'Nome da Variável', + envValue: 'Valor da Variável', + externalUrl: 'Endereço de Conexão Externo', + operatorHelper: 'Será realizada a operação {1} no {0}, continuar?', + domain: 'Endereço de Acesso Padrão', + domainHelper: 'Por exemplo: 192.168.1.1 ou example.com', + bindDomain: 'Vincular Site', + commandPlaceHolder: 'Atualmente, apenas comandos de inicialização npx e binários são suportados', + importMcpJson: 'Importar Configuração do Servidor MCP', + importMcpJsonError: 'A estrutura mcpServers está incorreta', + bindDomainHelper: + 'Após vincular o site, ele modificará o endereço de acesso de todos os servidores MCP instalados e fechará o acesso externo às portas', + outputTransport: 'Tipo de Saída', + streamableHttpPath: 'Caminho de Streaming', + streamableHttpPathHelper: 'Por exemplo: /mcp, certifique-se de que não se sobreponha a outros Servidores', + npxHelper: 'Adequado para mcp iniciado com npx ou binário', + uvxHelper: 'Adequado para mcp iniciado com uvx', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: 'Diretório do Modelo', + commandHelper: + 'Se for necessário acesso externo, defina a porta no comando para ser a mesma que a porta do aplicativo', + imageAlert: + 'Devido ao grande tamanho da imagem, recomenda-se baixar manualmente a imagem para o servidor antes da instalação', + modelSpeedup: 'Ativar aceleração de modelo', + modelType: 'Tipo de modelo', + }, + }, + container: { + create: 'Criar contêiner', + edit: 'Editar contêiner', + updateHelper1: 'Detectamos que este contêiner vem da loja de aplicativos. Observe os seguintes dois pontos:', + updateHelper2: + '1. As modificações atuais não serão sincronizadas com os aplicativos instalados na loja de aplicativos.', + updateHelper3: + '2. Se você modificar o aplicativo na página de instalados, o conteúdo atualmente editado se tornará inválido.', + updateHelper4: + 'Editar o contêiner requer reconstrução, e qualquer dado não persistente será perdido. Deseja continuar?', + containerList: 'Lista de contêineres', + operatorHelper: '{0} será realizado no seguinte contêiner. Deseja continuar?', + operatorAppHelper: + 'A operação "{0}" será realizada no(s) seguinte(s) contêiner(es) e pode afetar os serviços em execução. Deseja continuar?', + containerDeleteHelper: + "Detectado que o contêiner é da App Store. Excluir o contêiner não o removerá completamente do 1Panel. Para excluí-lo completamente, vá para a App Store -> menus 'Instalado' ou 'Ambiente de Execução'. Continuar?", + start: 'Iniciar', + stop: 'Parar', + restart: 'Reiniciar', + kill: 'Finalizar', + pause: 'Pausar', + unpause: 'Retomar', + rename: 'Renomear', + remove: 'Remover', + removeAll: 'Remover todos', + containerPrune: 'Limpar', + containerPruneHelper1: 'Isso excluirá todos os contêineres que estão no estado parado.', + containerPruneHelper2: + 'Se os contêineres forem da loja de aplicativos, você precisará ir para "Loja de Aplicativos -> Instalados" e clicar no botão "Reconstruir" para reinstalá-los após a limpeza.', + containerPruneHelper3: 'Esta operação não pode ser desfeita. Deseja continuar?', + imagePrune: 'Limpar', + imagePruneSome: 'Limpar sem rótulo', + imagePruneSomeEmpty: 'Não há imagens com a tag "none" para limpar.', + imagePruneSomeHelper: 'Limpar as imagens com a tag "none" que não são usadas por nenhum contêiner.', + imagePruneAll: 'Limpar não utilizadas', + imagePruneAllEmpty: 'Não há imagens não utilizadas para limpar.', + imagePruneAllHelper: 'Limpar as imagens que não são usadas por nenhum contêiner.', + networkPrune: 'Limpar', + networkPruneHelper: 'Isso removerá todas as redes não utilizadas. Deseja continuar?', + volumePrune: 'Limpar', + volumePruneHelper: 'Isso removerá todos os volumes locais não utilizados. Deseja continuar?', + cleanSuccess: 'A operação foi bem-sucedida, o número desta limpeza: {0}!', + cleanSuccessWithSpace: + 'A operação foi bem-sucedida. O número de discos limpos desta vez é {0}. O espaço em disco liberado foi {1}!', + unExposedPort: 'O endereço atual de mapeamento de porta é 127.0.0.1, o que não permite o acesso externo.', + upTime: 'Tempo de atividade', + fetch: 'Buscar', + lines: 'Linhas', + linesHelper: 'Por favor, insira o número correto de logs a serem recuperados!', + lastDay: 'Último dia', + last4Hour: 'Últimas 4 horas', + lastHour: 'Última hora', + last10Min: 'Últimos 10 minutos', + cleanLog: 'Limpar log', + downLogHelper1: 'Isso fará o download de todos os logs do contêiner {0}. Deseja continuar?', + downLogHelper2: 'Isso fará o download dos últimos {0} logs do contêiner {0}. Deseja continuar?', + cleanLogHelper: 'Isso exigirá a reinicialização do contêiner e não poderá ser desfeito. Deseja continuar?', + newName: 'Novo nome', + source: 'Uso de recursos', + cpuUsage: 'Uso de CPU', + cpuTotal: 'CPU total', + core: 'Núcleo', + memUsage: 'Uso de memória', + memTotal: 'Limite de memória', + memCache: 'Cache de memória', + loadSize: 'Obter Tamanho do Contêiner', + ip: 'Endereço IP', + cpuShare: 'Atribuição de CPU', + cpuShareHelper: + 'O mecanismo de contêiner usa um valor base de 1024 para a atribuição de CPU. Você pode aumentá-lo para dar mais tempo de CPU ao contêiner.', + inputIpv4: 'Exemplo: 192.168.1.1', + inputIpv6: 'Exemplo: 2001:0db8:85a3:0000:0000:8a2e:0370:7334', + + diskUsage: 'Uso do Disco', + localVolume: 'Volume de Armazenamento Local', + buildCache: 'Cache de Build', + usage: 'Usado: {0}, Liberável: {1}', + clean: 'Liberar', + imageClean: + 'Limpar imagens excluirá todas as imagens não utilizadas. Esta operação não pode ser desfeita. Continuar?', + containerClean: + 'Limpar contêineres excluirá todos os contêineres parados (incluindo aplicativos parados da Loja de Aplicativos). Esta operação não pode ser desfeita. Continuar?', + sizeRw: 'Tamanho da Camada do Contêiner', + sizeRwHelper: 'Tamanho da camada gravável exclusiva do contêiner', + sizeRootFs: 'Tamanho Virtual', + sizeRootFsHelper: + 'Tamanho total de todas as camadas de imagem das quais o contêiner depende + camada do contêiner', + + containerFromAppHelper: + 'Detectamos que este contêiner vem da loja de aplicativos. As operações no aplicativo podem fazer com que as edições atuais sejam invalidadas.', + containerFromAppHelper1: + 'Clique no botão [Param] na lista de aplicativos instalados para acessar a página de edição e modificar o nome do contêiner.', + command: 'Comando', + console: 'Interação com o contêiner', + tty: 'Atribuir pseudo-TTY (-t)', + openStdin: 'Manter STDIN aberto mesmo que não esteja anexado (-i)', + custom: 'Personalizado', + emptyUser: 'Se estiver vazio, você fará login com o padrão', + privileged: 'Privilegiado', + privilegedHelper: + 'Permite que o contêiner execute determinadas operações privilegiadas no host, o que pode aumentar os riscos do contêiner. Use com cautela!', + upgradeHelper: 'Nome do Repositório/Nome da Imagem: Versão da Imagem', + upgradeWarning2: + 'A operação de upgrade requer a reconstrução do contêiner, e qualquer dado não persistente será perdido. Deseja continuar?', + oldImage: 'Imagem atual', + sameImageContainer: 'Contêineres com mesma imagem', + sameImageHelper: 'Contêineres usando a mesma imagem podem ser atualizados em lote após seleção', + targetImage: 'Imagem alvo', + imageLoadErr: 'Nenhum nome de imagem detectado para o contêiner', + appHelper: 'O contêiner vem da loja de aplicativos, e o upgrade pode tornar o serviço indisponível.', + input: 'Entrada manual', + forcePull: 'Sempre puxar imagem', + forcePullHelper: + 'Isso ignorará as imagens existentes no servidor e puxará a imagem mais recente do repositório.', + server: 'Host', + serverExample: '80, 80-88, ip:80 ou ip:80-88', + containerExample: '80 ou 80-88', + exposePort: 'Expor porta', + exposeAll: 'Expor todas', + cmdHelper: 'Exemplo: nginx -g "daemon off;"', + entrypointHelper: 'Exemplo: docker-entrypoint.sh', + autoRemove: 'Remover automaticamente', + cpuQuota: 'Número de núcleos de CPU', + memoryLimit: 'Memória', + limitHelper: 'Se definido como 0, significa que não há limitação. O valor máximo é {0}', + mount: 'Montagem', + volumeOption: 'Volume', + hostOption: 'Host', + serverPath: 'Caminho do servidor', + containerDir: 'Caminho do contêiner', + networkEmptyHelper: 'Por favor confirme que a seleção de rede do contêiner está correta', + modeRW: 'RW', + modeR: 'R', + sharedLabel: 'Modo de Propagação', + private: 'Privado', + privateHelper: 'As alterações de montagem no container e no host não se afetam mutuamente', + rprivate: 'Privado Recursivo', + rprivateHelper: 'Todas as montagens no container estão completamente isoladas do host', + shared: 'Compartilhado', + sharedHelper: 'As alterações de montagem no host e no container são visíveis entre si', + rshared: 'Compartilhado Recursivo', + rsharedHelper: 'Todas as alterações de montagem no host e no container são visíveis entre sí', + slave: 'Escravo', + slaveHelper: + 'O container pode ver as alterações de montagem do host, mas suas próprias alterações não afetam o host', + rslave: 'Escravo Recursivo', + rslaveHelper: 'Todas as montagens no container podem ver as alterações do host, mas não afetam o host', + mode: 'Modo', + env: 'Ambientes', + restartPolicy: 'Política de reinício', + always: 'sempre', + unlessStopped: 'a menos que parado', + onFailure: 'em falha (cinco vezes por padrão)', + no: 'nunca', + refreshTime: 'Intervalo de atualização', + cache: 'Cache', + image: 'Imagem | Imagens', + imagePull: 'Puxar', + imagePullHelper: + 'Suporta selecionar múltiplas imagens para puxar, pressione Enter após inserir cada imagem para continuar', + imagePush: 'Enviar', + imagePushHelper: + 'Detectado que esta imagem possui múltiplas tags. Por favor, confirme que o nome da imagem usada para push é: {0}', + imageDelete: 'Excluir imagem', + repoName: 'Registro de contêiner', + imageName: 'Nome da imagem', + pull: 'Puxar', + path: 'Caminho', + importImage: 'Importar', + buildArgs: 'Argumentos de Build', + imageBuild: 'Construção de imagem', + pathSelect: 'Caminho', + label: 'Etiqueta', + imageTag: 'Tag de imagem', + imageTagHelper: + 'Suporta definir múltiplas tags de imagem, pressione Enter após inserir cada tag para continuar', + push: 'Enviar', + fileName: 'Nome do arquivo', + export: 'Exportar', + exportImage: 'Exportar imagem', + size: 'Tamanho', + tag: 'Tags', + tagHelper: 'Uma por linha. Por exemplo,\nchave1=valor1\nchave2=valor2', + imageNameHelper: 'Nome da imagem e tag, por exemplo: nginx:latest', + cleanBuildCache: 'Limpar cache de construção', + delBuildCacheHelper: + 'Isso excluirá todos os artefatos em cache gerados durante as construções e não poderá ser desfeito. Deseja continuar?', + urlWarning: 'O prefixo da URL não precisa incluir http:// ou https://. Por favor, modifique.', + + network: 'Rede | Redes', + networkHelper: + 'Isso pode fazer com que alguns aplicativos e ambientes de execução não funcionem corretamente. Deseja continuar?', + createNetwork: 'Criar', + networkName: 'Nome', + driver: 'Driver', + option: 'Opção', + attachable: 'Anexável', + parentNetworkCard: 'Placa de Rede Principal', + subnet: 'Sub-rede', + scope: 'Escopo IP', + gateway: 'Gateway', + auxAddress: 'Excluir IP', + + volume: 'Volume | Volumes', + volumeDir: 'Diretório do volume', + nfsEnable: 'Habilitar armazenamento NFS', + nfsAddress: 'Endereço', + mountpoint: 'Ponto de montagem', + mountpointNFSHelper: 'Exemplo: /nfs, /nfs-share', + options: 'Opções', + createVolume: 'Criar', + + repo: 'Registries', + createRepo: 'Adicionar', + httpRepoHelper: 'Operar um repositório do tipo HTTP requer reinicialização do serviço Docker.', + httpRepo: + 'Escolher o protocolo HTTP requer reiniciar o serviço Docker para adicioná-lo a registries inseguros.', + delInsecure: 'Remover da lista de segurança', + delInsecureHelper: + 'Isso reiniciará o serviço Docker para removê-lo dos registries inseguros. Deseja continuar?', + downloadUrl: 'Servidor', + imageRepo: 'Repositório de imagens', + repoHelper: 'Inclui repositório espelho/organização/projeto?', + auth: 'Exigir autenticação', + mirrorHelper: + 'Se houver múltiplos espelhos, devem ser exibidos em novas linhas, por exemplo:\nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com', + registrieHelper: + 'Se houver múltiplos repositórios privados, eles devem ser exibidos em novas linhas, por exemplo:\n172.16.10.111:8081 \n172.16.10.112:8081', + + compose: 'Compose | Composições', + composeFile: 'Arquivo de Orquestração', + fromChangeHelper: 'Trocar a origem limpará o conteúdo editado atual. Deseja continuar?', + composePathHelper: 'Caminho de salvamento do arquivo de configuração: {0}', + composeHelper: + 'A composição criada através do editor ou template do 1Panel será salva no diretório {0}/docker/compose.', + deleteFile: 'Excluir arquivo', + deleteComposeHelper: + 'Excluir todos os arquivos relacionados à composição do container, incluindo arquivos de configuração e arquivos persistentes. Prossiga com cautela!', + deleteCompose: 'Excluir esta composição.', + createCompose: 'Criar', + composeDirectory: 'Diretório', + template: 'Template', + composeTemplate: 'Template de composição | Templates de composição', + createComposeTemplate: 'Criar', + content: 'Conteúdo', + contentEmpty: 'O conteúdo da composição não pode estar vazio, por favor, insira algo e tente novamente!', + containerNumber: 'Número de containers', + containerStatus: 'Status do container', + exited: 'Finalizado', + running: 'Em execução ( {0} / {1} )', + composeDetailHelper: + 'A composição foi criada externamente ao 1Panel. As operações de iniciar e parar não são suportadas.', + composeOperatorHelper: 'A operação {1} será realizada no {0}. Deseja continuar?', + composeDownHelper: + 'Isso irá parar e remover todos os containers e redes sob a composição {0}. Deseja continuar?', + composeEnvHelper2: + 'Esta orquestração foi criada pela Loja de Aplicativos 1Panel. Modifique as variáveis de ambiente nos aplicativos instalados.', + + setting: 'Configuração | Configurações', + operatorStatusHelper: 'Isso irá "{0}" o serviço Docker. Deseja continuar?', + dockerStatus: 'Serviço Docker', + daemonJsonPathHelper: + 'Certifique-se de que o caminho de configuração seja o mesmo especificado no docker.service.', + mirrors: 'Espelhos de registro', + mirrorsHelper: '', + mirrorsHelper2: 'Para mais detalhes, consulte a documentação oficial.', + registries: 'Registries inseguros', + ipv6Helper: + 'Ao habilitar o IPv6, é necessário adicionar uma rede de containers IPv6. Consulte a documentação oficial para etapas específicas de configuração.', + ipv6CidrHelper: 'Faixa de endereços IPv6 para containers', + ipv6TablesHelper: 'Configuração automática do Docker IPv6 para regras do iptables.', + experimentalHelper: + 'Habilitar ip6tables requer que esta configuração esteja ativada; caso contrário, ip6tables será ignorado', + cutLog: 'Opção de log', + cutLogHelper1: 'A configuração atual afetará apenas containers recém-criados.', + cutLogHelper2: 'Containers existentes precisam ser recriados para que a configuração tenha efeito.', + cutLogHelper3: + 'Observe que recriar containers pode resultar em perda de dados. Se seus containers contiverem dados importantes, faça backup antes de realizar a operação de reconstrução.', + maxSize: 'Tamanho máximo', + maxFile: 'Arquivo máximo', + liveHelper: + 'Por padrão, quando o daemon Docker termina, ele desliga os containers em execução. Você pode configurar o daemon para que os containers permaneçam em execução se o daemon ficar indisponível. Essa funcionalidade é chamada de "restauração ao vivo". A opção de restauração ao vivo ajuda a reduzir o tempo de inatividade dos containers devido a falhas do daemon, interrupções planejadas ou atualizações.', + liveWithSwarmHelper: 'A configuração de restauração ao vivo é incompatível com o modo swarm.', + iptablesDisable: 'Desabilitar iptables', + iptablesHelper1: 'Configuração automática das regras do iptables para Docker.', + iptablesHelper2: + 'Desabilitar iptables fará com que os containers não consigam se comunicar com redes externas.', + daemonJsonPath: 'Caminho de configuração', + serviceUnavailable: 'O serviço Docker não foi iniciado no momento.', + startIn: 'para iniciar', + sockPath: 'Caminho do socket Unix', + sockPathHelper: 'Canal de comunicação entre o daemon Docker e o cliente.', + sockPathHelper1: 'Caminho padrão: /var/run/docker-x.sock', + sockPathMsg: + 'Salvar a configuração do Caminho do Socket pode tornar o serviço Docker indisponível. Deseja continuar?', + sockPathErr: 'Por favor, selecione ou insira o caminho correto do arquivo do Docker sock', + related: 'Relacionado', + includeAppstore: 'Exibir containers da loja de aplicativos', + excludeAppstore: 'Ocultar Contêiner da Loja de Aplicativos', + + cleanDockerDiskZone: 'Limpar o espaço em disco usado pelo Docker', + cleanImagesHelper: '(Limpar todas as imagens que não são usadas por nenhum container)', + cleanContainersHelper: '(Limpar todos os containers parados)', + cleanVolumesHelper: '(Limpar todos os volumes locais não usados)', + + makeImage: 'Criar imagem', + newImageName: 'Novo nome da imagem', + commitMessage: 'Mensagem de commit', + author: 'Autor', + ifPause: 'Pausar container durante a criação', + ifMakeImageWithContainer: 'Criar nova imagem a partir deste container?', + finishTime: 'Horário da última parada', + }, + cronjob: { + create: 'Criar tarefa cron', + edit: 'Editar tarefa cron', + importHelper: + 'Tarefas agendadas duplicadas serão automaticamente ignoradas durante a importação. As tarefas serão definidas como status 【Desativado】 por padrão, e como status 【Aguardando Edição】 quando a associação de dados for anormal.', + changeStatus: 'Alterar status', + disableMsg: 'Isso irá parar a execução automática da tarefa agendada. Você deseja continuar?', + enableMsg: 'Isso permitirá que a tarefa agendada seja executada automaticamente. Você deseja continuar?', + taskType: 'Tipo', + record: 'Registros', + viewRecords: 'Visualizar registros', + shell: 'Shell', + stop: 'Parada Manual', + stopHelper: 'Esta operação forçará a parada da execução da tarefa atual. Continuar?', + log: 'Logs de backup', + logHelper: 'Backup do log do sistema', + ogHelper1: '1. Log do sistema 1Panel', + logHelper2: '2. Log de login SSH do servidor', + logHelper3: '3. Todos os logs do site', + containerCheckBox: 'No container (não é necessário inserir o comando do container)', + containerName: 'Nome do container', + ntp: 'Sincronização de tempo', + ntp_helper: 'Você pode configurar o servidor NTP na página de Configuração Rápida da Caixa de Ferramentas.', + app: 'Backup de app', + website: 'Backup de site', + rulesHelper: 'Suporta múltiplas regras de exclusão, separadas por vírgulas inglesas , ex.: *.log,*.sql', + lastRecordTime: 'Última execução', + all: 'Todos', + failedRecord: 'Registros de falha', + successRecord: 'Registros de sucesso', + database: 'Backup de banco de dados', + backupArgs: 'Argumentos de Backup', + backupArgsHelper: + 'Argumentos de backup não listados podem ser inseridos e selecionados manualmente. Por exemplo: Digite --no-data e selecione a primeira opção da lista suspensa.', + singleTransaction: + 'Faz backup de tabelas InnoDB usando uma única transação, adequado para backups de dados de grande volume', + quick: 'Lê dados linha por linha em vez de carregar toda a tabela na memória, adequado para backups de dados de grande volume e máquinas com pouca memória', + skipLockTables: 'Backup sem bloquear todas as tabelas, adequado para bancos de dados altamente concorrentes', + missBackupAccount: 'A conta de backup não foi encontrada', + syncDate: 'Data de sincronização', + clean: 'Limpeza de cache', + curl: 'URL de acesso', + taskName: 'Nome', + cronSpec: 'Ciclo de execução', + cronSpeDoc: + 'Ciclos de execução personalizados suportam apenas o formato [minuto hora dia mês semana], por exemplo, 0 0 * * *. Consulte a documentação oficial para obter detalhes.', + cronSpecHelper: 'Digite o período correto de execução', + cleanHelper: + 'Esta operação registra todos os registros de execução de tarefas, arquivos de backup e logs. Você deseja continuar?', + directory: 'Diretório de backup', + sourceDir: 'Diretório de backup', + snapshot: 'Snapshot do sistema', + allOptionHelper: + 'O plano de tarefa atual é fazer backup de todos os [{0}]. O download direto não é suportado no momento. Você pode verificar a lista de backups no menu [{0}].', + exclusionRules: 'Regras de exclusão', + exclusionRulesHelper: + 'Selecione ou insira regras de exclusão, pressione Enter após cada conjunto para continuar. As regras de exclusão se aplicarão a todas as operações de compactação neste backup', + default_download_path: 'Link de download padrão', + saveLocal: 'Manter backups locais (o mesmo número de cópias na nuvem)', + url: 'Endereço URL', + urlHelper: 'Por favor insira um endereço URL válido', + targetHelper: 'As contas de backup são mantidas nas configurações do painel.', + withImageHelper: + 'Fazer backup das imagens da loja de aplicativos, mas isso aumentará o tamanho do arquivo de snapshot.', + ignoreApp: 'Excluir aplicativos', + withImage: 'Backup da Imagem do Aplicativo', + retainCopies: 'Manter cópias', + retryTimes: 'Tentativas de Repetição', + timeout: 'Tempo Limite', + ignoreErr: 'Ignorar erros', + ignoreErrHelper: 'Ignorar erros durante o backup para garantir a execução de todas as tarefas de backup', + retryTimesHelper: '0 significa não repetir após falha', + retainCopiesHelper: 'Número de cópias a serem mantidas para registros de execução e logs', + retainCopiesHelper1: 'Número de cópias a serem mantidas para arquivos de backup', + retainCopiesUnit: ' cópias (Visualizar)', + cronSpecRule: 'O formato do período de execução na linha {0} está incorreto. Verifique e tente novamente!', + perMonth: 'Todo mês', + perWeek: 'Toda semana', + perHour: 'Toda hora', + perNDay: 'A cada N dia(s)', + perDay: 'Todo dia', + perNHour: 'A cada N hora(s)', + perNMinute: 'A cada N minuto(s)', + perNSecond: 'A cada N segundo(s)', + per: 'A cada ', + handle: '', + day: 'dia(s)', + dayUnit: 'd', + monday: 'Segunda-feira', + tuesday: 'Terça-feira', + wednesday: 'Quarta-feira', + thursday: 'Quinta-feira', + friday: 'Sexta-feira', + saturday: 'Sábado', + sunday: 'Domingo', + shellContent: 'Script', + errRecord: 'Registro incorreto', + errHandle: 'Falha na execução do Cronjob', + noRecord: 'Acione a tarefa Cron e você verá os registros aqui.', + cleanData: 'Limpar dados', + cleanRemoteData: 'Excluir dados remotos', + cleanDataHelper: 'Excluir o arquivo de backup gerado durante esta tarefa.', + noLogs: 'Ainda não há saída de tarefa...', + errPath: 'Caminho de backup [{0}] com erro, não é possível fazer o download!', + cutWebsiteLog: 'Rotação de log do site', + cutWebsiteLogHelper: 'Os arquivos de log rotacionados serão salvos no diretório de backup do 1Panel.', + syncIpGroup: 'Sincronizar grupos de IP do WAF', + + requestExpirationTime: 'Tempo de expiração da solicitação de upload (Horas)', + unitHours: 'Unidade: Horas', + alertTitle: 'Tarefa Planejada - {0} 「{1}」 Alerta de Falha na Tarefa', + library: { + script: 'Script', + syncNow: 'Sincronizar Agora', + turnOnSync: 'Ativar Sincronização Automática', + turnOnSyncHelper: + 'Ativar a sincronização automática realizará sincronizações automáticas durante as primeiras horas da manhã diariamente', + turnOffSync: 'Desativar Sincronização Automática', + turnOffSyncHelper: + 'Desativar a sincronização automática pode causar atrasos na sincronização de scripts, confirmar?', + isInteractive: 'Interativo', + interactive: 'Script interativo', + interactiveHelper: + 'Requer entrada do usuário durante a execução e não pode ser usado em tarefas agendadas.', + library: 'Biblioteca de Scripts', + remoteLibrary: 'Biblioteca de Scripts Remota', + create: 'Adicionar Script', + edit: 'Editar Script', + groupHelper: + 'Defina grupos diferentes com base nas características do script, o que permite operações de filtragem de scripts mais rápidas.', + handleHelper: 'Executar o script {1} em {0}, continuar?', + noSuchApp: + 'O serviço {0} não foi detectado. Por favor, instale-o rapidamente usando a biblioteca de scripts primeiro!', + syncHelper: + 'Preparando para sincronizar a biblioteca de scripts do sistema. Esta operação afeta apenas scripts do sistema. Continuar?', + }, + }, + monitor: { + globalFilter: 'Filtro Global', + enableMonitor: 'Status de Monitoramento', + storeDays: 'Dias de Retenção', + defaultNetwork: 'Placa de Rede Padrão', + defaultNetworkHelper: 'Opção de placa de rede padrão exibida nas interfaces de monitoramento e visão geral', + defaultIO: 'Disco Padrão', + defaultIOHelper: 'Opção de disco padrão exibida nas interfaces de monitoramento e visão geral', + cleanMonitor: 'Limpar Registros de Monitoramento', + cleanHelper: 'Esta operação limpará todos os registros de monitoramento, incluindo GPU. Continuar?', + + avgLoad: 'Média de carga', + loadDetail: 'Detalhes da carga', + resourceUsage: 'Utilização', + networkCard: 'Interface de rede', + read: 'Leitura', + write: 'Gravação', + readWriteCount: 'Operações de I/O', + readWriteTime: 'Latência de I/O', + today: 'Hoje', + yesterday: 'Ontem', + lastNDay: 'Últimos {0} dias', + lastNMonth: 'Últimos {0} meses', + lastHalfYear: 'Último semestre', + memory: 'Memória', + percent: 'Percentual', + cache: 'Cache', + disk: 'Disco', + network: 'Rede', + up: 'Para cima', + down: 'Para baixo', + interval: 'Intervalo de Coleta', + intervalHelper: 'Insira um intervalo de coleta de monitoramento apropriado (5 segundos - 12 horas)', + }, + terminal: { + local: 'Local', + defaultConn: 'Conexão Padrão', + defaultConnHelper: + 'Esta operação conectará automaticamente ao terminal do nó após abrir o terminal para 【{0}】. Continuar?', + withReset: 'Redefinir Informações de Conexão', + localConnJump: + 'As informações de conexão padrão são mantidas em [Terminal - Configurações]. Se a conexão falhar, edite lá!', + localHelper: 'O nome local é usado apenas para identificação local do sistema.', + connLocalErr: + 'Невозможно автоматически аутентифицироваться, пожалуйста, заполните информацию для входа на локальный сервер.', + testConn: 'Testar conexão', + saveAndConn: 'Salvar e conectar', + connTestOk: 'Informações de conexão disponíveis', + connTestFailed: 'Conexão indisponível, por favor, verifique as informações de conexão.', + host: 'Host | Hosts', + createConn: 'Nova conexão', + manageGroup: 'Gerenciar grupos', + noHost: 'Nenhum host', + groupChange: 'Alterar grupo', + expand: 'Expandir todos', + fold: 'Contrair tudo', + batchInput: 'Processamento em lote', + quickCommand: 'Comando rápido | Comandos rápidos', + noSuchCommand: + 'Nenhum dado de comando rápido encontrado no arquivo CSV importado, verifique e tente novamente!', + quickCommandHelper: 'Você pode usar os comandos rápidos na parte inferior de "Terminais -> Terminais".', + groupDeleteHelper: + 'Após o grupo ser removido, todas as conexões no grupo serão migradas para o grupo padrão. Você deseja continuar?', + command: 'Comando', + quickCmd: 'Comando rápido', + addHost: 'Adicionar', + localhost: 'Localhost', + ip: 'Endereço', + authMode: 'Autenticação', + passwordMode: 'Senha', + rememberPassword: 'Lembrar informações de autenticação', + keyMode: 'Chave privada', + key: 'Chave privada', + keyPassword: 'Senha da chave privada', + emptyTerminal: 'Nenhum terminal está conectado no momento.', + }, + toolbox: { + common: { + toolboxHelper: 'Para alguns problemas de instalação e uso, consulte', + }, + swap: { + swap: 'Partição Swap', + swapHelper1: + 'O tamanho do swap deve ser de 1 a 2 vezes a memória física, ajustável conforme os requisitos específicos;', + swapHelper2: + 'Antes de criar um arquivo swap, verifique se o disco do sistema tem espaço disponível suficiente, pois o tamanho do arquivo swap ocupará o espaço correspondente no disco;', + swapHelper3: + 'Swap pode ajudar a aliviar a pressão de memória, mas é apenas uma alternativa. A dependência excessiva de swap pode levar a uma diminuição no desempenho do sistema. É recomendável priorizar o aumento de memória ou otimizar o uso de memória do aplicativo;', + swapHelper4: + 'É aconselhável monitorar regularmente o uso de swap para garantir o funcionamento normal do sistema.', + swapDeleteHelper: + 'Esta operação removerá a partição Swap {0}. Por motivos de segurança do sistema, o arquivo correspondente não será excluído automaticamente. Se a exclusão for necessária, por favor, faça manualmente!', + saveHelper: 'Por favor, salve as configurações atuais primeiro!', + saveSwap: + 'Salvar a configuração atual ajustará o tamanho da partição Swap {0} para {1}. Você deseja continuar?', + swapMin: 'O tamanho mínimo da partição é 40 KB. Por favor, modifique e tente novamente!', + swapMax: 'O valor máximo para o tamanho da partição é {0}. Por favor, modifique e tente novamente!', + swapOff: 'O tamanho mínimo da partição é 40 KB. Definir como 0 desabilitará a partição Swap.', + }, + }, + device: { + dnsHelper: 'Servidor DNS', + dnsAlert: + 'Atenção! Modificar a configuração do arquivo /etc/resolv.conf restaurará o arquivo para seus valores padrão após a reinicialização do sistema.', + dnsHelper1: + 'Quando houver várias entradas DNS, elas devem ser exibidas em novas linhas. Exemplo:\n114.114.114.114\n8.8.8.8', + hostsHelper: 'Resolução de hostname', + hosts: 'Domínio', + hostAlert: + 'Registros comentados ocultos, clique no botão "Todas as configurações" para visualizar ou configurar', + toolbox: 'Configurações rápidas', + hostname: 'Nome do host', + passwd: 'Senha do sistema', + passwdHelper: 'Os caracteres de entrada não podem incluir $ e &', + timeZone: 'Fuso horário', + localTime: 'Hora do servidor', + timeZoneChangeHelper: + 'Modificar o fuso horário do sistema requer a reinicialização do serviço. Deseja continuar?', + timeZoneHelper: `Se você não instalar o comando "timedatectl", poderá não conseguir alterar o fuso horário. O sistema usa esse comando para alterar o fuso horário.`, + timeZoneCN: 'Pequim', + timeZoneAM: 'Los Angeles', + timeZoneNY: 'Nova York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'Servidor NTP', + hostnameHelper: `A modificação do nome do host depende do comando "hostnamectl". Se o comando não estiver instalado, a modificação pode falhar.`, + userHelper: `O nome de usuário depende do comando "whoami" para recuperação. Se o comando não estiver instalado, a recuperação pode falhar.`, + passwordHelper: `A modificação da senha depende do comando "chpasswd". Se o comando não estiver instalado, a modificação pode falhar.`, + hostHelper: 'Há um valor vazio no conteúdo fornecido. Verifique e tente novamente após a modificação!', + dnsCheck: 'Testar disponibilidade', + dnsOK: 'As informações de configuração do DNS estão disponíveis!', + dnsTestFailed: `As informações de configuração do DNS não estão disponíveis.`, + }, + fail2ban: { + sshPort: 'Porta de escuta do SSH', + sshPortHelper: 'O Fail2ban atual escuta a porta de conexão SSH do host', + unActive: `O serviço Fail2ban não está ativado no momento.`, + operation: 'Você realizará a operação "{0}" no serviço Fail2ban. Deseja continuar?', + fail2banChange: 'Modificação da configuração do Fail2ban', + ignoreHelper: 'A lista de IPs na lista de permissão será ignorada para bloqueio. Deseja continuar?', + bannedHelper: 'A lista de IPs na lista de bloqueio será bloqueada pelo servidor. Deseja continuar?', + maxRetry: 'Máximo de tentativas de reclusão', + banTime: 'Tempo de banimento', + banTimeHelper: 'O tempo de banimento padrão é 10 minutos, -1 indica banimento permanente', + banTimeRule: 'Por favor, insira um tempo de banimento válido ou -1', + banAllTime: 'Banimento permanente', + findTime: 'Período de descoberta', + banAction: 'Ação de bloqueio', + banActionOption: 'Bloquear endereços IP especificados usando {0}', + allPorts: ' (Todas as portas)', + ignoreIP: 'Lista de permissão de IP', + bannedIP: 'Lista de bloqueio de IP', + logPath: 'Caminho do log', + logPathHelper: 'O padrão é /var/log/secure ou /var/log/auth.log', + }, + ftp: { + ftp: 'Conta FTP | Contas FTP', + notStart: 'O serviço FTP não está em execução, por favor, inicie-o primeiro!', + operation: 'Isso realizará a operação "{0}" no serviço FTP. Deseja continuar?', + noPasswdMsg: 'Não foi possível obter a senha atual da conta FTP, por favor, defina a senha e tente novamente!', + enableHelper: 'Ativar a conta FTP selecionada restaurará suas permissões de acesso. Deseja continuar?', + disableHelper: 'Desativar a conta FTP selecionada revogará suas permissões de acesso. Deseja continuar?', + syncHelper: 'Sincronizar os dados da conta FTP entre o servidor e o banco de dados. Deseja continuar?', + dirSystem: + 'Este diretório é reservado do sistema. Modificações podem causar falhas no sistema. Por favor, modifique e tente novamente!', + dirHelper: 'Habilitar FTP requer alterações nas permissões do diretório - por favor, escolha com cuidado', + dirMsg: 'Habilitar FTP modificará as permissões de todo o diretório {0}. Continuar?', + }, + clam: { + clam: 'Scan de vírus', + cron: 'Scan agendado', + cronHelper: 'A versão profissional suporta a funcionalidade de scan agendado', + specErr: 'Erro no formato do agendamento, por favor, verifique e tente novamente!', + disableMsg: + 'Parar a execução agendada impedirá que esta tarefa de scan seja executada automaticamente. Deseja continuar?', + enableMsg: + 'Ativar a execução agendada permitirá que esta tarefa de scan seja executada automaticamente em intervalos regulares. Deseja continuar?', + showFresh: 'Mostrar serviço de atualização de assinaturas', + hideFresh: 'Ocultar serviço de atualização de assinaturas', + clamHelper: + 'A configuração mínima recomendada para o ClamAV é: 3 GiB de RAM ou mais, CPU de 1 núcleo com 2.0 GHz ou superior, e pelo menos 5 GiB de espaço livre no disco rígido.', + notStart: 'O serviço ClamAV não está em execução, por favor, inicie-o primeiro!', + removeRecord: 'Excluir arquivos de relatório', + noRecords: 'Clique no botão "Acionar" para iniciar o scan e você verá registros aqui.', + removeInfected: 'Excluir arquivos infectados', + removeInfectedHelper: + 'Excluir arquivos de vírus detectados durante a tarefa para garantir a segurança e o funcionamento normal do servidor.', + clamCreate: 'Criar regra de scan', + infectedStrategy: 'Estratégia de arquivos infectados', + removeHelper: 'Excluir arquivos de vírus, escolha com cuidado!', + move: 'Mover', + moveHelper: 'Mover arquivos de vírus para o diretório especificado', + copyHelper: 'Copiar arquivos de vírus para o diretório especificado', + none: 'Não fazer nada', + noneHelper: 'Não tomar nenhuma ação sobre arquivos de vírus', + scanDir: 'Diretório de scan', + infectedDir: 'Diretório de arquivos infectados', + scanDate: 'Data do scan', + scanResult: 'Últimos logs de scan', + tail: 'Linhas', + infectedFiles: 'Arquivos infectados', + log: 'Detalhes', + clamConf: 'Daemon Clam AV', + clamLog: '@:toolbox.clam.clamConf logs', + freshClam: 'FreshClam', + freshClamLog: '@:toolbox.clam.freshClam logs', + alertHelper: 'A versão profissional suporta scan agendado e alerta por SMS', + alertTitle: 'Tarefa de scan de vírus 「{0}」 detectou alerta de arquivo infectado', + }, + logs: { + core: 'Serviço de Painel', + agent: 'Monitoramento de Nós', + panelLog: 'Logs do painel', + operation: 'Logs de operação', + login: 'Logs de login', + loginIP: 'IP de login', + loginAddress: 'Endereço de login', + loginAgent: 'Agente de login', + loginStatus: 'Status', + system: 'Logs do sistema', + deleteLogs: 'Limpar logs', + resource: 'Recurso', + detail: { + dashboard: 'Visão Geral', + ai: 'AI', + groups: 'Grupos', + hosts: 'Hosts', + apps: 'Aplicativos', + websites: 'Sites', + containers: 'Contêineres', + files: 'Gerenciamento de Arquivos', + runtimes: 'Ambientes de Execução', + process: 'Gerenciamento de Processos', + toolbox: 'Caixa de Ferramentas', + backups: 'Backup / Restauração', + tampers: 'Proteção contra Alterações', + xsetting: 'Configurações da Interface', + logs: 'Auditoria de Logs', + settings: 'Configurações do Painel', + cronjobs: 'Tarefas Agendadas', + waf: 'WAF', + databases: 'Bancos de Dados', + licenses: 'licenças', + nodes: 'nós', + commands: 'Comandos Rápidos', + }, + websiteLog: 'Logs do website', + runLog: 'Logs de execução', + errLog: 'Logs de erro', + }, + file: { + fileDirNum: '{0} diretórios, {1} arquivos,', + currentDir: 'Diretório atual', + dir: 'Pasta', + upload: 'Carregar', + uploadFile: '@:file.upload @.lower:file.file', + uploadDirectory: '@:file.upload @.lower:file.dir', + download: 'Baixar', + fileName: 'Nome do arquivo', + search: 'Pesquisar', + mode: 'Permissões', + editPermissions: '@:file.mode', + owner: 'Proprietário', + file: 'Arquivo', + remoteFile: 'Baixar de remoto', + share: 'Compartilhar', + sync: 'Sincronização de dados', + size: 'Tamanho', + updateTime: 'Modificado', + rename: 'Renomear', + role: 'Permissões', + info: 'Atributos', + linkFile: 'Link simbólico', + batchoperation: 'Operação em lote', + shareList: 'Lista de compartilhamento', + zip: 'Compactado', + group: 'Grupo', + path: 'Caminho', + public: 'Outros', + setRole: 'Configurar permissões', + link: 'Link do arquivo', + rRole: 'Leitura', + wRole: 'Escrita', + xRole: 'Executável', + name: 'Nome', + compress: 'Compactar', + deCompress: 'Descompactar', + compressType: 'Formato de compactação', + compressDst: 'Caminho de compactação', + replace: 'Substituir arquivos existentes', + compressSuccess: 'Compactado com sucesso', + deCompressSuccess: 'Descompactado com sucesso', + deCompressDst: 'Caminho de descompactação', + linkType: 'Tipo de link', + softLink: 'Link simbólico', + hardLink: 'Link físico', + linkPath: 'Caminho do link', + selectFile: 'Selecionar arquivo', + downloadUrl: 'URL remota', + downloadStart: 'Download iniciado', + moveSuccess: 'Movido com sucesso', + copySuccess: 'Copiado com sucesso', + pasteMsg: 'Clique no botão "Colar" no canto superior direito do diretório de destino', + move: 'Mover', + calculate: 'Calcular', + remark: 'Observação', + setRemark: 'Definir observação', + remarkPrompt: 'Digite uma observação', + remarkPlaceholder: 'Observação', + remarkToggle: 'Observações', + remarkToggleTip: 'Carregar observações do arquivo', + canNotDeCompress: 'Não é possível descompactar este arquivo', + uploadSuccess: 'Upload bem-sucedido', + downloadProcess: 'Progresso do download', + downloading: 'Baixando...', + infoDetail: 'Propriedades do arquivo', + root: 'Diretório raiz', + list: 'Lista de arquivos', + sub: 'Subpastas', + downloadSuccess: 'Baixado com sucesso', + theme: 'Tema', + language: 'Idioma', + eol: 'Fim de linha', + copyDir: 'Copiar', + paste: 'Colar', + changeOwner: 'Modificar usuário e grupo de usuários', + containSub: 'Aplicar mudança de permissões recursivamente', + ownerHelper: + 'O usuário padrão do ambiente PHP: o grupo de usuários é 1000:1000, é normal que os usuários dentro e fora do container mostrem inconsistências', + searchHelper: 'Suporte a curingas como *', + uploadFailed: '[{0}] Falha no upload do arquivo', + fileUploadStart: 'Carregando [{0}]....', + currentSelect: 'Selecionado atualmente: ', + unsupportedType: 'Tipo de arquivo não suportado', + deleteHelper: + 'Tem certeza de que deseja excluir os seguintes arquivos? Por padrão, eles irão para a lixeira após a exclusão', + fileHelper: `Nota:\n1. Os resultados da pesquisa não podem ser ordenados.\n2. Pastas não podem ser ordenadas por tamanho.`, + forceDeleteHelper: 'Excluir permanentemente o arquivo (sem entrar na lixeira, excluí-lo diretamente)', + recycleBin: 'Lixeira', + sourcePath: 'Caminho original', + deleteTime: 'Hora da exclusão', + confirmReduce: 'Tem certeza de que deseja restaurar os seguintes arquivos?', + reduceSuccess: 'Restaurado com sucesso', + reduce: 'Restaurar', + reduceHelper: + 'Se um arquivo ou diretório com o mesmo nome existir no caminho original, ele será substituído. Deseja continuar?', + clearRecycleBin: 'Limpar', + clearRecycleBinHelper: 'Você deseja limpar a lixeira?', + favorite: 'Favoritos', + removeFavorite: 'Remover dos favoritos?', + addFavorite: 'Adicionar/Remover aos favoritos', + clearList: 'Limpar lista', + deleteRecycleHelper: 'Tem certeza de que deseja excluir permanentemente os seguintes arquivos?', + typeErrOrEmpty: '[{0}] tipo de arquivo errado ou pasta vazia', + dropHelper: 'Arraste os arquivos que deseja carregar aqui', + fileRecycleBin: 'Habilitar lixeira', + fileRecycleBinMsg: '{0} lixeira', + wordWrap: 'Quebra automática de linha', + deleteHelper2: + 'Tem certeza de que deseja excluir o arquivo selecionado? A operação de exclusão não pode ser desfeita', + ignoreCertificate: 'Permitir conexões inseguras com o servidor', + ignoreCertificateHelper: + 'Permitir conexões inseguras com o servidor pode levar a vazamento ou adulteração de dados. Use esta opção apenas quando confiar na fonte de download.', + uploadOverLimit: 'O número de arquivos excede 1000! Por favor, compacte e envie novamente', + clashDitNotSupport: 'Os nomes de arquivos são proibidos de conter .1panel_clash', + clashDeleteAlert: 'A pasta "Lixeira" não pode ser excluída', + clashOpenAlert: 'Clique no botão "Lixeira" para abrir o diretório da lixeira', + right: 'Avançar', + back: 'Voltar', + top: 'Voltar ao topo', + up: 'Voltar', + openWithVscode: 'Abrir com VS Code', + vscodeHelper: + 'Por favor, certifique-se de que o VS Code está instalado localmente e o plugin SSH Remote está configurado', + saveContentAndClose: 'O arquivo foi modificado, deseja salvar e fechar?', + saveAndOpenNewFile: 'O arquivo foi modificado, deseja salvar e abrir o novo arquivo?', + noEdit: 'O arquivo não foi modificado, não é necessário fazer isso!', + noNameFolder: 'Pasta sem nome', + noNameFile: 'Arquivo sem nome', + minimap: 'Mini mapa de código', + fileCanNotRead: 'O arquivo não pode ser lido', + previewTruncated: 'O arquivo é muito grande, mostrando apenas a última parte', + previewEmpty: 'O arquivo está vazio ou não é um arquivo de texto', + previewLargeFile: 'Visualizar', + panelInstallDir: 'O diretório de instalação do 1Panel não pode ser excluído', + wgetTask: 'Tarefa de Download', + existFileTitle: 'Aviso de arquivo com o mesmo nome', + existFileHelper: 'O arquivo enviado contém um arquivo com o mesmo nome. Deseja substituí-lo?', + existFileSize: 'Tamanho do arquivo (novo -> antigo)', + existFileDirHelper: 'O arquivo/pasta selecionado tem um nome duplicado. Por favor, prossiga com cautela!\n', + coverDirHelper: 'As pastas selecionadas para substituição serão copiadas para o caminho de destino!', + noSuchFile: 'O arquivo ou diretório não foi encontrado. Por favor, verifique e tente novamente.', + setting: 'configuração', + showHide: 'Mostrar arquivos ocultos', + noShowHide: 'Não mostrar arquivos ocultos', + cancelUpload: 'Cancelar Upload', + cancelUploadHelper: 'Deseja cancelar o upload, após o cancelamento, a lista de upload será limpa.', + keepOneTab: 'Mantenha pelo menos uma aba', + notCanTab: 'Não é possível adicionar mais abas', + convert: 'Converter Formato', + converting: 'Convertendo Para', + fileCanNotConvert: 'Este arquivo não suporta conversão de formato', + formatType: 'Tipo de Formato', + sourceFormat: 'Formato de Origem', + sourceFile: 'Arquivo de Origem', + saveDir: 'Diretório de Salvamento', + deleteSourceFile: 'Excluir Arquivo de Origem', + convertHelper: 'Converter os arquivos selecionados para outro formato', + convertHelper1: 'Por favor, selecione os arquivos a serem convertidos', + execConvert: 'Iniciar conversão. Você pode visualizar os logs de conversão no Centro de Tarefas', + convertLogs: 'Logs de Conversão', + formatConvert: 'Conversão de Formato', + }, + ssh: { + autoStart: 'Início automático', + enable: 'Habilitar início automático', + disable: 'Desabilitar início automático', + sshAlert: + 'Os dados da lista são classificados com base na data de login. Alterar o fuso horário ou realizar outras operações pode causar desvios na data dos logs de login.', + sshAlert2: + 'Você pode usar o "Fail2ban" na "Caixa de ferramentas" para bloquear endereços IP que tentam ataques de força bruta, o que aumentará a segurança do host.', + sshOperate: 'A operação "{0}" no serviço SSH será realizada. Você deseja continuar?', + sshChange: 'Configuração SSH', + sshChangeHelper: 'Esta ação alterou "{0}" para "{1}". Você deseja continuar?', + sshFileChangeHelper: + 'Modificar o arquivo de configuração pode afetar a disponibilidade do serviço. Tenha cautela ao realizar esta operação. Você deseja continuar?', + port: 'Porta', + portHelper: 'Especifique a porta na qual o serviço SSH escutará.', + listenAddress: 'Endereço de escuta', + allV4V6: '0.0.0.0:{0}(IPv4) e :::{0}(IPv6)', + listenHelper: + 'Deixar os campos de IPv4 e IPv6 em branco fará com que o serviço escute em "0.0.0.0:{0}(IPv4)" e ":::{0}(IPv6)"', + addressHelper: 'Especifique o endereço em que o serviço SSH irá escutar.', + permitRootLogin: 'Permitir login de usuário root', + rootSettingHelper: 'O método de login padrão para o usuário root é "Permitir login SSH".', + rootHelper1: 'Permitir login SSH', + rootHelper2: 'Desabilitar login SSH', + rootHelper3: 'Somente login com chave é permitido', + rootHelper4: 'Somente comandos pré-definidos podem ser executados. Nenhuma outra operação pode ser realizada.', + passwordAuthentication: 'Autenticação por senha', + pwdAuthHelper: 'Se deve ou não habilitar a autenticação por senha. Esse parâmetro está habilitado por padrão.', + pubkeyAuthentication: 'Autenticação por chave', + privateKey: 'Chave Privada', + publicKey: 'Chave Pública', + password: 'Senha', + createMode: 'Método de Criação', + generate: 'Gerar Automaticamente', + unSyncPass: 'Senha da chave não pode ser sincronizada', + syncHelper: + 'A operação de sincronização limpará chaves inválidas e sincronizará novos pares de chaves completos. Continuar?', + input: 'Entrada Manual', + import: 'Upload de Arquivo', + authKeys: 'Chaves de Autorização', + authKeysHelper: 'Salvar informações atuais da chave pública?', + pubkey: 'Informações da chave', + pubKeyHelper: 'A informação da chave atual só tem efeito para o usuário {0}', + encryptionMode: 'Modo de criptografia', + passwordHelper: 'Pode conter de 6 a 10 dígitos e letras maiúsculas e minúsculas', + reGenerate: 'Regenerar chave', + keyAuthHelper: 'Se deve ou não habilitar a autenticação por chave.', + useDNS: 'Usar DNS', + dnsHelper: + 'Controla se a função de resolução DNS está habilitada no servidor SSH para verificar a identidade da conexão.', + analysis: 'Informações estatísticas', + denyHelper: + "Realizando uma operação de 'negar' nos seguintes endereços. Após a configuração, o IP será proibido de acessar o servidor. Você deseja continuar?", + acceptHelper: + "Realizando uma operação de 'aceitar' nos seguintes endereços. Após a configuração, o IP recuperará o acesso normal. Você deseja continuar?", + noAddrWarning: 'Nenhum endereço [{0}] foi selecionado atualmente. Por favor, verifique e tente novamente!', + loginLogs: 'Logs de login', + loginMode: 'Modo', + authenticating: 'Chave', + publickey: 'Chave', + belong: 'Pertence', + local: 'Local', + session: 'Sessão | Sessões', + loginTime: 'Hora do login', + loginIP: 'IP de login', + stopSSHWarn: 'Deseja desconectar esta conexão SSH?', + }, + setting: { + panel: 'Painel', + user: 'Usuário do painel', + userChange: 'Alterar usuário do painel', + userChangeHelper: 'Alterar o usuário do painel irá desconectá-lo. Continuar?', + passwd: 'Senha do painel', + emailHelper: 'Para recuperação de senha', + watermark: 'Configurações de Marca d Água', + watermarkContent: 'Conteúdo da Marca d Água', + contentHelper: + '{0} representa o nome do nó, {1} representa o endereço do nó. Você pode usar variáveis ou preencher nomes personalizados.', + watermarkColor: 'Cor da Marca d Água', + watermarkFont: 'Tamanho da Fonte da Marca d Água', + watermarkHeight: 'Altura da Marca d Água', + watermarkWidth: 'Largura da Marca d Água', + watermarkRotate: 'Ângulo de Rotação', + watermarkGap: 'Espaçamento', + watermarkCloseHelper: 'Tem certeza de que deseja desativar as configurações de marca d água do sistema?', + watermarkOpenHelper: 'Tem certeza de que deseja salvar as configurações atuais de marca d água do sistema?', + title: 'Alias do painel', + panelPort: 'Porta do painel', + titleHelper: + 'Suporta nomes com comprimento de 3 a 30 caracteres, incluindo letras, caracteres chineses, números, espaços e caracteres especiais comuns', + portHelper: + 'O intervalo recomendado de portas é de 8888 a 65535. Nota: Se o servidor tiver um grupo de segurança, permita a nova porta do grupo de segurança antecipadamente', + portChange: 'Alteração de porta', + portChangeHelper: 'Modificar a porta do serviço e reiniciar o serviço. Deseja continuar?', + theme: 'Tema', + menuTabs: 'Guias do menu', + dark: 'Escuro', + darkGold: 'Ouro escuro', + light: 'Claro', + auto: 'Seguir o sistema', + language: 'Idioma', + languageHelper: 'Por padrão, segue o idioma do navegador. Este parâmetro tem efeito apenas no navegador atual', + sessionTimeout: 'Tempo limite de sessão', + sessionTimeoutError: 'O tempo mínimo de sessão é de 300 segundos', + sessionTimeoutHelper: 'O painel será desconectado automaticamente após {0} segundo(s) de inatividade.', + systemIP: 'Endereço de acesso padrão', + systemIPHelper: + 'Redirecionamentos de aplicativos, acesso a containers e outras funcionalidades usarão este endereço para roteamento. Cada nó pode ser configurado com um endereço diferente.', + proxy: 'Proxy do servidor', + proxyHelper: 'Será eficaz nos seguintes cenários após configurar o servidor proxy:', + proxyHelper1: + 'Download de pacotes de instalação e sincronização da loja de aplicativos (apenas edição profissional)', + proxyHelper2: 'Atualização do sistema e recuperação de informações de atualização (apenas edição profissional)', + proxyHelper4: 'A rede Docker será acessada por meio de um servidor proxy (apenas edição profissional)', + proxyHelper3: 'Verificação e sincronização da licença do sistema', + proxyHelper5: + 'Download e sincronização unificados para bibliotecas de scripts do tipo sistema (Recurso da Edição Profissional)', + proxyHelper6: 'Solicitar certificado (Funcionalidade da versão Pro)', + proxyType: 'Tipo de proxy', + proxyUrl: 'Endereço do proxy', + proxyPort: 'Porta do proxy', + proxyPasswdKeep: 'Lembrar senha', + proxyDocker: 'Proxy Docker', + proxyDockerHelper: + 'Sincronize a configuração do servidor proxy com o Docker, suportando operações de puxar imagens de servidor offline e outras', + syncToNode: 'Sincronizar para o nó filho', + syncToNodeHelper: 'Sincronizar de alerta para outros nós', + nodes: 'Nós', + selectNode: 'Selecionar nó', + selectNodeError: 'Por favor, selecione um nó', + apiInterface: 'Habilitar API', + apiInterfaceClose: 'Uma vez fechado, as interfaces da API não poderão ser acessadas. Deseja continuar?', + apiInterfaceHelper: 'Permitir que aplicativos de terceiros acessem a API.', + apiInterfaceAlert1: + 'Não habilite em ambientes de produção, pois pode aumentar os riscos de segurança do servidor.', + apiInterfaceAlert2: + 'Não use aplicativos de terceiros para chamar a API, para evitar potenciais ameaças à segurança.', + apiInterfaceAlert3: 'Documentação da API', + apiInterfaceAlert4: 'Documentação de uso', + apiKey: 'Chave API', + apiKeyHelper: 'A chave da API é usada para aplicativos de terceiros acessarem a API.', + ipWhiteList: 'Lista de IPs permitidos', + ipWhiteListEgs: 'Um por linha. Exemplo: \n172.161.10.111\n172.161.10.0/24', + ipWhiteListHelper: + 'IPs na lista de permitidos podem acessar a API, 0.0.0.0/0 (todos os IPv4), ::/0 (todos os IPv6)', + apiKeyReset: 'Redefinir chave da interface', + apiKeyResetHelper: + 'O serviço associado à chave se tornará inválido. Por favor, adicione uma nova chave ao serviço', + confDockerProxy: 'Configurar proxy do Docker', + restartNowHelper: 'Configurar o proxy do Docker exige reiniciar o serviço Docker.', + restartNow: 'Reiniciar imediatamente', + restartLater: 'Reiniciar manualmente mais tarde', + systemIPWarning: + 'O nó atual não tem um endereço de acesso padrão configurado. Por favor, vá para as configurações do painel para configurá-lo!', + systemIPWarning1: + 'O endereço atual do servidor está configurado como {0}, e o redirecionamento rápido não é possível!', + defaultNetwork: 'Placa de rede', + syncTime: 'Hora do servidor', + timeZone: 'Fuso horário', + timeZoneChangeHelper: 'Alterar o fuso horário exige reiniciar o serviço. Deseja continuar?', + timeZoneHelper: + 'A modificação de fuso horário depende do serviço timedatectl do sistema. Entrará em vigor após reiniciar o serviço 1Panel.', + timeZoneCN: 'Pequim', + timeZoneAM: 'Los Angeles', + timeZoneNY: 'Nova York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'Servidor NTP', + syncSiteHelper: + 'Esta operação usará {0} como fonte para a sincronização do horário do sistema. Deseja continuar?', + changePassword: 'Alterar senha', + oldPassword: 'Senha original', + newPassword: 'Nova senha', + retryPassword: 'Confirmar senha', + noSpace: 'As informações inseridas não podem conter caracteres de espaço', + duplicatePassword: 'A nova senha não pode ser igual à senha original, por favor, insira novamente!', + diskClean: 'Limpeza de cache', + developerMode: 'Modo de desenvolvimento', + developerModeHelper: + 'Você terá a oportunidade de testar novos recursos e correções antes de serem amplamente lançados e fornecer feedback precoce.', + thirdParty: 'Contas de terceiros', + noTypeForCreate: 'Nenhum tipo de backup foi criado até o momento', + LOCAL: 'Disco do servidor', + OSS: 'Ali OSS', + S3: 'Amazon S3', + mode: 'Modo', + MINIO: 'MinIO', + SFTP: 'SFTP', + WebDAV: 'WebDAV', + WebDAVAlist: 'Conectar WebDAV Alist pode referir-se à documentação oficial', + OneDrive: 'Microsoft OneDrive', + isCN: 'Internet da China', + isNotCN: 'Versão internacional', + client_id: 'ID do cliente', + client_secret: 'Segredo do cliente', + redirect_uri: 'URL de redirecionamento', + onedrive_helper: 'A configuração personalizada pode ser referida na documentação oficial', + refreshTime: 'Tempo de atualização do token', + refreshStatus: 'Status da atualização do token', + backupDir: 'Diretório de backup', + codeWarning: 'O formato atual do código de autorização está incorreto, por favor, verifique novamente!', + code: 'Código de autorização', + codeHelper: + 'Clique no botão "Adquirir", faça login no OneDrive e copie o conteúdo após "code" no link redirecionado. Cole-o neste campo. Para instruções específicas, consulte a documentação oficial.', + googleHelper: + 'Por favor, primeiro crie um aplicativo Google e obtenha as informações do cliente, preencha o formulário e clique no botão obter. Para operações específicas, consulte a documentação oficial.', + loadCode: 'Obter', + COS: 'Tencent COS', + ap_beijing_1: 'Beijing Zona 1', + ap_beijing: 'Pequim', + ap_nanjing: 'Nanjing', + ap_shanghai: 'Xangai', + ap_guangzhou: 'Cantão', + ap_chengdu: 'Chengdu', + ap_chongqing: 'Chongqing', + ap_shenzhen_fsi: 'Shenzhen Financeiro', + ap_shanghai_fsi: 'Xangai Financeiro', + ap_beijing_fsi: 'Pequim Financeiro', + ap_hongkong: 'Hong Kong, China', + ap_singapore: 'Cingapura', + ap_mumbai: 'Bombaim', + ap_jakarta: 'Jacarta', + ap_seoul: 'Seul', + ap_bangkok: 'Bangkok', + ap_tokyo: 'Tóquio', + na_siliconvalley: 'Silicon Valley (EUA Oeste)', + na_ashburn: 'Ashburn (EUA Leste)', + na_toronto: 'Toronto', + sa_saopaulo: 'São Paulo', + eu_frankfurt: 'Frankfurt', + KODO: 'Qiniu Kodo', + scType: 'Tipo de armazenamento', + typeStandard: 'Padrão', + typeStandard_IA: 'Padrão_IA', + typeArchive: 'Arquivo', + typeDeep_Archive: 'Arquivo Profundo', + scLighthouse: 'Padrão, O armazenamento de objetos leve só suporta este tipo de armazenamento', + scStandard: + 'O armazenamento padrão é adequado para cenários de negócios com grande volume de arquivos quentes que exigem acesso em tempo real, interação frequente de dados, etc.', + scStandard_IA: + 'O armazenamento de baixa frequência é adequado para cenários de negócios com frequência de acesso relativamente baixa, e armazena dados por pelo menos 30 dias.', + scArchive: + 'Armazenamento para arquivos é adequado para cenários de negócios com frequência de acesso extremamente baixa.', + scDeep_Archive: + 'Armazenamento frio durável é adequado para cenários de negócios com frequência de acesso extremamente baixa.', + archiveHelper: + 'Arquivos de armazenamento arquivado não podem ser baixados diretamente e devem ser restaurados primeiro através do site do provedor de serviços de nuvem correspondente. Use com cautela!', + backupAlert: + 'Se um provedor de nuvem for compatível com o protocolo S3, você pode usar diretamente o Amazon S3 para backup.', + domain: 'Domínio de aceleração', + backupAccount: 'Conta de backup | Contas de backup', + loadBucket: 'Obter bucket', + accountName: 'Nome da conta', + accountKey: 'Chave da conta', + address: 'Endereço', + path: 'Caminho', + + safe: 'Segurança', + passkey: 'Passkey', + passkeyManage: 'Gerenciar', + passkeyHelper: 'Para login rápido, é possível vincular até 5 passkeys', + passkeyRequireSSL: 'Ative o HTTPS para usar passkeys', + passkeyNotSupported: 'O navegador ou ambiente atual não suporta passkeys', + passkeyCount: 'Vinculadas {0}/{1}', + passkeyName: 'Nome', + passkeyNameHelper: 'Digite um nome para distinguir dispositivos', + passkeyAdd: 'Adicionar Passkey', + passkeyCreatedAt: 'Criado em', + passkeyLastUsedAt: 'Último uso', + passkeyDeleteConfirm: 'Após excluir, esta passkey não poderá ser usada para login. Continuar?', + passkeyLimit: 'É possível vincular até 5 passkeys', + passkeyFailed: + 'Falha no cadastro da passkey, por favor, certifique-se de que o certificado SSL do painel é confiável', + bindInfo: 'Informações de Vinculação', + bindAll: 'Ouvir Todos', + bindInfoHelper: + 'Alterar o endereço de escuta ou protocolo do serviço pode resultar em indisponibilidade do serviço. Você deseja continuar?', + ipv6: 'Ouvir IPv6', + bindAddress: 'Endereço de escuta', + entrance: 'Entrada', + showEntrance: 'Mostrar alerta desativado na página "Visão Geral"', + entranceHelper: + 'Habilitar a entrada de segurança permitirá o login no painel apenas através do ponto de entrada de segurança especificado.', + entranceError: + 'Por favor, insira um ponto de entrada seguro de 5-116 caracteres, somente números ou letras são suportados.', + entranceInputHelper: 'Deixe em branco para desativar a entrada de segurança.', + randomGenerate: 'Aleatório', + expirationTime: 'Data de Expiração', + unSetting: 'Não definido', + noneSetting: + 'Defina o tempo de expiração da senha do painel. Após a expiração, será necessário redefinir a senha.', + expirationHelper: + 'Se o tempo de expiração da senha for [0] dias, a função de expiração da senha estará desativada.', + days: 'Dias de Expiração', + expiredHelper: 'A senha atual expirou. Por favor, altere a senha novamente.', + timeoutHelper: + '[ {0} dias ] A senha do painel está prestes a expirar. Após a expiração, será necessário redefinir a senha.', + complexity: 'Validação de Complexidade', + complexityHelper: + 'Após ativar, a regra de validação de senha será: 8-30 caracteres, incluindo letras, números e pelo menos dois caracteres especiais.', + bindDomain: 'Vincular domínio', + unBindDomain: 'Desvincular domínio', + panelSSL: 'SSL do Painel', + unBindDomainHelper: + 'A ação de desvincular um domínio pode causar insegurança no sistema. Você deseja continuar?', + bindDomainHelper: 'Após vincular o domínio, somente esse domínio poderá acessar o serviço 1Panel.', + bindDomainHelper1: 'Deixe em branco para desabilitar o vínculo de domínio.', + bindDomainWarning: + 'Após vincular o domínio, você será desconectado e poderá acessar o serviço 1Panel apenas através do domínio especificado nas configurações. Você deseja continuar?', + allowIPs: 'IP Autorizado', + unAllowIPs: 'IP Não Autorizado', + unAllowIPsWarning: + 'Autorizar um IP vazio permitirá que todos os IPs acessem o sistema, o que pode causar insegurança. Você deseja continuar?', + allowIPsHelper: + 'Após definir a lista de IPs autorizados, apenas os IPs da lista poderão acessar o serviço do painel.', + allowIPsWarning: + 'Após definir a lista de IPs autorizados, somente os IPs da lista poderão acessar o serviço do painel. Você deseja continuar?', + allowIPsHelper1: 'Deixe em branco para desabilitar a restrição de IP.', + allowIPEgs: 'Um por linha. Por exemplo,\n172.16.10.111\n172.16.10.0/24', + mfa: 'Autenticação de dois fatores (2FA)', + mfaClose: 'Desabilitar MFA reduzirá a segurança do serviço. Você deseja continuar?', + secret: 'Segredo', + mfaInterval: 'Intervalo de atualização (s)', + mfaTitleHelper: + 'O título é usado para distinguir diferentes hosts do 1Panel. Escaneie novamente ou adicione manualmente a chave secreta após modificar o título.', + mfaIntervalHelper: + 'Escaneie novamente ou adicione manualmente a chave secreta após modificar o intervalo de atualização.', + mfaAlert: + 'O token de uso único é um número dinâmico de 6 dígitos e baseado no tempo atual. Certifique-se de que o horário do servidor esteja sincronizado.', + mfaHelper: 'Após ativá-lo, o token de uso único precisará ser verificado.', + mfaHelper1: 'Baixe um aplicativo autenticador, por exemplo,', + mfaHelper2: + 'Para obter o token de uso único, escaneie o código QR abaixo usando seu aplicativo autenticador ou copie a chave secreta para o aplicativo de autenticação.', + mfaHelper3: 'Digite os seis dígitos do aplicativo', + mfaCode: 'Token de uso único', + sslChangeHelper: 'Modificar a configuração de https e reiniciar o serviço. Você deseja continuar?', + sslDisable: 'Desabilitar', + sslDisableHelper: + 'Se o serviço https for desabilitado, será necessário reiniciar o painel para que a alteração tenha efeito. Você deseja continuar?', + noAuthSetting: 'Configuração não autorizada', + noAuthSettingHelper: + 'Quando os usuários não fizerem login com a entrada de segurança especificada, ou não acessarem o painel a partir de IP ou domínio especificado, essa resposta pode ocultar características do painel.', + responseSetting: 'Configuração de resposta', + help200: 'Página de ajuda', + error400: 'Requisição inválida', + error401: 'Não autorizado', + error403: 'Proibido', + error404: 'Não encontrado', + error408: 'Tempo de solicitação expirado', + error416: 'Faixa não satisfatória', + error444: 'Conexão fechada', + error500: 'Erro no servidor', + + https: 'Configurar HTTPS para o painel melhora a segurança de acesso', + strictHelper: 'Tráfego não HTTPS não pode se conectar ao painel', + muxHelper: + 'O painel ouvirá nas portas HTTP e HTTPS e redirecionará HTTP para HTTPS, mas isso pode reduzir a segurança', + certType: 'Tipo de certificado', + selfSigned: 'Autoassinado', + selfSignedHelper: + 'Os navegadores podem não confiar em certificados autoassinados e podem exibir avisos de segurança.', + select: 'Selecionar', + domainOrIP: 'Domínio ou IP:', + timeOut: 'Tempo limite', + rootCrtDownload: 'Download do certificado raiz', + primaryKey: 'Chave primária', + certificate: 'Certificado', + backupJump: + 'Arquivos de backup não estão na lista de backup atual, tente fazer o download do diretório de arquivos e importar para o backup.', + + snapshot: 'Snapshot | Snapshots', + noAppData: 'Nenhum aplicativo do sistema disponível para seleção', + noBackupData: 'Nenhum dado de backup disponível para seleção', + stepBaseData: 'Dados Básicos', + stepAppData: 'Aplicativos do Sistema', + stepPanelData: 'Dados do Sistema', + stepBackupData: 'Dados de Backup', + stepOtherData: 'Outros Dados', + operationLog: 'Manter logs de operações', + loginLog: 'Manter logs de acesso', + systemLog: 'Manter logs do sistema', + taskLog: 'Manter logs de tarefas', + monitorData: 'Manter dados de monitoramento', + dockerConf: 'Manter Configuração do Docker', + selectAllImage: 'Fazer backup de todas as imagens de aplicativos', + logLabel: 'Log', + agentLabel: 'Configuração do Nó', + appDataLabel: 'Dados de Aplicativos', + appImage: 'Imagem do Aplicativo', + appBackup: 'Backup de Aplicativo', + backupLabel: 'Diretório de Backup', + confLabel: 'Arquivos de Configuração', + dockerLabel: 'Contêineres', + taskLabel: 'Tarefas Agendadas', + resourceLabel: 'Diretório de Recursos do Aplicativo', + runtimeLabel: 'Ambiente de Execução', + appLabel: 'Aplicativo', + databaseLabel: 'Banco de Dados', + snapshotLabel: 'Arquivos de Snapshot', + websiteLabel: 'Site', + directoryLabel: 'Diretório', + appStoreLabel: 'Loja de Aplicativos', + shellLabel: 'Script', + tmpLabel: 'Diretório Temporário', + sslLabel: 'Diretório de Certificados', + reCreate: 'Falha ao criar snapshot', + reRollback: 'Falha ao reverter snapshot', + deleteHelper: + 'Todos os arquivos de snapshot, incluindo os da conta de backup de terceiros, serão excluídos. Você deseja continuar?', + status: 'Status do snapshot', + ignoreRule: 'Ignorar regra', + editIgnoreRule: '@:commons.button.edit @.lower:setting.ignoreRule', + ignoreHelper: + 'Esta regra será usada para comprimir e fazer backup do diretório de dados do 1Panel durante a criação do snapshot. Por padrão, arquivos de socket são ignorados.', + ignoreHelper1: 'Um por linha. Exemplo,\n*.log\n/opt/1panel/cache', + panelInfo: 'Escrever informações básicas do 1Panel', + panelBin: 'Fazer backup dos arquivos do sistema 1Panel', + daemonJson: 'Fazer backup do arquivo de configuração do Docker', + appData: 'Fazer backup dos aplicativos instalados do 1Panel', + panelData: 'Fazer backup do diretório de dados do 1Panel', + backupData: 'Fazer backup do diretório de backup local do 1Panel', + compress: 'Criar arquivo de snapshot', + upload: 'Fazer upload do arquivo de snapshot', + recoverDetail: 'Detalhes da recuperação', + createSnapshot: 'Criar snapshot', + importSnapshot: 'Sincronizar snapshot', + importHelper: 'Diretório do snapshot: ', + lastRecoverAt: 'Última recuperação realizada', + lastRollbackAt: 'Último rollback realizado', + reDownload: 'Baixar o arquivo de backup novamente', + recoverErrArch: 'A recuperação de snapshot entre diferentes arquiteturas de servidor não é suportada!', + recoverErrSize: + 'Espaço em disco insuficiente detectado, por favor, verifique ou libere espaço e tente novamente!', + recoverHelper: + 'Iniciando a recuperação do snapshot {0}, por favor, confirme as seguintes informações antes de prosseguir:', + recoverHelper1: 'A recuperação requer reiniciar os serviços Docker e 1Panel', + recoverHelper2: + 'Por favor, assegure-se de que há espaço suficiente em disco no servidor (Tamanho do arquivo de snapshot: {0}, Espaço disponível: {1})', + recoverHelper3: + 'Por favor, assegure-se de que a arquitetura do servidor corresponda à arquitetura do servidor onde o snapshot foi criado (Arquitetura do servidor atual: {0})', + rollback: 'Rollback', + rollbackHelper: + 'Reverter essa recuperação substituirá todos os arquivos dessa recuperação e pode exigir reiniciar os serviços Docker e 1Panel. Você deseja continuar?', + + upgradeHelper: 'A atualização requer reiniciar o serviço 1Panel. Você deseja continuar?', + rollbackLocalHelper: + 'O nó principal não suporta rollback direto. Por favor, execute manualmente o comando [1pctl restore] para fazer o rollback!', + noUpgrade: 'Esta é a versão mais recente', + upgradeNotes: 'Notas de versão', + upgradeNow: 'Atualizar agora', + source: 'Fonte para download', + versionNotSame: + 'A versão do nó não corresponde à do nó principal. Atualize na Gestão de Nós antes de tentar novamente.', + versionCompare: + 'Detectado que o nó {0} já está na última versão atualizável. Por favor, verifique a versão do nó principal e tente novamente!', + + about: 'Sobre', + versionItem: 'Versão Atual', + backupCopies: 'Número de Cópias a Manter', + backupCopiesHelper: + 'Defina o número de cópias de backup de atualização para manter para reversão de versão. 0 significa manter todas.', + backupCopiesRule: 'Mantenha pelo menos 3 registros de backup de atualização', + release: 'Registro de Atualizações de Versão', + releaseHelper: + 'Falha ao obter o registro de atualizações para o ambiente atual. Você pode verificar a documentação oficial manualmente.', + project: 'GitHub', + issue: 'Problema', + doc: 'Documento oficial', + star: 'Estrela', + description: 'Painel de Servidor Linux', + forum: 'Discussões', + doc2: 'Docs', + currentVersion: 'Versão', + + license: 'Licença', + bindNode: 'Vincular Nó', + menuSetting: 'Configurações do Menu', + menuSettingHelper: 'Quando apenas 1 submenu existir, a barra de menus exibirá apenas esse submenu', + showAll: 'Mostrar Tudo', + hideALL: 'Ocultar Tudo', + ifShow: 'Exibir?', + menu: 'Menu', + confirmMessage: 'A página será atualizada para atualizar a lista de menus avançados. Continuar?', + recoverMessage: + 'A página será atualizada e a lista de menus será restaurada ao estado inicial. Deseja continuar?', + compressPassword: 'Senha de compressão', + backupRecoverMessage: + 'Por favor, insira a senha de compressão ou descompressão (deixe em branco para não definir)', + }, + license: { + offLine: 'Versão Offline', + community: 'Gratuito', + oss: 'Open Source Software', + pro: 'Pro', + trial: 'Teste', + add: 'Adicionar Edição Comunitária', + licenseBindHelper: 'As cotas de nó gratuitas só podem ser usadas quando a licença está vinculada a um nó', + licenseAlert: + 'Nós da Edição Comunitária só podem ser adicionados quando a licença está devidamente vinculada a um nó. Apenas nós devidamente vinculados à licença suportam troca.', + licenseUnbindHelper: + 'Nós da Edição Comunitária detectados para esta licença. Por favor, desvincule e tente novamente!', + subscription: 'Assinatura', + perpetual: 'Licença Perpétua', + versionConstraint: '{0} Compra do versão', + forceUnbind: 'Forçar Desvinculação', + forceUnbindHelper: + 'Forçar a desvinculação ignorará quaisquer erros que ocorram durante o processo de desvinculação e, em última análise, liberará a vinculação da licença.', + updateForce: + 'Atualização forçada (ignora todos os erros durante o desvinculamento para garantir o sucesso da operação final)', + trialInfo: 'Versão', + authorizationId: 'ID de autorização', + authorizedUser: 'Usuário autorizado', + lostHelper: + 'A licença atingiu o número máximo de tentativas de reenvio. Por favor, clique manualmente no botão de sincronização para garantir que os recursos da versão profissional estão funcionando corretamente. detalhes: ', + disableHelper: + 'A verificação de sincronização da licença falhou. Por favor, clique manualmente no botão de sincronização para garantir que os recursos da versão profissional estão funcionando corretamente. detalhes: ', + quickUpdate: 'Atualização rápida', + power: 'Autorizar', + unbindHelper: + 'Todas as configurações relacionadas ao Pro serão limpas após a desvinculação. Você deseja continuar?', + importLicense: 'Licença', + importHelper: 'Clique ou arraste o arquivo de licença aqui', + technicalAdvice: 'Consultoria técnica', + advice: 'Consultoria', + levelUpPro: 'Upgrade para Pro', + licenseSync: 'Sincronização de Licença', + knowMorePro: 'Saiba mais', + closeAlert: 'A página atual pode ser fechada nas configurações do painel', + introduce: 'Introdução de recursos', + waf: 'O upgrade para a versão profissional pode fornecer recursos como mapa de intercepção, logs, registros de bloqueio, bloqueio por localização geográfica, regras personalizadas, páginas de intercepção personalizadas, etc.', + tamper: 'O upgrade para a versão profissional pode proteger sites contra modificações ou adulterações não autorizadas.', + setting: + 'O upgrade para a versão profissional permite a personalização do logo do painel, mensagem de boas-vindas e outras informações.', + monitor: + 'Upgrade para a versão profissional para visualizar o status em tempo real do site, tendências de visitantes, fontes de visitantes, logs de solicitações e outras informações.', + alert: 'Upgrade para a versão profissional para receber informações de alarme via SMS e visualizar logs de alarmes, controlar completamente vários eventos chave e garantir a operação sem preocupações do sistema', + node: 'Atualizar para a Edição Profissional permite gerenciar vários servidores Linux com o 1Panel.', + nodeApp: + 'Atualizar para a Edição Profissional permite a atualização unificada de versões de aplicativos multi-nó sem a necessidade de alternar manualmente entre nós.', + nodeDashboard: + 'Atualizar para a Edição Profissional permite o gerenciamento centralizado de aplicativos, sites, bancos de dados e tarefas agendadas multi-nó.', + fileExchange: + 'Atualize para a Edição Profissional para transferir arquivos rapidamente entre vários servidores.', + app: 'Upgrade para a versão profissional para visualizar informações do serviço, monitoramento anômalo, etc., através do aplicativo móvel.', + cluster: + 'A atualização para a Edição Profissional permite gerenciar clusters mestre-escravo MySQL/Postgres/Redis.', + }, + clean: { + scan: 'Iniciar escaneamento', + scanHelper: 'Limpeza fácil de arquivos inúteis gerados durante a execução do 1Panel', + clean: 'Limpar agora', + reScan: 'Repetir escaneamento', + cleanHelper: 'Arquivos e diretórios selecionados não podem ser revertidos após a limpeza. Continuar?', + statusSuggest: '(Limpeza recomendada)', + statusClean: '(Muito limpo)', + statusEmpty: 'Muito limpo, nenhuma limpeza necessária!', + statusWarning: '(Prossiga com cautela)', + lastCleanTime: 'Última limpeza: {0}', + lastCleanHelper: 'Arquivos e diretórios limpos: {0}, total limpo: {1}', + cleanSuccessful: 'Limpeza realizada com sucesso', + currentCleanHelper: 'Arquivos e diretórios limpos nesta sessão: {0}, total limpo: {1}', + suggest: '(Recomendado)', + totalScan: 'Total de arquivos inúteis a serem limpos: ', + selectScan: 'Total de arquivos inúteis selecionados: ', + + system: 'Arquivos inúteis do sistema', + systemHelper: 'Arquivos temporários gerados durante processos como snapshots e atualizações.', + panelOriginal: 'Arquivos de backup antes da restauração do snapshot do sistema.', + upgrade: 'Arquivos de backup de atualização do sistema.', + agentPackages: 'Pacotes de atualização/instalação de nós filhos de versões históricas.', + upgradeHelper: '(Recomenda-se manter o backup de atualização mais recente para rollback do sistema)', + rollback: 'Arquivos de backup antes da recuperação', + + backup: 'Backup do Sistema', + backupHelper: 'Arquivos de backup não associados a contas de backup locais', + unknownBackup: 'Backup do Sistema', + tmpBackup: 'Backup Temporário', + unknownApp: 'Backup de Aplicativo não Associado', + unknownDatabase: 'Backup de Banco de Dados não Associado', + unknownWebsite: 'Backup de Site não Associado', + unknownSnapshot: 'Backup de Instantâneo não Associado', + + upload: 'Arquivos temporários de upload', + uploadHelper: 'Arquivos temporários enviados da lista de backup do sistema', + download: 'Arquivos temporários de download', + downloadHelper: 'Arquivos temporários baixados de contas de backup de terceiros pelo sistema', + directory: 'Diretório', + + systemLog: 'Arquivo de log', + systemLogHelper: 'Logs do sistema, logs de tarefas, arquivos de log do site', + dockerLog: 'Arquivos de log de operação de contêineres', + taskLog: 'Arquivos de log de execução de tarefas agendadas', + shell: 'Tarefas agendadas de script de shell', + containerShell: 'Tarefas agendadas de script de shell executadas dentro de contêineres', + curl: 'Tarefas agendadas de CURL', + + docker: 'Lixo de contêineres', + dockerHelper: 'Arquivos como contêineres, imagens, volumes, cache de compilação, etc.', + volumes: 'Volumes', + buildCache: 'Cache de construção do container', + + appTmpDownload: 'Arquivo de download temporário do aplicativo', + unknownWebsiteLog: 'Fail sandaran log laman web tidak dikaitkan', + }, + app: { + app: 'Aplicativo | Aplicativos', + installName: 'Nome', + installed: 'Instalado', + all: 'Todos', + version: 'Versão', + detail: 'Detalhes', + params: 'Editar', + author: 'Autor', + source: 'Fonte', + appName: 'Nome do Aplicativo', + deleteWarn: + 'A operação de exclusão excluirá todos os dados e backups juntos. Esta operação não pode ser desfeita. Deseja continuar?', + syncSuccess: 'Sincronizado com sucesso', + canUpgrade: 'Atualizações', + backupName: 'Nome do Arquivo', + backupPath: 'Caminho do Arquivo', + backupdate: 'Hora do Backup', + versionSelect: 'Por favor, selecione uma versão', + operatorHelper: 'A operação {0} será realizada no aplicativo selecionado. Deseja continuar?', + startOperatorHelper: 'O aplicativo será iniciado. Deseja continuar?', + stopOperatorHelper: 'O aplicativo será parado. Deseja continuar?', + restartOperatorHelper: 'O aplicativo será reiniciado. Deseja continuar?', + reloadOperatorHelper: 'O aplicativo será recarregado. Deseja continuar?', + checkInstalledWarn: `"{0}" não foi detectado. Vá para "Loja de Aplicativos" para instalar.`, + limitHelper: 'O aplicativo já foi instalado.', + deleteHelper: `"{0}" foi associado aos seguintes recursos. Por favor, verifique e tente novamente!`, + checkTitle: 'Dica', + defaultConfig: 'Configuração padrão', + defaultConfigHelper: 'Foi restaurado para a configuração padrão, ela entrará em vigor após salvar', + forceDelete: 'Excluir forçadamente', + forceDeleteHelper: 'A exclusão forçada ignorará erros durante o processo de exclusão e eliminará os metadados.', + deleteBackup: 'Excluir backup', + deleteBackupHelper: 'Excluir também o backup do aplicativo', + deleteDB: 'Excluir banco de dados', + deleteDBHelper: 'Excluir também o banco de dados', + noService: 'Sem {0}', + toInstall: 'Ir para instalar', + param: 'Parâmetros', + alreadyRun: 'Idade', + syncAppList: 'Sincronizar', + less1Minute: 'Menos de 1 minuto', + appOfficeWebsite: 'Site oficial', + github: 'Github', + document: 'Documento', + updatePrompt: 'Nenhuma atualização disponível', + installPrompt: 'Nenhum aplicativo instalado ainda', + updateHelper: 'Editar parâmetros pode causar falha no início do aplicativo. Por favor, proceda com cautela.', + updateWarn: 'Os parâmetros de atualização precisam reconstruir o aplicativo. Deseja continuar?', + busPort: 'Porta', + syncStart: 'Iniciando a sincronização! Por favor, atualize a loja de aplicativos mais tarde', + advanced: 'Configurações avançadas', + cpuCore: 'núcleo(s)', + containerName: 'Nome do container', + containerNameHelper: 'O nome do container será gerado automaticamente quando não definido', + allowPort: 'Acesso externo', + allowPortHelper: 'Permitir o acesso externo irá liberar a porta do firewall', + appInstallWarn: `O aplicativo não expõe a porta de acesso externo por padrão. Clique em "Configurações Avançadas" para expô-la.`, + upgradeStart: 'Iniciando a atualização! Por favor, atualize a página mais tarde', + toFolder: 'Abrir o diretório de instalação', + editCompose: 'Editar arquivo compose', + editComposeHelper: 'Editar o arquivo compose pode resultar em falha na instalação do software', + composeNullErr: 'O compose não pode estar vazio', + takeDown: 'Retirar', + allReadyInstalled: 'Instalado', + installHelper: 'Se houver problemas ao puxar a imagem, configure a aceleração da imagem.', + upgradeHelper: + 'Coloque aplicativos anormais de volta ao estado normal antes de atualizar. Se a atualização falhar, vá para "Logs > Logs do Sistema" para verificar a razão da falha.', + installWarn: `O acesso externo não foi habilitado, o que impede que o aplicativo seja acessado via redes externas. Deseja continuar?`, + showIgnore: 'Exibir aplicativos ignorados', + cancelIgnore: 'Cancelar ignorar', + ignoreList: 'Aplicativos ignorados', + appHelper: + 'Vá para a página de detalhes do aplicativo para aprender as instruções de instalação para alguns aplicativos especiais.', + backupApp: 'Fazer backup do aplicativo antes de atualizar', + backupAppHelper: + 'Se a atualização falhar, o backup será automaticamente revertido. Verifique a razão da falha no log de auditoria-sistema. O backup manterá as últimas 3 cópias por padrão', + openrestyDeleteHelper: 'Excluir forçadamente o OpenResty excluirá todos os sites. Deseja continuar?', + downloadLogHelper1: 'Todos os logs do aplicativo {0} estão prestes a ser baixados. Deseja continuar?', + downloadLogHelper2: 'Os últimos {1} logs do aplicativo {0} estão prestes a ser baixados. Deseja continuar?', + syncAllAppHelper: 'Todos os aplicativos serão sincronizados. Deseja continuar?', + hostModeHelper: + 'O modo de rede atual do aplicativo é o modo host. Se precisar abrir a porta, abra-a manualmente na página do firewall.', + showLocal: 'Exibir aplicativos locais', + reload: 'Recarregar', + upgradeWarn: + 'Atualizar o aplicativo substituirá o arquivo docker-compose.yml. Se houver alterações, você pode clicar para visualizar a comparação do arquivo', + newVersion: 'Nova versão', + oldVersion: 'Versão atual', + composeDiff: 'Comparação de arquivo', + showDiff: 'Exibir comparação', + useNew: 'Usar versão personalizada', + useDefault: 'Usar versão padrão', + useCustom: 'Personalizar docker-compose.yml', + useCustomHelper: + 'Usar um arquivo docker-compose.yml personalizado pode causar falha na atualização do aplicativo. Se não for necessário, não marque esta opção.', + diffHelper: + 'O lado esquerdo é a versão antiga, o lado direito é a nova versão. Após editar, clique para salvar a versão personalizada', + pullImage: 'Puxar Imagem', + pullImageHelper: 'Execute o comando docker pull para puxar a imagem antes de iniciar o aplicativo', + deleteImage: 'Excluir Imagem', + deleteImageHelper: + 'Exclua a imagem relacionada ao aplicativo. A tarefa não será encerrada se a exclusão falhar.', + requireMemory: 'Requisito de Memória', + supportedArchitectures: 'Arquiteturas Suportadas', + link: 'Link', + showCurrentArch: 'Aplicações da arquitetura atual do servidor', + syncLocalApp: 'Sincronizar Aplicativo Local', + memoryRequiredHelper: 'O aplicativo atual requer {0} de memória', + gpuConfig: 'Ativar Suporte a GPU', + gpuConfigHelper: + 'Certifique-se de que a máquina possui uma GPU NVIDIA e tenha os drivers NVIDIA e NVIDIA Docker Container Toolkit instalados', + webUI: 'Endereço de Acesso Web', + webUIPlaceholder: 'Exemplo: example.com:8080/login', + defaultWebDomain: 'Endereço de Acesso Padrão', + defaultWebDomainHepler: 'Se a porta do aplicativo for 8080, o endereço será http(s)://endereço padrão:8080', + webUIConfig: + 'O nó atual não tem um endereço de acesso padrão configurado. Por favor, defina-o nos parâmetros do aplicativo ou vá para as configurações do painel para configurar!', + toLink: 'Ir para', + customAppHelper: + 'Antes de instalar um pacote de loja de aplicativos personalizado, certifique-se de que não há aplicativos instalados.', + forceUninstall: 'Desinstalação Forçada', + syncCustomApp: 'Sincronizar Aplicativo Personalizado', + ignoreAll: 'Ignorar todas as versões subsequentes', + ignoreVersion: 'Ignorar versão especificada', + specifyIP: 'Vincular IP do Host', + specifyIPHelper: + 'Defina o endereço do host/interface de rede para vincular a porta (se você não tiver certeza sobre isso, por favor, não preencha)', + uninstallDeleteBackup: 'Desinstalar Aplicativo - Excluir Backup', + uninstallDeleteImage: 'Desinstalar Aplicativo - Excluir Imagem', + upgradeBackup: 'Fazer Backup do Aplicativo Antes de Atualizar', + noAppHelper: + 'Nenhuma aplicação detectada, por favor vá ao centro de tarefas para visualizar o log de sincronização da loja de aplicativos', + isEdirWarn: 'Detectada modificação no arquivo docker-compose.yml, por favor verifique a comparação', + }, + website: { + primaryDomain: 'Domínio principal', + otherDomains: 'Outros domínios', + static: 'Estático', + deployment: 'Implantação', + supportUpType: + 'Apenas o formato de arquivo .tar.gz é suportado, e o pacote compactado deve conter o arquivo {0}.json', + proxy: 'Proxy reverso', + alias: 'Alias', + ftpUser: 'Conta FTP', + ftpPassword: 'Senha FTP', + ftpHelper: + 'Após criar um site, uma conta FTP correspondente será criada e o diretório FTP será vinculado ao diretório do site.', + remark: 'Observação', + manageGroup: 'Gerenciar grupos', + groupSetting: 'Gerenciamento de grupos', + createGroup: 'Criar grupo', + appNew: 'Novo Aplicativo', + appInstalled: 'Aplicativo instalado', + create: 'Criar site', + delete: 'Excluir site', + deleteApp: 'Excluir Aplicativo', + deleteBackup: 'Excluir Backup', + domain: 'Domínio', + domainHelper: 'Um domínio por linha.\nSuporta curinga "*" e endereço IP.\nSuporta adição de porta.', + addDomain: 'Adicionar', + domainConfig: 'Domínios', + defaultDoc: 'Documento', + perserver: 'Concorrência', + perserverHelper: 'Limitar a concorrência máxima do site atual', + perip: 'IP único', + peripHelper: 'Limitar o número máximo de acessos simultâneos a um único IP', + rate: 'Limites de tráfego', + rateHelper: 'Limitar o fluxo de cada requisição (unidade: KB)', + limitHelper: 'Habilitar controle de fluxo', + other: 'Outro', + currentSSL: 'Certificado atual', + dnsAccount: 'Conta DNS', + applySSL: 'Solicitação de certificado', + SSLList: 'Lista de certificados', + createDnsAccount: 'Conta DNS', + aliyun: 'Aliyun', + aliEsa: 'Aliyun ESA', + awsRoute53: 'Amazon Route 53', + manual: 'Análise manual', + key: 'Chave', + check: 'Ver', + acmeAccountManage: 'Contas ACME', + email: 'Email', + acmeAccount: 'Conta ACME', + provider: 'Método de verificação', + dnsManual: 'Resolução manual', + expireDate: 'Data de expiração', + brand: 'Organização', + deploySSL: 'Implantação', + deploySSLHelper: 'Tem certeza de que deseja implantar o certificado?', + ssl: 'Certificado | Certificados', + dnsAccountManage: 'Provedores DNS', + renewSSL: 'Renovar', + renewHelper: 'Tem certeza de que deseja renovar o certificado?', + renewSuccess: 'Certificado renovado', + enableHTTPS: 'Habilitar', + aliasHelper: 'Alias é o nome do diretório do site', + lastBackupAt: 'Último backup realizado em', + null: 'nenhum', + nginxConfig: 'Configuração Nginx', + websiteConfig: 'Configurações do site', + proxySettings: 'Configurações de Proxy', + advancedSettings: 'Configurações Avançadas', + cacheSettings: 'Configurações de Cache', + sniSettings: 'Configurações de SNI', + basic: 'Básico', + source: 'Configuração', + security: 'Segurança', + nginxPer: 'Ajuste de desempenho', + neverExpire: 'Nunca', + setDefault: 'Definir como padrão', + default: 'Padrão', + deleteHelper: 'O status do aplicativo relacionado está anômalo, por favor verifique', + toApp: 'Ir para a lista de aplicativos instalados', + cycle: 'Ciclo', + frequency: 'Frequência', + ccHelper: + 'Solicitar cumulativamente a mesma URL mais de {1} vezes em {0} segundos, aciona a defesa CC, bloqueia este IP', + mustSave: 'A modificação precisa ser salva para ter efeito', + fileExt: 'Extensão de arquivo', + fileExtBlock: 'Lista negra de extensões de arquivo', + value: 'valor', + enable: 'Habilitar', + proxyAddress: 'Endereço Proxy', + proxyHelper: 'Exemplo: 127.0.0.1:8080', + forceDelete: 'Excluir forçadamente', + forceDeleteHelper: 'A exclusão forçada ignorará erros durante o processo de exclusão e excluirá os metadados.', + deleteAppHelper: 'Excluir aplicativos associados e backups de aplicativos ao mesmo tempo', + deleteBackupHelper: 'Excluir também backups do site.', + deleteDatabaseHelper: 'Também excluir o banco de dados associado ao site', + deleteConfirmHelper: `A operação de exclusão não pode ser desfeita. Digite "{0}" para confirmar a exclusão.`, + staticPath: 'O diretório principal correspondente é ', + limit: 'Plano', + blog: 'Fórum/Blog', + imageSite: 'Site de Imagens', + downloadSite: 'Site de Download', + shopSite: 'Loja', + doorSite: 'Portal', + qiteSite: 'Empresarial', + videoSite: 'Site de Vídeo', + errLog: 'Erro de log', + stopHelper: + 'Após parar o site, ele não poderá ser acessado normalmente, e o usuário verá a página de parada do site atual ao visitar. Deseja continuar?', + startHelper: 'Após ativar o site, os usuários podem acessar o conteúdo do site normalmente, deseja continuar?', + sitePath: 'Diretório', + siteAlias: 'Alias do site', + primaryPath: 'Diretório raiz', + folderTitle: 'O site contém principalmente as seguintes pastas', + wafFolder: 'Regras de firewall', + indexFolder: 'Diretório raiz do site', + sslFolder: 'Certificado do site', + enableOrNot: 'Habilitar', + oldSSL: 'Certificado existente', + manualSSL: 'Importar certificado', + select: 'Selecionar', + selectSSL: 'Selecionar certificado', + privateKey: 'Chave (KEY)', + certificate: 'Certificado (formato PEM)', + HTTPConfig: 'Opções HTTP', + HTTPSOnly: 'Bloquear requisições HTTP', + HTTPToHTTPS: 'Redirecionar para HTTPS', + HTTPAlso: 'Permitir requisições HTTP diretas', + sslConfig: 'Opções SSL', + disableHTTPS: 'Desabilitar HTTPS', + disableHTTPSHelper: 'Desabilitar HTTPS removerá a configuração do certificado relacionada, deseja continuar?', + SSLHelper: + 'Nota: Não use certificados SSL para sites ilegais.\nSe o acesso HTTPS não funcionar após a ativação, verifique se o grupo de segurança liberou corretamente a porta 443.', + SSLConfig: 'Configurações do certificado', + SSLProConfig: 'Configurações do protocolo', + supportProtocol: 'Versão do protocolo', + encryptionAlgorithm: 'Algoritmo de criptografia', + notSecurity: '(não seguro)', + encryptHelper: + "O Let's Encrypt tem um limite de frequência para a emissão de certificados, mas é suficiente para atender necessidades normais. Operações muito frequentes podem causar falha na emissão. Para restrições específicas, consulte documentação oficial", + ipValue: 'Valor', + ext: 'Extensão de arquivo', + wafInputHelper: 'Digite os dados por linha, uma linha por vez', + data: 'dados', + ever: 'permanente', + nextYear: 'Um ano depois', + noLog: 'Nenhum log encontrado', + defaultServer: 'Site padrão', + noDefaultServer: 'Não definido', + defaultServerHelper: + 'Após definir o site padrão, todos os nomes de domínio e IPs desvinculados serão redirecionados para o site padrão\nIsso pode evitar eficazmente a resolução maliciosa\nNo entanto, isso também pode causar falha na interceptação de nomes de domínio não autorizados pelo WAF', + restoreHelper: 'Tem certeza de que deseja restaurar com este backup?', + websiteDeploymentHelper: 'Use um aplicativo instalado ou crie um novo aplicativo para criar um site.', + websiteStatictHelper: 'Crie um diretório de site no host.', + websiteProxyHelper: + 'Use proxy reverso para redirecionar um serviço existente. Por exemplo, se um serviço estiver instalado e rodando na porta 8080, o endereço do proxy será "http://127.0.0.1:8080".', + runtimeProxyHelper: 'Use o ambiente de execução do site para criar um site.', + runtime: 'Ambiente de execução', + deleteRuntimeHelper: + 'O aplicativo de Runtime precisa ser excluído junto com o site, por favor, manuseie com cuidado', + proxyType: 'Tipo de rede', + unix: 'Rede Unix', + tcp: 'Rede TCP/IP', + phpFPM: 'Configuração FPM', + phpConfig: 'Configuração PHP', + updateConfig: 'Atualizar configurações', + isOn: 'Ligado', + isOff: 'Desligado', + rewrite: 'Pseudo-estático', + rewriteMode: 'Plano', + current: 'Atual', + rewriteHelper: + 'Se configurar pseudo-estático e o site se tornar inacessível, tente reverter para as configurações padrão.', + runDir: 'Diretório de execução', + runUserHelper: + 'Para sites implantados através do ambiente de execução PHP, você deve definir o proprietário e o grupo dos arquivos e pastas de índice e subdiretórios para 1000. Para o ambiente PHP local, consulte as configurações de usuário e grupo do PHP-FPM local', + userGroup: 'Usuário/Grupo', + uGroup: 'Grupo', + proxyPath: 'Caminho do proxy', + proxyPass: 'URL de destino', + cache: 'Cache', + cacheTime: 'Duração do cache', + enableCache: 'Habilitar cache', + proxyHost: 'Host do proxy', + disabled: 'Parado', + startProxy: 'Isso iniciará o proxy reverso. Deseja continuar?', + stopProxy: 'Isso interromperá o proxy reverso. Deseja continuar?', + sourceFile: 'Fonte', + proxyHelper1: 'Ao acessar este diretório, o conteúdo da URL de destino será retornado e exibido.', + proxyPassHelper: 'A URL de destino deve ser válida e acessível.', + proxyHostHelper: 'Passe o nome de domínio no cabeçalho da requisição para o servidor proxy.', + replacementHelper: + 'Até 5 substituições podem ser adicionadas, deixe em branco se não houver substituição necessária.', + modifier: 'Regras de correspondência', + modifierHelper: + 'Exemplo: "=" é correspondência exata, "~" é correspondência regular, "^~" corresponde ao início do caminho, etc.', + replace: 'Substituições de texto', + replaceHelper: + 'O recurso de substituição de texto do nginx permite a substituição de strings no conteúdo da resposta durante o proxy reverso. É comumente usado para modificar links, endereços de API, etc., em arquivos HTML, CSS, JavaScript e outros retornados pelo backend. Suporta correspondência de expressão regular para necessidades complexas de substituição de conteúdo.', + addReplace: 'Adicionar', + replaced: 'String de busca (não pode estar vazia)', + replaceText: 'Substituir por string', + replacedErr: 'A string de busca não pode estar vazia', + replacedErr2: 'A string de busca não pode ser repetida', + replacedListEmpty: 'Nenhuma regra de substituição de texto', + proxySslName: 'Nome SNI do Proxy', + basicAuth: 'Autenticação básica', + editBasicAuthHelper: + 'A senha é criptografada de forma assimétrica e não pode ser exibida. A edição requer a redefinição da senha', + antiLeech: 'Anti-leech', + extends: 'Extensão', + browserCache: 'Cache', + noModify: 'Não Modificar', + serverCache: 'Cache do servidor', + leechLog: 'Registrar log anti-leech', + accessDomain: 'Domínios permitidos', + leechReturn: 'Recurso de resposta', + noneRef: 'Permitir referrer vazio', + disable: 'não habilitado', + disableLeechHelper: 'Se deseja desabilitar o anti-leech', + disableLeech: 'Desabilitar anti-leech', + ipv6: 'Ouvir IPv6', + leechReturnError: 'Por favor, preencha o código de status HTTP', + blockedRef: 'Permitir Referer não padrão', + accessControl: 'Controle anti-leech', + leechcacheControl: 'Controle de cache', + logEnableControl: 'Registrar solicitações de ativos estáticos', + leechSpecialValidHelper: + "Quando 'Permitir Referer vazio' estiver ativado, as solicitações sem Referer (acesso direto etc.) não serão bloqueadas; ao ativar 'Permitir Referer não padrão', qualquer Referer que não comece com http/https será permitido (solicitações de cliente etc.).", + leechInvalidReturnHelper: 'Código de status HTTP retornado após bloquear solicitações de hotlink', + leechlogControlHelper: + 'Registra solicitações de ativos estáticos; geralmente desativado em produção para evitar logs excessivos e ruidosos', + selectAcme: 'Selecionar conta Acme', + imported: 'Criado manualmente', + importType: 'Tipo de importação', + pasteSSL: 'Colar código', + localSSL: 'Selecionar arquivo do servidor', + privateKeyPath: 'Caminho do arquivo da chave privada', + certificatePath: 'Caminho do arquivo do certificado', + ipWhiteListHelper: + 'O papel da lista de permissões de IP: todas as regras são inválidas para a lista de permissões de IP', + redirect: 'Redirecionar', + sourceDomain: 'Domínio de origem', + targetURL: 'Endereço da URL de destino', + keepPath: 'Parâmetros URI', + path: 'caminho', + redirectType: 'Tipo de redirecionamento', + redirectWay: 'Modo', + keep: 'manter', + notKeep: 'Não manter', + redirectRoot: 'Redirecionar para a página inicial', + redirectHelper: 'Redirecionamento permanente 301, redirecionamento temporário 302', + changePHPVersionWarn: + 'Trocar a versão do PHP vai excluir o contêiner PHP original (o código do site montado não será perdido), continuar?', + changeVersion: 'Trocar versão', + retainConfig: 'Deseja manter os arquivos php-fpm.conf e php.ini?', + runDirHelper2: + 'Por favor, certifique-se de que o diretório de execução secundário esteja no diretório do índice', + openrestyHelper: + 'Porta HTTP padrão do OpenResty: {0} Porta HTTPS: {1}, o que pode afetar o acesso ao domínio do site e o redirecionamento forçado para HTTPS', + primaryDomainHelper: 'Exemplo: exemplo.com ou exemplo.com:8080', + acmeAccountType: 'Tipo de conta', + keyType: 'Algoritmo de chave', + tencentCloud: 'Tencent Cloud', + containWarn: 'O nome de domínio contém o domínio principal, por favor, reentre', + rewriteHelper2: + 'Aplicações como o WordPress instaladas a partir da loja de aplicativos geralmente vêm com configuração de pseudo-estática predefinida. Reconfigurá-las pode causar erros.', + websiteBackupWarn: + 'Somente suporta importar backups locais, importar backups de outras máquinas pode causar falha na recuperação', + ipWebsiteWarn: + 'Sites com IP como nomes de domínio precisam ser configurados como site padrão para serem acessados normalmente', + hstsHelper: 'Ativar HSTS pode aumentar a segurança do site', + includeSubDomains: 'SubDomains', + hstsIncludeSubDomainsHelper: + 'Quando ativado, a política HSTS será aplicada a todos os subdomínios do domínio atual.', + defaultHtml: 'Página padrão', + website404: 'Página de erro 404 do site', + domain404: 'O domínio do site não existe', + indexHtml: 'Índice para site estático', + stopHtml: 'Site parado', + indexPHP: 'Índice para site PHP', + sslExpireDate: 'Data de expiração do certificado', + website404Helper: + 'A página de erro 404 do site suporta apenas sites com ambiente de execução PHP e sites estáticos', + sni: 'SNI de origem', + sniHelper: + 'Quando o proxy reverso de backend for HTTPS, você pode precisar configurar o SNI de origem. Consulte a documentação do provedor de serviços CDN para mais detalhes.', + huaweicloud: 'Huawei Cloud', + createDb: 'Criar Banco de Dados', + enableSSLHelper: 'A falha ao ativar o SSL não afetará a criação do site.', + batchAdd: 'Adicionar Domínios em Lote', + batchInput: 'Entrada em Lote', + domainNotFQDN: 'Este domínio pode não estar acessível na rede pública', + domainInvalid: 'Formato de domínio inválido', + domainBatchHelper: + 'Um domínio por linha, formato: domínio:porta@ssl\nExemplo: example.com:443@ssl ou example.com', + generateDomain: 'Gerar', + global: 'Global', + subsite: 'Subsite', + subsiteHelper: + 'Um subsite pode selecionar o diretório de um site PHP ou estático existente como seu diretório raiz.', + parentWebsite: 'Site Pai', + deleteSubsite: 'Para excluir o site atual, você deve primeiro excluir o subsite {0}.', + loadBalance: 'Balanceamento de Carga', + server: 'Nó', + algorithm: 'Algoritmo', + ipHash: 'Hash de IP', + ipHashHelper: + 'Distribui as solicitações para um servidor específico com base no endereço IP do cliente, garantindo que um cliente específico seja sempre roteado para o mesmo servidor.', + leastConn: 'Menos Conexões', + leastConnHelper: 'Envia solicitações para o servidor com o menor número de conexões ativas.', + leastTime: 'Menor Tempo', + leastTimeHelper: 'Envia solicitações para o servidor com o menor tempo de conexão ativa.', + defaultHelper: + 'Método padrão, as solicitações são distribuídas uniformemente para cada servidor. Se o servidor tiver configuração de peso, as solicitações são distribuídas de acordo com o peso especificado. Servidores com pesos mais altos recebem mais solicitações.', + weight: 'Peso', + maxFails: 'Máximo de Falhas', + maxConns: 'Máximo de Conexões', + strategy: 'Estratégia', + strategyDown: 'Desativar', + strategyBackup: 'Backup', + ipHashBackupErr: 'Hash IP não suporta nós de backup', + failTimeout: 'Tempo limite de falha', + failTimeoutHelper: + 'O comprimento da janela de tempo para verificações de integridade do servidor. Quando o número acumulado de falhas atinge o limite dentro deste período, o servidor será removido temporariamente e repetido após a mesma duração. Padrão 10 segundos', + + staticChangePHPHelper: 'Atualmente um site estático, pode ser alterado para um site PHP.', + proxyCache: 'Cache de Proxy Reverso', + cacheLimit: 'Limite de Espaço de Cache', + shareCache: 'Tamanho da Memória de Contagem de Cache', + cacheExpire: 'Tempo de Expiração do Cache', + shareCacheHelper: '1M de memória pode armazenar aproximadamente 8000 objetos de cache.', + cacheLimitHelper: 'Exceder o limite excluirá automaticamente os caches antigos.', + cacheExpireHelper: 'Caches não acessados dentro do tempo de expiração serão excluídos.', + realIP: 'IP Real', + ipFrom: 'Fonte do IP', + ipFromHelper: + 'Ao configurar fontes de IP confiáveis, o OpenResty analisará as informações de IP no cabeçalho HTTP para identificar e registrar com precisão o endereço IP real do visitante, incluindo nos logs de acesso.', + ipFromExample1: + 'Se o frontend for uma ferramenta como o Frp, você pode preencher o endereço IP do Frp, como 127.0.0.1.', + ipFromExample2: 'Se o frontend for um CDN, você pode preencher o intervalo de IPs do CDN.', + ipFromExample3: + 'Se não tiver certeza, você pode preencher 0.0.0.0/0 (IPv4) ou ::/0 (IPv6). [Nota: Permitir qualquer fonte de IP não é seguro.]', + http3Helper: + 'O HTTP/3 é uma versão atualizada do HTTP/2, fornecendo velocidades de conexão mais rápidas e melhor desempenho. No entanto, nem todos os navegadores suportam HTTP/3, e ativá-lo pode fazer com que alguns navegadores não consigam acessar o site.', + cors: 'Compartilhamento de Recursos de Origem Cruzada (CORS)', + enableCors: 'Habilitar CORS', + allowOrigins: 'Domínios permitidos', + allowMethods: 'Métodos de solicitação permitidos', + allowHeaders: 'Cabeçalhos de solicitação permitidos', + allowCredentials: 'Permitir envio de cookies', + preflight: 'Resposta rápida a solicitação de preflight', + preflightHleper: + 'Quando habilitado, quando o navegador envia uma solicitação de preflight de origem cruzada (solicitação OPTIONS), o sistema retornará automaticamente um código de status 204 e definirá os cabeçalhos de resposta de origem cruzada necessários', + + changeDatabase: 'Alterar Banco de Dados', + changeDatabaseHelper1: 'A associação do banco de dados é usada para backup e restauração do site.', + changeDatabaseHelper2: 'Alternar para outro banco de dados pode tornar backups anteriores irrecuperáveis.', + saveCustom: 'Salvar como Modelo', + rainyun: 'Rainyun', + volcengine: 'Volcengine', + runtimePortHelper: 'O ambiente de runtime atual possui várias portas. Por favor, selecione uma porta de proxy.', + runtimePortWarn: 'O ambiente de execução atual não possui portos, não é possível proxiar', + cacheWarn: 'Por favor, desligue o interruptor de cache no proxy reverso primeiro', + loadBalanceHelper: + 'Após criar o balanceamento de carga, vá para "Proxy Reverso", adicione um proxy e configure o endereço de backend para: http://.', + favorite: 'Favorito', + cancelFavorite: 'Cancelar Favorito', + useProxy: 'Usar Proxy', + useProxyHelper: 'Usar o endereço do servidor proxy nas configurações do painel', + westCN: 'West Digital', + openBaseDir: 'Prevenir Ataques entre Sites', + openBaseDirHelper: + 'open_basedir é usado para restringir o caminho de acesso a arquivos PHP, ajudando a prevenir acesso entre sites e aumentar a segurança', + serverCacheTime: 'Tempo de Cache do Servidor', + serverCacheTimeHelper: + 'O tempo que uma requisição é armazenada em cache no servidor. Durante este período, requisições idênticas retornarão o resultado em cache diretamente, sem pedir ao servidor de origem.', + browserCacheTime: 'Tempo de Cache do Navegador', + browserCacheTimeHelper: + 'O tempo que os recursos estáticos são armazenados em cache localmente no navegador, reduzindo requisições redundantes. Os usuários usarão o cache local diretamente antes de expirar ao atualizar a página.', + donotLinkeDB: 'Não Vincular Banco de Dados', + toWebsiteDir: 'Entrar no Diretório do Site', + execParameters: 'Parâmetros de Execução', + extCommand: 'Comando Suplementar', + mirror: 'Fonte de Espelho', + execUser: 'Usuário Executando', + execDir: 'Diretório de Execução', + packagist: 'Espelho Completo da China', + + batchOpreate: 'Operação em Lote', + batchOpreateHelper: 'Lote {0} sites, continuar operação?', + stream: 'Proxy TCP/UDP', + streamPorts: 'Portas de escuta', + streamPortsHelper: + 'Defina o número da porta de escuta externa, os clientes acessarão o serviço através desta porta, separados por vírgulas, por exemplo: 5222,5223', + streamHelper: 'Encaminhamento de Portas e Balanceamento de Carga TCP/UDP', + udp: 'Habilitar UDP', + + syncHtmlHelper: 'Sincronizar com PHP e sites estáticos', + }, + php: { + short_open_tag: 'Suporte para short tags', + max_execution_time: 'Tempo máximo de execução do script', + max_input_time: 'Tempo máximo de entrada de dados', + memory_limit: 'Limite de memória para scripts', + post_max_size: 'Tamanho máximo para dados enviados via POST', + file_uploads: 'Permitir upload de arquivos', + upload_max_filesize: 'Tamanho máximo permitido para upload de arquivos', + max_file_uploads: 'Número máximo de arquivos permitidos para upload simultâneo', + default_socket_timeout: 'Tempo limite do socket', + error_reporting: 'Nível de relatório de erros', + display_errors: 'Exibir informações detalhadas de erro', + cgi_fix_pathinfo: 'Ativar suporte a pathinfo', + date_timezone: 'Fuso horário', + disableFunction: 'Desabilitar função', + disableFunctionHelper: + 'Informe a função a ser desabilitada, como exec. Para múltiplas funções, separe com vírgulas', + uploadMaxSize: 'Limite de upload', + indexHelper: + 'Para garantir o funcionamento normal do site em PHP, coloque o código no diretório de índice e evite renomeações', + extensions: 'Modelos de extensão', + extension: 'Extensão', + extensionHelper: 'Para múltiplas extensões, separe com vírgulas', + toExtensionsList: 'Ver lista de extensões', + containerConfig: 'Configuração do Contêiner', + containerConfigHelper: + 'Variáveis de ambiente e outras informações podem ser modificadas em Configuração - Configuração do Contêiner após a criação', + dateTimezoneHelper: 'Exemplo: TZ=Asia/Shanghai (Adicione conforme necessário)', + }, + nginx: { + serverNamesHashBucketSizeHelper: 'The hash table size of the server name', + clientHeaderBufferSizeHelper: 'The header buffer size requested by the client', + clientMaxBodySizeHelper: 'Maximum Upload File', + keepaliveTimeoutHelper: 'Connection Timeout', + gzipMinLengthHelper: 'Minimum Compressed File', + gzipCompLevelHelper: 'Compression Rate', + gzipHelper: 'Enable compression for transmission', + connections: 'Active connections', + accepts: 'Accepts', + handled: 'Handled', + requests: 'Requests', + reading: 'Reading', + writing: 'Writing', + waiting: 'Waiting', + status: 'Current Status', + configResource: 'Configuration', + saveAndReload: 'Save and reload', + clearProxyCache: 'Clean reverse proxy cache', + clearProxyCacheWarn: + 'All websites that have configured with cache will be affected and "OpenResty" will be restarted. Do you want to continue?', + create: 'Criar Módulo', + update: 'Editar Módulo', + params: 'Parâmetros', + packages: 'Pacotes', + script: 'Script', + module: 'Módulo', + build: 'Construir', + buildWarn: + 'Construir OpenResty requer a reserva de certa quantidade de CPU e memória, e o processo pode ser demorado, por favor, seja paciente.', + mirrorUrl: 'Fonte de Software', + paramsHelper: 'Por exemplo: --add-module=/tmp/ngx_brotli', + packagesHelper: 'Por exemplo: git,curl separados por vírgulas', + scriptHelper: + 'Script a ser executado antes da compilação, geralmente para baixar o código-fonte do módulo, instalar dependências, etc.', + buildHelper: + 'Clique em Construir após adicionar/modificar um módulo. Construção bem-sucedida reiniciará automaticamente o OpenResty.', + defaultHttps: 'HTTPS Anti-tampering', + defaultHttpsHelper1: 'A ativação desta opção pode resolver problemas de adulteração HTTPS.', + sslRejectHandshake: 'Rejeitar handshake SSL padrão', + sslRejectHandshakeHelper: + 'Ativar isso pode evitar vazamento de certificados, definir um site padrão invalidará esta configuração', + }, + ssl: { + create: 'Solicitar', + provider: 'Tipo', + manualCreate: 'Criado manualmente', + acmeAccount: 'Conta ACME', + resolveDomain: 'Resolver nome de domínio', + err: 'Erro', + value: 'Valor do registro', + dnsResolveHelper: 'Vá ao provedor de serviço de DNS para adicionar os seguintes registros de resolução:', + detail: 'Detalhes', + msg: 'Informação', + ssl: 'Certificado', + key: 'Chave privada', + startDate: 'Data de início', + organization: 'Organização emissora', + renewConfirm: 'Isso renovará um novo certificado para o domínio {0}. Deseja continuar?', + autoRenew: 'Renovação automática', + autoRenewHelper: 'Renova automaticamente 30 dias antes da expiração', + renewSuccess: 'Renovação bem-sucedida', + renewWebsite: 'Este certificado está associado aos seguintes sites e será aplicado a eles simultaneamente', + createAcme: 'Criar conta', + acmeHelper: 'A conta ACME é usada para solicitar certificados gratuitos', + upload: 'Importar', + applyType: 'Tipo', + apply: 'Renovar', + applyStart: 'Início da solicitação do certificado', + getDnsResolve: 'Obtendo o valor de resolução DNS, por favor, aguarde...', + selfSigned: 'CA autoassinado', + ca: 'Autoridade certificadora', + commonName: 'Nome comum', + caName: 'Nome da autoridade certificadora', + company: 'Nome da organização', + department: 'Nome da unidade organizacional', + city: 'Nome da localidade', + province: 'Estado ou província', + country: 'Nome do país (código de 2 letras)', + commonNameHelper: 'Por exemplo, ', + selfSign: 'Emitir certificado', + days: 'Período de validade', + domainHelper: 'Um domínio por linha, suporta * e endereços IP', + pushDir: 'Enviar o certificado para o diretório local', + dir: 'Diretório', + pushDirHelper: + 'O arquivo do certificado "fullchain.pem" e o arquivo de chave "privkey.pem" serão gerados neste diretório.', + organizationDetail: 'Detalhes da organização', + fromWebsite: 'Do site', + dnsMauanlHelper: + 'No modo de resolução manual, você precisa clicar no botão de solicitação após a criação para obter o valor de resolução DNS', + httpHelper: + 'O uso do modo HTTP requer a instalação do OpenResty e não suporta a solicitação de certificados de domínio curinga.', + buypassHelper: 'O Buypass não está acessível na China continental', + googleHelper: 'Como obter EAB HmacKey e EAB kid', + googleCloudHelper: 'A API do Google Cloud não está acessível na maior parte da China continental', + skipDNSCheck: 'Pular verificação DNS', + skipDNSCheckHelper: + 'Marque esta opção apenas se enfrentar problemas de timeout durante a solicitação de certificação.', + cfHelper: 'Não use a Global API Key', + deprecated: 'será descontinuado', + deprecatedHelper: + 'A manutenção foi interrompida e pode ser abandonada em uma versão futura. Use o método Tencent Cloud para análise', + disableCNAME: 'Desativar CNAME', + disableCNAMEHelper: 'Marque esta opção se o domínio tiver um registro CNAME e a solicitação falhar.', + nameserver: 'Servidor DNS', + nameserverHelper: 'Use um servidor DNS personalizado para verificar os nomes de domínio.', + edit: 'Editar certificado', + execShell: 'Executar o script após a solicitação de certificação.', + shell: 'Conteúdo do script', + shellHelper: + 'O diretório padrão de execução do script é o diretório de instalação do 1Panel. Se um certificado for enviado para o diretório local, o diretório de execução será o diretório de envio do certificado. O tempo limite padrão de execução é de 30 minutos.', + customAcme: 'Serviço ACME Personalizado', + customAcmeURL: 'URL do Serviço ACME', + baiduCloud: 'Baidu Cloud', + pushNode: 'Sincronizar com Outros Nós', + pushNodeHelper: 'Enviar para os nós selecionados após a aplicação/renovação', + fromMaster: 'Envio do Nó Mestre', + hostedZoneID: 'Hosted Zone ID', + isIP: 'Certificado de IP', + useEAB: 'Usar autenticação EAB', + }, + firewall: { + create: 'Criar regra', + edit: 'Editar regra', + ccDeny: 'Proteção contra CC', + ipWhiteList: 'Lista de IPs permitidos', + ipBlockList: 'Lista de IPs bloqueados', + fileExtBlockList: 'Lista de extensões de arquivo bloqueadas', + urlWhiteList: 'Lista de URLs permitidas', + urlBlockList: 'Lista de URLs bloqueadas', + argsCheck: 'Verificação de parâmetros GET', + postCheck: 'Verificação de parâmetros POST', + cookieBlockList: 'Lista de cookies bloqueados', + + dockerHelper: + 'O firewall atual não pode desativar o mapeamento de porta de contêiner. Aplicativos instalados podem ir para a página [Instalados] para editar parâmetros do aplicativo e configurar regras de liberação de porta.', + iptablesHelper: + 'Detectado que o sistema está usando o firewall {0}. Para mudar para iptables, desinstale-o manualmente primeiro!', + quickJump: 'Acesso rápido', + used: 'Usado', + unUsed: 'Não usado', + dockerRestart: 'Operações de firewall exigem reinicialização do serviço Docker', + firewallHelper: 'Firewall do sistema {0}', + firewallNotStart: 'O firewall do sistema não está habilitado atualmente. Habilite-o primeiro.', + restartFirewallHelper: 'Esta operação reiniciará o firewall atual. Deseja continuar?', + stopFirewallHelper: 'Isso fará com que o servidor perca a proteção de segurança. Deseja continuar?', + startFirewallHelper: + 'Depois que o firewall for habilitado, a segurança do servidor será melhor protegida. Deseja continuar?', + noPing: 'Desativar ping', + enableBanPing: 'Bloquear Ping', + disableBanPing: 'Desbloquear Ping', + noPingTitle: 'Desativar ping', + noPingHelper: 'Isso desativará o ping, e o servidor não responderá ao ICMP. Deseja continuar?', + onPingHelper: 'Isso ativará o ping, permitindo que hackers descubram seu servidor. Deseja continuar?', + changeStrategy: 'Alterar a estratégia {0}', + changeStrategyIPHelper1: + 'Altere a estratégia de endereço IP para [negar]. Após definir o endereço IP, o acesso ao servidor será proibido. Deseja continuar?', + changeStrategyIPHelper2: + 'Altere a estratégia de endereço IP para [permitir]. Após definir o endereço IP, o acesso normal será restaurado. Deseja continuar?', + changeStrategyPortHelper1: + 'Altere a política de porta para [bloquear]. Após definir a política de porta, o acesso externo será negado. Deseja continuar?', + changeStrategyPortHelper2: + 'Altere a política de porta para [aceitar]. Após definir a política de porta, o acesso normal será restaurado. Deseja continuar?', + stop: 'Parar', + portFormatError: 'Este campo deve ser uma porta válida.', + portHelper1: 'Várias portas, ex.: 8080 e 8081', + portHelper2: 'Faixa de portas, ex.: 8080-8089', + changeStrategyHelper: + 'Alterar a estratégia [{1}] {0} para [{2}]. Após a definição, {0} acessará {2} externamente. Deseja continuar?', + + strategy: 'Estratégia', + accept: 'Aceitar', + drop: 'Bloquear', + anyWhere: 'Qualquer', + address: 'IPs especificados', + addressHelper: 'Suporta endereço IP ou segmento de IP', + allow: 'Permitir', + deny: 'Negar', + addressFormatError: 'Este campo deve ser um endereço IP válido.', + addressHelper1: 'Suporta endereço IP ou intervalo de IP. Por exemplo, "172.16.10.11" ou "172.16.10.0/24".', + addressHelper2: 'Para vários endereços IP, separe por vírgula. Por exemplo, "172.16.10.11, 172.16.0.0/24".', + allIP: 'Todos os IPs', + portRule: 'Regra | Regras', + createPortRule: '@:commons.button.create @.lower:firewall.portRule', + forwardRule: 'Regra de redirecionamento de porta | Regras de redirecionamento de porta', + createForwardRule: '@:commons.button.create @:firewall.forwardRule', + ipRule: 'Regra de IP | Regras de IP', + createIpRule: '@:commons.button.create @:firewall.ipRule', + userAgent: 'Filtro User-Agent', + sourcePort: 'Porta de origem', + targetIP: 'IP de destino', + targetPort: 'Porta de destino', + forwardHelper1: + 'Se você deseja redirecionar para a porta local, o IP de destino deve ser definido como "127.0.0.1".', + forwardHelper2: 'Deixe o IP de destino em branco para redirecionar para a porta local.', + forwardPortHelper: 'Suporta intervalos de portas, ex. 8080-8089', + forwardInboundInterface: 'Interface de Rede de Entrada para Encaminhamento', + exportHelper: 'Prestes a exportar {0} regras de firewall. Continuar?', + importSuccess: '{0} regras importadas com sucesso', + importPartialSuccess: 'Importação concluída: {0} sucesso, {1} falha', + + ipv4Limit: 'A operação atual suporta apenas endereços IPv4', + basicStatus: 'A cadeia atual {0} não está vinculada, vincule primeiro!', + baseIptables: 'Serviço iptables', + forwardIptables: 'Serviço de Encaminhamento de Porta iptables', + advanceIptables: 'Serviço de Configuração Avançada do iptables', + initMsg: 'Prestes a inicializar {0}, continuar?', + initHelper: + 'Detectado que {0} não está inicializado. Clique no botão de inicialização na barra de status superior para configurar!', + bindHelper: + 'Vincular - As regras de firewall só entrarão em vigor quando o status estiver vinculado. Confirmar?', + unbindHelper: + 'Desvincular - Quando desvinculado, todas as regras de firewall adicionadas se tornarão inválidas. Prossiga com cautela. Confirmar?', + defaultStrategy: 'A política padrão para a cadeia atual {0} é {1}', + defaultStrategy2: + 'A política padrão para a cadeia atual {0} é {1}, o status atual é não vinculado. As regras de firewall adicionadas entrarão em vigor após a vinculação!', + filterRule: 'Regra de Filtro', + filterHelper: + 'As regras de filtro permitem controlar o tráfego de rede no nível INPUT/OUTPUT. Configure com cuidado para evitar bloquear o sistema.', + chain: 'Cadeia', + targetChain: 'Cadeia de Destino', + sourceIP: 'IP de Origem', + destIP: 'IP de Destino', + inboundDirection: 'Direção de Entrada', + outboundDirection: 'Direção de Saída', + destPort: 'Porta de Destino', + action: 'Ação', + reject: 'Rejeitar', + sourceIPHelper: 'Formato CIDR, ex. 192.168.1.0/24. Deixe vazio para todos os endereços', + destIPHelper: 'Formato CIDR, ex. 10.0.0.0/8. Deixe vazio para todos os endereços', + portHelper: '0 significa qualquer porta', + allPorts: 'Todas as Portas', + deleteRuleConfirm: 'Excluirá {0} regras. Continuar?', + }, + runtime: { + runtime: 'Runtime', + workDir: 'Diretório de trabalho', + create: 'Criar runtime', + localHelper: 'Para problemas de instalação em ambiente local e uso em ambiente offline, consulte ', + versionHelper: 'Versão do PHP, por exemplo, v8.0', + buildHelper: + 'Quanto mais extensões, maior será o uso de CPU durante a criação da imagem. As extensões podem ser instaladas após a criação do ambiente.', + openrestyWarn: 'É necessário atualizar o PHP para OpenResty versão 1.21.4.1 ou superior para usar', + toupgrade: 'Atualizar', + edit: 'Editar runtime', + extendHelper: + 'Extensões não listadas podem ser inseridas e selecionadas manualmente. Por exemplo, digite "sockets" e escolha a primeira opção da lista suspensa para visualizar a lista de extensões.', + rebuildHelper: 'Após editar as extensões, é necessário recriar a aplicação PHP para aplicar as alterações', + rebuild: 'Recriar Aplicação PHP', + source: 'Fonte de extensões PHP', + ustc: 'Universidade de Ciência e Tecnologia da China', + netease: 'Netease', + aliyun: 'Alibaba Cloud', + default: 'Padrão', + tsinghua: 'Universidade Tsinghua', + xtomhk: 'Estação de Espelhos XTOM (Hong Kong)', + xtom: 'Estação de Espelhos XTOM (Global)', + phpsourceHelper: 'Escolha uma fonte adequada de acordo com o ambiente de rede.', + appPort: 'Porta da aplicação', + externalPort: 'Porta externa', + packageManager: 'Gerenciador de pacotes', + codeDir: 'Diretório do código', + appPortHelper: 'A porta usada pela aplicação.', + externalPortHelper: 'A porta exposta para o ambiente externo.', + runScript: 'Executar script', + runScriptHelper: + 'A lista de comandos de inicialização é gerada a partir do arquivo package.json no diretório de origem.', + open: 'Abrir', + operatorHelper: 'A operação {0} será realizada no ambiente selecionado. Deseja continuar?', + taobao: 'Taobao', + tencent: 'Tencent', + imageSource: 'Fonte da imagem', + moduleManager: 'Gerenciamento de Módulos', + module: 'Módulo', + nodeOperatorHelper: + 'Deseja {0} o módulo {1}? Esta operação pode causar instabilidade no ambiente, confirme antes de prosseguir.', + customScript: 'Comando de inicialização personalizado', + customScriptHelper: + 'Por favor, insira o comando de inicialização completo, por exemplo: npm run start. Para comandos de inicialização do PM2, substitua por pm2-runtime, caso contrário irá falhar ao iniciar.', + portError: `Evite repetir a mesma porta.`, + systemRestartHelper: + 'Descrição do status: Interrupção - falha ao adquirir status devido à reinicialização do sistema.', + javaScriptHelper: + 'Forneça um comando completo de inicialização. Por exemplo, "java -jar halo.jar -Xmx1024M -Xms256M".', + javaDirHelper: 'O diretório deve conter arquivos jar, subdiretórios também são aceitos.', + goHelper: 'Forneça um comando completo de inicialização. Por exemplo, "go run main.go" ou "./main".', + goDirHelper: 'O diretório ou subdiretório deve conter arquivos Go ou binários.', + pythonHelper: + 'Forneça um comando completo de inicialização. Por exemplo, "pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000".', + dotnetHelper: 'Por favor, preencha o comando completo de inicialização, por exemplo, dotnet MyWebApp.dll', + dirHelper: 'Nota: Preencha o caminho do diretório dentro do contêiner', + concurrency: 'Esquema de Concorrência', + loadStatus: 'Status de Carga', + extraHosts: 'Mapeamento de host', + }, + process: { + pid: 'ID do Processo', + ppid: 'ID do Processo Pai', + numThreads: 'Threads', + memory: 'Memória', + diskRead: 'Leitura de Disco', + diskWrite: 'Escrita de Disco', + netSent: 'Envio (uplink)', + netRecv: 'Recebimento (downlink)', + numConnections: 'Conexões', + startTime: 'Hora de Início', + state: 'Estado', + running: 'Executando', + sleep: 'Dormindo', + stop: 'Parado', + idle: 'Ocioso', + zombie: 'Processo zumbi', + wait: 'Aguardando', + lock: 'Travado', + blocked: 'Bloqueado', + cmdLine: 'Comando de Inicialização', + basic: 'Básico', + mem: 'Memória', + openFiles: 'Arquivos Abertos', + env: 'Ambientes', + noenv: 'Nenhum', + net: 'Conexões de Rede', + laddr: 'Endereço/porta de origem', + raddr: 'Endereço/porta de destino', + stopProcess: 'Encerrar', + viewDetails: 'Detalhes', + stopProcessWarn: 'Tem certeza de que deseja encerrar este processo (PID:{0})?', + kill: 'Encerrar Processo', + killNow: 'Encerrar Agora', + killHelper: 'Encerrar o processo {0} pode fazer com que alguns programas funcionem incorretamente. Continuar?', + processName: 'Nome do Processo', + }, + tool: { + supervisor: { + loadStatusErr: 'Falha ao recuperar o status do processo, verifique o status do serviço do supervisor.', + notSupport: + 'Serviço Supervisor não detectado. Vá para a página da biblioteca de scripts para instalar manualmente', + list: 'Processo daemon | Processos daemon', + config: 'Configuração do Supervisor', + primaryConfig: 'Localização do arquivo de configuração principal', + notSupportCtl: `O supervisorctl não foi detectado. Vá para a página da biblioteca de scripts para instalar manualmente.`, + user: 'Usuário', + command: 'Comando', + dir: 'Diretório', + numprocs: 'Nº de processos', + initWarn: + 'Isso irá modificar o valor "files" na seção "[include"] do arquivo de configuração principal. O diretório de outros arquivos de configuração será: "{diretório de instalação do 1Panel}/1panel/tools/supervisord/supervisor.d/".', + operatorHelper: 'A operação {1} será realizada em {0}, deseja continuar? ', + uptime: 'Tempo de execução', + notStartWarn: `O Supervisor não foi iniciado. Inicie-o primeiro.`, + serviceName: 'Nome do serviço', + initHelper: + 'O serviço Supervisor foi detectado, mas não está inicializado. Clique no botão de inicialização na barra de status superior para configurá-lo.', + serviceNameHelper: + 'Nome do serviço do Supervisor gerenciado pelo systemctl, geralmente supervisor ou supervisord', + restartHelper: + 'Isso reiniciará o serviço após a inicialização, o que fará com que todos os processos daemon existentes sejam interrompidos.', + RUNNING: 'Executando', + STOPPED: 'Parado', + STOPPING: 'Parando', + STARTING: 'Iniciando', + FATAL: 'Falha ao iniciar', + BACKOFF: 'Exceção ao iniciar', + ERROR: 'Erro', + statusCode: 'Código de status', + manage: 'Gerenciamento', + autoRestart: 'Reinicialização Automática', + EXITED: 'Saiu', + autoRestartHelper: 'Se o programa falhar, reiniciar automaticamente', + autoStart: 'Início Automático', + autoStartHelper: 'Se o serviço deve ser iniciado automaticamente após o Supervisor iniciar', + }, + }, + disk: { + management: 'Gerenciamento de Disco', + partition: 'Partição', + unmount: 'Desmontar', + unmountHelper: 'Deseja desmontar a partição {0}?', + mount: 'Montar', + partitionAlert: + 'O particionamento de disco requer formatação do disco, e os dados existentes serão excluídos. Salve ou tire snapshots dos dados com antecedência.', + mountPoint: 'Diretório de Montagem', + systemDisk: 'Disco do Sistema', + unpartitionedDisk: 'Disco Não Particionado', + handlePartition: 'Particionar Agora', + filesystem: 'Sistema de Arquivos', + unmounted: 'Desmontado', + cannotOperate: 'Não Pode Operar', + systemDiskHelper: 'Dica: O disco atual é o disco do sistema, não pode ser operado.', + autoMount: 'Montagem Automática', + model: 'Modelo do Dispositivo', + diskType: 'Tipo de Disco', + serial: 'Número de Série', + noFail: 'Falha na montagem não afeta a inicialização do sistema', + }, + xpack: { + expiresTrialAlert: + 'Lembrete: Sua versão de avaliação profissional expirará em {0} dias. Após isso, todas as funcionalidades da versão profissional não estarão mais disponíveis. Por favor, renove ou faça upgrade para a versão oficial a tempo.', + expiresAlert: + 'Lembrete: Sua licença profissional expirará em {0} dias. Após isso, todas as funcionalidades da versão profissional não estarão mais disponíveis. Por favor, renove sua licença para garantir o uso contínuo.', + menu: 'Pro', + upage: 'Construtor de Sites com IA', + proAlert: 'Atualize para Pro para usar este recurso', + app: { + app: 'APP', + title: 'Apelido do Painel', + titleHelper: 'O alias do painel é usado para exibição no APP (alias do painel padrão)', + qrCode: 'QR Code', + apiStatusHelper: 'O APP do Painel precisa habilitar a funcionalidade da interface API', + apiInterfaceHelper: + 'Suporta acesso à interface de API do painel (essa funcionalidade precisa ser ativada no aplicativo do painel)', + apiInterfaceHelper1: + 'O acesso ao aplicativo do painel requer que o visitante seja adicionado à lista de permissões; para IPs não fixos, recomenda-se adicionar 0.0.0.0/0(todos os IPv4), ::/0 (todos os IPv6)', + qrCodeExpired: 'Tempo de atualização', + apiLeakageHelper: 'Não revele o QR code. Garanta que ele seja usado apenas em ambientes confiáveis.', + }, + waf: { + name: 'WAF', + blackWhite: 'Lista negra e branca', + globalSetting: 'Configurações globais', + websiteSetting: 'Configurações do site', + blockRecords: 'Registros de bloqueio', + world: 'Mundo', + china: 'China', + intercept: 'Interceptar', + request: 'Requisição', + count4xx: 'Quantidade de 4xx', + count5xx: 'Quantidade de 5xx', + todayStatus: 'Status de hoje', + reqMap: 'Mapa de interceptação (30 dias)', + resource: 'Origem', + count: 'Quantidade', + hight: 'Alto', + low: 'Baixo', + reqCount: 'Requisições', + interceptCount: 'Número de interceptações', + requestTrends: 'Tendências de requisições (últimos 7 dias)', + interceptTrends: 'Tendências de interceptação (últimos 7 dias)', + whiteList: 'Lista branca', + blackList: 'Lista negra', + ipBlackListHelper: 'Endereços IP na lista negra estão bloqueados de acessar o site', + ipWhiteListHelper: 'Endereços IP na lista branca contornam todas as restrições', + uaBlackListHelper: 'Requisições com valores de User-Agent na lista negra serão bloqueadas', + uaWhiteListHelper: 'Requisições com valores de User-Agent na lista branca contornam todas as restrições', + urlBlackListHelper: 'Requisições para URLs na lista negra serão bloqueadas', + urlWhiteListHelper: 'Requisições para URLs na lista branca contornam todas as restrições', + ccHelper: + 'Se um site receber mais de {1} requisições do mesmo IP dentro de {0} segundos, o IP será bloqueado por {2}', + blockTime: 'Duração do bloqueio', + attackHelper: + 'Se as interceptações acumuladas excederem {1} dentro de {0} segundos, o IP será bloqueado por {2}', + notFoundHelper: + 'Se requisições acumuladas retornarem erros 404 mais de {1} vezes dentro de {0} segundos, o IP será bloqueado por {2}', + frequencyLimit: 'Limite de frequência', + regionLimit: 'Limite de região', + defaultRule: 'Regras padrão', + accessFrequencyLimit: 'Limite de frequência de acesso', + attackLimit: 'Limite de frequência de ataque', + notFoundLimit: 'Limite de frequência de 404', + urlLimit: 'Limite de frequência de URL', + urlLimitHelper: 'Defina a frequência de acesso para uma única URL', + sqliDefense: 'Proteção contra injeção SQL', + sqliHelper: 'Detectar injeção SQL em requisições e bloqueá-las', + xssHelper: 'Detectar XSS em requisições e bloqueá-las', + xssDefense: 'Proteção contra XSS', + uaDefense: 'Regras de User-Agent maliciosos', + uaHelper: 'Inclui regras para identificar bots maliciosos comuns', + argsDefense: 'Regras de parâmetros maliciosos', + argsHelper: 'Bloqueia requisições que contenham parâmetros maliciosos', + cookieDefense: 'Regras de cookies maliciosos', + cookieHelper: 'Proíbe cookies maliciosos de serem carregados nas requisições', + headerDefense: 'Regras de cabeçalhos maliciosos', + headerHelper: 'Proíbe requisições que contenham cabeçalhos maliciosos', + httpRule: 'Regras de método de requisição HTTP', + httpHelper: + 'Defina os tipos de métodos permitidos para acessar. Se você deseja restringir certos tipos de acesso, desative esse tipo de botão. Por exemplo: se apenas o acesso do tipo GET for permitido, desative todos os outros botões, exceto o GET', + geoRule: 'Restrições de acesso regional', + geoHelper: + 'Restringe o acesso ao seu site a partir de certas regiões, por exemplo: se o acesso for permitido a partir da China continental, então requisições de fora da China continental serão bloqueadas', + ipLocation: 'Localização do IP', + action: 'Ação', + ruleType: 'Tipo de ataque', + ipHelper: 'Digite o endereço IP', + attackLog: 'Log de ataques', + rule: 'Regra', + ipArr: 'Intervalo IPV4', + ipStart: 'IP de início', + ipEnd: 'IP de fim', + ipv4: 'IPv4', + ipv6: 'IPv6', + urlDefense: 'Regras de URL', + urlHelper: 'URL proibida', + dirFilter: 'Filtro de diretório', + sqlInject: 'Injeção de SQL', + xss: 'XSS', + phpExec: 'Execução de script PHP', + oneWordTrojan: 'Cavalo de Troia de uma palavra', + appFilter: 'Filtragem de diretórios perigosos', + webshell: 'Webshell', + args: 'Parâmetros maliciosos', + protocolFilter: 'Filtro de protocolo', + javaFilter: 'Filtragem de arquivos Java perigosos', + scannerFilter: 'Filtro de scanner', + escapeFilter: 'Filtro de escape', + customRule: 'Regras personalizadas', + httpMethod: 'Filtro de método HTTP', + fileExt: 'Limite de extensão de arquivo', + fileExtHelper: 'Extensões de arquivos proibidas para upload', + deny: 'Proibido', + allow: 'Permitir', + field: 'Objeto', + pattern: 'Condição', + ruleContent: 'Conteúdo', + contain: 'incluir', + equal: 'igual', + regex: 'expressão regular', + notEqual: 'Diferente de', + customRuleHelper: 'Realize ações com base nas condições especificadas', + actionAllow: 'Permitir', + blockIP: 'Bloquear IP', + code: 'Código de status retornado', + noRes: 'Desconectar (444)', + badReq: 'Parâmetros inválidos (400)', + forbidden: 'Acesso proibido (403)', + serverErr: 'Erro no servidor (500)', + resHtml: 'Página de resposta', + allowHelper: 'Permitir acesso pulará as regras subsequentes do WAF, use com cautela', + captcha: 'verificação humano-máquina', + fiveSeconds: 'Verificação de 5 segundos', + location: 'Região', + redisConfig: 'Configuração do Redis', + redisHelper: 'Ativar o Redis para persistir IPs temporariamente bloqueados', + wafHelper: 'Todos os sites perderão proteção após o fechamento', + attackIP: 'IP atacante', + attackParam: 'Detalhes do ataque', + execRule: 'Regra atingida', + acl: 'ACL', + sql: 'Injeção de SQL', + cc: 'Limite de frequência de acesso', + isBlocking: 'Bloqueado', + isFree: 'Desbloqueado', + unLock: 'Desbloquear', + unLockHelper: 'Você quer desbloquear o IP: {0}?', + saveDefault: 'Salvar padrão', + saveToWebsite: 'Aplicar ao site', + saveToWebsiteHelper: 'Aplicar as configurações atuais a todos os sites?', + websiteHelper: + 'Aqui estão as configurações padrão para criar um site. As modificações precisam ser aplicadas ao site para terem efeito', + websiteHelper2: + 'Aqui estão as configurações padrão para criar um site. Por favor, modifique a configuração específica no site', + ipGroup: 'Grupo de IP', + ipGroupHelper: + 'Um IP ou segmento de IP por linha, suporta IPv4 e IPv6, por exemplo: 192.168.1.1 ou 192.168.1.0/24', + ipBlack: 'Lista negra de IP', + openRestyAlert: 'A versão do OpenResty precisa ser superior a {0}', + initAlert: + 'A inicialização é necessária para o primeiro uso, o arquivo de configuração do site será modificado e a configuração original do WAF será perdida. Por favor, faça backup do OpenResty com antecedência', + initHelper: + 'A operação de inicialização apagará a configuração WAF existente. Tem certeza de que deseja inicializar?', + mainSwitch: 'Interruptor principal', + websiteAlert: 'Por favor, crie um site primeiro', + defaultUrlBlack: 'Regras de URL', + htmlRes: 'Página de interceptação', + urlSearchHelper: 'Por favor, insira a URL para suportar a pesquisa difusa', + toCreate: 'Criar', + closeWaf: 'Fechar WAF', + closeWafHelper: 'Fechar o WAF fará com que o site perca a proteção, você deseja continuar?', + addblack: 'Negro', + addwhite: 'Adicionar branco', + addblackHelper: 'Adicionar IP:{0} à lista negra padrão?', + addwhiteHelper: 'Adicionar IP:{0} à lista branca padrão?', + defaultUaBlack: 'Regra do User-Agent', + defaultIpBlack: 'Grupo de IPs Maliciosos', + cookie: 'Regras de Cookies', + urlBlack: 'Lista negra de URLs', + uaBlack: 'Lista negra de User-Agent', + attackCount: 'Limite de frequência de ataques', + fileExtCheck: 'Limite de upload de arquivos', + geoRestrict: 'Restrição de acesso regional', + attacklog: 'Registro de Interceptação', + unknownWebsite: 'Acesso a nome de domínio não autorizado', + geoRuleEmpty: 'A região não pode estar vazia', + unknown: 'Site não existe', + geo: 'Restrição Regional', + revertHtml: 'Você deseja restaurar {0} como a página padrão?', + five_seconds: 'Verificação de 5 segundos', + header: 'Regras de cabeçalho', + methodWhite: 'Regras HTTP', + expiryDate: 'Data de expiração', + expiryDateHelper: 'Após passar na verificação, não será mais verificado dentro do período de validade', + defaultIpBlackHelper: 'Alguns IPs maliciosos coletados da Internet para impedir o acesso', + notFoundCount: 'Limite de Frequência 404', + matchValue: 'Valor de correspondência', + headerName: 'Suporta inglês, números, -, comprimento de 3-30', + cdnHelper: 'Sites que usam CDN podem abrir aqui para obter o IP de origem correto', + clearLogWarn: 'Limpar o log não será possível, você deseja continuar?', + commonRuleHelper: 'A regra é correspondência difusa', + blockIPHelper: + 'IPs bloqueados são armazenados temporariamente no OpenResty e serão desbloqueados quando você reiniciar o OpenResty. Eles podem ser bloqueados permanentemente através da função de bloqueio', + addWhiteUrlHelper: 'Adicionar URL {0} à lista branca?', + dashHelper: + 'A versão comunitária também pode usar as funções nas configurações globais e configurações de site', + wafStatusHelper: 'O WAF não está ativado, por favor, ative-o nas configurações globais', + ccMode: 'Modo', + global: 'Modo Global', + uriMode: 'Modo URL', + globalHelper: + 'Modo Global: Ativado quando o número total de solicitações para qualquer URL dentro de uma unidade de tempo excede o limite', + uriModeHelper: + 'Modo URL: Ativado quando o número de solicitações para uma única URL dentro de uma unidade de tempo excede o limite', + + ip: 'Lista negra de IPs', + globalSettingHelper: + 'Configurações com a tag [Website] precisam ser ativadas em [Configurações do Website], e as configurações globais são apenas as configurações padrão para sites recém-criados', + globalSettingHelper2: + 'As configurações precisam ser ativadas tanto em [Configurações Globais] quanto em [Configurações do Website] ao mesmo tempo', + urlCCHelper: 'Mais de {1} solicitações para este URL dentro de {0} segundos, bloqueando este IP {2}', + urlCCHelper2: 'O URL não pode conter parâmetros', + notContain: 'Não contém', + urlcc: 'Limitação de frequência de URL', + method: 'Tipo de solicitação', + addIpsToBlock: 'Bloquear IPs em massa', + addUrlsToWhite: 'Adicionar URLs à lista branca em massa', + noBlackIp: 'O IP já está bloqueado, não é necessário bloquear novamente', + noWhiteUrl: 'O URL já está na lista branca, não é necessário adicionar novamente', + spiderIpHelper: + 'Inclui Baidu, Bing, Google, 360, Shenma, Sogou, ByteDance, DuckDuckGo, Yandex. Fechar isso bloqueará todos os acessos de spiders.', + spiderIp: 'Pool de IPs de spiders', + geoIp: 'Biblioteca de endereços IP', + geoIpHelper: 'Usado para determinar a localização geográfica do IP', + stat: 'Relatório de ataques', + statTitle: 'Relatório', + attackIp: 'IP de ataque', + attackCountNum: 'Número de ataques', + percent: 'Porcentagem', + addblackUrlHelper: 'Adicionar URL: {0} à lista negra padrão?', + rce: 'Execução remota de código', + software: 'Software', + cveHelper: 'Contém vulnerabilidades comuns de software e frameworks', + vulnCheck: 'Regras complementares', + ssrf: 'Vulnerabilidade SSRF', + afr: 'Leitura arbitrária de arquivos', + ua: 'Acesso não autorizado', + id: 'Dicas de informações', + aa: 'Desvio de autenticação', + dr: 'Trasversal de diretórios', + xxe: 'Vulnerabilidade XXE', + suid: 'Vulnerabilidade de serialização', + dos: 'Vulnerabilidade de negação de serviço', + afd: 'Download arbitrário de arquivos', + sqlInjection: 'Injeção de SQL', + afw: 'Escrita arbitrária de arquivos', + il: 'Dicas de informações', + clearAllLog: 'Limpar todos os logs', + exportLog: 'Exportar logs', + appRule: 'Regras de aplicação', + appRuleHelper: + 'Regras comuns de aplicações, habilitar pode reduzir falsos positivos, um site pode usar apenas uma regra', + logExternal: 'Excluir tipos de registro', + ipWhite: 'Lista branca de IPs', + urlWhite: 'Lista branca de URLs', + uaWhite: 'Lista branca de User-Agent', + logExternalHelper: + 'Tipos de registro excluídos não serão registrados nos logs, listas negras/brancas, restrições de acesso de região e regras personalizadas geram muitos logs, recomenda-se excluir', + ssti: 'Ataque SSTI', + crlf: 'Injeção CRLF', + strict: 'Modo estrito', + strictHelper: 'Usa regras mais rigorosas para validar solicitações', + saveLog: 'Salvar log', + remoteURLHelper: 'O URL remoto precisa garantir um IP por linha e nenhum outro caractere', + notFound: 'Not Found (404)', + serviceUnavailable: 'Serviço Indisponível (503)', + gatewayTimeout: 'Tempo Limite da Porta de Entrada (504)', + belongToIpGroup: 'Pertence ao Grupo de IP', + notBelongToIpGroup: 'Não pertence ao Grupo de IP', + unknownWebsiteKey: 'Domínio Desconhecido', + special: 'Domínio Especial', + fileToLarge: 'O arquivo excede 1MB e não pode ser enviado', + uploadOverLimit: 'O arquivo enviado excede o limite de quantidade, máximo 1 arquivo', + importRuleHelper: 'Uma regra por linha', + }, + monitor: { + name: 'Monitoramento de Websites', + pv: 'Visualizações de Página', + uv: 'Visitantes Únicos', + flow: 'Fluxo de Tráfego', + ip: 'IP', + spider: 'Spider', + visitors: 'Tendências de Visitantes', + today: 'Hoje', + last7days: 'Últimos 7 Dias', + last30days: 'Últimos 30 Dias', + uvMap: 'Mapa de Visitantes (30 dias)', + qps: 'Requisições em Tempo Real (por minuto)', + flowSec: 'Tráfego em Tempo Real (por minuto)', + excludeCode: 'Excluir Códigos de Status', + excludeUrl: 'Excluir URLs', + excludeExt: 'Excluir Extensões', + cdnHelper: 'Obter o IP real do Header fornecido pela CDN', + reqRank: 'Ranking de Visitas', + refererDomain: 'Domínio de Referência', + os: 'Sistema', + browser: 'Navegador/Cliente', + device: 'Dispositivo', + showMore: 'Mais', + unknown: 'Outro', + pc: 'Computador', + mobile: 'Dispositivo Móvel', + wechat: 'WeChat', + machine: 'Máquina', + tencent: 'Navegador Tencent', + ucweb: 'Navegador UC', + '2345explorer': 'Navegador 2345', + huaweibrowser: 'Navegador Huawei', + log: 'Logs de Requisição', + statusCode: 'Código de Status', + requestTime: 'Tempo de Resposta', + flowRes: 'Tráfego de Resposta', + method: 'Método de Requisição', + statusCodeHelper: 'Insira o código de status acima', + statusCodeError: 'Tipo de código de status inválido', + methodHelper: 'Insira o método de requisição acima', + all: 'Todos', + baidu: 'Baidu', + google: 'Google', + bing: 'Bing', + bytes: 'Manchetes do Dia', + sogou: 'Sogou', + failed: 'Erro', + ipCount: 'Contagem de IPs', + spiderCount: 'Requisições de Spider', + averageReqTime: 'Tempo Médio de Resposta', + totalFlow: 'Fluxo Total', + logSize: 'Tamanho do Arquivo de Log', + realIPType: 'Método de Obtenção do IP Real', + fromHeader: 'Obter do Header HTTP', + fromHeaders: 'Obter da lista de Headers', + header: 'Header HTTP', + cdnConfig: 'Configuração da CDN', + xff1: 'Proxy de Primeiro Nível de X-Forwarded-For', + xff2: 'Proxy de Segundo Nível de X-Forwarded-For', + xff3: 'Proxy de Terceiro Nível de X-Forwarded-For', + xffHelper: + 'Por exemplo: X-Forwarded-For: ,,,. O proxy superior pegará o último IP ', + headersHelper: + 'Obter o IP real dos Headers HTTP comuns fornecidos pela CDN, selecionando o primeiro valor disponível', + monitorCDNHelper: + 'Modificar a configuração da CDN para monitoramento do site também atualizará as configurações de CDN do WAF', + wafCDNHelper: + 'Modificar a configuração de CDN do WAF também atualizará as configurações de CDN do monitoramento do site', + statusErr: 'Formato de código de status inválido', + shenma: 'Busca Shenma', + duckduckgo: 'DuckDuckGo', + '360': 'Busca 360', + excludeUri: 'Excluir URIs', + top100Helper: 'Mostrar os 100 principais dados', + logSaveDay: 'Período de Retenção de Logs (dias)', + cros: 'Chrome OS', + theworld: 'Navegador TheWorld', + edge: 'Microsoft Edge', + maxthon: 'Navegador Maxthon', + monitorStatusHelper: 'O monitoramento não está habilitado, por favor habilite-o nas configurações', + excludeIp: 'Excluir Endereços IP', + excludeUa: 'Excluir User-Agent', + remotePort: 'Porta Remota', + unknown_browser: 'Desconhecido', + unknown_os: 'Desconhecido', + unknown_device: 'Desconhecido', + logSaveSize: 'Tamanho Máximo de Salvamento de Log', + logSaveSizeHelper: 'Este é o tamanho de salvamento de log para um único site', + '360se': 'Navegador de Segurança 360', + websites: 'Lista de Sites', + trend: 'Estatísticas de Tendência', + reqCount: 'Contagem de Solicitações', + uriHelper: 'Você pode usar /test/* ou /*/index.php para excluir Uri', + }, + tamper: { + tamper: 'Proteção contra adulteração do site', + ignoreTemplate: 'Modelo de Exclusão', + protectTemplate: 'Modelo de Proteção', + ignoreTemplateHelper: + 'Digite o conteúdo de exclusão, separado por Enter ou espaço. (Diretório específico ./log ou nome do diretório tmp, para excluir arquivos precisa inserir arquivo específico ./data/test.html)', + protectTemplateHelper: + 'Digite o conteúdo de proteção, separado por Enter ou espaço. (Arquivo específico ./index.html, extensão de arquivo .html, tipo de arquivo js, para proteger diretórios precisa inserir diretório específico ./log)', + templateContent: 'Conteúdo do Modelo', + template: 'Modelo', + saveTemplate: 'Salvar como Modelo', + tamperHelper1: + 'Para sites de implantação com um clique, é recomendável ativar a proteção contra violação de diretório de aplicativos; se o site não puder ser usado normalmente ou o backup/restauração falhar, desative primeiro a proteção contra violação;', + tamperHelper2: + 'Restringirá operações de leitura/gravação, exclusão, permissão e modificação de proprietário para arquivos protegidos em diretórios não excluídos', + tamperPath: 'Diretório de Proteção', + tamperPathEdit: 'Modificar Caminho', + log: 'Log de Bloqueio', + totalProtect: 'Proteção Total', + todayProtect: 'Proteção de Hoje', + templateRule: 'Comprimento 1-512, o nome não pode conter {0} e outros símbolos', + ignore: 'Excluir', + ignoreHelper: + 'Selecione ou digite o conteúdo de exclusão, separado por Enter ou espaço. (Diretório específico ./log ou nome do diretório tmp, para excluir arquivos precisa inserir ou selecionar arquivo específico ./data/test.html)', + protect: 'Proteger', + protectHelper: + 'Selecione ou digite o conteúdo de proteção, separado por Enter ou espaço. (Arquivo específico ./index.html, extensão de arquivo .html, tipo de arquivo js, para proteger diretórios precisa inserir ou selecionar diretório específico ./log)', + tamperHelper00: 'Exclusão e proteção suportam apenas caminhos relativos;', + tamperHelper01: + 'Após ativar a proteção contra violação, o sistema restringirá as operações de criação, edição e exclusão de arquivos protegidos em diretórios não excluídos;', + tamperHelper02: + 'Prioridade: Proteção de caminho específico > Exclusão de caminho específico > Proteção > Exclusão', + tamperHelper03: + 'As operações de monitoramento visam apenas diretórios não excluídos, monitorando a criação de arquivos não protegidos nesses diretórios.', + disableHelper: 'Prestes a desativar a proteção contra violação para os seguintes sites, continuar?', + appendOnly: 'Somente Acréscimo', + appendOnlyHelper: + 'Restringe operações de exclusão de arquivos neste diretório, permitindo apenas adição de diretórios excluídos ou arquivos não protegidos', + immutable: 'Imutável', + immutableHelper: + 'Restringe operações de edição, exclusão, permissão e modificação de proprietário para este arquivo', + onWatch: 'Monitorar', + onWatchHelper: + 'Monitora e intercepta a criação de arquivos protegidos ou diretórios não excluídos neste diretório', + forceStop: 'Forçar Fechamento', + forceStopHelper: 'Desativará forçadamente a função anti-violência para este diretório do site. Continuar?', + }, + setting: { + setting: 'Configurações do Painel', + title: 'Descrição do Painel', + titleHelper: + 'Será exibido na página de login do usuário (ex.: Painel de gerenciamento de manutenção de servidores Linux, recomendado 8-15 caracteres)', + logo: 'Logo (Sem Texto)', + logoHelper: + 'Será exibido no canto superior esquerdo da página de gerenciamento quando o menu estiver recolhido (tamanho recomendado da imagem: 82px*82px)', + logoWithText: 'Logo (Com Texto)', + logoWithTextHelper: + 'Será exibido no canto superior esquerdo da página de gerenciamento quando o menu estiver expandido (tamanho recomendado da imagem: 185px*55px)', + favicon: 'Ícone do Site', + faviconHelper: 'Ícone do site (tamanho recomendado da imagem: 16px*16px)', + setDefault: 'Restaurar Padrão', + setHelper: 'As configurações atuais serão salvas. Deseja continuar?', + setDefaultHelper: 'Todas as configurações do painel serão restauradas para o padrão. Deseja continuar?', + logoGroup: 'Logo', + imageGroup: 'Imagem', + loginImage: 'Imagem da Página de Login', + loginImageHelper: 'Será exibida na página de login (Tamanho recomendado: 500x416px)', + loginBgType: 'Tipo de Fundo da Página de Login', + loginBgImage: 'Imagem de Fundo da Página de Login', + loginBgImageHelper: + 'Será exibida como imagem de fundo na página de login (Tamanho recomendado: 1920x1080px)', + loginBgColor: 'Cor de Fundo da Página de Login', + loginBgColorHelper: 'Será exibida como cor de fundo na página de login', + image: 'Imagem', + bgColor: 'Cor de Fundo', + loginGroup: 'Página de login', + loginBtnLinkColor: 'Cor do botão/link', + loginBtnLinkColorHelper: 'Será exibido como a cor do botão/link na página de login', + }, + helper: { + wafTitle1: 'Mapa de Interceptação', + wafContent1: 'Exibe a distribuição geográfica das interceptações nos últimos 30 dias', + wafTitle2: 'Restrições de Acesso Regional', + wafContent2: 'Restringe o acesso ao site com base em localizações geográficas', + wafTitle3: 'Página de Interceptação Personalizada', + wafContent3: 'Crie uma página personalizada para exibir após uma solicitação ser interceptada', + wafTitle4: 'Regras Personalizadas (ACL)', + wafContent4: 'Intercepta solicitações com base em regras personalizadas', + + tamperTitle1: 'Monitoramento de Integridade de Arquivos', + tamperContent1: + 'Monitora a integridade dos arquivos do site, incluindo arquivos principais, scripts e arquivos de configuração.', + tamperTitle2: 'Varredura e Detecção em Tempo Real', + tamperContent2: + 'Detecta arquivos anormais ou alterados por meio de varredura em tempo real do sistema de arquivos do site.', + tamperTitle3: 'Configurações de Permissão de Segurança', + tamperContent3: + 'Restringe o acesso aos arquivos do site através de configurações adequadas de permissões e políticas de controle de acesso, reduzindo a superfície de ataque.', + tamperTitle4: 'Registro e Análise', + tamperContent4: + 'Registra logs de acesso e operações em arquivos para auditoria e análise posteriores, ajudando administradores a identificar ameaças de segurança potenciais.', + + settingTitle1: 'Mensagem de Boas-vindas Personalizada', + settingContent1: 'Defina uma mensagem de boas-vindas personalizada na página de login do 1Panel.', + settingTitle2: 'Logo Personalizado', + settingContent2: 'Permite o upload de imagens de logotipo contendo nomes de marcas ou outros textos.', + settingTitle3: 'Ícone Personalizado do Site', + settingContent3: + 'Permite o upload de ícones personalizados para substituir o ícone padrão do navegador, melhorando a experiência do usuário.', + + monitorTitle1: 'Tendência de Visitantes', + monitorContent1: 'Estatísticas e exibição das tendências de visitantes do site', + monitorTitle2: 'Mapa de Visitantes', + monitorContent2: 'Estatísticas e exibição da distribuição geográfica dos visitantes do site', + monitorTitle3: 'Estatísticas de Acesso', + monitorContent3: + 'Estatísticas sobre informações de solicitação do site, incluindo robôs de busca, dispositivos de acesso, status das solicitações, etc.', + monitorTitle4: 'Monitoramento em Tempo Real', + monitorContent4: + 'Monitoramento em tempo real de informações de solicitações do site, incluindo número de solicitações, tráfego, etc.', + + alertTitle1: 'Alertas por SMS', + alertContent1: + 'Quando houver uso anormal de recursos do servidor, expiração de sites e certificados, nova versão disponível, expiração de senha, entre outros, os usuários serão notificados via SMS para garantir um processamento oportuno.', + alertTitle2: 'Registro de Alertas', + alertContent2: + 'Oferece aos usuários a função de visualizar logs de alertas, facilitando o rastreamento e a análise de eventos históricos de alerta.', + alertTitle3: 'Configurações de Alertas', + alertContent3: + 'Permite que os usuários configurem números de telefone personalizados, frequência de envio diário e horários de envio, facilitando a criação de alertas mais adequados.', + + nodeDashTitle1: 'Gerenciamento de Aplicativos', + nodeDashContent1: + 'Gerenciamento unificado de aplicativos multi-nó, suporta monitoramento de status, início/parada rápida, conexão de terminal e backup', + nodeDashTitle2: 'Gerenciamento de Sites', + nodeDashContent2: + 'Gerenciamento unificado de sites multi-nó, monitoramento de status em tempo real, suporta início/parada em lote e backup rápido', + nodeDashTitle3: 'Gerenciamento de Bancos de Dados', + nodeDashContent3: + 'Gerenciamento unificado de bancos de dados multi-nó, status chave de relance, suporta backup com um clique', + nodeDashTitle4: 'Gerenciamento de Tarefas Agendadas', + nodeDashContent4: + 'Gerenciamento unificado de tarefas agendadas multi-nó, suporta monitoramento de status, início/parada rápida e execução manual por acionamento', + + nodeTitle1: 'Adição de Nó com Um Clique', + nodeContent1: 'Integre rapidamente vários nós de servidor', + nodeTitle2: 'Atualização em Lote', + nodeContent2: 'Sincronize e atualize todos os nós com uma única operação', + nodeTitle3: 'Monitoramento de Status do Nó', + nodeContent3: 'Acompanhe em tempo real o status operacional de cada nó', + nodeTitle4: 'Conexão Remota Rápida', + nodeContent4: 'Conecte-se diretamente a terminais remotos de nós com um clique', + + fileExchangeTitle1: 'Transmissão de Autenticação por Chave', + fileExchangeContent1: 'Autentique via chaves SSH para garantir a segurança da transmissão.', + fileExchangeTitle2: 'Sincronização de Arquivos Eficiente', + fileExchangeContent2: + 'Sincronize apenas o conteúdo alterado para melhorar significativamente a velocidade e estabilidade da transmissão.', + fileExchangeTitle3: 'Suporte a Troca Entre Múltiplos Nós', + fileExchangeContent3: + 'Transfira facilmente arquivos de projeto entre diferentes nós, gerencie vários servidores de forma flexível.', + + nodeAppTitle1: 'Gerenciamento de Atualização de Aplicativos', + nodeAppContent1: + 'Monitoramento unificado de atualizações de aplicativos multi-nó, suporta atualização com um clique', + + appTitle1: 'Gerenciamento Flexível do Painel', + appContent1: 'Gerencie facilmente seu servidor 1Panel a qualquer hora e em qualquer lugar.', + appTitle2: 'Informações Completas do Serviço', + appContent2: + 'Gerencie aplicativos, sites, Docker, bancos de dados, etc. pelo app móvel e crie rapidamente aplicativos e sites.', + appTitle3: 'Monitoramento Anômalo em Tempo Real', + appContent3: + 'Veja o status do servidor, monitoramento de segurança WAF, estatísticas de visitas ao site e saúde dos processos em tempo real no aplicativo móvel.', + + clusterTitle1: 'Implantação Mestre-Escravo', + clusterContent1: + 'Suporta a criação de instâncias mestre-escravo MySQL/Postgres/Redis em diferentes nós, completando automaticamente a associação e inicialização mestre-escravo', + clusterTitle2: 'Gestão Mestre-Escravo', + clusterContent2: + 'Página unificada para gerir centralmente vários nós mestre-escravo, visualizar seus papéis, status de execução, etc.', + clusterTitle3: 'Estado de Replicação', + clusterContent3: + 'Exibe o estado de replicação mestre-escravo e informações de atraso, auxiliando na resolução de problemas de sincronização', + }, + node: { + master: 'Nó Principal', + masterBackup: 'Backup do Nó Mestre', + backupNode: 'Nó de Backup', + backupFrequency: 'Frequência de Backup (horas)', + backupCopies: 'Número de cópias de backup a reter', + noBackupNode: + 'O nó de backup está vazio atualmente. Selecione um nó de backup para salvar e tente novamente!', + masterBackupAlert: + 'O backup do nó mestre não está configurado atualmente. Para garantir a segurança dos dados, configure um nó de backup o mais rápido possível para facilitar a troca manual para um novo nó mestre em caso de falha.', + node: 'Nó', + addr: 'Endereço', + nodeUpgrade: 'Configurações de Atualização', + nodeUpgradeHelper: + 'Os nós selecionados começarão a atualizar automaticamente após a conclusão da atualização do nó mestre, nenhuma operação manual necessária.', + nodeUnhealthy: 'Estado do nó anormal', + deletedNode: 'O nó excluído {0} não suporta atualmente operações de atualização!', + nodeUnhealthyHelper: + 'Estado anormal do nó detectado. Por favor verifique em [Gestão de Nós] e tente novamente!', + nodeUnbind: 'Nó não vinculado à licença', + nodeUnbindHelper: + 'Detectamos que este nó não está vinculado a uma licença. Por favor vincule no menu [Configurações do Painel - Licença] e tente novamente!', + memTotal: 'Memória Total', + nodeManagement: 'Gerenciamento Multi-Máquina', + nodeItem: 'Gerenciamento de Nós', + panelItem: 'Gerenciamento de Painéis', + addPanel: 'Adicionar Painel', + addPanelHelper: + 'Após adicionar o painel com sucesso, você pode acessar rapidamente o painel de destino em [Visão Geral - Painéis].', + panel: 'Painel 1Panel', + others: 'Outros Painéis', + addNode: 'Adicionar Nó', + connInfo: 'Informações de Conexão', + nodeInfo: 'Informações do Nó', + withProxy: 'Включить Доступ через Прокси', + withoutProxy: 'Отключить Доступ через Прокси', + withProxyHelper: + 'Будет использовать системный прокси {0}, поддерживаемый в настройках панели, для доступа к дочерним узлам. Продолжить?', + withoutProxyHelper: + 'Прекратит использование системного прокси, поддерживаемого в настройках панели, для доступа к дочерним узлам. Продолжить?', + syncInfo: 'Sincronização de dados,', + syncHelper: 'Quando os dados do nó mestre mudam, são sincronizados em tempo real para este nó filho,', + syncBackupAccount: 'Configurações de conta de backup', + syncWithMaster: + 'Após atualizar para Pro, todos os dados serão sincronizados por padrão. As políticas de sincronização podem ser ajustadas manualmente no gerenciamento de nós.', + syncProxy: 'Configurações de proxy do sistema', + syncProxyHelper: 'Sincronizar configurações de proxy do sistema requer reinicialização do Docker', + syncProxyHelper1: 'Reiniciar o Docker pode afetar os serviços de contêiner em execução.', + syncProxyHelper2: 'Você pode reiniciar manualmente na página Contêineres - Configuração.', + syncProxyHelper3: + 'Sincronizar configurações de proxy do sistema requer reinicialização do Docker, o que pode afetar os serviços de contêiner em execução', + syncProxyHelper4: + 'Sincronizar configurações de proxy do sistema requer reinicialização do Docker. Você pode reiniciar manualmente mais tarde na página Contêineres - Configuração.', + syncCustomApp: 'Sincronizar Repositório de Aplicativos Personalizados', + syncAlertSetting: 'Configurações de alerta do sistema', + syncNodeInfo: 'Dados básicos do nó,', + nodeSyncHelper: 'A sincronização das informações do nó irá sincronizar as seguintes informações:', + nodeSyncHelper1: '1. Informações da conta de backup pública', + nodeSyncHelper2: '2. Informações de conexão entre o nó principal e os sub-nós', + + nodeCheck: 'Verificação de disponibilidade', + checkSSH: 'Verificar conexão SSH do nó', + checkUserPermission: 'Verificar permissões de usuário do nó', + isNotRoot: 'Detectado que sudo sem senha não é suportado neste nó e o usuário atual não é root', + checkLicense: 'Verificar status da licença do nó', + checkService: 'Verificar informações de serviço existentes no nó', + checkPort: 'Verificar acessibilidade da porta do nó', + panelExist: + 'Detectado que este nó está executando o serviço 1Panel V1. Atualize para V2 usando o script de migração antes de adicionar.', + coreExist: + 'O nó atual já está habilitado como nó mestre e não pode ser adicionado diretamente como nó escravo. Por favor, faça o downgrade para nó escravo primeiro antes de adicionar, consulte a documentação para detalhes.', + agentExist: + 'Detectado que 1panel-agent já está instalado neste nó. Continuar irá reter os dados existentes e apenas substituir o serviço 1panel-agent.', + agentNotExist: + 'Foi detectado que o 1panel-agent não está instalado neste nó, portanto, as informações do nó não podem ser editadas diretamente. Por favor, exclua e adicione novamente.', + oldDataExist: + 'Detectados dados históricos do 1Panel V2 neste nó. As seguintes informações serão usadas para sobrescrever as configurações atuais:', + errLicense: 'A licença vinculada a este nó está indisponível. Por favor verifique e tente novamente!', + errNodePort: + 'A porta do nó [ {0} ] foi detectada como inacessível. Verifique se o firewall ou grupo de segurança liberou esta porta.', + + reinstallHelper: 'Reinstalar o nó {0}, deseja continuar?', + unhealthyCheck: 'Verificação Anormal', + fixOperation: 'Operação de Correção', + checkName: 'Item de Verificação', + checkSSHConn: 'Verificar Disponibilidade da Conexão SSH', + fixSSHConn: 'Edite o nó manualmente para confirmar as informações de conexão', + checkConnInfo: 'Verificar Informações de Conexão do Agente', + checkStatus: 'Verificar Disponibilidade do Serviço do Nó', + fixStatus: 'Execute "systemctl status 1panel-agent.service" para verificar se o serviço está em execução.', + checkAPI: 'Verificar Disponibilidade da API do Nó', + fixAPI: 'Verifique os logs do nó e confirme se as portas do firewall estão devidamente abertas.', + forceDelete: 'Excluir Forçadamente', + operateHelper: 'A operação {0} será realizada nos seguintes nós, deseja continuar?', + operatePanelHelper: 'A operação {0} será executada nos painéis a seguir. Continuar?', + forceDeleteHelper: + 'Excluir forçadamente ignorará erros de exclusão do nó e removerá os metadados do banco de dados', + uninstall: 'Excluir dados do nó', + uninstallHelper: 'Isso excluirá todos os dados relacionados ao 1Panel do nó. Escolha com cuidado!', + baseDir: 'Diretório de Instalação', + baseDirHelper: 'Quando o diretório de instalação está vazio, será instalado por padrão no diretório /opt', + nodePort: 'Porta do Nó', + offline: 'Modo offline', + freeCount: 'Cota gratuita [{0}]', + offlineHelper: 'Usado quando o nó está em ambiente offline', + + appUpgrade: 'Atualização do Aplicativo', + appUpgradeHelper: 'Existem {0} aplicativos que precisam ser atualizados', + }, + customApp: { + name: 'Repositório de Aplicativos Personalizados', + appStoreType: 'Fonte do Pacote da App Store', + appStoreUrl: 'URL do Repositório', + local: 'Caminho Local', + remote: 'Link Remoto', + imagePrefix: 'Prefixo de Imagem', + imagePrefixHelper: + 'Função: Personalize o prefixo da imagem e modifique o campo de imagem no arquivo compose. Por exemplo, quando o prefixo da imagem é configurado como 1panel/custom, o campo de imagem do MaxKB mudará para 1panel/custom/maxkb:v1.10.0', + closeHelper: 'Cancelar o uso do repositório de aplicativos personalizados', + appStoreUrlHelper: 'Apenas formato .tar.gz é suportado', + postNode: 'Sincronizar para subnó', + postNodeHelper: + 'Sincronize o pacote da loja personalizada para tmp/customApp/apps.tar.gz no diretório de instalação do sub-nó', + nodes: 'Selecionar Nós', + selectNode: 'Selecionar Node', + selectNodeError: 'Por favor, selecione um nó', + licenseHelper: 'A versão Pro suporta o recurso de repositório de aplicativos personalizados', + databaseHelper: 'Banco de dados associado ao aplicativo, selecione o banco de dados do nó de destino', + nodeHelper: 'Não é possível selecionar o nó atual', + migrateHelper: + 'Atualmente suporta apenas a migração de aplicações monolíticas e aplicações associadas apenas a bancos de dados MySQL, MariaDB, PostgreSQL', + opensslHelper: + 'Se usar backup criptografado, as versões do OpenSSL entre os dois nós devem ser consistentes, caso contrário a migração pode falhar.', + installApp: 'Instalação em lote', + installAppHelper: 'Instalar aplicativos em lote nos nós selecionados', + }, + alert: { + isAlert: 'Alerta', + alertCount: 'Contagem de Alertas', + clamHelper: 'Dispara alerta via ao detectar arquivos infectados durante a varredura', + cronJobHelper: 'Dispara alerta via ao falhar na execução de tarefas', + licenseHelper: 'A versão profissional suporta alertas via SMS', + alertCountHelper: 'Frequência máxima diária de alertas', + alert: 'Alerta por SMS', + logs: 'Registros de Alerta', + list: 'Lista de Alertas', + addTask: 'Criar Alerta', + editTask: 'Editar Alerta', + alertMethod: 'Método', + alertMsg: 'Mensagem de Alerta', + alertRule: 'Regras de Alerta', + titleSearchHelper: 'Digite o título do alerta para busca aproximada', + taskType: 'Tipo', + ssl: 'Expiração do Certificado', + siteEndTime: 'Expiração do Site', + panelPwdEndTime: 'Expiração da Senha do Painel', + panelUpdate: 'Nova Versão do Painel Disponível', + cpu: 'Alerta de CPU do Servidor', + memory: 'Alerta de Memória do Servidor', + load: 'Alerta de Carga do Servidor', + disk: 'Alerta de Disco do Servidor', + website: 'Site', + certificate: 'Certificado SSL', + remainingDays: 'Dias Restantes', + sendCount: 'Contagem de Envio', + sms: 'SMS', + wechat: 'WeChat', + dingTalk: 'DingTalk', + feiShu: 'FeiShu', + mail: 'E-mail', + weCom: 'WeCom', + sendCountRulesHelper: 'Alertas totais enviados antes da expiração (uma vez por dia)', + panelUpdateRulesHelper: 'Alertas totais enviados para nova versão do painel (uma vez por dia)', + oneDaySendCountRulesHelper: 'Número máximo de alertas enviados por dia', + siteEndTimeRulesHelper: 'Sites que nunca expiram não dispararão alertas', + autoRenewRulesHelper: + 'Certificados com renovação automática ativada e menos de 31 dias restantes não dispararão alertas', + panelPwdEndTimeRulesHelper: + 'Alertas de expiração da senha do painel não estão disponíveis se nenhuma expiração estiver definida', + sslRulesHelper: 'Todos os Certificados SSL', + diskInfo: 'Disco', + monitoringType: 'Tipo de Monitoramento', + autoRenew: 'Renovação Automática', + useDisk: 'Uso de Disco', + usePercentage: 'Porcentagem de Uso', + changeStatus: 'Alterar Status', + disableMsg: + 'Parar a tarefa de alerta impedirá que esta tarefa envie mensagens de alerta. Deseja continuar?', + enableMsg: + 'Ativar a tarefa de alerta permitirá que esta tarefa envie mensagens de alerta. Deseja continuar?', + useExceed: 'Uso Excedeu', + useExceedRulesHelper: 'Dispara alerta quando o uso excede o valor configurado', + cpuUseExceedAvg: 'O uso médio da CPU excede o valor especificado', + memoryUseExceedAvg: 'O uso médio da memória excede o valor especificado', + loadUseExceedAvg: 'O uso médio da carga excede o valor especificado', + cpuUseExceedAvgHelper: 'O uso médio da CPU dentro do tempo especificado excede o valor especificado', + memoryUseExceedAvgHelper: 'O uso médio da memória dentro do tempo especificado excede o valor especificado', + loadUseExceedAvgHelper: 'O uso médio da carga dentro do tempo especificado excede o valor especificado', + resourceAlertRulesHelper: 'Nota: Alertas contínuos em 30 minutos enviarão apenas um', + specifiedTime: 'Hora Especificada', + deleteTitle: 'Excluir Alerta', + deleteMsg: 'Tem certeza de que deseja excluir a tarefa de alerta?', + + allSslTitle: 'Alertas de Expiração de Todos os Certificados SSL do Site', + sslTitle: 'Alerta de Expiração do Certificado SSL do Site {0}', + allSiteEndTimeTitle: 'Alertas de Expiração de Todos os Sites', + siteEndTimeTitle: 'Alerta de Expiração do Site {0}', + panelPwdEndTimeTitle: 'Alerta de Expiração da Senha do Painel', + panelUpdateTitle: 'Notificação de Nova Versão do Painel', + cpuTitle: 'Alerta de Alto Uso de CPU', + memoryTitle: 'Alerta de Alto Uso de Memória', + loadTitle: 'Alerta de Alta Carga', + diskTitle: 'Alerta de Alto Uso de Disco para o Diretório de Montagem {0}', + allDiskTitle: 'Alerta de Alto Uso de Disco', + + timeRule: 'Tempo restante menor que {0} dias (se não tratado, será reenviado no próximo dia)', + panelUpdateRule: + 'Envia um alerta quando uma nova versão do painel é detectada (se não tratado, será reenviado no próximo dia)', + avgRule: 'Uso médio de {1} excede {2}% em {0} minutos, dispara alerta, envia {3} vezes por dia', + diskRule: + 'Uso de disco para o diretório de montagem {0} excede {1}{2}, dispara alerta, envia {3} vezes por dia', + allDiskRule: 'Uso de disco excede {0}{1}, dispara alerta, envia {2} vezes por dia', + + cpuName: 'CPU', + memoryName: 'Memória', + loadName: 'Carga', + diskName: 'Disco', + + syncAlertInfo: 'Envio Manual', + syncAlertInfoMsg: 'Deseja enviar a tarefa de alerta manualmente?', + pushError: 'Envio Falhou', + pushSuccess: 'Envio Bem-sucedido', + syncError: 'Sincronização Falhou', + success: 'Alerta Bem-sucedido', + pushing: 'Enviando...', + error: 'Falha no alerta', + cleanLog: 'Limpar Registros', + cleanAlertLogs: 'Limpar Registros de Alertas', + daily: 'Contagem Diária de Alertas: {0}', + cumulative: 'Contagem Acumulada de Alertas: {0}', + clams: 'Varredura de Vírus', + taskName: 'Nome da Tarefa', + cronJobType: 'Tipo de Tarefa', + clamPath: 'Diretório de Varredura', + cronjob: 'Tarefa Cron agendada {0} falhou', + app: 'Backup do Aplicativo', + web: 'Backup do Site', + database: 'Backup do Banco de Dados', + directory: 'Backup do Diretório', + log: 'Registros de Backup', + snapshot: 'Instantâneo do Sistema', + clamsRulesHelper: 'Tarefas de varredura de vírus que exigem alertas por', + cronJobRulesHelper: 'Este tipo de tarefa cron precisa ser configurado', + clamsTitle: 'Tarefa de Varredura de Vírus 「 {0} 」 detectou arquivo infectado', + cronJobAppTitle: 'Tarefa Cron - Falha no Backup do Aplicativo 「 {0} 」', + cronJobWebsiteTitle: 'Tarefa Cron - Falha no Backup do Site 「 {0} 」', + cronJobDatabaseTitle: 'Tarefa Cron - Falha no Backup do Banco de Dados 「 {0} 」', + cronJobDirectoryTitle: 'Tarefa Cron - Falha no Backup do Diretório 「 {0} 」', + cronJobLogTitle: 'Tarefa Cron - Falha nos Registros de Backup 「 {0} 」', + cronJobSnapshotTitle: 'Tarefa Cron - Falha no Instantâneo do Sistema 「 {0} 」', + cronJobShellTitle: 'Tarefa Cron - Falha no Script Shell 「 {0} 」', + cronJobCurlTitle: 'Tarefa Cron - Falha no Acesso à URL 「 {0} 」', + cronJobCutWebsiteLogTitle: 'Tarefa Cron - Falha no Corte do Registro do Site 「 {0} 」', + cronJobCleanTitle: 'Tarefa Cron - Falha na Limpeza de Cache 「 {0} 」', + cronJobNtpTitle: 'Tarefa Cron - Falha na Sincronização do Horário do Servidor 「 {0} 」', + clamsRule: 'Varredura de vírus detectou arquivo infectado, enviado {0} vezes por dia', + cronJobAppRule: 'Falha na tarefa de backup do aplicativo, enviada {0} vezes por dia', + cronJobWebsiteRule: 'Falha na tarefa de backup do site, enviada {0} vezes por dia', + cronJobDatabaseRule: 'Falha na tarefa de backup do banco de dados, enviada {0} vezes por dia', + cronJobDirectoryRule: 'Falha na tarefa de backup do diretório, enviada {0} vezes por dia', + cronJobLogRule: 'Falha na tarefa de backup de registros, enviada {0} vezes por dia', + cronJobSnapshotRule: 'Falha na tarefa de backup de instantâneo, enviada {0} vezes por dia', + cronJobShellRule: 'Falha na tarefa de script shell, enviada {0} vezes por dia', + cronJobCurlRule: 'Falha na tarefa de acesso à URL, enviada {0} vezes por dia', + cronJobCutWebsiteLogRule: 'Falha na tarefa de corte do registro do site, enviada {0} vezes por dia', + cronJobCleanRule: 'Falha na tarefa de limpeza de cache, enviada {0} vezes por dia', + cronJobNtpRule: 'Falha na tarefa de sincronização do horário do servidor, enviada {0} vezes por dia', + alertSmsHelper: 'Limite de SMS: total de {0} mensagens, {1} já usadas', + goBuy: 'Comprar Mais', + phone: 'Telefone', + phoneHelper: 'Forneça um número de telefone real para mensagens de alerta', + dailyAlertNum: 'Número de alertas diários', + dailyAlertNumHelper: 'Número total de alertas diários, até um máximo de 100 alertas', + timeRange: 'Intervalo de Tempo', + sendTimeRange: 'Intervalo de tempo para envio de', + sendTimeRangeHelper: 'Pode enviar alerta dentro do intervalo de tempo {0}', + to: '-', + startTime: 'hora de início', + endTime: 'hora de término', + defaultPhone: 'Número de telefone padrão vinculado à conta de licença', + noticeAlert: 'Alerta de Notificação', + resourceAlert: 'Alerta de Recursos', + agentOfflineAlertHelper: + 'Quando o alerta offline estiver ativado para o nó, o nó principal verificará a cada 30 minutos para executar as tarefas de alerta.', + offline: 'Alerta Offline', + offlineHelper: + 'Ao definir como alerta offline, o nó principal verificará a cada 30 minutos para executar tarefas de alerta.', + offlineOff: 'Ativar Alerta Offline', + offlineOffHelper: + 'Ao ativar o alerta offline, o nó principal verificará a cada 30 minutos para executar as tarefas de alerta.', + offlineClose: 'Desativar Alerta Offline', + offlineCloseHelper: + 'Ao desativar o alerta offline, os nós secundários deverão lidar com os alertas por conta própria. Certifique-se de que a conectividade de rede está boa para evitar falhas.', + alertNotice: 'Notificação de Alerta', + methodConfig: 'Configuração do Método de Notificação', + commonConfig: 'Configuração Global', + smsConfig: 'SMS', + smsConfigHelper: 'Configure os números para notificação por SMS', + emailConfig: 'E-mail', + emailConfigHelper: 'Configure o serviço de envio de e-mail SMTP', + deleteConfigTitle: 'Excluir Configuração de Alerta', + deleteConfigMsg: 'Tem certeza de que deseja excluir a configuração de alerta?', + test: 'Testar', + alertTestOk: 'Notificação de teste bem-sucedida', + alertTestFailed: 'Falha na notificação de teste', + displayName: 'Nome de Exibição', + sender: 'Endereço do Remetente', + password: 'Senha', + host: 'Servidor SMTP', + port: 'Porta', + encryption: 'Método de Criptografia', + recipient: 'Destinatário', + licenseTime: 'Lembrete de Expiração da Licença', + licenseTimeTitle: 'Lembrete de Expiração da Licença', + displayNameHelper: 'Nome exibido do remetente do e-mail', + senderHelper: 'Endereço de e-mail usado para envio', + passwordHelper: 'Código de autorização do serviço de e-mail', + hostHelper: 'Endereço do servidor SMTP, ex: smtp.qq.com', + portHelper: 'SSL geralmente usa 465, TLS geralmente usa 587', + sslHelper: 'Se a porta SMTP for 465, normalmente é necessário SSL', + tlsHelper: 'Se a porta SMTP for 587, normalmente é necessário TLS', + triggerCondition: 'Condição de Disparo', + loginFail: ' falhas de login em', + nodeException: 'Alerta de Exceção de Nó', + licenseException: 'Alerta de Exceção de Licença', + panelLogin: 'Alerta de Exceção de Login no Painel', + sshLogin: 'Alerta de Exceção de Login SSH', + panelIpLogin: 'Alerta de Exceção de IP de Login no Painel', + sshIpLogin: 'Alerta de Exceção de IP de Login SSH', + ipWhiteListHelper: + 'Os IPs na lista de permissões não são restringidos pelas regras e não haverá alerta em caso de login bem-sucedido', + nodeExceptionRule: 'Alerta de exceção de nó, enviado {0} vezes por dia', + licenseExceptionRule: 'Alerta de exceção de licença, enviado {0} vezes por dia', + panelLoginRule: 'Alerta de login no painel, enviado {0} vezes por dia', + sshLoginRule: 'Alerta de login SSH, enviado {0} vezes por dia', + userNameHelper: 'O nome de usuário está vazio, o endereço do remetente será usado por padrão', + }, + theme: { + lingXiaGold: 'Ling Xia Gold', + classicBlue: 'Azul Clássico', + freshGreen: 'Verde Fresco', + customColor: 'Cor do Tema Personalizada', + setDefault: 'Restaurar Padrão', + setDefaultHelper: 'O esquema de cores do tema será restaurado para o estado inicial. Deseja continuar?', + setHelper: 'O esquema de cores do tema selecionado será salvo. Deseja continuar?', + }, + exchange: { + exchange: 'Troca de Arquivos', + exchangeConfirm: 'Deseja transferir o arquivo/pasta {1} do nó {0} para o diretório {3} do nó {2}?', + }, + cluster: { + cluster: 'Alta Disponibilidade de Aplicações', + name: 'Nome do Cluster', + addCluster: 'Adicionar Cluster', + installNode: 'Instalar Nó', + master: 'Nó Mestre', + slave: 'Nó Escravo', + replicaStatus: 'Status Mestre-Escravo', + unhealthyDeleteError: + 'O status do nó de instalação está anormal, verifique a lista de nós e tente novamente!', + replicaStatusError: 'A aquisição do status está anormal, verifique o nó mestre.', + masterHostError: 'O IP do nó mestre não pode ser 127.0.0.1', + }, + }, +}; + +export default { + ...fit2cloudPtBrLocale, + ...message, +}; diff --git a/frontend/src/lang/modules/ru.ts b/frontend/src/lang/modules/ru.ts new file mode 100644 index 0000000..6056996 --- /dev/null +++ b/frontend/src/lang/modules/ru.ts @@ -0,0 +1,4148 @@ +import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/ru'; + +const message = { + commons: { + true: 'да', + false: 'нет', + colon: ': ', + example: 'Например, ', + fit2cloud: 'FIT2CLOUD', + lingxia: 'Lingxia', + button: { + run: 'Запуск', + create: 'Создать ', + add: 'Добавить ', + save: 'Сохранить ', + set: 'Изменить конфигурацию', + sync: 'Синхронизировать ', + delete: 'Удалить', + edit: 'Редактировать ', + enable: 'Включить', + disable: 'Отключить', + confirm: 'Подтвердить', + cancel: 'Отмена', + reset: 'Сбросить', + restart: 'Перезапустить', + conn: 'Подключить', + disConn: 'Отключить', + clean: 'Очистить', + login: 'Войти', + close: 'Закрыть', + off: 'Закрыть', + stop: 'Остановить', + start: 'Запустить', + view: 'Просмотр', + watch: 'Наблюдать', + handle: 'Запустить', + expand: 'Развернуть', + clone: 'Клонировать', + collapse: 'Свернуть', + log: 'Логи', + back: 'Назад', + backup: 'Бэкап', + recover: 'Восстановить', + retry: 'Повторить', + upload: 'Загрузить', + download: 'Скачать', + init: 'Инициализировать', + verify: 'Проверить', + saveAndEnable: 'Сохранить и включить', + import: 'Импорт', + export: 'Экспорт', + power: 'Авторизация', + search: 'Поиск', + refresh: 'Обновить', + get: 'Получить', + upgrade: 'Обновить', + update: 'обновление', + updateNow: 'Обновить Сейчас', + ignore: 'Игнорировать обновление', + install: 'установить', + copy: 'Копировать', + random: 'Случайно', + uninstall: 'Удалить', + fullscreen: 'Полный экран', + quitFullscreen: 'Выйти из полного экрана', + showAll: 'Показать все', + hideSome: 'Скрыть некоторые', + agree: 'Согласен', + notAgree: 'Не согласен', + preview: 'Предпросмотр', + open: 'Открыть', + notSave: 'Не сохранять', + createNewFolder: 'Создать новую папку', + createNewFile: 'Создать новый файл', + helpDoc: 'Справка', + unbind: 'Отвязать', + cover: 'Заменить', + skip: 'Пропустить', + fix: 'Исправить', + down: 'Остановить', + up: 'Запустить', + sure: 'Подтвердить', + show: 'Показать', + hide: 'Скрыть', + visit: 'Посетить', + migrate: 'Мигрировать', + }, + operate: { + start: 'Запустить', + stop: 'Остановить', + restart: 'Перезапустить', + reload: 'Перезагрузить', + rebuild: 'Перестроить', + sync: 'Синхронизировать', + up: 'Запустить', + down: 'Остановить', + delete: 'Удалить', + }, + search: { + timeStart: 'Время начала', + timeEnd: 'Время окончания', + timeRange: 'До', + dateStart: 'Дата начала', + dateEnd: 'Дата окончания', + date: 'Дата', + }, + table: { + all: 'Все', + total: 'Всего {0}', + name: 'Имя', + type: 'Тип', + status: 'Статус', + records: 'Записи', + group: 'Группа', + createdAt: 'Время создания', + publishedAt: 'Время публикации', + date: 'Дата', + updatedAt: 'Время обновления', + operate: 'Операции', + message: 'Сообщение', + description: 'Описание', + interval: 'Интервал', + user: 'Владелец', + title: 'Заголовок', + port: 'Порт', + forward: 'Переадресация', + protocol: 'Протокол', + tableSetting: 'Настройки таблицы', + refreshRate: 'Частота обновления', + selectColumn: 'Выбрать столбец', + local: 'локальный', + serialNumber: 'Серийный номер', + manageGroup: 'Управление группами', + backToList: 'Вернуться к списку', + keepEdit: 'Продолжить редактирование', + }, + loadingText: { + Upgrading: 'Обновление системы, пожалуйста, подождите...', + Restarting: 'Перезагрузка системы, пожалуйста, подождите...', + Recovering: 'Восстановление из снапшота, пожалуйста, подождите...', + Rollbacking: 'Откат из снапшота, пожалуйста, подождите...', + }, + msg: { + noneData: 'Нет данных', + delete: 'Эта операция удаления не может быть отменена. Хотите продолжить?', + clean: 'Эта операция очистки не может быть отменена. Хотите продолжить?', + closeDrawerHelper: 'Система может не сохранить внесённые вами изменения. Продолжить?', + deleteSuccess: 'Успешно удалено', + loginSuccess: 'Успешный вход', + operationSuccess: 'Операция выполнена успешно', + copySuccess: 'Успешно скопировано', + notSupportOperation: 'Эта операция не поддерживается', + requestTimeout: 'Время запроса истекло, попробуйте позже', + infoTitle: 'Подсказка', + notRecords: 'Для текущей задачи не создано записей выполнения', + sureLogOut: 'Вы уверены, что хотите выйти?', + createSuccess: 'Успешно создано', + updateSuccess: 'Успешно обновлено', + uploadSuccess: 'Успешно загружено', + operateConfirm: 'Если вы уверены в операции, введите её вручную: ', + inputOrSelect: 'Пожалуйста, выберите или введите', + copyFailed: 'Не удалось скопировать', + operatorHelper: 'Операция "{1}" будет выполнена над "{0}" и не может быть отменена. Хотите продолжить?', + notFound: 'Извините, запрошенная страница не существует.', + unSupportType: 'Текущий тип файла не поддерживается.', + unSupportSize: 'Загруженный файл превышает {0}M, пожалуйста, подтвердите!', + fileExist: 'Файл уже существует в текущей папке. Повторная загрузка не поддерживается.', + fileNameErr: + 'Вы можете загружать только файлы, имя которых содержит от 1 до 256 символов, включая английские буквы, китайские иероглифы, цифры или точки (.-_)', + confirmNoNull: 'Убедитесь, что значение {0} не пустое.', + errPort: 'Неверная информация о порте, пожалуйста, проверьте!', + remove: 'Удалить', + backupHelper: 'Текущая операция создаст резервную копию {0}. Хотите продолжить?', + recoverHelper: 'Восстановление из файла {0}. Эта операция необратима. Хотите продолжить?', + refreshSuccess: 'Обновление успешно', + rootInfoErr: 'Это уже корневой каталог', + resetSuccess: 'Сброс успешен', + creatingInfo: 'Создание, эта операция не нужна', + offlineTips: 'Офлайн версия не поддерживает эту операцию', + errImportFormat: 'Данные импорта или формат ненормальны, проверьте и повторите попытку!', + importHelper: + 'При импорте конфликтующих или дублирующихся данных импортированное содержимое будет использоваться в качестве стандарта для обновления исходных данных базы данных.', + errImport: 'Содержимое файла ненормально:', + }, + login: { + username: 'Имя пользователя', + password: 'Пароль', + passkey: 'Вход по passkey', + welcome: 'Добро пожаловать, введите имя пользователя и пароль для входа!', + errorAuthInfo: 'Введенное имя пользователя или пароль неверны, пожалуйста, введите заново!', + errorMfaInfo: 'Неверная информация аутентификации, попробуйте еще раз!', + captchaHelper: 'Капча', + errorCaptcha: 'Ошибка кода капчи!', + notSafe: 'Доступ запрещен', + safeEntrance1: 'В текущей среде включен безопасный вход', + safeEntrance2: 'Введите следующую команду в SSH-терминале для просмотра входа в панель: 1pctl user-info', + errIP1: 'В текущей среде включен доступ по авторизованному IP-адресу', + errDomain1: 'В текущей среде включена привязка домена доступа', + errHelper: 'Для сброса информации о привязке выполните следующую команду в SSH-терминале: ', + codeInput: 'Пожалуйста, введите 6-значный код подтверждения MFA', + mfaTitle: 'MFA Сертификация', + mfaCode: 'MFA код подтверждения', + title: 'Панель управления Linux сервером', + licenseHelper: '<Лицензионное соглашение сообщества>', + errorAgree: 'Нажмите, чтобы согласиться с Лицензией программного обеспечения сообщества', + logout: 'Выход', + agreeTitle: 'Соглашение', + agreeContent: + 'Для лучшей защиты ваших законных прав и интересов, пожалуйста, прочитайте и согласитесь со следующим соглашением « Лицензионное соглашение сообщества »', + passkeyFailed: 'Вход по passkey не удался, попробуйте снова', + passkeyNotSupported: 'Текущий браузер или среда не поддерживает passkey', + passkeyToPassword: 'Не получается использовать passkey? Войдите по паролю', + }, + rule: { + username: 'Введите имя пользователя', + password: 'Введите пароль', + rePassword: 'Подтверждение пароля не совпадает с паролем.', + requiredInput: 'Это поле обязательно для заполнения.', + requiredSelect: 'Выберите элемент из списка', + illegalChar: 'В настоящее время не поддерживается вставка символов & ; $ \' ` ( ) " > < |', + illegalInput: 'Это поле не должно содержать недопустимых символов.', + commonName: + 'Это поле должно начинаться с неспециальных символов и должно состоять из английских букв, китайских иероглифов, цифр, ".", "-" и "_" длиной 1-128.', + userName: 'Поддерживает начало без специальных символов, английский, китайский, цифры и _, длина 3-30', + simpleName: + 'Это поле не должно начинаться с подчеркивания и должно состоять из английских букв, цифр и "_" длиной 3-30.', + simplePassword: + 'Это поле не должно начинаться с подчеркивания и должно состоять из английских букв, цифр и "_" длиной 1-30.', + dbName: 'Это поле не должно начинаться с подчеркивания и должно состоять из английских букв, цифр и "_" длиной 1-64.', + imageName: 'Поддерживает начало без специальных символов, английский, цифры, :@/.-_, длина 1-256', + composeName: 'Поддерживаются неспециальные символы в начале, строчные буквы, цифры, - и _, длина 1-256', + volumeName: 'Это поле должно состоять из английских букв, цифр, ".", "-" и "_" длиной 2-30.', + supervisorName: + 'Это поле должно начинаться с неспециальных символов и должно состоять из английских букв, цифр, "-" и "_" длиной 1-128.', + complexityPassword: + 'Это поле должно состоять из английских букв, цифр длиной 8-30 и содержать как минимум два специальных символа.', + commonPassword: 'Длина этого поля должна быть больше 6.', + linuxName: + 'Длина этого поля должна быть от 1 до 128. Поле не должно содержать эти специальные символы: "{0}".', + email: 'Это поле должно быть действительным адресом электронной почты.', + number: 'Это поле должно быть числом.', + integer: 'Это поле должно быть положительным целым числом.', + ip: 'Это поле должно быть действительным IP-адресом.', + host: 'Это поле должно быть действительным IP-адресом или доменным именем.', + hostHelper: 'Поддерживается ввод IP-адреса или доменного имени', + port: 'Это поле должно быть действительным номером порта.', + selectHelper: 'Пожалуйста, выберите правильный файл {0}', + domain: 'Это поле должно быть в формате: example.com или example.com:8080.', + databaseName: 'Это поле должно состоять из английских букв, цифр и "_" длиной 1-30.', + ipErr: 'Это поле должно быть действительным IP-адресом.', + numberRange: 'Это поле должно быть числом между {0} и {1}.', + paramName: 'Это поле должно состоять из английских букв, цифр, ".", "-" и "_" длиной 2-30.', + paramComplexity: + 'Это поле не должно начинаться и заканчиваться специальными символами и должно состоять из английских букв, цифр, "{0}" длиной 6-128.', + paramUrlAndPort: 'Это поле должно быть в формате "http(s)://(доменное имя/ip):(порт)".', + nginxDoc: 'Это поле должно состоять из английских букв, цифр и ".".', + appName: + 'Это поле не должно начинаться и заканчиваться символами "-" и "_" и должно состоять из английских букв, цифр, "-" и "_" длиной 2-30.', + containerName: 'Поддерживаются буквы, цифры, -, _ и .; не может начинаться с - _ или .; длина: 2-128', + mirror: 'Адрес ускорения зеркала должен начинаться с http(s)://, поддерживает английские буквы (как заглавные, так и строчные), цифры, . / и -, и не должен содержать пустых строк.', + disableFunction: 'Поддерживаются только буквы, подчеркивания и запятые', + leechExts: 'Поддерживаются только буквы, цифры и запятые', + paramSimple: 'Поддерживаются строчные буквы и цифры, длина 1-128', + filePermission: 'Ошибка прав доступа к файлу', + formatErr: 'Ошибка формата, пожалуйста, проверьте и повторите попытку', + phpExtension: 'Поддерживаются только запятые, подчеркивания, строчные английские буквы и цифры', + paramHttp: 'Должно начинаться с http:// или https://', + phone: 'Неверный формат номера телефона', + authBasicPassword: 'Поддерживает буквы, цифры и общие специальные символы, длина 1-72', + length128Err: 'Длина не может превышать 128 символов', + maxLength: 'Длина не может превышать {0} символов', + alias: 'Поддерживает английский, цифры, - и _, длина 1-128, и не может начинаться или заканчиваться на -_.', + }, + res: { + paramError: 'Запрос не удался, попробуйте позже!', + forbidden: 'У текущего пользователя нет прав', + serverError: 'Ошибка сервиса', + notFound: 'Ресурс не существует', + commonError: 'Запрос не удался', + }, + service: { + serviceNotStarted: 'Сервис {0} не запущен.', + }, + status: { + running: 'Работает', + done: 'Завершено', + scanFailed: 'Неполный', + success: 'Успешно', + waiting: 'Ожидание', + waitForUpgrade: 'Ожидание Обновления', + waiting1: 'Ожидание', + failed: 'Ошибка', + stopped: 'Остановлен', + error: 'Ошибка', + created: 'Создан', + restarting: 'Перезапуск', + uploading: 'Загрузка', + unhealthy: 'Нездоровый', + removing: 'Удаление', + paused: 'Приостановлен', + exited: 'Вышел', + dead: 'Мертв', + installing: 'Установка', + enabled: 'Включен', + disabled: 'Отключен', + normal: 'Нормально', + building: 'Сборка', + upgrading: 'Обновление', + pending: 'Ожидает редактирования', + rebuilding: 'Пересборка', + deny: 'Отказано', + accept: 'Принято', + used: 'Используется', + unUsed: 'Не используется', + starting: 'Запуск', + recreating: 'Пересоздание', + creating: 'Создание', + init: 'Ожидание приложения', + ready: 'нормально', + applying: 'Применение', + uninstalling: 'Удаление', + lost: 'Потеряно', + bound: 'Привязано', + unbind: 'Не привязано', + exceptional: 'Исключение', + free: 'Свободно', + enable: 'Включено', + disable: 'Отключено', + deleted: 'Удалено', + downloading: 'Загрузка', + packing: 'Упаковка', + sending: 'Отправка', + healthy: 'Нормально', + executing: 'Выполнение', + installerr: 'Ошибка установки', + applyerror: 'Ошибка применения', + systemrestart: 'Прервано', + starterr: 'Ошибка запуска', + uperr: 'Ошибка запуска', + new: 'Новый', + conflict: 'Конфликт', + duplicate: 'Дубликат', + unexecuted: 'Не Выполнено', + }, + units: { + second: ' секунда | секунда | секунд', + minute: 'минута | минута | минут', + hour: 'час | час | часов', + day: 'день | день | дней', + week: 'неделя | неделя | недель', + month: 'месяц | месяц | месяцев', + year: 'год | год | лет', + time: 'раз', + core: 'ядро | ядро | ядер', + secondUnit: 'с', + minuteUnit: 'мин', + hourUnit: 'ч', + dayUnit: 'д', + millisecond: 'Миллисекунда', + }, + log: { + noLog: 'Нет доступных логов', + }, + }, + menu: { + home: 'Обзор', + apps: 'Приложения', + website: 'Сайт | Сайты', + project: 'Проект | Проекты', + config: 'Конфигурация | Конфигурации', + ssh: 'Настройки SSH', + firewall: 'Firewall', + ssl: 'Сертификат | Сертификаты', + database: 'База данных | Базы данных', + aiTools: 'AI', + mcp: 'MCP', + container: 'Контейнер | Контейнеры', + cronjob: 'Cron | Задачи Cron', + system: 'Система', + security: 'Безопасность', + files: 'Файлы', + monitor: 'Мониторинг', + terminal: 'Терминалы', + settings: 'Настройка | Настройки', + toolbox: 'Инструменты', + logs: 'Лог | Логи', + runtime: 'Среда исполнения | Среды исполнения', + processManage: 'Процесс | Процессы', + process: 'Процесс | Процессы', + network: 'Сеть | Сети', + supervisor: 'Супервизор', + tamper: 'Защита от несанкционированного доступа', + app: 'Приложение', + msgCenter: 'Центр задач', + disk: 'Диск', + }, + home: { + recommend: 'рекомендовать', + dir: 'каталог', + alias: 'Псевдоним', + quickDir: 'Быстрый каталог', + minQuickJump: 'Пожалуйста, установите хотя бы одну запись быстрого перехода!', + maxQuickJump: 'Вы можете установить до четырех записей быстрого перехода!', + database: 'База данных - Все', + restart_1panel: 'Перезапустить панель', + restart_system: 'Перезапустить сервер', + operationSuccess: 'Операция выполнена успешно, перезагрузка, пожалуйста, обновите браузер вручную позже!', + entranceHelper: + 'Безопасный вход не включен. Вы можете включить его в "Настройки -> Безопасность" для повышения безопасности системы.', + appInstalled: 'Приложения', + systemInfo: 'Системная информация', + hostname: 'Имя хоста', + platformVersion: 'Операционная система', + kernelVersion: 'Ядро', + kernelArch: 'Архитектура', + network: 'Сеть', + io: 'Диск I/O', + ip: 'Локальный IP', + proxy: 'Системный прокси', + baseInfo: 'Базовая информация', + totalSend: 'Всего отправлено', + totalRecv: 'Всего получено', + rwPerSecond: 'Операции ввода/вывода', + ioDelay: 'Задержка ввода/вывода', + uptime: 'Работает с', + runningTime: 'Время работы', + mem: 'Системная Память', + swapMem: 'Раздел подкачки', + + runSmoothly: 'Низкая нагрузка', + runNormal: 'Средняя нагрузка', + runSlowly: 'Высокая нагрузка', + runJam: 'Тяжелая нагрузка', + + core: 'Физических ядер', + logicCore: 'Логических ядер', + corePercent: 'Использование Ядра', + cpuFrequency: 'Частота CPU', + cpuDetailedPercent: 'Распределение Времени CPU', + cpuUser: 'Пользователь', + cpuSystem: 'Система', + cpuIdle: 'Простой', + cpuIrq: 'IRQ', + cpuSoftirq: 'Soft IRQ', + cpuSteal: 'Steal', + cpuTop: 'Топ 5 Процессов по Использованию ЦПУ', + memTop: 'Топ 5 Процессов по Использованию Памяти', + loadAverage: 'Средняя нагрузка за последнюю минуту | Средняя нагрузка за последние {n} минут', + load: 'Нагрузка', + mount: 'Точка монтирования', + fileSystem: 'Файловая система', + total: 'Всего', + used: 'Использовано', + cache: 'Кэш', + free: 'Свободно', + shard: 'Шардированный', + available: 'Доступно', + percent: 'Утилизация', + goInstall: 'Установить', + + networkCard: 'Интерфейс', + disk: 'Диск', + }, + tabs: { + more: 'Больше', + hide: 'Скрыть', + closeLeft: 'Закрыть слева', + closeRight: 'Закрыть справа', + closeCurrent: 'Закрыть текущую', + closeOther: 'Закрыть другие', + closeAll: 'Закрыть все', + }, + header: { + logout: 'Выход', + }, + database: { + manage: 'Управление', + deleteBackupHelper: 'Удалить резервные копии базы данных одновременно', + delete: 'Операция удаления не может быть отменена, пожалуйста, введите "', + deleteHelper: '" для удаления этой базы данных', + create: 'Создать базу данных', + noMysql: 'Сервис базы данных (MySQL или MariaDB)', + noPostgresql: 'Сервис базы данных PostgreSQL', + goUpgrade: 'Обновить', + goInstall: 'Установить', + isDelete: 'Удалено', + permission: 'Разрешения', + format: 'Набор Символов', + collation: 'Сопоставление', + collationHelper: 'Если пусто, используйте сопоставление по умолчанию для набора символов {0}', + permissionForIP: 'IP', + permissionAll: 'Все (%)', + databaseConnInfo: 'Информация о подключении', + rootPassword: 'Пароль root', + serviceName: 'Имя сервиса', + serviceNameHelper: 'Доступ между контейнерами в одной сети.', + backupList: 'Резервное копирование', + loadBackup: 'Импорт', + localUpload: 'Локальная загрузка', + hostSelect: 'Выбор сервера', + selectHelper: 'Вы уверены, что хотите импортировать файл резервной копии {0}?', + remoteAccess: 'Удаленный доступ', + remoteHelper: 'Несколько IP через запятую, например: 172.16.10.111, 172.16.10.112', + remoteConnHelper: + 'Удаленное подключение к MySQL как пользователь root может иметь риски безопасности. Поэтому выполняйте эту операцию с осторожностью.', + changePassword: 'Пароль', + changePasswordHelper: + 'База данных была связана с приложением. Изменение пароля изменит пароль базы данных приложения одновременно. Изменение вступит в силу после перезапуска приложения.', + recoverTimeoutHelper: '-1 означает отсутствие ограничения по времени ожидания', + + confChange: 'Конфигурация', + confNotFound: + 'Файл конфигурации не найден. Пожалуйста, обновите приложение до последней версии в магазине приложений и попробуйте снова!', + + portHelper: + 'Этот порт является открытым портом контейнера. Вам нужно сохранить изменение отдельно и перезапустить контейнер!', + + loadFromRemote: 'Синхронизировать', + userBind: 'Привязать пользователя', + pgBindHelper: + 'Эта операция используется для создания нового пользователя и привязки его к целевой базе данных. В настоящее время выбор уже существующих пользователей в базе данных не поддерживается.', + pgSuperUser: 'Суперпользователь', + loadFromRemoteHelper: 'Это синхронизирует информацию о базе данных на сервере с 1Panel. Хотите продолжить?', + passwordHelper: 'Не удается получить, нажмите для изменения', + remote: 'Удаленный', + remoteDB: 'Удаленный сервер | Удаленные серверы', + createRemoteDB: 'Привязать @.lower:database.remoteDB', + unBindRemoteDB: 'Отвязать @.lower:database.remoteDB', + unBindForce: 'Принудительная отвязка', + unBindForceHelper: + 'Игнорировать все ошибки во время процесса отвязки, чтобы обеспечить успешное завершение операции', + unBindRemoteHelper: + 'Отвязка удаленной базы данных только удалит связь привязки и не будет напрямую удалять удаленную базу данных', + editRemoteDB: 'Редактировать удаленный сервер', + localDB: 'Локальная база данных', + address: 'Адрес базы данных', + version: 'Версия базы данных', + userHelper: + 'Пользователь root или пользователь базы данных с привилегиями root может получить доступ к удаленной базе данных.', + pgUserHelper: 'Пользователь базы данных с привилегиями суперпользователя.', + ssl: 'Использовать SSL', + clientKey: 'Приватный ключ клиента', + clientCert: 'Сертификат клиента', + caCert: 'Сертификат CA', + hasCA: 'Есть сертификат CA', + skipVerify: 'Игнорировать проверку действительности сертификата', + initialDB: 'Исходная База Данных', + + formatHelper: + 'Текущая кодировка базы данных - {0}, несоответствие кодировок может привести к ошибке восстановления', + selectFile: 'Выбрать файл', + dropHelper: 'Вы можете перетащить загружаемый файл сюда или', + clickHelper: 'нажмите для загрузки', + supportUpType: + 'Поддерживаются только форматы файлов sql, sql.gz, tar.gz, .zip. Импортируемый сжатый файл должен содержать только один файл .sql или включать test.sql', + + currentStatus: 'Текущее состояние', + baseParam: 'Базовые параметры', + performanceParam: 'Параметры производительности', + runTime: 'Время запуска', + connections: 'Всего подключений', + bytesSent: 'Отправлено байт', + bytesReceived: 'Получено байт', + queryPerSecond: 'Запросов в секунду', + txPerSecond: 'Транзакций в секунду', + connInfo: 'активные/пиковые подключения', + connInfoHelper: 'Если значение слишком большое, увеличьте "max_connections".', + threadCacheHit: 'Попадания в кэш потоков', + threadCacheHitHelper: 'Если значение слишком низкое, увеличьте "thread_cache_size".', + indexHit: 'Попадания в индекс', + indexHitHelper: 'Если значение слишком низкое, увеличьте "key_buffer_size".', + innodbIndexHit: 'Попадания в индекс InnoDB', + innodbIndexHitHelper: 'Если значение слишком низкое, увеличьте "innodb_buffer_pool_size".', + cacheHit: 'Попадания в кэш запросов', + cacheHitHelper: 'Если значение слишком низкое, увеличьте "query_cache_size".', + tmpTableToDB: 'Временные таблицы на диске', + tmpTableToDBHelper: 'Если значение слишком большое, попробуйте увеличить "tmp_table_size".', + openTables: 'Открытые таблицы', + openTablesHelper: 'Значение конфигурации "table_open_cache" должно быть больше или равно этому значению.', + selectFullJoin: 'Полные соединения', + selectFullJoinHelper: 'Если значение не 0, проверьте правильность индексов таблиц данных.', + selectRangeCheck: 'Количество соединений без индекса', + selectRangeCheckHelper: 'Если значение не 0, проверьте правильность индексов таблиц данных.', + sortMergePasses: 'Количество сортировок слиянием', + sortMergePassesHelper: 'Если значение слишком большое, увеличьте "sort_buffer_size".', + tableLocksWaited: 'Количество блокировок таблиц', + tableLocksWaitedHelper: + 'Если значение слишком большое, рассмотрите возможность увеличения производительности базы данных.', + + performanceTuning: 'Настройка производительности', + optimizationScheme: 'Схема оптимизации', + keyBufferSizeHelper: 'Размер буфера для индексов', + queryCacheSizeHelper: 'Кэш запросов. Если эта функция отключена, установите этот параметр в 0.', + tmpTableSizeHelper: 'Размер кэша временных таблиц', + innodbBufferPoolSizeHelper: 'Размер буфера InnoDB', + innodbLogBufferSizeHelper: 'Размер буфера журнала InnoDB', + sortBufferSizeHelper: '* подключений, размер буфера сортировки на поток', + readBufferSizeHelper: '* подключений, размер буфера чтения', + readRndBufferSizeHelper: '* подключений, размер буфера случайного чтения', + joinBufferSizeHelper: '* подключений, размер кэша таблиц соединений', + threadStackelper: '* подключений, размер стека на поток', + binlogCacheSizeHelper: '* подключений, размер кэша бинарного журнала (кратно 4096)', + threadCacheSizeHelper: 'Размер пула потоков', + tableOpenCacheHelper: 'Кэш таблиц', + maxConnectionsHelper: 'Максимум подключений', + restart: 'Перезапустить', + + slowLog: 'Медленные запросы', + noData: 'Пока нет медленных запросов.', + + isOn: 'Включено', + longQueryTime: 'порог (сек)', + thresholdRangeHelper: 'Пожалуйста, введите корректный порог (1 - 600).', + + timeout: 'Таймаут(сек)', + timeoutHelper: 'Период таймаута неактивного подключения. 0 означает, что подключение постоянно активно.', + maxclients: 'Макс. клиентов', + requirepassHelper: + 'Оставьте пустым, если пароль не установлен. Изменения нужно сохранить отдельно и перезапустить контейнер!', + databases: 'Количество баз данных', + maxmemory: 'Максимальное использование памяти', + maxmemoryHelper: '0 означает без ограничений.', + tcpPort: 'Текущий порт прослушивания.', + uptimeInDays: 'Дней в работе.', + connectedClients: 'Количество подключенных клиентов.', + usedMemory: 'Текущее использование памяти Redis.', + usedMemoryRss: 'Размер памяти, запрошенный у операционной системы.', + usedMemoryPeak: 'Пиковое потребление памяти Redis.', + memFragmentationRatio: 'Коэффициент фрагментации памяти.', + totalConnectionsReceived: 'Общее количество подключенных клиентов с момента запуска.', + totalCommandsProcessed: 'Общее количество выполненных команд с момента запуска.', + instantaneousOpsPerSec: 'Количество команд, выполняемых сервером в секунду.', + keyspaceHits: 'Количество успешных поисков ключа в базе данных.', + keyspaceMisses: 'Количество неудачных попыток найти ключ в базе данных.', + hit: 'Коэффициент попаданий при поиске ключей.', + latestForkUsec: 'Количество микросекунд, затраченных на последнюю операцию fork().', + redisCliHelper: 'Сервис "redis-cli" не обнаружен. Сначала включите сервис.', + redisQuickCmd: 'Быстрые команды Redis', + recoverHelper: 'Это перезапишет данные с [{0}]. Хотите продолжить?', + submitIt: 'Перезаписать данные', + + baseConf: 'Базовая', + allConf: 'Все', + restartNow: 'Перезапустить сейчас', + restartNowHelper1: + 'Необходимо перезапустить систему после вступления изменений конфигурации в силу. Если ваши данные требуют сохранения, сначала выполните операцию сохранения.', + restartNowHelper: 'Это вступит в силу только после перезапуска системы.', + + persistence: 'Сохранение', + rdbHelper1: 'секунд(ы), вставить', + rdbHelper2: 'элементов данных', + rdbHelper3: 'Выполнение любого из условий запустит сохранение RDB.', + rdbInfo: 'Убедитесь, что значение в списке правил находится в диапазоне от 1 до 100000', + + containerConn: 'Подключение контейнера', + connAddress: 'Адрес', + containerConnHelper: + 'Этот адрес подключения может использоваться приложениями, работающими в среде выполнения веб-сайта (PHP и т.д.) или контейнере.', + remoteConn: 'Внешнее подключение', + remoteConnHelper2: 'Используйте этот адрес для неконтейнерных сред или внешних подключений.', + remoteConnHelper3: + 'Адрес доступа по умолчанию - это IP хоста. Для изменения перейдите к пункту конфигурации "Адрес доступа по умолчанию" на странице настроек панели.', + localIP: 'Локальный IP', + }, + aiTools: { + model: { + model: 'Модель', + create: 'Добавить модель', + create_helper: 'Загрузить "{0}"', + ollama_doc: 'Вы можете посетить официальный сайт Ollama, чтобы искать и находить больше моделей.', + container_conn_helper: 'Используйте этот адрес для доступа или подключения между контейнерами', + ollama_sync: + 'Синхронизация модели Ollama обнаружила, что следующие модели не существуют, хотите удалить их?', + from_remote: 'Эта модель не была загружена через 1Panel, нет связанных журналов извлечения.', + no_logs: 'Журналы извлечения для этой модели были удалены и не могут быть просмотрены.', + }, + proxy: { + proxy: 'Усиление AI-прокси', + proxyHelper1: 'Привяжите домен и включите HTTPS для повышения безопасности передачи данных', + proxyHelper2: 'Ограничьте доступ по IP, чтобы предотвратить утечку данных в публичной сети', + proxyHelper3: 'Включите потоковую передачу', + proxyHelper4: 'После создания вы можете просматривать и управлять этим в списке сайтов', + proxyHelper5: + 'После включения вы можете отключить внешний доступ к порту в Магазине приложений - Установленные - Ollama - Параметры для повышения безопасности.', + proxyHelper6: 'Чтобы отключить настройку прокси, вы можете удалить её из списка сайтов.', + whiteListHelper: 'Ограничить доступ только для IP-адресов из белого списка', + }, + gpu: { + gpu: 'Мониторинг GPU', + gpuHelper: 'Система не обнаружила команды NVIDIA-SMI или XPU-SMI. Проверьте и повторите попытку!', + process: 'Информация о Процессе', + type: 'Тип', + typeG: 'Графика', + typeC: 'Вычисления', + typeCG: 'Вычисления+Графика', + processName: 'Имя Процесса', + shr: 'Общая Память', + temperatureHelper: 'Высокая температура GPU может вызвать снижение частоты GPU', + gpuUtil: 'Использование GPU', + temperature: 'Температура', + performanceState: 'Состояние Производительности', + powerUsage: 'Потребление Мощности', + memoryUsage: 'Использование Памяти', + fanSpeed: 'Скорость Вентилятора', + power: 'Мощность', + powerCurrent: 'Текущая Мощность', + powerLimit: 'Лимит Мощности', + memory: 'Память', + memoryUsed: 'Использованная Память', + memoryTotal: 'Общая Память', + percent: 'Использование', + + base: 'Основная Информация', + driverVersion: 'Версия Драйвера', + cudaVersion: 'Версия CUDA', + processMemoryUsage: 'Использование Памяти', + performanceStateHelper: 'От P0 (максимальная производительность) до P12 (минимальная производительность)', + busID: 'Адрес Шины', + persistenceMode: 'Режим Постоянства', + enabled: 'Включено', + disabled: 'Выключено', + persistenceModeHelper: + 'Режим постоянства быстрее реагирует на задачи, но потребление энергии в режиме ожидания соответственно увеличивается', + displayActive: 'Инициализация GPU', + displayActiveT: 'Да', + displayActiveF: 'Нет', + ecc: 'Технология Обнаружения и Исправления Ошибок', + computeMode: 'Режим Вычислений', + default: 'По Умолчанию', + exclusiveProcess: 'Эксклюзивный Процесс', + exclusiveThread: 'Эксклюзивный Поток', + prohibited: 'Запрещено', + defaultHelper: 'По умолчанию: Процессы могут выполняться параллельно', + exclusiveProcessHelper: + 'Эксклюзивный Процесс: Только один контекст CUDA может использовать GPU, но он может быть разделен несколькими потоками', + exclusiveThreadHelper: 'Эксклюзивный Поток: Только один поток в контексте CUDA может использовать GPU', + prohibitedHelper: 'Запрещено: Параллельное выполнение процессов не разрешено', + migModeHelper: + 'Используется для создания экземпляров MIG, реализуя физическую изоляцию GPU на пользовательском уровне.', + migModeNA: 'Не Поддерживается', + current: 'Мониторинг в Реальном Времени', + history: 'Исторические Записи', + notSupport: 'Текущая версия или драйвер не поддерживает отображение этого параметра.', + }, + mcp: { + server: 'Сервер MCP', + create: 'Добавить сервер', + edit: 'Редактировать сервер', + baseUrl: 'Внешний путь доступа', + baseUrlHelper: 'Например: http://192.168.1.2:8000', + ssePath: 'Путь SSE', + ssePathHelper: 'Например: /sse, будьте осторожны, чтобы не дублировать с другими серверами', + environment: 'Переменные среды', + envKey: 'Имя переменной', + envValue: 'Значение переменной', + externalUrl: 'Внешний адрес подключения', + operatorHelper: 'Будет выполнена операция {1} на {0}, продолжить?', + domain: 'Адрес доступа по умолчанию', + domainHelper: 'Например: 192.168.1.1 или example.com', + bindDomain: 'Привязать сайт', + commandPlaceHolder: 'В настоящее время поддерживаются только команды запуска npx и двоичных файлов', + importMcpJson: 'Импортировать конфигурацию сервера MCP', + importMcpJsonError: 'Структура mcpServers некорректна', + bindDomainHelper: + 'После привязки веб-сайта он изменит адрес доступа для всех установленных серверов MCP и закроет внешний доступ к портам', + outputTransport: 'Тип вывода', + streamableHttpPath: 'Путь потоковой передачи', + streamableHttpPathHelper: 'Например: /mcp, обратите внимание, чтобы не перекрывать другие серверы', + npxHelper: 'Подходит для mcp, запущенного с помощью npx или бинарного файла', + uvxHelper: 'Подходит для mcp, запущенного с помощью uvx', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: 'Каталог модели', + commandHelper: 'Если требуется внешний доступ, установите порт в команде таким же, как порт приложения', + imageAlert: + 'Из-за большого размера образа рекомендуется вручную загрузить образ на сервер перед установкой', + modelSpeedup: 'Включить ускорение модели', + modelType: 'Тип модели', + }, + }, + container: { + create: 'Создать контейнер', + edit: 'Редактировать контейнер', + updateHelper1: + 'Обнаружено, что этот контейнер происходит из магазина приложений. Обратите внимание на следующие два пункта:', + updateHelper2: + '1. Текущие изменения не будут синхронизированы с установленными приложениями в магазине приложений.', + updateHelper3: + '2. Если вы измените приложение на странице установки, текущее отредактированное содержимое станет недействительным.', + updateHelper4: + 'Редактирование контейнера требует пересборки, и все непостоянные данные будут потеряны. Хотите продолжить?', + containerList: 'Список контейнеров', + operatorHelper: 'Действие {0} будет выполнено для следующего контейнера. Хотите продолжить?', + operatorAppHelper: + 'Операция "{0}" будет выполнена для следующего(-их) контейнера(-ов) и может повлиять на работающие сервисы. Хотите продолжить?', + containerDeleteHelper: + "Обнаружено, что контейнер создан из Магазина приложений. Удаление контейнера не удалит его полностью из 1Panel. Для полного удаления перейдите в Магазин приложений -> меню 'Установленные' или 'Среда выполнения'. Продолжить?", + start: 'Запустить', + stop: 'Остановить', + restart: 'Перезапустить', + kill: 'Завершить', + pause: 'Приостановить', + unpause: 'Возобновить', + rename: 'Переименовать', + remove: 'Удалить', + removeAll: 'Удалить все', + containerPrune: 'Очистить', + containerPruneHelper1: 'Это удалит все контейнеры, которые находятся в остановленном состоянии.', + containerPruneHelper2: + 'Если контейнеры из магазина приложений, вам нужно перейти в "Магазин приложений -> Установленные" и нажать кнопку "Пересобрать" для их переустановки после выполнения очистки.', + containerPruneHelper3: 'Это действие нельзя отменить. Хотите продолжить?', + imagePrune: 'Очистить', + imagePruneSome: 'Очистить непомеченные', + imagePruneSomeEmpty: 'Нет образов с тегом "none" для очистки.', + imagePruneSomeHelper: 'Очистить образы с тегом "none", которые не используются никакими контейнерами.', + imagePruneAll: 'Очистить неиспользуемые', + imagePruneAllEmpty: 'Нет неиспользуемых образов для очистки.', + imagePruneAllHelper: 'Очистить образы, которые не используются никакими контейнерами.', + networkPrune: 'Очистить', + networkPruneHelper: 'Это удалит все неиспользуемые сети. Хотите продолжить?', + volumePrune: 'Очистить', + volumePruneHelper: 'Это удалит все неиспользуемые локальные тома. Хотите продолжить?', + cleanSuccess: 'Операция успешна, количество очищенных элементов: {0}!', + cleanSuccessWithSpace: + 'Операция успешна. Количество очищенных дисков: {0}. Освобождено дискового пространства: {1}!', + unExposedPort: 'Текущий адрес сопоставления портов - 127.0.0.1, что не позволяет внешний доступ.', + upTime: 'Время работы', + fetch: 'Получить', + lines: 'Строки', + linesHelper: 'Пожалуйста, введите правильное количество логов для получения!', + lastDay: 'Последний день', + last4Hour: 'Последние 4 часа', + lastHour: 'Последний час', + last10Min: 'Последние 10 минут', + cleanLog: 'Очистить лог', + downLogHelper1: 'Это загрузит все логи из контейнера {0}. Хотите продолжить?', + downLogHelper2: 'Это загрузит последние {0} логов из контейнера {1}. Хотите продолжить?', + cleanLogHelper: 'Это потребует перезапуска контейнера и не может быть отменено. Хотите продолжить?', + newName: 'Новое имя', + source: 'Использование ресурсов', + cpuUsage: 'Использование CPU', + cpuTotal: 'Всего CPU', + core: 'Ядро', + memUsage: 'Использование памяти', + memTotal: 'Лимит памяти', + memCache: 'Кэш памяти', + loadSize: 'Получить Размер Контейнера', + ip: 'IP-адрес', + cpuShare: 'Доли CPU', + cpuShareHelper: + 'Движок контейнера использует базовое значение 1024 для долей CPU. Вы можете увеличить его, чтобы дать контейнеру больше времени CPU.', + inputIpv4: 'Пример: 192.168.1.1', + inputIpv6: 'Пример: 2001:0db8:85a3:0000:0000:8a2e:0370:7334', + + diskUsage: 'Использование Диска', + localVolume: 'Локальный Том Хранилища', + buildCache: 'Кэш Сборки', + usage: 'Использовано: {0}, Можно освободить: {1}', + clean: 'Освободить', + imageClean: 'Очистка образов удалит все неиспользуемые образы. Это действие нельзя отменить. Продолжить?', + containerClean: + 'Очистка контейнеров удалит все остановленные контейнеры (включая остановленные приложения из Магазина приложений). Это действие нельзя отменить. Продолжить?', + sizeRw: 'Размер Слоя Контейнера', + sizeRwHelper: 'Размер записываемого слоя, уникального для контейнера', + sizeRootFs: 'Виртуальный Размер', + sizeRootFsHelper: 'Общий размер всех слоев образа, от которых зависит контейнер + слой контейнера', + + containerFromAppHelper: + 'Обнаружено, что этот контейнер происходит из магазина приложений. Операции с приложением могут привести к недействительности текущих изменений.', + containerFromAppHelper1: + 'Нажмите кнопку [Параметры] в списке установленных приложений, чтобы перейти на страницу редактирования и изменить имя контейнера.', + command: 'Команда', + console: 'Взаимодействие с контейнером', + tty: 'Выделить псевдо-TTY (-t)', + openStdin: 'Держать STDIN открытым, даже если не подключен (-i)', + custom: 'Пользовательский', + emptyUser: 'Если пусто, вы войдете как пользователь по умолчанию', + privileged: 'Привилегированный', + privilegedHelper: + 'Разрешить контейнеру выполнять определенные привилегированные операции на хосте, что может повысить риски контейнера. Используйте с осторожностью!', + + upgradeHelper: 'Имя репозитория/Имя образа: Версия образа', + upgradeWarning2: + 'Операция обновления требует пересборки контейнера, все несохраненные данные будут потеряны. Хотите продолжить?', + oldImage: 'Текущий образ', + sameImageContainer: 'Контейнеры с одинаковым образом', + sameImageHelper: 'Контейнеры, использующие один образ, можно массово обновить после выбора', + targetImage: 'Целевой образ', + imageLoadErr: 'Не обнаружено имя образа для контейнера', + appHelper: 'Контейнер происходит из магазина приложений, и обновление может сделать сервис недоступным.', + + input: 'Ручной ввод', + forcePull: 'Всегда загружать образ', + forcePullHelper: + 'Это будет игнорировать существующие образы на сервере и загружать последний образ из реестра.', + server: 'Хост', + serverExample: '80, 80-88, ip:80 или ip:80-88', + containerExample: '80 или 80-88', + exposePort: 'Открыть порт', + exposeAll: 'Открыть все', + cmdHelper: 'Пример: nginx -g "daemon off;"', + entrypointHelper: 'Пример: docker-entrypoint.sh', + autoRemove: 'Автоудаление', + cpuQuota: 'Количество ядер CPU', + memoryLimit: 'Память', + limitHelper: 'Если установлено 0, это означает отсутствие ограничений. Максимальное значение {0}', + mount: 'Монтирование', + volumeOption: 'Том', + hostOption: 'Хост', + serverPath: 'Путь на сервере', + containerDir: 'Путь в контейнере', + volumeHelper: 'Убедитесь, что содержимое тома хранения корректно', + networkEmptyHelper: 'Пожалуйста, подтвердите, что выбор сети контейнера правильный', + modeRW: 'Чтение-Запись', + modeR: 'Только чтение', + sharedLabel: 'Режим Распространения', + private: 'Приватный', + privateHelper: 'Изменения монтирования в контейнере и хосте не влияют друг на друга', + rprivate: 'Рекурсивный Приватный', + rprivateHelper: 'Все монтирования в контейнере полностью изолированы от хоста', + shared: 'Общий', + sharedHelper: 'Изменения монтирования в хосте и контейнере видны друг другу', + rshared: 'Рекурсивный Общий', + rsharedHelper: 'Все изменения монтирования в хосте и контейнере видны друг другу', + slave: 'Подчиненный', + slaveHelper: + 'Контейнер может видеть изменения монтирования хоста, но его собственные изменения не влияют на хост', + rslave: 'Рекурсивный Подчиненный', + rslaveHelper: 'Все монтирования в контейнере могут видеть изменения хоста, но не влияют на хост', + mode: 'Режим', + env: 'Переменные окружения', + restartPolicy: 'Политика перезапуска', + always: 'всегда', + unlessStopped: 'если не остановлен', + onFailure: 'при сбое (по умолчанию пять раз)', + no: 'никогда', + + refreshTime: 'Интервал обновления', + cache: 'Кэш', + + image: 'Образ | Образы', + imagePull: 'Загрузить', + imagePullHelper: + 'Поддерживает выбор нескольких образов для загрузки, нажмите Enter после ввода каждого образа для продолжения', + imagePush: 'Отправить', + imagePushHelper: + 'Обнаружено, что у этого образа несколько тегов. Подтвердите, что имя образа, используемое для отправки: {0}', + imageDelete: 'Удалить образ', + repoName: 'Реестр контейнеров', + imageName: 'Имя образа', + pull: 'Загрузить', + path: 'Путь', + importImage: 'Импорт', + buildArgs: 'Аргументы Сборки', + imageBuild: 'Сборка', + pathSelect: 'Путь', + label: 'Метка', + imageTag: 'Тег образа', + imageTagHelper: + 'Поддерживает установку нескольких тегов образов, нажмите Enter после ввода каждого тега для продолжения', + push: 'Отправить', + fileName: 'Имя файла', + export: 'Экспорт', + exportImage: 'Экспорт образа', + size: 'Размер', + tag: 'Теги', + tagHelper: 'По одному в строке. Например,\nkey1=value1\nkey2=value2', + imageNameHelper: 'Имя образа и тег, например: nginx:latest', + cleanBuildCache: 'Очистить кэш сборки', + delBuildCacheHelper: + 'Это удалит все кэшированные артефакты, созданные во время сборки, и не может быть отменено. Хотите продолжить?', + urlWarning: 'Префикс URL не должен включать http:// или https://. Пожалуйста, измените.', + + network: 'Сеть | Сети', + networkHelper: + 'Это может привести к неправильной работе некоторых приложений и сред выполнения. Хотите продолжить?', + createNetwork: 'Создать', + networkName: 'Имя', + driver: 'Драйвер', + option: 'Опция', + attachable: 'Подключаемая', + parentNetworkCard: 'Родительская Сетевая Карта', + subnet: 'Подсеть', + scope: 'Диапазон IP', + gateway: 'Шлюз', + auxAddress: 'Исключить IP', + + volume: 'Том | Тома', + volumeDir: 'Директория тома', + nfsEnable: 'Включить NFS хранилище', + nfsAddress: 'Адрес', + mountpoint: 'Точка монтирования', + mountpointNFSHelper: 'например, /nfs, /nfs-share', + options: 'Опции', + createVolume: 'Создать', + + repo: 'Реестры', + createRepo: 'Добавить', + httpRepoHelper: 'Работа с репозиторием HTTP-типа требует перезапуска службы Docker.', + httpRepo: 'Выбор HTTP протокола требует перезапуска службы Docker для добавления в небезопасные реестры.', + delInsecure: 'Удаление учетных данных', + delInsecureHelper: 'Это перезапустит службу Docker для удаления из небезопасных реестров. Хотите продолжить?', + downloadUrl: 'Сервер', + imageRepo: 'Репозиторий образов', + repoHelper: 'Включает ли зеркало репозитория/организации/проекта?', + auth: 'Требуется аутентификация', + mirrorHelper: + 'Если есть несколько зеркал, они должны быть разделены новой строкой, например:\nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com', + registrieHelper: + 'Если существует несколько частных репозиториев, они должны быть разделены новой строкой, например:\n172.16.10.111:8081 \n172.16.10.112:8081', + + compose: 'Compose | Composes', + composeFile: 'Файл Оркестрации', + fromChangeHelper: 'Переключение источника очистит текущее отредактированное содержимое. Хотите продолжить?', + composePathHelper: 'Путь сохранения файла конфигурации: {0}', + composeHelper: + 'Композиция, созданная через редактор 1Panel или шаблон, будет сохранена в директории {0}/docker/compose.', + deleteFile: 'Удалить файл', + deleteComposeHelper: + 'Удалить все файлы, связанные с compose контейнера, включая файлы конфигурации и постоянные файлы. Пожалуйста, действуйте с осторожностью!', + deleteCompose: '" Удалить эту композицию.', + createCompose: 'Создать', + composeDirectory: 'Директория', + template: 'Шаблон', + composeTemplate: 'Шаблон Compose | Шаблоны Compose', + createComposeTemplate: 'Создать', + content: 'Содержимое', + contentEmpty: 'Содержимое Compose не может быть пустым, пожалуйста, введите и попробуйте снова!', + containerNumber: 'Количество контейнеров', + containerStatus: 'Статус контейнера', + exited: 'Завершен', + running: 'Работает ( {0} / {1} )', + composeDetailHelper: 'Compose создан вне 1Panel. Операции запуска и остановки не поддерживаются.', + composeOperatorHelper: 'Операция {1} будет выполнена для {0}. Хотите продолжить?', + composeDownHelper: 'Это остановит и удалит все контейнеры и сети под compose {0}. Хотите продолжить?', + composeEnvHelper2: + 'Эта оркестрация создана Магазином приложений 1Panel. Измените переменные среды в установленных приложениях.', + + setting: 'Настройка | Настройки', + operatorStatusHelper: 'Это выполнит "{0}" службы Docker. Хотите продолжить?', + dockerStatus: 'Служба Docker', + daemonJsonPathHelper: 'Убедитесь, что путь конфигурации совпадает с указанным в docker.service.', + mirrors: 'Зеркала реестра', + mirrorsHelper: '', + mirrorsHelper2: 'Подробности см. в официальной документации. ', + registries: 'Небезопасные реестры', + ipv6Helper: + 'При включении IPv6 необходимо добавить сеть контейнера IPv6. Конкретные шаги настройки см. в официальной документации.', + ipv6CidrHelper: 'Диапазон пула IPv6-адресов для контейнеров', + ipv6TablesHelper: 'Автоматическая настройка Docker IPv6 для правил iptables.', + experimentalHelper: + 'Включение ip6tables требует включения этой конфигурации; в противном случае ip6tables будет игнорироваться', + cutLog: 'Опция логирования', + cutLogHelper1: 'Текущая конфигурация будет влиять только на вновь созданные контейнеры.', + cutLogHelper2: 'Существующие контейнеры необходимо пересоздать для применения конфигурации.', + cutLogHelper3: + 'Обратите внимание, что пересоздание контейнеров может привести к потере данных. Если ваши контейнеры содержат важные данные, обязательно сделайте резервную копию перед выполнением операции пересборки.', + maxSize: 'Максимальный размер', + maxFile: 'Максимум файлов', + liveHelper: + 'По умолчанию, когда демон Docker завершает работу, он останавливает работающие контейнеры. Вы можете настроить демон так, чтобы контейнеры продолжали работать, если демон становится недоступным. Эта функциональность называется live restore. Опция live restore помогает уменьшить время простоя контейнера из-за сбоев демона, плановых простоев или обновлений.', + liveWithSwarmHelper: 'Конфигурация демона live-restore несовместима с режимом swarm.', + iptablesDisable: 'Отключить iptables', + iptablesHelper1: 'Автоматическая настройка правил iptables для Docker.', + iptablesHelper2: + 'Отключение iptables приведет к тому, что контейнеры не смогут взаимодействовать с внешними сетями.', + daemonJsonPath: 'Путь конфигурации', + serviceUnavailable: 'Служба Docker в настоящее время не запущена.', + startIn: ' для запуска', + sockPath: 'Unix domain socket', + sockPathHelper: 'Канал связи между демоном Docker и клиентом.', + sockPathHelper1: 'Путь по умолчанию: /var/run/docker-x.sock', + sockPathMsg: + 'Сохранение настройки Socket Path может привести к недоступности службы Docker. Хотите продолжить?', + sockPathErr: 'Пожалуйста, выберите или введите правильный путь к файлу Docker sock', + related: 'Связанные', + includeAppstore: 'Показывать контейнеры из магазина приложений', + excludeAppstore: 'Скрыть контейнер магазина приложений', + + cleanDockerDiskZone: 'Очистить дисковое пространство, используемое Docker', + cleanImagesHelper: '(Очистить все образы, не используемые контейнерами)', + cleanContainersHelper: '(Очистить все остановленные контейнеры)', + cleanVolumesHelper: '(Очистить все неиспользуемые локальные тома)', + + makeImage: 'Создать образ', + newImageName: 'Новое имя образа', + commitMessage: 'Сообщение коммита', + author: 'Автор', + ifPause: 'Приостановить контейнер во время создания', + ifMakeImageWithContainer: 'Создать новый образ из этого контейнера?', + finishTime: 'Время последней остановки', + }, + cronjob: { + create: 'Создать задачу cron', + edit: 'Редактировать задачу cron', + importHelper: + 'Повторяющиеся запланированные задачи будут автоматически пропущены при импорте. По умолчанию задачи устанавливаются в статус 【Отключено】, а при аномальной ассоциации данных - в статус 【Ожидает редактирования】.', + changeStatus: 'Изменить статус', + disableMsg: 'Это остановит автоматическое выполнение запланированной задачи. Хотите продолжить?', + enableMsg: 'Это позволит запланированной задаче автоматически выполняться. Хотите продолжить?', + taskType: 'Тип', + record: 'Записи', + viewRecords: 'Записи', + shell: 'Shell', + stop: 'Ручная Остановка', + stopHelper: 'Эта операция принудительно остановит выполнение текущей задачи. Продолжить?', + log: 'Логи резервного копирования', + logHelper: 'Резервное копирование системного лога', + ogHelper1: '1. Системный лог 1Panel', + logHelper2: '2. Лог SSH-входов на сервер', + logHelper3: '3. Все логи сайта', + containerCheckBox: 'В контейнере (не нужно вводить команду контейнера)', + containerName: 'Имя контейнера', + ntp: 'Синхронизация времени', + ntp_helper: 'Вы можете настроить NTP сервер на странице Быстрой настройки в Инструментах.', + app: 'Резервное копирование приложения', + website: 'Резервное копирование сайта', + rulesHelper: 'Поддерживает несколько правил исключения, разделяются английскими запятыми , напр.: *.log,*.sql', + lastRecordTime: 'Время последнего выполнения', + all: 'Все', + failedRecord: 'Неудачные записи', + successRecord: 'Успешные записи', + database: 'Резервное копирование базы данных', + backupArgs: 'Аргументы Резервного Копирования', + backupArgsHelper: + 'Неперечисленные аргументы резервного копирования могут быть введены и выбраны вручную. Например: Введите --no-data и выберите первый вариант из выпадающего списка.', + singleTransaction: + 'Резервное копирование таблиц InnoDB с использованием одной транзакции, подходит для резервного копирования данных большого объема', + quick: 'Чтение данных построчно вместо загрузки всей таблицы в память, подходит для резервного копирования данных большого объема и машин с низкой памятью', + skipLockTables: 'Резервное копирование без блокировки всех таблиц, подходит для высококонкурентных баз данных', + missBackupAccount: 'Не удалось найти учетную запись резервного копирования', + syncDate: 'Время синхронизации', + clean: 'Очистка кэша', + curl: 'Доступ к URL', + taskName: 'Имя', + cronSpec: 'Цикл запуска', + cronSpecDoc: + 'Пользовательское расписание поддерживает только формат [минута час день месяц неделя], например, 0 0 * * *. Подробности см. в официальной документации.', + cronSpecHelper: 'Введите правильный период выполнения', + cleanHelper: + 'Эта операция записывает все записи выполнения задач, файлы резервных копий и файлы логов. Хотите продолжить?', + directory: 'Директория резервного копирования', + sourceDir: 'Директория резервного копирования', + snapshot: 'Снапшот системы', + allOptionHelper: + 'Текущий план задачи - резервное копирование всех [{0}]. Прямое скачивание сейчас не поддерживается. Вы можете проверить список резервных копий в меню [{0}].', + exclusionRules: 'Правило исключения', + exclusionRulesHelper: + 'Выберите или введите правила исключения, нажмите Enter после каждого набора для продолжения. Правила исключения будут применяться ко всем операциям сжатия в этой резервной копии', + default_download_path: 'Ссылка для скачивания по умолчанию', + saveLocal: 'Сохранять локальные резервные копии (столько же, сколько копий в облачном хранилище)', + url: 'URL-адрес', + urlHelper: 'Пожалуйста, введите действительный URL-адрес', + targetHelper: 'Учетные записи резервного копирования управляются в настройках панели.', + withImageHelper: 'Резервное копирование образов из магазина приложений увеличит размер файла снимка.', + ignoreApp: 'Исключить приложения', + withImage: 'Резервная Копия Образа Приложения', + retainCopies: 'Сохранять записи', + retryTimes: 'Количество повторов', + timeout: 'Таймаут', + ignoreErr: 'Игнорировать ошибки', + ignoreErrHelper: + 'Игнорировать ошибки во время резервного копирования для выполнения всех задач резервного копирования', + retryTimesHelper: '0 означает отсутствие повторов после сбоя', + retainCopiesHelper: 'Количество копий для сохранения записей выполнения и логов', + retainCopiesHelper1: 'Количество копий для сохранения файлов резервных копий', + retainCopiesUnit: ' копий (Просмотр)', + cronSpecRule: 'Формат периода выполнения в строке {0} неверен. Пожалуйста, проверьте и попробуйте снова!', + perMonthHelper: 'Выполнять {0} числа каждого месяца в {1}:{2}', + perWeekHelper: 'Выполнять каждую неделю в {0} в {1}:{2}', + perDayHelper: 'Выполнять каждый день в {0}:{1}', + perHourHelper: 'Выполнять каждый час в {0} минут', + perNDayHelper: 'Выполнять каждые {0} дней в {1}:{2}', + perNHourHelper: 'Выполнять каждые {0} часов в {1}', + perNMinuteHelper: 'Выполнять каждые {0} минут', + perNSecondHelper: 'Выполнять каждые {0} секунд', + perMonth: 'Каждый месяц', + perWeek: 'Каждую неделю', + perHour: 'Каждый час', + perNDay: 'Каждые N дней', + perDay: 'Каждый день', + perNHour: 'Каждые N часов', + perNMinute: 'Каждые N минут', + perNSecond: 'Каждые N секунд', + day: 'дней', + dayUnit: 'д', + monday: 'Понедельник', + tuesday: 'Вторник', + wednesday: 'Среда', + thursday: 'Четверг', + friday: 'Пятница', + saturday: 'Суббота', + sunday: 'Воскресенье', + shellContent: 'Скрипт', + errRecord: 'Неправильное логирование', + errHandle: 'Сбой выполнения задачи Cron', + noRecord: 'Запустите задачу Cron, и вы увидите записи здесь.', + cleanData: 'Очистить данные', + cleanRemoteData: 'Удалить удалённые данные', + cleanDataHelper: 'Удалить файл резервной копии, созданный во время этой задачи.', + noLogs: 'Пока нет вывода задачи...', + errPath: 'Ошибка пути резервной копии [{0}], невозможно скачать!', + cutWebsiteLog: 'Ротация логов сайта', + cutWebsiteLogHelper: 'Ротированные файлы логов будут сохранены в директории резервных копий 1Panel.', + syncIpGroup: 'Синхронизировать группы IP WAF', + + requestExpirationTime: 'Время истечения запроса на загрузку (часы)', + unitHours: 'Единица: часы', + alertTitle: 'Плановая задача - {0} «{1}» Оповещение о сбое задачи', + library: { + script: 'Скрипт', + syncNow: 'Синхронизировать Сейчас', + turnOnSync: 'Включить Автосинхронизацию', + turnOnSyncHelper: + 'Включение автосинхронизации будет выполнять автоматическую синхронизацию в ранние утренние часы ежедневно', + turnOffSync: 'Отключить Автосинхронизацию', + turnOffSyncHelper: + 'Отключение автосинхронизации может вызвать задержки синхронизации скриптов, подтвердить?', + isInteractive: 'Интерактивный', + interactive: 'Интерактивный скрипт', + interactiveHelper: + 'Требует ввода пользователя во время выполнения и не может использоваться в запланированных задачах.', + library: 'Библиотека скриптов', + remoteLibrary: 'Удаленная Библиотека Скриптов', + create: 'Добавить скрипт', + edit: 'Редактировать скрипт', + groupHelper: + 'Установите разные группы на основе характеристик скрипта, что позволяет быстрее выполнять операции фильтрации скриптов.', + handleHelper: 'Выполнить сценарий {1} на {0}, продолжить?', + noSuchApp: + 'Служба {0} не обнаружена. Пожалуйста, сначала быстро установите её, используя библиотеку скриптов!', + syncHelper: + 'Выполнить синхронизацию библиотеки системных скриптов? Это действие затрагивает только системные скрипты. Продолжить?', + }, + }, + monitor: { + globalFilter: 'Глобальный Фильтр', + enableMonitor: 'Статус Мониторинга', + storeDays: 'Дни Хранения', + defaultNetwork: 'Сетевая Карта по Умолчанию', + defaultNetworkHelper: 'Опция сетевой карты по умолчанию, отображаемая в интерфейсах мониторинга и обзора', + defaultIO: 'Диск по Умолчанию', + defaultIOHelper: 'Опция диска по умолчанию, отображаемая в интерфейсах мониторинга и обзора', + cleanMonitor: 'Очистить Записи Мониторинга', + cleanHelper: 'Эта операция очистит все записи мониторинга, включая GPU. Продолжить?', + + avgLoad: 'Средняя нагрузка', + loadDetail: 'Детали нагрузки', + resourceUsage: 'Использование', + networkCard: 'Сетевой интерфейс', + read: 'Чтение', + write: 'Запись', + readWriteCount: 'Операции ввода/вывода', + readWriteTime: 'Задержка ввода/вывода', + today: 'Сегодня', + yesterday: 'Вчера', + lastNDay: 'Последние {0} дней', + lastNMonth: 'Последние {0} месяцев', + lastHalfYear: 'Последние полгода', + memory: 'Память', + percent: 'Процент', + cache: 'Кэш', + disk: 'Диск', + network: 'Сеть', + up: 'Исходящий', + down: 'Входящий', + interval: 'Интервал Сбора', + intervalHelper: 'Пожалуйста, введите подходящий интервал сбора мониторинга (5 секунд - 12 часов)', + }, + terminal: { + local: 'Локальный', + defaultConn: 'Соединение по умолчанию', + defaultConnHelper: + 'Эта операция автоматически подключится к узловому терминалу после открытия терминала для 【{0}】. Продолжить?', + withReset: 'Сбросить Информацию о Подключении', + localConnJump: + 'Информация о соединении по умолчанию поддерживается в [Терминал - Настройки]. Если соединение не удается, отредактируйте там!', + localHelper: 'Локальное имя используется только для локальной идентификации системы.', + connLocalErr: + 'Невозможно выполнить автоматическую аутентификацию, пожалуйста, заполните информацию для входа на локальный сервер!', + testConn: 'Проверить подключение', + saveAndConn: 'Сохранить и подключиться', + connTestOk: 'Информация о подключении доступна', + connTestFailed: 'Подключение недоступно, пожалуйста, проверьте информацию подключения.', + host: 'Хост | Хосты', + createConn: 'Новое подключение', + manageGroup: 'Управление группами', + noHost: 'Нет хостов', + groupChange: 'Изменить группу', + expand: 'Развернуть все', + fold: 'Свернуть все', + batchInput: 'Пакетная обработка', + quickCommand: 'Быстрая команда | Быстрые команды', + noSuchCommand: 'В импортированном CSV-файле не найдены данные быстрых команд, проверьте и повторите попытку!', + quickCommandHelper: 'Вы можете использовать быстрые команды внизу страницы "Терминалы -> Терминалы".', + groupDeleteHelper: + 'После удаления группы все подключения в группе будут перемещены в группу по умолчанию. Хотите продолжить?', + command: 'Команда', + quickCmd: 'Быстрая команда', + addHost: 'Добавить', + localhost: 'Локальный хост', + ip: 'Адрес', + authMode: 'Аутентификация', + passwordMode: 'Пароль', + rememberPassword: 'Запомнить информацию аутентификации', + keyMode: 'Приватный ключ', + key: 'Приватный ключ', + keyPassword: 'Пароль приватного ключа', + emptyTerminal: 'В настоящее время нет подключенных терминалов.', + }, + toolbox: { + common: { + toolboxHelper: 'Некоторые проблемы с установкой и использованием описаны в', + }, + swap: { + swap: 'Раздел Swap', + swapHelper1: + 'Размер swap должен быть в 1-2 раза больше физической памяти, регулируется в зависимости от конкретных требований;', + swapHelper2: + 'Перед созданием файла подкачки убедитесь, что на системном диске достаточно свободного места, так как размер файла подкачки будет занимать соответствующее дисковое пространство;', + swapHelper3: + 'Swap может помочь снизить нагрузку на память, но это только альтернатива. Чрезмерная зависимость от swap может привести к снижению производительности системы. Рекомендуется в первую очередь увеличить память или оптимизировать использование памяти приложениями;', + swapHelper4: + 'Рекомендуется регулярно отслеживать использование swap для обеспечения нормальной работы системы.', + swapDeleteHelper: + 'Эта операция удалит раздел Swap {0}. В целях безопасности системы соответствующий файл не будет удален автоматически. Если требуется удаление, выполните его вручную!', + saveHelper: 'Пожалуйста, сначала сохраните текущие настройки!', + saveSwap: 'Сохранение текущей конфигурации изменит размер раздела Swap {0} на {1}. Хотите продолжить?', + swapMin: 'Минимальный размер раздела - 40 КБ. Пожалуйста, измените и попробуйте снова!', + swapMax: 'Максимальное значение для размера раздела - {0}. Пожалуйста, измените и попробуйте снова!', + swapOff: 'Минимальный размер раздела - 40 КБ. Установка значения 0 отключит раздел Swap.', + }, + device: { + dnsHelper: 'DNS сервер', + dnsAlert: + 'Внимание! Изменение конфигурации файла /etc/resolv.conf будет восстановлено до значений по умолчанию после перезагрузки системы.', + dnsHelper1: + 'При наличии нескольких DNS-записей они должны отображаться с новой строки. Например:\n114.114.114.114\n8.8.8.8', + hostsHelper: 'Разрешение имен хостов', + hosts: 'Домен', + hostAlert: 'Скрыты закомментированные записи, нажмите кнопку "Все настройки" для просмотра или настройки', + toolbox: 'Быстрые настройки', + hostname: 'Имя хоста', + passwd: 'Системный пароль', + passwdHelper: 'Вводимые символы не могут включать $ и &', + timeZone: 'Часовой пояс', + localTime: 'Время сервера', + timeZoneChangeHelper: 'Изменение системного часового пояса требует перезапуска службы. Продолжить?', + timeZoneHelper: + 'Если команда "timedatectl" не установлена, вы не сможете изменить часовой пояс. Система использует эту команду для изменения часового пояса.', + timeZoneCN: 'Пекин', + timeZoneAM: 'Лос-Анджелес', + timeZoneNY: 'Нью-Йорк', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'NTP сервер', + hostnameHelper: + 'Изменение имени хоста зависит от команды "hostnamectl". Если команда не установлена, изменение может не удаться.', + userHelper: + 'Имя пользователя зависит от команды "whoami" для получения. Если команда не установлена, получение может не удаться.', + passwordHelper: + 'Изменение пароля зависит от команды "chpasswd". Если команда не установлена, изменение может не удаться.', + hostHelper: + 'В предоставленном содержимом есть пустое значение. Пожалуйста, проверьте и попробуйте снова после изменения!', + dnsCheck: 'Проверить доступность', + dnsOK: 'Информация о конфигурации DNS доступна!', + dnsTestFailed: 'Информация о конфигурации DNS недоступна.', + }, + fail2ban: { + sshPort: 'Прослушивание SSH порта', + sshPortHelper: 'Текущий Fail2ban прослушивает порт SSH-подключения хоста', + unActive: 'Служба Fail2ban в настоящее время не включена.', + operation: 'Вы собираетесь выполнить операцию "{0}" над службой Fail2ban. Хотите продолжить?', + fail2banChange: 'Изменение конфигурации Fail2ban', + ignoreHelper: 'IP-адреса из белого списка будут игнорироваться при блокировке. Хотите продолжить?', + bannedHelper: 'IP-адреса из черного списка будут заблокированы сервером. Хотите продолжить?', + maxRetry: 'Максимальное количество попыток', + banTime: 'Время блокировки', + banTimeHelper: 'Время блокировки по умолчанию 10 минут, -1 означает постоянную блокировку', + banTimeRule: 'Пожалуйста, введите допустимое время блокировки или -1', + banAllTime: 'Постоянная блокировка', + findTime: 'Период обнаружения', + banAction: 'Действие блокировки', + banActionOption: 'Блокировать указанные IP-адреса используя {0}', + allPorts: ' (Все порты)', + ignoreIP: 'Белый список IP', + bannedIP: 'Черный список IP', + logPath: 'Путь к логам', + logPathHelper: 'По умолчанию /var/log/secure или /var/log/auth.log', + }, + ftp: { + ftp: 'FTP аккаунт | FTP аккаунты', + notStart: 'Служба FTP в настоящее время не запущена, пожалуйста, сначала запустите её!', + operation: 'Это выполнит операцию "{0}" над службой FTP. Хотите продолжить?', + noPasswdMsg: + 'Невозможно получить текущий пароль FTP аккаунта, пожалуйста, установите пароль и попробуйте снова!', + enableHelper: 'Включение выбранного FTP аккаунта восстановит его права доступа. Хотите продолжить?', + disableHelper: 'Отключение выбранного FTP аккаунта отзовет его права доступа. Хотите продолжить?', + syncHelper: 'Синхронизировать данные FTP аккаунта между сервером и базой данных. Хотите продолжить?', + dirSystem: + 'Это системный каталог. Его изменение может вызвать сбой системы. Пожалуйста, измените и повторите попытку!', + dirHelper: 'Для включения FTP требуется изменение прав доступа к каталогу - выбирайте осторожно', + dirMsg: 'Включение FTP изменит права доступа для всего каталога {0}. Продолжить?', + }, + clam: { + clam: 'Антивирусное сканирование', + cron: 'Запланированное сканирование', + cronHelper: 'Профессиональная версия поддерживает функцию запланированного сканирования', + specErr: 'Ошибка формата расписания выполнения, пожалуйста, проверьте и попробуйте снова!', + disableMsg: + 'Остановка запланированного выполнения предотвратит автоматический запуск этой задачи сканирования. Хотите продолжить?', + enableMsg: + 'Включение запланированного выполнения позволит этой задаче сканирования запускаться автоматически через регулярные интервалы. Хотите продолжить?', + showFresh: 'Показать службу обновления сигнатур', + hideFresh: 'Скрыть службу обновления сигнатур', + clamHelper: + 'Минимальная рекомендуемая конфигурация для ClamAV: 3 ГБ ОЗУ или больше, одноядерный процессор с частотой 2.0 ГГц или выше, и не менее 5 ГБ свободного места на жестком диске.', + notStart: 'Служба ClamAV в настоящее время не запущена, пожалуйста, сначала запустите её!', + removeRecord: 'Удалить файлы отчетов', + noRecords: 'Нажмите кнопку "Запустить" для начала сканирования, и вы увидите записи здесь.', + removeInfected: 'Удалить зараженные файлы', + removeInfectedHelper: + 'Удалить зараженные файлы, обнаруженные во время задачи, для обеспечения безопасности и нормальной работы сервера.', + clamCreate: 'Создать правило сканирования', + infectedStrategy: 'Стратегия для зараженных файлов', + removeHelper: 'Удалить зараженные файлы, выбирайте осторожно!', + move: 'Переместить', + moveHelper: 'Переместить зараженные файлы в указанную директорию', + copyHelper: 'Копировать зараженные файлы в указанную директорию', + none: 'Ничего не делать', + noneHelper: 'Не предпринимать действий с зараженными файлами', + scanDir: 'Директория сканирования', + infectedDir: 'Директория зараженных файлов', + scanDate: 'Дата сканирования', + scanResult: 'Последние строки лога сканирования', + tail: 'Строк', + infectedFiles: 'Зараженные файлы', + log: 'Подробности', + clamConf: 'Демон Clam AV', + clamLog: '@:toolbox.clam.clamConf логи', + freshClam: 'FreshClam', + freshClamLog: '@:toolbox.clam.freshClam логи', + alertHelper: 'Профессиональная версия поддерживает запланированное сканирование и SMS-оповещения', + alertTitle: 'Задача антивирусного сканирования 「{0}」 обнаружила зараженный файл', + }, + }, + logs: { + core: 'Сервис панели', + agent: 'Мониторинг узлов', + panelLog: 'Логи панели', + operation: 'Логи операций', + login: 'Логи входа', + loginIP: 'IP входа', + loginAddress: 'Адрес входа', + loginAgent: 'Агент входа', + loginStatus: 'Статус', + system: 'Системные логи', + deleteLogs: 'Очистить логи', + resource: 'Ресурс', + detail: { + dashboard: 'Обзор', + ai: 'AI', + groups: 'Группы', + hosts: 'Хосты', + apps: 'Приложения', + websites: 'Вебсайты', + containers: 'Контейнеры', + files: 'Управление файлами', + runtimes: 'Среды выполнения', + process: 'Управление процессами', + toolbox: 'Инструментальный ящик', + backups: 'Резервное копирование / Восстановление', + tampers: 'Защита от подделки', + xsetting: 'Настройки интерфейса', + logs: 'Аудит журналов', + settings: 'Настройки панели', + cronjobs: 'Запланированные задачи', + waf: 'WAF', + databases: 'Базы данных', + licenses: 'лицензии', + nodes: 'ноды', + commands: 'Быстрые команды', + }, + websiteLog: 'Логи веб-сайта', + runLog: 'Логи выполнения', + errLog: 'Логи ошибок', + }, + file: { + fileDirNum: '{0} каталогов, {1} файлов,', + currentDir: 'Текущий каталог', + dir: 'Папка', + upload: 'Загрузить', + uploadFile: '@:file.upload @.lower:file.file', + uploadDirectory: '@:file.upload @.lower:file.dir', + download: 'Скачать', + fileName: 'Имя файла', + search: 'Поиск', + mode: 'Права доступа', + editPermissions: '@:file.mode', + owner: 'Владелец', + file: 'Файл', + remoteFile: 'Удалённая загрузка', + share: 'Поделиться', + sync: 'Синхронизация данных', + size: 'Размер', + updateTime: 'Изменен', + rename: 'Переименовать', + role: 'Права доступа', + info: 'Атрибуты', + linkFile: 'Символическая ссылка', + batchoperation: 'Пакетная операция', + shareList: 'Список общих ресурсов', + zip: 'Сжатый', + group: 'Группа', + path: 'Путь', + public: 'Другие', + setRole: 'Установить права', + link: 'Ссылка на файл', + rRole: 'Чтение', + wRole: 'Запись', + xRole: 'Исполнение', + name: 'Имя', + compress: 'Сжать', + deCompress: 'Распаковать', + compressType: 'Формат сжатия', + compressDst: 'Путь сжатия', + replace: 'Перезаписать существующие файлы', + compressSuccess: 'Успешно сжато', + deCompressSuccess: 'Успешно распаковано', + deCompressDst: 'Путь распаковки', + linkType: 'Тип ссылки', + softLink: 'Символическая ссылка', + hardLink: 'Жесткая ссылка', + linkPath: 'Путь ссылки', + selectFile: 'Выбрать файл', + downloadUrl: 'Удаленный URL', + downloadStart: 'Загрузка начата', + moveSuccess: 'Успешно перемещено', + copySuccess: 'Успешно скопировано', + pasteMsg: 'Нажмите кнопку «Вставить» в правом верхнем углу целевой директории', + move: 'Переместить', + calculate: 'Вычислить', + remark: 'Примечание', + setRemark: 'Задать примечание', + remarkPrompt: 'Введите примечание', + remarkPlaceholder: 'Примечание', + remarkToggle: 'Примечания', + remarkToggleTip: 'Загружать примечания файлов', + canNotDeCompress: 'Невозможно распаковать этот файл', + uploadSuccess: 'Успешно загружено', + downloadProcess: 'Прогресс загрузки', + downloading: 'Загрузка...', + infoDetail: 'Свойства файла', + root: 'Корневая директория', + list: 'Список файлов', + sub: 'Подпапки', + downloadSuccess: 'Успешно скачано', + theme: 'Тема', + language: 'Язык', + eol: 'Конец строки', + copyDir: 'Копировать', + paste: 'Вставить', + changeOwner: 'Изменить пользователя и группу', + containSub: 'Применить изменение прав рекурсивно', + ownerHelper: + 'Пользователь по умолчанию для среды PHP: группа пользователей 1000:1000, нормально что пользователи внутри и снаружи контейнера показывают несоответствия', + searchHelper: 'Поддерживает подстановочные знаки, такие как *', + uploadFailed: '[{0}] Ошибка загрузки файла', + fileUploadStart: 'Загрузка [{0}]....', + currentSelect: 'Текущий выбор: ', + unsupportedType: 'Неподдерживаемый тип файла', + deleteHelper: 'Вы уверены, что хотите удалить следующие файлы? По умолчанию они будут помещены в корзину', + fileHelper: 'Примечание:\n1. Результаты поиска нельзя сортировать.\n2. Папки нельзя сортировать по размеру.', + forceDeleteHelper: 'Удалить файл навсегда (без помещения в корзину, удалить напрямую)', + recycleBin: 'Корзина', + sourcePath: 'Исходный путь', + deleteTime: 'Время удаления', + confirmReduce: 'Вы уверены, что хотите восстановить следующие файлы?', + reduceSuccess: 'Успешно восстановлено', + reduce: 'Восстановление', + reduceHelper: + 'Если файл или директория с таким же именем существует в исходном пути, он будет перезаписан. Хотите продолжить?', + clearRecycleBin: 'Очистить', + clearRecycleBinHelper: 'Вы хотите очистить корзину?', + favorite: 'Избранное', + removeFavorite: 'Удалить из избранного?', + addFavorite: 'Добавить/Удалить в избранное', + clearList: 'Очистить список', + deleteRecycleHelper: 'Вы уверены, что хотите навсегда удалить следующие файлы?', + typeErrOrEmpty: '[{0}] неверный тип файла или пустая папка', + dropHelper: 'Перетащите сюда файлы для загрузки', + fileRecycleBin: 'Включить корзину', + fileRecycleBinMsg: '{0} корзина', + wordWrap: 'Автоматический перенос строк', + deleteHelper2: 'Вы уверены, что хотите удалить выбранный файл? Операцию удаления нельзя отменить', + ignoreCertificate: 'Разрешить небезопасные подключения к серверу', + ignoreCertificateHelper: + 'Разрешение небезопасных подключений к серверу может привести к утечке или подмене данных. Используйте эту опцию только если доверяете источнику загрузки.', + uploadOverLimit: 'Количество файлов превышает 1000! Пожалуйста, сожмите и загрузите', + clashDitNotSupport: 'Имена файлов не должны содержать .1panel_clash', + clashDeleteAlert: 'Папку "Корзина" нельзя удалить', + clashOpenAlert: 'Пожалуйста, нажмите кнопку "Корзина" чтобы открыть директорию корзины', + right: 'Вперед', + back: 'Назад', + top: 'Вернуться', + up: 'Назад', + openWithVscode: 'Открыть в VS Code', + vscodeHelper: 'Пожалуйста, убедитесь что VS Code установлен локально и настроен плагин SSH Remote', + saveContentAndClose: 'Файл был изменен, хотите сохранить и закрыть его?', + saveAndOpenNewFile: 'Файл был изменен, хотите сохранить и открыть новый файл?', + noEdit: 'Файл не был изменен, не нужно этого делать!', + noNameFolder: 'Безымянная папка', + noNameFile: 'Безымянный файл', + minimap: 'Мини-карта кода', + fileCanNotRead: 'Файл не может быть прочитан', + previewTruncated: 'Файл слишком большой, отображается только последняя часть', + previewEmpty: 'Файл пуст или не является текстовым файлом', + previewLargeFile: 'Предпросмотр', + panelInstallDir: 'Директорию установки 1Panel нельзя удалить', + wgetTask: 'Задача загрузки', + existFileTitle: 'Предупреждение о файле с тем же именем', + existFileHelper: 'Загруженный файл содержит файл с таким же именем. Заменить его?', + existFileSize: 'Размер файла (новый -> старый)', + existFileDirHelper: 'Выбранный файл/папка имеет дублирующееся имя. Пожалуйста, действуйте осторожно!\n', + coverDirHelper: 'При выборе перезаписываемой папки она будет скопирована в целевой путь!', + noSuchFile: 'Файл или каталог не найдены. Пожалуйста, проверьте и повторите попытку.', + setting: 'настройка', + showHide: 'Показывать скрытые файлы', + noShowHide: 'Не показывать скрытые файлы', + cancelUpload: 'Отменить загрузку', + cancelUploadHelper: 'Отменить загрузку или нет, после отмены список загрузок будет очищен.', + keepOneTab: 'Необходимо оставить как минимум одну вкладку', + notCanTab: 'Невозможно добавить больше вкладок', + convert: 'Конвертировать формат', + converting: 'Конвертация в', + fileCanNotConvert: 'Этот файл не поддерживает конвертацию формата', + formatType: 'Тип формата', + sourceFormat: 'Исходный формат', + sourceFile: 'Исходный файл', + saveDir: 'Каталог сохранения', + deleteSourceFile: 'Удалить исходный файл', + convertHelper: 'Конвертировать выбранные файлы в другой формат', + convertHelper1: 'Пожалуйста, выберите файлы для конвертации', + execConvert: 'Начать конвертацию. Вы можете просмотреть журналы конвертации в Центре задач', + convertLogs: 'Журналы конвертации', + formatConvert: 'Конвертация формата', + }, + ssh: { + autoStart: 'Автозапуск', + enable: 'Включить автозапуск', + disable: 'Отключить автозапуск', + sshAlert: + 'Данные списка отсортированы по дате входа. Изменение часового пояса или выполнение других операций может вызвать отклонения в дате логов входа.', + sshAlert2: + 'Вы можете использовать "Fail2ban" в "Инструментах" для блокировки IP-адресов, пытающихся выполнить брутфорс-атаки, это повысит безопасность хоста.', + sshOperate: 'Будет выполнена операция "{0}" над службой SSH. Хотите продолжить?', + sshChange: 'Настройки SSH', + sshChangeHelper: 'Это действие изменит "{0}" на "{1}". Хотите продолжить?', + sshFileChangeHelper: + 'Изменение конфигурационного файла может повлиять на доступность службы. Будьте осторожны при выполнении этой операции. Хотите продолжить?', + port: 'Порт', + portHelper: 'Указывает порт, который прослушивает служба SSH.', + listenAddress: 'Адрес прослушивания', + allV4V6: '0.0.0.0:{0}(IPv4) и :::{0}(IPv6)', + listenHelper: + 'Если оставить пустыми настройки IPv4 и IPv6, будет прослушиваться "0.0.0.0:{0}(IPv4)" и ":::{0}(IPv6)".', + addressHelper: 'Указывает адрес, который прослушивает служба SSH.', + permitRootLogin: 'Разрешить вход пользователя root', + rootSettingHelper: 'Метод входа по умолчанию для пользователя root - "Разрешить вход по SSH".', + rootHelper1: 'Разрешить вход по SSH', + rootHelper2: 'Отключить вход по SSH', + rootHelper3: 'Разрешен только вход по ключу', + rootHelper4: 'Можно выполнять только предопределенные команды. Другие операции недоступны.', + passwordAuthentication: 'Аутентификация по паролю', + pwdAuthHelper: 'Включить ли аутентификацию по паролю. Этот параметр включен по умолчанию.', + pubkeyAuthentication: 'Аутентификация по ключу', + privateKey: 'Приватный ключ', + publicKey: 'Публичный ключ', + password: 'Пароль', + createMode: 'Способ создания', + generate: 'Автогенерация', + unSyncPass: 'Пароль ключа не может быть синхронизирован', + syncHelper: + 'Операция синхронизации удалит недействительные ключи и синхронизирует новые полные ключевые пары. Продолжить?', + input: 'Ручной ввод', + import: 'Загрузка файла', + authKeys: 'Ключи Авторизации', + authKeysHelper: 'Сохранить текущую информацию об открытом ключе?', + pubkey: 'Информация о ключе', + pubKeyHelper: 'Текущая информация о ключе действительна только для пользователя {0}', + encryptionMode: 'Режим шифрования', + passwordHelper: 'Может содержать от 6 до 10 цифр и английских букв в разных регистрах', + reGenerate: 'Перегенерировать ключ', + keyAuthHelper: 'Включить ли аутентификацию по ключу.', + useDNS: 'useDNS', + dnsHelper: 'Управляет включением функции DNS-разрешения на SSH-сервере для проверки подлинности подключения.', + analysis: 'Статистическая информация', + denyHelper: + 'Выполнение операции "запретить" для следующих адресов. После установки IP будет запрещен доступ к серверу. Хотите продолжить?', + acceptHelper: + 'Выполнение операции "разрешить" для следующих адресов. После установки IP восстановит нормальный доступ. Хотите продолжить?', + noAddrWarning: 'В настоящее время не выбраны адреса [{0}]. Пожалуйста, проверьте и попробуйте снова!', + loginLogs: 'Логи входа', + loginMode: 'Режим', + authenticating: 'Ключ', + publickey: 'Ключ', + belong: 'Принадлежность', + local: 'Локальный', + session: 'Сессия | Сессии', + loginTime: 'Время входа', + loginIP: 'IP входа', + stopSSHWarn: 'Отключить это SSH-соединение', + }, + setting: { + panel: 'Панель', + user: 'Пользователь панели', + userChange: 'Изменить пользователя панели', + userChangeHelper: 'Изменение пользователя панели приведет к выходу из системы. Продолжить?', + passwd: 'Пароль панели', + emailHelper: 'Для восстановления пароля', + watermark: 'Настройки Водяного Знака', + watermarkContent: 'Содержимое Водяного Знака', + contentHelper: + '{0} представляет имя узла, {1} представляет адрес узла. Вы можете использовать переменные или ввести пользовательские имена.', + watermarkColor: 'Цвет Водяного Знака', + watermarkFont: 'Размер Шрифта Водяного Знака', + watermarkHeight: 'Высота Водяного Знака', + watermarkWidth: 'Ширина Водяного Знака', + watermarkRotate: 'Угол Поворота', + watermarkGap: 'Интервал', + watermarkCloseHelper: 'Вы уверены, что хотите отключить настройки системного водяного знака?', + watermarkOpenHelper: 'Вы уверены, что хотите сохранить текущие настройки системного водяного знака?', + title: 'Псевдоним панели', + panelPort: 'Порт панели', + titleHelper: + 'Поддерживаются строки длиной от 3 до 30 символов, включающие буквы, китайские иероглифы, цифры, пробелы и распространённые специальные символы', + portHelper: + 'Рекомендуемый диапазон портов от 8888 до 65535. Примечание: Если на сервере есть группа безопасности, заранее разрешите новый порт в группе безопасности', + portChange: 'Изменение порта', + portChangeHelper: 'Изменить порт службы и перезапустить службу. Хотите продолжить?', + theme: 'Тема', + menuTabs: 'Вкладки меню', + dark: 'Тёмная', + darkGold: 'Тёмное золото', + light: 'Светлая', + auto: 'Как в системе', + language: 'Язык', + languageHelper: 'По умолчанию следует языку браузера. Этот параметр действует только в текущем браузере', + sessionTimeout: 'Время сессии', + sessionTimeoutError: 'Минимальное время сессии 300 секунд', + sessionTimeoutHelper: 'Панель автоматически выйдет из системы, если не будет операций более {0} секунд.', + systemIP: 'Адрес доступа по умолчанию', + systemIPHelper: + 'Перенаправления приложений, доступ к контейнерам и другие функции будут использовать этот адрес для маршрутизации. Каждый узел можно настроить с разным адресом.', + proxy: 'Прокси', + proxyHelper: 'После настройки прокси-сервера он будет действовать в следующих сценариях:', + proxyHelper1: + 'Загрузка установочного пакета и синхронизация из магазина приложений (только профессиональная версия)', + proxyHelper2: 'Обновление системы и получение информации об обновлениях (только профессиональная версия)', + proxyHelper4: 'Сеть Docker будет доступна через прокси-сервер (только профессиональная версия)', + proxyHelper3: 'Проверка и синхронизация системной лицензии', + proxyHelper5: 'Единая загрузка и синхронизация системных скрипт-библиотек (функция Профессиональной версии)', + proxyHelper6: 'Подать заявку на сертификат (функция профессиональной версии)', + proxyType: 'Тип прокси', + proxyUrl: 'Адрес прокси', + proxyPort: 'Порт прокси', + proxyPasswdKeep: 'Запомнить пароль', + proxyDocker: 'Прокси Docker', + proxyDockerHelper: + 'Синхронизировать конфигурацию прокси-сервера с Docker, поддержка офлайн загрузки образов и других операций', + syncToNode: 'Синхронизация с дочерним узлом', + syncToNodeHelper: 'Синхронизация настроек с другими узлами', + nodes: 'Узлы', + selectNode: 'Выберите узел', + selectNodeError: 'Пожалуйста, выберите узел', + apiInterface: 'Включить API', + apiInterfaceClose: 'После закрытия API-интерфейсы будут недоступны. Хотите продолжить?', + apiInterfaceHelper: 'Разрешить сторонним приложениям доступ к API.', + apiInterfaceAlert1: + 'Не включайте в производственной среде, так как это может повысить риски безопасности сервера.', + apiInterfaceAlert2: + 'Не используйте сторонние приложения для вызова API во избежание потенциальных угроз безопасности.', + apiInterfaceAlert3: 'API документация', + apiInterfaceAlert4: 'Руководство по использованию', + apiKey: 'API ключ', + apiKeyHelper: 'API ключ используется сторонними приложениями для доступа к API.', + ipWhiteList: 'Белый список IP', + ipWhiteListEgs: 'По одному в строке. Например,\n172.161.10.111\n172.161.10.0/24', + ipWhiteListHelper: + 'IP-адреса из белого списка могут получить доступ к API, 0.0.0.0/0 (все IPv4), ::/0 (все IPv6)', + apiKeyValidityTime: 'Срок действия ключа интерфейса', + apiKeyValidityTimeEgs: 'Срок действия ключа интерфейса (в единицах)', + apiKeyValidityTimeHelper: + 'Интерфейс времени метки между текущей меткой времени на момент запроса действителен (в единицах), установлен как 0, не проводится проверка метки времени', + apiKeyReset: 'Сброс ключа интерфейса', + apiKeyResetHelper: + 'связанный ключевой сервис станет недействительным. Пожалуйста, добавьте новый ключ к сервису', + confDockerProxy: 'Настроить прокси docker', + restartNowHelper: 'Настройка прокси Docker требует перезапуска службы Docker.', + restartNow: 'Перезапустить немедленно', + restartLater: 'Перезагрузить вручную позже', + systemIPWarning: + 'Текущий узел не имеет настроенного адреса доступа по умолчанию. Пожалуйста, перейдите в настройки панели для его настройки!', + systemIPWarning1: 'Текущий адрес сервера установлен на {0}, быстрое перенаправление невозможно!', + defaultNetwork: 'Сетевой интерфейс', + syncTime: 'Время сервера', + timeZone: 'Часовой пояс', + timeZoneChangeHelper: 'Изменение часового пояса требует перезапуска службы. Хотите продолжить?', + timeZoneHelper: + 'Изменение часового пояса зависит от системной службы timedatectl. Вступит в силу после перезапуска службы 1Panel.', + timeZoneCN: 'Пекин', + timeZoneAM: 'Лос-Анджелес', + timeZoneNY: 'Нью-Йорк', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'NTP сервер', + syncSiteHelper: + 'Эта операция будет использовать {0} как источник для синхронизации системного времени. Хотите продолжить?', + changePassword: 'Изменить пароль', + oldPassword: 'Текущий пароль', + newPassword: 'Новый пароль', + retryPassword: 'Подтвердите пароль', + noSpace: 'Вводимая информация не может включать пробелы', + duplicatePassword: 'Новый пароль не может совпадать с текущим, пожалуйста, введите заново!', + diskClean: 'Очистка кэша', + developerMode: 'Режим предварительного просмотра', + developerModeHelper: + 'Вы сможете опробовать новые функции и исправления до их широкого релиза и оставить ранний отзыв.', + thirdParty: 'Сторонние аккаунты', + noTypeForCreate: 'В настоящее время не создано типов резервного копирования', + LOCAL: 'Диск сервера', + OSS: 'Ali OSS', + S3: 'Amazon S3', + mode: 'Режим', + MINIO: 'MinIO', + SFTP: 'SFTP', + WebDAV: 'WebDAV', + WebDAVAlist: 'Подключение WebDAV к Alist можно найти в официальной документации', + OneDrive: 'Microsoft OneDrive', + isCN: 'Китайский интернет', + isNotCN: 'Международная версия', + client_id: 'ID клиента', + client_secret: 'Секрет клиента', + redirect_uri: 'URL перенаправления', + onedrive_helper: 'Пользовательская конфигурация описана в официальной документации', + refreshTime: 'Время обновления токена', + refreshStatus: 'Статус обновления токена', + backupDir: 'Директория резервных копий', + codeWarning: 'Текущий формат кода авторизации неверен, пожалуйста, проверьте еще раз!', + code: 'Код авторизации', + codeHelper: + 'Пожалуйста, нажмите кнопку "Получить", затем войдите в OneDrive и скопируйте содержимое после "code" в перенаправленной ссылке. Вставьте его в это поле ввода. Подробные инструкции смотрите в официальной документации.', + googleHelper: + 'Сначала создайте приложение Google и получите информацию о клиенте, заполните форму и нажмите кнопку получения. Конкретные операции см. в официальной документации.', + loadCode: 'Получить', + COS: 'Tencent COS', + ap_beijing_1: 'Пекин Зона 1', + ap_beijing: 'Пекин', + ap_nanjing: 'Нанкин', + ap_shanghai: 'Шанхай', + ap_guangzhou: 'Гуанчжоу', + ap_chengdu: 'Чэнду', + ap_chongqing: 'Чунцин', + ap_shenzhen_fsi: 'Шэньчжэнь Финансовый', + ap_shanghai_fsi: 'Шанхай Финансовый', + ap_beijing_fsi: 'Пекин Финансовый', + ap_hongkong: 'Гонконг, Китай', + ap_singapore: 'Сингапур', + ap_mumbai: 'Мумбаи', + ap_jakarta: 'Джакарта', + ap_seoul: 'Сеул', + ap_bangkok: 'Бангкок', + ap_tokyo: 'Токио', + na_siliconvalley: 'Силиконовая долина (США Запад)', + na_ashburn: 'Ашберн (США Восток)', + na_toronto: 'Торонто', + sa_saopaulo: 'Сан-Паулу', + eu_frankfurt: 'Франкфурт', + KODO: 'Qiniu Kodo', + scType: 'Тип хранилища', + typeStandard: 'Стандартный', + typeStandard_IA: 'Стандартный_IA', + typeArchive: 'Архивный', + typeDeep_Archive: 'Глубокий_Архив', + scLighthouse: 'По умолчанию, Легковесное объектное хранилище поддерживает только этот тип хранилища', + scStandard: + 'Стандартное хранилище подходит для бизнес-сценариев с большим количеством горячих файлов, требующих мгновенного доступа, частого обмена данными и т.д.', + scStandard_IA: + 'Низкочастотное хранилище подходит для бизнес-сценариев с относительно низкой частотой доступа и хранит данные не менее 30 дней.', + scArchive: 'Архивное хранилище подходит для бизнес-сценариев с крайне низкой частотой доступа.', + scDeep_Archive: + 'Долговечное холодное хранилище подходит для бизнес-сценариев с крайне низкой частотой доступа.', + archiveHelper: + 'Файлы архивного хранилища нельзя скачать напрямую, сначала их нужно восстановить через веб-сайт соответствующего облачного провайдера. Пожалуйста, используйте с осторожностью!', + backupAlert: + 'Если облачный провайдер совместим с протоколом S3, вы можете напрямую использовать Amazon S3 для резервного копирования.', + domain: 'Домен ускорения', + backupAccount: 'Аккаунт резервного копирования | Аккаунты резервного копирования', + loadBucket: 'Получить корзину', + accountName: 'Имя аккаунта', + accountKey: 'Ключ аккаунта', + address: 'Адрес', + path: 'Путь', + + safe: 'Безопасность', + passkey: 'Passkey', + passkeyManage: 'Управление', + passkeyHelper: 'Для быстрого входа можно привязать до 5 passkey', + passkeyRequireSSL: 'Включите HTTPS, чтобы использовать passkey', + passkeyNotSupported: 'Текущий браузер или среда не поддерживает passkey', + passkeyCount: 'Привязано {0}/{1}', + passkeyName: 'Название', + passkeyNameHelper: 'Введите название для различения устройств', + passkeyAdd: 'Добавить passkey', + passkeyCreatedAt: 'Создано', + passkeyLastUsedAt: 'Последнее использование', + passkeyDeleteConfirm: 'После удаления эту passkey нельзя использовать для входа. Продолжить?', + passkeyLimit: 'Можно привязать до 5 passkey', + passkeyFailed: + 'Не удалось зарегистрировать passkey, пожалуйста, убедитесь, что сертификат SSL панели является доверенным', + bindInfo: 'IP-адрес', + bindAll: 'Прослушивать все', + bindInfoHelper: + 'Изменение адреса прослушивания службы или протокола может привести к недоступности службы. Хотите продолжить?', + ipv6: 'Прослушивать IPv6', + bindAddress: 'IP-адрес прослушивания', + entrance: 'Точка входа', + showEntrance: 'Показывать уведомление об отключении на странице "Обзор"', + entranceHelper: + 'Включение безопасной точки входа позволит входить в панель только через указанную точку входа.', + entranceError: + 'Пожалуйста, введите безопасную точку входа длиной 5-116 символов, поддерживаются только цифры или буквы.', + entranceInputHelper: 'Оставьте пустым, чтобы отключить безопасную точку входа.', + randomGenerate: 'Случайно', + expirationTime: 'Дата истечения', + unSetting: 'Не задано', + noneSetting: 'Установите срок действия пароля панели. После истечения срока необходимо сбросить пароль', + expirationHelper: 'Если срок действия пароля [0] дней, функция истечения срока действия пароля отключена', + days: 'Дней до истечения', + expiredHelper: 'Текущий пароль истек. Пожалуйста, измените пароль снова.', + timeoutHelper: + '[ {0} дней ] Срок действия пароля панели скоро истечет. После истечения срока необходимо сбросить пароль', + complexity: 'Проверка сложности', + complexityHelper: + 'После включения правило проверки пароля будет: 8-30 символов, включая английские буквы, цифры и как минимум два специальных символа.', + bindDomain: 'Привязать домен', + unBindDomain: 'Отвязать домен', + panelSSL: 'SSL панели', + unBindDomainHelper: + 'Действие по отвязке доменного имени может привести к небезопасности системы. Хотите продолжить?', + bindDomainHelper: 'После привязки домена только этот домен сможет получить доступ к службе 1Panel.', + bindDomainHelper1: 'Оставьте пустым, чтобы отключить привязку доменного имени.', + bindDomainWarning: + 'После привязки домена вы будете выходить из системы и сможете получить доступ к службе 1Panel только через указанное в настройках доменное имя. Хотите продолжить?', + allowIPs: 'Авторизованные IP', + unAllowIPs: 'Неавторизованные IP', + unAllowIPsWarning: + 'Авторизация пустого IP позволит всем IP получить доступ к системе, что может привести к небезопасности системы. Хотите продолжить?', + allowIPsHelper: + 'После установки списка авторизованных IP-адресов только IP-адреса из списка смогут получить доступ к службе панели.', + allowIPsWarning: + 'После установки списка авторизованных IP-адресов только IP-адреса из списка смогут получить доступ к службе панели. Хотите продолжить?', + allowIPsHelper1: 'Оставьте пустым, чтобы отключить ограничение IP-адресов.', + allowIPEgs: 'По одному в строке. Например,\n172.16.10.111\n172.16.10.0/24', + mfa: '2FA авторизация', + mfaClose: 'Отключение MFA снизит безопасность службы. Хотите продолжить?', + secret: 'Секрет', + mfaInterval: 'Интервал обновления(с)', + mfaTitleHelper: + 'Заголовок используется для различения разных хостов 1Panel. Отсканируйте снова или вручную добавьте секретный ключ после изменения заголовка.', + mfaIntervalHelper: 'Отсканируйте снова или вручную добавьте секретный ключ после изменения времени обновления.', + mfaAlert: + 'Одноразовый токен - это динамически генерируемое 6-значное число, основанное на текущем времени. Убедитесь, что время сервера синхронизировано.', + mfaHelper: 'После включения потребуется проверка одноразового токена.', + mfaHelper1: 'Загрузите приложение-аутентификатор, например,', + mfaHelper2: + 'Чтобы получить одноразовый токен, отсканируйте следующий QR-код с помощью приложения-аутентификатора или скопируйте секретный ключ в приложение аутентификации.', + mfaHelper3: 'Введите шесть цифр из приложения', + mfaCode: 'Одноразовый токен', + sslChangeHelper: 'Изменить настройку https и перезапустить службу. Хотите продолжить?', + sslDisable: 'Отключить', + sslDisableHelper: + 'Если служба https отключена, необходимо перезапустить панель, чтобы изменения вступили в силу. Хотите продолжить?', + noAuthSetting: 'Unauthorized', + noAuthSettingHelper: + 'Когда пользователи не входят через указанную точку входа безопасности или не получают доступ к панели с указанного IP или доменного имени, этот ответ может скрыть характеристики панели.', + responseSetting: 'Настройка ответа', + help200: 'Страница помощи', + error400: 'Неверный запрос', + error401: 'Не авторизован', + error403: 'Запрещено', + error404: 'Не найдено', + error408: 'Тайм-аут запроса', + error416: 'Диапазон не удовлетворяется', + error444: 'Соединение закрыто', + error500: 'Ошибка сервера', + + https: 'Настройка HTTPS для панели повышает безопасность доступа', + strictHelper: 'Трафик без HTTPS не может подключиться к панели', + muxHelper: + 'Панель будет прослушивать порты HTTP и HTTPS и перенаправлять HTTP на HTTPS, но это может снизить безопасность', + certType: 'Тип сертификата', + selfSigned: 'Самоподписанный', + selfSignedHelper: + 'Браузеры могут не доверять самоподписанным сертификатам и отображать предупреждения безопасности.', + select: 'Выбрать', + domainOrIP: 'Домен или IP:', + timeOut: 'Тайм-аут', + rootCrtDownload: 'Скачать корневой сертификат', + primaryKey: 'Закрытый ключ', + certificate: 'Сертификат', + backupJump: + 'Файлы резервной копии отсутствуют в текущем списке резервных копий, попробуйте скачать из директории файлов и импортировать для резервного копирования.', + + snapshot: 'Снапшот | Снапшоты', + noAppData: 'Нет доступных системных приложений для выбора', + noBackupData: 'Нет доступных данных для резервного копирования', + stepBaseData: 'Основные данные', + stepAppData: 'Системные приложения', + stepPanelData: 'Системные данные', + stepBackupData: 'Резервные данные', + stepOtherData: 'Другие данные', + operationLog: 'Сохранять журнал операций', + loginLog: 'Сохранять журнал доступа', + systemLog: 'Сохранять системный журнал', + taskLog: 'Сохранять журнал задач', + monitorData: 'Сохранять данные мониторинга', + dockerConf: 'Сохранять Конфигурация Docker', + selectAllImage: 'Резервное копирование всех образов приложений', + logLabel: 'Журнал', + agentLabel: 'Конфигурация узла', + appDataLabel: 'Данные приложения', + appImage: 'Образ приложения', + appBackup: 'Резервная копия приложения', + backupLabel: 'Каталог резервных копий', + confLabel: 'Конфигурационные файлы', + dockerLabel: 'Контейнеры', + taskLabel: 'Планировщик задач', + resourceLabel: 'Каталог ресурсов приложения', + runtimeLabel: 'Среда выполнения', + appLabel: 'Приложение', + databaseLabel: 'База данных', + snapshotLabel: 'Файлы снимков', + websiteLabel: 'Веб-сайт', + directoryLabel: 'Каталог', + appStoreLabel: 'Магазин приложений', + shellLabel: 'Скрипт', + tmpLabel: 'Временный каталог', + sslLabel: 'Каталог сертификатов', + reCreate: 'Не удалось создать снимок', + reRollback: 'Не удалось откатить снимок', + deleteHelper: + 'Все файлы снапшотов, включая те, что находятся в сторонних аккаунтах резервного копирования, будут удалены. Хотите продолжить?', + status: 'Статус снапшота', + ignoreRule: 'Правило игнорирования', + editIgnoreRule: '@:commons.button.edit @.lower:setting.ignoreRule', + ignoreHelper: + 'Это правило будет использоваться для сжатия и резервного копирования директории данных 1Panel при создании снапшота. По умолчанию игнорируются файлы сокетов.', + ignoreHelper1: 'По одному в строке. Например,\n*.log\n/opt/1panel/cache', + panelInfo: 'Записать базовую информацию 1Panel', + panelBin: 'Резервное копирование системных файлов 1Panel', + daemonJson: 'Резервное копирование конфигурационного файла Docker', + appData: 'Резервное копирование установленных приложений из 1Panel', + panelData: 'Резервное копирование директории данных 1Panel', + backupData: 'Резервное копирование локальной директории резервных копий для 1Panel', + compress: 'Создать файл снапшота', + upload: 'Загрузить файл снапшота', + recoverDetail: 'Детали восстановления', + createSnapshot: 'Создать снапшот', + importSnapshot: 'Синхронизировать снапшот', + importHelper: 'Директория снапшот: ', + lastRecoverAt: 'Время последнего восстановления', + lastRollbackAt: 'Время последнего отката', + reDownload: 'Скачать файл резервной копии снова', + recoverErrArch: 'Восстановление снапшотов между разными архитектурами серверов не поддерживается!', + recoverErrSize: + 'Обнаружено недостаточно места на диске, пожалуйста, проверьте или очистите и попробуйте снова!', + recoverHelper: + 'Начало восстановления из снапшота {0}, пожалуйста, подтвердите следующую информацию перед продолжением:', + recoverHelper1: 'Восстановление требует перезапуска служб Docker и 1Panel', + recoverHelper2: + 'Пожалуйста, убедитесь, что на сервере достаточно места на диске (Размер файла снапшота: {0}, Доступное место: {1})', + recoverHelper3: + 'Пожалуйста, убедитесь, что архитектура сервера соответствует архитектуре сервера, где был создан снапшот (Текущая архитектура сервера: {0})', + rollback: 'Откатить', + rollbackHelper: + 'Откат этого восстановления заменит все файлы из этого восстановления и может потребовать перезапуска служб Docker и 1Panel. Хотите продолжить?', + upgradeHelper: 'Обновление требует перезапуска службы 1Panel. Хотите продолжить?', + rollbackLocalHelper: + 'Основной узел не поддерживает прямой откат. Пожалуйста, вручную выполните команду [1pctl restore] для отката!', + noUpgrade: 'В настоящее время это последняя версия', + upgradeNotes: 'Примечания к выпуску', + upgradeNow: 'Обновить сейчас', + source: 'Источник загрузки', + versionNotSame: + 'Версия узла не совпадает с основной. Пожалуйста, обновите в Управлении узлами перед повторной попыткой.', + versionCompare: + 'Обнаружено, что узел {0} уже имеет последнюю обновляемую версию. Пожалуйста, проверьте версию основного узла и повторите попытку!', + + about: 'О программе', + versionItem: 'Текущая Версия', + backupCopies: 'Количество Копий для Сохранения', + backupCopiesHelper: + 'Установите количество копий резервных копий обновления для сохранения для отката версии. 0 означает сохранить все.', + backupCopiesRule: 'Пожалуйста, сохраните как минимум 3 записи резервных копий обновления', + release: 'Журнал обновлений версий', + releaseHelper: + 'Не удалось получить журнал обновлений для текущей среды. Вы можете вручную проверить официальную документацию.', + project: 'GitHub', + issue: 'Проблема', + doc: 'Официальная документация', + star: 'Звезда', + description: 'Панель управления Linux сервером', + forum: 'Обсуждения', + doc2: 'Документация', + currentVersion: 'Версия', + + license: 'Лицензия', + bindNode: 'Привязать Узел', + menuSetting: 'Настройки меню', + menuSettingHelper: 'Если существует только 1 подменю, в панели меню будет отображаться только это подменю', + showAll: 'Показать все', + hideALL: 'Скрыть все', + ifShow: 'Показывать', + menu: 'Меню', + confirmMessage: 'Страница будет обновлена для обновления списка расширенного меню. Продолжить?', + recoverMessage: + 'Страница будет обновлена, и список меню будет восстановлен до исходного состояния. Продолжить?', + compressPassword: 'Пароль сжатия', + backupRecoverMessage: + 'Пожалуйста, введите пароль для сжатия или распаковки (оставьте пустым, чтобы не устанавливать)', + }, + license: { + offLine: 'Офлайн версия', + community: 'OSS', + oss: 'Open Source Software', + pro: 'Pro', + trial: 'Пробная версия', + add: 'Добавить Community Edition', + licenseBindHelper: 'Бесплатные квоты узлов можно использовать только тогда, когда лицензия привязана к узлу', + licenseAlert: + 'Узлы Community Edition можно добавлять только при правильной привязке лицензии к узлу. Переключение поддерживается только для узлов, правильно привязанных к лицензии.', + licenseUnbindHelper: + 'Обнаружены узлы Community Edition для этой лицензии. Отвяжите лицензию и повторите попытку!', + subscription: 'Подписка', + perpetual: 'Пожизненная лицензия', + versionConstraint: '{0} Выкуп версии', + forceUnbind: 'Принудительное отвязывание', + forceUnbindHelper: + 'Принудительное отвязывание будет игнорировать любые ошибки, возникающие в процессе отвязывания, и в конечном итоге освободит привязку лицензии.', + updateForce: + 'Принудительное обновление (игнорировать все ошибки при отвязке для гарантии успешного завершения операции)', + trialInfo: 'Версия', + authorizationId: 'ID авторизации подписки', + authorizedUser: 'Авторизованный пользователь', + lostHelper: + 'Лицензия достигла максимального количества попыток повторной проверки. Пожалуйста, вручную нажмите кнопку синхронизации, чтобы убедиться, что функции профессиональной версии работают правильно. Детали: ', + disableHelper: + 'Синхронизация лицензии не удалась. Пожалуйста, вручную нажмите кнопку синхронизации, чтобы убедиться, что функции профессиональной версии работают правильно. Детали: ', + quickUpdate: 'Быстрое обновление', + power: 'Авторизовать', + unbindHelper: 'Все настройки Pro будут очищены после отвязки. Хотите продолжить?', + importLicense: 'Лицензия', + importHelper: 'Пожалуйста, нажмите или перетащите файл лицензии сюда', + technicalAdvice: 'Техническая консультация', + advice: 'Консультация', + levelUpPro: 'Обновить до Pro', + licenseSync: 'Синхронизация лицензии', + knowMorePro: 'Узнать больше', + closeAlert: 'Текущую страницу можно закрыть в настройках панели', + introduce: 'Описание функций', + waf: 'Обновление до профессиональной версии предоставляет такие функции, как карта перехватов, логи, записи блокировок, блокировка по географическому положению, пользовательские правила, пользовательские страницы перехвата и т.д.', + tamper: 'Обновление до профессиональной версии может защитить веб-сайты от несанкционированных изменений или подделок.', + setting: + 'Обновление до профессиональной версии позволяет настраивать логотип панели, приветственное сообщение и другую информацию.', + monitor: + 'Обновление до профессиональной версии позволяет просматривать статус веб-сайта в реальном времени, тенденции посещений, источники посетителей, логи запросов и другую информацию.', + alert: 'Обновление до профессиональной версии позволяет получать информацию о тревогах через SMS и просматривать логи тревог, полностью контролировать различные ключевые события и обеспечивать беспроблемную работу системы', + node: 'Обновление до профессиональной редакции позволяет управлять несколькими серверами Linux с помощью 1Panel.', + nodeApp: + 'Обновление до профессиональной редакции позволяет унифицированно обновлять версии приложений на нескольких узлах без необходимости ручного переключения узлов.', + nodeDashboard: + 'Обновление до профессиональной редакции позволяет централизованно управлять приложениями, веб-сайтами, базами данных и запланированными задачами на нескольких узлах.', + fileExchange: 'Обновите до Профессиональной версии, чтобы быстро передавать файлы между несколькими серверами.', + app: 'Обновите до профессиональной версии, чтобы просматривать информацию о сервисах, мониторинг аномалий и т.д. через мобильное приложение.', + cluster: + 'Обновление до профессиональной версии позволяет управлять кластерами мастер-слейв MySQL/Postgres/Redis.', + }, + clean: { + scan: 'Начать сканирование', + scanHelper: 'Легко очищайте мусорные файлы, созданные во время работы 1Panel', + clean: 'Очистить сейчас', + reScan: 'Пересканировать', + cleanHelper: 'Выбранные файлы и каталоги нельзя откатить после очистки. Продолжить?', + statusSuggest: '(Рекомендуется очистка)', + statusClean: '(Очень чисто)', + statusEmpty: 'Очень чисто, очистка не требуется!', + statusWarning: '(Действуйте с осторожностью)', + lastCleanTime: 'Последняя очистка: {0}', + lastCleanHelper: 'Очищено файлов и директорий: {0}, всего очищено: {1}', + cleanSuccessful: 'Успешно очищено', + currentCleanHelper: 'Очищено файлов и директорий в этой сессии: {0}, Всего очищено: {1}', + suggest: '(Рекомендуется)', + totalScan: 'Всего мусорных файлов для очистки: ', + selectScan: 'Всего выбранных мусорных файлов: ', + + system: 'Системные мусорные файлы', + systemHelper: 'Временные файлы, созданные во время снимков, обновлений и других процессов', + panelOriginal: 'Файлы резервных копий перед восстановлением снимка системы', + upgrade: 'Файлы резервных копий обновления системы', + agentPackages: 'Пакеты обновления/установки дочерних узлов исторических версий', + upgradeHelper: '(Рекомендуется сохранять последнюю резервную копию обновления для отката системы)', + rollback: 'Файлы резервных копий перед восстановлением', + + backup: 'Резервная Копия Системы', + backupHelper: 'Файлы резервных копий, не связанные с локальными учетными записями резервного копирования', + unknownBackup: 'Резервная Копия Системы', + tmpBackup: 'Временная Резервная Копия', + unknownApp: 'Несвязанная Резервная Копия Приложения', + unknownDatabase: 'Несвязанная Резервная Копия Базы Данных', + unknownWebsite: 'Несвязанная Резервная Копия Веб-сайта', + unknownSnapshot: 'Несвязанная Резервная Копия Снимка', + + upload: 'Временные файлы загрузки', + uploadHelper: 'Временные файлы, загруженные из списка системных резервных копий', + download: 'Временные файлы скачивания', + downloadHelper: 'Временные файлы, скачанные из сторонних аккаунтов резервного копирования системой', + directory: 'Директория', + + systemLog: 'Файл журнала', + systemLogHelper: 'Системные журналы, журналы задач, файлы журналов веб-сайта', + dockerLog: 'Файлы журналов операций с контейнерами', + taskLog: 'Файлы журналов выполнения запланированных задач', + shell: 'Запланированные задачи скриптов оболочки', + containerShell: 'Запланированные задачи скриптов оболочки, выполняемые внутри контейнеров', + curl: 'Запланированные задачи CURL', + + docker: 'Мусор контейнеров', + dockerHelper: 'Файлы, такие как контейнеры, образы, тома, кэш сборки и т.д.', + volumes: 'Тома', + buildCache: 'Кэш сборки контейнеров', + + appTmpDownload: 'Временный файл загрузки приложения', + unknownWebsiteLog: 'Arquivo de backup de log de site não vinculado', + }, + app: { + app: 'Приложение | Приложения', + installName: 'Имя', + installed: 'Установленные', + all: 'Все', + version: 'Версия', + detail: 'Детали', + params: 'Редактировать', + author: 'Автор', + source: 'Источник', + appName: 'Название приложения', + deleteWarn: + 'Операция удаления удалит все данные и резервные копии. Эту операцию нельзя отменить. Хотите продолжить?', + syncSuccess: 'Синхронизация выполнена успешно', + canUpgrade: 'Обновления', + backupName: 'Имя файла', + backupPath: 'Путь к файлу', + backupdate: 'Время резервного копирования', + versionSelect: 'Пожалуйста, выберите версию', + operatorHelper: 'Операция {0} будет выполнена для выбранного приложения. Хотите продолжить?', + startOperatorHelper: 'Приложение будет запущено. Хотите продолжить?', + stopOperatorHelper: 'Приложение будет остановлено. Хотите продолжить?', + restartOperatorHelper: 'Приложение будет перезапущено. Хотите продолжить?', + reloadOperatorHelper: 'Приложение будет перезагружено. Хотите продолжить?', + checkInstalledWarn: '"{0}" не обнаружено. Перейдите в "Магазин приложений" для установки.', + limitHelper: 'Приложение уже установлено.', + deleteHelper: '"{0}" связано со следующими ресурсами. Пожалуйста, проверьте и попробуйте снова!', + checkTitle: 'Подсказка', + defaultConfig: 'Конфигурация по умолчанию', + defaultConfigHelper: 'Восстановлено до конфигурации по умолчанию, вступит в силу после сохранения', + forceDelete: 'Принудительное удаление', + forceDeleteHelper: + 'Принудительное удаление будет игнорировать ошибки во время процесса удаления и в итоге удалит метаданные.', + deleteBackup: 'Удалить резервную копию', + deleteBackupHelper: 'Также удалить резервную копию приложения', + deleteDB: 'Удалить базу данных', + deleteDBHelper: 'Также удалить базу данных', + noService: 'Нет {0}', + toInstall: 'Перейти к установке', + param: 'Параметры', + alreadyRun: 'Возраст', + syncAppList: 'Синхронизировать', + less1Minute: 'Меньше 1 минуты', + appOfficeWebsite: 'Официальный сайт', + github: 'Github', + document: 'Документация', + updatePrompt: 'Нет доступных обновлений', + installPrompt: 'Пока нет установленных приложений', + updateHelper: + 'Редактирование параметров может привести к сбою запуска приложения. Пожалуйста, действуйте с осторожностью.', + updateWarn: 'Обновление параметров требует пересборки приложения. Хотите продолжить?', + busPort: 'Порт', + syncStart: 'Начало синхронизации! Пожалуйста, обновите магазин приложений позже', + advanced: 'Расширенные настройки', + cpuCore: 'ядро(а)', + containerName: 'Имя контейнера', + containerNameHelper: 'Имя контейнера будет автоматически сгенерировано, если не задано', + allowPort: 'Внешний доступ', + allowPortHelper: 'Разрешение внешнего доступа к порту откроет порт в брандмауэре', + appInstallWarn: + 'Приложение по умолчанию не открывает порт для внешнего доступа. Нажмите "Расширенные настройки" для открытия.', + upgradeStart: 'Начало обновления! Пожалуйста, обновите страницу позже', + toFolder: 'Открыть директорию установки', + editCompose: 'Редактировать файл compose', + editComposeHelper: 'Редактирование файла compose может привести к сбою установки программного обеспечения', + composeNullErr: 'compose не может быть пустым', + takeDown: 'Отключить', + allReadyInstalled: 'Установлено', + installHelper: 'Если есть проблемы с загрузкой образа, настройте ускорение образов.', + installWarn: + 'Внешний доступ не включен, что делает приложение недоступным через внешние сети. Хотите продолжить?', + showIgnore: 'Просмотреть игнорируемые приложения', + cancelIgnore: 'Отменить игнорирование', + ignoreList: 'Игнорируемые приложения', + appHelper: 'Перейдите на страницу приложения, чтобы узнать инструкции и подробности данного приложения.', + backupApp: 'Создать резервную копию приложения перед обновлением', + backupAppHelper: + 'Если обновление не удастся, резервная копия будет автоматически восстановлена. Пожалуйста, проверьте причину сбоя в логе аудита-системном логе. Резервная копия по умолчанию сохранит последние 3 копии', + openrestyDeleteHelper: 'Принудительное удаление OpenResty удалит все веб-сайты. Хотите продолжить?', + downloadLogHelper1: 'Будут загружены все логи приложения {0}. Хотите продолжить?', + downloadLogHelper2: 'Будут загружены последние {1} логов приложения {0}. Хотите продолжить?', + syncAllAppHelper: 'Все приложения будут синхронизированы. Хотите продолжить?', + hostModeHelper: + 'Текущий режим сети приложения - режим хоста. Если нужно открыть порт, пожалуйста, откройте его вручную на странице брандмауэра.', + showLocal: 'Показать локальные приложения', + reload: 'Перезагрузить', + upgradeWarn: + 'Обновление приложения заменит файл docker-compose.yml. Если есть изменения, вы можете нажать для просмотра сравнения файлов', + newVersion: 'Новая версия', + oldVersion: 'Текущая версия', + composeDiff: 'Сравнение файлов', + showDiff: 'Просмотреть сравнение', + useNew: 'Использовать пользовательскую версию', + useDefault: 'Использовать версию по умолчанию', + useCustom: 'Настроить docker-compose.yml', + useCustomHelper: + 'Использование пользовательского файла docker-compose.yml может привести к сбою обновления приложения. Если это не необходимо, не отмечайте это.', + diffHelper: + 'Слева старая версия, справа новая версия. После редактирования нажмите для сохранения пользовательской версии', + pullImage: 'Загрузить образ', + pullImageHelper: 'Выполнить docker pull для загрузки образа перед запуском приложения', + deleteImage: 'Удалить изображение', + deleteImageHelper: + 'Удалите изображение, связанное с приложением. Задача не завершится, если удаление не удастся.', + requireMemory: 'Требуемая память', + supportedArchitectures: 'Поддерживаемые архитектуры', + link: 'Ссылка', + showCurrentArch: 'Приложения для текущей архитектуры сервера', + syncLocalApp: 'Синхронизировать локальное приложение', + memoryRequiredHelper: 'Текущее приложение требует {0} памяти', + gpuConfig: 'Включить поддержку GPU', + gpuConfigHelper: + 'Убедитесь, что на машине установлен NVIDIA GPU и драйверы NVIDIA, а также NVIDIA Docker Container Toolkit', + webUI: 'Веб-адрес доступа', + webUIPlaceholder: 'Например: example.com:8080/login', + defaultWebDomain: 'Адрес доступа по умолчанию', + defaultWebDomainHepler: 'Если порт приложения 8080, то адрес будет http(s)://адрес по умолчанию:8080', + webUIConfig: + 'Текущий узел не имеет настроенного адреса доступа по умолчанию. Пожалуйста, настройте его в параметрах приложения или перейдите в настройки панели!', + toLink: 'Перейти', + customAppHelper: + 'Перед установкой пользовательского пакета из магазина приложений убедитесь, что нет установленных приложений.', + forceUninstall: 'Принудительное удаление', + syncCustomApp: 'Синхронизировать пользовательское приложение', + ignoreAll: 'Игнорировать все последующие версии', + ignoreVersion: 'Игнорировать указанную версию', + specifyIP: 'Привязать IP хоста', + specifyIPHelper: + 'Установите адрес хоста/сетевого интерфейса для привязки порта (если вы не уверены в этом, пожалуйста, не заполняйте)', + uninstallDeleteBackup: 'Деинсталляция приложения - Удаление резервной копии', + uninstallDeleteImage: 'Деинсталляция приложения - Удаление образа', + upgradeBackup: 'Резервное копирование приложения перед обновлением', + noAppHelper: + 'Приложения не обнаружены, пожалуйста, перейдите в центр задач для просмотра журнала синхронизации магазина приложений', + isEdirWarn: 'Обнаружено изменение файла docker-compose.yml, пожалуйста, проверьте сравнение', + }, + website: { + primaryDomain: 'Основной домен', + otherDomains: 'Другие домены', + static: 'Статический', + deployment: 'Развертывание', + supportUpType: 'Поддерживается только формат файла .tar.gz, и сжатый пакет должен содержать файл {0}.json', + proxy: 'Обратный прокси', + alias: 'Псевдоним', + ftpUser: 'FTP аккаунт', + ftpPassword: 'FTP пароль', + ftpHelper: + 'После создания веб-сайта будет создан соответствующий FTP-аккаунт, и FTP-директория будет связана с директорией веб-сайта.', + remark: 'Примечание', + manageGroup: 'Управление группами', + groupSetting: 'Управление группами', + createGroup: 'Создать группу', + appNew: 'Новое приложение', + appInstalled: 'Установленное приложение', + create: 'Создать веб-сайт', + delete: 'Удалить веб-сайт', + deleteApp: 'Удалить приложение', + deleteBackup: 'Удалить резервную копию', + domain: 'Домен', + domainHelper: 'Один домен в строке.\nПоддерживает wildcard "*" и IP-адреса.\nПоддерживает добавление порта.', + addDomain: 'Добавить', + domainConfig: 'Домены', + defaultDoc: 'Документ', + perserver: 'Параллельные подключения', + perserverHelper: 'Ограничить максимальное количество параллельных подключений для текущего сайта', + perip: 'Один IP', + peripHelper: 'Ограничить максимальное количество параллельных подключений с одного IP', + rate: 'Ограничения трафика', + rateHelper: 'Ограничить поток каждого запроса (единица: КБ)', + limitHelper: 'Включить контроль потока', + other: 'Другое', + currentSSL: 'Текущий сертификат', + dnsAccount: 'DNS аккаунт', + applySSL: 'Заявка на сертификат', + SSLList: 'Список сертификатов', + createDnsAccount: 'DNS аккаунт', + aliyun: 'Aliyun', + aliEsa: 'Aliyun ESA', + awsRoute53: 'Amazon Route 53', + manual: 'Ручная настройка', + key: 'Ключ', + check: 'Просмотр', + acmeAccountManage: 'ACME аккаунты', + email: 'Email', + acmeAccount: 'ACME аккаунт', + provider: 'Метод проверки', + dnsManual: 'Ручное разрешение', + expireDate: 'Дата истечения', + brand: 'Организация', + deploySSL: 'Развертывание', + deploySSLHelper: 'Вы уверены, что хотите развернуть сертификат?', + ssl: 'Сертификат | Сертификаты', + dnsAccountManage: 'DNS провайдеры', + renewSSL: 'Обновить', + renewHelper: 'Вы уверены, что хотите обновить сертификат?', + renewSuccess: 'Обновить сертификат', + enableHTTPS: 'Включить', + aliasHelper: 'Псевдоним - это имя директории веб-сайта', + lastBackupAt: 'время последнего резервного копирования', + null: 'нет', + nginxConfig: 'Конфигурация Nginx', + websiteConfig: 'Настройки веб-сайта', + proxySettings: 'Proxy Ayarları', + advancedSettings: 'Gelişmiş Ayarlar', + cacheSettings: 'Önbellek Ayarları', + sniSettings: 'SNI Ayarları', + basic: 'Основные', + source: 'Конфигурация', + security: 'Безопасность', + nginxPer: 'Настройка производительности', + neverExpire: 'Никогда', + setDefault: 'Установить по умолчанию', + default: 'По умолчанию', + deleteHelper: 'Статус связанного приложения аномальный, пожалуйста, проверьте', + toApp: 'Перейти к списку установленных', + cycle: 'Цикл', + frequency: 'Частота', + ccHelper: + 'При накоплении более {1} запросов к одному URL в течение {0} секунд срабатывает защита CC и блокируется этот IP', + mustSave: 'Изменения нужно сохранить, чтобы они вступили в силу', + fileExt: 'расширение файла', + fileExtBlock: 'черный список расширений файлов', + value: 'значение', + enable: 'Включить', + proxyAddress: 'Адрес прокси', + proxyHelper: 'Пример: 127.0.0.1:8080', + forceDelete: 'Принудительное удаление', + forceDeleteHelper: + 'Принудительное удаление будет игнорировать ошибки во время процесса удаления и в итоге удалит метаданные.', + deleteAppHelper: 'Удалить связанные приложения и резервные копии приложений одновременно', + deleteBackupHelper: 'Также удалить резервные копии веб-сайта.', + deleteDatabaseHelper: 'Также удалить базу данных, связанную с веб-сайтом', + deleteConfirmHelper: + 'Операцию удаления нельзя отменить. Введите "{0}" для подтверждения удаления.', + staticPath: 'Соответствующая основная директория - ', + limit: 'Схема', + blog: 'Форум/Блог', + imageSite: 'Сайт изображений', + downloadSite: 'Сайт загрузок', + shopSite: 'Магазин', + doorSite: 'Портал', + qiteSite: 'Корпоративный', + videoSite: 'Видео', + errLog: 'Лог ошибок', + stopHelper: + 'После остановки сайта он не будет доступен для нормального доступа, и пользователи будут видеть страницу остановки при посещении. Хотите продолжить?', + startHelper: + 'После включения сайта пользователи смогут нормально получить доступ к содержимому сайта, хотите продолжить?', + sitePath: 'Директория', + siteAlias: 'Псевдоним сайта', + primaryPath: 'Корневая директория', + folderTitle: 'Веб-сайт в основном содержит следующие папки', + wafFolder: 'Правила межсетевого экрана', + indexFolder: 'Корневая директория веб-сайта', + sslFolder: 'Сертификат веб-сайта', + enableOrNot: 'Включить', + oldSSL: 'Существующий сертификат', + manualSSL: 'Импорт сертификата', + select: 'Выбрать', + selectSSL: 'Выбрать сертификат', + privateKey: 'Ключ (KEY)', + certificate: 'Сертификат (формат PEM)', + HTTPConfig: 'Опции HTTP', + HTTPSOnly: 'Блокировать HTTP запросы', + HTTPToHTTPS: 'Перенаправлять на HTTPS', + HTTPAlso: 'Разрешить прямые HTTP запросы', + sslConfig: 'Опции SSL', + disableHTTPS: 'Отключить HTTPS', + disableHTTPSHelper: 'Отключение HTTPS удалит конфигурацию, связанную с сертификатом. Хотите продолжить?', + SSLHelper: + 'Примечание: Не используйте SSL-сертификаты для нелегальных веб-сайтов.\nЕсли после открытия невозможно использовать доступ по HTTPS, проверьте, правильно ли открыт порт 443 в группе безопасности.', + SSLConfig: 'Настройки сертификата', + SSLProConfig: 'Настройки протокола', + supportProtocol: 'Версия протокола', + encryptionAlgorithm: 'Алгоритм шифрования', + notSecurity: '(небезопасно)', + encryptHelper: + "Let's Encrypt имеет ограничение частоты выдачи сертификатов, но его достаточно для нормальных потребностей. Слишком частые операции приведут к сбою выдачи. Конкретные ограничения см. в официальной документации", + ipValue: 'Значение', + ext: 'расширение файла', + wafInputHelper: 'Вводите данные по строкам, одна строка', + data: 'данные', + ever: 'постоянно', + nextYear: 'Через год', + noLog: 'Логи не найдены', + defaultServer: 'Сайт по умолчанию', + noDefaultServer: 'Не установлен', + defaultServerHelper: + 'После установки сайта по умолчанию все несвязанные доменные имена и IP-адреса будут перенаправлены на сайт по умолчанию\nЭто может эффективно предотвратить вредоносное разрешение\nОднако это также может привести к сбою блокировки неавторизованных доменных имен WAF', + restoreHelper: 'Вы уверены, что хотите восстановить из этой резервной копии?', + websiteDeploymentHelper: + 'Используйте установленное приложение или создайте новое приложение для создания веб-сайта.', + websiteStatictHelper: 'Создать директорию веб-сайта на хосте.', + websiteProxyHelper: + 'Использовать обратный прокси для проксирования существующей службы. Например, если служба установлена и работает на порту 8080, адрес прокси будет "http://127.0.0.1:8080".', + runtimeProxyHelper: 'Использовать среду выполнения веб-сайта для создания веб-сайта.', + runtime: 'Среда выполнения', + deleteRuntimeHelper: + 'Приложение среды выполнения необходимо удалять вместе с веб-сайтом, пожалуйста, обращайтесь с этим осторожно', + proxyType: 'Тип сети', + unix: 'Unix сеть', + tcp: 'TCP/IP сеть', + phpFPM: 'Конфигурация FPM', + phpConfig: 'Конфигурация PHP', + updateConfig: 'Обновить конфигурацию', + isOn: 'Вкл', + isOff: 'Выкл', + rewrite: 'Псевдостатика', + rewriteMode: 'Схема', + current: 'Текущий', + rewriteHelper: + 'Если установка псевдостатики делает веб-сайт недоступным, попробуйте вернуться к настройкам по умолчанию.', + runDir: 'Рабочая директория', + runUserHelper: + 'Для веб-сайтов, развернутых через среду выполнения контейнера PHP, необходимо установить владельца и группу пользователей всех файлов и папок в index и поддиректориях на 1000. Для локальной среды PHP обратитесь к локальным настройкам пользователя и группы PHP-FPM', + userGroup: 'Пользователь/Группа', + uGroup: 'Группа', + proxyPath: 'Путь прокси', + proxyPass: 'Целевой URL', + cache: 'Кэш', + cacheTime: 'Длительность кэширования', + enableCache: 'Кэш', + proxyHost: 'Прокси хост', + disabled: 'Остановлен', + startProxy: 'Это запустит обратный прокси. Хотите продолжить?', + stopProxy: 'Это остановит обратный прокси. Хотите продолжить?', + sourceFile: 'Источник', + proxyHelper1: 'При доступе к этой директории будет возвращено и отображено содержимое целевого URL.', + proxyPassHelper: 'Целевой URL должен быть действительным и доступным.', + proxyHostHelper: 'Передать доменное имя в заголовке запроса прокси-серверу.', + replacementHelper: 'Можно добавить до 5 замен, оставьте пустым, если замена не требуется.', + modifier: 'Правила сопоставления', + modifierHelper: 'Пример: "=" точное совпадение, "~" регулярное совпадение, "^~" совпадение начала пути и т.д.', + replace: 'Замены текста', + replaceHelper: + 'Функция замены текста в nginx позволяет заменять строки в содержимом ответа при обратном проксировании. Она обычно используется для изменения ссылок, адресов API и т.д. в файлах HTML, CSS, JavaScript и других, возвращаемых бэкендом. Поддерживает сопоставление с регулярными выражениями для сложных потребностей в замене содержимого.', + addReplace: 'Добавить', + replaced: 'Искомая строка (не может быть пустой)', + replaceText: 'Заменить на строку', + replacedErr: 'Искомая строка не может быть пустой', + replacedErr2: 'Искомая строка не может повторяться', + replacedListEmpty: 'Нет правил замены текста', + proxySslName: 'Имя прокси SNI', + basicAuth: 'Базовая аутентификация', + editBasicAuthHelper: + 'Пароль асимметрично зашифрован и не может быть показан. При редактировании нужно сбросить пароль', + antiLeech: 'Анти-лич', + extends: 'Расширение', + browserCache: 'Кэш', + noModify: 'Не изменять', + serverCache: 'Кэш сервера', + leechLog: 'Записывать лог анти-лича', + accessDomain: 'Разрешенные домены', + leechReturn: 'Ответ ресурса', + noneRef: 'Разрешить пустой referrer', + disable: 'не включено', + disableLeechHelper: 'Отключить ли анти-лич', + disableLeech: 'Отключить анти-лич', + ipv6: 'Прослушивать IPv6', + leechReturnError: 'Пожалуйста, заполните HTTP код статуса', + blockedRef: 'Разрешить нестандартный Referer', + accessControl: 'Управление анти-личем', + leechcacheControl: 'Управление кэшем', + logEnableControl: 'Логировать запросы статических ресурсов', + leechSpecialValidHelper: + 'При включённой опции «Разрешить пустой referrer» запросы без Referer (прямой доступ и т. п.) не блокируются; включение «Разрешить нестандартный Referer» пропускает любой Referer, не начинающийся с http/https (клиентские запросы и т. п.).', + leechInvalidReturnHelper: 'HTTP‑код статуса, возвращаемый после блокировки хотлинкинга', + leechlogControlHelper: + 'Записывает запросы к статическим ресурсам; в продакшене обычно отключают, чтобы избежать избыточных и шумных логов', + selectAcme: 'Выберите Acme аккаунт', + imported: 'Создан вручную', + importType: 'Тип импорта', + pasteSSL: 'Вставить код', + localSSL: 'Выбрать файл сервера', + privateKeyPath: 'Файл приватного ключа', + certificatePath: 'Файл сертификата', + ipWhiteListHelper: 'Роль белого списка IP: все правила недействительны для белого списка IP', + redirect: 'Перенаправление', + sourceDomain: 'Исходный домен', + targetURL: 'Целевой URL адрес', + keepPath: 'URI параметры', + path: 'путь', + redirectType: 'тип перенаправления', + redirectWay: 'Способ', + keep: 'сохранить', + notKeep: 'Не сохранять', + redirectRoot: 'Перенаправить на главную страницу', + redirectHelper: '301 постоянное перенаправление, 302 временное перенаправление', + changePHPVersionWarn: + 'Переключение версии PHP удалит оригинальный контейнер PHP (смонтированный код веб-сайта не будет потерян), продолжить?', + changeVersion: 'Переключить версию', + retainConfig: 'Сохранить ли файлы php-fpm.conf и php.ini', + runDirHelper2: 'Пожалуйста, убедитесь, что вторичная рабочая директория находится в директории index', + openrestyHelper: + 'OpenResty порт HTTP по умолчанию: {0} порт HTTPS: {1}, что может повлиять на доступ к доменному имени веб-сайта и принудительное перенаправление HTTPS', + primaryDomainHelper: 'Пример: example.com или example.com:8080', + acmeAccountType: 'Тип аккаунта', + keyType: 'Алгоритм ключа', + tencentCloud: 'Tencent Cloud', + containWarn: 'Доменное имя содержит основной домен, пожалуйста, введите заново', + rewriteHelper2: + 'Приложения типа WordPress, установленные из магазина приложений, обычно поставляются с предустановленной конфигурацией псевдостатики. Их перенастройка может привести к ошибкам.', + websiteBackupWarn: + 'Поддерживается только импорт локальных резервных копий, импорт резервных копий с других машин может привести к сбою восстановления', + ipWebsiteWarn: + 'Веб-сайты с IP в качестве домена должны быть установлены как сайт по умолчанию для нормального доступа.', + hstsHelper: 'Включение HSTS может повысить безопасность веб-сайта', + includeSubDomains: 'Поддомены', + hstsIncludeSubDomainsHelper: + 'После включения политика HSTS будет применяться ко всем поддоменам текущего домена.', + defaultHtml: 'Страница по умолчанию', + website404: 'Страница ошибки 404 веб-сайта', + domain404: 'Домен веб-сайта не существует', + indexHtml: 'Индекс для статического веб-сайта', + stopHtml: 'Остановленный веб-сайт', + indexPHP: 'Индекс для PHP веб-сайта', + sslExpireDate: 'Дата истечения сертификата', + website404Helper: + 'Страница ошибки 404 веб-сайта поддерживается только для веб-сайтов со средой выполнения PHP и статических веб-сайтов', + sni: 'Origin SNI', + sniHelper: + 'Когда бэкенд обратного прокси использует HTTPS, может потребоваться установить origin SNI. Подробности см. в документации провайдера CDN.', + huaweicloud: 'Huawei Cloud', + rcreateDb: 'Создать Базу Данных', + enableSSLHelper: 'Неудача при включении SSL не повлияет на создание сайта.', + batchAdd: 'Пакетное Добавление Доменов', + batchInput: 'Пакетный ввод', + domainNotFQDN: 'Этот домен может быть недоступен в публичной сети', + domainInvalid: 'Неверный формат домена', + domainBatchHelper: 'Один домен на строку, формат: domain:port@ssl\nПример: example.com:443@ssl или example.com', + generateDomain: 'Сгенерировать', + global: 'Глобальный', + subsite: 'Подсайт', + subsiteHelper: + 'Подсайт может выбрать каталог существующего PHP или статического сайта в качестве корневого каталога.', + parentWebsite: 'Родительский Сайт', + deleteSubsite: 'Чтобы удалить текущий сайт, сначала необходимо удалить подсайт {0}.', + loadBalance: 'Балансировка Нагрузки', + server: 'Узел', + algorithm: 'Алгоритм', + ipHash: 'IP Хэш', + ipHashHelper: + 'Распределяет запросы на определенный сервер на основе IP-адреса клиента, гарантируя, что конкретный клиент всегда направляется на один и тот же сервер.', + leastConn: 'Наименьшее Количество Соединений', + leastConnHelper: 'Отправляет запросы на сервер с наименьшим количеством активных соединений.', + leastTime: 'Наименьшее Время', + leastTimeHelper: 'Отправляет запросы на сервер с наименьшим временем активного соединения.', + defaultHelper: + 'Метод по умолчанию, запросы равномерно распределяются между серверами. Если сервер имеет настройку веса, запросы распределяются в соответствии с указанным весом. Серверы с большим весом получают больше запросов.', + weight: 'Вес', + maxFails: 'Максимальное Количество Ошибок', + maxConns: 'Максимальное Количество Соединений', + strategy: 'Стратегия', + strategyDown: 'Отключить', + strategyBackup: 'Резервный', + ipHashBackupErr: 'IP хэш не поддерживает резервные узлы', + failTimeout: 'Таймаут отказа', + failTimeoutHelper: + 'Длина временного окна для проверки работоспособности сервера. Когда кумулятивное количество отказов достигает порога в течение этого периода, сервер будет временно удален и повторно проверен через тот же промежуток времени. По умолчанию 10 секунд', + + staticChangePHPHelper: 'В настоящее время статический сайт, можно переключить на PHP сайт.', + proxyCache: 'Кэш Обратного Прокси', + cacheLimit: 'Ограничение Пространства Кэша', + shareCache: 'Размер Памяти для Подсчета Кэша', + cacheExpire: 'Время Истечения Кэша', + shareCacheHelper: '1M памяти может хранить примерно 8000 объектов кэша.', + cacheLimitHelper: 'Превышение лимита автоматически удалит старые кэши.', + cacheExpireHelper: 'Кэши, не попавшие в срок истечения, будут удалены.', + realIP: 'Реальный IP', + ipFrom: 'Источник IP', + ipFromHelper: + 'Настроив доверенные источники IP, OpenResty проанализирует информацию об IP в HTTP-заголовке, чтобы точно идентифицировать и записать реальный IP-адрес посетителя, включая журналы доступа.', + ipFromExample1: + 'Если фронтенд — это инструмент, такой как Frp, вы можете указать IP-адрес Frp, например, 127.0.0.1.', + ipFromExample2: 'Если фронтенд — это CDN, вы можете указать диапазон IP-адресов CDN.', + ipFromExample3: + 'Если вы не уверены, вы можете указать 0.0.0.0/0 (IPv4) или ::/0 (IPv6). [Примечание: Разрешение любого источника IP небезопасно.]', + http3Helper: + 'HTTP/3 — это обновленная версия HTTP/2, обеспечивающая более высокую скорость соединения и лучшую производительность. Однако не все браузеры поддерживают HTTP/3, и его включение может привести к тому, что некоторые браузеры не смогут получить доступ к сайту.', + cors: 'Межсайтовый обмен ресурсами (CORS)', + enableCors: 'Включить CORS', + allowOrigins: 'Разрешенные домены', + allowMethods: 'Разрешенные методы запроса', + allowHeaders: 'Разрешенные заголовки запроса', + allowCredentials: 'Разрешить отправку cookies', + preflight: 'Быстрый ответ на предварительный запрос', + preflightHleper: + 'При включении, когда браузер отправляет межсайтовый предварительный запрос (запрос OPTIONS), система автоматически вернет статус 204 и установит необходимые межсайтовые заголовки ответа', + + changeDatabase: 'Сменить Базу Данных', + changeDatabaseHelper1: 'Связь базы данных используется для резервного копирования и восстановления сайта.', + changeDatabaseHelper2: + 'Переключение на другую базу данных может сделать предыдущие резервные копии невосстановимыми.', + saveCustom: 'Сохранить как Шаблон', + rainyun: 'Rainyun', + volcengine: 'Volcengine', + runtimePortHelper: 'O ambiente de runtime atual possui várias portas. Por favor, selecione uma porta de proxy.', + runtimePortWarn: 'В текущей среде выполнения нет портов, невозможно проксировать', + cacheWarn: 'Пожалуйста, сначала выключите кэш в обратном прокси', + loadBalanceHelper: + 'После создания балансировки нагрузки, пожалуйста, перейдите в "Обратный прокси", добавьте прокси и установите адрес бэкенда на: http://<название балансировки нагрузки>.', + favorite: 'Избранное', + cancelFavorite: 'Отменить избранное', + useProxy: 'Использовать прокси', + useProxyHelper: 'Использовать адрес прокси-сервера в настройках панели', + westCN: 'Западный цифровой', + openBaseDir: 'Предотвращение межсайтовых атак', + openBaseDirHelper: + 'open_basedir используется для ограничения пути доступа к файлам PHP, что помогает предотвратить межсайтовый доступ и повысить безопасность', + serverCacheTime: 'Время кеширования на сервере', + serverCacheTimeHelper: + 'Время, в течение которого запрос кешируется на сервере. В этот период идентичные запросы будут возвращать кешированный результат напрямую, без запроса к исходному серверу.', + browserCacheTime: 'Время кеширования в браузере', + browserCacheTimeHelper: + 'Время, в течение которого статические ресурсы кешируются локально в браузере, уменьшая повторные запросы. Пользователи будут использовать локальный кеш напрямую, если срок его действия не истек при обновлении страницы.', + donotLinkeDB: 'Не связывать с базой данных', + toWebsiteDir: 'Перейти в каталог сайта', + execParameters: 'Параметры выполнения', + extCommand: 'Дополнительная команда', + mirror: 'Зеркальный источник', + execUser: 'Пользователь выполнения', + execDir: 'Каталог выполнения', + packagist: 'Полное зеркало Китая', + + batchOpreate: 'Пакетная операция', + batchOpreateHelper: 'Пакетное {0} веб-сайтов, продолжить операцию?', + stream: 'Прокси TCP/UDP', + streamPorts: 'Порты прослушивания', + streamPortsHelper: + 'Установите номер внешнего порта прослушивания, клиенты будут получать доступ к службе через этот порт, разделяйте запятыми, например: 5222,5223', + streamHelper: 'Перенаправление портов и балансировка нагрузки TCP/UDP', + udp: 'Включить UDP', + + syncHtmlHelper: 'Синхронизировать с PHP и статическими сайтами', + }, + php: { + short_open_tag: 'Поддержка коротких тегов', + max_execution_time: 'Максимальное время выполнения скрипта', + max_input_time: 'Максимальное время ввода', + memory_limit: 'Лимит памяти скрипта', + post_max_size: 'Максимальный размер данных POST', + file_uploads: 'Разрешить загрузку файлов', + upload_max_filesize: 'Максимальный разрешенный размер загружаемых файлов', + max_file_uploads: 'Максимальное количество файлов, разрешенных для одновременной загрузки', + default_socket_timeout: 'Тайм-аут сокета', + error_reporting: 'Уровень ошибок', + display_errors: 'Выводить подробную информацию об ошибках', + cgi_fix_pathinfo: 'Включить pathinfo', + date_timezone: 'Часовой пояс', + disableFunction: 'Отключить функции', + disableFunctionHelper: 'Введите функции для отключения, например exec, разделяйте запятыми', + uploadMaxSize: 'Ограничение загрузки', + indexHelper: + 'Для обеспечения нормальной работы PHP-сайта разместите код в директории index и избегайте переименования', + extensions: 'Шаблоны расширений', + extension: 'Расширение', + extensionHelper: 'Используйте несколько расширений, разделяйте запятыми', + toExtensionsList: 'Просмотр списка расширений', + containerConfig: 'Конфигурация контейнера', + containerConfigHelper: + 'Переменные окружения и другие данные можно изменить в разделе Конфигурация - Конфигурация контейнера после создания', + dateTimezoneHelper: 'Пример: TZ=Asia/Shanghai (Пожалуйста, добавьте по мере необходимости)', + }, + nginx: { + serverNamesHashBucketSizeHelper: 'Размер хэш-таблицы для имен серверов', + clientHeaderBufferSizeHelper: 'Размер буфера заголовка для запросов клиента', + clientMaxBodySizeHelper: 'Максимальный размер загружаемого файла', + keepaliveTimeoutHelper: 'Тайм-аут соединения', + gzipMinLengthHelper: 'Минимальный размер сжатого файла', + gzipCompLevelHelper: 'Степень сжатия', + gzipHelper: 'Включить сжатие для передачи', + connections: 'Активные соединения', + accepts: 'Принято', + handled: 'Обработано', + requests: 'Запросы', + reading: 'Чтение', + writing: 'Запись', + waiting: 'Ожидание', + status: 'Текущий статус', + configResource: 'Конфигурация', + saveAndReload: 'Сохранить и перезагрузить', + clearProxyCache: 'Очистить кэш обратного прокси', + clearProxyCacheWarn: + 'Это повлияет на все веб-сайты с настроенным кэшем и перезапустит "OpenResty". Хотите продолжить?', + create: 'Создать модуль', + update: 'Редактировать модуль', + params: 'Параметры', + packages: 'Пакеты', + script: 'Скрипт', + module: 'Модуль', + build: 'Сборка', + buildWarn: + 'Сборка OpenResty требует резервирования определенного количества CPU и памяти, процесс может занять много времени, пожалуйста, подождите.', + mirrorUrl: 'Источник программного обеспечения', + paramsHelper: 'Например: --add-module=/tmp/ngx_brotli', + packagesHelper: 'Например: git,curl разделенные запятыми', + scriptHelper: + 'Скрипт, выполняемый перед компиляцией, обычно для загрузки исходного кода модуля, установки зависимостей и т.д.', + buildHelper: + 'Нажмите Сборка после добавления/изменения модуля. Успешная сборка автоматически перезапустит OpenResty.', + defaultHttps: 'HTTPS Анти-вмешательство', + defaultHttpsHelper1: 'Включение этого параметра может решить проблему вмешательства в HTTPS.', + sslRejectHandshake: 'Отклонить стандартное SSL-рукопожатие', + sslRejectHandshakeHelper: + 'Включение этого может предотвратить утечку сертификатов, установка веб-сайта по умолчанию сделает эту настройку недействительной', + }, + ssl: { + create: 'Запросить', + provider: 'Тип', + manualCreate: 'Создан вручную', + acmeAccount: 'ACME аккаунт', + resolveDomain: 'Разрешить доменное имя', + err: 'Ошибка', + value: 'значение записи', + dnsResolveHelper: 'Пожалуйста, добавьте следующие записи разрешения у провайдера DNS:', + detail: 'Подробности', + msg: 'Информация', + ssl: 'Сертификат', + key: 'Закрытый ключ', + startDate: 'Время начала действия', + organization: 'организация-издатель', + renewConfirm: 'Это обновит сертификат для доменного имени {0}. Хотите продолжить?', + autoRenew: 'Автопродление', + autoRenewHelper: 'Автоматически продлевать за 30 дней до истечения срока', + renewSuccess: 'Успешно продлено', + renewWebsite: + 'Этот сертификат связан со следующими веб-сайтами, и заявка будет применена к этим сайтам одновременно', + createAcme: 'Создать аккаунт', + acmeHelper: 'Acme аккаунт используется для запроса бесплатных сертификатов', + upload: 'Импорт', + applyType: 'Тип', + apply: 'Продлить', + applyStart: 'Начало запроса сертификата', + getDnsResolve: 'Получение значения DNS-разрешения, пожалуйста, подождите...', + selfSigned: 'Самоподписанный CA', + ca: 'Центр сертификации', + commonName: 'Общее имя', + caName: 'Имя центра сертификации', + company: 'Название организации', + department: 'Название подразделения', + city: 'Название населенного пункта', + province: 'Название штата или области', + country: 'Название страны (2-буквенный код)', + commonNameHelper: 'Например, ', + selfSign: 'Выпустить сертификат', + days: 'срок действия', + domainHelper: 'Одно доменное имя в строке, поддерживает * и IP-адрес', + pushDir: 'Отправить сертификат в локальную директорию', + dir: 'Директория', + pushDirHelper: 'В этой директории будут созданы файл сертификата "fullchain.pem" и файл ключа "privkey.pem".', + organizationDetail: 'Детали организации', + fromWebsite: 'С веб-сайта', + dnsMauanlHelper: + 'В режиме ручного разрешения необходимо нажать кнопку применить после создания для получения значения DNS-разрешения', + httpHelper: + 'Использование режима HTTP требует установки OpenResty и не поддерживает запрос сертификатов с подстановочными доменными именами.', + buypassHelper: 'Buypass недоступен в материковом Китае', + googleHelper: 'Как получить EAB HmacKey и EAB kid', + googleCloudHelper: 'Google Cloud API недоступен в большинстве регионов материкового Китая', + skipDNSCheck: 'Пропустить проверку DNS', + skipDNSCheckHelper: 'Отметьте здесь только если возникает проблема тайм-аута при запросе сертификата.', + cfHelper: 'Не использовать Global API Key', + deprecated: 'будет устарелым', + deprecatedHelper: + 'Обслуживание остановлено и может быть удалено в будущей версии. Пожалуйста, используйте метод Tencent Cloud для анализа', + disableCNAME: 'Отключить CNAME', + disableCNAMEHelper: 'Отметьте здесь, если доменное имя имеет запись CNAME и запрос не удается.', + nameserver: 'DNS сервер', + nameserverHelper: 'Использовать пользовательский DNS сервер для проверки доменных имен.', + edit: 'Редактировать сертификат', + execShell: 'Выполнить скрипт после запроса сертификата.', + shell: 'Содержимое скрипта', + shellHelper: + 'Директория выполнения скрипта по умолчанию - директория установки 1Panel. Если сертификат отправляется в локальную директорию, директорией выполнения будет директория отправки сертификата. Тайм-аут выполнения по умолчанию - 30 минут.', + customAcme: 'Пользовательская служба ACME', + customAcmeURL: 'URL службы ACME', + baiduCloud: 'Baidu Cloud', + pushNode: 'Синхронизация с другими узлами', + pushNodeHelper: 'Отправить на выбранные узлы после заявки/продления', + fromMaster: 'Отправка с главного узла', + hostedZoneID: 'Hosted Zone ID', + isIP: 'IP-сертификат', + useEAB: 'Использовать аутентификацию EAB', + }, + firewall: { + create: 'Создать правило', + edit: 'Редактировать правило', + ccDeny: 'CC защита', + ipWhiteList: 'Белый список IP', + ipBlockList: 'Черный список IP', + fileExtBlockList: 'Черный список расширений файлов', + urlWhiteList: 'Белый список URL', + urlBlockList: 'Черный список URL', + argsCheck: 'Проверка GET параметров', + postCheck: 'Проверка POST параметров', + cookieBlockList: 'Черный список Cookie', + + dockerHelper: + 'Текущий брандмауэр не может отключить сопоставление портов контейнера. Установленные приложения могут перейти на страницу [Установленные], чтобы редактировать параметры приложения и настраивать правила открытия портов.', + iptablesHelper: + 'Обнаружено, что система использует брандмауэр {0}. Чтобы переключиться на iptables, сначала удалите его вручную!', + quickJump: 'Быстрый доступ', + used: 'Используется', + unUsed: 'Не используется', + dockerRestart: 'Операции с брандмауэром требуют перезапуска службы Docker', + firewallHelper: '{0} межсетевой экран', + firewallNotStart: 'Межсетевой экран в настоящее время не включен. Сначала включите его.', + restartFirewallHelper: 'Эта операция перезапустит текущий межсетевой экран. Хотите продолжить?', + stopFirewallHelper: 'Это лишит сервер защиты безопасности. Хотите продолжить?', + startFirewallHelper: + 'После включения межсетевого экрана безопасность сервера будет лучше защищена. Хотите продолжить?', + noPing: 'Отключить ping', + enableBanPing: 'Блокировать Ping', + disableBanPing: 'Разблокировать Ping', + noPingTitle: 'Отключить ping', + noPingHelper: 'Это отключит ping, и сервер не будет отвечать на ICMP-запросы. Хотите продолжить?', + onPingHelper: 'Это включит ping, и хакеры смогут обнаружить ваш сервер. Хотите продолжить?', + changeStrategy: 'Изменить стратегию {0}', + changeStrategyIPHelper1: + 'Изменить стратегию IP-адреса на [deny]. После установки IP-адреса доступ к серверу будет запрещен. Хотите продолжить?', + changeStrategyIPHelper2: + 'Изменить стратегию IP-адреса на [allow]. После установки IP-адреса нормальный доступ будет восстановлен. Хотите продолжить?', + changeStrategyPortHelper1: + 'Изменить политику портов на [drop]. После установки политики портов внешний доступ будет запрещен. Хотите продолжить?', + changeStrategyPortHelper2: + 'Изменить политику портов на [accept]. После установки политики портов нормальный доступ к портам будет восстановлен. Хотите продолжить?', + stop: 'Остановить', + portFormatError: 'Это поле должно быть действительным портом.', + portHelper1: 'Несколько портов, например 8080 и 8081', + portHelper2: 'Диапазон портов, например 8080-8089', + changeStrategyHelper: + 'Изменить стратегию {0} [{1}] на [{2}]. После установки {0} будет иметь внешний доступ {2}. Хотите продолжить?', + + strategy: 'Стратегия', + accept: 'Принять', + drop: 'Отбросить', + anyWhere: 'Любой', + address: 'Указанные IP', + addressHelper: 'Поддерживает IP-адрес или сегмент IP', + allow: 'Разрешить', + deny: 'Запретить', + addressFormatError: 'Это поле должно быть действительным IP-адресом.', + addressHelper1: 'Поддерживает IP-адрес или диапазон IP. Например, "172.16.10.11" или "172.16.10.0/24".', + addressHelper2: 'Для нескольких IP-адресов разделяйте запятой. Например, "172.16.10.11, 172.16.0.0/24".', + allIP: 'Все IP', + portRule: 'Правило | Правила', + createPortRule: '@:commons.button.create @.lower:firewall.portRule', + forwardRule: 'Правило переадресации портов | Правила переадресации портов', + createForwardRule: '@:commons.button.create @:firewall.forwardRule', + ipRule: 'IP правило | IP правила', + createIpRule: '@:commons.button.create @:firewall.ipRule', + userAgent: 'Фильтр User-Agent', + sourcePort: 'Исходный порт', + targetIP: 'Целевой IP', + targetPort: 'Целевой порт', + forwardHelper1: + 'Если вы хотите перенаправить на локальный порт, целевой IP должен быть установлен как "127.0.0.1".', + forwardHelper2: 'Оставьте целевой IP пустым для перенаправления на локальный порт.', + forwardPortHelper: 'Поддерживает диапазоны портов, напр. 8080-8089', + forwardInboundInterface: 'Сетевой интерфейс для пересылки входящего трафика', + exportHelper: 'Собираюсь экспортировать {0} правил брандмауэра. Продолжить?', + importSuccess: 'Успешно импортировано {0} правил', + importPartialSuccess: 'Импорт завершён: {0} успешно, {1} с ошибкой', + + ipv4Limit: 'Текущая операция поддерживает только адреса IPv4', + basicStatus: 'Текущая цепочка {0} не привязана, сначала привяжите!', + baseIptables: 'Сервис iptables', + forwardIptables: 'Сервис Переадресации Порта iptables', + advanceIptables: 'Сервис Расширенной Конфигурации iptables', + initMsg: 'Собираюсь инициализировать {0}, продолжить?', + initHelper: + 'Обнаружено, что {0} не инициализирован. Нажмите кнопку инициализации в верхней строке состояния для настройки!', + bindHelper: 'Привязать - Правила брандмауэра вступят в силу только когда статус привязан. Подтвердить?', + unbindHelper: + 'Отвязать - При отвязке все добавленные правила брандмауэра станут недействительными. Действуйте осторожно. Подтвердить?', + defaultStrategy: 'Политика по умолчанию для текущей цепочки {0} - {1}', + defaultStrategy2: + 'Политика по умолчанию для текущей цепочки {0} - {1}, текущий статус - не привязан. Добавленные правила брандмауэра вступят в силу после привязки!', + filterRule: 'Правило Фильтра', + filterHelper: + 'Правила фильтра позволяют управлять сетевым трафиком на уровне INPUT/OUTPUT. Настраивайте осторожно, чтобы избежать блокировки системы.', + chain: 'Цепочка', + targetChain: 'Целевая Цепочка', + sourceIP: 'Исходный IP', + destIP: 'Целевой IP', + inboundDirection: 'Входящее Направление', + outboundDirection: 'Исходящее Направление', + destPort: 'Целевой Порт', + action: 'Действие', + reject: 'Отклонить', + sourceIPHelper: 'Формат CIDR, напр. 192.168.1.0/24. Оставьте пустым для всех адресов', + destIPHelper: 'Формат CIDR, напр. 10.0.0.0/8. Оставьте пустым для всех адресов', + portHelper: '0 означает любой порт', + allPorts: 'Все Порта', + deleteRuleConfirm: 'Удалит {0} правил. Продолжить?', + }, + runtime: { + runtime: 'Среда выполнения', + workDir: 'Рабочая директория', + create: 'Создать среду выполнения', + localHelper: 'По вопросам установки в локальной среде и использования в автономном режиме обратитесь к ', + versionHelper: 'Версия PHP, например v8.0', + buildHelper: + 'Чем больше расширений, тем выше загрузка процессора при создании образа. Расширения можно установить после создания среды.', + openrestyWarn: 'PHP требует обновления OpenResty до версии 1.21.4.1 или выше для использования', + toupgrade: 'Обновить', + edit: 'Редактировать среду выполнения', + extendHelper: + 'Неназванные расширения можно вручную ввести и выбрать. Например, введите "sockets" и выберите первый вариант из выпадающего списка, чтобы увидеть список расширений.', + rebuildHelper: 'После редактирования расширения необходимо пересобрать PHP приложение для применения изменений', + rebuild: 'Пересобрать PHP приложение', + source: 'Источник расширений PHP', + ustc: 'Научно-технический университет Китая', + netease: 'Netease', + aliyun: 'Alibaba Cloud', + default: 'по умолчанию', + tsinghua: 'Университет Цинхуа', + xtomhk: 'Зеркало XTOM (Гонконг)', + xtom: 'Зеркало XTOM (Глобальное)', + phpsourceHelper: 'Выберите подходящий источник в соответствии с вашей сетевой средой.', + appPort: 'Порт приложения', + externalPort: 'Внешний порт', + packageManager: 'Менеджер пакетов', + codeDir: 'Директория кода', + appPortHelper: 'Порт, используемый приложением.', + externalPortHelper: 'Порт, открытый для внешнего мира.', + runScript: 'Скрипт запуска', + runScriptHelper: 'Список команд запуска анализируется из файла package.json в исходной директории.', + open: 'Открыть', + operatorHelper: 'Операция {0} будет выполнена для выбранной среды выполнения. Хотите продолжить?', + taobao: 'Taobao', + tencent: 'Tencent', + imageSource: 'Источник образа', + moduleManager: 'Управление модулями', + module: 'Модуль', + nodeOperatorHelper: + 'Выполнить {0} модуля {1}? Операция может вызвать аномалии в среде выполнения, пожалуйста, подтвердите перед продолжением', + customScript: 'Пользовательская команда запуска', + customScriptHelper: + 'Пожалуйста, введите полную команду запуска, например: npm run start. Для команд запуска PM2 замените на pm2-runtime, иначе запуск завершится ошибкой.', + portError: 'Не повторяйте один и тот же порт.', + systemRestartHelper: 'Описание статуса: Прерывание - получение статуса не удалось из-за перезагрузки системы', + javaScriptHelper: 'Укажите полную команду запуска. Например, "java -jar halo.jar -Xmx1024M -Xms256M".', + javaDirHelper: 'Директория должна содержать jar файлы, поддиректории также допустимы', + goHelper: 'Укажите полную команду запуска. Например, "go run main.go" или "./main".', + goDirHelper: 'Директория или поддиректория должна содержать файлы Go или бинарные файлы.', + pythonHelper: + 'Укажите полную команду запуска. Например, "pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000".', + dotnetHelper: 'Пожалуйста, укажите полную команду запуска, например, dotnet MyWebApp.dll', + dirHelper: 'Примечание: Укажите путь к каталогу внутри контейнера', + concurrency: 'Схема параллелизма', + loadStatus: 'Состояние нагрузки', + extraHosts: 'Сопоставление хостов', + }, + process: { + pid: 'ID процесса', + ppid: 'Родительский PID', + numThreads: 'Потоки', + memory: 'Память', + diskRead: 'Чтение диска', + diskWrite: 'Запись диска', + netSent: 'исходящий', + netRecv: 'входящий', + numConnections: 'Соединения', + startTime: 'Время запуска', + state: 'Состояние', + running: 'Работает', + sleep: 'сон', + stop: 'остановлен', + idle: 'простой', + zombie: 'зомби-процесс', + wait: 'ожидание', + lock: 'блокировка', + blocked: 'заблокирован', + cmdLine: 'Команда запуска', + basic: 'Основное', + mem: 'Память', + openFiles: 'Открытые файлы', + env: 'Переменные окружения', + noenv: 'Нет', + net: 'Сетевые подключения', + laddr: 'Исходный адрес/порт', + raddr: 'Целевой адрес/порт', + stopProcess: 'Завершить', + viewDetails: 'Подробности', + stopProcessWarn: 'Вы уверены, что хотите завершить этот процесс (PID:{0})?', + kill: 'Завершить Процесс', + killNow: 'Завершить Сейчас', + killHelper: 'Завершение процесса {0} может привести к некорректной работе некоторых программ. Продолжить?', + processName: 'Имя процесса', + }, + tool: { + supervisor: { + loadStatusErr: 'Не удалось получить статус процесса, пожалуйста, проверьте состояние службы supervisor.', + notSupport: + 'Служба Supervisor не обнаружена. Перейдите на страницу библиотеки скриптов для ручной установки', + list: 'Демон-процесс | Демон-процессы', + config: 'Конфигурация Supervisor', + primaryConfig: 'Расположение основного конфигурационного файла', + notSupportCtl: + 'supervisorctl не обнаружен. Перейдите на страницу библиотеки скриптов для ручной установки.', + user: 'Пользователь', + command: 'Команда', + dir: 'Директория', + numprocs: 'Кол-во процессов', + initWarn: + 'Это изменит значение "files" в секции "[include"] в основном конфигурационном файле. Директория других конфигурационных файлов будет: "{директория установки 1Panel}/1panel/tools/supervisord/supervisor.d/".', + operatorHelper: 'Операция {1} будет выполнена для {0}, продолжить?', + uptime: 'Время работы', + notStartWarn: 'Supervisor не запущен. Сначала запустите его.', + serviceName: 'Имя службы', + initHelper: + 'Обнаружен сервис Supervisor, но он не инициализирован. Нажмите кнопку инициализации в верхней панели состояния для настройки.', + serviceNameHelper: 'Имя службы Supervisor, управляемой systemctl, обычно supervisor или supervisord', + restartHelper: + 'Это перезапустит службу после инициализации, что приведет к остановке всех существующих демон-процессов.', + RUNNING: 'Работает', + STOPPED: 'Остановлен', + STOPPING: 'Останавливается', + STARTING: 'Запускается', + FATAL: 'Не удалось запустить', + BACKOFF: 'Исключение при запуске', + ERROR: 'Ошибка', + statusCode: 'Код статуса', + manage: 'Управление', + autoRestart: 'Автоматический перезапуск', + EXITED: 'Вышел', + autoRestartHelper: 'Автоматически перезапускать программу после её аварийного завершения', + autoStart: 'Автозапуск', + autoStartHelper: 'Автоматически запускать сервис после запуска Supervisor', + }, + }, + disk: { + management: 'Управление дисками', + partition: 'Раздел', + unmount: 'Отмонтировать', + unmountHelper: 'Вы хотите отмонтировать раздел {0}?', + mount: 'Подключить', + partitionAlert: + 'Разделение диска требует форматирования диска, и существующие данные будут удалены. Пожалуйста, сохраните или сделайте снимки данных заранее.', + mountPoint: 'Точка монтирования', + systemDisk: 'Системный диск', + unpartitionedDisk: 'Неразделенный диск', + handlePartition: 'Разделить сейчас', + filesystem: 'Файловая система', + unmounted: 'Отмонтирован', + cannotOperate: 'Невозможно выполнить операцию', + systemDiskHelper: 'Подсказка: Текущий диск является системным диском, операции невозможны.', + autoMount: 'Автоматическое монтирование', + model: 'Модель устройства', + diskType: 'Тип диска', + serial: 'Серийный номер', + noFail: 'Сбой монтирования не влияет на запуск системы', + }, + xpack: { + expiresTrialAlert: + 'Дружеское напоминание: ваша пробная версия Pro истечет через {0} дней, и все функции Pro станут недоступны. Пожалуйста, своевременно продлите или обновите до полной версии.', + expiresAlert: + 'Дружеское напоминание: ваша лицензия Pro истечет через {0} дней, и все функции Pro станут недоступны. Пожалуйста, продлите лицензию вовремя, чтобы обеспечить дальнейшее использование.', + menu: 'Рro', + upage: 'AI Конструктор сайтов', + proAlert: 'Обновитесь до Pro, чтобы использовать эту функцию', + app: { + app: 'APP', + title: 'Псевдоним панели', + titleHelper: 'Псевдоним панели используется для отображения в приложении (псевдоним панели по умолчанию)', + qrCode: 'QR код', + apiStatusHelper: 'APP панели необходимо включить функцию API интерфейса', + apiInterfaceHelper: + 'Поддержка доступа к API-интерфейсу панели (эта функция должна быть включена для мобильного приложения панели)', + apiInterfaceHelper1: + 'Для доступа к приложению панели необходимо добавить посетителя в белый список. Для нестабильных IP рекомендуется добавить 0.0.0.0/0 (все IPv4), ::/0 (все IPv6)', + qrCodeExpired: 'Время обновления', + apiLeakageHelper: 'Не раскрывайте QR-код. Убедитесь, что он используется только в доверенных средах.', + }, + waf: { + name: 'WAF', + blackWhite: 'Черный и белый список', + globalSetting: 'Глобальные настройки', + websiteSetting: 'Настройки сайта', + blockRecords: 'Записи блокировки', + world: 'Мир', + china: 'Китай', + intercept: 'Перехват', + request: 'Запросы', + count4xx: 'Количество 4xx', + count5xx: 'Количество 5xx', + todayStatus: 'Статус на сегодня', + reqMap: 'Карта атак (последние 30 дней)', + resource: 'Источник', + count: 'Количество', + hight: 'Высокий', + low: 'Низкий', + reqCount: 'Запросы', + interceptCount: 'Количество перехватов', + requestTrends: 'Тренды запросов (последние 7 дней)', + interceptTrends: 'Тренды перехватов (последние 7 дней)', + whiteList: 'Белый список', + blackList: 'Черный список', + ipBlackListHelper: 'IP-адреса в черном списке блокируются от доступа к сайту', + ipWhiteListHelper: 'IP-адреса в белом списке обходят все ограничения', + uaBlackListHelper: 'Запросы с значениями User-Agent в черном списке будут заблокированы', + uaWhiteListHelper: 'Запросы с значениями User-Agent в белом списке обходят все ограничения', + urlBlackListHelper: 'Запросы к URL-адресам в черном списке будут заблокированы', + urlWhiteListHelper: 'Запросы к URL-адресам в белом списке обходят все ограничения', + ccHelper: + 'Если сайт получает более {1} запросов с одного IP-адреса в течение {0} секунд, этот IP будет заблокирован на {2}', + blockTime: 'Время блокировки', + attackHelper: 'Если кумулятивные перехваты превышают {1} за {0} секунд, IP будет заблокирован на {2}', + notFoundHelper: + 'Если кумулятивные запросы возвращают ошибку 404 более {1} раз за {0} секунд, IP будет заблокирован на {2}', + frequencyLimit: 'Ограничение частоты', + regionLimit: 'Ограничение по региону', + defaultRule: 'Правила по умолчанию', + accessFrequencyLimit: 'Ограничение частоты доступа', + attackLimit: 'Ограничение частоты атак', + notFoundLimit: 'Ограничение частоты 404', + urlLimit: 'Ограничение частоты URL', + urlLimitHelper: 'Установите частоту доступа для одного URL', + sqliDefense: 'Защита от SQL-инъекций', + sqliHelper: 'Обнаружение SQL-инъекций в запросах и их блокировка', + xssHelper: 'Обнаружение XSS в запросах и их блокировка', + xssDefense: 'Защита от XSS', + uaDefense: 'Правила для вредоносных User-Agent', + uaHelper: 'Включает правила для определения общих вредоносных ботов', + argsDefense: 'Правила для вредоносных параметров', + argsHelper: 'Блокирует запросы, содержащие вредоносные параметры', + cookieDefense: 'Правила для вредоносных Cookies', + cookieHelper: 'Запрещает передачу вредоносных Cookies в запросах', + headerDefense: 'Правила для вредоносных заголовков', + headerHelper: 'Запрещает запросы, содержащие вредоносные заголовки', + httpRule: 'Правила HTTP-запросов', + httpHelper: + 'Установите типы методов, которые могут получить доступ. Если вы хотите ограничить определенные типы доступа, отключите эти кнопки. Например: если разрешен только доступ GET, то нужно отключить другие методы, кроме GET', + geoRule: 'Ограничения по региону', + geoHelper: + 'Ограничьте доступ к вашему сайту с определенных регионов. Например, если доступ разрешен только из Китая, запросы из-за пределов Китая будут заблокированы', + ipLocation: 'Местоположение IP', + action: 'Действие', + ruleType: 'Тип атаки', + ipHelper: 'Введите IP-адрес', + attackLog: 'Журнал атак', + rule: 'Правило', + ipArr: 'Диапазон IPV4', + ipStart: 'Начальный IP', + ipEnd: 'Конечный IP', + ipv4: 'IPv4', + ipv6: 'IPv6', + urlDefense: 'Правила для URL', + urlHelper: 'Запрещенный URL', + dirFilter: 'Фильтрация по директориям', + sqlInject: 'SQL-инъекция', + xss: 'XSS', + phpExec: 'Выполнение PHP-скриптов', + oneWordTrojan: 'Троян одно слово', + appFilter: 'Фильтрация опасных директорий', + webshell: 'Webshell', + args: 'Вредоносные параметры', + protocolFilter: 'Фильтр протоколов', + javaFilter: 'Фильтрация опасных файлов Java', + scannerFilter: 'Фильтрация сканеров', + escapeFilter: 'Фильтрация экранирования', + customRule: 'Пользовательские правила', + httpMethod: 'Фильтрация HTTP-методов', + fileExt: 'Ограничение на загрузку файлов', + fileExtHelper: 'Запрещенные расширения файлов для загрузки', + deny: 'Запрещено', + allow: 'Разрешить', + field: 'Объект', + pattern: 'Условие', + ruleContent: 'Содержание', + contain: 'содержит', + equal: 'равно', + regex: 'регулярное выражение', + notEqual: 'Не равно', + customRuleHelper: 'Применяйте действия на основе указанных условий', + actionAllow: 'Разрешить', + blockIP: 'Заблокировать IP', + code: 'Код статуса ответа', + noRes: 'Отключить (444)', + badReq: 'Неверные параметры (400)', + forbidden: 'Запрещено (403)', + serverErr: 'Ошибка сервера (500)', + resHtml: 'Страница ответа', + allowHelper: 'Разрешение доступа пропустит последующие WAF-правила, используйте с осторожностью', + captcha: 'Проверка человеком', + fiveSeconds: '5-секундная проверка', + location: 'Регион', + redisConfig: 'Конфигурация Redis', + redisHelper: 'Включите Redis для сохранения временно заблокированных IP-адресов', + wafHelper: 'Все сайты потеряют защиту после отключения', + attackIP: 'IP атакующего', + attackParam: 'Детали атаки', + execRule: 'Срабатывающее правило', + acl: 'ACL', + sql: 'SQL-инъекция', + cc: 'Ограничение частоты доступа', + isBlocking: 'Заблокирован', + isFree: 'Не заблокирован', + unLock: 'Разблокировать', + unLockHelper: 'Вы хотите разблокировать IP: {0}?', + saveDefault: 'Сохранить по умолчанию', + saveToWebsite: 'Применить к сайту', + saveToWebsiteHelper: 'Применить текущие настройки ко всем сайтам?', + websiteHelper: + 'Это настройки по умолчанию для создания сайта. Изменения должны быть применены к сайту, чтобы вступить в силу', + websiteHelper2: + 'Это настройки по умолчанию для создания сайта. Пожалуйста, измените конкретную конфигурацию на сайте', + ipGroup: 'Группа IP', + ipGroupHelper: + 'По одному IP или диапазону IP на строку, поддерживает IPv4 и IPv6, например: 192.168.1.1 или 192.168.1.0/24', + ipBlack: 'Черный список IP', + openRestyAlert: 'Версия OpenResty должна быть выше {0}', + initAlert: + 'Необходима инициализация для первого использования, файл конфигурации сайта будет изменен, и оригинальная конфигурация WAF будет потеряна. Пожалуйста, сделайте резервную копию OpenResty заранее', + initHelper: 'Инициализация удалит текущую конфигурацию WAF. Вы уверены, что хотите инициализировать?', + mainSwitch: 'Главный переключатель', + websiteAlert: 'Пожалуйста, создайте сайт сначала', + defaultUrlBlack: 'Правила для URL', + htmlRes: 'Страница перехвата', + urlSearchHelper: 'Введите URL для поддержки нечеткого поиска', + toCreate: 'Создать', + closeWaf: 'Закрыть WAF', + closeWafHelper: 'Закрытие WAF приведет к потере защиты сайта, продолжить?', + addblack: 'Черный', + addwhite: 'Добавить белый', + addblackHelper: 'Добавить IP:{0} в черный список по умолчанию?', + addwhiteHelper: 'Добавить IP:{0} в белый список по умолчанию?', + defaultUaBlack: 'Правило для User-Agent', + defaultIpBlack: 'Вредоносная группа IP', + cookie: 'Правила для Cookies', + urlBlack: 'Черный список URL', + uaBlack: 'Черный список User-Agent', + attackCount: 'Ограничение частоты атак', + fileExtCheck: 'Ограничение на загрузку файлов', + geoRestrict: 'Ограничение по региону', + attacklog: 'Запись перехвата', + unknownWebsite: 'Неавторизованный доступ к домену', + geoRuleEmpty: 'Регион не может быть пустым', + unknown: 'Сайт не существует', + geo: 'Ограничение по региону', + revertHtml: 'Вы хотите восстановить {0} как страницу по умолчанию?', + five_seconds: '5-секундная проверка', + header: 'Правила заголовков', + methodWhite: 'HTTP-правила', + expiryDate: 'Дата истечения', + expiryDateHelper: 'После прохождения проверки она больше не будет выполняться в пределах срока действия', + defaultIpBlackHelper: 'Некоторые вредоносные IP-адреса, собранные с интернета, чтобы предотвратить доступ', + notFoundCount: 'Ограничение частоты 404', + matchValue: 'Значение для совпадения', + headerName: 'Поддерживает английский, цифры, -, длина 3-30', + cdnHelper: 'Сайты, использующие CDN, могут включить это для получения правильного исходного IP', + clearLogWarn: 'Очистка журнала невозможна, хотите продолжить?', + commonRuleHelper: 'Правило поддерживает нечеткое совпадение', + blockIPHelper: + 'Заблокированные IP-адреса временно хранятся в OpenResty и будут разблокированы при перезапуске OpenResty. Они могут быть заблокированы навсегда через функцию блокировки', + addWhiteUrlHelper: 'Добавить URL {0} в белый список?', + dashHelper: + 'В общественной версии также можно использовать функции в глобальных настройках и настройках сайта', + wafStatusHelper: 'WAF не включен, пожалуйста, включите его в глобальных настройках', + ccMode: 'Режим', + global: 'Глобальный режим', + uriMode: 'Режим URL', + globalHelper: + 'Глобальный режим: активируется, когда общее количество запросов к любому URL за определенный промежуток времени превышает порог', + uriModeHelper: + 'Режим URL: активируется, когда количество запросов к одному URL за определенный промежуток времени превышает порог', + + ip: 'Черный список IP', + globalSettingHelper: + 'Настройки с тегом [Website] должны быть включены в [Настройки сайта], а глобальные настройки являются только настройками по умолчанию для новых сайтов', + globalSettingHelper2: + 'Настройки должны быть включены как в [Глобальных настройках], так и в [Настройках сайта]', + urlCCHelper: 'Более {1} запросов к этому URL за {0} секунд, блокируем этот IP {2}', + urlCCHelper2: 'URL не может содержать параметры', + notContain: 'Не содержит', + urlcc: 'Ограничение частоты URL', + method: 'Тип запроса', + addIpsToBlock: 'Массовая блокировка IP', + addUrlsToWhite: 'Массовое добавление URL в белый список', + noBlackIp: 'IP уже заблокирован, не нужно блокировать снова', + noWhiteUrl: 'URL уже в белом списке, не нужно добавлять снова', + spiderIpHelper: + 'Включает Baidu, Bing, Google, 360, Shenma, Sogou, ByteDance, DuckDuckGo, Yandex. Закрытие этого параметра заблокирует доступ всех пауков.', + spiderIp: 'Пул IP-адресов поисковых роботов', + geoIp: 'Библиотека IP-адресов', + geoIpHelper: 'Используется для определения географического положения IP', + stat: 'Отчет об атаках', + statTitle: 'Отчет', + attackIp: 'IP-адрес атаки', + attackCountNum: 'Количество атак', + percent: 'Процент', + addblackUrlHelper: 'Добавить URL: {0} в черный список по умолчанию?', + rce: 'Удаленное выполнение кода', + software: 'Программное обеспечение', + cveHelper: 'Содержит уязвимости распространенных программ и фреймворков', + vulnCheck: 'Дополнительные правила', + ssrf: 'Уязвимость SSRF', + afr: 'Чтение произвольных файлов', + ua: 'Неавторизованный доступ', + id: 'Утечка информации', + aa: 'Обход аутентификации', + dr: 'Обход каталогов', + xxe: 'Уязвимость XXE', + suid: 'Уязвимость сериализации', + dos: 'Уязвимость отказа в обслуживании', + afd: 'Загрузка произвольных файлов', + sqlInjection: 'Внедрение SQL', + afw: 'Запись произвольных файлов', + il: 'Утечка информации', + clearAllLog: 'Очистить все логи', + exportLog: 'Экспортировать логи', + appRule: 'Правила приложений', + appRuleHelper: + 'Распространенные правила приложений, включение может уменьшить ложные срабатывания, один сайт может использовать только одно правило', + logExternal: 'Исключить типы записей', + ipWhite: 'Белый список IP', + urlWhite: 'Белый список URL', + uaWhite: 'Белый список User-Agent', + logExternalHelper: + 'Исключенные типы записей не будут регистрироваться в логах, черные/белые списки, региональные ограничения доступа и пользовательские правила генерируют много логов, рекомендуется исключить', + ssti: 'Атака SSTI', + crlf: 'Инъекция CRLF', + strict: 'Строгий режим', + strictHelper: 'Использует более строгие правила для проверки запросов', + saveLog: 'Сохранить лог', + remoteURLHelper: 'Удаленный URL должен содержать один IP на строку и не содержать других символов', + notFound: 'Not Found (404)', + serviceUnavailable: 'Сервис недоступен (503)', + gatewayTimeout: 'Тайм-аут шлюза (504)', + belongToIpGroup: 'Принадлежит к группе IP', + notBelongToIpGroup: 'Не принадлежит к группе IP', + unknownWebsiteKey: 'Неизвестный домен', + special: 'Специальный', + fileToLarge: 'Файл превышает 1MB и не может быть загружен', + uploadOverLimit: 'Загруженный файл превышает лимит количества, максимум 1 файл', + importRuleHelper: 'Одно правило на строку', + }, + monitor: { + name: 'Мониторинг веб-сайта', + pv: 'Просмотры страниц', + uv: 'Уникальные посетители', + flow: 'Трафик', + ip: 'IP', + spider: 'Поисковые роботы', + visitors: 'Тренды посетителей', + today: 'Сегодня', + last7days: 'Последние 7 дней', + last30days: 'Последние 30 дней', + uvMap: 'Карта посетителей (30-е число)', + qps: 'Запросы в реальном времени (в минуту)', + flowSec: 'Трафик в реальном времени (в минуту)', + excludeCode: 'Исключить статус-коды', + excludeUrl: 'Исключить URL', + excludeExt: 'Исключить расширения', + cdnHelper: 'Получение реального IP из заголовка, предоставленного CDN', + reqRank: 'Рейтинг посещений', + refererDomain: 'Домен-реферер', + os: 'Операционная система', + browser: 'Браузер/Клиент', + device: 'Устройство', + showMore: 'Подробнее', + unknown: 'Прочее', + pc: 'Компьютер', + mobile: 'Мобильное устройство', + wechat: 'WeChat', + machine: 'Машина', + tencent: 'Браузер Tencent', + ucweb: 'UC Browser', + '2345explorer': 'Браузер 2345', + huaweibrowser: 'Браузер Huawei', + log: 'Логи запросов', + statusCode: 'Статус-код', + requestTime: 'Время отклика', + flowRes: 'Трафик отклика', + method: 'Метод запроса', + statusCodeHelper: 'Введите статус-код выше', + statusCodeError: 'Неверный тип статус-кода', + methodHelper: 'Введите метод запроса выше', + all: 'Все', + baidu: 'Baidu', + google: 'Google', + bing: 'Bing', + bytes: 'Заголовки сегодня', + sogou: 'Sogou', + failed: 'Ошибка', + ipCount: 'Количество IP', + spiderCount: 'Запросы от поисковых роботов', + averageReqTime: 'Среднее время отклика', + totalFlow: 'Общий трафик', + logSize: 'Размер файла лога', + realIPType: 'Метод получения реального IP', + fromHeader: 'Получение из заголовка HTTP', + fromHeaders: 'Получение из списка заголовков', + header: 'HTTP-заголовок', + cdnConfig: 'Конфигурация CDN', + xff1: 'Первичный прокси из X-Forwarded-For', + xff2: 'Вторичный прокси из X-Forwarded-For', + xff3: 'Третичный прокси из X-Forwarded-For', + xffHelper: + 'Например: X-Forwarded-For: ,,,. Последний IP будет считаться прокси верхнего уровня', + headersHelper: + 'Получение реального IP из распространённых заголовков CDN HTTP, выбирая первое доступное значение', + monitorCDNHelper: 'Изменение конфигурации CDN для мониторинга веб-сайта также обновит настройки WAF CDN', + wafCDNHelper: 'Изменение конфигурации WAF CDN также обновит настройки мониторинга веб-сайта', + statusErr: 'Неверный формат статус-кода', + shenma: 'Shenma Search', + duckduckgo: 'DuckDuckGo', + '360': '360 Search', + excludeUri: 'Исключить URI', + top100Helper: 'Показать топ 100 данных', + logSaveDay: 'Период хранения логов (дни)', + cros: 'Chrome OS', + theworld: 'Браузер TheWorld', + edge: 'Microsoft Edge', + maxthon: 'Браузер Maxthon', + monitorStatusHelper: 'Мониторинг не включён, пожалуйста, включите его в настройках', + excludeIp: 'Исключить IP-адреса', + excludeUa: 'Исключить User-Agent', + remotePort: 'Удаленный порт', + unknown_browser: 'Неизвестно', + unknown_os: 'Неизвестно', + unknown_device: 'Неизвестно', + logSaveSize: 'Максимальный размер сохранения логов', + logSaveSizeHelper: 'Это размер сохранения логов для одного сайта', + '360se': '360 Secure Browser', + websites: 'Список веб-сайтов', + trend: 'Статистика тренда', + reqCount: 'Количество запросов', + uriHelper: 'Вы можете использовать /test/* или /*/index.php для исключения Uri', + }, + tamper: { + tamper: 'Защита от подделки сайта', + ignoreTemplate: 'Шаблон Исключения', + protectTemplate: 'Шаблон Защиты', + ignoreTemplateHelper: + 'Введите содержание исключения, разделенные Enter или пробелом. (Конкретный каталог ./log или имя каталога tmp, для исключения файлов нужно ввести конкретный файл ./data/test.html)', + protectTemplateHelper: + 'Введите содержание защиты, разделенные Enter или пробелом. (Конкретный файл ./index.html, расширение файла .html, тип файла js, для защиты каталогов нужно ввести конкретный каталог ./log)', + templateContent: 'Содержание Шаблона', + template: 'Шаблон', + saveTemplate: 'Сохранить как Шаблон', + tamperHelper1: + 'Для веб-сайтов с развертыванием в один клик рекомендуется включить защиту от несанкционированного доступа к каталогу приложений; если веб-сайт не может нормально использоваться или резервное копирование/восстановление не удается, сначала отключите защиту от несанкционированного доступа;', + tamperHelper2: + 'Ограничит операции чтения/записи, удаления, разрешений и изменения владельца для защищенных файлов в неисключенных каталогах', + tamperPath: 'Каталог Защиты', + tamperPathEdit: 'Изменить Путь', + log: 'Журнал Блокировки', + totalProtect: 'Общая Защита', + todayProtect: 'Сегодняшняя Защита', + templateRule: 'Длина 1-512, имя не может содержать {0} и другие символы', + ignore: 'Исключить', + ignoreHelper: + 'Выберите или введите содержание исключения, разделенные Enter или пробелом. (Конкретный каталог ./log или имя каталога tmp, для исключения файлов нужно ввести или выбрать конкретный файл ./data/test.html)', + protect: 'Защитить', + protectHelper: + 'Выберите или введите содержание защиты, разделенные Enter или пробелом. (Конкретный файл ./index.html, расширение файла .html, тип файла js, для защиты каталогов нужно ввести или выбрать конкретный каталог ./log)', + tamperHelper00: 'Исключение и защита поддерживают только относительные пути;', + tamperHelper01: + 'После включения защиты от несанкционированного доступа система ограничит операции создания, редактирования и удаления защищенных файлов в неисключенных каталогах;', + tamperHelper02: 'Приоритет: Защита конкретного пути > Исключение конкретного пути > Защита > Исключение', + tamperHelper03: + 'Операции мониторинга направлены только на неисключенные каталоги, отслеживая создание незащищенных файлов в этих каталогах.', + disableHelper: + 'Собираетесь отключить защиту от несанкционированного доступа для следующих веб-сайтов, продолжить?', + appendOnly: 'Только Добавление', + appendOnlyHelper: + 'Ограничивает операции удаления файлов в этом каталоге, разрешая только добавление исключенных каталогов или незащищенных файлов', + immutable: 'Неизменяемый', + immutableHelper: + 'Ограничивает операции редактирования, удаления, изменения разрешений и владельца для этого файла', + onWatch: 'Наблюдение', + onWatchHelper: + 'Отслеживает и перехватывает создание защищенных файлов или неисключенных каталогов в этом каталоге', + forceStop: 'Принудительное закрытие', + forceStopHelper: + 'Собираетесь принудительно отключить функцию защиты от несанкционированного доступа для этого каталога веб-сайта. Продолжить?', + }, + setting: { + setting: 'Настройки Панели', + title: 'Описание Панели', + titleHelper: + 'Будет отображаться на странице входа пользователя (например, панель управления Linux сервером, рекомендуется от 8 до 15 символов)', + logo: 'Логотип (Без Текста)', + logoHelper: + 'Будет отображаться в верхнем левом углу страницы управления при свёрнутом меню (рекомендуемый размер изображения: 82px*82px)', + logoWithText: 'Логотип (С Текстом)', + logoWithTextHelper: + 'Будет отображаться в верхнем левом углу страницы управления при развернутом меню (рекомендуемый размер изображения: 185px*55px)', + favicon: 'Иконка Сайта', + faviconHelper: 'Иконка сайта (рекомендуемый размер изображения: 16px*16px)', + setDefault: 'Восстановить По Умолчанию', + setHelper: 'Текущие настройки будут сохранены. Вы хотите продолжить?', + setDefaultHelper: 'Все настройки панели будут восстановлены по умолчанию. Вы хотите продолжить?', + logoGroup: 'Логотип', + imageGroup: 'Изображение', + loginImage: 'Изображение страницы входа', + loginImageHelper: 'Будет отображаться на странице входа (Рекомендуемый размер: 500x416px)', + loginBgType: 'Тип фона страницы входа', + loginBgImage: 'Фоновое изображение страницы входа', + loginBgImageHelper: 'Будет отображаться как фон страницы входа (Рекомендуемый размер: 1920x1080px)', + loginBgColor: 'Цвет фона страницы входа', + loginBgColorHelper: 'Будет отображаться как цвет фона страницы входа', + image: 'Изображение', + bgColor: 'Цвет фона', + loginGroup: 'Страница входа', + loginBtnLinkColor: 'Цвет кнопки/ссылки', + loginBtnLinkColorHelper: 'Будет отображаться как цвет кнопки/ссылки на странице входа', + }, + helper: { + wafTitle1: 'Карта Перехватов', + wafContent1: 'Отображает географическое распределение перехватов за последние 30 дней', + wafTitle2: 'Ограничения Регионального Доступа', + wafContent2: 'Ограничивайте источники доступа к сайту в зависимости от географического расположения', + wafTitle3: 'Пользовательская Страница Перехвата', + wafContent3: 'Создайте пользовательскую страницу для отображения после перехвата запроса', + wafTitle4: 'Пользовательские Правила (ACL)', + wafContent4: 'Перехватывайте запросы в соответствии с пользовательскими правилами', + + tamperTitle1: 'Мониторинг Целостности Файлов', + tamperContent1: + 'Мониторинг целостности файлов веб-сайта, включая основные файлы, скрипты и файлы конфигурации.', + tamperTitle2: 'Реальное Сканирование и Обнаружение', + tamperContent2: + 'Обнаруживайте аномальные или измененные файлы, выполняя сканирование файловой системы веб-сайта в реальном времени.', + tamperTitle3: 'Настройки Разрешений Безопасности', + tamperContent3: + 'Ограничивайте доступ к файлам веб-сайта с помощью правильных настроек разрешений и политик контроля доступа, снижая потенциальную поверхность атаки.', + tamperTitle4: 'Ведение Логов и Анализ', + tamperContent4: + 'Записывайте логи доступа и операций с файлами для последующего аудита и анализа администраторами, а также для выявления потенциальных угроз безопасности.', + + settingTitle1: 'Пользовательское Приветственное Сообщение', + settingContent1: 'Установите пользовательское приветственное сообщение на странице входа в 1Panel.', + settingTitle2: 'Пользовательский Логотип', + settingContent2: 'Разрешите загрузку изображений логотипов, содержащих названия брендов или другой текст.', + settingTitle3: 'Пользовательская Иконка Сайта', + settingContent3: + 'Разрешите загрузку пользовательских иконок для замены стандартной иконки браузера, улучшая пользовательский опыт.', + + monitorTitle1: 'Тренд Посетителей', + monitorContent1: 'Статистика и отображение тенденций посещаемости веб-сайта', + monitorTitle2: 'Карта Посетителей', + monitorContent2: 'Статистика и отображение географического распределения посетителей веб-сайта', + monitorTitle3: 'Статистика Доступа', + monitorContent3: + 'Статистика запросов веб-сайта, включая поисковые боты, устройства доступа, статус запросов и т. д.', + monitorTitle4: 'Мониторинг в Реальном Времени', + monitorContent4: + 'Мониторинг запросов веб-сайта в реальном времени, включая количество запросов, трафик и т. д.', + + alertTitle1: 'SMS Уведомления', + alertContent1: + 'При аномальном использовании ресурсов сервера, истечении срока действия сайта и сертификата, появлении новой версии или истечении срока действия пароля пользователи будут уведомлены через SMS, чтобы обеспечить своевременную обработку.', + alertTitle2: 'Журнал Уведомлений', + alertContent2: + 'Предоставляет пользователям возможность просмотра журналов уведомлений, чтобы облегчить отслеживание и анализ исторических событий.', + alertTitle3: 'Настройки Уведомлений', + alertContent3: + 'Предоставляет пользователям возможность настройки номеров телефонов, частоты и времени отправки уведомлений в день, что позволяет настраивать более удобные уведомления.', + + nodeDashTitle1: 'Управление Приложениями', + nodeDashContent1: + 'Единое управление приложениями на нескольких узлах, поддерживает мониторинг состояния, быстрое запуск/остановку, подключение терминала и резервное копирование', + nodeDashTitle2: 'Управление Веб-сайтами', + nodeDashContent2: + 'Единое управление веб-сайтами на нескольких узлах, мониторинг состояния в реальном времени, поддерживает пакетный запуск/остановку и быстрое резервное копирование', + nodeDashTitle3: 'Управление Базами Данных', + nodeDashContent3: + 'Единое управление базами данных на нескольких узлах, ключевые состояния с первого взгляда, поддерживает резервное копирование в один клик', + nodeDashTitle4: 'Управление Запланированными Задачами', + nodeDashContent4: + 'Единое управление запланированными задачами на нескольких узлах, поддерживает мониторинг состояния, быстрый запуск/остановку и выполнение по ручному триггеру', + + nodeTitle1: 'Добавление узла одним кликом', + nodeContent1: 'Быстро интегрируйте несколько серверных узлов', + nodeTitle2: 'Пакетное обновление', + nodeContent2: 'Синхронизируйте и обновите все узлы одной операцией', + nodeTitle3: 'Мониторинг статуса узла', + nodeContent3: 'Реальное наблюдение за рабочим статусом каждого узла', + nodeTitle4: 'Быстрое удаленное подключение', + nodeContent4: 'Одним кликом подключитесь к удаленным терминалам узлов', + + fileExchangeTitle1: 'Передача с аутентификацией по ключу', + fileExchangeContent1: 'Аутентифицируйтесь через SSH-ключи, чтобы обеспечить безопасность передачи.', + fileExchangeTitle2: 'Эффективная синхронизация файлов', + fileExchangeContent2: + 'Только синхронизируйте измененное содержимое, чтобы значительно повысить скорость и стабильность передачи.', + fileExchangeTitle3: 'Поддержка обмена между несколькими узлами', + fileExchangeContent3: + 'Легко передавайте проектные файлы между разными узлами, гибко управляйте несколькими серверами.', + + nodeAppTitle1: 'Управление Обновлениями Приложений', + nodeAppContent1: + 'Единый мониторинг обновлений приложений на нескольких узлах, поддерживает обновление в один клик', + + appTitle1: 'Гибкое управление панелью', + appContent1: 'Легко управляйте сервером 1Panel в любое время и в любом месте.', + appTitle2: 'Полная информация о сервисе', + appContent2: + 'Управляйте приложениями, сайтами, Docker, базами данных и т. д. через мобильное приложение и создавайте приложения и сайты быстро.', + appTitle3: 'Мониторинг аномалий в реальном времени', + appContent3: + 'Просматривайте статус сервера, мониторинг безопасности WAF, статистику посещений сайта и состояние процессов в реальном времени через мобильное приложение.', + + clusterTitle1: 'Развертывание мастер-слейв', + clusterContent1: + 'Поддерживает создание мастер-слейв экземпляров MySQL/Postgres/Redis на разных узлах, автоматически завершая связь мастер-слейв и инициализацию', + clusterTitle2: 'Управление мастер-слейв', + clusterContent2: + 'Единая страница для централизованного управления несколькими узлами мастер-слейв, просмотр их ролей, статуса выполнения и т.д.', + clusterTitle3: 'Состояние репликации', + clusterContent3: + 'Отображает состояние репликации мастер-слейв и информацию о задержке, помогая в устранении проблем синхронизации', + }, + node: { + master: 'Главный узел', + masterBackup: 'Резервная копия главного узла', + backupNode: 'Резервный узел', + backupFrequency: 'Частота резервного копирования (часы)', + backupCopies: 'Количество сохраняемых резервных копий', + noBackupNode: + 'Резервный узел в настоящее время пуст. Выберите резервный узел для сохранения и повторите попытку!', + masterBackupAlert: + 'Резервное копирование главного узла не настроено. Для обеспечения безопасности данных, пожалуйста, настройте резервный узел как можно скорее, чтобы можно было вручную переключиться на новый главный узел в случае сбоя.', + node: 'Узел', + addr: 'Адрес', + nodeUpgrade: 'Настройки Обновления', + nodeUpgradeHelper: + 'Выбранные узлы автоматически начнут обновляться после завершения обновления основного узла, ручное вмешательство не требуется.', + nodeUnhealthy: 'Некорректное состояние узла', + deletedNode: 'Удалённый узел {0} в настоящее время не поддерживает операции обновления!', + nodeUnhealthyHelper: + 'Обнаружено некорректное состояние узла. Проверьте в [Управление узлами] и повторите попытку!', + nodeUnbind: 'Узел не привязан к лицензии', + nodeUnbindHelper: + 'Обнаружено, что узел не привязан к лицензии. Привяжите в меню [Настройки панели - Лицензия] и повторите попытку!', + memTotal: 'Общая память', + nodeManagement: 'Управление Несколькими Машинами', + nodeItem: 'Управление Узлами', + panelItem: 'Управление Панелями', + addPanel: 'Добавить Панель', + addPanelHelper: + 'После успешного добавления панели вы можете быстро получить доступ к целевой панели в [Обзор - Панели].', + panel: 'Панель 1Panel', + others: 'Другие Панели', + addNode: 'Добавить узел', + connInfo: 'Информация о подключении', + nodeInfo: 'Информация об узле', + withProxy: 'Proxy Erişimini Etkinleştir', + withoutProxy: 'Proxy Erişimini Devre Dışı Bırak', + withProxyHelper: + 'Alt düğümlere erişmek için panel ayarlarında tutulan sistem proxy {0} kullanılacak. Devam etmek istiyor musunuz?', + withoutProxyHelper: + 'Alt düğümlere erişmek için panel ayarlarında tutulan sistem proxy kullanımı durdurulacak. Devam etmek istiyor musunuz?', + syncInfo: 'Синхронизация данных,', + syncHelper: + 'При изменении данных главного узла, происходит синхронизация с этим дочерним узлом в реальном времени,', + syncBackupAccount: 'Настройки резервной учётной записи', + syncWithMaster: + 'После обновления до Pro все данные будут синхронизироваться по умолчанию. Политики синхронизации можно настроить вручную в управлении узлами.', + syncProxy: 'Настройки системного прокси', + syncProxyHelper: 'Синхронизация настроек системного прокси требует перезапуска Docker', + syncProxyHelper1: 'Перезапуск Docker может повлиять на работающие сервисы контейнеров.', + syncProxyHelper2: 'Вы можете перезапустить вручную на странице Контейнеры - Конфигурация.', + syncProxyHelper3: + 'Синхронизация настроек системного прокси требует перезапуска Docker, что может повлиять на работающие сервисы контейнеров', + syncProxyHelper4: + 'Синхронизация настроек системного прокси требует перезапуска Docker. Вы можете перезапустить вручную позже на странице Контейнеры - Конфигурация.', + syncCustomApp: 'Синхронизировать пользовательский репозиторий приложений', + syncAlertSetting: 'Настройки системных предупреждений', + syncNodeInfo: 'Базовые данные узла,', + nodeSyncHelper: 'Синхронизация информации о узле будет синхронизировать следующую информацию:', + nodeSyncHelper1: '1. Информация о публичной резервной учетной записи', + nodeSyncHelper2: '2. Информация о соединении между основным узлом и подузлами', + + nodeCheck: 'Проверка доступности', + checkSSH: 'Проверить SSH-подключение узла', + checkUserPermission: 'Проверка прав пользователя узла', + isNotRoot: + 'Обнаружено, что sudo без пароля не поддерживается на этом узле и текущий пользователь не является root', + checkLicense: 'Проверить статус лицензии узла', + checkService: 'Проверить информацию о существующих службах на узле', + checkPort: 'Проверить доступность порта узла', + panelExist: + 'Обнаружено, что на этом узле работает служба 1Panel V1. Перед добавлением обновите до V2 с помощью скрипта миграции.', + coreExist: + 'Текущий узел уже активирован как мастер-узел и не может быть добавлен напрямую как подчинённый узел. Пожалуйста, сначала понизьте его до подчинённого узла перед добавлением, обратитесь к документации для подробностей.', + agentExist: + 'Обнаружено, что 1panel-agent уже установлен на этом узле. Продолжение сохранит существующие данные и заменит только службу 1panel-agent.', + agentNotExist: + 'Обнаружено, что на этом узле не установлен 1panel-agent, поэтому информацию об узле нельзя редактировать напрямую. Удалите его и добавьте снова.', + oldDataExist: + 'Обнаружены исторические данные 1Panel V2 на этом узле. Текущие настройки будут перезаписаны следующей информацией:', + errLicense: 'Лицензия, привязанная к этому узлу, недоступна. Пожалуйста, проверьте и повторите попытку!', + errNodePort: + 'Обнаружено, что порт узла [ {0} ] недоступен. Проверьте, разрешен ли этот порт в брандмауэре или группе безопасности.', + + reinstallHelper: 'Переустановить узел {0}, вы хотите продолжить?', + unhealthyCheck: 'Проверка на неисправности', + fixOperation: 'Решение проблемы', + checkName: 'Элемент проверки', + checkSSHConn: 'Проверка доступности SSH-соединения', + fixSSHConn: 'Вручную отредактируйте узел, чтобы подтвердить информацию о подключении', + checkConnInfo: 'Проверка информации о подключении агента', + checkStatus: 'Проверка доступности службы узла', + fixStatus: 'Запустите "systemctl status 1panel-agent.service", чтобы проверить, запущена ли служба.', + checkAPI: 'Проверка доступности API узла', + fixAPI: 'Проверьте журналы узла и убедитесь, что порты брандмауэра правильно открыты.', + forceDelete: 'Принудительное удаление', + operateHelper: 'Будет выполнена операция {0} для следующих узлов, вы хотите продолжить?', + operatePanelHelper: 'Операция {0} будет выполнена на следующих панелях. Продолжить?', + forceDeleteHelper: + 'Принудительное удаление проигнорирует ошибки удаления узла и удалит метаданные базы данных', + uninstall: 'Удалить данные узла', + uninstallHelper: 'Это удалит все данные 1Panel, связанные с узлом. Выбирайте осторожно!', + baseDir: 'Каталог установки', + baseDirHelper: 'Если каталог установки пуст, по умолчанию он будет установлен в каталоге /opt', + nodePort: 'Порт узла', + offline: 'Автономный режим', + freeCount: 'Бесплатная квота [{0}]', + offlineHelper: 'Используется, когда узел находится в автономной среде', + + appUpgrade: 'Обновление приложения', + appUpgradeHelper: 'Есть {0} приложений, которые необходимо обновить', + }, + customApp: { + name: 'Пользовательское хранилище приложений', + appStoreType: 'Источник пакета App Store', + appStoreUrl: 'URL хранилища', + local: 'Локальный путь', + remote: 'Удаленная ссылка', + imagePrefix: 'Префикс образа', + imagePrefixHelper: + 'Функция: Настройка префикса образа и изменение поля образа в файле compose. Например, если префикс образа установлен как 1panel/custom, поле образа для MaxKB изменится на 1panel/custom/maxkb:v1.10.0', + closeHelper: 'Отменить использование пользовательского хранилища приложений', + appStoreUrlHelper: 'Поддерживается только формат .tar.gz', + postNode: 'Синхронизировать с подузлом', + postNodeHelper: + 'Синхронизируйте пользовательский пакет магазина с tmp/customApp/apps.tar.gz в каталоге установки дочернего узла', + nodes: 'Выбрать узлы', + selectNode: 'Выбрать узел', + selectNodeError: 'Пожалуйста, выберите узел', + licenseHelper: 'Профессиональная версия поддерживает функцию пользовательского репозитория приложений', + databaseHelper: 'База данных, связанная с приложением, выберите базу данных целевого узла', + nodeHelper: 'Нельзя выбрать текущий узел', + migrateHelper: + 'В настоящее время поддерживает миграцию только монолитных приложений и приложений, связанных только с базами данных MySQL, MariaDB, PostgreSQL', + opensslHelper: + 'При использовании зашифрованного резервного копирования версии OpenSSL между двумя узлами должны быть согласованы, иначе миграция может завершиться неудачей.', + installApp: 'Пакетная установка', + installAppHelper: 'Пакетная установка приложений на выбранные узлы', + }, + alert: { + isAlert: 'Оповещение', + alertCount: 'Количество оповещений', + clamHelper: 'Отправлять оповещение при обнаружении зараженных файлов', + cronJobHelper: 'Отправлять оповещение при сбое выполнения задачи', + licenseHelper: 'Профессиональная версия поддерживает SMS-оповещения', + alertCountHelper: 'Максимальная дневная частота оповещений', + alert: 'SMS Уведомление', + logs: 'Журнал Уведомлений', + list: 'Список Уведомлений', + addTask: 'Создать Уведомление', + editTask: 'Редактировать Уведомление', + alertMethod: 'Метод', + alertMsg: 'Сообщение Уведомления', + alertRule: 'Правила Уведомлений', + titleSearchHelper: 'Введите название уведомления для нечеткого поиска', + taskType: 'Тип', + ssl: 'Срок действия сертификата', + siteEndTime: 'Истечение срока действия сайта', + panelPwdEndTime: 'Истечение срока действия пароля панели', + panelUpdate: 'Доступна новая версия панели', + cpu: 'Уведомление о загрузке процессора сервера', + memory: 'Уведомление о памяти сервера', + load: 'Уведомление о нагрузке сервера', + disk: 'Уведомление о диске сервера', + website: 'Веб-сайт', + certificate: 'SSL Сертификат', + remainingDays: 'Оставшиеся дни', + sendCount: 'Количество Отправок', + sms: 'SMS', + wechat: 'WeChat', + dingTalk: 'DingTalk', + feiShu: 'FeiShu', + mail: 'Электронная Почта', + weCom: 'WeCom', + sendCountRulesHelper: 'Общее количество уведомлений до истечения срока действия (раз в день)', + panelUpdateRulesHelper: 'Общее количество уведомлений для новой версии панели (раз в день)', + oneDaySendCountRulesHelper: 'Максимальное количество уведомлений в день', + siteEndTimeRulesHelper: 'Сайты с неограниченным сроком действия не будут вызывать уведомления', + autoRenewRulesHelper: + 'Сертификаты с включенным автоматическим продлением и оставшимися днями менее 31 не будут вызывать уведомления', + panelPwdEndTimeRulesHelper: + 'Уведомления об истечении срока действия пароля панели недоступны, если срок действия не задан', + sslRulesHelper: 'Все SSL Сертификаты', + diskInfo: 'Диск', + monitoringType: 'Тип Мониторинга', + autoRenew: 'Автопродление', + useDisk: 'Использование Диска', + usePercentage: 'Процент Использования', + changeStatus: 'Изменить Статус', + disableMsg: + 'Остановка задачи уведомления предотвратит отправку сообщений этой задачей. Вы хотите продолжить?', + enableMsg: 'Включение задачи уведомления позволит этой задаче отправлять сообщения. Вы хотите продолжить?', + useExceed: 'Использование Превышает', + useExceedRulesHelper: 'Отправить уведомление, если использование превышает установленное значение', + cpuUseExceedAvg: 'Среднее использование процессора превышает заданное значение', + memoryUseExceedAvg: 'Среднее использование памяти превышает заданное значение', + loadUseExceedAvg: 'Средняя нагрузка превышает заданное значение', + cpuUseExceedAvgHelper: 'Среднее использование процессора за указанное время превышает заданное значение', + memoryUseExceedAvgHelper: 'Среднее использование памяти за указанное время превышает заданное значение', + loadUseExceedAvgHelper: 'Средняя нагрузка за указанное время превышает заданное значение', + resourceAlertRulesHelper: 'Примечание: Непрерывные уведомления в течение 30 минут отправят только одно', + specifiedTime: 'Указанное Время', + deleteTitle: 'Удалить Уведомление', + deleteMsg: 'Вы уверены, что хотите удалить задачу уведомления?', + + allSslTitle: 'Уведомления об истечении срока действия всех SSL сертификатов сайтов', + sslTitle: 'Уведомление об истечении срока действия SSL сертификата для сайта {0}', + allSiteEndTimeTitle: 'Уведомления об истечении срока действия всех сайтов', + siteEndTimeTitle: 'Уведомление об истечении срока действия сайта {0}', + panelPwdEndTimeTitle: 'Уведомление об истечении срока действия пароля панели', + panelUpdateTitle: 'Уведомление о новой версии панели', + cpuTitle: 'Уведомление о высокой загрузке процессора', + memoryTitle: 'Уведомление о высокой загрузке памяти', + loadTitle: 'Уведомление о высокой нагрузке', + diskTitle: 'Уведомление о высокой загрузке диска для точки монтирования {0}', + allDiskTitle: 'Уведомление о высокой загрузке диска', + + timeRule: + 'Оставшееся время менее {0} дней (если не обработано, будет отправлено повторно на следующий день)', + panelUpdateRule: + 'Отправить уведомление один раз при обнаружении новой версии панели (если не обработано, будет отправлено повторно на следующий день)', + avgRule: 'Среднее использование {1} превышает {2}% в течение {0} минут, отправляется {3} раз в день', + diskRule: 'Использование диска для точки монтирования {0} превышает {1}{2}, отправляется {3} раз в день', + allDiskRule: 'Использование диска превышает {0}{1}, отправляется {2} раз в день', + + cpuName: 'Процессор', + memoryName: 'Память', + loadName: 'Нагрузка', + diskName: 'Диск', + + syncAlertInfo: 'Ручная отправка', + syncAlertInfoMsg: 'Вы хотите вручную отправить задачу уведомления?', + pushError: 'Не удалось отправить', + pushSuccess: 'Отправка успешна', + syncError: 'Ошибка синхронизации', + success: 'Уведомление успешно', + pushing: 'В процессе отправки...', + error: 'Ошибка оповещения', + cleanLog: 'Очистить логи', + cleanAlertLogs: 'Очистить журналы уведомлений', + daily: 'Ежедневное количество уведомлений: {0}', + cumulative: 'Общее количество уведомлений: {0}', + clams: 'Антивирусная проверка', + taskName: 'Имя задачи', + cronJobType: 'Тип задачи', + clamPath: 'Директория проверки', + cronjob: 'Ошибка при выполнении Задача cron {0}', + app: 'Резервное копирование приложения', + web: 'Резервное копирование сайта', + database: 'Резервное копирование базы данных', + directory: 'Резервное копирование директории', + log: 'Резервное копирование логов', + snapshot: 'Системный снимок', + clamsRulesHelper: 'Задачи сканирования на вирусы, требующие уведомлений', + cronJobRulesHelper: 'Этот тип планируемой задачи требует конфигурации', + clamsTitle: 'Задача сканирования на вирусы 「 {0} 」 обнаружила заражённый файл', + cronJobAppTitle: 'Задача Cron - ошибка задачи резервного копирования приложения 「 {0} 」', + cronJobWebsiteTitle: 'Задача Cron - ошибка задачи резервного копирования сайта 「 {0} 」', + cronJobDatabaseTitle: 'Задача Cron - ошибка задачи резервного копирования базы данных 「 {0} 」', + cronJobDirectoryTitle: 'Задача Cron - ошибка задачи резервного копирования директории 「 {0} 」', + cronJobLogTitle: 'Задача Cron - ошибка задачи резервного копирования логов 「 {0} 」', + cronJobSnapshotTitle: 'Задача Cron - ошибка задачи резервного копирования снимка системы 「 {0} 」', + cronJobShellTitle: 'Задача Cron - ошибка выполнения скрипта Shell 「 {0} 」', + cronJobCurlTitle: 'Задача Cron - ошибка доступа к URL 「 {0} 」', + cronJobCutWebsiteLogTitle: 'Задача Cron - ошибка задачи нарезки логов сайта 「 {0} 」', + cronJobCleanTitle: 'Задача Cron - ошибка задачи очистки кэша 「 {0} 」', + cronJobNtpTitle: 'Задача Cron - ошибка задачи синхронизации времени сервера 「 {0} 」', + clamsRule: 'Уведомление об обнаружении заражённого файла, отправляется {0} раз в день', + cronJobAppRule: + 'Уведомление об ошибке задачи резервного копирования приложения, отправляется {0} раз в день', + cronJobWebsiteRule: + 'Уведомление об ошибке задачи резервного копирования сайта, отправляется {0} раз в день', + cronJobDatabaseRule: + 'Уведомление об ошибке задачи резервного копирования базы данных, отправляется {0} раз в день', + cronJobDirectoryRule: + 'Уведомление об ошибке задачи резервного копирования директории, отправляется {0} раз в день', + cronJobLogRule: 'Уведомление об ошибке задачи резервного копирования логов, отправляется {0} раз в день', + cronJobSnapshotRule: + 'Уведомление об ошибке задачи резервного копирования снимка системы, отправляется {0} раз в день', + cronJobShellRule: 'Уведомление об ошибке выполнения скрипта Shell, отправляется {0} раз в день', + cronJobCurlRule: 'Уведомление об ошибке задачи доступа к URL, отправляется {0} раз в день', + cronJobCutWebsiteLogRule: 'Уведомление об ошибке задачи нарезки логов сайта, отправляется {0} раз в день', + cronJobCleanRule: 'Уведомление об ошибке задачи очистки кэша, отправляется {0} раз в день', + cronJobNtpRule: 'Уведомление об ошибке задачи синхронизации времени сервера, отправляется {0} раз в день', + alertSmsHelper: 'Лимит SMS: всего {0} сообщений, уже использовано {1}', + goBuy: 'Купить больше', + phone: 'Телефон', + phoneHelper: 'Укажите реальный номер телефона для получения уведомлений', + dailyAlertNum: 'Дневной Лимит Уведомлений', + dailyAlertNumHelper: 'Максимальное количество уведомлений в день (до 100)', + timeRange: 'Диапазон Времени', + sendTimeRange: 'Временной интервал отправки', + sendTimeRangeHelper: 'Можно отправлять в диапазоне {0}', + to: 'до', + startTime: 'Время Начала', + endTime: 'Время Завершения', + defaultPhone: 'По умолчанию используется номер телефона, привязанный к лицензии', + noticeAlert: 'Уведомление', + resourceAlert: 'Уведомление о Ресурсах', + agentOfflineAlertHelper: + 'При включении офлайн-оповещений для узла главный узел будет каждые 30 минут выполнять проверку и запускать задачи оповещения.', + offline: 'Оповещение об отключении', + offlineHelper: + 'Если выбрано офлайн-оповещение, главный узел будет каждые 30 минут выполнять проверку и запускать задачи оповещения.', + offlineOff: 'Включить офлайн-оповещение', + offlineOffHelper: + 'Включение офлайн-оповещений заставит главный узел каждые 30 минут выполнять задачи оповещения.', + offlineClose: 'Отключить офлайн-оповещение', + offlineCloseHelper: + 'Отключив офлайн-оповещения, вы передаёте ответственность за оповещения на подчинённые узлы. Убедитесь, что сеть работает стабильно, чтобы избежать сбоев.', + alertNotice: 'Уведомление об оповещении', + methodConfig: 'Настройка способа уведомления', + commonConfig: 'Глобальная настройка', + smsConfig: 'SMS', + smsConfigHelper: 'Настройка номеров для SMS-уведомлений', + emailConfig: 'электронной почты', + emailConfigHelper: 'Настройка службы отправки SMTP-писем', + deleteConfigTitle: 'Удалить конфигурацию оповещения', + deleteConfigMsg: 'Вы уверены, что хотите удалить конфигурацию оповещения?', + test: 'Тест', + alertTestOk: 'Тестовое уведомление успешно', + alertTestFailed: 'Не удалось отправить тестовое уведомление', + displayName: 'Отображаемое имя', + sender: 'Адрес отправителя', + password: 'Пароль', + host: 'SMTP сервер', + port: 'Порт', + encryption: 'Метод шифрования', + recipient: 'Получатель', + licenseTime: 'Напоминание об истечении лицензии', + licenseTimeTitle: 'Напоминание об истечении лицензии', + displayNameHelper: 'Отображаемое имя отправителя письма', + senderHelper: 'Адрес электронной почты для отправки сообщений', + passwordHelper: 'Код авторизации почтового сервиса', + hostHelper: 'Адрес SMTP-сервера, например: smtp.qq.com', + portHelper: 'SSL обычно использует 465, TLS — 587', + sslHelper: 'Если порт SMTP — 465, обычно требуется SSL', + tlsHelper: 'Если порт SMTP — 587, обычно требуется TLS', + triggerCondition: 'Условие срабатывания', + loginFail: ' неудачных попыток входа в течение', + nodeException: 'Оповещение о сбое узла', + licenseException: 'Оповещение о сбое лицензии', + panelLogin: 'Оповещение о сбое входа в панель', + sshLogin: 'Оповещение о сбое входа по SSH', + panelIpLogin: 'Оповещение о сбое IP входа в панель', + sshIpLogin: 'Оповещение о сбое IP входа по SSH', + ipWhiteListHelper: + 'IP-адреса в белом списке не ограничиваются правилами, и при успешном входе оповещение не создается', + nodeExceptionRule: 'Оповещение о сбое узла, отправляется {0} раз в день', + licenseExceptionRule: 'Оповещение о сбое лицензии, отправляется {0} раз в день', + panelLoginRule: 'Оповещение о входе в панель, отправляется {0} раз в день', + sshLoginRule: 'Оповещение о входе по SSH, отправляется {0} раз в день', + userNameHelper: 'Имя пользователя не указано, по умолчанию будет использоваться адрес отправителя', + }, + theme: { + lingXiaGold: 'Лин Ся Золотой', + classicBlue: 'Классический Синий', + freshGreen: 'Свежий Зелёный', + customColor: 'Пользовательский Цвет', + setDefault: 'По умолчанию', + setDefaultHelper: 'Цветовая схема темы будет восстановлена до исходного состояния. Вы хотите продолжить?', + setHelper: 'Выбранная в данный момент цветовая схема темы будет сохранена. Вы хотите продолжить?', + }, + exchange: { + exchange: 'Обмен файлами', + exchangeConfirm: 'Хотите перенести файл/папку {1} с узла {0} в каталог {3} узла {2}?', + }, + cluster: { + cluster: 'Высокая доступность приложений', + name: 'Имя кластера', + addCluster: 'Добавить кластер', + installNode: 'Установить узел', + master: 'Главный узел', + slave: 'Подчиненный узел', + replicaStatus: 'Состояние мастер-слейв', + unhealthyDeleteError: + 'Состояние узла установки аномально, пожалуйста, проверьте список узлов и повторите попытку!', + replicaStatusError: 'Получение статуса аномально, пожалуйста, проверьте главный узел.', + masterHostError: 'IP главного узла не может быть 127.0.0.1', + }, + }, +}; + +export default { + ...fit2cloudEnLocale, + ...message, +}; diff --git a/frontend/src/lang/modules/tr.ts b/frontend/src/lang/modules/tr.ts new file mode 100644 index 0000000..8d3ac72 --- /dev/null +++ b/frontend/src/lang/modules/tr.ts @@ -0,0 +1,4218 @@ +import fit2cloudEnLocale from 'fit2cloud-ui-plus/src/locale/lang/en'; + +const message = { + commons: { + true: 'doğru', + false: 'yanlış', + colon: ': ', + example: 'Örneğin, ', + fit2cloud: 'FIT2CLOUD', + lingxia: 'Lingxia', + button: { + run: 'Çalıştır', + prev: 'Önceki', + next: 'Sonraki', + create: 'Oluştur ', + add: 'Ekle ', + save: 'Kaydet ', + set: 'Ayarla', + sync: 'Senkronize Et ', + delete: 'Sil', + edit: 'Düzenle ', + enable: 'Etkinleştir', + disable: 'Devre Dışı Bırak', + confirm: 'Onayla', + cancel: 'İptal', + reset: 'Sıfırla', + setDefault: 'Varsayılanı Geri Yükle', + restart: 'Yeniden Başlat', + conn: 'Bağlan', + disconn: 'Bağlantıyı Kes', + clean: 'Temizle', + login: 'Giriş Yap', + close: 'Kapat', + stop: 'Durdur', + start: 'Başlat', + view: 'Görüntüle', + watch: 'İzle', + handle: 'Tetikle', + clone: 'Klonla', + expand: 'Genişlet', + collapse: 'Daralt', + log: 'Günlükleri görüntüle', + back: 'Geri', + backup: 'Yedekle', + recover: 'Kurtar', + retry: 'Tekrar Dene', + upload: 'Yükle', + download: 'İndir', + init: 'Başlat', + verify: 'Doğrula', + saveAndEnable: 'Kaydet ve etkinleştir', + import: 'İçe Aktar', + export: 'Dışa Aktar', + power: 'Yetkilendirme', + search: 'Ara', + refresh: 'Yenile', + get: 'Al', + upgrade: 'Yükselt', + update: 'Güncelle', + updateNow: 'Şimdi Güncelle', + ignore: 'Yükseltmeyi yoksay', + copy: 'Kopyala', + random: 'Rastgele', + install: 'Yükle', + uninstall: 'Kaldır', + fullscreen: 'Tam ekrana geç', + quitFullscreen: 'Tam ekrandan çık', + showAll: 'Tümünü Göster', + hideSome: 'Bazılarını Gizle', + agree: 'Kabul Et', + notAgree: 'Kabul Etme', + preview: 'Önizleme', + open: 'Aç', + notSave: 'Kaydetme', + createNewFolder: 'Yeni klasör oluştur', + createNewFile: 'Yeni dosya oluştur', + helpDoc: 'Yardım Belgesi', + bind: 'Bağla', + unbind: 'Bağlantıyı Çöz', + cover: 'kapla', + skip: 'atla', + fix: 'Düzelt', + down: 'Durdur', + up: 'Başlat', + sure: 'Onayla', + show: 'Göster', + hide: 'Gizle', + visit: 'Visit', + migrate: 'Taşı', + }, + operate: { + start: 'Başlat', + stop: 'Durdur', + restart: 'Yeniden Başlat', + reload: 'Yeniden Yükle', + rebuild: 'Yeniden İnşa Et', + sync: 'Senkronize Et', + up: 'Yukarı', + down: 'Aşağı', + delete: 'Sil', + }, + search: { + timeStart: 'Başlangıç zamanı', + timeEnd: 'Bitiş zamanı', + timeRange: 'İle', + dateStart: 'Başlangıç tarihi', + dateEnd: 'Bitiş tarihi', + date: 'Tarih', + }, + table: { + all: 'Tümü', + total: 'Toplam {0}', + name: 'Ad', + type: 'Tür', + status: 'Durum', + statusSuccess: 'Başarılı', + statusFailed: 'Başarısız', + statusWaiting: 'Bekliyor...', + records: 'Kayıtlar', + group: 'Grup', + default: 'Varsayılan', + createdAt: 'Oluşturulma zamanı', + publishedAt: 'Yayınlanma zamanı', + date: 'Tarih', + updatedAt: 'Güncellenme zamanı', + operate: 'İşlemler', + message: 'Mesaj', + description: 'Açıklama', + interval: 'Aralık', + user: 'Sahip', + title: 'Başlık', + port: 'Port', + forward: 'İleri', + protocol: 'Protokol', + tableSetting: 'Tablo ayarı', + refreshRate: 'Yenileme hızı', + noRefresh: 'Yenileme yok', + selectColumn: 'Sütun seç', + local: 'yerel', + serialNumber: 'Seri numarası', + manageGroup: 'Grupları Yönet', + backToList: 'Listeye Dön', + keepEdit: 'Düzenlemeye Devam Et', + }, + loadingText: { + Upgrading: 'Sistem yükseltiliyor, lütfen bekleyin...', + Restarting: 'Sistem yeniden başlatılıyor, lütfen bekleyin...', + Recovering: 'Anlık görüntüden kurtarılıyor, lütfen bekleyin...', + Rollbacking: 'Anlık görüntüden geri alınıyor, lütfen bekleyin...', + }, + msg: { + noneData: 'Veri mevcut değil', + delete: 'Bu silme işlemi geri alınamaz. Devam etmek istiyor musunuz?', + clean: 'Bu temizleme işlemi geri alınamaz. Devam etmek istiyor musunuz?', + closeDrawerHelper: 'Sistem yaptığınız değişiklikleri kaydetmeyebilir. Devam etmek istiyor musunuz?', + deleteSuccess: 'Başarıyla silindi', + loginSuccess: 'Başarıyla giriş yapıldı', + operationSuccess: 'Başarıyla tamamlandı', + copySuccess: 'Başarıyla kopyalandı', + notSupportOperation: 'Bu işlem desteklenmiyor', + requestTimeout: 'İstek zaman aşımına uğradı, lütfen daha sonra tekrar deneyin', + infoTitle: 'İpucu', + notRecords: 'Mevcut görev için yürütme kaydı oluşturulmadı', + sureLogOut: 'Çıkış yapmak istediğinizden emin misiniz?', + createSuccess: 'Başarıyla oluşturuldu', + updateSuccess: 'Başarıyla güncellendi', + uploadSuccess: 'Başarıyla yüklendi', + operateConfirm: 'İşlemden eminseniz, lütfen manuel olarak girin : ', + inputOrSelect: 'Lütfen seçin veya girin', + copyFailed: 'Kopyalama başarısız', + operatorHelper: + '"{0}" üzerinde "{1}" işlemi gerçekleştirilecek ve geri alınamaz. Devam etmek istiyor musunuz?', + notFound: 'Üzgünüz, istediğiniz sayfa mevcut değil.', + unSupportType: 'Mevcut dosya türü desteklenmiyor.', + unSupportSize: 'Yüklenen dosya {0}Myi aşıyor, lütfen onaylayın!', + fileExist: 'Dosya mevcut klasörde zaten var. Tekrar yükleme desteklenmiyor.', + fileNameErr: + 'Sadece 1 ile 256 karakter arasında İngilizce, Çince, rakam veya nokta (.-_) içeren dosyalar yükleyebilirsiniz', + confirmNoNull: '{0} değerinin boş olmadığından emin olun.', + errPort: 'Yanlış port bilgisi, lütfen onaylayın!', + remove: 'Kaldır', + backupHelper: 'Mevcut işlem {0}i yedekleyecek. Devam etmek istiyor musunuz?', + recoverHelper: '{0} dosyasından geri yükleniyor. Bu işlem geri alınamaz. Devam etmek istiyor musunuz?', + refreshSuccess: 'Yenileme başarılı', + rootInfoErr: 'Zaten kök dizinde', + resetSuccess: 'Sıfırlama başarılı', + creatingInfo: 'Oluşturuluyor, bu işlem gerekli değil', + installSuccess: 'Yükleme başarılı', + uninstallSuccess: 'Kaldırma başarılı', + offlineTips: 'A versão offline não suporta esta operação', + errImportFormat: 'İçe aktarma verisi veya biçimi anormal, lütfen kontrol edip tekrar deneyin!', + importHelper: + 'Çakışan veya yinelenen verileri içe aktarırken, içe aktarılan içerik orijinal veritabanı verilerini güncellemek için standart olarak kullanılacaktır.', + errImport: 'Dosya içeriği anormal:', + }, + login: { + username: 'Kullanıcı adı', + password: 'Şifre', + passkey: 'Passkey ile giriş', + welcome: 'Tekrar hoş geldiniz, giriş yapmak için kullanıcı adınızı ve şifrenizi girin!', + errorAuthInfo: 'Girdiğiniz kullanıcı adı veya şifre yanlış, lütfen tekrar girin!', + errorMfaInfo: 'Yanlış kimlik doğrulama bilgisi, lütfen tekrar deneyin!', + captchaHelper: 'Güvenlik kodu', + errorCaptcha: 'Güvenlik kodu hatası!', + notSafe: 'Erişim Reddedildi', + safeEntrance1: 'Mevcut ortamda güvenli giriş etkinleştirildi', + safeEntrance2: 'Panel girişini görüntülemek için SSH terminalinde şu komutu girin: 1pctl user-info', + errIP1: 'Mevcut ortamda yetkili IP adresi erişimi etkinleştirildi', + errDomain1: 'Mevcut ortamda erişim alan adı bağlama etkinleştirildi', + errHelper: 'Bağlama bilgilerini sıfırlamak için SSH terminalinde şu komutu çalıştırın: ', + codeInput: 'MFA doğrulayıcının 6 haneli doğrulama kodunu girin', + mfaTitle: 'MFA Sertifikasyonu', + mfaCode: 'MFA doğrulama kodu', + title: 'Linux Sunucu Yönetim Paneli', + licenseHelper: '', + errorAgree: 'Topluluk Yazılım Lisansını kabul etmek için tıklayın', + logout: 'Çıkış', + agreeTitle: 'Sözleşme', + agreeContent: + 'Meşru hak ve çıkarlarınızı daha iyi korumak için lütfen aşağıdaki sözleşmeyi okuyun ve kabul edin « Topluluk Lisans Sözleşmesi »', + passkeyFailed: 'Passkey ile giriş başarısız oldu, lütfen tekrar deneyin', + passkeyNotSupported: 'Mevcut tarayıcı veya ortam passkey desteklemiyor', + passkeyToPassword: 'Passkey kullanırken sorun mu yaşıyorsunuz? Şifreyi kullanın', + }, + rule: { + username: 'Kullanıcı adı girin', + password: 'Şifre girin', + rePassword: 'Şifre onayı, şifre ile tutarsız.', + requiredInput: 'Bu alan gereklidir.', + requiredSelect: 'Listeden bir öğe seçin', + illegalChar: '& ; $ ` ( ) " > < | karakterlerinin enjekte edilmesi şu anda desteklenmiyor', + illegalInput: 'Bu alan yasadışı karakterler içermemelidir.', + commonName: + 'Bu alan özel olmayan karakterlerle başlamalı ve İngilizce, Çince, rakam, ".", "-", ve "_" karakterlerinden oluşmalı, uzunluk 1-128 olmalıdır.', + userName: 'Bu alan İngilizce, Çince, rakam ve "_" karakterlerinden oluşmalı, uzunluk 3-30 olmalıdır.', + simpleName: + 'Bu alan alt çizgi karakteriyle başlamamalı ve İngilizce, rakam, ve "_" karakterlerinden oluşmalı, uzunluk 3-30 olmalıdır.', + simplePassword: + 'Bu alan alt çizgi karakteriyle başlamamalı ve İngilizce, rakam, ve "_" karakterlerinden oluşmalı, uzunluk 1-30 olmalıdır.', + dbName: 'Bu alan alt çizgi karakteriyle başlamamalı ve İngilizce, rakam, ve "_" karakterlerinden oluşmalı, uzunluk 1-64 olmalıdır.', + imageName: + 'Bu alan İngilizce, rakam, ":", "@", "/", ".", "-", ve "_" karakterlerinden oluşmalı, uzunluk 1-256 olmalıdır.', + volumeName: 'Bu alan İngilizce, rakam, ".", "-", ve "_" karakterlerinden oluşmalı, uzunluk 2-30 olmalıdır.', + supervisorName: + 'Bu alan özel olmayan karakterlerle başlamalı ve İngilizce, rakam, "-", ve "_" karakterlerinden oluşmalı, uzunluk 1-128 olmalıdır.', + composeName: + 'Başlangıçta özel olmayan karakterleri, küçük harfleri, rakamları, - ve _ destekler, uzunluk 1-256', + complexityPassword: + 'Bu alan İngilizce, rakamlardan oluşmalı, uzunluk 8-30 olmalı ve en az iki özel karakter içermelidir.', + commonPassword: 'Bu alanın uzunluğu 6dan fazla olmalıdır.', + linuxName: + 'Bu alanın uzunluğu 1 ile 128 arasında olmalıdır. Alan şu özel karakterleri içermemelidir: "{0}".', + email: 'Bu alan geçerli bir e-posta adresi olmalıdır.', + number: 'Bu alan bir sayı olmalıdır.', + integer: 'Bu alan pozitif bir tam sayı olmalıdır.', + ip: 'Bu alan geçerli bir IP adresi olmalıdır.', + host: 'Bu alan geçerli bir IP adresi veya alan adı olmalıdır.', + hostHelper: 'IP adres veya alan adı girişini destekler', + port: 'Bu alan geçerli bir port numarası olmalıdır.', + selectHelper: 'Lütfen doğru {0} dosyasını seçin', + domain: 'Bu alan şu şekilde olmalıdır: example.com veya example.com:8080.', + databaseName: 'Bu alan İngilizce, rakam, ve "_" karakterlerinden oluşmalı, uzunluk 1-30 olmalıdır.', + ipErr: 'Bu alan geçerli bir IP adresi olmalıdır.', + numberRange: 'Bu alan {0} ile {1} arasında bir sayı olmalıdır.', + paramName: 'Bu alan İngilizce, rakam, ".", "-", ve "_" karakterlerinden oluşmalı, uzunluk 2-30 olmalıdır.', + paramComplexity: + 'Bu alan özel karakterlerle başlayıp bitmemeli ve İngilizce, rakam, "{0}" karakterlerinden oluşmalı, uzunluk 6-128 olmalıdır.', + paramUrlAndPort: 'Bu alan "http(s)://(alan adı/ip):(port)" formatında olmalıdır.', + nginxDoc: 'Bu alan İngilizce, rakam ve "." karakterlerinden oluşmalıdır.', + appName: + 'Bu alan "-" ve "_" karakterleriyle başlayıp bitmemeli ve İngilizce, rakam, "-", ve "_" karakterlerinden oluşmalı, uzunluk 2-30 olmalıdır.', + containerName: 'Harf, rakam, -, _ ve . destekler; - _ veya . ile başlayamaz; uzunluk: 2-128', + mirror: 'Ayna hızlandırma adresi http(s):// ile başlamalı, İngilizce harfleri (büyük ve küçük), rakam, . / ve - desteklemeli, boş satır içermemelidir.', + disableFunction: 'Sadece harf, alt çizgi ve virgül destekler', + leechExts: 'Sadece harf, rakam ve virgül destekler', + paramSimple: 'Küçük harf ve rakam destekler, uzunluk 1-128', + filePermission: 'Dosya İzin Hatası', + formatErr: 'Format hatası, lütfen kontrol edin ve tekrar deneyin', + phpExtension: 'Sadece , _ küçük İngilizce ve rakam destekler', + paramHttp: 'http:// veya https:// ile başlamalıdır', + phone: 'Telefon numarası formatı yanlış', + authBasicPassword: 'Harf, rakam ve yaygın özel karakterler destekler, uzunluk 1-72', + length128Err: 'Uzunluk 128 karakteri geçemez', + maxLength: 'Uzunluk {0} karakteri geçemez', + alias: 'İngilizce, rakamlar, - ve _ destekler, uzunluk 1-128, ve -_ ile başlayamaz veya bitiremez.', + }, + res: { + paramError: 'İstek başarısız, lütfen daha sonra tekrar deneyin!', + forbidden: 'Mevcut kullanıcının izni yok', + serverError: 'Servis istisnası', + notFound: 'Kaynak mevcut değil', + commonError: 'İstek başarısız', + }, + service: { + serviceNotStarted: '{0} servisi başlatılmadı.', + }, + status: { + running: 'Çalışıyor', + done: 'Tamamlandı', + scanFailed: 'Eksik', + success: 'Başarılı', + waiting: 'Bekliyor', + waitForUpgrade: 'Yükseltme Bekleniyor', + failed: 'Başarısız', + stopped: 'Durduruldu', + error: 'Hata', + created: 'Oluşturuldu', + restarting: 'Yeniden Başlatılıyor', + uploading: 'Yükleniyor', + unhealthy: 'Sağlıksız', + removing: 'Kaldırılıyor', + paused: 'Duraklatıldı', + exited: 'Çıkıldı', + dead: 'Ölü', + installing: 'Yükleniyor', + enabled: 'Etkinleştirildi', + disabled: 'Devre Dışı', + normal: 'Normal', + building: 'İnşa Ediliyor', + upgrading: 'Yükseltiliyor', + pending: 'Düzenleme Bekliyor', + rebuilding: 'Yeniden İnşa Ediliyor', + deny: 'Reddedildi', + accept: 'Kabul Edildi', + used: 'Kullanıldı', + unused: 'Kullanılmadı', + starting: 'Başlatılıyor', + recreating: 'Yeniden Oluşturuluyor', + creating: 'Oluşturuluyor', + init: 'Uygulama bekleniyor', + ready: 'normal', + applying: 'Uygulanıyor', + uninstalling: 'Kaldırılıyor', + lost: 'İletişim Kesildi', + bound: 'Bağlandı', + unbind: 'Bağlantısı Kesildi', + exceptional: 'İstisnai', + free: 'Ücretsiz', + enable: 'Etkinleştirildi', + disable: 'Devre Dışı', + deleted: 'Silindi', + downloading: 'İndiriliyor', + packing: 'Paketleniyor', + sending: 'Gönderiliyor', + healthy: 'Normal', + executing: 'Yürütülüyor', + installerr: 'Yükleme başarısız', + applyerror: 'Uygulama başarısız', + systemrestart: 'Kesintiye Uğradı', + starterr: 'Başlatma başarısız', + uperr: 'Başlatma başarısız', + new: 'Yeni', + conflict: 'Çakışma', + duplicate: 'Yinelenen', + unexecuted: 'Yürütülmedi', + }, + units: { + second: ' saniye | saniye | saniye', + minute: 'dakika | dakika | dakika', + hour: 'saat | saat | saat', + day: 'gün | gün | gün', + week: 'hafta | hafta | hafta', + month: 'ay | ay | ay', + year: 'yıl | yıl | yıl', + time: 'rqm', + core: 'çekirdek | çekirdek | çekirdek', + millisecond: 'milisaniye | milisaniye', + secondUnit: 's', + minuteUnit: 'dk', + hourUnit: 'sa', + dayUnit: 'g', + }, + log: { + noLog: 'Günlük yok', + }, + }, + menu: { + home: 'Genel Bakış', + apps: 'Uygulama Mağazası', + website: 'Web Sitesi | Web Siteleri', + project: 'Proje | Projeler', + config: 'Yapılandırma | Yapılandırmalar', + ssh: 'SSH Ayarları', + firewall: 'Güvenlik Duvarı', + ssl: 'Sertifika | Sertifikalar', + database: 'Veritabanı | Veritabanları', + aiTools: 'AI', + mcp: 'MCP', + container: 'Konteyner | Konteynerler', + cronjob: 'Cron İşi | Cron İşleri', + system: 'Sistem', + security: 'Güvenlik', + files: 'Dosya Tarayıcısı', + monitor: 'İzleme', + terminal: 'Terminal | Terminaller', + settings: 'Ayar | Ayarlar', + toolbox: 'Araç Kutusu', + logs: 'Günlük | Günlükler', + runtime: 'Çalışma Zamanı | Çalışma Zamanları', + processManage: 'Süreç | Süreçler', + process: 'Süreç | Süreçler', + network: 'Ağ | Ağlar', + supervisor: 'Supervisor', + tamper: 'Kurcalama Koruması', + app: 'Uygulama', + msgCenter: 'Görev Merkezi', + }, + home: { + recommend: 'tavsiye etmek', + dir: 'dizin', + alias: 'Takma Ad', + quickDir: 'Hızlı Dizin', + minQuickJump: 'Lütfen en az bir hızlı atlama girişi ayarlayın!', + maxQuickJump: 'En fazla dört hızlı atlama girişi ayarlayabilirsiniz!', + database: 'Veritabanı - Tümü', + restart_1panel: 'Paneli yeniden başlat', + restart_system: 'Sunucuyu yeniden başlat', + operationSuccess: 'İşlem başarılı, yeniden başlatılıyor, lütfen tarayıcıyı daha sonra manuel olarak yenileyin!', + entranceHelper: + 'Güvenlik girişi etkinleştirilmedi. Sistem güvenliğini artırmak için "Ayarlar -> Güvenlik" bölümünden etkinleştirebilirsiniz.', + appInstalled: 'Uygulamalar', + systemInfo: 'Sistem bilgisi', + hostname: 'Host adı', + platformVersion: 'İşletim sistemi', + kernelVersion: 'Çekirdek', + kernelArch: 'Mimari', + network: 'Ağ', + io: 'Disk G/Ç', + ip: 'Yerel IP', + proxy: 'Sistem proxy', + baseInfo: 'Temel bilgi', + totalSend: 'Toplam gönderilen', + totalRecv: 'Toplam alınan', + rwPerSecond: 'G/Ç işlemleri', + ioDelay: 'G/Ç gecikmesi', + uptime: 'Çalışma süresi', + runningTime: 'Şu tarihten beri açık', + mem: 'Sistem Belleği', + swapMem: 'Swap Bölümü', + + runSmoothly: 'Düşük yük', + runNormal: 'Orta yük', + runSlowly: 'Yüksek yük', + runJam: 'Ağır yük', + + core: 'Fiziksel çekirdek', + logicCore: 'Mantıksal çekirdek', + corePercent: 'Çekirdek Kullanımı', + cpuFrequency: 'CPU Frekansı', + cpuDetailedPercent: 'CPU Zaman Dağılımı', + cpuUser: 'Kullanıcı', + cpuSystem: 'Sistem', + cpuIdle: 'Boşta', + cpuIrq: 'IRQ', + cpuSoftirq: 'Soft IRQ', + cpuSteal: 'Çalıntı', + cpuTop: 'CPU Kullanımına Göre İlk 5 İşlem', + memTop: 'Bellek Kullanımına Göre İlk 5 İşlem', + loadAverage: 'Son 1 dakikadaki yük ortalaması | Son {n} dakikadaki yük ortalaması', + load: 'Yük', + mount: 'Bağlama noktası', + fileSystem: 'Dosya sistemi', + total: 'Toplam', + used: 'Kullanılan', + cache: 'Önbellek', + free: 'Boş', + shard: 'Parçalı', + available: 'Kullanılabilir', + percent: 'Kullanım', + goInstall: 'Yüklemeye git', + + networkCard: 'Ağ kartı', + disk: 'Disk', + }, + tabs: { + more: 'Daha Fazla', + hide: 'Gizle', + closeLeft: 'Solları kapat', + closeRight: 'Sağları kapat', + closeCurrent: 'Mevcut olanı kapat', + closeOther: 'Diğerlerini kapat', + closeAll: 'Tümünü Kapat', + }, + header: { + logout: 'Çıkış', + }, + database: { + manage: 'Veritabanını yönet', + deleteBackupHelper: 'Veritabanı yedeklerini aynı anda sil', + delete: 'Silme işlemi geri alınamaz, lütfen "', + deleteHelper: '" girerek bu veritabanını silin', + create: 'Veritabanı oluştur', + noMysql: 'Veritabanı hizmeti (MySQL veya MariaDB)', + noPostgresql: 'Veritabanı hizmeti PostgreSQL', + goUpgrade: 'Yükseltmeye git', + goInstall: 'Yüklemeye git', + isDelete: 'Silindi', + permission: 'İzinleri değiştir', + format: 'Karakter Seti', + collation: 'Karşılaştırma', + collationHelper: 'Boşsa, {0} karakter setinin varsayılan karşılaştırmasını kullanın', + permissionForIP: 'IP', + permissionAll: 'Tümü(%)', + localhostHelper: + 'Konteyner dağıtımı için veritabanı izinlerini "localhost" olarak yapılandırmak konteynere dış erişimi engelleyecektir. Lütfen dikkatli seçin!', + databaseConnInfo: 'Bağlantı bilgilerini görüntüle', + rootPassword: 'Root şifresi', + serviceName: 'Servis Adı', + serviceNameHelper: 'Aynı ağdaki konteynerler arası erişim.', + backupList: 'Yedekleme', + loadBackup: 'İçe Aktar', + localUpload: 'Yerel Yükleme', + hostSelect: 'Sunucu Seçimi', + selectHelper: '{0} yedek dosyasını içe aktarmak istediğinizden emin misiniz?', + remoteAccess: 'Uzaktan erişim', + remoteHelper: 'Birden fazla IP virgülle ayrılır, örnek: 172.16.10.111, 172.16.10.112', + remoteConnHelper: + 'Root kullanıcısı olarak MySQLe uzaktan bağlantı güvenlik riski oluşturabilir. Bu nedenle bu işlemi dikkatli yapın.', + changePassword: 'Şifre değiştir', + changeConnHelper: 'Bu işlem mevcut {0} veritabanını değiştirecek. Devam etmek istiyor musunuz?', + changePasswordHelper: + 'Veritabanı bir uygulamayla ilişkilendirildi. Şifre değiştirmek aynı zamanda uygulamanın veritabanı şifresini de değiştirecek. Değişiklik uygulama yeniden başlatıldıktan sonra etkili olur.', + recoverTimeoutHelper: '-1, zaman aşımı sınırı olmadığı anlamına gelir', + + confChange: 'Yapılandırma', + confNotFound: + 'Yapılandırma dosyası bulunamadı. Lütfen uygulama mağazasından uygulamayı en son sürüme yükseltin ve tekrar deneyin!', + + portHelper: + 'Bu port konteynerin açığa çıkan portudur. Değişikliği ayrı olarak kaydetmeniz ve konteyneri yeniden başlatmanız gerekir!', + + loadFromRemote: 'Sunucudan senkronize et', + userBind: 'Kullanıcı bağla', + pgBindHelper: + 'Bu işlem yeni bir kullanıcı oluşturmak ve hedef veritabanına bağlamak için kullanılır. Şu anda veritabanında mevcut olan kullanıcıları seçmek desteklenmiyor.', + pgSuperUser: 'Süper Kullanıcı', + loadFromRemoteHelper: + 'Bu, sunucudaki veritabanı bilgilerini 1Panele senkronize edecek. Devam etmek istiyor musunuz?', + passwordHelper: 'Alınamıyor, değiştirmek için tıklayın', + remote: 'Uzak', + remoteDB: 'Uzak sunucu | Uzak sunucular', + createRemoteDB: '@.lower:database.remoteDB Bağla', + unBindRemoteDB: '@.lower:database.remoteDB Bağlantısını Çöz', + unBindForce: 'Zorla bağlantıyı çöz', + unBindForceHelper: + 'Son işlemin başarılı olmasını sağlamak için bağlantı çözme işlemi sırasındaki tüm hataları yoksay', + unBindRemoteHelper: + 'Uzak veritabanının bağlantısını çözmek sadece bağlantı ilişkisini kaldıracak ve uzak veritabanını doğrudan silmeyecek', + editRemoteDB: 'Uzak sunucuyu düzenle', + localDB: 'Yerel veritabanı', + address: 'Veritabanı adresi', + version: 'Veritabanı sürümü', + userHelper: + 'Root kullanıcı veya root yetkilerine sahip bir veritabanı kullanıcısı uzak veritabanına erişebilir.', + pgUserHelper: 'Süper kullanıcı yetkilerine sahip veritabanı kullanıcısı.', + ssl: 'SSL Kullan', + clientKey: 'İstemci özel anahtarı', + clientCert: 'İstemci sertifikası', + caCert: 'CA sertifikası', + hasCA: 'CA sertifikası var', + skipVerify: 'Sertifika geçerlilik kontrolünü yoksay', + initialDB: 'Başlangıç Veritabanı', + + formatHelper: + 'Mevcut veritabanı karakter seti {0}, karakter seti tutarsızlığı kurtarma işleminin başarısız olmasına neden olabilir', + dropHelper: 'Yüklenen dosyayı buraya sürükleyip bırakabilir veya', + clickHelper: 'yüklemek için tıklayın', + supportUpType: + 'Yalnızca sql, sql.gz, tar.gz, .zip dosya formatlarını destekler. İçe aktarılan sıkıştırılmış dosya yalnızca bir .sql dosyası içermeli veya test.sql içermelidir', + + currentStatus: 'Mevcut durum', + baseParam: 'Temel parametre', + performanceParam: 'Performans parametresi', + runTime: 'Başlatma zamanı', + connections: 'Toplam bağlantılar', + bytesSent: 'Gönderilen baytlar', + bytesReceived: 'Alınan baytlar', + queryPerSecond: 'Saniye başına sorgu', + txPerSecond: 'Saniye başına işlem', + connInfo: 'aktif/en yüksek bağlantılar', + connInfoHelper: 'Değer çok büyükse, "max_connections" değerini artırın.', + threadCacheHit: 'Thread önbellek isabet oranı', + threadCacheHitHelper: 'Çok düşükse, "thread_cache_size" değerini artırın.', + indexHit: 'İndeks isabet oranı', + indexHitHelper: 'Çok düşükse, "key_buffer_size" değerini artırın.', + innodbIndexHit: 'Innodb indeks isabet oranı', + innodbIndexHitHelper: 'Çok düşükse, "innodb_buffer_pool_size" değerini artırın.', + cacheHit: 'Sorgu önbellek isabet oranı', + cacheHitHelper: 'Çok düşükse, "query_cache_size" değerini artırın.', + tmpTableToDB: 'Diske geçici tablo', + tmpTableToDBHelper: 'Çok büyükse, "tmp_table_size" değerini artırmayı deneyin.', + openTables: 'Açık tablolar', + openTablesHelper: '"table_open_cache" yapılandırma değeri bu değerden büyük veya eşit olmalıdır.', + selectFullJoin: 'Tam birleştirme seçimi', + selectFullJoinHelper: 'Değer 0 değilse, veri tablosunun indeksinin doğru olup olmadığını kontrol edin.', + selectRangeCheck: 'İndekssiz birleştirme sayısı', + selectRangeCheckHelper: 'Değer 0 değilse, veri tablosunun indeksinin doğru olup olmadığını kontrol edin.', + sortMergePasses: 'Sıralı birleştirme sayısı', + sortMergePassesHelper: 'Değer çok büyükse, "sort_buffer_size" değerini artırın.', + tableLocksWaited: 'Kilitli tablo sayısı', + tableLocksWaitedHelper: 'Değer çok büyükse, veritabanı performansınızı artırmayı düşünün.', + + performanceTuning: 'Performans ayarlama', + optimizationScheme: 'Optimizasyon şeması', + keyBufferSizeHelper: 'İndeks için tampon boyutu', + queryCacheSizeHelper: 'Sorgu önbelleği. Bu işlev devre dışıysa, bu parametreyi 0 olarak ayarlayın.', + tmpTableSizeHelper: 'Geçici tablo önbellek boyutu', + innodbBufferPoolSizeHelper: 'Innodb tampon boyutu', + innodbLogBufferSizeHelper: 'Innodb log tampon boyutu', + sortBufferSizeHelper: '* bağlantılar, thread başına sıralama tampon boyutu', + readBufferSizeHelper: '* bağlantılar, okuma tampon boyutu', + readRndBufferSizeHelper: '* bağlantılar, rastgele okuma tampon boyutu', + joinBufferSizeHelper: '* bağlantılar, ilişki tablosu önbellek boyutu', + threadStackelper: '* bağlantılar, thread başına yığın boyutu', + binlogCacheSizeHelper: '* bağlantılar, ikili log önbellek boyutu (4096nın katları)', + threadCacheSizeHelper: 'Thread havuzu boyutu', + tableOpenCacheHelper: 'Tablo önbelleği', + maxConnectionsHelper: 'Maksimum bağlantılar', + restart: 'Yeniden başlat', + + slowLog: 'Yavaş loglar', + noData: 'Henüz yavaş log yok.', + + isOn: 'Açık', + longQueryTime: 'eşik(saniye)', + thresholdRangeHelper: 'Lütfen doğru eşik değerini girin (1 - 600).', + + timeout: 'Zaman aşımı(saniye)', + timeoutHelper: 'Boştaki bağlantı zaman aşımı süresi. 0, bağlantının sürekli açık olduğunu gösterir.', + maxclients: 'Maksimum istemci', + requirepassHelper: + 'Boş bırakırsanız şifre ayarlanmadığını gösterir. Değişikliklerin ayrı olarak kaydedilmesi ve konteyner yeniden başlatılması gerekir!', + databases: 'Veritabanı sayısı', + maxmemory: 'Maksimum bellek kullanımı', + maxmemoryHelper: '0 kısıtlama olmadığını gösterir.', + tcpPort: 'Mevcut dinleme portu.', + uptimeInDays: 'Çalışma günü.', + connectedClients: 'Bağlı istemci sayısı.', + usedMemory: 'Redisin mevcut bellek kullanımı.', + usedMemoryRss: 'İşletim sisteminden talep edilen bellek boyutu.', + usedMemoryPeak: 'Redisin en yüksek bellek tüketimi.', + memFragmentationRatio: 'Bellek parçalanma oranı.', + totalConnectionsReceived: 'Çalışma başlangıcından itibaren bağlanan toplam istemci sayısı.', + totalCommandsProcessed: 'Çalışma başlangıcından itibaren yürütülen toplam komut sayısı.', + instantaneousOpsPerSec: 'Sunucunun saniye başına yürüttüğü komut sayısı.', + keyspaceHits: 'Veritabanı anahtarının başarıyla bulunma sayısı.', + keyspaceMisses: 'Veritabanı anahtarını bulma girişimlerinin başarısız olma sayısı.', + hit: 'Veritabanı anahtarı isabet oranı.', + latestForkUsec: 'Son fork() işleminde harcanan mikrosaniye sayısı.', + redisCliHelper: '"redis-cli" servisi algılanmadı. Önce servisi etkinleştirin.', + redisQuickCmd: 'Redis hızlı komutları', + recoverHelper: 'Bu işlem verileri [{0}] ile üzerine yazacak. Devam etmek istiyor musunuz?', + submitIt: 'Verilerin üzerine yaz', + + baseConf: 'Temel', + allConf: 'Tümü', + restartNow: 'Şimdi yeniden başlat', + restartNowHelper1: + 'Yapılandırma değişikliklerinin etkili olması için sistemi yeniden başlatmanız gerekir. Verilerinizin kalıcı olması gerekiyorsa, önce kaydetme işlemini gerçekleştirin.', + restartNowHelper: 'Bu yalnızca sistem yeniden başlatıldıktan sonra etkili olacaktır.', + + persistence: 'Kalıcılık', + rdbHelper1: 'saniye, ekleme', + rdbHelper2: 'veri parçası', + rdbHelper3: 'Koşullardan herhangi birinin karşılanması RDB kalıcılığını tetikleyecektir.', + rdbInfo: 'Kural listesindeki değerin 1 ile 100000 arasında olduğundan emin olun', + + containerConn: 'Konteyner bağlantısı', + connAddress: 'Adres', + containerConnHelper: + 'Bu bağlantı adresi PHP yürütme ortamı/konteyner kurulumunda çalışan uygulamalar tarafından kullanılır.', + remoteConn: 'Harici bağlantı', + remoteConnHelper2: + 'Bu bağlantı adresi konteyner dışında veya harici uygulamalarda çalışan uygulamalar tarafından kullanılabilir.', + remoteConnHelper3: + 'Varsayılan erişim adresi ana bilgisayar IPsidir. Değiştirmek için panel ayarları sayfasındaki "Varsayılan Erişim Adresi" yapılandırma öğesine gidin.', + localIP: 'Yerel IP', + }, + aiTools: { + model: { + model: 'Model', + create: 'Model Ekle', + create_helper: '"{0}" çek', + ollama_doc: 'Daha fazla model aramak ve bulmak için Ollama resmi web sitesini ziyaret edebilirsiniz.', + container_conn_helper: 'Konteynerler arası erişim veya bağlantı için bu adresi kullanın', + ollama_sync: + 'Ollama modelini senkronize ederken aşağıdaki modellerin mevcut olmadığı tespit edildi, bunları silmek istiyor musunuz?', + from_remote: 'Bu model 1Panel aracılığıyla indirilmedi, ilgili çekme logları yok.', + no_logs: 'Bu modelin çekme logları silindi ve görüntülenemiyor.', + }, + proxy: { + proxy: 'AI Proxy Geliştirmesi', + proxyHelper1: 'Alan adı bağlayın ve gelişmiş iletim güvenliği için HTTPSi etkinleştirin', + proxyHelper2: 'Genel internette maruz kalmayı önlemek için IP erişimini sınırlayın', + proxyHelper3: 'Akışı etkinleştir', + proxyHelper4: 'Oluşturulduktan sonra web sitesi listesinde görüntüleyebilir ve yönetebilirsiniz', + proxyHelper5: + 'Etkinleştirdikten sonra, güvenliği artırmak için Uygulama Mağazası - Kurulu - Ollama - Parametrelerden porta harici erişimi devre dışı bırakabilirsiniz.', + proxyHelper6: 'Proxy yapılandırmasını devre dışı bırakmak için web sitesi listesinden silebilirsiniz.', + whiteListHelper: 'Erişimi yalnızca beyaz listedeki IPlerle sınırlayın', + }, + gpu: { + gpu: 'GPU İzleme', + gpuHelper: 'Sistem NVIDIA-SMI veya XPU-SMI komutlarını algılamadı. Lütfen kontrol edip tekrar deneyin!', + process: 'İşlem Bilgisi', + type: 'Tür', + typeG: 'Grafik', + typeC: 'Hesaplama', + typeCG: 'Hesaplama+Grafik', + processName: 'İşlem Adı', + shr: 'Paylaşılan Bellek', + temperatureHelper: 'Yüksek GPU sıcaklığı GPU frekansında düşüşe neden olabilir', + gpuUtil: 'GPU Kullanımı', + temperature: 'Sıcaklık', + performanceState: 'Performans Durumu', + powerUsage: 'Güç Tüketimi', + memoryUsage: 'Bellek Kullanımı', + fanSpeed: 'Fan Hızı', + power: 'Güç', + powerCurrent: 'Mevcut Güç', + powerLimit: 'Güç Limiti', + memory: 'Bellek', + memoryUsed: 'Kullanılan Bellek', + memoryTotal: 'Toplam Bellek', + percent: 'Kullanım', + + base: 'Temel Bilgiler', + driverVersion: 'Sürücü Sürümü', + cudaVersion: 'CUDA Sürümü', + processMemoryUsage: 'Bellek Kullanımı', + performanceStateHelper: 'P0 (maksimum performans) ile P12 (minimum performans) arası', + busID: 'Veriyolu Adresi', + persistenceMode: 'Kalıcılık Modu', + enabled: 'Etkin', + disabled: 'Devre Dışı', + persistenceModeHelper: + 'Kalıcılık modu görevlere daha hızlı yanıt verir, ancak bekleme güç tüketimi buna bağlı olarak artar', + displayActive: 'GPU Başlatma', + displayActiveT: 'Evet', + displayActiveF: 'Hayır', + ecc: 'Hata Denetleme ve Düzeltme Teknolojisi', + computeMode: 'Hesaplama Modu', + default: 'Varsayılan', + exclusiveProcess: 'Özel Süreç', + exclusiveThread: 'Özel İş Parçacığı', + prohibited: 'Yasak', + defaultHelper: 'Varsayılan: Süreçler eşzamanlı olarak yürütülebilir', + exclusiveProcessHelper: + "Özel Süreç: Yalnızca bir CUDA bağlamı GPU'yu kullanabilir, ancak birden çok iş parçacığı tarafından paylaşılabilir", + exclusiveThreadHelper: "Özel İş Parçacığı: CUDA bağlamındaki yalnızca bir iş parçacığı GPU'yu kullanabilir", + prohibitedHelper: 'Yasak: Eşzamanlı süreç yürütmeye izin verilmez', + migModeHelper: + "MIG örnekleri oluşturmak için kullanılır, GPU'nun kullanıcı katmanında fiziksel izolasyonunu uygular.", + migModeNA: 'Desteklenmiyor', + current: 'Gerçek Zamanlı İzleme', + history: 'Geçmiş Kayıtlar', + notSupport: 'Mevcut sürüm veya sürücü bu parametrenin görüntülenmesini desteklemiyor.', + }, + mcp: { + server: 'MCP Sunucusu', + create: 'MCP Sunucusu Ekle', + edit: 'MCP Sunucusunu Düzenle', + baseUrl: 'Harici Erişim Yolu', + baseUrlHelper: 'Örneğin: http://192.168.1.2:8000', + ssePath: 'SSE Yolu', + ssePathHelper: 'Örneğin: /sse, diğer sunucularla çoğaltmamaya dikkat edin', + environment: 'Ortam Değişkenleri', + envKey: 'Değişken Adı', + envValue: 'Değişken Değeri', + externalUrl: 'Harici Bağlantı Adresi', + operatorHelper: '{0} üzerinde {1} işlemi gerçekleştirilecek, devam edilsin mi?', + domain: 'Varsayılan Erişim Adresi', + domainHelper: 'Örneğin: 192.168.1.1 veya example.com', + bindDomain: 'Web Sitesi Bağla', + commandPlaceHolder: 'Şu anda yalnızca npx ve ikili başlatma komutları desteklenir', + importMcpJson: 'MCP Sunucu Yapılandırmasını İçe Aktar', + importMcpJsonError: 'mcpServers yapısı yanlış', + bindDomainHelper: + 'Web sitesini bağladıktan sonra, kurulu tüm MCP Sunucularının erişim adresini değiştirecek ve portlara harici erişimi kapatacaktır', + outputTransport: 'Çıktı Türü', + streamableHttpPath: 'Akış Yolu', + streamableHttpPathHelper: 'Örneğin: /mcp, diğer Sunucularla çakışmaması gerektiğine dikkat edin', + npxHelper: 'npx veya ikili dosya ile başlatılan mcp için uygundur', + uvxHelper: 'uvx ile başlatılan mcp için uygundur', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: 'Model Dizini', + commandHelper: + 'Harici erişim gerekiyorsa, komuttaki bağlantı noktasını uygulama bağlantı noktasıyla aynı olacak şekilde ayarlayın', + imageAlert: + 'Görüntü boyutu büyük olduğundan, kurulumdan önce görüntüyü sunucuya manuel olarak indirmeniz önerilir', + modelSpeedup: 'Model hızlandırmayı etkinleştir', + modelType: 'Model türü', + }, + }, + container: { + create: 'Oluştur', + createByCommand: 'Komutla oluştur', + commandInput: 'Komut girişi', + commandRule: 'Lütfen doğru docker run konteyner oluşturma komutunu girin!', + commandHelper: 'Bu komut konteyneri oluşturmak için sunucuda çalıştırılacak. Devam etmek istiyor musunuz?', + edit: 'Konteyneri düzenle', + updateHelper1: + 'Bu konteynerin uygulama mağazasından geldiği tespit edildi. Lütfen aşağıdaki iki noktaya dikkat edin:', + updateHelper2: '1. Mevcut değişiklikler uygulama mağazasındaki kurulu uygulamalarla senkronize edilmeyecektir.', + updateHelper3: + '2. Kurulu sayfasında uygulamayı değiştirirseniz, şu anda düzenlenen içerik geçersiz hale gelecektir.', + updateHelper4: + 'Konteyneri düzenlemek yeniden oluşturma gerektirir ve kalıcı olmayan tüm veriler kaybedilecektir. Devam etmek istiyor musunuz?', + containerList: 'Konteyner listesi', + operatorHelper: 'Aşağıdaki konteynerde {0} işlemi gerçekleştirilecek, devam etmek istiyor musunuz?', + operatorAppHelper: + 'Aşağıdaki konteyner(ler)de "{0}" işlemi gerçekleştirilecek ve çalışan hizmetleri etkileyebilir. Devam etmek istiyor musunuz?', + containerDeleteHelper: + "Kapsayıcının Uygulama Mağazası'ndan geldiği tespit edildi. Kapsayıcıyı silmek, onu 1Panel'den tamamen kaldırmaz. Tamamen silmek için lütfen Uygulama Mağazası -> 'Yüklü' veya 'Çalışma Ortamı' menülerine gidin. Devam edilsin mi?", + start: 'Başlat', + stop: 'Durdur', + restart: 'Yeniden başlat', + kill: 'Öldür', + pause: 'Duraklat', + unpause: 'Devam et', + rename: 'Yeniden adlandır', + remove: 'Kaldır', + removeAll: 'Tümünü kaldır', + containerPrune: 'Temizle', + containerPruneHelper1: 'Bu, durmuş durumdaki tüm konteynerleri silecektir.', + containerPruneHelper2: + 'Konteynerler uygulama mağazasından geliyorsa, temizleme işleminden sonra "Uygulama Mağazası -> Kurulu" bölümüne gidip "Yeniden Oluştur" düğmesine tıklayarak onları yeniden kurmanız gerekecektir.', + containerPruneHelper3: 'Bu işlem geri alınamaz. Devam etmek istiyor musunuz?', + imagePrune: 'Temizle', + imagePruneSome: 'Etiketlenmemiş temizle', + imagePruneSomeEmpty: '"none" etiketli temizlenebilecek imaj yok.', + imagePruneSomeHelper: 'Herhangi bir konteyner tarafından kullanılmayan "none" etiketli imajları temizle.', + imagePruneAll: 'Kullanılmayan temizle', + imagePruneAllEmpty: 'Temizlenebilecek kullanılmayan imaj yok.', + imagePruneAllHelper: 'Herhangi bir konteyner tarafından kullanılmayan imajları temizle.', + networkPrune: 'Temizle', + networkPruneHelper: 'Bu, kullanılmayan tüm ağları kaldıracaktır. Devam etmek istiyor musunuz?', + volumePrune: 'Temizle', + volumePruneHelper: 'Bu, kullanılmayan tüm yerel birimleri kaldıracaktır. Devam etmek istiyor musunuz?', + cleanSuccess: 'İşlem başarılı, bu temizleme sayısı: {0}!', + cleanSuccessWithSpace: 'İşlem başarılı. Bu sefer temizlenen disk sayısı {0}. Boşaltılan disk alanı {1}!', + unExposedPort: 'Mevcut port eşleme adresi 127.0.0.1, dış erişimi etkinleştiremez.', + upTime: 'Çalışma süresi', + fetch: 'Getir', + lines: 'Satırlar', + linesHelper: 'Lütfen alınacak log sayısını doğru girin!', + lastDay: 'Son gün', + last4Hour: 'Son 4 saat', + lastHour: 'Son saat', + last10Min: 'Son 10 dakika', + cleanLog: 'Log temizle', + downLogHelper1: 'Bu, {0} konteynerinden tüm logları indirecektir. Devam etmek istiyor musunuz?', + downLogHelper2: 'Bu, {0} konteynerinden son {0} logunu indirecektir. Devam etmek istiyor musunuz?', + cleanLogHelper: + 'Bu, konteynerin yeniden başlatılmasını gerektirir ve geri alınamaz. Devam etmek istiyor musunuz?', + newName: 'Yeni ad', + workingDir: 'Çalışma Dizini', + source: 'Kaynak kullanımı', + cpuUsage: 'CPU kullanımı', + cpuTotal: 'CPU toplam', + core: 'Çekirdek', + memUsage: 'Bellek kullanımı', + memTotal: 'Bellek sınırı', + memCache: 'Bellek önbelleği', + loadSize: 'Konteyner Boyutunu Al', + ip: 'IP adresi', + cpuShare: 'CPU paylaşımları', + cpuShareHelper: + 'Konteyner motoru CPU paylaşımları için 1024 temel değerini kullanır. Konteynere daha fazla CPU zamanı vermek için bunu artırabilirsiniz.', + inputIpv4: 'Örnek: 192.168.1.1', + inputIpv6: 'Örnek: 2001:0db8:85a3:0000:0000:8a2e:0370:7334', + + diskUsage: 'Disk Kullanımı', + localVolume: 'Yerel Depolama Birimi', + buildCache: 'Derleme Önbelleği', + usage: 'Kullanılan: {0}, Serbest Bırakılabilir: {1}', + clean: 'Serbest Bırak', + imageClean: + 'Görüntüleri temizlemek, kullanılmayan tüm görüntüleri silecektir. Bu işlem geri alınamaz. Devam etmek istiyor musunuz?', + containerClean: + 'Konteynerleri temizlemek, durdurulmuş tüm konteynerleri (Uygulama Mağazası ndaki durdurulmuş uygulamalar dahil) silecektir. Bu işlem geri alınamaz. Devam etmek istiyor musunuz?', + sizeRw: 'Konteyner Katman Boyutu', + sizeRwHelper: 'Konteynere özel yazılabilir katman boyutu', + sizeRootFs: 'Sanal Boyut', + sizeRootFsHelper: 'Konteynerin bağımlı olduğu tüm görüntü katmanları + konteyner katmanının toplam boyutu', + + containerFromAppHelper: + 'Bu konteynerin uygulama mağazasından geldiği tespit edildi. Uygulama işlemleri mevcut düzenlemelerin geçersiz hale gelmesine neden olabilir.', + containerFromAppHelper1: + 'Düzenleme sayfasına girmek ve konteyner adını değiştirmek için kurulu uygulamalar listesindeki [Param] düğmesine tıklayın.', + command: 'Komut', + console: 'Konteyner etkileşimi', + tty: 'Sözde-TTY tahsis et (-t)', + openStdin: 'Bağlı değilse bile STDINi açık tut (-i)', + custom: 'Özel', + emptyUser: 'Boş olduğunda, varsayılan olarak giriş yapacaksınız', + privileged: 'Ayrıcalıklı', + privilegedHelper: + 'Konteynerin ana bilgisayarda belirli ayrıcalıklı işlemler gerçekleştirmesine izin verir, bu da konteyner risklerini artırabilir. Dikkatli kullanın!', + + upgradeHelper: 'Depo Adı/İmaj Adı: İmaj Sürümü', + upgradeWarning2: + 'Yükseltme işlemi konteynerin yeniden oluşturulmasını gerektirir, kalıcı olmayan tüm veriler kaybedilecektir. Devam etmek istiyor musunuz?', + oldImage: 'Mevcut imaj', + sameImageContainer: 'Aynı imajlı konteynerler', + sameImageHelper: 'Aynı imajı kullanan konteynerlar seçilerek toplu şekilde güncellenebilir', + targetImage: 'Hedef imaj', + imageLoadErr: 'Konteyner için imaj adı algılanmadı', + appHelper: 'Konteyner uygulama mağazasından geliyor ve yükseltme hizmeti kullanılamaz hale getirebilir.', + + resource: 'Kaynak', + input: 'Manuel giriş', + forcePull: 'Her zaman imajı çek ', + forcePullHelper: 'Bu, sunucudaki mevcut imajları yok sayacak ve kayıt defterinden en son imajı çekecektir.', + server: 'Ana bilgisayar', + serverExample: '80, 80-88, ip:80 veya ip:80-88', + containerExample: '80 veya 80-88', + exposePort: 'Portu göster', + exposeAll: 'Tümünü göster', + cmdHelper: 'Örnek: nginx -g "daemon off;"', + entrypointHelper: 'Örnek: docker-entrypoint.sh', + autoRemove: 'Otomatik kaldır', + cpuQuota: 'CPU çekirdek sayısı', + memoryLimit: 'Bellek', + limitHelper: '0 olarak ayarlanırsa, sınırlama olmadığı anlamına gelir. Maksimum değer {0}', + macAddr: 'MAC Adresi', + mount: 'Bağla', + volumeOption: 'Birim', + hostOption: 'Ana bilgisayar', + serverPath: 'Sunucu yolu', + containerDir: 'Konteyner yolu', + volumeHelper: 'Depolama biriminin içeriğinin doğru olduğundan emin olun', + networkEmptyHelper: 'Lütfen konteyner ağ seçiminin doğru olduğunu onaylayın', + modeRW: 'RW', + modeR: 'R', + sharedLabel: 'Yayılma Modu', + private: 'Özel', + privateHelper: 'Konteyner ve hosttaki bağlama değişiklikleri birbirini etkilemez', + rprivate: 'Özyinelemeli Özel', + rprivateHelper: 'Konteynerdeki tüm bağlamalar hosttan tamamen izole edilmiştir', + shared: 'Paylaşılan', + sharedHelper: 'Host ve konteynerdeki bağlama değişiklikleri birbirine görünür', + rshared: 'Özyinelemeli Paylaşılan', + rsharedHelper: 'Host ve konteynerdeki tüm bağlama değişiklikleri birbirine görünür', + slave: 'Bağımlı', + slaveHelper: 'Konteyner host bağlama değişikliklerini görebilir, ancak kendi değişiklikleri hostu etkilemez', + rslave: 'Özyinelemeli Bağımlı', + rslaveHelper: 'Konteynerdeki tüm bağlamalar host değişikliklerini görebilir, ancak hostu etkilemez', + mode: 'Mod', + env: 'Ortamlar', + restartPolicy: 'Yeniden başlatma politikası', + always: 'always', + unlessStopped: 'unless-stopped', + onFailure: 'on-failure (varsayılan olarak beş kere)', + no: 'never', + + refreshTime: 'Yenileme aralığı', + cache: 'Önbellek', + + image: 'İmaj | İmajlar', + imagePull: 'Çek', + imagePullHelper: + 'Birden fazla görüntü seçmeyi destekler, her görüntü girdikten sonra Entera basarak devam edin', + imagePush: 'Gönder', + imagePushHelper: + 'Bu imgenin birden fazla etiketi olduğu tespit edildi. Lütfen gönderimde kullanılan imge adının şu olduğunu onaylayın: {0}', + imageDelete: 'İmaj sil', + repoName: 'Konteyner kayıt defteri', + imageName: 'İmaj adı', + pull: 'Çek', + path: 'Yol', + importImage: 'İçe aktar', + buildArgs: 'Derleme Argümanları', + imageBuild: 'Oluştur', + pathSelect: 'Yol', + label: 'Etiket', + imageTag: 'İmaj etiketi', + imageTagHelper: + "Birden fazla görüntü etiketi ayarlamayı destekler, her etiket girdikten sonra Enter'a basarak devam edin", + push: 'Gönder', + fileName: 'Dosya adı', + export: 'Dışa aktar', + exportImage: 'İmaj dışa aktarma', + size: 'Boyut', + tag: 'Etiketler', + tagHelper: 'Satır başına bir tane. Örneğin,\nkey1=value1\nkey2=value2', + imageNameHelper: 'İmaj adı ve Etiketi, örneğin: nginx:latest', + cleanBuildCache: 'Oluşturma önbelleğini temizle', + delBuildCacheHelper: + 'Bu, oluşturma sırasında üretilen tüm önbelleğe alınmış yapıları silecek ve geri alınamaz. Devam etmek istiyor musunuz?', + urlWarning: 'URL öneki http:// veya https:// içermemelidir. Lütfen değiştirin.', + + network: 'Ağ | Ağlar', + networkHelper: + 'Bu, bazı uygulamaların ve çalışma zamanı ortamlarının düzgün çalışmamasına neden olabilir. Devam etmek istiyor musunuz?', + createNetwork: 'Oluştur', + networkName: 'Ad', + driver: 'Sürücü', + option: 'Seçenek', + attachable: 'Eklenebilir', + parentNetworkCard: 'Ana Ağ Kartı', + subnet: 'Alt ağ', + scope: 'IP kapsamı', + gateway: 'Ağ geçidi', + auxAddress: 'IP hariç tut', + + volume: 'Birim | Birimler', + volumeDir: 'Birim dizini', + nfsEnable: 'NFS depolamayı etkinleştir', + nfsAddress: 'Adres', + mountpoint: 'Bağlama noktası', + mountpointNFSHelper: 'örn. /nfs, /nfs-share', + options: 'Seçenekler', + createVolume: 'Oluştur', + + repo: 'Konteyner kayıt defteri | Konteyner kayıt defterleri', + createRepo: 'Ekle', + httpRepoHelper: 'HTTP tipinde bir depo işlemi Docker servisinin yeniden başlatılmasını gerektirir.', + httpRepo: + 'HTTP protokolü seçilmesi Docker servisinin güvenli olmayan kayıt defterlerine eklemek için yeniden başlatılmasını gerektirir.', + delInsecure: 'Kredinin silinmesi', + delInsecureHelper: + 'Bu, güvenli olmayan kayıt defterlerinden kaldırmak için Docker servisini yeniden başlatacaktır. Devam etmek istiyor musunuz?', + downloadUrl: 'Sunucu', + imageRepo: 'İmaj deposu', + repoHelper: 'Ayna depo/organizasyon/proje içeriyor mu?', + auth: 'Kimlik doğrulama gerekli', + mirrorHelper: + 'Birden fazla ayna varsa, yeni satırlar gösterilmelidir, örneğin:\nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com', + registrieHelper: + 'Birden fazla özel depo varsa, yeni satırlar gösterilmelidir, örneğin:\n172.16.10.111:8081 \n172.16.10.112:8081', + + compose: 'Compose | Composelar', + composeFile: 'Düzenleme Dosyası', + fromChangeHelper: + 'Kaynağın değiştirilmesi mevcut düzenlenen içeriği temizleyecektir. Devam etmek istiyor musunuz?', + composePathHelper: 'Yapılandırma dosyası kaydetme yolu: {0}', + composeHelper: + '1Panel editörü veya şablonu aracılığıyla oluşturulan kompozisyon {0}/docker/compose dizinine kaydedilecektir.', + deleteFile: 'Dosyayı sil', + deleteComposeHelper: + 'Yapılandırma dosyaları ve kalıcı dosyalar dahil olmak üzere konteyner compose ile ilgili tüm dosyaları silin. Lütfen dikkatli ilerleyin!', + deleteCompose: '" Bu kompozisyonu sil.', + createCompose: 'Oluştur', + composeDirectory: 'Compose dizini', + template: 'Şablon', + composeTemplate: 'Compose şablonu | Compose şablonları', + createComposeTemplate: 'Oluştur', + content: 'İçerik', + contentEmpty: 'Compose içeriği boş olamaz, lütfen girin ve tekrar deneyin!', + containerNumber: 'Konteyner sayısı', + containerStatus: 'Konteyner durumu', + exited: 'Çıktı', + running: 'Çalışıyor ( {0} / {1} )', + composeDetailHelper: 'Compose, 1Panel dışında oluşturulmuştur. Başlatma ve durdurma işlemleri desteklenmez.', + composeOperatorHelper: '{0} üzerinde {1} işlemi gerçekleştirilecek. Devam etmek istiyor musunuz?', + composeDownHelper: + 'Bu, {0} compose altındaki tüm konteynerleri ve ağları durduracak ve kaldıracaktır. Devam etmek istiyor musunuz?', + composeEnvHelper2: + 'Bu düzenleme 1Panel Uygulama Mağazası tarafından oluşturuldu. Lütfen ortam değişkenlerini yüklü uygulamalarda değiştirin.', + + setting: 'Ayar | Ayarlar', + goSetting: 'Düzenlemeye git', + operatorStatusHelper: 'Bu, Docker servisini "{0}" yapacaktır. Devam etmek istiyor musunuz?', + dockerStatus: 'Docker Servisi', + daemonJsonPathHelper: 'Yapılandırma yolunun docker.servicede belirtilen ile aynı olduğundan emin olun.', + mirrors: 'Kayıt defteri aynaları', + mirrorsHelper: '', + mirrorsHelper2: 'Ayrıntılar için resmi belgelere bakın. ', + registries: 'Güvenli olmayan kayıt defterleri', + ipv6Helper: + 'IPv6yı etkinleştirirken, bir IPv6 konteyner ağı eklemeniz gerekir. Belirli yapılandırma adımları için resmi belgelere bakın.', + ipv6CidrHelper: 'Konteynerler için IPv6 adres havuzu aralığı', + ipv6TablesHelper: 'iptables kuralları için Docker IPv6nın otomatik yapılandırması.', + experimentalHelper: + 'ip6tablesı etkinleştirmek bu yapılandırmanın açılmasını gerektirir; aksi takdirde ip6tables yok sayılacaktır', + cutLog: 'Log seçeneği', + cutLogHelper1: 'Mevcut yapılandırma yalnızca yeni oluşturulan konteynerleri etkileyecektir.', + cutLogHelper2: 'Mevcut konteynerler yapılandırmanın etkili olması için yeniden oluşturulmalıdır.', + cutLogHelper3: + 'Konteynerleri yeniden oluşturmanın veri kaybına neden olabileceğini unutmayın. Konteynerleriniz önemli veriler içeriyorsa, yeniden oluşturma işlemini gerçekleştirmeden önce yedeklediğinizden emin olun.', + maxSize: 'Maksimum boyut', + maxFile: 'Maksimum dosya', + liveHelper: + 'Varsayılan olarak, Docker daemonı sonlandığında, çalışan konteynerleri kapatır. Daemon kullanılamaz hale gelirse konteynerlerin çalışmaya devam etmesi için daemonı yapılandırabilirsiniz. Bu işlevsellik canlı geri yükleme olarak adlandırılır. Canlı geri yükleme seçeneği, daemon çökmesi, planlı kesintiler veya yükseltmeler nedeniyle konteyner kesinti süresini azaltmaya yardımcı olur.', + liveWithSwarmHelper: 'live-restore daemon yapılandırması swarm modu ile uyumlu değildir.', + iptablesDisable: 'iptables kapat', + iptablesHelper1: 'Docker için iptables kurallarının otomatik yapılandırması.', + iptablesHelper2: + 'iptablesı devre dışı bırakmak konteynerlerin dış ağlarla iletişim kuramamasına neden olacaktır.', + daemonJsonPath: 'Yapılandırma Yolu', + serviceUnavailable: 'Docker servisi şu anda başlatılmamış.', + startIn: ' başlatmak için', + sockPath: 'Unix domain socket', + sockPathHelper: 'Docker daemon ile istemci arasındaki iletişim kanalı.', + sockPathHelper1: 'Varsayılan yol: /var/run/docker-x.sock', + sockPathMsg: + 'Socket Path ayarını kaydetmek Docker servisinin kullanılamaz hale gelmesine neden olabilir. Devam etmek istiyor musunuz?', + sockPathErr: 'Lütfen doğru Docker sock dosya yolunu seçin veya girin', + related: 'İlgili', + includeAppstore: 'Uygulama mağazasından konteynerleri göster', + excludeAppstore: 'Uygulama Mağazası Konteynerini Gizle', + + cleanDockerDiskZone: 'Docker tarafından kullanılan disk alanını temizle', + cleanImagesHelper: '( Herhangi bir konteyner tarafından kullanılmayan tüm imajları temizle )', + cleanContainersHelper: '( Durmuş olan tüm konteynerleri temizle )', + cleanVolumesHelper: '( Kullanılmayan tüm yerel birimleri temizle )', + + makeImage: 'İmaj oluştur', + newImageName: 'Yeni imaj adı', + commitMessage: 'Commit mesajı', + author: 'Yazar', + ifPause: 'Oluşturma Sırasında Konteyneri Duraklat', + ifMakeImageWithContainer: 'Bu Konteynerden Yeni İmaj Oluşturulsun mu?', + finishTime: 'Son durdurma zamanı', + }, + cronjob: { + create: 'Cron görevi oluştur', + edit: 'Cron görevini düzenle', + importHelper: + 'İçe aktarım sırasında aynı isimli zamanlanmış görevler otomatik olarak atlanacaktır. Görevler varsayılan olarak 【Devre Dışı】 durumuna ayarlanır ve veri ilişkilendirme anormalse 【Düzenleme Bekliyor】 durumuna ayarlanır.', + changeStatus: 'Durumu değiştir', + disableMsg: 'Bu, zamanlanmış görevin otomatik olarak yürütülmesini durduracaktır. Devam etmek istiyor musunuz?', + enableMsg: + 'Bu, zamanlanmış görevin otomatik olarak yürütülmesine izin verecektir. Devam etmek istiyor musunuz?', + taskType: 'Tür', + nextTime: 'Sonraki 5 yürütme', + record: 'Kayıtlar', + viewRecords: 'Kayıtları görüntüle', + shell: 'Shell', + stop: 'Manuel Durdur', + stopHelper: 'Bu işlem mevcut görev yürütmesini zorla durduracaktır. Devam etmek istiyor musunuz?', + log: 'Yedekleme logları', + logHelper: 'Sistem logunu yedekle', + ogHelper1: '1.1Panel Sistem logu ', + logHelper2: '2. Sunucunun SSH giriş logu ', + logHelper3: '3. Tüm site logları ', + containerCheckBox: 'Konteynerde (konteyner komutunu girmeye gerek yok)', + containerName: 'Konteyner adı', + ntp: 'Zaman senkronizasyonu', + ntp_helper: 'NTP sunucusunu Araç Kutusunun Hızlı Kurulum sayfasından yapılandırabilirsiniz.', + app: 'Uygulamayı yedekle', + website: 'Web sitesini yedekle', + rulesHelper: + 'Birden fazla sıkıştırma hariç tutma kuralı olduğunda, satır sonları ile gösterilmeleri gerekir. Örneğin,\n*.log \n*.sql', + lastRecordTime: 'Son yürütme zamanı', + all: 'Tümü', + failedRecord: 'Başarısız kayıtlar', + successRecord: 'Başarılı kayıtlar', + database: 'Veritabanını yedekle', + backupArgs: 'Yedekleme Argümanları', + backupArgsHelper: + 'Listelenmemiş yedekleme argümanları manuel olarak girilip seçilebilir. Örneğin: --no-data yazın ve açılır listeden ilk seçeneği seçin.', + singleTransaction: + 'InnoDB tablolarını tek bir işlem kullanarak yedekler, büyük hacimli veri yedeklemeleri için uygundur', + quick: 'Tüm tabloyu belleğe yüklemek yerine verileri satır satır okur, büyük hacimli veri ve düşük bellekli makine yedeklemeleri için uygundur', + skipLockTables: 'Tüm tabloları kilitlemeden yedekleme, yüksek eşzamanlılığa sahip veritabanları için uygundur', + missBackupAccount: 'Yedekleme hesabı bulunamadı', + syncDate: 'Senkronizasyon zamanı ', + clean: 'Önbellek temizleme', + curl: 'URLe erişim', + taskName: 'Ad', + cronSpec: 'Tetikleme döngüsü', + cronSpecDoc: + 'Özel çalışma döngüleri yalnızca [dakika saat gün ay hafta] formatını destekler, örneğin, 0 0 * * *. Ayrıntılar için resmi belgelere bakın.', + cronSpecHelper: 'Doğru yürütme dönemini girin', + cleanHelper: + 'Bu işlem tüm görev yürütme kayıtlarını, yedekleme dosyalarını ve log dosyalarını kaydeder. Devam etmek istiyor musunuz?', + backupContent: 'Yedekleme içeriği', + directory: 'Yedekleme dizini', + sourceDir: 'Yedekleme dizini', + snapshot: 'Sistem anlık görüntüsü', + allOptionHelper: + 'Mevcut görev planı tüm [{0}] öğelerini yedeklemektir. Doğrudan indirme şu anda desteklenmiyor. [{0}] menüsünün yedekleme listesini kontrol edebilirsiniz.', + exclusionRules: 'Hariç tutma kuralı', + exclusionRulesHelper: + 'Hariç tutma kurallarını seçin veya girin, her setten sonra Enter basarak devam edin. Hariç tutma kuralları bu yedeklemedeki tüm sıkıştırma işlemlerine uygulanacaktır', + default_download_path: 'Varsayılan indirme bağlantısı', + saveLocal: 'Yerel yedeklemeleri sakla (bulut depolama kopyalarının sayısı ile aynı)', + url: 'URL Adresi', + urlHelper: 'Lütfen geçerli bir URL adresi girin', + targetHelper: 'Yedekleme hesapları panel ayarlarında sürdürülür.', + withImageHelper: 'Uygulama mağazası imajlarını yedekle, ancak bu anlık görüntü dosya boyutunu artıracaktır.', + ignoreApp: 'Uygulamaları hariç tut', + withImage: 'Uygulama Görüntüsünü Yedekle', + retainCopies: 'Kayıtları sakla', + retryTimes: 'Yeniden Deneme Girişimleri', + timeout: 'Zaman aşımı', + retryTimesHelper: '0, başarısızlık sonrası yeniden deneme yok demektir', + retainCopiesHelper: 'Yürütme kayıtları ve loglar için saklanacak kopya sayısı', + retainCopiesHelper1: 'Yedekleme dosyaları için saklanacak kopya sayısı', + retainCopiesUnit: ' kopya (Görüntüle)', + cronSpecRule: '{0} satırındaki yürütme dönemi biçimi yanlış. Lütfen kontrol edin ve tekrar deneyin!', + cronSpecRule2: 'Yürütme dönemi biçimi yanlış, lütfen kontrol edin ve tekrar deneyin!', + perMonthHelper: 'Her ayın {0}. günü {1}:{2}de yürüt', + perWeekHelper: 'Her hafta {0}da {1}:{2}de yürüt', + perDayHelper: 'Her gün {0}:{1}de yürüt', + perHourHelper: 'Her saat {0}. dakikada yürüt', + perNDayHelper: 'Her {0} günde bir {1}:{2}de yürüt', + perNHourHelper: 'Her {0} saatte bir {1}de yürüt', + perNMinuteHelper: 'Her {0} dakikada bir yürüt', + perNSecondHelper: 'Her {0} saniyede bir yürüt', + perMonth: 'Her ay', + perWeek: 'Her hafta', + perHour: 'Her saat', + perNDay: 'Her N gün', + perDay: 'Her gün', + perNHour: 'Her N saat', + perNMinute: 'Her N dakika', + perNSecond: 'Her N saniye', + day: 'gün', + monday: 'Pazartesi', + tuesday: 'Salı', + wednesday: 'Çarşamba', + thursday: 'Perşembe', + friday: 'Cuma', + saturday: 'Cumartesi', + sunday: 'Pazar', + shellContent: 'Betik', + executor: 'Yürütücü', + errRecord: 'Hatalı kayıt', + errHandle: 'Cronjob yürütme başarısız', + noRecord: 'Cron Görevini tetikleyin, kayıtları burada göreceksiniz.', + cleanData: 'Veriyi temizle', + cleanRemoteData: 'Uzak veriyi sil', + cleanDataHelper: 'Bu görev sırasında oluşturulan yedekleme dosyasını sil.', + noLogs: 'Henüz görev çıktısı yok...', + errPath: 'Yedek yolu [{0}] hatası, indirilemez!', + cutWebsiteLog: 'Website log döndürme', + cutWebsiteLogHelper: 'Döndürülen log dosyaları 1Panel yedek dizinine yedeklenecektir.', + syncIpGroup: 'WAF IP gruplarını senkronize et', + + requestExpirationTime: 'Yükleme isteği son kullanma süresi(Saat)', + unitHours: 'Birim: Saat', + alertTitle: 'Planlanmış Görev - {0} 「{1}」 Görev Başarısızlık Uyarısı', + library: { + script: 'Script', + syncNow: 'Hemen Senkronize Et', + turnOnSync: 'Otomatik Senkronizasyonu Aç', + turnOnSyncHelper: + 'Otomatik senkronizasyonu açmak, her gün sabahın erken saatlerinde otomatik senkronizasyon gerçekleştirecektir', + turnOffSync: 'Otomatik Senkronizasyonu Kapat', + turnOffSyncHelper: + 'Otomatik senkronizasyonu kapatmak, betik senkronizasyon gecikmelerine neden olabilir, onaylıyor musunuz?', + isInteractive: 'Etkileşimli', + interactive: 'Etkileşimli script', + interactiveHelper: 'Yürütme sırasında kullanıcı girişi gerektirir ve zamanlanmış görevlerde kullanılamaz.', + library: 'Script Kütüphanesi', + remoteLibrary: 'Uzak Komut Dosyası Kütüphanesi', + create: 'Script Ekle', + edit: 'Script Düzenle', + groupHelper: + 'Script özelliklerine göre farklı gruplar ayarlayın, bu daha hızlı script filtreleme işlemlerine olanak tanır.', + handleHelper: '{0} üzerinde {1} scriptini çalıştır, devam edilsin mi?', + noSuchApp: '{0} servisi algılanmadı. Lütfen önce script kütüphanesi kullanarak hızlıca yükleyin!', + syncHelper: + 'Sistem script kütüphanesini senkronize etmek üzere. Bu işlem sadece sistem scriptlerini etkiler. Devam edilsin mi?', + }, + }, + monitor: { + globalFilter: 'Genel Filtre', + enableMonitor: 'İzleme Durumu', + storeDays: 'Saklama Günleri', + defaultNetwork: 'Varsayılan Ağ Kartı', + defaultNetworkHelper: 'İzleme ve genel bakış arayüzlerinde görüntülenen varsayılan ağ kartı seçeneği', + defaultIO: 'Varsayılan Disk', + defaultIOHelper: 'İzleme ve genel bakış arayüzlerinde görüntülenen varsayılan disk seçeneği', + cleanMonitor: 'İzleme Kayıtlarını Temizle', + cleanHelper: 'Bu işlem GPU dahil tüm izleme kayıtlarını temizleyecektir. Devam etmek istiyor musunuz?', + + avgLoad: 'Ortalama yük', + loadDetail: 'Yük detayı', + resourceUsage: 'Kullanım', + networkCard: 'Ağ arayüzü', + read: 'Okuma', + write: 'Yazma', + readWriteCount: 'I/O işlemleri', + readWriteTime: 'I/O gecikmesi', + today: 'Bugün', + yesterday: 'Dün', + lastNDay: 'Son {0} gün', + lastNMonth: 'Son {0} ay', + lastHalfYear: 'Son yarım yıl', + memory: 'Bellek', + percent: 'Yüzde', + cache: 'Önbellek', + disk: 'Disk', + network: 'Ağ', + up: 'Yukarı', + down: 'Aşağı', + interval: 'Toplama Aralığı', + intervalHelper: 'Lütfen uygun bir izleme toplama aralığı girin (5 saniye - 12 saat)', + }, + terminal: { + local: 'Yerel', + defaultConn: 'Varsayılan Bağlantı', + defaultConnHelper: + 'Bu işlem, 【{0}】 için terminal açıldıktan sonra otomatik olarak düğüm terminaline bağlanacaktır. Devam etmek istiyor musunuz?', + withReset: 'Bağlantı Bilgilerini Sıfırla', + localConnJump: + 'Varsayılan bağlantı bilgileri [Terminal - Ayarlar] bölümünde yönetilir. Bağlantı başarısız olursa lütfen oradan düzenleyin!', + localHelper: '`local` adı sadece sistem yerel tanımlaması için kullanılır', + connLocalErr: 'Otomatik kimlik doğrulama yapılamıyor, lütfen yerel sunucu giriş bilgilerini doldurun.', + testConn: 'Bağlantıyı test et', + saveAndConn: 'Kaydet ve bağlan', + connTestOk: 'Bağlantı bilgileri mevcut', + connTestFailed: 'Bağlantı mevcut değil, lütfen bağlantı bilgilerini kontrol edin.', + host: 'Ana Bilgisayar | Ana Bilgisayarlar', + createConn: 'Yeni bağlantı', + noHost: 'Ana bilgisayar yok', + groupChange: 'Grup değiştir', + expand: 'Tümünü genişlet', + fold: 'Tümünü daralt', + batchInput: 'Toplu işleme', + quickCommand: 'Hızlı komut | Hızlı komutlar', + noSuchCommand: + 'İçe aktarılan CSV dosyasında hızlı komut verisi bulunamadı, lütfen kontrol edip tekrar deneyin!', + quickCommandHelper: '"Terminaller -> Terminaller" altındaki hızlı komutları kullanabilirsiniz.', + groupDeleteHelper: + 'Grup kaldırıldıktan sonra, gruptaki tüm bağlantılar varsayılan gruba taşınacaktır. Devam etmek istiyor musunuz?', + command: 'Komut', + quickCmd: 'Hızlı komut', + addHost: 'Ekle', + localhost: 'Yerel sunucu', + ip: 'Adres', + authMode: 'Kimlik doğrulama', + passwordMode: 'Şifre', + rememberPassword: 'Kimlik doğrulama bilgilerini hatırla', + keyMode: 'Özel Anahtar', + key: 'Özel anahtar', + keyPassword: 'Özel anahtar şifresi', + emptyTerminal: 'Şu anda bağlı terminal yok.', + lineHeight: 'Satır Yüksekliği', + letterSpacing: 'Harf Aralığı', + fontSize: 'Font Boyutu', + cursorBlink: 'İmleç Yanıp Sönme', + cursorStyle: 'İmleç Stili', + cursorUnderline: 'Alt Çizgi', + cursorBlock: 'Blok', + cursorBar: 'Çubuk', + scrollback: 'Geri Kaydırma', + scrollSensitivity: 'Kaydırma Hassasiyeti', + saveHelper: 'Mevcut terminal yapılandırmasını kaydetmek istediğinizden emin misiniz?', + }, + toolbox: { + common: { + toolboxHelper: 'Bazı kurulum ve kullanım sorunları için lütfen başvurun', + }, + swap: { + swap: 'Swap Bölümü', + swapHelper1: 'Swap boyutu fiziksel belleğin 1 ila 2 katı olmalı, özel gereksinimlere göre ayarlanabilir;', + swapHelper2: + 'Swap dosyası oluşturmadan önce sistem diskinin yeterli kullanılabilir alanı olduğundan emin olun, çünkü swap dosya boyutu karşılık gelen disk alanını kaplayacaktır;', + swapHelper3: + 'Swap bellek baskısını hafifletmeye yardımcı olabilir, ancak sadece bir alternatiftir. Swap`a aşırı bağımlılık sistem performansında düşüşe yol açabilir. Öncelikle bellek artırma veya uygulama bellek kullanımını optimize etme önerilir;', + swapHelper4: 'Normal sistem çalışmasını sağlamak için swap kullanımını düzenli olarak izlemeniz önerilir.', + swapDeleteHelper: + 'Bu işlem {0} Swap bölümünü kaldıracaktır. Sistem güvenliği nedeniyle, karşılık gelen dosya otomatik olarak silinmeyecektir. Silme gerekiyorsa, lütfen manuel olarak ilerleyin!', + saveHelper: 'Lütfen önce mevcut ayarları kaydedin!', + saveSwap: + 'Mevcut yapılandırmayı kaydetmek {0} Swap bölüm boyutunu {1} olarak ayarlayacaktır. Devam etmek istiyor musunuz?', + swapMin: 'Minimum bölüm boyutu 40 KB`dir. Lütfen değiştirin ve tekrar deneyin!', + swapMax: 'Bölüm boyutu için maksimum değer {0}`dır. Lütfen değiştirin ve tekrar deneyin!', + swapOff: 'Minimum bölüm boyutu 40 KB`dir. 0`a ayarlamak Swap bölümünü devre dışı bırakacaktır.', + }, + device: { + dnsHelper: 'DNS sunucusu', + dnsAlert: + 'Dikkat! /etc/resolv.conf dosyasının yapılandırmasını değiştirmek, sistem yeniden başlatıldıktan sonra dosyayı varsayılan değerlerine geri yükleyecektir.', + dnsHelper1: + 'Birden fazla DNS girişi olduğunda, yeni satırlarda görüntülenmelidirler. örn.\n114.114.114.114\n8.8.8.8', + hostsHelper: 'Ana bilgisayar adı çözümlemesi', + hosts: 'Domain', + hostAlert: + 'Gizli yorumlanmış kayıtlar, lütfen görüntülemek veya ayarlamak için Tüm yapılandırma düğmesine tıklayın', + toolbox: 'Hızlı ayarlar', + hostname: 'Ana bilgisayar adı', + passwd: 'Sistem şifresi', + passwdHelper: 'Giriş karakterleri $ ve & içeremez', + timeZone: 'Sistem saat dilimi', + localTime: 'Sunucu saati', + timeZoneChangeHelper: 'Sistem saat dilimini değiştirmek servisi yeniden başlatmayı gerektirir. Devam et?', + timeZoneHelper: + '"timedatectl" komutunu yüklemezseniz, saat dilimini değiştiremeyebilirsiniz. Çünkü sistem saat dilimini değiştirmek için bu komutu kullanır.', + timeZoneCN: 'Pekin', + timeZoneAM: 'Los Angeles', + timeZoneNY: 'New York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'NTP sunucusu', + hostnameHelper: + 'Ana bilgisayar adı değişikliği "hostnamectl" komutuna bağlıdır. Komut yüklü değilse, değişiklik başarısız olabilir.', + userHelper: + 'Kullanıcı adı alma için "whoami" komutuna bağlıdır. Komut yüklü değilse, alma başarısız olabilir.', + passwordHelper: + 'Şifre değişikliği "chpasswd" komutuna bağlıdır. Komut yüklü değilse, değişiklik başarısız olabilir.', + hostHelper: + 'Sağlanan içerikte boş değer var. Lütfen kontrol edin ve değişiklik yaptıktan sonra tekrar deneyin!', + dnsCheck: 'Kullanılabilirliği Test Et', + dnsOK: 'DNS yapılandırma bilgileri mevcut!', + dnsTestFailed: 'DNS yapılandırma bilgileri mevcut değil.', + }, + fail2ban: { + sshPort: 'SSH portunu dinle', + sshPortHelper: 'Mevcut Fail2ban ana bilgisayarın SSH bağlantı portunu dinler', + unActive: 'Fail2ban servisi şu anda etkin değil.', + operation: 'Fail2ban servisinde "{0}" işlemini gerçekleştireceksiniz. Devam etmek istiyor musunuz?', + fail2banChange: 'Fail2ban Yapılandırma Değişikliği', + ignoreHelper: + 'İzin listesindeki IP listesi engelleme için göz ardı edilecektir. Devam etmek istiyor musunuz?', + bannedHelper: + 'Engelleme listesindeki IP listesi sunucu tarafından engellenecektir. Devam etmek istiyor musunuz?', + maxRetry: 'Maksimum yeniden deneme girişimi', + banTime: 'Engelleme süresi', + banTimeHelper: 'Varsayılan engelleme süresi 10 dakikadır, -1 kalıcı engellemeyi belirtir', + banTimeRule: 'Lütfen geçerli bir engelleme süresi veya -1 girin', + banAllTime: 'Kalıcı engelleme', + findTime: 'Keşif dönemi', + banAction: 'Engelleme eylemi', + banActionOption: '{0} kullanarak belirtilen IP adreslerini engelle', + allPorts: ' (Tüm Portlar)', + ignoreIP: 'IP izin listesi', + bannedIP: 'IP engelleme listesi', + logPath: 'Log yolu', + logPathHelper: 'Varsayılan /var/log/secure veya /var/log/auth.log`dur', + }, + ftp: { + ftp: 'FTP hesabı | FTP hesapları', + notStart: 'FTP servisi şu anda çalışmıyor, lütfen önce başlatın!', + operation: 'Bu FTP servisi üzerinde "{0}" işlemi gerçekleştirecektir. Devam etmek istiyor musunuz?', + noPasswdMsg: 'Mevcut FTP hesap şifresi alınamıyor, lütfen şifreyi ayarlayın ve tekrar deneyin! ', + enableHelper: + 'Seçilen FTP hesabını etkinleştirmek erişim izinlerini geri yükleyecektir. Devam etmek istiyor musunuz?', + disableHelper: + 'Seçilen FTP hesabını devre dışı bırakmak erişim izinlerini iptal edecektir. Devam etmek istiyor musunuz?', + syncHelper: + 'Sunucu ve veritabanı arasında FTP hesap verilerini senkronize et. Devam etmek istiyor musunuz?', + }, + clam: { + clam: 'Virüs taraması', + cron: 'Zamanlanmış tarama', + cronHelper: 'Profesyonel sürüm zamanlanmış tarama özelliğini destekler', + specErr: 'Yürütme programı format hatası, lütfen kontrol edin ve tekrar deneyin!', + disableMsg: + 'Zamanlanmış yürütmeyi durdurmak bu tarama görevinin otomatik olarak çalışmasını engelleyecektir. Devam etmek istiyor musunuz?', + enableMsg: + 'Zamanlanmış yürütmeyi etkinleştirmek bu tarama görevinin düzenli aralıklarla otomatik olarak çalışmasına olanak tanıyacaktır. Devam etmek istiyor musunuz?', + showFresh: 'İmza güncelleyici servisini göster', + hideFresh: 'İmza güncelleyici servisini gizle', + clamHelper: + 'ClamAV için önerilen minimum yapılandırma: 3 GiB RAM veya daha fazla, 2.0 GHz veya daha yüksek tek çekirdekli CPU ve en az 5 GiB kullanılabilir sabit disk alanı.', + notStart: 'ClamAV servisi şu anda çalışmıyor, lütfen önce başlatın!', + removeRecord: 'Rapor dosyalarını sil', + noRecords: 'Taramayı başlatmak için "Tetikle" düğmesine tıklayın ve kayıtları burada göreceksiniz.', + removeInfected: 'Virüs dosyalarını sil', + removeInfectedHelper: + 'Sunucu güvenliğini ve normal çalışmasını sağlamak için görev sırasında tespit edilen virüs dosyalarını silin.', + clamCreate: 'Tarama kuralı oluştur', + infectedStrategy: 'Enfekte strateji', + removeHelper: 'Virüs dosyalarını sil, dikkatli seçin!', + move: 'Taşı', + moveHelper: 'Virüs dosyalarını belirtilen dizine taşı', + copyHelper: 'Virüs dosyalarını belirtilen dizine kopyala', + none: 'Hiçbir şey yapma', + noneHelper: 'Virüs dosyalarında herhangi bir işlem yapma', + scanDir: 'Tarama dizini', + infectedDir: 'Enfekte dizin', + scanDate: 'Tarama Tarihi', + scanResult: 'Tarama logları sonu', + tail: 'Satırlar', + infectedFiles: 'Enfekte dosyalar', + log: 'Detaylar', + clamConf: 'Clam AV daemon', + clamLog: '@:toolbox.clam.clamConf logları', + freshClam: 'FreshClam', + freshClamLog: '@:toolbox.clam.freshClam logları', + alertHelper: 'Profesyonel sürüm zamanlanmış tarama ve SMS uyarısını destekler', + alertTitle: 'Virüs tarama görevi「{0}」enfekte dosya tespit uyarısı', + }, + }, + logs: { + core: 'Panel Servisi', + agent: 'Düğüm İzleme', + panelLog: 'Panel logları', + operation: 'İşlem logları', + login: 'Giriş logları', + loginIP: 'Giriş IP', + loginAddress: 'Giriş adresi', + loginAgent: 'Giriş aracısı', + loginStatus: 'Durum', + system: 'Sistem logları', + deleteLogs: 'Logları temizle', + resource: 'Kaynak', + detail: { + dashboard: 'Genel Bakış', + ai: 'AI', + groups: 'Grup', + hosts: 'Ana Bilgisayar', + apps: 'Uygulama', + websites: 'Website', + containers: 'Konteyner', + files: 'Dosya', + runtimes: 'Çalışma Zamanı', + process: 'İşlem', + toolbox: 'Araç Kutusu', + backups: 'Yedekleme / Geri Yükleme', + tampers: 'Kurcalama', + xsetting: 'Arayüz Ayarları', + logs: 'Log', + settings: 'Ayar', + cronjobs: 'Cronjob', + databases: 'Veritabanı', + waf: 'WAF', + licenses: 'Lisans', + nodes: 'Düğüm', + commands: 'Hızlı Komutlar', + }, + websiteLog: 'Website logları', + runLog: 'Çalıştırma logları', + errLog: 'Hata logları', + task: 'Görev Logu', + taskName: 'Görev Adı', + taskRunning: 'Çalışıyor', + }, + file: { + fileDirNum: '{0} dizin, {1} dosya,', + currentDir: 'Dizin', + dir: 'Klasör', + fileName: 'Dosya adı', + search: 'Ara', + mode: 'İzinler', + editPermissions: '@.lower:file.mode düzenle', + owner: 'Sahip', + file: 'Dosya', + remoteFile: 'Uzak sunucudan indir', + share: 'Paylaş', + sync: 'Veri Senkronizasyonu', + size: 'Boyut', + updateTime: 'Değiştirilme', + rename: 'Yeniden adlandır', + role: 'İzinler', + info: 'Özellikleri görüntüle', + linkFile: 'Yumuşak bağlantı', + shareList: 'Paylaşım listesi', + zip: 'Sıkıştırılmış', + group: 'Grup', + path: 'Yol', + public: 'Diğerleri', + setRole: 'İzinleri ayarla', + link: 'Dosya bağlantısı', + rRole: 'Oku', + wRole: 'Yaz', + xRole: 'Çalıştırılabilir', + name: 'Ad', + compress: 'Sıkıştır', + deCompress: 'Sıkıştırmayı aç', + compressType: 'Sıkıştırma formatı', + compressDst: 'Sıkıştırma yolu', + replace: 'Mevcut dosyaların üzerine yaz', + compressSuccess: 'Başarıyla sıkıştırıldı', + deCompressSuccess: 'Sıkıştırma başarıyla açıldı', + deCompressDst: 'Sıkıştırma açma yolu', + linkType: 'Bağlantı türü', + softLink: 'Yumuşak bağlantı', + hardLink: 'Sert bağlantı', + linkPath: 'Bağlantı yolu', + selectFile: 'Dosya seç', + downloadUrl: 'Uzak URL', + downloadStart: 'İndirme başladı', + moveSuccess: 'Başarıyla taşındı', + copySuccess: 'Başarıyla kopyalandı', + pasteMsg: 'Hedef dizinin sağ üst köşesindeki "Yapıştır" düğmesine tıklayın', + move: 'Taşı', + calculate: 'Hesapla', + remark: 'Not', + setRemark: 'Not ekle', + remarkPrompt: 'Bir not girin', + remarkPlaceholder: 'Not', + remarkToggle: 'Notlar', + remarkToggleTip: 'Dosya notlarını yükle', + canNotDeCompress: 'Bu dosyanın sıkıştırması açılamaz', + uploadSuccess: 'Başarıyla yüklendi', + downloadProcess: 'İndirme ilerlemesi', + downloading: 'İndiriliyor...', + infoDetail: 'Dosya özellikleri', + root: 'Kök dizin', + list: 'Dosya listesi', + sub: 'Alt dizinleri dahil et', + downloadSuccess: 'Başarıyla indirildi', + theme: 'Tema', + language: 'Dil', + eol: 'Satır sonu', + copyDir: 'Kopyala', + paste: 'Yapıştır', + changeOwner: 'Kullanıcı ve kullanıcı grubunu değiştir', + containSub: 'İzin değişikliğini özyinelemeli olarak uygula', + ownerHelper: + 'PHP çalışma ortamının varsayılan kullanıcısı: kullanıcı grubu 1000:1000, kapsayıcı içindeki ve dışındaki kullanıcıların tutarsız görünmesi normaldir', + searchHelper: '* gibi joker karakterleri destekler', + uploadFailed: '[{0}] Dosya yükleme hatası', + fileUploadStart: '[{0}] yükleniyor...', + currentSelect: 'Geçerli seçim: ', + unsupportedType: 'Desteklenmeyen dosya türü', + deleteHelper: + 'Aşağıdaki dosyaları silmek istediğinizden emin misiniz? Varsayılan olarak, silme işleminden sonra geri dönüşüm kutusuna gider', + fileHelper: 'Not:\n1. Arama sonuçları sıralanamaz.\n2. Klasörler boyuta göre sıralanamaz.', + forceDeleteHelper: 'Dosyayı kalıcı olarak sil (geri dönüşüm kutusuna girmeden doğrudan silinir)', + recycleBin: 'Geri dönüşüm kutusu', + sourcePath: 'Orijinal yol', + deleteTime: 'Silme zamanı', + confirmReduce: 'Aşağıdaki dosyaları geri yüklemek istediğinizden emin misiniz?', + reduceSuccess: 'Geri yükleme başarılı', + reduce: 'Geri yükle', + reduceHelper: + 'Orijinal yolda aynı ada sahip bir dosya veya dizin varsa, üzerine yazılacaktır. Devam etmek istiyor musunuz?', + clearRecycleBin: 'Temizle', + clearRecycleBinHelper: 'Geri dönüşüm kutusunu temizlemek istiyor musunuz?', + favorite: 'Favoriler', + removeFavorite: 'Favorilerden kaldır?', + addFavorite: 'Favorilere Ekle/Kaldır', + clearList: 'Listeyi temizle', + deleteRecycleHelper: 'Aşağıdaki dosyaları kalıcı olarak silmek istediğinizden emin misiniz?', + typeErrOrEmpty: '[{0}] dosya türü yanlış veya boş klasör', + dropHelper: 'Yüklemek istediğiniz dosyaları buraya sürükleyin', + fileRecycleBin: 'Geri dönüşüm kutusunu etkinleştir', + fileRecycleBinMsg: '{0} geri dönüşüm kutusu', + wordWrap: 'Otomatik satır sonu', + deleteHelper2: 'Seçilen dosyayı silmek istediğinizden emin misiniz? Silme işlemi geri alınamaz', + ignoreCertificate: 'Güvensiz sunucu bağlantılarına izin ver', + ignoreCertificateHelper: + 'Güvensiz sunucu bağlantılarına izin vermek, veri sızıntısına veya değiştirilmesine yol açabilir. Bu seçeneği yalnızca indirme kaynağına güvendiğinizde kullanın.', + uploadOverLimit: 'Dosya sayısı 1000’i aşıyor! Lütfen sıkıştırıp yükleyin', + clashDitNotSupport: 'Dosya adlarının .1panel_clash içermesi yasaktır', + clashDeleteAlert: '"Geri Dönüşüm Kutusu" klasörü silinemez', + clashOpenAlert: 'Geri dönüşüm kutusu dizinini açmak için lütfen "Geri Dönüşüm Kutusu" düğmesine tıklayın', + right: 'İleri', + back: 'Geri', + top: 'Yukarı', + up: 'Geri dön', + openWithVscode: 'VS Code ile aç', + vscodeHelper: + 'Lütfen VS Code’un yerel olarak yüklü olduğundan ve SSH Remote eklentisinin yapılandırıldığından emin olun', + saveContentAndClose: 'Dosya değiştirildi, kaydedip kapatmak istiyor musunuz?', + saveAndOpenNewFile: 'Dosya değiştirildi, kaydedip yeni dosyayı açmak istiyor musunuz?', + noEdit: 'Dosya değiştirilmedi, buna gerek yok!', + noNameFolder: 'İsimsiz klasör', + noNameFile: 'İsimsiz dosya', + minimap: 'Kod mini haritası', + fileCanNotRead: 'Dosya okunamıyor', + previewTruncated: 'Dosya çok büyük, yalnızca son kısım gösteriliyor', + previewEmpty: 'Dosya boş veya metin dosyası değil', + previewLargeFile: 'Önizleme', + panelInstallDir: '1Panel kurulum dizini silinemez', + wgetTask: 'İndirme Görevi', + existFileTitle: 'Aynı ada sahip dosya uyarısı', + existFileHelper: 'Yüklenen dosya, aynı ada sahip bir dosya içeriyor, üzerine yazmak istiyor musunuz?', + existFileSize: 'Dosya boyutu (yeni -> eski)', + existFileDirHelper: 'Seçilen dosya/klasörün adı çakışıyor. Lütfen dikkatle ilerleyin! \n', + coverDirHelper: 'Yerine geçecek seçilen klasörler hedef yola kopyalanacak!', + noSuchFile: 'Dosya veya dizin bulunamadı. Lütfen kontrol edin ve tekrar deneyin.', + setting: 'Ayar', + showHide: 'Gizli dosyaları göster', + noShowHide: 'Gizli dosyaları gösterme', + cancelUpload: 'Yüklemeyi İptal Et', + cancelUploadHelper: 'Yüklemeyi iptal etmek ister misiniz, iptal sonrası yükleme listesi temizlenecektir.', + keepOneTab: 'En az bir sekme açık kalmalıdır', + notCanTab: 'Daha fazla sekme eklenemez', + convert: 'Formatı Dönüştür', + converting: 'Dönüştürülüyor', + fileCanNotConvert: 'Bu dosya format dönüşümünü desteklemiyor', + formatType: 'Format Türü', + sourceFormat: 'Kaynak Format', + sourceFile: 'Kaynak Dosya', + saveDir: 'Kaydetme Dizini', + deleteSourceFile: 'Kaynak Dosyayı Sil', + convertHelper: 'Seçilen dosyaları başka bir formata dönüştür', + convertHelper1: 'Lütfen dönüştürülecek dosyaları seçin', + execConvert: 'Dönüştürmeyi başlatın. Dönüştürme günlüklerini Görev Merkezi’nde görüntüleyebilirsiniz', + convertLogs: 'Dönüştürme Günlükleri', + formatConvert: 'Format Dönüştürme', + }, + ssh: { + autoStart: 'Otomatik başlat', + enable: 'Otomatik başlatmayı etkinleştir', + disable: 'Otomatik başlatmayı devre dışı bırak', + sshAlert: + 'Liste verileri, oturum açma tarihine göre sıralanır. Saat dilimini değiştirmek veya başka işlemler yapmak, oturum açma günlüklerinin tarihinde sapmalara neden olabilir.', + sshAlert2: + 'Kaba kuvvet saldırılarını deneyen IP adreslerini engellemek için "Araç Kutusu"nda "Fail2ban" kullanabilirsiniz, bu ana bilgisayarın güvenliğini artırır.', + sshOperate: 'SSH servisinde "{0}" işlemi gerçekleştirilecek. Devam etmek istiyor musunuz?', + sshChange: 'SSH Ayarı', + sshChangeHelper: 'Bu işlem "{0}" değerini "{1}" olarak değiştirdi. Devam etmek istiyor musunuz?', + sshFileChangeHelper: + 'Yapılandırma dosyasını değiştirmek, hizmet kullanılabilirliğini etkileyebilir. Bu işlemi yaparken dikkatli olun. Devam etmek istiyor musunuz?', + port: 'Port', + portHelper: 'SSH servisinin dinlediği portu belirtir.', + listenAddress: 'Dinleme adresi', + allV4V6: '0.0.0.0:{0}(IPv4) ve :::{0}(IPv6)', + listenHelper: + 'IPv4 ve IPv6 ayarlarını boş bırakmak, "0.0.0.0:{0}(IPv4)" ve ":::{0}(IPv6)" üzerinde dinlemeyi sağlar.', + addressHelper: 'SSH servisinin dinlediği adresi belirtir.', + permitRootLogin: 'Kök kullanıcı girişine izin ver', + rootSettingHelper: 'Kök kullanıcı için varsayılan giriş yöntemi "SSH girişine izin ver"dir.', + rootHelper1: 'SSH girişine izin ver', + rootHelper2: 'SSH girişini devre dışı bırak', + rootHelper3: 'Yalnızca anahtar girişine izin verilir', + rootHelper4: 'Yalnızca önceden tanımlanmış komutlar çalıştırılabilir. Başka işlemler yapılamaz.', + passwordAuthentication: 'Parola kimlik doğrulaması', + pwdAuthHelper: + 'Parola kimlik doğrulamasının etkinleştirilip etkinleştirilmeyeceği. Bu parametre varsayılan olarak etkindir.', + pubkeyAuthentication: 'Anahtar kimlik doğrulaması', + privateKey: 'Özel Anahtar', + publicKey: 'Genel Anahtar', + password: 'Parola', + createMode: 'Oluşturma Yöntemi', + generate: 'Otomatik Oluştur', + unSyncPass: 'Anahtar parolası senkronize edilemez', + syncHelper: + 'Eşitleme işlemi geçersiz anahtarları temizleyecek ve yeni tam anahtar çiftlerini eşitleyecek. Devam edilsin mi?', + input: 'Manuel Giriş', + import: 'Dosya Yükleme', + authKeys: 'Yetki Anahtarları', + authKeysHelper: 'Mevcut ortak anahtar bilgilerini kaydet?', + pubkey: 'Anahtar bilgisi', + pubKeyHelper: 'Mevcut anahtar bilgileri yalnızca {0} kullanıcısı için geçerlidir', + encryptionMode: 'Şifreleme modu', + passwordHelper: '6 ila 10 hane ve İngilizce harfler içerebilir', + reGenerate: 'Anahtarı yeniden oluştur', + keyAuthHelper: 'Anahtar kimlik doğrulamasının etkinleştirilip etkinleştirilmeyeceği.', + useDNS: 'DNS kullanımı', + dnsHelper: + 'SSH sunucusunda DNS çözümleme işlevinin, bağlantının kimliğini doğrulamak için etkinleştirilip etkinleştirilmeyeceğini kontrol eder.', + analysis: 'İstatistiksel bilgi', + denyHelper: + 'Aşağıdaki adreslerde "reddet" işlemi gerçekleştiriliyor. Ayar yapıldıktan sonra IP, sunucuya erişimden yasaklanacak. Devam etmek istiyor musunuz?', + acceptHelper: + 'Aşağıdaki adreslerde "kabul et" işlemi gerçekleştiriliyor. Ayar yapıldıktan sonra IP, normal erişimi geri kazanacak. Devam etmek istiyor musunuz?', + noAddrWarning: 'Şu anda [{0}] adres seçilmedi. Lütfen kontrol edin ve tekrar deneyin!', + loginLogs: 'Oturum açma günlükleri', + loginMode: 'Mod', + authenticating: 'Anahtar', + publickey: 'Anahtar', + belong: 'Ait', + local: 'Yerel', + session: 'Oturum | Oturumlar', + loginTime: 'Oturum açma zamanı', + loginIP: 'Oturum açma IP’si', + stopSSHWarn: 'Bu SSH bağlantısını kesmek ister misiniz?', + }, + setting: { + panel: 'Panel', + user: 'Panel kullanıcısı', + userChange: 'Panel kullanıcısını değiştir', + userChangeHelper: 'Panel kullanıcısını değiştirmek sizi oturumdan çıkaracak. Devam etmek istiyor musunuz?', + passwd: 'Panel parolası', + emailHelper: 'Parola kurtarma için', + watermark: 'Filigran Ayarları', + watermarkContent: 'Filigran İçeriği', + contentHelper: + '{0} düğüm adını, {1} düğüm adresini temsil eder. Değişkenleri kullanabilir veya özel adlar girebilirsiniz.', + watermarkColor: 'Filigran Rengi', + watermarkFont: 'Filigran Yazı Tipi Boyutu', + watermarkHeight: 'Filigran Yüksekliği', + watermarkWidth: 'Filigran Genişliği', + watermarkRotate: 'Döndürme Açısı', + watermarkGap: 'Aralık', + watermarkCloseHelper: 'Sistem filigran ayarlarını kapatmak istediğinizden emin misiniz?', + watermarkOpenHelper: 'Mevcut sistem filigran ayarlarını kaydetmek istediğinizden emin misiniz?', + title: 'Panel takma adı', + panelPort: 'Panel portu', + titleHelper: + '3 ila 30 karakter uzunluğunda, İngilizce harfler, Çince karakterler, sayılar, boşluklar ve yaygın özel karakterler destekler', + portHelper: + 'Önerilen port aralığı 8888 ila 65535’tir. Not: Sunucuda bir güvenlik grubu varsa, yeni portu önceden güvenlik grubundan izin verin', + portChange: 'Port değişikliği', + portChangeHelper: 'Servis portunu değiştir ve servisi yeniden başlat. Devam etmek istiyor musunuz?', + theme: 'Tema', + menuTabs: 'Menü sekmeleri', + dark: 'Koyu', + darkGold: 'Koyu Altın', + light: 'Açık', + auto: 'Sistemi takip et', + language: 'Dil', + languageHelper: + 'Varsayılan olarak tarayıcı dilini takip eder. Bu parametre yalnızca geçerli tarayıcıda etkilidir', + sessionTimeout: 'Oturum zaman aşımı', + sessionTimeoutError: 'Minimum oturum zaman aşımı 300 saniyedir', + sessionTimeoutHelper: 'Panel, {0} saniye boyunca işlem yapılmazsa otomatik olarak oturumu kapatır.', + systemIP: 'Sistem adresi', + systemIPHelper: + 'Adres, uygulama yönlendirmesi, konteyner erişimi ve diğer işlevler için kullanılacaktır. Her düğüm farklı bir adresle yapılandırılabilir.', + proxy: 'Sunucu vekili', + proxyHelper: 'Vekil sunucuyu ayarladıktan sonra aşağıdaki senaryolarda etkili olacaktır:', + proxyHelper1: 'Uygulama mağazasından kurulum paketi indirme ve senkronizasyon (Yalnızca Profesyonel sürüm)', + proxyHelper2: 'Sistem güncelleme ve güncelleme bilgisi alma (Yalnızca Profesyonel sürüm)', + proxyHelper3: 'Sistem lisansı doğrulama ve senkronizasyon', + proxyHelper4: 'Docker ağı vekil sunucu üzerinden erişilecek (Yalnızca Profesyonel sürüm)', + proxyHelper5: 'Sistem tipi komut kütüphaneleri için birleşik indirme ve senkronizasyon (Profesyonel)', + proxyHelper6: 'Sertifika başvurusu (Profesyonel)', + proxyType: 'Vekil türü', + proxyUrl: 'Vekil Adresi', + proxyPort: 'Vekil Portu', + proxyPasswdKeep: 'Parolayı Hatırla', + proxyDocker: 'Docker Vekili', + proxyDockerHelper: + 'Vekil sunucu yapılandırmasını Docker’a senkronize et, çevrimdışı sunucu imaj çekme gibi işlemleri destekler', + syncToNode: 'Düğüme Senkronize Et', + syncToNodeHelper: 'Ayarları diğer düğümlere senkronize et', + nodes: 'Düğüm', + selectNode: 'Düğüm Seç', + selectNodeError: 'Lütfen bir düğüm seçin', + apiInterface: 'API’yi Etkinleştir', + apiInterfaceClose: 'Kapatıldığında, API arayüzlerine erişilemez. Devam etmek istiyor musunuz?', + apiInterfaceHelper: 'Üçüncü taraf uygulamaların API’ye erişmesine izin ver.', + apiInterfaceAlert1: 'Üretim ortamlarında etkinleştirmeyin çünkü bu, sunucu güvenlik risklerini artırabilir.', + apiInterfaceAlert2: + 'Potansiyel güvenlik tehditlerini önlemek için üçüncü taraf uygulamaları API’yi çağırmak için kullanmayın.', + apiInterfaceAlert3: 'API belgesi:', + apiInterfaceAlert4: 'Kullanım belgesi:', + apiKey: 'API anahtarı', + apiKeyHelper: 'API anahtarı, üçüncü taraf uygulamaların API’ye erişmesi için kullanılır.', + ipWhiteList: 'IP izin listesi', + ipWhiteListEgs: 'Her satıra bir tane. Örneğin,\n172.161.10.111\n172.161.10.0/24', + ipWhiteListHelper: 'İzin listesindeki IP’ler API’ye erişebilir, 0.0.0.0/0 (tüm IPv4), ::/0 (tüm IPv6)', + apiKeyValidityTime: 'Arayüz anahtarının geçerlilik süresi', + apiKeyValidityTimeEgs: 'Arayüz anahtarının geçerlilik süresi (dakika cinsinden)', + apiKeyValidityTimeHelper: + 'Arayüz zaman damgası, mevcut zaman damgasıyla farkı (dakika cinsinden) izin verilen aralıktaysa geçerlidir. 0 değeri doğrulamayı devre dışı bırakır.', + apiKeyReset: 'Arayüz anahtarı sıfırlama', + apiKeyResetHelper: 'İlişkili anahtar servisi geçersiz hale gelecektir. Lütfen servise yeni bir anahtar ekleyin', + confDockerProxy: 'Docker vekilini yapılandır', + restartNowHelper: 'Docker vekilini yapılandırmak, Docker servisinin yeniden başlatılmasını gerektirir.', + restartNow: 'Hemen yeniden başlat', + restartLater: 'Daha sonra manuel olarak yeniden başlat', + systemIPWarning: 'Sunucu adresi şu anda ayarlanmadı. Önce kontrol panelinde ayarlayın.', + systemIPWarning1: 'Geçerli sunucu adresi {0} olarak ayarlandı ve hızlı yönlendirme mümkün değil!', + syncTime: 'Sunucu Saati', + timeZone: 'Saat Dilimi', + timeZoneChangeHelper: + 'Saat dilimini değiştirmek servisin yeniden başlatılmasını gerektirir. Devam etmek istiyor musunuz?', + timeZoneHelper: + 'Saat dilimi değişikliği, sistem timedatectl servisine bağlıdır. 1Panel servisi yeniden başlatıldıktan sonra etkili olur.', + timeZoneCN: 'Pekin', + timeZoneAM: 'Los Angeles', + timeZoneNY: 'New York', + ntpALi: 'Alibaba', + ntpGoogle: 'Google', + syncSite: 'NTP Sunucusu', + syncSiteHelper: + 'Bu işlem, sistem saati senkronizasyonu için {0}’ı kaynak olarak kullanacak. Devam etmek istiyor musunuz?', + changePassword: 'Parolayı Değiştir', + oldPassword: 'Orijinal parola', + newPassword: 'Yeni parola', + retryPassword: 'Parolayı onayla', + noSpace: 'Girilen bilgiler boşluk karakteri içeremez', + duplicatePassword: 'Yeni parola, orijinal parolayla aynı olamaz, lütfen tekrar girin!', + diskClean: 'Önbellek temizleme', + developerMode: 'Önizleme Programı', + developerModeHelper: + 'Yayınlanmadan önce yeni özellikleri ve düzeltmeleri deneyimleyebilir ve erken geri bildirim sağlayabilirsiniz.', + thirdParty: 'Üçüncü taraf hesaplar', + scope: 'Kapsam', + public: 'Genel', + publicHelper: + 'Genel tip yedekleme hesapları her alt düğüme senkronize edilir ve alt düğümler bunları birlikte kullanabilir', + private: 'Özel', + privateHelper: + 'Özel tip yedekleme hesapları yalnızca mevcut düğümde oluşturulur ve yalnızca mevcut düğümün kullanımı içindir', + noTypeForCreate: 'Şu anda oluşturulmuş bir yedekleme türü yok', + LOCAL: 'Sunucu diski', + OSS: 'Ali OSS', + S3: 'Amazon S3', + mode: 'Mod', + MINIO: 'MinIO', + SFTP: 'SFTP', + WebDAV: 'WebDAV', + WebDAVAlist: 'WebDAV, Alist bağlantısı için resmi belgelere bakabilirsiniz', + UPYUN: 'UPYUN', + ALIYUN: 'Aliyun Drive', + ALIYUNHelper: + 'Aliyun Drive’da istemci dışı indirmeler için mevcut maksimum sınır 100 MB’dir. Bu sınırı aşmak, istemci üzerinden indirme gerektirir.', + ALIYUNRecover: + 'Aliyun Drive’da istemci dışı indirmeler için mevcut maksimum sınır 100 MB’dir. Bu sınırı aşmak, istemci üzerinden yerel cihaza indirme ve ardından anlık görüntü senkronizasyonu ile kurtarma gerektirir.', + GoogleDrive: 'Google Drive', + analysis: 'Analiz', + analysisHelper: + 'Gerekli parçaları otomatik olarak ayrıştırmak için tüm token içeriğini yapıştırın. Özel işlemler için lütfen resmi belgelere bakın.', + serviceName: 'Servis Adı', + operator: 'Operatör', + OneDrive: 'Microsoft OneDrive', + isCN: 'Yüzyıl İnternet', + isNotCN: 'Uluslararası Sürüm', + client_id: 'İstemci Kimliği', + client_secret: 'İstemci sırrı', + redirect_uri: 'Yönlendirme URL’si', + onedrive_helper: 'Özel yapılandırma için resmi belgelere bakılabilir', + clickToRefresh: 'Yenilemek için tıklayın', + refreshTime: 'Token Yenileme Zamanı', + refreshStatus: 'Token Yenileme Durumu', + backupDir: 'Yedekleme dizini', + codeWarning: 'Geçerli yetkilendirme kodu formatı yanlış, lütfen tekrar kontrol edin!', + code: 'Yetki kodu', + codeHelper: + '"Edin" düğmesine tıklayın, ardından {0}’a giriş yapın ve yönlendirilen bağlantıdaki "code" sonrası içeriği kopyalayın. Bu içeriği giriş kutusuna yapıştırın. Özel talimatlar için lütfen resmi belgelere bakın.', + googleHelper: + 'Lütfen önce bir Google uygulaması oluşturun ve istemci bilgilerini alın, formu doldurun ve al butonuna tıklayın. Belirli işlemler için lütfen resmi belgelere bakın.', + loadCode: 'Edin', + COS: 'Tencent COS', + ap_beijing_1: 'Pekin Bölgesi 1', + ap_beijing: 'Pekin', + ap_nanjing: 'Nanjing', + ap_shanghai: 'Şanghay', + ap_guangzhou: 'Guangzhou', + ap_chengdu: 'Chengdu', + ap_chongqing: 'Chongqing', + ap_shenzhen_fsi: 'Shenzhen Finansal', + ap_shanghai_fsi: 'Şanghay Finansal', + ap_beijing_fsi: 'Pekin Finansal', + ap_hongkong: 'Hong Kong, Çin', + ap_singapore: 'Singapur', + ap_mumbai: 'Mumbai', + ap_jakarta: 'Cakarta', + ap_seoul: 'Seul', + ap_bangkok: 'Bangkok', + ap_tokyo: 'Tokyo', + na_siliconvalley: 'Silikon Vadisi (ABD Batı)', + na_ashburn: 'Ashburn (ABD Doğu)', + na_toronto: 'Toronto', + sa_saopaulo: 'Sao Paulo', + eu_frankfurt: 'Frankfurt', + KODO: 'Qiniu Kodo', + scType: 'Depolama türü', + typeStandard: 'Standart', + typeStandard_IA: 'Standart_IA', + typeArchive: 'Arşiv', + typeDeep_Archive: 'Derin_Arşiv', + scLighthouse: 'Varsayılan, Hafif nesne depolama yalnızca bu depolama türünü destekler', + scStandard: + 'Standart Depolama, gerçek zamanlı erişim gerektiren çok sayıda sıcak dosya içeren iş senaryoları için uygundur, sık veri etkileşimi vb.', + scStandard_IA: + 'Düşük frekanslı depolama, nispeten düşük erişim frekansına sahip iş senaryoları için uygundur ve verileri en az 30 gün saklar.', + scArchive: 'Arşiv depolama, son derece düşük erişim frekansına sahip iş senaryoları için uygundur.', + scDeep_Archive: + 'Dayanıklı soğuk depolama, son derece düşük erişim frekansına sahip iş senaryoları için uygundur.', + archiveHelper: + 'Arşiv depolama dosyaları doğrudan indirilemez ve önce ilgili bulut hizmeti sağlayıcısının web sitesi üzerinden geri yüklenmelidir. Lütfen dikkatli kullanın!', + backupAlert: + 'Bir bulut sağlayıcısı S3 protokolüyle uyumluysa, yedekleme için doğrudan Amazon S3 kullanılabilir.', + domain: 'Hızlandırma alanı', + backupAccount: 'Yedekleme hesabı | Yedekleme hesapları', + loadBucket: 'Kova al', + accountName: 'Hesap adı', + accountKey: 'Hesap anahtarı', + address: 'Adres', + path: 'Yol', + safe: 'Güvenlik', + passkey: 'Passkey', + passkeyManage: 'Yönet', + passkeyHelper: 'Hızlı giriş için en fazla 5 passkey bağlanabilir', + passkeyRequireSSL: 'Passkey kullanmak için HTTPS’i etkinleştirin', + passkeyNotSupported: 'Mevcut tarayıcı veya ortam passkey desteklemiyor', + passkeyCount: 'Bağlı {0}/{1}', + passkeyName: 'Ad', + passkeyNameHelper: 'Cihazları ayırt etmek için bir ad girin', + passkeyAdd: 'Passkey ekle', + passkeyCreatedAt: 'Oluşturulma', + passkeyLastUsedAt: 'Son kullanım', + passkeyDeleteConfirm: 'Silindikten sonra bu passkey ile giriş yapılamaz. Devam edilsin mi?', + passkeyLimit: 'En fazla 5 passkey bağlanabilir', + passkeyFailed: 'Passkey kaydı başarısız oldu, lütfen panel SSL sertifikasının güvenilir olduğundan emin olun', + bindInfo: 'Bağlama bilgisi', + bindAll: 'Tümünü Dinle', + bindInfoHelper: + 'Servis dinleme adresini veya protokolü değiştirmek, hizmetin kullanılamamasına neden olabilir. Devam etmek istiyor musunuz?', + ipv6: 'IPv6’yı Dinle', + bindAddress: 'Dinleme adresi', + entrance: 'Giriş', + showEntrance: '"Genel Bakış" sayfasında devre dışı uyarısını göster', + entranceHelper: + 'Güvenlik girişini etkinleştirmek, panele yalnızca belirtilen güvenlik girişi üzerinden oturum açılmasına izin verecektir.', + entranceError: + 'Lütfen 5-116 karakterlik bir güvenli oturum açma giriş noktası girin, yalnızca sayılar veya harfler desteklenir.', + entranceInputHelper: 'Güvenlik girişini devre dışı bırakmak için boş bırakın.', + randomGenerate: 'Rastgele', + expirationTime: 'Son Kullanım Tarihi', + unSetting: 'Ayarlanmadı', + noneSetting: + 'Panel parolasının son kullanma tarihini ayarlayın. Son kullanma tarihinden sonra parolayı sıfırlamanız gerekir', + expirationHelper: 'Parola son kullanma süresi [0] gün ise, parola son kullanma işlevi devre dışı bırakılır', + days: 'Son Kullanım Günleri', + expiredHelper: 'Geçerli parola süresi doldu. Lütfen parolayı tekrar değiştirin.', + timeoutHelper: + '[ {0} gün ] Panel parolası süresi dolmak üzere. Süre dolduktan sonra parolayı sıfırlamanız gerekir', + complexity: 'Karmaşıklık doğrulaması', + complexityHelper: + 'Etkinleştirildiğinde, parola doğrulama kuralı şu olacaktır: 8-30 karakter, İngilizce, sayılar ve en az iki özel karakter içerir.', + bindDomain: 'Alan adı bağla', + unBindDomain: 'Alan adı bağlamasını kaldır', + panelSSL: 'Panel SSL', + panelSSLHelper: + 'Panel SSL’nin otomatik yenilenmesinden sonra, değişikliklerin etkili olması için 1Panel servisini manuel olarak yeniden başlatmanız gerekir.', + unBindDomainHelper: + 'Alan adı bağlamasını kaldırma işlemi sistem güvenliğini etkileyebilir. Devam etmek istiyor musunuz?', + bindDomainHelper: 'Alan adı bağlandıktan sonra, yalnızca o alan adı 1Panel servisine erişebilir.', + bindDomainHelper1: 'Alan adı bağlamasını devre dışı bırakmak için boş bırakın.', + bindDomainWarning: + 'Alan adı bağlamasından sonra oturumunuz kapatılacak ve yalnızca ayarlarınızda belirtilen alan adı üzerinden 1Panel servisine erişebilirsiniz. Devam etmek istiyor musunuz?', + allowIPs: 'Yetkili IP', + unAllowIPs: 'Yetkisiz IP', + unAllowIPsWarning: + 'Boş bir IP’yi yetkilendirmek, tüm IP’lerin sisteme erişmesine izin verecek ve bu, sistem güvenliğini etkileyebilir. Devam etmek istiyor musunuz?', + allowIPsHelper: + 'Yetkili IP adres listesini ayarladıktan sonra, yalnızca listedeki IP adresi panel servisine erişebilir.', + allowIPsWarning: + 'Yetkili IP adres listesini ayarladıktan sonra, yalnızca listedeki IP adresi panel servisine erişebilir. Devam etmek istiyor musunuz?', + allowIPsHelper1: 'IP adresi kısıtlamasını devre dışı bırakmak için boş bırakın.', + allowIPEgs: 'Her satıra bir tane. Örneğin,\n172.16.10.111\n172.16.10.0/24', + mfa: 'İki faktörlü kimlik doğrulama (2FA)', + mfaClose: 'MFA’yı devre dışı bırakmak servisin güvenliğini azaltabilir. Devam etmek istiyor musunuz?', + secret: 'Gizli', + mfaInterval: 'Yenileme aralığı(saniye)', + mfaTitleHelper: + 'Başlık, farklı 1Panel ana bilgisayarlarını ayırt etmek için kullanılır. Başlığı değiştirdikten sonra QR kodunu tekrar tarayın veya gizli anahtarı manuel olarak ekleyin.', + mfaIntervalHelper: + 'Yenileme süresini değiştirdikten sonra QR kodunu tekrar tarayın veya gizli anahtarı manuel olarak ekleyin.', + mfaAlert: + 'Tek kullanımlık token, geçerli zamana dayalı olarak dinamik olarak oluşturulan 6 haneli bir sayıdır. Sunucu saatinin senkronize olduğundan emin olun.', + mfaHelper: 'Etkinleştirildiğinde, tek kullanımlık token doğrulanmalıdır.', + mfaHelper1: 'Bir kimlik doğrulama uygulaması indirin, örneğin,', + mfaHelper2: + 'Tek kullanımlık token’ı elde etmek için, kimlik doğrulama uygulamanızla aşağıdaki QR kodunu tarayın veya gizli anahtarı kimlik doğrulama uygulamanıza kopyalayın.', + mfaHelper3: 'Uygulamadan altı haneli rakamı girin', + mfaCode: 'Tek kullanımlık token', + sslChangeHelper: 'Https ayarını değiştir ve servisi yeniden başlat. Devam etmek istiyor musunuz?', + sslDisable: 'Devre dışı bırak', + sslDisableHelper: + 'Https servisi devre dışı bırakılırsa, değişikliğin etkili olması için panelin yeniden başlatılması gerekir. Devam etmek istiyor musunuz?', + noAuthSetting: 'Yetkisiz ayar', + noAuthSettingHelper: + 'Kullanıcılar belirtilen güvenlik girişiyle oturum açmadığında veya panele belirtilen IP veya alan adından erişmediğinde, bu yanıt panel özelliklerini gizleyebilir.', + responseSetting: 'Yanıt ayarı', + help200: 'Yardım Sayfası', + error400: 'Hatalı İstek', + error401: 'Yetkisiz', + error403: 'Yasak', + error404: 'Bulunamadı', + error408: 'İstek Zaman Aşımı', + error416: 'Aralık Karşılanamadı', + error444: 'Bağlantı Kapalı', + error500: 'Dahili Sunucu Hatası', + https: 'Panel için HTTPS ayarlamak erişim güvenliğini artırır', + strictHelper: 'HTTPS olmayan trafik panele bağlanamaz', + muxHelper: + "Panel hem HTTP hem de HTTPS portlarını dinleyecek ve HTTP'yi HTTPS'ye yönlendirecektir, ancak bu güvenliği azaltabilir", + certType: 'Sertifika türü', + selfSigned: 'Kendi kendine imzalı', + selfSignedHelper: + 'Tarayıcılar kendi kendine imzalı sertifikalara güvenmeyebilir ve güvenlik uyarıları gösterebilir.', + select: 'Seç', + domainOrIP: 'Alan adı veya IP:', + timeOut: 'Zaman aşımı', + rootCrtDownload: 'Kök sertifika indirme', + primaryKey: 'Birincil anahtar', + certificate: 'Sertifika', + backupJump: + 'Geçerli yedekleme listesinde olmayan yedekleme dosyaları, lütfen dosya dizininden indirmeyi deneyin ve yedekleme için içe aktarın.', + snapshot: 'Anlık Görüntü | Anlık Görüntüler', + noAppData: 'Seçim için mevcut sistem uygulaması yok', + noBackupData: 'Seçim için mevcut yedekleme verisi yok', + stepBaseData: 'Temel Veri', + stepAppData: 'Sistem Uygulaması', + stepPanelData: 'Sistem Verisi', + stepBackupData: 'Yedekleme Verisi', + stepOtherData: 'Diğer Veri', + operationLog: 'İşlem Günlüğünü Sakla', + loginLog: 'Erişim Günlüğünü Sakla', + systemLog: 'Sistem Günlüğünü Sakla', + taskLog: 'Görev Günlüğünü Sakla', + monitorData: 'İzleme Verilerini Sakla', + selectAllImage: 'Tüm Uygulama Görüntülerini Yedekle', + logLabel: 'Günlük', + agentLabel: 'Düğüm Yapılandırması', + appDataLabel: 'Uygulama Verisi', + appImage: 'Uygulama Görüntüsü', + appBackup: 'Uygulama Yedeklemesi', + backupLabel: 'Yedekleme Dizini', + confLabel: 'Yapılandırma Dosyası', + dockerLabel: 'Konteyner', + taskLabel: 'Zamanlanmış Görev', + resourceLabel: 'Uygulama Kaynak Dizini', + runtimeLabel: 'Çalışma Zamanı Ortamı', + appLabel: 'Uygulama', + databaseLabel: 'Veritabanı', + snapshotLabel: 'Anlık Görüntü Dosyası', + websiteLabel: 'Web sitesi', + directoryLabel: 'Dizin', + appStoreLabel: 'Uygulama Mağazası', + shellLabel: 'Komut Dosyası', + tmpLabel: 'Geçici Dizin', + sslLabel: 'Sertifika Dizini', + reCreate: 'Anlık görüntü oluşturma başarısız', + reRollback: 'Anlık görüntü geri alma başarısız', + deleteHelper: + 'Üçüncü taraf yedekleme hesabındaki dosyalar da dahil olmak üzere tüm anlık görüntü dosyaları silinecek. Devam etmek istiyor musunuz?', + status: 'Anlık görüntü durumu', + ignoreRule: 'Yoksayma kuralı', + editIgnoreRule: '@:commons.button.edit @.lower:setting.ignoreRule', + ignoreHelper: + 'Bu kural, anlık görüntü oluştururken 1Panel veri dizinini sıkıştırmak ve yedeklemek için kullanılacaktır. Varsayılan olarak, soket dosyaları yok sayılır.', + ignoreHelper1: 'Her satıra bir tane. Örneğin,\n*.log\n/opt/1panel/cache', + panelInfo: '1Panel temel bilgilerini yaz', + panelBin: '1Panel sistem dosyalarını yedekle', + daemonJson: 'Docker yapılandırma dosyasını yedekle', + appData: '1Panel’den yüklenen uygulamaları yedekle', + panelData: '1Panel veri dizinini yedekle', + backupData: '1Panel için yerel yedekleme dizinini yedekle', + compress: 'Anlık görüntü dosyası oluştur', + upload: 'Anlık görüntü dosyasını yükle', + recoverDetail: 'Kurtarma detayı', + createSnapshot: 'Anlık görüntü oluştur', + importSnapshot: 'Anlık görüntüyü senkronize et', + importHelper: 'Anlık görüntü dizini: ', + lastRecoverAt: 'Son kurtarma zamanı', + lastRollbackAt: 'Son geri alma zamanı', + reDownload: 'Yedekleme dosyasını tekrar indir', + recoverErrArch: 'Farklı sunucu mimarileri arasında anlık görüntü kurtarma desteklenmez!', + recoverErrSize: 'Yetersiz disk alanı tespit edildi, lütfen kontrol edin veya temizleyin ve tekrar deneyin!', + recoverHelper: + '{0} anlık görüntüsünden kurtarma başlatılıyor, lütfen devam etmeden önce aşağıdaki bilgileri onaylayın:', + recoverHelper1: 'Kurtarma, Docker ve 1Panel servislerinin yeniden başlatılmasını gerektirir', + recoverHelper2: + 'Sunucuda yeterli disk alanının olduğundan emin olun (Anlık görüntü dosyası boyutu: {0}, Mevcut alan: {1})', + recoverHelper3: + 'Sunucu mimarisinin, anlık görüntünün oluşturulduğu sunucu mimarisiyle eşleştiğinden emin olun (Geçerli sunucu mimarisi: {0})', + rollback: 'Geri al', + rollbackHelper: + 'Bu kurtarmayı geri almak, bu kurtarmadan gelen tüm dosyaları değiştirecek ve Docker ile 1Panel servislerinin yeniden başlatılmasını gerektirebilir. Devam etmek istiyor musunuz?', + upgradeRecord: 'Yükseltme kaydı', + upgrading: 'Yükseltiliyor, lütfen bekleyin...', + upgradeHelper: 'Yükseltme, 1Panel servisinin yeniden başlatılmasını gerektirir. Devam etmek istiyor musunuz?', + noUpgrade: 'Şu anda en son sürüm', + versionHelper: + 'Ad kuralları: [ana sürüm].[fonksiyonel sürüm].[Hata düzeltme sürümü], aşağıdaki örnekte gösterildiği gibi:', + rollbackLocalHelper: + 'Birincil düğüm doğrudan geri almayı desteklemez. Lütfen geri almak için [1pctl restore] komutunu manuel olarak çalıştırın!', + upgradeCheck: 'Güncellemeleri kontrol et', + upgradeNotes: 'Sürüm notu', + upgradeNow: 'Şimdi yükselt', + source: 'İndirme kaynağı', + versionNotSame: 'Düğüm sürümü ana düğümle uyuşmuyor. Lütfen Düğüm Yönetiminde yükseltin ve tekrar deneyin.', + versionCompare: + '{0} düğümünün zaten en son yükseltilebilir sürümde olduğu tespit edildi. Lütfen birincil düğüm sürümünü kontrol edin ve tekrar deneyin!', + + about: 'Hakkında', + versionItem: 'Mevcut Sürüm', + backupCopies: 'Saklanacak Kopya Sayısı', + backupCopiesHelper: + 'Sürüm geri alma için saklanacak yükseltme yedek kopya sayısını ayarlayın. 0, tümünü sakla anlamına gelir.', + backupCopiesRule: 'Lütfen en az 3 yükseltme yedek kaydı saklayın', + release: 'Sürüm Güncelleme Günlüğü', + releaseHelper: + 'Mevcut ortam için güncelleme günlükleri alınırken hata oluştu. Resmi belgeleri manuel olarak kontrol edebilirsiniz.', + project: 'GitHub', + issue: 'Geri bildirim', + doc: 'Resmi belge', + star: 'Yıldız', + description: 'Linux Sunucu Paneli', + forum: 'Tartışmalar', + doc2: 'Belgeler', + currentVersion: 'Sürüm', + license: 'Lisans', + bindNode: 'Düğüm Bağla', + menuSetting: 'Menü Ayarları', + menuSettingHelper: 'Yalnızca 1 alt menü varsa, menü çubuğu yalnızca o alt menüyü gösterecektir', + showAll: 'Tümünü Göster', + hideALL: 'Tümünü Gizle', + ifShow: 'Gösterme Durumu', + menu: 'Menü', + confirmMessage: 'Gelişmiş menü listesini güncellemek için sayfa yenilenecek. Devam etmek istiyor musunuz?', + recoverMessage: + 'Sayfa yenilenecek ve menü listesi başlangıç durumuna geri yüklenecektir. Devam etmek istiyor musunuz?', + compressPassword: 'Sıkıştırma parolası', + backupRecoverMessage: 'Lütfen sıkıştırma veya sıkıştırma açma parolasını girin (ayarlamamak için boş bırakın)', + }, + license: { + offLine: 'Çevrimdışı Sürüm', + community: 'OSS', + oss: 'Açık Kaynak Yazılım', + pro: 'Pro', + trial: 'Deneme', + add: 'Topluluk Sürümünü Ekle', + licenseBindHelper: 'Ücretsiz düğüm kotası yalnızca lisans bir düğüme bağlı olduğunda kullanılabilir', + licenseAlert: + 'Topluluk Sürümü düğümleri yalnızca lisans bir düğüme düzgün şekilde bağlandığında eklenebilir. Yalnızca düzgün şekilde bağlanmış düğümler geçiş yapmayı destekler.', + licenseUnbindHelper: + 'Bu lisans için Topluluk Sürümü düğümleri tespit edildi. Lütfen bağlamayı kaldırın ve tekrar deneyin!', + subscription: 'Abonelik', + perpetual: 'Süresiz', + versionConstraint: '{0} Sürüm Satın Alma', + forceUnbind: 'Zorla Bağlantıyı Kaldır', + forceUnbindHelper: + 'Zorla bağlantı kaldırma, bağlantı kaldırma işlemi sırasında oluşan hataları yok sayar ve nihayetinde lisans bağını serbest bırakır.', + updateForce: + 'Zorla güncelle (bağlantı kaldırma sırasında tüm hataları yok sayarak son işlemin başarılı olmasını sağla)', + trialInfo: 'Sürüm', + authorizationId: 'Abonelik yetkilendirme kimliği', + authorizedUser: 'Yetkili kullanıcı', + lostHelper: + 'Lisans maksimum yeniden deneme sayısına ulaştı. Profesyonel sürüm özelliklerinin düzgün çalıştığından emin olmak için lütfen senkronizasyon düğmesine manuel olarak tıklayın.', + exceptionalHelper: + 'Lisans senkronizasyon doğrulaması anormal. Profesyonel sürüm işlevlerinin düzgün çalıştığından emin olmak için lütfen senkronizasyon düğmesine manuel olarak tıklayın. detay: ', + quickUpdate: 'Hızlı güncelleme', + import: 'İçe aktar', + power: 'Yetkilendir', + unbindHelper: 'Bağlantı kaldırıldığında tüm Pro ile ilgili ayarlar temizlenecek. Devam etmek istiyor musunuz? ', + importLicense: 'Lisansı içe aktar', + importHelper: 'Lütfen lisans dosyasını buraya tıklayın veya sürükleyin', + levelUpPro: 'Profesyonel Sürüme Yükselt', + licenseSync: 'Lisans Senkronizasyonu', + knowMorePro: 'Daha Fazla Bilgi Edinin', + closeAlert: 'Geçerli sayfa panel ayarlarında kapatılabilir', + introduce: 'Özellik Tanıtımı', + waf: 'Profesyonel sürüme yükseltme, engelleme haritası, günlükler, engelleme kayıtları, coğrafi konum engelleme, özel kurallar, özel engelleme sayfaları gibi özellikler sağlayabilir.', + tamper: 'Profesyonel sürüme yükseltme, web sitelerini yetkisiz değişikliklerden veya kurcalamalardan koruyabilir.', + tamperHelper: + 'İşlem başarısız oldu, dosya veya klasörde kurcalama koruması etkin. Lütfen kontrol edin ve tekrar deneyin!', + setting: + 'Profesyonel sürüme yükseltme, panel logosu, hoş geldiniz mesajı ve diğer bilgilerin özelleştirilmesine olanak tanır.', + monitor: + 'Profesyonel sürüme yükseltme, web sitesinin gerçek zamanlı durumunu, ziyaretçi trendlerini, ziyaretçi kaynaklarını, istek günlüklerini ve diğer bilgileri görüntülemenize olanak tanır. ', + alert: 'Profesyonel sürüme yükseltme, SMS yoluyla alarm bilgisi almanızı, alarm günlüklerini görüntülemenizi, çeşitli önemli olayları tamamen kontrol etmenizi ve sistemin sorunsuz çalışmasını sağlar.', + node: 'Profesyonel Sürüme yükseltme, 1Panel ile birden fazla Linux sunucusunu yönetmenize olanak tanır.', + nodeApp: + 'Profesyonel Sürüme yükseltme, düğümleri manuel olarak değiştirmeye gerek kalmadan çoklu düğüm uygulama sürümlerini birleşik olarak yükseltmenize olanak tanır.', + nodeDashboard: + 'Profesyonel Sürüme yükseltme, çoklu düğüm uygulamaları, web siteleri, veritabanları ve zamanlanmış görevleri merkezi olarak yönetmenizi sağlar.', + fileExchange: 'Profesyonel Sürüme yükseltme, birden fazla sunucu arasında hızlı dosya aktarımı sağlar.', + app: 'Profesyonel sürüme yükseltme, mobil uygulama üzerinden hizmet bilgilerini, anormal izlemeyi vb. görüntülemenize olanak tanır. ', + cluster: 'Profesyonel Sürüme Yükseltme, MySQL/Postgers/Redis ana-çalışan kümelerini yönetmenizi sağlar.', + }, + clean: { + scan: 'Taramayı başlat', + scanHelper: '1Panel çalışma zamanında üretilen gereksiz dosyaları kolayca temizleyin', + clean: 'Şimdi temizle', + reScan: 'Yeniden tara', + cleanHelper: 'Seçilen dosya ve dizinler temizlendikten sonra geri alınamaz. Devam etmek istiyor musunuz?', + statusStatusSuggest: '(Temizlik Önerilir)', + statusClean: '(Çok temiz)', + statusEmpty: 'Çok temiz, temizlik gerekmiyor!', + statusWarning: '(Dikkatli İlerleyin)', + lastCleanTime: 'Son Temizleme: {0}', + lastCleanHelper: 'Temizlenen dosya ve dizinler: {0}, toplam temizlenen: {1}', + cleanSuccessful: 'Başarıyla temizlendi', + currentCleanHelper: 'Bu oturumda temizlenen dosya ve dizinler: {0}, Toplam temizlenen: {1}', + suggest: '(Önerilen)', + totalScan: 'Temizlenecek toplam gereksiz dosya: ', + selectScan: 'Seçilen toplam gereksiz dosya: ', + + system: 'Sistem Gereksiz Dosyaları', + systemHelper: 'Anlık görüntü, yükseltme ve diğer işlemler sırasında oluşturulan geçici dosyalar', + panelOriginal: 'Sistem anlık görüntüsü geri yükleme öncesi yedekleme dosyaları', + upgrade: 'Sistem yükseltme yedekleme dosyaları', + agentPackages: 'Geçmiş sürüm alt düğüm yükseltme/kurulum paketleri', + upgradeHelper: '(Sistem geri alımı için en son yükseltme yedeğini tutmanız önerilir)', + snapshot: 'Sistem anlık görüntü geçici dosyaları', + rollback: 'Kurtarma öncesi yedek dosyaları', + + backup: 'Sistem Yedekleme', + backupHelper: 'Yerel yedekleme hesaplarıyla ilişkili olmayan yedekleme dosyaları', + unknownBackup: 'Sistem Yedekleme', + tmpBackup: 'Geçici Yedekleme', + unknownApp: 'İlişkili Olmayan Uygulama Yedeklemesi', + unknownDatabase: 'İlişkili Olmayan Veritabanı Yedeklemesi', + unknownWebsite: 'İlişkili Olmayan Web Sitesi Yedeklemesi', + unknownSnapshot: 'İlişkili Olmayan Anlık Görüntü Yedeklemesi', + + upload: 'Geçici Yükleme Dosyaları', + uploadHelper: 'Sistem yedek listesinden yüklenen geçici dosyalar', + download: 'Geçici İndirme Dosyaları', + downloadHelper: 'Sistem tarafından üçüncü taraf yedek hesaplarından indirilen geçici dosyalar', + directory: 'Dizin', + + systemLog: 'Günlük dosyası', + systemLogHelper: 'Sistem günlükleri, görev günlükleri, web sitesi günlük dosyaları', + dockerLog: 'Konteyner işlem günlüğü dosyaları', + taskLog: 'Zamanlanmış görev yürütme günlüğü dosyaları', + shell: 'Shell script zamanlanmış görevler', + containerShell: 'Konteynerler içinde yürütülen shell script zamanlanmış görevler', + curl: 'CURL zamanlanmış görevler', + + docker: 'Konteyner çöpü', + dockerHelper: 'Konteynerler, görüntüler, hacimler, derleme önbelleği vb. dosyalar', + volumes: 'Birimler', + buildCache: 'Konteyner Oluşturma Önbelleği', + + appTmpDownload: 'Uygulama geçici indirme dosyası', + unknownWebsiteLog: 'Bağlantısız web sitesi günlük yedekleme dosyası', + }, + app: { + app: 'Uygulama | Uygulamalar', + installName: 'Ad', + installed: 'Kurulu', + all: 'Tümü', + version: 'Sürüm', + detail: 'Detaylar', + params: 'Parametreleri düzenle', + author: 'Yazar', + source: 'Kaynak', + appName: 'Uygulama Adı', + deleteWarn: + 'Silme işlemi tüm verileri ve yedekleri birlikte silecektir. Bu işlem geri alınamaz. Devam etmek istiyor musunuz? ', + syncSuccess: 'Başarıyla senkronize edildi', + canUpgrade: 'Güncellemeler', + backupName: 'Dosya Adı', + backupPath: 'Dosya Yolu', + backupdate: 'Yedekleme zamanı', + versionSelect: 'Lütfen bir the sürüm seçin', + operatorHelper: 'Seçilen uygulamada {0} işlemi gerçekleştirilecek. Devam etmek istiyor musunuz?', + startOperatorHelper: 'Uygulama başlatılacak. Devam etmek istiyor musunuz?', + stopOperatorHelper: 'Uygulama durdurulacak. Devam etmek istiyor musunuz?', + restartOperatorHelper: 'Uygulama yeniden başlatılacak. Devam etmek istiyor musunuz?', + reloadOperatorHelper: 'Uygulama yeniden yüklenecek. Devam etmek istiyor musunuz?', + checkInstalledWarn: '"{0}" tespit edilmedi. Kurulum için "Uygulama Mağazası"na gidin.', + limitHelper: 'Uygulama zaten kurulmuş.', + deleteHelper: '"{0}" aşağıdaki kaynak(lar) ile ilişkilendirilmiş ve silinemez', + checkTitle: 'İpucu', + defaultConfig: 'Varsayılan yapılandırma', + defaultConfigHelper: 'Varsayılan yapılandırmaya geri yüklendi, kaydetmeden sonra etkili olacaktır', + forceDelete: 'Zorla sil', + forceDeleteHelper: + 'Zorla silme, silme işlemi sırasında oluşan hataları yok sayar ve nihayetinde meta verileri siler.', + deleteBackup: 'Yedeği sil', + deleteBackupHelper: 'Uygulama yedeğini de sil', + deleteDB: 'Veritabanını sil', + deleteDBHelper: 'Veritabanını da sil', + noService: '{0} Yok', + toInstall: 'Kurulum için git', + param: 'Parametreler', + alreadyRun: 'Yaş', + syncAppList: 'Senkronize et', + less1Minute: '1 dakikadan az', + appOfficeWebsite: 'Web sitesi', + github: 'Github', + document: 'Belge', + updatePrompt: 'Kullanılabilir güncelleme yok', + installPrompt: 'Henüz uygulama kurulmadı', + updateHelper: + 'Parametrelerin düzenlenmesi uygulamanın başlatılamamasına neden olabilir. Lütfen dikkatli ilerleyin.', + updateWarn: + 'Parametre güncellemesi uygulamanın yeniden oluşturulmasını gerektirir, Devam etmek istiyor musunuz? ', + busPort: 'Bağlantı Noktası', + syncStart: 'Senkronizasyon başlıyor! Lütfen uygulama mağazasını daha sonra yenileyin', + advanced: 'Gelişmiş ayarlar', + cpuCore: 'çekirdek(ler)', + containerName: 'Konteyner adı', + containerNameHelper: 'Konteyner adı ayarlanmadığında otomatik olarak oluşturulacaktır', + allowPort: 'Dış erişim', + allowPortHelper: + 'Dış bağlantı noktası erişimine izin vermek, güvenlik duvarı bağlantı noktasını serbest bırakacaktır', + appInstallWarn: + 'Uygulama varsayılan olarak dış erişim bağlantı noktasını açmaz. Açmak için "Gelişmiş ayarlar"a tıklayın.', + upgradeStart: 'Yükseltme başlıyor! Lütfen sayfayı daha sonra yenileyin', + toFolder: 'Kurulum dizinini aç', + editCompose: 'Compose dosyasını düzenle', + editComposeHelper: 'Compose dosyasını düzenlemek, yazılım kurulumunun başarısız olmasına neden olabilir', + composeNullErr: 'compose boş olamaz', + takeDown: 'Kaldır', + allReadyInstalled: 'Kurulu', + installHelper: 'Görüntü çekme sorunlarınız varsa, görüntü hızlandırmasını yapılandırın.', + installWarn: + 'Dış erişim işaretlenmedi, bu uygulama dış ağ üzerinden erişilemez hale getirecek. Devam etmek istiyor musunuz?', + showIgnore: 'Yoksayılan uygulamaları görüntüle', + cancelIgnore: 'Yoksaymayı iptal et', + ignoreList: 'Yoksayılan uygulamalar', + appHelper: 'Bazı özel uygulamalar için kurulum talimatlarını öğrenmek için uygulama detayları sayfasına gidin.', + backupApp: 'Yükseltmeden önce uygulamayı yedekle', + backupAppHelper: + 'Yükseltme başarısız olursa, yedek otomatik olarak geri alınır. Lütfen hata nedenini günlük denetim-sistem günlüğünde kontrol edin', + openrestyDeleteHelper: + 'OpenResty`nin zorla silinmesi tüm web sitelerini silecektir. Devam etmek istiyor musunuz?', + downloadLogHelper1: '{0} uygulamasının tüm günlükleri indirilmek üzere. Devam etmek istiyor musunuz? ', + downloadLogHelper2: '{0} uygulamasının en son {1} günlüğü indirilmek üzere. Devam etmek istiyor musunuz? ', + syncAllAppHelper: 'Tüm uygulamalar senkronize edilecek. Devam etmek istiyor musunuz? ', + hostModeHelper: + 'Geçerli uygulama ağ modu ana bilgisayar modudur. Bağlantı noktasını açmanız gerekiyorsa, lütfen güvenlik duvarı sayfasında manuel olarak açın.', + showLocal: 'Yerel uygulamaları göster', + reload: 'Yeniden yükle', + upgradeWarn: + 'Uygulamanın yükseltilmesi docker-compose.yml dosyasını değiştirecektir. Herhangi bir değişiklik varsa, dosya karşılaştırmasını görüntülemek için tıklayabilirsiniz', + newVersion: 'Yeni sürüm', + oldVersion: 'Mevcut sürüm', + composeDiff: 'Dosya karşılaştırması', + showDiff: 'Karşılaştırmayı görüntüle', + useNew: 'Özel sürümü kullan', + useDefault: 'Varsayılan sürümü kullan', + useCustom: 'docker-compose.yml dosyasını özelleştir', + useCustomHelper: + 'Özel bir docker-compose.yml dosyası kullanmak, uygulama yükseltmesinin başarısız olmasına neden olabilir. Gerekli değilse, işaretlemeyin.', + diffHelper: + 'Sol taraf eski sürüm, sağ taraf yeni sürümdür. Düzenlemeden sonra, özel sürümü kaydetmek için tıklayın', + pullImage: 'Görüntüyü Çek', + pullImageHelper: 'Uygulama başlamadan önce docker pull ile görüntüyü çekin', + deleteImage: 'Görüntüyü Sil', + deleteImageHelper: 'Uygulamayla ilgili görüntüyü silin. Silme başarısız olursa görev durmaz', + requireMemory: 'Bellek', + supportedArchitectures: 'Mimariler', + link: 'Bağlantı', + showCurrentArch: 'Mimari', + syncLocalApp: 'Yerel Uygulamayı Senkronize Et', + memoryRequiredHelper: 'Geçerli uygulama bellek gereksinimi {0}', + gpuConfig: 'GPU Desteğini Etkinleştir', + gpuConfigHelper: + 'Makinenin bir NVIDIA GPU`su olduğundan ve NVIDIA sürücülerinin ve NVIDIA Docker Container Toolkit`in kurulu olduğundan emin olun', + webUI: 'Web Erişim Adresi', + webUIPlaceholder: 'Örneğin: example.com:8080/login', + defaultWebDomain: 'Varsayılan Erişim Adresi', + defaultWebDomainHepler: + 'Uygulama bağlantı noktası 8080 ise, yönlendirme adresi http(s)://varsayılan erişim adresi:8080 olacaktır', + webUIConfig: + 'Geçerli düğümde varsayılan erişim adresi yapılandırılmamış. Lütfen bunu uygulama parametrelerinde veya panel ayarlarına giderek yapılandırın!', + toLink: 'Aç', + customAppHelper: 'Özel uygulama mağazası paketini kurmadan önce, kurulu uygulama olmadığından emin olun.', + forceUninstall: 'Zorla Kaldır', + syncCustomApp: 'Özel Uygulamayı Senkronize Et', + ignoreAll: 'Tüm sonraki sürümleri yoksay', + ignoreVersion: 'Belirtilen sürümü yoksay', + specifyIP: 'Ana Bilgisayar IP`sini Bağla', + specifyIPHelper: + 'Bağlantı noktasını bağlamak için ana bilgisayar adresini/ağ arayüzünü ayarlayın (bunda emin değilseniz, lütfen doldurmayın)', + uninstallDeleteBackup: 'Uygulamayı Kaldır - Yedeği Sil', + uninstallDeleteImage: 'Uygulamayı Kaldır - Görüntüyü Sil', + upgradeBackup: 'Yükseltmeden Önce Uygulamayı Yedekle', + noAppHelper: + 'Uygulama tespit edilmedi, lütfen uygulama mağazası senkronizasyon günlüğünü görüntülemek için görev merkezine gidin', + isEdirWarn: 'docker-compose.yml dosyasında değişiklik tespit edildi, lütfen karşılaştırmayı kontrol edin', + }, + website: { + primaryDomain: 'Birincil alan adı', + otherDomains: 'Diğer alan adları', + static: 'Statik', + deployment: 'Dağıtım', + supportUpType: 'Yalnızca .tar.gz dosya formatı desteklenir ve sıkıştırılmış paket {0}.json dosyası içermelidir', + proxy: 'Ters vekil', + alias: 'Takma ad', + ftpUser: 'FTP hesabı', + ftpPassword: 'FTP şifresi', + ftpHelper: + 'Bir web sitesi oluşturulduktan sonra, ilgili bir FTP hesabı oluşturulacak ve FTP dizini web sitesi dizinine bağlanacaktır.', + remark: 'Açıklama', + groupSetting: 'Grup Yönetimi', + createGroup: 'Grup Oluştur', + appNew: 'Yeni Uygulama', + appInstalled: 'Kurulu uygulama', + create: 'Oluştur', + delete: 'Web Sitesini Sil', + deleteApp: 'Uygulamayı Sil', + deleteBackup: 'Yedeği Sil', + domain: 'Alan Adı', + domainHelper: + 'Her satırda bir alan adı.\nJoker karakter "*" ve IP adresi desteklenir.\nBağlantı noktası ekleme desteklenir.', + addDomain: 'Ekle', + domainConfig: 'Alan Adları', + defaultDoc: 'Belge', + perserver: 'Eşzamanlılık', + perserverHelper: 'Geçerli sitenin maksimum eşzamanlılığını sınırlandır', + perip: 'Tek IP', + peripHelper: 'Tek bir IP için eşzamanlı erişim sayısını sınırlandır', + rate: 'Trafik sınırları', + rateHelper: 'Her istek için akış sınırlandır (birim: KB)', + limitHelper: 'Akış kontrolünü etkinleştir', + other: 'Diğer', + currentSSL: 'Geçerli Sertifika', + dnsAccount: 'DNS hesabı', + applySSL: 'Sertifika Başvurusu', + SSLList: 'Sertifika Listesi', + createDnsAccount: 'DNS hesabı', + aliyun: 'Aliyun DNS', + aliEsa: 'Aliyun ESA', + awsRoute53: 'Amazon Route 53', + manual: 'Manuel çözümleme', + key: 'Anahtar', + check: 'Görüntüle', + acmeAccountManage: 'ACME hesaplarını yönet', + email: 'E-posta', + acmeAccount: 'ACME hesabı', + provider: 'Doğrulama yöntemi', + dnsManual: 'Manuel Çözümleme', + expireDate: 'Son kullanma tarihi', + brand: 'Organizasyon', + deploySSL: 'Dağıtım', + deploySSLHelper: 'Sertifikayı dağıtmaya emin misiniz? ', + ssl: 'Sertifika | Sertifikalar', + dnsAccountManage: 'DNS sağlayıcılarını yönet', + renewSSL: 'Yenile', + renewHelper: 'Sertifikayı yenilemeye emin misiniz? ', + renewSuccess: 'Sertifika yenileme', + enableHTTPS: 'Etkinleştir', + aliasHelper: 'Takma ad, web sitesinin dizin adıdır', + lastBackupAt: 'son yedekleme zamanı', + null: 'hiçbiri', + nginxConfig: 'Nginx yapılandırması', + websiteConfig: 'Web sitesi ayarları', + proxySettings: 'Proxy Ayarları', + advancedSettings: 'Gelişmiş Ayarlar', + cacheSettings: 'Önbellek Ayarları', + sniSettings: 'SNI Ayarları', + basic: 'Temel', + source: 'Yapılandırma', + security: 'Güvenlik', + nginxPer: 'Performans ayarı', + neverExpire: 'Asla', + setDefault: 'Varsayılan olarak ayarla', + deleteHelper: 'İlgili uygulama durumu anormal, lütfen kontrol edin', + toApp: 'Kurulu listeye git', + cycle: 'Döngü', + frequency: 'Frekans', + ccHelper: + '{0} saniye içinde aynı URL’ye {1} defadan fazla istekte bulunulursa, CC savunması tetiklenir ve bu IP engellenir', + mustSave: 'Değişikliğin etkili olması için kaydedilmesi gerekiyor', + fileExt: 'dosya uzantısı', + fileExtBlock: 'dosya uzantısı engelleme listesi', + value: 'değer', + enable: 'Etkinleştir', + proxyAddress: 'Vekil Adresi', + proxyHelper: 'Örnek: 127.0.0.1:8080', + forceDelete: 'Zorla Sil', + forceDeleteHelper: + 'Zorla silme, silme işlemi sırasında oluşan hataları yok sayar ve nihayetinde meta verileri siler.', + deleteAppHelper: 'İlgili uygulamaları ve uygulama yedeklerini aynı anda sil', + deleteBackupHelper: 'Web sitesi yedeklerini de sil.', + deleteDatabaseHelper: 'Ayrıca web sitesi ile ilişkili veritabanını da sil', + deleteConfirmHelper: + 'Silme işlemi geri alınamaz. Silmeyi onaylamak için "{0}" yazın.', + staticPath: 'Karşılık gelen ana dizin ', + limit: 'Şema', + blog: 'Forum/Blog', + imageSite: 'Resim Sitesi', + downloadSite: 'İndirme Sitesi', + shopSite: 'Alışveriş Merkezi', + doorSite: 'Portal', + qiteSite: 'Kurumsal', + videoSite: 'Video', + errLog: 'Hata günlüğü', + stopHelper: + 'Site durdurulduktan sonra normal şekilde erişilemeyecek ve kullanıcı siteyi ziyaret ettiğinde mevcut sitenin durdurma sayfası görüntülenecektir. Devam etmek istiyor musunuz? ', + startHelper: + 'Site etkinleştirildikten sonra kullanıcılar site içeriğine normal şekilde erişebilir, devam etmek istiyor musunuz? ', + sitePath: 'Dizin', + siteAlias: 'Site takma adı', + primaryPath: 'Kök dizin', + folderTitle: 'Web sitesi esas olarak aşağıdaki klasörleri içerir', + wafFolder: 'Güvenlik duvarı kuralları', + indexFolder: 'Web sitesi kök dizini', + sslFolder: 'Web Sitesi Sertifikası', + enableOrNot: 'Etkinleştir', + oldSSL: 'Mevcut sertifika', + manualSSL: 'Sertifikayı içe aktar', + select: 'Seç', + selectSSL: 'Sertifika Seç', + privateKey: 'Anahtar (KEY)', + certificate: 'Sertifika (PEM formatı)', + HTTPConfig: 'HTTP Seçenekleri', + HTTPSOnly: 'HTTP isteklerini engelle', + HTTPToHTTPS: 'HTTPS’ye yönlendir', + HTTPAlso: 'Doğrudan HTTP isteklerine izin ver', + sslConfig: 'SSL seçenekleri', + disableHTTPS: 'HTTPS’yi devre dışı bırak', + disableHTTPSHelper: + 'HTTPS’nin devre dışı bırakılması, sertifika ile ilgili yapılandırmayı silecektir, Devam etmek istiyor musunuz?', + SSLHelper: + 'Not: SSL sertifikalarını yasa dışı web siteleri için kullanmayın.\nHTTPS erişimi açıldıktan sonra kullanılamıyorsa, güvenlik grubunun 443 numaralı bağlantı noktasını doğru şekilde serbest bırakıp bırakmadığını kontrol edin.', + SSLConfig: 'Sertifika ayarları', + SSLProConfig: 'Protokol ayarları', + supportProtocol: 'Protokol sürümü', + encryptionAlgorithm: 'Şifreleme algoritması', + notSecurity: '(güvenli değil)', + encryptHelper: + "Let's Encrypt, sertifika verme sıklığı için bir sınırlama getirir, ancak bu normal ihtiyaçları karşılamak için yeterlidir. Çok sık işlemler, verme işleminin başarısız olmasına neden olur. Özel kısıtlamalar için lütfen resmi belgeye bakın ", + ipValue: 'Değer', + ext: 'dosya uzantısı', + wafInputHelper: 'Verileri satır satır girin, her satır bir veri', + data: 'veri', + ever: 'kalıcı', + nextYear: 'Bir yıl sonra', + noLog: 'Günlük bulunamadı', + defaultServer: 'Varsayılan siteyi ayarla', + noDefaultServer: 'Ayarlanmadı', + defaultServerHelper: + 'Varsayılan site ayarlandıktan sonra, bağlanmamış tüm alan adları ve IP’ler varsayılan siteye yönlendirilecektir\nBu, kötü niyetli çözümlemeyi etkili bir şekilde önleyebilir\nAncak, bu aynı zamanda WAF yetkisiz alan adı engellemesinin başarısız olmasına neden olur', + restoreHelper: 'Bu yedeği kullanarak geri yüklemeye emin misiniz?', + websiteDeploymentHelper: + 'Bir web sitesi oluşturmak için kurulu bir uygulamayı kullanın veya yeni bir uygulama oluşturun.', + websiteStatictHelper: 'Ana bilgisayarda bir web sitesi dizini oluşturun.', + websiteProxyHelper: + 'Mevcut bir servisi vekil etmek için ters vekil kullanın. Örneğin, bir servis 8080 numaralı bağlantı noktasında kurulu ve çalışıyorsa, vekil adresi "http://127.0.0.1:8080" olacaktır.', + runtimeProxyHelper: 'Bir web sitesi oluşturmak için bir web sitesi çalışma zamanını kullanın.', + runtime: 'Çalışma Zamanı', + deleteRuntimeHelper: + 'Çalışma zamanı uygulaması, web sitesiyle birlikte silinmelidir, lütfen dikkatli bir şekilde işlem yapın', + proxyType: 'Ağ Türü', + unix: 'Unix Ağı', + tcp: 'TCP/IP Ağı', + phpFPM: 'FPM Yapılandırması', + phpConfig: 'PHP Yapılandırması', + updateConfig: 'Yapılandırmayı Güncelle', + isOn: 'Açık', + isOff: 'Kapalı', + rewrite: 'Sahte statik', + rewriteMode: 'Şema', + current: 'Mevcut', + rewriteHelper: + 'Sahte statik ayarlarının yapılması web sitesinin erişilemez hale gelmesine neden oluyorsa, varsayılan ayarlara geri dönmeyi deneyin.', + runDir: 'Çalışma Dizini', + runUserHelper: + 'PHP konteyner çalışma zamanı ortamı üzerinden dağıtılan web siteleri için, index ve alt dizinler altındaki tüm dosya ve klasörlerin sahibi ve kullanıcı grubu 1000 olarak ayarlanmalıdır. Yerel PHP ortamı için, yerel PHP-FPM kullanıcı ve kullanıcı grubu ayarlarına bakın', + userGroup: 'Kullanıcı/Grup', + uGroup: 'Grup', + proxyPath: 'Vekil yolu', + proxyPass: 'Hedef URL', + cache: 'Önbellek', + cacheTime: 'Önbellek süresi', + enableCache: 'Önbellek', + proxyHost: 'Vekil ana bilgisayarı', + disabled: 'Durduruldu', + startProxy: 'Bu, ters vekili başlatacak. Devam etmek istiyor musunuz?', + stopProxy: 'Bu, ters vekili durduracak. Devam etmek istiyor musunuz?', + sourceFile: 'Kaynağı görüntüle', + proxyHelper1: 'Bu dizine erişildiğinde, hedef URL’nin içeriği döndürülecek ve görüntülenecektir.', + proxyPassHelper: 'Hedef URL geçerli ve erişilebilir olmalıdır.', + proxyHostHelper: 'İstek başlığındaki alan adını vekil sunucusuna ilet.', + modifier: 'Eşleştirme kuralları', + modifierHelper: 'Örnek: "=" tam eşleşme, "~" düzenli eşleşme, "^~" yolun başlangıcıyla eşleşme vb.', + replace: 'Metin değiştirmeleri', + replaceHelper: + 'Nginx metin değiştirme özelliği, ters proxy sırasında yanıt içeriğindeki dizelerin değiştirilmesine olanak tanır. Genellikle arka uç tarafından döndürülen HTML, CSS, JavaScript ve diğer dosyalardaki bağlantıları, API adreslerini vb. değiştirmek için kullanılır. Karmaşık içerik değiştirme ihtiyaçları için normal ifade eşleştirmeyi destekler.', + addReplace: 'Ekle', + replaced: 'Arama Dizisi (boş olamaz)', + replaceText: 'Şununla değiştir', + replacedErr: 'Arama Dizisi boş olamaz', + replacedErr2: 'Arama Dizisi tekrarlanamaz', + replacedListEmpty: 'Metin değiştirme kuralı yok', + proxySslName: 'Proxy SNI Adı', + basicAuth: 'Temel kimlik doğrulama', + editBasicAuthHelper: + 'Şifre asimetrik olarak şifrelenir ve geri alınamaz. Düzenleme, şifrenin sıfırlanmasını gerektirir', + antiLeech: 'Sömürü karşıtı', + extends: 'Uzantı', + browserCache: 'Önbellek', + noModify: 'Değiştirme', + serverCache: 'Sunucu önbelleği', + leechLog: 'Sömürü karşıtı günlüğü kaydet', + accessDomain: 'İzin verilen alan adları', + leechReturn: 'Yanıt kaynağı', + noneRef: 'Boş yönlendirme izni ver', + disable: 'etkin değil', + disableLeechHelper: 'Sömürü karşıtını devre dışı bırakıp bırakmamak', + disableLeech: 'Sömürü karşıtını devre dışı bırak', + ipv6: 'IPv6’yı dinle', + leechReturnError: 'Lütfen HTTP durum kodunu doldurun', + blockedRef: 'Standart olmayan Referer’e izin ver', + accessControl: 'Sömürü karşıtı kontrol', + leechcacheControl: 'Önbellek kontrolü', + logEnableControl: 'Statik varlık isteklerini günlüğe al', + leechSpecialValidHelper: + "'Boş yönlendirme izni ver' etkinse yönlendiricisi olmayan istekler (doğrudan erişim vb.) engellenmez; 'Standart olmayan Referer’e izin ver' etkinse http/https ile başlamayan tüm Referer isteklerine (istemci istekleri vb.) izin verilir.", + leechInvalidReturnHelper: 'Hotlink isteklerini engelledikten sonra döndürülecek HTTP durum kodu', + leechlogControlHelper: + 'Statik varlık isteklerini kaydeder; üretimde genellikle aşırı ve gereksiz günlüklerden kaçınmak için kapatılır', + selectAcme: 'Acme hesabını seç', + imported: 'Manuel olarak oluşturuldu', + importType: 'İçe aktarma türü', + pasteSSL: 'Kodu yapıştır', + localSSL: 'Sunucu dosyasını seç', + privateKeyPath: 'Özel anahtar dosyası', + certificatePath: 'Sertifika dosyası', + ipWhiteListHelper: 'IP izin listesinin rolü: IP izin listesindeki tüm kurallar geçersizdir', + redirect: 'Yönlendirme', + sourceDomain: 'Kaynak alan adı', + targetURL: 'Hedef URL adresi', + keepPath: 'URI parametreleri', + path: 'yol', + redirectType: 'yönlendirme türü', + redirectWay: 'Yol', + keep: 'tut', + notKeep: 'Tutma', + redirectRoot: 'Ana sayfaya yönlendir', + redirectHelper: '301 kalıcı yönlendirme, 302 geçici yönlendirme', + changePHPVersionWarn: 'Bu işlem geri alınamaz, devam etmek istiyor musunuz?', + changeVersion: 'Sürümü değiştir', + retainConfig: 'php-fpm.conf ve php.ini dosyalarını saklayıp saklamamak', + runDirHelper2: 'Lütfen ikincil çalışma dizininin index dizini altında olduğundan emin olun', + openrestyHelper: + 'OpenResty varsayılan HTTP bağlantı noktası: {0} HTTPS bağlantı noktası: {1}, bu web sitesi alan adı erişimini ve HTTPS zorunlu yönlendirmesini etkileyebilir', + primaryDomainHelper: 'Örnek: example.com veya example.com:8080', + acmeAccountType: 'Hesap türü', + keyType: 'Anahtar algoritması', + tencentCloud: 'Tencent Cloud', + containWarn: 'Alan adı ana alanı içeriyor, lütfen tekrar girin', + rewriteHelper2: + 'Uygulama mağazasından yüklenen WordPress gibi uygulamalar genellikle önceden ayarlanmış sahte statik yapılandırmayla gelir. Bunları yeniden yapılandırmak hatalara yol açabilir.', + websiteBackupWarn: + 'Yalnızca yerel yedeklemelerin içe aktarılması desteklenir, diğer makinelerden yedeklerin içe aktarılması kurtarma hatasına neden olabilir', + ipWebsiteWarn: + 'IP alan adlarına sahip web sitelerinin normal şekilde erişilebilmesi için varsayılan site olarak ayarlanması gerekir.', + hstsHelper: 'HSTS’nin etkinleştirilmesi web sitesi güvenliğini artırabilir', + includeSubDomains: 'Alt Alan Adları', + hstsIncludeSubDomainsHelper: + 'Etkinleştirildiğinde, HSTS politikası geçerli etki alanının tüm alt alan adlarına uygulanacaktır.', + defaultHtml: 'Varsayılan sayfayı ayarla', + website404: 'Web sitesi 404 hata sayfası', + domain404: 'Web sitesi sayfası mevcut değil', + indexHtml: 'Statik web sitesi varsayılan sayfası', + stopHtml: 'Web sitesi durdurma sayfası', + indexPHP: 'PHP web sitesi varsayılan sayfası', + sslExpireDate: 'SSL Son Kullanma Tarihi', + website404Helper: + 'Web sitesi 404 hata sayfası yalnızca PHP çalışma zamanı ortamı web sitelerini ve statik web sitelerini destekler', + sni: 'Kaynak SNI', + sniHelper: + 'Ters vekil arka ucu HTTPS olduğunda, kaynak SNI’yi ayarlamanız gerekebilir. Ayrıntılar için CDN hizmet sağlayıcısının belgelerine bakın.', + huaweicloud: 'Huawei Cloud', + createDb: 'Veritabanı Oluştur', + enableSSLHelper: 'Etkinleştirme başarısızlığı web sitesinin oluşturulmasını etkilemez', + batchAdd: 'Toplu Alan Adı Ekle', + batchInput: 'Toplu Giriş', + domainNotFQDN: 'Bu alan adı genel ağda erişilebilir olmayabilir', + domainInvalid: 'Geçersiz alan adı biçimi', + domainBatchHelper: + 'Bir satırda bir alan adı, format: domain:port@ssl\nÖrnek: example.com:443@ssl veya example.com', + generateDomain: 'Oluştur', + global: 'Küresel', + subsite: 'Alt site', + subsiteHelper: 'Bir alt site, mevcut bir PHP veya statik web sitesi dizinini ana dizin olarak seçebilir.', + parentWbeiste: 'Ana Web Sitesi', + deleteSubsite: 'Geçerli web sitesini silmek için önce alt site(ler) {0} silinmelidir', + loadBalance: 'Yük Dengeleme', + server: 'Sunucu', + algorithm: 'Algoritma', + ipHash: 'IP Hash', + ipHashHelper: + 'İstemci IP adresine dayalı olarak istekleri belirli bir sunucuya dağıtır, belirli bir istemcinin her zaman aynı sunucuya yönlendirilmesini sağlar.', + leastConn: 'En Az Bağlantı', + leastConnHelper: 'En az aktif bağlantıya sahip sunucuya istek gönderir.', + leastTime: 'En Az Süre', + leastTimeHelper: 'En kısa aktif bağlantı süresine sahip sunucuya istek gönderir.', + defaultHelper: + 'Varsayılan yöntem, istekleri her sunucuya eşit şekilde dağıtır. Sunucuların ağırlıkları yapılandırılmışsa, istekler belirtilen ağırlıklara göre dağıtılır, daha yüksek ağırlıklı sunucular daha fazla istek alır.', + weight: 'Ağırlık', + maxFails: 'Maksimum Başarısızlık', + maxConns: 'Maksimum Bağlantılar', + strategy: 'Strateji', + strategyDown: 'Kapalı', + strategyBackup: 'Yedek', + ipHashBackupErr: 'IP 哈希不支持备用节点', + failTimeout: 'Hata zaman aşımı', + failTimeoutHelper: + 'Sunucu sağlık kontrolleri için zaman penceresi uzunluğu. Bu süre içinde biriken hata sayısı eşiğe ulaştığında, sunucu geçici olarak kaldırılacak ve aynı süre sonunda yeniden denenir. Varsayılan 10 saniye', + + staticChangePHPHelper: 'Şu anda statik bir web sitesi, PHP web sitesine geçiş yapabilirsiniz', + proxyCache: 'Ters Vekil Önbelleği', + cacheLimit: 'Önbellek Alanı Sınırı', + shareCahe: 'Önbellek Sayısı Bellek Boyutu', + cacheExpire: 'Önbellek Son Kullanma Süresi', + shareCaheHelper: 'Yaklaşık olarak 1M bellek başına 8000 önbellek nesnesi saklanabilir', + cacheLimitHelper: 'Sınır aşıldığında eski önbellek otomatik olarak silinir', + cacheExpireJHelper: 'Önbellek, son kullanma süresinden sonra kaçırılırsa silinir', + realIP: 'Gerçek IP', + ipFrom: 'IP Kaynağı', + ipFromHelper: + 'Güvenilir IP kaynaklarını yapılandırarak, OpenResty HTTP başlıklarında IP bilgilerini analiz eder, ziyaretçilerin gerçek IP adreslerini doğru bir şekilde tanımlar ve kaydeder, erişim günlükleri dahil', + ipFromExample1: "Ön uç bir Frp gibi bir araçsa, Frp'nin IP adresini girebilirsiniz, örneğin 127.0.0.1", + ipFromExample2: 'Ön uç bir CDN ise, CDN’nin IP adres aralığını girebilirsiniz', + ipFromExample3: + 'Emin değilseniz, 0.0.0.0/0 (ipv4) ::/0 (ipv6) girebilirsiniz [Not: Herhangi bir kaynak IP’ye izin vermek güvenli değildir]', + http3Helper: + 'HTTP/3, HTTP/2’nin bir yükseltmesidir, daha hızlı bağlantı hızları ve daha iyi performans sunar, ancak tüm tarayıcılar HTTP/3’ü desteklemez. Etkinleştirilmesi bazı tarayıcıların siteye erişememesine neden olabilir.', + cors: 'Çapraz Kaynak Paylaşımı (CORS)', + enableCors: 'CORS etkinleştir', + allowOrigins: 'İzin verilen alan adları', + allowMethods: 'İzin verilen istek yöntemleri', + allowHeaders: 'İzin verilen istek başlıkları', + allowCredentials: 'Çerezlerin gönderilmesine izin ver', + preflight: 'Ön istek hızlı yanıtı', + preflightHleper: + 'Etkinleştirildiğinde, tarayıcı çapraz kaynak ön isteği (OPTIONS isteği) gönderdiğinde, sistem otomatik olarak 204 durum kodunu döndürecek ve gerekli çapraz kaynak yanıt başlıklarını ayarlayacaktır', + + changeDatabase: 'Veritabanını Değiştir', + changeDatabaseHelper1: + 'Veritabanı ilişkilendirmesi, web sitesinin yedeklenmesi ve geri yüklenmesi için kullanılır.', + changeDatabaseHelper2: 'Başka bir veritabanına geçiş, önceki yedeklerin kurtarılamamasına neden olur.', + saveCustom: 'Şablon Olarak Kaydet', + rainyun: 'Rain Yun', + volcengine: 'Volcengine', + runtimePortHelper: + 'Geçerli çalışma zamanı ortamı birden fazla bağlantı noktasına sahip. Lütfen bir vekil bağlantı noktası seçin.', + runtimePortWarn: 'Geçerli çalışma zamanı ortamında bağlantı noktası yok, vekil yapılamıyor', + cacheWarn: 'Lütfen önce ters vekildeki önbellek anahtarını kapatın', + loadBalanceHelper: + 'Bu bölüm yalnızca yük dengeleme kuralları oluşturur, kuralları kullanmak için lütfen http(s):// adresine ters vekil yapın', + favorite: 'Favori', + cancelFavorite: 'Favoriyi İptal Et', + useProxy: 'Vekil Kullan', + useProxyHelper: 'Panel ayarlarındaki vekil sunucu adresini kullan', + westCN: 'Batı Dijital', + openBaseDir: 'Siteler Arası Saldırıları Önle', + openBaseDirHelper: + 'open_basedir, PHP dosya erişim yolunu kısıtlamak için kullanılır, bu siteler arası erişimi önlemeye ve güvenliği artırmaya yardımcı olur', + serverCacheTime: 'Sunucu Önbellek Süresi', + serverCacheTimeHelper: + 'Bir isteğin sunucuda önbelleğe alındığı süre. Bu süre boyunca, aynı istekler önbelleğe alınmış sonucu doğrudan döndürür ve kaynak sunucuya istekte bulunmaz.', + browserCacheTime: 'Tarayıcı Önbellek Süresi', + browserCacheTimeHelper: + 'Statik kaynakların tarayıcıda yerel olarak önbelleğe alındığı süre, tekrarlayan istekleri azaltır. Kullanıcılar süre dolmadan önce sayfayı yenilediğinde yerel önbelleği doğrudan kullanır.', + donotLinkeDB: 'Veritabanına Bağlanma', + toWebsiteDir: 'Web Sitesi Dizinine Gir', + execParameters: 'Çalıştırma Parametreleri', + extCommand: 'Ek Komut', + mirror: 'Ayna Kaynağı', + execUser: 'Çalıştıran Kullanıcı', + execDir: 'Çalıştırma Dizini', + packagist: 'Çin Tam Aynası', + + batchOpreate: 'Toplu İşlem', + batchOpreateHelper: 'Toplu {0} web siteleri, işlemi devam ettir?', + stream: 'TCP/UDP Proxy', + streamPorts: 'Dinleme Portları', + streamPortsHelper: + 'Harici dinleme bağlantı noktası numarasını ayarlayın, istemciler bu bağlantı noktası üzerinden hizmete erişecektir, virgülle ayırın, örn.: 5222,5223', + streamHelper: 'TCP/UDP Port Yönlendirme ve Yük Dengeleme', + udp: "UDP'yi etkinleştir", + + syncHtmlHelper: 'PHP ve statik web siteleriyle senkronize et', + }, + php: { + short_open_tag: 'Kısa etiket desteği', + max_execution_time: 'Maksimum betik yürütme süresi', + max_input_time: 'Maksimum giriş süresi', + memory_limit: 'Betik bellek sınırı', + post_max_size: 'POST veri maksimum boyutu', + file_uploads: 'Dosya yüklemeye izin verilip verilmeyeceği', + upload_max_filesize: 'Yüklenebilecek maksimum dosya boyutu', + max_file_uploads: 'Aynı anda yüklenebilecek maksimum dosya sayısı', + default_socket_timeout: 'Soket zaman aşımı', + error_reporting: 'Hata seviyesi', + display_errors: 'Ayrıntılı hata bilgilerinin 출력lenip 출력lenmeyeceği', + cgi_fix_pathinfo: 'Pathinfo’nun açılıp açılmayacağı', + date_timezone: 'Zaman dilimi', + disableFunction: 'Devre dışı bırakma fonksiyonu', + disableFunctionHelper: + 'Devre dışı bırakılacak fonksiyonu girin, örneğin exec, birden fazla için virgülle ayırın', + uploadMaxSize: 'Yükleme sınırı', + indexHelper: + 'PHP web sitesinin normal çalışmasını sağlamak için, lütfen kodu index dizinine yerleştirin ve yeniden adlandırmaktan kaçının', + extensions: 'Uzantı şablonlarını yönet', + extension: 'Uzantı', + extensionHelper: 'Lütfen birden fazla uzantı için virgülle ayırın', + toExtensionsList: 'Uzantı listesini görüntüle', + containerConfig: 'Konteyner Yapılandırması', + containerConfigHelper: + 'Oluşturulduktan sonra Yapılandırma - Konteyner Yapılandırması’nda ortam değişkenleri ve diğer bilgiler değiştirilebilir', + dateTimezoneHelper: 'Örnek: TZ=Asia/Shanghai (Gerektiğinde ekleyin)', + }, + nginx: { + serverNamesHashBucketSizeHelper: 'Sunucu adının karma tablosu boyutu', + clientHeaderBufferSizeHelper: 'İstemci tarafından istenen başlık arabelleği boyutu', + clientMaxBodySizeHelper: 'Maksimum Yükleme Dosyası', + keepaliveTimeoutHelper: 'Bağlantı Zaman Aşımı', + gzipMinLengthHelper: 'Minimum Sıkıştırılmış Dosya', + gzipCompLevelHelper: 'Sıkıştırma Oranı', + gzipHelper: 'İletim için sıkıştırmayı etkinleştir', + connections: 'Aktif bağlantılar', + accepts: 'Kabul edilenler', + handled: 'İşlenenler', + requests: 'İstekler', + reading: 'Okuma', + writing: 'Yazma', + waiting: 'Bekleme', + status: 'Geçerli Durum', + configResource: 'Yapılandırma', + saveAndReload: 'Kaydet ve yeniden yükle', + clearProxyCache: 'Ters vekil önbelleğini temizle', + clearProxyCacheWarn: 'Bu işlem önbellek dizinindeki tüm dosyaları silecektir. Devam etmek istiyor musunuz?', + create: 'Yeni bir modül ekle', + update: 'Bir modülü düzenle', + params: 'Parametreler', + packages: 'Paketler', + script: 'Betikler', + module: 'Modüller', + build: 'Oluştur', + buildWarn: + 'OpenResty’nin oluşturulması belirli miktarda CPU ve bellek ayırmayı gerektirir, bu uzun sürebilir, lütfen sabırlı olun', + mirrorUrl: 'Yazılım Kaynağı', + paramsHelper: 'Örnek: --add-module=/tmp/ngx_brotli', + packagesHelper: 'Örnek: git, curl (virgülle ayrılmış)', + scriptHelper: + 'Derlemeden önce çalıştırılacak betikler, genellikle modül kaynak kodunu indirmek, bağımlılıkları kurmak vb. için', + buildHelper: + 'Modül ekledikten/düzenledikten sonra oluştur’a tıklayın. OpenResty, başarılı oluşturma üzerine otomatik olarak yeniden başlatılacaktır.', + defaultHttps: 'HTTPS Anti-sızdırma', + defaultHttpsHelper1: 'Bu özelliği etkinleştirerek HTTPS sızdırma sorunlarını çözebilirsiniz.', + sslRejectHandshake: 'Varsayılan SSL el sıkışmasını reddet', + sslRejectHandshakeHelper: + 'Etkinleştirilmesi sertifika sızıntısını önleyebilir, varsayılan bir web sitesi ayarlamak bu ayarı geçersiz kılar', + }, + ssl: { + create: 'İstek', + provider: 'Tür', + manualCreate: 'Manuel olarak oluşturuldu', + acmeAccount: 'ACME hesabı', + resolveDomain: 'Alan adını çözümle', + err: 'Hata', + value: 'kayıt değeri', + dnsResolveHelper: + 'Lütfen aşağıdaki çözümleme kayıtlarını eklemek için DNS çözümleme servis sağlayıcısına gidin:', + detail: 'Detayları görüntüle', + msg: 'Bilgi', + ssl: 'Sertifika', + key: 'Özel anahtar', + startDate: 'Geçerlilik zamanı', + organization: 'veren organizasyon', + renewConfirm: '{0} alan adı için yeni bir sertifika yenilenecek. Devam etmek istiyor musunuz?', + autoRenew: 'Otomatik yenileme', + autoRenewHelper: 'Son kullanma tarihinden 30 gün önce otomatik olarak yenilenir', + renewSuccess: 'Yenileme başarılı', + renewWebsite: + 'Bu sertifika aşağıdaki web siteleriyle ilişkilendirilmiştir ve başvuru aynı anda bu web sitelerine uygulanacaktır', + createAcme: 'Hesap Oluştur', + acmeHelper: 'Acme Hesabı ücretsiz sertifikalar başvurusu için kullanılır', + upload: 'İçe aktar', + applyType: 'Başvuru yöntemi', + apply: 'Yenile', + applyStart: 'Sertifika başvurusu başlıyor', + getDnsResolve: 'DNS çözümleme değeri alınıyor, lütfen bekleyin...', + selfSigned: 'Kendi kendine imzalı CA’yı yönet', + ca: 'Sertifika yetkilisi', + commonName: 'Ortak ad', + caName: 'Sertifika yetkilisi adı', + company: 'Organizasyon adı', + department: 'Organizasyon birimi adı', + city: 'Yer adı', + province: 'Eyalet veya il adı', + country: 'Ülke adı (2 harfli kod)', + commonNameHelper: 'Örnek: ', + selfSign: 'Sertifika ver', + days: 'geçerlilik süresi', + domainHelper: 'Her satırda bir alan adı, * ve IP adresini destekler', + pushDir: 'Sertifikayı yerel dizine aktar', + dir: 'Dizin', + pushDirHelper: + 'Sertifika dosyası "fullchain.pem" ve anahtar dosyası "privkey.pem" bu dizinde oluşturulacaktır.', + organizationDetail: 'Organizasyon detayları', + fromWebsite: 'Web sitesinden', + dnsMauanlHelper: + 'Manuel çözümleme modunda, oluşturduktan sonra DNS çözümleme değerini almak için başvuru düğmesine tıklamanız gerekir', + httpHelper: + 'HTTP modunu kullanmak, OpenResty’nin kurulmasını gerektirir ve joker karakter alan adı sertifikaları için başvuruyu desteklemez.', + buypassHelper: 'Buypass, Çin anakarasında erişilebilir değil', + googleHelper: 'EAB HmacKey ve EAB kid nasıl alınır', + googleCloudHelper: 'Google Cloud API, Çin anakarasının çoğu yerinde erişilebilir değil', + skipDNSCheck: 'DNS kontrolünü atla', + skipDNSCheckHelper: 'Sertifika isteği sırasında zaman aşımı sorunuyla karşılaşırsanız burayı işaretleyin.', + cfHelper: 'Küresel API Anahtarını kullanmayın', + deprecated: 'kaldırılacak', + deprecatedHelper: + 'Bakım durduruldu ve gelecek bir sürümde terk edilebilir. Lütfen analiz için Tencent Cloud yöntemini kullanın', + disableCNAME: 'CNAME’yi devre dışı bırak', + disableCNAMEHelper: 'Alan adında bir CNAME kaydı varsa ve istek başarısız olursa burayı işaretleyin.', + nameserver: 'DNS sunucusu', + nameserverHelper: 'Alan adlarını doğrulamak için özel bir DNS sunucusu kullanın.', + edit: 'Sertifikayı düzenle', + execShell: 'Sertifika isteğinden sonra betiği çalıştır.', + shell: 'Betik içeriği', + shellHelper: + 'Betiğin varsayılan yürütme dizini, 1Panel kurulum dizinidir. Sertifika yerel dizine aktarılırsa, yürütme dizini sertifika aktarma dizini olacaktır. Varsayılan yürütme zaman aşımı 30 dakikadır.', + customAcme: 'Özel ACME Servisi', + customAcmeURL: 'ACME Servis URL’si', + baiduCloud: 'Baidu Cloud', + pushNode: 'Diğer Düğümlere Senkronize Et', + pushNodeHelper: 'Başvuru/yenilemeden sonra seçilen düğümlere gönder', + fromMaster: 'Ana Düğümden Gönder', + hostedZoneID: 'Hosted Zone ID', + isIP: 'IP Sertifikası', + useEAB: 'EAB kimlik doğrulamasını kullan', + }, + firewall: { + create: 'Kural oluştur', + edit: 'Kuralı düzenle', + ccDeny: 'CC Koruması', + ipWhiteList: 'IP izin listesi', + ipBlockList: 'IP engelleme listesi', + fileExtBlockList: 'Dosya uzantısı engelleme listesi', + urlWhiteList: 'URL izin listesi', + urlBlockList: 'URL engelleme listesi', + argsCheck: 'GET parametre kontrolü', + postCheck: 'POST parametre doğrulaması', + cookieBlockList: 'Çerez engelleme listesi', + + dockerHelper: + 'Mevcut güvenlik duvarı konteyner port eşlemesini devre dışı bırakamaz. Yüklü uygulamalar, uygulama parametrelerini düzenlemek ve port serbest bırakma kurallarını yapılandırmak için [Yüklü] sayfasına gidebilir.', + iptablesHelper: + "Sistemin {0} güvenlik duvarını kullandığı tespit edildi. iptables'a geçmek için lütfen önce manuel olarak kaldırın!", + quickJump: 'Hızlı erişim', + used: 'Kullanıldı', + unUsed: 'Kullanılmadı', + dockerRestart: 'Güvenlik duvarı işlemleri Docker hizmetinin yeniden başlatılmasını gerektirir', + firewallHelper: '{0} sistem güvenlik duvarı', + firewallNotStart: 'Sistem güvenlik duvarı şu anda etkin değil. Önce etkinleştirin.', + restartFirewallHelper: 'Bu işlem mevcut güvenlik duvarını yeniden başlatacak. Devam etmek istiyor musunuz?', + stopFirewallHelper: 'Bu, sunucunun güvenlik korumasını kaybetmesine neden olacak. Devam etmek istiyor musunuz?', + startFirewallHelper: + 'Güvenlik duvarı etkinleştirildiğinde, sunucu güvenliği daha iyi korunabilir. Devam etmek istiyor musunuz?', + noPing: 'Ping’i devre dışı bırak', + enableBanPing: 'Ping Engelle', + disableBanPing: 'Ping Engellemeyi Kaldır', + noPingTitle: 'Ping’i devre dışı bırak', + noPingHelper: + 'Bu, ping’i devre dışı bırakacak ve sunucu ICMP yanıtını geri göndermeyecek. Devam etmek istiyor musunuz?', + onPingHelper: 'Bu, ping’i etkinleştirecek ve hackerlar sunucunuzu keşfedebilir. Devam etmek istiyor musunuz?', + changeStrategy: '{0} stratejisini değiştir', + changeStrategyIPHelper1: + 'IP adresi stratejisini [reddet] olarak değiştirin. IP adresi ayarlandıktan sonra sunucuya erişim yasaklanır. Devam etmek istiyor musunuz?', + changeStrategyIPHelper2: + 'IP adresi stratejisini [izin ver] olarak değiştirin. IP adresi ayarlandıktan sonra normal erişim geri yüklenir. Devam etmek istiyor musunuz?', + changeStrategyPortHelper1: + 'Bağlantı noktası politikasını [bırak] olarak değiştirin. Bağlantı noktası politikası ayarlandıktan sonra dış erişim reddedilir. Devam etmek istiyor musunuz?', + changeStrategyPortHelper2: + 'Port politikasını [accept] olarak değiştirin. Port politikası ayarlandıktan sonra normal port erişimi geri yüklenecek. Devam etmek istiyor musunuz?', + stop: 'Durdur', + portFormatError: 'Bu alan geçerli bir port olmalıdır.', + portHelper1: 'Birden fazla port, ör. 8080 ve 8081', + portHelper2: 'Aralık portu, ör. 8080-8089', + changeStrategyHelper: + '[{1}] {0} stratejisini [{2}] olarak değiştirin. Ayar yapıldıktan sonra {0}, dışarıdan {2} erişimi sağlayacak. Devam etmek istiyor musunuz?', + strategy: 'Strateji', + accept: 'Kabul Et', + drop: 'Reddet', + anyWhere: 'Herhangi', + address: 'Belirtilen IP’ler', + addressHelper: 'IP adresi veya IP segmentini destekler', + allow: 'İzin Ver', + deny: 'Reddet', + addressFormatError: 'Bu alan geçerli bir IP adresi olmalıdır.', + addressHelper1: 'IP adresi veya IP aralığını destekler. Örneğin, "172.16.10.11" veya "172.16.10.0/24".', + addressHelper2: 'Birden fazla IP adresi için virgülle ayırın. Örneğin, "172.16.10.11, 172.16.10.0/24".', + allIP: 'Tüm IP’ler', + portRule: 'Kural | Kurallar', + createPortRule: '@:commons.button.create @.lower:firewall.portRule', + forwardRule: 'Port-Yönlendirme kuralı | Port-Yönlendirme kuralları', + createForwardRule: '@:commons.button.create @:firewall.forwardRule', + ipRule: 'IP kuralı | IP kuralları', + createIpRule: '@:commons.button.create @:firewall.ipRule', + userAgent: 'Kullanıcı-Aracısı filtresi', + sourcePort: 'Kaynak port', + targetIP: 'Hedef IP', + targetPort: 'Hedef port', + forwardHelper1: 'Yerel porta yönlendirmek istiyorsanız, hedef IP "127.0.0.1" olarak ayarlanmalıdır.', + forwardHelper2: 'Yerel porta yönlendirmek için hedef IP’yi boş bırakın.', + forwardPortHelper: 'Port aralıklarını destekler, örn.: 8080-8089', + forwardInboundInterface: 'İletme Gelen Ağ Arayüzü', + exportHelper: '{0} güvenlik duvarı kuralını dışa aktarmak üzere. Devam etmek istiyor musunuz?', + importSuccess: '{0} kural başarıyla içe aktarıldı', + importPartialSuccess: 'İçe aktarma tamamlandı: {0} başarılı, {1} başarısız', + + ipv4Limit: 'Mevcut işlem yalnızca IPv4 adreslerini destekler', + basicStatus: 'Mevcut zincir {0} bağlı değil, lütfen önce bağlayın!', + baseIptables: 'iptables Servisi', + forwardIptables: 'iptables Port Yönlendirme Servisi', + advanceIptables: 'iptables Gelişmiş Yapılandırma Servisi', + initMsg: '{0} başlatılmak üzere, devam etmek istiyor musunuz?', + initHelper: + '{0} başlatılmadığı tespit edildi. Lütfen üst durum çubuğundaki başlatma düğmesine tıklayarak yapılandırın!', + bindHelper: 'Bağla - Güvenlik duvarı kuralları yalnızca durum bağlı olduğunda etkili olur. Onaylıyor musunuz?', + unbindHelper: + 'Bağlantıyı Kaldır - Bağlantı kaldırıldığında, eklenen tüm güvenlik duvarı kuralları geçersiz olacaktır. Dikkatli ilerleyin. Onaylıyor musunuz?', + defaultStrategy: 'Mevcut zincir {0} için varsayılan politika {1}', + defaultStrategy2: + 'Mevcut zincir {0} için varsayılan politika {1}, mevcut durum bağlı değil. Eklenen güvenlik duvarı kuralları bağlandıktan sonra etkili olacak!', + filterRule: 'Filtre Kuralı', + filterHelper: + 'Filtre kuralları, INPUT/OUTPUT seviyesinde ağ trafiğini kontrol etmenize izin verir. Sistemi kilitlememek için dikkatli yapılandırın.', + chain: 'Zincir', + targetChain: 'Hedef Zincir', + sourceIP: 'Kaynak IP', + destIP: 'Hedef IP', + inboundDirection: 'Gelen Yön', + outboundDirection: 'Giden Yön', + destPort: 'Hedef Port', + action: 'Eylem', + reject: 'Reddet', + sourceIPHelper: 'CIDR formatı, örn. 192.168.1.0/24. Tüm adresler için boş bırakın', + destIPHelper: 'CIDR formatı, örn. 10.0.0.0/8. Tüm adresler için boş bırakın', + portHelper: '0 herhangi bir port anlamına gelir', + allPorts: 'Tüm Portlar', + deleteRuleConfirm: '{0} kural silinecek. Devam etmek istiyor musunuz?', + }, + runtime: { + runtime: 'Çalışma Zamanı', + workDir: 'Çalışma dizini', + create: 'Oluştur', + localHelper: 'Yerel ortam kurulumu ve çevrimdışı ortam kullanımı sorunları için lütfen bakın ', + versionHelper: 'PHP sürümü, ör. v8.0', + buildHelper: + 'Daha fazla eklenti seçilirse, görüntü oluşturma sürecinde CPU kullanımı artar. Tüm eklentileri seçmekten kaçının.', + openrestyWarn: 'PHP’nin OpenResty’ye yükseltilmesi için 1.21.4.1 veya daha yeni bir sürüm gereklidir', + toupgrade: 'Yükseltmek İçin', + edit: 'Çalışma zamanını düzenle', + extendHelper: + 'İhtiyacınız olan eklentiler listede yoksa, eklenti adını manuel olarak girebilirsiniz. Örneğin, "sockets" girin ve ardından ilkini seçin.', + rebuildHelper: 'Eklenti düzenlendikten sonra PHP uygulamasının yeniden oluşturulması gerekir', + rebuild: 'PHP Uygulamasını Yeniden Oluştur', + source: 'PHP eklenti kaynağı', + ustc: 'Çin Bilim ve Teknoloji Üniversitesi', + netease: 'Netease', + aliyun: 'Alibaba Cloud', + tsinghua: 'Tsinghua Üniversitesi', + xtomhk: 'XTOM Ayna İstasyonu (Hong Kong)', + xtom: 'XTOM Ayna İstasyonu (Global)', + phpsourceHelper: 'Ağ ortamınıza göre uygun bir kaynak seçin.', + appPort: 'Uygulama portu', + externalPort: 'Dış port', + packageManager: 'Paket yöneticisi', + codeDir: 'Kod dizini', + appPortHelper: 'Uygulama tarafından kullanılan port.', + externalPortHelper: 'Dış dünyaya açılan port.', + runScript: 'Çalıştırma betiği', + runScriptHelper: 'Kaynak dizindeki package.json dosyasından ayrıştırılan başlatma komutları listesi.', + open: 'Aç', + operatorHelper: 'Seçilen çalışma ortamında {0} işlemi gerçekleştirilecek. Devam etmek istiyor musunuz?', + taobao: 'Taobao', + tencent: 'Tencent', + imageSource: 'Görüntü kaynağı', + moduleManager: 'Modül Yönetimi', + module: 'Modül', + nodeOperatorHelper: + '{0} {1} modülü mü? İşlem, çalışma ortamında anormalliklere neden olabilir, lütfen devam etmeden önce onaylayın', + customScript: 'Özel başlatma komutu', + customScriptHelper: + 'Lütfen tam başlatma komutunu girin, örneğin: npm run start. PM2 başlatma komutları için lütfen pm2-runtime ile değiştirin, aksi takdirde başlatma başarısız olacaktır.', + portError: 'Aynı portu tekrarlamayın.', + systemRestartHelper: + 'Durum açıklaması: Kesinti - sistem yeniden başlatılması nedeniyle durum alımı başarısız oldu', + javaScriptHelper: 'Tam bir başlatma komutu sağlayın. Örneğin, "java -jar halo.jar -Xmx1024M -Xms256M".', + javaDirHelper: 'Dizin, jar dosyalarını içermeli, alt dizinler de kabul edilir', + goHelper: 'Tam bir başlatma komutu sağlayın. Örneğin, "go run main.go" veya "./main".', + goDirHelper: 'Dizin veya alt dizin, Go veya ikili dosyaları içermelidir.', + extension: 'Eklenti', + installExtension: '{0} eklentisini yüklemeyi onaylıyor musunuz?', + loadedExtension: 'Yüklü Eklenti', + popularExtension: 'Popüler Eklenti', + uninstallExtension: '{0} eklentisini kaldırmak istediğinizden emin misiniz?', + phpConfigHelper: + 'Yapılandırmayı değiştirmek, çalışma ortamını yeniden başlatmayı gerektirir, devam etmek istiyor musunuz?', + operateMode: 'çalışma modu', + dynamic: 'dinamik', + static: 'statik', + ondemand: 'isteğe bağlı', + dynamicHelper: + 'işlem sayısını dinamik olarak ayarlar, yüksek esneklik, büyük trafik dalgalanmaları olan veya düşük bellekli web siteleri için uygundur', + staticHelper: + 'sabit işlem sayısı, yüksek eşzamanlılık ve sabit trafikli web siteleri için uygundur, yüksek kaynak tüketimi', + ondemandHelper: + 'işlemler gerektiğinde başlatılır ve yok edilir, kaynak kullanımı optimize edilir, ancak ilk yanıt yavaş olabilir', + max_children: 'oluşturulmasına izin verilen maksimum işlem sayısı', + start_servers: 'başlangıçta oluşturulan işlem sayısı', + min_spare_servers: 'boşta kalan minimum işlem sayısı', + max_spare_servers: 'boşta kalan maksimum işlem sayısı', + envKey: 'Ad', + envValue: 'Değer', + environment: 'Çevre Değişkeni', + pythonHelper: + 'Tam bir başlatma komutu sağlayın. Örneğin, "pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000".', + dotnetHelper: 'Tam bir başlatma komutu sağlayın. Örneğin, "dotnet MyWebApp.dll".', + dirHelper: 'Not: Lütfen kapsayıcı içindeki dizin yolunu doldurun', + concurrency: 'Eşzamanlılık Şeması', + loadStatus: 'Yük Durumu', + extraHosts: 'Ana bilgisayar eşleme', + }, + process: { + pid: 'İşlem Kimliği', + ppid: 'Üst işlem Kimliği', + numThreads: 'Konular', + memory: 'Bellek', + diskRead: 'Disk okuma', + diskWrite: 'Disk yazma', + netSent: 'yukarı bağlantı', + netRecv: 'aşağı bağlantı', + numConnections: 'Bağlantılar', + startTime: 'Başlangıç zamanı', + running: 'Çalışıyor', + sleep: 'uyku', + stop: 'durdur', + idle: 'boşta', + zombie: 'zombi işlem', + wait: 'bekliyor', + lock: 'kilit', + blocked: 'engellendi', + cmdLine: 'Başlatma komutu', + basic: 'Temel', + mem: 'Bellek', + openFiles: 'Açık dosyalar', + env: 'Çevreler', + noenv: 'Hiçbiri', + net: 'Ağ bağlantıları', + laddr: 'Yerel adres/port', + raddr: 'Uzak adres/port', + stopProcess: 'Sonlandır', + viewDetails: 'Detayları görüntüle', + stopProcessWarn: 'Bu işlemi (PID:{0}) sonlandırmak istediğinizden emin misiniz?', + kill: 'İşlemi Sonlandır', + killNow: 'Hemen Sonlandır', + killHelper: + '{0} işlemini sonlandırmak bazı programların düzgün çalışmamasına neden olabilir. Devam etmek istiyor musunuz?', + processName: 'İşlem adı', + }, + tool: { + supervisor: { + loadStatusErr: 'İşlem durumu alınamadı, lütfen supervisor hizmetinin durumunu kontrol edin.', + notSupport: 'Supervisor hizmeti algılanmadı, lütfen betik kütüphanesi sayfasından manuel olarak kurun', + list: 'Arka plan işlemi', + config: 'Supervisor yapılandırması', + primaryConfig: 'Ana yapılandırma dosyası konumu', + notSupportCtl: 'supervisorctl algılanmadı, lütfen betik kütüphanesi sayfasından manuel olarak kurun', + user: 'Kullanıcı', + command: 'Komut', + dir: 'Dizin', + numprocs: 'İşlem sayısı', + initWarn: + 'Bu, ana yapılandırma dosyasındaki "[include]" bölümündeki "files" değerini değiştirecektir. Diğer yapılandırma dosyasının dizini şu şekilde olacaktır: "{1Panel kurulum dizini}/1panel/tools/supervisord/supervisor.d/".', + operatorHelper: '{0} üzerinde {1} işlemi gerçekleştirilecek, devam etmek istiyor musunuz?', + uptime: 'Çalışma süresi', + notStartWarn: 'Supervisor başlatılmadı. Önce başlatın.', + serviceName: 'Hizmet adı', + initHelper: + 'Supervisor hizmeti algılandı ancak başlatılmadı. Lütfen üst durum çubuğundaki başlatma düğmesine tıklayarak yapılandırın.', + serviceNameHelper: + 'systemctl tarafından yönetilen Supervisor hizmet adı, genellikle supervisor veya supervisord', + restartHelper: + 'Bu, başlatma işleminden sonra hizmeti yeniden başlatacak ve mevcut tüm arka plan işlemlerinin durmasına neden olacaktır.', + RUNNING: 'Çalışıyor', + STOPPED: 'Durduruldu', + STOPPING: 'Durduruluyor', + STARTING: 'Başlatılıyor', + FATAL: 'Başlatma başarısız', + BACKOFF: 'Başlatma istisnası', + ERROR: 'Hata', + statusCode: 'Durum kodu', + manage: 'Yönetim', + autoRestart: 'Otomatik Yeniden Başlatma', + EXITED: 'Çıkıldı', + autoRestartHelper: 'Program çöktükten sonra otomatik olarak yeniden başlatılsın mı?', + autoStart: 'Otomatik Başlat', + autoStartHelper: 'Supervisor başlatıldıktan sonra servis otomatik olarak başlatılsın mı?', + }, + }, + disk: { + management: 'Disk Yönetimi', + partition: 'Bölüm', + unmount: 'Bağını Kaldır', + unmountHelper: "Bölüm {0}'ın bağını kaldırmak istiyor musunuz?", + mount: 'Bağla', + partitionAlert: + 'Disk bölümleme, diske biçimlendirme gerektirir ve mevcut veriler silinir. Lütfen verilerinizi önceden kaydedin veya anlık görüntü alın.', + mountPoint: 'Bağlama Noktası', + systemDisk: 'Sistem Diski', + unpartitionedDisk: 'Bölümlendirilmemiş Disk', + handlePartition: 'Şimdi Bölümle', + filesystem: 'Dosya Sistemi', + unmounted: 'Bağlı Değil', + cannotOperate: 'Operasyon Yapılamıyor', + systemDiskHelper: 'İpucu: Mevcut disk sistem diskidir, işlem yapılamaz.', + autoMount: 'Otomatik Bağlama', + model: 'Cihaz Modeli', + diskType: 'Disk Türü', + serial: 'Seri Numarası', + noFail: 'Bağlama hatası sistem başlangıcını etkilemez', + }, + xpack: { + expiresTrialAlert: + 'Nazik hatırlatma: Pro deneme sürümünüz {0} gün içinde sona erecek ve tüm Pro özellikleri kullanılamaz hale gelecektir. Lütfen zamanında yenileyin veya tam sürüme yükseltin.', + expiresAlert: + 'Nazik hatırlatma: Pro lisansınız {0} gün içinde sona erecek ve tüm Pro özellikleri kullanılamaz hale gelecektir. Lütfen devam eden kullanım için zamanında yenileyin.', + menu: 'Pro', + upage: 'AI Web Sitesi Oluşturucu', + proAlert: 'Bu özelliği kullanmak için Proya yükseltin', + app: { + app: 'Uygulama', + title: 'Panel Takma Adı', + titleHelper: 'Uygulamada gösterim için kullanılan panel takma adı (varsayılan panel takma adı)', + qrCode: 'QR Kodu', + apiStatusHelper: 'Panel Uygulaması, API arayüz özelliğinin etkinleştirilmesini gerektirir', + apiInterfaceHelper: + 'Panel API arayüz erişimini destekler (bu özellik, panel uygulaması için etkinleştirilmelidir)', + apiInterfaceHelper1: + 'Panel uygulama erişimi, ziyaretçinin beyaz listeye eklenmesini gerektirir; sabit olmayan IP’ler için 0.0.0.0/0(tüm IPv4), ::/0 (tüm IPv6) eklenmesi önerilir', + qrCodeExpired: 'Yenileme zamanı', + apiLeakageHelper: 'QR kodunu paylaşmayın. Yalnızca güvenilir ortamlarda kullanıldığından emin olun.', + }, + waf: { + name: 'WAF', + blackWhite: 'Kara ve Beyaz Liste', + globalSetting: 'Genel Ayarlar', + websiteSetting: 'Web Sitesi Ayarları', + blockRecords: 'Engellenen Kayıtlar', + world: 'Dünya', + china: 'Çin', + intercept: 'Engelleme', + request: 'İstekler', + count4xx: '4xx Miktarı', + count5xx: '5xx Miktarı', + todayStatus: 'Bugünün Durumu', + reqMap: 'Saldırı Haritası (Son 30 gün)', + resource: 'Kaynak', + count: 'Miktar', + hight: 'Yüksek', + low: 'Düşük', + reqCount: 'İstekler', + interceptCount: 'Engelleme Sayısı', + requestTrends: 'İstek Trendleri (Son 7 Gün)', + interceptTrends: 'Engelleme Trendleri (Son 7 Gün)', + whiteList: 'Beyaz Liste', + blackList: 'Kara Liste', + ipBlackListHelper: 'Kara listedeki IP adresleri web sitesine erişimden engellenir', + ipWhiteListHelper: 'Beyaz listedeki IP adresleri tüm kısıtlamaları bypass eder', + uaBlackListHelper: 'Kara listedeki Kullanıcı-Aracısı değerlerine sahip istekler engellenir', + uaWhiteListHelper: + 'Beyaz listedeki Kullanıcı-Aracısı değerlerine sahip istekler tüm kısıtlamaları bypass eder', + urlBlackListHelper: 'Kara listedeki URL’lere yapılan istekler engellenir', + urlWhiteListHelper: 'Beyaz listedeki URL’lere yapılan istekler tüm kısıtlamaları bypass eder', + ccHelper: 'Bir web sitesi, aynı IP’den {0} saniye içinde {1} istekten fazla alırsa, IP {2} için engellenir', + blockTime: 'Engelleme Süresi', + attackHelper: 'Toplam engellemeler {0} saniye içinde {1}’i aşarsa, IP {2} için engellenir', + notFoundHelper: 'Toplam istekler {0} saniye içinde {1} kez 404 hatası döndürürse, IP {2} için engellenir', + frequencyLimit: 'Sıklık Sınırı', + regionLimit: 'Bölge Sınırı', + defaultRule: 'Varsayılan Kurallar', + accessFrequencyLimit: 'Erişim Sıklığı Sınırı', + attackLimit: 'Saldırı Sıklığı Sınırı', + notFoundLimit: '404 Sıklık Sınırı', + urlLimit: 'URL Sıklık Sınırı', + urlLimitHelper: 'Tek bir URL için erişim sıklığını ayarlayın', + sqliDefense: 'SQL Enjeksiyon Koruması', + sqliHelper: 'İsteklerde SQL enjeksiyonunu algılar ve engeller', + xssHelper: 'İsteklerde XSS’i algılar ve engeller', + xssDefense: 'XSS Koruması', + uaDefense: 'Kötü Amaçlı Kullanıcı-Aracısı Kuralları', + uaHelper: 'Yaygın kötü amaçlı botları tanımlamak için kuralları içerir', + argsDefense: 'Kötü Amaçlı Parametre Kuralları', + argsHelper: 'Kötü amaçlı parametreler içeren istekleri engeller', + cookieDefense: 'Kötü Amaçlı Çerez Kuralları', + cookieHelper: 'Kötü amaçlı çerezlerin isteklerde taşınmasını yasaklar', + headerDefense: 'Kötü Amaçlı Başlık Kuralları', + headerHelper: 'Kötü amaçlı başlıklar içeren istekleri yasaklar', + httpRule: 'HTTP İstek Yöntemi Kuralları', + httpHelper: + 'İzin verilen yöntem türlerini ayarlayın. Belirli erişim türlerini kısıtlamak istiyorsanız, bu tür düğmeyi kapatın. Örneğin: yalnızca GET türüne izin veriliyorsa, GET dışındaki diğer tür düğmeleri kapatmanız gerekir', + geoRule: 'Bölgesel Erişim Kısıtlamaları', + geoHelper: + 'Web sitenize belirli bölgelerden erişimi kısıtlayın, örneğin: Çin anakarasına erişime izin veriliyorsa, Çin anakarası dışındaki istekler engellenir', + ipLocation: 'IP Konumu', + action: 'Eylem', + ruleType: 'Saldırı Türü', + ipHelper: 'IP adresini girin', + attackLog: 'Saldırı Günlüğü', + rule: 'Kural', + ipArr: 'IPV4 Aralığı', + ipStart: 'Başlangıç IP', + ipEnd: 'Bitiş IP', + ipv4: 'IPv4', + ipv6: 'IPv6', + urlDefense: 'URL Kuralları', + urlHelper: 'Yasak URL', + dirFilter: 'Dizin Filtresi', + sqlInject: 'SQL Enjeksiyonu', + xss: 'XSS', + phpExec: 'PHP Betiği Yürütme', + oneWordTrojan: 'Tek Kelime Truva Atı', + appFilter: 'Tehlikeli Dizin Filtreleme', + webshell: 'Webshell', + args: 'Kötü Amaçlı Parametreler', + protocolFilter: 'Protokol Filtresi', + javaFilter: 'Java Tehlikeli Dosya Filtreleme', + scannerFilter: 'Tarayıcı Filtresi', + escapeFilter: 'Kaçış Filtresi', + customRule: 'Özel Kurallar', + httpMethod: 'HTTP Yöntemi Filtresi', + fileExt: 'Dosya Yükleme Sınırı', + fileExtHelper: 'Yükleme için yasak dosya uzantıları', + deny: 'Yasakla', + allow: 'İzin Ver', + field: 'Nesne', + pattern: 'Koşul', + ruleContent: 'İçerik', + contain: 'içerir', + equal: 'eşittir', + regex: 'düzenli ifade', + notEqual: 'Eşit değil', + customRuleHelper: 'Belirtilen koşullara göre eylemler gerçekleştirin', + actionAllow: 'İzin Ver', + blockIP: 'IP’yi Engelle', + code: 'Dönüş Durum Kodu', + noRes: 'Bağlantıyı Kes (444)', + badReq: 'Geçersiz Parametreler (400)', + forbidden: 'Erişim Yasak (403)', + serverErr: 'Sunucu Hatası (500)', + resHtml: 'Yanıt Sayfası', + allowHelper: 'Erişime izin vermek, sonraki WAF kurallarını atlar, lütfen dikkatli kullanın', + captcha: 'insan-makine doğrulaması', + fiveSeconds: '5 Saniye doğrulama', + location: 'Bölge', + redisConfig: 'Redis Yapılandırması', + redisHelper: 'Geçici olarak engellenen IP’leri sürdürmek için Redis’i etkinleştirin', + wafHelper: 'WAF kapatıldığında tüm web siteleri korumayı kaybeder', + attackIP: 'Saldıran IP', + attackParam: 'Saldırı Detayları', + execRule: 'Vurulan Kural', + acl: 'ACL', + sql: 'SQL Enjeksiyonu', + cc: 'Erişim Sıklığı Sınırı', + isBlocking: 'Engellendi', + isFree: 'Engel Kaldırıldı', + unLock: 'Kilidi Aç', + unLockHelper: 'IP: {0} kilidini açmak istiyor musunuz?', + saveDefault: 'Varsayılanı Kaydet', + saveToWebsite: 'Web Sitesine Uygula', + saveToWebsiteHelper: 'Mevcut ayarları tüm web sitelerine uygulamak mı?', + websiteHelper: + 'Web sitesi oluşturmak için varsayılan ayarlar buradadır. Değişikliklerin etkili olması için web sitesine uygulanması gerekir', + websiteHelper2: + 'Web sitesi oluşturmak için varsayılan ayarlar buradadır. Lütfen web sitesinde özel yapılandırmayı değiştirin', + ipGroup: 'IP Grubu', + ipGroupHelper: + 'Her satırda bir IP veya IP segmenti, IPv4 ve IPv6 destekler, örneğin: 192.168.1.1 veya 192.168.1.0/24', + ipBlack: 'IP kara listesi', + openRestyAlert: 'OpenResty sürümünün {0} üzerinde olması gerekir', + initAlert: + 'İlk kullanım için başlatma gereklidir, web sitesi yapılandırma dosyası değiştirilecek ve mevcut WAF yapılandırması kaybolacaktır. Lütfen önceden OpenResty’yi yedeklediğinizden emin olun', + initHelper: 'Başlatma işlemi mevcut WAF yapılandırmasını temizler. Başlatmak istediğinizden emin misiniz?', + mainSwitch: 'Ana Anahtar', + websiteAlert: 'Lütfen önce bir web sitesi oluşturun', + defaultUrlBlack: 'URL Kuralları', + htmlRes: 'Engelleme Sayfası', + urlSearchHelper: 'Bulanık arama için URL girin', + toCreate: 'Oluştur', + closeWaf: 'WAF’ı Kapat', + closeWafHelper: + 'WAF’ı kapatmak web sitesinin korumasını kaybetmesine neden olur, devam etmek istiyor musunuz?', + addblack: 'Kara', + addwhite: 'Beyaz Ekle', + addblackHelper: 'IP:{0}’ı varsayılan kara listeye eklemek mi?', + addwhiteHelper: 'IP:{0}’ı varsayılan beyaz listeye eklemek mi?', + defaultUaBlack: 'Kullanıcı-Aracısı kuralı', + defaultIpBlack: 'Kötü Amaçlı IP Grubu', + cookie: 'Çerez Kuralları', + urlBlack: 'URL Kara Listesi', + uaBlack: 'Kullanıcı-Aracısı kara listesi', + attackCount: 'Saldırı sıklığı sınırı', + fileExtCheck: 'Dosya yükleme sınırı', + geoRestrict: 'Bölgesel erişim kısıtlaması', + attacklog: 'Engelleme Kaydı', + unknownWebsite: 'Yetkisiz alan adı erişimi', + geoRuleEmpty: 'Bölge boş olamaz', + unknown: 'Web Sitesi Mevcut Değil', + geo: 'Bölge Kısıtlaması', + revertHtml: '{0}’ı varsayılan sayfa olarak geri yüklemek istiyor musunuz?', + five_seconds: '5 Saniye doğrulama', + header: 'Başlık kuralları', + methodWhite: 'HTTP kuralları', + expiryDate: 'Son Kullanım Tarihi', + expiryDateHelper: 'Doğrulama geçildikten sonra, geçerlilik süresi içinde tekrar doğrulanmaz', + defaultIpBlackHelper: 'İnternetten toplanan bazı kötü amaçlı IP’ler erişimi önlemek için', + notFoundCount: '404 Sıklık Sınırı', + matchValue: 'Eşleşme değeri', + headerName: 'İngilizce, sayılar, - ile başlayan özel olmayan karakterleri destekler, uzunluk 3-30', + cdnHelper: 'CDN kullanan web siteleri, doğru kaynak IP’yi almak için burayı açabilir', + clearLogWarn: 'Günlük temizleme geri alınamaz, devam etmek istiyor musunuz?', + commonRuleHelper: 'Kural bulanık eşleşmedir', + blockIPHelper: + 'Engellenen IP’ler geçici olarak OpenResty’de saklanır ve OpenResty yeniden başlatıldığında engel kaldırılır. Engelleme fonksiyonu ile kalıcı olarak engellenebilir', + addWhiteUrlHelper: 'URL {0}’ı beyaz listeye eklemek mi?', + dashHelper: 'Topluluk sürümü, genel ayarlar ve web sitesi ayarlarındaki işlevleri de kullanabilir', + wafStatusHelper: 'WAF etkin değil, lütfen genel ayarlarda etkinleştirin', + ccMode: 'Mod', + global: 'Genel Mod', + uriMode: 'URL Modu', + globalHelper: + 'Genel Mod: Birim zamanda herhangi bir URL’ye yapılan toplam istek sayısı eşiği aştığında tetiklenir', + uriModeHelper: 'URL Modu: Birim zamanda tek bir URL’ye yapılan istek sayısı eşiği aştığında tetiklenir', + ip: 'IP Kara Listesi', + globalSettingHelper: + '[Web Sitesi] etiketli ayarlar [Web Sitesi Ayarları]’nda etkinleştirilmelidir, genel ayarlar yalnızca yeni oluşturulan web siteleri için varsayılan ayarlardır', + globalSettingHelper2: + 'Ayarların hem [Genel Ayarlar] hem de [Web Sitesi Ayarları]’nda aynı anda etkinleştirilmesi gerekir', + urlCCHelper: 'Bu URL’ye {0} saniye içinde {1} istekten fazla olursa, bu IP {2} için engellenir', + urlCCHelper2: 'URL parametre içeremez', + notContain: 'İçermez', + urlcc: 'URL sıklık sınırı', + method: 'İstek türü', + addIpsToBlock: 'Toplu IP engelleme', + addUrlsToWhite: 'Toplu URL’yi beyaz listeye ekleme', + noBlackIp: 'IP zaten engellenmiş, tekrar engellemeye gerek yok', + noWhiteUrl: 'URL zaten beyaz listede, tekrar eklemeye gerek yok', + spiderIpHelper: + "Baidu, Bing, Google, 360, Shenma, Sogou, ByteDance, DuckDuckGo, Yandex'i içerir. Bunu kapatmak tüm örümcek erişimlerini engeller.", + spiderIp: 'Örümcek IP Havuzu', + geoIp: 'IP Adres Kütüphanesi', + geoIpHelper: 'IP’nin coğrafi konumunu doğrulamak için kullanılır', + stat: 'Saldırı Raporu', + statTitle: 'Rapor', + attackIp: 'IP', + attackCountNum: 'Sayılar', + percent: 'Yüzde', + addblackUrlHelper: 'URL: {0}’ı varsayılan kara listeye eklemek mi?', + rce: 'Uzaktan Kod Yürütme', + software: 'Yazılım', + cveHelper: 'Yaygın yazılım ve çerçevelerdeki güvenlik açıklarını içerir', + vulnCheck: 'Tamamlayıcı Kurallar', + ssrf: 'SSRF Güvenlik Açığı', + afr: 'Keyfi Dosya Okuma', + ua: 'Yetkisiz Erişim', + id: 'Bilgi Sızıntısı', + aa: 'Kimlik Doğrulama Atlatma', + dr: 'Dizin Geçişi', + xxe: 'XXE Güvenlik Açığı', + suid: 'Serileştirme Güvenlik Açığı', + dos: 'Hizmet Engelleme Güvenlik Açığı', + afd: 'Keyfi Dosya İndirme', + sqlInjection: 'SQL Enjeksiyonu', + afw: 'Keyfi Dosya Yazma', + il: 'Bilgi Sızıntısı', + clearAllLog: 'Tüm günlükleri temizle', + exportLog: 'Günlükleri dışa aktar', + appRule: 'Uygulama Kuralları', + appRuleHelper: + 'Yaygın uygulama kuralları, etkinleştirme yanlış pozitifleri azaltabilir, bir web sitesi yalnızca bir kural kullanabilir', + logExternal: 'Kayıt Türlerini Hariç Tut', + ipWhite: 'IP Beyaz Listesi', + urlWhite: 'URL Beyaz Listesi', + uaWhite: 'Kullanıcı-Aracısı Beyaz Listesi', + logExternalHelper: + 'Hariç tutulan kayıt türleri günlüklerde kaydedilmez, kara liste/beyaz liste, bölgesel erişim kısıtlamaları ve özel kurallar çok sayıda günlük oluşturur, hariç tutulması önerilir', + ssti: 'SSTI Saldırısı', + crlf: 'CRLF Enjeksiyonu', + strict: 'Katı Mod', + strictHelper: 'İstekleri doğrulamak için daha katı kurallar kullanın', + saveLog: 'Günlüğü Kaydet', + remoteURLHelper: 'Uzak URL, her satırda bir IP içermeli ve başka karakter olmamalıdır', + notFound: 'Bulunamadı (404)', + serviceUnavailable: 'Hizmet Kullanılamıyor (503)', + gatewayTimeout: 'Ağ Geçidi Zaman Aşımı (504)', + belongToIpGroup: 'IP Grubuna Ait', + notBelongToIpGroup: 'IP Grubuna Ait Değil', + unknownWebsiteKey: 'Bilinmeyen Alan', + special: 'Özel', + fileToLarge: "Dosya 1MB'yi aştığı için yüklenemez", + uploadOverLimit: 'Yüklenen dosya sayısı sınırı aşıyor, maksimum 1 dosya', + importRuleHelper: 'Her satıra bir kural', + }, + monitor: { + name: 'Web Sitesi İzleme', + pv: 'Sayfa Görüntülemeleri', + uv: 'Benzersiz Ziyaretçiler', + flow: 'Trafik Akışı', + ip: 'IP', + spider: 'Örümcek', + visitors: 'Ziyaretçi Trendleri', + today: 'Bugün', + last7days: 'Son 7 Gün', + last30days: 'Son 30 Gün', + uvMap: 'Ziyaretçi Haritası (30’uncu)', + qps: 'Gerçek Zamanlı İstekler (dakikada)', + flowSec: 'Gerçek Zamanlı Trafik (dakikada)', + excludeCode: 'Durum Kodlarını Hariç Tut', + excludeUrl: 'URL’leri Hariç Tut', + excludeExt: 'Uzantıları Hariç Tut', + cdnHelper: 'CDN tarafından sağlanan Başlıktan gerçek IP’yi alın', + reqRank: 'Ziyaret Sıralaması', + refererDomain: 'Yönlendiren Alan Adı', + os: 'Sistem', + browser: 'Tarayıcı/İstemci', + device: 'Cihaz', + showMore: 'Daha Fazla', + unknown: 'Diğer', + pc: 'Bilgisayar', + mobile: 'Mobil Cihaz', + wechat: 'WeChat', + machine: 'makine', + tencent: 'Tencent Tarayıcı', + ucweb: 'UC Tarayıcı', + '2345explorer': '2345 tarayıcı', + huaweibrowser: 'Huawei Tarayıcı', + log: 'İstek Günlükleri', + statusCode: 'Durum Kodu', + requestTime: 'Yanıt Süresi', + flowRes: 'Yanıt Trafiği', + method: 'İstek Yöntemi', + statusCodeHelper: 'Yukarıdaki durum kodunu girin', + statusCodeError: 'Geçersiz durum kodu türü', + methodHelper: 'Yukarıdaki istek yöntemini girin', + all: 'Tümü', + baidu: 'Baidu', + google: 'Google', + bing: 'Bing', + bytes: 'Bugün manşetler', + sogou: 'Sogou', + failed: 'Hata', + ipCount: 'IP Sayısı', + spiderCount: 'Örümcek İstekleri', + averageReqTime: 'Ortalama Yanıt Süresi', + totalFlow: 'Toplam Trafik', + logSize: 'Günlük Dosyası Boyutu', + realIPType: 'Gerçek IP alma yöntemi', + fromHeader: 'HTTP Başlığından Al', + fromHeaders: 'Başlık listesinden Al', + header: 'HTTP Başlığı', + cdnConfig: 'CDN Yapılandırması', + xff1: 'X-Forwarded-For’dan birinci seviye Proxy', + xff2: 'X-Forwarded-For’dan ikinci seviye Proxy', + xff3: 'X-Forwarded-For’dan üçüncü seviye Proxy', + xffHelper: + 'Örneğin: X-Forwarded-For: ,,, Üst seviye proxy, son IP’yi alır', + headersHelper: 'Yaygın CDN HTTP başlıklarından gerçek IP’yi alın, ilk mevcut değeri seçer', + monitorCDNHelper: + 'Web sitesi izleme için CDN yapılandırmasını değiştirmek, WAF CDN ayarlarını da güncelleyecektir', + wafCDNHelper: 'WAF CDN yapılandırmasını değiştirmek, web sitesi izleme CDN ayarlarını da güncelleyecektir', + statusErr: 'Geçersiz durum kodu formatı', + shenma: 'Shenma Arama', + duckduckgo: 'DuckDuckGo', + '360': '360 Arama', + excludeUri: 'URI’leri Hariç Tut', + top100Helper: 'İlk 100 veriyi göster', + logSaveDay: 'Günlük Saklama Süresi (gün)', + cros: 'Chrome OS', + theworld: 'TheWorld Tarayıcı', + edge: 'Microsoft Edge', + maxthon: 'Maxthon Tarayıcı', + monitorStatusHelper: 'İzleme etkin değil, lütfen ayarlarda etkinleştirin', + excludeIp: 'IP Adreslerini Hariç Tut', + excludeUa: 'Kullanıcı-Aracısını Hariç Tut', + remotePort: 'Uzak Port', + unknown_browser: 'Bilinmeyen', + unknown_os: 'Bilinmeyen', + unknown_device: 'Bilinmeyen', + logSaveSize: 'Maksimum Günlük Saklama Boyutu', + logSaveSizeHelper: 'Bu, tek bir web sitesi için günlük saklama boyutudur', + '360se': '360 Güvenlik Tarayıcı', + websites: 'Web Sitesi Listesi', + trend: 'Trend İstatistikleri', + reqCount: 'İstek Sayısı', + uriHelper: 'Uri’yi hariç tutmak için /test/* veya /*/index.php kullanabilirsiniz', + }, + tamper: { + tamper: 'Web Sitesi Değiştirme Koruması', + ignoreTemplate: 'Hariç Tutma Şablonu', + protectTemplate: 'Koruma Şablonu', + ignoreTemplateHelper: + 'Hariç tutma içeriğini girin, Enter veya boşlukla ayrılmış. (Belirli dizin ./log veya dizin adı tmp, dosyaları hariç tutmak için belirli dosya ./data/test.html girmeniz gerekir)', + protectTemplateHelper: + 'Koruma içeriğini girin, Enter veya boşlukla ayrılmış. (Belirli dosya ./index.html, dosya uzantısı .html, dosya türü js, dizinleri korumak için belirli dizin ./log girmeniz gerekir)', + templateContent: 'Şablon İçeriği', + template: 'Şablon', + saveTemplate: 'Şablon Olarak Kaydet', + tamperHelper1: + 'Tek tıklamayla dağıtım web siteleri için uygulama dizini değişiklik korumasını etkinleştirmeniz önerilir; web sitesi normal olarak kullanılamıyorsa veya yedekleme/geri yükleme başarısız olursa, lütfen önce değişiklik korumasını devre dışı bırakın;', + tamperHelper2: + 'Hariç tutulmayan dizinlerdeki korumalı dosyalar için okuma/yazma, silme, izin ve sahip değiştirme işlemlerini kısıtlayacaktır', + tamperPath: 'Koruma Dizini', + tamperPathEdit: 'Yolu Değiştir', + log: 'Engelleme Günlüğü', + totalProtect: 'Toplam Koruma', + todayProtect: 'Bugünkü Koruma', + templateRule: 'Uzunluk 1-512, isim {0} ve diğer sembolleri içeremez', + ignore: 'Hariç Tut', + ignoreHelper: + 'Hariç tutma içeriğini seçin veya girin, Enter veya boşlukla ayrılmış. (Belirli dizin ./log veya dizin adı tmp, dosyaları hariç tutmak için belirli dosya ./data/test.html girmeniz veya seçmeniz gerekir)', + protect: 'Koru', + protectHelper: + 'Koruma içeriğini seçin veya girin, Enter veya boşlukla ayrılmış. (Belirli dosya ./index.html, dosya uzantısı .html, dosya türü js, dizinleri korumak için belirli dizin ./log girmeniz veya seçmeniz gerekir)', + tamperHelper00: 'Hariç tutma ve koruma yalnızca göreli yolları destekler;', + tamperHelper01: + 'Değişiklik korumasını etkinleştirdikten sonra sistem, hariç tutulmayan dizinlerdeki korumalı dosyaların oluşturulmasını, düzenlenmesini ve silinmesini kısıtlayacaktır;', + tamperHelper02: 'Öncelik: Belirli yol koruması > Belirli yol hariç tutma > Koruma > Hariç tutma', + tamperHelper03: + 'İzleme işlemleri yalnızca hariç tutulmayan dizinleri hedefler, bu dizinlerdeki korunmayan dosyaların oluşturulmasını izler.', + disableHelper: + 'Aşağıdaki web siteleri için değişiklik korumasını devre dışı bırakmak üzeresiniz, devam etmek istiyor musunuz?', + appendOnly: 'Sadece Ekleme', + appendOnlyHelper: + 'Bu dizindeki dosya silme işlemlerini kısıtlar, yalnızca hariç tutulan dizinlerin veya korunmayan dosyaların eklenmesine izin verir', + immutable: 'Değişmez', + immutableHelper: 'Bu dosya için düzenleme, silme, izin ve sahip değiştirme işlemlerini kısıtlar', + onWatch: 'İzleme', + onWatchHelper: + 'Bu dizinde korumalı dosyaların veya hariç tutulmayan dizinlerin oluşturulmasını izler ve kesintiye uğratır', + forceStop: 'Zorla Kapat', + forceStopHelper: + 'Bu web sitesi dizini için değişiklik önleme işlevini zorla devre dışı bırakacak. Devam etmek istiyor musunuz?', + }, + setting: { + setting: 'Panel Ayarları', + title: 'Panel Açıklaması', + titleHelper: + 'Kullanıcı giriş sayfasında gösterilecektir (ör. Linux sunucu operasyon ve bakım yönetim paneli, önerilen 8-15 karakter)', + logo: 'Logo (Metinsiz)', + logoHelper: + 'Menü daraltıldığında yönetim sayfasının sol üst köşesinde gösterilecektir (önerilen görüntü boyutu: 82px*82px)', + logoWithText: 'Logo (Metinli)', + logoWithTextHelper: + 'Menü genişletildiğinde yönetim sayfasının sol üst köşesinde gösterilecektir (önerilen görüntü boyutu: 185px*55px)', + favicon: 'Web Sitesi Simgesi', + faviconHelper: 'Web sitesi simgesi (önerilen görüntü boyutu: 16px*16px)', + setDefault: 'Varsayılana Geri Yükle', + setHelper: 'Mevcut ayarlar kaydedilecek. Devam etmek istiyor musunuz?', + setDefaultHelper: 'Tüm panel ayarları varsayılana geri yüklenecek. Devam etmek istiyor musunuz?', + logoGroup: 'Logo', + imageGroup: 'Görüntü', + loginImage: 'Giriş Sayfası Görüntüsü', + loginImageHelper: 'Giriş sayfasında gösterilir (önerilen görüntü boyutu: 500*416px)', + loginBgType: 'Giriş Sayfası Arka Plan Türü', + loginBgImage: 'Giriş Sayfası Arka Plan Görüntüsü', + loginBgImageHelper: + 'Giriş sayfasında arka plan görüntüsü olarak gösterilir (önerilen görüntü boyutu: 1920*1080px)', + loginBgColor: 'Giriş Sayfası Arka Plan Rengi', + loginBgColorHelper: 'Giriş sayfasında arka plan rengi olarak gösterilir', + image: 'Görüntü', + bgColor: 'Arka Plan Rengi', + loginGroup: 'Giriş Sayfası', + loginBtnLinkColor: 'Buton/Bağlantı Rengi', + loginBtnLinkColorHelper: 'Giriş sayfasındaki buton/bağlantı rengi olarak gösterilecektir', + }, + helper: { + wafTitle1: 'Engelleme Haritası', + wafContent1: 'Son 30 gün içindeki engellemelerin coğrafi dağılımını gösterir', + wafTitle2: 'Bölgesel Erişim Kısıtlamaları', + wafContent2: 'Web sitesi erişim kaynaklarını coğrafi konumlara göre kısıtlar', + wafTitle3: 'Özel Engelleme Sayfası', + wafContent3: 'Bir istek engellendiğinde gösterilecek özel bir sayfa oluşturun', + wafTitle4: 'Özel Kurallar (ACL)', + wafContent4: 'İstekleri özel kurallara göre engelleyin', + + tamperTitle1: 'Dosya Bütünlüğü İzleme', + tamperContent1: + 'Web sitesi dosyalarının bütünlüğünü izler, çekirdek dosyalar, betik dosyaları ve yapılandırma dosyaları dahil.', + tamperTitle2: 'Gerçek Zamanlı Tarama ve Algılama', + tamperContent2: + 'Web sitesi dosya sistemini gerçek zamanlı tarayarak anormal veya değiştirilmiş dosyaları algılar.', + tamperTitle3: 'Güvenlik İzin Ayarları', + tamperContent3: + 'Uygun izin ayarları ve erişim kontrol politikaları aracılığıyla web sitesi dosyalarına erişimi kısıtlar, potansiyel saldırı yüzeyini azaltır.', + tamperTitle4: 'Günlük Kaydı ve Analiz', + tamperContent4: + 'Yöneticilerin sonraki denetim ve analiz için dosya erişimi ve işlem günlüklerini kaydeder, potansiyel güvenlik tehditlerini belirler.', + + settingTitle1: 'Özel Hoş Geldiniz Mesajı', + settingContent1: '1Panel giriş sayfasında özel bir hoş geldiniz mesajı ayarlayın.', + settingTitle2: 'Özel Logo', + settingContent2: 'Marka adları veya başka metinler içeren logo görüntülerinin yüklenmesine izin verir.', + settingTitle3: 'Özel Web Sitesi Simgesi', + settingContent3: + 'Varsayılan tarayıcı simgesini değiştirmek için özel simgelerin yüklenmesine izin verir, kullanıcı deneyimini iyileştirir.', + + monitorTitle1: 'Ziyaretçi Trendi', + monitorContent1: 'Web sitesi ziyaretçi trendlerini istatistiksel olarak toplar ve gösterir', + monitorTitle2: 'Ziyaretçi Haritası', + monitorContent2: + 'Web sitesine gelen ziyaretçilerin coğrafi dağılımını istatistiksel olarak toplar ve gösterir', + monitorTitle3: 'Erişim İstatistikleri', + monitorContent3: + 'Web sitesi istek bilgileri hakkında istatistikler, örümcekler, erişim cihazları, istek durumu vb. içerir.', + monitorTitle4: 'Gerçek Zamanlı İzleme', + monitorContent4: 'Web sitesi istek bilgilerinin gerçek zamanlı izlenmesi, istek sayısı, trafik vb. içerir.', + + alertTitle1: 'SMS Uyarıları', + alertContent1: + 'Anormal sunucu kaynak kullanımı, web sitesi ve sertifika sona ermesi, yeni sürüm güncellemesi, şifre sona ermesi vb. durumlarda kullanıcılar SMS alarmı ile bilgilendirilir, zamanında işlem yapılmasını sağlar.', + alertTitle2: 'Uyarı Günlüğü', + alertContent2: + 'Kullanıcılara geçmiş uyarı olaylarını takip etme ve analiz etme için uyarı günlüklerini görüntüleme işlevi sağlar.', + alertTitle3: 'Uyarı Ayarları', + alertContent3: + 'Kullanıcılara özel telefon numaraları, günlük gönderim sıklığı ve günlük gönderim zamanı yapılandırmaları sağlar, kullanıcıların daha makul gönderim uyarıları ayarlamasını kolaylaştırır.', + + nodeDashTitle1: 'Uygulama Yönetimi', + nodeDashContent1: + 'Çoklu düğüm uygulamalarının birleşik yönetimi, durum izleme, hızlı başlatma/durdurma, terminal bağlantısı ve yedekleme desteği', + nodeDashTitle2: 'Web Sitesi Yönetimi', + nodeDashContent2: + 'Çoklu düğüm web sitelerinin birleşik yönetimi, gerçek zamanlı durum izleme, toplu başlatma/durdurma ve hızlı yedekleme desteği', + nodeDashTitle3: 'Veritabanı Yönetimi', + nodeDashContent3: + 'Çoklu düğüm veritabanlarının birleşik yönetimi, önemli durumlar tek bakışta, tek tıklamayla yedekleme desteği', + nodeDashTitle4: 'Zamanlanmış Görev Yönetimi', + nodeDashContent4: + 'Çoklu düğüm zamanlanmış görevlerinin birleşik yönetimi, durum izleme, hızlı başlatma/durdurma ve manuel tetikleme çalıştırma desteği', + + nodeTitle1: 'Tek Tıkla Düğüm Ekleme', + nodeContent1: 'Birden fazla sunucu düğümünü hızlıca entegre eder', + nodeTitle2: 'Toplu Yükseltme', + nodeContent2: 'Tüm düğümleri tek bir işlemle senkronize eder ve yükseltir', + nodeTitle3: 'Düğüm Durumu İzleme', + nodeContent3: 'Her düğümün çalışma durumunu gerçek zamanlı olarak izler', + nodeTitle4: 'Hızlı Uzak Bağlantı', + nodeContent4: 'Düğümlere tek tıkla doğrudan uzak terminal bağlantısı', + + fileExchangeTitle1: 'Anahtar Kimlik Doğrulama İletimi', + fileExchangeContent1: 'İletim güvenliğini sağlamak için SSH anahtarları ile kimlik doğrulaması yapar.', + fileExchangeTitle2: 'Verimli Dosya Senkronizasyonu', + fileExchangeContent2: + 'Yalnızca değiştirilen içeriği senkronize ederek iletim hızını ve kararlılığını önemli ölçüde artırır.', + fileExchangeTitle3: 'Çoklu Düğüm İletişimini Destekler', + fileExchangeContent3: + 'Farklı düğümler arasında proje dosyalarını kolayca aktarır, birden fazla sunucuyu esnek bir şekilde yönetir.', + + nodeAppTitle1: 'Uygulama Yükseltme Yönetimi', + nodeAppContent1: + 'Çoklu düğüm uygulama güncellemelerinin birleşik izlenmesi, tek tıklamayla yükseltme desteği', + + appTitle1: 'Esnek Panel Yönetimi', + appContent1: '1Panel sunucunuzu her zaman, her yerde kolayca yönetin.', + appTitle2: 'Kapsamlı Hizmet Bilgisi', + appContent2: + 'Temel uygulamaları, web sitelerini, Docker’ı, veritabanlarını vb. yönetin ve mobil uygulama üzerinden hızlıca uygulamalar ve web siteleri oluşturun.', + appTitle3: 'Gerçek Zamanlı Anormallik İzleme', + appContent3: + 'Mobil uygulama üzerinden gerçek zamanlı sunucu durumu, WAF güvenlik izleme, web sitesi trafik istatistikleri ve işlem sağlığı durumunu görüntüleyin.', + + clusterTitle1: 'Ana-Çalışan Dağıtımı', + clusterContent1: + 'Farklı düğümlerde MySQL/Postgres/Redis ana-çalışan örnekleri oluşturmayı destekler, ana-çalışan ilişkisi ve başlatmayı otomatik olarak tamamlar', + clusterTitle2: 'Ana-Çalışan Yönetimi', + clusterContent2: + 'Birleşik bir sayfa ile birden çok ana-çalışan düğümünü merkezi olarak yönetin, rollerini, çalışma durumlarını vb. görüntüleyin', + clusterTitle3: 'Çoğaltma Durumu', + clusterContent3: + 'Ana-çalışan çoğaltma durumunu ve gecikme bilgilerini görüntüleyerek senkronizasyon sorunlarını gidermeye yardımcı olur', + }, + node: { + master: 'Ana Düğüm', + masterBackup: 'Ana Düğüm Yedekleme', + backupNode: 'Yedek Düğüm', + backupFrequency: 'Yedekleme Sıklığı (saat)', + backupCopies: 'Saklanacak yedek kopya sayısı', + noBackupNode: 'Yedek düğüm şu anda boş. Lütfen kaydetmek için bir yedek düğüm seçin ve tekrar deneyin!', + masterBackupAlert: + 'Ana düğüm yedeklemesi şu anda yapılandırılmamış. Veri güvenliği için, lütfen arıza durumunda yeni bir ana düğüme manuel geçiş yapabilmek amacıyla en kısa sürede bir yedek düğüm ayarlayın.', + node: 'Düğüm', + addr: 'Adres', + nodeUpgrade: 'Güncelleme Ayarları', + nodeUpgradeHelper: + 'Seçilen düğümler, ana düğüm yükseltmesi tamamlandıktan sonra otomatik olarak yükseltmeye başlayacaktır, manuel işlem gerekmez.', + nodeUnhealthy: 'Düğüm durumu anormal', + deletedNode: 'Silinmiş düğüm {0} şu anda yükseltme işlemlerini desteklemiyor!', + nodeUnhealthyHelper: + 'Anormal düğüm durumu algılandı. Lütfen [Düğüm Yönetimi]’nde kontrol edin ve tekrar deneyin!', + nodeUnbind: 'Düğüm lisansa bağlı değil', + nodeUnbindHelper: + 'Bu düğümün lisansa bağlı olmadığı algılandı. Lütfen [Panel Ayarları - Lisans] menüsünde bağlayın ve tekrar deneyin!', + memTotal: 'Toplam Bellek', + nodeManagement: 'Çoklu Makine Yönetimi', + nodeItem: 'Düğüm Yönetimi', + panelItem: 'Panel Yönetimi', + addPanel: 'Panel Ekle', + addPanelHelper: + 'Panel başarıyla eklendikten sonra, [Genel Bakış - Paneller] bölümünden hedef panele hızlıca erişebilirsiniz.', + panel: '1Panel Paneli', + others: 'Diğer Paneller', + addNode: 'Düğüm Ekle', + connInfo: 'Bağlantı Bilgileri', + nodeInfo: 'Düğüm Bilgileri', + withProxy: 'Proxy Erişimini Etkinleştir', + withoutProxy: 'Proxy Erişimini Devre Dışı Bırak', + withProxyHelper: + 'Alt düğümlere erişmek için panel ayarlarında tutulan sistem proxy {0} kullanılacak. Devam etmek istiyor musunuz?', + withoutProxyHelper: + 'Alt düğümlere erişmek için panel ayarlarında tutulan sistem proxy kullanımı durdurulacak. Devam etmek istiyor musunuz?', + syncInfo: 'Senkronizasyon', + syncHelper: 'Ana düğüm verileri değiştiğinde, bu alt düğüme gerçek zamanlı olarak senkronize edilir', + syncBackupAccount: 'Yedekleme hesabı ayarları', + syncWithMaster: + 'Pro’ya yükseltildikten sonra, tüm veriler varsayılan olarak senkronize edilir. Senkronizasyon politikaları düğüm yönetiminde manuel olarak ayarlanabilir.', + syncProxy: 'Sistem proxy ayarları', + syncProxyHelper: 'Sistem proxy ayarlarını senkronize etmek Docker yeniden başlatılmasını gerektirir', + syncProxyHelper1: 'Docker’ın yeniden başlatılması, şu anda çalışan kapsayıcı hizmetlerini etkileyebilir.', + syncProxyHelper2: 'Kapsayıcılar - Yapılandırma sayfasında manuel olarak yeniden başlatabilirsiniz.', + syncProxyHelper3: + 'Sistem proxy ayarlarını senkronize etmek Docker yeniden başlatılmasını gerektirir, bu da şu anda çalışan kapsayıcı hizmetlerini etkileyebilir', + syncProxyHelper4: + 'Sistem proxy ayarlarını senkronize etmek Docker yeniden başlatılmasını gerektirir. Kapsayıcılar - Yapılandırma sayfasında daha sonra manuel olarak yeniden başlatabilirsiniz.', + syncCustomApp: 'Özel Uygulama Deposunu Senkronize Et', + syncAlertSetting: 'Sistem uyarı ayarları', + syncNodeInfo: 'Düğüm temel verileri,', + nodeSyncHelper: 'Düğüm bilgisi senkronizasyonu aşağıdaki bilgileri senkronize eder:', + nodeSyncHelper1: '1. Genel yedekleme hesabı bilgileri', + nodeSyncHelper2: '2. Ana düğüm ile alt düğümler arasındaki bağlantı bilgileri', + + nodeCheck: 'Kullanılabilirlik kontrolü', + checkSSH: 'Düğüm SSH bağlantısını kontrol et', + checkUserPermission: 'Düğüm kullanıcı izinlerini kontrol et', + isNotRoot: 'Bu düğümde şifresiz sudo desteklenmediği ve mevcut kullanıcının root olmadığı algılandı', + checkLicense: 'Düğüm lisans durumunu kontrol et', + checkService: 'Düğümdeki mevcut hizmet bilgilerini kontrol et', + checkPort: 'Düğüm port erişilebilirliğini kontrol et', + panelExist: + 'Bu düğümde 1Panel V1 hizmeti çalıştığı algılandı. Lütfen eklemeden önce geçiş betiğini kullanarak V2’ye yükseltin.', + coreExist: + 'Mevcut düğüm zaten ana düğüm olarak etkinleştirilmiş durumda ve doğrudan alt düğüm olarak eklenemez. Lütfen eklemeden önce alt düğüme dönüştürün, ayrıntılar için belgelere bakın.', + agentExist: + 'Bu düğümde 1panel-agent’ın zaten kurulu olduğu algılandı. Devam edilmesi mevcut verileri koruyacak ve yalnızca 1panel-agent hizmetini değiştirecektir.', + agentNotExist: + 'Bu düğümde 1panel-agent yüklü olmadığı tespit edildi, düğüm bilgileri doğrudan düzenlenemez. Lütfen silip tekrar ekleyin.', + oldDataExist: + 'Bu düğümde geçmiş 1Panel V2 verileri algılandı. Aşağıdaki bilgiler mevcut ayarları üzerine yazmak için kullanılacaktır:', + errLicense: 'Bu düğüme bağlı lisans kullanılamıyor. Lütfen kontrol edin ve tekrar deneyin!', + errNodePort: + 'Düğüm portu [ {0} ] erişilemez olarak algılandı. Lütfen güvenlik duvarı veya güvenlik grubunun bu portu izin verdiğinden emin olun.', + + reinstallHelper: '{0} düğümünü yeniden kurmak istiyor musunuz?', + unhealthyCheck: 'Anormal Kontrol', + fixOperation: 'Düzeltme İşlemi', + checkName: 'Kontrol Öğesi', + checkSSHConn: 'SSH Bağlantı Kullanılabilirliğini Kontrol Et', + fixSSHConn: 'Bağlantı bilgilerini doğrulamak için düğümü manuel olarak düzenleyin', + checkConnInfo: 'Agent Bağlantı Bilgilerini Kontrol Et', + checkStatus: 'Düğüm Hizmeti Kullanılabilirliğini Kontrol Et', + fixStatus: + 'Hizmetin çalıştığından emin olmak için "systemctl status 1panel-agent.service" komutunu çalıştırın.', + checkAPI: 'Düğüm API Kullanılabilirliğini Kontrol Et', + fixAPI: 'Düğüm günlüklerini kontrol edin ve güvenlik duvarı portlarının doğru şekilde açıldığını doğrulayın.', + forceDelete: 'Zorla Sil', + operateHelper: 'Aşağıdaki düğümler {0} işlemini geçirecek, devam etmek istiyor musunuz?', + operatePanelHelper: 'Aşağıdaki panellerde {0} işlemi gerçekleştirilecek. Devam etmek istiyor musunuz?', + forceDeleteHelper: 'Zorla silme, düğüm silme hatalarını yok sayar ve veritabanı meta verilerini siler', + uninstall: 'Düğüm verilerini sil', + uninstallHelper: 'Bu, düğümle ilgili tüm 1Panel verilerini silecektir. Dikkatli ilerleyin!', + baseDir: 'Kurulum Dizini', + baseDirHelper: 'Kurulum dizini boş olduğunda, varsayılan olarak /opt dizinine kurulur', + nodePort: 'Düğüm Portu', + offline: 'Çevrimdışı mod', + freeCount: 'Ücretsiz kota [{0}]', + offlineHelper: 'Düğüm çevrimdışı bir ortamda olduğunda kullanılır', + + appUpgrade: 'Uygulama Yükseltme', + appUpgradeHelper: 'Yükseltilmesi gereken {0} uygulama var', + }, + customApp: { + name: 'Özel Uygulama Deposu', + appStoreType: 'Uygulama Mağazası Paket Kaynağı', + appStoreUrl: 'Depo URL’si', + local: 'Yerel Yol', + remote: 'Uzak Bağlantı', + imagePrefix: 'Görüntü Öneki', + imagePrefixHelper: + 'İşlev: Görüntü önekini özelleştirir ve compose dosyasındaki görüntü alanını değiştirir. Örneğin, görüntü öneki 1panel/custom olarak ayarlandığında, MaxKB için görüntü alanı 1panel/custom/maxkb:v1.10.0 olarak değişir', + closeHelper: 'Özel uygulama deposunu kullanmayı iptal et', + appStoreUrlHelper: 'Yalnızca .tar.gz formatı desteklenir', + postNode: 'Alt düğüme senkronize et', + postNodeHelper: + 'Özel mağaza paketini alt düğümün kurulum dizinindeki tmp/customApp/apps.tar.gz’e senkronize eder', + nodes: 'Düğümler', + selectNode: 'Düğüm Seç', + selectNodeError: 'Lütfen bir düğüm seçin', + licenseHelper: 'Pro sürümü, özel uygulama deposu özelliğini destekler', + databaseHelper: 'Uygulama ilişkili veritabanı, lütfen hedef düğüm veritabanını seçin', + nodeHelper: 'Geçerli düğüm seçilemez', + migrateHelper: + 'Şu anda yalnızca tek parça uygulamaların ve yalnızca MySQL, MariaDB, PostgreSQL veritabanlarıyla ilişkili uygulamaların taşınmasını destekler', + opensslHelper: + 'Şifreli yedekleme kullanılıyorsa, iki düğüm arasındaki OpenSSL sürümleri tutarlı olmalıdır, aksi takdirde geçiş başarısız olabilir.', + installApp: 'Toplu kurulum', + installAppHelper: 'Seçilen düğümlere uygulamaları toplu kur', + }, + alert: { + isAlert: 'Uyarı', + alertCount: 'Uyarı Sayısı', + clamHelper: 'Enfekte dosyalar tarandığında uyarısını tetikle', + cronJobHelper: 'Görev yürütme başarısız olduğunda uyarısını tetikle', + licenseHelper: 'Profesyonel sürüm SMS uyarısını destekler', + alertCountHelper: 'Günlük maksimum uyarı sıklığı', + alert: 'SMS Uyarısı', + logs: 'Uyarı Günlükleri', + list: 'Uyarı Listesi', + addTask: 'Uyarı Oluştur', + editTask: 'Uyarıyı Düzenle', + alertMethod: 'Yöntem', + alertMsg: 'Uyarı Mesajı', + alertRule: 'Uyarı Kuralları', + titleSearchHelper: 'Bulanık arama için uyarı başlığını girin', + taskType: 'Tür', + ssl: 'Sertifika Sona Ermesi', + siteEndTime: 'Web Sitesi Sona Ermesi', + panelPwdEndTime: 'Panel Şifresi Sona Ermesi', + panelUpdate: 'Yeni Panel Sürümü Mevcut', + cpu: 'Sunucu CPU Uyarısı', + memory: 'Sunucu Bellek Uyarısı', + load: 'Sunucu Yük Uyarısı', + disk: 'Sunucu Disk Uyarısı', + website: 'Web Sitesi', + certificate: 'SSL Sertifikası', + remainingDays: 'Kalan Günler', + sendCount: 'Gönderim Sayısı', + sms: 'SMS', + wechat: 'WeChat', + dingTalk: 'DingTalk', + feiShu: 'FeiShu', + mail: 'E-posta', + weCom: 'WeCom', + sendCountRulesHelper: 'Sona ermeden önce gönderilen toplam uyarılar (günde bir kez)', + panelUpdateRulesHelper: + 'Yeni panel sürümü algılandığında bir kez uyarı gönder (işlenmezse ertesi gün tekrar gönderilir)', + oneDaySendCountRulesHelper: 'Günde gönderilen maksimum uyarılar', + siteEndTimeRulesHelper: 'Asla sona ermeyen web siteleri uyarı tetiklemez', + autoRenewRulesHelper: + 'Otomatik yenileme etkin olan ve kalan gün sayısı 31’den az olan sertifikalar uyarı tetiklemez', + panelPwdEndTimeRulesHelper: 'Panel şifresi sona erme uyarıları, sona erme ayarlanmadıysa kullanılamaz', + sslRulesHelper: 'Tüm SSL Sertifikaları', + diskInfo: 'Disk', + monitoringType: 'İzleme Türü', + autoRenew: 'Otomatik Yenile', + useDisk: 'Disk Kullanımı', + usePercentage: 'Kullanım Yüzdesi', + changeStatus: 'Durumu Değiştir', + disableMsg: + 'Uyarı görevini durdurmak, bu görevin uyarı mesajları göndermesini engeller. Devam etmek istiyor musunuz?', + enableMsg: + 'Uyarı görevini etkinleştirmek, bu görevin uyarı mesajları göndermesini sağlar. Devam etmek istiyor musunuz?', + useExceed: 'Kullanım Aşımı', + useExceedRulesHelper: 'Kullanım ayarlanan değeri aştığında uyarı tetiklenir', + cpuUseExceedAvg: 'Ortalama CPU kullanımı belirtilen değeri aşar', + memoryUseExceedAvg: 'Ortalama bellek kullanımı belirtilen değeri aşar', + loadUseExceedAvg: 'Ortalama yük kullanımı belirtilen değeri aşar', + cpuUseExceedAvgHelper: 'Belirtilen süre içinde ortalama CPU kullanımı belirtilen değeri aşar', + memoryUseExceedAvgHelper: 'Belirtilen süre içinde ortalama bellek kullanımı belirtilen değeri aşar', + loadUseExceedAvgHelper: 'Belirtilen süre içinde ortalama yük kullanımı belirtilen değeri aşar', + resourceAlertRulesHelper: 'Not: 30 dakika içinde sürekli uyarılar yalnızca bir gönderir', + specifiedTime: 'Belirtilen Süre', + deleteTitle: 'Uyarıyı Sil', + deleteMsg: 'Uyarı görevini silmek istediğinizden emin misiniz?', + + allSslTitle: 'Tüm Web Sitesi SSL Sertifikası Sona Erme Uyarıları', + sslTitle: 'Web Sitesi {0} için SSL Sertifikası Sona Erme Uyarısı', + allSiteEndTimeTitle: 'Tüm Web Sitesi Sona Erme Uyarıları', + siteEndTimeTitle: 'Web Sitesi {0} Sona Erme Uyarısı', + panelPwdEndTimeTitle: 'Panel Şifresi Sona Erme Uyarısı', + panelUpdateTitle: 'Yeni Panel Sürümü Bildirimi', + cpuTitle: 'Yüksek CPU Kullanımı Uyarısı', + memoryTitle: 'Yüksek Bellek Kullanımı Uyarısı', + loadTitle: 'Yüksek Yük Uyarısı', + diskTitle: 'Bağlama Dizini {0} için Yüksek Disk Kullanımı Uyarısı', + allDiskTitle: 'Yüksek Disk Kullanımı Uyarısı', + + timeRule: 'Kalan süre {0} günden az olduğunda (işlenmezse ertesi gün tekrar gönderilir)', + panelUpdateRule: + 'Yeni bir panel sürümü algılandığında bir kez uyarı gönder (işlenmezse ertesi gün tekrar gönderilir)', + avgRule: + '{0} dakika içinde ortalama {1} kullanımı {2}%’yi aşarsa, uyarı tetiklenir, günde {3} kez gönderilir', + diskRule: + '{0} bağlama dizini için disk kullanımı {1}{2}’yi aşarsa, uyarı tetiklenir, günde {3} kez gönderilir', + allDiskRule: 'Disk kullanımı {0}{1}’yi aşarsa, uyarı tetiklenir, günde {2} kez gönderilir', + + cpuName: ' CPU ', + memoryName: 'Bellek', + loadName: 'Yük', + diskName: 'Disk', + + syncAlertInfo: 'Manuel Gönderim', + syncAlertInfoMsg: 'Uyarı görevini manuel olarak göndermek istiyor musunuz?', + pushError: 'Gönderim Başarısız', + pushSuccess: 'Gönderim Başarılı', + syncError: 'Senkronizasyon Başarısız', + success: 'Uyarı Başarılı', + pushing: 'Gönderiliyor...', + error: 'Uyarı Başarısız', + cleanLog: 'Zamanlanmış görev yürütmesi {0} hatası', + cleanAlertLogs: 'Uyarı Günlüklerini Temizle', + daily: 'Günlük Uyarı Sayısı: {0}', + cumulative: 'Toplam Uyarı Sayısı: {0}', + clams: 'Virüs tarama', + taskName: 'Görev Adı', + cronJobType: 'Görev Türü', + clamPath: 'Tarama Dizini', + cronjob: 'Zamanlanmış Görev', + app: 'Yedekleme Uygulaması', + web: 'Yedekleme Web Sitesi', + database: 'Yedekleme Veritabanı', + directory: 'Yedekleme Dizini', + log: 'Yedekleme Günlükleri', + snapshot: 'Sistem Anlık Görüntüsü', + clamsRulesHelper: 'Uyarısı gerektiren virüs tarama görevleri', + cronJobRulesHelper: 'Bu tür zamanlanmış görevlerin yapılandırılması gerekir', + clamsTitle: 'Virüs tarama görevi 「 {0} 」 enfekte dosya algıladı uyarısı', + cronJobAppTitle: 'Zamanlanmış Görev - Yedekleme Uygulaması 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobWebsiteTitle: 'Zamanlanmış Görev - Yedekleme Web Sitesi 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobDatabaseTitle: 'Zamanlanmış Görev - Yedekleme Veritabanı 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobDirectoryTitle: 'Zamanlanmış Görev - Yedekleme Dizini 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobLogTitle: 'Zamanlanmış Görev - Yedekleme Günlükleri 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobSnapshotTitle: 'Zamanlanmış Görev - Yedekleme Anlık Görüntüsü 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobShellTitle: 'Zamanlanmış Görev - Kabuk betiği 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobCurlTitle: 'Zamanlanmış Görev - URL erişimi 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobCutWebsiteLogTitle: + 'Zamanlanmış Görev - Web sitesi günlüğünü kes 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobCleanTitle: 'Zamanlanmış Görev - Önbellek temizleme 「 {0} 」 Görev Başarısızlık Uyarısı', + cronJobNtpTitle: 'Zamanlanmış Görev - Sunucu zamanını senkronize etme 「 {0} 」 Görev Başarısızlık Uyarısı', + clamsRule: 'Virüs tarama enfekte dosya algıladı uyarısı, günde {0} kez gönderilir', + cronJobAppRule: 'Yedekleme uygulaması görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobWebsiteRule: 'Yedekleme web sitesi görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobDatabaseRule: 'Yedekleme veritabanı görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobDirectoryRule: 'Yedekleme dizini görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobLogRule: 'Yedekleme günlükleri görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobSnapshotRule: 'Yedekleme anlık görüntüsü görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobShellRule: 'Kabuk betiği görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobCurlRule: 'URL erişimi görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobCutWebsiteLogRule: 'Web sitesi günlüğünü kesme görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobCleanRule: 'Önbellek temizleme görevi başarısız uyarısı, günde {0} kez gönderilir', + cronJobNtpRule: 'Sunucu zamanını senkronize etme görevi başarısız uyarısı, günde {0} kez gönderilir', + alertSmsHelper: 'SMS sınırı: toplam {0} mesaj, {1} zaten kullanıldı', + goBuy: 'Daha Fazla Satın Al', + phone: 'Telefon', + phoneHelper: 'Uyarı mesajları için gerçek telefon numarası sağlayın', + dailyAlertNum: 'Günlük Uyarı Sınırı', + dailyAlertNumHelper: 'Günde maksimum uyarı sayısı (100’e kadar)', + timeRange: 'Zaman Aralığı', + sendTimeRange: 'gönderim zaman aralığı', + sendTimeRangeHelper: '{0} zaman aralığında gönderim yapılabilir', + to: 'kadar', + startTime: 'Başlangıç Zamanı', + endTime: 'Bitiş Zamanı', + defaultPhone: 'Varsayılan olarak lisansa bağlı hesap telefon numarasına', + noticeAlert: 'Bildirim Uyarısı', + resourceAlert: 'Kaynak Uyarısı', + agentOfflineAlertHelper: + 'Düğüm için çevrimdışı uyarı etkinleştirildiğinde, ana düğüm her 30 dakikada bir tarama yaparak uyarı görevlerini yürütür.', + offline: 'Çevrimdışı Uyarı', + offlineHelper: + 'Çevrimdışı uyarı olarak ayarlandığında, ana düğüm her 30 dakikada bir tarama yaparak uyarı görevlerini yürütür.', + offlineOff: 'Çevrimdışı Uyarısını Etkinleştir', + offlineOffHelper: + 'Çevrimdışı uyarının etkinleştirilmesi, ana düğümün her 30 dakikada bir tarama yaparak uyarı görevlerini yürütmesini sağlar.', + offlineClose: 'Çevrimdışı Uyarısını Devre Dışı Bırak', + offlineCloseHelper: + 'Çevrimdışı uyarının devre dışı bırakılması, alt düğümlerin uyarıları bağımsız olarak işlemesini gerektirir. Uyarı başarısızlığını önlemek için ağ bağlantısını sağlayın.', + alertNotice: 'Uyarı Bildirimi', + methodConfig: 'Bildirim Yöntemi Ayarı', + commonConfig: 'Genel Ayar', + smsConfig: 'SMS', + smsConfigHelper: 'SMS bildirimi için telefon numaralarını yapılandırın', + emailConfig: 'E-posta', + emailConfigHelper: 'SMTP e-posta gönderme hizmetini yapılandırın', + deleteConfigTitle: 'Uyarı Yapılandırmasını Sil', + deleteConfigMsg: 'Uyarı yapılandırmasını silmek istediğinizden emin misiniz?', + test: 'Test', + alertTestOk: 'Test bildirimi başarılı', + alertTestFailed: 'Test bildirimi başarısız', + displayName: 'Görünen Ad', + sender: 'Gönderen Adresi', + password: 'Şifre', + host: 'SMTP Sunucusu', + port: 'Port Numarası', + encryption: 'Şifreleme Yöntemi', + recipient: 'Alıcı', + licenseTime: 'Lisans Süresi Hatırlatması', + licenseTimeTitle: 'Lisans Süresi Hatırlatması', + displayNameHelper: 'E-posta göndericisinin görünen adı', + senderHelper: 'E-posta göndermek için kullanılan adres', + passwordHelper: 'E-posta servisinin yetkilendirme kodu', + hostHelper: 'SMTP sunucu adresi, örneğin: smtp.qq.com', + portHelper: 'SSL genellikle 465, TLS genellikle 587', + sslHelper: 'SMTP portu 465 ise genellikle SSL gerekir', + tlsHelper: 'SMTP portu 587 ise genellikle TLS gerekir', + triggerCondition: 'Tetikleme Koşulu', + loginFail: ' içinde oturum açma başarısızlığı', + nodeException: 'Düğüm Hatası Uyarısı', + licenseException: 'Lisans Hatası Uyarısı', + panelLogin: 'Panel Girişi Hatası Uyarısı', + sshLogin: 'SSH Girişi Hatası Uyarısı', + panelIpLogin: 'Panel Girişi IP Hatası Uyarısı', + sshIpLogin: 'SSH Girişi IP Hatası Uyarısı', + ipWhiteListHelper: + 'Beyaz listedeki IP’ler kurallara tabi değildir ve başarılı girişlerde uyarı oluşturulmaz', + nodeExceptionRule: 'Düğüm hatası uyarısı, günde {0} kez gönderilir', + licenseExceptionRule: 'Lisans hatası uyarısı, günde {0} kez gönderilir', + panelLoginRule: 'Panel girişi uyarısı, günde {0} kez gönderilir', + sshLoginRule: 'SSH girişi uyarısı, günde {0} kez gönderilir', + userNameHelper: 'Kullanıcı adı boşsa, varsayılan olarak gönderici adresi kullanılacaktır', + }, + theme: { + lingXiaGold: 'Ling Xia Altın', + classicBlue: 'Klasik Mavi', + freshGreen: 'Taze Yeşil', + customColor: 'Özel Renk', + setDefault: 'Varsayılan', + setDefaultHelper: 'Tema renk şeması başlangıç durumuna geri yüklenecek. Devam etmek istiyor musunuz?', + setHelper: 'Seçilen tema renk şeması kaydedilecek. Devam etmek istiyor musunuz?', + }, + exchange: { + exchange: 'Dosya Değişimi', + exchangeConfirm: '{0} düğümünden {1} dosya/klasörünü {2} düğümünün {3} dizinine aktarmak istiyor musunuz?', + }, + cluster: { + cluster: 'Высокая доступность приложений', + name: 'Имя кластера', + addCluster: 'Добавить кластер', + installNode: 'Установить узел', + master: 'Главный узел', + slave: 'Подчиненный узел', + replicaStatus: 'Ana-Çalışan Durumu', + unhealthyDeleteError: + 'Yükleme düğümü durumu anormal, lütfen düğüm listesini kontrol edin ve tekrar deneyin!', + replicaStatusError: 'Durum alımı anormal, lütfen ana düğümü kontrol edin.', + masterHostError: "Ana düğüm IP'si 127.0.0.1 olamaz", + }, + }, +}; + +export default { + ...fit2cloudEnLocale, + ...message, +}; diff --git a/frontend/src/lang/modules/zh-Hant.ts b/frontend/src/lang/modules/zh-Hant.ts new file mode 100644 index 0000000..3fb222d --- /dev/null +++ b/frontend/src/lang/modules/zh-Hant.ts @@ -0,0 +1,3867 @@ +import fit2cloudTwLocale from 'fit2cloud-ui-plus/src/locale/lang/zh-tw'; + +const message = { + commons: { + true: '是', + false: '否', + example: '例如:', + fit2cloud: '飛致雲', + lingxia: '凌霞', + colon: ':', + button: { + run: '執行', + prev: '上一步', + next: '下一步', + create: '建立', + add: '新增', + save: '儲存', + set: '設定', + sync: '同步', + delete: '刪除', + edit: '編輯', + enable: '啟用', + disable: '停用', + confirm: '確認', + cancel: '取消', + reset: '重設', + setDefault: '復原預設', + restart: '重啟', + conn: '連接', + disConn: '斷開', + clean: '清空', + login: '登入', + close: '關閉', + stop: '關閉', + start: '開啟', + view: '詳情', + watch: '追蹤', + handle: '執行', + clone: '複製', + expand: '展開', + collapse: '收起', + log: '日誌', + back: '返回', + backup: '備份', + recover: '復原', + retry: '重試', + upload: '上傳', + download: '下載', + init: '初始化', + verify: '驗證', + saveAndEnable: '儲存並啟用', + import: '匯入', + export: '匯出', + power: '授權', + search: '搜尋', + refresh: '重新整理', + get: '取得', + upgrade: '升級', + update: '更新', + updateNow: '立即更新', + ignore: '忽略升級', + copy: '複製', + random: '隨機密碼', + install: '安裝', + uninstall: '移除', + fullscreen: '網頁全螢幕', + quitFullscreen: '退出網頁全螢幕', + showAll: '顯示所有', + hideSome: '隱藏部分', + agree: '同意', + notAgree: '不同意', + preview: '預覽', + open: '打開', + notSave: '不儲存', + createNewFolder: '建立資料夾', + createNewFile: '建立檔案', + helpDoc: '說明文件', + bind: '綁定', + unbind: '解綁', + cover: '覆蓋', + skip: '跳過', + fix: '修復', + down: '停止', + up: '啟動', + sure: '確定', + show: '顯示', + hide: '隱藏', + visit: '訪問', + migrate: '遷移', + }, + operate: { + start: '啟動', + stop: '停止', + restart: '重新啟動', + reload: '重新載入', + rebuild: '重建', + sync: '同步', + up: '啟動', + down: '停止', + delete: '刪除', + }, + search: { + timeStart: '開始時間', + timeEnd: '結束時間', + timeRange: '至', + dateStart: '開始日期', + dateEnd: '結束日期', + date: '日期', + }, + table: { + all: '所有', + total: '共 {0} 條', + name: '名稱', + type: '類型', + status: '狀態', + records: '任務輸出', + group: '分組', + default: '預設', + createdAt: '建立時間', + publishedAt: '發布時間', + date: '時間', + updatedAt: '更新時間', + operate: '操作', + message: '資訊', + description: '描述', + interval: '耗時', + user: '使用者', + title: '標題', + port: '埠', + forward: '轉發', + protocol: '協議', + tableSetting: '列表設定', + refreshRate: '重新整理頻率', + noRefresh: '不重新整理', + selectColumn: '選擇列', + local: '本機', + serialNumber: '序號', + manageGroup: '管理群組', + backToList: '返回列表', + keepEdit: '繼續編輯', + }, + loadingText: { + Upgrading: '系統升級中,請稍候...', + Restarting: '系統重啟中,請稍候...', + Recovering: '快照復原中,請稍候...', + Rollbacking: '快照回滾中,請稍候...', + }, + msg: { + noneData: '暫無資料', + delete: '刪除 操作不可回滾,是否繼續?', + clean: '清空 操作不可回滾,是否繼續?', + closeDrawerHelper: '系統可能不會儲存您所做的變更,是否繼續?', + deleteSuccess: '刪除成功', + loginSuccess: '登入成功', + operationSuccess: '操作成功', + copySuccess: '複製成功', + notSupportOperation: '不支援的目前操作', + requestTimeout: '請求超時,請稍後重試', + infoTitle: '提示', + notRecords: '目前任務未產生執行記錄', + sureLogOut: '您是否確認退出登入?', + createSuccess: '建立成功', + updateSuccess: '更新成功', + uploadSuccess: '上傳成功', + operateConfirm: '如果確認操作,請手動輸入 ', + inputOrSelect: '請選擇或輸入', + copyFailed: '複製失敗', + operatorHelper: '將對以下{0}進行{1}操作,是否繼續?', + backupSuccess: '備份成功', + restoreSuccess: '備份成功', + notFound: '抱歉,您訪問的頁面不存在', + unSupportType: '不支援目前文件類型!', + unSupportSize: '上傳文件超過 {0}M,請確認!', + fileExist: '目前資料夾已存在該文件,不支援重複上傳!', + fileNameErr: '僅支援上傳名稱包含英文、中文、數字或者 .-_ ,長度 1-256 位的文件', + confirmNoNull: '請確認 {0} 值不為空', + errPort: '錯誤的埠資訊,請確認!', + remove: '移出', + backupHelper: '目前操作將對 {0} 進行備份,是否繼續?', + recoverHelper: '將從 {0} 文件進行復原,該操作不可回滾,是否繼續?', + refreshSuccess: '重繪成功', + rootInfoErr: '已經是根目錄了', + resetSuccess: '重設成功', + creatingInfo: '正在建立,無需此操作', + installSuccess: '安裝成功', + uninstallSuccess: '移除成功', + offlineTips: '離線版本不支援此操作', + errImportFormat: '導入數據或格式異常,請檢查後重試!', + importHelper: '導入衝突或重複數據時,將以導入內容為標準,更新原數據庫數據。', + errImport: '文件內容異常:', + }, + login: { + username: '使用者名稱', + password: '密碼', + passkey: '通行密鑰登入', + welcome: '歡迎回來,請輸入使用者名稱和密碼登入!', + errorAuthInfo: '您輸入的使用者名稱或密碼不正確,請重新輸入!', + errorMfaInfo: '錯誤的驗證資訊,請重試!', + captchaHelper: '驗證碼', + errorCaptcha: '驗證碼錯誤!', + notSafe: '暫無權限訪問', + safeEntrance1: '目前環境已經開啟了安全入口登入', + safeEntrance2: '在 SSH 終端輸入以下指令來查看面板入口: 1pctl user-info', + errIP1: '目前環境已經開啟了授權 IP 訪問', + errDomain1: '目前環境已經開啟了訪問域名綁定', + errHelper: '可在 SSH 終端輸入以下指令來重設綁定資訊: ', + codeInput: '請輸入 MFA 驗證器的 6 位驗證碼', + mfaTitle: 'MFA 認證', + mfaCode: 'MFA 驗證碼', + title: 'Linux 伺服器運維管理面板', + licenseHelper: '《飛致雲社區軟體許可協議》', + errorAgree: '請點擊同意社區軟體許可協議', + agreeTitle: '服務協議及隱私保護', + agreeContent: + '為了更好的保障您的合法權益,請您閱讀並同意以下協議 « 飛致雲社區軟體許可協議 »', + logout: '退出登入', + passkeyFailed: '通行密鑰登入失敗,請重試', + passkeyNotSupported: '目前瀏覽器或環境不支援通行密鑰', + passkeyToPassword: '無法使用通行密鑰? 使用帳號密碼登入', + }, + rule: { + username: '請輸入使用者名稱', + password: '請輸入密碼', + rePassword: '密碼不一致,請檢查後重新輸入', + requiredInput: '請填寫必填項', + requiredSelect: '請選擇必選項', + illegalChar: '暫不支援注入字元 & ; $ \' ` ( ) " > < |', + illegalInput: '輸入框中存在不合法字元', + commonName: '支援非特殊字元開頭,英文、中文、數字、.-和_,長度1-128', + userName: '支援非特殊字元開頭、英文、中文、數字和_,長度3-30', + simpleName: '支援非底線開頭,英文、數字、_,長度3-30', + simplePassword: '支援非底線開頭,英文、數字、_,長度1-30', + dbName: '支援非特殊字元開頭,英文、中文、數字、.-_,長度1-64', + composeName: '支援非特殊字元開頭,小寫英文、數字、-和_,長度1-256', + imageName: '支援非特殊字元開頭、英文、數字、:@/.-_,長度1-256', + volumeName: '支援英文、數字、.-和_,長度2-30', + supervisorName: '支援非特殊字元開頭,英文、數字、-和_,長度1-128', + complexityPassword: '請輸入長度為 8-30 位,並包含字母、數字、至少兩種特殊字元的密碼組合', + commonPassword: '請輸入 6 位以上長度密碼', + linuxName: '長度1-128,名稱不能含有{0}等符號', + email: '請輸入正確的信箱', + number: '請輸入正確的數字', + integer: '請輸入正確的正整數', + ip: '請輸入正確的 IP 位址', + host: '請輸入正確的 IP 或者域名', + hostHelper: '支援輸入 ip 或者域名', + port: '請輸入正確的埠,1-65535', + domain: '域名格式錯誤', + databaseName: '支援英文、數字、_,長度1-30', + numberRange: '數字範圍: {0} - {1}', + paramName: '支援英文、數字、.-和_,長度2-64', + paramComplexity: '支援英文、數字、{0},長度6-128,特殊字元不能在首尾', + paramUrlAndPort: '格式為 http(s)://(域名/ip):(埠)', + nginxDoc: '僅支援英文大小寫,數字,和.', + appName: '支援英文、數字、-和_,長度2-30,並且不能以-_開頭和結尾', + containerName: '支援字母、數字、_-和.,不能以-_或.開頭,長度2-128', + mirror: '支援以 http(s):// 開頭,英文大小寫,數字,. / 和 - 的鏡像加速地址,且不能有空行', + disableFunction: '僅支援字母、下劃線和,', + leechExts: '僅支援字母數字和,', + paramSimple: '支援小寫字母和數字,長度 1-128', + filePermission: '權限錯誤', + formatErr: '格式錯誤,檢查後重試', + phpExtension: '僅支援 , _ 小寫英文和數字', + paramHttp: '必須以 http:// 或 https:// 開頭', + phone: '手機號碼格式不正確', + authBasicPassword: '支援字母、數字以及常見特殊字元,長度1-72', + length128Err: '長度不能超過128位', + maxLength: '長度不能超過 {0} 位', + alias: '支援英文、數字、-和_,長度1-128,並且不能以-、_開頭或結尾', + }, + res: { + paramError: '請求失敗,請稍後重試!', + forbidden: '目前使用者無權限', + serverError: '服務異常', + notFound: '資源不存在', + commonError: '請求失敗', + }, + service: { + serviceNotStarted: '目前未啟動 {0} 服務', + }, + status: { + running: '已啟動', + done: '已完成', + scanFailed: '未完成', + success: '成功', + waiting: '請等待', + waitForUpgrade: '等待升級', + failed: '失敗', + stopped: '已停止', + error: '失敗', + created: '已建立', + restarting: '重啟中', + uploading: '上傳中', + unhealthy: '異常', + removing: '移除中', + paused: '已暫停', + exited: '已停止', + dead: '已結束', + installing: '安裝中', + enabled: '已啟用', + disabled: '已停止', + normal: '正常', + building: '製作鏡像中', + upgrading: '升級中', + pending: '待編輯', + rebuilding: '重建中', + deny: '已封鎖', + accept: '已放行', + used: '已使用', + unused: '未使用', + starting: '啟動中', + recreating: '重建中', + creating: '建立中', + init: '等待申請', + ready: '正常', + applying: '申請中', + uninstalling: '移除中', + lost: '已失聯', + bound: '已綁定', + unbind: '未綁定', + exceptional: '異常', + free: '空閒', + enable: '已啟用', + disable: '已停止', + deleted: '已刪除', + downloading: '下載中', + packing: '打包中', + sending: '下發中', + healthy: '正常', + executing: '執行中', + installerr: '安裝失敗', + applyerror: '申請失敗', + systemrestart: '中斷', + starterr: '啟動失敗', + uperr: '啟動失敗', + new: '新', + conflict: '衝突', + duplicate: '重複', + unexecuted: '未執行', + }, + units: { + second: '秒', + minute: '分鐘', + hour: '小時', + day: '天', + week: '周', + month: '月', + year: '年', + time: '次', + core: '核', + secondUnit: '秒', + minuteUnit: '分鐘', + hourUnit: '小時', + dayUnit: '天', + millisecond: '毫秒', + }, + log: { + noLog: '暫無日誌', + }, + }, + menu: { + home: '概覽', + apps: '應用商店', + website: '網站', + project: '項目', + config: '配置', + ssh: 'SSH 配置', + firewall: '防火牆', + ssl: '證書', + database: '資料庫', + aiTools: 'AI', + mcp: 'MCP', + container: '容器', + cronjob: '計劃任務', + system: '系統', + files: '文件', + monitor: '監控', + terminal: '終端', + settings: '面板設定', + toolbox: '工具箱', + logs: '日誌審計', + runtime: '執行環境', + processManage: '行程管理', + process: '行程', + network: '網路', + supervisor: '行程守護', + tamper: '防篡改', + app: '應用', + msgCenter: '任務中心', + disk: '磁碟管理', + }, + home: { + recommend: '推薦', + dir: '目錄', + alias: '別名', + quickDir: '快捷目錄', + minQuickJump: '請至少設定一個快速跳轉入口!', + maxQuickJump: '最多可設定四個快速跳轉入口!', + database: '資料庫 - 全部', + restart_1panel: '重啟面板', + restart_system: '重啟伺服器', + operationSuccess: '操作成功,正在重啟,請稍後手動重新整理瀏覽器!', + entranceHelper: '設定安全入口有利於提高系統的安全性,如有需要,前往 面板設定-安全 中,啟用安全入口', + appInstalled: '已安裝應用', + systemInfo: '系統資訊', + hostname: '主機名稱', + platformVersion: '發行版本', + kernelVersion: '核心版本', + kernelArch: '系統類型', + network: '流量', + io: '磁碟 IO', + ip: '主機地址', + proxy: '系統代理', + baseInfo: '基本資訊', + totalSend: '總發送', + totalRecv: '總接收', + rwPerSecond: '讀寫次數', + ioDelay: '讀寫延遲', + uptime: '啟動時間', + runningTime: '執行時間', + mem: '系統記憶體', + swapMem: 'Swap 分區', + + runSmoothly: '執行流暢', + runNormal: '執行正常', + runSlowly: '執行緩慢', + runJam: '執行堵塞', + + core: '物理核心', + logicCore: '邏輯核心', + corePercent: '核心使用率', + cpuFrequency: 'CPU 頻率', + cpuDetailedPercent: 'CPU 佔用', + cpuUser: '用戶態', + cpuSystem: '內核態', + cpuIdle: '空閒', + cpuIrq: '硬中斷', + cpuSoftirq: '軟中斷', + cpuSteal: '被VM搶佔', + cpuTop: 'CPU 佔用率 Top5 的行程資訊', + memTop: '記憶體佔用率 Top5 的行程資訊', + loadAverage: '最近 {0} 分鐘平均負載', + load: '負載', + mount: '掛載點', + fileSystem: '文件系統', + total: '總計', + used: '已用', + cache: '快取', + free: '空閒', + shard: '分片', + available: '可用', + percent: '使用率', + goInstall: '去安裝', + + networkCard: '網卡', + disk: '磁碟', + }, + tabs: { + more: '更多', + hide: '收起', + closeLeft: '關閉左側', + closeRight: '關閉右側', + closeCurrent: '關閉目前', + closeOther: '關閉其它', + closeAll: '關閉所有', + }, + header: { + logout: '退出登入', + }, + database: { + manage: '管理', + deleteBackupHelper: '同時刪除資料庫備份', + delete: '刪除操作無法回滾,請輸入 "', + deleteHelper: '" 刪除此資料庫', + create: '建立資料庫', + noMysql: '資料庫服務 (MySQL 或 MariaDB)', + noPostgresql: '資料庫服務 PostgreSQL', + goUpgrade: '去應用列表升級', + goInstall: '去應用商店安裝', + isDelete: '已刪除', + permission: '權限', + format: '字元集', + collation: '排序規則', + collationHelper: '為空則使用 {0} 字元集的預設排序規則', + permissionForIP: '指定 IP', + permissionAll: '所有人(%)', + localhostHelper: '將容器部署的資料庫權限配置為"localhost"會導致容器外部無法存取,請謹慎選擇!', + databaseConnInfo: '連接資訊', + rootPassword: 'root 密碼', + serviceName: '服務名稱', + serviceNameHelper: '用於同一 network 下的容器間訪問', + backupList: '備份列表', + loadBackup: '匯入備份', + localUpload: '本機上傳', + hostSelect: '伺服器選擇', + selectHelper: '是否確認匯入備份文件 {0}?', + remoteAccess: '遠端存取', + remoteHelper: '多個 ip 以逗號分隔,例:172.16.10.111,172.16.10.112', + remoteConnHelper: 'root 帳號遠端連接 MySQL 有安全風險,開啟需謹慎!', + changePassword: '改密', + changeConnHelper: '此操作將修改目前資料庫 {0},是否繼續?', + changePasswordHelper: '目前資料庫已經關聯應用,修改密碼將同步修改應用中資料庫密碼,修改後重啟生效。', + recoverTimeoutHelper: '-1 表示不限制超時時間', + + portHelper: '該埠為容器對外暴露埠,修改需要單獨儲存並且重啟容器!', + + confChange: '配置修改', + confNotFound: '未能找到該應用設定檔,請在應用商店升級該應用至最新版本後重試!', + + loadFromRemote: '從伺服器同步', + userBind: '綁定使用者', + pgBindHelper: '此操作用於建立新使用者並將其綁定到目標資料庫,暫不支援選擇已存在於資料庫中的使用者。', + pgSuperUser: '超級使用者', + loadFromRemoteHelper: '此操作將同步伺服器上資料庫資訊到 1Panel,是否繼續?', + passwordHelper: '無法獲取,可點擊修改', + remote: '遠端', + remoteDB: '遠端伺服器', + createRemoteDB: '新增遠端伺服器', + unBindRemoteDB: '解綁遠端伺服器', + unBindForce: '強制解綁', + unBindForceHelper: '忽略解綁過程中的所有錯誤,確保最終操作成功', + unBindRemoteHelper: '解綁遠端資料庫只會刪除綁定關係,不會直接刪除遠端資料庫', + editRemoteDB: '編輯遠端伺服器', + localDB: '本機資料庫', + address: '資料庫地址', + version: '資料庫版本', + userHelper: 'root 使用者或者擁有 root 權限的資料庫使用者', + pgUserHelper: '具有超級管理員權限的資料庫使用者', + ssl: '使用 SSL', + clientKey: '用戶端私鑰', + clientCert: '用戶端證書', + hasCA: '擁有 CA 證書', + caCert: 'CA 證書', + skipVerify: '忽略校驗證書可用性檢測', + initialDB: '初始資料庫', + + formatHelper: '目前資料庫字元集為 {0},字元集不一致可能導致復原失敗', + dropHelper: '將上傳文件拖曳到此處,或者', + clickHelper: '點擊上傳', + supportUpType: + '僅支援 sql、sql.gz、tar.gz、.zip 檔案格式,匯入的壓縮文件必須保證只有一個 .sql 文件或者包含 test.sql', + + currentStatus: '目前狀態', + baseParam: '基礎參數', + performanceParam: '性能參數', + runTime: '啟動時間', + connections: '總連接數', + bytesSent: '發送', + bytesReceived: '接收', + queryPerSecond: '每秒查詢', + txPerSecond: '每秒事務', + connInfo: '活動/峰值連接數', + connInfoHelper: '若值過大,增加 max_connections', + threadCacheHit: '執行緒快取命中率', + threadCacheHitHelper: '若過低,增加 thread_cache_size', + indexHit: '索引命中率', + indexHitHelper: '若過低,增加 key_buffer_size', + innodbIndexHit: 'Innodb 索引命中率', + innodbIndexHitHelper: '若過低,增加 innodb_buffer_pool_size', + cacheHit: '查詢快取命中率', + cacheHitHelper: '若過低,增加 query_cache_size', + tmpTableToDB: '建立臨時表到磁碟', + tmpTableToDBHelper: '若過大,嘗試增加 tmp_table_size', + openTables: '已打開的表', + openTablesHelper: 'table_open_cache 配置值應大於等於此值', + selectFullJoin: '沒有使用索引的量', + selectFullJoinHelper: '若不為0,請檢查資料表的索引是否合理', + selectRangeCheck: '沒有索引的 JOIN 量', + selectRangeCheckHelper: '若不為0,請檢查資料表的索引是否合理', + sortMergePasses: '排序後的合併次數', + sortMergePassesHelper: '若值過大,增加sort_buffer_size', + tableLocksWaited: '鎖表次數', + tableLocksWaitedHelper: '若值過大,請考慮增加您的資料庫性能', + + performanceTuning: '性能調整', + optimizationScheme: '最佳化方案', + keyBufferSizeHelper: '用於索引的緩衝區大小', + queryCacheSizeHelper: '查詢快取,不開啟請設為0', + tmpTableSizeHelper: '臨時表快取大小', + innodbBufferPoolSizeHelper: 'Innodb 緩衝區大小', + innodbLogBufferSizeHelper: 'Innodb 日誌緩衝區大小', + sortBufferSizeHelper: '* 連接數, 每個執行緒排序的緩衝大小', + readBufferSizeHelper: '* 連接數, 讀入緩衝區大小', + readRndBufferSizeHelper: '* 連接數, 隨機讀取緩衝區大小', + joinBufferSizeHelper: '* 連接數, 關聯表快取大小', + threadStackelper: '* 連接數, 每個執行緒的堆疊大小', + binlogCacheSizeHelper: '* 連接數, 二進制日誌快取大小(4096的倍數)', + threadCacheSizeHelper: '執行緒池大小', + tableOpenCacheHelper: '表快取', + maxConnectionsHelper: '最大連接數', + restart: '重啟資料庫', + + slowLog: '慢日誌', + noData: '暫無慢日誌...', + + isOn: '開啟', + longQueryTime: '閾值(秒)', + thresholdRangeHelper: '請輸入正確的閾值(1 - 600)', + + timeout: '超時時間(秒)', + timeoutHelper: '空閒連接超時時間,0表示不斷開', + maxclients: '最大連接數', + requirepassHelper: '留空代表沒有設定密碼,修改需要單獨儲存並且重啟容器!', + databases: '資料庫數量', + maxmemory: '最大記憶體使用', + maxmemoryHelper: '0 表示不做限制', + tcpPort: '目前監聽埠', + uptimeInDays: '已執行天數', + connectedClients: '連接的用戶端數量', + usedMemory: '目前 Redis 使用的記憶體大小', + usedMemoryRss: '向作業系統申請的記憶體大小', + usedMemoryPeak: 'Redis 的記憶體消耗峰值', + memFragmentationRatio: '記憶體碎片比率', + totalConnectionsReceived: '執行以來連接過的用戶端的總數量', + totalCommandsProcessed: '執行以來執行過的指令的總數量', + instantaneousOpsPerSec: '伺服器每秒鐘執行的指令數量', + keyspaceHits: '尋找資料庫鍵成功的次數', + keyspaceMisses: '尋找資料庫鍵失敗的次數', + hit: '尋找資料庫鍵命中率', + latestForkUsec: '最近一次 fork() 操作耗費的微秒數', + redisCliHelper: '未檢測到 redis-cli 服務,請先啟用服務!', + redisQuickCmd: 'Redis 快速指令', + + recoverHelper: '即將使用 [{0}] 對資料進行覆蓋,是否繼續?', + submitIt: '覆蓋資料', + + baseConf: '基礎配置', + allConf: '全部配置', + restartNow: '立即重啟', + restartNowHelper1: '修改配置後需要重啟生效,若您的資料需要持久化請先執行 save 操作。', + restartNowHelper: '修改配置後需要重啟生效。', + + persistence: '持久化', + rdbHelper1: '秒內,插入', + rdbHelper2: '條資料', + rdbHelper3: '符合任意一個條件將會觸發RDB持久化', + rdbInfo: '請確認規則列表中值在 1-100000 之間', + + containerConn: '容器連接', + connAddress: '地址', + containerConnHelper: 'PHP 執行環境/容器安裝的應用程式使用此連接地址', + remoteConn: '外部連接', + remoteConnHelper2: '非容器環境或外部連接需使用此地址。', + remoteConnHelper3: '預設存取地址為主機IP,修改請前往面板設定頁面的「預設存取地址」配置項。', + localIP: '本機 IP', + }, + aiTools: { + model: { + model: '模型', + create: '新增模型', + create_helper: '拉取 "{0}"', + ollama_doc: '您可以瀏覽 Ollama 官方網站,搜尋並尋找更多模型。', + container_conn_helper: '容器間瀏覽或連接使用此地址', + ollama_sync: '同步 Ollama 模型發現下列模型不存在,是否刪除?', + from_remote: '該模型並非透過 1Panel 下載,無相關拉取日誌。', + no_logs: '該模型的拉取日誌已被刪除,無法查看相關日誌。', + }, + proxy: { + proxy: 'AI 代理增強', + proxyHelper1: '綁定域名並啟用 HTTPS,提高傳輸安全性', + proxyHelper2: '限制 IP 瀏覽,防止在網路上暴露', + proxyHelper3: '啟用即時串流', + proxyHelper4: '建立後,您可以在網站列表中查看並管理', + proxyHelper5: '啟用後,您可以在應用商店 - 已安裝 - Ollama - 參數中取消埠外部瀏覽以提高安全性', + proxyHelper6: '如需關閉代理配置,可以在網站列表中刪除', + whiteListHelper: '限制僅白名單中的 IP 可瀏覽', + }, + gpu: { + gpu: 'GPU 監控', + gpuHelper: '目前系統未偵測到 NVIDIA-SMI 或者 XPU-SMI 指令,請檢查後重試!', + process: '行程資訊', + type: '類型', + typeG: '圖形', + typeC: '計算', + typeCG: '計算+圖形', + processName: '行程名稱', + shr: '共享顯存', + temperatureHelper: 'GPU 溫度過高會導致 GPU 頻率下降', + gpuUtil: 'GPU 使用率', + temperature: '溫度', + performanceState: '效能狀態', + powerUsage: '功耗', + memoryUsage: '顯存使用率', + fanSpeed: '風扇轉速', + power: '功率', + powerCurrent: '目前功率', + powerLimit: '功率上限', + memory: '顯存', + memoryUsed: '顯存使用', + memoryTotal: '顯存總計', + percent: '使用率', + + base: '基礎資訊', + driverVersion: '驅動程式版本', + cudaVersion: 'CUDA 版本', + processMemoryUsage: '顯存使用', + performanceStateHelper: '從 P0 (最大效能) 到 P12 (最小效能)', + busID: '匯流排位址', + persistenceMode: '持續模式', + enabled: '開啟', + disabled: '關閉', + persistenceModeHelper: '持續模式能更加快速地響應任務,但相應待機功耗也會增加', + displayActive: '顯示卡初始化', + displayActiveT: '是', + displayActiveF: '否', + ecc: '是否開啟錯誤檢查和糾正技術', + computeMode: '計算模式', + default: '預設', + exclusiveProcess: '行程排他', + exclusiveThread: '執行緒排他', + prohibited: '禁止', + defaultHelper: '預設: 行程可以並發執行', + exclusiveProcessHelper: '行程排他: 只有一個 CUDA 上下文可以使用 GPU,但可以由多個執行緒共享', + exclusiveThreadHelper: '執行緒排他: 只有一個執行緒在 CUDA 上下文中可以使用 GPU', + prohibitedHelper: '禁止: 不允許行程同時執行', + migModeHelper: '用於建立 MIG 實例,在用戶層實現 GPU 的物理隔離。', + migModeNA: '不支援', + current: '即時監控', + history: '歷史記錄', + notSupport: '目前版本或驅動不支援顯示該參數。', + }, + mcp: { + server: 'MCP Server', + create: '建立 MCP Server', + edit: '編輯 MCP Server', + baseUrl: '外部訪問路徑', + baseUrlHelper: '例如:http://192.168.1.1:8000', + ssePath: 'SSE 路徑', + ssePathHelper: '例如:/sse,注意不要與其他 Server 重複', + environment: '環境變數', + envKey: '變數名', + envValue: '變數值', + externalUrl: '外部連接地址', + operatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?', + domain: '預設訪問地址', + domainHelper: '例如:192.168.1.1 或者 example.com', + bindDomain: '綁定網站', + commandPlaceHolder: '目前僅支援 npx 和 二進制啟動的指令', + importMcpJson: '匯入 MCP Server 配置', + importMcpJsonError: 'mcpServers 結構不正確', + bindDomainHelper: '綁定網站之後會修改所有已安裝 MCP Server 的訪問地址,並關閉埠的外部訪問', + outputTransport: '輸出類型', + streamableHttpPath: '流式傳輸路徑', + streamableHttpPathHelper: '例如:/mcp, 注意不要與其他 Server 重複', + npxHelper: '適合 npx 或者 二進制啟動的 mcp', + uvxHelper: '適合 uvx 啟動的 mcp', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: '模型目錄', + commandHelper: '若需外部訪問,請將命令中的端口設置為與應用端口相同', + imageAlert: '由於鏡像較大,建議先手動將鏡像下載到伺服器後再進行安裝', + modelSpeedup: '啟用模型加速', + modelType: '模型類型', + }, + }, + container: { + create: '建立容器', + createByCommand: '指令建立', + commandInput: '指令輸入', + commandRule: '請輸入正確的 docker run 容器建立指令!', + commandHelper: '將在伺服器上執行該條指令以建立容器,是否繼續?', + edit: '編輯容器', + updateHelper1: '檢測到該容器來源於應用商店,請注意以下兩點:', + updateHelper2: '1. 目前修改內容不會同步到應用商店的已安裝應用。', + updateHelper3: '2. 如果在已安裝頁面修改應用,目前編輯的部分內容將失效。', + updateHelper4: '編輯容器需要重建,任何未持久化的資料將遺失,是否繼續操作?', + containerList: '容器列表', + operatorHelper: '將對以下容器進行 {0} 操作,是否繼續?', + operatorAppHelper: + '將對以下容器進行 {0} 操作,\n其中部分來源於應用商店,該操作可能會影響到該服務的正常使用。\n是否確認?', + containerDeleteHelper: + '檢測到容器來源於應用商店,刪除容器不會將其從 1Panel 中完全移除。如需徹底刪除,請前往應用商店-『已安裝』或『運行環境』等選單中操作。是否繼續?', + start: '啟動', + stop: '停止', + restart: '重啟', + kill: '強制停止', + pause: '暫停', + unpause: '復原', + rename: '重新命名', + remove: '刪除', + removeAll: '刪除所有', + containerPrune: '清理容器', + containerPruneHelper1: '清理容器 將刪除所有處於停止狀態的容器。', + containerPruneHelper2: + '若容器來自於應用商店,在執行清理操作後,您需要前往 [應用商店] 的 [已安裝] 列表,點擊 [重建] 按鈕進行重新安裝。', + containerPruneHelper3: '該操作無法回滾,是否繼續?', + imagePrune: '清理鏡像', + imagePruneSome: '未標籤鏡像', + imagePruneSomeEmpty: '暫無待清理的未使用 none 標籤鏡像', + imagePruneSomeHelper: '清理下列標籤為 none 且未被任何容器使用的鏡像', + imagePruneAll: '未使用鏡像', + imagePruneAllEmpty: '暫無待清理的未使用鏡像', + imagePruneAllHelper: '清理下列未被任何容器使用的鏡像', + networkPrune: '清理網路', + networkPruneHelper: '清理網路 將刪除所有未被使用的網路,該操作無法回滾,是否繼續?', + volumePrune: '清理磁碟區', + volumePruneHelper: '清理磁碟區 將刪除所有未被使用的本機磁碟區,該操作無法回滾,是否繼續?', + cleanSuccess: '操作成功,本次清理數量: {0} 個!', + cleanSuccessWithSpace: '操作成功,本次清理數量: {0} 個,釋放磁碟空間: {1}!', + unExposedPort: '目前埠映射地址為 127.0.0.1,無法實現外部訪問', + upTime: '執行時長', + fetch: '過濾', + lines: '條數', + linesHelper: '請輸入正確的日誌取得條數!', + lastDay: '最近一天', + last4Hour: '最近 4 小時', + lastHour: '最近 1 小時', + last10Min: '最近 10 分鐘', + cleanLog: '清空日誌', + downLogHelper1: '即將下載 {0} 容器所有日誌,是否繼續?', + downLogHelper2: '即將下載 {0} 容器最近 {1} 條日誌,是否繼續?', + cleanLogHelper: '清空日誌需要重啟容器,該操作無法回滾,是否繼續?', + newName: '新名稱', + workingDir: '工作目錄', + source: '資源使用率', + cpuUsage: 'CPU 使用', + cpuTotal: 'CPU 總計', + core: '核心數', + memUsage: '記憶體使用', + memTotal: '記憶體限額', + memCache: '快取使用', + loadSize: '取得容器大小', + ip: 'IP 位址', + cpuShare: 'CPU 權重', + cpuShareHelper: '容器預設份額為 1024 個 CPU,增大可使目前容器獲得更多的 CPU 時間', + inputIpv4: '請輸入 IPv4 位址', + inputIpv6: '請輸入 IPv6 位址', + + diskUsage: '磁碟佔用', + localVolume: '本機儲存卷', + buildCache: '建置快取', + usage: '已佔用:{0}, 可釋放:{1}', + clean: '釋放', + imageClean: '清理映像將刪除所有未被使用的映像,該操作無法復原,是否繼續?', + containerClean: '清理容器將刪除所有處於停止中狀態的容器(包括應用商店停止應用),該操作無法復原,是否繼續?', + sizeRw: '容器層大小', + sizeRwHelper: '容器獨有的可寫層大小', + sizeRootFs: '虛擬大小', + sizeRootFsHelper: '容器依賴的所有映像層 + 容器層的總大小', + + containerFromAppHelper: '檢測到該容器來源於應用商店,應用操作可能會導致目前編輯失效', + containerFromAppHelper1: '在已安裝應用程式列表點擊 [參數] 按鈕,進入編輯頁面即可修改容器名稱。', + command: '指令', + console: '控制臺互動', + tty: '偽終端 ( -t )', + openStdin: '標準輸入 ( -i )', + custom: '自訂', + emptyUser: '為空時,將使用容器預設的使用者登入', + privileged: '特權模式', + privilegedHelper: '允許容器在主機上執行某些特權操作,可能會增加容器風險,請謹慎開啟!', + + upgradeHelper: '倉庫名稱/鏡像名稱:鏡像版本', + upgradeWarning2: '升級操作需要重建容器,任何未持久化的資料將會遺失,是否繼續?', + oldImage: '目前鏡像', + sameImageContainer: '同鏡像容器', + sameImageHelper: '同鏡像容器可勾選後批次升級', + targetImage: '目標鏡像', + imageLoadErr: '未檢測到容器的鏡像名稱', + appHelper: '該容器來源於應用商店,升級可能導致該服務不可用', + + resource: '資源', + input: '手動輸入', + forcePull: '強制拉取鏡像', + forcePullHelper: '忽略伺服器已存在的鏡像,重新拉取一次', + server: '伺服器', + serverExample: '80, 80-88, ip:80 或者 ip:80-88', + containerExample: '80 或者 80-88', + exposePort: '暴露埠', + exposeAll: '暴露所有', + cmdHelper: '例: nginx -g "daemon off;"', + entrypointHelper: '例: docker-entrypoint.sh', + autoRemove: '容器退出後自動刪除容器', + cpuQuota: 'CPU 限制', + memoryLimit: '記憶體限制', + limitHelper: '限制為 0 則關閉限制,最大可用為 {0}', + macAddr: 'MAC 地址', + mount: '掛載', + volumeOption: '掛載卷', + hostOption: '本機目錄', + serverPath: '伺服器目錄', + containerDir: '容器目錄', + volumeHelper: '請確認磁碟區內容輸入正確', + networkEmptyHelper: '請確認容器網路選擇正確', + modeRW: '讀寫', + modeR: '唯讀', + sharedLabel: '傳播模式', + private: '私有', + privateHelper: '容器裡的掛載變化和主機互不干擾', + rprivate: '遞迴私有', + rprivateHelper: '容器裡所有掛載都和主機完全隔離', + shared: '共享', + sharedHelper: '主機和容器裡的掛載變化互相可見', + rshared: '遞迴共享', + rsharedHelper: '主機和容器裡所有掛載變化都互相可見', + slave: '從屬', + slaveHelper: '容器能看見主機的掛載變化,但自己的變化不影響主機', + rslave: '遞迴從屬', + rslaveHelper: '容器裡所有掛載都能看見主機變化,但不影響主機', + mode: '權限', + env: '環境變數', + restartPolicy: '重啟規則', + always: '一直重啟', + unlessStopped: '未手動停止則重啟', + onFailure: '失敗後重啟(預設重啟 5 次)', + no: '不重啟', + + refreshTime: '重新整理間隔', + cache: '快取', + + image: '鏡像', + imagePull: '拉取鏡像', + imagePullHelper: '支援選擇拉取多個鏡像,輸入一組鏡像後Enter繼續', + imagePush: '推送鏡像', + imagePushHelper: '檢測到該映像存在多個標籤,請確認推送時使用的映像名稱為:{0}', + imageDelete: '刪除鏡像', + repoName: '倉庫名', + imageName: '鏡像名', + httpRepo: 'http 倉庫新增授信需要重啟 docker 服務', + delInsecure: '刪除授信', + delInsecureHelper: '刪除授信需要重啟 docker 服務,是否刪除?', + pull: '拉取', + path: '路徑', + importImage: '匯入鏡像', + imageBuild: '構建鏡像', + buildArgs: '構建參數', + pathSelect: '路徑選擇', + label: '標籤', + imageTag: '鏡像標籤', + imageTagHelper: '支援設定多個映像標籤,輸入一個標籤後Enter繼續', + push: '推送', + fileName: '檔案名', + export: '匯出', + exportImage: '匯出鏡像', + size: '大小', + tag: '標籤', + tagHelper: '一行一個,例: \nkey1=value1\nkey2=value2', + imageNameHelper: '鏡像名稱及 Tag,例:nginx:latest', + cleanBuildCache: '清理建置快取', + delBuildCacheHelper: '清理建置快取將刪除所有建置所產生的快取,此操作無法回復。是否繼續?', + urlWarning: '路徑前綴不需要新增 http:// 或 https://,請修改', + + network: '網路', + networkHelper: '刪除 1panel-network 容器網路將影響部分應用和執行環境的正常使用,是否繼續?', + createNetwork: '建立網路', + networkName: '網路名', + driver: '模式', + option: '參數', + attachable: '可用', + parentNetworkCard: '父網卡', + subnet: '子網', + scope: 'IP 範圍', + gateway: '閘道器', + auxAddress: '排除 IP', + + volume: '磁碟區', + volumeDir: '磁碟區目錄', + nfsEnable: '啟用 NFS 儲存', + nfsAddress: '地址', + mountpoint: '掛載點', + mountpointNFSHelper: '例:/nfs, /nfs-share', + options: '可選參數', + createVolume: '建立磁碟區', + + repo: '倉庫', + createRepo: '新增倉庫', + httpRepoHelper: '操作 HTTP 類型倉庫需要重啟 Docker 服務。', + downloadUrl: '下載網址', + imageRepo: '鏡像倉庫', + repoHelper: '是否包含鏡像倉庫/組織/項目?', + auth: '認證', + mirrorHelper: + '當存在多個加速器時,需要換行顯示,例: \nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com', + registrieHelper: '當存在多個私有倉庫時,需要換行顯示,例:\n172.16.10.111:8081 \n172.16.10.112:8081', + + compose: '編排', + composeFile: '編排檔案', + fromChangeHelper: '切換來源將清空檔前已編輯內容,是否繼續?', + composePathHelper: '設定檔儲存路徑: {0}', + composeHelper: '通過 1Panel 編輯或者模版建立的編排,將儲存在 {0}/docker/compose 路徑下', + deleteFile: '刪除文件', + deleteComposeHelper: '刪除容器編排的所有檔案,包括設定檔和持久化文件,請謹慎操作!', + deleteCompose: '" 刪除此編排', + createCompose: '建立編排', + composeDirectory: '編排目錄', + template: '模版', + composeTemplate: '編排模版', + createComposeTemplate: '建立編排模版', + content: '內容', + contentEmpty: '編排內容不能為空,請輸入後重試!', + containerNumber: '容器數量', + containerStatus: '容器狀態', + exited: '已停止', + running: '執行中 ( {0} / {1} )', + composeDetailHelper: '該 compose 為 1Panel 編排外部建立。暫不支援啟停操作。', + composeOperatorHelper: '將對 {0} 進行 {1} 操作,是否繼續?', + composeDownHelper: '將停止並刪除 {0} 編排下所有容器及網路,是否繼續?', + composeEnvHelper2: '該編排為 1Panel 應用商店建立,請在已安裝應用中修改環境變數。', + + setting: '配置', + goSetting: '去修改', + operatorStatusHelper: '此操作將{0}Docker 服務,是否繼續?', + dockerStatus: 'Docker 服務', + daemonJsonPathHelper: '請保證配置路徑與 docker.service 中指定的配置路徑保持一致。', + mirrors: '鏡像加速', + mirrorsHelper: '優先使用加速 URL 執行操作,設定為空則取消鏡像加速。', + mirrorsHelper2: '具體操作配置請參照官方文件', + registries: '私有倉庫', + ipv6Helper: '開啟 IPv6 後,需要增加 IPv6 的容器網路,具體操作配置請參照官方文件', + ipv6CidrHelper: '容器的 IPv6 位址池範圍', + ipv6TablesHelper: 'Docker IPv6 對 iptables 規則的自動配置', + experimentalHelper: '開啟 ip6tables 必須開啟此配置,否則 ip6tables 會被忽略', + cutLog: '日誌切割', + cutLogHelper1: '目前配置只會影響新建立的容器;', + cutLogHelper2: '已經建立的容器需要重新建立使配置生效;', + cutLogHelper3: + '注意,重新建立容器可能會導致資料遺失。如果你的容器中有重要資料,確保在執行重建操作之前進行備份。', + maxSize: '檔案大小', + maxFile: '保留份數', + liveHelper: '允許在 Docker 守護行程發生意外停機或崩潰時保留正在執行的容器狀態', + liveWithSwarmHelper: 'live-restore 守護行程配置與 Swarm 模式不相容', + iptablesDisable: '關閉 iptables', + iptablesHelper1: 'Docker 對 iptables 規則的自動配置', + iptablesHelper2: '關閉 iptables 會導致容器無法與外部網路通信。', + daemonJsonPath: '配置路徑', + serviceUnavailable: '目前未啟動 Docker 服務,請在', + startIn: '中開啟', + sockPath: 'Socket 路徑', + sockPathHelper: 'Docker 守護行程(Docker Daemon)與用戶端之間的通信通道', + sockPathHelper1: '預設路徑:/var/run/docker.sock', + sockPathMsg: '儲存設定 Socket 路徑可能導致 Docker 服務不可用,是否繼續?', + sockPathErr: '請選擇或輸入正確的 Docker sock 文件路徑', + related: '相關資源', + includeAppstore: '顯示應用商店容器', + excludeAppstore: '隱藏應用商店容器', + + cleanDockerDiskZone: '清理 Docker 使用的磁碟空間', + cleanImagesHelper: '( 清理所有未被任何容器使用的鏡像 )', + cleanContainersHelper: '( 清理所有處於停止狀態的容器 )', + cleanVolumesHelper: '( 清理所有未被使用的本機磁碟區 )', + + makeImage: '製作鏡像', + newImageName: '新鏡像名稱', + commitMessage: '提交資訊', + author: '作者', + ifPause: '製作過程中是否暫停容器', + ifMakeImageWithContainer: '是否根據此容器製作新鏡像?', + finishTime: '上一次停止時間', + }, + cronjob: { + create: '建立計劃任務', + edit: '編輯計劃任務', + importHelper: + '匯入時將自動跳過重名計劃任務。任務預設設定為【停用】狀態,資料關聯異常時,設定為【待編輯】狀態。', + changeStatus: '狀態修改', + disableMsg: '停止計劃任務會導致該任務不再自動執行。是否繼續?', + enableMsg: '啟用計劃任務會讓該任務定期自動執行。是否繼續?', + taskType: '任務類型', + nextTime: '近 5 次執行', + record: '報告', + viewRecords: '查看報告', + shell: 'Shell 腳本', + stop: '手動結束', + stopHelper: '該操作將強制停止該次任務執行,是否繼續?', + log: '備份日誌', + logHelper: '備份系統日誌', + logHelper1: '1. 1Panel 系統日誌', + logHelper2: '2. 伺服器的 SSH 登入日誌', + logHelper3: '3. 所有網站日誌', + containerCheckBox: '在容器中執行(無需再輸入進入容器指令)', + containerName: '容器名稱', + ntp: '同步伺服器時間', + ntp_helper: '您可以在工具箱的快速設定頁面配置 NTP 伺服器', + app: '備份應用', + website: '備份網站', + rulesHelper: '支援多個排除規則,使用英文逗號 , 分隔,例如:*.log,*.sql', + lastRecordTime: '上次執行情況', + database: '備份資料庫', + backupArgs: '備份參數', + backupArgsHelper: '未列出的備份參數可手動輸入並選擇,例如:輸入 --no-data 後選擇下拉列表中的第一個選項', + singleTransaction: '使用單一事務備份 InnoDB 表,適用於大數據量的備份', + quick: '逐行讀取數據,而不是將整個表加載到內存中,適用於大數據量和低內存機器的備份', + skipLockTables: '不鎖定所有表進行備份,適用於高併發的資料庫', + missBackupAccount: '未能找到備份帳號', + syncDate: '同步時間 ', + clean: '快取清理', + curl: '訪問 URL', + taskName: '任務名稱', + cronSpec: '執行週期', + cronSpecDoc: '自訂執行週期僅支援【分 時 日 月 週】格式,例如 0 0 * * *,具體可參考官方文件。', + cronSpecHelper: '請輸入正確的執行週期', + cleanHelper: '該操作將所有任務執行記錄、備份文件和日誌檔案,是否繼續?', + backupContent: '備份內容', + directory: '備份目錄 / 檔案', + sourceDir: '備份目錄', + snapshot: '系統快照', + allOptionHelper: '目前計劃任務為備份所有【{0}】,暫不支援直接下載,可在【{0}】備份列表中查看', + exclusionRules: '排除規則', + exclusionRulesHelper: '選擇或輸入排除規則,輸入完一組後Enter繼續,排除規則將對此次備份的所有壓縮操作生效', + default_download_path: '預設下載網址', + saveLocal: '同時保留本機備份(和雲端儲存保留份數一致)', + url: 'URL 地址', + urlHelper: '請輸入正確的 URL 地址', + targetHelper: '備份帳號可在面板設定中維護', + ignoreApp: '排除應用', + withImage: '備份應用映像', + retainCopies: '保留份數', + retryTimes: '失敗重試次數', + timeout: '逾時時間', + ignoreErr: '忽略錯誤', + ignoreErrHelper: '忽略備份過程中出現的錯誤,保證所有備份任務執行', + retryTimesHelper: '為 0 表示失敗後不重試', + retainCopiesHelper: '執行記錄及日誌保留份數', + retainCopiesHelper1: '備份文件保留份數', + retainCopiesUnit: ' 份 (查看)', + cronSpecRule: '第 {0} 行中執行週期格式錯誤,請檢查後重試!', + cronSpecRule2: '執行週期格式錯誤,請檢查後重試!', + perMonthHelper: '每月 {0} 日 {1}:{2} 執行', + perWeekHelper: '每週 {0} {1}:{2} 執行', + perDayHelper: '每日 {0}:{1} 執行', + perHourHelper: '每小時 {0}分 執行', + perNDayHelper: '每 {0} 日 {1}:{2} 執行', + perNHourHelper: '每 {0}小時 {1}分 執行', + perNMinuteHelper: '每 {0}分 執行', + perNSecondHelper: '每 {0}秒 執行', + perMonth: '每月', + perWeek: '每週', + perHour: '每小時', + perNDay: '每 N 日', + perDay: '每天', + perNHour: '每 N 時', + perNMinute: '每 N 分鐘', + perNSecond: '每 N 秒', + day: '日', + monday: '週一', + tuesday: '週二', + wednesday: '週三', + thursday: '週四', + friday: '週五', + saturday: '週六', + sunday: '週日', + shellContent: '腳本內容', + executor: '解釋器', + errRecord: '錯誤的日誌記錄', + errHandle: '任務執行失敗', + noRecord: '目前計劃任務暫未產生記錄', + cleanData: '刪除備份文件', + cleanRemoteData: '刪除遠端備份檔案', + cleanDataHelper: '刪除該任務執行過程中產生的備份文件', + noLogs: '暫無任務輸出...', + errPath: '備份路徑 [{0}] 錯誤,無法下載!', + cutWebsiteLog: '切割網站日誌', + cutWebsiteLogHelper: '切割的日誌檔案會備份到 1Panel 的 backup 目錄下', + syncIpGroup: '同步 WAF IP 組', + + requestExpirationTime: '上傳請求過期時間(小時)', + unitHours: '單位:小時', + alertTitle: '計劃任務-{0}「{1}」任務失敗告警', + library: { + script: '腳本', + syncNow: '立即同步', + turnOnSync: '開啟自動同步', + turnOnSyncHelper: '開啟自動同步將在每天凌晨時段進行自動同步', + turnOffSync: '關閉自動同步', + turnOffSyncHelper: '關閉自動同步可能導致腳本同步不及時,是否確認?', + isInteractive: '互動式', + interactive: '互動式腳本', + interactiveHelper: '在腳本執行過程中需要使用者輸入參數或做出選擇,且無法用於計劃任務中。', + library: '腳本庫', + remoteLibrary: '遠端腳本庫', + create: '新增腳本', + edit: '修改腳本', + groupHelper: '根據腳本特徵設定不同的分組,可以更快地對腳本進行篩選操作。', + handleHelper: '將在 {0} 上執行 {1} 腳本,是否繼續?', + noSuchApp: '未檢測到 {0} 服務,請前往腳本庫頁面手動安裝!', + syncHelper: '即將同步系統腳本庫,該操作僅針對系統腳本,是否繼續?', + }, + }, + monitor: { + globalFilter: '全域過濾', + enableMonitor: '監控狀態', + storeDays: '保存天數', + defaultNetwork: '預設網卡', + defaultNetworkHelper: '預設監控和概覽介面顯示的網卡選項', + defaultIO: '預設磁碟', + defaultIOHelper: '預設監控和概覽介面顯示的磁碟選項', + cleanMonitor: '清空監控記錄', + cleanHelper: '該操作將清空包括 GPU 在內的所有監控記錄,是否繼續?', + + avgLoad: '平均負載', + loadDetail: '負載詳情', + resourceUsage: '資源使用率', + networkCard: '網卡', + read: '讀取', + write: '寫入', + readWriteCount: '讀寫次數', + readWriteTime: '讀寫延遲', + today: '今天', + yesterday: '昨天', + lastNDay: '近 {0} 天', + lastNMonth: '近 {0} 月', + lastHalfYear: '近半年', + memory: '記憶體', + percent: '佔比', + cache: '快取', + disk: '磁碟', + network: '網路', + up: '上行', + down: '下行', + interval: '採集間隔', + intervalHelper: '請輸入合適的監控採集時間間隔(5秒 - 12小時)', + }, + terminal: { + local: '本機', + defaultConn: '預設連接', + defaultConnHelper: '該操作將【{0}】開啟終端後自動連線到所在節點終端,是否繼續?', + withReset: '重置連線資訊', + localConnJump: '預設連線資訊在【終端 - 設定】中維護,連線失敗可前往編輯!', + localHelper: 'local 名稱僅用於系統本機標識', + connLocalErr: '無法自動認證,請填寫本機伺服器的登入資訊!', + testConn: '連接測試', + saveAndConn: '儲存並連接', + connTestOk: '連接資訊可用', + connTestFailed: '連接不可用,請檢查連接資訊!', + host: '主機', + createConn: '建立連接', + noHost: '暫無主機', + groupChange: '切換分組', + expand: '全部展開', + fold: '全部收縮', + batchInput: '批次輸入', + quickCommand: '快速指令', + noSuchCommand: '匯入的CSV文件中未能發現快速指令資料,請檢查後重試!', + quickCommandHelper: '常用命令列表,用於在終端介面底部快速選擇', + groupDeleteHelper: '移除組後,組內所有連接將遷移到 default 組內,是否繼續?', + command: '指令', + addHost: '新增主機', + localhost: '本機伺服器', + ip: '主機地址', + authMode: '認證方式', + passwordMode: '密碼認證', + rememberPassword: '記住認證資訊', + keyMode: '私鑰認證', + key: '私鑰', + keyPassword: '私鑰密碼', + emptyTerminal: '暫無終端連接', + lineHeight: '字體行高', + letterSpacing: '字體間距', + fontSize: '字體大小', + cursorBlink: '游標閃爍', + cursorStyle: '游標樣式', + cursorUnderline: '下劃線', + cursorBlock: '塊狀', + cursorBar: '條形', + scrollback: '滾動行數', + scrollSensitivity: '滾動速度', + saveHelper: '是否確認儲存目前終端配置?', + }, + toolbox: { + common: { + toolboxHelper: '部分安裝和使用問題,可參考', + }, + swap: { + swap: 'Swap', + swapHelper1: 'Swap 的大小應該是物理記憶體的 1 到 2 倍,可根據具體情況進行調整;', + swapHelper2: '在建立 Swap 文件之前,請確保系統硬碟有足夠的可用空間,Swap 文件的大小將佔用相應的磁碟空間;', + swapHelper3: + 'Swap 可以幫助紓解記憶體壓力,但僅是一個備選項,過多依賴可能導致系統效能下降,建議優先考慮增加記憶體或者最佳化應用程式記憶體使用;', + swapHelper4: '建議定期監控 Swap 的使用情況,以確保系統正常執行。', + swapDeleteHelper: '此操作將移除 Swap 分區 {0},出於系統安全考慮,不會自動刪除該文件,如需刪除請手動操作!', + saveHelper: '請先儲存目前設定!', + saveSwap: '儲存目前配置將調整 Swap 分區 {0} 大小到 {1},是否繼續?', + swapMin: '分區大小最小值為 40 KB,請修改後重試!', + swapMax: '分區大小最大值為 {0},請修改後重試!', + swapOff: '分區大小最小值為 40 KB,設定為 0 則關閉 Swap 分區。', + }, + device: { + dnsHelper: '伺服器地址域名解析', + dnsAlert: '請注意!修改 /etc/resolv.conf 文件的配置時,重啟系統後會將文件復原為預設值', + dnsHelper1: '當存在多個DNS時,需換行顯示,例:\n114.114.114.114\n8.8.8.8', + hostsHelper: '主機名稱解析', + hosts: '域名', + hostAlert: '隱藏了已註釋的紀錄,請點擊 全部配置 按鈕以查看或設定', + toolbox: '快速設定', + hostname: '主機名稱', + passwd: '系統密碼', + passwdHelper: '輸入的字元不能包含 $ 和 &', + timeZone: '系統時區', + localTime: '伺服器時間', + timeZoneChangeHelper: '修改系統時區需要重新啟動服務,是否繼續?', + timeZoneHelper: '時區修改依賴於 timedatectl 指令,如未安裝可能導致修改失敗', + timeZoneCN: '北京', + timeZoneAM: '洛杉磯', + timeZoneNY: '紐約', + ntpALi: '阿里', + ntpGoogle: 'Google', + syncSite: 'NTP 伺服器', + syncSiteHelper: '該操作將使用 {0} 作為源進行系統時間同步,是否繼續?', + hostnameHelper: '主機名稱修改依賴於 hostnamectl 指令,如未安裝可能導致修改失敗', + userHelper: '使用者名稱依賴於 whoami 指令取得,如未安裝可能導致取得失敗。', + passwordHelper: '密碼修改依賴於 chpasswd 指令,如未安裝可能導致修改失敗', + hostHelper: '填寫的內容中存在空值,請檢查修改後重試!', + dnsCheck: '測試可用性', + dnsOK: 'DNS 配置資訊可用!', + dnsTestFailed: 'DNS 配置資訊不可用,請修改後重試!', + }, + fail2ban: { + sshPort: '監聽 SSH 埠', + sshPortHelper: '目前 Fail2ban 監聽主機 SSH 連接埠', + unActive: '目前未開啟 Fail2ban 服務,請先開啟!', + operation: '對 Fail2ban 服務進行 [{0}] 操作,是否繼續?', + fail2banChange: 'Fail2ban 配置修改', + ignoreHelper: '白名單中的 IP 列表將被忽略封鎖,是否繼續?', + bannedHelper: '黑名單中的 IP 列表將被伺服器封鎖,是否繼續?', + maxRetry: '最大重試次數', + banTime: '停用時間', + banTimeHelper: '預設停用時間為 10 分鐘,停用時間為 -1 則表示永久停用', + banTimeRule: '請輸入正確的停用時間或 -1', + banAllTime: '永久停用', + findTime: '發現週期', + banAction: '停用方式', + banActionOption: '通過 {0} 來停用指定的 IP 位址', + allPorts: ' (所有埠)', + ignoreIP: 'IP 白名單', + bannedIP: 'IP 黑名單', + logPath: '日誌路徑', + logPathHelper: '預設為 /var/log/secure 或者 /var/log/auth.log', + }, + ftp: { + ftp: 'FTP 帳戶', + notStart: '目前未 FTP 服務,請先開啟!', + operation: '對 FTP 服務進行 [{0}] 操作,是否繼續?', + noPasswdMsg: '無法取得目前 FTP 帳號密碼,請先設定密碼後重試!', + enableHelper: '啟用選取的 FTP 帳號後,該 FTP 帳號將復原訪問權限,是否繼續操作?', + disableHelper: '停用選取的 FTP 帳號後,該 FTP 帳號將失去訪問權限,是否繼續操作?', + syncHelper: '同步伺服器與資料庫中的 FTP 帳戶資料,是否繼續操作?', + dirSystem: '該目錄為系統保留目錄,修改可能導致系統崩潰,請修改後重試!', + dirHelper: '開啟 FTP 需要修改目錄權限,請謹慎選擇', + dirMsg: '開啟 FTP 將修改整個 {0} 目錄權限,是否繼續?', + }, + clam: { + clam: '病毒掃描', + cron: '定時掃描', + cronHelper: '專業版支援定時掃描功能', + specErr: '執行週期格式錯誤,請檢查後重試!', + disableMsg: '停止定時執行會導致該掃描任務不再自動執行。是否繼續?', + enableMsg: '啟用定時執行會讓該掃描任務定期自動執行。是否繼續?', + showFresh: '顯示病毒庫服務', + hideFresh: '隱藏病毒庫服務', + clamHelper: 'ClamAV 建議最低配置:3 GiB 以上記憶體、單核 2.0 GHz 以上 CPU,以及至少 5 GiB 可用硬碟空間。', + notStart: '目前未開啟 ClamAV 服務,請先開啟!', + removeRecord: '刪除報告文件', + noRecords: '點擊「執行」按鈕開始掃描,掃描結果將會記錄在這裡。', + removeInfected: '刪除病毒文件', + removeInfectedHelper: '刪除任務檢測到的病毒文件,以確保伺服器的安全和正常執行。', + clamCreate: '建立掃描規則', + infectedStrategy: '感染文件策略', + removeHelper: '刪除病毒文件,請謹慎選擇!', + move: '移動', + moveHelper: '將病毒文件移動到指定目錄下', + copyHelper: '將病毒文件複製到指定目錄下', + none: '不操作', + noneHelper: '不對病毒文件採取任何操作', + scanDir: '掃描目錄', + infectedDir: '隔離目錄', + scanDate: '掃描時間', + scanResult: '掃描報告條數', + tail: '日誌顯示行數', + infectedFiles: '感染文件數', + log: '詳情', + clamConf: '掃描配置', + clamLog: '掃描日誌', + freshClam: '病毒庫重新整理配置', + freshClamLog: '病毒庫重新整理日誌', + alertHelper: '專業版支援定時掃描和簡訊告警功能', + alertTitle: '病毒掃描「{0}」任務檢測到感染文件告警', + }, + }, + logs: { + core: '面板服務', + agent: '節點監控', + panelLog: '面板日誌', + operation: '操作日誌', + login: '訪問日誌', + loginIP: '登入 IP', + loginAddress: '登入地址', + loginAgent: '使用者代理', + loginStatus: '登入狀態', + system: '系統日誌', + deleteLogs: '清空日誌', + resource: '資源', + detail: { + dashboard: '概覽', + ai: 'AI', + groups: '分組', + hosts: '主機', + apps: '應用', + websites: '網站', + containers: '容器', + files: '文件管理', + runtimes: '執行環境', + process: '行程管理', + toolbox: '工具箱', + backups: '備份 / 還原', + tampers: '防篡改', + xsetting: '介面設定', + logs: '日誌審計', + settings: '面板設定', + cronjobs: '計劃任務', + databases: '資料庫', + waf: 'WAF', + licenses: '許可證', + nodes: '節點', + commands: '快速指令', + }, + websiteLog: '網站日誌', + runLog: '執行日誌', + errLog: '錯誤日誌', + task: '任務日誌', + taskName: '任務名稱', + taskRunning: '執行中', + }, + file: { + fileDirNum: '共 {0} 個目錄,{1} 個檔案,', + currentDir: '目前目錄', + dir: '資料夾', + upload: '上傳', + download: '下載', + uploadFile: '@:file.upload@:file.file', + uploadDirectory: '@:file.upload@:file.dir', + fileName: '檔案名', + search: '在目前目錄下尋找', + mode: '權限', + editPermissions: '編輯@:file.mode', + owner: '所有者', + file: '文件', + remoteFile: '遠端下載', + share: '分享', + sync: '資料同步', + size: '大小', + updateTime: '修改時間', + rename: '重新命名', + role: '權限', + info: '屬性', + linkFile: '軟連接文件', + shareList: '分享列表', + zip: '壓縮', + group: '使用者群組', + path: '路徑', + public: '公共', + setRole: '設定權限', + link: '是否連結', + rRole: '讀取', + wRole: '寫入', + xRole: '可執行', + compress: '壓縮', + deCompress: '解壓', + compressType: '壓縮格式', + compressDst: '壓縮路徑', + replace: '覆蓋已存在的文件', + compressSuccess: '壓縮成功', + deCompressSuccess: '解壓成功', + deCompressDst: '解壓路徑', + linkType: '連結類型', + softLink: '軟連結', + hardLink: '硬連結', + linkPath: '連結路徑', + selectFile: '選擇文件', + downloadSuccess: '下載成功', + downloadUrl: '下載網址', + downloadStart: '下載開始!', + moveSuccess: '移動成功', + copySuccess: '複製成功', + pasteMsg: '請在目標目錄點擊右上角【貼上】按鈕', + move: '移動', + calculate: '計算', + remark: '備註', + setRemark: '設定備註', + remarkPrompt: '請輸入備註內容', + remarkPlaceholder: '備註內容', + remarkToggle: '備註', + remarkToggleTip: '載入檔案備註', + canNotDeCompress: '無法解壓此文件', + uploadSuccess: '上傳成功!', + downloadProcess: '下載進度', + downloading: '正在下載...', + infoDetail: '文件屬性', + root: '根目錄', + list: '文件列表', + sub: '子目錄', + theme: '主題', + language: '語言', + eol: '行尾符', + copyDir: '複製路徑', + paste: '貼上', + changeOwner: '修改使用者和使用者群組', + containSub: '同時修改子文件屬性', + ownerHelper: 'PHP 執行環境預設使用者:使用者群組為 1000:1000, 容器內外使用者顯示不一致為正常現象', + searchHelper: '支援 * 等萬用字元', + uploadFailed: '【{0}】 文件上傳失敗', + fileUploadStart: '正在上傳【{0}】....', + currentSelect: '目前選中: ', + unsupportedType: '不支援的文件類型', + deleteHelper: '確定刪除所選檔案? 預設刪除之後將進入回收站?', + fileHelper: '注意:1. 搜尋結果不支援排序功能 2. 資料夾無法依大小排序。', + forceDeleteHelper: '永久刪除檔案(不進入回收站,直接刪除)', + recycleBin: '回收站', + sourcePath: '原路徑', + deleteTime: '刪除時間', + reduce: '還原', + confirmReduce: '確定還原以下文件?', + reduceSuccess: '還原成功', + reduceHelper: '如果原路徑存在同名檔案或目錄,將會被覆蓋,是否繼續?', + clearRecycleBin: '清空回收站', + clearRecycleBinHelper: '是否清空回收站?', + favorite: '收藏夾', + removeFavorite: '是否從收藏夾移出?', + addFavorite: '新增/移出收藏夾', + clearList: '清空列表', + deleteRecycleHelper: '確定永久刪除以下文件?', + typeErrOrEmpty: '【{0}】 檔案類型錯誤或為空資料夾', + dropHelper: '將需要上傳的文件拖曳到此處', + fileRecycleBin: '檔案回收站', + fileRecycleBinMsg: '已{0}回收站', + wordWrap: '自動換行', + deleteHelper2: '確定刪除所選檔案? 刪除操作不可回滾', + ignoreCertificate: '忽略不可信證書', + ignoreCertificateHelper: + '下載時忽略不可信證書可能導致資料洩露或篡改。請謹慎使用此選項,僅在信任下載源的情況下啟用', + uploadOverLimit: '文件數量超過 1000! 請壓縮後上傳', + clashDitNotSupport: '檔名禁止包含 .1panel_clash', + clashDeleteAlert: '回收站資料夾不能刪除', + clashOpenAlert: '回收站目錄請點選【回收站】按鈕開啟', + right: '前進', + back: '後退', + top: '返回上一層', + up: '上一層', + openWithVscode: 'VS Code 打開', + vscodeHelper: '請確保本機已安裝 VS Code 並配置了 SSH Remote 外掛程式', + saveContentAndClose: '檔案已被修改,是否儲存並關閉?', + saveAndOpenNewFile: '檔案已被修改,是否儲存並打開新檔案?', + noEdit: '檔案未修改,無需此操作!', + noNameFolder: '未命名資料夾', + noNameFile: '未命名檔案', + minimap: '縮圖', + fileCanNotRead: '此文件不支援預覽', + previewTruncated: '檔案過大,僅顯示末尾內容', + previewEmpty: '檔案內容為空或不是文字檔案', + previewLargeFile: '預覽', + panelInstallDir: '1Panel 安裝目錄不能刪除', + wgetTask: '下載任務', + existFileTitle: '同名檔案提示', + existFileHelper: '上傳的檔案存在同名檔案,是否覆蓋?', + existFileSize: '檔案大小(新->舊)', + existFileDirHelper: '選擇的檔案/資料夾存在同名,請謹慎操作!', + coverDirHelper: '選取要覆蓋的資料夾,將複製到目標路徑!', + noSuchFile: '找不到該檔案或目錄,請檢查後重試。', + setting: '設定', + showHide: '顯示隱藏檔案', + noShowHide: '不顯示隱藏檔案', + cancelUpload: '取消上傳', + cancelUploadHelper: '是否取消上傳,取消後將清空上傳列表', + keepOneTab: '至少保留一個分頁', + notCanTab: '無法新增更多分頁', + convert: '檔案格式轉換', + converting: '轉換為', + fileCanNotConvert: '此文件不支援轉換格式', + formatType: '格式類型', + sourceFormat: '來源格式', + sourceFile: '來源檔案', + saveDir: '儲存目錄', + deleteSourceFile: '是否刪除來源檔案', + convertHelper: '將選中的檔案轉換為其他格式', + convertHelper1: '請選擇需要轉換格式的檔案', + execConvert: '開始轉換,可在任務中心查看轉換日誌', + convertLogs: '轉換日誌', + formatConvert: '格式轉換', + }, + ssh: { + autoStart: '開機自啟', + enable: '設定開機自啟', + disable: '關閉開機自啟', + sshAlert: '列表資料根據登入時間排序,但請注意,切換時區或其他操作可能導致登入日誌的時間出現偏差。', + sshAlert2: '您可以透過工具箱中的 Fail2ban 封鎖嘗試暴力破解的 IP 位址,從而提高主機的安全性。', + sshOperate: '對 SSH 服務進行 [{0}] 操作,是否繼續?', + sshChange: 'SSH 配置修改', + sshChangeHelper: '此操作將 {0} 修改為 [{1}] ,是否繼續?', + sshFileChangeHelper: '直接修改設定檔可能會導致服務不可用,請謹慎操作,是否繼續?', + port: '連接埠', + portHelper: '指定 SSH 服務監聽的埠號,預設為 22。', + listenAddress: '監聽地址', + allV4V6: '0.0.0.0:{0}(IPv4)和 :::{0}(IPv6)', + listenHelper: '同時取消 IPv4 和 IPv6 設定,將會同時監聽 0.0.0.0:{0}(IPv4) 和 :::{0}(IPv6)', + addressHelper: '指定 SSH 服務監聽的 IP 位址', + permitRootLogin: 'root 使用者', + rootSettingHelper: 'root 使用者 SSH 登入方式,預設所有 SSH 登入。', + rootHelper1: '允許 SSH 登入', + rootHelper2: '禁止 SSH 登入', + rootHelper3: '僅允許金鑰登入', + rootHelper4: '僅允許執行預先定義的指令,不能進行其他操作', + passwordAuthentication: '密碼認證', + pwdAuthHelper: '是否啟用密碼認證,預設啟用。', + pubkeyAuthentication: '金鑰認證', + privateKey: '私鑰', + publicKey: '公鑰', + password: '密碼', + createMode: '建立方式', + generate: '自動生成', + unSyncPass: '金鑰密碼無法同步', + syncHelper: '同步操作將清理失效金鑰並同步新增的完整金鑰對,是否繼續?', + input: '手動輸入', + import: '文件上傳', + authKeys: '授權金鑰', + authKeysHelper: '是否儲存目前公鑰資訊?', + pubkey: '金鑰資訊', + pubKeyHelper: '目前金鑰資訊僅對使用者 {0} 生效', + encryptionMode: '加密方式', + passwordHelper: '支援大小寫英文、數字,長度6-10', + reGenerate: '重新生成金鑰', + keyAuthHelper: '是否啟用金鑰認證,預設啟用。', + useDNS: '反向解析', + dnsHelper: '控制 SSH 伺服器是否啟用 DNS 解析功能,從而驗證連接方的身份。', + analysis: '統計資訊', + denyHelper: '將對下列地址進行【封鎖】操作,設定後該 IP 將禁止訪問伺服器,是否繼續?', + acceptHelper: '將對下列地址進行【放行】操作,設定後該 IP 將復原正常訪問,是否繼續?', + noAddrWarning: '目前未選中任何可【{0}】地址,請檢查後重試!', + loginLogs: '登入日誌', + loginMode: '登入方式', + authenticating: '金鑰', + publickey: '金鑰', + belong: '歸屬地', + local: '內網', + remote: '外網', + session: '工作階段', + loginTime: '登入時間', + loginIP: '登入IP', + stopSSHWarn: '是否斷開此SSH連接', + }, + setting: { + panel: '面板', + user: '面板使用者', + userChange: '修改面板使用者', + userChangeHelper: '修改面板使用者將退出登入,是否繼續?', + passwd: '面板密碼', + emailHelper: '用於密碼找回', + watermark: '浮水印設定', + watermarkContent: '浮水印內容', + contentHelper: '{0} 表示節點名稱,{1} 表示節點地址,既可以使用變數,也可以填寫自訂名稱。', + watermarkColor: '浮水印顏色', + watermarkFont: '浮水印字型大小', + watermarkHeight: '浮水印高度', + watermarkWidth: '浮水印寬度', + watermarkRotate: '旋轉角度', + watermarkGap: '間距', + watermarkCloseHelper: '是否確認關閉系統浮水印設定?', + watermarkOpenHelper: '是否確認儲存目前系統浮水印設定?', + title: '面板別名', + titleHelper: '支援長度 3 至 30 的英文字母、中文、數字、空格和常見的特殊符號。', + panelPort: '面板埠', + portHelper: '建議埠範圍8888 - 65535,注意:有安全組的伺服器請提前在安全組放行新埠', + portChange: '埠修改', + portChangeHelper: '服務埠修改需要重啟服務,是否繼續?', + theme: '主題顏色', + menuTabs: '選單分頁', + componentSize: '元件大小', + dark: '暗色', + darkGold: '黑金', + light: '亮色', + auto: '跟隨系統', + language: '系統語言', + languageHelper: '預設跟隨瀏覽器語言,設定後只對目前瀏覽器生效,更換瀏覽器後需要重新設定', + sessionTimeout: '超時時間', + sessionTimeoutError: '最小超時時間為 300 秒', + sessionTimeoutHelper: '如果使用者超過 {0} 秒未操作面板,面板將自動退出登入', + systemIP: '預設存取地址', + systemIPHelper: '應用跳轉、容器存取等功能將使用此地址進行路由。每個節點可設定不同地址。', + proxy: '代理伺服器', + proxyHelper: '設定代理伺服器後,將在以下場景中生效:', + proxyHelper1: '應用商店的安裝包下載和同步(專業版功能)', + proxyHelper2: '系統版本升級及取得更新說明(專業版功能)', + proxyHelper3: '系統許可證的驗證和同步', + proxyHelper4: 'Docker 的網路訪問將透過代理伺服器進行(專業版功能)', + proxyHelper5: '系統類型腳本庫的統一下載與同步(專業版功能)', + proxyHelper6: '申請證書(專業版功能)', + proxyType: '代理類型', + proxyUrl: '代理地址', + proxyPort: '代理埠', + proxyPasswdKeep: '記住密碼', + proxyDocker: 'Docker 代理', + proxyDockerHelper: '將代理伺服器配寘同步至 Docker,支援離線伺服器拉取鏡像等操作', + syncToNode: '同步至子節點', + syncToNodeHelper: '同步設定至其他節點', + nodes: '節點', + selectNode: '選擇節點', + selectNodeError: '請選擇節點', + apiInterface: 'API 介面', + apiInterfaceClose: '關閉後將不能使用 API 介面進行訪問,是否繼續?', + apiInterfaceHelper: '提供面板支援 API 介面訪問', + apiInterfaceAlert1: '請不要在生產環境開啟,這可能新增伺服器安全風險', + apiInterfaceAlert2: '請不要使用協力廠商應用呼叫面板 API,以防止潛在的安全威脅。', + apiInterfaceAlert3: 'API 介面檔案', + apiInterfaceAlert4: '使用檔案', + apiKey: '介面金鑰', + apiKeyHelper: '介面金鑰用於外部應用訪問 API 介面', + ipWhiteList: 'IP白名單', + ipWhiteListEgs: '當存在多個 IP 時,需要換行顯示,例:\n172.16.10.111 \n172.16.10.0/24', + ipWhiteListHelper: + '必需在 IP 白名單清單中的 IP 才能訪問面板 API 介面,0.0.0.0/0(所有 IPv4),::/0(所有 IPv6)', + apiKeyValidityTime: '介面金鑰有效期', + apiKeyValidityTimeEgs: '介面金鑰有效期(組織分)', + apiKeyValidityTimeHelper: '介面時間戳記到請求時的目前時間戳之間有效(組織分),設定為0時,不做時間戳記校驗', + apiKeyReset: '介面金鑰重設', + apiKeyResetHelper: '重設金鑰後,已關聯金鑰服務將失效,請重新新增新金鑰至服務。', + confDockerProxy: '配寘 Docker 代理', + restartNowHelper: '配寘 Docker 代理需要重啟 Docker 服務。', + restartNow: '立即重啟', + restartLater: '稍後手動重啟', + systemIPWarning: '目前節點尚未配置預設存取地址,請前往面板設定進行設定!', + systemIPWarning1: '目前伺服器地址設定為 {0},無法快速跳轉!', + changePassword: '密碼修改', + oldPassword: '原密碼', + newPassword: '新密碼', + retryPassword: '確認密碼', + noSpace: '輸入資訊不能包括空格符號', + duplicatePassword: '新密碼不能與原始密碼一致,請重新輸入!', + diskClean: '快取清理', + developerMode: '預覽體驗計劃', + developerModeHelper: '取得 1Panel 的預覽版本,以分享有關新功能和更新的回饋', + + thirdParty: '第三方帳號', + scope: '使用範圍', + public: '公有', + publicHelper: '公有類型的備份帳號會同步到各個子節點,子節點可以一起使用', + private: '私有', + privateHelper: '私有類型的備份帳號只建立在目前節點上,僅供目前節點使用', + noTypeForCreate: '目前無可建立備份類型', + LOCAL: '伺服器磁碟', + OSS: '阿里雲 OSS', + S3: '亞馬遜 S3 雲端儲存', + mode: '模式', + MINIO: 'MINIO', + SFTP: 'SFTP', + WebDAV: 'WebDAV', + WebDAVAlist: 'WebDAV 連接 Alist 可參考官方文件', + UPYUN: '又拍雲', + ALIYUN: '阿里雲端硬碟', + ALIYUNHelper: '目前阿里雲端硬碟非用戶端下載最大限制為 100 MB,超過限制需要透過用戶端下載', + ALIYUNRecover: + '目前阿里雲端硬碟非用戶端下載最大限制為 100 MB,超過限制需要透過用戶端下載到本機後,同步快照進行復原', + GoogleDrive: 'Google雲端硬碟', + analysis: '解析', + analysisHelper: '貼上整個 token 內容,自動解析所需部分,具體操作可參考官方文件', + serviceName: '服務名稱', + operator: '操作員', + OneDrive: '微軟 OneDrive', + isCN: '世紀互聯', + isNotCN: '國際版', + client_id: '用戶端 ID', + client_secret: '用戶端金鑰', + redirect_uri: '重定向 URL', + onedrive_helper: '自訂配置可參考官方文件', + clickToRefresh: '單擊可手動重新整理', + refreshTime: '令牌重新整理時間', + refreshStatus: '令牌重新整理狀態', + codeWarning: '目前授權碼格式錯誤,請重新確認!', + backupDir: '備份目錄', + code: '授權碼', + codeHelper: + '請點擊取得按鈕,然後登入 {0} 複製跳轉連結中 code 後面的內容,貼到該輸入框中,具體操作可參考官方文件。', + googleHelper: '請先建立Google應用並取得用戶端資訊,填寫表單後點擊取得按鈕,具體操作可參考官方文件。', + loadCode: '取得', + COS: '騰訊雲 COS', + ap_beijing_1: '北京一區', + ap_beijing: '北京', + ap_nanjing: '南京', + ap_shanghai: '上海', + ap_guangzhou: '廣州', + ap_chengdu: '成都', + ap_chongqing: '重慶', + ap_shenzhen_fsi: '深圳金融', + ap_shanghai_fsi: '上海金融', + ap_beijing_fsi: '北京金融', + ap_hongkong: '中國香港', + ap_singapore: '新加坡', + ap_mumbai: '孟買', + ap_jakarta: '雅加達', + ap_seoul: '首爾', + ap_bangkok: '曼谷', + ap_tokyo: '東京', + na_siliconvalley: '矽谷(美西)', + na_ashburn: '維吉尼亞(美東)', + na_toronto: '多倫多', + sa_saopaulo: '聖保羅', + eu_frankfurt: '法蘭克福', + KODO: '七牛雲 Kodo', + scType: '儲存類型', + typeStandard: '標準儲存', + typeStandard_IA: '低頻儲存', + typeArchive: '歸檔儲存', + typeDeep_Archive: '深度歸檔儲存', + scLighthouse: '預設,輕量物件儲存僅支援該儲存類型', + scStandard: '標準儲存,適用於即時訪問的大量熱點文件、頻繁的資料互動等業務場景。', + scStandard_IA: '低頻儲存,適用於較低訪問頻率(例如平均每月訪問頻率1到2次)的業務場景,最少儲存30天。', + scArchive: '歸檔儲存,適用於極低訪問頻率(例如半年訪問1次)的業務場景。', + scDeep_Archive: '深度歸檔儲存,適用於極低訪問頻率(例如1年訪問1~2次)的業務場景。', + archiveHelper: '歸檔儲存的文件無法直接下載,需要先在對應的雲服務商網站進行復原操作,請謹慎使用!', + backupAlert: '理論上只要雲廠商相容 S3 協議,就可以用現有的亞馬遜 S3 雲端儲存來備份,具體配置參考 ', + domain: '加速域名', + backupAccount: '備份帳號', + loadBucket: '取得桶', + accountName: '帳戶名稱', + accountKey: '帳戶金鑰', + address: '地址', + path: '路徑', + backupJump: '未在目前備份列表中的備份檔案,請嘗試從檔案目錄中下載後匯入備份。', + + snapshot: '快照', + noAppData: '暫無可選擇系統應用', + noBackupData: '暫無可選擇備份資料', + stepBaseData: '基礎資料', + stepAppData: '系統應用', + stepPanelData: '系統資料', + stepBackupData: '備份資料', + stepOtherData: '其他資料', + operationLog: '保留操作日誌', + loginLog: '保留訪問日誌', + systemLog: '保留系統日誌', + taskLog: '保留任務日誌', + monitorData: '保留監控資料', + dockerConf: '保留 Docker 配置', + selectAllImage: '備份所有應用鏡像', + logLabel: '日誌', + agentLabel: '節點配置', + appDataLabel: '應用資料', + appImage: '應用鏡像', + appBackup: '應用備份', + backupLabel: '備份目錄', + confLabel: '設定檔', + dockerLabel: '容器', + taskLabel: '計劃任務', + resourceLabel: '應用資源目錄', + runtimeLabel: '執行環境', + appLabel: '應用', + databaseLabel: '資料庫', + snapshotLabel: '快照文件', + websiteLabel: '網站', + directoryLabel: '目錄', + appStoreLabel: '應用商店', + shellLabel: '腳本', + tmpLabel: '暫存資料夾', + sslLabel: '證書目錄', + reCreate: '建立快照失敗', + reRollback: '回滾快照失敗', + deleteHelper: '將刪除該快照的所有備份文件,包括第三方備份帳號中的文件。', + ignoreRule: '排除規則', + editIgnoreRule: '@:commons.button.edit@:setting.ignoreRule', + ignoreHelper: '快照時將使用該規則對 1Panel 資料目錄進行壓縮備份,請謹慎修改。', + ignoreHelper1: '一行一個,例: \n*.log\n/opt/1panel/cache', + status: '快照狀態', + panelInfo: '寫入 1Panel 基礎資訊', + panelBin: '備份 1Panel 系統檔案', + daemonJson: '備份 Docker 設定檔', + appData: '備份 1Panel 已安裝應用', + panelData: '備份 1Panel 資料目錄', + backupData: '備份 1Panel 本機備份目錄', + compress: '製作快照文件', + upload: '上傳快照文件', + recoverDetail: '復原詳情', + recoverFailed: '快照復原失敗', + createSnapshot: '建立快照', + importSnapshot: '同步快照', + importHelper: '快照文件目錄:', + lastRecoverAt: '上次復原時間', + lastRollbackAt: '上次回滾時間', + reDownload: '重新下載備份文件', + recoverErrArch: '不支援在不同伺服器架構之間進行快照復原操作!', + recoverErrSize: '檢測到目前磁碟空間不足,請檢查或清理後重試!', + recoverHelper: '即將從快照 {0} 開始復原,復原前請確認以下資訊:', + recoverHelper1: '復原需要重新啟動 Docker 以及 1Panel 服務', + recoverHelper2: '請確保伺服器磁碟空間充足 ( 快照檔案大小: {0}, 可用空間: {1} )', + recoverHelper3: '請確保伺服器架構與建立快照伺服器架構資訊保持一致 (目前伺服器架構: {0} )', + rollback: '回滾', + rollbackHelper: + '即將回滾本次復原,回滾將取代所有本次復原的檔案,過程中可能需要重新啟動 Docker 以及 1Panel 服務,是否繼續?', + + upgradeRecord: '升級記錄', + upgrading: '正在升級中,請稍候...', + upgradeHelper: '升級操作需要重啟 1Panel 服務,是否繼續?', + noUpgrade: '目前已經是最新版本', + versionHelper: '1Panel 版本號命名規則為: [大版本].[功能版本].[Bug 修復版本],例:', + rollbackLocalHelper: '主節點暫不支援直接回滾,請手動執行「1pctl restore」指令回滾!', + upgradeCheck: '檢查更新', + upgradeNotes: '更新內容', + upgradeNow: '立即更新', + source: '下載源', + versionNotSame: '節點版本與主節點不一致,請在節點管理中升級後重試。', + versionCompare: '檢測到節點 {0} 版本已是目前可升級最新版本,請檢查主節點版本後重試!', + + safe: '安全', + passkey: '通行密鑰', + passkeyManage: '管理', + passkeyHelper: '用於快速登入,最多可綁定 5 個通行密鑰', + passkeyRequireSSL: '啟用 HTTPS 後可使用通行密鑰', + passkeyNotSupported: + '目前瀏覽器或環境不支援通行密鑰 \n 請確認您已綁定域名並啟用面板 SSL, 並且訪問時使用了可信的證書', + passkeyCount: '已綁定 {0}/{1}', + passkeyName: '名稱', + passkeyNameHelper: '請輸入用於區分裝置的名稱', + passkeyAdd: '新增通行密鑰', + passkeyCreatedAt: '建立時間', + passkeyLastUsedAt: '最近使用', + passkeyDeleteConfirm: '刪除後將無法使用此通行密鑰登入,是否繼續?', + passkeyLimit: '最多可綁定 5 個通行密鑰', + passkeyFailed: '通行密鑰註冊失敗,請確認面板SSL證書是否為可信證書', + bindInfo: '監聽地址', + bindAll: '監聽所有', + bindInfoHelper: '修改服務監聽地址或協議可能導致服務不可用,是否繼續?', + ipv6: '監聽 IPv6', + bindAddress: '監聽地址', + entrance: '安全入口', + showEntrance: '啟用概覽頁未開啟提醒', + entranceHelper: '開啟安全入口後只能透過指定安全入口登入面板', + entranceError: '請輸入 5-116 位安全登入入口,僅支援輸入數字或字母', + entranceInputHelper: '安全入口設定為空時,則取消安全入口', + randomGenerate: '隨機生成', + expirationTime: '密碼過期時間', + unSetting: '未設定', + noneSetting: '為面板密碼設定過期時間,過期後需要重新設定密碼', + expirationHelper: '密碼過期時間為 [0] 天時,則關閉密碼過期功能', + days: '過期天數', + expiredHelper: '目前密碼已過期,請重新修改密碼:', + timeoutHelper: '【 {0} 天後 】面板密碼即將過期,過期後需要重新設定密碼', + complexity: '密碼複雜度驗證', + complexityHelper: '開啟後密碼必須滿足長度為 8-30 位,並包含字母、數字、至少兩種特殊字元', + bindDomain: '域名綁定', + unBindDomain: '域名解綁', + panelSSL: '面板 SSL', + panelSSLHelper: '面板 SSL 自動續期後需要手動重啟 1Panel 服務才可生效', + unBindDomainHelper: '解除域名綁定可能造成系統不安全,是否繼續?', + bindDomainHelper: '設定域名綁定後,僅能透過設定中域名訪問 1Panel 服務', + bindDomainHelper1: '綁定域名為空時,則取消域名綁定', + bindDomainWarning: '設定域名綁定後,將退出目前登入,且僅能透過設定中域名訪問 1Panel 服務,是否繼續?', + allowIPs: '授權 IP', + unAllowIPs: '取消授權', + unAllowIPsWarning: '授權 IP 為空將允許所有 IP 訪問系統,可能造成系統不安全,是否繼續?', + allowIPsHelper: '設定授權 IP 後,僅有設定中的 IP 可以訪問 1Panel 服務', + allowIPsWarning: '設定授權 IP 後,僅有設定中的 IP 可以訪問 1Panel 服務,是否繼續?', + allowIPsHelper1: '授權 IP 為空時,則取消授權 IP', + allowIPEgs: '當存在多個授權 IP 時,需要換行顯示,例: \n172.16.10.111 \n172.16.10.0/24', + mfa: '兩步驗證', + mfaClose: '關閉兩步驗證將導致服務安全性降低,是否繼續?', + secret: '金鑰', + mfaAlert: '兩步驗證密碼是基於目前時間生成,請確保伺服器時間已同步', + mfaHelper: '開啟後會驗證手機應用驗證碼', + mfaHelper1: '下載兩步驗證手機應用 如:', + mfaHelper2: '使用手機應用掃描以下二維碼,取得 6 位驗證碼', + mfaHelper3: '輸入手機應用上的 6 位數字', + mfaCode: '驗證碼', + mfaInterval: '重新整理時間(秒)', + mfaTitleHelper: '用於區分不同 1Panel 主機,修改後請重新掃描或手動新增金鑰資訊!', + mfaIntervalHelper: '修改重新整理時間後,請重新掃描或手動新增金鑰資訊!', + sslChangeHelper: 'https 設定修改需要重啟服務,是否繼續?', + sslDisable: '停用', + sslDisableHelper: '停用 https 服務,需要重啟面板才能生效,是否繼續?', + noAuthSetting: '未認證設定', + noAuthSettingHelper: '當使用者未登入且未正確輸入安全入口、授權 IP 或綁定域名時,此回應可以隱藏面板特徵。', + responseSetting: '響應設定', + help200: '幫助頁面', + error400: '錯誤請求', + error401: '未授權', + error403: '禁止訪問', + error404: '未找到', + error408: '請求超時', + error416: '無效請求', + error444: '連線已關閉', + error500: '內部伺服器錯誤', + + https: '為面板設置 HTTPS 可提升訪問安全性', + strictHelper: '非 HTTPS 流量無法連接面板', + muxHelper: '面板將同時監聽 HTTP 和 HTTPS 端口,並將 HTTP 重定向到 HTTPS,但可能會降低安全性', + certType: '證書類型', + selfSigned: '自簽名', + selfSignedHelper: '自簽證書,不被瀏覽器信任,顯示不安全是正常現象', + select: '選擇已有', + domainOrIP: '域名或 IP:', + timeOut: '過期時間:', + rootCrtDownload: '根證書下載', + primaryKey: '金鑰', + certificate: '證書', + + about: '關於', + versionItem: '目前版本', + backupCopies: '保留份數', + backupCopiesHelper: '設定用於版本回滾的升級備份保留份數。0 表示保留所有。', + backupCopiesRule: '請至少儲存 3 份升級備份記錄', + release: '版本更新日誌', + releaseHelper: '目前環境更新日誌取得異常,可手動前往官方文件查看', + project: '項目地址', + issue: '問題回饋', + doc: '官方文件', + star: '點亮 Star', + description: 'Linux 伺服器運維管理面板', + forum: '論壇求助', + doc2: '使用手冊', + currentVersion: '目前執行版本:', + + license: '許可證', + bindNode: '綁定節點', + menuSetting: '選單設定', + menuSettingHelper: '當只存在 1 個子選單時,選單列將僅展示該子選單', + showAll: '全部顯示', + hideALL: '全部隱藏', + ifShow: '是否顯示', + menu: '選單', + confirmMessage: '即將重新整理頁面更新進階功能選單列表,是否繼續?', + recoverMessage: '即將重新整理頁面,並復原選單列表至初始狀態。是否繼續?', + compressPassword: '壓縮密碼', + backupRecoverMessage: '請輸入壓縮或解壓縮密碼(留空則不設定)', + }, + license: { + offLine: '離線版', + community: '社區版', + oss: '社區版', + pro: '專業版:', + trial: '試用', + add: '新增社區版', + licenseBindHelper: '僅當授權已綁定節點時,才能使用其免費節點額度', + licenseAlert: '僅當許可證正常綁定到節點時,該許可證才能新增社區版節點,只有正常綁定到許可證的節點支援切換。', + licenseUnbindHelper: '檢測到該許可證存在社區版節點,請解除綁定後重試!', + subscription: '訂閱', + perpetual: '永久授權', + versionConstraint: '{0} 版本買斷', + forceUnbind: '強制解除綁定', + forceUnbindHelper: '強制解除綁定將忽略解除過程中產生的錯誤,最終解除許可證綁定。', + updateForce: '強制更新(忽略解除綁定過程中的所有錯誤,確保最終操作成功)', + trialInfo: '版本', + authorizationId: '訂閱授權 ID', + authorizedUser: '被授權方', + lostHelper: '許可證已達到最大重試次數,請手動點擊同步按鈕,以確保專業版功能正常使用,詳情:', + exceptionalHelper: '許可證同步驗證異常,請手動點擊同步按鈕,以確保專業版功能正常使用,詳情:', + quickUpdate: '快速更新', + power: '授權', + unbindHelper: '解除綁定後將清除該節點所有專業版相關設定,是否繼續?', + importLicense: '匯入許可證', + importHelper: '請點擊或拖動許可文件到此處', + levelUpPro: '升級專業版', + licenseSync: '許可證同步', + knowMorePro: '了解更多', + closeAlert: '目前頁面可在面板設定中關閉顯示', + introduce: '功能介紹', + waf: '升級專業版可以獲得攔截地圖、日誌、封鎖記錄、地理位置封禁、自訂規則、自訂攔截頁面等功能。', + tamper: '升級專業版可以保護網站免受未經授權的修改或篡改。', + tamperHelper: '操作失敗,該文件或資料夾已經開啟防篡改,請檢查後重試!', + setting: '升級專業版可以自訂面板 Logo、歡迎簡介等資訊。', + monitor: '升級專業版可以查看網站的即時狀態、訪客趨勢、訪客來源、請求日誌等資訊。', + alert: '升級專業版可透過簡訊接收告警資訊,並查看告警日誌,全面掌控各類關鍵事件,確保系統執行無憂。', + node: '升級專業版可以使用 1Panel 管理多台 Linux 伺服器。', + nodeApp: '升級專業版可以統一升級多節點應用版本,無需手動切換節點。', + nodeDashboard: '升級專業版可以集中管理多節點應用、網站、資料庫和計劃任務。', + fileExchange: '升級專業版可以在多台伺服器之間快速傳輸文件。', + app: '升級專業版可透過手機APP,查看服務資訊、異常監控等。', + cluster: '升級專業版可以管理 MySQL/Postgres/Redis 主從叢集。', + }, + clean: { + scan: '開始掃描', + scanHelper: '輕鬆梳理 1Panel 執行期間積累的垃圾文件', + clean: '立即清理', + reScan: '重新掃描', + cleanHelper: '已勾選文件及目錄清理後無法回滾,是否繼續?', + statusSuggest: '( 建議清理 )', + statusClean: '( 很乾淨 )', + statusEmpty: '非常乾淨,無需清理!', + statusWarning: '( 謹慎操作 )', + lastCleanTime: '上次清理時間: {0}', + lastCleanHelper: '清理文件及目錄:{0} 個, 總計清理:{1}', + cleanSuccessful: '清理成功!', + currentCleanHelper: '本次清理文件及目錄:{0} 個, 總計清理:{1}', + suggest: '( 建議清理 )', + totalScan: '待清理垃圾文件共計: ', + selectScan: '已選中垃圾文件共計: ', + + system: '系統垃圾', + systemHelper: '快照、升級等過程中產生的臨時檔案', + panelOriginal: '系統快照恢復前備份檔案', + upgrade: '系統升級備份檔案', + agentPackages: '歷史版本子節點升級/安裝套件', + upgradeHelper: '( 建議保留最新的升級備份用於系統回滾 )', + snapshot: '系統快照暫存檔', + rollback: '復原前備份目錄', + + backup: '系統備份', + backupHelper: '本地備份帳號中未關聯的備份檔案', + unknownBackup: '系統備份', + tmpBackup: '臨時備份', + unknownApp: '未關聯應用備份', + unknownDatabase: '未關聯資料庫備份', + unknownWebsite: '未關聯網站備份', + unknownSnapshot: '未關聯快照備份', + + upload: '臨時上傳文件', + uploadHelper: '系統上傳備份列表中上傳的暫存檔', + download: '臨時下載文件', + downloadHelper: '系統從第三方備份帳號下載的暫存檔', + directory: '資料夾', + + systemLog: '日誌檔案', + systemLogHelper: '系統日誌、任務日誌、網站日誌檔案', + dockerLog: '容器操作日誌檔案', + taskLog: '計劃任務執行日誌檔案', + shell: 'Shell 腳本計劃任務', + containerShell: '容器內執行 Shell 腳本計劃任務', + curl: 'CURL 計劃任務', + + docker: '容器垃圾', + dockerHelper: '容器、映像、儲存卷、建置快取等檔案', + volumes: '磁碟區', + buildCache: '容器建置快取', + + appTmpDownload: '應用程式暫存下載檔案', + unknownWebsiteLog: '未關聯網站日誌備份檔案', + }, + app: { + app: '應用', + installName: '安裝名稱', + installed: '已安裝', + all: '全部', + version: '版本', + detail: '詳情', + params: '參數', + author: '作者', + source: '來源', + appName: '應用名稱', + deleteWarn: '刪除操作會把所有資料和備份一併刪除,此操作不可回滾,是否繼續?', + syncSuccess: '同步成功', + canUpgrade: '可升級', + backupName: '檔案名稱', + backupPath: '文件路徑', + backupdate: '備份時間', + versionSelect: '請選擇版本', + operatorHelper: '將對選中應用進行 {0} 操作,是否繼續?', + checkInstalledWarn: '未檢測到 {0} ,請進入應用商店點擊安裝!', + limitHelper: '該應用已安裝,不支援重複安裝', + deleteHelper: '{0}已經關聯以下資源,請檢查後重試!', + checkTitle: '提示', + defaultConfig: '預設配置', + defaultConfigHelper: '已復原為預設配置,儲存後生效', + forceDelete: '強制刪除', + forceDeleteHelper: '強制刪除,會忽略刪除過程中產生的錯誤並最終刪除中繼資料', + deleteBackup: '刪除備份', + deleteBackupHelper: '同時刪除應用備份', + deleteDB: '刪除資料庫', + deleteDBHelper: '同時刪除與應用關聯的資料庫', + noService: '無{0}', + toInstall: '去安裝', + param: '參數配置', + syncAppList: '更新應用列表', + alreadyRun: '已安裝', + less1Minute: '小於1分鐘', + appOfficeWebsite: '官方網站', + github: '開源社區', + document: '文件說明', + updatePrompt: '目前應用均為最新版本', + installPrompt: '尚未安裝任何應用', + updateHelper: '更新參數可能導致應用無法啟動,請提前備份並謹慎操作', + updateWarn: '更新參數需要重建應用,是否繼續?', + busPort: '服務埠', + syncStart: '開始同步!請稍後重新整理應用商店', + advanced: '進階設定', + cpuCore: '核心數', + containerName: '容器名稱', + containerNameHelper: '可以為空,為空自動生成', + allowPort: '埠外部訪問', + allowPortHelper: '允許埠外部訪問會放開防火牆埠', + appInstallWarn: '應用埠預設不允許外部訪問,可以在下方進階設定中選擇放開', + upgradeStart: '開始升級!請稍後重新整理頁面', + toFolder: '進入安裝目錄', + editCompose: '編輯 compose 文件', + editComposeHelper: '編輯 compose 文件可能導致軟體安裝失敗', + composeNullErr: 'compose 不能為空', + takeDown: '已廢棄', + allReadyInstalled: '已安裝', + installHelper: '配置鏡像加速可以解決鏡像拉取失敗的問題', + installWarn: '目前未勾選埠外部訪問,將無法透過外網IP:埠訪問,是否繼續? ', + showIgnore: '查看忽略應用', + cancelIgnore: '取消忽略', + ignoreList: '忽略列表', + appHelper: '部分應用的安裝使用說明請在應用詳情頁查看', + backupApp: '升級前備份應用', + backupAppHelper: '升級失敗會使用備份自動回滾,請在日誌審計-系統日誌中查看失敗原因', + openrestyDeleteHelper: '強制刪除 OpenResty 會刪除所有的網站,請確認風險後操作', + downloadLogHelper1: '即將下載 {0} 套用所有日誌,是否繼續? ', + downloadLogHelper2: '即將下載 {0} 應用最近 {1} 條日誌,是否繼續? ', + syncAllAppHelper: '即將同步所有應用,是否繼續? ', + hostModeHelper: '目前應用網路模式為 host 模式,如需放開埠,請在防火牆頁面手動放開', + showLocal: '顯示本機應用程式', + reload: '重載', + upgradeWarn: '升級應用程式會取代 docker-compose.yml 文件,如有更改,可以點擊查看文件對比', + newVersion: '新版本', + oldVersion: '目前版本', + composeDiff: '文件對比', + showDiff: '看對比', + useNew: '使用自訂版本', + useDefault: '使用預設版本', + useCustom: '自訂 docker-compose.yml', + useCustomHelper: '使用自訂 docker-compose.yml 文件,可能會導致應用程式升級失敗,如無必要,請勿勾選', + diffHelper: '左側為舊版本,右側為新版,編輯之後點擊使用自訂版本儲存', + pullImage: '拉取鏡像', + pullImageHelper: '在應用啟動之前執行 docker pull 來拉取鏡像', + deleteImage: '刪除鏡像', + deleteImageHelper: '刪除應用相關鏡像,刪除失敗任務不會終止', + requireMemory: '最低記憶體', + supportedArchitectures: '支援架構', + link: '連結', + showCurrentArch: '本前伺服器架構應用', + syncLocalApp: '同步本機應用', + memoryRequiredHelper: '目前應用記憶體需求 {0}', + gpuConfig: '開啟 GPU 支援', + gpuConfigHelper: '請確保機器有 NVIDIA GPU 並且安裝 NVIDIA 驅動 和 NVIDIA docker Container Toolkit', + webUI: 'Web 訪問地址', + webUIPlaceholder: '例如:example.com:8080/login', + defaultWebDomain: '預設訪問地址', + defaultWebDomainHepler: '預設訪問用於應用埠跳轉,例如應用埠為 8080 則跳轉地址為 http(s)://預設訪問地址:8080', + webUIConfig: '目前節點尚未配置預設存取地址,請在應用參數或者前往面板設定進行設定!', + toLink: '連結', + customAppHelper: '在安裝自訂應用商店包之前,請確保沒有任何已安裝的應用。', + forceUninstall: '強制移除', + syncCustomApp: '同步自訂應用', + ignoreAll: '忽略後續所有版本', + ignoreVersion: '忽略指定版本', + specifyIP: '綁定主機 IP', + specifyIPHelper: '設定埠綁定的主機地址/網卡(如果你不清楚這個的作用,請不要填寫)', + uninstallDeleteBackup: '移除應用-刪除備份', + uninstallDeleteImage: '移除應用-刪除鏡像', + upgradeBackup: '應用升級前備份應用', + noAppHelper: '未檢測到應用程式,請前往任務中心查看應用商店同步日誌', + isEdirWarn: '檢測到 docker-compose.yml 檔案被修改,請查看對比', + }, + website: { + primaryDomain: '主域名', + otherDomains: '其他域名', + static: '靜態網站', + deployment: '一鍵部署', + supportUpType: '僅支援 .tar.gz 檔案格式,且壓縮檔內必須包含 {0}.json 文件', + proxy: '反向代理', + alias: '代號', + ftpUser: 'FTP 帳號', + ftpPassword: 'FTP 密碼', + ftpHelper: '建立站點的同時,為站點建立一個對應 FTP 帳戶,並且 FTP 目錄指向站點所在目錄。', + remark: '備註', + groupSetting: '分組管理', + createGroup: '建立分組', + appNew: '新裝應用', + appInstalled: '已裝應用', + create: '建立網站', + delete: '刪除網站', + deleteApp: '刪除應用', + deleteBackup: '刪除備份', + domain: '域名', + domainHelper: '一行一個域名,支援*和IP位址,支援域名:埠', + addDomain: '新增域名', + domainConfig: '域名設定', + defaultDoc: '預設文件', + perserver: '並發限制', + perserverHelper: '限制目前站點最大並發數', + perip: '單 IP 限制', + peripHelper: '限制單個 IP 訪問最大並發數', + rate: '流量限制', + rateHelper: '限制每個請求的流量上限(單位:KB)', + limitHelper: '啟用流量控制', + other: '其他', + currentSSL: '目前證書', + dnsAccount: 'DNS 帳號', + applySSL: '證書申請', + SSLList: '證書列表', + createDnsAccount: 'DNS 帳戶', + aliyun: '阿里雲 DNS', + aliEsa: '阿里雲 ESA', + awsRoute53: 'Amazon Route 53(亞馬遜)', + manual: '手動解析', + key: '金鑰', + check: '查看', + acmeAccountManage: 'Acme 帳戶', + email: '信箱', + acmeAccount: 'Acme 帳戶', + provider: '驗證方式', + dnsManual: '手動解析', + expireDate: '過期時間', + brand: '組織', + deploySSL: '部署', + deploySSLHelper: '確定部署證書?', + ssl: '證書', + dnsAccountManage: 'DNS 帳戶', + renewSSL: '續簽', + renewHelper: '確定續簽證書?', + renewSuccess: '續簽證書', + enableHTTPS: '啟用 HTTPS', + aliasHelper: '代號是網站目錄的資料夾名稱', + lastBackupAt: '上次備份時間', + null: '無', + nginxConfig: 'Nginx配置', + websiteConfig: '網站設定', + proxySettings: '代理設定', + advancedSettings: '進階設定', + cacheSettings: '快取設定', + sniSettings: 'SNI 設定', + basic: '基本', + source: '設定檔', + security: '安全', + nginxPer: '性能調整', + neverExpire: '永不過期', + setDefault: '設為預設', + deleteHelper: '相關應用狀態不正常,請檢查', + toApp: '去已安裝列表', + cycle: '週期', + frequency: '頻率', + ccHelper: '{0} 秒內累計請求同一URL超過 {1} 次,觸發CC防禦,封鎖此IP', + mustSave: '修改之後需要儲存才能生效', + fileExt: '文件副檔名', + fileExtBlock: '文件副檔名黑名單', + value: '值', + enable: '開啟', + proxyAddress: '代理地址', + proxyHelper: '例: 127.0.0.1:8080', + forceDelete: '強制刪除', + forceDeleteHelper: '強制刪除,會忽略刪除過程中產生的錯誤並最終刪除中繼資料', + deleteAppHelper: '同時刪除關聯應用、資料庫以及應用備份', + deleteBackupHelper: '同時刪除網站備份', + deleteDatabaseHelper: '同時刪除網站關聯資料庫', + deleteConfirmHelper: '刪除操作無法回滾,請輸入 "{0}" 刪除', + staticPath: '對應主目錄:', + limit: '限制方案', + blog: '論壇/部落格', + imageSite: '圖片站', + downloadSite: '下載站', + shopSite: '商城', + doorSite: '門戶', + qiteSite: '企業', + videoSite: '影片', + errLog: '錯誤日誌', + stopHelper: '停止站點後將無法正常訪問,使用者瀏覽會顯示目前站點停止頁面,是否繼續操作?', + startHelper: '啟用站點後,使用者可以正常瀏覽網站內容,是否繼續操作?', + sitePath: '網站目錄', + siteAlias: '網站代號', + primaryPath: 'root 目錄', + folderTitle: '網站主要包含以下資料夾', + wafFolder: '防火牆規則', + indexFolder: '網站root目錄', + sslFolder: '網站證書', + enableOrNot: '是否啟用', + oldSSL: '選擇已有證書', + manualSSL: '手動匯入證書', + select: '選擇', + selectSSL: '選擇證書', + privateKey: '私鑰(KEY)', + certificate: '證書(PEM格式)', + HTTPConfig: 'HTTP 選項', + HTTPSOnly: '禁止 HTTP', + HTTPToHTTPS: '訪問HTTP自動跳轉到HTTPS', + HTTPAlso: 'HTTP可直接訪問', + sslConfig: 'SSL 選項', + disableHTTPS: '停用 HTTPS', + disableHTTPSHelper: '停用 HTTPS會刪除證書相關配置,是否繼續?', + SSLHelper: '注意:請勿將SSL證書用於非法網站 \n 如開啟後無法使用HTTPS訪問,請檢查安全組是否正確放行443埠', + SSLConfig: '證書設定', + SSLProConfig: 'SSL 協議設定', + supportProtocol: '支援的協議版本', + encryptionAlgorithm: '加密演算法', + notSecurity: '(不安全)', + encryptHelper: + "Let's Encrypt 簽發證書有頻率限制,但足以滿足正常需求,過於頻繁操作會導致簽發失敗。具體限制請看 官方文件 ", + ipValue: '值', + ext: '文件副檔名', + wafInputHelper: '按行輸入資料,一行一個', + data: '資料', + ever: '永久', + nextYear: '一年後', + noLog: '目前沒有日誌...', + defaultServer: '預設站點', + noDefaultServer: '未設定', + defaultServerHelper: + '設定預設站點後,所有未綁定的域名和IP都被定向到預設站點\n可有效防止惡意解析\n但同時會導致 WAF 未授權域名攔截失敗', + websiteDeploymentHelper: '使用從 1Panel 部署的應用建立網站', + websiteStatictHelper: '在主機上建立網站目錄', + websiteProxyHelper: '代理已有服務,例如本機已安裝使用 8080 埠的 halo 服務,那麼代理地址為 http://127.0.0.1:8080', + restoreHelper: '確認使用此備份復原?', + wafValueHelper: '值', + runtimeProxyHelper: '使用從 1Panel 建立的執行環境', + runtime: '執行環境', + deleteRuntimeHelper: '執行環境應用需要跟網站一併刪除,請謹慎處理', + proxyType: '監聽網路類型', + unix: 'Unix 網路', + tcp: 'TCP/IP 網路', + phpFPM: 'FPM 設定檔', + phpConfig: 'PHP 設定檔', + updateConfig: '配置修改', + isOn: '開啟', + isOff: '關閉', + rewrite: '偽靜態', + rewriteMode: '方案', + current: '目前', + rewriteHelper: '若設定偽靜態後,網站無法正常訪問,請嘗試設定回default', + runDir: '執行目錄', + runUserHelper: + '透過 PHP 容器執行環境部署的網站,需要將 index 和子目錄下的所有檔案、資料夾擁有者和使用者群組設定為 1000,本機 PHP 環境需要參考本機 PHP-FPM 使用者和使用者群組設定', + userGroup: '執行使用者/組', + uGroup: '使用者群組', + proxyPath: '前端請求路徑', + proxyPass: '後端代理地址', + cache: '快取', + cacheTime: '快取時間', + enableCache: '開啟快取', + proxyHost: '後端域名', + disabled: '已停止', + startProxy: '開啟反向代理', + stopProxy: '關閉反向代理', + sourceFile: '源文', + proxyHelper1: '訪問這個目錄時將會把目標URL的內容返回並顯示', + proxyPassHelper: '代理的站點,必須為可正常訪問的URL', + proxyHostHelper: '將域名新增到請求頭傳遞到代理伺服器', + modifier: '匹配規則', + modifierHelper: '例:= 精確匹配,~ 正則匹配,^~ 匹配路徑開頭 等', + replace: '文字取代', + replaceHelper: + 'nginx文字取代功能可以在反向代理時對響應內容進行字串取代。常用於修改代理後端返回的HTML、CSS、JavaScript等文件中的連結地址、API介面地址等。支援正規表示式匹配,可實現複雜的內容取代需求。', + addReplace: '新增文字取代', + replaced: '搜尋字串(不能為空)', + replaceText: '取代為字串', + replacedErr: '搜尋字串不能為空', + replacedErr2: '搜尋字串不能重複', + replacedListEmpty: '無文字取代規則', + proxySslName: '代理 SNI 名稱', + basicAuth: '密碼訪問', + editBasicAuthHelper: '密碼為非對稱加密,無法回顯,編輯需要重新設定密碼', + antiLeech: '防盜鏈', + extends: '副檔名', + browserCache: '瀏覽器快取', + noModify: '不修改', + serverCache: '伺服器快取', + leechLog: '記錄防盜鏈日誌', + accessDomain: '允許的域名', + leechReturn: '響應資源', + noneRef: '允許來源為空', + disable: '未啟用', + disableLeechHelper: '是否停用防盜鏈', + disableLeech: '停用防盜鏈', + ipv6: '監聽 IPV6', + leechReturnError: '請填寫 HTTP 狀態碼', + blockedRef: '允許非標準 Referer', + accessControl: '防盜鏈控制', + leechcacheControl: '快取控制', + logEnableControl: '記錄靜態資源請求日誌', + leechSpecialValidHelper: + '啟用「允許 Referer 為空」時,不會阻止無 Referer 的請求(直接訪問等);啟用「允許非標準 Referer」時,會放行任何不以 http/https 開頭的 Referer 請求(用戶端請求等)', + leechInvalidReturnHelper: '攔截盜鏈請求後返回的 HTTP 狀態碼', + leechlogControlHelper: '記錄靜態資源的請求,生產環境通常可關閉以避免過多無意義的日誌', + selectAcme: '選擇 Acme 帳號', + imported: '手動建立', + importType: '匯入方式', + pasteSSL: '貼上程式碼', + localSSL: '選擇伺服器文件', + privateKeyPath: '私鑰文件', + certificatePath: '證書文件', + ipWhiteListHelper: 'IP白名單的作用:所有規則對IP白名單無效', + redirect: '重定向', + sourceDomain: '源域名/路徑', + targetURL: '目標URL地址', + keepPath: '保留URI參數', + path: '路徑', + redirectType: '重定向類型', + redirectWay: '方式', + keep: '保留', + notKeep: '不保留', + redirectRoot: '重定向到首頁', + redirectHelper: '301永久重定向,302臨時重定向', + changePHPVersionWarn: '此動作不可回滾,是否繼續', + changeVersion: '切換版本', + retainConfig: '是否保留 php-fpm.conf 和 php.ini 文件', + runDirHelper2: '請確保二級執行目錄位於 index 目錄下', + openrestyHelper: 'OpenResty 預設 HTTP 埠:{0} HTTPS 埠:{1},可能影響網站域名訪問和 HTTPS 強制跳轉', + primaryDomainHelper: '支援網域:port', + acmeAccountType: '帳號類型', + keyType: '金鑰演算法', + tencentCloud: '騰訊雲', + containWarn: '其他域名中包含主域名,請重新輸入', + rewriteHelper2: '從應用程式商店安裝的 WordPress 等應用,預設已經配置好偽靜態,重複配置可能會報錯', + websiteBackupWarn: '僅支援匯入本機備份,匯入其他機器備份可能會復原失敗', + ipWebsiteWarn: 'IP 為網域名稱的網站,需要設定為預設網站才能正常存取', + hstsHelper: '開啟 HSTS 可以增加網站安全性', + includeSubDomains: '子域', + hstsIncludeSubDomainsHelper: '啟用後,HSTS策略將應用於目前域名的所有子域名', + defaultHtml: '預設頁面', + website404: '網站 404 錯誤頁', + domain404: '網站不存在頁面', + indexHtml: '靜態網站預設頁', + stopHtml: '網站停用頁', + indexPHP: 'PHP 網站預設頁', + sslExpireDate: '憑證過期時間', + website404Helper: '網站 404 錯誤頁僅支援 PHP 執行環境網站和靜態網站', + sni: '回源 SNI', + sniHelper: '反代後端為 https 的時候可能需要設定回源 SNI,具體需要看 CDN 服務商文件', + huaweicloud: '華為雲', + createDb: '建立資料庫', + enableSSLHelper: '開啟失敗不會影響網站建立', + batchAdd: '批次新增域名', + batchInput: '批次輸入', + domainNotFQDN: '此域名可能無法在公網訪問', + domainInvalid: '域名格式不正確', + domainBatchHelper: '一行一個域名,格式:域名:端口@ssl\n範例:example.com:443@ssl 或 example.com', + generateDomain: '生成', + global: '全域', + subsite: '子網站', + subsiteHelper: '子網站可以選擇已存在的 PHP 和靜態網站的目錄作為主目錄。', + parentWbeiste: '父級網站', + deleteSubsite: '刪除目前網站需要先刪除子網站 {0}', + loadBalance: '負載均衡', + server: '節點', + algorithm: '演算法', + ipHash: 'IP 雜湊', + ipHashHelper: '基於用戶端 IP 位址將請求分配到特定伺服器,可以確保特定用戶端總是被路由到同一伺服器。', + leastConn: '最少連接', + leastConnHelper: '將請求發送到目前活動連接數最少的伺服器。', + leastTime: '最少時間', + leastTimeHelper: '將請求發送到目前活動連接時間最短的伺服器。', + defaultHelper: + '預設方法,請求被均勻分配到每個伺服器。如果伺服器有權重配置,則根據指定的權重分配請求,權重越高的伺服器接收更多請求。', + weight: '權重', + maxFails: '最大失敗次數', + maxConns: '最大連接數', + strategy: '策略', + strategyDown: '停用', + strategyBackup: '備用', + ipHashBackupErr: 'IP 雜湊不支援備用節點', + failTimeout: '故障超時', + failTimeoutHelper: + '服務器健康檢查的時間窗口長度。在該時間段內累計失敗次數達到閾值時,服務器將被暫時移除,並在經過相同時長後重新嘗試。默認 10 秒', + + staticChangePHPHelper: '目前為靜態網站,可切換為 PHP 網站', + proxyCache: '反向代理快取', + cacheLimit: '快取空間限制', + shareCahe: '快取計數記憶體大小', + cacheExpire: '快取過期時間', + shareCaheHelper: '每1M記憶體可以儲存約8000個快取物件', + cacheLimitHelper: '超過限制會自動刪除舊的快取', + cacheExpireJHelper: '超出時間快取未命中將會被刪除', + realIP: '真實 IP', + ipFrom: 'IP 來源', + ipFromHelper: + '透過配置可信 IP 來源,OpenResty 會分析 HTTP Header 中的 IP 資訊,準確識別並記錄訪客的真實 IP 位址,包括在存取日誌中', + ipFromExample1: '如果前端是 Frp 等工具,可以填寫 Frp 的 IP 位址,類似 127.0.0.1', + ipFromExample2: '如果前端是 CDN,可以填寫 CDN 的 IP 位址段', + ipFromExample3: '如果不確定,可以填 0.0.0.0/0(ipv4) ::/0(ipv6) [注意:允許任意來源 IP 不安全]', + http3Helper: + 'HTTP/3 是 HTTP/2 的升級版本,提供更快的連線速度和更好的效能,但並非所有瀏覽器都支援 HTTP/3,啟用後可能會導致部分瀏覽器無法訪問', + cors: '跨域存取(CORS)', + enableCors: '開啟跨域', + allowOrigins: '允許存取的網域名稱', + allowMethods: '允許的請求方法', + allowHeaders: '允許的請求標頭', + allowCredentials: '允許攜帶cookies', + preflight: '預檢請求快速回應', + preflightHleper: + '開啟後,當瀏覽器發送跨域預檢請求(OPTIONS 請求)時,系統會自動返回 204 狀態碼並設定必要的跨域回應標頭', + + changeDatabase: '切換資料庫', + changeDatabaseHelper1: '資料庫關聯用於備份復原網站。', + changeDatabaseHelper2: '切換其他資料庫會導致以前的備份無法復原。', + saveCustom: '另存為模版', + rainyun: '雨雲', + volcengine: 'Volcengine', + runtimePortHelper: '目前執行環境存在多個埠,請選擇一個代理埠。', + runtimePortWarn: '目前執行環境沒有埠,無法代理', + cacheWarn: '請先關閉反代中的快取開關', + loadBalanceHelper: '建立負載均衡後,請前往『反向代理』,新增代理並將後端地址設定為:http://<負載均衡名稱>', + favorite: '收藏', + cancelFavorite: '取消收藏', + useProxy: '使用代理', + useProxyHelper: '使用面板設定中的代理伺服器地址', + westCN: '西部數碼', + openBaseDir: '防跨站攻擊', + openBaseDirHelper: 'open_basedir 用於限制 PHP 文件訪問路徑,有助於防止跨站訪問和提升安全性', + serverCacheTime: '伺服器快取時間', + serverCacheTimeHelper: '請求在伺服器端快取的時間,到期前相同請求會直接返回快取結果,不再請求源站。', + browserCacheTime: '瀏覽器快取時間', + browserCacheTimeHelper: + '靜態資源在瀏覽器本機快取的時間,減少重複請求。到期前使用者重新整理頁面會直接使用本機快取。', + donotLinkeDB: '不關聯資料庫', + toWebsiteDir: '進入網站目錄', + execParameters: '執行參數', + extCommand: '補充指令', + mirror: '鏡像源', + execUser: '執行使用者', + execDir: '執行目錄', + packagist: '中國全量鏡像', + + batchOpreate: '批次操作', + batchOpreateHelper: '批次{0}網站,是否繼續操作?', + stream: 'TCP/UDP 代理', + streamPorts: '監聽端口', + streamPortsHelper: '設定對外監聽的埠號,客戶端將透過此埠存取服務,按逗號分割,例如:5222,5223', + streamHelper: 'TCP/UDP 端口轉發與負載均衡', + udp: '啟用 UDP', + + syncHtmlHelper: '同步到 PHP 和靜態網站', + }, + php: { + short_open_tag: '短標籤支援', + max_execution_time: '最大腳本執行時間', + max_input_time: '最大輸入時間', + memory_limit: ' 腳本記憶體限制', + post_max_size: 'POST資料最大尺寸', + file_uploads: '是否允許上傳文件', + upload_max_filesize: '允許上傳文件的最大尺寸', + max_file_uploads: '允許同時上傳文件的最大數量', + default_socket_timeout: 'Socket超時時間', + error_reporting: '錯誤級別', + display_errors: '是否輸出詳細錯誤資訊', + cgi_fix_pathinfo: '是否開啟pathinfo', + date_timezone: '時區', + disableFunction: '停用函式', + disableFunctionHelper: '輸入要停用的函式,例如exec,多個請用,分割', + uploadMaxSize: '上傳限制', + indexHelper: '為保障PHP網站正常執行,請將程式碼放置於 index 目錄,並避免重新命名', + extensions: '擴充範本', + extension: '擴充', + extensionHelper: '多個擴充功能,分割', + toExtensionsList: '檢視擴充清單', + containerConfig: '容器配置', + containerConfigHelper: '環境變數等資訊可以在建立完成之後在配置-容器配置中修改', + dateTimezoneHelper: '範例:TZ=Asia/Shanghai(請根據需要自行新增)', + }, + nginx: { + serverNamesHashBucketSizeHelper: '伺服器名字的hash表大小', + clientHeaderBufferSizeHelper: '用戶端請求的頭buffer大小', + clientMaxBodySizeHelper: '最大上傳文件', + keepaliveTimeoutHelper: '連接超時時間', + gzipMinLengthHelper: '最小壓縮文件', + gzipCompLevelHelper: '壓縮率', + gzipHelper: '是否開啟壓縮傳輸', + connections: '活動連接(Active connections)', + accepts: '總連接次數(accepts)', + handled: '總握手次數(handled)', + requests: '總請求數(requests)', + reading: '請求數(Reading)', + writing: '響應數(Writing)', + waiting: '駐留行程(Waiting)', + status: '目前狀態', + configResource: '配置修改', + saveAndReload: '儲存並重載', + clearProxyCache: '清除反代快取', + clearProxyCacheWarn: '此操作將刪除快取目錄下的所有文件,是否繼續?', + create: '新增模組', + update: '編輯模組', + params: '參數', + packages: '軟體包', + script: '腳本', + module: '模組', + build: '建構', + buildWarn: '建構 OpenResty 需要預留一定的 CPU 和記憶體,時間較長,請耐心等待', + mirrorUrl: '軟體源', + paramsHelper: '例如:--add-module=/tmp/ngx_brotli', + packagesHelper: '例如:git,curl 以逗號分割', + scriptHelper: '編譯之前執行的腳本,通常用於下載模組原始碼,安裝依賴等', + buildHelper: '新增/修改模組後點擊構建,構建成功後會自動重啟 OpenResty', + defaultHttps: 'HTTPS 防竄站', + defaultHttpsHelper1: '開啟後可以解決 HTTPS 竄站問題', + sslRejectHandshake: '拒絕默認 SSL 握手', + sslRejectHandshakeHelper: '開啟之後可以避免證書洩露,設置默認網站會讓此設置失效', + }, + ssl: { + create: '申請證書', + provider: '類型', + manualCreate: '手動建立', + acmeAccount: 'Acme 帳號', + resolveDomain: '解析域名', + err: '錯誤', + value: '記錄值', + dnsResolveHelper: '請到DNS解析服務商處新增以下解析記錄:', + detail: '詳情', + msg: '證書資訊', + ssl: '證書', + key: '私鑰', + startDate: '生效時間', + organization: '簽發機構', + renewConfirm: '是否確定給網域名稱 {0} 申請證書? ', + autoRenew: '自動續簽', + autoRenewHelper: '距離到期時間30天自動續約', + renewSuccess: '續簽成功', + renewWebsite: '該證書已經和以下網站關聯,申請會同步應用到這些網站', + createAcme: '建立帳戶', + acmeHelper: 'Acme 帳戶用於申請免費證書', + upload: '上傳證書', + applyType: '申請方式', + apply: '申請', + applyStart: '證書申請開始', + getDnsResolve: '正在取得 DNS 解析值,請稍後 ...', + selfSigned: '自簽證書', + ca: '證書頒發機構', + commonName: '憑證主體名稱(CN)', + caName: '機構名稱', + company: '公司/組織', + department: '部門', + city: '城市', + province: '省份', + country: '國家代號', + commonNameHelper: '例如:', + selfSign: '簽發證書', + days: '有效期限', + domainHelper: '一行一個網域名稱,支援*和IP位址', + pushDir: '推送憑證到本機目錄', + dir: '目錄', + pushDirHelper: '會在此目錄下產生兩個文件,憑證檔案:fullchain.pem 金鑰檔案:privkey.pem', + organizationDetail: '機構詳情', + fromWebsite: '從網站取得', + dnsMauanlHelper: '手動解析模式需要在建立完之後點選申請按鈕取得 DNS 解析值', + httpHelper: '使用 HTTP 模式需安裝 OpenResty,且不支援申請泛域名證書。', + buypassHelper: 'Buypass 大陸地區無法訪問', + googleHelper: '如何取得EAB HmacKey 和EAB kid', + googleCloudHelper: 'Google Cloud API 大陸大部分地區無法存取', + skipDNSCheck: '跳過 DNS 校驗', + skipDNSCheckHelper: '如果出現申請超時問題,請勾選此處,其他情況請勿勾選', + cfHelper: '請勿使用 Global API Key', + deprecated: '即將廢棄', + deprecatedHelper: '已經停止維護,可能會在以後的某個版本廢棄,請使用騰訊雲方式解析', + disableCNAME: '停用 CNAME', + disableCNAMEHelper: '有 CNAME 配置的域名,如果申請失敗,可以勾選此處', + nameserver: 'DNS 伺服器', + nameserverHelper: '使用自訂的 DNS 伺服器來校驗網域名稱', + edit: '編輯證書', + execShell: '申請憑證之後執行腳本', + shell: '腳本內容', + shellHelper: + '腳本預設執行目錄為 1Panel 安裝目錄,如果有推送證書,那麼執行目錄為證書推送目錄。預設超時時間 30 分鐘', + customAcme: '自訂 ACME 服務', + customAcmeURL: 'ACME 服務 URL', + baiduCloud: '百度雲', + pushNode: '同步到其他節點', + pushNodeHelper: '申請/續期之後推送到選擇的節點', + fromMaster: '主節點推送', + hostedZoneID: '託管區域 ID', + isIP: 'IP 憑證', + useEAB: '使用 EAB 認證', + }, + firewall: { + create: '建立規則', + edit: '編輯規則', + ccDeny: 'CC 防護', + ipWhiteList: 'IP 白名單', + ipBlockList: 'IP 黑名單', + fileExtBlockList: '文件副檔名黑名單', + urlWhiteList: 'URL 白名單', + urlBlockList: 'URL 黑名單', + argsCheck: 'GET 參數校驗', + postCheck: 'POST 參數校驗', + cookieBlockList: 'Cookie 黑名單', + + dockerHelper: '目前防火牆無法停用容器端口映射,已安裝應用可前往【已安裝】頁面編輯應用參數,設定端口放行規則。', + iptablesHelper: '偵測到系統正在使用 {0} 防火牆,如需切換至 iptables,請先手動解除安裝!', + quickJump: '快速跳轉', + used: '已使用', + unUsed: '未使用', + dockerRestart: '防火牆操作需要重啟 Docker 服務', + firewallHelper: '{0}系統防火牆', + firewallNotStart: '目前未開啟系統防火牆,請先開啟!', + restartFirewallHelper: '該操作將對目前防火牆進行重啟操作,是否繼續?', + stopFirewallHelper: '系統防火牆關閉後,伺服器將失去安全防護,是否繼續?', + startFirewallHelper: '系統防火牆開啟後,可以更好的防護伺服器安全,是否繼續?', + noPing: '禁 ping', + enableBanPing: '禁 Ping', + disableBanPing: '解除禁 Ping', + noPingTitle: '是否禁 ping', + noPingHelper: '禁 ping 後將無法 ping 通伺服器,是否繼續?', + onPingHelper: '解除禁 ping 後您的伺服器可能會被駭客發現,是否繼續?', + changeStrategy: '修改{0}策略', + changeStrategyIPHelper1: 'IP 策略修改為【封鎖】,設定後該 IP 將禁止訪問伺服器,是否繼續?', + changeStrategyIPHelper2: 'IP 策略修改為【放行】,設定後該 IP 將復原正常訪問,是否繼續?', + changeStrategyPortHelper1: '埠策略修改為【拒絕】,設定後埠將拒絕外部訪問,是否繼續?', + changeStrategyPortHelper2: '埠策略修改為【允許】,設定後埠將復原正常訪問,是否繼續?', + stop: '禁止', + portFormatError: '請輸入正確的埠資訊!', + portHelper1: '多個埠,如:8080,8081', + portHelper2: '範圍埠,如:8080-8089', + strategy: '策略', + accept: '允許', + drop: '拒絕', + anyWhere: '所有 IP', + address: '指定 IP', + addressHelper: '支援輸入 IP 或 IP 段', + allow: '放行', + deny: '封鎖', + addressFormatError: '請輸入合法的 ip 地址!', + addressHelper1: '支援輸入 IP 或 IP 段:172.16.10.11 或 172.16.0.0/24', + addressHelper2: '多個 IP 或 IP 段 請用 "," 隔開:172.16.10.11,172.16.0.0/24', + allIP: '所有 IP', + portRule: '埠規則', + createPortRule: '@:commons.button.create@:firewall.portRule', + forwardRule: '埠轉發', + createForwardRule: '@:commons.button.create@:firewall.forwardRule', + ipRule: 'IP 規則', + createIpRule: '@:commons.button.create @:firewall.ipRule', + userAgent: 'User-Agent 過濾', + sourcePort: '來源埠', + targetIP: '目標 IP', + targetPort: '目標埠', + forwardHelper1: '如果是本機埠轉發,目標 IP 為:127.0.0.1', + forwardHelper2: '如果目標 IP 不填寫,預設為本機埠轉發', + forwardPortHelper: '支援端口範圍,如:8080-8089', + forwardInboundInterface: '轉發入站網路介面', + exportHelper: '即將導出 {0} 條防火牆規則,是否繼續?', + importSuccess: '成功匯入 {0} 條規則', + importPartialSuccess: '匯入完成:成功 {0} 條,失敗 {1} 條', + + ipv4Limit: '目前操作暫僅支援 IPv4 地址', + basicStatus: '目前未綁定鏈 {0} ,請先綁定!', + baseIptables: 'iptables 服務', + forwardIptables: 'iptables 端口轉發服務', + advanceIptables: 'iptables 進階設定服務', + initMsg: '即將初始化 {0}, 是否繼續?', + initHelper: '偵測到 {0} 未初始化,請點選頂部狀態列的初始化按鈕進行設定!', + bindHelper: '綁定 僅當狀態為綁定時,防火牆規則才能生效,是否確認?', + unbindHelper: '解除綁定 解除綁定時,已新增的所有防火牆規則將失效,請謹慎操作,是否確認?', + defaultStrategy: '目前鏈 {0} 的預設策略為 {1}', + defaultStrategy2: '目前鏈 {0} 的預設策略為 {1},目前狀態為未綁定,已新增的防火牆規則需要綁定後生效!', + filterRule: 'Filter 規則', + filterHelper: 'Filter 規則允許您在 INPUT/OUTPUT 層級控制網路流量。請謹慎設定,避免鎖定系統。', + chain: '鏈', + targetChain: '目標鏈', + sourceIP: '來源 IP', + destIP: '目標 IP', + inboundDirection: '入站方向', + outboundDirection: '出站方向', + destPort: '目標端口', + action: '動作', + reject: '拒絕', + sourceIPHelper: 'CIDR 格式,如 192.168.1.0/24,留空表示所有地址', + destIPHelper: 'CIDR 格式,如 10.0.0.0/8,留空表示所有地址', + portHelper: '0 表示任意端口', + allPorts: '所有端口', + deleteRuleConfirm: '將刪除 {0} 條規則,是否繼續?', + }, + runtime: { + runtime: '執行環境', + workDir: '工作目錄', + create: '建立執行環境', + localHelper: '本機環境安裝及離線環境使用相關問題,可參考 ', + versionHelper: 'PHP的版本,例 v8.0', + buildHelper: '擴展越多,製作映像檔時占用的 CPU 越高,可在建立環境後再安裝擴展。', + openrestyWarn: 'PHP 需要升級 OpenResty 至 1.21.4.1 版本以上才能使用', + toupgrade: '去升級', + edit: '編輯執行環境', + extendHelper: '列表中不存在的擴展,可以手動輸入之後選擇,例:輸入 sockets ,然後在下拉選單中選擇第一個', + rebuildHelper: '編輯擴展後需要【重建】PHP 應用之後才能生效', + rebuild: '重建 PHP 應用', + source: 'PHP 擴展源', + ustc: '中國科學技術大學', + netease: '網易', + aliyun: '阿里雲', + tsinghua: '清華大學', + xtomhk: 'XTOM 鏡像站(香港)', + xtom: 'XTOM 鏡像站(全球)', + phpsourceHelper: '根據你的網路環境選擇合適的源', + appPort: '應用埠', + externalPort: '外部映射埠', + packageManager: '包管理器', + codeDir: '原始碼目錄', + appPortHelper: '應用埠是指容器內部執行的埠', + externalPortHelper: '外部映射埠是指將容器內部埠映射到外部的埠', + runScript: '啟動指令', + runScriptHelper: '啟動指令是指容器啟動後執行的指令', + open: '開啟', + operatorHelper: '將對選取的執行環境進行 {0} 操作,是否繼續? ', + taobao: '淘寶', + tencent: '騰訊', + imageSource: '鏡像源', + moduleManager: '模組管理', + module: '模組', + nodeOperatorHelper: '是否{0} {1} 模組? 操作可能導致運轉環境異常,請確認後操作', + customScript: '自訂啟動指令', + customScriptHelper: '請填寫完整的啟動指令,例如:npm run start,pm2 啟動指令請換為 pm2-runtime,否則會啟動失敗', + portError: '不能填寫相同連接埠', + systemRestartHelper: '狀態說明:中斷-系統重新啟動導致狀態取得失敗', + javaScriptHelper: '請填寫完整啟動指令,例如:java -jar halo.jar -Xmx1024M -Xms256M', + javaDirHelper: '目錄中要包含 jar 包,子目錄中包含也可', + goHelper: '請填寫完整啟動指令,例如:go run main.go 或 ./main', + goDirHelper: '目錄中要包含 go 文件或者二進制文件,子目錄中包含也可', + extension: '擴充', + installExtension: '是否確認安裝擴充功能 {0}', + loadedExtension: '已載入擴充功能', + popularExtension: '常用擴充', + uninstallExtension: '是否確認移除擴充功能 {0}', + phpConfigHelper: '修改配置需要重新啟動執行環境,是否繼續', + operateMode: '執行模式', + dynamic: '動態', + static: '靜態', + ondemand: '按需', + dynamicHelper: '動態調整行程數,彈性高,適合流量波動較大或低記憶體的網站', + staticHelper: '固定行程數,適合高併發穩定流量的網站,資源消耗較高', + ondemandHelper: '行程按需啟動和銷毀,資源利用最優,但初始回應可能較慢', + max_children: '允許建立的最大行程數', + start_servers: '啟動時所建立的行程數', + min_spare_servers: '最小空閒行程數', + max_spare_servers: '最大空閒行程數', + envKey: '名稱', + envValue: '值', + environment: '環境變數', + pythonHelper: + '請填寫完整啟動指令,例如:pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000', + dotnetHelper: '請填寫完整的啟動指令,例如 dotnet MyWebApp.dll', + dirHelper: '說明:請填寫容器內的目錄路徑', + concurrency: '並發方案', + loadStatus: '負載狀態', + extraHosts: '主機映射', + }, + process: { + pid: '行程ID', + ppid: '父行程ID', + numThreads: '執行緒', + memory: '記憶體', + diskRead: '磁碟讀', + diskWrite: '磁碟寫', + netSent: '上行', + netRecv: '下行', + numConnections: '連接', + startTime: '啟動時間', + running: '執行中', + sleep: '睡眠', + stop: '停止', + idle: '空閒', + zombie: '僵屍行程', + wait: '等待', + lock: '鎖定', + blocked: '阻塞', + cmdLine: '啟動指令', + basic: '基本資訊', + mem: '記憶體資訊', + openFiles: '文件打開', + env: '環境變數', + noenv: '無', + net: '網路連接', + laddr: '本機地址/埠', + raddr: '遠端地址/埠', + stopProcess: '結束', + viewDetails: '查看詳情', + stopProcessWarn: '是否確定結束此行程 (PID:{0})?', + kill: '結束行程', + killNow: '立即結束', + killHelper: '結束行程 {0} 可能導致部分程式無法正常運作,是否繼續?', + processName: '行程名稱', + }, + tool: { + supervisor: { + loadStatusErr: '取得行程狀態失敗,請檢查 supervisor 服務狀態', + notSupport: '未檢測到 Supervisor 服務,請前往腳本庫頁面手動安裝', + list: '守護行程', + config: 'Supervisor 配置', + primaryConfig: '主配置檔案位置', + notSupportCtl: '未檢測到 supervisorctl,請前往腳本庫頁面手動安裝', + user: '啟動使用者', + command: '啟動指令', + dir: '執行目錄', + numprocs: '行程數量', + initWarn: + '初始化操作需要修改設定檔的 [include] files 參數,修改後的服務設定檔所在目錄: 1panel安裝目錄/1panel/tools/supervisord/supervisor.d/', + operatorHelper: '將對 {0} 進行 {1} 操作,是否繼續? ', + uptime: '執行時長', + notStartWarn: 'Supervisor 未啟動,請先啟動', + serviceName: '服務名稱', + initHelper: '偵測到 Supervisor 服務尚未初始化,請點擊上方狀態列的初始化按鈕進行設定。', + serviceNameHelper: 'systemctl 管理的 Supervisor 服務名稱,一般為 supervisor 或 supervisord', + restartHelper: '初始化會重啟服務,導致原有的守護行程全部關閉', + RUNNING: '執行中', + STOPPED: '已停止', + STOPPING: '停止中', + STARTING: '啟動中', + FATAL: '啟動失敗', + BACKOFF: '啟動異常', + ERROR: '錯誤', + statusCode: '狀態碼', + manage: '管理', + autoRestart: '自動重啟', + EXITED: '已退出', + autoRestartHelper: '程式異常退出後是否自動重啟', + autoStart: '自動啟動', + autoStartHelper: 'Supervisor 啟動後是否自動啟動服務', + }, + }, + disk: { + management: '磁碟管理', + partition: '分區', + unmount: '取消掛載', + unmountHelper: '是否取消掛載分區 {0}?', + mount: '掛載', + partitionAlert: '進行磁碟分區時需要格式化磁碟,原有資料將被刪除,請提前儲存或快照資料', + mountPoint: '掛載目錄', + systemDisk: '系統磁碟', + unpartitionedDisk: '未分區磁碟', + handlePartition: '立即分區', + filesystem: '文件系統', + unmounted: '未掛載', + cannotOperate: '無法操作', + systemDiskHelper: '提示:目前磁碟為系統盤,無法進行操作', + autoMount: '自動掛載', + model: '裝置型號', + diskType: '磁碟類型', + serial: '序號', + noFail: '掛載失敗不影響系統啟動', + }, + xpack: { + expiresAlert: '溫馨提醒:您的專業版許可證將在 {0} 天後到期,屆時所有專業版功能將無法繼續使用。', + name: '專業版', + menu: '進階功能', + upage: 'AI 建站', + proAlert: '升級專業版以使用此功能', + waf: { + name: 'WAF', + blackWhite: '黑白名單', + globalSetting: '全域設定', + websiteSetting: '網站設定', + blockRecords: '封鎖紀錄', + world: '世界', + china: '中國', + intercept: '攔截', + request: '請求', + count4xx: '4xx 數量', + count5xx: '5xx 數量', + todayStatus: '今日狀態', + reqMap: '攔截地圖(30日)', + resource: '來源', + count: '數量', + hight: '高', + low: '低', + reqCount: '請求數', + interceptCount: '攔截數', + requestTrends: '請求趨勢(7天)', + interceptTrends: '攔截趨勢(7天)', + whiteList: '白名單', + blackList: '黑名單', + ipBlackListHelper: '黑名單中的 IP 無法存取網站', + ipWhiteListHelper: '白名單中的 IP 不受任何規則限制', + uaBlackListHelper: '攜帶黑名單中的 User-Agent 的請求將被攔截', + uaWhiteListHelper: '攜帶白名單中的 User-Agent 的請求不受任何規則限制', + urlBlackListHelper: '請求黑名單中的 URL 將被攔截', + urlWhiteListHelper: '請求白名單中的 URL 請求不受任何規則限制', + ccHelper: '{0} 秒內累積請求任意網站超過 {1} 次,封鎖此 IP {2}', + blockTime: '封鎖時間', + attackHelper: '{0} 秒內累計攔截超過 {1} 次,封鎖此 IP {2}', + notFoundHelper: '{0} 秒內累計請求回傳 404 超過 {1} 次,封鎖此 IP {2}', + frequencyLimit: '頻率限制', + regionLimit: '地區限制', + defaultRule: '預設規則', + accessFrequencyLimit: '存取頻率限制', + attackLimit: '攻擊頻率限制', + notFoundLimit: '404 頻率限制', + urlLimit: 'URL 頻率限制', + urlLimitHelper: '為單一 URL 設定存取頻率', + sqliDefense: 'SQL 注入防禦', + sqliHelper: '辨識請求中的 SQL 注入並攔截', + xssHelper: '辨識請求中的 XSS 並攔截', + xssDefense: 'XSS 防禦', + uaDefense: '惡意 User-Agent 規則', + uaHelper: '包含常見的惡意爬蟲規則', + argsDefense: '惡意參數規則', + argsHelper: '在禁止請求中攜帶惡意參數', + cookieDefense: '惡意 Cookie 規則', + cookieHelper: '禁止請求中攜帶惡意 Cookie', + headerDefense: '惡意 Header 規則', + headerHelper: '禁止請求中攜帶惡意 Header', + httpRule: 'HTTP 請求方法規則', + httpHelper: + '設定允許存取的方法類型,如果想限制某些類型瀏覽,請關閉這個類型的按鈕,例如:僅允許 GET 類型瀏覽,那麼需要關閉除了 GET 之外的其他類型按鈕', + geoRule: '地區存取限制', + geoHelper: '限制某些地區瀏覽你的網站,例如:允許中國大陸瀏覽,那麼中國大陸以外的請求都會被攔截', + ipLocation: 'IP 歸屬地', + action: '動作', + ruleType: '攻擊類型', + ipHelper: '請輸入 IP', + attackLog: '攻擊日誌', + rule: '規則', + ipArr: 'IPV4 範圍', + ipStart: '起始 IP', + ipEnd: '結束 IP', + ipv4: 'IPV4', + ipv6: 'IPV6', + urlDefense: 'URL 規則', + urlHelper: '禁止存取的 URL', + dirFilter: '目錄過濾', + sqlInject: 'SQL 注入', + xss: 'XSS', + phpExec: 'PHP 腳本執行', + oneWordTrojan: '一句話木馬', + appFilter: '套用危險目錄過濾', + webshell: 'Webshell', + args: '惡意參數', + protocolFilter: '協議過濾', + javaFilter: 'Java 危險檔案過濾', + scannerFilter: '掃描器過濾', + escapeFilter: '轉義過濾', + customRule: '自訂規則', + httpMethod: 'HTTP 方法過濾', + fileExt: '檔案上傳限制', + fileExtHelper: '禁止上傳的檔案副檔名', + deny: '禁止', + allow: '允許', + field: '匹配物件', + pattern: '符合條件', + ruleContent: '符合內容', + contain: '包含', + equal: '等於', + regex: '正規表示式', + notEqual: '不等於', + customRuleHelper: '根據條件匹配執行對應動作', + actionAllow: '允許', + blockIP: '封鎖 IP', + code: '返回狀態碼', + noRes: '斷開連線 (444)', + badReq: '參數錯誤 (400)', + forbidden: '禁止瀏覽 (403)', + serverErr: '伺服器錯誤 (500)', + resHtml: '回應頁面', + allowHelper: '允許瀏覽會跳過後續的 WAF 規則,請謹慎使用', + captcha: '人機驗證', + fiveSeconds: '5 秒驗證', + location: '地區', + redisConfig: 'Redis 配置', + redisHelper: '開啟 Redis 可以將暫時封鎖的 IP 持久化', + wafHelper: '關閉之後所有網站將失去防護', + attackIP: '攻擊 IP', + attackParam: '攻擊訊息', + execRule: '命中規則', + acl: 'ACL', + sql: 'SQL 注入', + cc: '瀏覽頻率限制', + isBlocking: '封鎖中', + isFree: '已解封', + unLock: '解封', + unLockHelper: '是否解封 IP:{0}?', + saveDefault: '儲存預設', + saveToWebsite: '應用在網站', + saveToWebsiteHelper: '是否將目前設定套用到所有網站? ', + websiteHelper: '此處為建立網站的預設設定,修改之後需要應用到網站才能生效', + websiteHelper2: '此處為建立網站的預設設定,具體配置請在網站處修改', + ipGroup: 'IP 組', + ipGroupHelper: '一行一個 IP 或 IP 段,支援 IPv4 和 IPv6, 例如:192.168.1.1 或 192.168.1.0/24', + ipBlack: 'IP 黑名單', + openRestyAlert: 'OpenResty 版本需要高於 {0}', + initAlert: '首次使用需要初始化,會修改網站設定檔案,原有的 WAF 設定會遺失,請一定提前備份 OpenResty', + initHelper: '初始化操作將清除現有的 WAF 配置,您確定要進行初始化嗎? ', + mainSwitch: '總開關', + websiteAlert: '請先建立網站', + defaultUrlBlack: 'URL 規則', + htmlRes: '攔截頁面', + urlSearchHelper: '請輸入 URL,支援模糊搜尋', + toCreate: '去建立', + closeWaf: '關閉 WAF', + closeWafHelper: '關閉 WAF 會使網站失去防護,是否繼續', + addblack: '封鎖', + addwhite: '加白', + addblackHelper: '是否把 IP:{0} 加到預設黑名單?', + addwhiteHelper: '是否把 IP:{0} 加到預設白名單?', + defaultUaBlack: 'User-Agent 規則', + defaultIpBlack: '惡意 IP 群組', + cookie: 'Cookie 規則', + urlBlack: 'URL 黑名單', + uaBlack: 'User-Agent 黑名單', + attackCount: '攻擊頻率限制', + fileExtCheck: '檔案上傳限制', + geoRestrict: '地區存取限制', + attacklog: '攔截紀錄', + unknownWebsite: '未授權網域存取', + geoRuleEmpty: '地區不能為空', + unknown: '網站不存在', + geo: '地區限制', + revertHtml: '是否還原{0}為預設頁面?', + five_seconds: '5 秒驗證', + header: 'Header 規則', + methodWhite: 'HTTP 規則', + expiryDate: '有效期限', + expiryDateHelper: '驗證通過後有效期內不再驗證', + defaultIpBlackHelper: '從網路收集的一些惡意 IP,阻止其存取', + notFoundCount: '404 頻率限制', + matchValue: '匹配值', + headerName: '支援非特殊字元開頭、英文、數字、-,長度3-30', + cdnHelper: '使用 CDN 的網站可以打開此處來取得正確來源 IP', + clearLogWarn: '清空日誌將無法復原,是否繼續?', + commonRuleHelper: '規則為模糊匹配', + blockIPHelper: '封鎖 IP 暫時儲存在 OpenResty 中,重啟 OpenResty 會解封,可以透過封鎖功能永久封鎖', + addWhiteUrlHelper: '是否把 URL {0} 加到白名單?', + dashHelper: '社群版也可使用全域設定和網站設定中的功能', + wafStatusHelper: 'WAF 未開啟,請在全域設定中開啟', + ccMode: '模式', + global: '全域模式', + uriMode: 'URL 模式', + globalHelper: '全域模式:當單位時間內任意URL的請求總數超過閾值時觸發', + uriModeHelper: 'URL模式:單位時間內對單一URL的請求數量超過閾值時觸發', + ip: 'IP 黑名單', + globalSettingHelper: '有【網站】標籤的設定,需要在【網站設定】生效,全域設定僅為建立網站的預設設定', + globalSettingHelper2: '設定生效需要【全域設定】和【網站設定】的開關同時開啟', + urlCCHelper: '{0} 秒內累計請求此 URL 超過 {1} 次,封鎖此 IP {2}', + urlCCHelper2: 'URL 不能帶參數', + notContain: '不包含', + urlcc: 'URL 頻率限制', + method: '請求類型', + addIpsToBlock: '批次封鎖 IP', + addUrlsToWhite: '批次加白 URL', + noBlackIp: 'IP 已封鎖,無需再次封鎖', + noWhiteUrl: 'URL 已加白,無需再次加白', + spiderIpHelper: + '包含百度、Bing、Google、360、神馬、搜狗、位元組、DuckDuckGo、Yandex,關閉之後會攔截所有蜘蛛訪問', + spiderIp: '蜘蛛 IP 池', + geoIp: 'IP 位址庫', + geoIpHelper: '用來確認 IP 的地理位置', + stat: '攻擊報表', + statTitle: '報表', + attackIp: '攻擊 IP', + attackCountNum: '攻擊次數', + percent: '佔比', + addblackUrlHelper: '是否把 URL:{0} 新增到預設黑名單?', + rce: '遠端程式碼執行', + software: '軟體', + cveHelper: '包含常見軟體、框架的漏洞', + vulnCheck: '補充規則', + ssrf: 'SSRF 漏洞', + afr: '任意文件讀取', + ua: '未授權訪問', + id: '資訊洩露', + aa: '認證繞過', + dr: '目錄遍歷', + xxe: 'XXE 漏洞', + suid: '序列化漏洞', + dos: '拒絕服務漏洞', + afd: '任意文件下載', + sqlInjection: 'SQL 注入', + afw: '任意文件寫入', + il: '資訊洩露', + clearAllLog: '清空所有日誌', + exportLog: '匯出日誌', + appRule: '應用規則', + appRuleHelper: '常見應用的規則,開啟之後可以減少誤報,一個網站只能使用一個規則', + logExternal: '排除記錄類型', + ipWhite: 'IP 白名單', + urlWhite: 'URL 白名單', + uaWhite: 'User-Agent 白名單', + logExternalHelper: + '排除記錄類型不會被記錄到日誌中,黑白名單、地區訪問限制、自訂規則會產生大量日誌,建議排除', + ssti: 'SSTI 攻擊', + crlf: 'CRLF 注入', + strict: '嚴格模式', + strictHelper: '使用更嚴格的規則來校驗請求', + saveLog: '儲存日誌', + remoteURLHelper: '遠端 URL 需要保證每行一個 IP 並且沒有其他字元', + notFound: 'Not Found (404)', + serviceUnavailable: '服務不可用 (503)', + gatewayTimeout: '閘道器超時 (504)', + belongToIpGroup: '屬於 IP 組', + notBelongToIpGroup: '不屬於 IP 組', + unknownWebsiteKey: '未知域名', + special: '指定', + fileToLarge: '檔案超過 1MB 無法上傳', + uploadOverLimit: '上傳檔案超過數量限制,最大1個', + importRuleHelper: '一行一個規則', + }, + monitor: { + name: '網站監控', + pv: '瀏覽量', + uv: '訪客數', + flow: '流量', + ip: '獨立 IP', + spider: '蜘蛛', + visitors: '訪客趨勢', + today: '今天', + last7days: '最近 7 天', + last30days: '最近 30 天', + uvMap: '訪客地圖 (30日)', + qps: '即時請求數(1分鐘)', + flowSec: '即時流量(1分鐘)', + excludeCode: '排除狀態碼', + excludeUrl: '排除 URL', + excludeExt: '排除副檔名', + cdnHelper: '透過 CDN 設定的 Header 來取得真實 IP', + reqRank: '瀏覽排行', + refererDomain: '來源網域', + os: '作業系統', + browser: '瀏覽器/用戶端', + device: '裝置', + showMore: '看更多', + unknown: '其他', + pc: '電腦', + mobile: '行動端', + wechat: '微信', + machine: '機器', + tencent: '騰訊瀏覽器', + ucweb: 'UC 瀏覽器', + '2345explorer': '2345 瀏覽器', + huaweibrowser: '華為瀏覽器', + log: '請求日誌', + statusCode: '狀態碼', + requestTime: '回應時間', + flowRes: '回應流量', + method: '請求類型', + statusCodeHelper: '可在上方輸入狀態碼', + statusCodeError: '狀態碼型別錯誤', + methodHelper: '可在上方輸入請求類型', + all: '所有', + baidu: '百度', + google: 'Google', + bing: '必應', + bytes: '今日頭條', + sogou: '搜狗', + failed: '錯誤', + ipCount: 'IP 數', + spiderCount: '蜘蛛請求', + averageReqTime: '平均回應時間', + totalFlow: '總流量', + logSize: '日誌檔案大小', + realIPType: '真實IP取得方式', + fromHeader: '從 HTTP Header 取得', + fromHeaders: '從 Header 清單中取得', + header: 'HTTP Header', + cdnConfig: 'CDN 適配', + xff1: '取得 X-Forwarded-For 的上一級代理程式', + xff2: '取得 X-Forwarded-For 的上上一級代理程式', + xff3: '取得 X-Forwarded-For 的上上上一級代理程式', + xffHelper: + '例如:X-Forwarded-For: ,,, 上一階代理程式會取最後一個 IP ', + headersHelper: '從下列常用的 CDN 攜帶真實 IP 的 HTTP Header 中取得,取第一個能取得到的值', + monitorCDNHelper: '修改網站監控的 CDN 設定會同步更新 WAF 的 CDN 設定', + wafCDNHelper: '修改 WAF 的 CDN 設定會同步更新網站監控的 CDN 設定', + statusErr: '狀態碼格式錯誤', + shenma: '神馬搜尋', + duckduckgo: 'DuckDuckGo', + '360': '360 搜尋', + exceptUri: '排除 Uri', + top100Helper: '顯示 Top 100 的資料', + logSaveDay: '日誌儲存天數', + cros: 'Chrome OS', + theworld: '世界之窗瀏覽器', + edge: 'Edge', + maxthon: '遨遊瀏覽器', + monitorStatusHelper: '監控未開啟,請在設定中開啟', + excludeIp: '排除 IP', + excludeUa: '排除 User-Agent', + remotePort: '遠端埠', + unknown_browser: '未知', + unknown_os: '未知', + unknown_device: '未知', + logSaveSize: '最大日誌儲存大小', + logSaveSizeHelper: '此處為單個網站的日誌儲存大小', + '360se': '360 安全瀏覽器', + websites: '網站列表', + trend: '趨勢統計', + reqCount: '請求數', + uriHelper: '可以使用 /test/* 或者 /*/index.php 來排除 Uri', + }, + tamper: { + tamper: '網站防篡改', + ignoreTemplate: '排除模版', + protectTemplate: '保護模版', + ignoreTemplateHelper: + '請輸入排除內容,Enter或空格分隔。(具體目錄 ./log 或目錄名 tmp,排除文件需要輸入具體檔案 ./data/test.html)', + protectTemplateHelper: + '請輸入保護內容,Enter或空格分隔。(具體檔案 ./index.html、文件後綴 .html、文件類型 js,保護目錄需要輸入具體目錄 ./log)', + templateContent: '模版內容', + template: '模版', + saveTemplate: '儲存為模版', + tamperHelper1: + '一鍵部署類型的網站,建議啟用應用目錄防篡改功能;如出現網站無法正常使用或備份、復原失敗的情況,請先關閉防篡改功能;', + tamperHelper2: '將限制非排除目錄下受保護文件的讀寫、刪除、權限和所有者修改操作', + tamperPath: '防護目錄', + tamperPathEdit: '修改路徑', + log: '攔截日誌', + totalProtect: '總防護', + todayProtect: '今日防護', + templateRule: '長度1-512,名稱不能含有{0}等符號', + ignore: '排除', + ignoreHelper: + '請選擇或輸入排除內容,Enter或空格分隔。(具體目錄 ./log 或目錄名 tmp,排除文件需要輸入或選擇具體檔案 ./data/test.html)', + protect: '保護', + protectHelper: + '請選擇或輸入保護內容,Enter或空格分隔。(具體檔案 ./index.html、文件後綴 .html、文件類型 js,保護目錄需要輸入或選擇具體目錄 ./log)', + tamperHelper00: '排除和保護僅支援輸入相對路徑;', + tamperHelper01: '開啟防篡改後,系統將限制非排除目錄下保護文件的建立、編輯和刪除操作等操作;', + tamperHelper02: '優先度:具體路徑保護 > 具體路徑排除 > 保護 > 排除', + tamperHelper03: '監聽操作只針對於非排除目錄,監聽該目錄下非保護文件的建立操作。', + disableHelper: '即將關閉下列網站的防篡改功能,是否繼續?', + appendOnly: '僅追加', + appendOnlyHelper: '將限制該目錄下文件的刪除操作,僅允許新增排除目錄或非保護文件', + immutable: '不可變', + immutableHelper: '將限制該文件的編輯、刪除、權限和所有者修改操作', + onWatch: '監聽', + onWatchHelper: '監聽該目錄下保護文件或非排除目錄的建立攔截', + forceStop: '強制關閉', + forceStopHelper: '即將強制停用該網站目錄的防篡改功能,是否繼續?', + }, + setting: { + setting: '介面設定', + title: '面板描述', + titleHelper: '將會顯示在使用者登入頁面 (例: Linux 伺服器運維管理面板,建議 8-15 位)', + logo: 'Logo (不帶文字)', + logoHelper: '將會顯示在選單收縮時管理頁面左上方 (建議圖片大小為: 82px*82px)', + logoWithText: 'Logo (帶文字)', + logoWithTextHelper: '將會顯示在選單展開時管理頁面左上方 (建議圖片大小為: 185px*55px)', + favicon: '網站圖示', + faviconHelper: '網站圖示 (建議圖片大小為: 16px*16px)', + setDefault: '復原預設', + setHelper: '即將儲存目前介面設定內容,是否繼續?', + setDefaultHelper: '即將復原所有介面設定到初始狀態,是否繼續?', + logoGroup: 'Logo', + imageGroup: '圖片', + loginImage: '登入頁圖片', + loginImageHelper: '將會顯示在登入頁面(建議圖片大小為:500*416px)', + loginBgType: '登入頁背景類型', + loginBgImage: '登入頁背景圖片', + loginBgImageHelper: '將會顯示在登入頁面背景圖片(建議圖片大小為:1920*1080px)', + loginBgColor: '登入頁背景顏色', + loginBgColorHelper: '將會顯示在登入頁面背景顏色', + image: '圖片', + bgColor: '背景色', + loginGroup: '登入頁面', + loginBtnLinkColor: '按鈕顏色', + loginBtnLinkColorHelper: '將顯示為登入頁面上的按鈕顏色', + }, + helper: { + wafTitle1: '攔截地圖', + wafContent1: '統計並顯示 30 天內的攔截地理位置分佈', + wafTitle2: '地區瀏覽限制', + wafContent2: '依地理位置限制網站的存取來源', + wafTitle3: '自訂攔截頁面', + wafContent3: '自訂請求被攔截之後的顯示頁面', + wafTitle4: '自訂規則(ACL)', + wafContent4: '根據自訂的規則攔截請求', + + tamperTitle1: '檔案完整性監控', + tamperContent1: '監控網站檔案的完整性,包括核心檔案、腳本檔案和配置檔案等。', + tamperTitle2: '即時掃描和檢測', + tamperContent2: '透過即時掃描網站檔案系統,檢測是否存在異常或被篡改的檔案。', + tamperTitle3: '安全權限設定', + tamperContent3: + '透過合理的權限設定和瀏覽控制策略,網站防篡改功能可以限制對網站檔案的瀏覽權限,減少潛在的攻擊面。', + tamperTitle4: '日誌紀錄與分析', + tamperContent4: '紀錄檔案瀏覽和操作日誌,以便管理員進行後續的審計和分析,以及發現潛在的安全威脅。', + + settingTitle1: '自訂歡迎語', + settingContent1: '在 1Panel 登入頁上設定自訂的歡迎語。', + settingTitle2: '自訂 Logo', + settingContent2: '允許上傳包含品牌名稱或其他文字的 Logo 圖像。', + settingTitle3: '自訂網站圖示', + settingContent3: '允許上傳自訂的圖示,以替代預設的瀏覽器圖示,提升使用者體驗。', + + monitorTitle1: '訪客趨勢', + monitorContent1: '統計並顯示網站的訪客趨勢', + monitorTitle2: '訪客地圖', + monitorContent2: '統計並顯示網站的訪客地理位置分佈', + monitorTitle3: '瀏覽統計', + monitorContent3: '統計網站的請求資訊,包括蜘蛛,瀏覽裝置,請求狀態等', + monitorTitle4: '即時監控', + monitorContent4: '即時監控網站的請求訊息,包括請求數,流量等', + + alertTitle1: '簡訊告警', + alertContent1: + '當伺服器資源使用異常、網站及證書過期、新版本更新、密碼過期等情況發生時,透過簡訊告警通知使用者,確保及時處理。', + alertTitle2: '告警日誌', + alertContent2: '為使用者提供查看告警日誌的功能,方便追跡和分析歷史告警事件。', + alertTitle3: '告警設定', + alertContent3: + '為使用者提供自訂手機號碼、每日推送次數、每日推送時間的配寘,方便使用者的設定來更加合理的進行推送告警。', + + nodeDashTitle1: '應用管理', + nodeDashContent1: '統一管理多節點應用,支援狀態監控、快速起停、終端連線及備份', + nodeDashTitle2: '網站管理', + nodeDashContent2: '統一管理多節點網站,即時監控狀態,支援批次起停與快速備份', + nodeDashTitle3: '資料庫管理', + nodeDashContent3: '統一管理多節點資料庫,關鍵狀態一目了然,支援一鍵備份', + nodeDashTitle4: '計劃任務管理', + nodeDashContent4: '統一管理多節點計劃任務,支援狀態監控、快速啟停與手動觸發執行', + + nodeTitle1: '一鍵新增節點', + nodeContent1: '快速接入多台伺服器節點', + nodeTitle2: '批次升級', + nodeContent2: '一次操作同步升級所有節點', + nodeTitle3: '節點狀態監控', + nodeContent3: '即時掌握各節點執行狀態', + nodeTitle4: '快速遠端連接', + nodeContent4: '一鍵直連節點遠端終端', + + fileExchangeTitle1: '金鑰認證傳輸', + fileExchangeContent1: '通過 SSH 金鑰進行身份驗證,確保傳輸的安全性。', + fileExchangeTitle2: '高效文件同步', + fileExchangeContent2: '僅同步變化內容,大幅提高傳輸速度與穩定性。', + fileExchangeTitle3: '支援多節點互傳', + fileExchangeContent3: '可在不同節點間便捷傳送項目文件,靈活管理多台伺服器。', + + nodeAppTitle1: '應用升級管理', + nodeAppContent1: '統一監控多節點應用更新情況,支援一鍵升級', + + appTitle1: '靈活管理面板', + appContent1: '隨時隨地輕鬆管理你的 1Panel 伺服器。', + appTitle2: '全面服務資訊', + appContent2: '在行動端進行應用、網站、Docker、資料庫等基礎管理,支援快速建立應用與網站。', + appTitle3: '即時異常監控', + appContent3: '行動端即時查看伺服器狀態、WAF 安全監控、網站訪問統計與行程健康狀況。', + + clusterTitle1: '主從部署', + clusterContent1: '支援在不同節點建立 MySQL/Postgres/Redis 主從實例,自動完成主從關聯與初始化', + clusterTitle2: '主從管理', + clusterContent2: '統一頁面集中管理多個主從節點,查看其角色、執行狀態等資訊', + clusterTitle3: '複製狀態', + clusterContent3: '展示主從複製狀態與延遲資訊,輔助排查同步異常問題', + }, + node: { + master: '主節點', + masterBackup: '主節點備份', + backupNode: '備份節點', + backupFrequency: '備份頻率(小時)', + backupCopies: '備份記錄保留份數', + noBackupNode: '目前備份節點為空,請選擇備份節點儲存後重試!', + masterBackupAlert: + '目前未配置主節點備份,為保障資料安全,請盡快設定備份節點,便於主節點故障時可人工切換新主節點。', + node: '節點', + addr: '地址', + nodeUpgrade: '更新設定', + nodeUpgradeHelper: '選中的節點將在主節點升級完成後自動開始升級,無需手動操作。', + nodeUnhealthy: '節點狀態異常', + deletedNode: '已刪除節點 {0} 暫不支援升級操作!', + nodeUnhealthyHelper: '檢測到該節點狀態異常,請在 [節點管理] 中檢查後重試!', + nodeUnbind: '節點未綁定許可證', + nodeUnbindHelper: '檢測到該節點未綁定許可證,請在 [面板設定 - 許可證] 選單中綁定後重試!', + memTotal: '記憶體總計', + nodeManagement: '多機管理', + nodeItem: '節點管理', + panelItem: '面板管理', + addPanel: '添加面板', + addPanelHelper: '面板新增成功後,您可在 [ 概覽 - 面板 ] 中快速存取目標面板。', + panel: '1Panel 面板', + others: '其他面板', + addNode: '新增節點', + connInfo: '連接資訊', + nodeInfo: '節點資訊', + withProxy: '開啟代理存取', + withoutProxy: '關閉代理存取', + withProxyHelper: '將使用面板設定中維護的系統代理 {0} 存取子節點,是否繼續?', + withoutProxyHelper: '將停止使用面板設定中維護的系統代理存取子節點,是否繼續?', + syncInfo: '資料同步,', + syncHelper: '當主節點資料發生變化時,即時同步到該子節點,', + syncBackupAccount: '備份帳號設定', + syncWithMaster: '升級為專業版後,將預設同步所有資料,可在節點管理中手動調整同步策略。', + syncProxy: '系統代理設定', + syncProxyHelper: '同步系統代理設定需要重啟 Docker', + syncProxyHelper1: '重啟 Docker 可能會影響目前正在執行的容器服務。', + syncProxyHelper2: '可前往 容器 - 設定 頁面手動重啟。', + syncProxyHelper3: '同步系統代理設定需要重啟 Docker,重啟可能會影響目前正在執行的容器服務', + syncProxyHelper4: '同步系統代理設定需要重啟 Docker,可稍後前往 容器 - 設定 頁面手動重啟。', + syncCustomApp: '同步自訂應用倉庫', + syncAlertSetting: '系統告警設定', + syncNodeInfo: '節點基礎資料,', + nodeSyncHelper: '節點資訊同步將同步以下資訊:', + nodeSyncHelper1: '1. 公用的備份帳號資訊', + nodeSyncHelper2: '2. 主節點與子節點的連接資訊', + + nodeCheck: '可用性檢查', + checkSSH: '檢查節點 SSH 連接', + checkUserPermission: '檢查節點使用者權限', + isNotRoot: '檢測到該節點不支援免密 sudo,且目前為非 root 使用者', + checkLicense: '檢查節點許可證狀態', + checkService: '檢查節點已存在服務資訊', + checkPort: '檢查節點埠可達', + panelExist: '檢查到該節點正在執行 1Panel V1 服務,請先透過遷移腳本升級至 V2 後再進行新增。', + coreExist: + '目前節點已作為主節點啟用,無法直接作為從節點新增。請先將其降級為從節點後再新增,具體可參考文件。', + agentExist: '檢查到該節點已安裝 1panel-agent,繼續新增將保留現有資料,僅取代 1panel-agent 服務。', + agentNotExist: '檢測到該節點未安裝 1panel-agent,無法直接編輯該節點資訊,請先刪除後重新新增。', + oldDataExist: '檢查到該節點存在 1Panel V2 歷史資料,將使用以下資訊覆蓋目前設定', + errLicense: '檢查到該節點綁定的許可證不可用,請檢查後重試!', + errNodePort: '檢查到節點埠 [ {0} ] 無法訪問,請檢查防火牆或安全組是否已放行該埠。', + + reinstallHelper: '重新安裝節點 {0},是否繼續?', + unhealthyCheck: '異常檢查', + fixOperation: '修復方案', + checkName: '檢查項目', + checkSSHConn: '檢查 SSH 連接可用性', + fixSSHConn: '手動編輯節點,確認連接資訊', + checkConnInfo: '檢查 Agent 連接資訊', + checkStatus: '檢查節點服務可用性', + fixStatus: '執行 "systemctl status 1panel-agent.service" 以檢查服務是否已啟動。', + checkAPI: '檢查節點 API 可用性', + fixAPI: '檢查節點日誌,確認防火牆埠是否正常放行。', + forceDelete: '強制刪除', + operateHelper: '將對以下節點執行 {0} 操作,是否繼續?', + operatePanelHelper: '將對以下面板進行 {0} 操作,是否繼續?', + uninstall: '刪除節點資料', + uninstallHelper: '此操作將刪除與節點相關的所有 1Panel 資料,請謹慎選擇!', + forceDeleteHelper: '強制刪除將忽略節點刪除錯誤並刪除資料庫中繼資料', + baseDir: '安裝目錄', + baseDirHelper: '安裝目錄為空時,預設安裝於 /opt 目錄下', + nodePort: '節點埠', + offline: '離線模式', + freeCount: '免費額度 [{0}]', + offlineHelper: '當節點處於離線環境時使用', + + appUpgrade: '應用程式升級', + appUpgradeHelper: '有 {0} 個應用程式需要升級', + }, + customApp: { + name: '自訂應用倉庫', + appStoreType: '應用商店包來源', + appStoreUrl: '倉庫地址', + local: '本機路徑', + remote: '遠端連結', + imagePrefix: '映像前綴', + imagePrefixHelper: + '作用:自訂映像前綴,修改 compose 檔案中的映像欄位,例如:當映像前綴設定為 1panel/custom 時,MaxKB 的映像欄位將變更為 1panel/custom/maxkb:v1.10.0', + closeHelper: '是否取消使用自訂應用倉庫', + appStoreUrlHelper: '僅支援 .tar.gz 格式', + postNode: '同步至子節點', + postNodeHelper: '把自訂商店包同步至子節點的安裝目錄下的 tmp/customApp/apps.tar.gz 中', + nodes: '節點', + selectNode: '選擇節點', + selectNodeError: '請選擇節點', + licenseHelper: '專業版支援自訂應用倉庫功能', + databaseHelper: '應用關聯數據庫,請選擇目標節點數據庫', + nodeHelper: '不能選擇當前節點', + migrateHelper: '目前僅支持遷移單體應用和只關聯 MySQL、MariaDB、PostgreSQL 數據庫的應用', + opensslHelper: '如果使用加密備份,兩個節點之間的 OpenSSL 版本必須保持一致,否則可能導致遷移失敗。', + installApp: '批量安裝', + installAppHelper: '批量安裝應用到選擇的節點中', + }, + alert: { + isAlert: '是否告警', + alertCount: '告警次數', + clamHelper: '掃描到感染檔案時觸發告警', + cronJobHelper: '定時任務執行失敗時將觸發告警', + licenseHelper: '專業版支援簡訊告警功能', + alertCountHelper: '每日最大告警次數', + alert: '簡訊告警', + logs: '告警日誌', + list: '告警清單', + addTask: '建立簡訊告警', + editTask: '編輯簡訊告警', + alertMethod: '告警方式', + alertMsg: '告警內容', + alertRule: '告警規則', + titleSearchHelper: '請輸入告警標題,支援模糊搜尋', + taskType: '告警類型', + ssl: '網站證書到期', + siteEndTime: '網站到期', + panelPwdEndTime: '面板密碼到期', + panelUpdate: '面板新版本提醒', + cpu: '面板伺服器 CPU 佔用過高告警', + memory: '面板伺服器記憶體佔用過高告警', + load: '面板伺服器負載佔用過高告警', + disk: '面板伺服器磁碟佔用過高告警', + website: '網站', + certificate: '證書', + remainingDays: '剩餘天數', + sendCount: '告警次數', + sms: '簡訊通知', + wechat: '微信公眾號', + dingTalk: '釘釘通知', + feiShu: '飛書通知', + mail: '信箱通知', + weCom: '企業微信', + sendCountRulesHelper: '到期前發送告警的總數(每日僅發送一次)', + panelUpdateRulesHelper: '新版本發送告警總數(每日僅發送一次)', + oneDaySendCountRulesHelper: '每日發送告警的總數', + siteEndTimeRulesHelper: '永不過期的網站,不觸發告警', + autoRenewRulesHelper: '證書開啟自動續期,剩餘天數小於31天,不觸發告警', + panelPwdEndTimeRulesHelper: '面板未設定密碼到期時長,不能使用密碼到期告警', + sslRulesHelper: '所有ssl證書', + diskInfo: '磁碟資訊', + monitoringType: '監測類型', + autoRenew: '自動續簽', + useDisk: '佔用磁碟', + usePercentage: '佔用百分比', + changeStatus: '狀態修改', + disableMsg: '停止告警任務會導致該任務不再發送告警消息。 是否繼續?', + enableMsg: '啟用告警任務會讓該任務發送告警消息。 是否繼續?', + useExceed: '使用超過', + useExceedRulesHelper: '使用超過指定值觸發告警', + cpuUseExceedAvg: 'CPU 平均使用率超過指定值', + memoryUseExceedAvg: '記憶體平均使用率超過指定值', + loadUseExceedAvg: '負載平均使用率超過指定值', + cpuUseExceedAvgHelper: '指定時間內 CPU 平均使用率超過指定值', + memoryUseExceedAvgHelper: '指定時間內記憶體平均使用率超過指定值', + loadUseExceedAvgHelper: '指定時間內負載平均使用率超過指定值', + resourceAlertRulesHelper: '注意:30分鐘內持續告警只發送一次', + specifiedTime: '指定時間', + deleteTitle: '刪除告警', + deleteMsg: '是否確認刪除告警任務?', + + allSslTitle: '所有網站證書到期告警', + sslTitle: '網站「{0}」證書到期告警', + allSiteEndTimeTitle: '所有網站到期告警', + siteEndTimeTitle: '網站「{0}」到期告警', + panelPwdEndTimeTitle: '面板密碼到期告警', + panelUpdateTitle: '面板新版本提醒', + cpuTitle: 'CPU 佔用過高告警', + memoryTitle: '記憶體佔用過高告警', + loadTitle: '負載佔用過高告警', + diskTitle: '掛載目錄「{0}」的磁碟佔用過高告警', + allDiskTitle: '磁碟佔用過高告警', + + timeRule: '剩餘時間小於{0}天(如未處理,次日會重新發送)', + panelUpdateRule: '檢測到面板有新版本時發送一次(如未處理,次日會重新發送)', + avgRule: '{0}分鐘內平均{1}佔用超過{2}%觸發,每天發送{3}次', + diskRule: '掛載目錄「{0}」的磁碟佔用超過{1}{2}觸發,每天發送{3}次', + allDiskRule: '磁碟佔用超過{0}{1}觸發,每天發送{2}次', + + cpuName: ' CPU ', + memoryName: '記憶體', + loadName: '負載', + diskName: '磁碟', + + syncAlertInfo: '同步告警資訊', + syncAlertInfoMsg: '是否同步告警資訊內容狀態?', + pushError: '推送失敗', + pushSuccess: '推送成功', + syncError: '同步失敗', + success: '告警成功', + pushing: '推送中...', + error: '告警失敗', + cleanLog: '清空日誌', + cleanAlertLogs: '清空告警日誌', + daily: '當日第 {0} 次告警', + cumulative: '累計第 {0} 次告警', + clams: '病毒掃描告警', + taskName: '任務名稱', + cronJobType: '任務類型', + clamPath: '掃描目錄', + cronjob: '計劃任務執行{0}異常', + app: '備份應用', + web: '備份網站', + database: '備份資料庫', + directory: '備份目錄', + log: '備份日誌', + snapshot: '系統快照', + clamsRulesHelper: '需要開啟告警的病毒掃描任務', + cronJobRulesHelper: '需要配寘此類型的計劃任務', + clamsTitle: '病毒掃描「{0}」任務檢測到感染文件告警', + cronJobAppTitle: '計劃任務-備份應用「{0}」任務失敗告警', + cronJobWebsiteTitle: '計劃任務-備份網站「{0}」任務失敗告警', + cronJobDatabaseTitle: '計劃任務-備份資料庫「{0}」任務失敗告警', + cronJobDirectoryTitle: '計劃任務-備份目錄「{0}」任務失敗告警', + cronJobLogTitle: '計劃任務-備份日誌「{0}」任務失敗告警', + cronJobSnapshotTitle: '計劃任務-系統快照「{0}」任務失敗告警', + cronJobShellTitle: '計劃任務-Shell 腳本「 {0} 」任務失敗告警', + cronJobCurlTitle: '計劃任務-瀏覽 URL「 {0} 」任務失敗告警', + cronJobCutWebsiteLogTitle: '計劃任務-切割網站日誌「 {0} 」任務失敗告警', + cronJobCleanTitle: '計劃任務-快取清理「 {0} 」任務失敗告警', + cronJobNtpTitle: '計劃任務-同步伺服器時間「 {0} 」任務失敗告警', + clamsRule: '病毒掃描任務檢測到感染文件告警,每天發送{0}次', + cronJobAppRule: '備份應用任務失敗告警,每天發送{0}次', + cronJobWebsiteRule: '備份網站任務失敗告警,每天發送{0}次', + cronJobDatabaseRule: '備份資料庫任務失敗告警,每天發送{0}次', + cronJobDirectoryRule: '備份目錄任務失敗告警,每天發送{0}次', + cronJobLogRule: '備份日誌任務失敗告警,每天發送{0}次', + cronJobSnapshotRule: '系統快照任務失敗告警,每天發送{0}次', + cronJobShellRule: 'Shell 腳本任務失敗告警,每天發送{0}次', + cronJobCurlRule: '瀏覽 URL任務失敗告警,每天發送{0}次', + cronJobCutWebsiteLogRule: '切割網站日誌任務失敗告警,每天發送{0}次', + cronJobCleanRule: '快取清理任務失敗告警,每天發送{0}次', + cronJobNtpRule: '同步伺服器時間任務失敗告警,每天發送{0}次', + alertSmsHelper: '簡訊額度:總量{0}條,已使用{1}條', + goBuy: '去購買', + phone: '手機號碼', + phoneHelper: '請請填寫真實的手機號碼,以免不能正常接收告警資訊', + dailyAlertNum: '每日告警次數', + dailyAlertNumHelper: '每日告警通知的總次數,最多通知 100 次', + timeRange: '時間範圍', + sendTimeRange: '可發送時間範圍', + sendTimeRangeHelper: '可推送{0}時間範圍', + to: '至', + startTime: '開始時間', + endTime: '結束時間', + defaultPhone: '預設使用與許可證綁定的帳戶手機號碼', + noticeAlert: '通知告警', + resourceAlert: '資源告警', + agentOfflineAlertHelper: '當節點啟用離線告警時,主節點將每半小時掃描並執行一次告警任務。', + offline: '離線告警', + offlineHelper: '設為離線告警時,主節點將每半小時掃描並執行一次告警任務。', + offlineOff: '開啟離線告警', + offlineOffHelper: '開啟離線告警後,主節點將每半小時掃描並執行一次告警任務。', + offlineClose: '關閉離線告警', + offlineCloseHelper: '關閉離線告警後,需由子節點自行告警,請確保子節點網路通暢,以免告警失敗。', + alertNotice: '警報通知', + methodConfig: '發送方式設定', + commonConfig: '全域設定', + smsConfig: '簡訊', + smsConfigHelper: '設定簡訊通知號碼', + emailConfig: '郵件', + emailConfigHelper: '設定 SMTP 郵件發送服務', + deleteConfigTitle: '刪除警報設定', + deleteConfigMsg: '是否確定刪除此警報設定?', + test: '測試', + alertTestOk: '測試通知成功', + alertTestFailed: '測試通知失敗', + displayName: '顯示名稱', + sender: '寄件地址', + password: '密碼', + host: 'SMTP 伺服器', + port: '連接埠', + encryption: '加密方式', + recipient: '收件者', + licenseTime: '授權到期提醒', + licenseTimeTitle: '授權到期提醒', + displayNameHelper: '郵件的寄件人顯示名稱', + senderHelper: '用於發送郵件的電子信箱地址', + passwordHelper: '郵件服務的授權碼', + hostHelper: 'SMTP 伺服器地址,例如:smtp.qq.com', + portHelper: 'SSL 通常為 465,TLS 通常為 587', + sslHelper: '若 SMTP 連接埠為 465,通常需要啟用 SSL', + tlsHelper: '若 SMTP 連接埠為 587,通常需要啟用 TLS', + triggerCondition: '觸發條件', + loginFail: '內,登入失敗', + nodeException: '節點異常告警', + licenseException: '許可證異常告警', + panelLogin: '面板登入異常告警', + sshLogin: 'SSH 登入異常告警', + panelIpLogin: '面板登入 IP 異常告警', + sshIpLogin: 'SSH 登入 IP 異常告警', + ipWhiteListHelper: '在白名單中的 IP 不受規則限制,成功登入時也不會產生警報', + nodeExceptionRule: '節點異常告警,每天發送 {0} 次', + licenseExceptionRule: '許可證異常告警,每天發送 {0} 次', + panelLoginRule: '面板登入告警,每天發送 {0} 次', + sshLoginRule: 'SSH 登入告警,每天發送 {0} 次', + userNameHelper: '使用者名稱為空時,將預設使用寄件者地址', + }, + theme: { + lingXiaGold: '凌霞金', + classicBlue: '經典藍', + freshGreen: '清新綠', + customColor: '自訂主題色', + setDefault: '復原預設', + setDefaultHelper: '即將復原主題配色到初始狀態,是否繼續?', + setHelper: '即將儲存目前選定的主題配色,是否繼續?', + }, + app: { + app: 'APP', + title: '面板別名', + titleHelper: '面板別名用於 APP 端的顯示(預設面板別名)', + qrCode: '二維碼', + apiStatusHelper: '面板 APP 需要開啟 API 介面功能', + apiInterfaceHelper: '支援面板 API 介面訪問功能(面板 APP 需要開啟該功能)', + apiInterfaceHelper1: + '面板 APP 訪問需將訪問者新增至白名單,非固定 IP 建議新增 0.0.0.0/0(所有 IPv4),::/0(所有 IPv6)', + qrCodeExpired: '重新整理時間', + apiLeakageHelper: '請勿洩露二維碼,確保僅在受信任的環境中使用', + }, + exchange: { + exchange: '文件對傳', + exchangeConfirm: '是否將 {0} 節點文件/資料夾 {1} 傳輸到 {2} 節點 {3} 目錄?', + }, + cluster: { + cluster: '應用高可用', + name: '叢集名稱', + addCluster: '新增叢集', + installNode: '安裝節點', + master: '主節點', + slave: '從節點', + replicaStatus: '主從狀態', + unhealthyDeleteError: '安裝節點狀態異常,請在節點列表檢查後重試!', + replicaStatusError: '狀態取得異常,請檢查主節點。', + masterHostError: '主節點 IP 不能為 127.0.0.1', + }, + }, +}; +export default { + ...fit2cloudTwLocale, + ...message, +}; diff --git a/frontend/src/lang/modules/zh.ts b/frontend/src/lang/modules/zh.ts new file mode 100644 index 0000000..dea62f6 --- /dev/null +++ b/frontend/src/lang/modules/zh.ts @@ -0,0 +1,3855 @@ +import fit2cloudZhLocale from 'fit2cloud-ui-plus/src/locale/lang/zh-cn'; + +const message = { + commons: { + true: '是', + false: '否', + example: '例:', + fit2cloud: '飞致云', + lingxia: '凌霞', + colon: ':', + button: { + run: '运行', + prev: '上一步', + next: '下一步', + create: '创建', + add: '添加', + save: '保存', + set: '设置', + sync: '同步', + delete: '删除', + edit: '编辑', + enable: '启用', + disable: '停用', + confirm: '确认', + cancel: '取消', + reset: '重置', + setDefault: '恢复默认', + restart: '重启', + conn: '连接', + disConn: '断开', + clean: '清空', + login: '登录', + close: '关闭', + stop: '关闭', + start: '开启', + view: '详情', + watch: '追踪', + handle: '执行', + clone: '克隆', + expand: '展开', + collapse: '收起', + log: '日志', + back: '返回', + backup: '备份', + recover: '恢复', + retry: '重试', + upload: '上传', + download: '下载', + init: '初始化', + verify: '验证', + saveAndEnable: '保存并启用', + import: '导入', + export: '导出', + power: '授权', + search: '搜索', + refresh: '刷新', + get: '获取', + upgrade: '升级', + update: '更新', + updateNow: '立即更新', + ignore: '忽略升级', + copy: '复制', + random: '随机密码', + install: '安装', + uninstall: '卸载', + fullscreen: '网页全屏', + quitFullscreen: '退出网页全屏', + showAll: '显示所有', + hideSome: '隐藏部分', + agree: '同意', + notAgree: '不同意', + preview: '预览', + open: '打开', + notSave: '不保存', + createNewFolder: '新建文件夹', + createNewFile: '新建文件', + helpDoc: '帮助文档', + bind: '绑定', + unbind: '解绑', + cover: '覆盖', + skip: '跳过', + fix: '修复', + down: '停止', + up: '启动', + sure: '确定', + show: '显示', + hide: '隐藏', + visit: '访问', + migrate: '迁移', + }, + operate: { + start: '启动', + stop: '停止', + restart: '重启', + reload: '重载', + rebuild: '重建', + sync: '同步', + up: '启动', + down: '停止', + delete: '删除', + }, + search: { + timeStart: '开始时间', + timeEnd: '结束时间', + timeRange: '至', + dateStart: '开始日期', + dateEnd: '结束日期', + date: '日期', + }, + table: { + all: '所有', + total: '共 {0} 条', + name: '名称', + type: '类型', + status: '状态', + records: '任务输出', + group: '分组', + default: '默认', + createdAt: '创建时间', + publishedAt: '发布时间', + date: '时间', + updatedAt: '更新时间', + operate: '操作', + message: '信息', + description: '描述', + interval: '耗时', + user: '用户', + title: '标题', + port: '端口', + forward: '转发', + protocol: '协议', + tableSetting: '列表设置', + refreshRate: '刷新频率', + noRefresh: '不刷新', + selectColumn: '选择列', + local: '本地', + serialNumber: '序号', + manageGroup: '管理分组', + backToList: '返回列表', + keepEdit: '继续编辑', + }, + loadingText: { + Upgrading: '系统升级中,请稍候...', + Restarting: '系统重启中,请稍候...', + Recovering: '快照恢复中,请稍候...', + Rollbacking: '快照回滚中,请稍候...', + }, + msg: { + noneData: '暂无数据', + delete: '删除 操作不可回滚,是否继续?', + clean: '清空 操作不可回滚,是否继续?', + closeDrawerHelper: '系统可能不会保存您所做的更改,是否继续?', + deleteSuccess: '删除成功', + loginSuccess: '登录成功', + operationSuccess: '操作成功', + copySuccess: '复制成功', + notSupportOperation: '不支持的当前操作', + requestTimeout: '请求超时,请稍后重试', + infoTitle: '提示', + notRecords: '当前任务未产生执行记录', + sureLogOut: '您是否确认退出登录?', + createSuccess: '创建成功', + updateSuccess: '更新成功', + uploadSuccess: '上传成功', + operateConfirm: '如果确认操作,请手动输入 ', + inputOrSelect: '请选择或输入', + copyFailed: '复制失败', + operatorHelper: '将对以下{0}进行 {1} 操作,是否继续?', + backupSuccess: '备份成功', + restoreSuccess: '备份成功', + notFound: '抱歉,您访问的页面不存在', + unSupportType: '不支持当前文件类型!', + unSupportSize: '上传文件超过 {0}M,请确认!', + fileExist: '当前文件夹已存在该文件,不支持重复上传!', + fileNameErr: '仅支持上传名称包含英文、中文、数字或者 .-_ ,长度 1-256 位的文件', + confirmNoNull: '请确认 {0} 值不为空', + errPort: '错误的端口信息,请确认!', + remove: '移出', + backupHelper: '当前操作将对 {0} 进行备份,是否继续?', + recoverHelper: '将从 {0} 文件进行恢复,该操作不可回滚,是否继续?', + refreshSuccess: '刷新成功', + rootInfoErr: '已经是根目录了', + resetSuccess: '重置成功', + creatingInfo: '正在创建,无需此操作', + installSuccess: '安装成功', + uninstallSuccess: '卸载成功', + offlineTips: '离线版不支持此操作', + errImportFormat: '导入数据或格式异常,请检查后重试!', + importHelper: '导入冲突或重复数据时,将以导入内容为标准,更新原数据库数据。', + errImport: '文件内容异常:', + }, + login: { + username: '用户名', + password: '密码', + welcome: '欢迎回来,请输入用户名和密码登录!', + errorAuthInfo: '您输入的用户名或密码不正确,请重新输入!', + errorMfaInfo: '错误的验证信息,请重试!', + captchaHelper: '验证码', + errorCaptcha: '验证码错误!', + notSafe: '暂无权限访问', + safeEntrance1: '当前环境已经开启了安全入口登录', + safeEntrance2: '在 SSH 终端输入以下命令来查看面板入口: 1pctl user-info', + errIP1: '当前环境已经开启了授权 IP 访问', + errDomain1: '当前环境已经开启了访问域名绑定', + errHelper: '可在 SSH 终端输入以下命令来重置绑定信息: ', + codeInput: '请输入 MFA 验证器的 6 位验证码', + mfaTitle: 'MFA 认证', + mfaCode: 'MFA 验证码', + passkey: '使用通行密钥', + passkeyFailed: '通行密钥登录失败,请重试', + passkeyNotSupported: '当前浏览器或环境不支持通行密钥', + passkeyToPassword: '无法使用通行密钥? 使用账号密码登录', + title: 'Linux 服务器运维管理面板', + licenseHelper: '《飞致云社区软件许可协议》', + errorAgree: '请点击同意社区软件许可协议', + agreeTitle: '服务协议及隐私保护', + agreeContent: + '为了更好的保障您的合法权益,请您阅读并同意以下协议 « 飞致云社区软件许可协议 »', + logout: '退出登录', + }, + rule: { + username: '请输入用户名', + password: '请输入密码', + rePassword: '密码不一致,请检查后重新输入', + requiredInput: '请填写必填项', + requiredSelect: '请选择必选项', + illegalChar: '暂不支持注入字符 & ; $ \' ` ( ) " > < |', + illegalInput: '输入框中存在不合法字符', + commonName: '支持非特殊字符开头,英文、中文、数字、.-和_,长度1-128', + userName: '支持非特殊字符开头、英文、中文、数字和_,长度3-30', + simpleName: '支持非下划线开头,英文、数字、_,长度3-30', + simplePassword: '支持非下划线开头,英文、数字、_,长度1-30', + dbName: '支持非特殊字符开头,英文、中文、数字、.-_,长度1-64', + composeName: '支持非特殊字符开头,小写英文、数字、-和_,长度1-256', + imageName: '支持非特殊字符开头、英文、数字、:@/.-_,长度1-256', + volumeName: '支持英文、数字、.-和_,长度2-30', + supervisorName: '支持非特殊字符开头,英文、数字、-和_,长度1-128', + complexityPassword: '请输入长度为 8-30 位且包含字母、数字、特殊字符至少两项的密码组合', + commonPassword: '请输入 6 位以上长度密码', + linuxName: '长度1-128,名称不能含有{0}等符号', + email: '请输入正确的邮箱', + number: '请输入正确的数字', + integer: '请输入正确的正整数', + ip: '请输入正确的 IP 地址', + host: '请输入正确的 IP 或者域名', + hostHelper: '支持输入 ip 或者域名', + port: '请输入正确的端口,1-65535', + domain: '域名格式错误', + databaseName: '支持英文、数字、_,长度1-30', + numberRange: '数字范围: {0} - {1}', + paramName: '支持英文、数字、.-和_,长度2-64', + paramComplexity: '支持英文、数字、{0},长度6-128,特殊字符不能在首尾', + paramUrlAndPort: '格式为 http(s)://(域名/ip):(端口)', + nginxDoc: '仅支持英文大小写,数字,和.', + appName: '支持英文、数字、-和_,长度2-30,并且不能以-_开头和结尾', + containerName: '支持字母、数字、_-和.,不能以-_或.开头,长度2-128', + mirror: '支持以 http(s):// 开头,英文大小写,数字,. / 和 - 的镜像加速地址,且不能有空行', + disableFunction: '仅支持字母、下划线和,', + leechExts: '仅支持字母数字和,', + paramSimple: '支持小写字母和数字,长度1-128', + filePermission: '权限错误', + formatErr: '格式错误,检查后重试', + phpExtension: '仅支持 , _ 小写英文和数字', + paramHttp: '必须以 http:// 或 https:// 开头', + phone: '手机号码格式不正确', + authBasicPassword: '支持字母、数字以及常见特殊字符,长度1-72', + length128Err: '长度不能超过128位', + maxLength: '长度不能超过 {0} 位', + alias: '支持英文、数字、-和_,长度1-128,并且不能以-_开头和结尾', + }, + res: { + paramError: '请求失败,请稍后重试!', + forbidden: '当前用户无权限', + serverError: '服务异常', + notFound: '资源不存在', + commonError: '请求失败', + }, + service: { + serviceNotStarted: '当前未启动 {0} 服务', + }, + status: { + running: '已启动', + done: '已完成', + scanFailed: '未完成', + success: '成功', + waiting: '请等待', + waitForUpgrade: '等待升级', + failed: '失败', + stopped: '已停止', + error: '失败', + created: '已创建', + restarting: '重启中', + uploading: '上传中', + unhealthy: '异常', + removing: '移除中', + paused: '已暂停', + exited: '已停止', + dead: '已结束', + installing: '安装中', + enabled: '已启用', + disabled: '已停止', + normal: '正常', + building: '制作镜像中', + upgrading: '升级中', + pending: '待编辑', + rebuilding: '重建中', + deny: '已屏蔽', + accept: '已放行', + used: '已使用', + unused: '未使用', + starting: '启动中', + recreating: '重建中', + creating: '创建中', + init: '等待申请', + ready: '正常', + applying: '申请中', + uninstalling: '卸载中', + lost: '已失联', + bound: '已绑定', + unbind: '未绑定', + exceptional: '异常', + free: '空闲', + enable: '已启用', + disable: '已停止', + deleted: '已删除', + downloading: '下载中', + packing: '打包中', + sending: '下发中', + healthy: '正常', + executing: '执行中', + installerr: '安装失败', + applyerror: '申请失败', + systemrestart: '中断', + starterr: '启动失败', + uperr: '启动失败', + new: '新', + conflict: '冲突', + duplicate: '重复', + unexecuted: '未执行', + }, + units: { + second: '秒', + minute: '分钟', + hour: '小时', + day: '天', + week: '周', + month: '月', + year: '年', + time: '次', + core: '核', + secondUnit: '秒', + minuteUnit: '分钟', + hourUnit: '小时', + dayUnit: '天', + millisecond: '毫秒', + }, + log: { + noLog: '暂无日志', + }, + }, + menu: { + home: '概览', + apps: '应用商店', + website: '网站', + project: '项目', + config: '配置', + ssh: 'SSH 管理', + firewall: '防火墙', + filter: '过滤器', + ssl: '证书', + database: '数据库', + aiTools: 'AI', + mcp: 'MCP', + container: '容器', + cronjob: '计划任务', + system: '系统', + files: '文件', + monitor: '监控', + terminal: '终端', + settings: '面板设置', + toolbox: '工具箱', + logs: '日志审计', + runtime: '运行环境', + processManage: '进程管理', + process: '进程', + network: '网络', + supervisor: '进程守护', + tamper: '防篡改', + app: '应用', + msgCenter: '任务中心', + disk: '磁盘管理', + }, + home: { + recommend: '推荐', + dir: '目录', + alias: '别名', + quickDir: '快捷目录', + minQuickJump: '请至少设置一个快速跳转入口!', + maxQuickJump: '最多可设置四个快速跳转入口!', + database: '数据库 - 所有', + restart_1panel: '重启面板', + restart_system: '重启服务器', + operationSuccess: '操作成功,正在重启,请稍后手动刷新浏览器!', + entranceHelper: '设置安全入口有利于提高系统的安全性,如有需要,前往 面板设置-安全 中,启用安全入口', + appInstalled: '已安装应用', + systemInfo: '系统信息', + hostname: '主机名称', + platformVersion: '发行版本', + kernelVersion: '内核版本', + kernelArch: '系统类型', + network: '流量', + io: '磁盘 IO', + ip: '主机地址', + proxy: '系统代理', + baseInfo: '基本信息', + totalSend: '总发送', + totalRecv: '总接收', + rwPerSecond: '读写次数', + ioDelay: '读写延迟', + uptime: '启动时间', + runningTime: '运行时间', + mem: '系统内存', + swapMem: 'Swap 分区', + + runSmoothly: '运行流畅', + runNormal: '运行正常', + runSlowly: '运行缓慢', + runJam: '运行堵塞', + + core: '物理核心', + logicCore: '逻辑核心', + corePercent: '核心使用率', + cpuFrequency: 'CPU 频率', + cpuDetailedPercent: 'CPU 占用', + cpuUser: '用户态', + cpuSystem: '内核态', + cpuIdle: '空闲', + cpuIrq: '硬中断', + cpuSoftirq: '软中断', + cpuSteal: '被VM抢占', + cpuTop: 'CPU 占用率 Top5 的进程信息', + memTop: '内存占用率 Top5 的进程信息', + loadAverage: '最近 {0} 分钟平均负载', + load: '负载', + mount: '挂载点', + fileSystem: '文件系统', + total: '总数', + used: '已用', + cache: '缓存', + free: '空闲', + shard: '共享', + available: '可用', + percent: '使用率', + goInstall: '去安装', + + networkCard: '网卡', + disk: '磁盘', + }, + tabs: { + more: '更多', + hide: '收起', + closeLeft: '关闭左侧', + closeRight: '关闭右侧', + closeCurrent: '关闭当前', + closeOther: '关闭其它', + closeAll: '关闭所有', + }, + header: { + logout: '退出登录', + }, + database: { + manage: '管理', + deleteBackupHelper: '同时删除数据库备份', + delete: '删除操作无法回滚,请输入 "', + deleteHelper: '" 删除此数据库', + create: '创建数据库', + noMysql: '数据库服务 (MySQL 或 MariaDB)', + noPostgresql: '数据库服务 PostgreSQL', + goUpgrade: '去应用列表升级', + goInstall: '去应用商店安装', + isDelete: '已删除', + permission: '权限', + format: '字符集', + collation: '排序规则', + collationHelper: '为空则使用 {0} 字符集的默认排序规则', + permissionForIP: '指定 IP', + permissionAll: '所有人(%)', + localhostHelper: '将容器部署的数据库权限配置为 localhost 会导致容器外部无法访问,请谨慎选择!', + databaseConnInfo: '连接信息', + rootPassword: 'root 密码', + serviceName: '服务名称', + serviceNameHelper: '用于同一 network 下的容器间访问', + backupList: '备份列表', + loadBackup: '导入备份', + localUpload: '本地上传', + hostSelect: '服务器选择', + selectHelper: '是否确认导入备份文件 {0}?', + remoteAccess: '远程访问', + remoteHelper: '多个 ip 以逗号分隔,例:172.16.10.111,172.16.10.112', + remoteConnHelper: 'root 帐号远程连接 MySQL 有安全风险,开启需谨慎!', + changePassword: '改密', + changeConnHelper: '此操作将修改当前数据库 {0},是否继续?', + changePasswordHelper: '当前数据库已经关联应用,修改密码将同步修改应用中数据库密码,修改后重启生效。', + recoverTimeoutHelper: '-1 表示不限制超时时间', + + portHelper: '该端口为容器对外暴露端口,修改需要单独保存并且重启容器!', + + confChange: '配置修改', + confNotFound: '未能找到该应用配置文件,请在应用商店升级该应用至最新版本后重试!', + + loadFromRemote: '从服务器同步', + userBind: '绑定用户', + pgBindHelper: '该操作用于创建新用户并将其绑定到目标数据库,暂不支持选择已存在于数据库中的用户。', + pgSuperUser: '超级用户', + loadFromRemoteHelper: '此操作将同步服务器上数据库信息到 1Panel,是否继续?', + passwordHelper: '无法获取,可点击修改', + remote: '远程', + remoteDB: '远程服务器', + createRemoteDB: '添加远程服务器', + unBindRemoteDB: '解绑远程服务器', + unBindForce: '强制解绑', + unBindForceHelper: '忽略解绑过程中的所有错误,确保最终操作成功', + unBindRemoteHelper: '解绑远程数据库只会删除绑定关系,不会直接删除远程数据库', + editRemoteDB: '编辑远程服务器', + localDB: '本地数据库', + address: '数据库地址', + version: '数据库版本', + userHelper: 'root 用户或者拥有 root 权限的数据库用户', + pgUserHelper: '拥有超级管理员权限的数据库用户', + ssl: '使用 SSL', + clientKey: '客户端私钥', + clientCert: '客户端证书', + hasCA: '拥有 CA 证书', + caCert: 'CA 证书', + skipVerify: '忽略校验证书可用性检测', + initialDB: '初始数据库', + + formatHelper: '当前数据库字符集为 {0},字符集不一致可能导致恢复失败', + dropHelper: '将上传文件拖拽到此处,或者', + clickHelper: '点击上传', + supportUpType: + '仅支持 sql、sql.gz、tar.gz、.zip 文件格式,导入的压缩文件必须保证只有一个 .sql 文件或者包含 test.sql', + + currentStatus: '当前状态', + baseParam: '基础参数', + performanceParam: '性能参数', + runTime: '启动时间', + connections: '总连接数', + bytesSent: '发送', + bytesReceived: '接收', + queryPerSecond: '每秒查询', + txPerSecond: '每秒事务', + connInfo: '活动/峰值连接数', + connInfoHelper: '若值过大,增加 max_connections', + threadCacheHit: '线程缓存命中率', + threadCacheHitHelper: '若过低,增加 thread_cache_size', + indexHit: '索引命中率', + indexHitHelper: '若过低,增加 key_buffer_size', + innodbIndexHit: 'Innodb 索引命中率', + innodbIndexHitHelper: '若过低,增加 innodb_buffer_pool_size', + cacheHit: '查询缓存命中率', + cacheHitHelper: '若过低,增加 query_cache_size', + tmpTableToDB: '创建临时表到磁盘', + tmpTableToDBHelper: '若过大,尝试增加 tmp_table_size', + openTables: '已打开的表', + openTablesHelper: 'table_open_cache 配置值应大于等于此值', + selectFullJoin: '没有使用索引的量', + selectFullJoinHelper: '若不为0,请检查数据表的索引是否合理', + selectRangeCheck: '没有索引的 JOIN 量', + selectRangeCheckHelper: '若不为0,请检查数据表的索引是否合理', + sortMergePasses: '排序后的合并次数', + sortMergePassesHelper: '若值过大,增加sort_buffer_size', + tableLocksWaited: '锁表次数', + tableLocksWaitedHelper: '若值过大,请考虑增加您的数据库性能', + + performanceTuning: '性能调整', + optimizationScheme: '优化方案', + keyBufferSizeHelper: '用于索引的缓冲区大小', + queryCacheSizeHelper: '查询缓存,不开启请设为0', + tmpTableSizeHelper: '临时表缓存大小', + innodbBufferPoolSizeHelper: 'Innodb 缓冲区大小', + innodbLogBufferSizeHelper: 'Innodb 日志缓冲区大小', + sortBufferSizeHelper: '* 连接数, 每个线程排序的缓冲大小', + readBufferSizeHelper: '* 连接数, 读入缓冲区大小', + readRndBufferSizeHelper: '* 连接数, 随机读取缓冲区大小', + joinBufferSizeHelper: '* 连接数, 关联表缓存大小', + threadStackelper: '* 连接数, 每个线程的堆栈大小', + binlogCacheSizeHelper: '* 连接数, 二进制日志缓存大小(4096的倍数)', + threadCacheSizeHelper: '线程池大小', + tableOpenCacheHelper: '表缓存', + maxConnectionsHelper: '最大连接数', + restart: '重启数据库', + + slowLog: '慢日志', + noData: '暂无慢日志...', + + isOn: '开启', + longQueryTime: '阈值(秒)', + thresholdRangeHelper: '请输入正确的阈值(1 - 600)', + + timeout: '超时时间(秒)', + timeoutHelper: '空闲连接超时时间,0表示不断开', + maxclients: '最大连接数', + requirepassHelper: '留空代表没有设置密码,修改需要单独保存并且重启容器!', + databases: '数据库数量', + maxmemory: '最大内存使用', + maxmemoryHelper: '0 表示不做限制', + tcpPort: '当前监听端口', + uptimeInDays: '已运行天数', + connectedClients: '连接的客户端数量', + usedMemory: '当前 Redis 使用的内存大小', + usedMemoryRss: '向操作系统申请的内存大小', + usedMemoryPeak: 'Redis 的内存消耗峰值', + memFragmentationRatio: '内存碎片比率', + totalConnectionsReceived: '运行以来连接过的客户端的总数量', + totalCommandsProcessed: '运行以来执行过的命令的总数量', + instantaneousOpsPerSec: '服务器每秒钟执行的命令数量', + keyspaceHits: '查找数据库键成功的次数', + keyspaceMisses: '查找数据库键失败的次数', + hit: '查找数据库键命中率', + latestForkUsec: '最近一次 fork() 操作耗费的微秒数', + redisCliHelper: '未检测到 redis-cli 服务,请先启用服务!', + redisQuickCmd: 'Redis 快速命令', + + recoverHelper: '即将使用 [{0}] 对数据进行覆盖,是否继续?', + submitIt: '覆盖数据', + + baseConf: '基础配置', + allConf: '全部配置', + restartNow: '立即重启', + restartNowHelper1: '修改配置后需要重启生效,若您的数据需要持久化请先执行 save 操作。', + restartNowHelper: '修改配置后需要重启生效。', + + persistence: '持久化', + rdbHelper1: '秒內,插入', + rdbHelper2: '条数据', + rdbHelper3: '符合任意一个条件将会触发RDB持久化', + rdbInfo: '请确认规则列表中值在 1-100000 之间', + + containerConn: '容器连接', + connAddress: '地址', + containerConnHelper: 'PHP 运行环境/容器安装的应用使用此连接地址', + remoteConn: '外部连接', + remoteConnHelper2: '非容器环境或外部连接需使用此地址。', + remoteConnHelper3: '默认访问地址为主机IP,修改请前往面板设置页面的「默认访问地址」配置项。', + localIP: '本机 IP', + }, + aiTools: { + model: { + model: '模型', + create: '添加模型', + create_helper: '拉取 "{0}"', + ollama_doc: '您可以访问 Ollama 官网,搜索并查找更多模型。', + container_conn_helper: '容器间访问或连接使用此地址', + ollama_sync: '同步 Ollama 模型发现下列模型不存在,是否删除?', + from_remote: '该模型并非通过 1Panel 下载,无相关拉取日志。', + no_logs: '该模型的拉取日志已被删除,无法查看相关日志。', + }, + proxy: { + proxy: 'AI 代理增强', + proxyHelper1: '绑定域名并开启 HTTPS,增强传输安全性', + proxyHelper2: '限制 IP 访问,防止在公网暴露', + proxyHelper3: '开启流式传输', + proxyHelper4: '创建完成之后可以在网站列表中查看并管理', + proxyHelper5: '创建完成之后可以在应用商店 - 已安装 - ollama - 参数中取消端口外部访问以提高安全性', + proxyHelper6: '如需关闭代理配置,可以在网站列表中删除', + whiteListHelper: '限制仅白名单中的 IP 可访问', + }, + gpu: { + gpu: 'GPU 监控', + gpuHelper: '当前系统未检测到 NVIDIA-SMI 或者 XPU-SMI 指令,请检查后重试!', + process: '进程信息', + processCount: '进程数', + type: '类型', + typeG: '图形', + typeC: '计算', + typeCG: '计算+图形', + processName: '进程名称', + shr: '共享显存', + temperatureHelper: 'GPU 温度过高会导致 GPU 频率下降', + gpuUtil: 'GPU 使用率', + temperature: '温度', + performanceState: '性能状态', + powerUsage: '功耗', + memoryUsage: '显存使用率', + fanSpeed: '风扇转速', + power: '功率', + powerCurrent: '当前功率', + powerLimit: '功率上限', + memory: '显存', + memoryUsed: '显存使用', + memoryTotal: '显存总计', + percent: '使用率', + + base: '基础信息', + driverVersion: '驱动版本', + cudaVersion: 'CUDA 版本', + processMemoryUsage: '显存使用', + performanceStateHelper: '从 P0 (最大性能) 到 P12 (最小性能)', + busID: '总线地址', + persistenceMode: '持续模式', + enabled: '开启', + disabled: '关闭', + persistenceModeHelper: '持续模式能更加快速地响应任务,但相应待机功耗也会增加', + displayActive: '显卡初始化', + displayActiveT: '是', + displayActiveF: '否', + ecc: '是否开启错误检查和纠正技术', + computeMode: '计算模式', + default: '默认', + exclusiveProcess: '进程排他', + exclusiveThread: '线程排他', + prohibited: '禁止', + defaultHelper: '默认: 进程可以并发执行', + exclusiveProcessHelper: '进程排他: 只有一个 CUDA 上下文可以使用 GPU, 但可以由多个线程共享', + exclusiveThreadHelper: '线程排他: 只有一个线程在 CUDA 上下文中可以使用 GPU', + prohibitedHelper: '禁止: 不允许进程同时执行', + migModeHelper: '用于创建 MIG 实例,在用户层实现 GPU 的物理隔离。', + migModeNA: '不支持', + current: '实时监控', + history: '历史记录', + notSupport: '当前版本或驱动不支持显示该参数。', + }, + mcp: { + server: 'MCP Server', + create: '创建 MCP Server', + edit: '编辑 MCP Server', + baseUrl: '外部访问路径', + baseUrlHelper: '例如:http://192.168.1.1:8000', + ssePath: 'SSE 路径', + ssePathHelper: '例如:/sse,注意不要与其他 Server 重复', + environment: '环境变量', + envKey: '变量名', + envValue: '变量值', + externalUrl: '外部连接地址', + operatorHelper: '将对 {0} 进行 {1} 操作,是否继续?', + domain: '默认访问地址', + domainHelper: '例如:192.168.1.1 或者 example.com', + bindDomain: '绑定网站', + commandPlaceHolder: '当前仅支持 npx 和 二进制启动的命令', + importMcpJson: '导入 MCP Server 配置', + importMcpJsonError: 'mcpServers 结构不正确', + bindDomainHelper: '绑定网站之后会修改所有已安装 MCP Server 的访问地址,并关闭端口的外部访问', + outputTransport: '输出类型', + streamableHttpPath: '流式传输路径', + streamableHttpPathHelper: '例如:/mcp, 注意不要与其他 Server 重复', + npxHelper: '适合 npx 或者 二进制启动的 mcp', + uvxHelper: '适合 uvx 启动的 mcp', + }, + tensorRT: { + llm: 'TensorRT LLM', + modelDir: '模型目录', + commandHelper: '若需外部访问,请将命令中的端口设置为与应用端口相同', + imageAlert: '由于镜像较大,建议先手动将镜像下载到服务器后再进行安装', + modelSpeedup: '启用模型加速', + modelType: '模型类型', + }, + }, + container: { + create: '创建容器', + createByCommand: '命令创建', + commandInput: '命令输入', + commandRule: '请输入正确的 docker run 容器创建命令!', + commandHelper: '将在服务器上执行该条命令以创建容器,是否继续?', + edit: '编辑容器', + updateHelper1: '检测到该容器来源于应用商店,请注意以下两点:', + updateHelper2: '1. 当前修改内容不会同步到应用商店的已安装应用。', + updateHelper3: '2. 如果在已安装页面修改应用,当前编辑的部分内容将失效。', + updateHelper4: '编辑容器需要重建,任何未持久化的数据将丢失,是否继续操作?', + containerList: '容器列表', + operatorHelper: '将对以下容器进行 {0} 操作,是否继续?', + operatorAppHelper: + '将对以下容器进行 {0} 操作,\n其中部分来源于应用商店,该操作可能会影响到该服务的正常使用。\n是否继续?', + containerDeleteHelper: + '检测到容器来源于应用商店,删除容器不会将其从 1Panel 中完全移除。如需彻底删除,请前往应用商店-『已安装』或『运行环境』等菜单中操作。是否继续?', + start: '启动', + stop: '停止', + restart: '重启', + kill: '强制停止', + pause: '暂停', + unpause: '恢复', + rename: '重命名', + remove: '删除', + removeAll: '删除所有', + containerPrune: '清理容器', + containerPruneHelper1: '清理容器 将删除所有处于停止状态的容器。', + containerPruneHelper2: + '若容器来自于应用商店,在执行清理操作后,您需要前往 [应用商店] 的 [已安装] 列表,点击 [重建] 按钮进行重新安装。', + containerPruneHelper3: '该操作无法回滚,是否继续?', + imagePrune: '清理镜像', + imagePruneSome: '未标签镜像', + imagePruneSomeEmpty: '暂无待清理的未使用 none 标签镜像', + imagePruneSomeHelper: '清理下列标签为 none 且未被任何容器使用的镜像', + imagePruneAll: '未使用镜像', + imagePruneAllEmpty: '暂无待清理的未使用镜像', + imagePruneAllHelper: '清理下列未被任何容器使用的镜像', + networkPrune: '清理网络', + networkPruneHelper: '清理网络 将删除所有未被使用的网络,该操作无法回滚,是否继续?', + volumePrune: '清理存储卷', + volumePruneHelper: '清理存储卷 将删除所有未被使用的本地存储卷,该操作无法回滚,是否继续?', + cleanSuccess: '操作成功,本次清理数量: {0} 个!', + cleanSuccessWithSpace: '操作成功,本次清理数量: {0} 个,释放磁盘空间: {1}!', + unExposedPort: '当前端口映射地址为 127.0.0.1,无法实现外部访问', + upTime: '运行时长', + fetch: '过滤', + lines: '条数', + linesHelper: '请输入正确的日志获取条数!', + lastDay: '最近一天', + last4Hour: '最近 4 小时', + lastHour: '最近 1 小时', + last10Min: '最近 10 分钟', + cleanLog: '清空日志', + downLogHelper1: '即将下载 {0} 容器所有日志,是否继续?', + downLogHelper2: '即将下载 {0} 容器最近 {1} 条日志,是否继续?', + cleanLogHelper: '清空日志需要重启容器,该操作无法回滚,是否继续?', + newName: '新名称', + workingDir: '工作目录', + source: '资源使用率', + cpuUsage: 'CPU 使用', + cpuTotal: 'CPU 总计', + core: '核心数', + memUsage: '内存使用', + memTotal: '内存限额', + memCache: '缓存使用', + loadSize: '获取容器大小', + ip: 'IP 地址', + cpuShare: 'CPU 权重', + cpuShareHelper: '容器默认份额为 1024 个 CPU,增大可使当前容器获得更多的 CPU 时间', + inputIpv4: '请输入 IPv4 地址', + inputIpv6: '请输入 IPv6 地址', + + diskUsage: '磁盘占用', + localVolume: '本地存储卷', + buildCache: '构建缓存', + usage: '已占用:{0}, 可释放:{1}', + clean: '释放', + imageClean: '清理镜像 将删除所有未被使用的镜像,该操作无法回滚,是否继续?', + containerClean: '清理容器 将删除所有处于停止中状态的容器(包括应用商店停止应用),该操作无法回滚,是否继续?', + sizeRw: '容器层大小', + sizeRwHelper: '容器独有的可写层大小', + sizeRootFs: '虚拟大小', + sizeRootFsHelper: ' 容器依赖的所有镜像层 + 容器层的总大小', + + containerFromAppHelper: '检测到该容器来源于应用商店,应用操作可能会导致当前编辑失效', + containerFromAppHelper1: '在应用商店的已安装页面,点击 [参数] 按钮,进入编辑页面修改容器名称。', + command: '命令', + console: '控制台交互', + tty: '伪终端 ( -t )', + openStdin: '标准输入 ( -i )', + custom: '自定义', + emptyUser: '为空时,将使用容器默认的用户登录', + privileged: '特权模式', + privilegedHelper: '允许容器在主机上执行某些特权操作,可能会增加容器风险,谨慎开启!', + + upgradeHelper: '仓库名称/镜像名称:镜像版本', + upgradeWarning2: '升级操作需要重建容器,任何未持久化的数据将会丢失,是否继续?', + oldImage: '当前镜像', + sameImageContainer: '同镜像容器', + sameImageHelper: '同镜像容器可勾选后批量升级', + targetImage: '目标镜像', + imageLoadErr: '未检测到容器的镜像名称', + appHelper: '该容器来源于应用商店,升级可能导致该服务不可用', + + resource: '资源', + input: '手动输入', + forcePull: '强制拉取镜像', + forcePullHelper: '忽略服务器已存在的镜像,重新拉取一次', + server: '服务器', + serverExample: '80, 80-88, ip:80 或者 ip:80-88', + containerExample: '80 或者 80-88', + exposePort: '暴露端口', + exposeAll: '暴露所有', + cmdHelper: '例: nginx -g "daemon off;"', + entrypointHelper: '例: docker-entrypoint.sh', + autoRemove: '容器退出后自动删除容器', + cpuQuota: 'CPU 限制', + memoryLimit: '内存限制', + limitHelper: '限制为 0 则关闭限制,最大可用为 {0}', + macAddr: 'MAC 地址', + mount: '挂载', + volumeOption: '挂载卷', + hostOption: '本机目录', + serverPath: '服务器目录', + containerDir: '容器目录', + volumeHelper: '请确认存储卷内容输入正确', + networkEmptyHelper: '请确认容器网络选择正确', + modeRW: '读写', + modeR: '只读', + sharedLabel: '传播模式', + private: '私有', + privateHelper: '容器里的挂载变化和主机互不干扰', + rprivate: '递归私有', + rprivateHelper: '容器里所有挂载都和主机完全隔离', + shared: '共享', + sharedHelper: '主机和容器里的挂载变化互相可见', + rshared: '递归共享', + rsharedHelper: '主机和容器里所有挂载变化都互相可见', + slave: '从属', + slaveHelper: '容器能看见主机的挂载变化,但自己的变化不影响主机', + rslave: '递归从属', + rslaveHelper: '容器里所有挂载都能看见主机变化,但不影响主机', + mode: '权限', + env: '环境变量', + restartPolicy: '重启规则', + always: '一直重启', + unlessStopped: '未手动停止则重启', + onFailure: '失败后重启(默认重启 5 次)', + no: '不重启', + + refreshTime: '刷新间隔', + cache: '缓存', + + image: '镜像', + imagePull: '拉取镜像', + imagePullHelper: '支持选择拉取多个镜像,输入一组镜像后回车继续', + imagePush: '推送镜像', + imagePushHelper: '检测到该镜像存在多个标签,请确认推送时使用的镜像名称为:{0}', + imageDelete: '删除镜像', + repoName: '仓库名', + imageName: '镜像名', + httpRepo: 'http 仓库添加授信需要重启 docker 服务', + delInsecure: '删除授信', + delInsecureHelper: '删除授信需要重启 docker 服务,是否删除?', + pull: '拉取', + path: '路径', + importImage: '导入镜像', + imageBuild: '构建镜像', + buildArgs: '构建参数', + pathSelect: '路径选择', + label: '标签', + imageTag: '镜像标签', + imageTagHelper: '支持设置多个镜像 tag,输入一个 tag 后回车继续', + push: '推送', + fileName: '文件名', + export: '导出', + exportImage: '导出镜像', + size: '大小', + tag: '标签', + tagHelper: '一行一个,例: \nkey1=value1\nkey2=value2', + imageNameHelper: '镜像名称及 Tag,例:nginx:latest', + cleanBuildCache: '清理构建缓存', + delBuildCacheHelper: '清理构建缓存 将删除所有构建产生的缓存,该操作无法回滚,是否继续?', + urlWarning: '路径前缀不需要添加 http:// 或 https://, 请修改', + + network: '网络', + networkHelper: '删除 1panel-network 容器网络将影响部分应用和运行环境的正常使用,是否继续?', + createNetwork: '创建网络', + networkName: '网络名', + driver: '模式', + option: '参数', + attachable: '可用', + parentNetworkCard: '父网卡', + subnet: '子网', + scope: 'IP 范围', + gateway: '网关', + auxAddress: '排除 IP', + + volume: '存储卷', + volumeDir: '存储卷目录', + nfsEnable: '启用 NFS 存储', + nfsAddress: '地址', + mountpoint: '挂载点', + mountpointNFSHelper: '例:/nfs, /nfs-share', + options: '可选参数', + createVolume: '创建存储卷', + + repo: '仓库', + createRepo: '添加仓库', + httpRepoHelper: '操作 http 类型仓库需要重启 Docker 服务。', + downloadUrl: '下载地址', + imageRepo: '镜像仓库', + repoHelper: '是否包含镜像仓库/组织/项目?', + auth: '认证', + mirrorHelper: + '当存在多个加速器时,需要换行显示,例: \nhttp://xxxxxx.m.daocloud.io \nhttps://xxxxxx.mirror.aliyuncs.com', + registrieHelper: '当存在多个私有仓库时,需要换行显示,例:\n172.16.10.111:8081 \n172.16.10.112:8081', + + compose: '编排', + composeFile: '编排文件', + fromChangeHelper: '切换来源将清空当前已编辑内容,是否继续?', + composePathHelper: '配置文件保存路径: {0}', + composeHelper: '通过 1Panel 编辑或者模版创建的编排,将保存在 {0}/docker/compose 路径下', + deleteFile: '删除文件', + deleteComposeHelper: '删除容器编排的所有文件,包括配置文件和持久化文件,请谨慎操作!', + deleteCompose: '" 删除此编排', + createCompose: '创建编排', + composeDirectory: '编排目录', + template: '模版', + composeTemplate: '编排模版', + createComposeTemplate: '创建编排模版', + exportHelper: '即将导出 {0} 条编排模版,是否继续?', + content: '内容', + contentEmpty: '编排内容不能为空,请输入后重试!', + containerNumber: '容器数量', + containerStatus: '容器状态', + exited: '已停止', + running: '运行中 ( {0} / {1} )', + composeDetailHelper: '该 compose 为 1Panel 编排外部创建。暂不支持启停操作。', + composeOperatorHelper: '将对 {0} 进行 {1} 操作,是否继续?', + composeDownHelper: '将停止并删除 {0} 编排下所有容器及网络,是否继续?', + composeEnvHelper2: '该编排为 1Panel 应用商店创建,请在已安装应用中修改环境变量。', + + setting: '配置', + goSetting: '去修改', + operatorStatusHelper: '此操作将{0}Docker 服务,是否继续?', + dockerStatus: 'Docker 服务', + daemonJsonPathHelper: '请保证配置路径与 docker.service 中指定的配置路径保持一致。', + mirrors: '镜像加速', + mirrorsHelper: '优先使用加速 URL 执行操作,设置为空则取消镜像加速。', + mirrorsHelper2: '具体操作配置请参照官方文档', + registries: '私有仓库', + ipv6Helper: '开启 IPv6 后,需要增加 IPv6 的容器网络,具体操作配置请参照官方文档', + ipv6CidrHelper: '容器的 IPv6 地址池范围', + ipv6TablesHelper: 'Docker IPv6 对 iptables 规则的自动配置', + experimentalHelper: '开启 ip6tables 必须开启此配置,否则 ip6tables 会被忽略', + cutLog: '日志切割', + cutLogHelper1: '当前配置只会影响新创建的容器;', + cutLogHelper2: '已经创建的容器需要重新创建使配置生效;', + cutLogHelper3: + '注意,重新创建容器可能会导致数据丢失。如果你的容器中有重要数据,确保在执行重建操作之前进行备份。', + maxSize: '文件大小', + maxFile: '保留份数', + liveHelper: '允许在 Docker 守护进程发生意外停机或崩溃时保留正在运行的容器状态', + liveWithSwarmHelper: 'live-restore 守护进程配置与 Swarm 模式不兼容', + iptablesDisable: '关闭 iptables', + iptablesHelper1: 'Docker 对 iptables 规则的自动配置', + iptablesHelper2: '关闭 iptables 会导致容器无法与外部网络通信。', + daemonJsonPath: '配置路径', + serviceUnavailable: '当前未启动 Docker 服务,请在', + startIn: '中开启', + sockPath: 'Socket 路径', + sockPathHelper: 'Docker 守护进程(Docker Daemon)与客户端之间的通信通道', + sockPathHelper1: '默认路径:/var/run/docker.sock', + sockPathMsg: '保存设置 Socket 路径可能导致 Docker 服务不可用,是否继续?', + sockPathErr: '请选择或输入正确的 Docker sock 文件路径', + related: '关联资源', + includeAppstore: '显示应用商店容器', + excludeAppstore: '隐藏应用商店容器', + + cleanDockerDiskZone: '清理 Docker 使用的磁盘空间', + cleanImagesHelper: '( 清理所有未被任何容器使用的镜像 )', + cleanContainersHelper: '( 清理所有处于停止状态的容器 )', + cleanVolumesHelper: '( 清理所有未被使用的本地存储卷 )', + + makeImage: '制作镜像', + newImageName: '新镜像名称', + commitMessage: '提交信息', + author: '作者', + ifPause: '制作过程中是否暂停容器', + ifMakeImageWithContainer: '是否根据此容器制作新镜像?', + finishTime: '上一次停止时间', + }, + cronjob: { + create: '创建计划任务', + edit: '编辑计划任务', + importHelper: + '导入时将自动跳过重名计划任务。任务默认设置为【停用】状态,数据关联异常时,设置为【待编辑】状态。', + changeStatus: '状态修改', + disableMsg: '停止计划任务会导致该任务不再自动执行。是否继续?', + enableMsg: '启用计划任务会让该任务定期自动执行。是否继续?', + taskType: '任务类型', + nextTime: '近 5 次执行', + record: '报告', + viewRecords: '查看报告', + shell: 'Shell 脚本', + stop: '手动结束', + stopHelper: '该操作将强制停止该次任务执行,是否继续?', + log: '备份日志', + logHelper: '备份系统日志', + logHelper1: '1. 1Panel 系统日志', + logHelper2: '2. 服务器的 SSH 登录日志', + logHelper3: '3. 所有网站日志', + containerCheckBox: '在容器中执行(无需再输入进入容器命令)', + containerName: '容器名称', + ntp: '同步服务器时间', + ntp_helper: '您可以在工具箱的快速设置页面配置 NTP 服务器', + app: '备份应用', + website: '备份网站', + rulesHelper: '支持多个排除规则,使用英文逗号 , 分隔,例如:*.log,*.sql', + lastRecordTime: '上次执行情况', + database: '备份数据库', + backupArgs: '备份参数', + backupArgsHelper: '未列出的备份参数可手动输入并选择,如: 输入 --no-data 后选择下拉列表中的第一个', + singleTransaction: '使用单一事务备份 InnoDB 表,适用于大数据量的备份', + quick: '逐行读取数据,而不是将整个表加载到内存中,适用于大数据量和低内存机器的备份', + skipLockTables: '不锁定所有表进行备份,适用于高并发的数据库', + missBackupAccount: '未能找到备份账号', + syncDate: '同步时间 ', + clean: '缓存清理', + curl: '访问 URL', + taskName: '任务名称', + cronSpec: '执行周期', + cronSpecDoc: '自定义执行周期仅支持【分时日月周】格式,如 0 0 * * * ,具体可参考官方文档', + cronSpecHelper: '请输入正确的执行周期', + cleanHelper: '该操作将所有任务执行记录、备份文件和日志文件,是否继续?', + backupContent: '备份内容', + directory: '备份目录 / 文件', + sourceDir: '备份目录', + snapshot: '系统快照', + allOptionHelper: '当前计划任务为备份所有【{0}】,暂不支持直接下载,可在【{0}】备份列表中查看', + exclusionRules: '排除规则', + exclusionRulesHelper: '选择或输入排除规则,输入完一组后回车继续,排除规则将对此次备份的所有压缩操作生效', + default_download_path: '默认下载地址', + saveLocal: '同时保留本地备份(和云存储保留份数一致)', + url: 'URL 地址', + urlHelper: '请输入正确的 URL 地址', + targetHelper: '备份账号可在面板设置中维护', + withImageHelper: '备份应用商店镜像,但是会增大快照文件体积。', + ignoreApp: '排除应用', + withImage: '备份应用镜像', + retainCopies: '保留份数', + retryTimes: '失败重试次数', + timeout: '超时时间', + ignoreErr: '忽略错误', + ignoreErrHelper: '忽略备份过程中出现的错误,保证所有备份任务执行', + retryTimesHelper: '为 0 表示失败后不重试', + retainCopiesHelper: '执行记录及日志保留份数', + retainCopiesHelper1: '备份文件保留份数', + retainCopiesUnit: ' 份 (查看)', + cronSpecRule: '第 {0} 行中执行周期格式错误,请检查后重试!', + cronSpecRule2: '执行周期格式错误,请检查后重试!', + perMonthHelper: '每月 {0} 日 {1}:{2} 执行', + perWeekHelper: '每周 {0} {1}:{2} 执行', + perDayHelper: '每日 {0}:{1} 执行', + perHourHelper: '每小时 {0}分 执行', + perNDayHelper: '每 {0} 日 {1}:{2} 执行', + perNHourHelper: '每 {0}小时 {1}分 执行', + perNMinuteHelper: '每 {0}分 执行', + perNSecondHelper: '每 {0}秒 执行', + perMonth: '每月', + perWeek: '每周', + perHour: '每小时', + perNDay: '每 N 日', + perDay: '每天', + perNHour: '每 N 时', + perNMinute: '每 N 分钟', + perNSecond: '每 N 秒', + day: '日', + monday: '周一', + tuesday: '周二', + wednesday: '周三', + thursday: '周四', + friday: '周五', + saturday: '周六', + sunday: '周日', + shellContent: '脚本内容', + executor: '解释器', + errRecord: '错误的日志记录', + errHandle: '任务执行失败', + noRecord: '当前计划任务暂未产生记录', + cleanData: '删除备份文件', + cleanRemoteData: '删除远程备份文件', + cleanDataHelper: '删除该任务执行过程中产生的备份文件', + noLogs: '暂无任务输出...', + errPath: '备份路径 [{0}] 错误,无法下载!', + cutWebsiteLog: '切割网站日志', + cutWebsiteLogHelper: '切割的日志文件会备份到 1Panel 的 backup 目录下', + syncIpGroup: '同步 WAF IP 组', + + requestExpirationTime: '上传请求过期时间(小时)', + unitHours: '单位:小时', + alertTitle: '计划任务-{0}「 {1} 」任务失败告警', + library: { + script: '脚本', + syncNow: '立即同步', + turnOnSync: '开启自动同步', + turnOnSyncHelper: '开启自动同步将在每天凌晨时段进行自动同步', + turnOffSync: '关闭自动同步', + turnOffSyncHelper: '关闭自动同步可能导致脚本同步不及时,是否确认?', + isInteractive: '交互式', + interactive: '交互式脚本', + interactiveHelper: '在脚本执行过程中需要用户输入参数或做出选择,且无法用于计划任务中。', + library: '脚本库', + remoteLibrary: '远程脚本库', + create: '添加脚本', + edit: '修改脚本', + groupHelper: '针对脚本特征设置不同的分组,可以更加快速的对脚本进行筛选操作。', + handleHelper: '将在 {0} 上执行 {1} 脚本,是否继续?', + noSuchApp: '未检测到 {0} 服务,请前往脚本库页面手动安装!', + syncHelper: '即将同步系统脚本库,该操作仅针对系统脚本,是否继续?', + }, + + cleanLog: '清理日志', + cleanLogscope: '清理类型', + }, + monitor: { + globalFilter: '全局过滤', + enableMonitor: '监控状态', + storeDays: '保存天数', + defaultNetwork: '默认网卡', + defaultNetworkHelper: '默认监控和概览界面显示的网卡选项', + defaultIO: '默认磁盘', + defaultIOHelper: '默认监控和概览界面显示的磁盘选项', + cleanMonitor: '清空监控记录', + cleanHelper: '该操作将清空包括 GPU 在内的所有监控记录,是否继续?', + + avgLoad: '平均负载', + loadDetail: '负载详情', + resourceUsage: '资源使用率', + networkCard: '网卡', + read: '读取', + write: '写入', + readWriteCount: '读写次数', + readWriteTime: '读写延迟', + today: '今天', + yesterday: '昨天', + lastNDay: '近 {0} 天', + lastNMonth: '近 {0} 月', + lastHalfYear: '近半年', + memory: '内存', + percent: '占比', + cache: '缓存', + disk: '磁盘', + network: '网络', + up: '上行', + down: '下行', + interval: '采集间隔', + intervalHelper: '请输入合适的监控采集时间间隔(5秒 - 12小时)', + }, + terminal: { + local: '本机', + defaultConn: '默认连接', + defaultConnHelper: '该操作将【{0}】打开终端后自动连接所在节点终端,是否继续?', + withReset: '重置连接信息', + localConnJump: '默认连接信息在 【 终端 - 配置 】中维护,连接失败可前往编辑!', + localHelper: 'local 名称仅用于系统本机标识', + connLocalErr: '无法自动认证,请填写本地服务器的登录信息!', + testConn: '连接测试', + saveAndConn: '保存并连接', + connTestOk: '连接信息可用', + connTestFailed: '连接不可用,请检查连接信息!', + host: '主机', + createConn: '新建连接', + noHost: '暂无主机', + groupChange: '切换分组', + expand: '全部展开', + fold: '全部收缩', + batchInput: '批量输入', + quickCommand: '快速命令', + noSuchCommand: '导入的 csv 文件中未能发现快速命令数据,请检查后重试!', + quickCommandHelper: '常用命令列表,用于在终端界面底部快速选择', + groupDeleteHelper: '移除组后,组内所有连接将迁移到 default 组内,是否继续?', + command: '命令', + addHost: '添加主机', + localhost: '本地服务器', + ip: '主机地址', + authMode: '认证方式', + passwordMode: '密码认证', + rememberPassword: '记住认证信息', + keyMode: '私钥认证', + key: '私钥', + keyPassword: '私钥密码', + emptyTerminal: '暂无终端连接', + lineHeight: '字体行高', + letterSpacing: '字体间距', + fontSize: '字体大小', + cursorBlink: '光标闪烁', + cursorStyle: '光标样式', + cursorUnderline: '下划线', + cursorBlock: '块状', + cursorBar: '条形', + scrollback: '滚动行数', + scrollSensitivity: '滚动速度', + saveHelper: '是否确认保存当前终端配置?', + }, + toolbox: { + common: { + toolboxHelper: '部分安装和使用问题,可参考', + }, + swap: { + swap: 'Swap 分区', + swapHelper1: 'Swap 的大小应该是物理内存的 1 到 2 倍,可根据具体情况进行调整;', + swapHelper2: '在创建 Swap 文件之前,请确保系统硬盘有足够的可用空间,Swap 文件的大小将占用相应的磁盘空间;', + swapHelper3: + 'Swap 可以帮助缓解内存压力,但仅是一个备选项,过多依赖可能导致系统性能下降,建议优先考虑增加内存或者优化应用程序内存使用;', + swapHelper4: '建议定期监控 Swap 的使用情况,以确保系统正常运行。', + swapDeleteHelper: '此操作将移除 Swap 分区 {0},出于系统安全考虑,不会自动删除该文件,如需删除请手动操作!', + saveHelper: '请先保存当前设置!', + saveSwap: '保存当前配置将调整 Swap 分区 {0} 大小到 {1},是否继续?', + swapMin: '分区大小最小值为 40 KB,请修改后重试!', + swapMax: '分区大小最大值为 {0},请修改后重试!', + swapOff: '分区大小最小值为 40 KB,设置成 0 则关闭 Swap 分区。', + }, + device: { + dnsHelper: '服务器地址域名解析', + dnsAlert: '请注意!修改 /etc/resolv.conf 文件的配置时,重启系统后会将文件恢复为默认值', + dnsHelper1: '当存在多个 DNS 时,需换行显示,例:\n114.114.114.114\n8.8.8.8', + hostsHelper: '主机名解析', + hosts: '域名', + hostAlert: '隐藏了已注释的记录,请点击 全部配置 按钮以查看或设置', + toolbox: '快速设置', + hostname: '主机名', + passwd: '系统密码', + passwdHelper: '输入字符不能包含 $ 和 &', + timeZone: '系统时区', + localTime: '服务器时间', + timeZoneChangeHelper: '系统时区修改需要重启服务,是否继续?', + timeZoneHelper: '时区修改依赖于 timedatectl 命令,如未安装可能导致修改失败', + timeZoneCN: '北京', + timeZoneAM: '洛杉矶', + timeZoneNY: '纽约', + ntpALi: '阿里', + ntpGoogle: '谷歌', + syncSite: 'NTP 服务器', + syncSiteHelper: '该操作将使用 {0} 作为源进行系统时间同步,是否继续?', + hostnameHelper: '主机名修改依赖于 hostnamectl 命令,如未安装可能导致修改失败', + userHelper: '用户名依赖于 whoami 命令获取,如未安装可能导致获取失败。', + passwordHelper: '密码修改依赖于 chpasswd 命令,如未安装可能导致修改失败', + hostHelper: '填写的内容中存在空值,请检查修改后重试!', + dnsCheck: '测试可用性', + dnsOK: 'DNS 配置信息可用!', + dnsTestFailed: 'DNS 配置信息不可用,请修改后重试!', + }, + fail2ban: { + sshPort: '监听 SSH 端口', + sshPortHelper: '当前 Fail2ban 监听主机 SSH 连接端口', + unActive: '当前未开启 Fail2ban 服务,请先开启!', + operation: '对 Fail2ban 服务进行 [{0}] 操作,是否继续?', + fail2banChange: 'Fail2ban 配置修改', + ignoreHelper: '白名单中的 IP 列表将被忽略屏蔽,是否继续?', + bannedHelper: '黑名单中的 IP 列表将被服务器屏蔽,是否继续?', + maxRetry: '最大重试次数', + banTime: '禁用时间', + banTimeHelper: '默认禁用时间为 10 分钟,禁用时间为 -1 则表示永久禁用', + banTimeRule: '请输入正确的禁用时间或者 -1', + banAllTime: '永久禁用', + findTime: '发现周期', + banAction: '禁用方式', + banActionOption: '通过 {0} 来禁用指定的 IP 地址', + allPorts: ' (所有端口)', + ignoreIP: 'IP 白名单', + bannedIP: 'IP 黑名单', + logPath: '日志路径', + logPathHelper: '默认 /var/log/secure 或者 /var/log/auth.log', + }, + ftp: { + ftp: 'FTP 账户', + notStart: '当前未开启 FTP 服务,请先开启!', + operation: '对 FTP 服务进行 [{0}] 操作,是否继续?', + noPasswdMsg: '无法获取当前 FTP 账号密码,请先设置密码后重试!', + enableHelper: '启用选中的 FTP 账号后,该 FTP 账号恢复访问权限,是否继续操作?', + disableHelper: '停用选中的 FTP 账号后,该 FTP 账号将失去访问权限,是否继续操作?', + syncHelper: '同步服务器与数据库中的 FTP 账户数据,是否继续操作?', + dirSystem: '该目录为系统保留目录,修改可能导致系统崩溃,请修改后重试!', + dirHelper: '开启 FTP 需要修改目录权限,请谨慎选择', + dirMsg: '开启 FTP 将修改整个 {0} 目录权限,是否继续?', + }, + clam: { + clam: '病毒扫描', + cron: '定时扫描', + cronHelper: '专业版支持定时扫描功能 ', + specErr: '执行周期格式错误,请检查后重试!', + disableMsg: '停止定时执行会导致该扫描任务不再自动执行。是否继续?', + enableMsg: '启用定时执行会让该扫描任务定期自动执行。是否继续?', + showFresh: '显示病毒库服务', + hideFresh: '隐藏病毒库服务', + clamHelper: 'ClamAV 建议最低配置:3 GiB 以上内存、单核 2.0 GHz 以上 CPU,以及至少 5 GiB 可用硬盘空间。', + notStart: '当前未开启 ClamAV 服务,请先开启!', + removeRecord: '删除报告文件', + noRecords: '点击“执行”按钮开始扫描,扫描结果将会记录在这里。', + removeInfected: '删除病毒文件', + removeInfectedHelper: '删除任务检测到的病毒文件,以确保服务器的安全和正常运行。', + clamCreate: '创建扫描规则', + infectedStrategy: '感染文件策略', + removeHelper: '删除病毒文件,请谨慎选择!', + move: '移动', + moveHelper: '将病毒文件移动到指定目录下', + copyHelper: '将病毒文件复制到指定目录下', + none: '不操作', + noneHelper: '不对病毒文件采取任何操作', + scanDir: '扫描目录', + infectedDir: '隔离目录', + scanDate: '扫描时间', + scanResult: '扫描报告条数', + tail: '日志显示行数', + infectedFiles: '感染文件数', + log: '详情', + clamConf: '扫描配置', + clamLog: '扫描日志', + freshClam: '病毒库刷新配置', + freshClamLog: '病毒库刷新日志', + alertHelper: '专业版支持定时扫描和短信告警功能', + alertTitle: '病毒扫描「 {0} 」任务检测到感染文件告警', + }, + }, + logs: { + core: '面板服务', + agent: '节点监控', + panelLog: '面板日志', + operation: '操作日志', + login: '访问日志', + loginIP: '登录 IP', + loginAddress: '登录地址', + loginAgent: '用户代理', + loginStatus: '登录状态', + system: '系统日志', + deleteLogs: '清空日志', + resource: '资源', + detail: { + dashboard: '概览', + ai: 'AI', + groups: '分组', + hosts: '主机', + apps: '应用', + websites: '网站', + containers: '容器', + files: '文件管理', + runtimes: '运行环境', + process: '进程管理', + toolbox: '工具箱', + backups: '备份 / 恢复', + tampers: '防篡改', + xsetting: '界面设置', + logs: '日志审计', + settings: '面板设置', + cronjobs: '计划任务', + databases: '数据库', + waf: 'WAF', + licenses: '许可证', + nodes: '节点', + commands: '快速命令', + }, + websiteLog: '网站日志', + runLog: '运行日志', + errLog: '错误日志', + task: '任务日志', + taskName: '任务名称', + taskRunning: '执行中', + }, + file: { + fileDirNum: '共 {0} 个目录,{1} 个文件,', + currentDir: '当前目录', + dir: '文件夹', + fileName: '文件名', + search: '在当前目录下查找', + mode: '权限', + editPermissions: '编辑@:file.mode', + owner: '所有者', + file: '文件', + remoteFile: '远程下载', + share: '分享', + sync: '数据同步', + size: '大小', + updateTime: '修改时间', + rename: '重命名', + role: '权限', + info: '属性', + linkFile: '软连接文件', + shareList: '分享列表', + zip: '压缩', + group: '用户组', + path: '路径', + public: '公共', + setRole: '设置权限', + link: '是否链接', + rRole: '读取', + wRole: '写入', + xRole: '可执行', + compress: '压缩', + deCompress: '解压', + compressType: '压缩格式', + compressDst: '压缩路径', + replace: '覆盖已存在的文件', + compressSuccess: '压缩成功', + deCompressSuccess: '解压成功', + deCompressDst: '解压路径', + linkType: '链接类型', + softLink: '软链接', + hardLink: '硬链接', + linkPath: '链接路径', + selectFile: '选择文件', + downloadSuccess: '下载成功', + downloadUrl: '下载地址', + downloadStart: '下载开始!', + moveSuccess: '移动成功', + copySuccess: '复制成功', + pasteMsg: '请在目标目录点击右上角【粘贴】按钮', + move: '移动', + calculate: '计算', + remark: '备注', + setRemark: '设置备注', + remarkPrompt: '请输入备注内容', + remarkPlaceholder: '备注内容', + remarkToggle: '备注', + remarkToggleTip: '加载文件备注', + canNotDeCompress: '无法解压此文件', + uploadSuccess: '上传成功!', + downloadProcess: '下载进度', + downloading: '正在下载...', + infoDetail: '文件属性', + root: '根目录', + list: '文件列表', + sub: '子目录', + theme: '主题', + language: '语言', + eol: '行尾符', + copyDir: '复制路径', + paste: '粘贴', + changeOwner: '修改用户和用户组', + containSub: '同时修改子文件属性', + ownerHelper: 'PHP 运行环境默认用户:用户组为 1000:1000, 容器内外用户显示不一致为正常现象', + searchHelper: '支持 * 等通配符', + uploadFailed: '【{0}】 文件上传失败', + fileUploadStart: '正在上传【{0}】....', + currentSelect: '当前选中: ', + unsupportedType: '不支持的文件类型', + deleteHelper: '确定删除所选文件? 默认删除之后将进入回收站', + fileHelper: '注意:1. 搜索结果不支持排序功能 2. 文件夹无法按大小排序。', + forceDeleteHelper: '永久删除文件(不进入回收站,直接删除)', + recycleBin: '回收站', + sourcePath: '原路径', + deleteTime: '删除时间', + reduce: '还原', + confirmReduce: '确定还原以下文件?', + reduceSuccess: '还原成功', + reduceHelper: '如果原路径存在同名文件或目录,将会被覆盖,是否继续?', + clearRecycleBin: '清空回收站', + clearRecycleBinHelper: '是否清空回收站?', + favorite: '收藏夹', + removeFavorite: '是否从收藏夹移出?', + addFavorite: '添加/移出收藏夹', + clearList: '清空列表', + deleteRecycleHelper: '确定永久删除以下文件?', + typeErrOrEmpty: '【{0}】 文件类型错误或为空文件夹', + dropHelper: '将需要上传的文件拖曳到此处', + fileRecycleBin: '文件回收站', + fileRecycleBinMsg: '已{0}回收站', + wordWrap: '自动换行', + deleteHelper2: '确定删除所选文件?删除操作不可回滚', + ignoreCertificate: '忽略不可信证书', + ignoreCertificateHelper: + '下载时忽略不可信证书可能导致数据泄露或篡改。请谨慎使用此选项,仅在信任下载源的情况下启用', + uploadOverLimit: '文件数量超过 1000!请压缩后上传', + clashDitNotSupport: '文件名禁止包含 .1panel_clash', + clashDeleteAlert: '回收站文件夹不能删除', + clashOpenAlert: '回收站目录请点击【回收站】按钮打开', + right: '前进', + back: '后退', + top: '返回上一级', + up: '上一级', + openWithVscode: 'VS Code 打开', + vscodeHelper: '请确保本地已安装 VS Code 并配置了 SSH Remote 插件', + saveContentAndClose: '文件已被修改,是否保存并关闭?', + saveAndOpenNewFile: '文件已被修改,是否保存并打开新文件?', + noEdit: '文件未修改,无需此操作!', + noNameFolder: '未命名文件夹', + noNameFile: '未命名文件', + minimap: '缩略图', + fileCanNotRead: '此文件不支持预览', + previewTruncated: '文件过大,仅显示末尾内容', + previewEmpty: '文件内容为空或不是文本文件', + previewLargeFile: '预览', + panelInstallDir: '1Panel 安装目录不能删除', + wgetTask: '下载任务', + existFileTitle: '同名文件提示', + existFileHelper: '上传的文件存在同名文件,是否覆盖?', + existFileSize: '文件大小 (新 -> 旧)', + existFileDirHelper: '选择的文件/文件夹存在同名,请谨慎操作!', + coverDirHelper: '选中覆盖的文件夹,将复制到目标路径!', + noSuchFile: '未能找到该文件或目录,请检查后重试', + setting: '设置', + showHide: '显示隐藏文件', + noShowHide: '不显示隐藏文件', + cancelUpload: '取消上传', + cancelUploadHelper: '是否取消上传,取消后将清空上传列表', + keepOneTab: '至少保留一个标签页', + notCanTab: '不可增加更多的标签页', + convert: '转换格式', + converting: '转换为', + fileCanNotConvert: '此文件不支持转换格式', + formatType: '格式类型', + sourceFormat: '源格式', + sourceFile: '源文件', + saveDir: '保存目录', + deleteSourceFile: '是否删除源文件', + convertHelper: '是否为选中的文件进行格式转换', + convertHelper1: '请选择需要转换格式的文件', + execConvert: '开始转换,可以在任务中心查看转换日志', + convertLogs: '转换日志', + formatConvert: '格式转换', + }, + ssh: { + autoStart: '开机自启', + enable: '设置开机自启', + disable: '关闭开机自启', + sshAlert: '列表数据根据登录时间排序,但请注意,切换时区或其他操作可能导致登录日志的时间出现偏差。', + sshAlert2: '可通过工具箱中的 Fail2ban 屏蔽暴力破解 IP,从而保护主机安全。', + sshOperate: '对 SSH 服务进行 [{0}] 操作,是否继续?', + sshChange: 'SSH 配置修改', + sshChangeHelper: '此操作将 {0} 修改为 [{1}] ,是否继续?', + sshFileChangeHelper: '直接修改配置文件可能会导致服务不可用,请谨慎操作,是否继续?', + port: '连接端口', + portHelper: '指定 SSH 服务监听的端口号,默认为 22。', + listenAddress: '监听地址', + allV4V6: '0.0.0.0:{0}(IPv4) 和 :::{0}(IPv6)', + listenHelper: '同时取消 IPv4 和 IPv6 设置,将会同时监听 0.0.0.0:{0}(IPv4) 和 :::{0}(IPv6)', + addressHelper: '指定 SSH 服务监听的 IP 地址', + permitRootLogin: 'root 用户', + rootSettingHelper: 'root 用户 SSH 登录方式,默认所有 SSH 登录。', + rootHelper1: '允许 SSH 登录', + rootHelper2: '禁止 SSH 登录', + rootHelper3: '仅允许密钥登录', + rootHelper4: '仅允许执行预先定义的命令,不能进行其他操作', + passwordAuthentication: '密码认证', + pwdAuthHelper: '是否启用密码认证,默认启用。', + pubkeyAuthentication: '密钥认证', + privateKey: '私钥', + publicKey: '公钥', + password: '密码', + createMode: '创建方式', + generate: '自动生成', + unSyncPass: '密钥密码无法同步', + syncHelper: '同步操作将清理失效密钥并同步新增的完整密钥对,是否继续?', + input: '手动输入', + import: '文件上传', + authKeys: '授权密钥', + authKeysHelper: '是否保存当前公钥信息?', + pubkey: '密钥信息', + pubKeyHelper: '当前密钥信息仅对用户 {0} 生效', + encryptionMode: '加密方式', + passwordHelper: '支持大小写英文、数字,长度6-10', + reGenerate: '重新生成密钥', + keyAuthHelper: '是否启用密钥认证,默认启用。', + useDNS: '反向解析', + dnsHelper: '控制 SSH 服务器是否启用 DNS 解析功能,从而验证连接方的身份。', + analysis: '统计信息', + denyHelper: '将对下列地址进行【屏蔽】操作,设置后该 IP 将禁止访问服务器,是否继续?', + acceptHelper: '将对下列地址进行【放行】操作,设置后该 IP 将恢复正常访问,是否继续?', + noAddrWarning: '当前未选中任何可【{0}】地址,请检查后重试!', + loginLogs: '登录日志', + loginMode: '登录方式', + authenticating: '密钥', + publickey: '密钥', + belong: '归属地', + local: '内网', + remote: '外网', + session: '会话', + loginTime: '登录时间', + loginIP: '登录IP', + stopSSHWarn: '是否断开此SSH连接', + }, + setting: { + panel: '面板', + user: '面板用户', + userChange: '修改面板用户', + userChangeHelper: '修改面板用户将退出登录,是否继续?', + passwd: '面板密码', + emailHelper: '用于密码找回', + watermark: '水印设置', + watermarkContent: '水印内容', + contentHelper: '{0} 表示节点名称,{1} 表示节点地址,既可以使用变量,也可以填写自定义名称。', + watermarkColor: '水印颜色', + watermarkFont: '水印字号', + watermarkHeight: '水印高度', + watermarkWidth: '水印宽度', + watermarkRotate: '旋转角', + watermarkGap: '间距', + watermarkCloseHelper: '是否确认关闭系统水印设置', + watermarkOpenHelper: '是否确认保存当前系统水印设置', + title: '面板别名', + titleHelper: '支持长度3-30的英文、中文、数字、空格和常见的特殊字符', + panelPort: '面板端口', + portHelper: '建议端口范围8888 - 65535,注意:有安全组的服务器请提前在安全组放行新端口', + portChange: '端口修改', + portChangeHelper: '服务端口修改需要重启服务,是否继续?', + theme: '主题颜色', + menuTabs: '菜单标签页', + componentSize: '组件大小', + dark: '暗色', + darkGold: '黑金', + light: '亮色', + auto: '跟随系统', + language: '系统语言', + languageHelper: '默认跟随浏览器语言,设置后只对当前浏览器生效,更换浏览器后需要重新设置', + sessionTimeout: '超时时间', + sessionTimeoutError: '最小超时时间为 300 秒', + sessionTimeoutHelper: '如果用户超过 {0} 秒未操作面板,面板将自动退出登录', + systemIP: '默认访问地址', + systemIPHelper: '应用跳转、容器访问等功能将使用此地址进行跳转,每个节点可设置不同地址。', + proxy: '代理服务器', + proxyHelper: '设置代理服务器后,将在以下场景中生效:', + proxyHelper1: '应用商店的安装包下载和同步(专业版功能)', + proxyHelper2: '系统版本升级及获取更新说明(专业版功能)', + proxyHelper3: '系统许可证的验证和同步', + proxyHelper4: 'Docker 的网络访问将通过代理服务器进行(专业版功能)', + proxyHelper5: '系统类型脚本库的统一下载与同步(专业版功能)', + proxyHelper6: '申请证书(专业版功能)', + proxyType: '代理类型', + proxyUrl: '代理地址', + proxyPort: '代理端口', + proxyPasswdKeep: '记住密码', + proxyDocker: 'Docker 代理', + proxyDockerHelper: '将代理服务器配置同步至 Docker,支持离线服务器拉取镜像等操作', + syncToNode: '同步至子节点', + syncToNodeHelper: '同步设置至所选节点', + nodes: '节点', + selectNode: '选择节点', + selectNodeError: '请选择节点', + apiInterface: 'API 接口', + apiInterfaceClose: '关闭后将不能使用 API 接口进行访问,是否继续?', + apiInterfaceHelper: '提供面板支持 API 接口访问', + apiInterfaceAlert1: '请不要在生产环境开启,这可能增加服务器安全风险', + apiInterfaceAlert2: '请不要使用第三方应用调用面板 API,以防止潜在的安全威胁。', + apiInterfaceAlert3: 'API 接口文档', + apiInterfaceAlert4: '使用文档', + apiKey: '接口密钥', + apiKeyHelper: '接口密钥用于外部应用访问 API 接口', + ipWhiteList: 'IP 白名单', + ipWhiteListEgs: '当存在多个 IP 时,需要换行显示,例: \n172.16.10.111 \n172.16.10.0/24', + ipWhiteListHelper: + '必需在 IP 白名单列表中的 IP 才能访问面板 API 接口,0.0.0.0/0(所有 IPv4),::/0(所有 IPv6)', + apiKeyValidityTime: '接口密钥有效期', + apiKeyValidityTimeEgs: '接口密钥有效期(单位分)', + apiKeyValidityTimeHelper: '接口时间戳到请求时的当前时间戳之间有效(单位分),设置为 0 时,不做时间戳校验', + apiKeyReset: '接口密钥重置', + apiKeyResetHelper: '重置密钥后,已关联密钥服务将失效,请重新添加新密钥至服务。', + confDockerProxy: '配置 Docker 代理', + restartNowHelper: '配置 Docker 代理需要重启 Docker 服务。', + restartNow: '立即重启', + restartLater: '稍后手动重启', + systemIPWarning: '当前节点尚未配置默认访问地址,请前往面板设置进行设置!', + systemIPWarning1: '当前服务器地址设置为 {0},无法快速跳转!', + changePassword: '密码修改', + oldPassword: '原密码', + newPassword: '新密码', + retryPassword: '确认密码', + noSpace: '输入信息不能包含空格符', + duplicatePassword: '新密码不能与原始密码一致,请重新输入!', + diskClean: '缓存清理', + developerMode: '预览体验计划', + developerModeHelper: '获取 1Panel 的预览版本,以分享有关新功能和更新的反馈', + + thirdParty: '第三方账号', + scope: '使用范围', + public: '公有', + publicHelper: '公有类型的备份账号会同步到各个子节点,子节点可以一起使用', + private: '私有', + privateHelper: '私有类型的备份账号只创建在当前节点上,仅供当前节点使用', + noTypeForCreate: '当前无可创建备份类型', + LOCAL: '服务器磁盘', + OSS: '阿里云 OSS', + S3: '亚马逊 S3 云存储', + mode: '模式', + MINIO: 'MINIO', + SFTP: 'SFTP', + WebDAV: 'WebDAV', + WebDAVAlist: 'WebDAV 连接 Alist 可参考官方文档', + UPYUN: '又拍云', + ALIYUN: '阿里云盘', + ALIYUNHelper: '当前阿里云盘非客户端下载最大限制为 100 MB,超过限制需要通过客户端下载', + ALIYUNRecover: + '当前阿里云盘非客户端下载最大限制为 100 MB,超过限制需要通过客户端下载到本地后,同步快照进行恢复', + GoogleDrive: '谷歌云盘', + analysis: '解析', + analysisHelper: '粘贴整个 token 内容,自动解析所需部分,具体操作可参考官方文档', + serviceName: '服务名称', + operator: '操作员', + OneDrive: '微软 OneDrive', + isCN: '世纪互联', + isNotCN: '国际版', + client_id: '客户端 ID', + client_secret: '客户端密钥', + redirect_uri: '重定向 Url', + onedrive_helper: '自定义配置可参考官方文档', + clickToRefresh: '单击可手动刷新', + refreshTime: '令牌刷新时间', + refreshStatus: '令牌刷新状态', + codeWarning: '当前授权码格式错误,请重新确认!', + backupDir: '备份目录', + code: '授权码', + codeHelper: + '请点击获取按钮,然后登录 {0} 复制跳转链接中 code 后面的内容,粘贴到该输入框中,具体操作可参考官方文档。', + googleHelper: '请先在创建 google 应用并获取客户端信息,写入表单后点击获取按钮,具体操作可参考官方文档。', + loadCode: '获取', + COS: '腾讯云 COS', + ap_beijing_1: '北京一区', + ap_beijing: '北京', + ap_nanjing: '南京', + ap_shanghai: '上海', + ap_guangzhou: '广州', + ap_chengdu: '成都', + ap_chongqing: '重庆', + ap_shenzhen_fsi: '深圳金融', + ap_shanghai_fsi: '上海金融', + ap_beijing_fsi: '北京金融', + ap_hongkong: '中国香港', + ap_singapore: '新加坡', + ap_mumbai: '孟买', + ap_jakarta: '雅加达', + ap_seoul: '首尔', + ap_bangkok: '曼谷', + ap_tokyo: '东京', + na_siliconvalley: '硅谷(美西)', + na_ashburn: '弗吉尼亚(美东)', + na_toronto: '多伦多', + sa_saopaulo: '圣保罗', + eu_frankfurt: '法兰克福', + KODO: '七牛云 Kodo', + scType: '存储类型', + typeStandard: '标准存储', + typeStandard_IA: '低频存储', + typeArchive: '归档存储', + typeDeep_Archive: '深度归档存储', + scLighthouse: '默认,轻量对象存储仅支持该存储类型', + scStandard: '标准存储,适用于实时访问的大量热点文件、频繁的数据交互等业务场景。', + scStandard_IA: '低频存储,适用于较低访问频率(例如平均每月访问频率1到2次)的业务场景,最少存储30天。', + scArchive: '归档存储,适用于极低访问频率(例如半年访问1次)的业务场景。', + scDeep_Archive: '深度归档存储,适用于极低访问频率(例如1年访问1~2次)的业务场景。', + archiveHelper: '归档存储的文件无法直接下载,需要先在对应的云服务商网站进行恢复操作,请谨慎使用!', + backupAlert: '理论上只要云厂商兼容 S3 协议,就可以用现有的亚马逊 S3 云存储来备份,具体配置参考', + domain: '加速域名', + backupAccount: '备份账号', + loadBucket: '获取桶', + accountName: '账户名称', + accountKey: '账户密钥', + address: '地址', + path: '路径', + backupJump: '未在当前备份列表中的备份文件,请尝试从文件目录中下载后导入备份。', + + snapshot: '快照', + noAppData: '暂无可选择系统应用', + noBackupData: '暂无可选择备份数据', + stepBaseData: '基础数据', + stepAppData: '系统应用', + stepPanelData: '系统数据', + stepBackupData: '备份数据', + stepOtherData: '其他数据', + monitorData: '监控数据', + dockerConf: 'Docker 配置', + selectAllImage: '备份所有应用镜像', + logLabel: '日志', + agentLabel: '节点配置', + appDataLabel: '应用数据', + appImage: '应用镜像', + appBackup: '应用备份', + backupLabel: '备份目录', + confLabel: '配置文件', + dockerLabel: '容器', + taskLabel: '计划任务', + resourceLabel: '应用资源目录', + runtimeLabel: '运行环境', + appLabel: '应用', + databaseLabel: '数据库', + snapshotLabel: '快照文件', + websiteLabel: '网站', + directoryLabel: '目录', + appStoreLabel: '应用商店', + shellLabel: '脚本', + tmpLabel: '临时目录', + sslLabel: '证书目录', + reCreate: '创建快照失败', + reRollback: '回滚快照失败', + deleteHelper: '将删除该快照的所有备份文件,包括第三方备份账号中的文件。', + ignoreRule: '排除规则', + editIgnoreRule: '@:commons.button.edit@:setting.ignoreRule', + ignoreHelper: '快照时将使用该规则对 1Panel 数据目录进行压缩备份,请谨慎修改。', + ignoreHelper1: '一行一个,例: \n*.log\n/opt/1panel/cache', + status: '快照状态', + panelInfo: '写入 1Panel 基础信息', + panelBin: '备份 1Panel 系统文件', + daemonJson: '备份 Docker 配置文件', + appData: '备份 1Panel 已安装应用', + panelData: '备份 1Panel 数据目录', + backupData: '备份 1Panel 本地备份目录', + compress: '制作快照文件', + upload: '上传快照文件', + recoverDetail: '恢复详情', + recoverFailed: '快照恢复失败', + createSnapshot: '创建快照', + importSnapshot: '同步快照', + importHelper: '快照文件目录:', + lastRecoverAt: '上次恢复时间', + lastRollbackAt: '上次回滚时间', + reDownload: '重新下载', + recoverErrArch: '不支持在不同服务器架构之间进行快照恢复操作!', + recoverErrSize: '检测到当前磁盘空间不足,请检查或清理后重试!', + recoverHelper: '即将从快照 {0} 开始恢复,恢复前请确认以下信息:', + recoverHelper1: '恢复需要重启 Docker 以及 1Panel 服务', + recoverHelper2: '请确保服务器磁盘空间充足 ( 快照文件大小: {0}, 可用空间: {1} )', + recoverHelper3: '请确保服务器架构与创建快照服务器架构信息保持一致 (当前服务器架构: {0} )', + rollback: '回滚', + rollbackHelper: + '即将回滚本次恢复,回滚将替换所有本次恢复的文件,过程中可能需要重启 Docker 以及 1Panel 服务,是否继续?', + + upgradeRecord: '更新记录', + upgrading: '正在升级中,请稍候...', + upgradeHelper: '升级操作需要重启 1Panel 服务,是否继续?', + noUpgrade: '当前已经是最新版本', + versionHelper: '1Panel 版本号命名规则为: [大版本].[功能版本].[Bug 修复版本],例:', + rollbackLocalHelper: '主节点暂不支持直接回滚,请手动执行 1pctl restore 命令回滚!', + upgradeCheck: '检查更新', + upgradeNotes: '更新内容', + upgradeNow: '立即更新', + source: '下载源', + versionNotSame: '节点版本与主节点不一致,暂不支持切换,请在节点管理中升级后重试!', + versionCompare: '检测到节点 {0} 版本已是当前可升级最新版本,请检查主节点版本后重试!', + + safe: '安全', + bindInfo: '监听地址', + bindAll: '监听所有', + bindInfoHelper: '修改服务监听地址或协议可能导致服务不可用,是否继续?', + ipv6: '监听 IPv6', + bindAddress: '监听地址', + entrance: '安全入口', + showEntrance: '启用概览页未开启提醒', + entranceHelper: '开启安全入口后只能通过指定安全入口登录面板', + entranceError: '请输入 5-116 位安全登录入口,仅支持输入数字或字母', + entranceInputHelper: '安全入口设置为空时,则取消安全入口', + randomGenerate: '随机生成', + expirationTime: '密码过期时间', + unSetting: '未设置', + noneSetting: '为面板密码设置过期时间,过期后需要重新设置密码', + expirationHelper: '密码过期时间为 [0] 天时,则关闭密码过期功能', + days: '过期天数', + expiredHelper: '当前密码已过期,请重新修改密码:', + timeoutHelper: '【 {0} 天后 】面板密码即将过期,过期后需要重新设置密码', + complexity: '密码复杂度验证', + complexityHelper: '开启后密码必须满足长度为 8-30 位且包含字母、数字、特殊字符至少两项', + bindDomain: '域名绑定', + unBindDomain: '域名解绑', + panelSSL: '面板 SSL', + panelSSLHelper: '面板 SSL 自动续期后需要手动重启 1Panel 服务才可生效', + unBindDomainHelper: '解除域名绑定可能造成系统不安全,是否继续?', + bindDomainHelper: '设置域名绑定后,仅能通过设置中域名访问 1Panel 服务', + bindDomainHelper1: '绑定域名为空时,则取消域名绑定', + bindDomainWarning: '设置域名绑定后,将退出当前登录,且仅能通过设置中域名访问 1Panel 服务,是否继续?', + allowIPs: '授权 IP', + unAllowIPs: '取消授权', + unAllowIPsWarning: '授权 IP 为空将允许所有 IP 访问系统,可能造成系统不安全,是否继续?', + allowIPsHelper: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务', + allowIPsWarning: '设置授权 IP 后,仅有设置中的 IP 可以访问 1Panel 服务,是否继续?', + allowIPsHelper1: '授权 IP 为空时,则取消授权 IP', + allowIPEgs: '当存在多个授权 IP 时,需要换行显示,例: \n172.16.10.111 \n172.16.10.0/24', + mfa: '两步验证', + mfaClose: '关闭两步验证将导致服务安全性降低,是否继续?', + secret: '密钥', + mfaAlert: '两步验证密码是基于当前时间生成,请确保服务器时间已同步', + mfaHelper: '开启后会验证手机应用验证码', + mfaHelper1: '下载两步验证手机应用 如:', + mfaHelper2: '使用手机应用扫描以下二维码,获取 6 位验证码', + mfaHelper3: '输入手机应用上的 6 位数字', + mfaCode: '验证码', + mfaInterval: '刷新时间(秒)', + mfaTitleHelper: '用于区分不同 1Panel 主机,修改后请重新扫描或手动添加密钥信息!', + mfaIntervalHelper: '修改刷新时间后,请重新扫描或手动添加密钥信息!', + passkey: '通行密钥', + passkeyManage: '管理', + passkeyHelper: '用于快速登录,最多可绑定 5 个通行密钥', + passkeyRequireSSL: '开启 HTTPS 后可使用通行密钥', + passkeyNotSupported: + '当前浏览器或环境不支持通行密钥 \n 请确认您已经设置域名绑定并启用了面板 SSL, 并且访问时使用了可信的证书', + passkeyCount: '已绑定 {0}/{1}', + passkeyName: '名称', + passkeyNameHelper: '请输入用于区分设备的名称', + passkeyAdd: '添加通行密钥', + passkeyCreatedAt: '创建时间', + passkeyLastUsedAt: '最近使用', + passkeyDeleteConfirm: '删除后将无法使用该通行密钥登录,是否继续?', + passkeyLimit: '最多可绑定 5 个通行密钥', + passkeyFailed: '通行密钥注册失败,请确认面板SSL证书是否为可信证书', + sslChangeHelper: 'https 设置修改需要重启服务,是否继续?', + sslDisable: '禁用', + sslDisableHelper: '禁用 https 服务,需要重启面板才能生效,是否继续?', + noAuthSetting: '未认证设置', + noAuthSettingHelper: '用户在未登录且未正确输入安全入口、授权 IP、或绑定域名时,该响应可隐藏面板特征。', + responseSetting: '响应设置', + help200: '帮助页面', + error400: '错误请求', + error401: '未授权', + error403: '禁止访问', + error404: '未找到', + error408: '请求超时', + error416: '无效请求', + error444: '连接被关闭', + error500: '内部错误', + + https: '为面板设置HTTPS可提升访问安全性', + strictHelper: '非 HTTPS 流量无法连接面板', + muxHelper: '面板将同时监听 HTTP 和 HTTPS 端口,并将 HTTP 重定向到 HTTPS,但可能会降低安全性', + certType: '证书类型', + selfSigned: '自签名', + selfSignedHelper: '自签证书,不被浏览器信任,显示不安全是正常现象', + select: '选择已有', + domainOrIP: '域名或 IP:', + timeOut: '过期时间:', + rootCrtDownload: '根证书下载', + primaryKey: '密钥', + certificate: '证书', + + about: '关于', + versionItem: '当前版本', + backupCopies: '保留份数', + backupCopiesHelper: '设置用于版本回滚的升级备份保留份数,为 0 则保留所有。', + backupCopiesRule: '请至少保存 3 份升级备份记录', + release: '版本更新日志', + releaseHelper: '当前环境更新日志获取异常,可手动前往官方文档查看', + project: '项目地址', + issue: '问题反馈', + doc: '官方文档', + star: '点亮 Star', + description: 'Linux 服务器运维管理面板', + forum: '论坛求助', + doc2: '使用手册', + currentVersion: '当前运行版本:', + + license: '许可证', + bindNode: '绑定节点', + menuSetting: '菜单设置', + menuSettingHelper: '当只存在 1 个子菜单时,菜单栏将仅展示该子菜单', + showAll: '全部显示', + hideALL: '全部隐藏', + ifShow: '是否显示', + menu: '菜单', + confirmMessage: '即将刷新页面更新高级功能菜单列表,是否继续?', + recoverMessage: '即将刷新页面恢复菜单列表到初始状态,是否继续?', + compressPassword: '压缩密码', + backupRecoverMessage: '请输入压缩或解压缩密码(留空则不设置)', + }, + license: { + offLine: '离线版', + community: '社区版', + oss: '社区版', + pro: '专业版', + trial: '试用', + add: '添加社区版', + licenseBindHelper: '仅当许可证已绑定节点时,才能使用其免费节点额度', + licenseAlert: '仅当许可证正常绑定到节点时,该许可证才能添加社区版节点,只有正常绑定到许可证的节点支持切换。', + licenseUnbindHelper: '检测到该许可证存在社区版节点,请解绑后重试!', + subscription: '订阅', + perpetual: '永久授权', + versionConstraint: '{0} 版本买断', + forceUnbind: '强制解绑', + forceUnbindHelper: '强制解绑,会忽略解绑过程中产生的错误并最终解除许可证绑定', + updateForce: '强制更新(忽略解绑过程中的所有错误,确保最终操作成功)', + trialInfo: '版本', + authorizationId: '订阅授权 ID', + authorizedUser: '被授权方', + lostHelper: '许可证已达到最大重试次数,请手动点击同步按钮,以确保专业版功能正常使用,详情:', + exceptionalHelper: '许可证同步验证异常,请手动点击同步按钮,以确保专业版功能正常使用,详情:', + quickUpdate: '快速更新', + unbindHelper: '解除绑定后将清除该节点所有专业版相关设置,是否继续?', + importLicense: '导入许可证', + importHelper: '请点击或拖动许可文件到此处', + levelUpPro: '升级专业版', + licenseSync: '许可证同步', + knowMorePro: '了解更多', + closeAlert: '当前页面可在面板设置中关闭显示', + introduce: '功能介绍', + waf: '升级专业版可以获得拦截地图、日志、封锁记录、地理位置封禁、自定义规则、自定义拦截页面等功能。', + tamper: '升级专业版可以保护网站免受未经授权的修改或篡改。', + tamperHelper: '操作失败,该文件或文件夹已经开启防篡改,请检查后重试!', + setting: '升级专业版可以自定义面板 Logo、欢迎简介等信息。', + monitor: '升级专业版可以查看网站的实时状态、访客趋势、访客来源、请求日志等信息。', + alert: '升级专业版可通过短信接收告警信息,并查看告警日志,全面掌控各类关键事件,确保系统运行无忧。', + node: '升级专业版可以使用 1Panel 管理多台 linux 服务器。', + nodeApp: '升级专业版可以统一升级多节点应用版本,无需手动切换节点。', + nodeDashboard: '升级专业版可以集中管理多节点应用、网站、数据库和计划任务。', + fileExchange: '升级专业版可以在多台服务器之间快速传输文件。', + app: '升级专业版可通过手机 APP,查看服务信息、异常监控等。', + cluster: '升级专业版可以管理 MySQL/Postgres/Redis 主从集群。', + }, + clean: { + scan: '开始扫描', + scanHelper: '轻松梳理 1Panel 运行期间积累的垃圾文件', + clean: '立即清理', + reScan: '重新扫描', + cleanHelper: '已勾选文件及目录清理后无法回滚,是否继续?', + statusSuggest: '( 建议清理 )', + statusClean: '( 很干净 )', + statusEmpty: '非常干净,无需清理!', + statusWarning: '( 谨慎操作 )', + lastCleanTime: '上次清理时间: {0}', + lastCleanHelper: '清理文件及目录:{0} 个, 总计清理:{1}', + cleanSuccessful: '清理成功!', + currentCleanHelper: '本次清理文件及目录:{0} 个, 总计清理:{1}', + totalScan: '待清理垃圾文件共计: ', + selectScan: '已选中垃圾文件共计: ', + + system: '系统垃圾', + systemHelper: '快照、升级等过程中产生的临时文件', + panelOriginal: '系统快照恢复前备份文件', + upgrade: '系统升级备份文件', + agentPackages: '历史版本子节点升级 / 安装包', + upgradeHelper: '( 建议保留最新的升级备份用于系统回滚 )', + snapshot: '系统快照临时文件', + rollback: '恢复前备份目录', + + backup: '系统备份', + backupHelper: '本地备份账号中未关联的备份文件', + unknownBackup: '系统备份', + tmpBackup: '临时备份', + unknownApp: '未关联应用备份', + unknownDatabase: '未关联数据库备份', + unknownWebsite: '未关联网站备份', + unknownSnapshot: '未关联快照备份', + + upload: '临时上传文件', + uploadHelper: '系统上传备份列表中上传的临时文件', + download: '临时下载文件', + downloadHelper: '系统从第三方备份账号下载的临时文件', + directory: '文件夹', + + systemLog: '日志文件', + systemLogHelper: '系统日志、任务日志、网站日志文件', + dockerLog: '容器操作日志文件', + taskLog: '计划任务执行日志文件', + shell: 'Shell 脚本计划任务', + containerShell: '容器内执行 Shell 脚本计划任务', + curl: 'CURL 计划任务', + + docker: '容器垃圾', + dockerHelper: '容器、镜像、存储卷、构建缓存等文件', + volumes: '存储卷', + buildCache: '构建缓存', + + appTmpDownload: '应用临时下载文件', + unknownWebsiteLog: '未关联网站日志备份文件', + }, + app: { + app: '应用', + installName: '安装名称', + installed: '已安装', + all: '全部', + version: '版本', + detail: '详情', + params: '参数', + author: '作者', + source: '来源', + appName: '应用名称', + deleteWarn: '删除操作会把所有数据和备份一并删除,此操作不可回滚,是否继续?', + syncSuccess: '同步成功', + canUpgrade: '可升级', + backupName: '文件名称', + backupPath: '文件路径', + backupdate: '备份时间', + versionSelect: '请选择版本', + operatorHelper: '将对选中应用进行 {0} 操作,是否继续?', + checkInstalledWarn: '未检测到 {0} ,请进入应用商店点击安装!', + limitHelper: '该应用已安装,不支持重复安装', + deleteHelper: '{0}已经关联以下资源,请检查后重试!', + checkTitle: '提示', + defaultConfig: '默认配置', + defaultConfigHelper: '已恢复为默认配置,保存后生效', + forceDelete: '强制删除', + forceDeleteHelper: '强制删除,会忽略删除过程中产生的错误并最终删除元数据', + deleteBackup: '删除备份', + deleteBackupHelper: '同时删除应用备份', + deleteDB: '删除数据库', + deleteDBHelper: '同时删除与应用关联的数据库', + noService: '无{0}', + toInstall: '去安装', + param: '参数配置', + syncAppList: '更新远程应用', + alreadyRun: '已安装', + less1Minute: '小于1分钟', + appOfficeWebsite: '官方网站', + github: '开源社区', + document: '文档说明', + updatePrompt: '当前应用均为最新版本', + installPrompt: '尚未安装任何应用', + updateHelper: '更新参数可能导致应用无法启动,请提前备份并谨慎操作', + updateWarn: '更新参数需要重建应用,是否继续?', + busPort: '服务端口', + syncStart: '开始同步!请稍后刷新应用商店', + advanced: '高级设置', + cpuCore: '核心数', + containerName: '容器名称', + containerNameHelper: '可以为空,为空自动生成', + allowPort: '端口外部访问', + allowPortHelper: '允许端口外部访问会放开防火墙端口', + appInstallWarn: '应用端口默认不允许外部访问,可以在下方高级设置中选择放开', + upgradeStart: '开始升级!请稍后刷新页面', + toFolder: '进入安装目录', + editCompose: '编辑 compose 文件', + editComposeHelper: '编辑 compose 文件可能导致软件安装失败', + composeNullErr: 'compose 不能为空', + takeDown: '已废弃', + allReadyInstalled: '已安装', + installHelper: '配置镜像加速可以解决镜像拉取失败的问题', + installWarn: '当前未勾选端口外部访问,将无法通过外网IP:端口访问,是否继续?', + showIgnore: '查看忽略应用', + cancelIgnore: '取消忽略', + ignoreList: '忽略列表', + appHelper: '部分应用的安装使用说明请在应用详情页查看', + backupApp: '升级前备份应用', + backupAppHelper: '升级失败会使用备份自动回滚,请在日志审计-系统日志中查看失败原因', + openrestyDeleteHelper: '强制删除 OpenResty 会删除所有的网站,请确认风险之后操作', + downloadLogHelper1: '即将下载 {0} 应用所有日志,是否继续?', + downloadLogHelper2: '即将下载 {0} 应用最近 {1} 条日志,是否继续?', + syncAllAppHelper: '即将同步所有应用,是否继续?', + hostModeHelper: '当前应用网络模式为 host 模式,如需放开端口,请在防火墙页面手动放开', + showLocal: '本地应用', + reload: '重载', + upgradeWarn: '升级应用会替换 docker-compose.yml 文件,如有更改,可以点击查看文件对比', + newVersion: '新版本', + oldVersion: '当前版本', + composeDiff: '文件对比', + showDiff: '查看对比', + useNew: '使用自定义版本', + useDefault: '使用默认版本', + useCustom: '自定义 docker-compose.yml', + useCustomHelper: '使用自定义 docker-compose.yml 文件,可能会导致应用升级失败,如无必要,请勿勾选', + diffHelper: '左侧为旧版本,右侧为新版,编辑之后点击使用自定义版本保存', + pullImage: '拉取镜像', + pullImageHelper: '在应用启动之前执行 docker pull 来拉取镜像', + deleteImage: '删除镜像', + deleteImageHelper: '删除应用相关镜像,删除失败任务不会终止', + requireMemory: '内存需求', + supportedArchitectures: '支持架构', + link: '链接', + showCurrentArch: '本服务器架构应用', + syncLocalApp: '同步本地应用', + memoryRequiredHelper: '当前应用内存需求 {0}', + gpuConfig: '开启 GPU 支持', + gpuConfigHelper: '请确保机器有 NVIDIA GPU 并且安装 NVIDIA 驱动 和 NVIDIA docker Container Toolkit', + webUI: 'Web 访问地址', + webUIPlaceholder: '例如:example.com:8080/login', + defaultWebDomain: '默认访问地址', + defaultWebDomainHepler: '如果应用端口为 8080,则跳转地址为 http(s)://默认访问地址:8080', + webUIConfig: '当前应用尚未配置访问地址,请在应用参数或者前往面板设置进行设置!', + toLink: '跳转', + customAppHelper: '在使用自定义应用商店仓库之前,请确保没有任何已安装的应用。', + forceUninstall: '强制卸载', + syncCustomApp: '同步自定义应用', + ignoreAll: '忽略后续所有版本', + ignoreVersion: '忽略指定版本', + specifyIP: '绑定主机 IP', + specifyIPHelper: '设置端口绑定的主机地址/网卡(如果你不清楚这个的作用,请不要填写)', + uninstallDeleteBackup: '卸载应用-删除备份', + uninstallDeleteImage: '卸载应用-删除镜像', + upgradeBackup: '应用升级前备份应用', + noAppHelper: '未检测到应用,请前往任务中心查看应用商店同步日志', + isEdirWarn: '检测到 docker-compose.yml 文件被修改,请查看对比', + }, + website: { + primaryDomain: '主域名', + otherDomains: '其他域名', + static: '静态网站', + deployment: '一键部署', + supportUpType: '仅支持 .tar.gz 文件格式,且压缩包内必须包含 {0}.json 文件', + proxy: '反向代理', + alias: '代号', + ftpUser: 'FTP 账号', + ftpPassword: 'FTP 密码', + ftpHelper: '创建站点的同时,为站点创建一个对应 FTP 帐户,并且 FTP 目录指向站点所在目录。', + remark: '备注', + groupSetting: '分组管理', + createGroup: '创建分组', + appNew: '新装应用', + appInstalled: '已装应用', + create: '创建网站', + delete: '删除网站', + deleteApp: '删除应用', + deleteBackup: '删除备份', + domain: '域名', + domainHelper: '一行一个域名,支持*和IP地址,支持"域名:端口"', + addDomain: '新增域名', + domainConfig: '域名设置', + defaultDoc: '默认文档', + perserver: '并发限制', + perserverHelper: '限制当前站点最大并发数', + perip: '单 IP 限制', + peripHelper: '限制单个 IP 访问最大并发数', + rate: '流量限制', + rateHelper: '限制每个请求的流量上限(单位:KB)', + limitHelper: '启用流量控制', + other: '其他', + currentSSL: '当前证书', + dnsAccount: 'DNS 账号', + applySSL: '证书申请', + SSLList: '证书列表', + createDnsAccount: 'DNS 账户', + aliyun: '阿里云', + aliEsa: '阿里云 ESA', + awsRoute53: 'Amazon Route 53', + manual: '手动解析', + key: '密钥', + check: '查看', + acmeAccountManage: 'Acme 账户', + email: '邮箱', + acmeAccount: 'Acme 账户', + provider: '验证方式', + dnsManual: '手动解析', + expireDate: '过期时间', + brand: '颁发组织', + deploySSL: '部署', + deploySSLHelper: '确定部署证书?', + ssl: '证书', + dnsAccountManage: 'DNS 账户', + renewSSL: '续签', + renewHelper: '确定续签证书?', + renewSuccess: '续签证书', + enableHTTPS: '启用 HTTPS', + aliasHelper: '代号是网站目录的文件夹名称', + lastBackupAt: '上次备份时间', + null: '无', + nginxConfig: 'Nginx配置', + websiteConfig: '网站设置', + proxySettings: '代理设置', + advancedSettings: '高级设置', + cacheSettings: '缓存设置', + sniSettings: 'SNI 设置', + basic: '基本', + source: '配置文件', + security: '安全', + nginxPer: '性能调整', + neverExpire: '永不过期', + setDefault: '设为默认', + deleteHelper: '相关应用状态不正常,请检查', + toApp: '去已安装列表', + cycle: '周期', + frequency: '频率', + ccHelper: '{0} 秒内累计请求同一URL超过 {1} 次,触发CC防御,封锁此IP', + mustSave: '修改之后需要保存才能生效', + fileExt: '文件扩展名', + fileExtBlock: '文件扩展名黑名单', + value: '值', + enable: '开启', + proxyAddress: '代理地址', + proxyHelper: '例: 127.0.0.1:8080', + forceDelete: '强制删除', + forceDeleteHelper: '强制删除,会忽略删除过程中产生的错误并最终删除元数据', + deleteAppHelper: '同时删除关联应用、数据库以及应用备份', + deleteBackupHelper: '同时删除网站备份', + deleteDatabaseHelper: '同时删除网站关联数据库', + deleteConfirmHelper: '删除操作无法回滚,请输入 "{0}" 删除', + staticPath: '对应主目录:', + limit: '限制方案', + blog: '论坛/博客', + imageSite: '图片站', + downloadSite: '下载站', + shopSite: '商城', + doorSite: '门户', + qiteSite: '企业', + videoSite: '视频', + errLog: '错误日志', + stopHelper: '停止站点后将无法正常访问,用户访问会显示当前站点停止页面,是否继续操作?', + startHelper: '启用站点后,用户可以正常访问网站内容,是否继续操作?', + sitePath: '网站目录', + siteAlias: '网站代号', + primaryPath: 'root 目录', + folderTitle: '网站主要包含以下文件夹', + wafFolder: '防火墙规则', + indexFolder: '网站 root 目录(PHP 运行环境 静态网站代码存放目录)', + sslFolder: '网站证书', + enableOrNot: '是否启用', + oldSSL: '选择已有证书', + manualSSL: '手动导入证书', + select: '选择', + selectSSL: '选择证书', + privateKey: '私钥(KEY)', + certificate: '证书(PEM格式)', + HTTPConfig: 'HTTP 选项', + HTTPSOnly: '禁止 HTTP', + HTTPToHTTPS: '访问HTTP自动跳转到HTTPS', + HTTPAlso: 'HTTP可直接访问', + sslConfig: 'SSL 选项', + disableHTTPS: '禁用 HTTPS', + disableHTTPSHelper: '禁用 HTTPS会删除证书相关配置,是否继续?', + SSLHelper: '注意:请勿将SSL证书用于非法网站 \n 如开启后无法使用HTTPS访问,请检查安全组是否正确放行443端口', + SSLConfig: '证书设置', + SSLProConfig: 'SSL 协议设置', + supportProtocol: '支持的协议版本', + encryptionAlgorithm: '加密算法', + notSecurity: '(不安全)', + encryptHelper: + "Let's Encrypt 签发证书有频率限制,但足以满足正常需求,过于频繁操作会导致签发失败。具体限制请看 官方文档 ", + ipValue: '值', + ext: '文件扩展名', + wafInputHelper: '按行输入数据,一行一个', + data: '数据', + ever: '永久', + nextYear: '一年后', + noLog: '当前没有日志...', + defaultServer: '默认站点', + noDefaultServer: '未设置', + defaultServerHelper: + '设置默认站点后,所有未绑定的域名和IP都被定向到默认站点\n可有效防止恶意解析\n但同时会导致 WAF 未授权域名拦截失败', + websiteDeploymentHelper: '使用从 1Panel 部署的应用创建网站', + websiteStatictHelper: '在主机上创建网站目录', + websiteProxyHelper: + '代理已有服务。例如本机已安装使用 8080 端口的 halo 服务,那么代理地址为 http://127.0.0.1:8080', + restoreHelper: '确认使用此备份恢复?', + wafValueHelper: '值', + runtimeProxyHelper: '使用从 1Panel 创建的运行环境', + runtime: '运行环境', + deleteRuntimeHelper: '运行环境应用需要跟网站一并删除,请谨慎处理', + proxyType: '监听网络类型', + unix: 'Unix 网络', + tcp: 'TCP/IP 网络', + phpFPM: 'FPM 配置文件', + phpConfig: 'PHP 配置文件', + updateConfig: '配置修改', + isOn: '开启', + isOff: '关闭', + rewrite: '伪静态', + rewriteMode: '方案', + current: '当前', + rewriteHelper: '若设置伪静态后,网站无法正常访问,请尝试设置回default', + runDir: '运行目录', + runUserHelper: + '通过 PHP 容器运行环境部署的网站,需要将 index 和子目录下的所有文件、文件夹所有者和用户组设置为 1000,本地 PHP 环境需要参考本地 PHP-FPM 用户和用户组设置', + userGroup: '运行用户/组', + uGroup: '用户组', + proxyPath: '前端请求路径', + proxyPass: '后端代理地址', + cache: '缓存', + cacheTime: '缓存时间', + enableCache: '开启缓存', + proxyHost: '后端域名', + disabled: '已停止', + startProxy: '开启反向代理', + stopProxy: '关闭反向代理', + sourceFile: '源文', + proxyHelper1: '访问这个目录时将会把目标URL的内容返回并显示', + proxyPassHelper: '代理的站点,必须为可正常访问的URL', + proxyHostHelper: '将域名添加到请求头传递到代理服务器', + modifier: '匹配规则', + modifierHelper: '例:= 精确匹配,~ 正则匹配,^~ 匹配路径开头 等', + replace: '文本替换', + replaceHelper: + 'nginx文本替换功能可以在反向代理时对响应内容进行字符串替换。常用于修改代理后端返回的HTML、CSS、JavaScript等文件中的链接地址、API接口地址等。支持正则表达式匹配,可实现复杂的内容替换需求。', + addReplace: '添加文本替换', + replaced: '搜索字符串(不能为空)', + replaceText: '替换为字符串', + replacedErr: '搜索字符串不能为空', + replacedErr2: '搜索字符串不能重复', + replacedListEmpty: '无文本替换规则', + proxySslName: '代理 SNI 名称', + basicAuth: '密码访问', + editBasicAuthHelper: '密码为非对称加密,无法回显,编辑需要重新设置密码', + antiLeech: '防盗链', + extends: '扩展名', + browserCache: '浏览器缓存', + noModify: '不修改', + serverCache: '服务器缓存', + leechLog: '记录防盗链日志', + accessDomain: '允许的域名', + leechReturn: '响应资源', + noneRef: '允许 Referer 为空', + disable: '未启用', + disableLeechHelper: '是否禁用防盗链', + disableLeech: '禁用防盗链', + ipv6: '监听 IPV6', + leechReturnError: '请填写 HTTP 状态码', + blockedRef: '允许非标准 Referer', + accessControl: '防盗链控制', + leechcacheControl: '缓存控制', + logEnableControl: '记录请求日志', + leechSpecialValidHelper: + '允许 Referer 为空启用时,不会阻止无 Referer 的请求(直接访问等),启用非标准 Referer 时会放行任何不以 http/https 开头的 Referer 请求(客户端请求等)', + leechInvalidReturnHelper: '拦截盗链请求后,返回的 HTTP 状态码', + leechlogControlHelper: '记录静态资源的请求,生产环境通常可以关闭避免过多无意义的日志', + selectAcme: '选择 acme 账号', + imported: '手动创建', + importType: '导入方式', + pasteSSL: '粘贴代码', + localSSL: '选择服务器文件', + privateKeyPath: '私钥文件', + certificatePath: '证书文件', + ipWhiteListHelper: 'IP 白名单的作用:所有规则对IP白名单无效', + redirect: '重定向', + sourceDomain: '源域名/路径', + targetURL: '目标URL地址', + keepPath: '保留URI参数', + path: '路径', + redirectType: '重定向类型', + redirectWay: '方式', + keep: '保留', + notKeep: '不保留', + redirectRoot: '重定向到首页', + redirectHelper: '301永久重定向,302临时重定向', + changePHPVersionWarn: '此操作不可回滚,是否继续', + changeVersion: '切换版本', + retainConfig: '是否保留 php-fpm.conf 和 php.ini 文件', + runDirHelper2: '请确保二级运行目录位于 index 目录下', + openrestyHelper: 'OpenResty 默认 HTTP 端口:{0} HTTPS 端口 :{1},可能影响网站域名访问和 HTTPS 强制跳转', + primaryDomainHelper: '支持域名:端口', + acmeAccountType: '账号类型', + keyType: '密钥算法', + tencentCloud: '腾讯云', + containWarn: '其他域名中包含主域名,请重新输入', + rewriteHelper2: '从应用商店安装的 WordPress 等应用,默认已经配置好伪静态,重复配置可能会报错', + websiteBackupWarn: '仅支持导入本机备份,导入其他机器备份可能会恢复失败', + ipWebsiteWarn: 'IP 为域名的网站,需要设置为默认站点才能正常访问', + hstsHelper: '开启 HSTS 可以增加网站安全性', + includeSubDomains: '子域', + hstsIncludeSubDomainsHelper: '启用后,HSTS策略将应用于当前域名的所有子域名', + defaultHtml: '默认页面', + website404: '网站 404 错误页', + domain404: '网站不存在页', + indexHtml: '静态网站默认页', + stopHtml: '网站停用页', + indexPHP: 'PHP 网站默认页', + sslExpireDate: '证书过期时间', + website404Helper: '网站 404 错误页仅支持 PHP 运行环境网站和静态网站', + sni: '回源 SNI', + sniHelper: '反代后端为 https 的时候可能需要设置回源 SNI,具体需要看 CDN 服务商文档', + huaweicloud: '华为云', + createDb: '创建数据库', + enableSSLHelper: '开启失败不会影响网站创建', + batchAdd: '批量添加域名', + batchInput: '批量编辑', + domainNotFQDN: '该域名可能无法在公网访问', + domainInvalid: '域名格式不正确', + domainBatchHelper: '一行一个域名,格式:域名:端口\n示例:example.com', + generateDomain: '生成', + global: '全局', + subsite: '子网站', + subsiteHelper: '子网站可以选择已存在的 PHP 和静态网站的目录作为主目录', + parentWbeiste: '父级网站', + deleteSubsite: '删除当前网站需要先删除子网站 {0}', + loadBalance: '负载均衡', + server: '节点', + algorithm: '算法', + ipHash: 'IP 哈希', + ipHashHelper: '基于客户端 IP 地址将请求分配到特定服务器,可以确保特定客户端总是被路由到同一服务器', + leastConn: '最小连接', + leastConnHelper: '将请求发送到当前活动连接数最少的服务器', + leastTime: '最小时间', + leastTimeHelper: '将请求发送到当前活动连接时间最短的服务器', + defaultHelper: + '默认方法,请求被均匀分配到每个服务器,如果服务器有权重配置,则根据指定的权重分配请求,权重越高的服务器接收更多请求', + weight: '权重', + maxFails: '最大失败次数', + maxConns: '最大连接数', + strategy: '策略', + strategyDown: '停用', + strategyBackup: '备用', + ipHashBackupErr: 'IP 哈希不支持备用节点', + failTimeout: '故障超时', + failTimeoutHelper: + '服务器健康检查的时间窗口长度。在该时间段内累计失败次数达到阈值时,服务器将被暂时移除,并在经过相同时长后重新尝试。默认 10 秒', + + staticChangePHPHelper: '当前为静态网站,可以切换为 PHP 网站', + proxyCache: '反代缓存', + cacheLimit: '缓存空间限制', + shareCahe: '缓存计数内存大小', + cacheExpire: '缓存过期时间', + shareCaheHelper: '每1M内存可以存储约8000个缓存对象', + cacheLimitHelper: '超过限制会自动删除旧的缓存', + cacheExpireJHelper: '超出时间缓存未命中将会被删除', + realIP: '真实 IP', + ipFrom: 'IP 来源', + ipFromHelper: + '通过配置可信 IP 来源,OpenResty 会分析 HTTP Header 中的 IP 信息,准确识别并记录访客的真实 IP 地址,包括在访问日志中', + ipFromExample1: '如果前端是 Frp 等工具,可以填写 Frp 的 IP 地址,类似 127.0.0.1', + ipFromExample2: '如果前端是 CDN,可以填写 CDN 的 IP 地址段', + ipFromExample3: '如果不确定,可以填 0.0.0.0/0(ipv4) ::/0(ipv6) [注意:允许任意来源 IP 不安全]', + http3Helper: + 'HTTP/3 是 HTTP/2 的升级版本,提供更快的连接速度和更好的性能,但是不是所有浏览器都支持 HTTP/3,开启后可能会导致部分浏览器无法访问', + cors: '跨域访问', + enableCors: '开启跨域', + allowOrigins: '允许访问的域名', + allowMethods: '允许的请求方法', + allowHeaders: '允许的请求头', + allowCredentials: '允许携带cookies', + preflight: '预检请求快速响应', + preflightHleper: + '开启后,当浏览器发送跨域预检请求(OPTIONS 请求)时,系统会自动返回 204 状态码并设置必要的跨域响应头', + + changeDatabase: '切换数据库', + changeDatabaseHelper1: '数据库关联用于备份恢复网站', + changeDatabaseHelper2: '切换其他数据库会导致以前的备份无法恢复', + saveCustom: '另存为模版', + rainyun: '雨云', + volcengine: '火山引擎', + runtimePortHelper: '当前运行环境存在多个端口,请选择一个代理端口', + runtimePortWarn: '当前运行环境没有端口,无法代理', + cacheWarn: '请先关闭反代中的缓存开关', + loadBalanceHelper: '创建负载均衡后,请前往‘反向代理’,添加代理并将后端地址设置为:http://<负载均衡名称>。', + favorite: '收藏', + cancelFavorite: '取消收藏', + useProxy: '使用代理', + useProxyHelper: '使用面板设置中的代理服务器地址', + westCN: '西部数码', + openBaseDir: '防跨站攻击', + openBaseDirHelper: 'open_basedir 用于限制 PHP 文件访问路径,有助于防止跨站访问和提升安全性', + serverCacheTime: '服务器缓存时间', + serverCacheTimeHelper: '请求在服务器端缓存的时间,到期前相同请求会直接返回缓存结果,不再请求源站。', + browserCacheTime: '浏览器缓存时间', + browserCacheTimeHelper: '静态资源在浏览器本地缓存的时间,减少重复请求。到期前用户刷新页面会直接使用本地缓存。', + donotLinkeDB: '不关联数据库', + toWebsiteDir: '进入网站目录', + execParameters: '执行参数', + extCommand: '补充命令', + mirror: '镜像源', + execUser: '执行用户', + execDir: '执行目录', + packagist: '中国全量镜像', + + batchOpreate: '批量操作', + batchOpreateHelper: '批量{0}网站,是否继续操作?', + stream: 'TCP/UDP 代理', + streamPorts: '监听端口', + streamPortsHelper: '设置对外监听的端口号,客户端将通过此端口访问服务,按逗号分割,例如:5222,5223', + streamHelper: 'TCP/UDP 端口转发与负载均衡', + udp: '启用 UDP', + + syncHtmlHelper: '同步到 PHP 和静态网站', + }, + php: { + short_open_tag: '短标签支持', + max_execution_time: '最大脚本运行时间', + max_input_time: '最大输入时间', + memory_limit: ' 脚本内存限制', + post_max_size: 'POST数据最大尺寸', + file_uploads: '是否允许上传文件', + upload_max_filesize: '允许上传文件的最大尺寸', + max_file_uploads: '允许同时上传文件的最大数量', + default_socket_timeout: 'Socket超时时间', + error_reporting: '错误级别', + display_errors: '是否输出详细错误信息', + cgi_fix_pathinfo: '是否开启pathinfo', + date_timezone: '时区', + disableFunction: '禁用函数', + disableFunctionHelper: '输入要禁用的函数,例如exec,多个请用,分割', + uploadMaxSize: '上传限制', + indexHelper: '为保障 PHP 网站正常运行,请将代码放置于主目录下的 index 目录,并避免重命名', + extensions: '扩展模版', + extension: '扩展', + extensionsHelper: '多个扩展请用,分割', + toExtensionsList: '查看扩展列表', + containerConfig: '容器配置', + containerConfigHelper: '环境变量等信息可以在创建完成之后在配置-容器配置中修改', + dateTimezoneHelper: '示例:TZ=Asia/Shanghai(请根据需要自行添加)', + }, + nginx: { + serverNamesHashBucketSizeHelper: '服务器名字的hash表大小', + clientHeaderBufferSizeHelper: '客户端请求的头buffer大小', + clientMaxBodySizeHelper: '最大上传文件', + keepaliveTimeoutHelper: '连接超时时间', + gzipMinLengthHelper: '最小压缩文件', + gzipCompLevelHelper: '压缩率', + gzipHelper: '是否开启压缩传输', + connections: '活动连接(Active connections)', + accepts: '总连接次数(accepts)', + handled: '总握手次数(handled)', + requests: '总请求数(requests)', + reading: '请求数(Reading)', + writing: '响应数(Writing)', + waiting: '驻留进程(Waiting)', + status: '当前状态', + configResource: '配置修改', + saveAndReload: '保存并重载', + clearProxyCache: '清除反代缓存', + clearProxyCacheWarn: '此操作将删除缓存目录下的所有文件, 是否继续?', + create: '新增模块', + update: '编辑模块', + params: '参数', + packages: '软件包', + script: '脚本', + module: '模块', + build: '构建', + buildWarn: '构建 OpenResty 需要预留一定的 CPU 和内存,时间较长,请耐心等待', + mirrorUrl: '软件源', + paramsHelper: '例如:--add-module=/tmp/ngx_brotli', + packagesHelper: '例如:git,curl 按,分割', + scriptHelper: '编译之前执行的脚本,一般为下载模块源码,安装依赖等', + buildHelper: '添加/修改模块之后点击构建,构建成功后会自动重启 OpenResty', + defaultHttps: 'HTTPS 防窜站', + defaultHttpsHelper1: '开启后可以解决 HTTPS 窜站问题', + sslRejectHandshake: '拒绝默认 SSL 握手', + sslRejectHandshakeHelper: '开启之后可以避免证书泄露,设置默认网站会让此设置失效', + }, + ssl: { + create: '申请证书', + provider: '类型', + manualCreate: '手动创建', + acmeAccount: 'Acme 账号', + resolveDomain: '解析域名', + err: '错误', + value: '记录值', + dnsResolveHelper: '请到DNS解析服务商处添加以下解析记录:', + detail: '详情', + msg: '证书信息', + ssl: '证书', + key: '私钥', + startDate: '生效时间', + organization: '签发机构', + renewConfirm: '是否确定给域名 {0} 申请证书?', + autoRenew: '自动续签', + autoRenewHelper: '距离到期时间30天自动续签', + renewSuccess: '续签成功', + renewWebsite: '该证书已经和以下网站关联,申请会同步应用到这些网站', + createAcme: '创建账户', + acmeHelper: 'Acme 账户用于申请免费证书', + upload: '上传证书', + applyType: '申请方式', + apply: '申请', + applyStart: '证书申请开始', + getDnsResolve: '正在获取 DNS 解析值,请稍后 ...', + selfSigned: '自签证书', + ca: '证书颁发机构', + commonName: '证书主体名称(CN)', + caName: '机构名称', + company: '公司/组织', + department: '部门', + city: '城市', + province: '省份', + country: '国家代号', + commonNameHelper: '例如:', + selfSign: '签发证书', + days: '有效期', + domainHelper: '一行一个域名,支持*和IP地址', + pushDir: '推送证书到本地目录', + dir: '目录', + pushDirHelper: '会在此目录下生成两个文件,证书文件:fullchain.pem 密钥文件:privkey.pem', + organizationDetail: '机构详情', + fromWebsite: '从网站中获取', + dnsMauanlHelper: '手动解析模式需要在创建完之后点击申请按钮获取 DNS 解析值', + httpHelper: '使用 HTTP 模式需安装 OpenResty,且不支持申请泛域名证书。', + buypassHelper: 'Buypass 大陆地区无法访问', + googleHelper: '如何获取 EAB HmacKey 和 EAB kid', + googleCloudHelper: 'Google Cloud API 大陆大部分地区无法访问', + skipDNSCheck: '跳过 DNS 校验', + skipDNSCheckHelper: '如果出现申请超时问题,请勾选此处,其他情况请勿勾选', + cfHelper: '请勿使用 Global API Key', + deprecated: '即将废弃', + deprecatedHelper: '已经停止维护,可能会在以后的某个版本废弃,请使用腾讯云方式解析', + disableCNAME: '禁用 CNAME', + disableCNAMEHelper: '有 CNAME 配置的域名,如果申请失败,可以勾选此处', + nameserver: 'DNS 服务器', + nameserverHelper: '使用自定义的 DNS 服务器来校验域名', + edit: '编辑证书', + execShell: '申请证书之后执行脚本', + shell: '脚本内容', + shellHelper: + '脚本默认执行目录为 1Panel 安装目录,如果有推送证书,那么执行目录为证书推送目录。默认超时时间 30 分钟', + customAcme: '自定义 ACME 服务', + customAcmeURL: 'ACME 服务 URL', + baiduCloud: '百度云', + pushNode: '同步到其他节点', + pushNodeHelper: '申请/续期之后推送到选择的节点', + fromMaster: '主节点推送', + hostedZoneID: '托管区域 ID', + isIP: 'IP 证书', + useEAB: '使用 EAB 认证', + }, + firewall: { + create: '创建规则', + edit: '编辑规则', + advancedControl: '高级控制', + advancedControlNotAvailable: '当前使用 {0} 防火墙,高级规则仅支持 iptables', + ccDeny: 'CC 防护', + ipWhiteList: 'IP 白名单', + ipBlockList: 'IP 黑名单', + fileExtBlockList: '文件扩展名黑名单', + urlWhiteList: 'URL 白名单', + urlBlockList: 'URL 黑名单', + argsCheck: 'GET 参数校验', + postCheck: 'POST 参数校验', + cookieBlockList: 'Cookie 黑名单', + + dockerHelper: '当前防火墙无法禁用容器端口映射,已安装应用可前往【已安装】页面编辑应用参数,配置端口放行规则。', + iptablesHelper: '检测到系统正在使用 {0} 防火墙,如需切换至 iptables,请先手动卸载!', + quickJump: '快速跳转', + used: '已使用', + unUsed: '未使用', + dockerRestart: '防火墙操作需要重启 Docker 服务', + firewallHelper: '{0}系统防火墙', + firewallNotStart: '当前未开启系统防火墙,请先开启!', + restartFirewallHelper: '该操作将对当前防火墙进行重启操作,是否继续?', + stopFirewallHelper: '系统防火墙关闭后,服务器将失去安全防护,是否继续?', + startFirewallHelper: '系统防火墙开启后,可以更好的防护服务器安全,是否继续?', + noPing: '禁 ping', + enableBanPing: '禁 ping', + disableBanPing: '解除禁 ping', + noPingTitle: '是否禁 ping', + noPingHelper: '禁 ping 后将无法 ping 通服务器,是否继续?', + onPingHelper: '解除禁 ping 后您的服务器可能会被黑客发现,是否继续?', + changeStrategy: '修改{0}策略', + changeStrategyIPHelper1: 'IP 策略修改为【屏蔽】,设置后该 IP 将禁止访问服务器,是否继续?', + changeStrategyIPHelper2: 'IP 策略修改为【放行】,设置后该 IP 将恢复正常访问,是否继续?', + changeStrategyPortHelper1: '端口策略修改为【拒绝】,设置后端口将拒绝外部访问,是否继续?', + changeStrategyPortHelper2: '端口策略修改为【允许】,设置后端口将恢复正常访问,是否继续?', + stop: '禁止', + portFormatError: '请输入正确的端口信息!', + portHelper1: '多个端口,如:8080,8081', + portHelper2: '范围端口,如:8080-8089', + strategy: '策略', + accept: '允许', + drop: '拒绝', + anyWhere: '所有 IP', + address: '指定 IP', + addressHelper: '支持输入 IP 或 IP 段', + allow: '放行', + deny: '屏蔽', + addressFormatError: '请输入合法的 ip 地址!', + addressHelper1: '支持输入 IP 或 IP 段:172.16.10.11 或 172.16.0.0/24', + addressHelper2: '多个 IP 或 IP 段 请用 "," 隔开:172.16.10.11,172.16.0.0/24', + allIP: '所有 IP', + portRule: '端口规则', + createPortRule: '@:commons.button.create@:firewall.portRule', + forwardRule: '端口转发', + createForwardRule: '@:commons.button.create@:firewall.forwardRule', + ipRule: 'IP 规则', + createIpRule: '@:commons.button.create @:firewall.ipRule', + userAgent: 'User-Agent 过滤', + destination: '目的地', + sourcePort: '源端口', + targetIP: '目标 IP', + targetPort: '目标端口', + forwardHelper1: '如果是本机端口转发,目标IP为:127.0.0.1', + forwardHelper2: '如果目标IP不填写,则默认为本机端口转发', + forwardPortHelper: '支持端口范围,如:8080-8089', + forwardInboundInterface: '转发入站网卡', + exportHelper: '即将导出 {0} 条防火墙规则,是否继续?', + importSuccess: '成功导入 {0} 条规则', + importPartialSuccess: '导入完成:成功 {0} 条,失败 {1} 条', + + ipv4Limit: '当前操作暂仅支持 IPv4 地址', + basicStatus: '当前未绑定链 {0} ,请先绑定!', + baseIptables: 'iptables 服务', + forwardIptables: 'iptables 端口转发服务', + advanceIptables: 'iptables 高级配置服务', + initMsg: '即将初始化 {0}, 是否继续?', + initHelper: '检测到 {0} 未初始化,请点击顶部状态栏的初始化按钮进行配置!', + bindHelper: '绑定 仅当状态为绑定时,防火墙规则才能生效,是否确认?', + unbindHelper: '解绑 解除绑定时,已添加的所有防火墙规则将失效,请谨慎操作,是否确认?', + defaultStrategy: '当前链 {0} 的默认策略为 {1}', + defaultStrategy2: '当前链 {0} 的默认策略为 {1},当前状态为未绑定,已添加的防火墙规则需要绑定后生效!', + filterRule: 'Filter 规则', + filterHelper: 'Filter 规则允许您在 INPUT/OUTPUT 级别控制网络流量。请谨慎配置,避免锁定系统。', + chain: '链', + targetChain: '目标链', + sourceIP: '源 IP', + destIP: '目标 IP', + inboundDirection: '入站方向', + outboundDirection: '出站方向', + destPort: '目标端口', + action: '动作', + reject: '拒绝', + sourceIPHelper: 'CIDR 格式,如 192.168.1.0/24,留空表所有地址', + destIPHelper: 'CIDR 格式,如 10.0.0.0/留空表所有地址', + portHelper: '0 表示任意端口', + allPorts: '所有端口', + deleteRuleConfirm: '将删除 {0} 条规则,是否继续?', + }, + runtime: { + runtime: '运行环境', + workDir: '工作目录', + create: '创建运行环境', + localHelper: '本地环境安装及离线环境使用相关问题,可参考 ', + versionHelper: 'PHP的版本,例 v8.0', + buildHelper: '扩展越多,制作镜像时占用的 CPU 越高,可在创建环境后再安装扩展', + openrestyWarn: 'PHP 需要升级 OpenResty 至 1.21.4.1 版本以上才能使用', + toupgrade: '去升级', + edit: '编辑运行环境', + extendHelper: '未列出的扩展可手动输入并选择,如:输入 sockets 后选择下拉列表中的第一个', + rebuildHelper: '编辑扩展后需要【重建】PHP 应用之后才能生效', + rebuild: '重建 PHP 应用', + source: 'PHP 扩展源', + ustc: '中国科学技术大学', + netease: '网易', + aliyun: '阿里云', + tsinghua: '清华大学', + xtomhk: 'XTOM 镜像站(香港)', + xtom: 'XTOM 镜像站(全球)', + phpsourceHelper: '根据你的网络环境选择合适的源', + appPort: '应用端口', + externalPort: '外部映射端口', + packageManager: '包管理器', + codeDir: '项目目录', + appPortHelper: '应用端口是指容器内部的端口', + externalPortHelper: '外部映射端口是指容器对外暴露的端口', + runScript: '启动命令', + runScriptHelper: '启动命令列表是从源码目录下的 package.json 文件中解析而来', + open: '放开', + operatorHelper: '将对选中的运行环境进行 {0} 操作,是否继续?', + taobao: '淘宝', + tencent: '腾讯', + imageSource: '镜像源', + moduleManager: '模块管理', + module: '模块', + nodeOperatorHelper: '是否{0} {1} 模块?操作可能导致运行环境异常,请确认后操作', + customScript: '自定义启动命令', + customScriptHelper: '请填写完整的启动命令,例如:npm run start,pm2 启动命令请换为 pm2-runtime,否则会启动失败', + portError: '不能填写相同端口', + systemRestartHelper: '状态说明:中断-系统重启导致状态获取失败', + javaScriptHelper: '请填写完整启动命令,例如:java -jar halo.jar -Xmx1024M -Xms256M', + javaDirHelper: '目录中要包含 jar 包,子目录中包含也可', + goHelper: '请填写完整启动命令,例如:go run main.go 或 ./main', + goDirHelper: '目录中要包含 go 文件或者二进制文件,子目录中包含也可', + extension: '扩展', + installExtension: '是否确认安装扩展 {0}', + loadedExtension: '已加载扩展', + popularExtension: '常用扩展', + uninstallExtension: '是否确认卸载扩展 {0}', + phpConfigHelper: '修改配置需要重启运行环境,是否继续', + operateMode: '运行模式', + dynamic: '动态', + static: '静态', + ondemand: '按需', + dynamicHelper: '动态调整进程数,灵活性高,适合流量波动较大或者低内存的网站', + staticHelper: '固定进程数,适合高并发和稳定流量的网站,资源消耗较高', + ondemandHelper: '进程按需启动和销毁,资源利用最优,但初始响应可能较慢', + max_children: '允许创建的最大进程数', + start_servers: '启动时创建的进程数', + min_spare_servers: '最小空闲进程数', + max_spare_servers: '最大空闲进程数', + envKey: '名称', + envValue: '值', + environment: '环境变量', + pythonHelper: + '请填写完整启动命令,例如:pip install -r requirements.txt && python manage.py runserver 0.0.0.0:5000', + dotnetHelper: '请填写完整启动命令,例如 dotnet MyWebApp.dll', + dirHelper: '说明:请填写容器内的目录路径', + concurrency: '并发方案', + loadStatus: '负载状态', + extraHosts: '主机映射', + }, + process: { + pid: '进程ID', + ppid: '父进程ID', + numThreads: '线程', + memory: '内存', + diskRead: '磁盘读', + diskWrite: '磁盘写', + netSent: '上行', + netRecv: '下行', + numConnections: '连接', + startTime: '启动时间', + running: '运行中', + sleep: '睡眠', + stop: '停止', + idle: '空闲', + zombie: '僵尸进程', + wait: '等待', + lock: '锁定', + blocked: '阻塞', + cmdLine: '启动命令', + basic: '基本信息', + mem: '内存信息', + openFiles: '文件打开', + env: '环境变量', + noenv: '无', + net: '网络连接', + laddr: '本地地址/端口', + raddr: '远程地址/端口', + stopProcess: '结束', + viewDetails: '查看详情', + stopProcessWarn: '是否确定结束此进程 (PID:{0})?', + kill: '结束进程', + killNow: '立即结束', + killHelper: '结束进程 {0} 可能导致部分程序无法正常运行,是否继续?', + processName: '进程名称', + }, + tool: { + supervisor: { + loadStatusErr: '获取进程状态失败,请检查 supervisor 服务状态', + notSupport: '未检测到 Supervisor 服务,请前往脚本库页面手动安装', + list: '守护进程', + config: 'Supervisor 配置', + primaryConfig: '主配置文件位置', + notSupportCtl: '未检测到 supervisorctl,请前往脚本库页面手动安装', + user: '启动用户', + command: '启动命令', + dir: '运行目录', + numprocs: '进程数量', + initWarn: + '初始化操作需要修改配置文件的 [include] files 参数,修改后的服务配置文件所在目录: 1panel安装目录/1panel/tools/supervisord/supervisor.d/', + operatorHelper: '将对 {0} 进行 {1} 操作,是否继续?', + uptime: '运行时长', + notStartWarn: '当前未开启 Supervisor ,请先启动', + serviceName: '服务名称', + initHelper: '检测到 Supervisor 服务未初始化,请点击顶部状态栏的初始化按钮进行配置', + serviceNameHelper: 'systemctl 管理的 Supervisor 服务名称,一般为 supervisor、supervisord', + restartHelper: '初始化会重启服务,导致原有的守护进程全部关闭', + RUNNING: '运行中', + STOPPED: '已停止', + STOPPING: '停止中', + STARTING: '启动中', + FATAL: '启动失败', + BACKOFF: '启动异常', + ERROR: '错误', + statusCode: '状态码', + manage: '管理', + autoRestart: '自动重启', + EXITED: '已退出', + autoRestartHelper: '程序异常退出后是否自动重启', + autoStart: '自动启动', + autoStartHelper: 'Supervisor 启动后是否自动启动服务', + }, + }, + disk: { + management: '磁盘管理', + partition: '分区', + unmount: '取消挂载', + unmountHelper: '是否取消挂载分区 {0}?', + mount: '挂载', + partitionAlert: '进行磁盘分区时需要格式化磁盘,原有数据将被删除,请提前保存或快照数据', + mountPoint: '挂载目录', + systemDisk: '系统磁盘', + unpartitionedDisk: '未分区磁盘', + handlePartition: '立即分区', + filesystem: '文件系统', + unmounted: '未挂载', + cannotOperate: '无法操作', + systemDiskHelper: '提示:当前磁盘为系统盘,无法进行操作', + autoMount: '开机自动挂载', + model: '设备型号', + diskType: '磁盘类型', + serial: '序列号', + noFail: '挂载失败不影响系统启动', + }, + xpack: { + expiresAlert: '温馨提醒:专业版试用将于 [{0}] 天后到期,届时将停止使用所有专业版功能。', + name: '专业版', + menu: '高级功能', + upage: 'AI 建站', + proAlert: '升级专业版使用此功能', + waf: { + name: 'WAF', + blackWhite: '黑白名单', + globalSetting: '全局设置', + websiteSetting: '网站设置', + blockRecords: '封锁记录', + world: '世界', + china: '中国', + intercept: '拦截', + request: '请求', + count4xx: '4xx 数量', + count5xx: '5xx 数量', + todayStatus: '今日状态', + reqMap: '拦截地图(30日)', + count: '数量', + hight: '高', + low: '低', + reqCount: '请求数', + interceptCount: '拦截数', + requestTrends: '请求趋势(7日)', + interceptTrends: '拦截趋势(7日)', + whiteList: '白名单', + blackList: '黑名单', + ipBlackListHelper: '黑名单中的 IP 无法访问网站', + ipWhiteListHelper: '白名单中的 IP 不受任何规则限制', + uaBlackListHelper: '携带黑名单中的 User-Agent 的请求将被拦截', + uaWhiteListHelper: '携带白名单中的 User-Agent 的请求不受任何规则限制', + urlBlackListHelper: '请求黑名单中的 URL 将被拦截', + urlWhiteListHelper: '请求白名单中的 URL 不受任何规则限制', + ccHelper: '{0} 秒内累计请求任意网站超过 {1} 次,封锁此 IP {2}', + blockTime: '封禁时间', + attackHelper: '{0} 秒内累计拦截超过 {1} 次,封锁此 IP {2} ', + notFoundHelper: '{0} 秒内累计请求返回 404 超过 {1} 次,封锁此 IP {2} ', + frequencyLimit: '频率限制', + regionLimit: '地区限制', + defaultRule: '默认规则', + accessFrequencyLimit: '访问频率限制', + attackLimit: '攻击频率限制', + notFoundLimit: '404 频率限制', + urlLimit: 'URL 频率限制', + urlLimitHelper: '为单个 URL 设置访问频率', + sqliDefense: 'SQL 注入防御', + sqliHelper: '识别请求中的 SQL 注入并拦截', + xssHelper: '识别请求中的 XSS 并拦截', + xssDefense: 'XSS 防御', + uaDefense: 'User-Agent 规则', + uaHelper: '包含常见的恶意爬虫规则', + argsDefense: '参数规则', + argsHelper: '禁止请求中携带恶意参数', + cookieDefense: 'Cookie 规则', + cookieHelper: '禁止请求中携带恶意 Cookie', + headerDefense: 'Header 规则', + headerHelper: '禁止请求中携带恶意 Header', + httpRule: 'HTTP 规则', + httpHelper: + '设置允许访问的方法类型,如果想限制某些类型访问,请关闭这个类型的按钮,例如:仅允许 GET 类型访问,那么需要关闭除了 GET 之外的其他类型按钮', + geoRule: '地区访问限制', + geoHelper: '限制某些地区访问你的网站,例如:允许中国大陆访问,那么中国大陆以外的请求都会被拦截', + ipLocation: 'IP 归属地', + action: '动作', + ruleType: '攻击类型', + ipHelper: '请输入 IP', + attackLog: '攻击日志', + rule: '规则', + ipArr: 'IPV4 范围', + ipStart: '起始 IP', + ipEnd: '结束 IP', + ipv4: 'IPV4', + ipv6: 'IPV6', + urlDefense: 'URL 规则', + urlHelper: '禁止访问的 URL', + dirFilter: '目录过滤', + sqlInject: 'SQL 注入', + xss: 'XSS', + phpExec: 'PHP 脚本执行', + oneWordTrojan: '一句话木马', + appFilter: '应用危险目录过滤', + webshell: 'Webshell', + args: '参数规则', + protocolFilter: '协议过滤', + javaFileter: 'Java 危险文件过滤', + scannerFilter: '扫描器过滤', + escapeFilter: '转义过滤', + customRule: '自定义规则', + httpMethod: 'HTTP 方法过滤', + fileExt: '文件上传限制', + fileExtHelper: '禁止上传的文件扩展名', + deny: '禁止', + allow: '允许', + field: '匹配对象', + pattern: '匹配条件', + ruleContent: '匹配内容', + contain: '包含', + equal: '等于', + regex: '正则表达式', + notEqual: '不等于', + customRuleHelper: '根据条件匹配执行相应动作', + actionAllow: '允许', + blockIP: '封禁 IP', + code: '返回状态码', + noRes: '断开连接 (444)', + badReq: '参数错误 (400)', + forbidden: '禁止访问 (403)', + serverErr: '服务器错误 (500)', + resHtml: '响应页面', + allowHelper: '允许访问会跳过 WAF 规则,请谨慎使用', + captcha: '人机验证', + fiveSeconds: '5 秒验证', + location: '地区', + redisConfig: 'Redis 配置', + redisHelper: '开启 Redis 可以将临时拉黑的 IP 持久化', + wafHelper: '关闭之后所有网站将失去防护', + attackIP: '攻击 IP', + attackParam: '攻击信息', + execRule: '命中规则', + acl: '自定义规则', + sql: 'SQL 注入', + cc: '访问频率限制', + isBlocking: '封禁中', + isFree: '已解封', + unLock: '解封', + unLockHelper: '是否解封 IP:{0}?', + saveDefault: '保存默认', + saveToWebsite: '应用到网站', + saveToWebsiteHelper: '是否将当前设置应用到所有网站?', + websiteHelper: '此处为创建网站的默认设置,修改之后需要应用到网站才能生效', + websiteHelper2: '此处为创建网站的默认设置,具体配置请在网站处修改', + ipGroup: 'IP 组', + ipGroupHelper: '一行一个 IP 或者 IP 段,支持 IPv4 和 IPv6, 例如:192.168.1.1 或 192.168.1.0/24', + ipBlack: 'IP 黑名单', + openRestyAlert: 'OpenResty 版本需要高于 {0}', + initAlert: '首次使用需要初始化,会修改网站配置文件,原有的 WAF 配置会丢失,请一定提前备份 OpenResty', + initHelper: '初始化操作将清除现有的 WAF 配置,您确定要进行初始化吗?', + mainSwitch: '总开关', + websiteAlert: '请先创建网站', + defaultUrlBlack: 'URL 规则', + htmlRes: '拦截页面', + urlSearchHelper: '请输入 URL,支持模糊搜索', + toCreate: '去创建', + closeWaf: '关闭 WAF', + closeWafHelper: '关闭 WAF 会使网站失去防护,是否继续', + addblack: '拉黑', + addwhite: '加白', + addblackHelper: '是否把 IP:{0} 添加到默认黑名单?', + addwhiteHelper: '是否把 IP:{0} 添加到默认白名单?', + defaultUaBlack: 'User-Agent 规则', + defaultIpBlack: '恶意 IP 组', + cookie: 'Cookie 规则', + urlBlack: 'URL 黑名单', + uaBlack: 'User-Agent 黑名单', + attackCount: '攻击频率限制', + fileExtCheck: '文件上传限制', + geoRestrict: '地区访问限制', + attacklog: '拦截记录', + unknownWebsite: '未授权域名访问', + geoRuleEmpty: '地区不能为空', + unknown: '网站不存在', + geo: '地区限制', + revertHtml: '是否还原{0}为默认页面?', + five_seconds: '5 秒验证', + header: 'Header 规则', + methodWhite: 'HTTP 规则', + expiryDate: '有效期', + expiryDateHelper: '验证通过后有效期内不再验证', + defaultIpBlackHelper: '从互联网收集的一些恶意 IP,阻止其访问', + notFoundCount: '404 频率限制', + matchValue: '匹配值', + headerName: '支持非特殊字符开头,英文、数字、-,长度3-30', + cdnHelper: '使用 CDN 的网站可以打开此处来获取正确来源 IP', + clearLogWarn: '清空日志将无法恢复,是否继续?', + commonRuleHelper: '规则为模糊匹配', + blockIPHelper: '封锁 IP 临时存储在 OpenResty 中,重启 OpenResty 会解封,可以通过拉黑功能永久拉黑', + addWhiteUrlHelper: '是否把 URL {0} 添加到白名单?', + dashHelper: '社区版也可使用全局设置和网站设置中的功能', + wafStatusHelper: 'WAF 未开启,请在全局设置中打开', + ccMode: '模式', + global: '全局模式', + uriMode: 'URL 模式', + globalHelper: '全局模式:单位时间请求任意 URL 次数之和超过阈值即触发', + uriModeHelper: 'URL 模式:单位时间请求单个 URL 次数超过阈值即触发', + ip: 'IP 黑名单', + globalSettingHelper: '带有【网站】标签的设置,需要在【网站设置】配置生效,全局设置仅为新建网站的默认设置', + globalSettingHelper2: '设置生效需要【全局设置】和【网站设置】的开关同时打开', + urlCCHelper: '{0} 秒内累计请求此 URL 超过 {1} 次,封锁此 IP {2} ', + urlCCHelper2: 'URL 不能带参数', + notContain: '不包含', + urlcc: 'URL 频率限制', + method: '请求类型', + addIpsToBlock: '批量拉黑 IP', + addUrlsToWhite: '批量加白 URL', + noBlackIp: 'IP 已拉黑,无需再次拉黑', + noWhiteUrl: 'URL 已加白,无需再次加白', + spiderIpHelper: + '包含百度、Bing、谷歌、360、神马、搜狗、字节、DuckDuckGo、Yandex,关闭之后会拦截所有蜘蛛访问', + spiderIp: '蜘蛛 IP 池', + geoIp: 'IP 地址库', + geoIpHelper: '用来确认 IP 的地理位置', + stat: '攻击报表', + statTitle: '报表', + attackIp: '攻击 IP', + attackCountNum: '攻击次数', + percent: '占比', + addblackUrlHelper: '是否把 URL:{0} 添加到默认黑名单?', + rce: '远程代码执行', + software: '软件', + cveHelper: '包含常见软件、框架的漏洞', + vulnCheck: '补充规则', + ssrf: 'SSRF 漏洞', + afr: '任意文件读取', + ua: '未授权访问', + id: '信息泄露', + aa: '认证绕过', + dr: '目录遍历', + xxe: 'XXE 漏洞', + suid: '序列化漏洞', + dos: '拒绝服务漏洞', + afd: '任意文件下载', + sqlInjection: 'SQL 注入', + afw: '任意文件写入', + il: '信息泄漏', + clearAllLog: '清空所有日志', + exportLog: '导出日志', + appRule: '应用规则', + appRuleHelper: '常见应用的规则,开启之后可以减少误报,一个网站只能使用一个规则', + logExternal: '排除记录类型', + ipWhite: 'IP 白名单', + urlWhite: 'URL 白名单', + uaWhite: 'User-Agent 白名单', + logExternalHelper: + '排除记录类型不会被记录到日志中,黑白名单、地区访问限制、自定义规则会产生大量日志,建议排除', + ssti: 'SSTI 攻击', + crlf: 'CRLF 注入', + strict: '严格模式', + strictHelper: '使用更严格的规则来校验请求', + saveLog: '保存日志', + remoteURLHelper: '远程 URL 需要保证每行一个 IP 并且没有其他字符', + notFound: 'Not Found (404)', + serviceUnavailable: '服务不可用 (503)', + gatewayTimeout: '网关超时 (504)', + belongToIpGroup: '属于 IP 组', + notBelongToIpGroup: '不属于 IP 组', + unknownWebsiteKey: '未知域名', + special: '指定', + fileToLarge: '文件超过 1MB 无法上传', + uploadOverLimit: '上传文件超过数量限制,最大1个', + importRuleHelper: '一行一个规则', + }, + monitor: { + name: '网站监控', + pv: '浏览量', + uv: '访客数', + flow: '流量', + ip: '独立 IP', + spider: '蜘蛛', + visitors: '访客趋势', + uvMap: '访客地图 (30日)', + qps: '实时请求数(1分钟)', + flowSec: '实时流量(1分钟)', + excludeCode: '排除状态码', + excludeUrl: '排除 URL', + excludeExt: '排除扩展名', + cdnHelper: '通过 CDN 设置的 Header 来获取真实 IP', + reqRank: '访问统计', + refererDomain: '来源', + os: '操作系统', + browser: '浏览器/客户端', + device: '设备', + showMore: '查看更多', + unknown: '其他', + pc: '电脑', + mobile: '移动端', + wechat: '微信', + machine: '机器', + tencent: '腾讯浏览器', + ucweb: 'UC 浏览器', + '2345explorer': '2345 浏览器', + huaweibrowser: '华为浏览器', + log: '请求日志', + statusCode: '状态码', + requestTime: '响应时间', + flowRes: '响应流量', + method: '请求类型', + statusCodeHelper: '可在上方输入状态码', + statusCodeError: '状态码类型错误', + methodHeper: '可在上方输入请求类型', + baidu: '百度', + google: '谷歌', + bing: '必应', + bytes: '今日头条', + sogou: '搜狗', + failed: '错误', + ipCount: 'IP 数', + spiderCount: '蜘蛛请求', + averageReqTime: '平均响应时间', + totalFlow: '总流量', + logSize: '日志文件大小', + realIPType: '真实IP获取方式', + fromHeader: '从 HTTP Header 中获取', + fromHeaders: '从 Header 列表中获取', + header: 'HTTP Header', + cdnConfig: 'CDN 适配', + xff1: '获取 X-Forwarded-For 的上一级代理地址', + xff2: '获取 X-Forwarded-For 的上上一级代理地址', + xff3: '获取 X-Forwarded-For 的上上上一级代理地址', + xffHealper: '例如:X-Forwarded-For: ,,, 上一级代理会取最后一个 IP ', + headersHelper: '从下列常用的 CDN 携带真实 IP 的 HTTP Header 中获取,取第一个能获取到的值', + monitorCDNHelper: '修改网站监控的 CDN 配置会同步更新 WAF 的 CDN 配置', + wafCDNHelper: '修改 WAF 的 CDN 配置会同步更新网站监控的 CDN 配置', + statusErr: '状态码格式错误', + shenma: '神马搜索', + duckduckgo: 'DuckDuckGo', + '360': '360 搜索', + excludeUri: '排除 Uri', + top100Helper: '显示 Top 100 的数据', + logSaveDay: '日志保存天数', + cros: 'Chrome OS', + theworld: '世界之窗浏览器', + edge: 'Edge', + maxthon: '遨游浏览器', + monitorStatusHelper: '监控未开启,请在设置中打开', + excludeIp: '排除 IP', + excludeUa: '排除 User-Agent', + remotePort: '远程端口', + unknown_browser: '未知', + unknown_os: '未知', + unknown_device: '未知', + logSaveSize: '最大日志保存大小', + logSaveSizeHelper: '此处为单个网站的日志保存大小', + '360se': '360 安全浏览器', + websites: '网站列表', + trend: '趋势统计', + reqCount: '请求数', + uriHelper: '可以使用 /test/* 或者 /*/index.php 来排除 Uri', + }, + tamper: { + tamper: '网站防篡改', + ignoreTemplate: '排除模版', + protectTemplate: '保护模版', + ignoreTemplateHelper: + '请输入排除内容,回车或空格分隔。(具体目录 ./log 或目录名 tmp,排除文件需要输入具体文件 ./data/test.html)', + protectTemplateHelper: + '请输入保护内容,回车或空格分隔。(具体文件 ./index.html、文件后缀 .html、 文件类型 js,保护目录需要输入具体目录 ./log)', + templateContent: '模版内容', + template: '模版', + saveTemplate: '保存为模版', + tamperHelper1: + '一键部署类型的网站,建议启用应用目录防篡改功能;如出现网站无法正常使用或备份、恢复失败的情况,请先关闭防篡改功能;', + tamperHelper2: '将限制非排除目录下受保护文件的读写、删除、权限和所有者修改操作', + tamperPath: '防护目录', + tamperPathEdit: '修改路径', + log: '拦截日志', + totalProtect: '总防护', + todayProtect: '今日防护', + templateRule: '长度1-512,名称不能含有{0}等符号', + ignore: '排除', + ignoreHelper: + '请选择或输入排除内容,回车或空格分隔。(具体目录 ./log 或目录名 tmp,排除文件需要输入或选择具体文件 ./data/test.html)', + protect: '保护', + protectHelper: + '请选择或输入保护内容,回车或空格分隔。(具体文件 ./index.html、文件后缀 .html、文件类型 js, 保护目录需要输入或选择具体目录 ./log)', + tamperHelper00: '排除和保护仅支持输入相对路径;', + tamperHelper01: '开启防篡改后,系统将限制非排除目录下保护文件的创建、编辑和删除操作等操作;', + tamperHelper02: '优先级:具体路径保护 > 具体路径排除 > 保护 > 排除', + tamperHelper03: '监听操作只针对于非排除目录,监听该目录下非保护文件的创建操作。', + disableHelper: '即将关闭下列网站的防篡改功能,是否继续?', + appendOnly: '仅追加', + appendOnlyHelper: '将限制该目录下文件的删除操作,仅允许添加排除目录或非保护文件', + immutable: '保护', + immutableHelper: '保护该文件的编辑、删除、权限和所有者修改操作', + onWatch: '监听', + onWatchHelper: '监听该目录下保护文件或非排除目录的创建拦截', + forceStop: '强制关闭', + forceStopHelper: '即将强制关闭该网站目录的防篡改功能,是否继续?', + }, + setting: { + setting: '界面设置', + title: '面板描述', + titleHelper: '将会显示在用户登录页面 (例: Linux 服务器运维管理面板,建议 8-15 位)', + logo: 'Logo (不带文字)', + logoHelper: '将会显示在菜单收缩时管理页面左上方 (建议图片大小为: 82px*82px)', + logoWithText: 'Logo (带文字)', + logoWithTextHelper: '将会显示在菜单展开时管理页面左上方 (建议图片大小为: 185px*55px)', + favicon: '网站图标', + faviconHelper: '网站图标 (建议图片大小为: 16px*16px)', + setHelper: '即将保存当前界面设置内容,是否继续?', + setDefaultHelper: '即将恢复所有界面设置到初始状态,是否继续?', + logoGroup: 'Logo', + imageGroup: '图片', + loginImage: '登录页图片', + loginImageHelper: '将会显示在登录页面(建议图片大小为: 500*416px)', + loginBgType: '登录页背景类型', + loginBgImage: '登录页背景图片', + loginBgImageHelper: '将会显示在登录页面背景图片(建议图片大小为: 1920*1080px)', + loginBgColor: '登录页背景颜色', + loginBgColorHelper: '将会显示在登录页面背景颜色', + image: '图片', + bgColor: '背景色', + loginGroup: '登录页', + loginBtnLinkColor: '按钮/链接颜色', + loginBtnLinkColorHelper: '将会显示在登录页面按钮/链接颜色', + }, + helper: { + wafTitle1: '拦截地图', + wafContent1: '统计并展示 30 天内的拦截地理位置分布', + wafTitle2: '地区访问限制', + wafContent2: '按照地理位置限制网站的访问来源', + wafTitle3: '自定义拦截页面', + wafContent3: '自定义请求被拦截之后的显示页面', + wafTitle4: '自定义规则(ACL)', + wafContent4: '根据自定义的规则拦截请求', + + tamperTitle1: '文件完整性监控', + tamperContent1: '监控网站文件的完整性,包括核心文件、脚本文件和配置文件等。', + tamperTitle2: '实时扫描和检测', + tamperContent2: '通过实时扫描网站文件系统,检测是否存在异常或被篡改的文件。', + tamperTitle3: '安全权限设置', + tamperContent3: + '通过合理的权限设置和访问控制策略,网站防篡改功能可以限制对网站文件的访问权限,减少潜在的攻击面。', + tamperTitle4: '日志记录与分析', + tamperContent4: '记录文件访问和操作日志,以便管理员进行后续的审计和分析,以及发现潜在的安全威胁。', + + settingTitle1: '自定义欢迎语', + settingContent1: '在 1Panel 登录页上设置自定义的欢迎语。', + settingTitle2: '自定义 Logo', + settingContent2: '允许上传包含品牌名称或其他文字的 Logo 图像。', + settingTitle3: '自定义网站 icon', + settingContent3: '允许上传自定义的图标,以替代默认的浏览器图标,提升用户体验。', + + monitorTitle1: '访客趋势', + monitorContent1: '统计并展示网站的访客趋势', + monitorTitle2: '访客地图', + monitorContent2: '统计并展示网站的访客地理位置分布', + monitorTitle3: '访问统计', + monitorContent3: '统计网站的请求信息,包括蜘蛛,访问设备,请求状态等', + monitorTitle4: '实时监控', + monitorContent4: '实时监控网站的请求信息,包括请求数,流量等', + + alertTitle1: '短信告警', + alertContent1: + '当服务器资源使用异常、网站及证书过期、新版本更新、密码过期等情况发生时,通过短信告警通知用户,确保及时处理。', + alertTitle2: '告警日志', + alertContent2: '为用户提供查看告警日志的功能,方便追踪和分析历史告警事件。', + alertTitle3: '告警设置', + alertContent3: + '为用户提供自定义手机号、每日推送次数、每日推送时间的配置,方便用户的设置来更加合理的进行推送告警。', + + nodeDashTitle1: '应用管理', + nodeDashContent1: '统一管理多节点应用,支持状态监控、快速起停、终端连接及备份', + nodeDashTitle2: '网站管理', + nodeDashContent2: '统一管理多节点网站,实时监控状态,支持批量起停与快速备份', + nodeDashTitle3: '数据库管理', + nodeDashContent3: '统一管理多节点数据库,关键状态一目了然,支持一键备份', + nodeDashTitle4: '计划任务管理', + nodeDashContent4: '统一管理多节点计划任务,支持状态监控、快速启停与手动触发执行', + + nodeTitle1: '一键添加节点', + nodeContent1: '快速接入多台服务器节点', + nodeTitle2: '批量升级', + nodeContent2: '一次操作同步升级所有节点', + nodeTitle3: '节点状态监控', + nodeContent3: '实时掌握各节点运行状态', + nodeTitle4: '快速远程连接', + nodeContent4: '一键直连节点远程终端', + + fileExchangeTitle1: '密钥认证传输', + fileExchangeContent1: '通过 SSH 密钥进行身份验证,确保传输的安全性。', + fileExchangeTitle2: '高效文件同步', + fileExchangeContent2: '仅同步变化内容,大幅提高传输速度与稳定性。', + fileExchangeTitle3: '支持多节点互传', + fileExchangeContent3: '可在不同节点间便捷传送项目文件,灵活管理多台服务器。', + + nodeAppTitle1: '应用升级管理', + nodeAppContent1: '统一监控多节点应用更新情况,支持一键升级', + + appTitle1: '灵活管理面板', + appContent1: '随时随地轻松管理你的 1Panel 服务器。', + appTitle2: '全面服务信息', + appContent2: '在移动端进行应用、网站、Docker、数据库等基础管理,支持快速创建应用与网站。', + appTitle3: '实时异常监控', + appContent3: '移动端实时查看服务器状态、WAF 安全监控、网站访问统计与进程健康状况。', + + clusterTitle1: '主从部署', + clusterContent1: '支持在不同节点创建 MySQL/Postgres/Redis 主从实例,自动完成主从关联与初始化', + clusterTitle2: '主从管理', + clusterContent2: '统一页面集中管理多个主从节点,查看其角色、运行状态等信息', + clusterTitle3: '复制状态', + clusterContent3: '展示主从复制状态与延迟信息,辅助排查同步异常问题', + }, + node: { + master: '主节点', + masterBackup: '主节点备份', + backupNode: '备份节点', + backupFrequency: '备份频率(小时)', + backupCopies: '备份记录保留份数', + noBackupNode: '当前备份节点为空,请选择备份节点保存后重试!', + masterBackupAlert: + '当前未配置主节点备份,为保障数据安全,请尽快设置备份节点,便于主节点故障时可人工切换新主节点。', + node: '节点', + addr: '地址', + nodeUpgrade: '更新设置', + nodeUpgradeHelper: '选中的节点将在主节点升级完成后自动开始升级,无需手动操作。', + nodeUnhealthy: '节点状态异常', + deletedNode: '已删除节点 {0} 暂不支持升级操作!', + nodeUnhealthyHelper: '检测到该节点状态异常,请在 [节点管理] 中检查后重试!', + nodeUnbind: '节点未绑定许可证', + nodeUnbindHelper: '检测到该节点未绑定许可证,请在 [ 面板设置 - 许可证 ] 菜单中绑定后重试!', + memTotal: '内存总计', + nodeManagement: '多机管理', + nodeItem: '节点管理', + panelItem: '面板管理', + addPanel: '添加面板', + addPanelHelper: '面板添加成功后,您可在 [ 概览 - 面板 ] 中快速访问目标面板。', + panel: '1Panel 面板', + others: '其他面板', + addNode: '添加节点', + connInfo: '连接信息', + nodeInfo: '节点信息', + withProxy: '开启代理访问', + withoutProxy: '关闭代理访问', + withProxyHelper: '将使用面板设置中维护的系统代理 {0} 访问子节点,是否继续?', + withoutProxyHelper: '将停止使用面板设置中维护的系统代理访问子节点,是否继续?', + syncInfo: '数据同步', + syncHelper: '主节点数据发生变化时,实时同步到该子节点', + syncBackupAccount: '备份账号设置', + syncWithMaster: '升级为专业版后,将默认同步所有数据,可在节点管理中手动调整同步策略。', + syncProxy: '系统代理设置', + syncProxyHelper: '同步系统代理设置需要重启 Docker', + syncProxyHelper1: '重启 Docker 可能会影响当前正在运行的容器服务。', + syncProxyHelper2: '可前往 容器 - 配置 页面手动重启。', + syncProxyHelper3: '同步系统代理设置需要重启 Docker,重启可能会影响当前正在运行的容器服务', + syncProxyHelper4: '同步系统代理设置需要重启 Docker,可稍后前往 容器 - 配置 页面手动重启。', + syncCustomApp: '同步自定义应用仓库', + syncAlertSetting: '系统告警设置', + syncNodeInfo: '节点基础数据', + nodeSyncHelper: '节点信息同步将同步以下信息:', + nodeSyncHelper1: '1. 公用的备份账号信息', + nodeSyncHelper2: '2. 主节点与子节点的连接信息', + + nodeCheck: '可用性检查', + checkSSH: '检查节点 SSH 连接', + checkUserPermission: '检查节点用户权限', + isNotRoot: '检查到该节点不支持免密 sudo,且当前为非 root 用户', + checkLicense: '检查节点许可证状态', + checkService: '检查节点已存在服务信息', + checkPort: '检查节点端口可达', + panelExist: '检查到该节点正在运行 1Panel V1 服务,请先通过迁移脚本升级至 V2 后再进行添加。', + coreExist: + '当前节点已作为主节点启用,无法直接作为从节点添加。请先将其降级为从节点后再添加,具体可参考文档。', + agentExist: '检查到该节点已安装 1panel-agent,继续添加将保留现有数据,仅替换 1panel-agent 服务。', + agentNotExist: '检查到该节点未安装 1panel-agent,无法直接编辑该节点信息,请先删除后重新添加。', + oldDataExist: '检查到该节点存在 1Panel V2 历史数据,将使用以下信息覆盖当前设置', + errLicense: '检查到该节点绑定的许可证不可用,请检查后重试!', + errNodePort: '检查到节点端口 [ {0} ] 无法访问,请检查防火墙或安全组是否已放行该端口。', + + reinstallHelper: '重新安装节点 {0}, 是否继续?', + unhealthyCheck: '异常检查', + fixOperation: '修复方案', + checkName: '检查项', + checkSSHConn: '检查 SSH 连接可用性', + fixSSHConn: '手动编辑节点,确认连接信息', + checkConnInfo: '检查 Agent 连接信息', + checkStatus: '检查节点服务可用性', + fixStatus: 'systemctl status 1panel-agent.service 检查服务是否已启动', + checkAPI: '检查节点 API 可用性', + fixAPI: '排查节点日志,检查防火墙端口是否正常放行', + forceDelete: '强制删除', + operateHelper: '将对以下节点进行 {0} 操作,是否继续?', + operatePanelHelper: '将对以下面板进行 {0} 操作,是否继续?', + uninstall: '删除节点数据', + uninstallHelper: '将删除节点所有 1Panel 相关数据,谨慎选择!', + forceDeleteHelper: '强制删除将会忽略节点删除错误,删除数据库元数据', + baseDir: '安装目录', + baseDirHelper: '安装目录为空时,默认安装在 /opt 目录下', + nodePort: '节点端口', + offline: '离线模式', + freeCount: '免费额度 [{0}] ', + offlineHelper: '节点为离线环境时使用', + + appUpgrade: '应用升级', + appUpgradeHelper: '有 {0} 个应用需要升级', + }, + customApp: { + name: '自定义仓库', + appStoreType: '仓库来源', + appStoreUrl: '仓库地址', + local: '本地', + remote: '远程', + imagePrefix: '镜像前缀', + imagePrefixHelper: + '用于自定义镜像前缀,自动修改 Compose 文件中的镜像字段。\n 例如,当镜像前缀设置为 1panel/custom 时,MaxKB 的镜像将变更为 1panel/custom/maxkb:v1.10.0。', + closeHelper: '是否取消使用自定义仓库', + appStoreUrlHelper: '仅支持 .tar.gz 格式', + postNode: '同步至子节点', + postNodeHelper: '把自定义商店包同步至子节点的安装目录下的 tmp/customApp/apps.tar.gz 中', + nodes: '节点', + selectNode: '选择节点', + selectNodeError: '请选择节点', + licenseHelper: '专业版支持自定义应用仓库功能', + databaseHelper: '应用关联数据库,请选择目标节点数据库', + nodeHelper: '不能选择当前节点', + migrateHelper: '当前仅支持迁移单体应用和只关联 MySQL、MariaDB、PostgreSQL 数据库的应用', + opensslHelper: '如果使用加密备份,两个节点之间的 openssl 版本必须保持一致,不然会导致迁移失败', + installApp: '批量安装', + installAppHelper: '批量安装应用到选择的节点中', + }, + alert: { + isAlert: '是否告警', + alertCount: '告警次数', + clamHelper: '扫描到感染文件时触发告警通知', + cronJobHelper: '定时任务执行失败时将触发告警通知', + licenseHelper: '专业版支持短信告警功能', + alertCountHelper: '每日最大告警次数', + alert: '短信告警', + logs: '告警日志', + list: '告警列表', + addTask: '创建告警', + editTask: '编辑告警', + alertMethod: '告警方式', + alertMsg: '告警内容', + alertRule: '告警规则', + titleSearchHelper: '请输入告警标题,支持模糊搜索', + taskType: '告警类型', + ssl: '网站证书到期', + siteEndTime: '网站到期', + panelPwdEndTime: '面板密码到期', + panelUpdate: '面板新版本提醒', + cpu: '面板服务器 CPU 过高告警', + memory: '面板服务器内存过高告警', + load: '面板服务器负载过高告警', + disk: '面板服务器磁盘过高告警', + certificate: '证书', + remainingDays: '剩余天数', + sendCount: '告警次数', + sms: '短信通知', + wechat: '微信公众号', + dingTalk: '钉钉通知', + feiShu: '飞书通知', + mail: '邮箱通知', + weCom: '企业微信', + sendCountRulesHelper: '到期前发送告警的总数(每日仅发送一次)', + panelUpdateRulesHelper: '新版本发送告警总数(每日仅发送一次)', + oneDaySendCountRulesHelper: '每日发送告警的总数', + siteEndTimeRulesHelper: '永不过期的网站,不触发告警', + autoRenewRulesHelper: '证书开启自动续期,剩余天数小于 31 天,不触发告警', + panelPwdEndTimeRulesHelper: '面板未设置密码到期时长,不能使用密码到期告警', + sslRulesHelper: '所有 ssl 证书', + diskInfo: '磁盘信息', + monitoringType: '监测类型', + autoRenew: '自动续签', + useDisk: '占用磁盘', + usePercentage: '占用百分比', + changeStatus: '状态修改', + disableMsg: '停止告警任务会导致该任务不再发送告警消息。是否继续?', + enableMsg: '启用告警任务会让该任务发送告警消息。是否继续?', + useExceed: '使用超过', + useExceedRulesHelper: '使用超过指定值触发告警', + cpuUseExceedAvg: 'CPU 平均使用率超过指定值', + memoryUseExceedAvg: '内存平均使用率超过指定值', + loadUseExceedAvg: '负载平均使用率超过指定值', + cpuUseExceedAvgHelper: '指定时间内 CPU 平均使用率超过指定值', + memoryUseExceedAvgHelper: '指定时间内内存平均使用率超过指定值', + loadUseExceedAvgHelper: '指定时间内负载平均使用率超过指定值', + resourceAlertRulesHelper: '注意:30 分钟内持续告警只发送一次', + specifiedTime: '指定时间', + deleteTitle: '删除告警', + deleteMsg: '是否确认删除告警任务?', + + allSslTitle: '所有网站证书到期告警', + sslTitle: '网站「 {0} 」证书到期告警', + allSiteEndTimeTitle: '所有网站到期告警', + siteEndTimeTitle: '网站「 {0} 」到期告警', + panelPwdEndTimeTitle: '面板密码到期告警', + panelUpdateTitle: '面板新版本提醒', + cpuTitle: 'CPU 占用过高告警', + memoryTitle: '内存占用过高告警', + loadTitle: '负载占用过高告警', + diskTitle: '挂载目录「{0}」的磁盘占用过高告警', + allDiskTitle: '磁盘占用过高告警', + + timeRule: '剩余时间小于 {0} 天 (如未处理,次日会重新发送)', + panelUpdateRule: '检测到面板有新版本时发送一次 (如未处理,次日会重新发送)', + avgRule: '{0} 分钟内平均{1}占用超过 {2}% 触发,每天发送 {3} 次', + diskRule: '挂载目录「 {0} 」的磁盘占用超过 {1}{2} 触发,每天发送 {3} 次', + allDiskRule: '磁盘占用超过 {0}{1} 触发,每天发送 {2} 次', + + cpuName: ' CPU ', + memoryName: '内存', + loadName: '负载', + diskName: '磁盘', + + syncAlertInfo: '同步告警信息', + syncAlertInfoMsg: '是否同步告警信息内容状态?', + pushError: '推送失败', + pushSuccess: '推送成功', + syncError: '同步失败', + success: '告警成功', + pushing: '推送中...', + error: '告警失败', + cleanLog: '清空日志', + cleanAlertLogs: '清空告警日志', + daily: '当日第 {0} 次告警', + cumulative: '累计第 {0} 次告警', + clams: '病毒扫描告警', + taskName: '任务名称', + cronJobType: '任务类型', + clamPath: '扫描目录', + cronjob: '计划任务执行{0}异常', + app: '备份应用', + web: '备份网站', + database: '备份数据库', + directory: '备份目录', + log: '备份日志', + snapshot: '系统快照', + clamsRulesHelper: '需要开启告警通知的病毒扫描任务', + cronJobRulesHelper: '需要配置此类型的计划任务', + clamsTitle: '病毒扫描「 {0} 」任务检测到感染文件告警', + cronJobAppTitle: '计划任务-备份应用「 {0} 」任务失败告警', + cronJobWebsiteTitle: '计划任务-备份网站「 {0} 」任务失败告警', + cronJobDatabaseTitle: '计划任务-备份数据库「 {0} 」任务失败告警', + cronJobDirectoryTitle: '计划任务-备份目录「 {0} 」任务失败告警', + cronJobLogTitle: '计划任务-备份日志「 {0} 」任务失败告警', + cronJobSnapshotTitle: '计划任务-系统快照「 {0} 」任务失败告警', + cronJobShellTitle: '计划任务-Shell 脚本「 {0} 」任务失败告警', + cronJobCurlTitle: '计划任务-访问 URL「 {0} 」任务失败告警', + cronJobCutWebsiteLogTitle: '计划任务-切割网站日志「 {0} 」任务失败告警', + cronJobCleanTitle: '计划任务-缓存清理「 {0} 」任务失败告警', + cronJobNtpTitle: '计划任务-同步服务器时间「 {0} 」任务失败告警', + clamsRule: '病毒扫描任务检测到感染文件告警,每天发送 {0} 次', + cronJobAppRule: '备份应用任务失败告警,每天发送 {0} 次', + cronJobWebsiteRule: '备份网站任务失败告警,每天发送 {0} 次', + cronJobDatabaseRule: '备份数据库任务失败告警,每天发送 {0} 次', + cronJobDirectoryRule: '备份目录任务失败告警,每天发送 {0} 次', + cronJobLogRule: '备份日志任务失败告警,每天发送 {0} 次', + cronJobSnapshotRule: '系统快照任务失败告警,每天发送 {0} 次', + cronJobShellRule: 'Shell 脚本任务失败告警,每天发送 {0} 次', + cronJobCurlRule: '访问 URL任务失败告警,每天发送 {0} 次', + cronJobCutWebsiteLogRule: '切割网站日志任务失败告警,每天发送 {0} 次', + cronJobCleanRule: '缓存清理任务失败告警,每天发送 {0} 次', + cronJobNtpRule: '同步服务器时间任务失败告警,每天发送 {0} 次', + alertSmsHelper: '短信额度:总量 {0} 条,已使用 {1} 条', + goBuy: '去购买', + phone: '手机号', + phoneHelper: '请填写真实的手机号,以免不能正常接收告警信息', + dailyAlertNum: '每日告警次数', + dailyAlertNumHelper: '每日告警通知的总次数,最多通知 100 次', + timeRange: '时间范围', + sendTimeRange: '可发送时间范围', + sendTimeRangeHelper: '可推送{0}时间范围', + defaultPhone: '默认使用与许可证绑定的账户手机号', + noticeAlert: '通知告警', + resourceAlert: '资源告警', + agentOfflineAlertHelper: '节点开启离线告警,将通过主节点每半小时扫描执行一次告警任务', + offline: '离线告警', + offlineHelper: '设置为离线告警,将通过主节点每半小时扫描执行一次告警任务', + offlineOff: '开启离线告警', + offlineOffHelper: '开启离线告警,将通过主节点每半小时扫描执行一次告警任务', + offlineClose: '关闭离线告警', + offlineCloseHelper: '关闭离线告警,告警需通过子节点自行告警,请保证子节点网络畅通,以避免告警失败', + alertNotice: '告警通知', + methodConfig: '发送方式配置', + commonConfig: '全局配置', + smsConfig: '短信通知', + smsConfigHelper: '配置短信通知号码', + emailConfig: '邮箱', + emailConfigHelper: '配置 SMTP 邮件发送服务', + deleteConfigTitle: '删除告警配置', + deleteConfigMsg: '是否确认删除告警配置?', + test: '测试', + alertTestOk: '测试通知成功', + alertTestFailed: '测试通知失败', + displayName: '显示名称', + sender: '发信地址', + password: '密码', + host: 'SMTP 服务器', + port: '端口号', + encryption: '加密方式', + recipient: '收件人', + licenseTime: '许可证到期提醒', + licenseTimeTitle: '许可证到期提醒', + displayNameHelper: '邮件的发件人显示名称', + senderHelper: '用于发送邮件的邮箱地址', + passwordHelper: '邮件服务的授权码', + hostHelper: 'SMTP 服务器地址,例如: smtp.qq.com', + portHelper: 'SSL 通常为465,TLS 通常为587', + sslHelper: '如果 SMTP 端口是 465,通常需要启用 SSL', + tlsHelper: '如果 SMTP 端口是 587,通常需要启用 TLS', + triggerCondition: '触发条件', + loginFail: '内,登录失败', + nodeException: '节点异常告警', + licenseException: '许可证异常告警', + panelLogin: '面板登录异常告警', + sshLogin: 'SSH 登录异常告警', + panelIpLogin: '面板登录 IP 异常告警', + sshIpLogin: 'SSH 登录 IP 异常告警', + ipWhiteListHelper: '白名单中的 IP 不受规则限制,登录成功不进行告警', + nodeExceptionRule: '节点异常告警,每天发送 {0} 次', + licenseExceptionRule: '许可证异常告警,每天发送 {0} 次', + panelLoginRule: '面板登录告警,每天发送 {0} 次', + sshLoginRule: 'SSH 登录告警告警,每天发送 {0} 次', + userNameHelper: '用户名为空会默认使用发件箱地址', + }, + theme: { + lingXiaGold: '凌霞金', + classicBlue: '经典蓝', + freshGreen: '清新绿', + customColor: '自定义主题色', + setDefaultHelper: '即将恢复主题配色到初始状态,是否继续?', + setHelper: '即将保存当前选定的主题配色,是否继续?', + }, + app: { + app: 'APP', + title: '面板别名', + titleHelper: '面板别名用于 APP 端的显示(默认面板别名)', + qrCode: '二维码', + apiStatusHelper: '面板 APP 需要开启 API 接口功能', + apiInterfaceHelper: '支持面板 API 接口访问功能(面板 APP 需要开启该功能)', + apiInterfaceHelper1: + '面板 APP 访问需将访问者添加至白名单,非固定 IP 建议添加 0.0.0.0/0(所有 IPv4),::/0(所有 IPv6)', + qrCodeExpired: '刷新时间', + apiLeakageHelper: '请勿泄露二维码,确保仅在受信任的环境中使用', + }, + exchange: { + exchange: '文件对传', + exchangeConfirm: '是否将 {0} 节点文件/文件夹 {1} 传输到 {2} 节点 {3} 目录?', + }, + cluster: { + cluster: '应用高可用', + name: '集群名称', + addCluster: '添加集群', + installNode: '安装节点', + master: '主节点', + slave: '从节点', + replicaStatus: '主从状态', + unhealthyDeleteError: '安装节点状态异常,请在节点列表检查后重试!', + replicaStatusError: '状态获取异常 请检查主节点', + masterHostError: '主节点 IP 不能为 127.0.0.1', + }, + }, +}; +export default { + ...fit2cloudZhLocale, + ...message, +}; diff --git a/frontend/src/layout/components/AppFooter.vue b/frontend/src/layout/components/AppFooter.vue new file mode 100644 index 0000000..7b56561 --- /dev/null +++ b/frontend/src/layout/components/AppFooter.vue @@ -0,0 +1,53 @@ + + + + + diff --git a/frontend/src/layout/components/AppMain.vue b/frontend/src/layout/components/AppMain.vue new file mode 100644 index 0000000..c00a0ed --- /dev/null +++ b/frontend/src/layout/components/AppMain.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/layout/components/MobileHeader.vue b/frontend/src/layout/components/MobileHeader.vue new file mode 100644 index 0000000..7dbc3e1 --- /dev/null +++ b/frontend/src/layout/components/MobileHeader.vue @@ -0,0 +1,24 @@ + + + + diff --git a/frontend/src/layout/components/Sidebar/components/Collapse.vue b/frontend/src/layout/components/Sidebar/components/Collapse.vue new file mode 100644 index 0000000..cf4ab37 --- /dev/null +++ b/frontend/src/layout/components/Sidebar/components/Collapse.vue @@ -0,0 +1,343 @@ + + + + + diff --git a/frontend/src/layout/components/Sidebar/components/Logo.vue b/frontend/src/layout/components/Sidebar/components/Logo.vue new file mode 100644 index 0000000..ea03622 --- /dev/null +++ b/frontend/src/layout/components/Sidebar/components/Logo.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/frontend/src/layout/components/Sidebar/components/SubItem.vue b/frontend/src/layout/components/Sidebar/components/SubItem.vue new file mode 100644 index 0000000..0acba34 --- /dev/null +++ b/frontend/src/layout/components/Sidebar/components/SubItem.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/frontend/src/layout/components/Sidebar/index.scss b/frontend/src/layout/components/Sidebar/index.scss new file mode 100644 index 0000000..c99e3d0 --- /dev/null +++ b/frontend/src/layout/components/Sidebar/index.scss @@ -0,0 +1,90 @@ +@use '@/styles/var.scss' as *; +.el-menu { + user-select: none; + background: none; + width: 100%; + padding: 0 7px; + .el-menu-item { + border-radius: 4px; + background-color: var(--el-menu-item-bg-color); + margin: 7px 0; + height: 42px; + box-shadow: 0px 0px 4px rgba(0, 94, 235, 0.1); + box-sizing: border-box; + + &:hover { + .el-icon { + color: var(--el-color-primary); + } + + span { + color: var(--el-color-primary); + } + } + &.is-active { + background-color: var(--el-menu-item-bg-color-active); + border: 2px solid var(--el-color-primary); + + &::before { + position: absolute; + border-radius: 4px; + left: 12px; + width: 4px; + height: 14px; + content: ''; + background: var(--el-color-primary); + } + } + } + .el-sub-menu { + &.is-active { + .el-sub-menu__title { + span { + color: var(--el-color-primary); + } + + .el-icon { + color: var(--el-color-primary); + } + } + } + .el-sub-menu__title { + background-color: var(--el-menu-item-bg-color); + box-shadow: 0 0 4px rgba(0, 94, 235, 0.1); + margin-top: 7px; + height: 46px; + border-radius: 4px; + &:hover { + .el-icon { + color: var(--el-color-primary); + } + + span { + color: var(--el-color-primary); + } + } + } + .el-menu { + padding: 0; + .el-menu-item { + box-shadow: none; + padding-left: 35px; + } + } + } +} + +.sidebar-container-popper { + .el-menu { + background-color: var(--el-menu-bg-color); + padding: 4px 8px; + } +} + +.el-sub-menu__title { + padding-right: 0; +} + +.p-mr-5 { + margin-right: 5px; +} diff --git a/frontend/src/layout/components/Sidebar/index.vue b/frontend/src/layout/components/Sidebar/index.vue new file mode 100644 index 0000000..3c2b7a7 --- /dev/null +++ b/frontend/src/layout/components/Sidebar/index.vue @@ -0,0 +1,259 @@ + + + + + diff --git a/frontend/src/layout/components/Tabs/components/TabItem.vue b/frontend/src/layout/components/Tabs/components/TabItem.vue new file mode 100644 index 0000000..146f7cb --- /dev/null +++ b/frontend/src/layout/components/Tabs/components/TabItem.vue @@ -0,0 +1,93 @@ + + + + + diff --git a/frontend/src/layout/components/Tabs/index.vue b/frontend/src/layout/components/Tabs/index.vue new file mode 100644 index 0000000..4a88c83 --- /dev/null +++ b/frontend/src/layout/components/Tabs/index.vue @@ -0,0 +1,95 @@ + + + + + diff --git a/frontend/src/layout/components/index.ts b/frontend/src/layout/components/index.ts new file mode 100644 index 0000000..d3a36e6 --- /dev/null +++ b/frontend/src/layout/components/index.ts @@ -0,0 +1,5 @@ +export { default as Sidebar } from './Sidebar/index.vue'; +export { default as Footer } from './AppFooter.vue'; +export { default as AppMain } from './AppMain.vue'; +export { default as MobileHeader } from './MobileHeader.vue'; +export { default as Tabs } from '@/layout/components/Tabs/index.vue'; diff --git a/frontend/src/layout/hooks/useResize.ts b/frontend/src/layout/hooks/useResize.ts new file mode 100644 index 0000000..296b328 --- /dev/null +++ b/frontend/src/layout/hooks/useResize.ts @@ -0,0 +1,51 @@ +import { watch, onBeforeMount, onMounted, onBeforeUnmount } from 'vue'; +import { useRoute } from 'vue-router'; +import { GlobalStore, MenuStore } from '@/store'; +import { DeviceType } from '@/enums/app'; +/** 参考 Bootstrap 的响应式设计 WIDTH = 600 */ +const WIDTH = 600; + +/** 根据大小变化重新布局 */ +export default () => { + const route = useRoute(); + const globalStore = GlobalStore(); + const menuStore = MenuStore(); + const _isMobile = () => { + const rect = document.body.getBoundingClientRect(); + return rect.width - 1 < WIDTH; + }; + + const _resizeHandler = () => { + if (!document.hidden) { + const isMobile = _isMobile(); + globalStore.toggleDevice(isMobile ? DeviceType.Mobile : DeviceType.Desktop); + if (isMobile) { + menuStore.closeSidebar(true); + } + } + }; + + watch( + () => route.name, + () => { + if (globalStore.device === DeviceType.Mobile && !menuStore.isCollapse) { + menuStore.closeSidebar(false); + } + }, + ); + + onBeforeMount(() => { + window.addEventListener('resize', _resizeHandler); + }); + + onMounted(() => { + if (_isMobile()) { + globalStore.toggleDevice(DeviceType.Mobile); + menuStore.closeSidebar(true); + } + }); + + onBeforeUnmount(() => { + window.removeEventListener('resize', _resizeHandler); + }); +}; diff --git a/frontend/src/layout/index.vue b/frontend/src/layout/index.vue new file mode 100644 index 0000000..87b968e --- /dev/null +++ b/frontend/src/layout/index.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/frontend/src/main.ts b/frontend/src/main.ts new file mode 100644 index 0000000..baf5916 --- /dev/null +++ b/frontend/src/main.ts @@ -0,0 +1,50 @@ +import { createApp } from 'vue'; +import App from './App.vue'; + +import '@/styles/index.scss'; +import '@/styles/common.scss'; +import '@/assets/iconfont/iconfont.css'; +import '@/assets/iconfont/iconfont.js'; +import '@/styles/style.css'; + +const styleModule = import.meta.glob('xpack/styles/index.scss'); +for (const path in styleModule) { + styleModule[path]?.(); +} + +import router from '@/routers/index'; +import i18n, { ensureFallbackLocale, loadLocaleMessages } from '@/lang/index'; +import pinia from '@/store/index'; +import SvgIcon from './components/svg-icon/svg-icon.vue'; +import Components from '@/components'; + +import ElementPlus from 'element-plus'; +import Fit2CloudPlus from 'fit2cloud-ui-plus'; +import * as Icons from '@element-plus/icons-vue'; + +import directives from '@/directives/index'; + +const bootstrap = async () => { + const currentLocale = i18n.global.locale.value; + + await Promise.all([loadLocaleMessages(currentLocale), ensureFallbackLocale()]); + + const app = createApp(App); + app.component('SvgIcon', SvgIcon); + app.use(ElementPlus); + app.use(Fit2CloudPlus, { locale: i18n.global.getLocaleMessage(currentLocale) }); + + Object.keys(Icons).forEach((key) => { + app.component(key, Icons[key as keyof typeof Icons]); + }); + + app.use(router); + app.use(i18n); + app.use(pinia); + app.use(Components); + app.use(directives); + + app.mount('#app'); +}; + +bootstrap(); diff --git a/frontend/src/routers/cache-router.ts b/frontend/src/routers/cache-router.ts new file mode 100644 index 0000000..d53cc74 --- /dev/null +++ b/frontend/src/routers/cache-router.ts @@ -0,0 +1,20 @@ +import { RouteRecordRaw, RouteRecordName } from 'vue-router'; +import { routerArray } from '@/routers/router'; + +/** + * @description 使用递归,过滤需要缓存的路由 + * @param {Array} _route 所有路由表 + * @param {Array} _cache 缓存的路由表 + * @return void + * */ +let cacheRouter: RouteRecordName[] = []; +const filterKeepAlive = (_route: RouteRecordRaw[], _cache: RouteRecordName[]): void => { + _route.forEach((item) => { + item.meta?.keepAlive && item.name && _cache.push(item.name); + item?.children?.length && filterKeepAlive(item.children, _cache); + }); +}; + +filterKeepAlive(routerArray, cacheRouter); + +export default cacheRouter; diff --git a/frontend/src/routers/constant.ts b/frontend/src/routers/constant.ts new file mode 100644 index 0000000..94c63fd --- /dev/null +++ b/frontend/src/routers/constant.ts @@ -0,0 +1,4 @@ +/** + * @description: default layout + */ +export const Layout = () => import('@/layout/index.vue'); diff --git a/frontend/src/routers/index.ts b/frontend/src/routers/index.ts new file mode 100644 index 0000000..3655512 --- /dev/null +++ b/frontend/src/routers/index.ts @@ -0,0 +1,88 @@ +import router from '@/routers/router'; +import NProgress from '@/config/nprogress'; +import { GlobalStore } from '@/store'; +import { AxiosCanceler } from '@/api/helper/axios-cancel'; + +const axiosCanceler = new AxiosCanceler(); + +let isRedirecting = false; + +router.beforeEach((to, from, next) => { + NProgress.start(); + axiosCanceler.removeAllPending(); + const globalStore = GlobalStore(); + if (to.name !== 'entrance' && !globalStore.isLogin) { + next({ + name: 'entrance', + params: to.params, + }); + NProgress.done(); + return; + } + if (to.name === 'entrance' && globalStore.isLogin) { + if (to.params.code === globalStore.entrance) { + next({ + name: 'home', + }); + NProgress.done(); + return; + } + next({ name: '404' }); + NProgress.done(); + return; + } + + if (to.path === '/apps/all' && to.query.install != undefined) { + return next(); + } + const activeMenuKey = 'cachedRoute' + (to.meta.activeMenu || ''); + if (to.query.uncached != undefined) { + const query = { ...to.query }; + delete query.uncached; + localStorage.removeItem(activeMenuKey); + return next({ path: to.path, query }); + } + + const cachedRoute = localStorage.getItem(activeMenuKey); + if ( + to.meta.activeMenu && + to.meta.activeMenu != from.meta.activeMenu && + cachedRoute && + cachedRoute !== to.path && + !isRedirecting + ) { + isRedirecting = true; + next(cachedRoute); + NProgress.done(); + return; + } + + if (!to.matched.some((record) => record.meta.requiresAuth)) return next(); + + return next(); +}); + +router.afterEach((to) => { + if (to.meta.activeMenu && !isRedirecting) { + let notMathParam = true; + if (to.matched.some((record) => record.path.includes(':'))) { + notMathParam = false; + } + if (notMathParam) { + if (to.meta.activeMenu === '/cronjobs' && to.path === '/cronjobs/cronjob/operate') { + localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/cronjobs/cronjob'); + } else if (to.meta.activeMenu === '/containers' && to.path === '/containers/container/operate') { + localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/containers/container'); + } else if (to.meta.activeMenu === '/toolbox' && to.path === '/toolbox/clam/setting') { + localStorage.setItem('cachedRoute' + to.meta.activeMenu, '/toolbox/clam'); + } else { + localStorage.setItem('cachedRoute' + to.meta.activeMenu, to.path); + } + } + } + + isRedirecting = false; + NProgress.done(); +}); + +export default router; diff --git a/frontend/src/routers/modules/ai.ts b/frontend/src/routers/modules/ai.ts new file mode 100644 index 0000000..e4bf716 --- /dev/null +++ b/frontend/src/routers/modules/ai.ts @@ -0,0 +1,69 @@ +import { Layout } from '@/routers/constant'; + +const databaseRouter = { + sort: 4, + path: '/ai', + name: 'AI-Menu', + component: Layout, + redirect: '/ai/model', + meta: { + icon: 'p-jiqiren2', + title: 'menu.aiTools', + }, + children: [ + { + path: '/ai/model/ollama', + name: 'OllamaModel', + component: () => import('@/views/ai/model/ollama/index.vue'), + meta: { + icon: 'p-moxing-menu', + title: 'aiTools.model.model', + requiresAuth: true, + }, + }, + { + path: '/ai/mcp', + name: 'MCPServer', + component: () => import('@/views/ai/mcp/server/index.vue'), + meta: { + icon: 'p-mcp-menu', + title: 'menu.mcp', + requiresAuth: true, + }, + }, + { + path: '/ai/model/tensorrt', + hidden: true, + name: 'TensorRTLLm', + component: () => import('@/views/ai/model/tensorrt/index.vue'), + meta: { + title: 'aiTools.tensorRT.llm', + activeMenu: '/ai/model/ollama', + requiresAuth: true, + }, + }, + { + path: '/ai/gpu/current', + name: 'GPU', + component: () => import('@/views/ai/gpu/current/index.vue'), + meta: { + icon: 'p-gpu-menu', + title: 'aiTools.gpu.gpu', + activeMenu: '/ai/gpu', + requiresAuth: true, + }, + }, + { + path: '/ai/gpu/history', + name: 'GPUHistory', + component: () => import('@/views/ai/gpu/history/index.vue'), + meta: { + title: 'aiTools.gpu.history', + activeMenu: '/ai/gpu', + requiresAuth: true, + }, + }, + ], +}; + +export default databaseRouter; diff --git a/frontend/src/routers/modules/app-store.ts b/frontend/src/routers/modules/app-store.ts new file mode 100644 index 0000000..b640618 --- /dev/null +++ b/frontend/src/routers/modules/app-store.ts @@ -0,0 +1,78 @@ +import { Layout } from '@/routers/constant'; + +const appStoreRouter = { + sort: 2, + path: '/apps', + name: 'App-Menu', + component: Layout, + redirect: '/apps/all', + meta: { + icon: 'p-appstore', + title: 'menu.apps', + }, + children: [ + { + path: '/apps', + name: 'App', + redirect: '/apps/all', + component: () => import('@/views/app-store/index.vue'), + meta: {}, + children: [ + { + path: 'all', + name: 'AppAll', + component: () => import('@/views/app-store/apps/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/apps', + requiresAuth: false, + parent: 'menu.app', + title: 'app.all', + }, + }, + { + path: 'installed', + name: 'AppInstalled', + component: () => import('@/views/app-store/installed/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/apps', + requiresAuth: false, + parent: 'menu.app', + title: 'app.installed', + }, + }, + { + path: 'upgrade', + name: 'AppUpgrade', + component: () => import('@/views/app-store/installed/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/apps', + requiresAuth: false, + parent: 'menu.app', + title: 'app.canUpgrade', + }, + }, + { + path: 'setting', + name: 'AppStoreSetting', + component: () => import('@/views/app-store/setting/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/apps', + requiresAuth: false, + parent: 'menu.app', + title: 'commons.button.set', + }, + }, + ], + }, + ], +}; + +export default appStoreRouter; diff --git a/frontend/src/routers/modules/container.ts b/frontend/src/routers/modules/container.ts new file mode 100644 index 0000000..7cdc31e --- /dev/null +++ b/frontend/src/routers/modules/container.ts @@ -0,0 +1,148 @@ +import { Layout } from '@/routers/constant'; + +const containerRouter = { + sort: 6, + path: '/containers', + name: 'Container-Menu', + component: Layout, + redirect: '/containers/container', + meta: { + icon: 'p-docker1', + title: 'menu.container', + }, + children: [ + { + path: '/containers', + name: 'Container', + redirect: '/containers/dashboard', + component: () => import('@/views/container/index.vue'), + meta: {}, + children: [ + { + path: 'dashboard', + name: 'ContainerDashboard', + component: () => import('@/views/container/dashboard/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'menu.home', + }, + }, + { + path: 'container', + name: 'ContainerItem', + component: () => import('@/views/container/container/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'menu.container', + }, + }, + { + path: 'container/operate', + name: 'ContainerCreate', + component: () => import('@/views/container/container/operate/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + ignoreTab: true, + }, + }, + { + path: 'image', + name: 'Image', + component: () => import('@/views/container/image/index.vue'), + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'container.image', + }, + }, + { + path: 'network', + name: 'Network', + component: () => import('@/views/container/network/index.vue'), + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'container.network', + }, + }, + { + path: 'volume', + name: 'Volume', + component: () => import('@/views/container/volume/index.vue'), + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'container.volume', + }, + }, + { + path: 'repo', + name: 'Repo', + component: () => import('@/views/container/repo/index.vue'), + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'container.repo', + }, + }, + { + path: 'compose', + name: 'Compose', + component: () => import('@/views/container/compose/index.vue'), + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'container.compose', + }, + }, + { + path: 'template', + name: 'ComposeTemplate', + component: () => import('@/views/container/template/index.vue'), + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'container.composeTemplate', + }, + }, + { + path: 'setting', + name: 'ContainerSetting', + component: () => import('@/views/container/setting/index.vue'), + hidden: true, + meta: { + activeMenu: '/containers', + requiresAuth: false, + parent: 'menu.container', + title: 'container.setting', + }, + }, + ], + }, + ], +}; + +export default containerRouter; diff --git a/frontend/src/routers/modules/cronjob.ts b/frontend/src/routers/modules/cronjob.ts new file mode 100644 index 0000000..8c7ecdd --- /dev/null +++ b/frontend/src/routers/modules/cronjob.ts @@ -0,0 +1,59 @@ +import { Layout } from '@/routers/constant'; + +const cronRouter = { + sort: 9, + path: '/cronjobs', + name: 'Cronjob-Menu', + component: Layout, + redirect: '/cronjobs/cronjob', + meta: { + icon: 'p-plan', + title: 'menu.cronjob', + }, + children: [ + { + path: '/cronjobs', + name: 'Cronjob', + redirect: '/cronjobs/cronjob', + component: () => import('@/views/cronjob/index.vue'), + meta: {}, + children: [ + { + path: 'cronjob', + name: 'CronjobItem', + component: () => import('@/views/cronjob/cronjob/index.vue'), + hidden: true, + meta: { + activeMenu: '/cronjobs', + requiresAuth: false, + title: 'menu.cronjob', + }, + }, + { + path: 'cronjob/operate', + name: 'CronjobOperate', + component: () => import('@/views/cronjob/cronjob/operate/index.vue'), + hidden: true, + meta: { + activeMenu: '/cronjobs', + requiresAuth: false, + ignoreTab: true, + }, + }, + { + path: 'library', + name: 'Library', + component: () => import('@/views/cronjob/library/index.vue'), + hidden: true, + meta: { + activeMenu: '/cronjobs', + requiresAuth: false, + title: 'cronjob.library.library', + }, + }, + ], + }, + ], +}; + +export default cronRouter; diff --git a/frontend/src/routers/modules/database.ts b/frontend/src/routers/modules/database.ts new file mode 100644 index 0000000..85e91cd --- /dev/null +++ b/frontend/src/routers/modules/database.ts @@ -0,0 +1,125 @@ +import { Layout } from '@/routers/constant'; + +const databaseRouter = { + sort: 5, + path: '/databases', + name: 'Database-Menu', + component: Layout, + redirect: '/databases/mysql', + meta: { + icon: 'p-database', + title: 'menu.database', + }, + children: [ + { + path: '/databases', + name: 'Database', + redirect: '/databases/mysql', + component: () => import('@/views/database/index.vue'), + meta: {}, + children: [ + { + path: 'mysql', + name: 'MySQL', + component: () => import('@/views/database/mysql/index.vue'), + hidden: true, + meta: { + activeMenu: '/databases', + requiresAuth: false, + parent: 'menu.database', + title: 'MySQL', + }, + }, + { + path: 'mysql/setting/:type/:database', + name: 'MySQL-Setting', + component: () => import('@/views/database/mysql/setting/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/databases', + requiresAuth: false, + ignoreTab: true, + }, + }, + { + path: 'mysql/remote', + name: 'MySQL-Remote', + component: () => import('@/views/database/mysql/remote/index.vue'), + hidden: true, + meta: { + activeMenu: '/databases', + requiresAuth: false, + parent: 'menu.database', + title: 'MySQL', + detail: 'database.remote', + }, + }, + { + path: 'postgresql', + name: 'PostgreSQL', + component: () => import('@/views/database/postgresql/index.vue'), + hidden: true, + meta: { + activeMenu: '/databases', + requiresAuth: false, + parent: 'menu.database', + title: 'PostgreSQL', + }, + }, + { + path: 'postgresql/remote', + name: 'PostgreSQL-Remote', + component: () => import('@/views/database/postgresql/remote/index.vue'), + hidden: true, + meta: { + activeMenu: '/databases', + requiresAuth: false, + parent: 'menu.database', + title: 'PostgreSQL', + detail: 'database.remote', + }, + }, + { + path: 'postgresql/setting/:type/:database', + name: 'PostgreSQL-Setting', + component: () => import('@/views/database/postgresql/setting/index.vue'), + props: true, + hidden: true, + meta: { + activeMenu: '/databases', + requiresAuth: false, + ignoreTab: true, + }, + }, + { + path: 'redis', + name: 'Redis', + component: () => import('@/views/database/redis/index.vue'), + hidden: true, + meta: { + activeMenu: '/databases', + requiresAuth: false, + parent: 'menu.database', + title: 'Redis', + }, + }, + { + path: 'redis/remote', + name: 'Redis-Remote', + component: () => import('@/views/database/redis/remote/index.vue'), + hidden: true, + meta: { + activeMenu: '/databases', + requiresAuth: false, + parent: 'menu.database', + title: 'Redis', + detail: 'database.remote', + }, + }, + ], + }, + ], +}; + +export default databaseRouter; diff --git a/frontend/src/routers/modules/error.ts b/frontend/src/routers/modules/error.ts new file mode 100644 index 0000000..3fd4ff3 --- /dev/null +++ b/frontend/src/routers/modules/error.ts @@ -0,0 +1,20 @@ +import { Layout } from '@/routers/constant'; + +const errorRouter = { + path: '/error', + component: Layout, + children: [ + { + path: '404', + name: '404', + hidden: true, + component: () => import('@/components/error-message/404.vue'), + meta: { + requiresAuth: false, + title: '404页面', + key: '404', + }, + }, + ], +}; +export default errorRouter; diff --git a/frontend/src/routers/modules/host.ts b/frontend/src/routers/modules/host.ts new file mode 100644 index 0000000..a0996b6 --- /dev/null +++ b/frontend/src/routers/modules/host.ts @@ -0,0 +1,171 @@ +import { Layout } from '@/routers/constant'; + +const hostRouter = { + sort: 7, + path: '/hosts', + name: 'System-Menu', + component: Layout, + redirect: '/hosts/security', + meta: { + icon: 'p-host', + title: 'menu.system', + }, + children: [ + { + path: '/hosts/files', + name: 'File', + props: true, + component: () => import('@/views/host/file-management/index.vue'), + meta: { + icon: 'p-file-menu', + title: 'menu.files', + requiresAuth: false, + }, + }, + { + path: '/hosts/monitor/monitor', + name: 'Monitorx', + component: () => import('@/views/host/monitor/monitor/index.vue'), + meta: { + icon: 'p-system-monitor-menu', + title: 'menu.monitor', + requiresAuth: false, + }, + }, + { + path: '/hosts/monitor/setting', + name: 'HostMonitorSetting', + component: () => import('@/views/host/monitor/setting/index.vue'), + hidden: true, + meta: { + activeMenu: '/hosts/monitor/monitor', + title: 'menu.monitor', + detail: 'commons.button.set', + requiresAuth: false, + }, + }, + { + path: '/hosts/firewall/port', + name: 'FirewallPort', + component: () => import('@/views/host/firewall/port/index.vue'), + meta: { + icon: 'p-firewalld-menu', + activeMenu: '/hosts/firewall/port', + title: 'menu.firewall', + detail: 'firewall.portRule', + requiresAuth: false, + }, + }, + { + path: '/hosts/firewall/forward', + name: 'FirewallForward', + component: () => import('@/views/host/firewall/forward/index.vue'), + hidden: true, + meta: { + activeMenu: '/hosts/firewall/port', + parent: 'menu.firewall', + title: 'firewall.forwardRule', + requiresAuth: false, + }, + }, + { + path: '/hosts/firewall/ip', + name: 'FirewallIP', + component: () => import('@/views/host/firewall/ip/index.vue'), + hidden: true, + meta: { + activeMenu: '/hosts/firewall/port', + parent: 'menu.firewall', + title: 'firewall.ipRule', + requiresAuth: false, + }, + }, + { + path: '/hosts/firewall/advance', + name: 'FirewallAdvance', + component: () => import('@/views/host/firewall/advance/index.vue'), + hidden: true, + meta: { + activeMenu: '/hosts/firewall/port', + parent: 'menu.firewall', + title: 'firewall.advancedControl', + requiresAuth: false, + }, + }, + { + path: '/hosts/disk', + name: 'Disk', + props: true, + component: () => import('@/views/host/disk-management/disk/index.vue'), + meta: { + icon: 'p-disk-menu', + title: 'menu.disk', + requiresAuth: false, + }, + }, + { + path: '/hosts/process/process', + name: 'Process', + component: () => import('@/views/host/process/process/index.vue'), + meta: { + icon: 'p-process-menu', + title: 'menu.processManage', + detail: 'menu.process', + activeMenu: '/hosts/process/process', + keepAlive: true, + requiresAuth: false, + }, + }, + { + path: '/hosts/process/network', + name: 'ProcessNetwork', + hidden: true, + component: () => import('@/views/host/process/network/index.vue'), + meta: { + parent: 'menu.processManage', + title: 'menu.network', + activeMenu: '/hosts/process/process', + requiresAuth: false, + }, + }, + { + path: '/hosts/ssh/ssh', + name: 'SSH', + component: () => import('@/views/host/ssh/ssh/index.vue'), + meta: { + icon: 'p-ssh-menu', + title: 'menu.ssh', + detail: 'menu.config', + activeMenu: '/hosts/ssh/ssh', + keepAlive: true, + requiresAuth: false, + }, + }, + { + path: '/hosts/ssh/log', + name: 'SSHLog', + component: () => import('@/views/host/ssh/log/index.vue'), + hidden: true, + meta: { + parent: 'menu.ssh', + title: 'ssh.loginLogs', + activeMenu: '/hosts/ssh/ssh', + requiresAuth: false, + }, + }, + { + path: '/hosts/ssh/session', + name: 'SSHSession', + component: () => import('@/views/host/ssh/session/index.vue'), + hidden: true, + meta: { + parent: 'menu.ssh', + title: 'ssh.session', + activeMenu: '/hosts/ssh/ssh', + requiresAuth: false, + }, + }, + ], +}; + +export default hostRouter; diff --git a/frontend/src/routers/modules/log.ts b/frontend/src/routers/modules/log.ts new file mode 100644 index 0000000..d7112e7 --- /dev/null +++ b/frontend/src/routers/modules/log.ts @@ -0,0 +1,98 @@ +import { Layout } from '@/routers/constant'; + +const logsRouter = { + sort: 11, + path: '/logs', + name: 'Log-Menu', + component: Layout, + redirect: '/logs/operation', + meta: { + title: 'menu.logs', + icon: 'p-log', + }, + children: [ + { + path: '/logs', + name: 'Log', + redirect: '/logs/operation', + component: () => import('@/views/log/index.vue'), + meta: {}, + children: [ + { + path: 'operation', + name: 'OperationLog', + component: () => import('@/views/log/operation/index.vue'), + hidden: true, + meta: { + parent: 'menu.logs', + title: 'logs.operation', + activeMenu: '/logs', + requiresAuth: false, + }, + }, + { + path: 'login', + name: 'LoginLog', + component: () => import('@/views/log/login/index.vue'), + hidden: true, + meta: { + parent: 'menu.logs', + title: 'logs.login', + activeMenu: '/logs', + requiresAuth: false, + }, + }, + { + path: 'website', + name: 'WebsiteLog', + component: () => import('@/views/log/website/index.vue'), + hidden: true, + meta: { + parent: 'menu.logs', + title: 'logs.websiteLog', + activeMenu: '/logs', + requiresAuth: false, + }, + }, + { + path: 'system', + name: 'SystemLog', + component: () => import('@/views/log/system/index.vue'), + hidden: true, + meta: { + parent: 'menu.logs', + title: 'logs.system', + activeMenu: '/logs', + requiresAuth: false, + }, + }, + { + path: 'ssh', + name: 'SSHLog2', + component: () => import('@/views/host/ssh/log/log.vue'), + hidden: true, + meta: { + parent: 'menu.logs', + title: 'ssh.loginLogs', + activeMenu: '/logs', + requiresAuth: false, + }, + }, + { + path: 'task', + name: 'Task', + component: () => import('@/views/log/task/index.vue'), + hidden: true, + meta: { + parent: 'menu.logs', + title: 'logs.task', + activeMenu: '/logs', + requiresAuth: false, + }, + }, + ], + }, + ], +}; + +export default logsRouter; diff --git a/frontend/src/routers/modules/setting.ts b/frontend/src/routers/modules/setting.ts new file mode 100644 index 0000000..b2c5cf1 --- /dev/null +++ b/frontend/src/routers/modules/setting.ts @@ -0,0 +1,121 @@ +import { Layout } from '@/routers/constant'; + +const settingRouter = { + sort: 12, + path: '/settings', + name: 'Setting-Menu', + component: Layout, + redirect: '/settings/panel', + meta: { + title: 'menu.settings', + icon: 'p-config', + }, + children: [ + { + path: '/settings', + name: 'Setting', + redirect: '/settings/panel', + component: () => import('@/views/setting/index.vue'), + meta: {}, + children: [ + { + path: 'panel', + name: 'Panel', + component: () => import('@/views/setting/panel/index.vue'), + hidden: true, + meta: { + parent: 'menu.settings', + title: 'setting.panel', + requiresAuth: true, + activeMenu: '/settings', + }, + }, + { + path: 'alert', + name: 'Alert', + component: () => import('@/views/setting/alert/index.vue'), + hidden: true, + meta: { + parent: 'menu.settings', + title: 'xpack.alert.alertNotice', + requiresAuth: true, + activeMenu: '/settings', + }, + }, + { + path: 'backupaccount', + name: 'BackupAccount', + component: () => import('@/views/setting/backup-account/index.vue'), + hidden: true, + meta: { + parent: 'menu.settings', + title: 'setting.backupAccount', + requiresAuth: true, + activeMenu: '/settings', + }, + }, + { + path: 'license', + name: 'License', + component: () => import('@/views/setting/license/index.vue'), + hidden: true, + meta: { + parent: 'menu.settings', + title: 'setting.license', + requiresAuth: true, + activeMenu: '/settings', + }, + }, + { + path: 'about', + name: 'About', + component: () => import('@/views/setting/about/index.vue'), + hidden: true, + meta: { + parent: 'menu.settings', + title: 'setting.about', + requiresAuth: true, + activeMenu: '/settings', + }, + }, + { + path: 'safe', + name: 'Safe', + component: () => import('@/views/setting/safe/index.vue'), + hidden: true, + meta: { + parent: 'menu.settings', + title: 'setting.safe', + requiresAuth: true, + activeMenu: '/settings', + }, + }, + { + path: 'snapshot', + name: 'Snapshot', + hidden: true, + component: () => import('@/views/setting/snapshot/index.vue'), + meta: { + parent: 'menu.settings', + title: 'setting.snapshot', + requiresAuth: true, + activeMenu: '/settings', + }, + }, + { + path: 'expired', + name: 'Expired', + hidden: true, + component: () => import('@/views/setting/expired.vue'), + meta: { + requiresAuth: true, + activeMenu: 'Expired', + ignoreTab: true, + }, + }, + ], + }, + ], +}; + +export default settingRouter; diff --git a/frontend/src/routers/modules/terminal.ts b/frontend/src/routers/modules/terminal.ts new file mode 100644 index 0000000..d5214c8 --- /dev/null +++ b/frontend/src/routers/modules/terminal.ts @@ -0,0 +1,27 @@ +import { Layout } from '@/routers/constant'; + +const terminalRouter = { + sort: 8, + path: '/terminal', + name: 'Terminal-Menu', + component: Layout, + redirect: '/terminal', + meta: { + title: 'menu.terminal', + icon: 'p-terminal2', + }, + children: [ + { + path: '/terminal', + name: 'Terminal', + props: true, + component: () => import('@/views/terminal/index.vue'), + meta: { + keepAlive: true, + requiresAuth: false, + }, + }, + ], +}; + +export default terminalRouter; diff --git a/frontend/src/routers/modules/toolbox.ts b/frontend/src/routers/modules/toolbox.ts new file mode 100644 index 0000000..06a047e --- /dev/null +++ b/frontend/src/routers/modules/toolbox.ts @@ -0,0 +1,109 @@ +import { Layout } from '@/routers/constant'; + +const toolboxRouter = { + sort: 9, + path: '/toolbox', + name: 'Toolbox-Menu', + component: Layout, + redirect: '/toolbox/supervisor', + meta: { + title: 'menu.toolbox', + icon: 'p-toolbox', + }, + children: [ + { + path: '/toolbox', + name: 'Toolbox', + redirect: '/toolbox/device', + component: () => import('@/views/toolbox/index.vue'), + meta: {}, + children: [ + { + path: 'device', + name: 'Device', + component: () => import('@/views/toolbox/device/index.vue'), + hidden: true, + meta: { + parent: 'menu.toolbox', + title: 'toolbox.device.toolbox', + activeMenu: '/toolbox', + requiresAuth: false, + }, + }, + { + path: 'supervisor', + name: 'Supervisor', + component: () => import('@/views/toolbox/supervisor/index.vue'), + hidden: true, + meta: { + parent: 'menu.toolbox', + title: 'menu.supervisor', + activeMenu: '/toolbox', + requiresAuth: false, + }, + }, + { + path: 'clam', + name: 'Clam', + component: () => import('@/views/toolbox/clam/index.vue'), + hidden: true, + meta: { + parent: 'menu.toolbox', + title: 'toolbox.clam.clam', + activeMenu: '/toolbox', + requiresAuth: false, + }, + }, + { + path: 'clam/setting', + name: 'Clam-Setting', + component: () => import('@/views/toolbox/clam/setting/index.vue'), + hidden: true, + meta: { + ignoreTab: true, + activeMenu: '/toolbox', + requiresAuth: false, + }, + }, + { + path: 'ftp', + name: 'FTP', + component: () => import('@/views/toolbox/ftp/index.vue'), + hidden: true, + meta: { + parent: 'menu.toolbox', + title: 'FTP', + activeMenu: '/toolbox', + requiresAuth: false, + }, + }, + { + path: 'fail2ban', + name: 'Fail2ban', + component: () => import('@/views/toolbox/fail2ban/index.vue'), + hidden: true, + meta: { + parent: 'menu.toolbox', + title: 'Fail2Ban', + activeMenu: '/toolbox', + requiresAuth: false, + }, + }, + { + path: 'clean', + name: 'Clean', + component: () => import('@/views/toolbox/clean/index.vue'), + hidden: true, + meta: { + parent: 'menu.toolbox', + title: 'setting.diskClean', + activeMenu: '/toolbox', + requiresAuth: false, + }, + }, + ], + }, + ], +}; + +export default toolboxRouter; diff --git a/frontend/src/routers/modules/website.ts b/frontend/src/routers/modules/website.ts new file mode 100644 index 0000000..5b50dcd --- /dev/null +++ b/frontend/src/routers/modules/website.ts @@ -0,0 +1,121 @@ +import { Layout } from '@/routers/constant'; + +const webSiteRouter = { + sort: 3, + path: '/websites', + name: 'Website-Menu', + component: Layout, + redirect: '/websites', + meta: { + icon: 'p-website', + title: 'menu.website', + }, + children: [ + { + path: '/websites', + name: 'Website', + component: () => import('@/views/website/website/index.vue'), + meta: { + icon: 'p-website', + title: 'menu.website', + requiresAuth: false, + }, + }, + { + path: '/websites/:id/config/:tab', + name: 'WebsiteConfig', + component: () => import('@/views/website/website/config/index.vue'), + hidden: true, + props: true, + meta: { + activeMenu: '/websites', + requiresAuth: false, + ignoreTab: true, + }, + }, + { + path: '/websites/ssl', + name: 'SSL', + component: () => import('@/views/website/ssl/index.vue'), + meta: { + icon: 'p-ssl-menu', + title: 'menu.ssl', + requiresAuth: false, + }, + }, + { + path: '/websites/runtimes/php', + name: 'PHP', + component: () => import('@/views/website/runtime/php/index.vue'), + meta: { + icon: 'p-run-menu', + title: 'menu.runtime', + detail: 'PHP', + activeMenu: '/websites/runtimes/php', + requiresAuth: false, + }, + }, + { + path: '/websites/runtimes/node', + name: 'node', + hidden: true, + component: () => import('@/views/website/runtime/node/index.vue'), + meta: { + title: 'menu.runtime', + detail: 'Node', + activeMenu: '/websites/runtimes/php', + requiresAuth: false, + }, + }, + { + path: '/websites/runtimes/java', + name: 'java', + hidden: true, + component: () => import('@/views/website/runtime/java/index.vue'), + meta: { + title: 'menu.runtime', + detail: 'Java', + activeMenu: '/websites/runtimes/php', + requiresAuth: false, + }, + }, + { + path: '/websites/runtimes/go', + name: 'go', + hidden: true, + component: () => import('@/views/website/runtime/go/index.vue'), + meta: { + title: 'menu.runtime', + detail: 'Go', + activeMenu: '/websites/runtimes/php', + requiresAuth: false, + }, + }, + { + path: '/websites/runtimes/python', + name: 'python', + hidden: true, + component: () => import('@/views/website/runtime/python/index.vue'), + meta: { + title: 'menu.runtime', + detail: 'Python', + activeMenu: '/websites/runtimes/php', + requiresAuth: false, + }, + }, + { + path: '/websites/runtimes/dotnet', + name: 'dotNet', + hidden: true, + component: () => import('@/views/website/runtime/dotnet/index.vue'), + meta: { + title: 'menu.runtime', + detail: '.Net', + activeMenu: '/websites/runtimes/php', + requiresAuth: false, + }, + }, + ], +}; + +export default webSiteRouter; diff --git a/frontend/src/routers/router.ts b/frontend/src/routers/router.ts new file mode 100644 index 0000000..7ebde9a --- /dev/null +++ b/frontend/src/routers/router.ts @@ -0,0 +1,95 @@ +import { createRouter, createWebHistory, RouteRecordRaw } from 'vue-router'; +import { Layout } from '@/routers/constant'; + +let modules: Record = import.meta.glob('./modules/*.ts', { eager: true }); +const xpackModules: Record = import.meta.glob('../xpack/routers/*.ts', { eager: true }); +modules = { ...modules, ...xpackModules }; + +const homeRouter: RouteRecordRaw = { + path: '/', + name: 'Home-Menu', + component: Layout, + redirect: '/', + meta: { + keepAlive: true, + title: 'menu.home', + icon: 'p-home', + }, + children: [ + { + path: '/', + name: 'home', + component: () => import('@/views/home/index.vue'), + meta: { + requiresAuth: true, + }, + }, + ], +}; + +export const routerArray: RouteRecordRaw[] = []; + +export const rolesRoutes = [ + ...Object.keys(modules) + .map((key) => modules[key]['default']) + .sort((r1, r2) => { + r1.sort ??= Number.MAX_VALUE; + r2.sort ??= Number.MAX_VALUE; + return r1.sort - r2.sort; + }), +]; + +rolesRoutes.forEach((item) => { + const menu = item as RouteRecordRaw; + routerArray.push(menu); +}); + +export const menuList: RouteRecordRaw[] = []; +rolesRoutes.forEach((item) => { + let menuItem = JSON.parse(JSON.stringify(item)); + let menuChildren: RouteRecordRaw[] = []; + if (menuItem.children == undefined) { + return; + } + menuItem.children.forEach((child: any) => { + if (child.hidden == undefined || child.hidden == false) { + menuChildren.push(child); + } + }); + menuItem.children = menuChildren as RouteRecordRaw[]; + menuList.push(menuItem); +}); +menuList.unshift(homeRouter); + +export const routes: RouteRecordRaw[] = [ + homeRouter, + { + path: '/login', + name: 'login', + props: true, + component: () => import('@/views/login/index.vue'), + meta: { + requiresAuth: false, + key: 'login', + }, + }, + { + path: '/:code?', + name: 'entrance', + component: () => import('@/views/login/index.vue'), + props: true, + }, + ...routerArray, + { + path: '/:pathMatch(.*)', + redirect: { name: '404' }, + }, +]; +const router = createRouter({ + history: createWebHistory('/'), + routes: routes as RouteRecordRaw[], + strict: false, + scrollBehavior: () => ({ left: 0, top: 0 }), +}); + +export default router; diff --git a/frontend/src/store/index.ts b/frontend/src/store/index.ts new file mode 100644 index 0000000..fc4671e --- /dev/null +++ b/frontend/src/store/index.ts @@ -0,0 +1,14 @@ +import { createPinia } from 'pinia'; +import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'; +import GlobalStore from './modules/global'; +import MenuStore from './modules/menu'; +import TabsStore from './modules/tabs'; +import TerminalStore from './modules/terminal'; +import ProcessStore from './modules/process'; + +const pinia = createPinia(); +pinia.use(piniaPluginPersistedstate); + +export { GlobalStore, MenuStore, TabsStore, TerminalStore, ProcessStore }; + +export default pinia; diff --git a/frontend/src/store/interface/index.ts b/frontend/src/store/interface/index.ts new file mode 100644 index 0000000..09e670d --- /dev/null +++ b/frontend/src/store/interface/index.ts @@ -0,0 +1,79 @@ +import { RouteRecordRaw } from 'vue-router'; +import { DeviceType } from '@/enums/app'; +export interface ThemeConfigProp { + panelName: string; + primary: string; + theme: string; // dark | bright | auto + footer: boolean; + + title: string; + logo: string; + logoWithText: string; + favicon: string; + loginImage: string; + loginBgType: string; + loginBackground: string; + loginBtnLinkColor: string; + themeColor: string; +} + +export interface Watermark { + lightColor: string; + darkColor: string; + fontSize: number; + content: string; + rotate: number; + gap: number; +} + +export interface GlobalState { + isLoading: boolean; + loadingText: string; + isLogin: boolean; + entrance: string; + language: string; // zh | en | tw + themeConfig: ThemeConfigProp; + isFullScreen: boolean; + openMenuTabs: boolean; + watermark: Watermark; + watermarkShow: boolean; + isOnRestart: boolean; + agreeLicense: boolean; + hasNewVersion: boolean; + ignoreCaptcha: boolean; + device: DeviceType; + lastFilePath: string; + currentDB: string; + currentPgDB: string; + currentRedisDB: string; + showEntranceWarn: boolean; + defaultNetwork: string; + defaultIO: string; + isFxplay: boolean; + + isProductPro: boolean; + isIntl: boolean; + productProExpires: number; + isMasterProductPro: boolean; + isOffLine: boolean; + + masterAlias: string; + currentNode: string; + currentNodeAddr: string; +} + +export interface MenuState { + isCollapse: boolean; + menuList: RouteRecordRaw[]; + withoutAnimation: boolean; +} + +export interface TerminalState { + lineHeight: number; + letterSpacing: number; + fontSize: number; + cursorBlink: string; + cursorStyle: string; + scrollback: number; + scrollSensitivity: number; +} diff --git a/frontend/src/store/modules/global.ts b/frontend/src/store/modules/global.ts new file mode 100644 index 0000000..53eb2e2 --- /dev/null +++ b/frontend/src/store/modules/global.ts @@ -0,0 +1,133 @@ +import { defineStore } from 'pinia'; +import piniaPersistConfig from '@/config/pinia-persist'; +import { GlobalState, ThemeConfigProp } from '../interface'; +import { DeviceType } from '@/enums/app'; +import i18n, { setActiveLocale } from '@/lang'; + +const GlobalStore = defineStore({ + id: 'GlobalState', + state: (): GlobalState => ({ + isLoading: false, + loadingText: '', + isLogin: false, + entrance: '', + language: '', + themeConfig: { + panelName: '', + primary: '#005eeb', + theme: 'auto', + footer: true, + themeColor: '', + title: '', + logo: '', + logoWithText: '', + favicon: '', + loginImage: '', + loginBackground: '', + loginBgType: '', + loginBtnLinkColor: '', + }, + watermark: null, + watermarkShow: false, + openMenuTabs: false, + isFullScreen: false, + isOnRestart: false, + agreeLicense: false, + hasNewVersion: false, + ignoreCaptcha: true, + device: DeviceType.Desktop, + lastFilePath: '', + currentDB: '', + currentPgDB: '', + currentRedisDB: '', + showEntranceWarn: true, + defaultNetwork: 'all', + defaultIO: 'all', + isFxplay: false, + + isProductPro: false, + isIntl: false, + productProExpires: 0, + isMasterProductPro: false, + isOffLine: false, + + masterAlias: '', + currentNode: 'local', + currentNodeAddr: '', + }), + getters: { + isDarkTheme: (state) => + state.themeConfig.theme === 'dark' || + (state.themeConfig.theme === 'auto' && window.matchMedia('(prefers-color-scheme: dark)').matches), + isDarkGoldTheme: (state) => state.themeConfig.primary === '#F0BE96' && state.isProductPro, + docsUrl: (state) => (state.isIntl ? 'https://docs.1panel.hk' : 'https://1panel.cn/docs/v2'), + isMaster: (state) => state.currentNode === 'local', + }, + actions: { + setOpenMenuTabs(openMenuTabs: boolean) { + this.openMenuTabs = openMenuTabs; + }, + setScreenFull() { + this.isFullScreen = !this.isFullScreen; + }, + setLogStatus(login: boolean) { + this.isLogin = login; + }, + setGlobalLoading(loading: boolean) { + this.isLoading = loading; + }, + setLoadingText(text: string) { + this.loadingText = i18n.global.t('commons.loadingText.' + text); + }, + setCsrfToken(token: string) { + this.csrfToken = token; + }, + async updateLanguage(language: string) { + const activeLocale = await setActiveLocale(language); + this.language = activeLocale; + return activeLocale; + }, + setThemeConfig(themeConfig: ThemeConfigProp) { + this.themeConfig = themeConfig; + }, + setAgreeLicense(agree: boolean) { + this.agreeLicense = agree; + }, + toggleDevice(value: DeviceType) { + this.device = value; + }, + isMobile() { + return this.device === DeviceType.Mobile; + }, + getMasterAlias() { + return this.masterAlias || i18n.global.t('xpack.node.master'); + }, + isMasterPro() { + return this.isMasterProductPro; + }, + setLastFilePath(path: string) { + this.lastFilePath = path; + }, + setCurrentDB(name: string) { + this.currentDB = name; + }, + setCurrentPgDB(name: string) { + this.currentPgDB = name; + }, + setCurrentRedisDB(name: string) { + this.currentRedisDB = name; + }, + setShowEntranceWarn(show: boolean) { + this.showEntranceWarn = show; + }, + setDefaultNetwork(net: string) { + this.defaultNetwork = net; + }, + setDefaultIO(net: string) { + this.defaultIO = net; + }, + }, + persist: piniaPersistConfig('GlobalState'), +}); + +export default GlobalStore; diff --git a/frontend/src/store/modules/menu.ts b/frontend/src/store/modules/menu.ts new file mode 100644 index 0000000..564010c --- /dev/null +++ b/frontend/src/store/modules/menu.ts @@ -0,0 +1,34 @@ +import { defineStore } from 'pinia'; +import { MenuState } from '../interface'; +import piniaPersistConfig from '@/config/pinia-persist'; +import { RouteRecordRaw } from 'vue-router'; +const whiteList = ['/login', '/error']; + +export const MenuStore = defineStore({ + id: 'MenuState', + state: (): MenuState => ({ + isCollapse: false, + menuList: [], + withoutAnimation: false, + }), + getters: {}, + actions: { + async setCollapse() { + this.isCollapse = !this.isCollapse; + this.withoutAnimation = false; + }, + async setMenuList(menuList: RouteRecordRaw[]) { + const menus = menuList.filter((item) => { + return whiteList.indexOf(item.path) < 0; + }); + this.menuList = menus; + }, + closeSidebar(withoutAnimation: boolean) { + this.isCollapse = true; + this.withoutAnimation = withoutAnimation; + }, + }, + persist: piniaPersistConfig('MenuStore'), +}); + +export default MenuStore; diff --git a/frontend/src/store/modules/process.ts b/frontend/src/store/modules/process.ts new file mode 100644 index 0000000..c32ed89 --- /dev/null +++ b/frontend/src/store/modules/process.ts @@ -0,0 +1,320 @@ +import { defineStore } from 'pinia'; +import { ref, reactive } from 'vue'; + +export interface PsSearch { + type: 'ps'; + pid: number | undefined; + username: string; + name: string; +} + +export interface NetSearch { + type: 'net'; + processID: number | undefined; + processName: string; + port: number | undefined; +} + +export const ProcessStore = defineStore('ProcessStore', () => { + let websocket: WebSocket | null = null; + let pollingTimer: ReturnType | null = null; + let disconnectTimer: ReturnType | null = null; + + let connectionRefCount = 0; + + const isConnected = ref(false); + const isConnecting = ref(false); + + const psData = ref([]); + const psLoading = ref(false); + const psSearch = reactive({ + type: 'ps', + pid: undefined, + username: '', + name: '', + }); + + const netData = ref([]); + const netLoading = ref(false); + const netSearch = reactive({ + type: 'net', + processID: undefined, + processName: '', + port: undefined, + }); + + let pendingRequestType: 'ps' | 'net' | null = null; + + let queuedRequestType: 'ps' | 'net' | null = null; + + const isPsFetching = ref(false); + const isNetFetching = ref(false); + + const activePollingType = ref<'ps' | 'net' | null>(null); + + const isWsOpen = () => { + return websocket && websocket.readyState === WebSocket.OPEN; + }; + + const onOpen = () => { + isConnected.value = true; + isConnecting.value = false; + }; + + const doSendMessage = (type: 'ps' | 'net') => { + pendingRequestType = type; + + if (type === 'ps') { + isPsFetching.value = true; + psLoading.value = psData.value.length === 0; + + const searchParams = { ...psSearch }; + if (typeof searchParams.pid === 'string') { + searchParams.pid = Number(searchParams.pid); + } + websocket!.send(JSON.stringify(searchParams)); + } else { + isNetFetching.value = true; + netLoading.value = netData.value.length === 0; + + const searchParams = { ...netSearch }; + if (typeof searchParams.processID === 'string') { + searchParams.processID = Number(searchParams.processID); + } + if (typeof searchParams.port === 'string') { + searchParams.port = Number(searchParams.port); + } + websocket!.send(JSON.stringify(searchParams)); + } + }; + + const onMessage = (event: MessageEvent) => { + try { + const data = JSON.parse(event.data); + const responseType = pendingRequestType; + + if (pendingRequestType === 'ps') { + isPsFetching.value = false; + } else if (pendingRequestType === 'net') { + isNetFetching.value = false; + } + pendingRequestType = null; + + if (responseType === activePollingType.value) { + if (responseType === 'ps') { + psData.value = data || []; + psLoading.value = false; + } else if (responseType === 'net') { + netData.value = data || []; + netLoading.value = false; + } + } + + if (queuedRequestType && isWsOpen()) { + const typeToSend = queuedRequestType; + queuedRequestType = null; + doSendMessage(typeToSend); + } + } catch (e) { + console.error('Failed to parse WebSocket message:', e); + } + }; + + const onError = () => { + console.error('WebSocket error'); + }; + + const onClose = () => { + isConnected.value = false; + isConnecting.value = false; + websocket = null; + }; + + const initWebSocket = (currentNode: string) => { + if (websocket || isConnecting.value) { + return; + } + + isConnecting.value = true; + + const href = window.location.href; + const protocol = href.split('//')[0] === 'http:' ? 'ws' : 'wss'; + const ipLocal = href.split('//')[1].split('/')[0]; + + websocket = new WebSocket(`${protocol}://${ipLocal}/api/v2/process/ws?operateNode=${currentNode}`); + websocket.onopen = onOpen; + websocket.onmessage = onMessage; + websocket.onerror = onError; + websocket.onclose = onClose; + }; + + const closeWebSocket = () => { + stopPolling(); + + if (websocket) { + websocket.close(); + websocket = null; + } + + isConnected.value = false; + isConnecting.value = false; + }; + + const connect = (currentNode: string) => { + if (disconnectTimer) { + clearTimeout(disconnectTimer); + disconnectTimer = null; + } + + connectionRefCount++; + + if (!websocket && !isConnecting.value) { + initWebSocket(currentNode); + } + }; + + const disconnect = () => { + connectionRefCount = Math.max(0, connectionRefCount - 1); + + if (connectionRefCount === 0) { + disconnectTimer = setTimeout(() => { + if (connectionRefCount === 0) { + closeWebSocket(); + } + }, 500); + } + }; + + const sendPsMessage = () => { + if (!isWsOpen()) { + return; + } + + if (pendingRequestType !== null) { + queuedRequestType = 'ps'; + return; + } + + if (isPsFetching.value) { + return; + } + + doSendMessage('ps'); + }; + + const sendNetMessage = () => { + if (!isWsOpen()) { + return; + } + + if (pendingRequestType !== null) { + queuedRequestType = 'net'; + return; + } + + if (isNetFetching.value) { + return; + } + + doSendMessage('net'); + }; + + const startPolling = (type: 'ps' | 'net', interval = 3000, initialDelay = 0) => { + stopPolling(); + activePollingType.value = type; + + const sendInitial = () => { + if (type === 'ps') { + sendPsMessage(); + } else { + sendNetMessage(); + } + }; + + const scheduleInitialFetch = () => { + if (initialDelay > 0) { + setTimeout(sendInitial, initialDelay); + } else { + sendInitial(); + } + }; + + if (isWsOpen()) { + scheduleInitialFetch(); + } else { + const checkConnection = setInterval(() => { + if (isWsOpen()) { + clearInterval(checkConnection); + scheduleInitialFetch(); + } + }, 100); + setTimeout(() => clearInterval(checkConnection), 5000); + } + + pollingTimer = setInterval(() => { + if (type === 'ps') { + sendPsMessage(); + } else { + sendNetMessage(); + } + }, interval); + }; + + const stopPolling = () => { + if (pollingTimer) { + clearInterval(pollingTimer); + pollingTimer = null; + } + activePollingType.value = null; + }; + + const updatePsSearch = (params: Partial>) => { + Object.assign(psSearch, params); + }; + + const updateNetSearch = (params: Partial>) => { + Object.assign(netSearch, params); + }; + + const resetPsSearch = () => { + psSearch.pid = undefined; + psSearch.username = ''; + psSearch.name = ''; + }; + + const resetNetSearch = () => { + netSearch.processID = undefined; + netSearch.processName = ''; + netSearch.port = undefined; + }; + + return { + isConnected, + isConnecting, + psData, + psLoading, + psSearch, + netData, + netLoading, + netSearch, + isPsFetching, + isNetFetching, + activePollingType, + + isWsOpen, + connect, + disconnect, + initWebSocket, + closeWebSocket, + sendPsMessage, + sendNetMessage, + startPolling, + stopPolling, + updatePsSearch, + updateNetSearch, + resetPsSearch, + resetNetSearch, + }; +}); + +export default ProcessStore; diff --git a/frontend/src/store/modules/tabs.ts b/frontend/src/store/modules/tabs.ts new file mode 100644 index 0000000..0408500 --- /dev/null +++ b/frontend/src/store/modules/tabs.ts @@ -0,0 +1,142 @@ +import { ref } from 'vue'; +import { defineStore } from 'pinia'; + +const TabsStore = defineStore( + 'TabsStore', + () => { + const isShowTabIcon = ref(true); + // 缓存的KEY,直接给keepalive使用 + const cachedTabs = ref([]); + const openedTabs = ref([]); + const activeTabPath = ref(''); + + const getActivePath = (path) => { + let firstSlashIndex = path.indexOf('/'); + let lastSlashIndex = path.lastIndexOf('/'); + if (firstSlashIndex === -1 || firstSlashIndex === lastSlashIndex) { + return path; + } + return path.substring(firstSlashIndex, lastSlashIndex); + }; + + const getTabIdxByPath = (path) => { + return openedTabs.value.findIndex((v) => v.path === path); + }; + + const removeAllTabs = () => { + openedTabs.value = []; + cachedTabs.value = []; + }; + + const removeUnActiveTabs = () => { + if (openedTabs.value.length) { + let idx = getTabIdxByPath(activeTabPath.value); + idx = idx > -1 ? idx : 0; + const tab = openedTabs.value[idx]; + removeOtherTabs(tab); + } + }; + + const findTab = (path) => { + const idx = getTabIdxByPath(path); + if (idx > -1) { + return openedTabs.value[idx]; + } + }; + + const addTab = (tab) => { + const idx = getTabIdxByPath(tab.path); + if (idx < 0) { + openedTabs.value.push(Object.assign({}, tab)); + addCachedTab(tab.name); + } + }; + + const removeTab = (path) => { + if (openedTabs.value.length > 1) { + const idx = getTabIdxByPath(path); + if (idx > -1) { + removeCachedTab(openedTabs.value[idx].name); + openedTabs.value.splice(idx, 1); + } + return openedTabs.value[openedTabs.value.length - 1].path; + } + }; + + const removeOtherTabs = (path) => { + const idx = getTabIdxByPath(path); + if (idx > -1) { + const tab = openedTabs.value[idx]; + openedTabs.value = [tab]; + cachedTabs.value = []; + cachedTabs.value = [tab.name]; + } + }; + + const removeTabs = (path, type) => { + if (path) { + const idx = getTabIdxByPath(path); + let removeTabs = []; + if (type === 'right') { + removeTabs = openedTabs.value.splice(idx + 1); + } else if (type === 'left') { + removeTabs = openedTabs.value.splice(0, idx); + } + if (removeTabs.length) { + removeTabs.forEach((e) => removeCachedTab(e.name)); + } + } + }; + + const addCachedTab = (name) => { + if (name && !cachedTabs.value.includes(name)) { + cachedTabs.value.push(name); + } + }; + + const removeCachedTab = (name) => { + if (name) { + const idx = cachedTabs.value.findIndex((v) => v === name); + if (idx > -1) { + cachedTabs.value.splice(idx, 1); + } + } + }; + + const hasCloseDropdown = (path, type) => { + const idx = getTabIdxByPath(path); + switch (type) { + case 'close': + case 'other': + return openedTabs.value.length > 1; + case 'left': + return idx !== 0; + case 'right': + return idx !== openedTabs.value.length - 1; + } + }; + + return { + isShowTabIcon, + activeTabPath, + openedTabs, + cachedTabs, + addTab, + findTab, + addCachedTab, + removeCachedTab, + removeTab, + removeTabs, + removeOtherTabs, + removeAllTabs, + removeUnActiveTabs, + hasCloseDropdown, + getActivePath, + }; + }, + { + persist: true, + }, +); + +export default TabsStore; diff --git a/frontend/src/store/modules/terminal.ts b/frontend/src/store/modules/terminal.ts new file mode 100644 index 0000000..885a63c --- /dev/null +++ b/frontend/src/store/modules/terminal.ts @@ -0,0 +1,42 @@ +import { defineStore } from 'pinia'; +import piniaPersistConfig from '@/config/pinia-persist'; +import { TerminalState } from '../interface'; + +export const TerminalStore = defineStore({ + id: 'TerminalState', + state: (): TerminalState => ({ + lineHeight: 1.2, + letterSpacing: 1.2, + fontSize: 12, + cursorBlink: 'enable', + cursorStyle: 'underline', + scrollback: 1000, + scrollSensitivity: 10, + }), + actions: { + setLineHeight(lineHeight: number) { + this.lineHeight = lineHeight; + }, + setLetterSpacing(letterSpacing: number) { + this.letterSpacing = letterSpacing; + }, + setFontSize(fontSize: number) { + this.fontSize = fontSize; + }, + setCursorBlink(cursorBlink: string) { + this.cursorBlink = cursorBlink; + }, + setCursorStyle(cursorStyle: string) { + this.cursorStyle = cursorStyle; + }, + setScrollback(scrollback: number) { + this.scrollback = scrollback; + }, + setScrollSensitivity(scrollSensitivity: number) { + this.scrollSensitivity = scrollSensitivity; + }, + }, + persist: piniaPersistConfig('TerminalState'), +}); + +export default TerminalStore; diff --git a/frontend/src/styles/common.scss b/frontend/src/styles/common.scss new file mode 100644 index 0000000..c6ff129 --- /dev/null +++ b/frontend/src/styles/common.scss @@ -0,0 +1,392 @@ +html { + font-family: Helvetica Neue, Helvetica, PingFang SC, Hiragino Sans GB, Arial, sans-serif; +} +.flx-center { + display: flex; + align-items: center; + justify-content: center; +} + +.flx-align-center { + display: flex; + align-items: center; +} + +.sle { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.fade-transform-leave-active, +.fade-transform-enter-active { + transition: all 0.2s; +} +.fade-transform-enter-from { + opacity: 0; + transition: all 0.2s; + transform: translateX(-30px); +} +.fade-transform-leave-to { + opacity: 0; + transition: all 0.2s; + transform: translateX(30px); +} + +/* Breadcrumb */ +.breadcrumb-enter-active, +.breadcrumb-leave-active { + transition: all 0.2s ease; +} +.breadcrumb-enter-from, +.breadcrumb-leave-active { + opacity: 0; + transform: translateX(10px); +} +.breadcrumb-leave-active { + position: absolute; + z-index: -1; +} + +::-webkit-scrollbar { + width: 8px; + height: 8px; + background-color: white; +} +::-webkit-scrollbar-thumb { + background-color: #dddee0; + border-radius: 20px; + box-shadow: inset 0 0 0 white; +} + +#nprogress { + .bar { + background: $primary-color !important; + } + .spinner-icon { + border-top-color: $primary-color !important; + border-left-color: $primary-color !important; + } + .peg { + box-shadow: 0 0 10px $primary-color, 0 0 5px $primary-color !important; + } +} + +.form-button { + float: right; +} + +.input-help { + font-size: 12px; + word-break: keep-all; + color: #adb0bc; + width: 100%; + display: inline-block; + white-space: pre-line; +} + +.input-error { + font-size: 12px; + word-break: keep-all; + color: red; + transform: scale(0.9); + transform-origin: left; + width: 110%; + display: inline-block; + white-space: normal; +} + +.mask { + width: 100%; + height: 100%; + opacity: 0.4; + top: 0; + left: 0; + pointer-events: none; +} + +.mask-prompt { + position: absolute; + z-index: 1; + top: 220px; + left: 45%; + transform: translate(-50%, -50%); + width: 400px; + text-align: center; + font-size: 14px; + .bt { + margin-top: -2px; + } +} + +.sidebar-container-popper { + .el-menu--popup-right-start { + background-color: rgba(0, 94, 235, 0.1); + } +} + +.search-button { + width: 200px; +} + +.drawer-header-button { + span { + color: currentColor !important; + font-size: var(--el-font-size-base) !important; + } + .active-button { + color: var(--el-button-hover-text-color); + border-color: var(--el-button-hover-border-color); + } +} + +.app-status { + font-size: 12px; + + .el-card { + --el-card-padding: 9px; + + .buttons { + margin-left: 100px; + } + + .status-content { + margin-left: 50px; + } + } +} + +.mini-border-card { + width: 100%; + .el-card__body { + --el-card-padding: 12px 12px 0 22px; + } +} + +.xterm-viewport::-webkit-scrollbar { + width: 8px; + height: 8px; + background-color: #000000; +} + +.status-count { + font-size: 24px; +} +.status-label { + font-size: 14px; + color: #646a73; +} + +.table-link { + color: $primary-color; + cursor: pointer; +} + +.table-link:hover { + opacity: 0.6; +} + +.app-warn { + text-align: center; + margin-top: 100px; + span:first-child { + color: #bbbfc4; + } + + span:nth-child(2) { + color: $primary-color; + cursor: pointer; + } + + span:nth-child(2):hover { + color: var(--el-color-primary-light-7); + } + + img { + width: 300px; + height: 300px; + } +} + +.common-prompt { + margin-bottom: 20px !important; +} + +.mini-form-item { + width: 40% !important; +} + +.pre-select { + width: 85px !important; +} + +.el-input-group__append { + border-left: 0; + background-color: var(--el-fill-color-light) !important; + border-top-left-radius: 0; + border-bottom-left-radius: 0; + box-shadow: 0 1px 0 0 var(--el-input-border-color) inset, 0 -1px 0 0 var(--el-input-border-color) inset, + -1px 0 0 0 var(--el-input-border-color) inset; + + &:hover { + color: var(--el-color-primary); + background-color: var(--el-color-primary-light-9) !important; + } +} + +.tag-button { + margin-right: 10px; + &.no-active { + background: none; + border: none; + } +} + +.limit-height-popover { + max-height: 300px; + overflow: auto; +} + +.router-button { + margin-right: 20px; +} + +.text-ellipsis { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.star-center { + height: 16px; + display: inline-block; + vertical-align: middle; +} + +.p-ml-5 { + margin-left: 5px !important; +} + +.p-w-200 { + width: 200px !important; +} + +.card-interval { + margin-top: 7px !important; +} + +.p-w-300 { + width: 300px !important; +} + +.p-w-100 { + width: 100px !important; +} + +.p-w-150 { + width: 150px !important; +} + +.p-w-400 { + width: 400px !important; +} + +.p-ml-20 { + margin-left: 20px !important; +} + +.p-mt-20 { + margin-top: 20px !important; +} + +.el-tag { + cursor: pointer; +} + +.dialog-footer { + display: flex; + align-items: center; + justify-content: flex-end; +} + +.monaco-editor-tree-light .el-tree-node__content:hover { + background-color: #e5eefd; +} + +.monaco-editor-tree-light .el-tree-node.is-current > .el-tree-node__content { + background-color: #e5eefd; +} + +.monaco-editor-tree-dark .el-tree-node__content:hover { + background-color: #111417; +} + +.monaco-editor-tree-dark .el-tree-node.is-current > .el-tree-node__content { + background-color: #111417; +} + +.check-label { + background: var(--panel-main-bg-color-10) !important; + .check-label-a { + color: var(--panel-color-primary); + } +} +.check-content { + background: var(--panel-main-bg-color-10); + pre { + margin: 0; + width: 350px; + overflow: hidden; + text-overflow: ellipsis; + } +} + +.el-descriptions { + overflow: hidden; + text-overflow: ellipsis; +} + +.code-dialog { + .el-dialog__header { + --el-dialog-padding-primary: 0px !important; + } +} + +.node-dashboard-card { + .header { + display: flex; + justify-content: space-between; + align-items: center; + + .header-left { + display: flex; + align-items: center; + gap: 12px; + + .header-span { + position: relative; + font-size: 16px; + font-weight: 500; + margin-left: 18px; + display: flex; + align-items: center; + + &::before { + position: absolute; + top: 50%; + transform: translateY(-50%); + left: -13px; + width: 4px; + height: 14px; + content: ''; + background: $primary-color; + border-radius: 10px; + } + } + } + .header-right { + display: flex; + align-items: center; + } + } +} \ No newline at end of file diff --git a/frontend/src/styles/element-dark.scss b/frontend/src/styles/element-dark.scss new file mode 100644 index 0000000..42e6709 --- /dev/null +++ b/frontend/src/styles/element-dark.scss @@ -0,0 +1,489 @@ +html.dark { + --panel-color-primary: #3d8eff; + --panel-color-primary-light-8: #3674cc; + --panel-color-primary-light-1: #6eaaff; + --panel-color-primary-light-2: #366fc2; + --panel-color-primary-light-3: #3364ad; + --panel-color-primary-light-4: #2f558f; + --panel-color-primary-light-5: #372e46; + --panel-color-primary-light-6: #2a4066; + --panel-color-primary-light-7: #2d4a7a; + --panel-color-primary-light-9: #2d4a7a; + + --panel-main-bg-color-1: #e3e6f3; + --panel-main-bg-color-2: #c0c2cf; + --panel-main-bg-color-3: #adb0bc; + --panel-main-bg-color-4: #9597a4; + --panel-main-bg-color-5: #90929f; + --panel-main-bg-color-6: #787b88; + --panel-main-bg-color-7: #5b5e6a; + --panel-main-bg-color-8: #434552; + --panel-main-bg-color-9: #2e313d; + --panel-main-bg-color-10: #242633; + --panel-main-bg-color-11: #60626f; + --panel-main-bg-color-12: #1f2329; + --panel-main-bg-color-13: #000000; + + --panel-alert-error-bg-color: #fef0f0; + --panel-alert-error-text-color: #f56c6c; + --panel-alert-error-hover-bg-color: #e9657b; + + --panel-alert-success-bg-color: #e1f3d8; + --panel-alert-success-text-color: #67c23a; + --panel-alert-success-hover-bg-color: #4dc894; + + --panel-alert-warning-bg-color: #59472a; + --panel-alert-warning-text-color: #edac2c; + --panel-alert-warning-hover-bg-color: #f1c161; + + --panel-alert-info-bg-color: var(--panel-main-bg-color-7); + --panel-alert-info-text-color: var(--panel-text-color-white); + --panel-alert-info-hover-bg-color: var(--panel-main-bg-color-4); + + --el-color-success: #3fb950; + --el-color-success-light-5: #4dc894; + --el-color-success-light-8: #3fb950; + --el-color-success-light-9: var(--panel-main-bg-color-9); + + --el-color-warning: #edac2c; + --el-color-warning-light-5: #f1c161; + --el-color-warning-light-8: #edac2c; + --el-color-warning-light-9: var(--panel-main-bg-color-9); + + --el-color-danger: #e2324f; + --el-color-danger-light-5: #e9657b; + --el-color-danger-light-8: #e2324f; + --el-color-danger-light-9: var(--panel-main-bg-color-9); + + --el-color-error: #e2324f; + --el-color-error-light-5: #e9657b; + --el-color-error-light-8: #e2324f; + --el-color-error-light-9: var(--panel-main-bg-color-9); + + --el-color-info: var(--panel-main-bg-color-3); + --el-color-info-light-5: var(--panel-main-bg-color-3); + --el-color-info-light-8: var(--panel-main-bg-color-3); + --el-color-info-light-9: var(--panel-main-bg-color-9); + + --panel-pie-bg-color: #434552; + --panel-text-color-white: #ffffff; + + --el-color-primary: var(--panel-color-primary); + --el-color-primary-light-1: var(--panel-color-primary-light-1); + --el-color-primary-light-2: var(--panel-color-primary-light-2); + --el-color-primary-light-3: var(--panel-color-primary-light-3); + --el-color-primary-light-4: var(--panel-color-primary-light-4); + --el-color-primary-light-5: var(--panel-color-primary-light-5); + --el-color-primary-light-6: var(--panel-color-primary-light-6); + --el-color-primary-light-7: var(--panel-color-primary-light-7); + --el-color-primary-light-8: var(--panel-color-primary-light-8); + --el-color-primary-light-9: var(--panel-color-primary-light-9); + --el-color-primary-dark-2: var(--panel-color-primary); + --el-scrollbar-bg-color: var(--panel-main-bg-color-8); + --el-border-color-darker: var(--panel-main-bg-color-6); + + --panel-border: 1px solid var(--panel-main-bg-color-8); + --panel-border-color: var(--panel-main-bg-color-8); + --panel-button-active: var(--panel-main-bg-color-10); + --panel-button-text-color: var(--panel-main-bg-color-10); + --panel-button-bg-color: var(--panel-color-primary); + --panel-footer-bg: var(--panel-main-bg-color-9); + --panel-footer-border: var(--panel-main-bg-color-7); + --panel-text-color: var(--panel-main-bg-color-1); + --panel-menu-bg-color: var(--panel-main-bg-color-10); + --panel-terminal-tag-bg-color: var(--panel-main-bg-color-10); + --panel-terminal-tag-active-bg-color: var(--panel-main-bg-color-10); + --panel-terminal-bg-color: var(--panel-main-bg-color-10); + --panel-terminal-tag-active-text-color: var(--panel-color-primary); + --panel-terminal-tag-hover-text-color: var(--panel-color-primary); + --panel-logs-bg-color: #1b1b1b; + --panel-alert-bg-color: var(--panel-main-bg-color-10); + + --panel-node-card-bg-color: #434552; + + --el-menu-item-bg-color: var(--panel-main-bg-color-9); + --el-menu-item-bg-color-active: var(--panel-main-bg-color-8); + --el-menu-hover-bg-color: var(--panel-main-bg-color-8); + --el-menu-text-color: var(--panel-main-bg-color-2); + --el-fill-color-blank: var(--panel-main-bg-color-10); + --el-fill-color-light: var(--panel-main-bg-color-10); + --el-fill-color-lighter: var(--panel-main-bg-color-9); + --el-border-color: var(--panel-main-bg-color-8); + --el-border-color-light: var(--panel-main-bg-color-8); + --el-border-color-lighter: var(--panel-main-bg-color-8); + + --el-text-color-primary: var(--panel-main-bg-color-2); + --el-text-color-regular: var(--panel-main-bg-color-2); + + --el-box-shadow: 0px 12px 32px 4px rgba(36, 38, 51, 0.36), 0px 8px 20px rgba(36, 38, 51, 0.72); + --el-box-shadow-light: 0px 0px 12px rgba(36, 38, 51, 0.72); + --el-box-shadow-lighter: 0px 0px 6px rgba(36, 38, 51, 0.72); + --el-box-shadow-dark: 0px 16px 48px 16px rgba(36, 38, 51, 0.72), 0px 12px 32px #242633, 0px 8px 16px -8px #242633; + --el-bg-color: var(--panel-main-bg-color-9); + --el-bg-color-overlay: var(--panel-main-bg-color-9); + --el-dialog-bg-color: var(--panel-main-bg-color-9); + --panel-code-header-footer-color: var(--panel-main-bg-color-12); + + --el-text-color-placeholder: var(--panel-main-bg-color-4); + + .el-radio-button { + --el-radio-button-checked-text-color: var(--panel-main-bg-color-10); + } + .el-descriptions__content:not(.is-bordered-label) { + color: var(--panel-main-bg-color-3); + } + + .el-menu-item:hover, + .el-sub-menu__title:hover { + background: var(--panel-main-bg-color-8) !important; + } + + .el-menu .el-menu-item { + box-shadow: 0 0 4px rgba(36, 38, 51, 0.72); + } + + .el-menu .el-sub-menu__title { + box-shadow: 0 0 4px rgba(36, 38, 51, 0.72); + } + + .el-overlay { + background-color: rgb(46 49 61 / 80%); + } + + .el-tag.el-tag--primary { + --el-tag-bg-color: var(--panel-main-bg-color-9); + --el-tag-border-color: var(--panel-main-bg-color-11); + --el-tag-hover-color: var(--panel-color-primary); + } + + .el-tabs--card > .el-tabs__header .el-tabs__nav { + border: 1px solid var(--panel-main-bg-color-8); + border-bottom: none; + } + + .el-tabs--card > .el-tabs__header .el-tabs__item.is-active { + border-bottom-color: var(--panel-color-primary); + --el-text-color-regular: var(--panel-color-primary); + } + .main-container { + .el-loading-mask { + background-color: #24263375; + } + } + + .el-loading-mask { + background-color: rgba(0, 0, 0, 0.8); + } + + .el-input { + --el-input-border-color: var(--panel-main-bg-color-8); + } + + input:-webkit-autofill { + box-shadow: 0 0 0 1000px var(--el-box-shadow) inset; + background-color: var(--panel-main-bg-color-1); + transition: background-color 1000s ease-out 0.5s; + } + .el-form-item .el-input__inner:-webkit-autofill { + -webkit-text-fill-color: var(--el-text-color-regular) !important; + } + + .el-input.is-disabled .el-input__wrapper { + --el-disabled-bg-color: var(--panel-main-bg-color-9); + --el-disabled-border-color: var(--panel-main-bg-color-8); + } + + .el-input > .el-input-group__append:hover { + background-color: var(--panel-main-bg-color-9) !important; + } + + .el-form-item__label { + color: var(--panel-main-bg-color-3); + } + + .el-card { + --el-card-bg-color: var(--panel-main-bg-color-10); + } + + .el-button:hover { + --el-button-hover-border-color: var(--panel-main-bg-color-11); + --el-button-hover-bg-color: var(--panel-main-bg-color-10); + } + + .el-button--primary { + --el-button-text-color: var(--panel-main-bg-color-10); + --el-button-hover-link-text-color: var(--panel-color-primary-light-1); + &.tag-button, + &.brief-button { + --el-button-text-color: var(--panel-main-bg-color-10); + --el-button-hover-text-color: var(--el-color-white); + --el-button-hover-border-color: var(--el-color-primary); + --el-button-hover-bg-color: var(--el-color-primary); + } + + &.app-button { + --el-button-text-color: var(--el-color-primary); + } + + &.h-app-button { + --el-button-text-color: var(--panel-main-bg-color-10); + --el-button-hover-text-color: var(--el-color-white); + --el-button-hover-border-color: var(--el-color-primary); + --el-button-hover-bg-color: var(--el-color-primary); + } + } + + .el-button--primary.is-plain, + .el-button--primary.is-text, + .el-button--primary.is-link { + --el-button-text-color: var(--panel-main-bg-color-2); + --el-button-bg-color: var(--panel-main-bg-color-9); + --el-button-border-color: var(--panel-main-bg-color-8); + --el-button-hover-bg-color: var(--panel-main-bg-color-9); + --el-button-hover-border-color: var(--panel-main-bg-color-8); + } + + .el-button--primary.is-text, + .el-button--primary.is-link { + --el-button-text-color: var(--panel-color-primary); + } + + .el-button--primary:hover { + --el-button-hover-text-color: var(--panel-main-bg-color-7); + --el-button-border-color: var(--el-color-primary); + --el-button-hover-bg-color: var(--panel-color-primary-light-2); + --el-button-hover-border-color: var(--panel-main-bg-color-8); + } + + .el-button--primary.is-plain:hover { + --el-button-hover-text-color: var(--panel-main-bg-color-10); + --el-button-border-color: var(--el-color-primary); + --el-button-hover-bg-color: var(--el-color-primary); + --el-button-hover-border-color: var(--el-color-primary); + } + + .el-button--primary:active { + --el-button-hover-text-color: var(--panel-main-bg-color-7); + --el-button-active-bg-color: var(--el-color-primary-light-3); + --el-button-active-border-color: var(--el-color-primary-light-3); + } + .el-button--primary.is-plain:active { + color: var(--panel-main-bg-color-10); + } + + .el-button:focus-visible { + outline: none; + } + + .el-button.is-disabled { + color: var(--panel-main-bg-color-7); + border-color: var(--panel-main-bg-color-8); + background: var(--panel-main-bg-color-9); + } + + .el-button.is-disabled:hover { + border-color: var(--panel-main-bg-color-8); + background: var(--panel-main-bg-color-9); + } + + .el-button--primary.is-link.is-disabled { + color: var(--panel-main-bg-color-8); + } + + .el-dropdown-menu__item:hover { + background-color: var(--panel-main-bg-color-7); + } + + .el-drawer .el-drawer__header span { + color: var(--panel-text-color); + } + + .el-dialog { + background-color: var(--panel-main-bg-color-9); + border: 1px solid var(--panel-border-color); + + .el-dialog__header { + color: var(--el-text-color-primary); + + .el-dialog__title { + color: var(--el-menu-text-color); + } + } + } + + .el-alert--error { + --el-alert-bg-color: var(--panel-alert-error-bg-color); + --el-color-error: var(--panel-alert-error-text-color); + } + + .el-alert--success { + --el-alert-bg-color: var(--panel-alert-success-bg-color); + --el-color-success: var(--panel-alert-success-text-color); + } + + .el-alert--warning { + --el-alert-bg-color: var(--panel-alert-warning-bg-color); + --el-color-warning: var(--panel-alert-warning-text-color); + } + + .el-alert--info { + --el-alert-bg-color: var(--panel-alert-info-bg-color); + --el-color-info: var(--panel-alert-info-text-color); + } + + .md-editor-dark { + --md-bk-color: var(--panel-main-bg-color-9); + } + + .md-editor-dark .md-editor-preview { + --md-theme-color: var(--el-text-color-primary); + } + + .md-editor-dark .default-theme a { + --md-theme-link-color: var(--el-color-primary); + } + + .md-editor-dark .default-theme pre code { + background-color: var(--panel-main-bg-color-8); + } + .md-editor-dark .default-theme pre:before { + background-color: var(--panel-main-bg-color-10); + } + + .el-descriptions__title { + color: var(--el-text-color-primary); + } + .el-descriptions__content.el-descriptions__cell.is-bordered-content { + color: var(--el-text-color-primary); + } + .el-descriptions--large .el-descriptions__body .el-descriptions__table.is-bordered .el-descriptions__cell { + padding: 12px 15px; + background-color: transparent; + } + .el-descriptions__body { + background-color: transparent; + } + .el-descriptions__label { + color: var(--el-color-primary) !important; + margin-right: 16px; + } + + .el-avatar { + --el-avatar-bg-color: var(--panel-text-color-white) !important; + box-shadow: 0 0 4px rgba(0, 94, 235, 0.1); + border: 0.5px solid var(--panel-main-bg-color-7); + } + .el-drawer { + .cm-editor { + background-color: var(--panel-logs-bg-color); + } + .cm-gutters { + background-color: var(--panel-logs-bg-color); + } + .log-container { + background-color: var(--panel-logs-bg-color); + } + } + + .cm-editor { + background-color: var(--panel-main-bg-color-9); + } + .cm-gutters { + background-color: var(--panel-main-bg-color-9); + } + + // scroll-bar + ::-webkit-scrollbar { + background-color: var(--el-scrollbar-bg-color) !important; + } + ::-webkit-scrollbar-thumb { + background-color: var(--el-border-color-darker); + } + ::-webkit-scrollbar-corner { + background-color: var(--el-scrollbar-bg-color); + } + + .app-warn { + span { + &:nth-child(2) { + color: var(--panel-color-primary); + + &:hover { + color: var(--panel-color-primary-light-3); + } + } + } + } + + .el-table { + --el-table-bg-color: var(--el-bg-color); + --el-table-tr-bg-color: var(--el-bg-color); + --el-table-header-bg-color: var(--el-bg-color); + --el-table-row-hover-bg-color: var(--panel-main-bg-color-8); + --el-table-border: 1px solid var(--panel-main-bg-color-8); + --el-table-border-color: var(--panel-main-bg-color-8); + } + + .el-message-box { + --el-messagebox-title-color: var(--el-menu-text-color); + border: 1px solid var(--panel-border-color); + } + + .el-popover { + --el-popover-title-text-color: var(--panel-main-bg-color-2); + border: 1px solid var(--panel-border-color); + } + + .app-wrapper { + .main-container { + background-color: var(--panel-main-bg-color-9) !important; + } + .app-footer { + background-color: var(--panel-main-bg-color-9) !important; + border-top: var(--panel-border); + } + .mobile-header { + background-color: var(--panel-main-bg-color-9) !important; + border-bottom: var(--panel-border); + color: #ffffff; + } + } + + .router_card_button { + .el-radio-button__inner { + background: none !important; + } + .el-radio-button__original-radio:checked + .el-radio-button__inner { + color: var(--panel-main-bg-color-10); + background-color: var(--panel-color-primary) !important; + box-shadow: none !important; + border: none !important; + } + } + + .el-date-table td.in-range .el-date-table-cell { + background-color: var(--panel-main-bg-color-8); + } + + .el-collapse-item__header { + color: #ffffff; + background-color: transparent !important; + } + + .el-checkbox__input.is-checked .el-checkbox__inner::after { + border-color: var(--panel-main-bg-color-10); + } + + .el-checkbox__input.is-indeterminate .el-checkbox__inner::before { + background-color: var(--panel-main-bg-color-10); + } + + .custom-input-textarea { + background-color: var(--panel-main-bg-color-10) !important; + color: var(--el-color-info) !important; + } + .custom-input-textarea:hover { + background-color: var(--panel-main-bg-color-9) !important; + color: var(--el-color-primary) !important; + } +} diff --git a/frontend/src/styles/element.scss b/frontend/src/styles/element.scss new file mode 100644 index 0000000..93ad7ea --- /dev/null +++ b/frontend/src/styles/element.scss @@ -0,0 +1,285 @@ +:root { + --panel-gradient-end-color: var(--el-color-primary-light-7); + + --el-color-primary-light-1: #196eed; + --el-color-primary-light-2: #337eef; + --el-color-primary-light-4: #669ef3; + --el-color-primary-light-6: #99bef7; + + --panel-color-primary: #005eeb; + --panel-color-primary-light-8: #196eed; + --panel-color-primary-light-1: #196eed; + --panel-color-primary-light-2: #337eef; + --panel-color-primary-light-3: #4c8ef1; + --panel-color-primary-light-4: #669ef3; + --panel-color-primary-light-5: #7faef5; + --panel-color-primary-light-6: #99bef7; + --panel-color-primary-light-7: #b2cef9; + --panel-color-primary-light-9: #e5eefd; + + --el-color-primary: var(--panel-color-primary); + --el-color-primary-light-3: var(--panel-color-primary-light-1); + --el-color-primary-light-5: var(--panel-color-primary-light-5); + --el-color-primary-light-7: var(--panel-color-primary-light-7); + --el-color-primary-light-8: var(--panel-color-primary-light-8); + --el-color-primary-light-9: var(--panel-color-primary-light-9); + --panel-code-header-footer-color: var(--panel-color-primary-light-9); + + --el-text-color-regular: #646a73; +} + +html { + --el-box-shadow-light: 0px 0px 4px rgba(0, 94, 235, 0.1) !important; + + // * menu + --el-menu-item-bg-color: rgba(255, 255, 255, 0.3); + --el-menu-item-bg-color-active: #ffffff; + --panel-main-bg-color-9: #f4f4f4; + --panel-menu-bg-color: rgba(0, 94, 235, 0.1); + --panel-menu-width: 180px; + --panel-menu-hide-width: 75px; + --panel-text-color: #1f2329; + --panel-border: 1px solid #f2f2f2; + --panel-button-active: #ffffff; + --panel-button-text-color: var(--panel-color-primary); + --panel-button-bg-color: #ffffff; + --panel-footer-border: #e4e7ed; + --panel-terminal-tag-bg-color: #efefef; + --panel-terminal-tag-active-bg-color: #575758; + --panel-terminal-tag-active-text-color: #ebeef5; + --panel-terminal-tag-hover-text-color: #575758; + --panel-terminal-bg-color: #1b1b1b; + --panel-logs-bg-color: #1b1b1b; + --panel-alert-bg-color: rgba(0, 94, 235, 0.03); + + --panel-alert-bg: #e2e4ec; + --panel-path-bg: #ffffff; + --panel-footer-bg: #ffffff; + --panel-pie-bg-color: #ffffff; + + --el-fill-color-light: #ffffff; + --el-disabled-bg-color: var(--panel-main-bg-color-9) !important; + + --panel-node-card-bg-color: #e3e6f3; +} + +.el-notification { + z-index: 99999 !important; +} + +.el-message { + z-index: 99999 !important; +} + +.table-box { + display: flex; + flex-direction: column; + height: 100%; + + .table-search { + display: flex; + margin-bottom: 10px; + .el-form { + max-width: 1260px; + .el-form-item { + margin-right: 5px; + .el-input, + .el-select, + .el-date-editor--timerange { + width: 210px; + } + .el-date-editor--datetimerange, + .el-date-editor--daterange { + width: 400px; + } + + .el-range-editor.el-input__wrapper { + padding: 0 10px; + } + + .el-select__tags { + overflow: hidden; + white-space: nowrap; + } + } + .more-item { + display: inline; + } + } + .search-operation { + margin-left: 15px; + white-space: nowrap; + .search-isOpen { + margin-left: 20px; + } + } + } + + .table-header { + .header-button-lf { + float: left; + .el-button { + margin-bottom: 20px; + } + } + .header-button-ri { + float: right; + .el-button { + margin-bottom: 20px; + } + } + } + + .el-table { + flex: 1; + .el-table__header th, + .el-table__body td { + text-align: center; + } + .el-table__header th { + height: 50px; + font-size: 15px; + font-weight: bold; + color: #252525; + background: #fafafa; + } + + .table-image { + width: 50px; + height: 50px; + border-radius: 50%; + } + + .el-table__header .el-table__cell > .cell { + white-space: nowrap; + } + .el-table__row { + height: 52px; + } + + .el-table__empty-block { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + .table-empty { + line-height: 30px; + } + } + + .el-pagination { + display: flex; + justify-content: flex-end; + margin: 23px 0 10px; + } +} + +.el-drawer { + .el-drawer__header { + padding: 15px 20px 14px; + margin-bottom: 0; + border-bottom: var(--panel-border); + span { + font-size: 17px; + color: #303133; + } + } + + .el-drawer__footer { + border-top: var(--panel-border); + } + .el-select { + width: 100%; + } + + .drawer-multiColumn-form { + display: flex; + flex-wrap: wrap; + .el-form-item { + width: 47%; + &:nth-child(2n-1) { + margin-right: 5%; + } + } + } +} + +.el-dialog { + border-radius: 5px; + + .el-dialog__header { + .el-dialog__title { + font-size: 17px; + } + } +} + +.row-box { + display: flex; + flex-flow: wrap; + .el-card { + min-width: 100%; + height: 100%; + margin-right: 20px; + border: 0; + } +} + +.el-avatar { + --el-avatar-bg-color: #ffffff !important; + border: 0.5px solid #eaeaea; + box-shadow: 0px 0px 2px 1px rgba(186, 186, 186, 0.22); + border-radius: 8px !important; + padding: 5px; +} + +.el-card { + border: none !important; +} + +.el-input-group__append { + button.el-button { + span { + vertical-align: text-top !important; + } + } +} + +.el-input.is-disabled .el-input__wrapper { + --el-disabled-bg-color: var(--panel-main-bg-color-9); +} + +.el-radio-button__inner { + [class*='el-icon'] + span { + margin-left: 6px; + } + span { + vertical-align: text-top !important; + } +} + +.el-tabs--card > .el-tabs__header .el-tabs__item.is-active { + border-bottom-color: var(--panel-color-primary) !important; +} + +.logo { + color: var(--el-color-primary); +} + +.custom-input-textarea { + background-color: #f5f7fa !important; + color: var(--el-color-info) !important; +} + +.custom-input-textarea:hover { + color: var(--el-color-primary) !important; + background-color: var(--el-color-primary-light-9) !important; + border-color: var(--el-button-border-color) !important; +} + +.el-collapse-item__header { + color: var(--el-text-color-regular) !important; + border: 1px solid var(--panel-main-bg-color-10); + background-color: var(--panel-main-bg-color-10) !important; +} diff --git a/frontend/src/styles/index.scss b/frontend/src/styles/index.scss new file mode 100644 index 0000000..dd2f475 --- /dev/null +++ b/frontend/src/styles/index.scss @@ -0,0 +1,7 @@ +@use 'fit2cloud-ui-plus/src/styles/index.scss' as *; +@use './element.scss'; +@use './element-dark.scss'; +@use './moblie.scss'; +@use './reset.scss'; +@use './var.scss'; +@use 'md-editor-v3/lib/style.css'; diff --git a/frontend/src/styles/mixins.scss b/frontend/src/styles/mixins.scss new file mode 100644 index 0000000..248cff5 --- /dev/null +++ b/frontend/src/styles/mixins.scss @@ -0,0 +1,15 @@ +@mixin flex-row($justify: flex-start, $align: stretch) { + display: flex; + @if $justify != flex-start { + justify-content: $justify; + } + @if $align != stretch { + align-items: $align; + } +} + +@mixin variant($color, $background-color, $border-color) { + color: $color; + background-color: $background-color; + border-color: $border-color; +} diff --git a/frontend/src/styles/moblie.scss b/frontend/src/styles/moblie.scss new file mode 100644 index 0000000..1f5c988 --- /dev/null +++ b/frontend/src/styles/moblie.scss @@ -0,0 +1,89 @@ +.mobile { + .monitor-tags { + position: inherit; + top: 13px; + } + .mobile-monitor-chart { + margin-top: 20px !important; + } + .search-button { + float: none !important; + .table-button { + display: inline-flex !important; + } + } + + .app-card { + .app-button { + margin-right: 0 !important; + } + } + .install-card .a-detail { + .d-name { + height: auto !important; + .h-button { + float: none !important; + margin: 5px; + } + } + .d-button { + min-width: auto !important; + } + .d-description { + overflow: inherit !important; + } + } + .router_card_button { + padding: 2px 0; + } + .el-drawer.rtl { + width: 80% !important; + } + .site-form-wrapper { + width: 90% !important; + min-width: auto !important; + .el-form-item__label { + width: auto !important; + } + } + .moblie-form { + overflow: auto; + .el-input { + width: 350px; + } + .el-textarea__inner { + width: 350px; + } + } + + .app-status { + font-size: 12px; + .el-card { + .status-content { + margin-left: 20px; + } + } + } + + .mini-form-item { + width: 100% !important; + } + + .database-status { + .title { + margin-left: 10px !important; + } + .content { + margin-left: 10px !important; + } + } + + @media only screen and (max-width: 768px) { + .el-col-xs-24 { + margin-bottom: 10px; + } + } + .el-dialog { + --el-dialog-width: 80% !important; + } +} diff --git a/frontend/src/styles/reset.scss b/frontend/src/styles/reset.scss new file mode 100644 index 0000000..52a8a46 --- /dev/null +++ b/frontend/src/styles/reset.scss @@ -0,0 +1,30 @@ +html, +body, +#app { + width: 100%; + height: 100%; + padding: 0; + margin: 0; +} + +:-webkit-any(article, aside, nav, section) h1 { + font-size: 2em; +} + +.el-switch--small .el-switch__core { + width: 36px; +} + +.el-switch--small .el-switch__core::after { + width: 12px; + height: 12px; +} + +.el-switch--small.is-checked .el-switch__core::after { + margin-left: -13px; +} + +.el-alert__title { + display: flex; + align-items: center; +} \ No newline at end of file diff --git a/frontend/src/styles/style.css b/frontend/src/styles/style.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/frontend/src/styles/style.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/frontend/src/styles/var.scss b/frontend/src/styles/var.scss new file mode 100644 index 0000000..14a1394 --- /dev/null +++ b/frontend/src/styles/var.scss @@ -0,0 +1 @@ +$primary-color: var(--el-color-primary); diff --git a/frontend/src/typings/global.d.ts b/frontend/src/typings/global.d.ts new file mode 100644 index 0000000..e6100b9 --- /dev/null +++ b/frontend/src/typings/global.d.ts @@ -0,0 +1,31 @@ +declare namespace Menu { + interface MenuOptions { + path: string; + title: string; + icon?: string; + isLink?: string; + close?: boolean; + children?: MenuOptions[]; + } +} + +declare type Recordable = Record; + +declare interface ViteEnv { + VITE_API_URL: string; + VITE_PORT: number; + VITE_OPEN: boolean; + VITE_GLOB_APP_TITLE: string; + VITE_DROP_CONSOLE: boolean; + VITE_PROXY_URL: string; + VITE_BUILD_GZIP: boolean; + VITE_REPORT: boolean; + PANEL_XPACK: boolean; +} + +declare interface RouterButton { + label: string; + path?: string; + name?: string; + count?: number; +} diff --git a/frontend/src/typings/plugins.d.ts b/frontend/src/typings/plugins.d.ts new file mode 100644 index 0000000..f98b942 --- /dev/null +++ b/frontend/src/typings/plugins.d.ts @@ -0,0 +1,2 @@ +declare module 'nprogress'; +declare module 'qs'; diff --git a/frontend/src/typings/window.d.ts b/frontend/src/typings/window.d.ts new file mode 100644 index 0000000..6fa5162 --- /dev/null +++ b/frontend/src/typings/window.d.ts @@ -0,0 +1,9 @@ +// * global +declare global { + interface Navigator { + msSaveOrOpenBlob: (blob: Blob, fileName: string) => void; + browserLanguage: string; + } +} + +export {}; diff --git a/frontend/src/utils/app.ts b/frontend/src/utils/app.ts new file mode 100644 index 0000000..d7edb17 --- /dev/null +++ b/frontend/src/utils/app.ts @@ -0,0 +1,27 @@ +import { jumpToPath } from './util'; +import router from '@/routers'; + +export const jumpToInstall = (type: string, key: string) => { + switch (type) { + case 'php': + case 'node': + case 'java': + case 'go': + case 'python': + case 'dotnet': + jumpToPath(router, '/websites/runtimes/' + type); + return true; + } + switch (key) { + case 'mysql-cluster': + jumpToPath(router, '/xpack/cluster/mysql'); + return true; + case 'redis-cluster': + jumpToPath(router, '/xpack/cluster/redis'); + return true; + case 'postgresql-cluster': + jumpToPath(router, '/xpack/cluster/postgres'); + return true; + } + return false; +}; diff --git a/frontend/src/utils/bus.ts b/frontend/src/utils/bus.ts new file mode 100644 index 0000000..84a19e8 --- /dev/null +++ b/frontend/src/utils/bus.ts @@ -0,0 +1,26 @@ +class Bus { + list: { [key: string]: Array }; + constructor() { + this.list = {}; + } + + on(name: string, fn: Function) { + this.list[name] = this.list[name] || []; + this.list[name].push(fn); + } + + emit(name: string, data?: any) { + if (this.list[name]) { + this.list[name].forEach((fn: Function) => { + fn(data); + }); + } + } + + off(name: string) { + if (this.list[name]) { + delete this.list[name]; + } + } +} +export default Bus; diff --git a/frontend/src/utils/dashboardCache.ts b/frontend/src/utils/dashboardCache.ts new file mode 100644 index 0000000..2755ca7 --- /dev/null +++ b/frontend/src/utils/dashboardCache.ts @@ -0,0 +1,61 @@ +const DASHBOARD_CACHE_KEY = 'dashboardCache'; + +type CacheEntry = { + value: any; + expireAt: number; +}; + +const readCache = (): Record | null => { + try { + const cacheRaw = localStorage.getItem(DASHBOARD_CACHE_KEY); + return cacheRaw ? JSON.parse(cacheRaw) : {}; + } catch { + return null; + } +}; + +export const getDashboardCache = (key: string) => { + const cache = readCache(); + if (!cache) return null; + const entry = cache[key]; + if (entry && entry.expireAt > Date.now()) { + return entry.value; + } + return null; +}; + +export const setDashboardCache = (key: string, value: any, ttl: number) => { + try { + const cacheRaw = localStorage.getItem(DASHBOARD_CACHE_KEY); + const cache = cacheRaw ? JSON.parse(cacheRaw) : {}; + cache[key] = { + value, + expireAt: Date.now() + ttl, + }; + localStorage.setItem(DASHBOARD_CACHE_KEY, JSON.stringify(cache)); + } catch { + localStorage.removeItem(DASHBOARD_CACHE_KEY); + } +}; + +export const clearDashboardCache = () => { + localStorage.removeItem(DASHBOARD_CACHE_KEY); +}; + +export const clearDashboardCacheByPrefix = (prefixes: string[]) => { + try { + const cacheRaw = localStorage.getItem(DASHBOARD_CACHE_KEY); + if (!cacheRaw) return; + const cache = JSON.parse(cacheRaw); + Object.keys(cache).forEach((key: string) => { + if (prefixes.some((prefix) => key.startsWith(prefix))) { + delete cache[key]; + } + }); + localStorage.setItem(DASHBOARD_CACHE_KEY, JSON.stringify(cache)); + } catch { + clearDashboardCache(); + } +}; + +export { DASHBOARD_CACHE_KEY }; diff --git a/frontend/src/utils/docker.ts b/frontend/src/utils/docker.ts new file mode 100644 index 0000000..b233b12 --- /dev/null +++ b/frontend/src/utils/docker.ts @@ -0,0 +1,28 @@ +import { isJson } from './util'; + +export function formatImageStdout(stdout: string) { + let lines = stdout.split('\r\n'); + for (let i = 0; i < lines.length; i++) { + if (isJson(lines[i])) { + const data = JSON.parse(lines[i]); + if (data.errorDetail || data.error) { + lines[i] = data.errorDetail || data.errorDetail; + lines[i] = data.error || data.error; + continue; + } + if (data.stream) { + lines[i] = data.stream; + continue; + } + if (data.id) { + lines[i] = data.id + ': ' + data.status; + } else { + lines[i] = data.status; + } + if (data.progress) { + lines[i] = lines[i] + data.progress; + } + } + } + return lines.join('\r\n'); +} diff --git a/frontend/src/utils/echarts.ts b/frontend/src/utils/echarts.ts new file mode 100644 index 0000000..60acfe2 --- /dev/null +++ b/frontend/src/utils/echarts.ts @@ -0,0 +1,36 @@ +import * as echarts from 'echarts/core'; + +import { LineChart, BarChart, PieChart, MapChart } from 'echarts/charts'; + +import { + TitleComponent, + TooltipComponent, + GridComponent, + LegendComponent, + VisualMapComponent, + GeoComponent, + PolarComponent, + DataZoomComponent, +} from 'echarts/components'; + +import { CanvasRenderer } from 'echarts/renderers'; + +echarts.use([ + LineChart, + BarChart, + PieChart, + MapChart, + + TitleComponent, + TooltipComponent, + GridComponent, + LegendComponent, + VisualMapComponent, + GeoComponent, + PolarComponent, + DataZoomComponent, + + CanvasRenderer, +]); + +export default echarts; diff --git a/frontend/src/utils/get-env.ts b/frontend/src/utils/get-env.ts new file mode 100644 index 0000000..0630520 --- /dev/null +++ b/frontend/src/utils/get-env.ts @@ -0,0 +1,73 @@ +import fs from 'fs'; +import path from 'path'; +import dotenv from 'dotenv'; + +export function isDevFn(mode: string): boolean { + return mode === 'development'; +} + +export function isProdFn(mode: string): boolean { + return mode === 'production'; +} + +/** + * Whether to generate package preview + */ +export function isReportMode(): boolean { + return process.env.VITE_REPORT === 'true'; +} + +// Read all environment variable configuration files to process.env +export function wrapperEnv(envConf: Recordable): ViteEnv { + const ret: any = {}; + + for (const envName of Object.keys(envConf)) { + let realName = envConf[envName].replace(/\\n/g, '\n'); + realName = realName === 'true' ? true : realName === 'false' ? false : realName; + + if (envName === 'VITE_PORT') { + realName = Number(realName); + } + if (envName === 'VITE_PROXY') { + try { + realName = JSON.parse(realName); + } catch (error) {} + } + ret[envName] = realName; + process.env[envName] = realName; + } + return ret; +} + +/** + * Get the environment variables starting with the specified prefix + * @param match prefix + * @param confFiles ext + */ +export function getEnvConfig(match = 'VITE_GLOB_', confFiles = ['.env', '.env.production']) { + let envConfig = {}; + confFiles.forEach((item) => { + try { + const env = dotenv.parse(fs.readFileSync(path.resolve(process.cwd(), item))); + envConfig = { ...envConfig, ...env }; + } catch (error) { + console.error(`Error in parsing ${item}`, error); + } + }); + + Object.keys(envConfig).forEach((key) => { + const reg = new RegExp(`^(${match})`); + if (!reg.test(key)) { + Reflect.deleteProperty(envConfig, key); + } + }); + return envConfig; +} + +/** + * Get user root directory + * @param dir file path + */ +export function getRootPath(...dir: string[]) { + return path.resolve(process.cwd(), ...dir); +} diff --git a/frontend/src/utils/message.ts b/frontend/src/utils/message.ts new file mode 100644 index 0000000..c675dca --- /dev/null +++ b/frontend/src/utils/message.ts @@ -0,0 +1,51 @@ +import { ElMessage } from 'element-plus'; + +let messageDom: any = null; +const messageTypes: Array = ['success', 'error', 'warning', 'info']; +const Message: any = (options) => { + if (messageDom) messageDom.close(); + messageDom = ElMessage(options); +}; +messageTypes.forEach((type) => { + Message[type] = (options) => { + if (typeof options === 'string') options = { message: options }; + options.type = type; + return Message(options); + }; +}); + +export const MsgSuccess = (message) => { + Message.success({ + message: message, + type: 'success', + showClose: true, + duration: 3000, + }); +}; + +export const MsgInfo = (message) => { + Message.info({ + message: message, + type: 'info', + showClose: true, + duration: 3000, + }); +}; + +export const MsgWarning = (message) => { + Message.warning({ + message: message, + type: 'warning', + showClose: true, + duration: 3000, + }); +}; + +export const MsgError = (message) => { + Message.error({ + message: message, + type: 'error', + showClose: true, + duration: 3000, + }); +}; diff --git a/frontend/src/utils/node.ts b/frontend/src/utils/node.ts new file mode 100644 index 0000000..379367d --- /dev/null +++ b/frontend/src/utils/node.ts @@ -0,0 +1,38 @@ +import { listNodeOptions } from '@/api/modules/setting'; +import { GlobalStore } from '@/store'; + +const globalStore = GlobalStore(); + +export const changeToLocal = async () => { + if (!globalStore.isMasterPro) { + setDefaultNodeInfo(); + return; + } + await listNodeOptions('all') + .then((res) => { + if (!res) { + setDefaultNodeInfo(); + return; + } + let nodes = res.data || []; + if (nodes.length === 0) { + setDefaultNodeInfo(); + return; + } + for (const item of nodes) { + if (item.name === 'local') { + globalStore.currentNode = 'local'; + globalStore.currentNodeAddr = item.addr; + return; + } + } + }) + .catch(() => { + setDefaultNodeInfo(); + }); +}; + +export const setDefaultNodeInfo = () => { + globalStore.currentNode = 'local'; + globalStore.currentNodeAddr = '127.0.0.1'; +}; diff --git a/frontend/src/utils/router.ts b/frontend/src/utils/router.ts new file mode 100644 index 0000000..5981271 --- /dev/null +++ b/frontend/src/utils/router.ts @@ -0,0 +1,44 @@ +import router from '@/routers'; +import { TabsStore } from '@/store'; + +export const routerToName = async (name: string) => { + await router.push({ name: name }); + tabStoreMiddleWare(); +}; + +export const routerToPath = async (path: string) => { + await router.push({ path: path }); + tabStoreMiddleWare(); +}; + +export const routerToFileWithPath = async (pathItem: string) => { + await router.push({ name: 'File', query: { path: pathItem, uncached: 'true' } }); + tabStoreMiddleWare(); +}; + +export const routerToNameWithQuery = async (name: string, query: any) => { + await router.push({ name: name, query: query }); + tabStoreMiddleWare(); +}; + +export const routerToPathWithQuery = async (path: string, query: any) => { + await router.push({ path: path, query: query }); + tabStoreMiddleWare(); +}; + +export const routerToNameWithParams = async (name: string, params: any) => { + await router.push({ name: name, params: params }); + tabStoreMiddleWare(); +}; + +const tabStoreMiddleWare = () => { + try { + let route = router.currentRoute; + if (route.value.meta.ignoreTab) { + return; + } + const tabsStore = TabsStore(); + tabsStore.addTab(route.value); + tabsStore.activeTabPath = route.value?.path; + } catch (error) {} +}; diff --git a/frontend/src/utils/runtime.ts b/frontend/src/utils/runtime.ts new file mode 100644 index 0000000..feea532 --- /dev/null +++ b/frontend/src/utils/runtime.ts @@ -0,0 +1,30 @@ +import { Runtime } from '@/api/interface/runtime'; + +export function disabledButton(row: Runtime.Runtime, type: string): boolean { + switch (type) { + case 'stop': + return ( + row.status === 'Recreating' || + row.status === 'Stopped' || + row.status === 'Building' || + row.resource == 'local' + ); + case 'start': + return ( + row.status === 'Starting' || + row.status === 'Recreating' || + row.status === 'Running' || + row.status === 'Building' || + row.resource == 'local' + ); + case 'restart': + return row.status === 'Recreating' || row.status === 'Building' || row.resource == 'local'; + case 'edit': + return row.status === 'Recreating' || row.status === 'Building'; + case 'extension': + case 'config': + return row.status != 'Running'; + default: + return false; + } +} diff --git a/frontend/src/utils/shortcuts.ts b/frontend/src/utils/shortcuts.ts new file mode 100644 index 0000000..498cb65 --- /dev/null +++ b/frontend/src/utils/shortcuts.ts @@ -0,0 +1,63 @@ +import i18n from '@/lang'; + +export const shortcuts = [ + { + text: i18n.global.t('monitor.today'), + value: () => { + const end = new Date(); + const start = new Date(new Date().setHours(0, 0, 0, 0)); + return [start, end]; + }, + }, + { + text: i18n.global.t('monitor.yesterday'), + value: () => { + const yesterday = new Date(new Date().getTime() - 3600 * 1000 * 24 * 1); + const end = new Date(yesterday.setHours(23, 59, 59, 999)); + const start = new Date(yesterday.setHours(0, 0, 0, 0)); + return [start, end]; + }, + }, + { + text: i18n.global.t('monitor.lastNDay', [3]), + value: () => { + const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 3); + const end = new Date(); + return [start, end]; + }, + }, + { + text: i18n.global.t('monitor.lastNDay', [7]), + value: () => { + const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 7); + const end = new Date(); + return [start, end]; + }, + }, + { + text: i18n.global.t('monitor.lastNDay', [30]), + value: () => { + const start = new Date(new Date().getTime() - 3600 * 1000 * 24 * 30); + const end = new Date(); + return [start, end]; + }, + }, + { + text: i18n.global.t('monitor.lastNMonth', [3]), + value: () => { + const end = new Date(); + const start = new Date(end); + start.setMonth(end.getMonth() - 3); + return [start, end]; + }, + }, + { + text: i18n.global.t('monitor.lastHalfYear', [30]), + value: () => { + const end = new Date(); + const start = new Date(end); + start.setMonth(end.getMonth() - 6); + return [start, end]; + }, + }, +]; diff --git a/frontend/src/utils/svg.ts b/frontend/src/utils/svg.ts new file mode 100644 index 0000000..6633dd0 --- /dev/null +++ b/frontend/src/utils/svg.ts @@ -0,0 +1,13 @@ +/** + * @description 自定义 loading svg + */ +export const loadingSvg = ` + +`; diff --git a/frontend/src/utils/theme.ts b/frontend/src/utils/theme.ts new file mode 100644 index 0000000..128191c --- /dev/null +++ b/frontend/src/utils/theme.ts @@ -0,0 +1,8 @@ +export function setPrimaryColor(color: string) { + let setPrimaryColor: (arg0: string) => any; + const xpackModules = import.meta.glob('../xpack/utils/theme/tool.ts', { eager: true }); + if (xpackModules['../xpack/utils/theme/tool.ts']) { + setPrimaryColor = xpackModules['../xpack/utils/theme/tool.ts']['setPrimaryColor'] || {}; + return setPrimaryColor(color); + } +} diff --git a/frontend/src/utils/util.ts b/frontend/src/utils/util.ts new file mode 100644 index 0000000..1452cfc --- /dev/null +++ b/frontend/src/utils/util.ts @@ -0,0 +1,973 @@ +import { AcmeAccountTypes, DNSTypes, KeyTypes } from '@/global/mimetype'; +import i18n from '@/lang'; +import useClipboard from 'vue-clipboard3'; +const { toClipboard } = useClipboard(); +import { MsgError, MsgSuccess } from '@/utils/message'; +import { v4 as uuidv4 } from 'uuid'; +import JSEncrypt from 'jsencrypt'; +import CryptoJS from 'crypto-js'; +import { routerToPathWithQuery } from './router'; +import { toUnicode } from 'punycode'; + +export function deepCopy(obj: any): T { + let newObj: any; + try { + newObj = obj.push ? [] : {}; + } catch (error) { + newObj = {}; + } + for (let attr in obj) { + if (typeof obj[attr] === 'object') { + newObj[attr] = deepCopy(obj[attr]); + } else { + newObj[attr] = obj[attr]; + } + } + return newObj; +} +export function randomNum(min: number, max: number): number { + let num = Math.floor(Math.random() * (min - max) + max); + return num; +} + +export function debounce(func: Function, wait: number) { + let timeout: NodeJS.Timeout | null = null; + return function executedFunction(...args: any[]) { + const later = () => { + if (timeout) clearTimeout(timeout); + func(...args); + }; + if (timeout) clearTimeout(timeout); + timeout = setTimeout(later, wait); + }; +} + +export function getBrowserLang() { + let browserLang = navigator.language ? navigator.language : navigator.browserLanguage; + let defaultBrowserLang = ''; + if ( + browserLang.toLowerCase() === 'cn' || + browserLang.toLowerCase() === 'zh' || + browserLang.toLowerCase() === 'zh-cn' + ) { + defaultBrowserLang = 'zh'; + } else { + defaultBrowserLang = 'en'; + } + return defaultBrowserLang; +} + +export function loadUpTime(timeSince: string) { + if (!timeSince) { + return ''; + } + const targetTime = new Date(timeSince); + const currentTime = new Date(); + const uptime = (currentTime.getTime() - targetTime.getTime()) / 1000; + if (uptime <= 0) { + return ''; + } + let days = Math.floor(uptime / 86400); + let hours = Math.floor((uptime % 86400) / 3600); + let minutes = Math.floor((uptime % 3600) / 60); + let seconds = Math.floor(uptime % 60); + if (days !== 0) { + return ( + days + + i18n.global.t('commons.units.day') + + ' ' + + hours + + i18n.global.t('commons.units.hour') + + ' ' + + minutes + + i18n.global.t('commons.units.minute') + + ' ' + + seconds + + i18n.global.t('commons.units.second') + ); + } + if (hours !== 0) { + return ( + hours + + i18n.global.t('commons.units.hour') + + ' ' + + minutes + + i18n.global.t('commons.units.minute') + + ' ' + + seconds + + i18n.global.t('commons.units.second') + ); + } + if (minutes !== 0) { + return minutes + i18n.global.t('commons.units.minute') + ' ' + seconds + i18n.global.t('commons.units.second'); + } + return seconds + i18n.global.t('commons.units.second'); +} + +// 20250310 +export function getCurrentDateFormatted() { + const now = new Date(); + const year = now.getFullYear(); + const month = String(now.getMonth() + 1).padStart(2, '0'); + const day = String(now.getDate()).padStart(2, '0'); + const hours = String(now.getHours()).padStart(2, '0'); + const minutes = String(now.getMinutes()).padStart(2, '0'); + const seconds = String(now.getSeconds()).padStart(2, '0'); + + return `${year}${month}${day}${hours}${minutes}${seconds}`; +} + +export function dateFormat(row: any, col: any, dataStr: any) { + const date = new Date(dataStr); + const y = date.getFullYear(); + let m: string | number = date.getMonth() + 1; + m = m < 10 ? `0${String(m)}` : m; + let d: string | number = date.getDate(); + d = d < 10 ? `0${String(d)}` : d; + let h: string | number = date.getHours(); + h = h < 10 ? `0${String(h)}` : h; + let minute: string | number = date.getMinutes(); + minute = minute < 10 ? `0${String(minute)}` : minute; + let second: string | number = date.getSeconds(); + second = second < 10 ? `0${String(second)}` : second; + return `${String(y)}-${String(m)}-${String(d)} ${String(h)}:${String(minute)}:${String(second)}`; +} + +//2016-01-12 +export function dateFormatSimple(dataStr: any) { + const date = new Date(dataStr); + const y = date.getFullYear(); + let m: string | number = date.getMonth() + 1; + m = m < 10 ? `0${String(m)}` : m; + let d: string | number = date.getDate(); + d = d < 10 ? `0${String(d)}` : d; + return `${String(y)}-${String(m)}-${String(d)}`; +} + +// 20221013151302 +export function dateFormatForName(dataStr: any) { + const date = new Date(dataStr); + const y = date.getFullYear(); + let m: string | number = date.getMonth() + 1; + m = m < 10 ? `0${String(m)}` : m; + let d: string | number = date.getDate(); + d = d < 10 ? `0${String(d)}` : d; + let h: string | number = date.getHours(); + h = h < 10 ? `0${String(h)}` : h; + let minute: string | number = date.getMinutes(); + minute = minute < 10 ? `0${String(minute)}` : minute; + let second: string | number = date.getSeconds(); + second = second < 10 ? `0${String(second)}` : second; + return `${String(y)}${String(m)}${String(d)}${String(h)}${String(minute)}${String(second)}`; +} + +// 10-13 \n 15:13 +export function dateFormatWithoutYear(dataStr: any) { + const date = new Date(dataStr); + let m: string | number = date.getMonth() + 1; + m = m < 10 ? `0${String(m)}` : m; + let d: string | number = date.getDate(); + d = d < 10 ? `0${String(d)}` : d; + let h: string | number = date.getHours(); + h = h < 10 ? `0${String(h)}` : h; + let minute: string | number = date.getMinutes(); + minute = minute < 10 ? `0${String(minute)}` : minute; + let s: string | number = date.getSeconds(); + s = s < 10 ? `0${String(s)}` : s; + return `${String(m)}-${String(d)}\n${String(h)}:${String(minute)}:${String(s)}`; +} + +// 20221013151302 +export function dateFormatForSecond(dataStr: any) { + const date = new Date(dataStr); + let h: string | number = date.getHours(); + h = h < 10 ? `0${String(h)}` : h; + let minute: string | number = date.getMinutes(); + minute = minute < 10 ? `0${String(minute)}` : minute; + let second: string | number = date.getSeconds(); + second = second < 10 ? `0${String(second)}` : second; + return `${String(h)}:${String(minute)}:${String(second)}`; +} + +export function getRandomStr(e: number): string { + const t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'; + const a = t.length; + let n = ''; + + for (let i = 0; i < e; i++) { + n += t.charAt(Math.floor(Math.random() * a)); + } + return n; +} + +export function getDBName(e: number): string { + const t = 'abcdefhijkmnprstwxyz2345678'; + const a = t.length; + let n = ''; + + for (let i = 0; i < e; i++) { + n += t.charAt(Math.floor(Math.random() * a)); + } + return n; +} + +export function loadZero(i: number) { + return i < 10 ? '0' + i : '' + i; +} + +export function computeSize(size: number): string { + const num = 1024.0; + if (size < num) return size + ' B'; + if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KB'; + if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MB'; + if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GB'; + return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TB'; +} + +export function computeSizeForDocker(size: number): string { + const num = 1024.0; + if (size < num) return size + ' B'; + if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KiB'; + if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MiB'; + if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GiB'; + return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TiB'; +} + +export function computeSize2(size: number): string { + const num = 1000.0; + if (size < num) return size + ' B'; + if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' KB'; + if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' MB'; + if (size < Math.pow(num, 4)) return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' GB'; + return formattedNumber((size / Math.pow(num, 4)).toFixed(2)) + ' TB'; +} + +export function computeCPU(size: number): string { + const num = 1000; + if (size < num) return size + ' ns'; + if (size < Math.pow(num, 2)) return formattedNumber((size / num).toFixed(2)) + ' μs'; + if (size < Math.pow(num, 3)) return formattedNumber((size / Math.pow(num, 2)).toFixed(2)) + ' ms'; + return formattedNumber((size / Math.pow(num, 3)).toFixed(2)) + ' s'; +} + +export function splitSize(size: number): any { + const num = 1024.0; + if (size < num) return { size: Number(size), unit: 'B' }; + if (size < Math.pow(num, 2)) return { size: formattedNumber((size / num).toFixed(2)), unit: 'KB' }; + if (size < Math.pow(num, 3)) + return { size: formattedNumber((size / Number(Math.pow(num, 2).toFixed(2))).toFixed(2)), unit: 'MB' }; + if (size < Math.pow(num, 4)) + return { size: formattedNumber((size / Number(Math.pow(num, 3))).toFixed(2)), unit: 'GB' }; + return { size: formattedNumber((size / Number(Math.pow(num, 4))).toFixed(2)), unit: 'TB' }; +} + +export function formattedNumber(num: string) { + return num.endsWith('.00') ? Number(num.slice(0, -3)) : Number(num); +} + +export function computeSizeFromMB(size: number): string { + const num = 1024.0; + if (size < num) return size + ' MB'; + if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' GB'; + return (size / Math.pow(num, 3)).toFixed(2) + ' TB'; +} + +export function computeSizeFromKB(size: number): string { + const num = 1024.0; + if (size < num) return size + ' KB'; + if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' MB'; + if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB'; + return (size / Math.pow(num, 3)).toFixed(2) + ' TB'; +} +export function computeSizeFromByte(size: number): string { + const num = 1024.0; + if (size < num) return size + ' B'; + if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' KB'; + if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' MB'; + if (size < Math.pow(num, 4)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB'; + return (size / Math.pow(num, 5)).toFixed(2) + ' TB'; +} + +export function computeSizeFromKBs(size: number): string { + const num = 1024.0; + if (size < num) return size + ' KB/s'; + if (size < Math.pow(num, 2)) return (size / num).toFixed(2) + ' MB/s'; + if (size < Math.pow(num, 3)) return (size / Math.pow(num, 2)).toFixed(2) + ' GB/s'; + return (size / Math.pow(num, 3)).toFixed(2) + ' TB/s'; +} + +let icons = new Map([ + ['.zip', 'p-file-zip'], + ['.gz', 'p-file-zip'], + ['.tar.bz2', 'p-file-zip'], + ['.bz2', 'p-file-zip'], + ['.xz', 'p-file-zip'], + ['.tar', 'p-file-zip'], + ['.tar.gz', 'p-file-zip'], + ['.war', 'p-file-zip'], + ['.tgz', 'p-file-zip'], + ['.7z', 'p-file-zip'], + ['.rar', 'p-file-zip'], + ['.mp3', 'p-file-mp3'], + ['.svg', 'p-file-svg'], + ['.txt', 'p-file-txt'], + ['.html', 'p-file-html'], + ['.word', 'p-file-word'], + ['.ppt', 'p-file-ppt'], + ['.jpg', 'p-file-jpg'], + ['.jpeg', 'p-file-jpg'], + ['.png', 'p-file-png'], + ['.xlsx', 'p-file-excel'], + ['.doc', 'p-file-word'], + ['.xls', 'p-file-excel'], + ['.docx', 'p-file-word'], + ['.pdf', 'p-file-pdf'], + ['.bmp', 'p-file-png'], + ['.gif', 'p-file-png'], + ['.tiff', 'p-file-png'], + ['.ico', 'p-file-png'], + ['.webp', 'p-file-png'], + ['.mp4', 'p-file-video'], + ['.webm', 'p-file-video'], + ['.mov', 'p-file-video'], + ['.wmv', 'p-file-video'], + ['.mkv', 'p-file-video'], + ['.avi', 'p-file-video'], + ['.wma', 'p-file-video'], + ['.flv', 'p-file-video'], + ['.wav', 'p-file-mp3'], + ['.wma', 'p-file-mp3'], + ['.ape', 'p-file-mp3'], + ['.acc', 'p-file-mp3'], + ['.ogg', 'p-file-mp3'], + ['.flac', 'p-file-mp3'], +]); + +export function getIcon(extension: string): string { + if (icons.get(extension) != undefined) { + const icon = icons.get(extension); + return String(icon); + } else { + return 'p-file-normal'; + } +} + +export function checkIp(value: string): boolean { + if (value === '') { + return true; + } + const 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])$/; + if (!reg.test(value) && value !== '') { + return true; + } else { + return false; + } +} + +export function checkDomain(value: string): boolean { + if (value === '') { + return true; + } + const reg = /^(?=^.{3,255}$)[a-zA-Z0-9][-a-zA-Z0-9]{0,62}(\.[a-zA-Z0-9][-a-zA-Z0-9]{0,62})+$/; + if (!reg.test(value) && value !== '') { + return true; + } else { + return false; + } +} + +export function isDomain(value: string): boolean { + if (value === '') { + return true; + } + const reg = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i; + if (value !== '' && reg.test(value)) { + return true; + } else { + return false; + } +} + +export function checkIpV4V6(value: string): boolean { + if (value === '') { + return true; + } + const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`; + const IPv4AddressRegExp = new RegExp(`^${IPv4AddressFormat}$`); + const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})'; + const IPv6AddressRegExp = new RegExp( + '^(' + + `(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` + + `(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` + + `(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` + + `(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` + + `(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` + + `(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` + + ')(%[0-9a-zA-Z-.:]{1,})?$', + ); + if (!IPv4AddressRegExp.test(value) && !IPv6AddressRegExp.test(value) && value !== '') { + return true; + } else { + return false; + } +} + +export function checkIpV6(value: string): boolean { + if (value === '' || typeof value === 'undefined' || value == null) { + return true; + } else { + const IPv4SegmentFormat = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])'; + const IPv4AddressFormat = `(${IPv4SegmentFormat}[.]){3}${IPv4SegmentFormat}`; + const IPv6SegmentFormat = '(?:[0-9a-fA-F]{1,4})'; + const IPv6AddressRegExp = new RegExp( + '^(' + + `(?:${IPv6SegmentFormat}:){7}(?:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){6}(?:${IPv4AddressFormat}|:${IPv6SegmentFormat}|:)|` + + `(?:${IPv6SegmentFormat}:){5}(?::${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,2}|:)|` + + `(?:${IPv6SegmentFormat}:){4}(?:(:${IPv6SegmentFormat}){0,1}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,3}|:)|` + + `(?:${IPv6SegmentFormat}:){3}(?:(:${IPv6SegmentFormat}){0,2}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,4}|:)|` + + `(?:${IPv6SegmentFormat}:){2}(?:(:${IPv6SegmentFormat}){0,3}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,5}|:)|` + + `(?:${IPv6SegmentFormat}:){1}(?:(:${IPv6SegmentFormat}){0,4}:${IPv4AddressFormat}|(:${IPv6SegmentFormat}){1,6}|:)|` + + `(?::((?::${IPv6SegmentFormat}){0,5}:${IPv4AddressFormat}|(?::${IPv6SegmentFormat}){1,7}|:))` + + ')(%[0-9a-zA-Z-.:]{1,})?$', + ); + if (!IPv6AddressRegExp.test(value) && value !== '') { + return true; + } else { + return false; + } + } +} + +export function checkCidr(value: string): boolean { + if (value === '') { + return true; + } + const reg = + /^(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)(?:\/([0-9]|[1-2][0-9]|3[0-2]))?$/; + if (!reg.test(value) && value !== '') { + return true; + } else { + return false; + } +} +export function checkCidrV6(value: string): boolean { + if (value === '') { + return true; + } + if (checkIpV6(value.split('/')[0])) { + return true; + } + const reg = /^(?:[0-9]|[1-9][0-9]|1[0-1][0-9]|12[0-8])$/; + if (!reg.test(value.split('/')[1])) { + return true; + } + return false; +} + +export function checkPort(value: string): boolean { + if (Number(value) <= 0) { + return true; + } + const reg = /^([1-9](\d{0,3}))$|^([1-5]\d{4})$|^(6[0-4]\d{3})$|^(65[0-4]\d{2})$|^(655[0-2]\d)$|^(6553[0-5])$/; + return !reg.test(value) && value !== ''; +} + +export function getProvider(provider: string): string { + switch (provider) { + case 'dnsAccount': + return i18n.global.t('website.dnsAccount'); + case 'dnsManual': + return i18n.global.t('website.dnsManual'); + case 'http': + return 'HTTP'; + case 'selfSigned': + return i18n.global.t('ssl.selfSigned'); + case 'fromMaster': + return i18n.global.t('ssl.fromMaster'); + default: + return i18n.global.t('ssl.manualCreate'); + } +} + +export function transferTimeToSecond(item: string): any { + if (item.indexOf('s') !== -1) { + return Number(item.replaceAll('s', '')); + } + if (item.indexOf('m') !== -1) { + return Number(item.replaceAll('m', '')) * 60; + } + if (item.indexOf('h') !== -1) { + return Number(item.replaceAll('h', '')) * 60 * 60; + } + if (item.indexOf('d') !== -1) { + return Number(item.replaceAll('d', '')) * 60 * 60 * 24; + } + return Number(item); +} +export function splitTime(item: string): any { + if (item.indexOf('s') !== -1) { + return { time: Number(item.replaceAll('s', '')), unit: 's' }; + } + if (item.indexOf('m') !== -1) { + return { time: Number(item.replaceAll('m', '')), unit: 'm' }; + } + if (item.indexOf('h') !== -1) { + return { time: Number(item.replaceAll('h', '')), unit: 'h' }; + } + if (item.indexOf('d') !== -1) { + return { time: Number(item.replaceAll('d', '')), unit: 'd' }; + } + if (item.indexOf('y') !== -1) { + return { time: Number(item.replaceAll('y', '')), unit: 'y' }; + } + return { time: Number(item), unit: 's' }; +} +export function transTimeUnit(val: string): any { + if (val.indexOf('s') !== -1) { + return val.replaceAll('s', i18n.global.t('commons.units.second')); + } + if (val.indexOf('m') !== -1) { + return val.replaceAll('m', i18n.global.t('commons.units.minute')); + } + if (val.indexOf('h') !== -1) { + return val.replaceAll('h', i18n.global.t('commons.units.hour')); + } + if (val.indexOf('d') !== -1) { + return val.replaceAll('d', i18n.global.t('commons.units.day')); + } + if (val.indexOf('y') !== -1) { + return val.replaceAll('y', i18n.global.t('commons.units.year')); + } + return val + i18n.global.t('commons.units.second'); +} +export function splitTimeFromSecond(item: number): any { + if (item < 60) return { timeItem: item, timeUnit: 's' }; + if (item < 3600) return { timeItem: item / 60, timeUnit: 'm' }; + return { timeItem: item / 3600, timeUnit: 'h' }; +} + +export function splitHttp(url: string) { + if (url.indexOf('https://') != -1) { + return { proto: 'https', url: url.replaceAll('https://', '') }; + } + if (url.indexOf('http://') != -1) { + return { proto: 'http', url: url.replaceAll('http://', '') }; + } + return { proto: '', url: url }; +} +export function spliceHttp(proto: string, url: string) { + return proto + '://' + url.replaceAll('https://', '').replaceAll('http://', ''); +} + +export function getAge(d1: string): string { + const dateBegin = new Date(d1); + const dateEnd = new Date(); + const dateDiff = dateEnd.getTime() - dateBegin.getTime(); + const dayDiff = Math.floor(dateDiff / (24 * 3600 * 1000)); + const leave1 = dateDiff % (24 * 3600 * 1000); + const hours = Math.floor(leave1 / (3600 * 1000)); + const leave2 = leave1 % (3600 * 1000); + const minutes = Math.floor(leave2 / (60 * 1000)); + + let res = ''; + if (dayDiff > 0) { + res += String(dayDiff) + ' ' + i18n.global.t('commons.units.day', dayDiff) + ' '; + if (hours <= 0) { + return res; + } + } + if (hours > 0) { + res += String(hours) + ' ' + i18n.global.t('commons.units.hour', hours); + return res; + } + if (minutes > 0) { + res += String(minutes) + ' ' + i18n.global.t('commons.units.minute', minutes); + return res; + } + return i18n.global.t('app.less1Minute'); +} + +export function isJson(str: string) { + try { + if (typeof JSON.parse(str) === 'object') { + return true; + } + } catch { + return false; + } +} + +export function toLowerCase(str: string) { + return str.toLowerCase(); +} + +export function downloadFile(filePath: string, currentNode: string) { + let url = `${import.meta.env.VITE_API_URL as string}/files/download?operateNode=${currentNode}&`; + let path = encodeURIComponent(filePath); + window.open(url + 'path=' + path, '_blank'); +} + +export function downloadWithContent(content: string, fileName: string) { + const downloadUrl = window.URL.createObjectURL(new Blob([content])); + const a = document.createElement('a'); + a.style.display = 'none'; + a.href = downloadUrl; + a.download = fileName; + const event = new MouseEvent('click'); + a.dispatchEvent(event); +} +export function getDateStr() { + let now: Date = new Date(); + + let year: number = now.getFullYear(); + let month: number = now.getMonth() + 1; + let date: number = now.getDate(); + let hours: number = now.getHours(); + let minutes: number = now.getMinutes(); + let seconds: number = now.getSeconds(); + + let timestamp: string = `${year}-${month < 10 ? '0' + month : month}-${date < 10 ? '0' + date : date}-${ + hours < 10 ? '0' + hours : hours + }-${minutes < 10 ? '0' + minutes : minutes}-${seconds < 10 ? '0' + seconds : seconds}`; + + return timestamp; +} + +export function getAccountName(type: string) { + for (const i of AcmeAccountTypes) { + if (i.value === type) { + return i.label; + } + } + return ''; +} + +export function getKeyName(type: string) { + for (const i of KeyTypes) { + if (i.value === type) { + return i.label; + } + } + return ''; +} + +export function getDNSName(type: string) { + for (const i of DNSTypes) { + if (i.value === type) { + return i.label; + } + } + return ''; +} + +export async function copyText(content: string) { + try { + await toClipboard(content); + MsgSuccess(i18n.global.t('commons.msg.copySuccess')); + } catch (e) { + MsgError(i18n.global.t('commons.msg.copyFailed')); + } +} + +export function getAction(action: string) { + if (action == '') { + return ''; + } + return i18n.global.t(`xpack.waf.${action}`); +} + +export function getLanguage() { + return localStorage.getItem('lang') || 'zh'; +} + +export function getLabel(row: any) { + const language = localStorage.getItem('lang') || 'zh'; + let lang = language == 'tw' ? 'zh-Hant' : language; + if (row.label && row.label[lang] != '') { + return row.label[lang]; + } + if (language == 'zh' || language == 'tw') { + return row.labelZh; + } else { + return row.labelEn; + } +} + +export function getDescription(row: any) { + const language = localStorage.getItem('lang') || 'zh'; + let lang = language == 'tw' ? 'zh-Hant' : language; + if (row.description && row.description[lang] != '') { + return row.description[lang]; + } + return ''; +} + +export function emptyLineFilter(str: string, spilt: string) { + let list = str.split(spilt); + let results = []; + for (let i = 0; i < list.length; i++) { + if (list[i].trim() !== '') { + results.push(list[i]); + } + } + return results.join(spilt); +} + +let fileTypes = { + image: ['.jpg', '.jpeg', '.png', '.bmp', '.gif', '.tiff', '.ico', '.svg', '.webp'], + compress: ['.zip', '.rar', '.gz', '.war', '.tgz', '.7z', '.tar.gz', '.tar', '.bz2', '.xz', '.tar.bz2', '.tar.xz'], + video: ['.mp4', '.webm', '.mov', '.wmv', '.mkv', '.avi', '.wma', '.flv'], + audio: ['.mp3', '.wav', '.wma', '.ape', '.acc', '.ogg', '.flac'], + pdf: ['.pdf'], + word: ['.doc', '.docx'], + excel: ['.xls', '.xlsx'], + text: ['.iso', '.tiff', '.exe', '.so', '.bz', '.dmg', '.apk', '.pptx', '.ppt', '.xlsb'], +}; + +export const getFileType = (extension: string) => { + let type = 'text'; + Object.entries(fileTypes).forEach(([key, extensions]) => { + if (extensions.includes(extension.toLowerCase())) { + type = key; + } + }); + return type; +}; + +export const newUUID = () => { + return uuidv4(); +}; + +export const escapeProxyURL = (url: string): string => { + const encodeMap: { [key: string]: string } = { + ':': '%%3A', + '/': '%%2F', + '?': '%%3F', + '#': '%%23', + '[': '%%5B', + ']': '%%5D', + '@': '%%40', + '!': '%%21', + $: '%%24', + '&': '%%26', + "'": '%%27', + '(': '%%28', + ')': '%%29', + '*': '%%2A', + '+': '%%2B', + ',': '%%2C', + ';': '%%3B', + '=': '%%3D', + '%': '%%25', + }; + + return url.replace(/[\/:?#[\]@!$&'()*+,;=%~]/g, (match) => encodeMap[match] || match); +}; + +export function getRuntimeLabel(type: string) { + if (type == 'appstore') { + return i18n.global.t('menu.apps'); + } + if (type == 'local') { + return i18n.global.t('commons.table.local'); + } + return ''; +} + +function getCookie(name: string) { + const value = `; ${document.cookie}`; + const parts = value.split(`; ${name}=`); + if (parts.length === 2) return parts.pop().split(';').shift(); +} + +function rsaEncrypt(data: string, publicKey: string) { + if (!data) { + return data; + } + const jsEncrypt = new JSEncrypt(); + jsEncrypt.setPublicKey(publicKey); + return jsEncrypt.encrypt(data); +} + +function aesEncrypt(data: string, key: string) { + const keyBytes = CryptoJS.enc.Utf8.parse(key); + const iv = CryptoJS.lib.WordArray.random(16); + const encrypted = CryptoJS.AES.encrypt(data, keyBytes, { + iv: iv, + mode: CryptoJS.mode.CBC, + padding: CryptoJS.pad.Pkcs7, + }); + return iv.toString(CryptoJS.enc.Base64) + ':' + encrypted.toString(); +} + +function urlDecode(value: string): string { + return decodeURIComponent(value.replace(/\+/g, ' ')); +} + +function generateAESKey(): string { + const keyLength = 16; + const randomBytes = new Uint8Array(keyLength); + crypto.getRandomValues(randomBytes); + return Array.from(randomBytes) + .map((b) => b.toString(16).padStart(2, '0')) + .join(''); +} + +export const encryptPassword = (password: string) => { + if (!password) { + return ''; + } + let rsaPublicKeyText = getCookie('panel_public_key'); + if (!rsaPublicKeyText) { + return password; + } + rsaPublicKeyText = urlDecode(rsaPublicKeyText); + + const aesKey = generateAESKey(); + rsaPublicKeyText = rsaPublicKeyText.replaceAll('"', ''); + const rsaPublicKey = atob(rsaPublicKeyText); + const keyCipher = rsaEncrypt(aesKey, rsaPublicKey); + const passwordCipher = aesEncrypt(password, aesKey); + return `${keyCipher}:${passwordCipher}`; +}; + +export async function loadJson(lang: string): Promise { + try { + lang = lang == 'zh' ? 'zh' : 'en'; + const jsonModule = await import(`@/assets/json/china/${lang}.json`); + return jsonModule.default; + } catch (error) { + throw new Error(`Language file not found: ${lang}`); + } +} + +export const jumpToPath = (router: any, path: string) => { + routerToPathWithQuery(path, { uncached: 'true' }); +}; + +export const toLink = (link: string) => { + const ipv6Regex = /^https?:\/\/([a-f0-9:]+):(\d+)(\/?.*)?$/i; + try { + if (ipv6Regex.test(link)) { + const match = link.match(ipv6Regex); + if (match) { + const ipv6 = match[1]; + const port = match[2]; + const path = match[3] || ''; + link = `${link.startsWith('https') ? 'https' : 'http'}://[${ipv6}]:${port}${path}`; + } + } + window.open(link, '_blank'); + } catch (e) {} +}; + +export const preloadImage = (url: string): Promise => { + return new Promise((resolve) => { + const img = new Image(); + img.onload = () => resolve(url); + img.onerror = () => resolve(url); + img.src = url; + }); +}; + +export const isSensitiveLinuxPath = (path) => { + let sensitivePath = [ + '/', + '/bin', + '/sbin', + '/usr/bin', + '/usr/sbin', + '/usr/local/bin', + '/etc', + '/lib', + '/lib64', + '/usr/lib', + '/home', + '/tmp', + '/var', + '/dev', + '/proc', + '/sys', + ]; + return sensitivePath.indexOf(path) !== -1; +}; + +const convertTypes = ['image', 'video', 'audio'] as const; +type ConvertType = (typeof convertTypes)[number]; + +export function isConvertible(extension: string, mimeType: string): boolean { + return convertTypes.includes(getFileType(extension) as ConvertType) && /^(image|audio|video)\//.test(mimeType); +} + +function compareById(a, b) { + return a.sort - b.sort; +} + +export function sortMenu(arr) { + if (!arr || arr.length === 0) { + return; + } + arr.forEach((item) => { + if (item.children && Array.isArray(item.children)) { + item.children.sort(compareById); + } + }); + + arr.sort(compareById); +} + +export function isPunycoded(domain: string): boolean { + return domain.includes('xn--'); +} + +export function GetPunyCodeDomain(domain: string): string { + if (!domain || typeof domain !== 'string') { + return ''; + } + + const lowerDomain = domain.toLowerCase(); + if (!lowerDomain.includes('xn--')) { + return ''; + } + + try { + const decoded = toUnicode(domain); + + if (decoded === domain) { + return ''; + } + + return decoded; + } catch (error) { + return ''; + } +} + +export function base64UrlToBuffer(base64url: string): ArrayBuffer { + let base64 = base64url.replace(/-/g, '+').replace(/_/g, '/'); + const pad = base64.length % 4; + if (pad) { + base64 += '='.repeat(4 - pad); + } + const binary = window.atob(base64); + const bytes = new Uint8Array(binary.length); + for (let i = 0; i < binary.length; i++) { + bytes[i] = binary.charCodeAt(i); + } + return bytes.buffer; +} + +export function bufferToBase64Url(buffer: ArrayBuffer): string { + const bytes = new Uint8Array(buffer); + let binary = ''; + bytes.forEach((byte) => { + binary += String.fromCharCode(byte); + }); + const base64 = window.btoa(binary); + return base64.replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +} diff --git a/frontend/src/utils/version.ts b/frontend/src/utils/version.ts new file mode 100644 index 0000000..f8bd374 --- /dev/null +++ b/frontend/src/utils/version.ts @@ -0,0 +1,42 @@ +export function compareVersion(version1: string, version2: string): boolean { + const v1s = extractNumbers(version1); + const v2s = extractNumbers(version2); + + const maxLen = Math.max(v1s.length, v2s.length); + v1s.push(...new Array(maxLen - v1s.length).fill('0')); + v2s.push(...new Array(maxLen - v2s.length).fill('0')); + + for (let i = 0; i < maxLen; i++) { + const v1 = parseInt(v1s[i], 10); + const v2 = parseInt(v2s[i], 10); + if (v1 !== v2) { + return v1 > v2; + } + } + return true; +} + +function extractNumbers(version: string): string[] { + const numbers: string[] = []; + let start = -1; + for (let i = 0; i < version.length; i++) { + if (isDigit(version[i])) { + if (start === -1) { + start = i; + } + } else { + if (start !== -1) { + numbers.push(version.slice(start, i)); + start = -1; + } + } + } + if (start !== -1) { + numbers.push(version.slice(start)); + } + return numbers; +} + +function isDigit(char: string): boolean { + return /^\d$/.test(char); +} diff --git a/frontend/src/utils/xpack.ts b/frontend/src/utils/xpack.ts new file mode 100644 index 0000000..5794939 --- /dev/null +++ b/frontend/src/utils/xpack.ts @@ -0,0 +1,141 @@ +import { getLicenseStatus, getMasterLicenseStatus, getSettingInfo } from '@/api/modules/setting'; +import { useTheme } from '@/global/use-theme'; +import { GlobalStore } from '@/store'; +const globalStore = GlobalStore(); +const { switchTheme } = useTheme(); +import faviconUrl from '@/assets/images/favicon.svg'; + +export function resetXSetting() { + globalStore.themeConfig.title = ''; + globalStore.themeConfig.logo = ''; + globalStore.themeConfig.logoWithText = ''; + globalStore.themeConfig.favicon = ''; + globalStore.watermark = null; + globalStore.watermarkShow = false; + globalStore.masterAlias = ''; +} + +async function getColoredFavicon(url: string, color: string) { + const res = await fetch(url); + let svgText = await res.text(); + svgText = svgText.replace(/fill=(["'])(.*?)\1/g, `fill="${color}"`); + return `data:image/svg+xml,${encodeURIComponent(svgText)}`; +} + +export async function initFavicon() { + document.title = globalStore.themeConfig.panelName; + const favicon = globalStore.themeConfig.favicon; + const isPro = globalStore.isMasterProductPro; + const themeColor = globalStore.themeConfig.primary; + const customFaviconUrl = `/api/v2/images/favicon?t=${Date.now()}`; + const fallbackSvg = isPro ? await getColoredFavicon(faviconUrl, themeColor) : '/public/favicon.png'; + const setLink = (href: string) => { + let link = document.querySelector("link[rel*='icon']") as HTMLLinkElement; + if (!link) { + link = document.createElement('link'); + link.rel = 'shortcut icon'; + link.type = 'image/x-icon'; + document.head.appendChild(link); + } + link.href = href; + }; + + if (favicon) { + const testImg = new Image(); + testImg.onload = () => setLink(customFaviconUrl); + testImg.onerror = () => setLink(fallbackSvg); + testImg.src = customFaviconUrl; + } else { + setLink(fallbackSvg); + } +} + +export async function getXpackSetting() { + let searchXSetting; + const xpackModules = import.meta.glob('../xpack/api/modules/setting.ts', { eager: true }); + if (xpackModules['../xpack/api/modules/setting.ts']) { + searchXSetting = xpackModules['../xpack/api/modules/setting.ts']['searchXSetting'] || {}; + const res = await searchXSetting(); + if (!res) { + initFavicon(); + resetXSetting(); + return; + } + initFavicon(); + return res; + } +} + +const loadDataFromDB = async () => { + const res = await getSettingInfo(); + document.title = res.data.panelName; + globalStore.entrance = res.data.securityEntrance; + globalStore.setOpenMenuTabs(res.data.menuTabs === 'Enable'); +}; + +export async function loadProductProFromDB() { + const res = await getLicenseStatus(); + if (!res || !res.data) { + globalStore.isProductPro = false; + } else { + globalStore.isProductPro = res.data.status === 'Bound'; + if (globalStore.isProductPro) { + globalStore.productProExpires = Number(res.data.productPro); + } + } +} + +export async function loadMasterProductProFromDB() { + const res = await getMasterLicenseStatus(); + if (!res || !res.data) { + globalStore.isMasterProductPro = false; + } else { + globalStore.isMasterProductPro = res.data.status === 'Bound'; + } + switchTheme(); + initFavicon(); + loadDataFromDB(); +} + +export async function getXpackSettingForTheme() { + let searchXSetting; + const xpackModules = import.meta.glob('../xpack/api/modules/setting.ts', { eager: true }); + if (xpackModules['../xpack/api/modules/setting.ts']) { + searchXSetting = xpackModules['../xpack/api/modules/setting.ts']['searchXSetting'] || {}; + const res2 = await searchXSetting(); + if (res2) { + globalStore.themeConfig.title = res2.data?.title; + globalStore.themeConfig.logo = res2.data?.logo; + globalStore.themeConfig.logoWithText = res2.data?.logoWithText; + globalStore.themeConfig.favicon = res2.data?.favicon; + globalStore.themeConfig.loginImage = res2.data?.loginImage; + globalStore.themeConfig.loginBgType = res2.data?.loginBgType; + globalStore.themeConfig.loginBackground = res2.data?.loginBackground; + globalStore.themeConfig.loginBtnLinkColor = res2.data?.loginBtnLinkColor; + globalStore.themeConfig.themeColor = res2.data?.themeColor; + globalStore.masterAlias = res2.data.masterAlias; + if (res2.data?.theme) { + globalStore.themeConfig.theme = res2.data.theme; + } + globalStore.watermarkShow = res2.data.watermarkShow === 'Enable'; + try { + globalStore.watermark = JSON.parse(res2.data.watermark); + } catch { + globalStore.watermark = null; + } + } else { + resetXSetting(); + } + } + switchTheme(); + initFavicon(); +} + +export async function updateXpackSettingByKey(key: string, value: string) { + let updateXSettingByKey; + const xpackModules = import.meta.glob('../xpack/api/modules/setting.ts', { eager: true }); + if (xpackModules['../xpack/api/modules/setting.ts']) { + updateXSettingByKey = xpackModules['../xpack/api/modules/setting.ts']['updateXSettingByKey'] || {}; + return updateXSettingByKey(key, value); + } +} diff --git a/frontend/src/views/ai/gpu/current/index.vue b/frontend/src/views/ai/gpu/current/index.vue new file mode 100644 index 0000000..1269580 --- /dev/null +++ b/frontend/src/views/ai/gpu/current/index.vue @@ -0,0 +1,349 @@ + + + + + diff --git a/frontend/src/views/ai/gpu/history/index.vue b/frontend/src/views/ai/gpu/history/index.vue new file mode 100644 index 0000000..0bd0bf9 --- /dev/null +++ b/frontend/src/views/ai/gpu/history/index.vue @@ -0,0 +1,545 @@ + + + + + diff --git a/frontend/src/views/ai/gpu/index.vue b/frontend/src/views/ai/gpu/index.vue new file mode 100644 index 0000000..9a30634 --- /dev/null +++ b/frontend/src/views/ai/gpu/index.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/views/ai/mcp/index.vue b/frontend/src/views/ai/mcp/index.vue new file mode 100644 index 0000000..712f614 --- /dev/null +++ b/frontend/src/views/ai/mcp/index.vue @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/bind/index.vue b/frontend/src/views/ai/mcp/server/bind/index.vue new file mode 100644 index 0000000..91d88e2 --- /dev/null +++ b/frontend/src/views/ai/mcp/server/bind/index.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/frontend/src/views/ai/mcp/server/config/index.vue b/frontend/src/views/ai/mcp/server/config/index.vue new file mode 100644 index 0000000..a9d75c5 --- /dev/null +++ b/frontend/src/views/ai/mcp/server/config/index.vue @@ -0,0 +1,54 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/import/index.vue b/frontend/src/views/ai/mcp/server/import/index.vue new file mode 100644 index 0000000..ced6c3c --- /dev/null +++ b/frontend/src/views/ai/mcp/server/import/index.vue @@ -0,0 +1,80 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/index.vue b/frontend/src/views/ai/mcp/server/index.vue new file mode 100644 index 0000000..b5da16f --- /dev/null +++ b/frontend/src/views/ai/mcp/server/index.vue @@ -0,0 +1,271 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/operate/index.vue b/frontend/src/views/ai/mcp/server/operate/index.vue new file mode 100644 index 0000000..d46b2a6 --- /dev/null +++ b/frontend/src/views/ai/mcp/server/operate/index.vue @@ -0,0 +1,310 @@ + + + diff --git a/frontend/src/views/ai/mcp/server/volume/index.vue b/frontend/src/views/ai/mcp/server/volume/index.vue new file mode 100644 index 0000000..3661506 --- /dev/null +++ b/frontend/src/views/ai/mcp/server/volume/index.vue @@ -0,0 +1,60 @@ + + + diff --git a/frontend/src/views/ai/model/index.vue b/frontend/src/views/ai/model/index.vue new file mode 100644 index 0000000..53b3a73 --- /dev/null +++ b/frontend/src/views/ai/model/index.vue @@ -0,0 +1,21 @@ + + + diff --git a/frontend/src/views/ai/model/ollama/add/index.vue b/frontend/src/views/ai/model/ollama/add/index.vue new file mode 100644 index 0000000..c774b58 --- /dev/null +++ b/frontend/src/views/ai/model/ollama/add/index.vue @@ -0,0 +1,87 @@ + + + diff --git a/frontend/src/views/ai/model/ollama/conn/index.vue b/frontend/src/views/ai/model/ollama/conn/index.vue new file mode 100644 index 0000000..7a1cc99 --- /dev/null +++ b/frontend/src/views/ai/model/ollama/conn/index.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/frontend/src/views/ai/model/ollama/del/index.vue b/frontend/src/views/ai/model/ollama/del/index.vue new file mode 100644 index 0000000..bd2e5e2 --- /dev/null +++ b/frontend/src/views/ai/model/ollama/del/index.vue @@ -0,0 +1,103 @@ + + + diff --git a/frontend/src/views/ai/model/ollama/domain/index.vue b/frontend/src/views/ai/model/ollama/domain/index.vue new file mode 100644 index 0000000..d1a46b9 --- /dev/null +++ b/frontend/src/views/ai/model/ollama/domain/index.vue @@ -0,0 +1,242 @@ + + + + + diff --git a/frontend/src/views/ai/model/ollama/index.vue b/frontend/src/views/ai/model/ollama/index.vue new file mode 100644 index 0000000..94eb24f --- /dev/null +++ b/frontend/src/views/ai/model/ollama/index.vue @@ -0,0 +1,461 @@ + + + + + diff --git a/frontend/src/views/ai/model/ollama/terminal/index.vue b/frontend/src/views/ai/model/ollama/terminal/index.vue new file mode 100644 index 0000000..0d83e19 --- /dev/null +++ b/frontend/src/views/ai/model/ollama/terminal/index.vue @@ -0,0 +1,70 @@ + + + diff --git a/frontend/src/views/ai/model/tensorrt/index.vue b/frontend/src/views/ai/model/tensorrt/index.vue new file mode 100644 index 0000000..57a8a2f --- /dev/null +++ b/frontend/src/views/ai/model/tensorrt/index.vue @@ -0,0 +1,227 @@ + + + diff --git a/frontend/src/views/ai/model/tensorrt/operate/index.vue b/frontend/src/views/ai/model/tensorrt/operate/index.vue new file mode 100644 index 0000000..661c301 --- /dev/null +++ b/frontend/src/views/ai/model/tensorrt/operate/index.vue @@ -0,0 +1,193 @@ + + + diff --git a/frontend/src/views/app-store/apps/app/index.vue b/frontend/src/views/app-store/apps/app/index.vue new file mode 100644 index 0000000..53996dc --- /dev/null +++ b/frontend/src/views/app-store/apps/app/index.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/frontend/src/views/app-store/apps/index.vue b/frontend/src/views/app-store/apps/index.vue new file mode 100644 index 0000000..5e260e9 --- /dev/null +++ b/frontend/src/views/app-store/apps/index.vue @@ -0,0 +1,278 @@ + + + + + diff --git a/frontend/src/views/app-store/apps/no-app/index.vue b/frontend/src/views/app-store/apps/no-app/index.vue new file mode 100644 index 0000000..4abaf4c --- /dev/null +++ b/frontend/src/views/app-store/apps/no-app/index.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/views/app-store/components/tag.vue b/frontend/src/views/app-store/components/tag.vue new file mode 100644 index 0000000..4e950c8 --- /dev/null +++ b/frontend/src/views/app-store/components/tag.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/frontend/src/views/app-store/detail/form/index.vue b/frontend/src/views/app-store/detail/form/index.vue new file mode 100644 index 0000000..926fde7 --- /dev/null +++ b/frontend/src/views/app-store/detail/form/index.vue @@ -0,0 +1,429 @@ + + + diff --git a/frontend/src/views/app-store/detail/index.vue b/frontend/src/views/app-store/detail/index.vue new file mode 100644 index 0000000..68800d4 --- /dev/null +++ b/frontend/src/views/app-store/detail/index.vue @@ -0,0 +1,187 @@ + + + + + diff --git a/frontend/src/views/app-store/detail/install/index.vue b/frontend/src/views/app-store/detail/install/index.vue new file mode 100644 index 0000000..91f3e6f --- /dev/null +++ b/frontend/src/views/app-store/detail/install/index.vue @@ -0,0 +1,139 @@ + + + diff --git a/frontend/src/views/app-store/detail/params/index.vue b/frontend/src/views/app-store/detail/params/index.vue new file mode 100644 index 0000000..74a1f34 --- /dev/null +++ b/frontend/src/views/app-store/detail/params/index.vue @@ -0,0 +1,323 @@ + + diff --git a/frontend/src/views/app-store/index.scss b/frontend/src/views/app-store/index.scss new file mode 100644 index 0000000..8c57c72 --- /dev/null +++ b/frontend/src/views/app-store/index.scss @@ -0,0 +1,150 @@ +.install-card { + margin-top: 10px; + cursor: pointer; + padding: 10px; + + .icon { + text-align: center; + } + + .a-detail { + .d-name { + .name { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + font-weight: 500; + font-size: 18px; + color: var(--panel-text-color); + + max-width: 100px; + + @media (min-width: 576px) { + max-width: 120px; + } + + @media (min-width: 768px) { + max-width: 150px; + } + + @media (min-width: 992px) { + max-width: 180px; + } + + @media (min-width: 1200px) { + max-width: 220px; + } + + @media (min-width: 1600px) { + max-width: 280px; + } + + @media (min-width: 1920px) { + max-width: 350px; + } + } + + .status { + margin-left: 5px; + } + + .h-button { + float: right; + } + + .msg { + margin-left: 5px; + } + } + + .d-description { + margin-top: 10px; + overflow: hidden; + + .el-tag { + margin-right: 5px; + } + } + + .description { + margin-top: 10px; + font-size: 14px; + color: var(--el-text-color-regular); + } + + .d-button { + margin-top: 10px; + min-width: 330px; + } + } + + .e-card { + border: var(--panel-border) !important; + + &:hover { + cursor: pointer; + border: 1px solid var(--el-color-primary) !important; + } + } +} + +.table-button { + display: inline; + margin-right: 5px; +} + +.app-divider { + margin-top: 5px; + border: 0; + border-top: var(--panel-border); +} + +.update-prompt { + text-align: center; + margin-top: 100px; + + span { + color: #bbbfc4; + } + + img { + width: 300px; + height: 300px; + } +} + +.tag-button { + &.no-active { + background: none; + border: none; + } +} + +.page-button { + float: right; + margin-bottom: 10px; + margin-top: 10px; +} + +@media (max-width: 575px) { + .install-card { + .a-detail { + .d-name { + .h-button { + float: none; + display: inline-block; + margin-left: 5px; + } + } + + .d-button { + min-width: auto; + + .app-button { + font-size: 12px; + padding: 5px 10px; + } + } + } + } +} diff --git a/frontend/src/views/app-store/index.vue b/frontend/src/views/app-store/index.vue new file mode 100644 index 0000000..c3c4c6b --- /dev/null +++ b/frontend/src/views/app-store/index.vue @@ -0,0 +1,60 @@ + + + diff --git a/frontend/src/views/app-store/installed/app/card.vue b/frontend/src/views/app-store/installed/app/card.vue new file mode 100644 index 0000000..57a1c6b --- /dev/null +++ b/frontend/src/views/app-store/installed/app/card.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/frontend/src/views/app-store/installed/app/header.vue b/frontend/src/views/app-store/installed/app/header.vue new file mode 100644 index 0000000..2b4ce6a --- /dev/null +++ b/frontend/src/views/app-store/installed/app/header.vue @@ -0,0 +1,193 @@ + + + + diff --git a/frontend/src/views/app-store/installed/app/icon.vue b/frontend/src/views/app-store/installed/app/icon.vue new file mode 100644 index 0000000..13cf0ec --- /dev/null +++ b/frontend/src/views/app-store/installed/app/icon.vue @@ -0,0 +1,17 @@ + + + diff --git a/frontend/src/views/app-store/installed/app/info.vue b/frontend/src/views/app-store/installed/app/info.vue new file mode 100644 index 0000000..da3c848 --- /dev/null +++ b/frontend/src/views/app-store/installed/app/info.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/frontend/src/views/app-store/installed/check/index.vue b/frontend/src/views/app-store/installed/check/index.vue new file mode 100644 index 0000000..8a408df --- /dev/null +++ b/frontend/src/views/app-store/installed/check/index.vue @@ -0,0 +1,142 @@ + + + + diff --git a/frontend/src/views/app-store/installed/delete/index.vue b/frontend/src/views/app-store/installed/delete/index.vue new file mode 100644 index 0000000..05f3010 --- /dev/null +++ b/frontend/src/views/app-store/installed/delete/index.vue @@ -0,0 +1,130 @@ + + diff --git a/frontend/src/views/app-store/installed/detail/index.vue b/frontend/src/views/app-store/installed/detail/index.vue new file mode 100644 index 0000000..5014c5d --- /dev/null +++ b/frontend/src/views/app-store/installed/detail/index.vue @@ -0,0 +1,440 @@ + + + + diff --git a/frontend/src/views/app-store/installed/ignore/create/index.vue b/frontend/src/views/app-store/installed/ignore/create/index.vue new file mode 100644 index 0000000..9193f4a --- /dev/null +++ b/frontend/src/views/app-store/installed/ignore/create/index.vue @@ -0,0 +1,93 @@ + + + diff --git a/frontend/src/views/app-store/installed/ignore/index.vue b/frontend/src/views/app-store/installed/ignore/index.vue new file mode 100644 index 0000000..8b3b785 --- /dev/null +++ b/frontend/src/views/app-store/installed/ignore/index.vue @@ -0,0 +1,79 @@ + + + + diff --git a/frontend/src/views/app-store/installed/index.vue b/frontend/src/views/app-store/installed/index.vue new file mode 100644 index 0000000..0f85cd1 --- /dev/null +++ b/frontend/src/views/app-store/installed/index.vue @@ -0,0 +1,479 @@ + + + + + diff --git a/frontend/src/views/app-store/installed/upgrade/diff/index.vue b/frontend/src/views/app-store/installed/upgrade/diff/index.vue new file mode 100644 index 0000000..607fcab --- /dev/null +++ b/frontend/src/views/app-store/installed/upgrade/diff/index.vue @@ -0,0 +1,92 @@ + + + + + diff --git a/frontend/src/views/app-store/installed/upgrade/index.vue b/frontend/src/views/app-store/installed/upgrade/index.vue new file mode 100644 index 0000000..34a0f12 --- /dev/null +++ b/frontend/src/views/app-store/installed/upgrade/index.vue @@ -0,0 +1,270 @@ + + diff --git a/frontend/src/views/app-store/setting/index.vue b/frontend/src/views/app-store/setting/index.vue new file mode 100644 index 0000000..ae838ce --- /dev/null +++ b/frontend/src/views/app-store/setting/index.vue @@ -0,0 +1,153 @@ + + + + + diff --git a/frontend/src/views/container/compose/delete/index.vue b/frontend/src/views/container/compose/delete/index.vue new file mode 100644 index 0000000..a90547e --- /dev/null +++ b/frontend/src/views/container/compose/delete/index.vue @@ -0,0 +1,104 @@ + + + + diff --git a/frontend/src/views/container/compose/index.vue b/frontend/src/views/container/compose/index.vue new file mode 100644 index 0000000..971b34d --- /dev/null +++ b/frontend/src/views/container/compose/index.vue @@ -0,0 +1,773 @@ + + + + + diff --git a/frontend/src/views/container/container/command/index.vue b/frontend/src/views/container/container/command/index.vue new file mode 100644 index 0000000..5e270c9 --- /dev/null +++ b/frontend/src/views/container/container/command/index.vue @@ -0,0 +1,111 @@ + + + diff --git a/frontend/src/views/container/container/commit/index.vue b/frontend/src/views/container/container/commit/index.vue new file mode 100644 index 0000000..d999816 --- /dev/null +++ b/frontend/src/views/container/container/commit/index.vue @@ -0,0 +1,126 @@ + + + diff --git a/frontend/src/views/container/container/index.vue b/frontend/src/views/container/container/index.vue new file mode 100644 index 0000000..b9a1ed6 --- /dev/null +++ b/frontend/src/views/container/container/index.vue @@ -0,0 +1,845 @@ + + + + + diff --git a/frontend/src/views/container/container/inspect/index.vue b/frontend/src/views/container/container/inspect/index.vue new file mode 100644 index 0000000..aa46f3a --- /dev/null +++ b/frontend/src/views/container/container/inspect/index.vue @@ -0,0 +1,306 @@ + + + + + diff --git a/frontend/src/views/container/container/monitor/index.vue b/frontend/src/views/container/container/monitor/index.vue new file mode 100644 index 0000000..a46d87b --- /dev/null +++ b/frontend/src/views/container/container/monitor/index.vue @@ -0,0 +1,231 @@ + + + diff --git a/frontend/src/views/container/container/operate/confirm.vue b/frontend/src/views/container/container/operate/confirm.vue new file mode 100644 index 0000000..7cadf13 --- /dev/null +++ b/frontend/src/views/container/container/operate/confirm.vue @@ -0,0 +1,55 @@ + + + diff --git a/frontend/src/views/container/container/operate/index.vue b/frontend/src/views/container/container/operate/index.vue new file mode 100644 index 0000000..71f09b1 --- /dev/null +++ b/frontend/src/views/container/container/operate/index.vue @@ -0,0 +1,715 @@ + + + + + diff --git a/frontend/src/views/container/container/operate/network.vue b/frontend/src/views/container/container/operate/network.vue new file mode 100644 index 0000000..fc2b0f3 --- /dev/null +++ b/frontend/src/views/container/container/operate/network.vue @@ -0,0 +1,113 @@ + + + diff --git a/frontend/src/views/container/container/operate/volume.vue b/frontend/src/views/container/container/operate/volume.vue new file mode 100644 index 0000000..a3476d2 --- /dev/null +++ b/frontend/src/views/container/container/operate/volume.vue @@ -0,0 +1,156 @@ + + + + + diff --git a/frontend/src/views/container/container/prune/index.vue b/frontend/src/views/container/container/prune/index.vue new file mode 100644 index 0000000..124268d --- /dev/null +++ b/frontend/src/views/container/container/prune/index.vue @@ -0,0 +1,74 @@ + + + + + diff --git a/frontend/src/views/container/container/rename/index.vue b/frontend/src/views/container/container/rename/index.vue new file mode 100644 index 0000000..f705336 --- /dev/null +++ b/frontend/src/views/container/container/rename/index.vue @@ -0,0 +1,89 @@ + + + diff --git a/frontend/src/views/container/container/terminal/index.vue b/frontend/src/views/container/container/terminal/index.vue new file mode 100644 index 0000000..be07120 --- /dev/null +++ b/frontend/src/views/container/container/terminal/index.vue @@ -0,0 +1,137 @@ + + + diff --git a/frontend/src/views/container/container/upgrade/index.vue b/frontend/src/views/container/container/upgrade/index.vue new file mode 100644 index 0000000..b0afddf --- /dev/null +++ b/frontend/src/views/container/container/upgrade/index.vue @@ -0,0 +1,185 @@ + + + diff --git a/frontend/src/views/container/dashboard/index.vue b/frontend/src/views/container/dashboard/index.vue new file mode 100644 index 0000000..9cd417f --- /dev/null +++ b/frontend/src/views/container/dashboard/index.vue @@ -0,0 +1,351 @@ + + + + + diff --git a/frontend/src/views/container/docker-status/index.vue b/frontend/src/views/container/docker-status/index.vue new file mode 100644 index 0000000..d202f9c --- /dev/null +++ b/frontend/src/views/container/docker-status/index.vue @@ -0,0 +1,51 @@ + + + diff --git a/frontend/src/views/container/image/build/index.vue b/frontend/src/views/container/image/build/index.vue new file mode 100644 index 0000000..c3ae531 --- /dev/null +++ b/frontend/src/views/container/image/build/index.vue @@ -0,0 +1,131 @@ + + + diff --git a/frontend/src/views/container/image/delete/index.vue b/frontend/src/views/container/image/delete/index.vue new file mode 100644 index 0000000..f30cc55 --- /dev/null +++ b/frontend/src/views/container/image/delete/index.vue @@ -0,0 +1,118 @@ + + diff --git a/frontend/src/views/container/image/index.vue b/frontend/src/views/container/image/index.vue new file mode 100644 index 0000000..5021414 --- /dev/null +++ b/frontend/src/views/container/image/index.vue @@ -0,0 +1,359 @@ + + + diff --git a/frontend/src/views/container/image/load/index.vue b/frontend/src/views/container/image/load/index.vue new file mode 100644 index 0000000..f1d3865 --- /dev/null +++ b/frontend/src/views/container/image/load/index.vue @@ -0,0 +1,96 @@ + + + diff --git a/frontend/src/views/container/image/prune/index.vue b/frontend/src/views/container/image/prune/index.vue new file mode 100644 index 0000000..6ba356f --- /dev/null +++ b/frontend/src/views/container/image/prune/index.vue @@ -0,0 +1,180 @@ + + + diff --git a/frontend/src/views/container/image/pull/index.vue b/frontend/src/views/container/image/pull/index.vue new file mode 100644 index 0000000..789eb5d --- /dev/null +++ b/frontend/src/views/container/image/pull/index.vue @@ -0,0 +1,136 @@ + + + diff --git a/frontend/src/views/container/image/push/index.vue b/frontend/src/views/container/image/push/index.vue new file mode 100644 index 0000000..6ff799a --- /dev/null +++ b/frontend/src/views/container/image/push/index.vue @@ -0,0 +1,117 @@ + + + diff --git a/frontend/src/views/container/image/save/index.vue b/frontend/src/views/container/image/save/index.vue new file mode 100644 index 0000000..6e9ca40 --- /dev/null +++ b/frontend/src/views/container/image/save/index.vue @@ -0,0 +1,123 @@ + + + diff --git a/frontend/src/views/container/image/tag/index.vue b/frontend/src/views/container/image/tag/index.vue new file mode 100644 index 0000000..e76e19b --- /dev/null +++ b/frontend/src/views/container/image/tag/index.vue @@ -0,0 +1,170 @@ + + + diff --git a/frontend/src/views/container/index.vue b/frontend/src/views/container/index.vue new file mode 100644 index 0000000..d46671b --- /dev/null +++ b/frontend/src/views/container/index.vue @@ -0,0 +1,51 @@ + + + diff --git a/frontend/src/views/container/network/create/index.vue b/frontend/src/views/container/network/create/index.vue new file mode 100644 index 0000000..997c56e --- /dev/null +++ b/frontend/src/views/container/network/create/index.vue @@ -0,0 +1,321 @@ + + + diff --git a/frontend/src/views/container/network/detail/index.vue b/frontend/src/views/container/network/detail/index.vue new file mode 100644 index 0000000..b12eca8 --- /dev/null +++ b/frontend/src/views/container/network/detail/index.vue @@ -0,0 +1,199 @@ + + + + + diff --git a/frontend/src/views/container/network/index.vue b/frontend/src/views/container/network/index.vue new file mode 100644 index 0000000..d12e42d --- /dev/null +++ b/frontend/src/views/container/network/index.vue @@ -0,0 +1,238 @@ + + + diff --git a/frontend/src/views/container/repo/index.vue b/frontend/src/views/container/repo/index.vue new file mode 100644 index 0000000..fe0588f --- /dev/null +++ b/frontend/src/views/container/repo/index.vue @@ -0,0 +1,199 @@ + + + diff --git a/frontend/src/views/container/repo/operator/index.vue b/frontend/src/views/container/repo/operator/index.vue new file mode 100644 index 0000000..5da60cf --- /dev/null +++ b/frontend/src/views/container/repo/operator/index.vue @@ -0,0 +1,195 @@ + + + diff --git a/frontend/src/views/container/setting/index.vue b/frontend/src/views/container/setting/index.vue new file mode 100644 index 0000000..bbb83bd --- /dev/null +++ b/frontend/src/views/container/setting/index.vue @@ -0,0 +1,517 @@ + + + diff --git a/frontend/src/views/container/setting/ipv6/index.vue b/frontend/src/views/container/setting/ipv6/index.vue new file mode 100644 index 0000000..84babd6 --- /dev/null +++ b/frontend/src/views/container/setting/ipv6/index.vue @@ -0,0 +1,143 @@ + + diff --git a/frontend/src/views/container/setting/log/index.vue b/frontend/src/views/container/setting/log/index.vue new file mode 100644 index 0000000..444b6c3 --- /dev/null +++ b/frontend/src/views/container/setting/log/index.vue @@ -0,0 +1,135 @@ + + diff --git a/frontend/src/views/container/setting/mirror/index.vue b/frontend/src/views/container/setting/mirror/index.vue new file mode 100644 index 0000000..b936e73 --- /dev/null +++ b/frontend/src/views/container/setting/mirror/index.vue @@ -0,0 +1,114 @@ + + diff --git a/frontend/src/views/container/setting/registry/index.vue b/frontend/src/views/container/setting/registry/index.vue new file mode 100644 index 0000000..8624a95 --- /dev/null +++ b/frontend/src/views/container/setting/registry/index.vue @@ -0,0 +1,109 @@ + + diff --git a/frontend/src/views/container/setting/sock-path/index.vue b/frontend/src/views/container/setting/sock-path/index.vue new file mode 100644 index 0000000..2010aa1 --- /dev/null +++ b/frontend/src/views/container/setting/sock-path/index.vue @@ -0,0 +1,112 @@ + + diff --git a/frontend/src/views/container/template/detail/index.vue b/frontend/src/views/container/template/detail/index.vue new file mode 100644 index 0000000..4ff9c0b --- /dev/null +++ b/frontend/src/views/container/template/detail/index.vue @@ -0,0 +1,39 @@ + + + diff --git a/frontend/src/views/container/template/import/index.vue b/frontend/src/views/container/template/import/index.vue new file mode 100644 index 0000000..199528a --- /dev/null +++ b/frontend/src/views/container/template/import/index.vue @@ -0,0 +1,224 @@ + + + diff --git a/frontend/src/views/container/template/index.vue b/frontend/src/views/container/template/index.vue new file mode 100644 index 0000000..e086dbd --- /dev/null +++ b/frontend/src/views/container/template/index.vue @@ -0,0 +1,213 @@ + + + diff --git a/frontend/src/views/container/template/operator/index.vue b/frontend/src/views/container/template/operator/index.vue new file mode 100644 index 0000000..dd452b3 --- /dev/null +++ b/frontend/src/views/container/template/operator/index.vue @@ -0,0 +1,122 @@ + + + diff --git a/frontend/src/views/container/volume/create/index.vue b/frontend/src/views/container/volume/create/index.vue new file mode 100644 index 0000000..c5d0e6d --- /dev/null +++ b/frontend/src/views/container/volume/create/index.vue @@ -0,0 +1,151 @@ + + + diff --git a/frontend/src/views/container/volume/index.vue b/frontend/src/views/container/volume/index.vue new file mode 100644 index 0000000..13ca633 --- /dev/null +++ b/frontend/src/views/container/volume/index.vue @@ -0,0 +1,268 @@ + + + diff --git a/frontend/src/views/cronjob/cronjob/backup/index.vue b/frontend/src/views/cronjob/cronjob/backup/index.vue new file mode 100644 index 0000000..a89b46e --- /dev/null +++ b/frontend/src/views/cronjob/cronjob/backup/index.vue @@ -0,0 +1,171 @@ + + + diff --git a/frontend/src/views/cronjob/cronjob/config/clean-log.vue b/frontend/src/views/cronjob/cronjob/config/clean-log.vue new file mode 100644 index 0000000..09ef32d --- /dev/null +++ b/frontend/src/views/cronjob/cronjob/config/clean-log.vue @@ -0,0 +1,31 @@ + + + diff --git a/frontend/src/views/cronjob/cronjob/helper.ts b/frontend/src/views/cronjob/cronjob/helper.ts new file mode 100644 index 0000000..94e3849 --- /dev/null +++ b/frontend/src/views/cronjob/cronjob/helper.ts @@ -0,0 +1,227 @@ +import { Cronjob } from '@/api/interface/cronjob'; +import i18n from '@/lang'; +import { loadZero } from '@/utils/util'; + +export const specOptions = [ + { label: i18n.global.t('cronjob.perMonth'), value: 'perMonth' }, + { label: i18n.global.t('cronjob.perWeek'), value: 'perWeek' }, + { label: i18n.global.t('cronjob.perDay'), value: 'perDay' }, + { label: i18n.global.t('cronjob.perHour'), value: 'perHour' }, + { label: i18n.global.t('cronjob.perNDay'), value: 'perNDay' }, + { label: i18n.global.t('cronjob.perNHour'), value: 'perNHour' }, + { label: i18n.global.t('cronjob.perNMinute'), value: 'perNMinute' }, + { label: i18n.global.t('cronjob.perNSecond'), value: 'perNSecond' }, +]; +export const weekOptions = [ + { label: i18n.global.t('cronjob.monday'), value: 1 }, + { label: i18n.global.t('cronjob.tuesday'), value: 2 }, + { label: i18n.global.t('cronjob.wednesday'), value: 3 }, + { label: i18n.global.t('cronjob.thursday'), value: 4 }, + { label: i18n.global.t('cronjob.friday'), value: 5 }, + { label: i18n.global.t('cronjob.saturday'), value: 6 }, + { label: i18n.global.t('cronjob.sunday'), value: 0 }, +]; +export const mysqlArgs = [ + { arg: '--single-transaction', description: i18n.global.t('cronjob.singleTransaction') }, + { arg: '--quick', description: i18n.global.t('cronjob.quick') }, + { arg: '--skip-lock-tables', description: i18n.global.t('cronjob.skipLockTables') }, +]; +function loadWeek(i: number) { + for (const week of weekOptions) { + if (week.value === i) { + return week.label; + } + } + return ''; +} + +export function loadDefaultSpec(type: string) { + let item = {} as Cronjob.SpecObj; + item.week = 0; + item.day = 0; + item.hour = 0; + item.minute = 0; + item.second = 0; + switch (type) { + case 'shell': + case 'clean': + case 'website': + case 'log': + case 'snapshot': + case 'curl': + item.specType = 'perWeek'; + item.week = 1; + item.hour = 1; + item.minute = 30; + break; + case 'app': + case 'database': + item.specType = 'perDay'; + item.hour = 2; + item.minute = 30; + break; + case 'directory': + case 'cutWebsiteLog': + case 'cleanLog': + case 'syncIpGroup': + case 'ntp': + item.specType = 'perDay'; + item.hour = 1; + item.minute = 30; + break; + } + return item; +} + +export function loadDefaultSpecCustom(type: string) { + switch (type) { + case 'shell': + case 'clean': + case 'website': + case 'log': + case 'snapshot': + case 'curl': + return '30 1 * * 1'; + case 'app': + case 'database': + return '30 2 * * *'; + case 'directory': + case 'cutWebsiteLog': + case 'ntp': + case 'syncIpGroup': + return '30 1 * * *'; + default: + return '30 1 * * 1'; + } +} + +export function transObjToSpec(specType: string, week, day, hour, minute, second): string { + switch (specType) { + case 'perMonth': + return `${minute} ${hour} ${day} * *`; + case 'perWeek': + return `${minute} ${hour} * * ${week}`; + case 'perNDay': + return `${minute} ${hour} */${day} * *`; + case 'perDay': + return `${minute} ${hour} * * *`; + case 'perNHour': + return `${minute} */${hour} * * *`; + case 'perHour': + return `${minute} * * * *`; + case 'perNMinute': + return `@every ${minute}m`; + case 'perNSecond': + return `@every ${second}s`; + default: + return ''; + } +} + +export function transSpecToObj(spec: string) { + let specs = spec.split(' '); + let specItem = { + specType: 'perNMinute', + week: 0, + day: 0, + hour: 0, + minute: 0, + second: 0, + }; + if (specs.length === 2) { + if (specs[1].indexOf('m') !== -1) { + specItem.specType = 'perNMinute'; + specItem.minute = Number(specs[1].replaceAll('m', '')); + return specItem; + } else { + specItem.specType = 'perNSecond'; + specItem.second = Number(specs[1].replaceAll('s', '')); + return specItem; + } + } + if (specs.length !== 5 || specs[0] === '*') { + return null; + } + specItem.minute = Number(specs[0]); + if (specs[1] === '*') { + specItem.specType = 'perHour'; + return specItem; + } + if (specs[1].indexOf('*/') !== -1) { + specItem.specType = 'perNHour'; + specItem.hour = Number(specs[1].replaceAll('*/', '')); + return specItem; + } + specItem.hour = Number(specs[1]); + if (specs[2].indexOf('*/') !== -1) { + specItem.specType = 'perNDay'; + specItem.day = Number(specs[2].replaceAll('*/', '')); + return specItem; + } + if (specs[2] !== '*') { + specItem.specType = 'perMonth'; + specItem.day = Number(specs[2]); + return specItem; + } + if (specs[4] !== '*') { + specItem.specType = 'perWeek'; + specItem.week = Number(specs[4]); + return specItem; + } + specItem.specType = 'perDay'; + return specItem; +} + +export function transSpecToStr(spec: string): string { + const specObj = transSpecToObj(spec); + switch (specObj.specType) { + case 'perMonth': + return i18n.global.t('cronjob.perMonthHelper', [specObj.day, specObj.hour, loadZero(specObj.minute)]); + case 'perWeek': + return i18n.global.t('cronjob.perWeekHelper', [ + loadWeek(specObj.week), + specObj.hour, + loadZero(specObj.minute), + ]); + case 'perDay': + return i18n.global.t('cronjob.perDayHelper', [specObj.hour, loadZero(specObj.minute)]); + case 'perHour': + return i18n.global.t('cronjob.perHourHelper', [loadZero(specObj.minute)]); + case 'perNDay': + return i18n.global.t('cronjob.perNDayHelper', [specObj.day, specObj.hour, loadZero(specObj.minute)]); + case 'perNHour': + return i18n.global.t('cronjob.perNHourHelper', [specObj.hour, loadZero(specObj.minute)]); + case 'perNMinute': + return i18n.global.t('cronjob.perNMinuteHelper', [loadZero(specObj.minute)]); + case 'perNSecond': + return i18n.global.t('cronjob.perNSecondHelper', [loadZero(specObj.second)]); + } +} + +export function hasBackup(type: string) { + return ( + type === 'app' || + type === 'website' || + type === 'database' || + type === 'directory' || + type === 'snapshot' || + type === 'log' || + type === 'cutWebsiteLog' + ); +} + +export const cronjobTypes = [ + { value: 'shell', label: i18n.global.t('cronjob.shell') }, + { value: 'app', label: i18n.global.t('cronjob.app') }, + { value: 'website', label: i18n.global.t('cronjob.website') }, + { value: 'database', label: i18n.global.t('cronjob.database') }, + { value: 'directory', label: i18n.global.t('cronjob.directory') }, + { value: 'log', label: i18n.global.t('cronjob.log') }, + { value: 'curl', label: i18n.global.t('cronjob.curl') }, + { value: 'cutWebsiteLog', label: i18n.global.t('cronjob.cutWebsiteLog') }, + { value: 'clean', label: i18n.global.t('setting.diskClean') }, + { value: 'snapshot', label: i18n.global.t('cronjob.snapshot') }, + { value: 'ntp', label: i18n.global.t('cronjob.ntp') }, + { value: 'syncIpGroup', label: i18n.global.t('cronjob.syncIpGroup') }, + { value: 'cleanLog', label: i18n.global.t('cronjob.cleanLog') }, +]; diff --git a/frontend/src/views/cronjob/cronjob/import/index.vue b/frontend/src/views/cronjob/cronjob/import/index.vue new file mode 100644 index 0000000..78131c4 --- /dev/null +++ b/frontend/src/views/cronjob/cronjob/import/index.vue @@ -0,0 +1,194 @@ + + + diff --git a/frontend/src/views/cronjob/cronjob/index.vue b/frontend/src/views/cronjob/cronjob/index.vue new file mode 100644 index 0000000..6d3fba5 --- /dev/null +++ b/frontend/src/views/cronjob/cronjob/index.vue @@ -0,0 +1,526 @@ + + + diff --git a/frontend/src/views/cronjob/cronjob/operate/index.vue b/frontend/src/views/cronjob/cronjob/operate/index.vue new file mode 100644 index 0000000..11b7349 --- /dev/null +++ b/frontend/src/views/cronjob/cronjob/operate/index.vue @@ -0,0 +1,1655 @@ + + + + diff --git a/frontend/src/views/cronjob/cronjob/record/index.vue b/frontend/src/views/cronjob/cronjob/record/index.vue new file mode 100644 index 0000000..75ae4a8 --- /dev/null +++ b/frontend/src/views/cronjob/cronjob/record/index.vue @@ -0,0 +1,494 @@ + + + + + diff --git a/frontend/src/views/cronjob/index.vue b/frontend/src/views/cronjob/index.vue new file mode 100644 index 0000000..ef0b94d --- /dev/null +++ b/frontend/src/views/cronjob/index.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/views/cronjob/library/index.vue b/frontend/src/views/cronjob/library/index.vue new file mode 100644 index 0000000..57c121e --- /dev/null +++ b/frontend/src/views/cronjob/library/index.vue @@ -0,0 +1,362 @@ + + + diff --git a/frontend/src/views/cronjob/library/operate/index.vue b/frontend/src/views/cronjob/library/operate/index.vue new file mode 100644 index 0000000..8a21370 --- /dev/null +++ b/frontend/src/views/cronjob/library/operate/index.vue @@ -0,0 +1,153 @@ + + + diff --git a/frontend/src/views/cronjob/library/run/index.vue b/frontend/src/views/cronjob/library/run/index.vue new file mode 100644 index 0000000..d9fb243 --- /dev/null +++ b/frontend/src/views/cronjob/library/run/index.vue @@ -0,0 +1,67 @@ + + + diff --git a/frontend/src/views/database/index.vue b/frontend/src/views/database/index.vue new file mode 100644 index 0000000..4d1cc57 --- /dev/null +++ b/frontend/src/views/database/index.vue @@ -0,0 +1,25 @@ + + + diff --git a/frontend/src/views/database/mysql/bind/index.vue b/frontend/src/views/database/mysql/bind/index.vue new file mode 100644 index 0000000..fd1ff0b --- /dev/null +++ b/frontend/src/views/database/mysql/bind/index.vue @@ -0,0 +1,129 @@ + + diff --git a/frontend/src/views/database/mysql/check/index.vue b/frontend/src/views/database/mysql/check/index.vue new file mode 100644 index 0000000..3af637e --- /dev/null +++ b/frontend/src/views/database/mysql/check/index.vue @@ -0,0 +1,63 @@ + + diff --git a/frontend/src/views/database/mysql/conn/index.vue b/frontend/src/views/database/mysql/conn/index.vue new file mode 100644 index 0000000..373a484 --- /dev/null +++ b/frontend/src/views/database/mysql/conn/index.vue @@ -0,0 +1,287 @@ + + + + + diff --git a/frontend/src/views/database/mysql/create/index.vue b/frontend/src/views/database/mysql/create/index.vue new file mode 100644 index 0000000..83a4b71 --- /dev/null +++ b/frontend/src/views/database/mysql/create/index.vue @@ -0,0 +1,179 @@ + + + diff --git a/frontend/src/views/database/mysql/delete/index.vue b/frontend/src/views/database/mysql/delete/index.vue new file mode 100644 index 0000000..6487b21 --- /dev/null +++ b/frontend/src/views/database/mysql/delete/index.vue @@ -0,0 +1,96 @@ + + diff --git a/frontend/src/views/database/mysql/index.vue b/frontend/src/views/database/mysql/index.vue new file mode 100644 index 0000000..58f72ef --- /dev/null +++ b/frontend/src/views/database/mysql/index.vue @@ -0,0 +1,701 @@ + + + + + diff --git a/frontend/src/views/database/mysql/password/index.vue b/frontend/src/views/database/mysql/password/index.vue new file mode 100644 index 0000000..f18f5a8 --- /dev/null +++ b/frontend/src/views/database/mysql/password/index.vue @@ -0,0 +1,213 @@ + + diff --git a/frontend/src/views/database/mysql/remote/delete/index.vue b/frontend/src/views/database/mysql/remote/delete/index.vue new file mode 100644 index 0000000..32579fc --- /dev/null +++ b/frontend/src/views/database/mysql/remote/delete/index.vue @@ -0,0 +1,83 @@ + + diff --git a/frontend/src/views/database/mysql/remote/index.vue b/frontend/src/views/database/mysql/remote/index.vue new file mode 100644 index 0000000..0b5651e --- /dev/null +++ b/frontend/src/views/database/mysql/remote/index.vue @@ -0,0 +1,170 @@ + + + diff --git a/frontend/src/views/database/mysql/remote/operate/index.vue b/frontend/src/views/database/mysql/remote/operate/index.vue new file mode 100644 index 0000000..136a1f0 --- /dev/null +++ b/frontend/src/views/database/mysql/remote/operate/index.vue @@ -0,0 +1,237 @@ + + + diff --git a/frontend/src/views/database/mysql/setting/helper.ts b/frontend/src/views/database/mysql/setting/helper.ts new file mode 100644 index 0000000..0547778 --- /dev/null +++ b/frontend/src/views/database/mysql/setting/helper.ts @@ -0,0 +1,107 @@ +export const planOptions = [ + { + id: 1, + title: '1-2GB', + data: { + version: '', + key_buffer_size: 32, + query_cache_size: 32, + tmp_table_size: 32, + innodb_buffer_pool_size: 64, + innodb_log_buffer_size: 64, + sort_buffer_size: 256, + read_buffer_size: 256, + read_rnd_buffer_size: 256, + join_buffer_size: 512, + thread_stack: 256, + binlog_cache_size: 64, + thread_cache_size: 64, + table_open_cache: 128, + max_connections: 100, + }, + }, + { + id: 2, + title: '2-4GB', + data: { + version: '', + key_buffer_size: 64, + query_cache_size: 64, + tmp_table_size: 64, + innodb_buffer_pool_size: 128, + innodb_log_buffer_size: 64, + sort_buffer_size: 512, + read_buffer_size: 512, + read_rnd_buffer_size: 512, + join_buffer_size: 1024, + thread_stack: 256, + binlog_cache_size: 64, + thread_cache_size: 96, + table_open_cache: 192, + max_connections: 200, + }, + }, + { + id: 3, + title: '4-8GB', + data: { + version: '', + key_buffer_size: 128, + query_cache_size: 128, + tmp_table_size: 128, + innodb_buffer_pool_size: 256, + innodb_log_buffer_size: 64, + sort_buffer_size: 1024, + read_buffer_size: 1024, + read_rnd_buffer_size: 768, + join_buffer_size: 2048, + thread_stack: 256, + binlog_cache_size: 128, + thread_cache_size: 128, + table_open_cache: 384, + max_connections: 300, + }, + }, + { + id: 4, + title: '8-16GB', + data: { + version: '', + key_buffer_size: 256, + query_cache_size: 256, + tmp_table_size: 256, + innodb_buffer_pool_size: 512, + innodb_log_buffer_size: 64, + sort_buffer_size: 1024, + read_buffer_size: 2048, + read_rnd_buffer_size: 1024, + join_buffer_size: 2048, + thread_stack: 384, + binlog_cache_size: 192, + thread_cache_size: 192, + table_open_cache: 1024, + max_connections: 400, + }, + }, + { + id: 5, + title: '16-32GB', + data: { + version: '', + key_buffer_size: 1024, + query_cache_size: 384, + tmp_table_size: 1024, + innodb_buffer_pool_size: 1024, + innodb_log_buffer_size: 64, + sort_buffer_size: 4096, + read_buffer_size: 4096, + read_rnd_buffer_size: 2048, + join_buffer_size: 4096, + thread_stack: 512, + binlog_cache_size: 256, + thread_cache_size: 256, + table_open_cache: 2048, + max_connections: 500, + }, + }, +]; diff --git a/frontend/src/views/database/mysql/setting/index.vue b/frontend/src/views/database/mysql/setting/index.vue new file mode 100644 index 0000000..3da6207 --- /dev/null +++ b/frontend/src/views/database/mysql/setting/index.vue @@ -0,0 +1,342 @@ + + + diff --git a/frontend/src/views/database/mysql/setting/slow-log/index.vue b/frontend/src/views/database/mysql/setting/slow-log/index.vue new file mode 100644 index 0000000..a7e85c0 --- /dev/null +++ b/frontend/src/views/database/mysql/setting/slow-log/index.vue @@ -0,0 +1,137 @@ + + diff --git a/frontend/src/views/database/mysql/setting/status/index.vue b/frontend/src/views/database/mysql/setting/status/index.vue new file mode 100644 index 0000000..b00db37 --- /dev/null +++ b/frontend/src/views/database/mysql/setting/status/index.vue @@ -0,0 +1,279 @@ + + + + diff --git a/frontend/src/views/database/mysql/setting/variables/index.vue b/frontend/src/views/database/mysql/setting/variables/index.vue new file mode 100644 index 0000000..fba18cb --- /dev/null +++ b/frontend/src/views/database/mysql/setting/variables/index.vue @@ -0,0 +1,313 @@ + + diff --git a/frontend/src/views/database/postgresql/bind/index.vue b/frontend/src/views/database/postgresql/bind/index.vue new file mode 100644 index 0000000..986f2ac --- /dev/null +++ b/frontend/src/views/database/postgresql/bind/index.vue @@ -0,0 +1,110 @@ + + diff --git a/frontend/src/views/database/postgresql/check/index.vue b/frontend/src/views/database/postgresql/check/index.vue new file mode 100644 index 0000000..f3dcd79 --- /dev/null +++ b/frontend/src/views/database/postgresql/check/index.vue @@ -0,0 +1,49 @@ + + diff --git a/frontend/src/views/database/postgresql/conn/index.vue b/frontend/src/views/database/postgresql/conn/index.vue new file mode 100644 index 0000000..ed03836 --- /dev/null +++ b/frontend/src/views/database/postgresql/conn/index.vue @@ -0,0 +1,254 @@ + + + + + diff --git a/frontend/src/views/database/postgresql/create/index.vue b/frontend/src/views/database/postgresql/create/index.vue new file mode 100644 index 0000000..b516083 --- /dev/null +++ b/frontend/src/views/database/postgresql/create/index.vue @@ -0,0 +1,124 @@ + + + diff --git a/frontend/src/views/database/postgresql/delete/index.vue b/frontend/src/views/database/postgresql/delete/index.vue new file mode 100644 index 0000000..fd4388a --- /dev/null +++ b/frontend/src/views/database/postgresql/delete/index.vue @@ -0,0 +1,96 @@ + + diff --git a/frontend/src/views/database/postgresql/index.vue b/frontend/src/views/database/postgresql/index.vue new file mode 100644 index 0000000..f44af0a --- /dev/null +++ b/frontend/src/views/database/postgresql/index.vue @@ -0,0 +1,628 @@ + + + + + diff --git a/frontend/src/views/database/postgresql/password/index.vue b/frontend/src/views/database/postgresql/password/index.vue new file mode 100644 index 0000000..76eeda3 --- /dev/null +++ b/frontend/src/views/database/postgresql/password/index.vue @@ -0,0 +1,162 @@ + + diff --git a/frontend/src/views/database/postgresql/privileges/index.vue b/frontend/src/views/database/postgresql/privileges/index.vue new file mode 100644 index 0000000..1c9f671 --- /dev/null +++ b/frontend/src/views/database/postgresql/privileges/index.vue @@ -0,0 +1,89 @@ + + diff --git a/frontend/src/views/database/postgresql/remote/delete/index.vue b/frontend/src/views/database/postgresql/remote/delete/index.vue new file mode 100644 index 0000000..94ff67a --- /dev/null +++ b/frontend/src/views/database/postgresql/remote/delete/index.vue @@ -0,0 +1,83 @@ + + diff --git a/frontend/src/views/database/postgresql/remote/index.vue b/frontend/src/views/database/postgresql/remote/index.vue new file mode 100644 index 0000000..cbec9a3 --- /dev/null +++ b/frontend/src/views/database/postgresql/remote/index.vue @@ -0,0 +1,170 @@ + + + diff --git a/frontend/src/views/database/postgresql/remote/operate/index.vue b/frontend/src/views/database/postgresql/remote/operate/index.vue new file mode 100644 index 0000000..fc1e9dc --- /dev/null +++ b/frontend/src/views/database/postgresql/remote/operate/index.vue @@ -0,0 +1,179 @@ + + + diff --git a/frontend/src/views/database/postgresql/setting/index.vue b/frontend/src/views/database/postgresql/setting/index.vue new file mode 100644 index 0000000..02d16e2 --- /dev/null +++ b/frontend/src/views/database/postgresql/setting/index.vue @@ -0,0 +1,223 @@ + + + diff --git a/frontend/src/views/database/redis/check/index.vue b/frontend/src/views/database/redis/check/index.vue new file mode 100644 index 0000000..f3dcd79 --- /dev/null +++ b/frontend/src/views/database/redis/check/index.vue @@ -0,0 +1,49 @@ + + diff --git a/frontend/src/views/database/redis/command/index.vue b/frontend/src/views/database/redis/command/index.vue new file mode 100644 index 0000000..a6c7e43 --- /dev/null +++ b/frontend/src/views/database/redis/command/index.vue @@ -0,0 +1,204 @@ + + + diff --git a/frontend/src/views/database/redis/conn/index.vue b/frontend/src/views/database/redis/conn/index.vue new file mode 100644 index 0000000..81752dc --- /dev/null +++ b/frontend/src/views/database/redis/conn/index.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/frontend/src/views/database/redis/index.vue b/frontend/src/views/database/redis/index.vue new file mode 100644 index 0000000..a61909d --- /dev/null +++ b/frontend/src/views/database/redis/index.vue @@ -0,0 +1,422 @@ + + + + + diff --git a/frontend/src/views/database/redis/remote/delete/index.vue b/frontend/src/views/database/redis/remote/delete/index.vue new file mode 100644 index 0000000..32579fc --- /dev/null +++ b/frontend/src/views/database/redis/remote/delete/index.vue @@ -0,0 +1,83 @@ + + diff --git a/frontend/src/views/database/redis/remote/index.vue b/frontend/src/views/database/redis/remote/index.vue new file mode 100644 index 0000000..063b975 --- /dev/null +++ b/frontend/src/views/database/redis/remote/index.vue @@ -0,0 +1,170 @@ + + + diff --git a/frontend/src/views/database/redis/remote/operate/index.vue b/frontend/src/views/database/redis/remote/operate/index.vue new file mode 100644 index 0000000..aa22d76 --- /dev/null +++ b/frontend/src/views/database/redis/remote/operate/index.vue @@ -0,0 +1,171 @@ + + + diff --git a/frontend/src/views/database/redis/setting/index.vue b/frontend/src/views/database/redis/setting/index.vue new file mode 100644 index 0000000..a3d16f3 --- /dev/null +++ b/frontend/src/views/database/redis/setting/index.vue @@ -0,0 +1,352 @@ + + + diff --git a/frontend/src/views/database/redis/setting/persistence/index.vue b/frontend/src/views/database/redis/setting/persistence/index.vue new file mode 100644 index 0000000..3d7382c --- /dev/null +++ b/frontend/src/views/database/redis/setting/persistence/index.vue @@ -0,0 +1,340 @@ + + + diff --git a/frontend/src/views/database/redis/setting/status/index.vue b/frontend/src/views/database/redis/setting/status/index.vue new file mode 100644 index 0000000..82ce9e9 --- /dev/null +++ b/frontend/src/views/database/redis/setting/status/index.vue @@ -0,0 +1,228 @@ + + + + + diff --git a/frontend/src/views/home/app/index.vue b/frontend/src/views/home/app/index.vue new file mode 100644 index 0000000..1a44d1f --- /dev/null +++ b/frontend/src/views/home/app/index.vue @@ -0,0 +1,377 @@ + + + + + diff --git a/frontend/src/views/home/index.vue b/frontend/src/views/home/index.vue new file mode 100644 index 0000000..0cce337 --- /dev/null +++ b/frontend/src/views/home/index.vue @@ -0,0 +1,981 @@ + + + + + diff --git a/frontend/src/views/home/quick/index.vue b/frontend/src/views/home/quick/index.vue new file mode 100644 index 0000000..af833fe --- /dev/null +++ b/frontend/src/views/home/quick/index.vue @@ -0,0 +1,117 @@ + + diff --git a/frontend/src/views/home/status/index.vue b/frontend/src/views/home/status/index.vue new file mode 100644 index 0000000..4ce7d46 --- /dev/null +++ b/frontend/src/views/home/status/index.vue @@ -0,0 +1,721 @@ + + + + + diff --git a/frontend/src/views/host/disk-management/components/disk-card.vue b/frontend/src/views/host/disk-management/components/disk-card.vue new file mode 100644 index 0000000..1c0ed10 --- /dev/null +++ b/frontend/src/views/host/disk-management/components/disk-card.vue @@ -0,0 +1,195 @@ + + + diff --git a/frontend/src/views/host/disk-management/disk/index.vue b/frontend/src/views/host/disk-management/disk/index.vue new file mode 100644 index 0000000..c3a1b73 --- /dev/null +++ b/frontend/src/views/host/disk-management/disk/index.vue @@ -0,0 +1,65 @@ + + diff --git a/frontend/src/views/host/disk-management/index.vue b/frontend/src/views/host/disk-management/index.vue new file mode 100644 index 0000000..07e428e --- /dev/null +++ b/frontend/src/views/host/disk-management/index.vue @@ -0,0 +1,19 @@ + + + diff --git a/frontend/src/views/host/disk-management/partition/index.vue b/frontend/src/views/host/disk-management/partition/index.vue new file mode 100644 index 0000000..1b30c86 --- /dev/null +++ b/frontend/src/views/host/disk-management/partition/index.vue @@ -0,0 +1,129 @@ + + + diff --git a/frontend/src/views/host/file-management/batch-role/index.vue b/frontend/src/views/host/file-management/batch-role/index.vue new file mode 100644 index 0000000..3222b44 --- /dev/null +++ b/frontend/src/views/host/file-management/batch-role/index.vue @@ -0,0 +1,133 @@ + + + diff --git a/frontend/src/views/host/file-management/change-role/index.vue b/frontend/src/views/host/file-management/change-role/index.vue new file mode 100644 index 0000000..67eb4d7 --- /dev/null +++ b/frontend/src/views/host/file-management/change-role/index.vue @@ -0,0 +1,64 @@ + + + diff --git a/frontend/src/views/host/file-management/chown/index.vue b/frontend/src/views/host/file-management/chown/index.vue new file mode 100644 index 0000000..23c4270 --- /dev/null +++ b/frontend/src/views/host/file-management/chown/index.vue @@ -0,0 +1,102 @@ + + + diff --git a/frontend/src/views/host/file-management/code-editor/index.vue b/frontend/src/views/host/file-management/code-editor/index.vue new file mode 100644 index 0000000..52116d8 --- /dev/null +++ b/frontend/src/views/host/file-management/code-editor/index.vue @@ -0,0 +1,1380 @@ + + + + + diff --git a/frontend/src/views/host/file-management/code-editor/tabs/index.vue b/frontend/src/views/host/file-management/code-editor/tabs/index.vue new file mode 100644 index 0000000..b45811f --- /dev/null +++ b/frontend/src/views/host/file-management/code-editor/tabs/index.vue @@ -0,0 +1,102 @@ + + + diff --git a/frontend/src/views/host/file-management/compress/index.vue b/frontend/src/views/host/file-management/compress/index.vue new file mode 100644 index 0000000..5e31c49 --- /dev/null +++ b/frontend/src/views/host/file-management/compress/index.vue @@ -0,0 +1,133 @@ + + + diff --git a/frontend/src/views/host/file-management/convert/index.vue b/frontend/src/views/host/file-management/convert/index.vue new file mode 100644 index 0000000..296ec99 --- /dev/null +++ b/frontend/src/views/host/file-management/convert/index.vue @@ -0,0 +1,403 @@ + + + diff --git a/frontend/src/views/host/file-management/create/index.vue b/frontend/src/views/host/file-management/create/index.vue new file mode 100644 index 0000000..18a9891 --- /dev/null +++ b/frontend/src/views/host/file-management/create/index.vue @@ -0,0 +1,149 @@ + + + diff --git a/frontend/src/views/host/file-management/decompress/index.vue b/frontend/src/views/host/file-management/decompress/index.vue new file mode 100644 index 0000000..032bc34 --- /dev/null +++ b/frontend/src/views/host/file-management/decompress/index.vue @@ -0,0 +1,105 @@ + + + diff --git a/frontend/src/views/host/file-management/delete/index.vue b/frontend/src/views/host/file-management/delete/index.vue new file mode 100644 index 0000000..0c5910c --- /dev/null +++ b/frontend/src/views/host/file-management/delete/index.vue @@ -0,0 +1,169 @@ + + + + diff --git a/frontend/src/views/host/file-management/detail/index.vue b/frontend/src/views/host/file-management/detail/index.vue new file mode 100644 index 0000000..27dab26 --- /dev/null +++ b/frontend/src/views/host/file-management/detail/index.vue @@ -0,0 +1,101 @@ + + + + diff --git a/frontend/src/views/host/file-management/download/index.vue b/frontend/src/views/host/file-management/download/index.vue new file mode 100644 index 0000000..da21605 --- /dev/null +++ b/frontend/src/views/host/file-management/download/index.vue @@ -0,0 +1,106 @@ + + + diff --git a/frontend/src/views/host/file-management/favorite/index.vue b/frontend/src/views/host/file-management/favorite/index.vue new file mode 100644 index 0000000..4e74694 --- /dev/null +++ b/frontend/src/views/host/file-management/favorite/index.vue @@ -0,0 +1,99 @@ + + + diff --git a/frontend/src/views/host/file-management/hooks/searchable.ts b/frontend/src/views/host/file-management/hooks/searchable.ts new file mode 100644 index 0000000..0b1e0c0 --- /dev/null +++ b/frontend/src/views/host/file-management/hooks/searchable.ts @@ -0,0 +1,89 @@ +import { nextTick, ref, watch } from 'vue'; + +export function useSearchable(paths) { + const searchableStatus = ref(false); + const searchablePath = ref(''); + const searchableInputRef = ref(); + + watch(searchableStatus, (val) => { + if (val) { + searchablePath.value = paths.value.at(-1)?.url; + nextTick(() => { + searchableInputRef.value?.focus(); + }); + } + }); + const searchableInputBlur = () => { + searchableStatus.value = false; + }; + + return { + searchableStatus, + searchablePath, + searchableInputRef, + searchableInputBlur, + }; +} + +export function useSearchableForSelect(paths) { + const searchableStatus = ref(false); + const searchablePath = ref(''); + const searchableInputRef = ref(); + + watch(searchableStatus, (val) => { + if (val) { + if (paths.value.length === 0) { + searchablePath.value = '/'; + } else { + searchablePath.value = '/' + paths.value.join('/'); + } + nextTick(() => { + searchableInputRef.value?.focus(); + }); + } + }); + const searchableInputBlur = () => { + searchableStatus.value = false; + }; + + return { + searchableStatus, + searchablePath, + searchableInputRef, + searchableInputBlur, + }; +} + +export function useMultipleSearchable(paths) { + const searchableStatus = ref(false); + const searchablePath = ref(''); + const searchableInputRefs = ref>({}); + const setSearchableInputRef = (id: string, el: HTMLInputElement | null) => { + if (el) { + searchableInputRefs.value[id] = el; + nextTick(() => { + searchableInputRefs.value[id]?.focus(); + }); + } else { + delete searchableInputRefs.value[id]; + } + }; + + watch(searchableStatus, (val) => { + if (val) { + searchablePath.value = paths.value.at(-1)?.url || ''; + } + }); + + const searchableInputBlur = () => { + searchableStatus.value = false; + }; + + return { + searchableStatus, + searchablePath, + searchableInputRefs, + setSearchableInputRef, + searchableInputBlur, + }; +} diff --git a/frontend/src/views/host/file-management/index.vue b/frontend/src/views/host/file-management/index.vue new file mode 100644 index 0000000..94ff106 --- /dev/null +++ b/frontend/src/views/host/file-management/index.vue @@ -0,0 +1,2140 @@ + + + + + diff --git a/frontend/src/views/host/file-management/move/index.vue b/frontend/src/views/host/file-management/move/index.vue new file mode 100644 index 0000000..8d48533 --- /dev/null +++ b/frontend/src/views/host/file-management/move/index.vue @@ -0,0 +1,321 @@ + + + + + diff --git a/frontend/src/views/host/file-management/preview/index.vue b/frontend/src/views/host/file-management/preview/index.vue new file mode 100644 index 0000000..7727326 --- /dev/null +++ b/frontend/src/views/host/file-management/preview/index.vue @@ -0,0 +1,197 @@ + + + + + diff --git a/frontend/src/views/host/file-management/process/index.vue b/frontend/src/views/host/file-management/process/index.vue new file mode 100644 index 0000000..1e72e3c --- /dev/null +++ b/frontend/src/views/host/file-management/process/index.vue @@ -0,0 +1,172 @@ + + + + + diff --git a/frontend/src/views/host/file-management/recycle-bin/delete/index.vue b/frontend/src/views/host/file-management/recycle-bin/delete/index.vue new file mode 100644 index 0000000..62895fb --- /dev/null +++ b/frontend/src/views/host/file-management/recycle-bin/delete/index.vue @@ -0,0 +1,80 @@ + + + + diff --git a/frontend/src/views/host/file-management/recycle-bin/index.vue b/frontend/src/views/host/file-management/recycle-bin/index.vue new file mode 100644 index 0000000..756197a --- /dev/null +++ b/frontend/src/views/host/file-management/recycle-bin/index.vue @@ -0,0 +1,192 @@ + + + diff --git a/frontend/src/views/host/file-management/recycle-bin/reduce/index.vue b/frontend/src/views/host/file-management/recycle-bin/reduce/index.vue new file mode 100644 index 0000000..1eb7b95 --- /dev/null +++ b/frontend/src/views/host/file-management/recycle-bin/reduce/index.vue @@ -0,0 +1,73 @@ + + diff --git a/frontend/src/views/host/file-management/rename/index.vue b/frontend/src/views/host/file-management/rename/index.vue new file mode 100644 index 0000000..284c67a --- /dev/null +++ b/frontend/src/views/host/file-management/rename/index.vue @@ -0,0 +1,98 @@ + + + diff --git a/frontend/src/views/host/file-management/terminal/index.vue b/frontend/src/views/host/file-management/terminal/index.vue new file mode 100644 index 0000000..3673739 --- /dev/null +++ b/frontend/src/views/host/file-management/terminal/index.vue @@ -0,0 +1,114 @@ + + + + + diff --git a/frontend/src/views/host/file-management/text-preview/index.vue b/frontend/src/views/host/file-management/text-preview/index.vue new file mode 100644 index 0000000..820908c --- /dev/null +++ b/frontend/src/views/host/file-management/text-preview/index.vue @@ -0,0 +1,173 @@ + + + + + diff --git a/frontend/src/views/host/file-management/upload/index.vue b/frontend/src/views/host/file-management/upload/index.vue new file mode 100644 index 0000000..824b6d7 --- /dev/null +++ b/frontend/src/views/host/file-management/upload/index.vue @@ -0,0 +1,501 @@ + + + + + diff --git a/frontend/src/views/host/file-management/wget/index.vue b/frontend/src/views/host/file-management/wget/index.vue new file mode 100644 index 0000000..599779e --- /dev/null +++ b/frontend/src/views/host/file-management/wget/index.vue @@ -0,0 +1,121 @@ + + + diff --git a/frontend/src/views/host/firewall/advance/index.vue b/frontend/src/views/host/firewall/advance/index.vue new file mode 100644 index 0000000..4a255e0 --- /dev/null +++ b/frontend/src/views/host/firewall/advance/index.vue @@ -0,0 +1,369 @@ + + + + + diff --git a/frontend/src/views/host/firewall/advance/operate/index.vue b/frontend/src/views/host/firewall/advance/operate/index.vue new file mode 100644 index 0000000..d34dc3f --- /dev/null +++ b/frontend/src/views/host/firewall/advance/operate/index.vue @@ -0,0 +1,169 @@ + + + diff --git a/frontend/src/views/host/firewall/forward/import/index.vue b/frontend/src/views/host/firewall/forward/import/index.vue new file mode 100644 index 0000000..4daf320 --- /dev/null +++ b/frontend/src/views/host/firewall/forward/import/index.vue @@ -0,0 +1,247 @@ + + + diff --git a/frontend/src/views/host/firewall/forward/index.vue b/frontend/src/views/host/firewall/forward/index.vue new file mode 100644 index 0000000..f3e850e --- /dev/null +++ b/frontend/src/views/host/firewall/forward/index.vue @@ -0,0 +1,286 @@ + + + + + diff --git a/frontend/src/views/host/firewall/forward/operate/index.vue b/frontend/src/views/host/firewall/forward/operate/index.vue new file mode 100644 index 0000000..0f816b0 --- /dev/null +++ b/frontend/src/views/host/firewall/forward/operate/index.vue @@ -0,0 +1,208 @@ + + + diff --git a/frontend/src/views/host/firewall/index.vue b/frontend/src/views/host/firewall/index.vue new file mode 100644 index 0000000..790c647 --- /dev/null +++ b/frontend/src/views/host/firewall/index.vue @@ -0,0 +1,31 @@ + + + diff --git a/frontend/src/views/host/firewall/ip/import/index.vue b/frontend/src/views/host/firewall/ip/import/index.vue new file mode 100644 index 0000000..8f606bd --- /dev/null +++ b/frontend/src/views/host/firewall/ip/import/index.vue @@ -0,0 +1,241 @@ + + + diff --git a/frontend/src/views/host/firewall/ip/index.vue b/frontend/src/views/host/firewall/ip/index.vue new file mode 100644 index 0000000..9688649 --- /dev/null +++ b/frontend/src/views/host/firewall/ip/index.vue @@ -0,0 +1,336 @@ + + + diff --git a/frontend/src/views/host/firewall/ip/operate/index.vue b/frontend/src/views/host/firewall/ip/operate/index.vue new file mode 100644 index 0000000..3940cb5 --- /dev/null +++ b/frontend/src/views/host/firewall/ip/operate/index.vue @@ -0,0 +1,153 @@ + + + diff --git a/frontend/src/views/host/firewall/port/import/index.vue b/frontend/src/views/host/firewall/port/import/index.vue new file mode 100644 index 0000000..0e25701 --- /dev/null +++ b/frontend/src/views/host/firewall/port/import/index.vue @@ -0,0 +1,250 @@ + + + diff --git a/frontend/src/views/host/firewall/port/index.vue b/frontend/src/views/host/firewall/port/index.vue new file mode 100644 index 0000000..8b77404 --- /dev/null +++ b/frontend/src/views/host/firewall/port/index.vue @@ -0,0 +1,457 @@ + + + + + diff --git a/frontend/src/views/host/firewall/port/operate/index.vue b/frontend/src/views/host/firewall/port/operate/index.vue new file mode 100644 index 0000000..222fa9f --- /dev/null +++ b/frontend/src/views/host/firewall/port/operate/index.vue @@ -0,0 +1,196 @@ + + + diff --git a/frontend/src/views/host/firewall/status/index.vue b/frontend/src/views/host/firewall/status/index.vue new file mode 100644 index 0000000..1521b01 --- /dev/null +++ b/frontend/src/views/host/firewall/status/index.vue @@ -0,0 +1,279 @@ + + + diff --git a/frontend/src/views/host/monitor/index.vue b/frontend/src/views/host/monitor/index.vue new file mode 100644 index 0000000..8a66f6b --- /dev/null +++ b/frontend/src/views/host/monitor/index.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/views/host/monitor/monitor/index.vue b/frontend/src/views/host/monitor/monitor/index.vue new file mode 100644 index 0000000..9a7831a --- /dev/null +++ b/frontend/src/views/host/monitor/monitor/index.vue @@ -0,0 +1,744 @@ + + + + + diff --git a/frontend/src/views/host/monitor/setting/days/index.vue b/frontend/src/views/host/monitor/setting/days/index.vue new file mode 100644 index 0000000..5a41782 --- /dev/null +++ b/frontend/src/views/host/monitor/setting/days/index.vue @@ -0,0 +1,70 @@ + + diff --git a/frontend/src/views/host/monitor/setting/default-io/index.vue b/frontend/src/views/host/monitor/setting/default-io/index.vue new file mode 100644 index 0000000..cdddc11 --- /dev/null +++ b/frontend/src/views/host/monitor/setting/default-io/index.vue @@ -0,0 +1,87 @@ + + diff --git a/frontend/src/views/host/monitor/setting/default-network/index.vue b/frontend/src/views/host/monitor/setting/default-network/index.vue new file mode 100644 index 0000000..29bffdf --- /dev/null +++ b/frontend/src/views/host/monitor/setting/default-network/index.vue @@ -0,0 +1,87 @@ + + diff --git a/frontend/src/views/host/monitor/setting/index.vue b/frontend/src/views/host/monitor/setting/index.vue new file mode 100644 index 0000000..508da63 --- /dev/null +++ b/frontend/src/views/host/monitor/setting/index.vue @@ -0,0 +1,165 @@ + + + diff --git a/frontend/src/views/host/monitor/setting/interval/index.vue b/frontend/src/views/host/monitor/setting/interval/index.vue new file mode 100644 index 0000000..7163933 --- /dev/null +++ b/frontend/src/views/host/monitor/setting/interval/index.vue @@ -0,0 +1,98 @@ + + diff --git a/frontend/src/views/host/process/index.vue b/frontend/src/views/host/process/index.vue new file mode 100644 index 0000000..9caeff9 --- /dev/null +++ b/frontend/src/views/host/process/index.vue @@ -0,0 +1,23 @@ + + + diff --git a/frontend/src/views/host/process/network/index.vue b/frontend/src/views/host/process/network/index.vue new file mode 100644 index 0000000..5d7590a --- /dev/null +++ b/frontend/src/views/host/process/network/index.vue @@ -0,0 +1,248 @@ + + + diff --git a/frontend/src/views/host/process/process/detail/index.vue b/frontend/src/views/host/process/process/detail/index.vue new file mode 100644 index 0000000..2bc7bf8 --- /dev/null +++ b/frontend/src/views/host/process/process/detail/index.vue @@ -0,0 +1,147 @@ + + + diff --git a/frontend/src/views/host/process/process/index.vue b/frontend/src/views/host/process/process/index.vue new file mode 100644 index 0000000..de09b3c --- /dev/null +++ b/frontend/src/views/host/process/process/index.vue @@ -0,0 +1,282 @@ + + + diff --git a/frontend/src/views/host/ssh/index.vue b/frontend/src/views/host/ssh/index.vue new file mode 100644 index 0000000..540f47a --- /dev/null +++ b/frontend/src/views/host/ssh/index.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/views/host/ssh/log/index.vue b/frontend/src/views/host/ssh/log/index.vue new file mode 100644 index 0000000..d1bbded --- /dev/null +++ b/frontend/src/views/host/ssh/log/index.vue @@ -0,0 +1,11 @@ + + + diff --git a/frontend/src/views/host/ssh/log/log.vue b/frontend/src/views/host/ssh/log/log.vue new file mode 100644 index 0000000..e089a85 --- /dev/null +++ b/frontend/src/views/host/ssh/log/log.vue @@ -0,0 +1,162 @@ + + + diff --git a/frontend/src/views/host/ssh/session/index.vue b/frontend/src/views/host/ssh/session/index.vue new file mode 100644 index 0000000..dc136d6 --- /dev/null +++ b/frontend/src/views/host/ssh/session/index.vue @@ -0,0 +1,127 @@ + + + diff --git a/frontend/src/views/host/ssh/ssh/address/index.vue b/frontend/src/views/host/ssh/ssh/address/index.vue new file mode 100644 index 0000000..5f82b26 --- /dev/null +++ b/frontend/src/views/host/ssh/ssh/address/index.vue @@ -0,0 +1,162 @@ + + diff --git a/frontend/src/views/host/ssh/ssh/auth-keys/index.vue b/frontend/src/views/host/ssh/ssh/auth-keys/index.vue new file mode 100644 index 0000000..5ed86fe --- /dev/null +++ b/frontend/src/views/host/ssh/ssh/auth-keys/index.vue @@ -0,0 +1,62 @@ + + + diff --git a/frontend/src/views/host/ssh/ssh/certification/index.vue b/frontend/src/views/host/ssh/ssh/certification/index.vue new file mode 100644 index 0000000..ea7e89a --- /dev/null +++ b/frontend/src/views/host/ssh/ssh/certification/index.vue @@ -0,0 +1,292 @@ + + + + diff --git a/frontend/src/views/host/ssh/ssh/certification/operate/index.vue b/frontend/src/views/host/ssh/ssh/certification/operate/index.vue new file mode 100644 index 0000000..5de4340 --- /dev/null +++ b/frontend/src/views/host/ssh/ssh/certification/operate/index.vue @@ -0,0 +1,252 @@ + + + diff --git a/frontend/src/views/host/ssh/ssh/index.vue b/frontend/src/views/host/ssh/ssh/index.vue new file mode 100644 index 0000000..8ec15d2 --- /dev/null +++ b/frontend/src/views/host/ssh/ssh/index.vue @@ -0,0 +1,344 @@ + + + diff --git a/frontend/src/views/host/ssh/ssh/port/index.vue b/frontend/src/views/host/ssh/ssh/port/index.vue new file mode 100644 index 0000000..5aceff9 --- /dev/null +++ b/frontend/src/views/host/ssh/ssh/port/index.vue @@ -0,0 +1,91 @@ + + diff --git a/frontend/src/views/host/ssh/ssh/root/index.vue b/frontend/src/views/host/ssh/ssh/root/index.vue new file mode 100644 index 0000000..59dcfba --- /dev/null +++ b/frontend/src/views/host/ssh/ssh/root/index.vue @@ -0,0 +1,109 @@ + + diff --git a/frontend/src/views/log/index.vue b/frontend/src/views/log/index.vue new file mode 100644 index 0000000..5590ce6 --- /dev/null +++ b/frontend/src/views/log/index.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/views/log/login/index.vue b/frontend/src/views/log/login/index.vue new file mode 100644 index 0000000..77e8ea1 --- /dev/null +++ b/frontend/src/views/log/login/index.vue @@ -0,0 +1,107 @@ + + + diff --git a/frontend/src/views/log/operation/index.vue b/frontend/src/views/log/operation/index.vue new file mode 100644 index 0000000..181bb81 --- /dev/null +++ b/frontend/src/views/log/operation/index.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/frontend/src/views/log/router/index.vue b/frontend/src/views/log/router/index.vue new file mode 100644 index 0000000..fecef37 --- /dev/null +++ b/frontend/src/views/log/router/index.vue @@ -0,0 +1,49 @@ + + diff --git a/frontend/src/views/log/system/index.vue b/frontend/src/views/log/system/index.vue new file mode 100644 index 0000000..afcec6f --- /dev/null +++ b/frontend/src/views/log/system/index.vue @@ -0,0 +1,89 @@ + + + diff --git a/frontend/src/views/log/task/index.vue b/frontend/src/views/log/task/index.vue new file mode 100644 index 0000000..ad8822c --- /dev/null +++ b/frontend/src/views/log/task/index.vue @@ -0,0 +1,95 @@ + + + diff --git a/frontend/src/views/log/website/index.vue b/frontend/src/views/log/website/index.vue new file mode 100644 index 0000000..d50afa2 --- /dev/null +++ b/frontend/src/views/log/website/index.vue @@ -0,0 +1,154 @@ + + diff --git a/frontend/src/views/login/components/login-form.vue b/frontend/src/views/login/components/login-form.vue new file mode 100644 index 0000000..013bc9b --- /dev/null +++ b/frontend/src/views/login/components/login-form.vue @@ -0,0 +1,815 @@ + + + + diff --git a/frontend/src/views/login/index.vue b/frontend/src/views/login/index.vue new file mode 100644 index 0000000..b5e0a4c --- /dev/null +++ b/frontend/src/views/login/index.vue @@ -0,0 +1,134 @@ + + + diff --git a/frontend/src/views/setting/about/index.vue b/frontend/src/views/setting/about/index.vue new file mode 100644 index 0000000..aee2835 --- /dev/null +++ b/frontend/src/views/setting/about/index.vue @@ -0,0 +1,103 @@ + + + + + diff --git a/frontend/src/views/setting/alert/dash/index.vue b/frontend/src/views/setting/alert/dash/index.vue new file mode 100644 index 0000000..7f6e591 --- /dev/null +++ b/frontend/src/views/setting/alert/dash/index.vue @@ -0,0 +1,341 @@ + + + + + diff --git a/frontend/src/views/setting/alert/dash/task/index.vue b/frontend/src/views/setting/alert/dash/task/index.vue new file mode 100644 index 0000000..e77c026 --- /dev/null +++ b/frontend/src/views/setting/alert/dash/task/index.vue @@ -0,0 +1,825 @@ + + + + diff --git a/frontend/src/views/setting/alert/index.vue b/frontend/src/views/setting/alert/index.vue new file mode 100644 index 0000000..c6497eb --- /dev/null +++ b/frontend/src/views/setting/alert/index.vue @@ -0,0 +1,39 @@ + + diff --git a/frontend/src/views/setting/alert/log/index.vue b/frontend/src/views/setting/alert/log/index.vue new file mode 100644 index 0000000..66d06b6 --- /dev/null +++ b/frontend/src/views/setting/alert/log/index.vue @@ -0,0 +1,307 @@ + + + diff --git a/frontend/src/views/setting/alert/setting/email/index.vue b/frontend/src/views/setting/alert/setting/email/index.vue new file mode 100644 index 0000000..08af360 --- /dev/null +++ b/frontend/src/views/setting/alert/setting/email/index.vue @@ -0,0 +1,209 @@ + + diff --git a/frontend/src/views/setting/alert/setting/index.vue b/frontend/src/views/setting/alert/setting/index.vue new file mode 100644 index 0000000..e223e38 --- /dev/null +++ b/frontend/src/views/setting/alert/setting/index.vue @@ -0,0 +1,435 @@ + + + + diff --git a/frontend/src/views/setting/alert/setting/phone/index.vue b/frontend/src/views/setting/alert/setting/phone/index.vue new file mode 100644 index 0000000..34d924d --- /dev/null +++ b/frontend/src/views/setting/alert/setting/phone/index.vue @@ -0,0 +1,95 @@ + + diff --git a/frontend/src/views/setting/alert/setting/time-range/index.vue b/frontend/src/views/setting/alert/setting/time-range/index.vue new file mode 100644 index 0000000..cff3bed --- /dev/null +++ b/frontend/src/views/setting/alert/setting/time-range/index.vue @@ -0,0 +1,274 @@ + + + + diff --git a/frontend/src/views/setting/backup-account/helper.ts b/frontend/src/views/setting/backup-account/helper.ts new file mode 100644 index 0000000..f49e31f --- /dev/null +++ b/frontend/src/views/setting/backup-account/helper.ts @@ -0,0 +1,26 @@ +import i18n from '@/lang'; + +export const cities = [ + { value: 'ap-beijing-1', label: i18n.global.t('setting.ap_beijing_1') }, + { value: 'ap-beijing', label: i18n.global.t('setting.ap_beijing') }, + { value: 'ap-nanjing', label: i18n.global.t('setting.ap_nanjing') }, + { value: 'ap-shanghai', label: i18n.global.t('setting.ap_shanghai') }, + { value: 'ap-guangzhou', label: i18n.global.t('setting.ap_guangzhou') }, + { value: 'ap-chengdu', label: i18n.global.t('setting.ap_chengdu') }, + { value: 'ap-chongqing', label: i18n.global.t('setting.ap_chongqing') }, + { value: 'ap-shenzhen_fsi', label: i18n.global.t('setting.ap_shenzhen_fsi') }, + { value: 'ap-shanghai_fsi', label: i18n.global.t('setting.ap_shanghai_fsi') }, + { value: 'ap-beijing_fsi', label: i18n.global.t('setting.ap_beijing_fsi') }, + { value: 'ap-hongkong', label: i18n.global.t('setting.ap_hongkong') }, + { value: 'ap-singapore', label: i18n.global.t('setting.ap_singapore') }, + { value: 'ap-mumbai', label: i18n.global.t('setting.ap_mumbai') }, + { value: 'ap-jakarta', label: i18n.global.t('setting.ap_jakarta') }, + { value: 'ap-seoul', label: i18n.global.t('setting.ap_seoul') }, + { value: 'ap-bangkok', label: i18n.global.t('setting.ap_bangkok') }, + { value: 'ap-tokyo', label: i18n.global.t('setting.ap_tokyo') }, + { value: 'na-siliconvalley', label: i18n.global.t('setting.na_siliconvalley') }, + { value: 'na-ashburn', label: i18n.global.t('setting.na_ashburn') }, + { value: 'na-toronto', label: i18n.global.t('setting.na_toronto') }, + { value: 'sa-saopaulo', label: i18n.global.t('setting.sa_saopaulo') }, + { value: 'eu-frankfurt', label: i18n.global.t('setting.eu_frankfurt') }, +]; diff --git a/frontend/src/views/setting/backup-account/index.vue b/frontend/src/views/setting/backup-account/index.vue new file mode 100644 index 0000000..902a585 --- /dev/null +++ b/frontend/src/views/setting/backup-account/index.vue @@ -0,0 +1,314 @@ + + diff --git a/frontend/src/views/setting/backup-account/operate/index.vue b/frontend/src/views/setting/backup-account/operate/index.vue new file mode 100644 index 0000000..b5fa710 --- /dev/null +++ b/frontend/src/views/setting/backup-account/operate/index.vue @@ -0,0 +1,804 @@ + + + + + diff --git a/frontend/src/views/setting/expired.vue b/frontend/src/views/setting/expired.vue new file mode 100644 index 0000000..7847d43 --- /dev/null +++ b/frontend/src/views/setting/expired.vue @@ -0,0 +1,105 @@ + + + diff --git a/frontend/src/views/setting/index.vue b/frontend/src/views/setting/index.vue new file mode 100644 index 0000000..a7329b3 --- /dev/null +++ b/frontend/src/views/setting/index.vue @@ -0,0 +1,54 @@ + + + diff --git a/frontend/src/views/setting/license/bind/free.vue b/frontend/src/views/setting/license/bind/free.vue new file mode 100644 index 0000000..eb9dd78 --- /dev/null +++ b/frontend/src/views/setting/license/bind/free.vue @@ -0,0 +1,93 @@ + + diff --git a/frontend/src/views/setting/license/bind/xpack.vue b/frontend/src/views/setting/license/bind/xpack.vue new file mode 100644 index 0000000..c977f91 --- /dev/null +++ b/frontend/src/views/setting/license/bind/xpack.vue @@ -0,0 +1,135 @@ + + diff --git a/frontend/src/views/setting/license/index.vue b/frontend/src/views/setting/license/index.vue new file mode 100644 index 0000000..0350e78 --- /dev/null +++ b/frontend/src/views/setting/license/index.vue @@ -0,0 +1,338 @@ + + + + + diff --git a/frontend/src/views/setting/panel/api-interface/index.vue b/frontend/src/views/setting/panel/api-interface/index.vue new file mode 100644 index 0000000..e3b4a27 --- /dev/null +++ b/frontend/src/views/setting/panel/api-interface/index.vue @@ -0,0 +1,198 @@ + + + + diff --git a/frontend/src/views/setting/panel/hidemenu/index.vue b/frontend/src/views/setting/panel/hidemenu/index.vue new file mode 100644 index 0000000..276f2ad --- /dev/null +++ b/frontend/src/views/setting/panel/hidemenu/index.vue @@ -0,0 +1,231 @@ + + + + diff --git a/frontend/src/views/setting/panel/index.vue b/frontend/src/views/setting/panel/index.vue new file mode 100644 index 0000000..81b8566 --- /dev/null +++ b/frontend/src/views/setting/panel/index.vue @@ -0,0 +1,543 @@ + + + + + diff --git a/frontend/src/views/setting/panel/name/index.vue b/frontend/src/views/setting/panel/name/index.vue new file mode 100644 index 0000000..f1c5c63 --- /dev/null +++ b/frontend/src/views/setting/panel/name/index.vue @@ -0,0 +1,86 @@ + + diff --git a/frontend/src/views/setting/panel/password/index.vue b/frontend/src/views/setting/panel/password/index.vue new file mode 100644 index 0000000..7491cf8 --- /dev/null +++ b/frontend/src/views/setting/panel/password/index.vue @@ -0,0 +1,118 @@ + + + diff --git a/frontend/src/views/setting/panel/proxy/index.vue b/frontend/src/views/setting/panel/proxy/index.vue new file mode 100644 index 0000000..b7f200b --- /dev/null +++ b/frontend/src/views/setting/panel/proxy/index.vue @@ -0,0 +1,226 @@ + + + diff --git a/frontend/src/views/setting/panel/systemip/index.vue b/frontend/src/views/setting/panel/systemip/index.vue new file mode 100644 index 0000000..e5277ee --- /dev/null +++ b/frontend/src/views/setting/panel/systemip/index.vue @@ -0,0 +1,81 @@ + + diff --git a/frontend/src/views/setting/panel/theme-color/index.vue b/frontend/src/views/setting/panel/theme-color/index.vue new file mode 100644 index 0000000..d6d4393 --- /dev/null +++ b/frontend/src/views/setting/panel/theme-color/index.vue @@ -0,0 +1,312 @@ + + + diff --git a/frontend/src/views/setting/panel/timeout/index.vue b/frontend/src/views/setting/panel/timeout/index.vue new file mode 100644 index 0000000..0b95e57 --- /dev/null +++ b/frontend/src/views/setting/panel/timeout/index.vue @@ -0,0 +1,72 @@ + + diff --git a/frontend/src/views/setting/panel/username/index.vue b/frontend/src/views/setting/panel/username/index.vue new file mode 100644 index 0000000..6f3927c --- /dev/null +++ b/frontend/src/views/setting/panel/username/index.vue @@ -0,0 +1,79 @@ + + diff --git a/frontend/src/views/setting/panel/watermark/index.vue b/frontend/src/views/setting/panel/watermark/index.vue new file mode 100644 index 0000000..e574cef --- /dev/null +++ b/frontend/src/views/setting/panel/watermark/index.vue @@ -0,0 +1,158 @@ + + + + diff --git a/frontend/src/views/setting/safe/allowips/index.vue b/frontend/src/views/setting/safe/allowips/index.vue new file mode 100644 index 0000000..7d21a62 --- /dev/null +++ b/frontend/src/views/setting/safe/allowips/index.vue @@ -0,0 +1,114 @@ + + diff --git a/frontend/src/views/setting/safe/bind/index.vue b/frontend/src/views/setting/safe/bind/index.vue new file mode 100644 index 0000000..d7b8f8a --- /dev/null +++ b/frontend/src/views/setting/safe/bind/index.vue @@ -0,0 +1,126 @@ + + diff --git a/frontend/src/views/setting/safe/domain/index.vue b/frontend/src/views/setting/safe/domain/index.vue new file mode 100644 index 0000000..7bdfc02 --- /dev/null +++ b/frontend/src/views/setting/safe/domain/index.vue @@ -0,0 +1,99 @@ + + diff --git a/frontend/src/views/setting/safe/entrance/index.vue b/frontend/src/views/setting/safe/entrance/index.vue new file mode 100644 index 0000000..197cbc5 --- /dev/null +++ b/frontend/src/views/setting/safe/entrance/index.vue @@ -0,0 +1,107 @@ + + diff --git a/frontend/src/views/setting/safe/index.vue b/frontend/src/views/setting/safe/index.vue new file mode 100644 index 0000000..19e77b8 --- /dev/null +++ b/frontend/src/views/setting/safe/index.vue @@ -0,0 +1,592 @@ + + + + + diff --git a/frontend/src/views/setting/safe/mfa/index.vue b/frontend/src/views/setting/safe/mfa/index.vue new file mode 100644 index 0000000..9514210 --- /dev/null +++ b/frontend/src/views/setting/safe/mfa/index.vue @@ -0,0 +1,164 @@ + + + diff --git a/frontend/src/views/setting/safe/port/index.vue b/frontend/src/views/setting/safe/port/index.vue new file mode 100644 index 0000000..79bef80 --- /dev/null +++ b/frontend/src/views/setting/safe/port/index.vue @@ -0,0 +1,86 @@ + + diff --git a/frontend/src/views/setting/safe/response/index.vue b/frontend/src/views/setting/safe/response/index.vue new file mode 100644 index 0000000..6155984 --- /dev/null +++ b/frontend/src/views/setting/safe/response/index.vue @@ -0,0 +1,113 @@ + + + + + diff --git a/frontend/src/views/setting/safe/ssl/index.vue b/frontend/src/views/setting/safe/ssl/index.vue new file mode 100644 index 0000000..e029688 --- /dev/null +++ b/frontend/src/views/setting/safe/ssl/index.vue @@ -0,0 +1,286 @@ + + + + diff --git a/frontend/src/views/setting/safe/timeout/index.vue b/frontend/src/views/setting/safe/timeout/index.vue new file mode 100644 index 0000000..49cec32 --- /dev/null +++ b/frontend/src/views/setting/safe/timeout/index.vue @@ -0,0 +1,72 @@ + + diff --git a/frontend/src/views/setting/snapshot/create/index.vue b/frontend/src/views/setting/snapshot/create/index.vue new file mode 100644 index 0000000..0578264 --- /dev/null +++ b/frontend/src/views/setting/snapshot/create/index.vue @@ -0,0 +1,639 @@ + + + + diff --git a/frontend/src/views/setting/snapshot/import/index.vue b/frontend/src/views/setting/snapshot/import/index.vue new file mode 100644 index 0000000..9874e2b --- /dev/null +++ b/frontend/src/views/setting/snapshot/import/index.vue @@ -0,0 +1,178 @@ + + + + + diff --git a/frontend/src/views/setting/snapshot/index.vue b/frontend/src/views/setting/snapshot/index.vue new file mode 100644 index 0000000..b253a1a --- /dev/null +++ b/frontend/src/views/setting/snapshot/index.vue @@ -0,0 +1,483 @@ + + + + + diff --git a/frontend/src/views/setting/snapshot/recover/index.vue b/frontend/src/views/setting/snapshot/recover/index.vue new file mode 100644 index 0000000..1b5d5b7 --- /dev/null +++ b/frontend/src/views/setting/snapshot/recover/index.vue @@ -0,0 +1,204 @@ + + + + diff --git a/frontend/src/views/setting/snapshot/status/index.vue b/frontend/src/views/setting/snapshot/status/index.vue new file mode 100644 index 0000000..8e0921c --- /dev/null +++ b/frontend/src/views/setting/snapshot/status/index.vue @@ -0,0 +1,222 @@ + + + + + diff --git a/frontend/src/views/terminal/command/import/index.vue b/frontend/src/views/terminal/command/import/index.vue new file mode 100644 index 0000000..0af6b70 --- /dev/null +++ b/frontend/src/views/terminal/command/import/index.vue @@ -0,0 +1,194 @@ + + + diff --git a/frontend/src/views/terminal/command/index.vue b/frontend/src/views/terminal/command/index.vue new file mode 100644 index 0000000..606c5c7 --- /dev/null +++ b/frontend/src/views/terminal/command/index.vue @@ -0,0 +1,250 @@ + + + diff --git a/frontend/src/views/terminal/command/operate/index.vue b/frontend/src/views/terminal/command/operate/index.vue new file mode 100644 index 0000000..3ea0993 --- /dev/null +++ b/frontend/src/views/terminal/command/operate/index.vue @@ -0,0 +1,125 @@ + + + diff --git a/frontend/src/views/terminal/host/index.vue b/frontend/src/views/terminal/host/index.vue new file mode 100644 index 0000000..08aad1e --- /dev/null +++ b/frontend/src/views/terminal/host/index.vue @@ -0,0 +1,203 @@ + + + diff --git a/frontend/src/views/terminal/host/operate/index.vue b/frontend/src/views/terminal/host/operate/index.vue new file mode 100644 index 0000000..96dbf09 --- /dev/null +++ b/frontend/src/views/terminal/host/operate/index.vue @@ -0,0 +1,190 @@ + + + diff --git a/frontend/src/views/terminal/index.vue b/frontend/src/views/terminal/index.vue new file mode 100644 index 0000000..c7abd72 --- /dev/null +++ b/frontend/src/views/terminal/index.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/frontend/src/views/terminal/setting/default_conn/index.vue b/frontend/src/views/terminal/setting/default_conn/index.vue new file mode 100644 index 0000000..a70e40f --- /dev/null +++ b/frontend/src/views/terminal/setting/default_conn/index.vue @@ -0,0 +1,148 @@ + + + diff --git a/frontend/src/views/terminal/setting/index.vue b/frontend/src/views/terminal/setting/index.vue new file mode 100644 index 0000000..c764b7e --- /dev/null +++ b/frontend/src/views/terminal/setting/index.vue @@ -0,0 +1,313 @@ + + + + + diff --git a/frontend/src/views/terminal/terminal/host-create.vue b/frontend/src/views/terminal/terminal/host-create.vue new file mode 100644 index 0000000..8572dae --- /dev/null +++ b/frontend/src/views/terminal/terminal/host-create.vue @@ -0,0 +1,210 @@ + + + diff --git a/frontend/src/views/terminal/terminal/index.vue b/frontend/src/views/terminal/terminal/index.vue new file mode 100644 index 0000000..213b62a --- /dev/null +++ b/frontend/src/views/terminal/terminal/index.vue @@ -0,0 +1,747 @@ + + + + + diff --git a/frontend/src/views/toolbox/clam/index.vue b/frontend/src/views/toolbox/clam/index.vue new file mode 100644 index 0000000..c97136a --- /dev/null +++ b/frontend/src/views/toolbox/clam/index.vue @@ -0,0 +1,378 @@ + + + diff --git a/frontend/src/views/toolbox/clam/operate/index.vue b/frontend/src/views/toolbox/clam/operate/index.vue new file mode 100644 index 0000000..8ab32b5 --- /dev/null +++ b/frontend/src/views/toolbox/clam/operate/index.vue @@ -0,0 +1,514 @@ + + + + diff --git a/frontend/src/views/toolbox/clam/record/index.vue b/frontend/src/views/toolbox/clam/record/index.vue new file mode 100644 index 0000000..c2b2cf9 --- /dev/null +++ b/frontend/src/views/toolbox/clam/record/index.vue @@ -0,0 +1,382 @@ + + + + + diff --git a/frontend/src/views/toolbox/clam/setting/index.vue b/frontend/src/views/toolbox/clam/setting/index.vue new file mode 100644 index 0000000..fb0ecd4 --- /dev/null +++ b/frontend/src/views/toolbox/clam/setting/index.vue @@ -0,0 +1,111 @@ + + + diff --git a/frontend/src/views/toolbox/clam/status/index.vue b/frontend/src/views/toolbox/clam/status/index.vue new file mode 100644 index 0000000..9f92908 --- /dev/null +++ b/frontend/src/views/toolbox/clam/status/index.vue @@ -0,0 +1,160 @@ + + + + diff --git a/frontend/src/views/toolbox/clean/index.vue b/frontend/src/views/toolbox/clean/index.vue new file mode 100644 index 0000000..3b78f63 --- /dev/null +++ b/frontend/src/views/toolbox/clean/index.vue @@ -0,0 +1,691 @@ + + + + diff --git a/frontend/src/views/toolbox/device/dns/index.vue b/frontend/src/views/toolbox/device/dns/index.vue new file mode 100644 index 0000000..ba5db25 --- /dev/null +++ b/frontend/src/views/toolbox/device/dns/index.vue @@ -0,0 +1,134 @@ + + diff --git a/frontend/src/views/toolbox/device/hostname/index.vue b/frontend/src/views/toolbox/device/hostname/index.vue new file mode 100644 index 0000000..831160f --- /dev/null +++ b/frontend/src/views/toolbox/device/hostname/index.vue @@ -0,0 +1,81 @@ + + diff --git a/frontend/src/views/toolbox/device/hosts/index.vue b/frontend/src/views/toolbox/device/hosts/index.vue new file mode 100644 index 0000000..35421b3 --- /dev/null +++ b/frontend/src/views/toolbox/device/hosts/index.vue @@ -0,0 +1,140 @@ + + diff --git a/frontend/src/views/toolbox/device/index.vue b/frontend/src/views/toolbox/device/index.vue new file mode 100644 index 0000000..8af3498 --- /dev/null +++ b/frontend/src/views/toolbox/device/index.vue @@ -0,0 +1,199 @@ + + + diff --git a/frontend/src/views/toolbox/device/ntp/index.vue b/frontend/src/views/toolbox/device/ntp/index.vue new file mode 100644 index 0000000..f6576e0 --- /dev/null +++ b/frontend/src/views/toolbox/device/ntp/index.vue @@ -0,0 +1,95 @@ + + + + diff --git a/frontend/src/views/toolbox/device/passwd/index.vue b/frontend/src/views/toolbox/device/passwd/index.vue new file mode 100644 index 0000000..62b3c55 --- /dev/null +++ b/frontend/src/views/toolbox/device/passwd/index.vue @@ -0,0 +1,104 @@ + + + diff --git a/frontend/src/views/toolbox/device/swap/index.vue b/frontend/src/views/toolbox/device/swap/index.vue new file mode 100644 index 0000000..8799a87 --- /dev/null +++ b/frontend/src/views/toolbox/device/swap/index.vue @@ -0,0 +1,218 @@ + + diff --git a/frontend/src/views/toolbox/device/time-zone/index.vue b/frontend/src/views/toolbox/device/time-zone/index.vue new file mode 100644 index 0000000..cbfa1bd --- /dev/null +++ b/frontend/src/views/toolbox/device/time-zone/index.vue @@ -0,0 +1,134 @@ + + + diff --git a/frontend/src/views/toolbox/fail2ban/ban-action/index.vue b/frontend/src/views/toolbox/fail2ban/ban-action/index.vue new file mode 100644 index 0000000..10f8ab5 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/ban-action/index.vue @@ -0,0 +1,121 @@ + + + + diff --git a/frontend/src/views/toolbox/fail2ban/ban-time/index.vue b/frontend/src/views/toolbox/fail2ban/ban-time/index.vue new file mode 100644 index 0000000..7e859fb --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/ban-time/index.vue @@ -0,0 +1,111 @@ + + diff --git a/frontend/src/views/toolbox/fail2ban/find-time/index.vue b/frontend/src/views/toolbox/fail2ban/find-time/index.vue new file mode 100644 index 0000000..18f8012 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/find-time/index.vue @@ -0,0 +1,96 @@ + + diff --git a/frontend/src/views/toolbox/fail2ban/index.vue b/frontend/src/views/toolbox/fail2ban/index.vue new file mode 100644 index 0000000..db8bbc0 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/index.vue @@ -0,0 +1,309 @@ + + + diff --git a/frontend/src/views/toolbox/fail2ban/ips/index.vue b/frontend/src/views/toolbox/fail2ban/ips/index.vue new file mode 100644 index 0000000..757c9e2 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/ips/index.vue @@ -0,0 +1,127 @@ + + diff --git a/frontend/src/views/toolbox/fail2ban/log-path/index.vue b/frontend/src/views/toolbox/fail2ban/log-path/index.vue new file mode 100644 index 0000000..4735920 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/log-path/index.vue @@ -0,0 +1,91 @@ + + diff --git a/frontend/src/views/toolbox/fail2ban/max-retry/index.vue b/frontend/src/views/toolbox/fail2ban/max-retry/index.vue new file mode 100644 index 0000000..c6dae8c --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/max-retry/index.vue @@ -0,0 +1,85 @@ + + diff --git a/frontend/src/views/toolbox/fail2ban/port/index.vue b/frontend/src/views/toolbox/fail2ban/port/index.vue new file mode 100644 index 0000000..53b5447 --- /dev/null +++ b/frontend/src/views/toolbox/fail2ban/port/index.vue @@ -0,0 +1,83 @@ + + diff --git a/frontend/src/views/toolbox/ftp/index.vue b/frontend/src/views/toolbox/ftp/index.vue new file mode 100644 index 0000000..452fa13 --- /dev/null +++ b/frontend/src/views/toolbox/ftp/index.vue @@ -0,0 +1,364 @@ + + + diff --git a/frontend/src/views/toolbox/ftp/log/index.vue b/frontend/src/views/toolbox/ftp/log/index.vue new file mode 100644 index 0000000..755cdf7 --- /dev/null +++ b/frontend/src/views/toolbox/ftp/log/index.vue @@ -0,0 +1,114 @@ + + + diff --git a/frontend/src/views/toolbox/ftp/operate/index.vue b/frontend/src/views/toolbox/ftp/operate/index.vue new file mode 100644 index 0000000..58b8ba2 --- /dev/null +++ b/frontend/src/views/toolbox/ftp/operate/index.vue @@ -0,0 +1,145 @@ + + + diff --git a/frontend/src/views/toolbox/index.vue b/frontend/src/views/toolbox/index.vue new file mode 100644 index 0000000..d3d3fd2 --- /dev/null +++ b/frontend/src/views/toolbox/index.vue @@ -0,0 +1,81 @@ + + + diff --git a/frontend/src/views/toolbox/supervisor/config/basic/index.vue b/frontend/src/views/toolbox/supervisor/config/basic/index.vue new file mode 100644 index 0000000..8857336 --- /dev/null +++ b/frontend/src/views/toolbox/supervisor/config/basic/index.vue @@ -0,0 +1,80 @@ + + + diff --git a/frontend/src/views/toolbox/supervisor/config/index.vue b/frontend/src/views/toolbox/supervisor/config/index.vue new file mode 100644 index 0000000..7de801a --- /dev/null +++ b/frontend/src/views/toolbox/supervisor/config/index.vue @@ -0,0 +1,38 @@ + + + diff --git a/frontend/src/views/toolbox/supervisor/config/source/index.vue b/frontend/src/views/toolbox/supervisor/config/source/index.vue new file mode 100644 index 0000000..405e796 --- /dev/null +++ b/frontend/src/views/toolbox/supervisor/config/source/index.vue @@ -0,0 +1,42 @@ + + diff --git a/frontend/src/views/toolbox/supervisor/create/index.vue b/frontend/src/views/toolbox/supervisor/create/index.vue new file mode 100644 index 0000000..d776b1b --- /dev/null +++ b/frontend/src/views/toolbox/supervisor/create/index.vue @@ -0,0 +1,153 @@ + + + diff --git a/frontend/src/views/toolbox/supervisor/file/index.vue b/frontend/src/views/toolbox/supervisor/file/index.vue new file mode 100644 index 0000000..bc5f07e --- /dev/null +++ b/frontend/src/views/toolbox/supervisor/file/index.vue @@ -0,0 +1,107 @@ + + + + diff --git a/frontend/src/views/toolbox/supervisor/index.vue b/frontend/src/views/toolbox/supervisor/index.vue new file mode 100644 index 0000000..5255de0 --- /dev/null +++ b/frontend/src/views/toolbox/supervisor/index.vue @@ -0,0 +1,379 @@ + + + diff --git a/frontend/src/views/toolbox/supervisor/log/index.vue b/frontend/src/views/toolbox/supervisor/log/index.vue new file mode 100644 index 0000000..f360cb5 --- /dev/null +++ b/frontend/src/views/toolbox/supervisor/log/index.vue @@ -0,0 +1,118 @@ + + + + diff --git a/frontend/src/views/toolbox/supervisor/status/index.vue b/frontend/src/views/toolbox/supervisor/status/index.vue new file mode 100644 index 0000000..2a6b637 --- /dev/null +++ b/frontend/src/views/toolbox/supervisor/status/index.vue @@ -0,0 +1,188 @@ + + + + diff --git a/frontend/src/views/toolbox/supervisor/status/init/index.vue b/frontend/src/views/toolbox/supervisor/status/init/index.vue new file mode 100644 index 0000000..55c4060 --- /dev/null +++ b/frontend/src/views/toolbox/supervisor/status/init/index.vue @@ -0,0 +1,112 @@ + + + diff --git a/frontend/src/views/website/runtime/app/index.vue b/frontend/src/views/website/runtime/app/index.vue new file mode 100644 index 0000000..95419fc --- /dev/null +++ b/frontend/src/views/website/runtime/app/index.vue @@ -0,0 +1,123 @@ + + + diff --git a/frontend/src/views/website/runtime/common/utils.ts b/frontend/src/views/website/runtime/common/utils.ts new file mode 100644 index 0000000..fa774f7 --- /dev/null +++ b/frontend/src/views/website/runtime/common/utils.ts @@ -0,0 +1,45 @@ +import { ElMessageBox } from 'element-plus'; +import i18n from '@/lang'; +import { OperateRuntime, updateRemark } from '@/api/modules/runtime'; +import { Ref } from 'vue'; +import { MsgError, MsgSuccess } from '@/utils/message'; +import { Runtime } from '@/api/interface/runtime'; + +export const operateRuntime = async (operate: string, ID: number, loading: Ref, search: () => void) => { + try { + const action = await ElMessageBox.confirm( + i18n.global.t('runtime.operatorHelper', [i18n.global.t('commons.operate.' + operate)]), + i18n.global.t('commons.operate.' + operate), + { + confirmButtonText: i18n.global.t('commons.button.confirm'), + cancelButtonText: i18n.global.t('commons.button.cancel'), + type: 'info', + }, + ); + + if (action === 'confirm') { + loading.value = true; + await OperateRuntime({ operate: operate, ID: ID }); + search(); + } + } catch (error) { + } finally { + loading.value = false; + } +}; + +export const updateRuntimeRemark = async (row: Runtime.Runtime, bulr: Function) => { + bulr(); + if (row.remark && row.remark.length > 128) { + MsgError(i18n.global.t('commons.rule.length128Err')); + return; + } + try { + await updateRemark({ + id: row.id, + remark: row.remark, + }).then(() => { + MsgSuccess(i18n.global.t('commons.msg.updateSuccess')); + }); + } catch (error) {} +}; diff --git a/frontend/src/views/website/runtime/components/dir/index.vue b/frontend/src/views/website/runtime/components/dir/index.vue new file mode 100644 index 0000000..0682034 --- /dev/null +++ b/frontend/src/views/website/runtime/components/dir/index.vue @@ -0,0 +1,142 @@ + + + diff --git a/frontend/src/views/website/runtime/components/environment/index.vue b/frontend/src/views/website/runtime/components/environment/index.vue new file mode 100644 index 0000000..fb0dbfc --- /dev/null +++ b/frontend/src/views/website/runtime/components/environment/index.vue @@ -0,0 +1,61 @@ + + + diff --git a/frontend/src/views/website/runtime/components/extra_hosts/index.vue b/frontend/src/views/website/runtime/components/extra_hosts/index.vue new file mode 100644 index 0000000..a8b38bd --- /dev/null +++ b/frontend/src/views/website/runtime/components/extra_hosts/index.vue @@ -0,0 +1,57 @@ + + + diff --git a/frontend/src/views/website/runtime/components/node-config.vue b/frontend/src/views/website/runtime/components/node-config.vue new file mode 100644 index 0000000..c82fbec --- /dev/null +++ b/frontend/src/views/website/runtime/components/node-config.vue @@ -0,0 +1,45 @@ + + + diff --git a/frontend/src/views/website/runtime/components/port-jump.vue b/frontend/src/views/website/runtime/components/port-jump.vue new file mode 100644 index 0000000..1110203 --- /dev/null +++ b/frontend/src/views/website/runtime/components/port-jump.vue @@ -0,0 +1,22 @@ + + + diff --git a/frontend/src/views/website/runtime/components/port/index.vue b/frontend/src/views/website/runtime/components/port/index.vue new file mode 100644 index 0000000..56983ec --- /dev/null +++ b/frontend/src/views/website/runtime/components/port/index.vue @@ -0,0 +1,69 @@ + + + diff --git a/frontend/src/views/website/runtime/components/runtime-status.vue b/frontend/src/views/website/runtime/components/runtime-status.vue new file mode 100644 index 0000000..3b177a0 --- /dev/null +++ b/frontend/src/views/website/runtime/components/runtime-status.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/views/website/runtime/components/terminal.vue b/frontend/src/views/website/runtime/components/terminal.vue new file mode 100644 index 0000000..c3f252a --- /dev/null +++ b/frontend/src/views/website/runtime/components/terminal.vue @@ -0,0 +1,105 @@ + + + + diff --git a/frontend/src/views/website/runtime/components/volume/index.vue b/frontend/src/views/website/runtime/components/volume/index.vue new file mode 100644 index 0000000..2a87506 --- /dev/null +++ b/frontend/src/views/website/runtime/components/volume/index.vue @@ -0,0 +1,57 @@ + + + diff --git a/frontend/src/views/website/runtime/delete/index.vue b/frontend/src/views/website/runtime/delete/index.vue new file mode 100644 index 0000000..892c448 --- /dev/null +++ b/frontend/src/views/website/runtime/delete/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/frontend/src/views/website/runtime/dotnet/index.vue b/frontend/src/views/website/runtime/dotnet/index.vue new file mode 100644 index 0000000..a08d00f --- /dev/null +++ b/frontend/src/views/website/runtime/dotnet/index.vue @@ -0,0 +1,261 @@ + + + + + diff --git a/frontend/src/views/website/runtime/dotnet/operate/index.vue b/frontend/src/views/website/runtime/dotnet/operate/index.vue new file mode 100644 index 0000000..1d75af6 --- /dev/null +++ b/frontend/src/views/website/runtime/dotnet/operate/index.vue @@ -0,0 +1,208 @@ + + + diff --git a/frontend/src/views/website/runtime/go/index.vue b/frontend/src/views/website/runtime/go/index.vue new file mode 100644 index 0000000..5800032 --- /dev/null +++ b/frontend/src/views/website/runtime/go/index.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/frontend/src/views/website/runtime/go/operate/index.vue b/frontend/src/views/website/runtime/go/operate/index.vue new file mode 100644 index 0000000..120f45d --- /dev/null +++ b/frontend/src/views/website/runtime/go/operate/index.vue @@ -0,0 +1,233 @@ + + + diff --git a/frontend/src/views/website/runtime/index.vue b/frontend/src/views/website/runtime/index.vue new file mode 100644 index 0000000..7561114 --- /dev/null +++ b/frontend/src/views/website/runtime/index.vue @@ -0,0 +1,37 @@ + + + diff --git a/frontend/src/views/website/runtime/java/index.vue b/frontend/src/views/website/runtime/java/index.vue new file mode 100644 index 0000000..bc7fe27 --- /dev/null +++ b/frontend/src/views/website/runtime/java/index.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/frontend/src/views/website/runtime/java/operate/index.vue b/frontend/src/views/website/runtime/java/operate/index.vue new file mode 100644 index 0000000..fcdf399 --- /dev/null +++ b/frontend/src/views/website/runtime/java/operate/index.vue @@ -0,0 +1,221 @@ + + + diff --git a/frontend/src/views/website/runtime/node/index.vue b/frontend/src/views/website/runtime/node/index.vue new file mode 100644 index 0000000..4ecc22b --- /dev/null +++ b/frontend/src/views/website/runtime/node/index.vue @@ -0,0 +1,276 @@ + + + + + diff --git a/frontend/src/views/website/runtime/node/module/index.vue b/frontend/src/views/website/runtime/node/module/index.vue new file mode 100644 index 0000000..91a1f3b --- /dev/null +++ b/frontend/src/views/website/runtime/node/module/index.vue @@ -0,0 +1,148 @@ + + + + diff --git a/frontend/src/views/website/runtime/node/operate/index.vue b/frontend/src/views/website/runtime/node/operate/index.vue new file mode 100644 index 0000000..bf3509b --- /dev/null +++ b/frontend/src/views/website/runtime/node/operate/index.vue @@ -0,0 +1,255 @@ + + + diff --git a/frontend/src/views/website/runtime/php/check/index.vue b/frontend/src/views/website/runtime/php/check/index.vue new file mode 100644 index 0000000..f203edb --- /dev/null +++ b/frontend/src/views/website/runtime/php/check/index.vue @@ -0,0 +1,130 @@ + + + + diff --git a/frontend/src/views/website/runtime/php/config/config/index.vue b/frontend/src/views/website/runtime/php/config/config/index.vue new file mode 100644 index 0000000..a16c359 --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/config/index.vue @@ -0,0 +1,201 @@ + + + diff --git a/frontend/src/views/website/runtime/php/config/container/index.vue b/frontend/src/views/website/runtime/php/config/container/index.vue new file mode 100644 index 0000000..e69af2c --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/container/index.vue @@ -0,0 +1,123 @@ + + + diff --git a/frontend/src/views/website/runtime/php/config/fpm-status/index.vue b/frontend/src/views/website/runtime/php/config/fpm-status/index.vue new file mode 100644 index 0000000..f33fae1 --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/fpm-status/index.vue @@ -0,0 +1,36 @@ + + + diff --git a/frontend/src/views/website/runtime/php/config/function/index.vue b/frontend/src/views/website/runtime/php/config/function/index.vue new file mode 100644 index 0000000..3922fba --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/function/index.vue @@ -0,0 +1,140 @@ + + diff --git a/frontend/src/views/website/runtime/php/config/index.vue b/frontend/src/views/website/runtime/php/config/index.vue new file mode 100644 index 0000000..4e6dbdb --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/index.vue @@ -0,0 +1,80 @@ + + + diff --git a/frontend/src/views/website/runtime/php/config/performance/index.vue b/frontend/src/views/website/runtime/php/config/performance/index.vue new file mode 100644 index 0000000..f114813 --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/performance/index.vue @@ -0,0 +1,223 @@ + + + diff --git a/frontend/src/views/website/runtime/php/config/php-fpm/index.vue b/frontend/src/views/website/runtime/php/config/php-fpm/index.vue new file mode 100644 index 0000000..71aadca --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/php-fpm/index.vue @@ -0,0 +1,81 @@ + + diff --git a/frontend/src/views/website/runtime/php/config/slow-log/index.vue b/frontend/src/views/website/runtime/php/config/slow-log/index.vue new file mode 100644 index 0000000..b8aac14 --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/slow-log/index.vue @@ -0,0 +1,16 @@ + + + diff --git a/frontend/src/views/website/runtime/php/config/timeout/index.vue b/frontend/src/views/website/runtime/php/config/timeout/index.vue new file mode 100644 index 0000000..52774c1 --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/timeout/index.vue @@ -0,0 +1,92 @@ + + diff --git a/frontend/src/views/website/runtime/php/config/upload/index.vue b/frontend/src/views/website/runtime/php/config/upload/index.vue new file mode 100644 index 0000000..0610461 --- /dev/null +++ b/frontend/src/views/website/runtime/php/config/upload/index.vue @@ -0,0 +1,93 @@ + + diff --git a/frontend/src/views/website/runtime/php/create/index.vue b/frontend/src/views/website/runtime/php/create/index.vue new file mode 100644 index 0000000..62d4c11 --- /dev/null +++ b/frontend/src/views/website/runtime/php/create/index.vue @@ -0,0 +1,535 @@ + + + + + diff --git a/frontend/src/views/website/runtime/php/extension-management/index.vue b/frontend/src/views/website/runtime/php/extension-management/index.vue new file mode 100644 index 0000000..5e38018 --- /dev/null +++ b/frontend/src/views/website/runtime/php/extension-management/index.vue @@ -0,0 +1,145 @@ + + + diff --git a/frontend/src/views/website/runtime/php/extension-template/index.vue b/frontend/src/views/website/runtime/php/extension-template/index.vue new file mode 100644 index 0000000..841e45b --- /dev/null +++ b/frontend/src/views/website/runtime/php/extension-template/index.vue @@ -0,0 +1,100 @@ + + diff --git a/frontend/src/views/website/runtime/php/extension-template/operate/index.vue b/frontend/src/views/website/runtime/php/extension-template/operate/index.vue new file mode 100644 index 0000000..8658a1b --- /dev/null +++ b/frontend/src/views/website/runtime/php/extension-template/operate/index.vue @@ -0,0 +1,127 @@ + + + diff --git a/frontend/src/views/website/runtime/php/index.vue b/frontend/src/views/website/runtime/php/index.vue new file mode 100644 index 0000000..ec2aa4f --- /dev/null +++ b/frontend/src/views/website/runtime/php/index.vue @@ -0,0 +1,388 @@ + + + + + diff --git a/frontend/src/views/website/runtime/php/supervisor/create/index.vue b/frontend/src/views/website/runtime/php/supervisor/create/index.vue new file mode 100644 index 0000000..7d7b4b4 --- /dev/null +++ b/frontend/src/views/website/runtime/php/supervisor/create/index.vue @@ -0,0 +1,142 @@ + + + diff --git a/frontend/src/views/website/runtime/php/supervisor/file/index.vue b/frontend/src/views/website/runtime/php/supervisor/file/index.vue new file mode 100644 index 0000000..2c08e44 --- /dev/null +++ b/frontend/src/views/website/runtime/php/supervisor/file/index.vue @@ -0,0 +1,148 @@ + + + + diff --git a/frontend/src/views/website/runtime/php/supervisor/index.vue b/frontend/src/views/website/runtime/php/supervisor/index.vue new file mode 100644 index 0000000..2b2b504 --- /dev/null +++ b/frontend/src/views/website/runtime/php/supervisor/index.vue @@ -0,0 +1,359 @@ + + + diff --git a/frontend/src/views/website/runtime/python/index.vue b/frontend/src/views/website/runtime/python/index.vue new file mode 100644 index 0000000..f5c2883 --- /dev/null +++ b/frontend/src/views/website/runtime/python/index.vue @@ -0,0 +1,260 @@ + + + + + diff --git a/frontend/src/views/website/runtime/python/operate/index.vue b/frontend/src/views/website/runtime/python/operate/index.vue new file mode 100644 index 0000000..cc28b26 --- /dev/null +++ b/frontend/src/views/website/runtime/python/operate/index.vue @@ -0,0 +1,197 @@ + + + diff --git a/frontend/src/views/website/ssl/acme-account/create/index.vue b/frontend/src/views/website/ssl/acme-account/create/index.vue new file mode 100644 index 0000000..2b9654f --- /dev/null +++ b/frontend/src/views/website/ssl/acme-account/create/index.vue @@ -0,0 +1,160 @@ + + diff --git a/frontend/src/views/website/ssl/acme-account/index.vue b/frontend/src/views/website/ssl/acme-account/index.vue new file mode 100644 index 0000000..5dbd290 --- /dev/null +++ b/frontend/src/views/website/ssl/acme-account/index.vue @@ -0,0 +1,127 @@ + + + diff --git a/frontend/src/views/website/ssl/apply/index.vue b/frontend/src/views/website/ssl/apply/index.vue new file mode 100644 index 0000000..497c6ae --- /dev/null +++ b/frontend/src/views/website/ssl/apply/index.vue @@ -0,0 +1,91 @@ + + + diff --git a/frontend/src/views/website/ssl/ca/create/index.vue b/frontend/src/views/website/ssl/ca/create/index.vue new file mode 100644 index 0000000..495d0ea --- /dev/null +++ b/frontend/src/views/website/ssl/ca/create/index.vue @@ -0,0 +1,123 @@ + + + diff --git a/frontend/src/views/website/ssl/ca/detail/index.vue b/frontend/src/views/website/ssl/ca/detail/index.vue new file mode 100644 index 0000000..016bc8d --- /dev/null +++ b/frontend/src/views/website/ssl/ca/detail/index.vue @@ -0,0 +1,81 @@ + + diff --git a/frontend/src/views/website/ssl/ca/index.vue b/frontend/src/views/website/ssl/ca/index.vue new file mode 100644 index 0000000..10ea1ad --- /dev/null +++ b/frontend/src/views/website/ssl/ca/index.vue @@ -0,0 +1,150 @@ + + + diff --git a/frontend/src/views/website/ssl/ca/obtain/index.vue b/frontend/src/views/website/ssl/ca/obtain/index.vue new file mode 100644 index 0000000..71fac9a --- /dev/null +++ b/frontend/src/views/website/ssl/ca/obtain/index.vue @@ -0,0 +1,162 @@ + + + diff --git a/frontend/src/views/website/ssl/create/index.vue b/frontend/src/views/website/ssl/create/index.vue new file mode 100644 index 0000000..576d9e5 --- /dev/null +++ b/frontend/src/views/website/ssl/create/index.vue @@ -0,0 +1,455 @@ + + + + diff --git a/frontend/src/views/website/ssl/detail/index.vue b/frontend/src/views/website/ssl/detail/index.vue new file mode 100644 index 0000000..cbaa95d --- /dev/null +++ b/frontend/src/views/website/ssl/detail/index.vue @@ -0,0 +1,100 @@ + + diff --git a/frontend/src/views/website/ssl/dns-account/create/index.vue b/frontend/src/views/website/ssl/dns-account/create/index.vue new file mode 100644 index 0000000..231e6a7 --- /dev/null +++ b/frontend/src/views/website/ssl/dns-account/create/index.vue @@ -0,0 +1,302 @@ + + + diff --git a/frontend/src/views/website/ssl/dns-account/index.vue b/frontend/src/views/website/ssl/dns-account/index.vue new file mode 100644 index 0000000..0439aa9 --- /dev/null +++ b/frontend/src/views/website/ssl/dns-account/index.vue @@ -0,0 +1,117 @@ + + + diff --git a/frontend/src/views/website/ssl/index.vue b/frontend/src/views/website/ssl/index.vue new file mode 100644 index 0000000..95e54f5 --- /dev/null +++ b/frontend/src/views/website/ssl/index.vue @@ -0,0 +1,417 @@ + + + diff --git a/frontend/src/views/website/ssl/obtain/index.vue b/frontend/src/views/website/ssl/obtain/index.vue new file mode 100644 index 0000000..4281fda --- /dev/null +++ b/frontend/src/views/website/ssl/obtain/index.vue @@ -0,0 +1,72 @@ + + + diff --git a/frontend/src/views/website/ssl/upload/index.vue b/frontend/src/views/website/ssl/upload/index.vue new file mode 100644 index 0000000..6deced5 --- /dev/null +++ b/frontend/src/views/website/ssl/upload/index.vue @@ -0,0 +1,204 @@ + + + diff --git a/frontend/src/views/website/website/batch-op/group.vue b/frontend/src/views/website/website/batch-op/group.vue new file mode 100644 index 0000000..293f980 --- /dev/null +++ b/frontend/src/views/website/website/batch-op/group.vue @@ -0,0 +1,65 @@ + + + diff --git a/frontend/src/views/website/website/batch-op/https.vue b/frontend/src/views/website/website/batch-op/https.vue new file mode 100644 index 0000000..9a48a9b --- /dev/null +++ b/frontend/src/views/website/website/batch-op/https.vue @@ -0,0 +1,96 @@ + + + diff --git a/frontend/src/views/website/website/check/index.vue b/frontend/src/views/website/website/check/index.vue new file mode 100644 index 0000000..f097cdc --- /dev/null +++ b/frontend/src/views/website/website/check/index.vue @@ -0,0 +1,53 @@ + + diff --git a/frontend/src/views/website/website/components/group/index.vue b/frontend/src/views/website/website/components/group/index.vue new file mode 100644 index 0000000..92ea924 --- /dev/null +++ b/frontend/src/views/website/website/components/group/index.vue @@ -0,0 +1,50 @@ + + + diff --git a/frontend/src/views/website/website/components/https/index.vue b/frontend/src/views/website/website/components/https/index.vue new file mode 100644 index 0000000..9771c91 --- /dev/null +++ b/frontend/src/views/website/website/components/https/index.vue @@ -0,0 +1,301 @@ + + + + + diff --git a/frontend/src/views/website/website/components/website-ssl/index.vue b/frontend/src/views/website/website/components/website-ssl/index.vue new file mode 100644 index 0000000..97a8b73 --- /dev/null +++ b/frontend/src/views/website/website/components/website-ssl/index.vue @@ -0,0 +1,38 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/anti-Leech/index.vue b/frontend/src/views/website/website/config/basic/anti-Leech/index.vue new file mode 100644 index 0000000..327fe75 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/anti-Leech/index.vue @@ -0,0 +1,282 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/auth-basic/create/index.vue b/frontend/src/views/website/website/config/basic/auth-basic/create/index.vue new file mode 100644 index 0000000..3267b27 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/auth-basic/create/index.vue @@ -0,0 +1,131 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/auth-basic/index.vue b/frontend/src/views/website/website/config/basic/auth-basic/index.vue new file mode 100644 index 0000000..9b886a0 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/auth-basic/index.vue @@ -0,0 +1,227 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/cors/index.vue b/frontend/src/views/website/website/config/basic/cors/index.vue new file mode 100644 index 0000000..30269ff --- /dev/null +++ b/frontend/src/views/website/website/config/basic/cors/index.vue @@ -0,0 +1,102 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/default-doc/index.vue b/frontend/src/views/website/website/config/basic/default-doc/index.vue new file mode 100644 index 0000000..47e881c --- /dev/null +++ b/frontend/src/views/website/website/config/basic/default-doc/index.vue @@ -0,0 +1,90 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/domain/create/index.vue b/frontend/src/views/website/website/config/basic/domain/create/index.vue new file mode 100644 index 0000000..b53b93f --- /dev/null +++ b/frontend/src/views/website/website/config/basic/domain/create/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/domain/index.vue b/frontend/src/views/website/website/config/basic/domain/index.vue new file mode 100644 index 0000000..95a6471 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/domain/index.vue @@ -0,0 +1,155 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/https/index.vue b/frontend/src/views/website/website/config/basic/https/index.vue new file mode 100644 index 0000000..bbaa16e --- /dev/null +++ b/frontend/src/views/website/website/config/basic/https/index.vue @@ -0,0 +1,193 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/index.vue b/frontend/src/views/website/website/config/basic/index.vue new file mode 100644 index 0000000..1aa45a4 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/index.vue @@ -0,0 +1,192 @@ + + + + diff --git a/frontend/src/views/website/website/config/basic/limit-conn/index.vue b/frontend/src/views/website/website/config/basic/limit-conn/index.vue new file mode 100644 index 0000000..24e23f5 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/limit-conn/index.vue @@ -0,0 +1,184 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/load-balance/file/index.vue b/frontend/src/views/website/website/config/basic/load-balance/file/index.vue new file mode 100644 index 0000000..8a6df71 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/load-balance/file/index.vue @@ -0,0 +1,72 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/load-balance/form/index.vue b/frontend/src/views/website/website/config/basic/load-balance/form/index.vue new file mode 100644 index 0000000..414c453 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/load-balance/form/index.vue @@ -0,0 +1,321 @@ + + + + + diff --git a/frontend/src/views/website/website/config/basic/load-balance/index.vue b/frontend/src/views/website/website/config/basic/load-balance/index.vue new file mode 100644 index 0000000..82bf00f --- /dev/null +++ b/frontend/src/views/website/website/config/basic/load-balance/index.vue @@ -0,0 +1,174 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/load-balance/operate/index.vue b/frontend/src/views/website/website/config/basic/load-balance/operate/index.vue new file mode 100644 index 0000000..c285984 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/load-balance/operate/index.vue @@ -0,0 +1,189 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/other/index.vue b/frontend/src/views/website/website/config/basic/other/index.vue new file mode 100644 index 0000000..96e876d --- /dev/null +++ b/frontend/src/views/website/website/config/basic/other/index.vue @@ -0,0 +1,102 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/php/composer/index.vue b/frontend/src/views/website/website/config/basic/php/composer/index.vue new file mode 100644 index 0000000..edd0d8d --- /dev/null +++ b/frontend/src/views/website/website/config/basic/php/composer/index.vue @@ -0,0 +1,129 @@ + + diff --git a/frontend/src/views/website/website/config/basic/php/index.vue b/frontend/src/views/website/website/config/basic/php/index.vue new file mode 100644 index 0000000..4b85922 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/php/index.vue @@ -0,0 +1,143 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/proxy/cache/index.vue b/frontend/src/views/website/website/config/basic/proxy/cache/index.vue new file mode 100644 index 0000000..0828dfc --- /dev/null +++ b/frontend/src/views/website/website/config/basic/proxy/cache/index.vue @@ -0,0 +1,144 @@ + + diff --git a/frontend/src/views/website/website/config/basic/proxy/create/index.vue b/frontend/src/views/website/website/config/basic/proxy/create/index.vue new file mode 100644 index 0000000..a3c3f22 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/proxy/create/index.vue @@ -0,0 +1,431 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/proxy/file/index.vue b/frontend/src/views/website/website/config/basic/proxy/file/index.vue new file mode 100644 index 0000000..ceddbd7 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/proxy/file/index.vue @@ -0,0 +1,76 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/proxy/index.vue b/frontend/src/views/website/website/config/basic/proxy/index.vue new file mode 100644 index 0000000..0d7446d --- /dev/null +++ b/frontend/src/views/website/website/config/basic/proxy/index.vue @@ -0,0 +1,225 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/real-ip/index.vue b/frontend/src/views/website/website/config/basic/real-ip/index.vue new file mode 100644 index 0000000..3fd5346 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/real-ip/index.vue @@ -0,0 +1,125 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/redirect/create/index.vue b/frontend/src/views/website/website/config/basic/redirect/create/index.vue new file mode 100644 index 0000000..e843800 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/redirect/create/index.vue @@ -0,0 +1,185 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/redirect/file/index.vue b/frontend/src/views/website/website/config/basic/redirect/file/index.vue new file mode 100644 index 0000000..ae03096 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/redirect/file/index.vue @@ -0,0 +1,60 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/redirect/index.vue b/frontend/src/views/website/website/config/basic/redirect/index.vue new file mode 100644 index 0000000..9a8576f --- /dev/null +++ b/frontend/src/views/website/website/config/basic/redirect/index.vue @@ -0,0 +1,200 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/resource/index.vue b/frontend/src/views/website/website/config/basic/resource/index.vue new file mode 100644 index 0000000..1e0d0cb --- /dev/null +++ b/frontend/src/views/website/website/config/basic/resource/index.vue @@ -0,0 +1,125 @@ + + diff --git a/frontend/src/views/website/website/config/basic/rewrite/custom/index.vue b/frontend/src/views/website/website/config/basic/rewrite/custom/index.vue new file mode 100644 index 0000000..63508b5 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/rewrite/custom/index.vue @@ -0,0 +1,72 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/rewrite/index.vue b/frontend/src/views/website/website/config/basic/rewrite/index.vue new file mode 100644 index 0000000..bf981e6 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/rewrite/index.vue @@ -0,0 +1,166 @@ + + + diff --git a/frontend/src/views/website/website/config/basic/site-folder/index.vue b/frontend/src/views/website/website/config/basic/site-folder/index.vue new file mode 100644 index 0000000..76b0883 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/site-folder/index.vue @@ -0,0 +1,196 @@ + + + + diff --git a/frontend/src/views/website/website/config/basic/stream/index.vue b/frontend/src/views/website/website/config/basic/stream/index.vue new file mode 100644 index 0000000..310fc97 --- /dev/null +++ b/frontend/src/views/website/website/config/basic/stream/index.vue @@ -0,0 +1,96 @@ + + + diff --git a/frontend/src/views/website/website/config/index.vue b/frontend/src/views/website/website/config/index.vue new file mode 100644 index 0000000..42ee786 --- /dev/null +++ b/frontend/src/views/website/website/config/index.vue @@ -0,0 +1,94 @@ + + + diff --git a/frontend/src/views/website/website/config/log/index.vue b/frontend/src/views/website/website/config/log/index.vue new file mode 100644 index 0000000..a037db0 --- /dev/null +++ b/frontend/src/views/website/website/config/log/index.vue @@ -0,0 +1,27 @@ + + + diff --git a/frontend/src/views/website/website/config/log/log-fiile/index.vue b/frontend/src/views/website/website/config/log/log-fiile/index.vue new file mode 100644 index 0000000..aa62f9e --- /dev/null +++ b/frontend/src/views/website/website/config/log/log-fiile/index.vue @@ -0,0 +1,97 @@ + + diff --git a/frontend/src/views/website/website/config/resource/index.vue b/frontend/src/views/website/website/config/resource/index.vue new file mode 100644 index 0000000..17f1bfd --- /dev/null +++ b/frontend/src/views/website/website/config/resource/index.vue @@ -0,0 +1,44 @@ + + + diff --git a/frontend/src/views/website/website/config/resource/nginx/index.vue b/frontend/src/views/website/website/config/resource/nginx/index.vue new file mode 100644 index 0000000..4fd970b --- /dev/null +++ b/frontend/src/views/website/website/config/resource/nginx/index.vue @@ -0,0 +1,61 @@ + + diff --git a/frontend/src/views/website/website/cors/index.vue b/frontend/src/views/website/website/cors/index.vue new file mode 100644 index 0000000..c90986c --- /dev/null +++ b/frontend/src/views/website/website/cors/index.vue @@ -0,0 +1,126 @@ + + + diff --git a/frontend/src/views/website/website/create/index.vue b/frontend/src/views/website/website/create/index.vue new file mode 100644 index 0000000..f9a4403 --- /dev/null +++ b/frontend/src/views/website/website/create/index.vue @@ -0,0 +1,1084 @@ + + + + + diff --git a/frontend/src/views/website/website/create/site-alert/index.vue b/frontend/src/views/website/website/create/site-alert/index.vue new file mode 100644 index 0000000..939a745 --- /dev/null +++ b/frontend/src/views/website/website/create/site-alert/index.vue @@ -0,0 +1,50 @@ + + + diff --git a/frontend/src/views/website/website/default/index.vue b/frontend/src/views/website/website/default/index.vue new file mode 100644 index 0000000..56909ff --- /dev/null +++ b/frontend/src/views/website/website/default/index.vue @@ -0,0 +1,75 @@ + + diff --git a/frontend/src/views/website/website/delete/index.vue b/frontend/src/views/website/website/delete/index.vue new file mode 100644 index 0000000..1f95bbf --- /dev/null +++ b/frontend/src/views/website/website/delete/index.vue @@ -0,0 +1,113 @@ + + + diff --git a/frontend/src/views/website/website/domain-create/index.vue b/frontend/src/views/website/website/domain-create/index.vue new file mode 100644 index 0000000..9668ffb --- /dev/null +++ b/frontend/src/views/website/website/domain-create/index.vue @@ -0,0 +1,373 @@ + + + diff --git a/frontend/src/views/website/website/domain/index.vue b/frontend/src/views/website/website/domain/index.vue new file mode 100644 index 0000000..f81e11b --- /dev/null +++ b/frontend/src/views/website/website/domain/index.vue @@ -0,0 +1,203 @@ + + + + + diff --git a/frontend/src/views/website/website/html/index.vue b/frontend/src/views/website/website/html/index.vue new file mode 100644 index 0000000..7dc8f64 --- /dev/null +++ b/frontend/src/views/website/website/html/index.vue @@ -0,0 +1,108 @@ + + + + diff --git a/frontend/src/views/website/website/index.vue b/frontend/src/views/website/website/index.vue new file mode 100644 index 0000000..3c311ec --- /dev/null +++ b/frontend/src/views/website/website/index.vue @@ -0,0 +1,685 @@ + + + diff --git a/frontend/src/views/website/website/nginx/index.vue b/frontend/src/views/website/website/nginx/index.vue new file mode 100644 index 0000000..ca2718a --- /dev/null +++ b/frontend/src/views/website/website/nginx/index.vue @@ -0,0 +1,84 @@ + + + diff --git a/frontend/src/views/website/website/nginx/module/build/index.vue b/frontend/src/views/website/website/nginx/module/build/index.vue new file mode 100644 index 0000000..d994e62 --- /dev/null +++ b/frontend/src/views/website/website/nginx/module/build/index.vue @@ -0,0 +1,97 @@ + + diff --git a/frontend/src/views/website/website/nginx/module/index.vue b/frontend/src/views/website/website/nginx/module/index.vue new file mode 100644 index 0000000..fb03787 --- /dev/null +++ b/frontend/src/views/website/website/nginx/module/index.vue @@ -0,0 +1,116 @@ + + diff --git a/frontend/src/views/website/website/nginx/module/operate/index.vue b/frontend/src/views/website/website/nginx/module/operate/index.vue new file mode 100644 index 0000000..830e0ed --- /dev/null +++ b/frontend/src/views/website/website/nginx/module/operate/index.vue @@ -0,0 +1,109 @@ + + + diff --git a/frontend/src/views/website/website/nginx/other/https/index.vue b/frontend/src/views/website/website/nginx/other/https/index.vue new file mode 100644 index 0000000..d4d3319 --- /dev/null +++ b/frontend/src/views/website/website/nginx/other/https/index.vue @@ -0,0 +1,61 @@ + + + diff --git a/frontend/src/views/website/website/nginx/other/index.vue b/frontend/src/views/website/website/nginx/other/index.vue new file mode 100644 index 0000000..95bbe44 --- /dev/null +++ b/frontend/src/views/website/website/nginx/other/index.vue @@ -0,0 +1,15 @@ + + + diff --git a/frontend/src/views/website/website/nginx/performance/index.vue b/frontend/src/views/website/website/nginx/performance/index.vue new file mode 100644 index 0000000..3a5dba9 --- /dev/null +++ b/frontend/src/views/website/website/nginx/performance/index.vue @@ -0,0 +1,145 @@ + + diff --git a/frontend/src/views/website/website/nginx/source/index.vue b/frontend/src/views/website/website/nginx/source/index.vue new file mode 100644 index 0000000..34b029f --- /dev/null +++ b/frontend/src/views/website/website/nginx/source/index.vue @@ -0,0 +1,73 @@ + + diff --git a/frontend/src/views/website/website/nginx/status/index.vue b/frontend/src/views/website/website/nginx/status/index.vue new file mode 100644 index 0000000..7e15c9d --- /dev/null +++ b/frontend/src/views/website/website/nginx/status/index.vue @@ -0,0 +1,86 @@ + + + diff --git a/frontend/src/views/website/website/status/index.vue b/frontend/src/views/website/website/status/index.vue new file mode 100644 index 0000000..d068cae --- /dev/null +++ b/frontend/src/views/website/website/status/index.vue @@ -0,0 +1,46 @@ + + + + diff --git a/frontend/tailwind.config.js b/frontend/tailwind.config.js new file mode 100644 index 0000000..b7ee962 --- /dev/null +++ b/frontend/tailwind.config.js @@ -0,0 +1,11 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: ['./index.html', './src/**/*.{vue,js,ts,jsx,tsx}'], + theme: { + extend: {}, + }, + plugins: [], + corePlugins: { + preflight: false, + }, +}; diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 0000000..1258e17 --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,47 @@ +{ + "compilerOptions": { + "target": "esnext", + "useDefineForClassFields": true, + "module": "esnext", + "moduleResolution": "node", + + /* Strict Type-Checking Options */ + "strict": false /* Enable all strict type-checking options. */, + // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* Enable strict null checks. */ + // "strictFunctionTypes": true, /* Enable strict checking of function types. */ + // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ + // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ + // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ + // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ + + "jsx": "preserve", + "jsxFactory": "h", + "jsxFragmentFactory": "Fragment", + "sourceMap": true, + "resolveJsonModule": true, + "esModuleInterop": true, + "lib": ["esnext", "dom"], + // 解析非相对模块名的基准目录 + "baseUrl": "./", + // 模块名到基于 baseUrl的路径映射的列表。 + "paths": { + "@": ["src"], + "@/*": ["src/*"] + }, + // 跳过库检查,解决打包失败 + "skipLibCheck": true, + "ignoreDeprecations": "5.0" + }, + "include": [ + "src/**/*.ts", + "src/**/*.d.ts", + "src/**/*.tsx", + "src/**/*.vue", + "build/**/*.ts", + "build/**/*.d.ts", + "vite.config.ts", + "auto-imports.d.ts" + ], + "exclude": ["node_modules", "dist", "**/*.js", "*.json", "*.md"] +} diff --git a/frontend/vite-env.d.ts b/frontend/vite-env.d.ts new file mode 100644 index 0000000..8cee151 --- /dev/null +++ b/frontend/vite-env.d.ts @@ -0,0 +1,2 @@ +/// +/// diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts new file mode 100644 index 0000000..5c16067 --- /dev/null +++ b/frontend/vite.config.ts @@ -0,0 +1,127 @@ +import { resolve } from 'path'; +import { wrapperEnv } from './src/utils/get-env'; +import { visualizer } from 'rollup-plugin-visualizer'; +import viteCompression from 'vite-plugin-compression'; +import VueSetupExtend from 'vite-plugin-vue-setup-extend'; +import eslintPlugin from 'vite-plugin-eslint'; +import vueJsx from '@vitejs/plugin-vue-jsx'; +import DefineOptions from 'unplugin-vue-define-options/vite'; +import { defineConfig, loadEnv, ConfigEnv, UserConfig } from 'vite'; +import vue from '@vitejs/plugin-vue'; +import pkg from './package.json'; +import dayjs from 'dayjs'; + +import AutoImport from 'unplugin-auto-import/vite'; +import Components from 'unplugin-vue-components/vite'; +import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'; +import svgLoader from 'vite-svg-loader'; + +const prefix = `monaco-editor/esm/vs`; + +const { dependencies, devDependencies, name, version } = pkg; +const __APP_INFO__ = { + pkg: { dependencies, devDependencies, name, version }, + lastBuildTime: dayjs().format('YYYY-MM-DD HH:mm:ss'), +}; + +export default defineConfig(({ mode }: ConfigEnv): UserConfig => { + const env = loadEnv(mode, process.cwd()); + const viteEnv = wrapperEnv(env); + + return { + resolve: { + alias: { + '@': resolve(__dirname, './src'), + 'vue-i18n': 'vue-i18n/dist/vue-i18n.cjs.js', + xpack: resolve(__dirname, './src/xpack'), + }, + }, + define: { + __APP_INFO__: JSON.stringify(__APP_INFO__), + }, + css: { + preprocessorOptions: { + scss: { + additionalData: `@use "@/styles/var.scss" as *;`, + silenceDeprecations: ['legacy-js-api'], + }, + }, + }, + server: { + port: viteEnv.VITE_PORT, + open: viteEnv.VITE_OPEN, + host: '0.0.0.0', + sourcemapIgnoreList: (sourcePath) => { + return sourcePath.includes('node_modules'); + }, + proxy: { + '/api/v2': { + target: 'http://localhost:9999/', + changeOrigin: true, + ws: true, + }, + }, + }, + plugins: [ + vue(), + DefineOptions(), + eslintPlugin({ + exclude: ['**/*.js'], + }), + vueJsx(), + VueSetupExtend(), + viteEnv.VITE_REPORT && visualizer(), + viteEnv.VITE_BUILD_GZIP && + viteCompression({ + verbose: true, + disable: false, + threshold: 10240, + algorithm: 'gzip', + ext: '.gz', + }), + AutoImport({ + imports: ['vue', 'vue-router'], + resolvers: [ + ElementPlusResolver({ + importStyle: 'sass', + }), + ], + }), + Components({ + resolvers: [ + ElementPlusResolver({ + importStyle: 'sass', + }), + ], + }), + svgLoader({ + defaultImport: 'url', + }), + ], + esbuild: { + pure: viteEnv.VITE_DROP_CONSOLE ? ['console.log'] : [], + drop: viteEnv.VITE_DROP_CONSOLE && process.env.NODE_ENV === 'production' ? ['debugger'] : [], + }, + build: { + sourcemap: false, + outDir: '../core/cmd/server/web', + minify: 'esbuild', + target: 'esnext', + cssCodeSplit: false, + rollupOptions: { + output: { + chunkFileNames: 'assets/js/[name]-[hash].js', + entryFileNames: 'assets/js/[name]-[hash].js', + assetFileNames: 'assets/[ext]/[name]-[hash].[ext]', + manualChunks: { + jsonWorker: [`${prefix}/language/json/json.worker`], + cssWorker: [`${prefix}/language/css/css.worker`], + htmlWorker: [`${prefix}/language/html/html.worker`], + tsWorker: [`${prefix}/language/typescript/ts.worker`], + editorWorker: [`${prefix}/editor/editor.worker`], + }, + }, + }, + }, + }; +}); diff --git a/sonar-project.properties b/sonar-project.properties new file mode 100644 index 0000000..ad37f98 --- /dev/null +++ b/sonar-project.properties @@ -0,0 +1,15 @@ +sonar.projectKey=1Panel-dev_1Panel +sonar.organization=1panel-dev + +sonar.exclusions=frontend/src/lang/modules/*,backend/i18n/lang/* + +# This is the name and version displayed in the SonarCloud UI. +#sonar.projectName=1Panel +#sonar.projectVersion=1.0 + + +# Path is relative to the sonar-project.properties file. Replace "\" by "/" on Windows. +#sonar.sources=. + +# Encoding of the source code. Default is default system encoding +#sonar.sourceEncoding=UTF-8